Skip to content

Commit 03b815d

Browse files
committed
Merge remote-tracking branch 'origin/kpavlov/JSONRPCRequest-constructors' into kpavlov/JSONRPCRequest-constructors
2 parents 36a0d0e + 8909dee commit 03b815d

File tree

50 files changed

+1359
-1159
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1359
-1159
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ jobs:
5959

6060
- name: Upload Reports
6161
if: ${{ !cancelled() }}
62-
uses: actions/upload-artifact@v4
62+
uses: actions/upload-artifact@v5
6363
with:
6464
name: reports
6565
path: |
6666
**/build/reports/
6767
6868
- name: Publish Test Report
69-
uses: mikepenz/action-junit-report@v5
69+
uses: mikepenz/action-junit-report@v6
7070
if: ${{ !cancelled() }} # always run even if the previous step fails
7171
with:
7272
report_paths: '**/test-results/**/TEST-*.xml'

.github/workflows/samples.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151

5252
- name: Upload Reports
5353
if: ${{ !cancelled() }}
54-
uses: actions/upload-artifact@v4
54+
uses: actions/upload-artifact@v5
5555
with:
5656
name: reports-${{ matrix.sample }}
5757
path: |

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ out/
2121

2222
### Kotlin ###
2323
.kotlin
24+
kotlin-js-store
2425
yarn.lock
2526

2627
### Eclipse ###

AGENTS.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# kotlin-mcp-sdk
2+
3+
MCP Kotlin SDK — Kotlin Multiplatform implementation of the Model Context Protocol.
4+
5+
## Repository Layout
6+
- `kotlin-sdk-core`: Core protocol types, transport abstractions, WebSocket implementation
7+
- `kotlin-sdk-client`: Client transports (STDIO, SSE, StreamableHttp, WebSocket)
8+
- `kotlin-sdk-server`: Server transports + Ktor integration (STDIO, SSE, WebSocket)
9+
- `kotlin-sdk`: Umbrella module that aggregates all three above
10+
- `kotlin-sdk-test`: Shared test utilities
11+
- `samples/`: Three sample projects (composite builds)
12+
13+
## General Guidance
14+
- Avoid changing public API signatures. Run `./gradlew apiCheck` before every commit.
15+
- **Explicit API mode** is strict: all public APIs must have explicit visibility modifiers and return types.
16+
- Anything under an `internal` directory is not part of the public API and may change freely.
17+
- Package structure follows: `io.modelcontextprotocol.kotlin.sdk.*`
18+
- The SDK targets Kotlin 2.2+ and JVM 1.8+ as minimum.
19+
20+
## Building and Testing
21+
1. Build the project:
22+
```bash
23+
./gradlew build
24+
```
25+
2. Run tests. To save time, run only the module you changed:
26+
```bash
27+
./gradlew :kotlin-sdk-core:test
28+
./gradlew :kotlin-sdk-client:test
29+
./gradlew :kotlin-sdk-server:test
30+
```
31+
3. For platform-specific tests:
32+
```bash
33+
./gradlew jsTest
34+
./gradlew wasmJsTest
35+
```
36+
4. Check binary compatibility (required before commit):
37+
```bash
38+
./gradlew apiCheck
39+
```
40+
5. If you intentionally changed public APIs, update the API dump:
41+
```bash
42+
./gradlew apiDump
43+
```
44+
45+
## Tests
46+
- All tests for each module are located in `src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/`
47+
- Platform-specific tests go in `src/jvmTest/`, `src/jsTest/`, etc.
48+
- Use Kotest assertions (`shouldBe`, `shouldContain`, etc.) for readable test failures.
49+
- Use `shouldMatchJson` from Kotest for JSON validation.
50+
- Mock Ktor HTTP clients using `MockEngine` from `io.ktor:ktor-client-mock`.
51+
- Always add tests for new features or bug fixes, even if not explicitly requested.
52+
53+
## Code Conventions
54+
55+
### Multiplatform Patterns
56+
- Use `expect`/`actual` pattern for platform-specific implementations in `utils.*` files.
57+
- Test changes on JVM first, then verify platform-specific behavior if needed.
58+
- Supported targets: JVM (1.8+), JS/Wasm, iOS, watchOS, tvOS.
59+
60+
### Serialization
61+
- Use Kotlinx Serialization with explicit `@Serializable` annotations.
62+
- Custom serializers should be registered in the companion object.
63+
- JSON config is defined in `McpJson.kt` — use it consistently.
64+
65+
### Concurrency and State
66+
- Use `kotlinx.atomicfu` for thread-safe operations across platforms.
67+
- Prefer coroutines over callbacks where possible.
68+
- Transport implementations extend `AbstractTransport` for consistent callback management.
69+
70+
### Error Handling
71+
- Use sealed classes for representing different result states.
72+
- Map errors to JSON-RPC error codes in protocol handlers.
73+
- Log errors using `io.github.oshai.kotlinlogging.KotlinLogging`.
74+
75+
### Logging
76+
- Use `KotlinLogging.logger {}` for structured logging.
77+
- Never log sensitive data (credentials, tokens).
78+
79+
## Pull Requests
80+
- Base all PRs on the `main` branch.
81+
- PR title format: Brief description of the change
82+
- Include in PR description:
83+
- **What changed?**
84+
- **Why? Motivation and context**
85+
- **Breaking changes?** (if any)
86+
- Run `./gradlew apiCheck` before submitting.
87+
- Link PR to related issue (except for minor docs/typo fixes).
88+
- Follow [Kotlin Coding Conventions](https://kotlinlang.org/docs/reference/coding-conventions.html).
89+
90+
## Review Checklist
91+
- `./gradlew apiCheck` must pass.
92+
- `./gradlew test` must succeed for affected modules.
93+
- New tests added for any new feature or bug fix.
94+
- Documentation updated for user-facing changes.
95+
- No breaking changes to public APIs without discussion.

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true
1414
kotlin.native.ignoreDisabledTargets=true
1515

1616
group=io.modelcontextprotocol
17-
version=0.7.4-SNAPSHOT
17+
version=0.7.4

gradle/libs.versions.toml

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[versions]
22
# plugins version
3-
kotlin = "2.2.10"
4-
dokka = "2.0.0"
3+
kotlin = "2.2.21"
4+
dokka = "2.1.0"
55
atomicfu = "0.29.0"
66
ktlint = "13.1.0"
77
kover = "0.9.3"
@@ -14,18 +14,13 @@ serialization = "1.9.0"
1414
collections-immutable = "0.4.0"
1515
coroutines = "1.10.2"
1616
kotlinx-io = "0.8.0"
17-
ktor = "3.3.0"
17+
ktor = "3.2.3"
1818
logging = "7.0.13"
1919
slf4j = "2.0.17"
2020
kotest = "6.0.4"
2121
awaitility = "4.3.0"
2222
mokksy = "0.6.1"
2323

24-
# Samples
25-
mcp-kotlin = "0.7.3"
26-
anthropic = "2.9.0"
27-
shadow = "9.2.2"
28-
2924
[libraries]
3025
# Plugins
3126
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
@@ -47,9 +42,7 @@ ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" }
4742
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
4843
ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging"}
4944
ktor-client-apache5 = { group = "io.ktor", name = "ktor-client-apache5" }
50-
ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp" }
5145
ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" }
52-
ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty" }
5346
ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" }
5447
ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" }
5548

@@ -67,19 +60,8 @@ netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" }
6760
# Samples
6861
ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
6962
ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
70-
mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" }
71-
mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" }
72-
anthropic-java = { group = "com.anthropic", name = "anthropic-java", version.ref = "anthropic" }
73-
ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
74-
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
7563

7664
[plugins]
7765
kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" }
7866
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
7967
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
80-
81-
# Samples
82-
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
83-
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
84-
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
85-
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }

