Initial commit

This commit is contained in:
leonid.stashevsky 2024-12-15 20:17:55 +01:00
commit 7b32c3e3da
52 changed files with 7267 additions and 5 deletions

6
.gitignore vendored
View file

@ -42,4 +42,8 @@ bin/
.vscode/
### Mac OS ###
.DS_Store
.DS_Store
### Node.js ###
node_modules
dist

6
.idea/gradle.xml generated
View file

@ -1,11 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="" />
<option name="gradleJvm" value="#JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>

6
.idea/jsLibraryMappings.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<excludedPredefinedLibrary name="mcp-kotlin-sdk/src/test/resources/node-server/node_modules" />
</component>
</project>

6
.idea/kotlinc.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.21" />
</component>
</project>

9
.idea/misc.xml generated
View file

@ -1,10 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ASMSmaliIdeaPluginConfiguration">
<asm skipDebug="true" skipFrames="true" skipCode="false" expandFrames="false" />
<groovy codeStyle="LEGACY" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
</project>

124
.idea/uiDesigner.xml generated Normal file
View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

141
README.md Normal file
View file

@ -0,0 +1,141 @@
# MCP Kotlin SDK
Kotlin implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), providing both client and server capabilities for integrating with LLM surfaces.
## Overview
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This Kotlin SDK implements the full MCP specification, making it easy to:
- Build MCP clients that can connect to any MCP server
- Create MCP servers that expose resources, prompts and tools
- Use standard transports like stdio, SSE, and WebSocket
- Handle all MCP protocol messages and lifecycle events
## Installation
Add the JitPack repository to your build file:
```kotlin
repositories {
maven { url = uri("https://jitpack.io") }
}
```
Add the dependency:
```kotlin
dependencies {
implementation("com.github.e5l:mcp-kotlin-sdk:main-SNAPSHOT")
}
```
## Quick Start
### Creating a Client
```kotlin
import org.jetbrains.kotlinx.mcp.client.Client
import org.jetbrains.kotlinx.mcp.client.StdioClientTransport
import org.jetbrains.kotlinx.mcp.Implementation
val client = Client(
clientInfo = Implementation(
name = "example-client",
version = "1.0.0"
)
)
val transport = StdioClientTransport(
inputStream = processInputStream,
outputStream = processOutputStream
)
// Connect to server
client.connect(transport)
// List available resources
val resources = client.listResources()
// Read a specific resource
val resourceContent = client.readResource(
ReadResourceRequest(uri = "file:///example.txt")
)
```
### Creating a Server
```kotlin
import org.jetbrains.kotlinx.mcp.server.Server
import org.jetbrains.kotlinx.mcp.server.ServerOptions
import org.jetbrains.kotlinx.mcp.server.StdioServerTransport
import org.jetbrains.kotlinx.mcp.ServerCapabilities
val server = Server(
serverInfo = Implementation(
name = "example-server",
version = "1.0.0"
),
options = ServerOptions(
capabilities = ServerCapabilities(
resources = ServerCapabilities.Resources(
subscribe = true,
listChanged = true
)
)
)
)
// Add a resource
server.addResource(
uri = "file:///example.txt",
name = "Example Resource",
description = "An example text file",
mimeType = "text/plain"
) { request ->
ReadResourceResult(
contents = listOf(
TextResourceContents(
text = "This is the content of the example resource.",
uri = request.uri,
mimeType = "text/plain"
)
)
)
}
// Start server with stdio transport
val transport = StdioServerTransport()
server.connect(transport)
```
### Using SSE Transport
```kotlin
import io.ktor.server.application.*
import org.jetbrains.kotlinx.mcp.server.MCP
fun Application.module() {
MCP {
Server(
serverInfo = Implementation(
name = "example-sse-server",
version = "1.0.0"
),
options = ServerOptions(
capabilities = ServerCapabilities(
prompts = ServerCapabilities.Prompts(listChanged = null),
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null)
)
)
)
}
}
```
## Contributing
Issues and pull requests are welcome on GitHub at https://github.com/e5l/mcp-kotlin-sdk.
## License
This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.

