Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Compiler-driven MCP framework for Kotlin Multiplatform

NotificationsYou must be signed in to change notification settings

ondrsh/mcp4k

Repository files navigation

mcp4k banner

Maven CentralLicense

mcp4k is a compiler-driven framework for building bothclients and servers using theModel Context Protocol (MCP) in Kotlin.It implements the vast majority of the MCP specification, including resources, prompts, tools, sampling, and more.

mcp4k automatically generates JSON-RPC handlers, schema metadata, and manages the complete lifecycle for you.


Overview

  • Client: Connects to any MCP server to request prompts, read resources, or invoke tools.
  • Server: Exposes resources, prompts, and tools to MCP-compatible clients, handling standard JSON-RPC messages and protocol events.
  • Transports: Supportsstdio, with HTTP-Streaming and other transports on the roadmap.
  • Lifecycle: Manages initialization, cancellation, sampling, progress tracking, and more.

mcp4k also enforces correct parameter typing at compile time.If you describe a tool parameter incorrectly, you get a compile-time error instead of a runtime mismatch.


Installation

Add mcp4k to your build:

plugins {  kotlin("multiplatform") version"2.2.0"// or kotlin("jvm")  kotlin("plugin.serialization") version"2.2.0"  id("sh.ondr.mcp4k") version"0.4.2"// <-- Add this}

Version Compatibility

mcp4k includes a compiler plugin that requires exact Kotlin version matching. Each mcp4k version is hard-linked to a specific Kotlin version:

mcp4k VersionRequired Kotlin Version
0.4.22.2.0

Quick Start

Create a Simple Server

/** * Reverses an input string * * @param input The string to be reversed*/@McpToolfunreverseString(input:String):ToolContent {return"Reversed:${input.reversed()}".toTextContent()}funmain()= runBlocking {val server=Server.Builder()    .withTool(::reverseString)    .withTransport(StdioTransport())    .build()      server.start()// Keep server runningwhile (true) {     delay(1000)  }}

In this example, your new@McpTool is exposed via JSON-RPC asreverseString.Clients can call it by sendingtools/call messages.


Create a Simple Client

funmain()= runBlocking {val client=Client.Builder()    .withClientInfo("MyClient","1.0.0")    .withTransport(StdioTransport())    .build()    client.start()  client.initialize()}

All boilerplate (capability negotiation, JSON-RPC ID handling, etc.) is handled by mcp4k.

Once connected, the client can discover prompts/tools/resources and make calls according to the MCP spec:

val allTools= client.getAllTools()println("Server tools =$allTools")val response= client.callTool(  name="reverseString",  arguments= buildMap {    put("input",JsonPrimitive("Some string we want reversed"))  },)val result= response.result?.deserializeResult<CallToolResult>()println(result)// --> "desrever tnaw ew gnirts emoS"

If you want to get notified when the server changes its tools, you can provide a callback:

val client=Client.Builder()// ...  .withOnToolsChanged { updatedTools:List<Tool>->println("Updated tools:$updatedTools")  }  .build()

Transport Logging

You can observe raw incoming/outgoing messages by providingwithTransportLogger lambdas:

val server=Server.Builder()  .withTransport(StdioTransport())  .withTransportLogger(    logIncoming= { msg->println("SERVER INCOMING:$msg") },    logOutgoing= { msg->println("SERVER OUTGOING:$msg") },  )  .build()

BothServer andClient accept this configuration. Super useful for debugging and tests.


Tools

Let's look at a more advanced tool example:

@JsonSchema @SerializableenumclassPriority {LOW,NORMAL,HIGH}/** * @property title The email's title * @property body The email's body * @property priority The email's priority*/@JsonSchema @Serializabledata classEmail(valtitle:String,valbody:String?,valpriority:Priority =Priority.NORMAL,)/** * Sends an email * @param recipients The email addresses of the recipients * @param email The email to send*/@McpToolfunsendEmail(recipients:List<String>,email:Email,)= buildString {  append("Email sent to${recipients.joinToString()} with")  append("title '${email.title}' and")  append("body '${email.body}' and")  append("priority${email.priority}")}.toTextContent()

When clients calltools/list, they see a JSON schema describing the tool's input:

{"type":"object","description":"Sends an email","properties": {"recipients": {"type":"array","description":"The email addresses of the recipients","items": {"type":"string"      }    },"email": {"type":"object","description":"The email to send","properties": {"title": {"type":"string","description":"The email's title"        },"body": {"type":"string","description":"The email's body"        },"priority": {"type":"string","description":"The email's priority","enum": ["LOW","NORMAL","HIGH"          ]        }      },"required": ["title"      ]    }  },"required": ["recipients","email"  ]}

KDoc parameter descriptions are type-safe and will throw a compile-time error if you specify a non-existing property. Tool call invocation and type-safe deserialization will be handled by mcp4k.

Server can also add or remove tools at runtime:

server.addTool(::sendEmail)// ...server.removeTool(::sendEmail)

Both calls will automatically sendToolListChanged notifications to the client.

Tools can also be added or removed from inside tool functions if they are implemented asServer extension functions:

@McpToolfun Server.toolThatAddsSecondTool():ToolContent {  addTool(::secondTool)return"Second tool added!".toTextContent()}

Prompts

Annotate functions with@McpPrompt to define parameterized conversation templates:

@McpPromptfuncodeReviewPrompt(code:String)= buildPrompt {  user("Please review the following code:")  user("'''\n$code\n'''")}

Clients can callprompts/get to retrieve the underlying messages.


Server Context

In some cases, you want multiple tools or prompts to share state. mcp4k allows you to attach a customcontext object that tools and prompts can reference.

  1. Create aServerContext object
  2. Pass it in with.withContext(...)
  3. Each tool or prompt can access it by callinggetContextAs()

For example:

// 1) Create your contextclassMyServerContext :ServerContext {var userName:String=""}// 2) A tool function that writes into the context@McpToolfun Server.setUserName(name:String):ToolContent {  getContextAs<MyServerContext>().userName= namereturn"Username set to:$name".toTextContent()}// 3) Another tool that reads from the context@McpToolfun Server.greetUser():ToolContent {val name= getContextAs<MyServerContext>().userNameif (name.isEmpty())return"No user set yet!".toTextContent()return"Hello,$name!".toTextContent()}funmain()= runBlocking {val context=MyServerContext()val server=Server.Builder()    .withContext(context)// <-- Provide the context    .withTool(Server::setUserName)    .withTool(Server::greetUser)    .withTransport(StdioTransport())    .build()    server.start()while(true) {    delay(1000)  }}

But looking at the above code, it doesn't make sense that thegreetUser function is callable beforesetUserName has been called.

Thus, we can improve the code by doing:

funmain() {val server=Server.Builder()// ...  .withTool(Server::setUserName)// only add this// ...}@McpToolfun Server.setUserName(name:String):ToolContent {  getContextAs<MyServerContext>().userName= name  addTool(Server::greetUser)// Now, add greetUserreturn"Username set to:$name".toTextContent()}

Resources

Resources in MCP allow servers to expose data that clients can read. TheResourceProvider interface is the core abstraction for implementing resource support:

interfaceResourceProvider {suspendfunlistResources():List<Resource>suspendfunreadResource(uri:String):ResourceContentssuspendfunlistResourceTemplates():List<ResourceTemplate>suspendfunsubscribe(uri:String)suspendfununsubscribe(uri:String)funonResourceListChanged(callback: ()->Unit)funonResourceUpdated(callback: (uri:String)->Unit)}

You can implement this interface to expose any type of data as resources - databases, APIs, files, or any other data source.

File-Based Resource Providers

For common file-based use cases, mcp4k provides ready-to-use implementations in the optionalmcp4k-file-provider module. See thefile provider documentation for details on:

  • DiscreteFileProvider - Expose specific files with discrete URIs
  • TemplateFileProvider - Expose entire directories with URI templates

Creating Custom Resource Providers

Here's a simple example of a custom resource provider:

classDatabaseResourceProvider :ResourceProvider {overridesuspendfunlistResources():List<Resource> {returnlistOf(Resource(        uri="db://users",        name="Users Table",        description="Access to user data",        mimeType="application/json"      )    )  }overridesuspendfunreadResource(uri:String):ResourceContents {returnwhen (uri) {"db://users"->ResourceContents(        uri= uri,        mimeType="application/json",        text= fetchUsersAsJson()      )else->throwResourceNotFoundException(uri)    }  }// Implement other methods as needed...}

Then add it to your server:

val server=Server.Builder()  .withResourceProvider(DatabaseResourceProvider())  .withTransport(StdioTransport())  .build()

Sampling

Clients can fulfill server-initiated LLM requests by providing aSamplingProvider.

In a real application, you would call your favorite LLM API (e.g., OpenAI, Anthropic) inside the provider. Here’s a simplified example that always returns a dummy completion:

// 1) Define a sampling providerval samplingProvider=SamplingProvider { params:CreateMessageParams->CreateMessageResult(    model="dummy-model",    role=Role.ASSISTANT,    content=TextContent("Dummy completion result"),    stopReason="endTurn",  )}// 2) Build the client with sampling supportval client=Client.Builder()  .withTransport(StdioTransport())  .withPermissionCallback { userApprovable->// Prompt the user for confirmation heretrue   }  .withSamplingProvider(samplingProvider)// Register the provider  .build()runBlocking {  client.start()  client.initialize()// Now, if a server sends a "sampling/createMessage" request,// the samplingProvider will be invoked to generate a response.}

Request Cancellations

mcp4k uses Kotlin coroutines for cooperative cancellation. For example, a long-running server tool:

@McpToolsuspendfunslowToolOperation(iterations:Int = 10):ToolContent {for (iin1..iterations) {    delay(1000)  }return"Operation completed after$iterations".toTextContent()}

The client can cancel mid-operation:

val requestJob= launch {  client.sendRequest { id->CallToolRequest(      id= id,      params=CallToolRequest.CallToolParams(        name="slowToolOperation",        arguments=mapOf("iterations" to20),      ),    )  }}delay(600)requestJob.cancel("User doesn't want to wait anymore")

Under the hood, mcp4k sends a notification to the server:

{"method":"notifications/cancelled","jsonrpc":"2.0","params": {"requestId":"2","reason":"Client doesn't want to wait anymore"  }}

and the server will abort the suspended tool operation.


Roadmap

✅ Add resource capability✅ @McpTool and @McpPrompt functions✅ Request cancellations✅ Pagination✅ Sampling (client-side)✅ Roots✅ Transport logging✅ onToolsChanged callback in Client⬜ Support other Kotlin versions⬜ Completions⬜ Support logging levels⬜ Proper version negotiation⬜ Emit progress notifications from @McpTool functions⬜ Proper MIME detection⬜ Add FileWatcher to automate resources/updated notifications⬜ HTTP-Streaming transport⬜ Add references, property descriptions and validation keywords to the JSON schemas

How mcp4k Works

  • Annotated@McpTool and@McpPrompt functions are processed at compile time.
  • mcp4k generates JSON schemas, request handlers, and registration code automatically.
  • Generated code is injected during Kotlin's IR compilation phase, guaranteeing type-safe usage.
  • If your KDoc references unknown parameters, the build fails, forcing you to keep docs in sync with code.

Contributing

Issues and pull requests are welcome!Feel free to open a discussion or contribute improvements.

License: mcp4k is available under theApache License 2.0.

About

Compiler-driven MCP framework for Kotlin Multiplatform

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp