diff --git a/.claude/skills/kdoc/SKILL.md b/.claude/skills/kdoc/SKILL.md
new file mode 100644
index 0000000..cfacfc3
--- /dev/null
+++ b/.claude/skills/kdoc/SKILL.md
@@ -0,0 +1,81 @@
+---
+name: kdoc
+description: "Add KDoc documentation to Kotlin public API. Use whenever the user asks to document Kotlin code, add KDoc, generate API docs, mentions undocumented public declarations, or wants to improve existing documentation. Also trigger when the user says 'add docs', 'document this class/file/module', 'write KDoc', or asks about missing documentation in Kotlin code."
+---
+
+# KDoc Generator
+
+Add KDoc to all public Kotlin declarations (classes, interfaces, objects, functions, properties, enums, type aliases, annotations — including `@Deprecated`). Read the implementation, not just the signature, to write accurate descriptions.
+
+## Tag placement
+
+| Where the declaration lives | Tag |
+|---|---|
+| Primary-ctor `val` / `var` (public, internal, or protected) | `@property` in class KDoc |
+| Primary-ctor `name: Type` (no `val`/`var`) | `@param` in class KDoc |
+| Primary-ctor `private val` / `private var` | `@param` in class KDoc |
+| Body of class/object/interface/sealed interface | inline `/** */` above the declaration |
+| Function parameter | `@param` |
+| Non-Unit return type | `@return` |
+
+## Style
+
+- Summary: one concise sentence, third-person verb ("Creates", "Returns", "Represents"). Expand to 2-3 sentences only when genuinely complex.
+- **Example block**: mandatory for DSL builders (show full receiver-scope usage); add for complex functions or non-obvious extension functions; skip for one-liners and simple getters.
+- KDoc links (`[ClassName]`, `[methodName]`) only where they add navigational value.
+- Don't write `@throws` or "suspend" notes.
+- Rewrite existing KDoc if incomplete or low quality.
+
+## Examples
+
+**Class with mixed primary-constructor parameters:**
+
+````kotlin
+/**
+ * Manages active client sessions and their lifecycle.
+ *
+ * @property maxSessions upper limit on concurrent sessions
+ * @param scope coroutine scope for session lifecycle tasks
+ * @param secret used internally for session token signing
+ */
+public class SessionManager(
+ public val maxSessions: Int, // public val → @property
+ scope: CoroutineScope, // no val/var → @param
+ private val secret: ByteArray, // private val → @param
+)
+````
+
+**Body and interface properties (inline `/** */`):**
+
+````kotlin
+/** Represents an entity that includes additional metadata. */
+public interface WithMeta {
+ /** Optional metadata attached to this entity. */
+ public val meta: JsonObject?
+}
+
+public class Connection(public val id: String) {
+ /** Capabilities negotiated during the handshake, or `null` before initialize completes. */
+ public val capabilities: ServerCapabilities? get() = ...
+}
+````
+
+**Function with Example block:**
+
+````kotlin
+/**
+ * Registers a new tool with the given handler.
+ *
+ * Example:
+ * ```kotlin
+ * server.addTool(name = "echo", description = "Echoes input back") { request ->
+ * CallToolResult(content = listOf(TextContent("Echo: ${request.arguments}")))
+ * }
+ * ```
+ *
+ * @param name unique tool identifier
+ * @param description human-readable tool description
+ * @param handler suspend function invoked when the tool is called
+ * @return the registered tool definition
+ */
+````
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8d02aac..b951eaa 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,20 +1,109 @@
-# Configuration options:
-# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#about-the-dependabotyml-file
+# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json
+# Configuration: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
+
updates:
+ # Root Gradle project (includes buildSrc)
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
+ day: "monday"
+ commit-message:
+ prefix: "chore(deps):"
labels:
- "dependencies"
- "kotlin"
+ open-pull-requests-limit: 10
+ groups:
+ ktor:
+ patterns:
+ - "io.ktor:*"
+ kotlinx:
+ patterns:
+ - "org.jetbrains.kotlinx:*"
+ kotlin:
+ patterns:
+ - "org.jetbrains.kotlin:*"
+ kotest:
+ patterns:
+ - "io.kotest:*"
+ testing:
+ patterns:
+ - "io.mockk:*"
+ - "dev.mokksy:*"
+ - "org.awaitility:*"
+ - "org.junit.jupiter:*"
+ infrastructure:
+ patterns:
+ - "io.netty:*"
+ - "io.github.oshai:*"
+ - "org.slf4j:*"
+ gradle-plugins:
+ patterns:
+ - "dev.detekt"
+ - "org.jetbrains.kotlinx.kover"
+ - "org.jlleitschuh.gradle.ktlint"
+ - "org.jetbrains.kotlinx.knit"
+ - "org.jetbrains.kotlinx.binary-compatibility-validator"
+ - "org.openapi.generator"
+ - "org.jetbrains.dokka:*"
+ - "com.vanniktech:*"
+ # Samples (independent Gradle builds)
+ - package-ecosystem: "gradle"
+ directories:
+ - "/samples/*"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ commit-message:
+ prefix: "chore(deps):"
+ labels:
+ - "dependencies"
+ - "kotlin"
+ - "samples"
+ open-pull-requests-limit: 10
+ groups:
+ other-dependencies:
+ patterns:
+ - "*"
+ exclude-patterns:
+ - "io.ktor*"
+ - "org.jetbrains.kotlin*"
+ - "io.modelcontextprotocol*"
+
+ # npm (integration tests)
+ - package-ecosystem: "npm"
+ directory: "/integration-test/src/jvmTest/typescript"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ commit-message:
+ prefix: "chore(deps):"
+ labels:
+ - "dependencies"
+ - "javascript"
+ open-pull-requests-limit: 10
+ groups:
+ all-dependencies:
+ patterns:
+ - "*"
+
+ # GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+ day: "monday"
+ commit-message:
+ prefix: "chore(deps):"
labels:
- "dependencies"
- "github-actions"
+ open-pull-requests-limit: 10
+ groups:
+ all-actions:
+ patterns:
+ - "*"
diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml
index 5c7ed4c..67770a7 100644
--- a/.github/workflows/apidocs.yaml
+++ b/.github/workflows/apidocs.yaml
@@ -24,27 +24,27 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK ${{ env.JAVA_VERSION }}
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
cache: gradle
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
- name: Generate Dokka Site
run: |-
./gradlew knit docs:classes docs:knitCheck dokkaGenerate
- name: Upload artifact
- uses: actions/upload-pages-artifact@v4
+ uses: actions/upload-pages-artifact@v5
with:
path: build/dokka/html
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v4
+ uses: actions/deploy-pages@v5
diff --git a/.github/workflows/automations.yml b/.github/workflows/automations.yml
index 4f88d37..60a9d3b 100644
--- a/.github/workflows/automations.yml
+++ b/.github/workflows/automations.yml
@@ -27,7 +27,7 @@ jobs:
name: Mark/unmark stale PRs and issues
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v10
+ - uses: actions/stale@v11
with:
debug-only: ${{ env.DRY_RUN }}
exempt-draft-pr: true
@@ -66,7 +66,7 @@ jobs:
name: Close stale PRs and issues
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v10
+ - uses: actions/stale@v11
with:
debug-only: ${{ env.DRY_RUN }}
only-labels: 'stale'
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4fd2522..ff99581 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -43,17 +43,17 @@ jobs:
max-workers: 2
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK ${{ env.JAVA_VERSION }}
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- name: Set up Node.js
if: matrix.os == 'ubuntu-latest'
- uses: actions/setup-node@v6
+ uses: actions/setup-node@v7
with:
node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed
@@ -63,7 +63,7 @@ jobs:
run: npm ci
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
validate-wrappers: true
add-job-summary: 'always'
@@ -104,22 +104,6 @@ jobs:
report_paths: '**/test-results/**/TEST-*.xml'
truncate_stack_traces: false
- - name: Upload coverage reports to Codecov
- if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }}
- uses: codecov/codecov-action@v5
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- report_type: coverage
- directory: ./build/reports/kover
- files: report.xml
-
- - name: Upload test results to Codecov
- if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }}
- uses: codecov/codecov-action@v5
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- report_type: test_results
-
- name: Archive Maven Local Repository
if: ${{ success() && matrix.os == 'ubuntu-latest' }}
@@ -142,16 +126,16 @@ jobs:
contents: read
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK ${{ env.JAVA_VERSION }}
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
validate-wrappers: true
add-job-summary: 'always'
@@ -184,16 +168,16 @@ jobs:
name: "Build Sample: ${{ matrix.sample }}"
timeout-minutes: 10
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK ${{ env.JAVA_VERSION }}
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
add-job-summary: 'always'
cache-read-only: true
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 41d1fde..2e7c58f 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -25,22 +25,22 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Set up JDK ${{ env.JAVA_VERSION }}
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
distribution: ${{ env.JAVA_DISTRIBUTION }}
java-version: ${{ env.JAVA_VERSION }}
- name: Initialize CodeQL
- uses: github/codeql-action/init@v4
+ uses: github/codeql-action/init@v4.38.0
with:
languages: java-kotlin
build-mode: manual
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
validate-wrappers: true
cache-read-only: true
@@ -52,6 +52,6 @@ jobs:
--no-daemon --rerun-tasks --stacktrace
- name: Analyze
- uses: github/codeql-action/analyze@v4
+ uses: github/codeql-action/analyze@v4.38.0
with:
category: '/language:java-kotlin'
diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml
index b218b4c..b3e3935 100644
--- a/.github/workflows/conformance.yml
+++ b/.github/workflows/conformance.yml
@@ -25,16 +25,16 @@ jobs:
timeout-minutes: 20
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
@@ -53,7 +53,7 @@ jobs:
done
- name: Run conformance tests
- uses: modelcontextprotocol/conformance@v0.1.15
+ uses: modelcontextprotocol/conformance@v0.1.16
with:
mode: server
url: http://localhost:3001/mcp
@@ -76,16 +76,16 @@ jobs:
- sse-retry
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
@@ -93,7 +93,7 @@ jobs:
run: ./gradlew :conformance-test:installDist
- name: Run conformance tests
- uses: modelcontextprotocol/conformance@v0.1.15
+ uses: modelcontextprotocol/conformance@v0.1.16
with:
mode: client
command: conformance-test/build/install/conformance-test/bin/conformance-client
@@ -107,16 +107,16 @@ jobs:
timeout-minutes: 20
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
@@ -124,7 +124,7 @@ jobs:
run: ./gradlew :conformance-test:installDist
- name: Run conformance tests
- uses: modelcontextprotocol/conformance@v0.1.15
+ uses: modelcontextprotocol/conformance@v0.1.16
with:
mode: client
command: conformance-test/build/install/conformance-test/bin/conformance-client
@@ -146,16 +146,16 @@ jobs:
- auth/cross-app-access-complete-flow
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
@@ -163,7 +163,7 @@ jobs:
run: ./gradlew :conformance-test:installDist
- name: Run conformance tests
- uses: modelcontextprotocol/conformance@v0.1.15
+ uses: modelcontextprotocol/conformance@v0.1.16
with:
mode: client
command: conformance-test/build/install/conformance-test/bin/conformance-client
diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml
new file mode 100644
index 0000000..e7e94c3
--- /dev/null
+++ b/.github/workflows/dependabot-auto-merge.yml
@@ -0,0 +1,23 @@
+name: Auto-merge Dependabot
+
+on: pull_request
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ auto-merge:
+ if: github.actor == 'dependabot[bot]'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dependabot/fetch-metadata@v3
+ id: metadata
+
+ - if: >-
+ steps.metadata.outputs.package-ecosystem == 'github_actions' ||
+ steps.metadata.outputs.package-ecosystem == 'npm_and_yarn'
+ run: gh pr merge --auto --squash "$PR_URL"
+ env:
+ PR_URL: ${{ github.event.pull_request.html_url }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml
index ed9a63a..f01f9c8 100644
--- a/.github/workflows/gradle-publish.yml
+++ b/.github/workflows/gradle-publish.yml
@@ -17,18 +17,23 @@ jobs:
packages: write
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK 21
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: '21'
distribution: 'temurin'
- - name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ - name: Set up Node.js
+ uses: actions/setup-node@v7
+ with:
+ node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed
- - name: Build
- run: ./gradlew build --rerun-tasks --max-workers 2 --continue
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v6.3.0
+
+ - name: Check
+ run: ./gradlew check --no-configuration-cache
- name: Publish to Maven Central Portal
id: publish
diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml
index 632b710..7463a9e 100644
--- a/.github/workflows/samples.yml
+++ b/.github/workflows/samples.yml
@@ -36,16 +36,16 @@ jobs:
name: Build Sample (${{ matrix.sample }})
timeout-minutes: 10
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up JDK ${{ env.JAVA_VERSION }}
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}
- name: Setup Gradle
- uses: gradle/actions/setup-gradle@v5
+ uses: gradle/actions/setup-gradle@v6.3.0
with:
validate-wrappers: true
add-job-summary: 'always'
diff --git a/.gitignore b/.gitignore
index d9f0fde..805579c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,7 +56,7 @@ node_modules
dist
### SWE agents ###
-.claude/settings.local.json
+.claude/
.junie/
### Conformance test results ###
diff --git a/.idea/detekt.xml b/.idea/detekt.xml
deleted file mode 100644
index d89f908..0000000
--- a/.idea/detekt.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml
deleted file mode 100644
index 0a081df..0000000
--- a/.idea/externalDependencies.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/.idea/ktlint-plugin.xml b/.idea/ktlint-plugin.xml
deleted file mode 100644
index e8bd90c..0000000
--- a/.idea/ktlint-plugin.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- DISTRACT_FREE
- DEFAULT
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 3e65b26..aefadcf 100644
--- a/README.md
+++ b/README.md
@@ -19,34 +19,34 @@ standardized protocol interface.
* [Overview](#overview)
* [Installation](#installation)
- * [Artifacts](#artifacts)
- * [Gradle setup (JVM)](#gradle-setup-jvm)
- * [Multiplatform](#multiplatform)
- * [Ktor dependencies](#ktor-dependencies)
+ * [Artifacts](#artifacts)
+ * [Gradle setup (JVM)](#gradle-setup-jvm)
+ * [Multiplatform](#multiplatform)
+ * [Ktor dependencies](#ktor-dependencies)
* [Quickstart](#quickstart)
- * [Creating a Client](#creating-a-client)
- * [Creating a Server](#creating-a-server)
+ * [Creating a Client](#creating-a-client)
+ * [Creating a Server](#creating-a-server)
* [Core Concepts](#core-concepts)
- * [MCP Primitives](#mcp-primitives)
- * [Capabilities](#capabilities)
- * [Server Capabilities](#server-capabilities)
- * [Client Capabilities](#client-capabilities)
- * [Server Features](#server-features)
- * [Prompts](#prompts)
- * [Resources](#resources)
- * [Tools](#tools)
- * [Completion](#completion)
- * [Logging](#logging)
- * [Pagination](#pagination)
- * [Client Features](#client-features)
- * [Roots](#roots)
- * [Sampling](#sampling)
+ * [MCP Primitives](#mcp-primitives)
+ * [Capabilities](#capabilities)
+ * [Server Capabilities](#server-capabilities)
+ * [Client Capabilities](#client-capabilities)
+ * [Server Features](#server-features)
+ * [Prompts](#prompts)
+ * [Resources](#resources)
+ * [Tools](#tools)
+ * [Completion](#completion)
+ * [Logging](#logging)
+ * [Pagination](#pagination)
+ * [Client Features](#client-features)
+ * [Roots](#roots)
+ * [Sampling](#sampling)
* [Transports](#transports)
- * [STDIO Transport](#stdio-transport)
- * [Streamable HTTP Transport](#streamable-http-transport)
- * [SSE Transport](#sse-transport)
- * [WebSocket Transport](#websocket-transport)
- * [ChannelTransport (testing)](#channeltransport-testing)
+ * [STDIO Transport](#stdio-transport)
+ * [Streamable HTTP Transport](#streamable-http-transport)
+ * [SSE Transport](#sse-transport)
+ * [WebSocket Transport](#websocket-transport)
+ * [ChannelTransport (testing)](#channeltransport-testing)
* [Connecting your server](#connecting-your-server)
* [Examples](#examples)
* [Documentation](#documentation)
@@ -188,24 +188,19 @@ the [simple-streamable-server](samples/simple-streamable-server) sample.
```kotlin
-import io.ktor.serialization.kotlinx.json.json
-import io.ktor.server.application.install
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
-import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
-import io.modelcontextprotocol.kotlin.sdk.types.McpJson
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
-
fun main(args: Array) {
val port = args.firstOrNull()?.toIntOrNull() ?: 3000
val mcpServer = Server(
@@ -231,11 +226,8 @@ fun main(args: Array) {
) { request ->
CallToolResult(content = listOf(TextContent("Hello, world!")))
}
-
+
embeddedServer(CIO, host = "127.0.0.1", port = port) {
- install(ContentNegotiation) {
- json(McpJson)
- }
mcpStreamableHttp {
mcpServer
}
@@ -737,7 +729,7 @@ val client = Client(
)
client.setRequestHandler(Method.Defined.SamplingCreateMessage) { request, _ ->
- val content = request.messages.lastOrNull()?.content
+ val content = request.messages.lastOrNull()?.content?.firstOrNull()
val prompt = if (content is TextContent) content.text else "your topic"
CreateMessageResult(
model = "gpt-4o-mini",
@@ -774,8 +766,10 @@ CLI tooling that spawns a helper process. No networking setup is required.
`StreamableHttpClientTransport` and the Ktor `mcpStreamableHttp()` / `mcpStatelessStreamableHttp()` helpers expose MCP
over a single HTTP endpoint with optional JSON-only or SSE streaming responses. This is the recommended choice for
-remote deployments and integrates nicely with proxies or service meshes. Both accept a `path` parameter (default:
-`"/mcp"`) to mount the endpoint at any URL:
+remote deployments and integrates nicely with proxies or service meshes.
+
+These helpers automatically install `ContentNegotiation` with `McpJson` — do not install it yourself, or a warning will
+be logged. Both accept a `path` parameter (default: `"/mcp"`) to mount the endpoint at any URL:
+**CORS for browser-based clients (e.g. MCP Inspector):** if you connect from a browser-based
+client you need to install the Ktor CORS plugin so that MCP-specific headers are allowed and exposed:
+
+```kotlin
+install(CORS) {
+ anyHost() // restrict to specific origins in production
+ allowMethod(HttpMethod.Options)
+ allowMethod(HttpMethod.Get)
+ allowMethod(HttpMethod.Post)
+ allowMethod(HttpMethod.Delete)
+ allowNonSimpleContentTypes = true
+ allowHeader("Mcp-Session-Id")
+ allowHeader("Mcp-Protocol-Version")
+ exposeHeader("Mcp-Session-Id")
+ exposeHeader("Mcp-Protocol-Version")
+}
+```
+
### SSE Transport
Server-Sent Events remain available for backwards compatibility with older MCP clients. Two Ktor helpers are provided:
-- **`Application.mcp { }`** — installs the SSE plugin automatically and registers MCP endpoints at `/`.
+- **`Application.mcp { }`** — installs SSE and `ContentNegotiation` with `McpJson` automatically, then registers MCP
+ endpoints at `/`. Do not install `ContentNegotiation` yourself — the SDK handles it.
- **`Route.mcp { }`** — registers MCP endpoints at the current route path; requires `install(SSE)` in the application
first. Use this to host MCP alongside other routes or under a path prefix:
diff --git a/build.gradle.kts b/build.gradle.kts
index 72c5553..2c2dbe3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,8 +1,5 @@
-import dev.detekt.gradle.extensions.FailOnSeverity
-
plugins {
id("mcp.dokka")
- alias(libs.plugins.detekt)
alias(libs.plugins.ktlint)
alias(libs.plugins.kover)
}
@@ -23,16 +20,6 @@ dependencies {
subprojects {
apply(plugin = "org.jlleitschuh.gradle.ktlint")
apply(plugin = "org.jetbrains.kotlinx.kover")
-
- if (name != "conformance-test" && name != "docs") {
- apply(plugin = "dev.detekt")
-
- detekt {
- config = files("$rootDir/config/detekt/detekt.yml")
- buildUponDefaultConfig = true
- failOnSeverity.set(FailOnSeverity.Error)
- }
- }
}
dokka {
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 84676a4..4a057ce 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -8,6 +8,7 @@ repositories {
}
dependencies {
+ implementation(libs.detekt.gradle)
implementation(libs.kotlin.gradle)
implementation(libs.kotlin.serialization)
implementation(libs.kotlinx.atomicfu.gradle)
diff --git a/buildSrc/src/main/kotlin/mcp.detekt.gradle.kts b/buildSrc/src/main/kotlin/mcp.detekt.gradle.kts
new file mode 100644
index 0000000..3c1419e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/mcp.detekt.gradle.kts
@@ -0,0 +1,23 @@
+import dev.detekt.gradle.Detekt
+import dev.detekt.gradle.extensions.FailOnSeverity
+
+plugins {
+ id("dev.detekt")
+}
+
+detekt {
+ config = files("$rootDir/config/detekt/detekt.yml")
+ buildUponDefaultConfig = false
+ parallel = true
+ failOnSeverity = FailOnSeverity.Error
+}
+
+tasks.withType().configureEach {
+ exclude("**/generated/**", "**/generated-sources/**", "**/build/**")
+}
+
+pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform") {
+ tasks.named("detekt").configure {
+ dependsOn("detektMainJvm")
+ }
+}
diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts
index f8d89c0..98a0a5e 100644
--- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts
+++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts
@@ -45,14 +45,8 @@ kotlin {
events("failed")
}
systemProperty("kotest.output.ansi", "true")
- reports {
- junitXml.required.set(true)
- junitXml.includeSystemOutLog.set(true)
- junitXml.includeSystemErrLog.set(true)
- }
}
}
- macosX64()
macosArm64()
linuxX64()
linuxArm64()
@@ -63,12 +57,3 @@ kotlin {
explicitApi = ExplicitApiMode.Strict
jvmToolchain(21)
}
-
-tasks.named("detekt").configure {
- dependsOn(
- "detektMainJvm",
- "detektCommonMainSourceSet",
- "detektTestJvm",
- "detektCommonTestSourceSet",
- )
-}
diff --git a/codecov.yml b/codecov.yml
deleted file mode 100644
index 9135aae..0000000
--- a/codecov.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-# Codecov configuration for Mokksy and AI-Mocks
-# https://docs.codecov.com/docs/codecov-yaml
-
-codecov:
- require_ci_to_pass: yes
-
-coverage:
- precision: 2
- round: down
- status:
- project:
- default:
- target: auto
- threshold: 1%
- patch:
- default:
- target: auto
- threshold: 1%
-
-comment:
- layout: "header, diff, flags, components"
- behavior: default
- require_changes: true
-
-component_management:
- default_rules:
- statuses:
- - type: project
- target: auto
- individual_components:
-
- - component_id: kotlin-sdk-core
- name: SDK Core
- paths:
- - kotlin-sdk-core/**
-
- - component_id: kotlin-sdk-client
- name: SDK Client
- paths:
- - kotlin-sdk-client/**
-
- - component_id: kotlin-sdk-server
- name: SDK Server
- paths:
- - kotlin-sdk-server/**
-
- - component_id: kotlin-sdk-testing
- name: SDK Testing
- paths:
- - kotlin-sdk-testing/**
-
diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml
index 2cb135a..a82b404 100644
--- a/config/detekt/detekt.yml
+++ b/config/detekt/detekt.yml
@@ -3,22 +3,839 @@ config:
warningsAsErrors: false
checkExhaustiveness: false
+processors:
+ active: false
+
+console-reports:
+ active: true
+ exclude:
+ - 'ProjectStatisticsReport'
+ - 'ComplexityReport'
+ - 'NotificationReport'
+ - 'IssuesReport'
+ - 'FileBasedIssuesReport'
+ # - 'LiteIssuesReport'
+
+comments:
+ active: true
+ AbsentOrWrongFileLicense:
+ active: false
+ licenseTemplateIsRegex: false
+ licenseTemplate: ''
+ DeprecatedBlockTag:
+ active: true
+ DocumentationOverPrivateFunction:
+ active: false
+ DocumentationOverPrivateProperty:
+ active: false
+ EndOfSentenceFormat:
+ active: false
+ endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
+ KDocReferencesNonPublicProperty:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ OutdatedDocumentation:
+ active: false
+ matchTypeParameters: true
+ matchDeclarationsOrder: true
+ allowParamOnConstructorProperties: false
+ UndocumentedPublicClass:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchInNestedClass: true
+ searchInInnerClass: true
+ searchInInnerObject: true
+ searchInInnerInterface: true
+ searchInProtectedClass: false
+ ignoreDefaultCompanionObject: true
+ UndocumentedPublicFunction:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchProtectedFunction: false
+ UndocumentedPublicProperty:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchProtectedProperty: false
+ ignoreEnumEntries: false
+
complexity:
+ active: true
+ CognitiveComplexMethod:
+ active: false
+ allowedComplexity: 20
+ ComplexCondition:
+ active: true
+ allowedConditions: 4
+ ComplexInterface:
+ active: false
+ allowedDefinitions: 10
+ includeStaticDeclarations: false
+ includePrivateDeclarations: false
+ ignoreOverloaded: false
+ CyclomaticComplexMethod:
+ active: false
+ allowedComplexity: 15
+ ignoreSingleWhenExpression: false
+ ignoreSimpleWhenEntries: false
+ ignoreNestingFunctions: false
+ ignoreLocalFunctions: false
+ nestingFunctions:
+ - 'also'
+ - 'apply'
+ - 'forEach'
+ - 'isNotNull'
+ - 'ifNull'
+ - 'let'
+ - 'run'
+ - 'use'
+ - 'with'
+ LabeledExpression:
+ active: false
+ ignoredLabels: []
LargeClass:
- excludes: &testFolders
- - '**/test/**'
- - '**/commonTest/**'
- - '**/jvmTest/**'
- - '**/jsTest/**'
- - '**/iosTest/**'
+ active: false
+ allowedLines: 700
LongMethod:
- excludes: *testFolders
+ active: false
+ allowedLines: 110
+ LongParameterList:
+ active: false
+ allowedFunctionParameters: 8
+ allowedConstructorParameters: 8
+ ignoreDefaultParameters: false
+ ignoreDataClasses: true
+ ignoreAnnotatedParameter: []
+ MethodOverloading:
+ active: false
+ allowedOverloads: 6
+ NamedArguments:
+ active: false
+ allowedArguments: 3
+ ignoreMethods: []
+ ignoreArgumentsMatchingNames: false
+ NestedBlockDepth:
+ active: true
+ allowedDepth: 4
+ NestedScopeFunctions:
+ active: false
+ allowedDepth: 1
+ functions:
+ - 'kotlin.apply'
+ - 'kotlin.run'
+ - 'kotlin.with'
+ - 'kotlin.let'
+ - 'kotlin.also'
+ ReplaceSafeCallChainWithRun:
+ active: false
+ StringLiteralDuplication:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ allowedDuplications: 3
+ ignoreAnnotation: true
+ allowedWithLengthLessThan: 5
+ ignoreStringsRegex: '$^'
+ TooManyFunctions:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ allowedFunctionsPerFile: 20
+ allowedFunctionsPerClass: 20
+ allowedFunctionsPerInterface: 20
+ allowedFunctionsPerObject: 20
+ allowedFunctionsPerEnum: 11
+ ignoreDeprecated: true
+ ignorePrivate: true
+ ignoreInternal: true
+ ignoreOverridden: false
+ ignoreAnnotatedFunctions: []
coroutines:
+ active: true
+ CoroutineLaunchedInTestWithoutRunTest:
+ active: false
+ GlobalCoroutineUsage:
+ active: true
InjectDispatcher:
- excludes: *testFolders
+ active: false
+ dispatcherNames:
+ - 'IO'
+ - 'Default'
+ - 'Unconfined'
+ RedundantSuspendModifier:
+ active: true
+ SleepInsteadOfDelay:
+ active: true
+ SuspendFunInFinallySection:
+ active: false
+ SuspendFunSwallowedCancellation:
+ active: false
+ SuspendFunWithCoroutineScopeReceiver:
+ active: false
+ aliases: ['SuspendFunctionOnCoroutineScope']
+ SuspendFunWithFlowReturnType:
+ active: true
empty-blocks:
+ active: true
+ EmptyCatchBlock:
+ active: true
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ EmptyClassBlock:
+ active: true
+ EmptyDefaultConstructor:
+ active: true
+ EmptyDoWhileBlock:
+ active: true
+ EmptyElseBlock:
+ active: true
+ EmptyFinallyBlock:
+ active: true
+ EmptyForBlock:
+ active: true
EmptyFunctionBlock:
- excludes: *testFolders
+ active: true
+ ignoreOverridden: false
+ EmptyIfBlock:
+ active: true
+ EmptyInitBlock:
+ active: true
+ EmptyKotlinFile:
+ active: true
+ EmptySecondaryConstructor:
+ active: true
+ EmptyTryBlock:
+ active: true
+ EmptyWhenBlock:
+ active: true
+ EmptyWhileBlock:
+ active: true
+exceptions:
+ active: true
+ ErrorUsageWithThrowable:
+ active: false
+ ExceptionRaisedInUnexpectedLocation:
+ active: true
+ methodNames:
+ - 'equals'
+ - 'finalize'
+ - 'hashCode'
+ - 'toString'
+ InstanceOfCheckForException:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ NotImplementedDeclaration:
+ active: true
+ ObjectExtendsThrowable:
+ active: true
+ PrintStackTrace:
+ active: true
+ RethrowCaughtException:
+ active: true
+ ReturnFromFinally:
+ active: true
+ ignoreLabeled: false
+ SwallowedException:
+ active: true
+ ignoredExceptionTypes:
+ - 'InterruptedException'
+ - 'MalformedURLException'
+ - 'NumberFormatException'
+ - 'ParseException'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ ThrowingExceptionFromFinally:
+ active: true
+ ThrowingExceptionInMain:
+ active: false
+ ThrowingExceptionsWithoutMessageOrCause:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ exceptions:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Exception'
+ - 'IllegalArgumentException'
+ - 'IllegalMonitorStateException'
+ - 'IllegalStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ ThrowingNewInstanceOfSameException:
+ active: true
+ TooGenericExceptionCaught:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ exceptionNames:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Error'
+ - 'Exception'
+ - 'IllegalMonitorStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ TooGenericExceptionThrown:
+ active: false
+ exceptionNames:
+ - 'Error'
+ - 'Exception'
+ - 'RuntimeException'
+ - 'Throwable'
+
+naming:
+ active: true
+ BooleanPropertyNaming:
+ active: false
+ allowedPattern: '^(is|has|are)'
+ ClassNaming:
+ active: true
+ aliases: ['ClassName']
+ classPattern: '[A-Z][a-zA-Z0-9]*'
+ ConstructorParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ privateParameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ EnumNaming:
+ active: true
+ aliases: ['EnumEntryName']
+ enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
+ ForbiddenClassName:
+ active: false
+ forbiddenName: []
+ FunctionNameMaxLength:
+ active: false
+ aliases: ['FunctionMaxNameLength']
+ maximumFunctionNameLength: 30
+ FunctionNameMinLength:
+ active: false
+ aliases: ['FunctionMinNameLength']
+ minimumFunctionNameLength: 3
+ FunctionNaming:
+ active: true
+ aliases: ['FunctionName']
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ functionPattern: '[a-z][a-zA-Z0-9]*'
+ excludeClassPattern: '$^'
+ FunctionParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ InvalidPackageDeclaration:
+ active: true
+ aliases: ['PackageDirectoryMismatch']
+ rootPackage: ''
+ requireRootInDeclaration: false
+ LambdaParameterNaming:
+ active: false
+ parameterPattern: '[a-z][A-Za-z0-9]*|_'
+ MatchingDeclarationName:
+ active: false
+ mustBeFirst: true
+ multiplatformTargets:
+ - 'ios'
+ - 'android'
+ - 'js'
+ - 'jvm'
+ - 'native'
+ - 'iosArm64'
+ - 'iosX64'
+ - 'macosX64'
+ - 'mingwX64'
+ - 'linuxX64'
+ MemberNameEqualsClassName:
+ active: true
+ ignoreOverridden: true
+ NoNameShadowing:
+ active: true
+ NonBooleanPropertyPrefixedWithIs:
+ active: true
+ ObjectPropertyNaming:
+ active: true
+ aliases: ['ObjectPropertyName']
+ constantPattern: '[A-Za-z][_A-Za-z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
+ PackageNaming:
+ active: true
+ aliases: ['PackageName']
+ packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
+ TopLevelPropertyNaming:
+ active: true
+ constantPattern: '[A-Z][_A-Z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
+ VariableMaxLength:
+ active: false
+ maximumVariableNameLength: 64
+ VariableMinLength:
+ active: false
+ minimumVariableNameLength: 1
+ VariableNaming:
+ active: false
+ aliases: ['PropertyName']
+ variablePattern: '[a-z][A-Za-z0-9]*'
+ privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+
+performance:
+ active: true
+ ArrayPrimitive:
+ active: true
+ CouldBeSequence:
+ active: false
+ allowedOperations: 3
+ ForEachOnRange:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ SpreadOperator:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ UnnecessaryInitOnArray:
+ active: false
+ UnnecessaryPartOfBinaryExpression:
+ active: true
+ UnnecessaryTemporaryInstantiation:
+ active: true
+ UnnecessaryTypeCasting:
+ active: true
+
+potential-bugs:
+ active: true
+ AvoidReferentialEquality:
+ active: true
+ forbiddenTypePatterns:
+ - 'kotlin.String'
+ CastNullableToNonNullableType:
+ active: true
+ ignorePlatformTypes: true
+ CastToNullableType:
+ active: false
+ CharArrayToStringCall:
+ active: false
+ Deprecation:
+ active: false
+ aliases: ['DEPRECATION']
+ DontDowncastCollectionTypes:
+ active: false
+ DoubleMutabilityForCollection:
+ active: true
+ aliases: ['DoubleMutability']
+ mutableTypes:
+ - 'kotlin.collections.MutableList'
+ - 'kotlin.collections.MutableMap'
+ - 'kotlin.collections.MutableSet'
+ - 'java.util.ArrayList'
+ - 'java.util.LinkedHashSet'
+ - 'java.util.HashSet'
+ - 'java.util.LinkedHashMap'
+ - 'java.util.HashMap'
+ ElseCaseInsteadOfExhaustiveWhen:
+ active: false
+ ignoredSubjectTypes: []
+ EqualsAlwaysReturnsTrueOrFalse:
+ active: true
+ EqualsWithHashCodeExist:
+ active: true
+ ExitOutsideMain:
+ active: false
+ ExplicitGarbageCollectionCall:
+ active: true
+ HasPlatformType:
+ active: true
+ IgnoredReturnValue:
+ active: true
+ restrictToConfig: true
+ returnValueAnnotations:
+ - 'CheckResult'
+ - '*.CheckResult'
+ - 'CheckReturnValue'
+ - '*.CheckReturnValue'
+ ignoreReturnValueAnnotations:
+ - 'CanIgnoreReturnValue'
+ - '*.CanIgnoreReturnValue'
+ returnValueTypes:
+ - 'kotlin.Function*'
+ - 'kotlin.sequences.Sequence'
+ - 'kotlinx.coroutines.flow.*Flow'
+ - 'java.util.stream.*Stream'
+ ignoreFunctionCall: []
+ ImplicitDefaultLocale:
+ active: true
+ ImplicitUnitReturnType:
+ active: false
+ ignoreAnnotated: ['Test']
+ allowExplicitReturnType: true
+ InvalidRange:
+ active: true
+ IteratorHasNextCallsNextMethod:
+ active: true
+ IteratorNotThrowingNoSuchElementException:
+ active: true
+ LateinitUsage:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ ignoreOnClassesPattern: ''
+ MapGetWithNotNullAssertionOperator:
+ active: true
+ MissingPackageDeclaration:
+ active: true
+ excludes: ['**/*.kts']
+ MissingSuperCall:
+ active: false
+ mustInvokeSuperAnnotations:
+ - 'androidx.annotation.CallSuper'
+ - 'javax.annotation.OverridingMethodsMustInvokeSuper'
+ MissingUseCall:
+ active: false
+ ignoreClass:
+ - 'java.io.ByteArrayInputStream'
+ - 'java.io.ByteArrayOutputStream'
+ NullCheckOnMutableProperty:
+ active: false
+ NullableToStringCall:
+ active: false
+ PropertyUsedBeforeDeclaration:
+ active: false
+ UnconditionalJumpStatementInLoop:
+ active: false
+ UnnamedParameterUse:
+ active: false
+ allowAdjacentDifferentTypeParams: true
+ allowSingleParamUse: true
+ ignoreArgumentsMatchingNames: true
+ ignoreFunctionCall: []
+ UnnecessaryNotNullCheck:
+ active: false
+ UnnecessaryNotNullOperator:
+ active: true
+ UnnecessarySafeCall:
+ active: true
+ UnreachableCatchBlock:
+ active: true
+ UnreachableCode:
+ active: true
+ UnsafeCallOnNullableType:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ UnsafeCast:
+ active: true
+ aliases: ['UNCHECKED_CAST']
+ UnusedUnaryOperator:
+ active: true
+ UselessPostfixExpression:
+ active: true
+ WrongEqualsTypeParameter:
+ active: true
+
+style:
+ active: true
+ AbstractClassCanBeConcreteClass:
+ active: true
+ AbstractClassCanBeInterface:
+ active: true
+ AlsoCouldBeApply:
+ active: false
+ BracesOnIfStatements:
+ active: false
+ singleLine: 'never'
+ multiLine: 'always'
+ BracesOnWhenStatements:
+ active: false
+ singleLine: 'necessary'
+ multiLine: 'consistent'
+ CanBeNonNullable:
+ active: false
+ CascadingCallWrapping:
+ active: false
+ includeElvis: true
+ ClassOrdering:
+ active: false
+ CollapsibleIfStatements:
+ active: false
+ DataClassContainsFunctions:
+ active: false
+ conversionFunctionPrefix:
+ - 'to'
+ allowOperators: false
+ DataClassShouldBeImmutable:
+ active: false
+ DestructuringDeclarationWithTooManyEntries:
+ active: true
+ maxDestructuringEntries: 3
+ DoubleNegativeExpression:
+ active: false
+ DoubleNegativeLambda:
+ active: false
+ negativeFunctions:
+ - reason: 'Use `takeIf` instead.'
+ value: 'takeUnless'
+ - reason: 'Use `all` instead.'
+ value: 'none'
+ negativeFunctionNameParts:
+ - 'not'
+ - 'non'
+ EqualsNullCall:
+ active: true
+ EqualsOnSignatureLine:
+ active: false
+ ExplicitCollectionElementAccessMethod:
+ active: false
+ ExplicitItLambdaMultipleParameters:
+ active: true
+ ExplicitItLambdaParameter:
+ active: false
+ ExpressionBodySyntax:
+ active: false
+ includeLineWrapping: false
+ ForbiddenAnnotation:
+ active: true
+ annotations:
+ - reason: 'it is a java annotation. Use `Suppress` instead.'
+ value: 'java.lang.SuppressWarnings'
+ - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.'
+ value: 'java.lang.Deprecated'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.'
+ value: 'java.lang.annotation.Documented'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.'
+ value: 'java.lang.annotation.Target'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.'
+ value: 'java.lang.annotation.Retention'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.'
+ value: 'java.lang.annotation.Repeatable'
+ ForbiddenComment:
+ active: true
+ comments:
+ - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
+ value: 'FIXME:'
+ - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
+ value: 'STOPSHIP:'
+ - reason: 'Forbidden TODO todo marker in comment, please do the changes.'
+ value: 'TODO:'
+ allowedPatterns: ''
+ ForbiddenImport:
+ active: false
+ forbiddenImports: []
+ allowedImports: []
+ ForbiddenMethodCall:
+ active: true
+ methods:
+ - reason: 'print does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.print'
+ - reason: 'println does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.println'
+ - reason: 'using `BigDecimal(Double)` can result in unexpected floating point precision behavior. Use `BigDecimal.valueOf(Double)` or `String.toBigDecimalOrNull()` instead.'
+ value: 'java.math.BigDecimal.(kotlin.Double)'
+ - reason: 'using `BigDecimal(String)` can result in a `NumberFormatException`. Use `String.toBigDecimalOrNull()`'
+ value: 'java.math.BigDecimal.(kotlin.String)'
+ - reason: 'It is marked as obsolete. Use `kotlin.time.measureTime` instead.'
+ value: 'kotlin.system.measureTimeMillis'
+ ForbiddenNamedParam:
+ active: false
+ methods: []
+ ForbiddenOptIn:
+ active: false
+ markerClasses: []
+ ForbiddenSuppress:
+ active: false
+ rules: []
+ ForbiddenVoid:
+ active: true
+ ignoreOverridden: false
+ ignoreUsageInGenerics: false
+ FunctionOnlyReturningConstant:
+ active: true
+ ignoreOverridableFunction: true
+ ignoreActualFunction: true
+ excludedFunctions: []
+ LoopWithTooManyJumpStatements:
+ active: true
+ maxJumpCount: 1
+ MagicNumber:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts']
+ ignoreNumbers:
+ - '-1'
+ - '0'
+ - '1'
+ - '2'
+ ignoreHashCodeFunction: true
+ ignorePropertyDeclaration: true
+ ignoreLocalVariableDeclaration: true
+ ignoreConstantDeclaration: true
+ ignoreCompanionObjectPropertyDeclaration: true
+ ignoreAnnotation: false
+ ignoreNamedArgument: true
+ ignoreEnums: false
+ ignoreRanges: false
+ ignoreExtensionFunctions: true
+ MandatoryBracesLoops:
+ active: false
+ MaxChainedCallsOnSameLine:
+ active: false
+ maxChainedCalls: 5
+ MaxLineLength:
+ active: false
+ maxLineLength: 120
+ excludePackageStatements: true
+ excludeImportStatements: true
+ excludeCommentStatements: false
+ excludeRawStrings: true
+ MayBeConstant:
+ active: true
+ ModifierOrder:
+ active: true
+ MultilineLambdaItParameter:
+ active: false
+ MultilineRawStringIndentation:
+ active: false
+ indentSize: 4
+ trimmingMethods:
+ - 'trimIndent'
+ - 'trimMargin'
+ NestedClassesVisibility:
+ active: true
+ NewLineAtEndOfFile:
+ active: true
+ NoTabs:
+ active: false
+ NullableBooleanCheck:
+ active: false
+ ObjectLiteralToLambda:
+ active: true
+ OptionalAbstractKeyword:
+ active: true
+ OptionalUnit:
+ active: false
+ ProtectedMemberInFinalClass:
+ active: true
+ RangeUntilInsteadOfRangeTo:
+ active: true
+ RedundantConstructorKeyword:
+ active: false
+ RedundantExplicitType:
+ active: false
+ RedundantHigherOrderMapUsage:
+ active: true
+ RedundantVisibilityModifier:
+ active: false
+ ReturnCount:
+ active: false
+ max: 2
+ excludedFunctions:
+ - 'equals'
+ excludeLabeled: false
+ excludeReturnFromLambda: true
+ excludeGuardClauses: false
+ SafeCast:
+ active: true
+ SerialVersionUIDInSerializableClass:
+ active: true
+ SpacingAfterPackageAndImports:
+ active: false
+ StringShouldBeRawString:
+ active: false
+ maxEscapedCharacterCount: 2
+ ignoredCharacters: []
+ ThrowsCount:
+ active: false
+ max: 2
+ excludeGuardClauses: false
+ TrailingWhitespace:
+ active: false
+ TrimMultilineRawString:
+ active: false
+ trimmingMethods:
+ - 'trimIndent'
+ - 'trimMargin'
+ UnderscoresInNumericLiterals:
+ active: false
+ acceptableLength: 4
+ allowNonStandardGrouping: false
+ UnnecessaryAny:
+ active: false
+ UnnecessaryApply:
+ active: true
+ UnnecessaryBackticks:
+ active: false
+ UnnecessaryBracesAroundTrailingLambda:
+ active: false
+ UnnecessaryFilter:
+ active: true
+ UnnecessaryFullyQualifiedName:
+ active: true
+ UnnecessaryInheritance:
+ active: true
+ UnnecessaryInnerClass:
+ active: false
+ UnnecessaryLet:
+ active: false
+ UnnecessaryParentheses:
+ active: false
+ allowForUnclearPrecedence: false
+ UnnecessaryReversed:
+ active: false
+ UnusedImport:
+ active: false
+ additionalOperatorSet: []
+ UnusedParameter:
+ active: true
+ aliases: ['UNUSED_PARAMETER', 'unused']
+ allowedNames: 'ignored|expected'
+ UnusedPrivateClass:
+ active: true
+ aliases: ['unused']
+ UnusedPrivateFunction:
+ active: true
+ aliases: ['unused']
+ allowedNames: ''
+ UnusedPrivateProperty:
+ active: true
+ aliases: ['unused']
+ allowedNames: 'ignored|expected|serialVersionUID'
+ UnusedVariable:
+ active: true
+ aliases: ['UNUSED_VARIABLE', 'unused']
+ allowedNames: 'ignored|_'
+ UseAnyOrNoneInsteadOfFind:
+ active: true
+ UseArrayLiteralsInAnnotations:
+ active: true
+ UseCheckNotNull:
+ active: true
+ UseCheckOrError:
+ active: true
+ UseDataClass:
+ active: false
+ allowVars: false
+ UseEmptyCounterpart:
+ active: false
+ UseIfEmptyOrIfBlank:
+ active: false
+ UseIfInsteadOfWhen:
+ active: false
+ ignoreWhenContainingVariableDeclaration: false
+ UseIsNullOrEmpty:
+ active: true
+ UseLet:
+ active: false
+ UseOrEmpty:
+ active: true
+ UseRequire:
+ active: true
+ UseRequireNotNull:
+ active: true
+ UseSumOfInsteadOfFlatMapSize:
+ active: false
+ UselessCallOnNotNull:
+ active: true
+ UtilityClassWithPublicConstructor:
+ active: true
+ VarCouldBeVal:
+ active: true
+ aliases: ['CanBeVal']
+ ignoreLateinitVar: false
+ WildcardImport:
+ active: false
+ excludeImports:
+ - 'java.util.*'
diff --git a/conformance-test/conformance-baseline.yml b/conformance-test/conformance-baseline.yml
index 337d8ce..f349b8c 100644
--- a/conformance-test/conformance-baseline.yml
+++ b/conformance-test/conformance-baseline.yml
@@ -2,5 +2,4 @@
# Add entries here as tests are identified as known SDK limitations
server: []
-client:
- - elicitation-sep1034-client-defaults
+client: []
diff --git a/conformance-test/run-conformance.sh b/conformance-test/run-conformance.sh
index 8f15ac0..279254e 100755
--- a/conformance-test/run-conformance.sh
+++ b/conformance-test/run-conformance.sh
@@ -9,7 +9,7 @@ set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1; pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." || exit 1; pwd)"
-CONFORMANCE_VERSION="0.1.15"
+CONFORMANCE_VERSION="0.1.16"
PORT="${MCP_PORT:-3001}"
SERVER_URL="http://localhost:${PORT}/mcp"
RESULTS_DIR="$SCRIPT_DIR/results"
diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt
index b420775..8a18957 100644
--- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt
+++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt
@@ -1,26 +1,17 @@
package io.modelcontextprotocol.kotlin.sdk.conformance
-import io.ktor.serialization.kotlinx.json.json
-import io.ktor.server.application.install
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
-import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
-import io.modelcontextprotocol.kotlin.sdk.types.McpJson
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
fun main() {
val port = System.getenv("MCP_PORT")?.toIntOrNull() ?: 3001
embeddedServer(CIO, port = port) {
- install(ContentNegotiation) {
- json(McpJson)
- }
mcpStreamableHttp(
- enableDnsRebindingProtection = true,
- allowedHosts = listOf("localhost", "127.0.0.1", "localhost:$port", "127.0.0.1:$port"),
eventStore = InMemoryEventStore(),
) {
createConformanceServer()
diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt
index 579b222..c81c98c 100644
--- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt
+++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt
@@ -3,12 +3,17 @@ package io.modelcontextprotocol.kotlin.sdk.conformance
import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.types.AudioContent
+import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource
+import io.modelcontextprotocol.kotlin.sdk.types.EnumOption
import io.modelcontextprotocol.kotlin.sdk.types.ImageContent
+import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema
+import io.modelcontextprotocol.kotlin.sdk.types.LegacyTitledEnumSchema
import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel
import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification
import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams
@@ -16,11 +21,15 @@ import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification
import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams
import io.modelcontextprotocol.kotlin.sdk.types.Role
import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage
+import io.modelcontextprotocol.kotlin.sdk.types.StringSchema
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents
+import io.modelcontextprotocol.kotlin.sdk.types.TitledMultiSelectEnumSchema
+import io.modelcontextprotocol.kotlin.sdk.types.TitledSingleSelectEnumSchema
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
+import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema
+import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema
import kotlinx.coroutines.delay
-import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.double
@@ -36,7 +45,6 @@ internal const val WAV_BASE64 = "UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABA
private val logger = KotlinLogging.logger {}
-@Suppress("LongMethod")
fun Server.registerConformanceTools() {
// 1. Simple text
addTool(
@@ -167,11 +175,17 @@ fun Server.registerConformanceTools() {
CreateMessageRequest(
CreateMessageRequestParams(
maxTokens = 10000,
- messages = listOf(SamplingMessage(Role.User, TextContent(prompt))),
+ messages = listOf(SamplingMessage(Role.User, listOf(TextContent(prompt)))),
),
),
)
- CallToolResult(listOf(TextContent(result.content.toString())))
+ val sampledText = result.content.joinToString("\n") { content ->
+ when (content) {
+ is TextContent -> content.text
+ else -> "Non-text sampling content: ${content::class.simpleName}"
+ }
+ }
+ CallToolResult(listOf(TextContent(sampledText)))
}
// 9. Elicitation
@@ -187,22 +201,10 @@ fun Server.registerConformanceTools() {
) { request ->
val message = request.arguments?.get("message")?.jsonPrimitive?.content ?: "Please provide input"
val schema = ElicitRequestParams.RequestedSchema(
- properties = buildJsonObject {
- put(
- "username",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put("description", JsonPrimitive("User's response"))
- },
- )
- put(
- "email",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put("description", JsonPrimitive("User's email address"))
- },
- )
- },
+ properties = mapOf(
+ "username" to StringSchema(description = "User's response"),
+ "email" to StringSchema(description = "User's email address"),
+ ),
required = listOf("username", "email"),
)
val result = createElicitation(message, schema)
@@ -215,54 +217,17 @@ fun Server.registerConformanceTools() {
description = "test_elicitation_sep1034_defaults",
) {
val schema = ElicitRequestParams.RequestedSchema(
- properties = buildJsonObject {
- put(
- "name",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put("description", JsonPrimitive("User name"))
- put("default", JsonPrimitive("John Doe"))
- },
- )
- put(
- "age",
- buildJsonObject {
- put("type", JsonPrimitive("integer"))
- put("description", JsonPrimitive("User age"))
- put("default", JsonPrimitive(30))
- },
- )
- put(
- "score",
- buildJsonObject {
- put("type", JsonPrimitive("number"))
- put("description", JsonPrimitive("User score"))
- put("default", JsonPrimitive(95.5))
- },
- )
- put(
- "status",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put("description", JsonPrimitive("User status"))
- put("default", JsonPrimitive("active"))
- put(
- "enum",
- JsonArray(
- listOf(JsonPrimitive("active"), JsonPrimitive("inactive"), JsonPrimitive("pending")),
- ),
- )
- },
- )
- put(
- "verified",
- buildJsonObject {
- put("type", JsonPrimitive("boolean"))
- put("description", JsonPrimitive("Verification status"))
- put("default", JsonPrimitive(true))
- },
- )
- },
+ properties = mapOf(
+ "name" to StringSchema(description = "User name", default = "John Doe"),
+ "age" to IntegerSchema(description = "User age", default = 30),
+ "score" to DoubleSchema(description = "User score", default = 95.5),
+ "status" to UntitledSingleSelectEnumSchema(
+ description = "User status",
+ enumValues = listOf("active", "inactive", "pending"),
+ default = "active",
+ ),
+ "verified" to BooleanSchema(description = "Verification status", default = true),
+ ),
required = listOf("name", "age", "score", "status", "verified"),
)
val result = createElicitation(
@@ -278,122 +243,43 @@ fun Server.registerConformanceTools() {
description = "test_elicitation_sep1330_enums",
) {
val schema = ElicitRequestParams.RequestedSchema(
- properties = buildJsonObject {
+ properties = mapOf(
// Untitled single-select
- put(
- "untitledSingle",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put(
- "enum",
- JsonArray(
- listOf(JsonPrimitive("option1"), JsonPrimitive("option2"), JsonPrimitive("option3")),
- ),
- )
- },
- )
+ "untitledSingle" to UntitledSingleSelectEnumSchema(
+ enumValues = listOf("option1", "option2", "option3"),
+ ),
// Titled single-select
- put(
- "titledSingle",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put(
- "oneOf",
- JsonArray(
- listOf(
- buildJsonObject {
- put("const", JsonPrimitive("value1"))
- put("title", JsonPrimitive("First Option"))
- },
- buildJsonObject {
- put("const", JsonPrimitive("value2"))
- put("title", JsonPrimitive("Second Option"))
- },
- buildJsonObject {
- put("const", JsonPrimitive("value3"))
- put("title", JsonPrimitive("Third Option"))
- },
- ),
- ),
- )
- },
- )
- // Legacy titled (deprecated) - uses enum + enumNames arrays
- put(
- "legacyEnum",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put(
- "enum",
- JsonArray(listOf(JsonPrimitive("opt1"), JsonPrimitive("opt2"), JsonPrimitive("opt3"))),
- )
- put(
- "enumNames",
- JsonArray(
- listOf(
- JsonPrimitive("Option One"),
- JsonPrimitive("Option Two"),
- JsonPrimitive("Option Three"),
- ),
- ),
- )
- },
- )
+ "titledSingle" to TitledSingleSelectEnumSchema(
+ oneOf = listOf(
+ EnumOption(const = "value1", title = "First Option"),
+ EnumOption(const = "value2", title = "Second Option"),
+ EnumOption(const = "value3", title = "Third Option"),
+ ),
+ ),
+ // Legacy titled (deprecated) — uses enum + enumNames
+ @Suppress("DEPRECATION")
+ "legacyEnum"
+ to LegacyTitledEnumSchema(
+ enumValues = listOf("opt1", "opt2", "opt3"),
+ enumNames = listOf("Option One", "Option Two", "Option Three"),
+ ),
// Untitled multi-select
- put(
- "untitledMulti",
- buildJsonObject {
- put("type", JsonPrimitive("array"))
- put(
- "items",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put(
- "enum",
- JsonArray(
- listOf(
- JsonPrimitive("option1"),
- JsonPrimitive("option2"),
- JsonPrimitive("option3"),
- ),
- ),
- )
- },
- )
- },
- )
- // Titled multi-select - uses items.anyOf with const/title pairs
- put(
- "titledMulti",
- buildJsonObject {
- put("type", JsonPrimitive("array"))
- put(
- "items",
- buildJsonObject {
- put(
- "anyOf",
- JsonArray(
- listOf(
- buildJsonObject {
- put("const", JsonPrimitive("value1"))
- put("title", JsonPrimitive("First Choice"))
- },
- buildJsonObject {
- put("const", JsonPrimitive("value2"))
- put("title", JsonPrimitive("Second Choice"))
- },
- buildJsonObject {
- put("const", JsonPrimitive("value3"))
- put("title", JsonPrimitive("Third Choice"))
- },
- ),
- ),
- )
- },
- )
- },
- )
- },
+ "untitledMulti" to UntitledMultiSelectEnumSchema(
+ items = UntitledMultiSelectEnumSchema.Items(
+ enumValues = listOf("option1", "option2", "option3"),
+ ),
+ ),
+ // Titled multi-select
+ "titledMulti" to TitledMultiSelectEnumSchema(
+ items = TitledMultiSelectEnumSchema.Items(
+ anyOf = listOf(
+ EnumOption(const = "value1", title = "First Choice"),
+ EnumOption(const = "value2", title = "Second Choice"),
+ EnumOption(const = "value3", title = "Third Choice"),
+ ),
+ ),
+ ),
+ ),
)
val result = createElicitation(
"Please review and update the form fields with defaults",
@@ -538,54 +424,17 @@ fun Server.registerConformanceTools() {
description = "test_client_elicitation_defaults",
) {
val schema = ElicitRequestParams.RequestedSchema(
- properties = buildJsonObject {
- put(
- "name",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put("description", JsonPrimitive("User name"))
- put("default", JsonPrimitive("John Doe"))
- },
- )
- put(
- "age",
- buildJsonObject {
- put("type", JsonPrimitive("integer"))
- put("description", JsonPrimitive("User age"))
- put("default", JsonPrimitive(30))
- },
- )
- put(
- "score",
- buildJsonObject {
- put("type", JsonPrimitive("number"))
- put("description", JsonPrimitive("User score"))
- put("default", JsonPrimitive(95.5))
- },
- )
- put(
- "status",
- buildJsonObject {
- put("type", JsonPrimitive("string"))
- put("description", JsonPrimitive("User status"))
- put("default", JsonPrimitive("active"))
- put(
- "enum",
- JsonArray(
- listOf(JsonPrimitive("active"), JsonPrimitive("inactive"), JsonPrimitive("pending")),
- ),
- )
- },
- )
- put(
- "verified",
- buildJsonObject {
- put("type", JsonPrimitive("boolean"))
- put("description", JsonPrimitive("Verification status"))
- put("default", JsonPrimitive(true))
- },
- )
- },
+ properties = mapOf(
+ "name" to StringSchema(description = "User name", default = "John Doe"),
+ "age" to IntegerSchema(description = "User age", default = 30),
+ "score" to DoubleSchema(description = "User score", default = 95.5),
+ "status" to UntitledSingleSelectEnumSchema(
+ description = "User status",
+ enumValues = listOf("active", "inactive", "pending"),
+ default = "active",
+ ),
+ "verified" to BooleanSchema(description = "Verification status", default = true),
+ ),
required = emptyList(),
)
val result = createElicitation(
diff --git a/context7.json b/context7.json
index a28c0fc..5fde250 100644
--- a/context7.json
+++ b/context7.json
@@ -12,7 +12,6 @@
"CONTRIBUTING.md",
"LICENSE",
"SECURITY.md",
- "codecov.yml",
"gradlew",
"gradlew.bat",
".editorconfig",
diff --git a/gradle.properties b/gradle.properties
index 43b4acb..a5655c3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,4 +8,4 @@ kotlin.daemon.jvmargs=-Xmx4G
kotlin.native.ignoreDisabledTargets=true
group=io.modelcontextprotocol
-version=0.9.0
+version=0.15.0
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 313c8f6..a8d034f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,33 +1,33 @@
[versions]
# plugins version
-atomicfu = "0.31.0"
-binaryCompatibilityValidatorPlugin = "0.18.1"
-detekt = "2.0.0-alpha.2"
-dokka = "2.1.0"
-kotlin = "2.2.21"
-kover = "0.9.7"
-ktlint = "14.0.1"
+atomicfu = "0.33.0"
+binaryCompatibilityValidatorPlugin = "0.18.2"
+detekt = "2.0.0-alpha.6"
+dokka = "2.2.0"
+kotlin = "2.4.10"
+kover = "0.9.9"
+ktlint = "14.2.0"
knit = "0.5.1"
-mavenPublish = "0.36.0"
-netty = "4.2.10.Final"
-openapi-generator = "7.18.0"
+mavenPublish = "0.37.0"
+netty = "4.2.18.Final"
# libraries version
awaitility = "4.3.0"
-collections-immutable = "0.4.0"
-coroutines = "1.10.2"
-kotest = "6.1.3"
-kotlinx-io = "0.8.2"
-ktor = "3.2.3"
-logging = "7.0.14"
-mockk = "1.14.9"
-mokksy = "0.8.0"
-serialization = "1.9.0"
-slf4j = "2.0.17"
-junit = "5.14.2"
+collections-immutable = "0.5.2"
+coroutines = "1.11.0"
+kotest = "6.2.5"
+kotlinx-io = "0.9.1"
+ktor = "3.5.2"
+logging = "8.0.4"
+mockk = "1.14.11"
+serialization = "1.11.0"
+slf4j = "2.0.19"
+logback = "1.6.3"
+junit = "5.14.4"
[libraries]
# Plugins
+detekt-gradle = { module = "dev.detekt:detekt-gradle-plugin", version.ref = "detekt" }
dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
@@ -64,9 +64,9 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor
ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" }
ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
-mokksy = { group = "dev.mokksy", name = "mokksy", version.ref = "mokksy" }
netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" }
slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
+logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
# Samples
@@ -74,9 +74,7 @@ ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "
ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
[plugins]
-detekt = { id = "dev.detekt", version.ref = "detekt" }
kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
knit = { id = "org.jetbrains.kotlinx.knit", version.ref = "knit" }
-openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index d997cfc..eddabd2 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index c61a118..ad7845b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
+retries=0
+retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 739907d..249efbb 100755
--- a/gradlew
+++ b/gradlew
@@ -20,7 +20,7 @@
##############################################################################
#
-# Gradle start up script for POSIX generated by Gradle.
+# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
-# ksh Gradle
+# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
diff --git a/gradlew.bat b/gradlew.bat
index e509b2d..8508ef6 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
-@rem Gradle startup script for Windows
+@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
+@rem Set local scope for the variables, and ensure extensions are enabled
+setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
-goto fail
+"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,29 +65,18 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
-goto fail
+"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+@rem Execute gradlew
+@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
+@rem which allows us to clear the local environment before executing the java command
+endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
-:end
-@rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+:exitWithErrorLevel
+@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
+"%COMSPEC%" /c exit %ERRORLEVEL%
diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts
index 30afe9e..7a3a4a3 100644
--- a/integration-test/build.gradle.kts
+++ b/integration-test/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
id("mcp.multiplatform")
+ id("mcp.detekt")
}
kotlin {
diff --git a/integration-test/detekt-baseline-test.xml b/integration-test/detekt-baseline-test.xml
deleted file mode 100644
index 2df611a..0000000
--- a/integration-test/detekt-baseline-test.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
- AbstractClassCanBeConcreteClass:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$AbstractPromptIntegrationTest
- AbstractClassCanBeConcreteClass:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$AbstractResourceIntegrationTest
- AbstractClassCanBeConcreteClass:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$AbstractToolIntegrationTest
- AbstractClassCanBeConcreteClass:BaseTransportTest.kt:BaseTransportTest$BaseTransportTest
- CyclomaticComplexMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool
- ForbiddenComment:StdioClientTransportTest.kt:StdioClientTransportTest$// TODO: fix running on windows
- MatchingDeclarationName:PromptIntegrationTestSse.kt:SchemaPromptIntegrationTestSse : AbstractPromptIntegrationTest
- ThrowsCount:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer
- UseCheckOrError:ClientTest.kt:ClientTest.<no name provided>$throw IllegalStateException("Test error")
- UseOrEmpty:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$(request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { (it as? JsonPrimitive)?.content } ?: emptyList()
- UseRequire:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$throw IllegalArgumentException("Missing required argument: $argName")
-
-
diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt
index b117dbf..099329d 100644
--- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt
+++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt
@@ -7,16 +7,24 @@ import io.modelcontextprotocol.kotlin.sdk.server.ServerSession
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions
+import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult
+import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema
+import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestURLParams
import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult
+import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotification
+import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotificationParams
import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult
+import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse
import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
@@ -35,20 +43,28 @@ import io.modelcontextprotocol.kotlin.sdk.types.Root
import io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.StringSchema
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.TitledMultiSelectEnumSchema
import io.modelcontextprotocol.kotlin.sdk.types.Tool
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
+import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema
+import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema
+import io.modelcontextprotocol.kotlin.sdk.types.UrlElicitationRequiredException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withTimeout
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
-import kotlinx.serialization.json.putJsonObject
import kotlin.coroutines.cancellation.CancellationException
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -57,6 +73,7 @@ import kotlin.test.assertIs
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.fail
+import kotlin.time.Duration.Companion.seconds
class ClientTest {
@Test
@@ -96,7 +113,7 @@ class ClientTest {
),
options = ClientOptions(
capabilities = ClientCapabilities(
- sampling = EmptyJsonObject,
+ sampling = ClientCapabilities.sampling,
),
),
)
@@ -142,7 +159,7 @@ class ClientTest {
),
options = ClientOptions(
capabilities = ClientCapabilities(
- sampling = EmptyJsonObject,
+ sampling = ClientCapabilities.sampling,
),
),
)
@@ -329,7 +346,7 @@ class ClientTest {
val client = Client(
clientInfo = Implementation(name = "test client", version = "1.0"),
options = ClientOptions(
- capabilities = ClientCapabilities(sampling = EmptyJsonObject),
+ capabilities = ClientCapabilities(sampling = ClientCapabilities.sampling),
),
)
@@ -417,13 +434,14 @@ class ClientTest {
clientInfo = Implementation(name = "test client without capability", version = "1.0"),
options = ClientOptions(
capabilities = ClientCapabilities(),
- // enforceStrictCapabilities = true // TODO()
),
)
- clientWithoutCapability.connect(clientTransport)
- // Using the same transport pair might not be realistic - in a real scenario you'd create another pair.
- // Adjust if necessary.
+ val (clientTransport2, serverTransport2) = InMemoryTransport.createLinkedPair()
+ listOf(
+ launch { clientWithoutCapability.connect(clientTransport2) },
+ launch { server.createSession(serverTransport2) },
+ ).joinAll()
// This should fail
val ex = assertFailsWith {
@@ -528,6 +546,15 @@ class ClientTest {
val serverSession = serverSessionResult.await()
+ // Register the tap after createSession so it chains onto the session's subscription instead
+ // of pre-empting the handshake.
+ val cancelledOnWire = CompletableDeferred()
+ serverTransport.onMessage { message ->
+ if (message is JSONRPCNotification && message.method == Method.Defined.NotificationsCancelled.value) {
+ cancelledOnWire.complete(Unit)
+ }
+ }
+
serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ ->
// Simulate delay
def.complete(Unit)
@@ -553,6 +580,7 @@ class ClientTest {
def.await()
runCatching { job.cancel("Cancelled by test") }
defCancel.await()
+ cancelledOnWire.await()
defTimeOut.await()
}
@@ -591,17 +619,9 @@ class ClientTest {
val serverSession = serverSessionResult.await()
serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ ->
- // Simulate a delayed response
- // Wait ~100ms unless canceled
- try {
- withTimeout(100L) {
- // Just delay here, if timeout is 0 on the client side, this won't return in time
- delay(100)
- }
- } catch (_: Exception) {
- // If aborted, just rethrow or return early
- }
- ListResourcesResult(resources = emptyList())
+ // Never respond: the handler runs on a real dispatcher while the 1 ms timeout runs on the
+ // virtual test clock, so a real-time delay could race it. Parking keeps the test deterministic.
+ awaitCancellation()
}
// Request with 1 msec timeout should fail immediately
@@ -622,18 +642,16 @@ class ClientTest {
),
options = ClientOptions(
capabilities = ClientCapabilities(
- sampling = EmptyJsonObject,
+ sampling = ClientCapabilities.sampling,
),
),
)
client.setRequestHandler(Method.Defined.SamplingCreateMessage) { _, _ ->
CreateMessageResult(
- model = "test-model",
role = Role.Assistant,
- content = TextContent(
- text = "Test response",
- ),
+ content = TextContent(text = "Test response"),
+ model = "test-model",
)
}
@@ -659,7 +677,7 @@ class ClientTest {
val client = Client(
clientInfo = Implementation(name = "test client", version = "1.0"),
options = ClientOptions(
- capabilities = ClientCapabilities(sampling = EmptyJsonObject),
+ capabilities = ClientCapabilities(sampling = ClientCapabilities.sampling),
),
)
@@ -882,8 +900,8 @@ class ClientTest {
),
)
- // Track notifications
- var rootListChangedNotificationReceived = false
+ // The handler runs on Dispatchers.Default after concurrent dispatch is enabled; await the deferred.
+ val rootListChangedReceived = CompletableDeferred()
val serverSessionResult = CompletableDeferred()
@@ -902,16 +920,13 @@ class ClientTest {
serverSession.setNotificationHandler(
Method.Defined.NotificationsRootsListChanged,
) {
- rootListChangedNotificationReceived = true
+ rootListChangedReceived.complete(Unit)
CompletableDeferred(Unit)
}
client.sendRootsListChanged()
- assertTrue(
- rootListChangedNotificationReceived,
- "Notification should be sent when sendRootsListChanged is called",
- )
+ withTimeout(5.seconds) { rootListChangedReceived.await() }
}
@Test
@@ -952,11 +967,7 @@ class ClientTest {
serverSession.createElicitation(
message = "Please provide your GitHub username",
requestedSchema = ElicitRequestParams.RequestedSchema(
- properties = buildJsonObject {
- putJsonObject("name") {
- put("type", "string")
- }
- },
+ properties = mapOf("name" to StringSchema()),
required = listOf("name"),
),
)
@@ -1053,18 +1064,14 @@ class ClientTest {
Implementation(name = "test client", version = "1.0"),
ClientOptions(
capabilities = ClientCapabilities(
- elicitation = EmptyJsonObject,
+ elicitation = ClientCapabilities.Elicitation(),
),
),
)
val elicitationMessage = "Please provide your GitHub username"
val requestedSchema = ElicitRequestParams.RequestedSchema(
- properties = buildJsonObject {
- putJsonObject("name") {
- put("type", "string")
- }
- },
+ properties = mapOf("name" to StringSchema()),
required = listOf("name"),
)
@@ -1075,7 +1082,8 @@ class ClientTest {
client.setElicitationHandler { request ->
assertEquals(elicitationMessage, request.params.message)
- assertEquals(requestedSchema, request.params.requestedSchema)
+ val formParams = request.params as ElicitRequestFormParams
+ assertEquals(requestedSchema, formParams.requestedSchema)
ElicitResult(
action = elicitationResultAction,
@@ -1115,4 +1123,455 @@ class ClientTest {
assertEquals(elicitationResultAction, result.action)
assertEquals(elicitationResultContent, result.content)
}
+
+ @Test
+ fun `should reject accepted elicitation content that does not match requested schema`() = runTest {
+ val requestedSchema = ElicitRequestParams.RequestedSchema(
+ properties = mapOf("name" to StringSchema()),
+ required = listOf("name"),
+ )
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept, content = buildJsonObject { put("name", 42) })
+ }
+
+ val exception = assertFailsWith {
+ serverSession.createElicitation(message = "mismatch", requestedSchema = requestedSchema)
+ }
+ assertTrue(
+ exception.message.orEmpty()
+ .startsWith("Elicitation response content does not match requested schema: "),
+ "Unexpected message: ${exception.message}",
+ )
+ assertTrue("'name' must be string" in exception.message.orEmpty(), "Unexpected message: ${exception.message}")
+
+ client.close()
+ }
+
+ @Test
+ fun `should reject accepted elicitation without content when schema has required properties`() = runTest {
+ val requestedSchema = ElicitRequestParams.RequestedSchema(
+ properties = mapOf("name" to StringSchema()),
+ required = listOf("name"),
+ )
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept, content = null)
+ }
+
+ val exception = assertFailsWith {
+ serverSession.createElicitation(message = "no content", requestedSchema = requestedSchema)
+ }
+ assertTrue(
+ "must have required property 'name'" in exception.message.orEmpty(),
+ "Unexpected message: ${exception.message}",
+ )
+
+ client.close()
+ }
+
+ @Test
+ fun `should accept elicitation without content when schema has no required properties`() = runTest {
+ val requestedSchema = ElicitRequestParams.RequestedSchema(
+ properties = mapOf("name" to StringSchema()),
+ )
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept, content = null)
+ }
+
+ val result = serverSession.createElicitation(message = "no required", requestedSchema = requestedSchema)
+
+ assertEquals(ElicitResult.Action.Accept, result.action)
+ assertNull(result.content)
+
+ client.close()
+ }
+
+ @Test
+ fun `should pass through declined elicitation without validation`() = runTest {
+ val requestedSchema = ElicitRequestParams.RequestedSchema(
+ properties = mapOf("name" to StringSchema()),
+ required = listOf("name"),
+ )
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Decline)
+ }
+
+ val result = serverSession.createElicitation(message = "decline", requestedSchema = requestedSchema)
+
+ assertEquals(ElicitResult.Action.Decline, result.action)
+ assertNull(result.content)
+
+ client.close()
+ }
+
+ @Test
+ fun `should pass through cancelled elicitation without validation`() = runTest {
+ val requestedSchema = ElicitRequestParams.RequestedSchema(
+ properties = mapOf("name" to StringSchema()),
+ required = listOf("name"),
+ )
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Cancel)
+ }
+
+ val result = serverSession.createElicitation(message = "cancel", requestedSchema = requestedSchema)
+
+ assertEquals(ElicitResult.Action.Cancel, result.action)
+ assertNull(result.content)
+
+ client.close()
+ }
+
+ @Test
+ fun `should apply elicitation defaults for missing fields in empty content`() = runTest {
+ val schema = defaultsTestSchema()
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept, content = JsonObject(emptyMap()))
+ }
+
+ val result = serverSession.createElicitation(message = "fill defaults", requestedSchema = schema)
+
+ assertEquals(ElicitResult.Action.Accept, result.action)
+ val content = result.content!!
+ assertEquals(JsonPrimitive("John Doe"), content["name"])
+ assertEquals(JsonPrimitive(30), content["age"])
+ assertEquals(JsonPrimitive(95.5), content["score"])
+ assertEquals(JsonPrimitive("active"), content["status"])
+ assertEquals(JsonPrimitive(true), content["verified"])
+
+ client.close()
+ }
+
+ @Test
+ fun `should apply elicitation defaults only for missing fields preserving user values`() = runTest {
+ val schema = defaultsTestSchema()
+ val userContent = buildJsonObject { put("name", "Custom Name") }
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept, content = userContent)
+ }
+
+ val result = serverSession.createElicitation(message = "partial", requestedSchema = schema)
+
+ val content = result.content!!
+ assertEquals(JsonPrimitive("Custom Name"), content["name"])
+ assertEquals(JsonPrimitive(30), content["age"])
+ assertEquals(JsonPrimitive(95.5), content["score"])
+ assertEquals(JsonPrimitive("active"), content["status"])
+ assertEquals(JsonPrimitive(true), content["verified"])
+
+ client.close()
+ }
+
+ @Test
+ fun `should not apply elicitation defaults when action is decline`() = runTest {
+ val schema = defaultsTestSchema()
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Decline)
+ }
+
+ val result = serverSession.createElicitation(message = "decline", requestedSchema = schema)
+
+ assertEquals(ElicitResult.Action.Decline, result.action)
+ assertNull(result.content)
+
+ client.close()
+ }
+
+ @Test
+ fun `should not apply elicitation defaults when action is cancel`() = runTest {
+ val schema = defaultsTestSchema()
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Cancel)
+ }
+
+ val result = serverSession.createElicitation(message = "cancel", requestedSchema = schema)
+
+ assertEquals(ElicitResult.Action.Cancel, result.action)
+ assertNull(result.content)
+
+ client.close()
+ }
+
+ @Test
+ fun `should not modify content when schema has no defaults`() = runTest {
+ val schema = ElicitRequestParams.RequestedSchema(
+ properties = mapOf(
+ "name" to StringSchema(description = "name"),
+ "age" to IntegerSchema(description = "age"),
+ ),
+ )
+ val emptyContent = JsonObject(emptyMap())
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept, content = emptyContent)
+ }
+
+ val result = serverSession.createElicitation(message = "no defaults", requestedSchema = schema)
+
+ assertEquals(ElicitResult.Action.Accept, result.action)
+ assertTrue(result.content!!.isEmpty())
+
+ client.close()
+ }
+
+ @Test
+ fun `should apply elicitation defaults for multi-select enum schemas`() = runTest {
+ val schema = ElicitRequestParams.RequestedSchema(
+ properties = mapOf(
+ "tags" to UntitledMultiSelectEnumSchema(
+ description = "tags",
+ items = UntitledMultiSelectEnumSchema.Items(enumValues = listOf("a", "b", "c")),
+ default = listOf("a", "b"),
+ ),
+ "options" to TitledMultiSelectEnumSchema(
+ description = "options",
+ items = TitledMultiSelectEnumSchema.Items(
+ anyOf = listOf(
+ io.modelcontextprotocol.kotlin.sdk.types.EnumOption("x", "X"),
+ io.modelcontextprotocol.kotlin.sdk.types.EnumOption("y", "Y"),
+ ),
+ ),
+ default = listOf("x"),
+ ),
+ ),
+ )
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept, content = JsonObject(emptyMap()))
+ }
+
+ val result = serverSession.createElicitation(message = "multi-select", requestedSchema = schema)
+
+ val content = result.content!!
+ val tags = content["tags"]!!
+ assertIs(tags)
+ assertEquals(2, tags.size)
+ assertEquals("a", tags[0].jsonPrimitive.content)
+ assertEquals("b", tags[1].jsonPrimitive.content)
+
+ val options = content["options"]!!
+ assertIs(options)
+ assertEquals(1, options.size)
+ assertEquals("x", options[0].jsonPrimitive.content)
+
+ client.close()
+ }
+
+ // ── URL-mode elicitation (SEP-1036) ─────────────────────────────────
+
+ @Test
+ fun `should handle URL mode elicitation end-to-end`() = runTest {
+ val client = Client(
+ Implementation(name = "test client", version = "1.0"),
+ ClientOptions(
+ capabilities = ClientCapabilities(
+ elicitation = ClientCapabilities.Elicitation(url = EmptyJsonObject),
+ ),
+ ),
+ )
+
+ val elicitationId = "550e8400-e29b-41d4-a716-446655440000"
+ val url = "https://oauth.example.com/authorize"
+
+ client.setElicitationHandler { request ->
+ val params = assertIs(request.params)
+ assertEquals(elicitationId, params.elicitationId)
+ assertEquals(url, params.url)
+ ElicitResult(action = ElicitResult.Action.Accept)
+ }
+
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+ val server = Server(
+ serverInfo = Implementation(name = "test server", version = "1.0"),
+ options = ServerOptions(capabilities = ServerCapabilities()),
+ )
+
+ val serverSessionResult = CompletableDeferred()
+ listOf(
+ launch { client.connect(clientTransport) },
+ launch { serverSessionResult.complete(server.createSession(serverTransport)) },
+ ).joinAll()
+ val serverSession = serverSessionResult.await()
+
+ val result = serverSession.createElicitation(
+ message = "Authorize access to continue",
+ elicitationId = elicitationId,
+ url = url,
+ )
+
+ assertEquals(ElicitResult.Action.Accept, result.action)
+ assertNull(result.content)
+
+ client.close()
+ }
+
+ @Test
+ fun `should reject URL mode elicitation when client supports only form mode`() = runTest {
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept)
+ }
+
+ val exception = assertFailsWith {
+ serverSession.createElicitation(
+ message = "Authorize",
+ elicitationId = "id-1",
+ url = "https://example.com/auth",
+ )
+ }
+ assertTrue(exception.message!!.contains("elicitation.url"))
+
+ client.close()
+ }
+
+ @Test
+ fun `should deliver elicitation complete notification to client`() = runTest {
+ val received = CompletableDeferred()
+ val client = Client(
+ Implementation(name = "test client", version = "1.0"),
+ ClientOptions(
+ capabilities = ClientCapabilities(
+ elicitation = ClientCapabilities.Elicitation(url = EmptyJsonObject),
+ ),
+ ),
+ )
+ client.setElicitationCompleteHandler { received.complete(it) }
+
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+ val server = Server(
+ serverInfo = Implementation(name = "test server", version = "1.0"),
+ options = ServerOptions(capabilities = ServerCapabilities()),
+ )
+
+ val serverSessionResult = CompletableDeferred()
+ listOf(
+ launch { client.connect(clientTransport) },
+ launch { serverSessionResult.complete(server.createSession(serverTransport)) },
+ ).joinAll()
+ val serverSession = serverSessionResult.await()
+
+ val elicitationId = "complete-id-1"
+ serverSession.sendElicitationComplete(
+ ElicitationCompleteNotification(ElicitationCompleteNotificationParams(elicitationId = elicitationId)),
+ )
+
+ val notification = received.await()
+ assertEquals(elicitationId, notification.params.elicitationId)
+
+ client.close()
+ }
+
+ @Test
+ fun `should reject elicitation complete when client supports only form mode`() = runTest {
+ val (client, serverSession) = setupElicitationPair {
+ ElicitResult(action = ElicitResult.Action.Accept)
+ }
+
+ val exception = assertFailsWith {
+ serverSession.sendElicitationComplete(
+ ElicitationCompleteNotification(ElicitationCompleteNotificationParams(elicitationId = "id-1")),
+ )
+ }
+ assertTrue(exception.message!!.contains("elicitation.url"))
+
+ client.close()
+ }
+
+ @Test
+ fun `setElicitationCompleteHandler should require url capability`() = runTest {
+ val client = Client(
+ Implementation(name = "test client", version = "1.0"),
+ ClientOptions(
+ capabilities = ClientCapabilities(
+ elicitation = ClientCapabilities.Elicitation(),
+ ),
+ ),
+ )
+
+ assertFailsWith {
+ client.setElicitationCompleteHandler { }
+ }
+ }
+
+ @Test
+ fun `should surface URL elicitation required error to client as typed exception`() = runTest {
+ val client = Client(
+ Implementation(name = "test client", version = "1.0"),
+ ClientOptions(
+ capabilities = ClientCapabilities(
+ elicitation = ClientCapabilities.Elicitation(url = EmptyJsonObject),
+ ),
+ ),
+ )
+
+ val elicitationId = "auth-required-1"
+ val url = "https://oauth.example.com/authorize"
+
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+ val server = Server(
+ serverInfo = Implementation(name = "test server", version = "1.0"),
+ options = ServerOptions(capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(true))),
+ )
+ server.addTool("needs-auth", "Requires URL elicitation") {
+ throw UrlElicitationRequiredException(
+ listOf(
+ ElicitRequestURLParams(
+ message = "Authorize to continue",
+ elicitationId = elicitationId,
+ url = url,
+ ),
+ ),
+ )
+ }
+
+ val serverSessionResult = CompletableDeferred()
+ listOf(
+ launch { client.connect(clientTransport) },
+ launch { serverSessionResult.complete(server.createSession(serverTransport)) },
+ ).joinAll()
+ serverSessionResult.await()
+
+ val exception = assertFailsWith {
+ client.callTool(name = "needs-auth", arguments = emptyMap())
+ }
+ val elicitation = exception.elicitations.single()
+ assertEquals(elicitationId, elicitation.elicitationId)
+ assertEquals(url, elicitation.url)
+
+ client.close()
+ }
+
+ private fun defaultsTestSchema(): ElicitRequestParams.RequestedSchema = ElicitRequestParams.RequestedSchema(
+ properties = mapOf(
+ "name" to StringSchema(description = "User name", default = "John Doe"),
+ "age" to IntegerSchema(description = "User age", default = 30),
+ "score" to DoubleSchema(description = "User score", default = 95.5),
+ "status" to UntitledSingleSelectEnumSchema(
+ description = "User status",
+ enumValues = listOf("active", "inactive", "pending"),
+ default = "active",
+ ),
+ "verified" to BooleanSchema(description = "Verification status", default = true),
+ ),
+ )
+
+ private suspend fun setupElicitationPair(
+ handler: (io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest) -> ElicitResult,
+ ): Pair = kotlinx.coroutines.coroutineScope {
+ val client = Client(
+ Implementation(name = "test client", version = "1.0"),
+ ClientOptions(capabilities = ClientCapabilities(elicitation = ClientCapabilities.Elicitation())),
+ )
+ client.setElicitationHandler(handler)
+
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+ val server = Server(
+ serverInfo = Implementation(name = "test server", version = "1.0"),
+ options = ServerOptions(capabilities = ServerCapabilities()),
+ )
+
+ val serverSessionResult = CompletableDeferred()
+ listOf(
+ launch { client.connect(clientTransport) },
+ launch { serverSessionResult.complete(server.createSession(serverTransport)) },
+ ).joinAll()
+
+ client to serverSessionResult.await()
+ }
}
diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt
index d82ff47..8abf75d 100644
--- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt
+++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt
@@ -12,9 +12,10 @@ import io.modelcontextprotocol.kotlin.sdk.server.mcp
import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withContext
import kotlin.test.BeforeTest
-import kotlin.test.Ignore
import kotlin.test.Test
import io.ktor.client.plugins.sse.SSE as ClientSSE
import io.ktor.server.sse.SSE as ServerSSE
@@ -37,7 +38,6 @@ class SseTransportTest : BaseTransportTest() {
}
@Test
- @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3
fun `should start then close cleanly`() = runTest {
val server = embeddedServer(CIO, port = 0) {
install(ServerSSE)
@@ -58,13 +58,12 @@ class SseTransportTest : BaseTransportTest() {
}
try {
- testTransportRead(transport)
+ testTransportOpenClose(transport)
} finally {
server.stopSuspend()
}
}
- @Ignore
@Test
fun `should read messages`() = runTest {
val server = embeddedServer(CIO, port = 0) {
@@ -85,20 +84,28 @@ class SseTransportTest : BaseTransportTest() {
}
}
+ val client = Client(
+ clientInfo = Implementation(name = "test-client", version = "1.0"),
+ options = ClientOptions(),
+ )
+
try {
- testTransportRead(transport)
+ withContext(Dispatchers.Default) {
+ client.connect(transport)
+ client.ping()
+ }
} finally {
+ client.close()
server.stopSuspend()
}
}
- @Ignore
@Test
fun `test sse path not root path`() = runTest {
val server = embeddedServer(CIO, port = 0) {
install(ServerSSE)
routing {
- mcp { mcpServer }
+ mcp("/sse") { mcpServer }
}
}.startSuspend(wait = false)
@@ -114,9 +121,18 @@ class SseTransportTest : BaseTransportTest() {
}
}
+ val client = Client(
+ clientInfo = Implementation(name = "test-client", version = "1.0"),
+ options = ClientOptions(),
+ )
+
try {
- testTransportRead(transport)
+ withContext(Dispatchers.Default) {
+ client.connect(transport)
+ client.ping()
+ }
} finally {
+ client.close()
server.stopSuspend()
}
}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt
index 585af3f..fc3c1d3 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt
@@ -7,9 +7,13 @@ import io.modelcontextprotocol.kotlin.sdk.types.McpException
import io.modelcontextprotocol.kotlin.test.utils.createSleepyProcessBuilder
import io.modelcontextprotocol.kotlin.test.utils.createTeeProcessBuilder
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withTimeout
+import kotlinx.coroutines.withTimeoutOrNull
+import kotlinx.io.Buffer
+import kotlinx.io.RawSource
import kotlinx.io.asSink
import kotlinx.io.asSource
import kotlinx.io.buffered
@@ -18,10 +22,12 @@ import org.junit.jupiter.api.Timeout
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.condition.DisabledOnOs
import org.junit.jupiter.api.condition.OS
+import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.concurrent.atomics.AtomicBoolean
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.fail
import kotlin.time.Duration.Companion.seconds
@@ -109,6 +115,43 @@ class StdioClientTransportTest : BaseTransportTest() {
}
}
+ @OptIn(ExperimentalAtomicApi::class)
+ @Test
+ fun `should close cleanly while stdin read is blocked`(): Unit = runBlocking(Dispatchers.IO) {
+ val input = BlockingRawSource()
+
+ val transport = StdioClientTransport(
+ input = input.buffered(),
+ output = Buffer(),
+ )
+
+ val didClose = AtomicBoolean(false)
+ transport.onClose { didClose.store(true) }
+ transport.onError { error ->
+ fail("Unexpected error while closing transport: $error")
+ }
+
+ transport.start()
+
+ eventually(2.seconds) {
+ assertTrue(input.readStarted, "Transport should start reading stdin before close")
+ }
+
+ val closeJob = async {
+ transport.close()
+ }
+
+ val closed = withTimeoutOrNull(1.seconds) {
+ closeJob.await()
+ }
+
+ input.close()
+ closeJob.await()
+
+ assertNotNull(closed, "Transport.close() should not wait for stdin to produce data or EOF")
+ assertTrue(didClose.load(), "Transport should be closed after close() call")
+ }
+
@Test
fun `should read messages`() = runTest {
val processBuilder = createTeeProcessBuilder()
@@ -147,4 +190,22 @@ class StdioClientTransportTest : BaseTransportTest() {
process.waitFor()
process.destroyForcibly()
}
+
+ private class BlockingRawSource : RawSource {
+ private val closed = CountDownLatch(1)
+
+ @Volatile
+ var readStarted: Boolean = false
+ private set
+
+ override fun readAtMostTo(sink: Buffer, byteCount: Long): Long {
+ readStarted = true
+ closed.await()
+ return -1L
+ }
+
+ override fun close() {
+ closed.countDown()
+ }
+ }
}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ProtocolConcurrencyIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ProtocolConcurrencyIntegrationTest.kt
new file mode 100644
index 0000000..d8d64ec
--- /dev/null
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ProtocolConcurrencyIntegrationTest.kt
@@ -0,0 +1,306 @@
+package io.modelcontextprotocol.kotlin.sdk.integration
+
+import io.kotest.matchers.shouldBe
+import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
+import io.modelcontextprotocol.kotlin.sdk.client.Client
+import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions
+import io.modelcontextprotocol.kotlin.sdk.server.Server
+import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
+import io.modelcontextprotocol.kotlin.sdk.server.ServerSession
+import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
+import io.modelcontextprotocol.kotlin.sdk.shared.currentRequestHandlerExtra
+import io.modelcontextprotocol.kotlin.sdk.testing.ChannelTransport
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult
+import io.modelcontextprotocol.kotlin.sdk.types.Implementation
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse
+import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
+import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest
+import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult
+import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest
+import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult
+import io.modelcontextprotocol.kotlin.sdk.types.Method
+import io.modelcontextprotocol.kotlin.sdk.types.RequestId
+import io.modelcontextprotocol.kotlin.sdk.types.Role
+import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage
+import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.toJSON
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * End-to-end tests for concurrent inbound dispatch enabled after the MCP handshake.
+ *
+ * Handlers run on a real [kotlinx.coroutines.Dispatchers.Default], so these use [runBlocking] with
+ * explicit [withTimeout] guards. A virtual test clock would false-fire timeouts against cross-thread
+ * real dispatchers.
+ */
+@OptIn(ExperimentalMcpApi::class)
+class ProtocolConcurrencyIntegrationTest {
+
+ private fun rootsClient(): Client = Client(
+ clientInfo = Implementation(name = "test client", version = "1.0"),
+ options = ClientOptions(
+ capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = null)),
+ ),
+ )
+
+ private fun toolsServer(): Server = Server(
+ serverInfo = Implementation(name = "test server", version = "1.0"),
+ options = ServerOptions(
+ capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = null)),
+ ),
+ )
+
+ // ChannelTransport delivers inbound messages as soon as its read loop starts, which is before
+ // start() marks it operational. Creating the session first keeps the client's initialize from
+ // landing on a transport that would reject the session's response.
+ private suspend fun connect(
+ client: Client,
+ clientTransport: ChannelTransport,
+ server: Server,
+ serverTransport: ChannelTransport,
+ ): ServerSession {
+ val serverSession = server.createSession(serverTransport)
+ client.connect(clientTransport)
+ return serverSession
+ }
+
+ // The motivating deadlock over real serial read loops: a server tool handler makes a
+ // server-to-client request (roots/list) while still handling the client's tools/call.
+ @Test
+ fun `server tool handler can call roots list mid-handling without deadlock`() = runBlocking {
+ val client = rootsClient()
+ val server = toolsServer()
+ val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair()
+
+ val serverSession = connect(client, clientTransport, server, serverTransport)
+
+ // Register the tools/call handler directly on the session so it can call back to the client
+ // mid-handling. Before concurrent dispatch this would deadlock: the server read loop is
+ // blocked by the tool handler and can never deliver the roots/list response.
+ serverSession.setRequestHandler(Method.Defined.ToolsCall) { _, _ ->
+ val roots = serverSession.request(ListRootsRequest())
+ CallToolResult(content = listOf(TextContent(text = "roots=${roots.roots.size}")))
+ }
+
+ val result = withTimeout(10.seconds) {
+ client.callTool(CallToolRequest(CallToolRequestParams("needs-roots")))
+ }
+ val text = (result as CallToolResult).content.filterIsInstance().single().text
+ text shouldBe "roots=0"
+
+ client.close()
+ server.close()
+ }
+
+ // Client-side mirror: a client sampling handler makes a client-to-server request (tools/list)
+ // while still handling the server's sampling/createMessage.
+ @Test
+ fun `client sampling handler can call the server mid-handling without deadlock`() = runBlocking {
+ val client = Client(
+ clientInfo = Implementation(name = "test client", version = "1.0"),
+ options = ClientOptions(
+ capabilities = ClientCapabilities(sampling = ClientCapabilities.Sampling()),
+ ),
+ )
+ val server = toolsServer()
+ val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair()
+
+ client.setRequestHandler(Method.Defined.SamplingCreateMessage) { _, _ ->
+ val tools = client.request(ListToolsRequest())
+ CreateMessageResult(
+ role = Role.Assistant,
+ content = TextContent(text = "tools=${tools.tools.size}"),
+ model = "test-model",
+ )
+ }
+
+ val serverSession = connect(client, clientTransport, server, serverTransport)
+
+ val result = withTimeout(10.seconds) {
+ serverSession.request(
+ CreateMessageRequest(
+ CreateMessageRequestParams(
+ maxTokens = 100,
+ messages = listOf(SamplingMessage(role = Role.User, content = TextContent(text = "hi"))),
+ ),
+ ),
+ )
+ }
+ val text = result.content.filterIsInstance().single().text
+ text shouldBe "tools=0"
+
+ client.close()
+ server.close()
+ }
+
+ // Nested-cancel cascade: cancelling the client's tools/call must reach the server tool handler
+ // over the wire and cascade into its nested roots/list request, cancelling the parked
+ // client-side roots handler. No tools/call response is ever sent.
+ @Test
+ fun `cancelling a tool call cascades into its nested roots list request`() = runBlocking {
+ val client = rootsClient()
+ val server = toolsServer()
+ val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair()
+
+ val capturedToolCallId = AtomicReference(null)
+ val toolHandlerCancelled = CompletableDeferred()
+ val rootsHandlerEntered = CompletableDeferred()
+ val rootsHandlerCancelled = CompletableDeferred()
+ val parkForever = CompletableDeferred()
+ val unexpectedToolCallResponse = CompletableDeferred()
+
+ // Tap the server->client direction (client transport inbound) to prove no tools/call
+ // response is ever delivered once the call is cancelled.
+ clientTransport.onMessage { message ->
+ if (message is JSONRPCResponse && message.id == capturedToolCallId.get()) {
+ unexpectedToolCallResponse.complete(Unit)
+ }
+ }
+
+ val serverSession = connect(client, clientTransport, server, serverTransport)
+
+ // Client roots handler parks until cancelled and records the cancellation.
+ client.setRequestHandler(Method.Defined.RootsList) { _, _ ->
+ rootsHandlerEntered.complete(Unit)
+ try {
+ parkForever.await()
+ } catch (e: CancellationException) {
+ rootsHandlerCancelled.complete(e)
+ throw e
+ }
+ }
+
+ // Tool handler makes a nested server->client request and records its own cancellation.
+ serverSession.setRequestHandler(Method.Defined.ToolsCall) { _, _ ->
+ val extra = currentRequestHandlerExtra()!!
+ capturedToolCallId.set(extra.requestId)
+ try {
+ extra.sendRequest(ListRootsRequest())
+ } catch (e: CancellationException) {
+ toolHandlerCancelled.complete(e)
+ throw e
+ }
+ CallToolResult(content = listOf(TextContent(text = "unreachable")))
+ }
+
+ val call = launch { client.callTool(CallToolRequest(CallToolRequestParams("nested"))) }
+
+ withTimeout(10.seconds) { rootsHandlerEntered.await() }
+ call.cancel(CancellationException("user aborted"))
+
+ withTimeout(10.seconds) { toolHandlerCancelled.await() }
+ withTimeout(10.seconds) { rootsHandlerCancelled.await() }
+
+ // The server suppresses the response for a cancelled request, so none is ever sent.
+ assertFalse(
+ unexpectedToolCallResponse.isCompleted,
+ "no tools/call response should be delivered after the call was cancelled",
+ )
+
+ client.close()
+ server.close()
+ }
+
+ // Init gate plus context element in a high-level handler: initialize and tools/list are
+ // pipelined before notifications/initialized and answered serially in order. After the
+ // handshake a high-level addTool handler can observe currentRequestHandlerExtra().
+ @Test
+ fun `pipelined initialize and tools list are processed serially then context element is available`(): Unit =
+ runBlocking {
+ val server = toolsServer()
+ server.addTool(name = "ctx-tool", description = "returns its request id") {
+ val extra = currentRequestHandlerExtra()
+ checkNotNull(extra) { "currentRequestHandlerExtra() must be non-null inside a tool handler" }
+ CallToolResult(content = listOf(TextContent(text = extra.requestId.asText())))
+ }
+
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+
+ val arrivalOrder = CopyOnWriteArrayList()
+ val pending = ConcurrentHashMap>()
+ clientTransport.onMessage { message ->
+ val id = when (message) {
+ is JSONRPCResponse -> message.id
+ is JSONRPCError -> message.id
+ else -> null
+ }
+ if (id != null) {
+ arrivalOrder.add(id)
+ pending[id]?.complete(message)
+ }
+ }
+
+ // Drive the client side manually over the raw transport.
+ server.createSession(serverTransport)
+
+ val initRequest = InitializeRequest(
+ InitializeRequestParams(
+ protocolVersion = LATEST_PROTOCOL_VERSION,
+ capabilities = ClientCapabilities(),
+ clientInfo = Implementation(name = "raw-client", version = "1.0"),
+ ),
+ ).toJSON()
+ val toolsListRequest = ListToolsRequest().toJSON()
+
+ val initAnswered = pending.expect(initRequest.id)
+ val toolsListAnswered = pending.expect(toolsListRequest.id)
+
+ // Pipelined before notifications/initialized: both handled in the serial dispatch phase.
+ clientTransport.send(initRequest)
+ clientTransport.send(toolsListRequest)
+
+ withTimeout(10.seconds) { initAnswered.await() }
+ withTimeout(10.seconds) { toolsListAnswered.await() }
+
+ // Deterministic serial ordering: initialize answered before tools/list.
+ assertEquals(
+ listOf(initRequest.id, toolsListRequest.id),
+ arrivalOrder.toList(),
+ "pipelined requests must be answered in arrival order",
+ )
+
+ // Complete the handshake, then call the high-level tool.
+ clientTransport.send(InitializedNotification().toJSON())
+
+ val toolCallRequest = CallToolRequest(CallToolRequestParams("ctx-tool")).toJSON()
+ val toolCallAnswered = pending.expect(toolCallRequest.id)
+ clientTransport.send(toolCallRequest)
+
+ val toolCallResponse = withTimeout(10.seconds) { toolCallAnswered.await() } as JSONRPCResponse
+ val text = (toolCallResponse.result as CallToolResult).content.filterIsInstance().single().text
+ // The handler echoed its request id, proving currentRequestHandlerExtra() was available.
+ text shouldBe toolCallRequest.id.asText()
+ }
+
+ private fun ConcurrentHashMap>.expect(
+ id: RequestId,
+ ): CompletableDeferred = CompletableDeferred().also { put(id, it) }
+
+ private fun RequestId.asText(): String = when (this) {
+ is RequestId.StringId -> value
+ is RequestId.NumberId -> value.toString()
+ }
+}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt
index d14ae88..fb97782 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt
@@ -6,9 +6,15 @@ import io.kotest.matchers.string.shouldContain
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult
+import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest
+import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult
import io.modelcontextprotocol.kotlin.sdk.types.McpException
+import io.modelcontextprotocol.kotlin.sdk.types.Method
+import io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.Prompt
import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument
import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage
+import io.modelcontextprotocol.kotlin.sdk.types.RPCError
import io.modelcontextprotocol.kotlin.sdk.types.Role
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
@@ -18,7 +24,9 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
+import java.util.concurrent.CopyOnWriteArrayList
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@@ -690,11 +698,76 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() {
val expectedMessage = "Prompt not found: non-existent-prompt"
- withClue("Exception code should be INTERNAL_ERROR: -32603") {
- exception.code shouldBe -32603
+ withClue("Exception code should be INVALID_PARAMS: ${RPCError.ErrorCode.INVALID_PARAMS}") {
+ exception.code shouldBe RPCError.ErrorCode.INVALID_PARAMS
}
withClue("Unexpected error message for non-existent prompt") {
exception.message shouldBe expectedMessage
}
}
+
+ @Test
+ fun testListPromptsPagination() = runBlocking(Dispatchers.IO) {
+ val receivedCursors = CopyOnWriteArrayList()
+ val page1 = listOf(Prompt(name = "p-1", description = "desc"), Prompt(name = "p-2", description = "desc"))
+ val page2 = listOf(Prompt(name = "p-3", description = "desc"), Prompt(name = "p-4", description = "desc"))
+ val page3 = listOf(Prompt(name = "p-5", description = "desc"))
+
+ server.sessions.forEach { (_, session) ->
+ session.setRequestHandler(Method.Defined.PromptsList) { request, _ ->
+ receivedCursors += request.cursor
+ when (request.cursor) {
+ null -> ListPromptsResult(prompts = page1, nextCursor = "cursor-2")
+ "cursor-2" -> ListPromptsResult(prompts = page2, nextCursor = "cursor-3")
+ "cursor-3" -> ListPromptsResult(prompts = page3, nextCursor = null)
+ else -> error("Unexpected cursor: ${request.cursor}")
+ }
+ }
+ }
+
+ val collected = mutableListOf()
+ var cursor: String? = null
+ do {
+ val request = if (cursor == null) {
+ ListPromptsRequest()
+ } else {
+ ListPromptsRequest(PaginatedRequestParams(cursor = cursor))
+ }
+ val response = client.listPrompts(request)
+ collected += response.prompts
+ cursor = response.nextCursor
+ } while (cursor != null)
+
+ assertEquals(
+ listOf(null, "cursor-2", "cursor-3"),
+ receivedCursors.toList(),
+ "Client must forward each nextCursor into the next request",
+ )
+ assertEquals(
+ listOf("p-1", "p-2", "p-3", "p-4", "p-5"),
+ collected.map { it.name },
+ "Client must accumulate pages in order without duplicates or reordering",
+ )
+ }
+
+ @Test
+ fun testListPromptsInvalidCursor() = runBlocking(Dispatchers.IO) {
+ val invalidCursor = "not-a-valid-cursor"
+
+ server.sessions.forEach { (_, session) ->
+ session.setRequestHandler(Method.Defined.PromptsList) { _, _ ->
+ throw McpException(
+ code = RPCError.ErrorCode.INVALID_PARAMS,
+ message = "Invalid cursor: $invalidCursor",
+ )
+ }
+ }
+
+ val exception = assertFailsWith {
+ client.listPrompts(ListPromptsRequest(PaginatedRequestParams(cursor = invalidCursor)))
+ }
+
+ assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code)
+ assertEquals("Invalid cursor: $invalidCursor", exception.message)
+ }
}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt
index 4fc53a5..ab2a854 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt
@@ -1,11 +1,16 @@
package io.modelcontextprotocol.kotlin.sdk.integration.kotlin
import io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents
+import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest
+import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult
import io.modelcontextprotocol.kotlin.sdk.types.McpException
+import io.modelcontextprotocol.kotlin.sdk.types.Method
+import io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.RPCError
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult
+import io.modelcontextprotocol.kotlin.sdk.types.Resource
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest
import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams
@@ -18,8 +23,10 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
+import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@@ -70,23 +77,6 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() {
)
}
- server.addResource(
- uri = testResourceUri,
- name = testResourceName,
- description = testResourceDescription,
- mimeType = "text/plain",
- ) { request ->
- ReadResourceResult(
- contents = listOf(
- TextResourceContents(
- text = testResourceContent,
- uri = request.params.uri,
- mimeType = "text/plain",
- ),
- ),
- )
- }
-
server.addResource(
uri = binaryResourceUri,
name = binaryResourceName,
@@ -309,4 +299,76 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() {
assertTrue(result.contents.isNotEmpty(), "Result contents should not be empty")
}
}
+
+ @Test
+ fun testListResourcesPagination() = runBlocking(Dispatchers.IO) {
+ val receivedCursors = CopyOnWriteArrayList()
+ val page1 = listOf(
+ Resource(uri = "test://r-1", name = "r-1"),
+ Resource(uri = "test://r-2", name = "r-2"),
+ Resource(uri = "test://r-3", name = "r-3"),
+ )
+ val page2 = listOf(
+ Resource(uri = "test://r-4", name = "r-4"),
+ Resource(uri = "test://r-5", name = "r-5"),
+ )
+ val page3 = listOf(Resource(uri = "test://r-6", name = "r-6"))
+
+ server.sessions.forEach { (_, session) ->
+ session.setRequestHandler(Method.Defined.ResourcesList) { request, _ ->
+ receivedCursors += request.cursor
+ when (request.cursor) {
+ null -> ListResourcesResult(resources = page1, nextCursor = "cursor-2")
+ "cursor-2" -> ListResourcesResult(resources = page2, nextCursor = "cursor-3")
+ "cursor-3" -> ListResourcesResult(resources = page3, nextCursor = null)
+ else -> error("Unexpected cursor: ${request.cursor}")
+ }
+ }
+ }
+
+ val collected = mutableListOf()
+ var cursor: String? = null
+ do {
+ val request = if (cursor == null) {
+ ListResourcesRequest()
+ } else {
+ ListResourcesRequest(PaginatedRequestParams(cursor = cursor))
+ }
+ val response = client.listResources(request)
+ collected += response.resources
+ cursor = response.nextCursor
+ } while (cursor != null)
+
+ assertEquals(
+ listOf(null, "cursor-2", "cursor-3"),
+ receivedCursors.toList(),
+ "Client must forward each nextCursor into the next request",
+ )
+ assertEquals(
+ listOf("test://r-1", "test://r-2", "test://r-3", "test://r-4", "test://r-5", "test://r-6"),
+ collected.map { it.uri },
+ "Client must accumulate pages in order without duplicates or reordering",
+ )
+ }
+
+ @Test
+ fun testListResourcesInvalidCursor() = runBlocking(Dispatchers.IO) {
+ val invalidCursor = "not-a-valid-cursor"
+
+ server.sessions.forEach { (_, session) ->
+ session.setRequestHandler(Method.Defined.ResourcesList) { _, _ ->
+ throw McpException(
+ code = RPCError.ErrorCode.INVALID_PARAMS,
+ message = "Invalid cursor: $invalidCursor",
+ )
+ }
+ }
+
+ val exception = assertFailsWith {
+ client.listResources(ListResourcesRequest(PaginatedRequestParams(cursor = invalidCursor)))
+ }
+
+ assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code)
+ assertEquals("Invalid cursor: $invalidCursor", exception.message)
+ }
}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt
index 7da82cc..b802122 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt
@@ -6,8 +6,15 @@ import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.ContentBlock
import io.modelcontextprotocol.kotlin.sdk.types.ImageContent
+import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest
+import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult
+import io.modelcontextprotocol.kotlin.sdk.types.McpException
+import io.modelcontextprotocol.kotlin.sdk.types.Method
+import io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.RPCError
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.Tool
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@@ -24,7 +31,9 @@ import org.junit.jupiter.api.Test
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Locale
+import java.util.concurrent.CopyOnWriteArrayList
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@@ -791,4 +800,75 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() {
"Error message should indicate the tool was not found",
)
}
+
+ @Test
+ fun testListToolsPagination() = runBlocking(Dispatchers.IO) {
+ val receivedCursors = CopyOnWriteArrayList()
+ val page1 = listOf(
+ Tool(name = "t-1", inputSchema = ToolSchema()),
+ Tool(name = "t-2", inputSchema = ToolSchema()),
+ )
+ val page2 = listOf(
+ Tool(name = "t-3", inputSchema = ToolSchema()),
+ Tool(name = "t-4", inputSchema = ToolSchema()),
+ )
+ val page3 = listOf(Tool(name = "t-5", inputSchema = ToolSchema()))
+
+ server.sessions.forEach { (_, session) ->
+ session.setRequestHandler(Method.Defined.ToolsList) { request, _ ->
+ receivedCursors += request.cursor
+ when (request.cursor) {
+ null -> ListToolsResult(tools = page1, nextCursor = "cursor-2")
+ "cursor-2" -> ListToolsResult(tools = page2, nextCursor = "cursor-3")
+ "cursor-3" -> ListToolsResult(tools = page3, nextCursor = null)
+ else -> error("Unexpected cursor: ${request.cursor}")
+ }
+ }
+ }
+
+ val collected = mutableListOf()
+ var cursor: String? = null
+ do {
+ val request = if (cursor == null) {
+ ListToolsRequest()
+ } else {
+ ListToolsRequest(PaginatedRequestParams(cursor = cursor))
+ }
+ val response = client.listTools(request)
+ collected += response.tools
+ cursor = response.nextCursor
+ } while (cursor != null)
+
+ assertEquals(
+ listOf(null, "cursor-2", "cursor-3"),
+ receivedCursors.toList(),
+ "Client must forward each nextCursor into the next request",
+ )
+ assertEquals(
+ listOf("t-1", "t-2", "t-3", "t-4", "t-5"),
+ collected.map { it.name },
+ "Client must accumulate pages in order without duplicates or reordering",
+ )
+ }
+
+ @Test
+ fun testListToolsInvalidCursor() = runBlocking(Dispatchers.IO) {
+ val invalidCursor = "not-a-valid-cursor"
+
+ server.sessions.forEach { (_, session) ->
+ session.setRequestHandler(Method.Defined.ToolsList) { _, _ ->
+ throw McpException(
+ code = RPCError.ErrorCode.INVALID_PARAMS,
+ message = "Invalid cursor: $invalidCursor",
+ )
+ }
+ }
+
+ val exception = assertFailsWith {
+ client.listTools(ListToolsRequest(PaginatedRequestParams(cursor = invalidCursor)))
+ }
+
+ assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code)
+ assertEquals("Invalid cursor: $invalidCursor", exception.message)
+ }
}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt
index 0f6646e..535393e 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt
@@ -1,17 +1,14 @@
package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp
-import io.ktor.serialization.kotlinx.json.json
-import io.ktor.server.application.install
import io.ktor.server.cio.CIOApplicationEngine
import io.ktor.server.engine.EmbeddedServer
import io.ktor.server.engine.embeddedServer
-import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.server.sse.Heartbeat
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
-import io.modelcontextprotocol.kotlin.sdk.types.McpJson
import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument
import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage
import io.modelcontextprotocol.kotlin.sdk.types.Role
@@ -21,7 +18,10 @@ import io.ktor.server.cio.CIO as ServerCIO
open class AbstractStreamableHttpIntegrationTest {
- suspend fun initTestServer(name: String? = null): StreamableHttpTestServer {
+ suspend fun initTestServer(
+ name: String? = null,
+ sseHeartbeatConfig: (Heartbeat.() -> Unit)? = null,
+ ): StreamableHttpTestServer {
val mcpServer = Server(
Implementation(name = name ?: DEFAULT_SERVER_NAME, version = VERSION),
ServerOptions(
@@ -56,10 +56,7 @@ open class AbstractStreamableHttpIntegrationTest {
host = URL,
port = PORT,
) {
- install(ContentNegotiation) {
- json(McpJson)
- }
- mcpStreamableHttp { mcpServer }
+ mcpStreamableHttp(sseHeartbeatConfig = sseHeartbeatConfig) { mcpServer }
}
return StreamableHttpTestServer(
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpClientTransportIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpClientTransportIntegrationTest.kt
new file mode 100644
index 0000000..598825f
--- /dev/null
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpClientTransportIntegrationTest.kt
@@ -0,0 +1,270 @@
+package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp
+
+import io.kotest.matchers.collections.shouldContain
+import io.kotest.matchers.shouldBe
+import io.ktor.client.HttpClient
+import io.ktor.client.plugins.sse.SSE
+import io.ktor.http.ContentType
+import io.ktor.http.HttpStatusCode
+import io.ktor.server.engine.embeddedServer
+import io.ktor.server.request.receiveText
+import io.ktor.server.response.header
+import io.ktor.server.response.respond
+import io.ktor.server.response.respondText
+import io.ktor.server.response.respondTextWriter
+import io.ktor.server.routing.Route
+import io.ktor.server.routing.delete
+import io.ktor.server.routing.get
+import io.ktor.server.routing.post
+import io.ktor.server.routing.routing
+import io.modelcontextprotocol.kotlin.sdk.client.Client
+import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions
+import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.Implementation
+import io.modelcontextprotocol.kotlin.sdk.types.Tool
+import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
+import io.modelcontextprotocol.kotlin.test.utils.actualPort
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import kotlinx.serialization.json.put
+import kotlinx.serialization.json.putJsonObject
+import kotlin.test.Test
+import io.ktor.client.engine.cio.CIO as ClientCIO
+import io.ktor.server.cio.CIO as ServerCIO
+
+private const val SESSION_ID_HEADER = "mcp-session-id"
+private const val HOST = "127.0.0.1"
+private const val MCP_PATH = "/mcp"
+
+private val INITIALIZE_RESULT = """
+ {
+ "capabilities": {"tools": {"listChanged": false}},
+ "protocolVersion": "2025-03-26",
+ "serverInfo": {"name": "test-server", "version": "1.0.0"}
+ }
+""".trimIndent()
+
+private val TOOLS_LIST_RESULT = """
+ {
+ "tools": [
+ {
+ "name": "get_weather",
+ "title": "Weather Information Provider",
+ "description": "Get current weather information for a location",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "location": {"type": "string", "description": "City name or zip code"}
+ },
+ "required": ["location"]
+ },
+ "outputSchema": {
+ "type": "object",
+ "properties": {
+ "temperature": {"type": "number", "description": "Temperature, Celsius"}
+ },
+ "required": ["temperature"]
+ },
+ "_meta": {}
+ }
+ ]
+ }
+""".trimIndent()
+
+private const val PROGRESS_NOTIFICATION_1 =
+ """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}"""
+
+private const val PROGRESS_NOTIFICATION_2 =
+ """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":75,"total":100}}"""
+
+internal class StreamableHttpClientTransportIntegrationTest {
+
+ @Test
+ fun `client receives progress notifications via GET SSE and lists tools`(): Unit = runBlocking(Dispatchers.IO) {
+ val sessionId = "sid-progress"
+ runWithServer({
+ mcpPostRoute(sessionId, extraResults = mapOf("tools/list" to TOOLS_LIST_RESULT))
+ get(MCP_PATH) {
+ call.response.header(SESSION_ID_HEADER, sessionId)
+ call.respondTextWriter(contentType = ContentType.Text.EventStream) {
+ write(sseEvent(id = "1", data = PROGRESS_NOTIFICATION_1))
+ flush()
+ delay(200)
+ write(sseEvent(id = "2", data = PROGRESS_NOTIFICATION_2))
+ flush()
+ awaitCancellation()
+ }
+ }
+ }) { url ->
+ val client = newClient()
+ client.connect(StreamableHttpClientTransport(client = newHttpClient(), url = url))
+
+ val listToolsResult = client.listTools()
+
+ listToolsResult.tools shouldContain Tool(
+ name = "get_weather",
+ title = "Weather Information Provider",
+ description = "Get current weather information for a location",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("location") {
+ put("type", "string")
+ put("description", "City name or zip code")
+ }
+ },
+ required = listOf("location"),
+ ),
+ outputSchema = ToolSchema(
+ properties = buildJsonObject {
+ putJsonObject("temperature") {
+ put("type", "number")
+ put("description", "Temperature, Celsius")
+ }
+ },
+ required = listOf("temperature"),
+ ),
+ annotations = null,
+ meta = EmptyJsonObject,
+ )
+
+ client.close()
+ }
+ }
+
+ @Test
+ fun `terminateSession sends DELETE request and clears sessionId`(): Unit = runBlocking(Dispatchers.IO) {
+ val sessionId = "sid-terminate"
+ runWithServer({
+ mcpPostRoute(sessionId)
+ get(MCP_PATH) {
+ call.response.header(SESSION_ID_HEADER, sessionId)
+ call.respondTextWriter(contentType = ContentType.Text.EventStream) {
+ awaitCancellation()
+ }
+ }
+ delete(MCP_PATH) {
+ call.respond(HttpStatusCode.OK)
+ }
+ }) { url ->
+ val client = newClient()
+ val transport = StreamableHttpClientTransport(client = newHttpClient(), url = url)
+ client.connect(transport)
+
+ transport.sessionId shouldBe sessionId
+ transport.terminateSession()
+ transport.sessionId shouldBe null
+
+ client.close()
+ }
+ }
+
+ @Test
+ fun `client survives 405 on GET SSE`(): Unit = runBlocking(Dispatchers.IO) {
+ val sessionId = "sid-405"
+ runWithServer({
+ mcpPostRoute(sessionId, extraResults = mapOf("ping" to "{}"))
+ get(MCP_PATH) {
+ call.respondText(
+ text = "",
+ contentType = ContentType.Text.EventStream,
+ status = HttpStatusCode.MethodNotAllowed,
+ )
+ }
+ }) { url ->
+ val client = newClient()
+ client.connect(StreamableHttpClientTransport(client = newHttpClient(), url = url))
+
+ client.ping()
+
+ client.close()
+ }
+ }
+
+ @Test
+ fun `client survives non streaming JSON response on GET SSE`(): Unit = runBlocking(Dispatchers.IO) {
+ val sessionId = "sid-json"
+ runWithServer({
+ mcpPostRoute(sessionId, extraResults = mapOf("ping" to "{}"))
+ get(MCP_PATH) {
+ call.response.header(SESSION_ID_HEADER, sessionId)
+ call.respondText(
+ text = "",
+ contentType = ContentType.Application.Json,
+ status = HttpStatusCode.OK,
+ )
+ }
+ }) { url ->
+ val client = newClient()
+ client.connect(StreamableHttpClientTransport(client = newHttpClient(), url = url))
+
+ client.ping()
+
+ client.close()
+ }
+ }
+
+ private fun Route.mcpPostRoute(sessionId: String, extraResults: Map = emptyMap()) {
+ post(MCP_PATH) {
+ val body = call.receiveText()
+ val parsed = Json.parseToJsonElement(body).jsonObject
+ val method = parsed["method"]?.jsonPrimitive?.content
+ val idJson = parsed["id"]?.toString() ?: "null"
+ call.response.header(SESSION_ID_HEADER, sessionId)
+ when {
+ method == "initialize" -> call.respondText(
+ text = jsonRpcResult(idJson, INITIALIZE_RESULT),
+ contentType = ContentType.Application.Json,
+ status = HttpStatusCode.OK,
+ )
+
+ method == "notifications/initialized" -> call.respond(HttpStatusCode.Accepted)
+
+ method != null && method in extraResults -> call.respondText(
+ text = jsonRpcResult(idJson, extraResults.getValue(method)),
+ contentType = ContentType.Application.Json,
+ status = HttpStatusCode.OK,
+ )
+
+ else -> call.respond(HttpStatusCode.NotFound)
+ }
+ }
+ }
+
+ private suspend fun runWithServer(routes: Route.() -> Unit, block: suspend (url: String) -> Unit) {
+ val server = embeddedServer(ServerCIO, host = HOST, port = 0) {
+ routing { routes() }
+ }.startSuspend(wait = false)
+ try {
+ val port = server.actualPort()
+ block("http://$HOST:$port$MCP_PATH")
+ } finally {
+ server.stopSuspend(1000, 2000)
+ }
+ }
+
+ private fun newClient(): Client = Client(
+ clientInfo = Implementation(name = "client1", version = "1.0.0"),
+ options = ClientOptions(capabilities = ClientCapabilities()),
+ )
+
+ private fun newHttpClient(): HttpClient = HttpClient(ClientCIO) {
+ install(SSE)
+ }
+
+ private fun jsonRpcResult(idJson: String, resultJson: String): String =
+ """{"jsonrpc":"2.0","id":$idJson,"result":$resultJson}"""
+
+ private fun sseEvent(id: String, data: String): String = buildString {
+ append("event: message\n")
+ append("id: $id\n")
+ append("data: $data\n\n")
+ }
+}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpHeartbeatIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpHeartbeatIntegrationTest.kt
new file mode 100644
index 0000000..8019557
--- /dev/null
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpHeartbeatIntegrationTest.kt
@@ -0,0 +1,174 @@
+package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp
+
+import io.kotest.matchers.shouldBe
+import io.ktor.client.HttpClient
+import io.ktor.client.plugins.sse.SSE
+import io.ktor.client.request.header
+import io.ktor.client.request.post
+import io.ktor.client.request.prepareGet
+import io.ktor.client.request.setBody
+import io.ktor.client.statement.bodyAsChannel
+import io.ktor.http.ContentType
+import io.ktor.http.HttpHeaders
+import io.ktor.http.HttpStatusCode
+import io.ktor.http.contentType
+import io.ktor.sse.ServerSentEvent
+import io.ktor.utils.io.ByteReadChannel
+import io.ktor.utils.io.readUTF8Line
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.Implementation
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
+import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
+import io.modelcontextprotocol.kotlin.sdk.types.toJSON
+import io.modelcontextprotocol.kotlin.test.utils.actualPort
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
+import kotlinx.serialization.json.Json
+import kotlin.test.Test
+import kotlin.test.assertNotNull
+import kotlin.time.Duration.Companion.milliseconds
+import io.ktor.client.engine.cio.CIO as ClientCIO
+
+private const val SESSION_ID_HEADER = "mcp-session-id"
+private const val PROTOCOL_VERSION_HEADER = "mcp-protocol-version"
+private const val DEFAULT_HEARTBEAT_LINE = ": heartbeat"
+private const val CUSTOM_HEARTBEAT_LINE = ": mcp-heartbeat"
+
+class StreamableHttpHeartbeatIntegrationTest : AbstractStreamableHttpIntegrationTest() {
+
+ @Test
+ fun `GET SSE stream emits configured heartbeat`(): Unit = runBlocking(Dispatchers.IO) {
+ var server: StreamableHttpTestServer? = null
+ var httpClient: HttpClient? = null
+
+ try {
+ server = initTestServer("heartbeat-test") {
+ period = 50.milliseconds
+ event = ServerSentEvent(comments = "mcp-heartbeat")
+ }
+ val mcpUrl = "http://$URL:${server.ktorServer.actualPort()}/mcp"
+ httpClient = HttpClient(ClientCIO) { install(SSE) }
+ val sessionId = initializeSession(httpClient, mcpUrl)
+
+ httpClient.prepareGet(mcpUrl) {
+ addSseHeaders(sessionId)
+ }.execute { response ->
+ response.status shouldBe HttpStatusCode.OK
+ response.headers[SESSION_ID_HEADER] shouldBe sessionId
+
+ response.bodyAsChannel().readLineMatching(CUSTOM_HEARTBEAT_LINE) shouldBe CUSTOM_HEARTBEAT_LINE
+ }
+ } finally {
+ httpClient?.close()
+ server?.ktorServer?.stopSuspend(1000, 2000)
+ }
+ }
+
+ @Test
+ fun `GET SSE stream does not emit heartbeat by default`(): Unit = runBlocking(Dispatchers.IO) {
+ var server: StreamableHttpTestServer? = null
+ var httpClient: HttpClient? = null
+
+ try {
+ server = initTestServer("no-heartbeat-test")
+ val mcpUrl = "http://$URL:${server.ktorServer.actualPort()}/mcp"
+ httpClient = HttpClient(ClientCIO) { install(SSE) }
+ val sessionId = initializeSession(httpClient, mcpUrl)
+
+ httpClient.prepareGet(mcpUrl) {
+ addSseHeaders(sessionId)
+ }.execute { response ->
+ response.status shouldBe HttpStatusCode.OK
+ response.headers[SESSION_ID_HEADER] shouldBe sessionId
+
+ response.bodyAsChannel().readLineMatching(timeoutMillis = 150) { line ->
+ line.isHeartbeatSseLine()
+ } shouldBe null
+ }
+ } finally {
+ httpClient?.close()
+ server?.ktorServer?.stopSuspend(1000, 2000)
+ }
+ }
+
+ @Test
+ fun `GET SSE stream emits configured heartbeat repeatedly`(): Unit = runBlocking(Dispatchers.IO) {
+ var server: StreamableHttpTestServer? = null
+ var httpClient: HttpClient? = null
+
+ try {
+ server = initTestServer("repeating-heartbeat-test") {
+ period = 50.milliseconds
+ event = ServerSentEvent(comments = "mcp-heartbeat")
+ }
+ val mcpUrl = "http://$URL:${server.ktorServer.actualPort()}/mcp"
+ httpClient = HttpClient(ClientCIO) { install(SSE) }
+ val sessionId = initializeSession(httpClient, mcpUrl)
+
+ httpClient.prepareGet(mcpUrl) {
+ addSseHeaders(sessionId)
+ }.execute { response ->
+ response.status shouldBe HttpStatusCode.OK
+ response.headers[SESSION_ID_HEADER] shouldBe sessionId
+
+ val channel = response.bodyAsChannel()
+ channel.readLineMatching(CUSTOM_HEARTBEAT_LINE) shouldBe CUSTOM_HEARTBEAT_LINE
+ channel.readLineMatching(CUSTOM_HEARTBEAT_LINE) shouldBe CUSTOM_HEARTBEAT_LINE
+ }
+ } finally {
+ httpClient?.close()
+ server?.ktorServer?.stopSuspend(1000, 2000)
+ }
+ }
+
+ private suspend fun initializeSession(client: HttpClient, mcpUrl: String): String {
+ val response = client.post(mcpUrl) {
+ contentType(ContentType.Application.Json)
+ header(
+ HttpHeaders.Accept,
+ "${ContentType.Application.Json}, ${ContentType.Text.EventStream}",
+ )
+ setBody(Json.encodeToString(buildInitPayload()))
+ }
+
+ response.status shouldBe HttpStatusCode.OK
+ return assertNotNull(response.headers[SESSION_ID_HEADER])
+ }
+
+ private fun buildInitPayload(): JSONRPCRequest = InitializeRequest(
+ InitializeRequestParams(
+ protocolVersion = LATEST_PROTOCOL_VERSION,
+ capabilities = ClientCapabilities(),
+ clientInfo = Implementation(name = "heartbeat-test-client", version = "1.0.0"),
+ ),
+ ).toJSON()
+
+ private fun io.ktor.client.request.HttpRequestBuilder.addSseHeaders(sessionId: String) {
+ header(HttpHeaders.Accept, ContentType.Text.EventStream.toString())
+ header(SESSION_ID_HEADER, sessionId)
+ header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION)
+ }
+
+ private suspend fun ByteReadChannel.readLineMatching(expectedLine: String, timeoutMillis: Long = 2_000): String? =
+ readLineMatching(timeoutMillis) { line -> line == expectedLine }
+
+ private suspend fun ByteReadChannel.readLineMatching(
+ timeoutMillis: Long = 2_000,
+ matches: (String) -> Boolean,
+ ): String? = withTimeoutOrNull(timeoutMillis.milliseconds) {
+ var line = readUTF8Line()
+ while (line != null) {
+ if (matches(line)) return@withTimeoutOrNull line
+ line = readUTF8Line()
+ }
+ null
+ }
+
+ private fun String.isHeartbeatSseLine(): Boolean = this == DEFAULT_HEARTBEAT_LINE ||
+ this == CUSTOM_HEARTBEAT_LINE ||
+ (startsWith(":") && contains("heartbeat", ignoreCase = true)) ||
+ (startsWith("event:") && contains("heartbeat", ignoreCase = true))
+}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpSseReconnectTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpSseReconnectTest.kt
new file mode 100644
index 0000000..751f8dd
--- /dev/null
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpSseReconnectTest.kt
@@ -0,0 +1,171 @@
+package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp
+
+import io.kotest.matchers.nulls.shouldNotBeNull
+import io.kotest.matchers.shouldBe
+import io.ktor.client.HttpClient
+import io.ktor.client.plugins.sse.SSE
+import io.ktor.client.request.header
+import io.ktor.client.request.post
+import io.ktor.client.request.prepareGet
+import io.ktor.client.request.setBody
+import io.ktor.client.statement.bodyAsChannel
+import io.ktor.http.ContentType
+import io.ktor.http.HttpHeaders
+import io.ktor.http.HttpStatusCode
+import io.ktor.http.contentType
+import io.ktor.utils.io.readUTF8Line
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.Implementation
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
+import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
+import io.modelcontextprotocol.kotlin.sdk.types.toJSON
+import io.modelcontextprotocol.kotlin.test.utils.actualPort
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.Json
+import kotlin.test.Test
+import kotlin.test.assertNotNull
+import io.ktor.client.engine.cio.CIO as ClientCIO
+
+private const val SESSION_ID_HEADER = "mcp-session-id"
+private const val PROTOCOL_VERSION_HEADER = "mcp-protocol-version"
+
+/**
+ * Integration tests for GET SSE stream reconnection using a real embedded CIO server.
+ *
+ * Verifies that the transport correctly evicts stale STANDALONE_SSE_STREAM_ID
+ * entries when a client reconnects after a disconnect, rather than silently
+ * rejecting the new stream.
+ */
+class StreamableHttpSseReconnectTest : AbstractStreamableHttpIntegrationTest() {
+
+ /**
+ * Verifies that after a GET SSE stream disconnects and the client
+ * immediately reconnects, the server evicts the stale stream mapping
+ * and allows the new stream to succeed.
+ */
+ @Test
+ fun `GET SSE reconnect after disconnect should succeed`(): Unit = runBlocking(Dispatchers.IO) {
+ var server: StreamableHttpTestServer? = null
+ var httpClient: HttpClient? = null
+
+ try {
+ server = initTestServer("reconnect-test")
+ val port = server.ktorServer.actualPort()
+ val mcpUrl = "http://$URL:$port/mcp"
+
+ httpClient = HttpClient(ClientCIO) { install(SSE) }
+
+ // Step 1: Initialize session via POST
+ val initResponse = httpClient.post(mcpUrl) {
+ contentType(ContentType.Application.Json)
+ header(
+ HttpHeaders.Accept,
+ "${ContentType.Application.Json}, ${ContentType.Text.EventStream}",
+ )
+ setBody(Json.encodeToString(buildInitPayload()))
+ }
+ initResponse.status shouldBe HttpStatusCode.OK
+ val sessionId = assertNotNull(initResponse.headers[SESSION_ID_HEADER])
+
+ // Step 2: Open GET SSE stream, consume the flush event, then disconnect
+ httpClient.prepareGet(mcpUrl) {
+ header(HttpHeaders.Accept, ContentType.Text.EventStream.toString())
+ header(SESSION_ID_HEADER, sessionId)
+ header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION)
+ }.execute { response ->
+ response.status shouldBe HttpStatusCode.OK
+ response.bodyAsChannel().readUTF8Line()
+ }
+
+ // Step 3: Immediately reconnect. The transport detects that the
+ // previous stream's coroutine is no longer active and evicts the
+ // stale mapping, allowing the new stream to succeed.
+ httpClient.prepareGet(mcpUrl) {
+ header(HttpHeaders.Accept, ContentType.Text.EventStream.toString())
+ header(SESSION_ID_HEADER, sessionId)
+ header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION)
+ }.execute { response ->
+ response.status shouldBe HttpStatusCode.OK
+ response.headers[SESSION_ID_HEADER] shouldBe sessionId
+
+ val channel = response.bodyAsChannel()
+ val firstLine = channel.readUTF8Line()
+ firstLine.shouldNotBeNull()
+ channel.isClosedForRead shouldBe false
+ }
+ } finally {
+ httpClient?.close()
+ server?.ktorServer?.stopSuspend(1000, 2000)
+ }
+ }
+
+ /**
+ * Verifies that a second concurrent GET SSE stream on the same session
+ * closes the old stream and takes over. The new stream should be live.
+ */
+ @Test
+ fun `concurrent GET SSE stream closes old stream and takes over`(): Unit = runBlocking(Dispatchers.IO) {
+ var server: StreamableHttpTestServer? = null
+ var httpClient: HttpClient? = null
+
+ try {
+ server = initTestServer("takeover-test")
+ val port = server.ktorServer.actualPort()
+ val mcpUrl = "http://$URL:$port/mcp"
+
+ httpClient = HttpClient(ClientCIO) { install(SSE) }
+
+ // Step 1: Initialize session via POST
+ val initResponse = httpClient.post(mcpUrl) {
+ contentType(ContentType.Application.Json)
+ header(
+ HttpHeaders.Accept,
+ "${ContentType.Application.Json}, ${ContentType.Text.EventStream}",
+ )
+ setBody(Json.encodeToString(buildInitPayload()))
+ }
+ initResponse.status shouldBe HttpStatusCode.OK
+ val sessionId = assertNotNull(initResponse.headers[SESSION_ID_HEADER])
+
+ // Step 2: Open first GET SSE stream and keep it open
+ httpClient.prepareGet(mcpUrl) {
+ header(HttpHeaders.Accept, ContentType.Text.EventStream.toString())
+ header(SESSION_ID_HEADER, sessionId)
+ header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION)
+ }.execute { firstResponse ->
+ firstResponse.status shouldBe HttpStatusCode.OK
+ firstResponse.bodyAsChannel().readUTF8Line()
+
+ // Step 3: Open a second GET — closes old stream, new one takes over
+ httpClient.prepareGet(mcpUrl) {
+ header(HttpHeaders.Accept, ContentType.Text.EventStream.toString())
+ header(SESSION_ID_HEADER, sessionId)
+ header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION)
+ }.execute { secondResponse ->
+ secondResponse.status shouldBe HttpStatusCode.OK
+ secondResponse.headers[SESSION_ID_HEADER] shouldBe sessionId
+
+ // New stream is alive
+ val secondChannel = secondResponse.bodyAsChannel()
+ val firstLine = secondChannel.readUTF8Line()
+ firstLine.shouldNotBeNull()
+ secondChannel.isClosedForRead shouldBe false
+ }
+ }
+ } finally {
+ httpClient?.close()
+ server?.ktorServer?.stopSuspend(1000, 2000)
+ }
+ }
+
+ private fun buildInitPayload(): JSONRPCRequest = InitializeRequest(
+ InitializeRequestParams(
+ protocolVersion = LATEST_PROTOCOL_VERSION,
+ capabilities = ClientCapabilities(),
+ clientInfo = Implementation(name = "reconnect-test-client", version = "1.0.0"),
+ ),
+ ).toJSON()
+}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt
index 81a3b41..a1cf797 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt
@@ -1,5 +1,7 @@
package io.modelcontextprotocol.kotlin.sdk.server
+import io.kotest.assertions.nondeterministic.continually
+import io.kotest.assertions.nondeterministic.eventually
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
@@ -17,6 +19,9 @@ import kotlinx.serialization.json.JsonPrimitive
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource
+import java.util.concurrent.CopyOnWriteArrayList
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
class ClientConnectionLoggingTest : AbstractServerFeaturesTest() {
@@ -56,7 +61,7 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() {
@ParameterizedTest
@EnumSource(LoggingLevel::class)
fun `notification should filter logging messages below level`(minLevel: LoggingLevel): Unit = runBlocking {
- val receivedMessages = mutableListOf()
+ val receivedMessages = CopyOnWriteArrayList()
client.setNotificationHandler(Method.Defined.NotificationsMessage) {
receivedMessages.add(it)
CompletableDeferred(Unit)
@@ -79,8 +84,10 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() {
client.callTool(CallToolRequest(CallToolRequestParams("test-notification-filtered")))
+ // Handlers dispatch on Dispatchers.Default after the handshake, so await the expected count
+ // before asserting order.
val expectedLevels = LoggingLevel.entries.filter { it >= minLevel }
- receivedMessages shouldHaveSize expectedLevels.size
+ eventually(5.seconds) { receivedMessages shouldHaveSize expectedLevels.size }
receivedMessages.map { it.params.level } shouldBe expectedLevels
}
@@ -115,7 +122,7 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() {
@ParameterizedTest
@EnumSource(LoggingLevel::class)
fun `sendLoggingMessage should filter messages below level`(minLevel: LoggingLevel): Unit = runBlocking {
- val receivedMessages = mutableListOf()
+ val receivedMessages = CopyOnWriteArrayList()
client.setNotificationHandler(Method.Defined.NotificationsMessage) {
receivedMessages.add(it)
CompletableDeferred(Unit)
@@ -138,14 +145,16 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() {
client.callTool(CallToolRequest(CallToolRequestParams("test-logging-level")))
+ // Handlers dispatch on Dispatchers.Default after the handshake, so await the expected count
+ // before asserting order.
val expectedLevels = LoggingLevel.entries.filter { it >= minLevel }
- receivedMessages shouldHaveSize expectedLevels.size
+ eventually(5.seconds) { receivedMessages shouldHaveSize expectedLevels.size }
receivedMessages.map { it.params.level } shouldBe expectedLevels
}
@Test
fun `sendLoggingMessage should send no messages when level is set to highest`(): Unit = runBlocking {
- val receivedMessages = mutableListOf()
+ val receivedMessages = CopyOnWriteArrayList()
client.setNotificationHandler(Method.Defined.NotificationsMessage) {
receivedMessages.add(it)
CompletableDeferred(Unit)
@@ -167,6 +176,7 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() {
}
client.callTool(CallToolRequest(CallToolRequestParams("test-logging-highest")))
- receivedMessages.shouldBeEmpty()
+ // Handlers dispatch asynchronously, so hold for a window to confirm nothing ever arrives.
+ continually(500.milliseconds) { receivedMessages.shouldBeEmpty() }
}
}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt
index 71f8129..581e5ae 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt
@@ -7,6 +7,9 @@ import io.kotest.matchers.string.shouldNotBeBlank
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotification
+import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotificationParams
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest
@@ -59,6 +62,7 @@ class ClientConnectionTest : AbstractServerFeaturesTest() {
override fun getClientCapabilities(): ClientCapabilities = ClientCapabilities(
roots = ClientCapabilities.Roots(listChanged = true),
+ elicitation = ClientCapabilities.Elicitation(url = EmptyJsonObject),
)
private val sampleRoots = listOf(Root("file:///project", "Project Root"))
@@ -77,6 +81,8 @@ class ClientConnectionTest : AbstractServerFeaturesTest() {
val loggingMessage = onClientNotification(Method.Defined.NotificationsMessage)
val resourceUpdated =
onClientNotification(Method.Defined.NotificationsResourcesUpdated)
+ val elicitationComplete =
+ onClientNotification(Method.Defined.NotificationsElicitationComplete)
init {
onClientRequest(Method.Defined.RootsList) {
@@ -109,6 +115,12 @@ class ClientConnectionTest : AbstractServerFeaturesTest() {
withClue("sendResourceUpdated() delivered wrong URI") {
resourceUri shouldBe "test://res"
}
+ withClue("sendElicitationComplete() did not deliver a notification to the client") {
+ elicitationComplete.isCompleted shouldBe true
+ }
+ withClue("sendElicitationComplete() delivered wrong elicitationId") {
+ elicitationComplete.await().params.elicitationId shouldBe "elicit-123"
+ }
capturedSessionId.shouldNotBeBlank()
}
}
@@ -128,6 +140,9 @@ class ClientConnectionTest : AbstractServerFeaturesTest() {
),
)
sendResourceUpdated(ResourceUpdatedNotification(ResourceUpdatedNotificationParams("test://res")))
+ sendElicitationComplete(
+ ElicitationCompleteNotification(ElicitationCompleteNotificationParams(elicitationId = "elicit-123")),
+ )
cap.sessionId.complete(sessionId)
}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt
new file mode 100644
index 0000000..f161d8e
--- /dev/null
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt
@@ -0,0 +1,251 @@
+package io.modelcontextprotocol.kotlin.sdk.server
+
+import io.modelcontextprotocol.kotlin.sdk.client.Client
+import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions
+import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.Implementation
+import io.modelcontextprotocol.kotlin.sdk.types.IncludeContext
+import io.modelcontextprotocol.kotlin.sdk.types.Method
+import io.modelcontextprotocol.kotlin.sdk.types.Role
+import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage
+import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.StopReason
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.Tool
+import io.modelcontextprotocol.kotlin.sdk.types.ToolChoice
+import io.modelcontextprotocol.kotlin.sdk.types.ToolResultContent
+import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
+import io.modelcontextprotocol.kotlin.sdk.types.ToolUseContent
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.buildJsonObject
+import org.junit.jupiter.api.assertDoesNotThrow
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class SamplingTest {
+
+ private val dummyTool = Tool(
+ name = "t",
+ inputSchema = ToolSchema(properties = buildJsonObject { }, required = emptyList()),
+ )
+
+ private val weatherTool = Tool(
+ name = "get_weather",
+ description = "Return the current temperature in Celsius.",
+ inputSchema = ToolSchema(
+ properties = buildJsonObject {
+ put("location", buildJsonObject { put("type", JsonPrimitive("string")) })
+ },
+ required = listOf("location"),
+ ),
+ )
+
+ private val minimalMessages = listOf(SamplingMessage(Role.User, TextContent("hi")))
+
+ /**
+ * Builds a connected [Server]+[Client] pair using [InMemoryTransport].
+ *
+ * @param clientCapabilities the capabilities the client advertises during initialize
+ * @param samplingHandler the handler the client uses to respond to sampling requests
+ * @return Pair of (server, sessionId) ready for [Server.createMessage] calls.
+ */
+ private fun buildPair(
+ clientCapabilities: ClientCapabilities = ClientCapabilities(
+ sampling = ClientCapabilities.Sampling(),
+ ),
+ samplingHandler: (CreateMessageRequest) -> CreateMessageResult = { _ ->
+ CreateMessageResult(role = Role.Assistant, content = TextContent("ok"), model = "m")
+ },
+ ): Pair {
+ val server = Server(
+ serverInfo = Implementation(name = "srv", version = "1.0"),
+ options = ServerOptions(capabilities = ServerCapabilities()),
+ )
+
+ val client = Client(
+ clientInfo = Implementation(name = "cli", version = "1.0"),
+ options = ClientOptions(capabilities = clientCapabilities),
+ )
+
+ client.setRequestHandler(Method.Defined.SamplingCreateMessage) { req, _ ->
+ samplingHandler(req)
+ }
+
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+
+ var sessionId: String? = null
+ runBlocking {
+ val sessionDeferred = CompletableDeferred()
+ launch { client.connect(clientTransport) }
+ launch {
+ val session = server.createSession(serverTransport)
+ sessionDeferred.complete(session.sessionId)
+ }
+ sessionId = sessionDeferred.await()
+ }
+
+ return Pair(server, checkNotNull(sessionId))
+ }
+
+ // ============================================================================
+ // Server.createMessage — capability enforcement (SEP-1577)
+ // ============================================================================
+
+ @Test
+ fun `tools field rejected when client has no sampling tools capability`() {
+ val (server, sessionId) = buildPair()
+ assertFailsWith {
+ runBlocking {
+ server.createMessage(
+ sessionId = sessionId,
+ params = CreateMessageRequest(
+ params = CreateMessageRequestParams(
+ maxTokens = 100,
+ messages = minimalMessages,
+ tools = listOf(dummyTool),
+ ),
+ ),
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `toolChoice field rejected when client has no sampling tools capability`() {
+ val (server, sessionId) = buildPair()
+ assertFailsWith {
+ runBlocking {
+ server.createMessage(
+ sessionId = sessionId,
+ params = CreateMessageRequest(
+ params = CreateMessageRequestParams(
+ maxTokens = 100,
+ messages = minimalMessages,
+ toolChoice = ToolChoice(),
+ ),
+ ),
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `includeContext with no sampling context capability succeeds with a warning`() {
+ val (server, sessionId) = buildPair()
+ assertDoesNotThrow {
+ runBlocking {
+ server.createMessage(
+ sessionId = sessionId,
+ params = CreateMessageRequest(
+ params = CreateMessageRequestParams(
+ maxTokens = 100,
+ messages = minimalMessages,
+ includeContext = IncludeContext.ThisServer,
+ ),
+ ),
+ )
+ }
+ }
+ }
+
+ // ============================================================================
+ // End-to-end tool-loop integration
+ // ============================================================================
+
+ @Test
+ fun `server sends tools, client returns tool_use then final text`() {
+ var turn = 0
+ val (server, sessionId) = buildPair(
+ clientCapabilities = ClientCapabilities(
+ sampling = ClientCapabilities.Sampling(tools = EmptyJsonObject),
+ ),
+ samplingHandler = { _ ->
+ turn++
+ if (turn == 1) {
+ CreateMessageResult(
+ role = Role.Assistant,
+ content = listOf(
+ TextContent("Let me check."),
+ ToolUseContent(
+ id = "call_1",
+ name = "get_weather",
+ input = buildJsonObject { put("location", JsonPrimitive("London")) },
+ ),
+ ),
+ model = "test",
+ stopReason = StopReason.ToolUse,
+ )
+ } else {
+ CreateMessageResult(
+ role = Role.Assistant,
+ content = TextContent("The temperature in London is 20°C."),
+ model = "test",
+ stopReason = StopReason.EndTurn,
+ )
+ }
+ },
+ )
+
+ runBlocking {
+ val messages = mutableListOf(
+ SamplingMessage(Role.User, TextContent("What is the weather in London?")),
+ )
+
+ // — Turn 1: server sends tools, expects tool_use stop reason
+ val first = server.createMessage(
+ sessionId = sessionId,
+ params = CreateMessageRequest(
+ params = CreateMessageRequestParams(
+ maxTokens = 256,
+ messages = messages.toList(),
+ tools = listOf(weatherTool),
+ toolChoice = ToolChoice(mode = ToolChoice.Mode.Auto),
+ ),
+ ),
+ )
+ assertEquals(StopReason.ToolUse, first.stopReason)
+ assertEquals(2, first.content.size)
+
+ // — Append assistant turn and inject tool result
+ messages.add(SamplingMessage(Role.Assistant, first.content))
+ val toolUse = first.content.filterIsInstance().single()
+ messages.add(
+ SamplingMessage(
+ Role.User,
+ ToolResultContent(
+ toolUseId = toolUse.id,
+ content = listOf(TextContent("""{"tempC":20}""")),
+ ),
+ ),
+ )
+
+ // — Turn 2: server sends updated history, expects final text
+ val second = server.createMessage(
+ sessionId = sessionId,
+ params = CreateMessageRequest(
+ params = CreateMessageRequestParams(
+ maxTokens = 256,
+ messages = messages.toList(),
+ tools = listOf(weatherTool),
+ toolChoice = ToolChoice(mode = ToolChoice.Mode.Auto),
+ ),
+ ),
+ )
+ assertEquals(StopReason.EndTurn, second.stopReason)
+ assertEquals(1, second.content.size)
+ val text = second.content.single() as TextContent
+ assertEquals("The temperature in London is 20°C.", text.text)
+
+ server.close()
+ }
+ }
+}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt
index 1eb828f..52f6908 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt
@@ -1,5 +1,6 @@
package io.modelcontextprotocol.kotlin.sdk.server
+import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import io.kotest.matchers.collections.shouldHaveSize
@@ -57,6 +58,39 @@ class ServerBulkFeaturesTest : AbstractServerFeaturesTest() {
tools.map { it.name } shouldContainExactlyInAnyOrder listOf("existing", "new-a", "new-b")
}
+ @Test
+ fun `addTools should throw and register nothing when a tool is already registered`() = runTest {
+ server.addTool("existing", "Existing") { CallToolResult(emptyList()) }
+
+ shouldThrow {
+ server.addTools(
+ listOf(
+ RegisteredTool(Tool("new-a", ToolSchema())) { CallToolResult(emptyList()) },
+ RegisteredTool(Tool("existing", ToolSchema())) { CallToolResult(emptyList()) },
+ ),
+ )
+ }
+
+ val tools = client.listTools().tools
+
+ tools shouldHaveSize 1
+ tools.map { it.name } shouldContainExactlyInAnyOrder listOf("existing")
+ }
+
+ @Test
+ fun `addTools should throw when batch contains duplicate names`() = runTest {
+ shouldThrow {
+ server.addTools(
+ listOf(
+ RegisteredTool(Tool("dup", ToolSchema())) { CallToolResult(emptyList()) },
+ RegisteredTool(Tool("dup", ToolSchema())) { CallToolResult(emptyList()) },
+ ),
+ )
+ }
+
+ client.listTools().tools.shouldBeEmpty()
+ }
+
// ── addPrompts ─────────────────────────────────────────────────────────────
@Test
@@ -89,6 +123,25 @@ class ServerBulkFeaturesTest : AbstractServerFeaturesTest() {
prompts.map { it.name } shouldContainExactlyInAnyOrder listOf("existing-prompt", "new-p")
}
+ @Test
+ fun `addPrompts should throw and register nothing when a prompt is already registered`() = runTest {
+ server.addPrompt("existing-prompt", "Existing") { GetPromptResult(messages = emptyList()) }
+
+ shouldThrow {
+ server.addPrompts(
+ listOf(
+ RegisteredPrompt(Prompt("new-p")) { GetPromptResult(messages = emptyList()) },
+ RegisteredPrompt(Prompt("existing-prompt")) { GetPromptResult(messages = emptyList()) },
+ ),
+ )
+ }
+
+ val prompts = client.listPrompts().prompts
+
+ prompts shouldHaveSize 1
+ prompts.map { it.name } shouldContainExactlyInAnyOrder listOf("existing-prompt")
+ }
+
// ── addResources ───────────────────────────────────────────────────────────
@Test
@@ -123,6 +176,27 @@ class ServerBulkFeaturesTest : AbstractServerFeaturesTest() {
resources.map { it.uri } shouldContainExactlyInAnyOrder listOf("test://existing-res", "test://new-res")
}
+ @Test
+ fun `addResources should throw and register nothing when a resource is already registered`() = runTest {
+ server.addResource("test://existing-res", "Existing", "Existing resource") {
+ ReadResourceResult(emptyList())
+ }
+
+ shouldThrow {
+ server.addResources(
+ listOf(
+ RegisteredResource(Resource("test://new-res", "New")) { ReadResourceResult(emptyList()) },
+ RegisteredResource(Resource("test://existing-res", "Dup")) { ReadResourceResult(emptyList()) },
+ ),
+ )
+ }
+
+ val resources = client.listResources().resources
+
+ resources shouldHaveSize 1
+ resources.map { it.uri } shouldContainExactlyInAnyOrder listOf("test://existing-res")
+ }
+
// ── Partial-batch remove ───────────────────────────────────────────────────
@Test
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt
index d7ae46d..44b1e42 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt
@@ -1,5 +1,8 @@
package io.modelcontextprotocol.kotlin.sdk.server
+import io.kotest.matchers.string.shouldContain
+import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
+import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.Method
@@ -110,6 +113,26 @@ class ServerPromptsTest : AbstractServerFeaturesTest() {
assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist")
}
+ @Test
+ fun `addPrompt should throw when prompt with same name is already registered`() = runTest {
+ server.addPrompt(Prompt("test-prompt", "Original Prompt", null)) {
+ GetPromptResult(description = "original", messages = emptyList())
+ }
+
+ val exception = assertThrows {
+ server.addPrompt(Prompt("test-prompt", "Duplicate Prompt", null)) {
+ GetPromptResult(description = "duplicate", messages = emptyList())
+ }
+ }
+ exception.message.orEmpty() shouldContain "Prompt \"test-prompt\" is already registered"
+
+ // The original registration is intact
+ val prompts = client.listPrompts().prompts
+ assertEquals(1, prompts.size, "Only the original prompt should be registered")
+ val result = client.getPrompt(GetPromptRequest(GetPromptRequestParams("test-prompt")))
+ assertEquals("original", result.description)
+ }
+
@Test
fun `removePrompt should throw when prompts capability is not supported`() = runTest {
var promptListChangedNotificationReceived = false
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt
index c8ace19..8405469 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt
@@ -5,6 +5,7 @@ import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.maps.shouldContainKey
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest
import io.modelcontextprotocol.kotlin.sdk.types.McpException
@@ -162,6 +163,24 @@ class ServerResourceTemplateTest : AbstractServerFeaturesTest() {
removed shouldBe false
}
+ @Test
+ fun `addResourceTemplate should throw when template with same uriTemplate is already registered`() {
+ server.addResourceTemplate("test://items/{id}", "Item") { _, _ ->
+ ReadResourceResult(emptyList())
+ }
+
+ val exception = assertThrows {
+ server.addResourceTemplate("test://items/{id}", "Duplicate Item") { _, _ ->
+ ReadResourceResult(emptyList())
+ }
+ }
+ exception.message.orEmpty() shouldContain "ResourceTemplate \"test://items/{id}\" is already registered"
+
+ // The original registration is intact
+ server.resourceTemplates shouldHaveSize 1
+ server.resourceTemplates[0].name shouldBe "Item"
+ }
+
@Test
fun `addResourceTemplate should throw when resources capability is not supported`() {
val noResourcesServer = Server(
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt
index fafc3c8..a830788 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt
@@ -1,7 +1,10 @@
package io.modelcontextprotocol.kotlin.sdk.server
+import io.kotest.matchers.string.shouldContain
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.Method
+import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest
+import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult
import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
@@ -115,6 +118,24 @@ class ServerResourcesTest : AbstractServerFeaturesTest() {
)
}
+ @Test
+ fun `addResource should throw when resource with same uri is already registered`() = runTest {
+ server.addResource("test://resource", "Original", "Original resource") {
+ ReadResourceResult(listOf(TextResourceContents(text = "original", uri = "test://resource")))
+ }
+
+ val exception = assertThrows {
+ server.addResource("test://resource", "Duplicate", "Duplicate resource") {
+ ReadResourceResult(listOf(TextResourceContents(text = "duplicate", uri = "test://resource")))
+ }
+ }
+ exception.message.orEmpty() shouldContain "Resource \"test://resource\" is already registered"
+
+ // The original registration is intact
+ val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://resource")))
+ assertEquals("original", (result.contents.single() as TextResourceContents).text)
+ }
+
@Test
fun `removeResource should throw when resources capability is not supported`() = runTest {
// Create server without resources capability
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt
new file mode 100644
index 0000000..38b169a
--- /dev/null
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt
@@ -0,0 +1,194 @@
+package io.modelcontextprotocol.kotlin.sdk.server
+
+import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.Implementation
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse
+import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
+import io.modelcontextprotocol.kotlin.sdk.types.RPCError
+import io.modelcontextprotocol.kotlin.sdk.types.RequestId
+import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.toJSON
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.joinAll
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.put
+import kotlinx.serialization.json.putJsonObject
+import org.junit.jupiter.api.Test
+import java.util.concurrent.CopyOnWriteArrayList
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class ServerSessionInitializeTest {
+
+ private fun createSession(): ServerSession = ServerSession(
+ serverInfo = Implementation(name = "test-server", version = "1.0"),
+ options = ServerOptions(capabilities = ServerCapabilities()),
+ instructions = null,
+ )
+
+ private fun createInitializeRequest(clientName: String = "test-client"): InitializeRequest = InitializeRequest(
+ InitializeRequestParams(
+ protocolVersion = LATEST_PROTOCOL_VERSION,
+ capabilities = ClientCapabilities(),
+ clientInfo = Implementation(name = clientName, version = "1.0"),
+ ),
+ )
+
+ private fun createMalformedInitializeRequest(): JSONRPCRequest = JSONRPCRequest(
+ id = RequestId(1),
+ method = "initialize",
+ params = buildJsonObject {
+ putJsonObject("capabilities") {}
+ putJsonObject("clientInfo") {
+ put("name", "repro")
+ put("version", "0.1.0")
+ }
+ },
+ )
+
+ @Test
+ fun `should handle first initialize request successfully`() = runTest {
+ val session = createSession()
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+
+ assertNull(session.clientCapabilities)
+ assertNull(session.clientVersion)
+
+ val responseDone = CompletableDeferred()
+ clientTransport.onMessage { message ->
+ if (message is JSONRPCResponse) {
+ responseDone.complete(message)
+ }
+ }
+
+ session.connect(serverTransport)
+ clientTransport.send(createInitializeRequest().toJSON())
+
+ val response = responseDone.await()
+ assertNotNull(response.result)
+ assertNotNull(session.clientCapabilities)
+ assertEquals("test-client", session.clientVersion?.name)
+ }
+
+ // Both initialize requests arrive before notifications/initialized, in the serial dispatch
+ // phase, so processing and response order stay deterministic.
+ @Test
+ fun `should classify malformed initialize params as invalid params`() = runTest {
+ val session = createSession()
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+
+ val responseDone = CompletableDeferred()
+ clientTransport.onMessage { message ->
+ if (message is JSONRPCError) {
+ responseDone.complete(message)
+ }
+ }
+
+ session.connect(serverTransport)
+ clientTransport.send(createMalformedInitializeRequest())
+
+ val response = responseDone.await()
+ assertEquals(RPCError.ErrorCode.INVALID_PARAMS, response.error.code)
+ assertFalse(response.error.message.contains("kotlinx.serialization"))
+ assertNull(session.clientCapabilities)
+ assertNull(session.clientVersion)
+ }
+
+ @Test
+ fun `should reject duplicate initialize request`() = runTest {
+ val session = createSession()
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+
+ val responses = CopyOnWriteArrayList()
+ val secondResponseDone = CompletableDeferred()
+
+ clientTransport.onMessage { message ->
+ when (message) {
+ is JSONRPCResponse, is JSONRPCError -> {
+ responses.add(message)
+ if (responses.size == 2) secondResponseDone.complete(Unit)
+ }
+
+ else -> {}
+ }
+ }
+
+ session.connect(serverTransport)
+
+ // First initialize should succeed
+ clientTransport.send(createInitializeRequest(clientName = "first-client").toJSON())
+
+ // Second initialize should be rejected
+ clientTransport.send(createInitializeRequest(clientName = "second-client").toJSON())
+
+ secondResponseDone.await()
+
+ assertEquals(2, responses.size)
+ assertTrue(responses[0] is JSONRPCResponse, "First response should be success")
+ assertTrue(responses[1] is JSONRPCError, "Second response should be error")
+ assertEquals(RPCError.ErrorCode.INVALID_REQUEST, (responses[1] as JSONRPCError).error.code)
+
+ // Capabilities still reflect the first client, not overwritten
+ assertEquals("first-client", session.clientVersion?.name)
+ }
+
+ @Test
+ fun `should reject concurrent initialize requests - only first succeeds`() = runTest {
+ val session = createSession()
+ val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
+
+ val n = 10
+ val allResponsesDone = CompletableDeferred()
+ val successes = CopyOnWriteArrayList()
+ val errors = CopyOnWriteArrayList()
+
+ clientTransport.onMessage { message ->
+ when (message) {
+ is JSONRPCResponse -> successes.add(message)
+ is JSONRPCError -> errors.add(message)
+ else -> {}
+ }
+ if (successes.size + errors.size == n) {
+ allResponsesDone.complete(Unit)
+ }
+ }
+
+ session.connect(serverTransport)
+
+ // Use Dispatchers.Default for true parallelism on JVM
+ withContext(Dispatchers.Default) {
+ val barrier = CompletableDeferred()
+ val jobs = (1..n).map { i ->
+ launch {
+ barrier.await()
+ clientTransport.send(
+ createInitializeRequest(clientName = "client-$i").toJSON(),
+ )
+ }
+ }
+ barrier.complete(Unit)
+ jobs.joinAll()
+ }
+
+ allResponsesDone.await()
+
+ assertEquals(1, successes.size, "Exactly one initialize should succeed")
+ assertEquals(n - 1, errors.size, "All other initializes should be rejected")
+ errors.forEach { error ->
+ assertEquals(RPCError.ErrorCode.INVALID_REQUEST, error.error.code)
+ }
+ }
+}
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt
index 3572082..9ac744b 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt
@@ -11,6 +11,7 @@ import kotlinx.coroutines.test.runTest
import org.awaitility.kotlin.await
import org.awaitility.kotlin.untilAsserted
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@@ -81,6 +82,27 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() {
}
}
+ @Test
+ fun `addTool should not send notification when duplicate is rejected`() = runTest {
+ // Track notifications
+ val notifications = mutableListOf()
+ client.setNotificationHandler(
+ Method.Defined.NotificationsToolsListChanged,
+ ) { notification ->
+ notifications.add(notification)
+ CompletableDeferred(Unit)
+ }
+
+ server.addTool("test-tool", "Test Tool") { CallToolResult(emptyList()) }
+ assertThrows {
+ server.addTool("test-tool", "Duplicate Tool") { CallToolResult(emptyList()) }
+ }
+ // Close the server to stop processing further events and flush notifications
+ server.close()
+
+ assertEquals(1, notifications.size, "Only the first registration should send a notification")
+ }
+
@Test
fun `notification should not be send when removed tool does not exists`() = runTest {
// Track notifications
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt
index c2e8201..1b3df91 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt
@@ -1,5 +1,8 @@
package io.modelcontextprotocol.kotlin.sdk.server
+import io.kotest.matchers.string.shouldContain
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.Method
@@ -98,6 +101,52 @@ class ServerToolsTest : AbstractServerFeaturesTest() {
assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist")
}
+ @Test
+ fun `addTool should succeed with non-conforming tool name`() = runTest {
+ server.addTool("my invalid tool!", "Tool with non-conforming name", ToolSchema()) {
+ CallToolResult(listOf(TextContent("It works")))
+ }
+
+ val result = server.removeTool("my invalid tool!")
+ assertTrue(result, "Tool with non-conforming name should have been registered and removable")
+ }
+
+ @Test
+ fun `addTool should throw when tool with same name is already registered`() = runTest {
+ server.addTool("test-tool", "Original Tool") {
+ CallToolResult(listOf(TextContent("Original result")))
+ }
+
+ val exception = assertThrows {
+ server.addTool("test-tool", "Duplicate Tool") {
+ CallToolResult(listOf(TextContent("Duplicate result")))
+ }
+ }
+ exception.message.orEmpty() shouldContain "Tool \"test-tool\" is already registered"
+
+ // The original registration is intact
+ val tools = client.listTools().tools
+ assertEquals(1, tools.size, "Only the original tool should be registered")
+ val result = client.callTool(CallToolRequest(CallToolRequestParams("test-tool")))
+ assertEquals("Original result", (result.content.single() as TextContent).text)
+ }
+
+ @Test
+ fun `addTool should succeed after removing existing tool`() = runTest {
+ server.addTool("test-tool", "Original Tool") {
+ CallToolResult(listOf(TextContent("Original result")))
+ }
+
+ server.removeTool("test-tool")
+ server.addTool("test-tool", "Replacement Tool") {
+ CallToolResult(listOf(TextContent("Replacement result")))
+ }
+
+ assertEquals(1, client.listTools().tools.size, "Re-adding after removal should not duplicate the tool")
+ val result = client.callTool(CallToolRequest(CallToolRequestParams("test-tool")))
+ assertEquals("Replacement result", (result.content.single() as TextContent).text)
+ }
+
@Test
fun `removeTool should throw when tools capability is not supported`() = runTest {
var toolListChangedNotificationReceived = false
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt
index ea246db..4d798ce 100644
--- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt
+++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt
@@ -6,7 +6,6 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
-import io.modelcontextprotocol.kotlin.sdk.server.ServerSession
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult
@@ -16,9 +15,6 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult
import io.modelcontextprotocol.kotlin.sdk.types.Method
import io.modelcontextprotocol.kotlin.sdk.types.Resource
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.joinAll
-import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
@@ -43,18 +39,9 @@ class ChannelTransportTest {
clientInfo = Implementation(name = "test client", version = "1.0"),
)
- val serverSessionResult = CompletableDeferred()
+ val serverSession = server.createSession(serverTransport)
+ client.connect(clientTransport)
- listOf(
- launch {
- client.connect(clientTransport)
- },
- launch {
- serverSessionResult.complete(server.createSession(serverTransport))
- },
- ).joinAll()
-
- val serverSession = serverSessionResult.await()
serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ ->
InitializeResult(
protocolVersion = LATEST_PROTOCOL_VERSION,
diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json
index 1f26245..ffb53fc 100644
--- a/integration-test/src/jvmTest/typescript/package-lock.json
+++ b/integration-test/src/jvmTest/typescript/package-lock.json
@@ -8,23 +8,23 @@
"name": "mcp-typescript-test-sdk",
"version": "1.0.0",
"dependencies": {
- "@modelcontextprotocol/sdk": "^1.28.0",
+ "@modelcontextprotocol/sdk": "^1.30.0",
"express": "^5.2.1",
- "zod": "^4.3.5"
+ "zod": "^4.6.2"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
- "@types/node": "^24.10.6",
- "cors": "^2.8.5",
- "tsx": "^4.21.0",
- "typescript": "^5.9.3"
+ "@types/node": "^26.5.1",
+ "cors": "^2.8.6",
+ "tsx": "^4.23.13",
+ "typescript": "^7.0.2"
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
- "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
"cpu": [
"ppc64"
],
@@ -39,9 +39,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
- "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
"cpu": [
"arm"
],
@@ -56,9 +56,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
- "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
"cpu": [
"arm64"
],
@@ -73,9 +73,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
- "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
"cpu": [
"x64"
],
@@ -90,9 +90,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
- "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
"cpu": [
"arm64"
],
@@ -107,9 +107,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
- "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
"cpu": [
"x64"
],
@@ -124,9 +124,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
- "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
"cpu": [
"arm64"
],
@@ -141,9 +141,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
- "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
"cpu": [
"x64"
],
@@ -158,9 +158,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
- "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
"cpu": [
"arm"
],
@@ -175,9 +175,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
- "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
"cpu": [
"arm64"
],
@@ -192,9 +192,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
- "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
"cpu": [
"ia32"
],
@@ -209,9 +209,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
- "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
"cpu": [
"loong64"
],
@@ -226,9 +226,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
- "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
"cpu": [
"mips64el"
],
@@ -243,9 +243,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
- "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
"cpu": [
"ppc64"
],
@@ -260,9 +260,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
- "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
"cpu": [
"riscv64"
],
@@ -277,9 +277,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
- "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
"cpu": [
"s390x"
],
@@ -294,9 +294,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
- "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
"cpu": [
"x64"
],
@@ -311,9 +311,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
- "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
"cpu": [
"arm64"
],
@@ -328,9 +328,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
- "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
"cpu": [
"x64"
],
@@ -345,9 +345,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
- "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
"cpu": [
"arm64"
],
@@ -362,9 +362,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
- "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
"cpu": [
"x64"
],
@@ -379,9 +379,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
- "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
"cpu": [
"arm64"
],
@@ -396,9 +396,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
- "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
"cpu": [
"x64"
],
@@ -413,9 +413,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
- "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
"cpu": [
"arm64"
],
@@ -430,9 +430,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
- "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
"cpu": [
"ia32"
],
@@ -447,9 +447,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
- "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
"cpu": [
"x64"
],
@@ -464,24 +464,24 @@
}
},
"node_modules/@hono/node-server": {
- "version": "1.19.11",
- "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
- "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.0.tgz",
+ "integrity": "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==",
"license": "MIT",
"engines": {
- "node": ">=18.14.1"
+ "node": ">=20"
},
"peerDependencies": {
"hono": "^4"
}
},
"node_modules/@modelcontextprotocol/sdk": {
- "version": "1.28.0",
- "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.28.0.tgz",
- "integrity": "sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw==",
+ "version": "1.30.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",
+ "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==",
"license": "MIT",
"dependencies": {
- "@hono/node-server": "^1.19.9",
+ "@hono/node-server": "^1.19.9 || ^2.0.5",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"content-type": "^1.0.5",
@@ -579,13 +579,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.10.6",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.6.tgz",
- "integrity": "sha512-B8h60xgJMR/xmgyX9fncRzEW9gCxoJjdenUhke2v1JGOd/V66KopmWrLPXi5oUI4VuiGK+d+HlXJjDRZMj21EQ==",
+ "version": "26.5.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.1.tgz",
+ "integrity": "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~7.16.0"
+ "undici-types": "~8.9.0"
}
},
"node_modules/@types/qs": {
@@ -623,6 +623,346 @@
"@types/node": "*"
}
},
+ "node_modules/@typescript/typescript-aix-ppc64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
+ "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
+ "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
+ "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
+ "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
+ "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
+ "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-loong64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
+ "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-mips64el": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
+ "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-ppc64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
+ "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-riscv64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
+ "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-s390x": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
+ "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
+ "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
+ "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
+ "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-sunos-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
+ "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
+ "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
+ "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -670,20 +1010,20 @@
}
},
"node_modules/body-parser": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
- "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
- "content-type": "^1.0.5",
+ "content-type": "^2.0.0",
"debug": "^4.4.3",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.7.0",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
"on-finished": "^2.4.1",
- "qs": "^6.14.1",
- "raw-body": "^3.0.1",
- "type-is": "^2.0.1"
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
},
"engines": {
"node": ">=18"
@@ -693,6 +1033,19 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/body-parser/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -772,9 +1125,9 @@
}
},
"node_modules/cors": {
- "version": "2.8.5",
- "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
- "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
@@ -782,6 +1135,10 @@
},
"engines": {
"node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": {
@@ -872,9 +1229,9 @@
}
},
"node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -884,9 +1241,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
- "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -897,32 +1254,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.2",
- "@esbuild/android-arm": "0.27.2",
- "@esbuild/android-arm64": "0.27.2",
- "@esbuild/android-x64": "0.27.2",
- "@esbuild/darwin-arm64": "0.27.2",
- "@esbuild/darwin-x64": "0.27.2",
- "@esbuild/freebsd-arm64": "0.27.2",
- "@esbuild/freebsd-x64": "0.27.2",
- "@esbuild/linux-arm": "0.27.2",
- "@esbuild/linux-arm64": "0.27.2",
- "@esbuild/linux-ia32": "0.27.2",
- "@esbuild/linux-loong64": "0.27.2",
- "@esbuild/linux-mips64el": "0.27.2",
- "@esbuild/linux-ppc64": "0.27.2",
- "@esbuild/linux-riscv64": "0.27.2",
- "@esbuild/linux-s390x": "0.27.2",
- "@esbuild/linux-x64": "0.27.2",
- "@esbuild/netbsd-arm64": "0.27.2",
- "@esbuild/netbsd-x64": "0.27.2",
- "@esbuild/openbsd-arm64": "0.27.2",
- "@esbuild/openbsd-x64": "0.27.2",
- "@esbuild/openharmony-arm64": "0.27.2",
- "@esbuild/sunos-x64": "0.27.2",
- "@esbuild/win32-arm64": "0.27.2",
- "@esbuild/win32-ia32": "0.27.2",
- "@esbuild/win32-x64": "0.27.2"
+ "@esbuild/aix-ppc64": "0.28.1",
+ "@esbuild/android-arm": "0.28.1",
+ "@esbuild/android-arm64": "0.28.1",
+ "@esbuild/android-x64": "0.28.1",
+ "@esbuild/darwin-arm64": "0.28.1",
+ "@esbuild/darwin-x64": "0.28.1",
+ "@esbuild/freebsd-arm64": "0.28.1",
+ "@esbuild/freebsd-x64": "0.28.1",
+ "@esbuild/linux-arm": "0.28.1",
+ "@esbuild/linux-arm64": "0.28.1",
+ "@esbuild/linux-ia32": "0.28.1",
+ "@esbuild/linux-loong64": "0.28.1",
+ "@esbuild/linux-mips64el": "0.28.1",
+ "@esbuild/linux-ppc64": "0.28.1",
+ "@esbuild/linux-riscv64": "0.28.1",
+ "@esbuild/linux-s390x": "0.28.1",
+ "@esbuild/linux-x64": "0.28.1",
+ "@esbuild/netbsd-arm64": "0.28.1",
+ "@esbuild/netbsd-x64": "0.28.1",
+ "@esbuild/openbsd-arm64": "0.28.1",
+ "@esbuild/openbsd-x64": "0.28.1",
+ "@esbuild/openharmony-arm64": "0.28.1",
+ "@esbuild/sunos-x64": "0.28.1",
+ "@esbuild/win32-arm64": "0.28.1",
+ "@esbuild/win32-ia32": "0.28.1",
+ "@esbuild/win32-x64": "0.28.1"
}
},
"node_modules/escape-html": {
@@ -1005,12 +1362,13 @@
}
},
"node_modules/express-rate-limit": {
- "version": "8.3.1",
- "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
- "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==",
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.0.tgz",
+ "integrity": "sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==",
"license": "MIT",
"dependencies": {
- "ip-address": "10.1.0"
+ "debug": "^4.4.3",
+ "ip-address": "^10.2.0"
},
"engines": {
"node": ">= 16"
@@ -1029,9 +1387,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
- "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
+ "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
"funding": [
{
"type": "github",
@@ -1143,19 +1501,6 @@
"node": ">= 0.4"
}
},
- "node_modules/get-tsconfig": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
- "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "resolve-pkg-maps": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
- }
- },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -1181,9 +1526,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -1193,9 +1538,9 @@
}
},
"node_modules/hono": {
- "version": "4.12.9",
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
- "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==",
+ "version": "4.13.7",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz",
+ "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
@@ -1244,9 +1589,9 @@
"license": "ISC"
},
"node_modules/ip-address": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
- "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz",
+ "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==",
"license": "MIT",
"engines": {
"node": ">= 12"
@@ -1425,9 +1770,9 @@
}
},
"node_modules/path-to-regexp": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
- "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz",
+ "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -1457,12 +1802,13 @@
}
},
"node_modules/qs": {
- "version": "6.14.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
- "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
+ "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
"license": "BSD-3-Clause",
"dependencies": {
- "side-channel": "^1.1.0"
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
},
"engines": {
"node": ">=0.6"
@@ -1504,16 +1850,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/resolve-pkg-maps": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
- "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
- }
- },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -1609,14 +1945,14 @@
}
},
"node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
@@ -1628,13 +1964,13 @@
}
},
"node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
+ "object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
@@ -1699,14 +2035,13 @@
}
},
"node_modules/tsx": {
- "version": "4.21.0",
- "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
- "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "version": "4.23.13",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz",
+ "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "esbuild": "~0.27.0",
- "get-tsconfig": "^4.7.5"
+ "esbuild": "~0.28.0"
},
"bin": {
"tsx": "dist/cli.mjs"
@@ -1719,37 +2054,75 @@
}
},
"node_modules/type-is": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
- "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"license": "MIT",
"dependencies": {
- "content-type": "^1.0.5",
+ "content-type": "^2.0.0",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
- "node": ">= 0.6"
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
+ "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
+ "tsc": "bin/tsc"
},
"engines": {
- "node": ">=14.17"
+ "node": ">=16.20.0"
+ },
+ "optionalDependencies": {
+ "@typescript/typescript-aix-ppc64": "7.0.2",
+ "@typescript/typescript-darwin-arm64": "7.0.2",
+ "@typescript/typescript-darwin-x64": "7.0.2",
+ "@typescript/typescript-freebsd-arm64": "7.0.2",
+ "@typescript/typescript-freebsd-x64": "7.0.2",
+ "@typescript/typescript-linux-arm": "7.0.2",
+ "@typescript/typescript-linux-arm64": "7.0.2",
+ "@typescript/typescript-linux-loong64": "7.0.2",
+ "@typescript/typescript-linux-mips64el": "7.0.2",
+ "@typescript/typescript-linux-ppc64": "7.0.2",
+ "@typescript/typescript-linux-riscv64": "7.0.2",
+ "@typescript/typescript-linux-s390x": "7.0.2",
+ "@typescript/typescript-linux-x64": "7.0.2",
+ "@typescript/typescript-netbsd-arm64": "7.0.2",
+ "@typescript/typescript-netbsd-x64": "7.0.2",
+ "@typescript/typescript-openbsd-arm64": "7.0.2",
+ "@typescript/typescript-openbsd-x64": "7.0.2",
+ "@typescript/typescript-sunos-x64": "7.0.2",
+ "@typescript/typescript-win32-arm64": "7.0.2",
+ "@typescript/typescript-win32-x64": "7.0.2"
}
},
"node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
+ "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
"dev": true,
"license": "MIT"
},
@@ -1793,9 +2166,9 @@
"license": "ISC"
},
"node_modules/zod": {
- "version": "4.3.5",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
- "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.2.tgz",
+ "integrity": "sha512-lh5RCAGFa1Cm2hjtNwLQhSs/AsqdWnTQaBER9fEwN/88pSh7KOtJavtBx/0VlkN/uFd61SwYmljLMDAsHlvzBQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json
index 3ce560c..b0147e4 100644
--- a/integration-test/src/jvmTest/typescript/package.json
+++ b/integration-test/src/jvmTest/typescript/package.json
@@ -9,16 +9,16 @@
"server:http": "tsx server/http-server.ts"
},
"dependencies": {
- "@modelcontextprotocol/sdk": "^1.28.0",
+ "@modelcontextprotocol/sdk": "^1.30.0",
"express": "^5.2.1",
- "zod": "^4.3.5"
+ "zod": "^4.6.2"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
- "@types/node": "^24.10.6",
- "cors": "^2.8.5",
- "tsx": "^4.21.0",
- "typescript": "^5.9.3"
+ "@types/node": "^26.5.1",
+ "cors": "^2.8.6",
+ "tsx": "^4.23.13",
+ "typescript": "^7.0.2"
}
}
diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api
index da0e752..d856f5b 100644
--- a/kotlin-sdk-client/api/kotlin-sdk-client.api
+++ b/kotlin-sdk-client/api/kotlin-sdk-client.api
@@ -34,6 +34,7 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextp
public final fun removeRoot (Ljava/lang/String;)Z
public final fun removeRoots (Ljava/util/List;)I
public final fun sendRootsListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public final fun setElicitationCompleteHandler (Lkotlin/jvm/functions/Function1;)V
public final fun setElicitationHandler (Lkotlin/jvm/functions/Function1;)V
public final fun setLoggingLevel (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun setLoggingLevel$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
@@ -41,6 +42,7 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextp
public static synthetic fun subscribeResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public final fun unsubscribeResource (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun unsubscribeResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+ protected fun wrapRequestHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function3;
}
public final class io/modelcontextprotocol/kotlin/sdk/client/ClientKt {
@@ -50,8 +52,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/ClientKt {
public final class io/modelcontextprotocol/kotlin/sdk/client/ClientOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions {
public fun ()V
- public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Z)V
- public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;ZLkotlin/coroutines/CoroutineContext;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;ZLkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
}
@@ -100,8 +102,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/StdioClientTranspor
}
public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport {
- public fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;)V
- public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;ILkotlin/jvm/functions/Function1;)V
+ public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;ILkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getProtocolVersion ()Ljava/lang/String;
diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts
index f50266c..fbb1300 100644
--- a/kotlin-sdk-client/build.gradle.kts
+++ b/kotlin-sdk-client/build.gradle.kts
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
id("mcp.multiplatform")
+ id("mcp.detekt")
id("mcp.publishing")
id("mcp.dokka")
alias(libs.plugins.kotlinx.binary.compatibility.validator)
@@ -14,10 +15,8 @@ kotlin {
iosArm64()
iosX64()
iosSimulatorArm64()
- watchosX64()
watchosArm64()
watchosSimulatorArm64()
- tvosX64()
tvosArm64()
tvosSimulatorArm64()
js {
@@ -56,7 +55,6 @@ kotlin {
implementation(libs.ktor.client.apache5)
implementation(libs.mockk)
implementation(libs.junit.jupiter.params)
- implementation(libs.mokksy)
implementation(dependencies.platform(libs.netty.bom))
runtimeOnly(libs.slf4j.simple)
}
diff --git a/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml b/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml
deleted file mode 100644
index 5cddbe1..0000000
--- a/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- CyclomaticComplexMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize
- LongMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize
- MaxLineLength:StdioClientTransport.kt:StdioClientTransport$*
- ThrowsCount:SseClientTransport.kt:SseClientTransport$private suspend fun CoroutineScope.collectMessages
- ThrowsCount:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$@Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend
- TooGenericExceptionCaught:Client.kt:Client$error: Throwable
- TooGenericExceptionCaught:SseClientTransport.kt:SseClientTransport$e: Throwable
- TooGenericExceptionCaught:StdioClientTransport.kt:StdioClientTransport$e: Throwable
-
-
diff --git a/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml b/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml
deleted file mode 100644
index cab4322..0000000
--- a/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}"""
- MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}"""
-
-
diff --git a/kotlin-sdk-client/detekt-baseline-main.xml b/kotlin-sdk-client/detekt-baseline-main.xml
index 8411ab0..c34e75c 100644
--- a/kotlin-sdk-client/detekt-baseline-main.xml
+++ b/kotlin-sdk-client/detekt-baseline-main.xml
@@ -2,18 +2,6 @@
- CyclomaticComplexMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize
- InjectDispatcher:StdioClientTransport.kt:StdioClientTransport$Default
- InjectDispatcher:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$Default
- LongMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize
- MaxLineLength:StdioClientTransport.kt:StdioClientTransport$*
- NoNameShadowing:StdioClientTransport.kt:StdioClientTransport$source
- ThrowsCount:SseClientTransport.kt:SseClientTransport$private suspend fun CoroutineScope.collectMessages
- ThrowsCount:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$@Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend
- TooGenericExceptionCaught:Client.kt:Client$error: Throwable
- TooGenericExceptionCaught:SseClientTransport.kt:SseClientTransport$e: Throwable
- TooGenericExceptionCaught:StdioClientTransport.kt:StdioClientTransport$e: Throwable
- UseCheckOrError:Client.kt:Client$throw IllegalStateException( "Server does not support resource subscriptions (required for $method)", )
- UseCheckOrError:Client.kt:Client$throw IllegalStateException("Client does not support roots capability.")
+ LoopWithTooManyJumpStatements:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$while
diff --git a/kotlin-sdk-client/detekt-baseline-test.xml b/kotlin-sdk-client/detekt-baseline-test.xml
deleted file mode 100644
index dd6adfa..0000000
--- a/kotlin-sdk-client/detekt-baseline-test.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- AbstractClassCanBeConcreteClass:AbstractStreamableHttpClientTest.kt:AbstractStreamableHttpClientTest$AbstractStreamableHttpClientTest
- ForbiddenComment:StreamableHttpClientTest.kt:StreamableHttpClientTest$// TODO: how to get notifications via Client API?
- LongParameterList:MockMcp.kt:MockMcp$fun handleJSONRPCRequest
- LongParameterList:MockMcp.kt:MockMcp$fun handleWithResult
- LongParameterList:MockMcp.kt:MockMcp$fun onInitialize
- MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}"""
- MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}"""
- UseOrEmpty:ClientMetaParameterTest.kt:ClientMetaParameterTest$exception.message ?: ""
-
-
diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt
index e3added..3352104 100644
--- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt
+++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt
@@ -4,16 +4,22 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.shared.Protocol
import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions
+import io.modelcontextprotocol.kotlin.sdk.shared.RequestHandlerExtra
import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions
import io.modelcontextprotocol.kotlin.sdk.shared.Transport
+import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest
import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
+import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest
+import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams
import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult
+import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotification
import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult
@@ -22,7 +28,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult
import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification
+import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema
import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
+import io.modelcontextprotocol.kotlin.sdk.types.LegacyTitledEnumSchema
import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest
import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult
import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest
@@ -37,17 +45,26 @@ import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel
import io.modelcontextprotocol.kotlin.sdk.types.McpException
import io.modelcontextprotocol.kotlin.sdk.types.Method
import io.modelcontextprotocol.kotlin.sdk.types.PingRequest
+import io.modelcontextprotocol.kotlin.sdk.types.PrimitiveSchemaDefinition
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult
+import io.modelcontextprotocol.kotlin.sdk.types.Request
import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta
+import io.modelcontextprotocol.kotlin.sdk.types.RequestResult
import io.modelcontextprotocol.kotlin.sdk.types.Root
import io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest
import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.StringSchema
import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest
+import io.modelcontextprotocol.kotlin.sdk.types.TitledMultiSelectEnumSchema
+import io.modelcontextprotocol.kotlin.sdk.types.TitledSingleSelectEnumSchema
import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest
+import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema
+import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema
+import io.modelcontextprotocol.kotlin.sdk.types.supportsUrl
import io.modelcontextprotocol.kotlin.sdk.types.toJson
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.getAndUpdate
@@ -55,8 +72,14 @@ import kotlinx.atomicfu.update
import kotlinx.collections.immutable.minus
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentSet
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.SerializationException
+import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.buildJsonArray
+import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.cancellation.CancellationException
private val logger = KotlinLogging.logger {}
@@ -65,12 +88,17 @@ private val logger = KotlinLogging.logger {}
* Options for configuring the MCP client.
*
* @property capabilities The capabilities this client supports.
- * @property enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with the server.
+ * @param enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with the server.
+ * @param handlerCoroutineContext Coroutine context for inbound handlers. See [ProtocolOptions.handlerCoroutineContext].
*/
public class ClientOptions(
public val capabilities: ClientCapabilities = ClientCapabilities(),
enforceStrictCapabilities: Boolean = true,
-) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities)
+ handlerCoroutineContext: CoroutineContext = Dispatchers.Default,
+) : ProtocolOptions(
+ enforceStrictCapabilities = enforceStrictCapabilities,
+ handlerCoroutineContext = handlerCoroutineContext,
+)
/**
* Initializes and connects an MCP client using the provided clientInfo [Implementation], client options,
@@ -107,7 +135,6 @@ public suspend fun mcpClient(
* @param clientInfo Information about the client implementation (name, version).
* @param options Configuration options for this client.
*/
-@Suppress("TooManyFunctions")
public open class Client(private val clientInfo: Implementation, options: ClientOptions = ClientOptions()) :
Protocol(options) {
@@ -157,6 +184,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
"prompts" -> caps?.prompts != null
"resources" -> caps?.resources != null
"tools" -> caps?.tools != null
+ "tasks" -> caps?.tasks != null
else -> true
}
@@ -195,6 +223,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
serverInstructions = result.instructions
notification(InitializedNotification())
+ enableConcurrentDispatch()
} catch (error: Throwable) {
logger.error(error) { "Failed to initialize client: ${error.message}" }
close()
@@ -221,10 +250,15 @@ public open class Client(private val clientInfo: Implementation, options: Client
Method.Defined.PromptsGet,
Method.Defined.PromptsList,
- Method.Defined.CompletionComplete,
-> {
checkNotNull(serverCapabilities?.prompts) {
- throw IllegalStateException("Server does not support prompts (required for $method)")
+ "Server does not support prompts (required for $method)"
+ }
+ }
+
+ Method.Defined.CompletionComplete -> {
+ checkNotNull(serverCapabilities?.completions) {
+ "Server does not support completions (required for $method)"
}
}
@@ -237,10 +271,10 @@ public open class Client(private val clientInfo: Implementation, options: Client
val resCaps = serverCapabilities?.resources
?: error("Server does not support resources (required for $method)")
- if (method == Method.Defined.ResourcesSubscribe && resCaps.subscribe != true) {
- throw IllegalStateException(
- "Server does not support resource subscriptions (required for $method)",
- )
+ if (method == Method.Defined.ResourcesSubscribe) {
+ check(resCaps.subscribe == true) {
+ "Server does not support resource subscriptions (required for $method)"
+ }
}
}
@@ -250,6 +284,12 @@ public open class Client(private val clientInfo: Implementation, options: Client
}
}
+ Method.Defined.TasksGet,
+ Method.Defined.TasksResult,
+ Method.Defined.TasksList,
+ Method.Defined.TasksCancel,
+ -> assertTasksCapabilityForMethod(method)
+
Method.Defined.Initialize, Method.Defined.Ping -> {
// No specific capability required
}
@@ -260,6 +300,24 @@ public open class Client(private val clientInfo: Implementation, options: Client
}
}
+ private fun assertTasksCapabilityForMethod(method: Method) {
+ val tasks = serverCapabilities?.tasks
+ ?: error("Server does not support tasks (required for $method)")
+ when (method) {
+ Method.Defined.TasksList -> checkNotNull(tasks.list) {
+ "Server does not support listing tasks (required for $method)"
+ }
+
+ Method.Defined.TasksCancel -> checkNotNull(tasks.cancel) {
+ "Server does not support cancelling tasks (required for $method)"
+ }
+
+ else -> {
+ // TasksGet, TasksResult: base tasks capability suffices.
+ }
+ }
+ }
+
override fun assertNotificationCapability(method: Method) {
when (method) {
Method.Defined.NotificationsRootsListChanged -> {
@@ -268,6 +326,12 @@ public open class Client(private val clientInfo: Implementation, options: Client
}
}
+ Method.Defined.NotificationsTasksStatus -> {
+ checkNotNull(capabilities.tasks) {
+ "Client does not support tasks (required for $method)"
+ }
+ }
+
Method.Defined.NotificationsInitialized,
Method.Defined.NotificationsCancelled,
Method.Defined.NotificationsProgress,
@@ -309,6 +373,26 @@ public open class Client(private val clientInfo: Implementation, options: Client
}
}
+ /**
+ * Wraps incoming-request handlers with SEP-1577 client-side enforcement.
+ *
+ * For `sampling/createMessage`: if the incoming request carries `tools` or
+ * `toolChoice` but this client did not advertise [ClientCapabilities.Sampling.tools],
+ * the wrapper throws an [McpException] with JSON-RPC error code `InvalidParams`
+ * before the user-supplied handler runs. Matches the TypeScript SDK wrapper in
+ * `Client.setRequestHandler`.
+ */
+ override fun wrapRequestHandler(
+ method: Method,
+ block: suspend (T, RequestHandlerExtra) -> RequestResult?,
+ ): suspend (T, RequestHandlerExtra) -> RequestResult? {
+ if (method != Method.Defined.SamplingCreateMessage) return block
+ return { request, extra ->
+ (request as? CreateMessageRequest)?.let { validateSamplingToolsCapability(it, capabilities) }
+ block(request, extra)
+ }
+ }
+
/**
* Sends a ping request to the server to check connectivity.
*
@@ -323,7 +407,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
* @param params The completion request parameters.
* @param options Optional request options.
* @return The completion result returned by the server, or `null` if none.
- * @throws IllegalStateException If the server does not support prompts or completion.
+ * @throws IllegalStateException If the server does not support completions.
*/
public suspend fun complete(params: CompleteRequest, options: RequestOptions? = null): CompleteResult =
request(params, options)
@@ -518,9 +602,8 @@ public open class Client(private val clientInfo: Implementation, options: Client
* @throws IllegalStateException If the client does not support roots.
*/
public fun removeRoot(uri: String): Boolean {
- if (capabilities.roots == null) {
- logger.error { "Failed to remove root '$uri': Client does not support roots capability" }
- throw IllegalStateException("Client does not support roots capability.")
+ checkNotNull(capabilities.roots) {
+ "Client does not support roots capability."
}
logger.info { "Removing root: $uri" }
val oldMap = roots.getAndUpdate { current -> current.remove(uri) }
@@ -576,6 +659,19 @@ public open class Client(private val clientInfo: Implementation, options: Client
/**
* Sets the elicitation handler.
*
+ * The handler receives both form-mode ([ElicitRequestFormParams]) and URL-mode
+ * ([io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestURLParams]) requests;
+ * branch on `request.params` to tell them apart. For URL mode,
+ * the host application must obtain explicit user consent and display the target domain before
+ * navigating — the SDK never opens or validates the URL — and should return
+ * [ElicitResult.Action.Decline] or [ElicitResult.Action.Cancel] when it cannot or will not proceed.
+ * A URL-mode [ElicitResult.Action.Accept] only signals consent; the outcome arrives out-of-band via
+ * [setElicitationCompleteHandler].
+ *
+ * When a form-mode handler returns [ElicitResult.Action.Accept], any properties missing from
+ * [ElicitResult.content] are automatically populated with default values defined in the
+ * elicitation schema. URL-mode responses carry no content.
+ *
* @param handler The elicitation handler.
* @throws IllegalStateException if the client does not support elicitation.
*/
@@ -587,12 +683,88 @@ public open class Client(private val clientInfo: Implementation, options: Client
logger.info { "Setting the elicitation handler" }
setRequestHandler(Method.Defined.ElicitationCreate) { request, _ ->
- handler(request)
+ val result = handler(request)
+ applyElicitationDefaults(request, result)
+ }
+ }
+
+ /**
+ * Sets the handler invoked when the server reports that a URL-mode elicitation has completed.
+ *
+ * The handler is called for every `notifications/elicitation/complete` notification. Because the
+ * server only sends this for an out-of-band (URL-mode) interaction, the client must support url-mode
+ * elicitation. The client is responsible for correlating the notification's `elicitationId` with a
+ * pending elicitation, ignoring unknown or already-completed identifiers, and providing a manual way
+ * to continue if a notification never arrives.
+ *
+ * @param handler Invoked with each completion notification.
+ * @throws IllegalStateException if the client does not support url-mode elicitation.
+ */
+ public fun setElicitationCompleteHandler(handler: (ElicitationCompleteNotification) -> Unit) {
+ check(capabilities.elicitation.supportsUrl) {
+ logger.error {
+ "Failed to set elicitation-complete handler: client does not support url-mode elicitation"
+ }
+ "Client does not support url-mode elicitation."
+ }
+ logger.info { "Setting the elicitation-complete handler" }
+
+ setNotificationHandler(
+ Method.Defined.NotificationsElicitationComplete,
+ ) { notification ->
+ handler(notification)
+ CompletableDeferred(Unit)
}
}
// --- Internal Handlers ---
+ private fun applyElicitationDefaults(request: ElicitRequest, result: ElicitResult): ElicitResult {
+ if (result.action != ElicitResult.Action.Accept) return result
+ val formParams = request.params as? ElicitRequestFormParams ?: return result
+ val content = result.content ?: return result
+
+ val merged = buildMap {
+ putAll(content)
+ for ((key, schemaDef) in formParams.requestedSchema.properties) {
+ if (key !in content) {
+ schemaDef.defaultJsonValue()?.let { put(key, it) }
+ }
+ }
+ }
+
+ return if (merged.size == content.size) {
+ result
+ } else {
+ result.copy(content = JsonObject(merged))
+ }
+ }
+
+ @Suppress("DEPRECATION", "CyclomaticComplexMethod")
+ private fun PrimitiveSchemaDefinition.defaultJsonValue(): JsonElement? = when (this) {
+ is StringSchema -> default?.let { JsonPrimitive(it) }
+
+ is IntegerSchema -> default?.let { JsonPrimitive(it) }
+
+ is DoubleSchema -> default?.let { JsonPrimitive(it) }
+
+ is BooleanSchema -> default?.let { JsonPrimitive(it) }
+
+ is UntitledSingleSelectEnumSchema -> default?.let { JsonPrimitive(it) }
+
+ is TitledSingleSelectEnumSchema -> default?.let { JsonPrimitive(it) }
+
+ is LegacyTitledEnumSchema -> default?.let { JsonPrimitive(it) }
+
+ is UntitledMultiSelectEnumSchema -> default?.let { list ->
+ buildJsonArray { list.forEach { add(JsonPrimitive(it)) } }
+ }
+
+ is TitledMultiSelectEnumSchema -> default?.let { list ->
+ buildJsonArray { list.forEach { add(JsonPrimitive(it)) } }
+ }
+ }
+
private fun handleListRoots(): ListRootsResult {
val rootList = roots.value.values.toList()
return ListRootsResult(rootList)
diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SamplingValidation.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SamplingValidation.kt
new file mode 100644
index 0000000..1290cb0
--- /dev/null
+++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SamplingValidation.kt
@@ -0,0 +1,24 @@
+package io.modelcontextprotocol.kotlin.sdk.client
+
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
+import io.modelcontextprotocol.kotlin.sdk.types.McpException
+import io.modelcontextprotocol.kotlin.sdk.types.RPCError
+
+/**
+ * Validates SEP-1577 client-side enforcement: an incoming `sampling/createMessage`
+ * request that carries `tools` or `toolChoice` requires the client to have advertised
+ * the `sampling.tools` sub-capability. When the capability is missing, throws an
+ * [McpException] with JSON-RPC error code `InvalidParams`, matching TypeScript SDK
+ * client enforcement.
+ */
+internal fun validateSamplingToolsCapability(request: CreateMessageRequest, capabilities: ClientCapabilities) {
+ val params = request.params
+ if (params.tools == null && params.toolChoice == null) return
+ if (capabilities.sampling?.tools != null) return
+ val field = if (params.tools != null) "tools" else "toolChoice"
+ throw McpException(
+ code = RPCError.ErrorCode.INVALID_PARAMS,
+ message = "Client does not support sampling with tools but request contains $field parameter",
+ )
+}
diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt
index 3c9d88b..0295eb9 100644
--- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt
+++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt
@@ -24,11 +24,12 @@ import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
+import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.serialization.SerializationException
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.time.Duration
@@ -135,6 +136,7 @@ public class SseClientTransport(
throw e
} finally {
closeResources()
+ invokeOnCloseCallback()
}
}
@@ -171,13 +173,15 @@ public class SseClientTransport(
}
override suspend fun closeResources() {
- job?.cancelAndJoin()
- try {
- if (::session.isInitialized) session.cancel()
- if (::scope.isInitialized) scope.cancel()
- endpoint.cancel()
- } catch (e: Throwable) {
- _onError(e)
+ withContext(NonCancellable) {
+ job?.cancel()
+ try {
+ if (::session.isInitialized) session.cancel()
+ if (::scope.isInitialized) scope.cancel()
+ endpoint.cancel()
+ } catch (e: Throwable) {
+ _onError(e)
+ }
}
}
}
diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt
index 12529cc..5e990ee 100644
--- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt
+++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt
@@ -21,6 +21,7 @@ import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
+import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelAndJoin
@@ -31,6 +32,7 @@ import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import kotlinx.io.Buffer
import kotlinx.io.IOException
@@ -225,7 +227,6 @@ public class StdioClientTransport @JvmOverloads public constructor(
}
override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) {
- @Suppress("SwallowedException")
try {
sendChannel.send(message)
} catch (e: CancellationException) {
@@ -241,8 +242,20 @@ public class StdioClientTransport @JvmOverloads public constructor(
}
override suspend fun closeResources() {
- scope.stopProcessing("Closed")
- scope.coroutineContext[Job]?.join() // Wait for all coroutines to complete
+ withContext(NonCancellable) {
+ scope.stopProcessing("Closed")
+ closeReadSources()
+ scope.coroutineContext[Job]?.join() // Wait for all coroutines to complete
+ }
+ }
+
+ private fun closeReadSources() {
+ runCatching { input.close() }
+ .onFailure { logger.debug(it) { "Error closing stdin source" } }
+ error?.let { source ->
+ runCatching { source.close() }
+ .onFailure { logger.debug(it) { "Error closing stderr source" } }
+ }
}
private fun sendOutboundMessage(message: JSONRPCMessage, sink: Sink, mainScope: CoroutineScope) {
@@ -264,6 +277,8 @@ public class StdioClientTransport @JvmOverloads public constructor(
private suspend fun handleJSONRPCMessage(msg: JSONRPCMessage) {
try {
_onMessage.invoke(msg)
+ } catch (e: CancellationException) {
+ throw e
} catch (e: Throwable) {
logger.error(e) { "Error processing message." }
runCatching { _onError.invoke(e) }
@@ -284,7 +299,7 @@ public class StdioClientTransport @JvmOverloads public constructor(
) {
val buffer = Buffer()
try {
- source.use { source ->
+ source.use {
while (isActive) {
val bytesRead = source.readAtMostTo(buffer, BUFFER_SIZE)
if (bytesRead == -1L) {
diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt
index 80f37ae..6df352a 100644
--- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt
+++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt
@@ -21,8 +21,10 @@ import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.ktor.http.isSuccess
+import io.ktor.utils.io.charsets.TooLongLineException
import io.ktor.utils.io.readUTF8Line
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractClientTransport
+import io.modelcontextprotocol.kotlin.sdk.shared.TooLongFrameException
import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification
@@ -41,6 +43,10 @@ import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlin.io.encoding.Base64
+import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.math.pow
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@@ -49,9 +55,24 @@ import kotlin.time.Duration.Companion.seconds
private const val MCP_SESSION_ID_HEADER = "mcp-session-id"
private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version"
private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID"
+private const val MCP_METHOD_HEADER = "Mcp-Method"
+private const val MCP_NAME_HEADER = "Mcp-Name"
+private const val MCP_BASE64_PREFIX = "=?base64?"
+private const val MCP_BASE64_SUFFIX = "?="
/**
- * Error class for Streamable HTTP transport errors.
+ * Default maximum size, in characters, of a single inline SSE event assembled from a POST response.
+ *
+ * Mirrors the stdio transport's 16 MiB frame cap: a server that streams `data:` lines without ever
+ * terminating the event cannot grow the client's buffer without bound.
+ */
+private const val DEFAULT_MAX_INLINE_SSE_EVENT_SIZE: Int = 16 * 1024 * 1024
+
+/**
+ * Represents an error from the Streamable HTTP transport.
+ *
+ * @property code HTTP status code associated with the error, or `null` if unavailable
+ * @param message detailed error description appended to the exception message
*/
public class StreamableHttpError(public val code: Int? = null, message: String? = null) :
Exception("Streamable HTTP error: $message")
@@ -63,18 +84,32 @@ private sealed interface ConnectResult {
}
/**
- * Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
- * It will connect to a server using HTTP POST for sending messages and HTTP GET with Server-Sent Events
- * for receiving messages.
+ * Client transport implementing the MCP Streamable HTTP transport specification.
+ *
+ * Sends messages via HTTP POST and receives messages via HTTP GET with Server-Sent Events.
+ * Supports automatic SSE reconnection with exponential backoff, stream resumption via the
+ * `Last-Event-ID` header, and explicit session termination.
+ *
+ * @param client Ktor HTTP client used for all requests
+ * @param url MCP endpoint URL
+ * @param reconnectionOptions reconnection backoff and retry-limit settings for the SSE stream
+ * @param maxInlineSseEventSize maximum size, in characters, of a single inline SSE event parsed from a
+ * POST response; a server that exceeds it (including by never terminating an event) fails the send
+ * with [io.modelcontextprotocol.kotlin.sdk.shared.TooLongFrameException]. Defaults to 16 MiB.
+ * @param requestBuilder builder applied to every outgoing HTTP request, e.g. for adding auth headers
*/
-@Suppress("TooManyFunctions")
public class StreamableHttpClientTransport(
private val client: HttpClient,
private val url: String,
private val reconnectionOptions: ReconnectionOptions = ReconnectionOptions(),
+ private val maxInlineSseEventSize: Int = DEFAULT_MAX_INLINE_SSE_EVENT_SIZE,
private val requestBuilder: HttpRequestBuilder.() -> Unit = {},
) : AbstractClientTransport() {
+ init {
+ require(maxInlineSseEventSize > 0) { "maxInlineSseEventSize must be greater than 0" }
+ }
+
@Deprecated(
"Use constructor with ReconnectionOptions",
replaceWith = ReplaceWith(
@@ -89,12 +124,20 @@ public class StreamableHttpClientTransport(
url: String,
reconnectionTime: Duration?,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
- ) : this(client, url, ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), requestBuilder)
+ ) : this(
+ client,
+ url,
+ ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds),
+ requestBuilder = requestBuilder,
+ )
override val logger: KLogger = KotlinLogging.logger {}
+ /** Session identifier assigned by the server after initialization, or `null` before connection. */
public var sessionId: String? = null
private set
+
+ /** MCP protocol version negotiated with the server, or `null` before connection. */
public var protocolVersion: String? = null
private var sseJob: Job? = null
@@ -116,7 +159,6 @@ public class StreamableHttpClientTransport(
/**
* Sends a single message with optional resumption support
*/
- @Suppress("ReturnCount", "CyclomaticComplexMethod", "LongMethod", "TooGenericExceptionCaught", "ThrowsCount")
override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) {
logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" }
@@ -133,6 +175,7 @@ public class StreamableHttpClientTransport(
val jsonBody = McpJson.encodeToString(message)
val response = client.post(url) {
applyCommonHeaders(this)
+ applyStandardPostHeaders(this, message)
headers.append(HttpHeaders.Accept, "${ContentType.Application.Json}, ${ContentType.Text.EventStream}")
contentType(ContentType.Application.Json)
setBody(jsonBody)
@@ -255,7 +298,6 @@ public class StreamableHttpClientTransport(
var attempt = 0
var needsDelay = initialServerRetryDelay != null
- @Suppress("LoopWithTooManyJumpStatements")
while (isActive) {
// Delay before (re)connection: skip only for first fresh SSE connection
if (needsDelay) {
@@ -291,7 +333,6 @@ public class StreamableHttpClientTransport(
}
}
- @Suppress("TooGenericExceptionCaught")
private suspend fun connectSse(lastEventId: String?): ConnectResult {
logger.debug { "Client attempting to start SSE session at url: $url" }
return try {
@@ -358,7 +399,36 @@ public class StreamableHttpClientTransport(
}
}
- @Suppress("TooGenericExceptionCaught")
+ private fun applyStandardPostHeaders(builder: HttpRequestBuilder, message: JSONRPCMessage) {
+ val (method, params) = when (message) {
+ is JSONRPCRequest -> message.method to message.params
+ is JSONRPCNotification -> message.method to message.params
+ else -> return
+ }
+
+ builder.headers {
+ append(MCP_METHOD_HEADER, method)
+
+ val paramsObject = params as? JsonObject ?: return@headers
+ val mcpName = paramsObject.stringValue("name") ?: paramsObject.stringValue("uri")
+ mcpName?.let { append(MCP_NAME_HEADER, it.encodeMcpHeaderValue()) }
+ }
+ }
+
+ private fun JsonObject.stringValue(key: String): String? =
+ (get(key) as? JsonPrimitive)?.takeIf { it.isString }?.content
+
+ @OptIn(ExperimentalEncodingApi::class)
+ private fun String.encodeMcpHeaderValue(): String {
+ val containsUnsafeCharacters = any { it != '\t' && it.code !in 0x20..0x7e }
+ val hasEdgeWhitespace = firstOrNull()?.isWhitespace() == true || lastOrNull()?.isWhitespace() == true
+ val matchesBase64Sentinel = startsWith(MCP_BASE64_PREFIX) && endsWith(MCP_BASE64_SUFFIX)
+
+ if (!containsUnsafeCharacters && !hasEdgeWhitespace && !matchesBase64Sentinel) return this
+
+ return "$MCP_BASE64_PREFIX${Base64.Default.encode(encodeToByteArray())}$MCP_BASE64_SUFFIX"
+ }
+
private suspend fun collectSse(
session: ClientSSESession,
replayMessageId: RequestId?,
@@ -403,7 +473,6 @@ public class StreamableHttpClientTransport(
return SseStreamResult(hasPrimingEvent, receivedResponse, localLastEventId, localServerRetryDelay)
}
- @Suppress("CyclomaticComplexMethod")
private suspend fun handleInlineSse(
response: HttpResponse,
replayMessageId: RequestId?,
@@ -450,9 +519,15 @@ public class StreamableHttpClientTransport(
}
}
- @Suppress("LoopWithTooManyJumpStatements")
while (!channel.isClosedForRead) {
- val line = channel.readUTF8Line() ?: break
+ // Bound each line so a server that streams a line without ever terminating it cannot
+ // exhaust client memory; readUTF8Line returns null at the end of the stream.
+ val line = try {
+ channel.readUTF8Line(maxInlineSseEventSize)
+ } catch (_: TooLongLineException) {
+ throw TooLongFrameException(maxInlineSseEventSize.toLong() + 1, maxInlineSseEventSize)
+ }
+ if (line == null) break
if (line.isEmpty()) {
dispatch(id = id, eventName = eventName, data = sb.toString())
// reset
@@ -466,7 +541,13 @@ public class StreamableHttpClientTransport(
line.startsWith("event:") -> eventName = line.substringAfter("event:").trim()
- line.startsWith("data:") -> sb.append(line.substringAfter("data:").trim())
+ line.startsWith("data:") -> {
+ sb.append(line.substringAfter("data:").trim())
+ // Cap an event assembled from many data: lines that never sees a terminating blank line.
+ if (sb.length > maxInlineSseEventSize) {
+ throw TooLongFrameException(sb.length.toLong(), maxInlineSseEventSize)
+ }
+ }
line.startsWith("retry:") -> line.substringAfter("retry:").trim().toLongOrNull()?.let {
localServerRetryDelay = it.milliseconds
diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt
new file mode 100644
index 0000000..e9379a7
--- /dev/null
+++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt
@@ -0,0 +1,171 @@
+package io.modelcontextprotocol.kotlin.sdk.client
+
+import io.kotest.matchers.string.shouldContain
+import io.modelcontextprotocol.kotlin.sdk.shared.Transport
+import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.Implementation
+import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
+import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse
+import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
+import io.modelcontextprotocol.kotlin.sdk.types.Method
+import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
+import kotlinx.coroutines.test.runTest
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+/**
+ * Tests for the protected helper [Client.assertCapability].
+ *
+ * `assertCapability(capability, method)` is `protected`, so this suite uses
+ * a small subclass [TestClient] that re-exports it via [TestClient.exposedAssertCapability].
+ * Server capabilities are seeded by completing the initialization handshake through
+ * [CapabilitiesTransport], which replays a configurable [ServerCapabilities] in its
+ * `initialize` response.
+ */
+class ClientAssertCapabilityTest {
+
+ @Test
+ fun `assertCapability tasks throws when server has no tasks capability`() = runTest {
+ val client = newTestClient(serverCapabilities = ServerCapabilities())
+ val ex = assertFailsWith {
+ client.exposedAssertCapability("tasks", "tasks/list")
+ }
+ ex.message.orEmpty() shouldContain "Server does not support tasks"
+ }
+
+ @Test
+ fun `TasksGet throws when server has no tasks capability`() = runTest {
+ val client = newTestClient(serverCapabilities = ServerCapabilities())
+ val ex = assertFailsWith {
+ client.exposedAssertCapabilityForMethod(Method.Defined.TasksGet)
+ }
+ ex.message.orEmpty() shouldContain "Server does not support tasks"
+ }
+
+ @Test
+ fun `TasksGet does not throw when server declared tasks`() = runTest {
+ val client = newTestClient(
+ serverCapabilities = ServerCapabilities(tasks = ServerCapabilities.Tasks()),
+ )
+ client.exposedAssertCapabilityForMethod(Method.Defined.TasksGet)
+ }
+
+ @Test
+ fun `TasksList throws when server tasks list is null`() = runTest {
+ val client = newTestClient(
+ serverCapabilities = ServerCapabilities(tasks = ServerCapabilities.Tasks()),
+ )
+ val ex = assertFailsWith {
+ client.exposedAssertCapabilityForMethod(Method.Defined.TasksList)
+ }
+ ex.message.orEmpty() shouldContain "Server does not support listing tasks"
+ }
+
+ @Test
+ fun `TasksCancel throws when server tasks cancel is null`() = runTest {
+ val client = newTestClient(
+ serverCapabilities = ServerCapabilities(tasks = ServerCapabilities.Tasks()),
+ )
+ val ex = assertFailsWith {
+ client.exposedAssertCapabilityForMethod(Method.Defined.TasksCancel)
+ }
+ ex.message.orEmpty() shouldContain "Server does not support cancelling tasks"
+ }
+
+ @Test
+ fun `NotificationsTasksStatus throws when client has no tasks capability`() = runTest {
+ val client = newTestClient(clientCapabilities = ClientCapabilities())
+ val ex = assertFailsWith {
+ client.exposedAssertNotificationCapability(Method.Defined.NotificationsTasksStatus)
+ }
+ ex.message.orEmpty() shouldContain "Client does not support tasks"
+ }
+
+ @Test
+ fun `CompletionComplete does not throw when server declared completions`() = runTest {
+ val client = newTestClient(
+ serverCapabilities = ServerCapabilities(completions = ServerCapabilities.Completions),
+ )
+ client.exposedAssertCapabilityForMethod(Method.Defined.CompletionComplete)
+ }
+
+ @Test
+ fun `CompletionComplete throws when server has no completions capability`() = runTest {
+ val client = newTestClient(
+ serverCapabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts()),
+ )
+ val ex = assertFailsWith {
+ client.exposedAssertCapabilityForMethod(Method.Defined.CompletionComplete)
+ }
+ ex.message.orEmpty() shouldContain "Server does not support completions"
+ }
+
+ private suspend fun newTestClient(
+ serverCapabilities: ServerCapabilities = ServerCapabilities(),
+ clientCapabilities: ClientCapabilities = ClientCapabilities(),
+ ): TestClient {
+ val client = TestClient(
+ Implementation("test-client", "1.0.0"),
+ ClientOptions(capabilities = clientCapabilities),
+ )
+ val transport = CapabilitiesTransport(serverCapabilities)
+ client.connect(transport)
+ return client
+ }
+
+ /**
+ * Test-only [Client] subclass that exposes the protected
+ * [Client.assertCapability], [Client.assertCapabilityForMethod], and
+ * [Client.assertNotificationCapability] helpers to the test code.
+ */
+ private class TestClient(clientInfo: Implementation, options: ClientOptions = ClientOptions()) :
+ Client(clientInfo, options) {
+ fun exposedAssertCapability(capability: String, method: String): Unit = assertCapability(capability, method)
+ fun exposedAssertCapabilityForMethod(method: Method): Unit = assertCapabilityForMethod(method)
+ fun exposedAssertNotificationCapability(method: Method): Unit = assertNotificationCapability(method)
+ }
+
+ /**
+ * Minimal in-memory [Transport] that responds to `initialize` with a configurable
+ * [ServerCapabilities]. Other JSON-RPC traffic (e.g. the `notifications/initialized`
+ * sent by [Client.connect]) is silently consumed.
+ */
+ private class CapabilitiesTransport(private val serverCapabilities: ServerCapabilities) : Transport {
+ private var onMessageBlock: (suspend (JSONRPCMessage) -> Unit)? = null
+ private var onCloseBlock: (() -> Unit)? = null
+
+ override suspend fun start() = Unit
+
+ override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) {
+ if (message is JSONRPCRequest && message.method == "initialize") {
+ onMessageBlock?.invoke(
+ JSONRPCResponse(
+ id = message.id,
+ result = InitializeResult(
+ protocolVersion = LATEST_PROTOCOL_VERSION,
+ capabilities = serverCapabilities,
+ serverInfo = Implementation("mock-server", "1.0.0"),
+ ),
+ ),
+ )
+ }
+ }
+
+ override suspend fun close() {
+ onCloseBlock?.invoke()
+ }
+
+ override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) {
+ onMessageBlock = block
+ }
+
+ override fun onClose(block: () -> Unit) {
+ onCloseBlock = block
+ }
+
+ override fun onError(block: (Throwable) -> Unit) = Unit
+ }
+}
diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt
index 3627b6c..14af546 100644
--- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt
+++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt
@@ -53,6 +53,14 @@ internal open class MockSseClientEngine(
job.cancel()
}
+ /**
+ * Closes the SSE byte channel without disposing the engine, simulating a server-side
+ * disconnect of the SSE stream.
+ */
+ fun disconnectSseStream() {
+ channel.close()
+ }
+
private fun responseContext(): CoroutineContext = dispatcher + Job()
private suspend fun respondWithSseEndpointEvent(data: HttpRequestData): HttpResponseData {
diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt
index 6cc7f6e..319ba2a 100644
--- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt
+++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt
@@ -1,5 +1,6 @@
package io.modelcontextprotocol.kotlin.sdk.client.sse
+import io.kotest.assertions.nondeterministic.eventually
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import io.ktor.client.HttpClient
@@ -9,6 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
+import kotlin.time.Duration.Companion.seconds
class SseClientTransportTest {
@@ -90,6 +92,31 @@ class SseClientTransportTest {
engine.close()
}
+ @Test
+ fun `onClose callback fires when the SSE stream is disconnected by the server`() = runTest {
+ // Given
+ val sseUrl = "http://example.com/api/mcp/sse"
+
+ // And
+ val engine = CapturingSseClientEngine(endpoint = "/messages?sessionId=abc")
+ val transport = sseTransport(sseUrl, engine)
+ var onCloseFired = false
+ transport.onClose { onCloseFired = true }
+
+ // When
+ transport.start()
+ engine.disconnectSseStream()
+
+ // Then
+ eventually(2.seconds) {
+ onCloseFired shouldBe true
+ }
+
+ // Cleanup
+ transport.close()
+ engine.close()
+ }
+
private fun sseTransport(sseUrl: String, engine: CapturingSseClientEngine) =
SseClientTransport(HttpClient(engine) { install(SSE) }, sseUrl)
diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt
index 67b2f07..d9aaf7a 100644
--- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt
+++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt
@@ -19,6 +19,7 @@ import io.ktor.http.headersOf
import io.ktor.utils.io.ByteReadChannel
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport
+import io.modelcontextprotocol.kotlin.sdk.shared.TooLongFrameException
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification
@@ -43,11 +44,15 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
import kotlin.test.assertTrue
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class StreamableHttpClientTransportTest {
- private fun createTransport(handler: MockRequestHandler): StreamableHttpClientTransport {
+ private fun createTransport(
+ maxInlineSseEventSize: Int = 16 * 1024 * 1024,
+ handler: MockRequestHandler,
+ ): StreamableHttpClientTransport {
val mockEngine = MockEngine(handler)
val httpClient = HttpClient(mockEngine) {
install(SSE) {
@@ -55,7 +60,11 @@ class StreamableHttpClientTransportTest {
}
}
- return StreamableHttpClientTransport(httpClient, url = "http://localhost:8080/mcp")
+ return StreamableHttpClientTransport(
+ httpClient,
+ url = "http://localhost:8080/mcp",
+ maxInlineSseEventSize = maxInlineSseEventSize,
+ )
}
private fun buildSseMessage(id: String, method: String, params: String): String = buildString {
@@ -237,6 +246,100 @@ class StreamableHttpClientTransportTest {
transport.close()
}
+ @Test
+ fun `should send MCP method and name headers from name`() = runTest {
+ val transport = createTransport { request ->
+ assertEquals("tools/call", request.headers["Mcp-Method"])
+ assertEquals("read_file", request.headers["Mcp-Name"])
+ respond(content = "", status = HttpStatusCode.Accepted)
+ }
+
+ transport.start()
+ transport.send(
+ JSONRPCRequest(
+ id = "test-id",
+ method = "tools/call",
+ params = buildJsonObject {
+ put("name", JsonPrimitive("read_file"))
+ put("arguments", buildJsonObject { put("path", JsonPrimitive("README.md")) })
+ },
+ ),
+ )
+ transport.close()
+ }
+
+ @Test
+ fun `should send MCP name header from URI`() = runTest {
+ val transport = createTransport { request ->
+ assertEquals("resources/read", request.headers["Mcp-Method"])
+ assertEquals("file:///README.md", request.headers["Mcp-Name"])
+ respond(content = "", status = HttpStatusCode.Accepted)
+ }
+
+ transport.start()
+ transport.send(
+ JSONRPCRequest(
+ id = "test-id",
+ method = "resources/read",
+ params = buildJsonObject { put("uri", JsonPrimitive("file:///README.md")) },
+ ),
+ )
+ transport.close()
+ }
+
+ @Test
+ fun `should encode MCP name header values when required`() = runTest {
+ val cases = listOf(
+ "Hello, 世界" to "=?base64?SGVsbG8sIOS4lueVjA==?=",
+ " padded " to "=?base64?IHBhZGRlZCA=?=",
+ "line1\nline2" to "=?base64?bGluZTEKbGluZTI=?=",
+ "=?base64?literal?=" to "=?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=",
+ )
+
+ cases.forEachIndexed { index, (name, expectedHeader) ->
+ val transport = createTransport { request ->
+ assertEquals(expectedHeader, request.headers["Mcp-Name"])
+ respond(content = "", status = HttpStatusCode.Accepted)
+ }
+
+ transport.start()
+ transport.send(
+ JSONRPCRequest(
+ id = "test-id-$index",
+ method = "tools/call",
+ params = buildJsonObject { put("name", JsonPrimitive(name)) },
+ ),
+ )
+ transport.close()
+ }
+ }
+
+ @Test
+ fun `should send MCP method header for notifications`() = runTest {
+ val transport = createTransport { request ->
+ assertEquals("notifications/tools/list_changed", request.headers["Mcp-Method"])
+ assertNull(request.headers["Mcp-Name"])
+ respond(content = "", status = HttpStatusCode.Accepted)
+ }
+
+ transport.start()
+ transport.send(JSONRPCNotification(method = "notifications/tools/list_changed"))
+ transport.close()
+ }
+
+ @Test
+ fun `should omit MCP standard headers for responses`() = runTest {
+ val transport = createTransport { request ->
+ assertNull(request.headers["Mcp-Method"])
+ assertNull(request.headers["Mcp-Name"])
+ respond(content = "", status = HttpStatusCode.Accepted)
+ }
+
+ transport.start()
+ transport.send(JSONRPCResponse(id = RequestId.StringId("test-id")))
+ transport.close()
+ }
+
// Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support
@Ignore
@Test
@@ -478,6 +581,170 @@ class StreamableHttpClientTransportTest {
}
}
+ @Test
+ fun testSkipEmptySSE() = runTest {
+ val transport = createTransport { request ->
+ if (request.method == HttpMethod.Post) {
+ val sseContent = buildString {
+ // Event 1: empty data — must be skipped without error
+ appendLine("id: a")
+ appendLine("data:")
+ appendLine()
+ // Event 2: whitespace-only data — must be skipped without error
+ appendLine("id: b")
+ appendLine("data: \t ")
+ appendLine()
+ // Event 3: multi-line data lines that together form a valid JSON-RPC response
+ appendLine("id: c")
+ appendLine("event: message")
+ appendLine("data: {")
+ appendLine("""data: "jsonrpc":"2.0",""")
+ appendLine("""data: "id":"test-1",""")
+ appendLine("""data: "result":{""")
+ appendLine("""data: "protocolVersion":"2025-06-18",""")
+ appendLine("""data: "capabilities":{},""")
+ appendLine("""data: "serverInfo":{"name":"server","version":"1.0.0"}""")
+ appendLine("data: }")
+ appendLine("data: }")
+ appendLine()
+ }
+
+ respond(
+ content = ByteReadChannel(sseContent),
+ status = HttpStatusCode.OK,
+ headers = headersOf(
+ HttpHeaders.ContentType,
+ ContentType.Text.EventStream.toString(),
+ ),
+ )
+ } else {
+ respond("", HttpStatusCode.OK)
+ }
+ }
+
+ val receivedMessages = mutableListOf()
+ val messageReceived = CompletableDeferred()
+
+ transport.onMessage { message ->
+ receivedMessages.add(message)
+ if (!messageReceived.isCompleted) {
+ messageReceived.complete(Unit)
+ }
+ }
+
+ transport.start()
+
+ transport.send(
+ JSONRPCRequest(
+ id = "test-1",
+ method = "initialize",
+ params = buildJsonObject { },
+ ),
+ )
+
+ eventually {
+ messageReceived.await()
+ }
+
+ receivedMessages shouldHaveSize 1
+ val response = receivedMessages[0]
+ response.shouldBeInstanceOf()
+ response.id shouldBe RequestId.StringId("test-1")
+
+ transport.close()
+ }
+
+ @Test
+ fun testInlineSseRejectsEventExceedingMaxSize() = runTest {
+ // A malicious server streams an endless single event: many `data:` lines that accumulate
+ // past the cap and never send the blank-line terminator that would flush the buffer.
+ val maxInlineSseEventSize = 64
+ val transport = createTransport(maxInlineSseEventSize) { request ->
+ if (request.method == HttpMethod.Post) {
+ val sseContent = buildString {
+ appendLine("event: message")
+ // 20 lines × 16 chars = 320 chars of accumulated data, no terminating blank line.
+ repeat(20) { appendLine("data: ${"A".repeat(16)}") }
+ }
+ respond(
+ content = ByteReadChannel(sseContent),
+ status = HttpStatusCode.OK,
+ headers = headersOf(HttpHeaders.ContentType, ContentType.Text.EventStream.toString()),
+ )
+ } else {
+ respond("", HttpStatusCode.OK)
+ }
+ }
+
+ val receivedMessages = mutableListOf()
+ val receivedErrors = mutableListOf()
+ transport.onMessage { receivedMessages.add(it) }
+ transport.onError { receivedErrors.add(it) }
+ transport.start()
+
+ val error = assertFailsWith {
+ transport.send(JSONRPCRequest(id = "req-1", method = "test", params = buildJsonObject { }))
+ }
+
+ error.cause.shouldBeInstanceOf()
+ receivedErrors.filterIsInstance() shouldHaveSize 1
+ receivedMessages shouldHaveSize 0
+ transport.close()
+ }
+
+ @Test
+ fun testInlineSseEventExactlyAtMaxSizeIsAccepted() = runTest {
+ // An event whose assembled data length equals the cap must still be accepted and dispatched:
+ // the guard rejects only sizes strictly greater than the cap (parity with ReadBuffer).
+ val part1 = """{"jsonrpc":"2.0","""
+ val part2 = """"method":"notifications/tools/list_changed"}"""
+ val maxInlineSseEventSize = (part1 + part2).length
+
+ val transport = createTransport(maxInlineSseEventSize) { request ->
+ if (request.method == HttpMethod.Post) {
+ val sseContent = buildString {
+ appendLine("event: message")
+ appendLine("data: $part1")
+ appendLine("data: $part2")
+ appendLine()
+ }
+ respond(
+ content = ByteReadChannel(sseContent),
+ status = HttpStatusCode.OK,
+ headers = headersOf(HttpHeaders.ContentType, ContentType.Text.EventStream.toString()),
+ )
+ } else {
+ respond("", HttpStatusCode.OK)
+ }
+ }
+
+ val receivedMessages = mutableListOf()
+ val receivedErrors = mutableListOf()
+ val messageReceived = CompletableDeferred()
+ transport.onMessage {
+ receivedMessages.add(it)
+ if (!messageReceived.isCompleted) messageReceived.complete(Unit)
+ }
+ transport.onError { receivedErrors.add(it) }
+ transport.start()
+
+ transport.send(JSONRPCRequest(id = "req-1", method = "test", params = buildJsonObject { }))
+
+ eventually { messageReceived.await() }
+
+ receivedMessages shouldHaveSize 1
+ (receivedMessages[0] as JSONRPCNotification).method shouldBe "notifications/tools/list_changed"
+ receivedErrors shouldHaveSize 0
+ transport.close()
+ }
+
+ @Test
+ fun testNonPositiveMaxInlineSseEventSizeThrows() {
+ assertFailsWith {
+ createTransport(maxInlineSseEventSize = 0) { respond("", HttpStatusCode.OK) }
+ }
+ }
+
@Test
fun testInlineSSEInResponse() = runTest {
val transport = createTransport { request ->
@@ -643,7 +910,7 @@ class StreamableHttpClientTransportTest {
eventually {
while (receivedMessages.isEmpty()) {
- delay(10)
+ delay(10.milliseconds)
}
}
@@ -834,7 +1101,6 @@ class StreamableHttpClientTransportTest {
transport.close()
}
- @Suppress("DEPRECATION")
@Test
fun testDeprecatedConstructorStillWorks() = runTest {
val mockEngine = MockEngine { _ ->
diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt
deleted file mode 100644
index 4a04e81..0000000
--- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package io.modelcontextprotocol.kotlin.sdk.client
-
-import io.ktor.client.HttpClient
-import io.ktor.client.engine.apache5.Apache5
-import io.ktor.client.plugins.logging.LogLevel
-import io.ktor.client.plugins.logging.Logging
-import io.ktor.client.plugins.sse.SSE
-import org.junit.jupiter.api.AfterAll
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.TestInstance
-
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-internal abstract class AbstractStreamableHttpClientTest {
-
- // start mokksy on random port
- protected val mockMcp: MockMcp = MockMcp(verbose = true)
-
- @AfterEach
- fun afterEach() {
- mockMcp.verifyNoUnexpectedRequests()
- }
-
- @AfterAll
- fun afterAll() {
- mockMcp.verifyNoUnmatchedStubs()
- mockMcp.close()
- }
-
- protected suspend fun connect(client: Client) {
- client.connect(
- StreamableHttpClientTransport(
- url = mockMcp.url,
- client = HttpClient(Apache5) {
- install(SSE)
- install(Logging) {
- level = LogLevel.ALL
- }
- },
- ),
- )
- }
-}
diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientSamplingValidationTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientSamplingValidationTest.kt
new file mode 100644
index 0000000..7775a08
--- /dev/null
+++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientSamplingValidationTest.kt
@@ -0,0 +1,98 @@
+package io.modelcontextprotocol.kotlin.sdk.client
+
+import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
+import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams
+import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
+import io.modelcontextprotocol.kotlin.sdk.types.McpException
+import io.modelcontextprotocol.kotlin.sdk.types.RPCError
+import io.modelcontextprotocol.kotlin.sdk.types.Role
+import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage
+import io.modelcontextprotocol.kotlin.sdk.types.TextContent
+import io.modelcontextprotocol.kotlin.sdk.types.Tool
+import io.modelcontextprotocol.kotlin.sdk.types.ToolChoice
+import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
+import kotlinx.serialization.json.buildJsonObject
+import org.junit.jupiter.api.assertDoesNotThrow
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class ClientSamplingValidationTest {
+
+ private val dummyTool = Tool(
+ name = "t",
+ inputSchema = ToolSchema(properties = buildJsonObject { }, required = emptyList()),
+ )
+
+ private val minimalMessages = listOf(SamplingMessage(Role.User, TextContent("hi")))
+
+ private val noToolsCaps = ClientCapabilities(sampling = ClientCapabilities.sampling)
+ private val withToolsCaps = ClientCapabilities(
+ sampling = ClientCapabilities.Sampling(tools = EmptyJsonObject),
+ )
+
+ @Test
+ fun `request without tools or toolChoice is always accepted`() {
+ val request = CreateMessageRequest(
+ CreateMessageRequestParams(maxTokens = 10, messages = minimalMessages),
+ )
+ assertDoesNotThrow { validateSamplingToolsCapability(request, noToolsCaps) }
+ }
+
+ @Test
+ fun `tools without sampling tools capability throws InvalidParams`() {
+ val request = CreateMessageRequest(
+ CreateMessageRequestParams(
+ maxTokens = 10,
+ messages = minimalMessages,
+ tools = listOf(dummyTool),
+ ),
+ )
+ val exception = assertFailsWith {
+ validateSamplingToolsCapability(request, noToolsCaps)
+ }
+ assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code)
+ check(exception.message?.contains("tools") == true)
+ }
+
+ @Test
+ fun `toolChoice without sampling tools capability throws InvalidParams`() {
+ val request = CreateMessageRequest(
+ CreateMessageRequestParams(
+ maxTokens = 10,
+ messages = minimalMessages,
+ toolChoice = ToolChoice(mode = ToolChoice.Mode.Required),
+ ),
+ )
+ val exception = assertFailsWith {
+ validateSamplingToolsCapability(request, noToolsCaps)
+ }
+ assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code)
+ check(exception.message?.contains("toolChoice") == true)
+ }
+
+ @Test
+ fun `tools with sampling tools capability is accepted`() {
+ val request = CreateMessageRequest(
+ CreateMessageRequestParams(
+ maxTokens = 10,
+ messages = minimalMessages,
+ tools = listOf(dummyTool),
+ ),
+ )
+ assertDoesNotThrow { validateSamplingToolsCapability(request, withToolsCaps) }
+ }
+
+ @Test
+ fun `toolChoice with sampling tools capability is accepted`() {
+ val request = CreateMessageRequest(
+ CreateMessageRequestParams(
+ maxTokens = 10,
+ messages = minimalMessages,
+ toolChoice = ToolChoice(mode = ToolChoice.Mode.Auto),
+ ),
+ )
+ assertDoesNotThrow { validateSamplingToolsCapability(request, withToolsCaps) }
+ }
+}
diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt
deleted file mode 100644
index fb71904..0000000
--- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-package io.modelcontextprotocol.kotlin.sdk.client
-
-import dev.mokksy.mokksy.BuildingStep
-import dev.mokksy.mokksy.Mokksy
-import dev.mokksy.mokksy.StubConfiguration
-import dev.mokksy.mokksy.shutdown
-import dev.mokksy.mokksy.start
-import io.ktor.http.ContentType
-import io.ktor.http.HttpMethod
-import io.ktor.http.HttpStatusCode
-import io.ktor.sse.ServerSentEvent
-import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
-import io.modelcontextprotocol.kotlin.sdk.types.RequestId
-import kotlinx.coroutines.flow.Flow
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonObject
-import kotlinx.serialization.json.JsonPrimitive
-import kotlinx.serialization.json.buildJsonObject
-import kotlinx.serialization.json.contentOrNull
-import kotlinx.serialization.json.jsonObject
-import kotlinx.serialization.json.jsonPrimitive
-import kotlinx.serialization.json.putJsonObject
-
-const val MCP_SESSION_ID_HEADER = "MCP-Session-Id"
-
-internal class MockMcp(verbose: Boolean = false) : AutoCloseable {
-
- private val mokksy: Mokksy = Mokksy(verbose = verbose).apply {
- start()
- }
-
- fun verifyNoUnexpectedRequests() {
- mokksy.verifyNoUnexpectedRequests()
- }
-
- fun verifyNoUnmatchedStubs() {
- mokksy.verifyNoUnmatchedStubs()
- }
-
- val url = "${mokksy.baseUrl()}/mcp"
-
- @Suppress("LongParameterList")
- fun onInitialize(
- clientName: String? = null,
- sessionId: String,
- protocolVersion: String = "2025-03-26",
- serverName: String = "Mock MCP Server",
- serverVersion: String = "1.0.0",
- capabilities: JsonObject = buildJsonObject {
- putJsonObject("tools") {
- put("listChanged", JsonPrimitive(false))
- }
- },
- ) {
- val predicates = if (clientName != null) {
- arrayOf<(JSONRPCRequest?) -> Boolean>({
- it?.params?.jsonObject
- ?.get("clientInfo")?.jsonObject
- ?.get("name")?.jsonPrimitive
- ?.contentOrNull == clientName
- })
- } else {
- emptyArray()
- }
-
- handleWithResult(
- jsonRpcMethod = "initialize",
- sessionId = sessionId,
- bodyPredicates = predicates,
- // language=json
- result = """
- {
- "capabilities": $capabilities,
- "protocolVersion": "$protocolVersion",
- "serverInfo": {
- "name": "$serverName",
- "version": "$serverVersion"
- },
- "_meta": {
- "foo": "bar"
- }
- }
- """.trimIndent(),
- )
- }
-
- fun onJSONRPCRequest(
- httpMethod: HttpMethod = HttpMethod.Post,
- jsonRpcMethod: String,
- expectedSessionId: String? = null,
- vararg bodyPredicates: (JSONRPCRequest) -> Boolean,
- ): BuildingStep = mokksy.method(
- configuration = StubConfiguration(removeAfterMatch = true),
- httpMethod = httpMethod,
- requestType = JSONRPCRequest::class,
- ) {
- path("/mcp")
- expectedSessionId?.let {
- containsHeader(MCP_SESSION_ID_HEADER, it)
- }
- bodyMatchesPredicate(
- description = "JSON-RPC version is '2.0'",
- predicate =
- {
- it!!.jsonrpc == "2.0"
- },
- )
- bodyMatchesPredicate(
- description = "JSON-RPC Method should be '$jsonRpcMethod'",
- predicate =
- {
- it!!.method == jsonRpcMethod
- },
- )
- bodyPredicates.forEach { predicate ->
- bodyMatchesPredicate(predicate = { predicate.invoke(it!!) })
- }
- }
-
- @Suppress("LongParameterList")
- fun handleWithResult(
- httpMethod: HttpMethod = HttpMethod.Post,
- jsonRpcMethod: String,
- expectedSessionId: String? = null,
- sessionId: String,
- contentType: ContentType = ContentType.Application.Json,
- statusCode: HttpStatusCode = HttpStatusCode.OK,
- vararg bodyPredicates: (JSONRPCRequest) -> Boolean,
- result: () -> JsonObject,
- ) {
- onJSONRPCRequest(
- httpMethod = httpMethod,
- jsonRpcMethod = jsonRpcMethod,
- expectedSessionId = expectedSessionId,
- bodyPredicates = bodyPredicates,
- ) respondsWith {
- val requestId = when (val id = request.body().id) {
- is RequestId.NumberId -> id.value.toString()
- is RequestId.StringId -> "\"${id.value}\""
- }
- val resultObject = result.invoke()
- // language=json
- body = """
- {
- "jsonrpc": "2.0",
- "id": $requestId,
- "result": $resultObject
- }
- """.trimIndent()
- this.contentType = contentType
- headers += MCP_SESSION_ID_HEADER to sessionId
- httpStatus = statusCode
- }
- }
-
- @Suppress("LongParameterList")
- fun handleWithResult(
- httpMethod: HttpMethod = HttpMethod.Post,
- jsonRpcMethod: String,
- expectedSessionId: String? = null,
- sessionId: String,
- contentType: ContentType = ContentType.Application.Json,
- statusCode: HttpStatusCode = HttpStatusCode.OK,
- vararg bodyPredicates: (JSONRPCRequest) -> Boolean,
- result: String,
- ) {
- handleWithResult(
- httpMethod = httpMethod,
- jsonRpcMethod = jsonRpcMethod,
- expectedSessionId = expectedSessionId,
- sessionId = sessionId,
- contentType = contentType,
- statusCode = statusCode,
- bodyPredicates = bodyPredicates,
- result = {
- Json.parseToJsonElement(result).jsonObject
- },
- )
- }
-
- @Suppress("LongParameterList")
- fun handleJSONRPCRequest(
- httpMethod: HttpMethod = HttpMethod.Post,
- jsonRpcMethod: String,
- expectedSessionId: String? = null,
- sessionId: String,
- contentType: ContentType = ContentType.Application.Json,
- statusCode: HttpStatusCode = HttpStatusCode.OK,
- vararg bodyPredicates: (JSONRPCRequest?) -> Boolean,
- bodyBuilder: () -> String = { "" },
- ) {
- onJSONRPCRequest(
- httpMethod = httpMethod,
- jsonRpcMethod = jsonRpcMethod,
- expectedSessionId = expectedSessionId,
- bodyPredicates = bodyPredicates,
- ) respondsWith {
- body = bodyBuilder.invoke()
- this.contentType = contentType
- headers += MCP_SESSION_ID_HEADER to sessionId
- httpStatus = statusCode
- }
- }
-
- fun onSubscribe(httpMethod: HttpMethod = HttpMethod.Post, sessionId: String): BuildingStep = mokksy.method(
- httpMethod = httpMethod,
- name = "MCP GETs",
- requestType = Any::class,
- ) {
- path("/mcp")
- containsHeader(MCP_SESSION_ID_HEADER, sessionId)
- containsHeader("Accept", "application/json,text/event-stream")
- containsHeader("Cache-Control", "no-store")
- }
-
- fun handleSubscribeWithGet(sessionId: String, block: () -> Flow) {
- onSubscribe(
- httpMethod = HttpMethod.Get,
- sessionId = sessionId,
- ) respondsWithSseStream {
- headers += MCP_SESSION_ID_HEADER to sessionId
- this.flow = block.invoke()
- }
- }
-
- fun mockUnsubscribeRequest(sessionId: String) {
- mokksy.delete(
- configuration = StubConfiguration(removeAfterMatch = true),
- requestType = JSONRPCRequest::class,
- ) {
- path("/mcp")
- containsHeader(MCP_SESSION_ID_HEADER, sessionId)
- } respondsWith {
- body = null
- }
- }
-
- override fun close() {
- mokksy.shutdown()
- }
-}
diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt
deleted file mode 100644
index a1fe89e..0000000
--- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt
+++ /dev/null
@@ -1,293 +0,0 @@
-package io.modelcontextprotocol.kotlin.sdk.client
-
-import io.kotest.matchers.collections.shouldContain
-import io.kotest.matchers.shouldBe
-import io.ktor.http.ContentType
-import io.ktor.http.HttpMethod
-import io.ktor.http.HttpStatusCode
-import io.ktor.sse.ServerSentEvent
-import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
-import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
-import io.modelcontextprotocol.kotlin.sdk.types.Implementation
-import io.modelcontextprotocol.kotlin.sdk.types.Tool
-import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.runBlocking
-import kotlinx.serialization.json.buildJsonObject
-import kotlinx.serialization.json.put
-import kotlinx.serialization.json.putJsonObject
-import org.junit.jupiter.api.TestInstance
-import kotlin.test.Test
-import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
-import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.Uuid
-
-@OptIn(ExperimentalUuidApi::class)
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {
-
- @Test
- fun `Should skip empty SSE`(): Unit = runBlocking {
- val client = Client(
- clientInfo = Implementation(
- name = "client1",
- version = "1.0.0",
- ),
- options = ClientOptions(
- capabilities = ClientCapabilities(),
- ),
- )
- val sessionId = Uuid.random().toString()
-
- mockMcp.onJSONRPCRequest(
- httpMethod = HttpMethod.Post,
- jsonRpcMethod = "initialize",
- ).respondsWithStream {
- headers += MCP_SESSION_ID_HEADER to sessionId
- flow = flowOf(
- "id: ${Uuid.random()}\n",
- "data:\n", // empty data
- "\n",
- "id: ${Uuid.random()}\n",
- "data: \t \n", // tabs and spaces
- "\n",
- "id: ${Uuid.random()}\n",
- "event: message\n",
- // multiline data
- "data: {\n",
- "data: \"result\":{\n" +
- "data: \"protocolVersion\":\"2025-06-18\",\n" +
- "data: \"capabilities\":{},\n" +
- "data: \"serverInfo\":{\"name\":\"simple-streamable-http-server\",\"version\":\"1.0.0\"}\n" +
- "data: },\n" +
- "data: \"jsonrpc\":\"2.0\",\n" +
- "data: \"id\":\"7ce065b0678f49e5b04ce5a0fcc7d518\"\n" +
- "data: }\n",
- "\n",
- )
- }
-
- mockMcp.handleJSONRPCRequest(
- jsonRpcMethod = "notifications/initialized",
- expectedSessionId = sessionId,
- sessionId = sessionId,
- statusCode = HttpStatusCode.Accepted,
- )
-
- mockMcp.handleSubscribeWithGet(sessionId) {
- emptyFlow()
- }
-
- connect(client)
- }
-
- @Test
- fun `test streamableHttpClient`() = runBlocking {
- val client = Client(
- clientInfo = Implementation(
- name = "client1",
- version = "1.0.0",
- ),
- options = ClientOptions(
- capabilities = ClientCapabilities(),
- ),
- )
-
- val sessionId = Uuid.random().toString()
-
- mockMcp.onInitialize(
- clientName = "client1",
- sessionId = sessionId,
- )
-
- mockMcp.handleJSONRPCRequest(
- jsonRpcMethod = "notifications/initialized",
- expectedSessionId = sessionId,
- sessionId = sessionId,
- statusCode = HttpStatusCode.Accepted,
- )
-
- mockMcp.handleSubscribeWithGet(sessionId) {
- flow {
- delay(500.milliseconds)
- emit(
- ServerSentEvent(
- event = "message",
- id = "1",
- data = @Suppress("MaxLineLength")
- //language=json
- """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""",
- ),
- )
- delay(200.milliseconds)
- emit(
- ServerSentEvent(
- data = @Suppress("MaxLineLength")
- //language=json
- """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""",
- ),
- )
- }
- }
-
- // TODO: how to get notifications via Client API?
-
- mockMcp.handleWithResult(
- jsonRpcMethod = "tools/list",
- sessionId = sessionId,
- // language=json
- result = """
- {
- "tools": [
- {
- "name": "get_weather",
- "title": "Weather Information Provider",
- "description": "Get current weather information for a location",
- "inputSchema": {
- "type": "object",
- "properties": {
- "location": {
- "type": "string",
- "description": "City name or zip code"
- }
- },
- "required": ["location"]
- },
- "outputSchema": {
- "type": "object",
- "properties": {
- "temperature": {
- "type": "number",
- "description": "Temperature, Celsius"
- }
- },
- "required": ["temperature"]
- },
- "_meta": {}
- }
- ]
- }
- """.trimIndent(),
- )
-
- connect(client)
-
- val listToolsResult = client.listTools()
-
- listToolsResult.tools shouldContain Tool(
- name = "get_weather",
- title = "Weather Information Provider",
- description = "Get current weather information for a location",
- inputSchema = ToolSchema(
- properties = buildJsonObject {
- putJsonObject("location") {
- put("type", "string")
- put("description", "City name or zip code")
- }
- },
- required = listOf("location"),
- ),
- outputSchema = ToolSchema(
- properties = buildJsonObject {
- putJsonObject("temperature") {
- put("type", "number")
- put("description", "Temperature, Celsius")
- }
- },
- required = listOf("temperature"),
- ),
- annotations = null,
- meta = EmptyJsonObject,
- )
-
- client.close()
- }
-
- @Test
- fun `terminateSession sends DELETE request`() = runBlocking {
- val client = Client(
- clientInfo = Implementation(name = "client1", version = "1.0.0"),
- options = ClientOptions(capabilities = ClientCapabilities()),
- )
- val sessionId = Uuid.random().toString()
-
- mockMcp.onInitialize(clientName = "client1", sessionId = sessionId)
- mockMcp.handleJSONRPCRequest(
- jsonRpcMethod = "notifications/initialized",
- expectedSessionId = sessionId,
- sessionId = sessionId,
- statusCode = HttpStatusCode.Accepted,
- )
- mockMcp.handleSubscribeWithGet(sessionId) { emptyFlow() }
-
- connect(client)
-
- mockMcp.mockUnsubscribeRequest(sessionId = sessionId)
- (client.transport as StreamableHttpClientTransport).terminateSession()
- (client.transport as StreamableHttpClientTransport).sessionId shouldBe null
-
- client.close()
- }
-
- @Test
- fun `handle MethodNotAllowed`() = runBlocking {
- checkSupportNonStreamingResponse(
- ContentType.Text.EventStream,
- HttpStatusCode.MethodNotAllowed,
- )
- }
-
- @Test
- fun `handle non-streaming response`() = runBlocking {
- checkSupportNonStreamingResponse(
- ContentType.Application.Json,
- HttpStatusCode.OK,
- )
- }
-
- private suspend fun checkSupportNonStreamingResponse(contentType: ContentType, statusCode: HttpStatusCode) {
- val sessionId = "SID_${Uuid.random().toHexString()}"
- val clientName = "client-${Uuid.random().toHexString()}"
- val client = Client(
- clientInfo = Implementation(name = clientName, version = "1.0.0"),
- options = ClientOptions(
- capabilities = ClientCapabilities(),
- ),
- )
-
- mockMcp.onInitialize(clientName = clientName, sessionId = sessionId)
-
- mockMcp.handleJSONRPCRequest(
- jsonRpcMethod = "notifications/initialized",
- expectedSessionId = sessionId,
- sessionId = sessionId,
- statusCode = HttpStatusCode.Accepted,
- )
-
- mockMcp.onSubscribe(
- httpMethod = HttpMethod.Get,
- sessionId = sessionId,
- ) respondsWith {
- headers += MCP_SESSION_ID_HEADER to sessionId
- body = null
- httpStatus = statusCode
- this.contentType = contentType
- }
-
- mockMcp.handleWithResult(jsonRpcMethod = "ping", sessionId = sessionId) {
- buildJsonObject {}
- }
-
- connect(client)
-
- delay(1.seconds)
-
- client.ping() // connection is still alive
-
- client.close()
- }
-}
diff --git a/kotlin-sdk-core/.openapi-generator-ignore b/kotlin-sdk-core/.openapi-generator-ignore
deleted file mode 100644
index dbdf930..0000000
--- a/kotlin-sdk-core/.openapi-generator-ignore
+++ /dev/null
@@ -1,29 +0,0 @@
-# OpenAPI Generator Ignore
-# Generated by openapi-generator https://github.com/openapitools/openapi-generator
-
-# Use this file to prevent files from being overwritten by the generator.
-# The patterns follow closely to .gitignore or .dockerignore.
-
-# As an example, the C# client generator defines ApiClient.cs.
-# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
-**/auth/**
-**/infrastructure/**
-!**/Base64ByteArray.kt
-!**/Bytes.kt
-
-
-## Exclude Models which are causing problems
-**/JSONRPCResponse.kt
-**/Result.kt
-**/ListToolsResult.kt
-**/InitializeRequest.kt
-**/InitializeResult.kt
-**/ServerCapabilities.kt
-**/InitializeRequestParams.kt
-**/CreateMessageRequest.kt
-**/CreateMessageRequestParams.kt
-**/ClientCapabilities.kt
-**/Tool.kt
-**/ToolInputSchema.kt
-**/ToolOutputSchema.kt
-
diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api
index e7cd0b8..b3e88d8 100644
--- a/kotlin-sdk-core/api/kotlin-sdk-core.api
+++ b/kotlin-sdk-core/api/kotlin-sdk-core.api
@@ -59,6 +59,7 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol {
public abstract fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V
public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ protected final fun enableConcurrentDispatch ()V
public final fun getFallbackNotificationHandler ()Lkotlin/jvm/functions/Function2;
public final fun getFallbackRequestHandler ()Lkotlin/jvm/functions/Function3;
public final fun getNotificationHandlers ()Ljava/util/Map;
@@ -71,6 +72,7 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol {
public static synthetic fun notification$default (Lio/modelcontextprotocol/kotlin/sdk/shared/Protocol;Lio/modelcontextprotocol/kotlin/sdk/types/Notification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public fun onClose ()V
public fun onError (Ljava/lang/Throwable;)V
+ protected fun onInitializedNotification ()V
public final fun removeNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V
public final fun removeRequestHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V
public final fun request (Lio/modelcontextprotocol/kotlin/sdk/types/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -79,19 +81,23 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol {
public final fun setFallbackRequestHandler (Lkotlin/jvm/functions/Function3;)V
public final fun setNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function1;)V
public final fun setRequestHandlerInternal (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function3;)V
+ protected fun wrapRequestHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function3;
}
public final class io/modelcontextprotocol/kotlin/sdk/shared/ProtocolKt {
public static final field IMPLEMENTATION_NAME Ljava/lang/String;
+ public static final fun currentRequestHandlerExtra (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun getDEFAULT_REQUEST_TIMEOUT ()J
}
public class io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions {
- public synthetic fun (ZJILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public synthetic fun (ZJLkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (ZJLkotlin/coroutines/CoroutineContext;IIILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (ZJLkotlin/coroutines/CoroutineContext;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getEnforceStrictCapabilities ()Z
+ public final fun getHandlerCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
public final fun getTimeout-UwyO8pc ()J
public final fun setEnforceStrictCapabilities (Z)V
+ public final fun setHandlerCoroutineContext (Lkotlin/coroutines/CoroutineContext;)V
public final fun setTimeout-LRDsOJo (J)V
}
@@ -106,8 +112,16 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferKt {
public static final fun serializeMessage (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;)Ljava/lang/String;
}
-public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra {
- public fun ()V
+public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra : kotlin/coroutines/AbstractCoroutineContextElement {
+ public static final field Key Lio/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra$Key;
+ public final fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method;
+ public final fun getRequestId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;
+ public final fun sendNotification (Lio/modelcontextprotocol/kotlin/sdk/types/Notification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public final fun sendRequest (Lio/modelcontextprotocol/kotlin/sdk/types/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun sendRequest$default (Lio/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra;Lio/modelcontextprotocol/kotlin/sdk/types/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra$Key : kotlin/coroutines/CoroutineContext$Key {
}
public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestOptions : io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions {
@@ -124,6 +138,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestOptions : io
public fun toString ()Ljava/lang/String;
}
+public final class io/modelcontextprotocol/kotlin/sdk/shared/TooLongFrameException : java/io/IOException {
+ public fun (JI)V
+ public final fun getFrameSize ()J
+ public final fun getMaxFrameSize ()I
+}
+
public abstract interface class io/modelcontextprotocol/kotlin/sdk/shared/Transport {
public abstract fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun onClose (Lkotlin/jvm/functions/Function0;)V
@@ -336,6 +356,53 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/BooleanSchema : io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema$Companion;
+ public fun ()V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/Boolean;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDefault ()Ljava/lang/Boolean;
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getTitle ()Ljava/lang/String;
+ public final fun getType ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/BooleanSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/BooleanSchema$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/CacheScope : java/lang/Enum {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope$Companion;
+ public static final field Private Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope;
+ public static final field Public Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope;
+ public static fun getEntries ()Lkotlin/enums/EnumEntries;
+ public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope;
+ public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/CacheScope$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$Companion;
public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;)V
@@ -349,6 +416,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequest : io
public final fun getName ()Ljava/lang/String;
public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;
public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams;
+ public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -373,24 +441,26 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestBuild
public final fun arguments (Lkotlin/jvm/functions/Function1;)V
public final fun arguments (Lkotlinx/serialization/json/JsonObject;)V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
public final fun getName ()Ljava/lang/String;
public final fun setName (Ljava/lang/String;)V
}
public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams$Companion;
- public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lkotlinx/serialization/json/JsonObject;
- public final fun component3-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
- public final fun copy-EDEVuBg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;
- public static synthetic fun copy-EDEVuBg$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;
+ public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
+ public final fun component4-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy-6R815Ik (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;
+ public static synthetic fun copy-6R815Ik$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;
public fun equals (Ljava/lang/Object;)Z
public final fun getArguments ()Lkotlinx/serialization/json/JsonObject;
public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
public final fun getName ()Ljava/lang/String;
+ public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -569,22 +639,32 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesKt {
+ public static final fun getSupportsUrl (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;)Z
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion;
public fun ()V
- public fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
- public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
+ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V
+ public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;
- public final fun component3 ()Lkotlinx/serialization/json/JsonObject;
- public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
- public final fun copy (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
- public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
+ public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;
+ public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;
+ public final fun component5 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun component6 ()Ljava/util/Map;
+ public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
public fun equals (Ljava/lang/Object;)Z
- public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getElicitation ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;
public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getExtensions ()Ljava/util/Map;
public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;
- public final fun getSampling ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getSampling ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;
+ public final fun getTasks ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -601,8 +681,39 @@ public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapa
}
public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion {
- public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject;
- public final fun getSampling ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getElicitation ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;
+ public final fun getSampling ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation$Companion;
+ public fun ()V
+ public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
+ public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun component2 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getForm ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getUrl ()Lkotlinx/serialization/json/JsonObject;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
@@ -635,17 +746,169 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$R
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling$Companion;
+ public fun ()V
+ public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
+ public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun component2 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getContext ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getTools ()Lkotlinx/serialization/json/JsonObject;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Companion;
+ public fun ()V
+ public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;)V
+ public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun component2 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;
+ public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getCancel ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getList ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getRequests ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Companion;
+ public fun ()V
+ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;
+ public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;
+ public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getElicitation ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;
+ public final fun getSampling ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation$Companion;
+ public fun ()V
+ public fun (Lkotlinx/serialization/json/JsonObject;)V
+ public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getCreate ()Lkotlinx/serialization/json/JsonObject;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling$Companion;
+ public fun ()V
+ public fun (Lkotlinx/serialization/json/JsonObject;)V
+ public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getCreateMessage ()Lkotlinx/serialization/json/JsonObject;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder {
public fun ()V
public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
- public final fun elicitation (Lkotlin/jvm/functions/Function1;)V
- public final fun elicitation (Lkotlinx/serialization/json/JsonObject;)V
+ public final fun elicitation (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;)V
public final fun experimental (Lkotlin/jvm/functions/Function1;)V
public final fun experimental (Lkotlinx/serialization/json/JsonObject;)V
+ public final fun extensions (Ljava/util/Map;)V
public final fun roots (Ljava/lang/Boolean;)V
public static synthetic fun roots$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder;Ljava/lang/Boolean;ILjava/lang/Object;)V
- public final fun sampling (Lkotlin/jvm/functions/Function1;)V
- public final fun sampling (Lkotlinx/serialization/json/JsonObject;)V
+ public final fun sampling (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;)V
}
public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ClientNotification : io/modelcontextprotocol/kotlin/sdk/types/Notification {
@@ -717,7 +980,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestBuild
public fun ()V
public final fun argument (Ljava/lang/String;Ljava/lang/String;)V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
public final fun context (Ljava/util/Map;)V
public final fun context (Lkotlin/jvm/functions/Function1;)V
public final fun ref (Lio/modelcontextprotocol/kotlin/sdk/types/Reference;)V
@@ -897,6 +1160,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ContentTypes : java/
public static final field IMAGE Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes;
public static final field RESOURCE_LINK Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes;
public static final field TEXT Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes;
+ public static final field TOOL_RESULT Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes;
+ public static final field TOOL_USE Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public final fun getValue ()Ljava/lang/String;
public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes;
@@ -925,6 +1190,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest
public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams;
public final fun getStopSequences ()Ljava/util/List;
public final fun getSystemPrompt ()Ljava/lang/String;
+ public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
public final fun getTemperature ()Ljava/lang/Double;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
@@ -948,7 +1214,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest
public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
public final fun getContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;
public final fun getMaxTokens ()Ljava/lang/Integer;
public final fun getStopSequences ()Ljava/util/List;
@@ -969,9 +1235,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest
public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams$Companion;
- public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
+ public final fun component10 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;
+ public final fun component11 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
+ public final fun component12-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
public final fun component2 ()Ljava/util/List;
public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;
public final fun component4 ()Ljava/lang/String;
@@ -979,9 +1248,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest
public final fun component6 ()Ljava/lang/Double;
public final fun component7 ()Ljava/util/List;
public final fun component8 ()Lkotlinx/serialization/json/JsonObject;
- public final fun component9-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
- public final fun copy-qtvSJzw (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;
- public static synthetic fun copy-qtvSJzw$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;
+ public final fun component9 ()Ljava/util/List;
+ public final fun copy-7OuPiQU (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;
+ public static synthetic fun copy-7OuPiQU$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;
public fun equals (Ljava/lang/Object;)Z
public final fun getIncludeContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;
public final fun getMaxTokens ()I
@@ -991,7 +1260,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest
public final fun getModelPreferences ()Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;
public final fun getStopSequences ()Ljava/util/List;
public final fun getSystemPrompt ()Ljava/lang/String;
+ public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
public final fun getTemperature ()Ljava/lang/Double;
+ public final fun getToolChoice ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;
+ public final fun getTools ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -1013,17 +1285,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest
public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult$Companion;
- public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Role;
- public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;
+ public final fun component2 ()Ljava/util/List;
public final fun component3 ()Ljava/lang/String;
public final fun component4-6olV-UY ()Ljava/lang/String;
public final fun component5 ()Lkotlinx/serialization/json/JsonObject;
- public final fun copy-ctVX1tw (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;
- public static synthetic fun copy-ctVX1tw$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;
+ public final fun copy-ctVX1tw (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;
+ public static synthetic fun copy-ctVX1tw$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;
public fun equals (Ljava/lang/Object;)Z
- public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;
+ public final fun getContent ()Ljava/util/List;
public fun getMeta ()Lkotlinx/serialization/json/JsonObject;
public final fun getModel ()Ljava/lang/String;
public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role;
@@ -1134,6 +1408,142 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CustomRequest$Compan
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest$Companion;
+ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;)V
+ public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;
+ public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest;Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getMeta--Khlwt8 ()Lkotlinx/serialization/json/JsonObject;
+ public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method;
+ public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;
+ public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams$Companion;
+ public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1--Khlwt8 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy-hVVZdjo (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;
+ public static synthetic fun copy-hVVZdjo$default (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getMeta--Khlwt8 ()Lkotlinx/serialization/json/JsonObject;
+ public synthetic fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverResult : io/modelcontextprotocol/kotlin/sdk/types/ServerResult {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult$Companion;
+ public fun (Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Ljava/lang/String;Ljava/lang/String;JLio/modelcontextprotocol/kotlin/sdk/types/CacheScope;Lkotlinx/serialization/json/JsonObject;)V
+ public synthetic fun (Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Ljava/lang/String;Ljava/lang/String;JLio/modelcontextprotocol/kotlin/sdk/types/CacheScope;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/List;
+ public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()J
+ public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope;
+ public final fun component7 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy (Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Ljava/lang/String;Ljava/lang/String;JLio/modelcontextprotocol/kotlin/sdk/types/CacheScope;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Ljava/lang/String;Ljava/lang/String;JLio/modelcontextprotocol/kotlin/sdk/types/CacheScope;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getCacheScope ()Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope;
+ public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
+ public final fun getInstructions ()Ljava/lang/String;
+ public fun getMeta ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getResultType ()Ljava/lang/String;
+ public final fun getSupportedVersions ()Ljava/util/List;
+ public final fun getTtlMs ()J
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/DiscoverResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverResult$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/DoubleSchema : io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema$Companion;
+ public fun ()V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/Double;
+ public final fun component4 ()Ljava/lang/Double;
+ public final fun component5 ()Ljava/lang/Double;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDefault ()Ljava/lang/Double;
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getMaximum ()Ljava/lang/Double;
+ public final fun getMinimum ()Ljava/lang/Double;
+ public final fun getTitle ()Ljava/lang/String;
+ public final fun getType ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/DoubleSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/DoubleSchema$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequest : io/modelcontextprotocol/kotlin/sdk/types/ServerRequest {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$Companion;
public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;)V
@@ -1169,56 +1579,70 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$Compan
public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
public final fun getMessage ()Ljava/lang/String;
public final fun requestedSchema (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;)V
public final fun requestedSchema (Lkotlin/jvm/functions/Function1;)V
public final fun setMessage (Ljava/lang/String;)V
}
-public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams {
- public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$Companion;
- public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams : io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams$Companion;
+ public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
- public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;
- public final fun component3-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
- public final fun copy-EDEVuBg (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;
- public static synthetic fun copy-EDEVuBg$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;
+ public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
+ public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;
+ public final fun component4-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy-6R815Ik (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;
+ public static synthetic fun copy-6R815Ik$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;
public fun equals (Ljava/lang/Object;)Z
- public final fun getMessage ()Ljava/lang/String;
+ public fun getMessage ()Ljava/lang/String;
public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getMode ()Ljava/lang/String;
public final fun getRequestedSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;
+ public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
-public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
- public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$$serializer;
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams$$serializer;
public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
- public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
- public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$Companion;
+ public abstract fun getMessage ()Ljava/lang/String;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema$Companion;
- public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V
- public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
- public final fun component2 ()Ljava/util/List;
- public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;
- public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;
+ public fun (Ljava/lang/String;Ljava/util/Map;Ljava/util/List;)V
+ public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun component3 ()Ljava/util/List;
+ public final fun copy (Ljava/lang/String;Ljava/util/Map;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;
public fun equals (Ljava/lang/Object;)Z
- public final fun getProperties ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getProperties ()Ljava/util/Map;
public final fun getRequired ()Ljava/util/List;
+ public final fun getSchema ()Ljava/lang/String;
public final fun getType ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
@@ -1239,6 +1663,43 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams : io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams$Companion;
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
+ public final fun component5-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy-4asOmiE (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams;
+ public static synthetic fun copy-4asOmiE$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getElicitationId ()Ljava/lang/String;
+ public fun getMessage ()Ljava/lang/String;
+ public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getMode ()Ljava/lang/String;
+ public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;
+ public final fun getUrl ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestedSchemaBuilder {
public fun ()V
public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;
@@ -1294,6 +1755,70 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Compani
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification$Companion;
+ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;)V
+ public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
+ public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method;
+ public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
+ public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams$Companion;
+ public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V
+ public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getElicitationId ()Ljava/lang/String;
+ public fun getMeta ()Lkotlinx/serialization/json/JsonObject;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationKt {
+ public static final fun ElicitRequestParams-EDEVuBg (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;
+ public static synthetic fun ElicitRequestParams-EDEVuBg$default (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/Elicitation_dslKt {
public static final fun buildElicitRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;
}
@@ -1360,6 +1885,43 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/EmptyResult$Companio
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/EnumOption {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption$Companion;
+ public fun (Ljava/lang/String;Ljava/lang/String;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getConst ()Ljava/lang/String;
+ public final fun getTitle ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/EnumOption$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/EnumOption$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition : io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition$Companion;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Companion;
public fun (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;)V
@@ -1395,7 +1957,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Com
public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
public final fun getArguments ()Ljava/util/Map;
public final fun getName ()Ljava/lang/String;
public final fun setArguments (Ljava/util/Map;)V
@@ -1840,7 +2402,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequest$Co
public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
public final fun capabilities (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;)V
public final fun capabilities (Lkotlin/jvm/functions/Function1;)V
public final fun getProtocolVersion ()Ljava/lang/String;
@@ -1955,6 +2517,44 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializedNotificat
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/IntegerSchema : io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema$Companion;
+ public fun ()V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/Integer;
+ public final fun component4 ()Ljava/lang/Integer;
+ public final fun component5 ()Ljava/lang/Integer;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDefault ()Ljava/lang/Integer;
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getMaximum ()Ljava/lang/Integer;
+ public final fun getMinimum ()Ljava/lang/Integer;
+ public final fun getTitle ()Ljava/lang/String;
+ public final fun getType ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/IntegerSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/IntegerSchema$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCEmptyMessage : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage {
public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCEmptyMessage;
public fun equals (Ljava/lang/Object;)Z
@@ -2119,6 +2719,43 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/JsonUtilsKt {
public static final fun toJson (Ljava/util/Map;)Ljava/util/Map;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema$Companion;
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/util/List;
+ public final fun component4 ()Ljava/util/List;
+ public final fun component5 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDefault ()Ljava/lang/String;
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getEnumNames ()Ljava/util/List;
+ public final fun getEnumValues ()Ljava/util/List;
+ public final fun getTitle ()Ljava/lang/String;
+ public final fun getType ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$Companion;
public fun ()V
@@ -2155,7 +2792,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$C
public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult {
@@ -2226,7 +2863,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate
public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult {
@@ -2297,7 +2934,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest
public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult {
@@ -2367,7 +3004,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$Com
public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult {
@@ -2501,7 +3138,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$Com
public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult {
@@ -2625,7 +3262,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Logging_dslKt {
public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/types/McpDsl : java/lang/annotation/Annotation {
}
-public final class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/lang/Exception {
+public class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/lang/Exception {
public fun (I)V
public fun (ILjava/lang/String;)V
public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V
@@ -2635,7 +3272,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/
public final fun getData ()Lkotlinx/serialization/json/JsonElement;
}
-public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/MediaContent : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock {
+public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/MediaContent : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock, io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent$Companion;
}
@@ -2699,6 +3336,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Defined : jav
public static final field Initialize Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field LoggingSetLevel Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsCancelled Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
+ public static final field NotificationsElicitationComplete Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsInitialized Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsMessage Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsProgress Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
@@ -2718,6 +3356,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Defined : jav
public static final field ResourcesUnsubscribe Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field RootsList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field SamplingCreateMessage Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
+ public static final field ServerDiscover Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field TasksCancel Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field TasksGet Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field TasksList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
@@ -2796,6 +3435,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ModelPreferences$Com
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema$Companion;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Notification {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Notification$Companion;
public abstract fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method;
@@ -2814,6 +3461,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams$C
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition : io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition$Companion;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest : io/modelcontextprotocol/kotlin/sdk/types/Request {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Companion;
public fun getCursor ()Ljava/lang/String;
@@ -2910,13 +3565,21 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companio
public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest_dslKt {
public static final fun buildPingRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest;
}
+public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition$Companion;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/Progress {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Progress$Companion;
public fun (DLjava/lang/Double;Ljava/lang/String;)V
@@ -3216,6 +3879,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError$ErrorCode {
public static final field PARSE_ERROR I
public static final field REQUEST_TIMEOUT I
public static final field RESOURCE_NOT_FOUND I
+ public static final field UNSUPPORTED_PROTOCOL_VERSION I
+ public static final field URL_ELICITATION_REQUIRED I
}
public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest {
@@ -3252,7 +3917,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$
public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder {
public fun ()V
public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest;
- public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
+ public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
public final fun getUri ()Ljava/lang/String;
public final fun setUri (Ljava/lang/String;)V
}
@@ -3460,8 +4125,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMeta {
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun get-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement;
+ public static final fun getClientCapabilities-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
+ public static final fun getClientInfo-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;
public final fun getJson ()Lkotlinx/serialization/json/JsonObject;
+ public static final fun getLogLevel-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;
public static final fun getProgressToken-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;
+ public static final fun getProtocolVersion-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public static final fun getRelatedTask-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
@@ -3872,13 +4541,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Roots_dslKt {
public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessage {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage$Companion;
- public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V
+ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent;Lkotlinx/serialization/json/JsonObject;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Role;
- public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;
- public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;
- public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;
+ public final fun component2 ()Ljava/util/List;
+ public final fun component3 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;
public fun equals (Ljava/lang/Object;)Z
- public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;
+ public final fun getContent ()Ljava/util/List;
+ public final fun getMeta ()Lkotlinx/serialization/json/JsonObject;
public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
@@ -3906,6 +4580,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuild
public final fun user (Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V
}
+public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent : io/modelcontextprotocol/kotlin/sdk/types/WithMeta {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent$Companion;
+ public abstract fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt {
public static final fun assistant (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function0;)V
public static final fun assistantAudio (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V
@@ -3921,22 +4604,26 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt {
public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Companion;
public fun ()V
- public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
- public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V
+ public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;
public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;
public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
public final fun component5 ()Lkotlinx/serialization/json/JsonObject;
- public final fun component6 ()Lkotlinx/serialization/json/JsonObject;
- public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
- public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
+ public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;
+ public final fun component7 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun component8 ()Ljava/util/Map;
+ public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
public fun equals (Ljava/lang/Object;)Z
public final fun getCompletions ()Lkotlinx/serialization/json/JsonObject;
public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getExtensions ()Ljava/util/Map;
public final fun getLogging ()Lkotlinx/serialization/json/JsonObject;
public final fun getPrompts ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;
public final fun getResources ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;
+ public final fun getTasks ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;
public final fun getTools ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
@@ -4019,6 +4706,97 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$R
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Companion;
+ public fun ()V
+ public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;)V
+ public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun component2 ()Lkotlinx/serialization/json/JsonObject;
+ public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;
+ public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;
+ public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getCancel ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getList ()Lkotlinx/serialization/json/JsonObject;
+ public final fun getRequests ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests {
+ public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Companion;
+ public fun ()V
+ public fun