View file

@ -1,5 +1,9 @@
import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier
plugins {
kotlin("jvm") version "2.0.21"
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.dokka)
}
group = "org.jetbrains.mcp"
@ -10,12 +14,72 @@ repositories {
}
dependencies {
testImplementation(kotlin("test"))
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.client.apache)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.server.cio)
implementation(libs.ktor.server.sse)
implementation(libs.ktor.server.websockets)
implementation(libs.kotlin.logging)
testImplementation(libs.kotlin.test)
testImplementation(libs.mockk)
testImplementation(libs.ktor.server.test.host)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.coroutines.debug)
}
tasks.test {
useJUnitPlatform()
}
abstract class GenerateLibVersionTask @Inject constructor(
@get:Input val libVersion: String,
@get:OutputDirectory val sourcesDir: File
) : DefaultTask() {
@TaskAction
fun generate() {
val sourceFile = File(sourcesDir, "LibVersion.kt")
sourceFile.writeText(
"""
package shared
const val LIB_VERSION = "$libVersion"
""".trimIndent()
)
}
}
dokka {
moduleName.set("MCP Kotlin SDK")
dokkaSourceSets.main {
sourceLink {
localDirectory.set(file("src/main/kotlin"))
remoteUrl("https://github.com/e5l/mcp-kotlin-sdk")
remoteLineSuffix.set("#L")
documentedVisibilities(VisibilityModifier.Public)
}
}
dokkaPublications.html {
outputDirectory.set(project.layout.projectDirectory.dir("docs"))
}
}
val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/libVersion")
val generateLibVersionTask =
tasks.register<GenerateLibVersionTask>("generateLibVersion", version.toString(), sourcesDir)
kotlin {
jvmToolchain(21)
}
sourceSets {
main {
kotlin.srcDir(generateLibVersionTask.map { it.sourcesDir })
}
}
}

View file

@ -1 +1,4 @@
kotlin.code.style=official
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true

37
gradle/libs.versions.toml Normal file
View file

@ -0,0 +1,37 @@
[versions]
# plugins version
kotlin = "2.0.21"
dokka = "2.0.0-Beta"
# libraries version
serialization = "1.7.3"
coroutines = "1.9.0"
ktor = "3.0.2"
mockk = "1.13.13"
logging = "7.0.0"
[libraries]
# Kotlinx libraries
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "logging" }
# Ktor
ktor-client-apache = { group = "io.ktor", name = "ktor-client-apache", version.ref = "ktor" }
ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" }
ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" }
ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
# Testing
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-coroutines-debug = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-debug", version.ref = "coroutines" }
ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }

297
samples/cli.kt Normal file
View file

