From 677fafcbcd2378e80a2b27d2bc6a49c0d32445e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 23:06:51 +0000 Subject: [PATCH 1/2] feat(binding-mcp-openapi): pick up OpenAPI operation summary and description for generated tools Parse `summary` and `description` from OpenAPI operations and wire them into the generated mcp_http tool config: `summary` feeds the (currently static) mcp_http tool summary, and tool description now falls back to the operation description before operationId. Also reorders McpHttpToolConfig fields/ctor args (summary before description) for readability. Updates create.pr fixtures in binding-mcp-openapi.spec since the OpenAPI summary field they already embedded is no longer inert. --- .../mcp/http/config/McpHttpToolConfig.java | 6 +-- .../config/McpHttpOptionsConfigAdapter.java | 18 +++---- .../McpOpenapiCompositeGenerator.java | 6 ++- .../McpOpenapiCompositeGeneratorTest.java | 52 +++++++++++++++++++ .../openapi/model/OpenapiOperation.java | 2 + .../openapi/view/OpenapiOperationView.java | 4 ++ .../streams/mcp/create.pr.10k/client.rpt | 4 +- .../streams/mcp/create.pr.10k/server.rpt | 3 +- .../openapi/streams/mcp/create.pr/client.rpt | 5 +- .../openapi/streams/mcp/create.pr/server.rpt | 3 +- 10 files changed, 80 insertions(+), 23 deletions(-) diff --git a/runtime/binding-mcp-http/src/main/java/io/aklivity/zilla/runtime/binding/mcp/http/config/McpHttpToolConfig.java b/runtime/binding-mcp-http/src/main/java/io/aklivity/zilla/runtime/binding/mcp/http/config/McpHttpToolConfig.java index d958350022..8299912b40 100644 --- a/runtime/binding-mcp-http/src/main/java/io/aklivity/zilla/runtime/binding/mcp/http/config/McpHttpToolConfig.java +++ b/runtime/binding-mcp-http/src/main/java/io/aklivity/zilla/runtime/binding/mcp/http/config/McpHttpToolConfig.java @@ -19,21 +19,21 @@ public final class McpHttpToolConfig { public final String name; - public final String description; public final String summary; + public final String description; public final ModelConfig input; public final ModelConfig output; public McpHttpToolConfig( String name, - String description, String summary, + String description, ModelConfig input, ModelConfig output) { this.name = name; - this.description = description; this.summary = summary; + this.description = description; this.input = input; this.output = output; } diff --git a/runtime/binding-mcp-http/src/main/java/io/aklivity/zilla/runtime/binding/mcp/http/internal/config/McpHttpOptionsConfigAdapter.java b/runtime/binding-mcp-http/src/main/java/io/aklivity/zilla/runtime/binding/mcp/http/internal/config/McpHttpOptionsConfigAdapter.java index 579f435154..6a8b7187bf 100644 --- a/runtime/binding-mcp-http/src/main/java/io/aklivity/zilla/runtime/binding/mcp/http/internal/config/McpHttpOptionsConfigAdapter.java +++ b/runtime/binding-mcp-http/src/main/java/io/aklivity/zilla/runtime/binding/mcp/http/internal/config/McpHttpOptionsConfigAdapter.java @@ -107,14 +107,14 @@ public JsonObject adaptToJson( for (McpHttpToolConfig tool : tools) { JsonObjectBuilder toolObject = Json.createObjectBuilder(); - if (tool.description != null) - { - toolObject.add(DESCRIPTION_NAME, tool.description); - } if (tool.summary != null) { toolObject.add(SUMMARY_NAME, tool.summary); } + if (tool.description != null) + { + toolObject.add(DESCRIPTION_NAME, tool.description); + } JsonObjectBuilder schemas = Json.createObjectBuilder(); if (tool.input != null) { @@ -240,14 +240,14 @@ public OptionsConfig adaptFromJson( for (String name : toolsObject.keySet()) { JsonObject toolObject = toolsObject.getJsonObject(name); - String description = toolObject.containsKey(DESCRIPTION_NAME) - ? toolObject.getString(DESCRIPTION_NAME) - : null; - String summary = toolObject.containsKey(SUMMARY_NAME) ? toolObject.getString(SUMMARY_NAME) : null; + String description = toolObject.containsKey(DESCRIPTION_NAME) + ? toolObject.getString(DESCRIPTION_NAME) + : null; + ModelConfig input = null; ModelConfig output = null; if (toolObject.containsKey(SCHEMAS_NAME)) @@ -263,7 +263,7 @@ public OptionsConfig adaptFromJson( } } - tools.add(new McpHttpToolConfig(name, description, summary, input, output)); + tools.add(new McpHttpToolConfig(name, summary, description, input, output)); } } diff --git a/runtime/binding-mcp-openapi/src/main/java/io/aklivity/zilla/runtime/binding/mcp/openapi/internal/config/composite/McpOpenapiCompositeGenerator.java b/runtime/binding-mcp-openapi/src/main/java/io/aklivity/zilla/runtime/binding/mcp/openapi/internal/config/composite/McpOpenapiCompositeGenerator.java index 23b391d32a..3d7dd6a1dc 100644 --- a/runtime/binding-mcp-openapi/src/main/java/io/aklivity/zilla/runtime/binding/mcp/openapi/internal/config/composite/McpOpenapiCompositeGenerator.java +++ b/runtime/binding-mcp-openapi/src/main/java/io/aklivity/zilla/runtime/binding/mcp/openapi/internal/config/composite/McpOpenapiCompositeGenerator.java @@ -298,8 +298,10 @@ private McpHttpOptionsConfig mcpHttpOptions( { final String description = entry.toolConfig != null && entry.toolConfig.description != null ? entry.toolConfig.description - : entry.operation.id; - tools.add(new McpHttpToolConfig(entry.tool, description, null, input, output)); + : entry.operation.description != null + ? entry.operation.description + : entry.operation.id; + tools.add(new McpHttpToolConfig(entry.tool, entry.operation.summary, description, input, output)); } else { diff --git a/runtime/binding-mcp-openapi/src/test/java/io/aklivity/zilla/runtime/binding/mcp/openapi/internal/config/composite/McpOpenapiCompositeGeneratorTest.java b/runtime/binding-mcp-openapi/src/test/java/io/aklivity/zilla/runtime/binding/mcp/openapi/internal/config/composite/McpOpenapiCompositeGeneratorTest.java index 748f6e93fd..66b58218f8 100644 --- a/runtime/binding-mcp-openapi/src/test/java/io/aklivity/zilla/runtime/binding/mcp/openapi/internal/config/composite/McpOpenapiCompositeGeneratorTest.java +++ b/runtime/binding-mcp-openapi/src/test/java/io/aklivity/zilla/runtime/binding/mcp/openapi/internal/config/composite/McpOpenapiCompositeGeneratorTest.java @@ -138,6 +138,8 @@ public class McpOpenapiCompositeGeneratorTest " }," + " \"get\": {" + " \"operationId\": \"issues/list\"," + + " \"summary\": \"List repository issues\"," + + " \"description\": \"Retrieve a paginated list of open and closed issues for the specified repository.\"," + " \"security\": []," + " \"parameters\": [" + " { \"name\": \"owner\", \"in\": \"path\", \"required\": true, \"schema\": { \"type\": \"string\" } }," + @@ -318,6 +320,56 @@ public void shouldOverrideOutputSchema() assertThat(tool.output, sameInstance(override)); } + @Test + public void shouldUseOpenapiSummaryAsToolSummary() + { + BindingConfig binding = bindingWithRoutes(route(0, "list_issues", null, "issues/list")); + + McpOpenapiCompositeConfig composite = generator.generate(new McpOpenapiBindingConfig(context, binding)); + + McpHttpOptionsConfig mcpHttpOptions = (McpHttpOptionsConfig) mcpHttp(composite).options; + McpHttpToolConfig tool = mcpHttpOptions.tools.stream() + .filter(t -> "list_issues".equals(t.name)) + .findFirst() + .orElse(null); + assertThat(tool, notNullValue()); + assertThat(tool.summary, equalTo("List repository issues")); + } + + @Test + public void shouldDefaultToolDescriptionToOperationDescriptionWhenPresent() + { + BindingConfig binding = bindingWithRoutes(route(0, "list_issues", null, "issues/list")); + + McpOpenapiCompositeConfig composite = generator.generate(new McpOpenapiBindingConfig(context, binding)); + + McpHttpOptionsConfig mcpHttpOptions = (McpHttpOptionsConfig) mcpHttp(composite).options; + McpHttpToolConfig tool = mcpHttpOptions.tools.stream() + .filter(t -> "list_issues".equals(t.name)) + .findFirst() + .orElse(null); + assertThat(tool, notNullValue()); + assertThat(tool.description, + equalTo("Retrieve a paginated list of open and closed issues for the specified repository.")); + } + + @Test + public void shouldDefaultToolDescriptionToOperationIdWhenDescriptionAbsent() + { + BindingConfig binding = bindingWithRoutes(route(0, "get_repo", null, "repos/get")); + + McpOpenapiCompositeConfig composite = generator.generate(new McpOpenapiBindingConfig(context, binding)); + + McpHttpOptionsConfig mcpHttpOptions = (McpHttpOptionsConfig) mcpHttp(composite).options; + McpHttpToolConfig tool = mcpHttpOptions.tools.stream() + .filter(t -> "get_repo".equals(t.name)) + .findFirst() + .orElse(null); + assertThat(tool, notNullValue()); + assertThat(tool.description, equalTo("repos/get")); + assertThat(tool.summary, nullValue()); + } + @Test public void shouldInterpolateQueryParameters() { diff --git a/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/model/OpenapiOperation.java b/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/model/OpenapiOperation.java index a6c5d851bf..41924cfc5a 100644 --- a/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/model/OpenapiOperation.java +++ b/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/model/OpenapiOperation.java @@ -20,6 +20,8 @@ public class OpenapiOperation { public String operationId; + public String summary; + public String description; public List parameters; public OpenapiRequestBody requestBody; public Map responses; diff --git a/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiOperationView.java b/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiOperationView.java index dfda07ced8..35815f5bcb 100644 --- a/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiOperationView.java +++ b/runtime/common-openapi/src/main/java/io/aklivity/zilla/runtime/common/openapi/view/OpenapiOperationView.java @@ -36,6 +36,8 @@ public final class OpenapiOperationView public final String method; public final String path; public final String id; + public final String summary; + public final String description; public final List parameters; public final OpenapiRequestBodyView requestBody; @@ -61,6 +63,8 @@ public final class OpenapiOperationView this.path = path; this.id = model.operationId; + this.summary = model.summary; + this.description = model.description; this.parameters = model.parameters != null ? model.parameters.stream() diff --git a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt index 345b12f807..2a464c3714 100644 --- a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt +++ b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt @@ -54,8 +54,8 @@ write '{"name":"create_pr","arguments":{"owner":"acme","repo":"widget","title":" write ${core:randomBase64(10000)} write '"}}' -read '{"content":[{"type":"text","text":' - '"{\\"number\\":42,\\"html_url\\":\\"https://github.com/acme/widget/pull/42\\",\\"state\\":\\"open\\",\\"title\\":\\"Add feature\\"}"}],' +# openapi operation summary "Create a pull request" is used as the content text +read '{"content":[{"type":"text","text":"Create a pull request"}],' '"structuredContent":{"number":42,"html_url":"https://github.com/acme/widget/pull/42","state":"open","title":"Add feature"},' '"isError":false' '}' diff --git a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/server.rpt b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/server.rpt index cc61d45c5e..ad5f9fe394 100644 --- a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/server.rpt +++ b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/server.rpt @@ -56,8 +56,7 @@ read '"}}' write flush -write '{"content":[{"type":"text","text":' - '"{\\"number\\":42,\\"html_url\\":\\"https://github.com/acme/widget/pull/42\\",\\"state\\":\\"open\\",\\"title\\":\\"Add feature\\"}"}],' +write '{"content":[{"type":"text","text":"Create a pull request"}],' '"structuredContent":{"number":42,"html_url":"https://github.com/acme/widget/pull/42","state":"open","title":"Add feature"},' '"isError":false' '}' diff --git a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt index e0f34731d6..a048cfd993 100644 --- a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt +++ b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt @@ -52,10 +52,9 @@ connected write '{"name":"create_pr","arguments":{"owner":"acme","repo":"widget","title":"Add feature","head":"feature","base":"main"}}' -# no summary configured -> mcp_http sets content text to the compact projected JSON +# openapi operation summary "Create a pull request" is used as the content text read '{' - '"content":[{"type":"text","text":' - '"{\\"number\\":42,\\"html_url\\":\\"https://github.com/acme/widget/pull/42\\",\\"state\\":\\"open\\",\\"title\\":\\"Add feature\\"}"}],' + '"content":[{"type":"text","text":"Create a pull request"}],' '"structuredContent":{"number":42,"html_url":"https://github.com/acme/widget/pull/42","state":"open","title":"Add feature"},' '"isError":false' '}' diff --git a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/server.rpt b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/server.rpt index e4645aa95d..e075cc117a 100644 --- a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/server.rpt +++ b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/server.rpt @@ -55,8 +55,7 @@ read '{"name":"create_pr","arguments":{"owner":"acme","repo":"widget","title":" write flush write '{' - '"content":[{"type":"text","text":' - '"{\\"number\\":42,\\"html_url\\":\\"https://github.com/acme/widget/pull/42\\",\\"state\\":\\"open\\",\\"title\\":\\"Add feature\\"}"}],' + '"content":[{"type":"text","text":"Create a pull request"}],' '"structuredContent":{"number":42,"html_url":"https://github.com/acme/widget/pull/42","state":"open","title":"Add feature"},' '"isError":false' '}' From a9e6429134224c11dcbe94e9afa13a0688e195e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 23:12:27 +0000 Subject: [PATCH 2/2] fix(binding-mcp-openapi.spec): remove redundant comments from create.pr scripts The scripts are self-documenting; the summary text is inline in the read. --- .../binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt | 1 - .../specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt | 1 - 2 files changed, 2 deletions(-) diff --git a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt index 2a464c3714..dd413fe107 100644 --- a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt +++ b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr.10k/client.rpt @@ -54,7 +54,6 @@ write '{"name":"create_pr","arguments":{"owner":"acme","repo":"widget","title":" write ${core:randomBase64(10000)} write '"}}' -# openapi operation summary "Create a pull request" is used as the content text read '{"content":[{"type":"text","text":"Create a pull request"}],' '"structuredContent":{"number":42,"html_url":"https://github.com/acme/widget/pull/42","state":"open","title":"Add feature"},' '"isError":false' diff --git a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt index a048cfd993..91ebc92c96 100644 --- a/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt +++ b/specs/binding-mcp-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/mcp/openapi/streams/mcp/create.pr/client.rpt @@ -52,7 +52,6 @@ connected write '{"name":"create_pr","arguments":{"owner":"acme","repo":"widget","title":"Add feature","head":"feature","base":"main"}}' -# openapi operation summary "Create a pull request" is used as the content text read '{' '"content":[{"type":"text","text":"Create a pull request"}],' '"structuredContent":{"number":42,"html_url":"https://github.com/acme/widget/pull/42","state":"open","title":"Add feature"},'