kotlin-sdk-client/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ kotlin {
4343
implementation(kotlin("test"))
4444
implementation(dependencies.platform(libs.ktor.bom))
4545
implementation(libs.ktor.client.mock)
46+
implementation(libs.ktor.server.websockets)
4647
implementation(libs.kotlinx.coroutines.test)
4748
implementation(libs.ktor.client.logging)
4849
}

kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
315315
* @throws IllegalStateException If the server does not support logging.
316316
*/
317317
public suspend fun setLoggingLevel(level: LoggingLevel, options: RequestOptions? = null): EmptyRequestResult =
318-
request<EmptyRequestResult>(SetLevelRequest(level), options)
318+
request(SetLevelRequest(level), options)
319319

320320
/**
321321
* Retrieves a prompt by name from the server.

kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification
1818
import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest
1919
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
2020
import kotlinx.coroutines.Dispatchers
21-
import kotlinx.coroutines.TimeoutCancellationException
2221
import kotlinx.coroutines.delay
2322
import kotlinx.coroutines.test.runTest
2423
import kotlinx.coroutines.withContext
@@ -29,9 +28,9 @@ import kotlinx.serialization.json.buildJsonObject
2928
import kotlin.test.Ignore
3029
import kotlin.test.Test
3130
import kotlin.test.assertEquals
31+
import kotlin.test.assertFailsWith
3232
import kotlin.test.assertNull
3333
import kotlin.test.assertTrue
34-
import kotlin.test.fail
3534
import kotlin.time.Duration.Companion.seconds
3635

3736
class StreamableHttpClientTransportTest {
@@ -404,26 +403,18 @@ class StreamableHttpClientTransportTest {
404403
),
405404
)
406405

407-
runCatching {
406+
try {
408407
// Real time-keeping is needed; otherwise Protocol will always throw TimeoutCancellationException in tests
409-
withContext(Dispatchers.Default.limitedParallelism(1)) {
410-
withTimeout(5.seconds) {
411-
client.connect(transport)
412-
}
413-
}
414-
}.onSuccess {
415-
fail("Expected client.connect to fail on invalid JSON response")
416-
}.onFailure { e ->
417-
when (e) {
418-
is TimeoutCancellationException -> fail("Client connect caused a hang", e)
419-
420-
is IllegalStateException -> {
421-
// Expected behavior: connect finishes and fails with an exception.
408+
assertFailsWith<IllegalStateException>(
409+
message = "Expected client.connect to fail on invalid JSON response",
410+
) {
411+
withContext(Dispatchers.Default.limitedParallelism(1)) {
412+
withTimeout(5.seconds) {
413+
client.connect(transport)
414+
}
422415
}
423-
424-
else -> fail("Unexpected exception during client.connect", e)
425416
}
426-
}.also {
417+
} finally {
427418
transport.close()
428419
}
429420
}

kotlin-sdk-core/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77
id("mcp.publishing")
88
id("mcp.dokka")
99
alias(libs.plugins.kotlinx.binary.compatibility.validator)
10-
id("org.openapi.generator") version "7.16.0"
10+
id("org.openapi.generator") version "7.17.0"
1111
}
1212

1313
/*
@@ -113,8 +113,8 @@ kotlin {
113113
api(libs.kotlinx.serialization.json)
114114
api(libs.kotlinx.coroutines.core)
115115
api(libs.kotlinx.io.core)
116-
api(libs.ktor.server.websockets)
117116
api(libs.kotlinx.collections.immutable)
117+
implementation(libs.ktor.server.websockets)
118118
implementation(libs.kotlin.logging)
119119
}
120120
}

0 commit comments

Comments
 (0)