This is an automated email from the ASF dual-hosted git repository. robertlazarski pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
commit 83500ec35b4e5b439e15f78f2817c99c299aa24a Author: Robert Lazarski <[email protected]> AuthorDate: Tue Apr 7 07:42:48 2026 -1000 B2/B3 mcpAuthScope + mcpStreaming hint parameters B2 — mcpAuthScope (operation > service level): When set, the tool node gains an "x-authScope" field that MCP clients supporting fine-grained OAuth2 / custom scopes can use to request only the required scope instead of a broad full-access token. Absent param → no x-authScope field (compact catalog). 4 tests: operation-level, absent, service-level fallback, op overrides svc. B3 — mcpStreaming (operation > service level): When true, the tool node gains "x-streaming": true to signal that the operation returns a stream (chunked JSON, SSE, long-poll) rather than a single response body. MCP clients that support progressive rendering can use this to display partial results as they arrive. false / absent → no x-streaming field (absent = non-streaming default). 4 tests: true, absent, explicit-false-suppressed, service-level fallback. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../apache/axis2/openapi/OpenApiSpecGenerator.java | 19 ++++ .../axis2/openapi/McpCatalogGeneratorTest.java | 108 +++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java b/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java index d23e35cb34..2a8cf9e0e3 100644 --- a/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java +++ b/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java @@ -855,6 +855,25 @@ public class OpenApiSpecGenerator { // Whether the caller must supply a Bearer token (from doLogin). toolNode.put("x-requiresAuth", requiresAuth); + // B2 — mcpAuthScope: optional OAuth2 / custom scope string. + // When present, MCP clients that support fine-grained auth can + // request just this scope rather than a full-access token. + // Declared at operation OR service level (operation wins). + // Example services.xml: <parameter name="mcpAuthScope">read:portfolio</parameter> + String authScope = getMcpStringParam(operation, service, "mcpAuthScope", null); + if (authScope != null) { + toolNode.put("x-authScope", authScope); + } + + // B3 — mcpStreaming: signals that this operation returns a stream + // (chunked JSON, SSE, or long-poll) rather than a single response. + // MCP clients that support progressive rendering can use this hint + // to display partial results as they arrive. + // Example services.xml: <parameter name="mcpStreaming">true</parameter> + if (getMcpBoolParam(operation, service, "mcpStreaming", false)) { + toolNode.put("x-streaming", true); + } + // MCP 2025-03-26 tool annotations. // Tunable via services.xml parameters at operation or service level: // mcpReadOnly → readOnlyHint (true for GET-equivalent operations) diff --git a/modules/openapi/src/test/java/org/apache/axis2/openapi/McpCatalogGeneratorTest.java b/modules/openapi/src/test/java/org/apache/axis2/openapi/McpCatalogGeneratorTest.java index 633127e7d3..e8c75e2dff 100644 --- a/modules/openapi/src/test/java/org/apache/axis2/openapi/McpCatalogGeneratorTest.java +++ b/modules/openapi/src/test/java/org/apache/axis2/openapi/McpCatalogGeneratorTest.java @@ -958,6 +958,114 @@ public class McpCatalogGeneratorTest extends TestCase { annotations.path("openWorldHint").asBoolean()); } + // ── B2: mcpAuthScope ───────────────────────────────────────────────────── + + public void testMcpAuthScopeParamAppearsAsXAuthScope() throws Exception { + AxisService svc = new AxisService("PortfolioService"); + AxisOperation op = new InOutAxisOperation(); + op.setName(QName.valueOf("readPositions")); + op.addParameter(new org.apache.axis2.description.Parameter( + "mcpAuthScope", "read:portfolio")); + svc.addOperation(op); + axisConfig.addService(svc); + + JsonNode tool = getCatalogTools().get(0); + assertEquals("x-authScope must reflect mcpAuthScope param", + "read:portfolio", tool.path("x-authScope").asText()); + } + + public void testAbsentMcpAuthScopeProducesNoXAuthScopeField() throws Exception { + addService("PortfolioService", "readPositions"); + + JsonNode tool = getCatalogTools().get(0); + assertTrue("x-authScope must be absent when mcpAuthScope not set", + tool.path("x-authScope").isMissingNode()); + } + + public void testServiceLevelMcpAuthScopeAppliedWhenNoOperationLevel() throws Exception { + AxisService svc = new AxisService("PortfolioService"); + svc.addParameter(new org.apache.axis2.description.Parameter( + "mcpAuthScope", "read:global")); + AxisOperation op = new InOutAxisOperation(); + op.setName(QName.valueOf("readPositions")); + svc.addOperation(op); + axisConfig.addService(svc); + + JsonNode tool = getCatalogTools().get(0); + assertEquals("service-level mcpAuthScope must apply when op level absent", + "read:global", tool.path("x-authScope").asText()); + } + + public void testOperationLevelMcpAuthScopeOverridesServiceLevel() throws Exception { + AxisService svc = new AxisService("PortfolioService"); + svc.addParameter(new org.apache.axis2.description.Parameter( + "mcpAuthScope", "read:global")); + AxisOperation op = new InOutAxisOperation(); + op.setName(QName.valueOf("writePositions")); + op.addParameter(new org.apache.axis2.description.Parameter( + "mcpAuthScope", "write:portfolio")); + svc.addOperation(op); + axisConfig.addService(svc); + + JsonNode tool = getCatalogTools().get(0); + assertEquals("operation-level mcpAuthScope must override service level", + "write:portfolio", tool.path("x-authScope").asText()); + } + + // ── B3: mcpStreaming ────────────────────────────────────────────────────── + + public void testMcpStreamingParamSetsXStreamingTrue() throws Exception { + AxisService svc = new AxisService("FeedService"); + AxisOperation op = new InOutAxisOperation(); + op.setName(QName.valueOf("streamPrices")); + op.addParameter(new org.apache.axis2.description.Parameter( + "mcpStreaming", "true")); + svc.addOperation(op); + axisConfig.addService(svc); + + JsonNode tool = getCatalogTools().get(0); + assertTrue("x-streaming must be true when mcpStreaming=true", + tool.path("x-streaming").asBoolean()); + } + + public void testAbsentMcpStreamingProducesNoXStreamingField() throws Exception { + addService("FeedService", "getSnapshot"); + + JsonNode tool = getCatalogTools().get(0); + assertTrue("x-streaming field must be absent when mcpStreaming not set", + tool.path("x-streaming").isMissingNode()); + } + + public void testMcpStreamingFalseProducesNoXStreamingField() throws Exception { + // mcpStreaming=false is the default; field must be suppressed (not emitted as false) + // to keep the catalog compact — clients treat absence as non-streaming. + AxisService svc = new AxisService("FeedService"); + AxisOperation op = new InOutAxisOperation(); + op.setName(QName.valueOf("getSnapshot")); + op.addParameter(new org.apache.axis2.description.Parameter( + "mcpStreaming", "false")); + svc.addOperation(op); + axisConfig.addService(svc); + + JsonNode tool = getCatalogTools().get(0); + assertTrue("x-streaming must be absent when mcpStreaming=false", + tool.path("x-streaming").isMissingNode()); + } + + public void testServiceLevelMcpStreamingApplied() throws Exception { + AxisService svc = new AxisService("FeedService"); + svc.addParameter(new org.apache.axis2.description.Parameter( + "mcpStreaming", "true")); + AxisOperation op = new InOutAxisOperation(); + op.setName(QName.valueOf("streamPrices")); + svc.addOperation(op); + axisConfig.addService(svc); + + JsonNode tool = getCatalogTools().get(0); + assertTrue("service-level mcpStreaming must apply when op level absent", + tool.path("x-streaming").asBoolean()); + } + // ── helpers ───────────────────────────────────────────────────────────── private void addService(String serviceName, String operationName) throws Exception {
