Skip to content

Commit 94a44db

Browse files
committed
add operation filters
1 parent bd30ae1 commit 94a44db

File tree

4 files changed

+121
-20
lines changed

4 files changed

+121
-20
lines changed

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ import picocli.CommandLine
44
import java.util.concurrent.Callable
55
import kotlin.system.exitProcess
66

7-
enum class TransportType {
8-
stdio, httpSse, streamableHttp
9-
}
10-
117
@CommandLine.Command(
128
name = "openapi-mcp",
139
mixinStandardHelpOptions = true,
@@ -19,14 +15,31 @@ class Cli : Callable<Int> {
1915
lateinit var openapiSpec: String
2016

2117
@CommandLine.Option(
22-
names = ["--transport"],
23-
defaultValue = "stdio",
24-
description = [$$"MCP transport type. Valid values: ${COMPLETION-CANDIDATES}"],
18+
names = ["--include-operation-id"],
19+
split = ",",
20+
description = ["Include operations with id (comma separated)"]
21+
)
22+
private val includeOperationIds: List<String>? = null
23+
24+
@CommandLine.Option(
25+
names = ["--include-http-method"],
26+
split = ",",
27+
description = ["Include operations with HTTP methods (comma separated)"]
28+
)
29+
private val includeHttpMethods: List<String>? = null
30+
31+
@CommandLine.Option(
32+
names = ["--include-path"],
33+
split = ",",
34+
description = ["Include operations with paths (comma separated)"]
2535
)
26-
var transportType: TransportType = TransportType.stdio
36+
private val includePaths: List<String>? = null
2737

2838
override fun call(): Int {
29-
McpServer.start(openapiSpec)
39+
McpServer.start(
40+
openapiSpec,
41+
OpenAPIOperationFilter(includeOperationIds, includeHttpMethods, includePaths)
42+
)
3043
return 0
3144
}
3245
}

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

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,45 @@ import io.swagger.v3.oas.models.media.Schema
1616
import io.swagger.v3.oas.models.media.StringSchema
1717
import io.swagger.v3.oas.models.parameters.Parameter
1818
import kotlinx.serialization.json.*
19+
import org.apache.commons.lang3.StringUtils
20+
import java.util.function.Supplier
21+
22+
data class OpenAPIOperation(
23+
val operation: Operation,
24+
val httpMethod: String,
25+
val path: String,
26+
)
27+
28+
data class OpenAPIOperationFilter(
29+
val operationIds: List<String>? = null,
30+
val httpMethods: List<String>? = null,
31+
val paths: List<String>? = null,
32+
) {
33+
fun match(operation: OpenAPIOperation): Boolean {
34+
val checkers = mutableListOf<Supplier<Boolean>>()
35+
operationIds?.let {
36+
checkers.add {
37+
StringUtils.isNotBlank(operation.operation.operationId)
38+
&& it.contains(operation.operation.operationId)
39+
}
40+
}
41+
httpMethods?.let {
42+
checkers.add {
43+
it.any { method ->
44+
method.equals(operation.httpMethod, true)
45+
}
46+
}
47+
}
48+
paths?.let {
49+
checkers.add {
50+
it.any { path ->
51+
path.equals(operation.path, true)
52+
}
53+
}
54+
}
55+
return checkers.all { it.get() }
56+
}
57+
}
1958

2059
data class McpTool(
2160
val tool: Tool,
@@ -46,18 +85,29 @@ object McpToolHelper {
4685
}
4786
}
4887

49-
fun toTools(openAPI: OpenAPI): List<McpTool> {
88+
fun toTools(
89+
openAPI: OpenAPI,
90+
openAPIOperationFilter: OpenAPIOperationFilter? = null
91+
): List<McpTool> {
92+
logger.info("Create tools with filter {}", openAPIOperationFilter)
5093
val serverUrl = openAPI.servers.first().url
5194
val components = openAPI.components?.schemas
52-
return openAPI.paths?.entries?.flatMap { entry ->
95+
val operations = openAPI.paths?.entries?.flatMap { entry ->
5396
val path = entry.key
5497
listOfNotNull(
55-
entry.value.get?.let { toTool(serverUrl, it, "GET", path, components) },
56-
entry.value.post?.let { toTool(serverUrl, it, "POST", path, components) },
57-
entry.value.put?.let { toTool(serverUrl, it, "PUT", path, components) },
58-
entry.value.delete?.let { toTool(serverUrl, it, "DELETE", path, components) },
59-
entry.value.patch?.let { toTool(serverUrl, it, "PATCH", path, components) },
98+
entry.value.get?.let { OpenAPIOperation(it, "GET", path) },
99+
entry.value.post?.let { OpenAPIOperation(it, "POST", path) },
100+
entry.value.put?.let { OpenAPIOperation(it, "PUT", path) },
101+
entry.value.delete?.let { OpenAPIOperation(it, "DELETE", path) },
102+
entry.value.patch?.let { OpenAPIOperation(it, "PATCH", path) },
60103
)
104+
}?.also {
105+
logger.info("Found {} operations", it.size)
106+
}?.filter { operation -> openAPIOperationFilter?.match(operation) ?: true }?.also {
107+
logger.info("Use {} operations after filtering", it.size)
108+
}
109+
return operations?.map {
110+
toTool(serverUrl, it.operation, it.httpMethod, it.path, components)
61111
} ?: listOf()
62112
}
63113

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@ import kotlinx.coroutines.Job
1010
import kotlinx.coroutines.runBlocking
1111
import kotlinx.io.asSink
1212
import kotlinx.io.buffered
13+
import org.slf4j.Logger
14+
import org.slf4j.LoggerFactory
15+
16+
val logger: Logger = LoggerFactory.getLogger("McpServer")
1317

1418
object McpServer {
15-
fun start(openapiSpec: String) {
19+
20+
fun start(openapiSpec: String, openAPIOperationFilter: OpenAPIOperationFilter? = null) {
21+
logger.info("Parse OpenAPI spec {} ", openapiSpec)
1622
val openAPI = OpenAPIParser.parse(openapiSpec)
1723

1824
val server = Server(
@@ -28,7 +34,7 @@ object McpServer {
2834
)
2935
)
3036

31-
McpToolHelper.toTools(openAPI).forEach { tool ->
37+
McpToolHelper.toTools(openAPI, openAPIOperationFilter).forEach { tool ->
3238
server.addTool(tool.tool, tool.handler)
3339
}
3440

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,53 @@
11
package com.javaaidev.mcp.openapi
22

33
import kotlin.test.Test
4+
import kotlin.test.assertEquals
45
import kotlin.test.assertTrue
56

67
class McpToolHelperTest {
78
@Test
89
fun testParsePetStoreAPI() {
910
val openAPI = OpenAPIParser.parse("https://petstore.swagger.io/v2/swagger.json")
10-
val tools = McpToolHelper.toTools(openAPI)
11+
val tools = McpToolHelper.toTools(openAPI, null)
1112
assertTrue(tools.isNotEmpty())
1213
}
1314

1415
@Test
1516
fun testParseCanadaHolidaysAPI() {
1617
val openAPI =
1718
OpenAPIParser.parse("https://api.apis.guru/v2/specs/canada-holidays.ca/1.8.0/openapi.json")
18-
val tools = McpToolHelper.toTools(openAPI)
19+
val tools = McpToolHelper.toTools(openAPI, null)
1920
assertTrue(tools.isNotEmpty())
2021
}
22+
23+
@Test
24+
fun testOperationFilter() {
25+
val openAPI =
26+
OpenAPIParser.parse("https://api.apis.guru/v2/specs/canada-holidays.ca/1.8.0/openapi.json")
27+
var tools = McpToolHelper.toTools(
28+
openAPI, OpenAPIOperationFilter(
29+
listOf("Root")
30+
)
31+
)
32+
assertEquals(1, tools.size)
33+
tools = McpToolHelper.toTools(
34+
openAPI, OpenAPIOperationFilter(
35+
httpMethods = listOf("POST")
36+
)
37+
)
38+
assertEquals(0, tools.size)
39+
tools = McpToolHelper.toTools(
40+
openAPI, OpenAPIOperationFilter(
41+
paths = listOf("/api/v1/holidays")
42+
)
43+
)
44+
assertEquals(1, tools.size)
45+
tools = McpToolHelper.toTools(
46+
openAPI, OpenAPIOperationFilter(
47+
listOf("Root"),
48+
httpMethods = listOf("GET")
49+
)
50+
)
51+
assertEquals(1, tools.size)
52+
}
2153
}

0 commit comments

Comments
 (0)