docs: update client and server samples#598
Conversation
…nhance tool schemas
There was a problem hiding this comment.
Pull request overview
Updates the Kotlin client and weather STDIO server samples to align with current MCP Kotlin SDK usage and to support the “build client/server” guides on modelcontextprotocol.io.
Changes:
- Updated the weather server sample to use a CIO-backed Ktor client, improved tool schemas/annotations, and added basic input validation plus Result-based API wrappers.
- Refined the Kotlin CLI client sample to require
ANTHROPIC_API_KEY, improve subprocess handling, and implement proper Anthropic tool-result message flow. - Bumped sample dependency versions (notably
mcp-kotlinto0.9.0and Anthropic SDK to2.15.0) and adjusted docs/example invocation paths.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt | Updates jar path + stderr handling; aligns tool name with server (get_alerts). |
| samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/WeatherApi.kt | Refactors Weather.gov API wrappers to return Result and updates response models. |
| samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt | Restructures server startup, adds CIO client creation, tool annotations, and improved tool behavior. |
| samples/weather-stdio-server/gradle/libs.versions.toml | Removes unused libs; bumps MCP Kotlin version. |
| samples/weather-stdio-server/build.gradle.kts | Cleans up dependencies (removes unused server/collections libs). |
| samples/weather-stdio-server/README.md | Minor table/order update for tool docs. |
| samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt | Requires ANTHROPIC_API_KEY and simplifies CLI usage. |
| samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt | Updates Anthropic/tool-calling flow and improves subprocess lifecycle handling. |
| samples/kotlin-mcp-client/gradle/libs.versions.toml | Bumps Anthropic + MCP Kotlin versions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| onFailure = { _ -> | ||
| CallToolResult( | ||
| content = listOf( | ||
| TextContent( | ||
| "Failed to retrieve grid point data for coordinates: $latitude, $longitude. " + | ||
| "This location may not be supported by the NWS API (only US locations are supported).", | ||
| ), | ||
| ), | ||
| isError = true, |
There was a problem hiding this comment.
The forecast failure path discards the exception and always returns a message implying the location is unsupported. This can mislead users when the real issue is e.g. network failure or an API outage; consider including the underlying error message (or at least differentiating HTTP 404 from other failures).
| alerts.features.map { feature -> | ||
| """ | ||
| ${period.name}: | ||
| Temperature: ${period.temperature} ${period.temperatureUnit} | ||
| Wind: ${period.windSpeed} ${period.windDirection} | ||
| Forecast: ${period.detailedForecast} | ||
| Event: ${feature.properties.event} | ||
| Area: ${feature.properties.areaDesc} | ||
| Severity: ${feature.properties.severity} | ||
| Status: ${feature.properties.status} | ||
| Headline: ${feature.properties.headline} | ||
| """.trimIndent() |
There was a problem hiding this comment.
These properties are nullable in AlertProperties, but the tool output interpolates them directly. If any field is absent, the user will see "null" in the response. Consider making these fields non-null with defaults, or using ?: fallbacks when formatting the output.
| forecast.properties.periods.map { period -> | ||
| """ | ||
| Event: ${feature.properties.event} | ||
| Area: ${feature.properties.areaDesc} | ||
| Severity: ${feature.properties.severity} | ||
| Description: ${feature.properties.description} | ||
| Instruction: ${feature.properties.instruction} | ||
| ${period.name}: | ||
| Temperature: ${period.temperature}°${period.temperatureUnit} | ||
| Wind: ${period.windSpeed} ${period.windDirection} | ||
| ${period.shortForecast} | ||
| """.trimIndent() |
There was a problem hiding this comment.
ForecastPeriod fields are nullable, but the formatted output uses them directly (e.g., temperature/unit/name). This can produce strings like "Temperature: null°null" if the API omits a field. Either make the fields non-null with sensible defaults or format with fallbacks / omit missing fields.
| val process = ProcessBuilder( | ||
| "java", | ||
| "-jar", | ||
| "${System.getProperty("user.dir")}/build/libs/weather-stdio-server-0.1.0-all.jar", | ||
| ).redirectErrorStream(true) | ||
| "${System.getProperty("user.dir")}/samples/weather-stdio-server/build/libs/weather-stdio-server-0.1.0-all.jar", | ||
| ).redirectError(ProcessBuilder.Redirect.INHERIT) |
There was a problem hiding this comment.
The jar path is hard-coded relative to user.dir as if the working directory is the repo root. If this example is run with the working directory set to samples/weather-stdio-server, the resulting path will be incorrect. Consider resolving the jar path relative to the module directory (or accept it as an argument/env var) to make the example robust.
| ) | ||
| val state = request.arguments?.get("state")?.jsonPrimitive?.content | ||
| ?: return@addTool CallToolResult( | ||
| content = listOf(TextContent("The 'state' parameter is required.")), |
There was a problem hiding this comment.
The missing required "state" argument returns a normal CallToolResult without isError=true, while other validation failures in this tool do set isError. Marking this as an error will let clients reliably detect invalid requests.
| content = listOf(TextContent("The 'state' parameter is required.")), | |
| content = listOf(TextContent("The 'state' parameter is required.")), | |
| isError = true, |
|
|
||
| CallToolResult(content = alerts.map { TextContent(it) }) | ||
| val stateCode = state.uppercase() | ||
|
|
There was a problem hiding this comment.
state.uppercase() is locale-sensitive and can produce unexpected results in some locales (e.g., Turkish), which could send an invalid state code to the NWS API. Prefer an explicit locale (e.g., uppercase(Locale.US)) for US state codes.
| if (latitude == null || longitude == null) { | ||
| return@addTool CallToolResult( | ||
| content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")), | ||
| ) | ||
| } |
There was a problem hiding this comment.
The missing required latitude/longitude case returns a success-shaped CallToolResult without isError=true. Consider setting isError=true here so clients can distinguish invalid input from a valid tool response.
| content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")), | ||
| ) | ||
| } | ||
|
|
There was a problem hiding this comment.
Even though the tool schema includes min/max for latitude/longitude, the handler doesn't enforce those bounds (or reject NaN/Infinity). Adding runtime validation (range + finite check) will prevent unnecessary calls to the NWS API with invalid coordinates.
| if (!latitude.isFinite() || !longitude.isFinite() || | |
| latitude !in -90.0..90.0 || longitude !in -180.0..180.0 | |
| ) { | |
| return@addTool CallToolResult( | |
| content = listOf( | |
| TextContent( | |
| "Invalid coordinates. Latitude must be between -90 and 90, and longitude between -180 and 180.", | |
| ), | |
| ), | |
| isError = true, | |
| ) | |
| } |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Updated the client and server examples for further use in the following guides: https://modelcontextprotocol.io/docs/develop/build-server and https://modelcontextprotocol.io/docs/develop/build-client
How Has This Been Tested?
claude desktop, cli
Breaking Changes
NaN
Types of changes
Checklist