modelcontextprotocol / modelcontextprotocol/kotlin-sdk

Add Declarative KSP for MCP Server

Open
#102 7 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

api enhancement help wanted P3
Dominant language
Kotlin
Stars
1.5k
Forks
248
Avg merge
1d 20h
Merged PRs (30d)
23

Description

We can make a declarative tool for MCP server developing.

The expected function is like following:

We write code:

@McpServer
public class MyServerComponent {
    @McpTool(description = "Response as a list")
    public fun response(
        @Description("The first value in the list") message1: String,
        @Description("The second value in the list") message2: String,
    ): List<String> {
        return listOf(message1, message2)
    }
}

And we can use it like:

val server = Server(...)
val component = MyServerComponent() // maybe injected from DI framework
server.addTools(component.tools())

This can be implemented by KSP codegen.

Expected behaviour:

Following code is generated:

// extension fun tools() for annotated class
public fun MyServerComponent.tools(adapter: ServerAdapter = ServerAdapter()): List<RegisteredTool> = listOf(
    adapter.makeTool(
        "response", 
        "Response as a list", 
        this::`response$boxed`
    ),
)

// schema and input type for annotated method
@Serializable
internal data class `MyServerComponent$response$Box`(
  @property:Description(`value` = "The first value in the list")
  public val message1: String,
  @property:Description(`value` = "The second value in the list")
  public val message2: String,
)

// Proper boxing logic for method parameters
internal suspend fun MyServerComponent.`response$boxed`(box: `MyServerComponent$response$Box`): List<String> = response(
    message1 = box.message1,
    message2 = box.message2,
)

When the annotated function returns String, the tool should return TextContent that exactly contains the string.

When the annotated function returns Serializable types, the tool should return TextContent that contains the serialized result.

When the annotated function throws an exception, the tool should return a error with TextContent that contains error message.

When the annotated function requires multiple parameters, the tool should box these parameters with their descriptions together as a Serializable class.

When the annotated function have parameters with @Description or other annotations about the schema, these annotations on parameters should be copied to the schema accordingly.

As the kotlin-sdk only supports object type input schema, when the parameter is: a primitive type, a sealed type, a nullable type, the schema cannot be "type": "object". Then we need to box the parameter with a object type:

@Serializable
data class Box<T>(
    val value: T,
)

So its schema can be expressed with "type": "object"

Otherwise, as there is a single parameter and no need to box, we should generate the schema directly according its type, allowing developers to customize the input schema.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the existing Server, ServerAdapter, RegisteredTool, and annotation entry points in the Kotlin SDK, then trace how tool schemas and results are currently represented. The work is complete when KSP-generated tools cover the annotated class and method cases described here, including parameter boxing, copied schema annotations, serialized results, and error text.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin
Domain
backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.