Skip to content

Commit af55286

Browse files
openapi-mcp: add Axis2 JSON-RPC format docs, auth annotations, MCP 2025 hints
Driven by gaps identified against Python rapi-mcp, pyRapi, and internal-alpha-theory-mcp. OpenApiSpecGenerator.generateMcpCatalogJson(): - Add catalog-level _meta object documenting the Axis2 JSON-RPC transport contract (axis2JsonRpcFormat, contentType, authHeader, tokenEndpoint) — MCP clients need this to call services without an intermediary proxy - Add x-axis2-payloadTemplate per tool: {"opName":[{"arg0":{}}]} (the wrapping format mandated by JsonUtils.invokeServiceClass()) - Add x-requiresAuth per tool: false for loginService (token endpoint), true for all other services — mirrors the two-phase auth flow in pyRapi/auth.py and rapi-mcp/server/rapi/api.py - Add MCP 2025-03-26 annotations per tool (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) — conservative defaults, overridable via @mcptool annotations (future work) SwaggerUIHandler.handleMcpCatalogRequest(): - Add Cache-Control: no-cache, no-store — catalog must not be cached as service list changes on deployment Tests (McpCatalogGeneratorTest, McpCatalogHandlerTest): - 30+ new test methods covering _meta, payload template structure, auth annotations, MCP 2025 annotations, Cache-Control, security headers New test file McpAxis2PayloadTest.java (30 tests): - Structural validation of the Axis2 JSON-RPC payload template format - Two-phase auth flow (loginService → Bearer → protected service) - pyRapi format compatibility ({"doLogin":[{"arg0":{}}]}) - All-tools consistency checks (template key matches tool name, etc.) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 93884cf commit af55286

5 files changed

Lines changed: 822 additions & 1 deletion

File tree

modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,19 @@ public String generateMcpCatalogJson(HttpServletRequest request) {
649649
com.fasterxml.jackson.databind.ObjectMapper jackson =
650650
new com.fasterxml.jackson.databind.ObjectMapper();
651651
com.fasterxml.jackson.databind.node.ObjectNode root = jackson.createObjectNode();
652+
653+
// Catalog-level metadata so MCP clients understand the transport layer.
654+
// Axis2 JSON-RPC requires every call to be wrapped as:
655+
// {"<operationName>":[{"arg0":{<params>}}]}
656+
// This is mandated by JsonUtils.invokeServiceClass() in axis2-json.
657+
// The loginService/doLogin operation is the token endpoint; all other
658+
// operations require "Authorization: Bearer <token>" in the request header.
659+
com.fasterxml.jackson.databind.node.ObjectNode meta = root.putObject("_meta");
660+
meta.put("axis2JsonRpcFormat", "{\"<operationName>\":[{\"arg0\":{<params>}}]}");
661+
meta.put("contentType", "application/json");
662+
meta.put("authHeader", "Authorization: Bearer <token>");
663+
meta.put("tokenEndpoint", "POST /services/loginService/doLogin");
664+
652665
com.fasterxml.jackson.databind.node.ArrayNode toolsArray = root.putArray("tools");
653666

654667
Iterator<AxisService> services = axisConfig.getServices().values().iterator();
@@ -657,6 +670,10 @@ public String generateMcpCatalogJson(HttpServletRequest request) {
657670
if (isSystemService(service)) continue;
658671
if (!shouldIncludeService(service)) continue;
659672

673+
// loginService is the unauthenticated token endpoint; all others require auth.
674+
String svcLower = service.getName().toLowerCase(java.util.Locale.ROOT);
675+
boolean requiresAuth = !svcLower.contains("login") && !svcLower.equals("adminconsole");
676+
660677
Iterator<AxisOperation> operations = service.getOperations();
661678
while (operations.hasNext()) {
662679
AxisOperation operation = operations.next();
@@ -678,6 +695,25 @@ public String generateMcpCatalogJson(HttpServletRequest request) {
678695
schema.putArray("required");
679696

680697
toolNode.put("endpoint", "POST " + path);
698+
699+
// Axis2 JSON-RPC payload template. MCP clients must wrap the call
700+
// body in this envelope — the bare {"field":value} object goes inside
701+
// "arg0". Example for portfolioVariance:
702+
// {"portfolioVariance":[{"arg0":{"nAssets":2,"weights":[0.6,0.4],...}}]}
703+
toolNode.put("x-axis2-payloadTemplate",
704+
"{\"" + opName + "\":[{\"arg0\":{}}]}");
705+
706+
// Whether the caller must supply a Bearer token (from doLogin).
707+
toolNode.put("x-requiresAuth", requiresAuth);
708+
709+
// MCP 2025-03-26 tool annotations — conservative defaults.
710+
// Override via @McpTool when richer metadata is available.
711+
com.fasterxml.jackson.databind.node.ObjectNode annotations =
712+
toolNode.putObject("annotations");
713+
annotations.put("readOnlyHint", false);
714+
annotations.put("destructiveHint", false);
715+
annotations.put("idempotentHint", false);
716+
annotations.put("openWorldHint", false);
681717
}
682718
}
683719

modules/openapi/src/main/java/org/apache/axis2/openapi/SwaggerUIHandler.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ public void handleMcpCatalogRequest(HttpServletRequest request, HttpServletRespo
206206

207207
addSecurityHeaders(response);
208208
addCorsHeaders(response);
209+
// MCP catalog must not be cached: service list changes on deployment and
210+
// a stale catalog causes MCP clients to attempt calls to unknown tools.
211+
response.setHeader("Cache-Control", "no-cache, no-store");
209212

210213
try {
211214
String mcpJson = specGenerator.generateMcpCatalogJson(request);

0 commit comments

Comments
 (0)