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 {

Reply via email to