#302 Update samples build workflow (#330)

# #302 Update samples build workflow

- Introduce workflow to build samples independently from the main
project
- Keep building samples against the current snapshot version of the SDK.
Update composite build configuration.
- Temporarily disable "samples/kotlin-mcp-server": the multiplatform
project is broken. **It doesn't compile for me even on the main
branch**. Not working with [MCP
Instepcor](https://modelcontextprotocol.io/docs/tools/inspector),
because CORS is not configured. Deserves a separate PR to fix.

## Motivation and Context

Samples should be independently buildable

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [x] Build improvement
- [x] Tests

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
This commit is contained in:
Konstantin Pavlov 2025-10-23 13:02:22 +03:00 • committed by GitHub
commit 2da14b0f2e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1271 additions and 73 deletions

View file

@ -1,23 +1,38 @@
package io.modelcontextprotocol.sample.server
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.utils.io.streams.*
import io.modelcontextprotocol.kotlin.sdk.*
import io.ktor.client.HttpClient
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.http.headers
import io.ktor.serialization.kotlinx.json.json
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.streams.asInput
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.TextContent
import io.modelcontextprotocol.kotlin.sdk.Tool
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import kotlinx.io.Sink
import kotlinx.io.asSink
import kotlinx.io.buffered
import kotlinx.serialization.json.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
// Main function to run the MCP server
fun `run mcp server`() {
/**
* Starts an MCP server that provides weather-related tools for fetching active weather alerts by state and weather forecasts by latitude/longitude.
*/
fun runMcpServer() {
// Base URL for the Weather API
val baseUrl = "https://api.weather.gov"
@ -33,10 +48,12 @@ fun `run mcp server`() {
}
// Install content negotiation plugin for JSON serialization/deserialization
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
prettyPrint = true
})
json(
Json {
ignoreUnknownKeys = true
prettyPrint = true
},
)
}
}
@ -44,11 +61,11 @@ fun `run mcp server`() {
val server = Server(
Implementation(
name = "weather", // Tool name is "weather"
version = "1.0.0" // Version of the implementation
version = "1.0.0", // Version of the implementation
),
ServerOptions(
capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true))
)
capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
),
)
// Register a tool to fetch weather alerts by state
@ -64,11 +81,11 @@ fun `run mcp server`() {
put("description", "Two-letter US state code (e.g. CA, NY)")
}
},
required = listOf("state")
)
required = listOf("state"),
),
) { request ->
val state = request.arguments["state"]?.jsonPrimitive?.content ?: return@addTool CallToolResult(
content = listOf(TextContent("The 'state' parameter is required."))
content = listOf(TextContent("The 'state' parameter is required.")),
)
val alerts = httpClient.getAlerts(state)
@ -91,14 +108,14 @@ fun `run mcp server`() {
put("type", "number")
}
},
required = listOf("latitude", "longitude")
)
required = listOf("latitude", "longitude"),
),
) { request ->
val latitude = request.arguments["latitude"]?.jsonPrimitive?.doubleOrNull
val longitude = request.arguments["longitude"]?.jsonPrimitive?.doubleOrNull
if (latitude == null || longitude == null) {
return@addTool CallToolResult(
content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required."))
content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
)
}
@ -110,7 +127,7 @@ fun `run mcp server`() {
// Create a transport using standard IO for server communication
val transport = StdioServerTransport(
System.`in`.asInput(),
System.out.asSink().buffered()
System.out.asSink().buffered(),
)
runBlocking {

View file

@ -1,3 +1,3 @@
package io.modelcontextprotocol.sample.server
fun main() = `run mcp server`()
fun main() = runMcpServer()

View file

@ -11,15 +11,15 @@ import kotlinx.io.asSource
import kotlinx.io.buffered
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
fun main(): Unit = runBlocking {
val process = ProcessBuilder("java", "-jar", "build/libs/weather-stdio-server-0.1.0-all.jar")
val process = ProcessBuilder("java", "-jar", "./build/libs/weather-stdio-server-0.1.0-all.jar")
.start()
val transport = StdioClientTransport(
input = process.inputStream.asSource().buffered(),
output = process.outputStream.asSink().buffered()
output = process.outputStream.asSink().buffered(),
)
// Initialize the MCP client with client information
@ -29,15 +29,17 @@ fun main(): Unit = runBlocking {
client.connect(transport)
val toolsList = client.listTools()?.tools?.map { it.name }
val toolsList = client.listTools().tools.map { it.name }
println("Available Tools = $toolsList")
val weatherForecastResult = client.callTool(
CallToolRequest(
name = "get_forecast",
arguments = JsonObject(mapOf("latitude" to JsonPrimitive(38.5816), "longitude" to JsonPrimitive(-121.4944)))
)
arguments = buildJsonObject {
put("latitude", JsonPrimitive(38.5816))
put("longitude", JsonPrimitive(-121.4944))
},
),
)?.content?.map { if (it is TextContent) it.text else it.toString() }
println("Weather Forcast: ${weatherForecastResult?.joinToString(separator = "\n", prefix = "\n", postfix = "\n")}")
@ -46,11 +48,11 @@ fun main(): Unit = runBlocking {
client.callTool(
CallToolRequest(
name = "get_alerts",
arguments = JsonObject(mapOf("state" to JsonPrimitive("TX")))
)
arguments = JsonObject(mapOf("state" to JsonPrimitive("TX"))),
),
)?.content?.map { if (it is TextContent) it.text else it.toString() }
println("Alert Response = $alertResult")
client.close()
}
}

View file

@ -0,0 +1,11 @@
package io.modelcontextprotocol.sample.client
import kotlin.test.Test
class FakeTest {
@Test
fun testSomething() {
assert(true) { "Bingo!" }
}
}