Skip to content

Commit db07647

Browse files
committed
add required parameters
1 parent 015346b commit db07647

File tree

5 files changed

+31
-14
lines changed

5 files changed

+31
-14
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# OpenAPI MCP Server
2+

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies {
2828
implementation("ch.qos.logback:logback-classic:${logbackVersion}")
2929
implementation("io.ktor:ktor-client-content-negotiation:${ktorVersion}")
3030
implementation("io.ktor:ktor-serialization-kotlinx-json:${ktorVersion}")
31+
implementation("io.ktor:ktor-client-logging:${ktorVersion}")
3132
implementation("info.picocli:picocli:${picocliVersion}")
3233
implementation("io.swagger.parser.v3:swagger-parser:${swaggerParserVersion}")
3334
testImplementation(kotlin("test"))

src/main/kotlin/com/javaaidev/mcp/openapi/McpTool.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.javaaidev.mcp.openapi
33
import io.ktor.client.*
44
import io.ktor.client.plugins.*
55
import io.ktor.client.plugins.contentnegotiation.*
6+
import io.ktor.client.plugins.logging.*
67
import io.ktor.client.request.*
78
import io.ktor.client.statement.*
89
import io.ktor.http.*
@@ -32,6 +33,11 @@ object McpToolHelper {
3233
prettyPrint = true
3334
})
3435
}
36+
install(Logging) {
37+
logger = Logger.DEFAULT
38+
level = LogLevel.INFO
39+
sanitizeHeader { header -> header == HttpHeaders.Authorization }
40+
}
3541
}
3642

3743
fun toTools(openAPI: OpenAPI): List<McpTool> {
@@ -58,7 +64,7 @@ object McpToolHelper {
5864
): McpTool {
5965
val name = operation.operationId ?: "${httpMethod}_$path"
6066
val description = operation.description ?: (operation.summary ?: "")
61-
val parameters = operationParameters(operation, components)
67+
val (parameters, requiredParams) = operationParameters(operation, components)
6268
val requestBody = operationRequestBody(operation, components)
6369
val responseBody = operationResponseBody(operation, components)
6470
val toolAnnotations = if (httpMethod == "GET")
@@ -76,15 +82,18 @@ object McpToolHelper {
7682
"parameters" to JsonObject(
7783
mapOf(
7884
"type" to JsonPrimitive("object"),
79-
"properties" to parameters
85+
"properties" to parameters,
86+
"required" to JsonArray((requiredParams ?: listOf()).map {
87+
JsonPrimitive(it)
88+
})
8089
)
8190
),
8291
"requestBody" to requestBody,
8392
)
8493
)
8594
)
8695
} else if (parameters?.isNotEmpty() == true) {
87-
Tool.Input(parameters, parameters.keys.toList())
96+
Tool.Input(parameters, requiredParams)
8897
} else if (requestBody?.isNotEmpty() == true) {
8998
Tool.Input(
9099
requestBody["properties"] as? JsonObject ?: JsonObject(mapOf()),
@@ -127,14 +136,16 @@ object McpToolHelper {
127136
private fun operationParameters(
128137
operation: Operation,
129138
components: Map<String, Schema<*>>?
130-
): JsonObject? {
131-
return operation.parameters?.filter { parameter ->
139+
): Pair<JsonObject?, List<String>?> {
140+
val parameters = operation.parameters?.filter { parameter ->
132141
setOf("query", "path").contains(parameter.`in`)
133-
}?.associate { parameter ->
142+
}
143+
val required = parameters?.filter { it.required }?.map { it.name }
144+
return (parameters?.associate { parameter ->
134145
parameter.name to schemaToJsonObject(parameter.schema, components, parameter)
135146
}?.let {
136147
JsonObject(it)
137-
}
148+
} to required)
138149
}
139150

140151
private fun operationRequestBody(operation: Operation, components: Map<String, Schema<*>>?) =
@@ -232,7 +243,9 @@ object McpToolHelper {
232243
private fun expandUriTemplate(template: String, values: JsonObject): String {
233244
var result = template
234245
values.forEach { (key, value) ->
235-
result = result.replace("{$key}", value.toString())
246+
if (value is JsonPrimitive) {
247+
result = result.replace("{$key}", value.contentOrNull ?: "")
248+
}
236249
}
237250
return result
238251
}

src/main/kotlin/com/javaaidev/mcp/openapi/OpenAPI.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import io.swagger.v3.parser.core.models.ParseOptions
77
object OpenAPIParser {
88
fun parse(spec: String): OpenAPI {
99
val parseOption = ParseOptions()
10-
// parseOption.isFlatten = true
10+
parseOption.isFlatten = false
1111
parseOption.isResolveFully = true
1212
parseOption.isResolveRequestBody = true
1313
parseOption.isResolveResponses = true
1414
parseOption.isResolve = true
15-
// parseOption.isFlattenComposedSchemas = true
15+
parseOption.isFlattenComposedSchemas = false
1616
val result = OpenAPIParser().readLocation(spec, null, parseOption)
1717
return result?.openAPI
1818
?: throw RuntimeException("OpenAPI spec parse error: ${result.messages}")

src/main/kotlin/com/javaaidev/mcp/openapi/Server.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import kotlinx.io.buffered
1313

1414
object McpServer {
1515
fun start(openapiSpec: String) {
16+
val openAPI = OpenAPIParser.parse(openapiSpec)
17+
1618
val server = Server(
1719
Implementation(
18-
name = "openapi",
19-
version = "1.0.0"
20+
name = openAPI.info?.title ?: "openapi-mcp-server",
21+
version = openAPI.info?.version ?: "1.0.0"
2022
),
2123
ServerOptions(
2224
capabilities = ServerCapabilities(
@@ -26,13 +28,12 @@ object McpServer {
2628
)
2729
)
2830

29-
val openAPI = OpenAPIParser.parse(openapiSpec)
3031
McpToolHelper.toTools(openAPI).forEach { tool ->
3132
server.addTool(tool.tool, tool.handler)
3233
}
3334

3435
val transport = StdioServerTransport(
35-
System.`in`.asInput(),
36+
System.`in`.asInput().buffered(),
3637
System.out.asSink().buffered()
3738
)
3839

0 commit comments

Comments
 (0)