@ -0,0 +1,297 @@
package org.jetbrains.kotlinx.mcp
import org.jetbrains.kotlinx.mcp.client.Client
import org.jetbrains.kotlinx.mcp.client.StdioClientTransport
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.cio.*
import io.ktor.server.engine.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.sse.*
import io.ktor.util.collections.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonObject
import org.jetbrains.kotlinx.mcp.server.MCP
import org.jetbrains.kotlinx.mcp.server.SSEServerTransport
import org.jetbrains.kotlinx.mcp.server.Server
import org.jetbrains.kotlinx.mcp.server.ServerOptions
import org.jetbrains.kotlinx.mcp.server.StdioServerTransport
fun main(args: Array<String>) {
if (args.isEmpty())
return
val first = args[0]
when (first) {
"--server" -> runServer()
"--demo" -> runDemo()
"--sse-server" -> {
if (args.size < 2) {
System.err.println("Missing port argument")
return
}
val port = args[1].toIntOrNull()
if (port == null) {
System.err.println("Invalid port: ${args[1]}")
return
}
runSseServer(port)
}
else -> {
System.err.println("Unknown argument: $first")
}
}
}
private fun runDemo() {
val processBuilder = ProcessBuilder("npx", "-y", "@jetbrains/mcp-proxy")
var process: Process? = null
try {
process = processBuilder.start()
val client = Client(
Implementation("test", "1.0"),
)
val transport = StdioClientTransport(process.inputStream, process.outputStream)
runBlocking {
client.connect(transport)
val serverCapabilities = client.getServerCapabilities()
// Resources capability check
serverCapabilities?.resources?.let {
try {
val resources = client.listResources()
System.err.println("Resources: ${resources?.resources?.joinToString { it.name }}")
} catch (e: Exception) {
System.err.println("Failed to list resources: ${e.message}")
}
}
// Tools capability check
serverCapabilities?.tools?.let {
try {
val terminal = client.callTool("execute_terminal_command", mapOf("command" to "ls"))
System.err.println(terminal?.content?.first())
val tools = client.listTools()
System.err.println(tools?.tools?.joinToString(", ") { tool -> tool.name })
// tools?.tools?.reversed()?.find { it.name == "toggle_debugger_breakpoint" }?.let { org.jetbrains.kotlinx.mcp.callTool(client, it) }
tools?.tools?.reversed()?.forEachIndexed { i, tool ->
System.err.println("$i out of ${tools.tools.size}: ${tool.name}")
callTool(client, tool)
}
} catch (e: Exception) {
System.err.println("Failed to list tools: ${e.message}")
}
}
// Prompts capability check
serverCapabilities?.prompts?.let {
try {
val prompts = client.listPrompts()
System.err.println("Prompts: ${prompts?.prompts?.joinToString { it.name }}")
} catch (e: Exception) {
System.err.println("Failed to list prompts: ${e.message}")
}
}
}
} finally {
process?.destroy()
}
}
private suspend fun callTool(client: Client, tool: Tool) {
System.err.println(tool.inputSchema)
val map = fillSchema(tool.inputSchema)
System.err.println("calling: ${tool.name}: $map")
val result = try {
client.callTool(CallToolRequest(tool.name, map))
} catch (e: Exception) {
System.err.println("Failed to call tool ${tool.name}: ${e.message}")
return
}
System.err.println("Result: ${result?.content?.first()}\n")
}
private fun fillSchema(schema: Tool.Input): JsonObject {
return buildJsonObject {
schema.properties.forEach { name, elt ->
val type = (elt.jsonObject["type"] as JsonPrimitive).content
val value = when (type) {
"string" -> JsonPrimitive("Hello")
"number" -> JsonPrimitive(42)
"boolean" -> JsonPrimitive(true)
else -> {
System.err.println("+".repeat(30) + " Unknown type: $type " + "+".repeat(30))
JsonPrimitive("Unknown")
}
}
put(name, value)
}
}
}
private fun runServer() {
val def = CompletableDeferred<Unit>()
val server = Server(
Implementation(
name = "mcp-kotlin test server",
version = "0.1.0"
),
ServerOptions(
capabilities = ServerCapabilities(
prompts = ServerCapabilities.Prompts(listChanged = true),
resources = ServerCapabilities.Resources(subscribe = true, listChanged = true),
tools = ServerCapabilities.Tools(listChanged = true),
)
),
onCloseCallback = {
def.complete(Unit)
}
)
server.addPrompt(
name = "Kotlin Developer",
description = "Develop small kotlin applications",
arguments = listOf(
PromptArgument(
name = "Project Name",
description = "Project name for the new project",
required = true
)
)
) { request ->
GetPromptResult(
"Description for ${request.name}",
messages = listOf(
PromptMessage(
role = Role.user,
content = TextContent("Develop a kotlin project named <name>${request.arguments?.get("Project Name")}</name>")
)
)
)
}
// Add a tool
server.addTool(
name = "Test org.jetbrains.kotlinx.mcp.Tool",
description = "A test tool",
inputSchema = Tool.Input()
) { request ->
CallToolResult(
content = listOf(TextContent("Hello, world!"))
)
}
// Add a resource
server.addResource(
uri = "https://google.com/",
name = "Google Search",
description = "Web search engine",
mimeType = "text/html"
) { request ->
ReadResourceResult(
contents = listOf(
TextResourceContents("Placeholder content for ${request.uri}", request.uri, "text/html")
)
)
}
// Note: The server will handle listing prompts, tools, and resources automatically.
// The handleListResourceTemplates will return empty as defined in the Server code.
val transport = StdioServerTransport()
val err = System.err
runBlocking {
server.connect(transport)
err.println("Server running on stdio")
def.await()
}
err.println("Server closed")
}
fun runSseServer(port: Int): Unit = runBlocking {
val servers = ConcurrentMap<String, Server>()
embeddedServer(CIO, host = "0.0.0.0", port = port) {
install(SSE)
routing {
sse("/sse") {
val transport = SSEServerTransport("/message", this)
val options = ServerOptions(
capabilities = ServerCapabilities(
prompts = ServerCapabilities.Prompts(listChanged = null),
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null),
)
)
val server = Server(
Implementation(
name = "mcp-kotlin test server",
version = "0.1.0"
),
options
)
// For SSE, you can also add prompts/tools/resources if needed:
// server.addTool(...), server.addPrompt(...), server.addResource(...)
servers[transport.sessionId] = server
server.onCloseCallback = {
println("Server closed")
servers.remove(transport.sessionId)
}
server.connect(transport)
}
post("/message") {
println("Received Message")
val sessionId: String = call.request.queryParameters["sessionId"]!!
val transport = servers[sessionId]?.transport as? SSEServerTransport
if (transport == null) {
call.respond(HttpStatusCode.NotFound, "Session not found")
return@post
}
transport.handlePostMessage(call)
}
}
}.start(wait = true)
}
fun runSseServerKtor(port: Int): Unit = runBlocking {
embeddedServer(CIO, host = "0.0.0.0", port = port) {
MCP {
val options = ServerOptions(
capabilities = ServerCapabilities(
prompts = ServerCapabilities.Prompts(listChanged = null),
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null),
)
)
Server(
Implementation(
name = "mcp-kotlin test server",
version = "0.1.0"
),
options
)
}
}
}

3
samples/connect-to-intellij.sh Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
../gradlew clean jar; java -jar ../build/libs/mcp-kotlin-sdk-1.0-SNAPSHOT.jar --demo

3
samples/inspector.sh Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
../gradlew clean jar && npx @modelcontextprotocol/inspector java -jar ../build/libs/mcp-kotlin-sdk-1.0-SNAPSHOT.jar --server

3
samples/node-server/build.sh Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
pnpm build

528
samples/node-server/package-lock.json generated Normal file
View file

@ -0,0 +1,528 @@
{
"name": "node-server",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@modelcontextprotocol/sdk": "1.0.3",
"node-fetch": "^3.3.2"
},
"bin": {
"node-server": "dist/server.js"
},
"devDependencies": {
"@types/node": "^20.11.0",
"shx": "^0.3.4",
"typescript": "^5.3.3"
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.3.tgz",
"integrity": "sha512-2as3cX/VJ0YBHGmdv3GFyTpoM8q2gqE98zh3Vf1NwnsSY0h3mvoO07MUzfygCKkWsFjcZm4otIiqD6Xh7kiSBQ==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"raw-body": "^3.0.0",
"zod": "^3.23.8"
}
},
"node_modules/@types/node": {
"version": "20.17.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz",
"integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true,
"license": "ISC"
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/interpret": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-core-module": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
"license": "MIT"
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
"dev": true,
"dependencies": {
"resolve": "^1.1.6"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/shelljs": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
"integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"glob": "^7.0.0",
"interpret": "^1.0.0",
"rechoir": "^0.6.2"
},
"bin": {
"shjs": "bin/shjs"
},
"engines": {
"node": ">=4"
}
},
"node_modules/shx": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz",
"integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.3",
"shelljs": "^0.8.5"
},
"bin": {
"shx": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/typescript": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"license": "MIT"
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/zod": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View file

@ -0,0 +1,26 @@
{
"main": "dist/server.js",
"type": "module",
"bin": {
"node-server": "dist/server.js"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc --outDir dist && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch --outDir dist",
"run_server": "dist/server.js",
"run_client": "dist/client.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.0.3",
"node-fetch": "^3.3.2"
},
"devDependencies": {
"@types/node": "^20.11.0",
"shx": "^0.3.4",
"typescript": "^5.3.3"
}
}

View file

@ -0,0 +1,2 @@
#!/bin/bash
./dist/client.js

View file

@ -0,0 +1,2 @@
#!/bin/bash
./dist/server.js

View file

@ -0,0 +1,52 @@
#!/usr/bin/env node
import * as net from 'net';
import { Readable, Writable } from 'stream';
const client = new net.Socket();
const PORT = 3000;
const HOST = '127.0.0.1';
// If startClient is supposed to be asynchronous and do something later,
// keep async. Otherwise, remove async if not needed.
function startClient(input: Readable, output: Writable) {
// TODO: Implement client logic here if needed
// For now, it's just a placeholder.
}
const input = new Readable({
read() {
// No-op: We'll push data into this stream externally.
}
});
const output = new Writable({
write(chunk, encoding, callback) {
console.log('Writing to output (and pushing to input):', chunk.toString('utf-8'));
input.push(chunk);
callback();
}
});
client.connect(PORT, HOST, () => {
console.log('Connected to server');
startClient(input, output);
});
client.on('data', (data) => {
// When we receive data from the server socket, push it into `input`.
input.push(data);
});
client.on('close', () => {
input.push(null); // Signal the end of the input stream
console.log('Connection closed by server');
});
client.on('error', (err) => {
console.error('Client error:', err);
});
// Push initial data into `input`. If no reader is attached, this might not be visible.
// Consider adding `input.on('data', ...)` somewhere else if you want to see this data.
input.push("Hello");

View file

@ -0,0 +1,95 @@
#!/usr/bin/env node
import * as net from 'net';
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { Readable, Writable, WritableOptions } from 'stream';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
const PORT = 3000;
const mcpServer = new Server(
{
name: "jetbrains/proxy",
version: "0.1.0",
},
{
capabilities: {
tools: {
listChanged: true,
},
resources: {},
},
},
);
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
console.log("ListToolsRequest received");
return {
tools: []
};
});
async function handleConnection(stdin: Readable, stdout: Writable) {
const transport = new StdioServerTransport(stdin, stdout);
try {
await mcpServer.connect(transport);
} catch (err) {
console.error('Error connecting MCP server:', err);
}
}
// A Writable stream class that writes data back to the socket
class SocketWritable extends Writable {
private socket: net.Socket;
constructor(socket: net.Socket, options?: WritableOptions) {
super(options);
this.socket = socket;
}
_write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void) {
console.log("Socket write:", chunk.toString());
this.socket.write(chunk, encoding, callback);
}
}
const server = net.createServer((socket) => {
console.log('Client connected');
// Create a Readable stream that we will push data into from the socket
const stdin = new Readable({
read() {
// We'll use stdin.push() from socket data events
},
});
// Create a Writable that writes back to the socket
const stdout = new SocketWritable(socket);
socket.on('data', (chunk) => {
const decoded = chunk.toString("utf-8");
console.log("Socket data:", decoded);
// Push the raw buffer into stdin so the server can read it
stdin.push(chunk);
});
socket.on('end', () => {
stdin.push(null); // Signal EOF to the stdin stream
console.log('Client disconnected');
});
socket.on('error', (err) => {
console.error('Socket error:', err);
});
handleConnection(stdin, stdout);
});
server.listen(PORT, () => {
console.log(`MCP proxy server listening on port ${PORT}`);
});
server.on('error', (err) => {
console.error('Server error:', err);
});

Some files were not shown because too many files have changed in this diff Show more