| <!DOCTYPE html> |
| |
| |
| <!-- |
| | Generated by Apache Maven Doxia Site Renderer 2.0.0 from src/site/xdoc/docs/json-rpc-mcp-guide.xml at 2026-05-18 |
| | Rendered using Apache Maven Fluido Skin 2.0.0-M11 |
| --> |
| <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| <meta name="generator" content="Apache Maven Doxia Site Renderer 2.0.0" /> |
| <title>Apache Axis2 MCP Integration Guide – Apache Axis2</title> |
| <link rel="stylesheet" href="../css/apache-maven-fluido-2.0.0-M11.min.css" /> |
| <link rel="stylesheet" href="../css/site.css" /> |
| <link rel="stylesheet" href="../css/print.css" media="print" /> |
| <script src="../js/apache-maven-fluido-2.0.0-M11.min.js"></script> |
| <meta http-equiv="content-type" content="" /> </head> |
| <body> |
| <div class="container-fluid container-fluid-top"> |
| <header> |
| <div id="banner"> |
| <div class="pull-left"><div id="bannerLeft"><h1><a href="https://www.apache.org/"><img class="class java.lang.Object" src="https://www.apache.org/images/asf_logo_wide.png" /> Apache Axis2</a></h1></div></div> |
| <div class="pull-right"><div id="bannerRight"><h1><a href="https://axis.apache.org/axis2/java/core/"><img class="class java.lang.Object" src="https://axis.apache.org/axis2/java/core/images/axis.jpg" /></a></h1></div></div> |
| <div class="clear"><hr/></div> |
| </div> |
| |
| <div id="breadcrumbs"> |
| <ul class="breadcrumb"> |
| <li id="publishDate">Last Published: 2026-05-17<span class="divider">|</span> |
| </li> |
| <li id="projectVersion">Version: 2.0.1<span class="divider">|</span></li> |
| <li><a href="https://www.apache.org" class="externalLink">Apache</a><span class="divider">/</span></li> |
| <li><a href="../index.html">Axis2/Java</a><span class="divider">/</span></li> |
| <li class="active">Apache Axis2 MCP Integration Guide</li> |
| </ul> |
| </div> |
| </header> |
| <div class="row-fluid"> |
| <header id="leftColumn" class="span2"> |
| <nav class="well sidebar-nav"> |
| <ul class="nav nav-list"> |
| <li class="nav-header">Axis2/Java</li> |
| <li><a href="../index.html">Home</a></li> |
| <li><a href="../download.html">Downloads</a></li> |
| <li><a href="javascript:void(0)"><span class="icon-chevron-down"></span>Release Notes</a> |
| <ul class="nav nav-list"> |
| <li><a href="../release-notes/1.6.1.html">1.6.1</a></li> |
| <li><a href="../release-notes/1.6.2.html">1.6.2</a></li> |
| <li><a href="../release-notes/1.6.3.html">1.6.3</a></li> |
| <li><a href="../release-notes/1.6.4.html">1.6.4</a></li> |
| <li><a href="../release-notes/1.7.0.html">1.7.0</a></li> |
| <li><a href="../release-notes/1.7.1.html">1.7.1</a></li> |
| <li><a href="../release-notes/1.7.2.html">1.7.2</a></li> |
| <li><a href="../release-notes/1.7.3.html">1.7.3</a></li> |
| <li><a href="../release-notes/1.7.4.html">1.7.4</a></li> |
| <li><a href="../release-notes/1.7.5.html">1.7.5</a></li> |
| <li><a href="../release-notes/1.7.6.html">1.7.6</a></li> |
| <li><a href="../release-notes/1.7.7.html">1.7.7</a></li> |
| <li><a href="../release-notes/1.7.8.html">1.7.8</a></li> |
| <li><a href="../release-notes/1.7.9.html">1.7.9</a></li> |
| <li><a href="../release-notes/1.8.0.html">1.8.0</a></li> |
| <li><a href="../release-notes/1.8.1.html">1.8.1</a></li> |
| <li><a href="../release-notes/1.8.2.html">1.8.2</a></li> |
| <li><a href="../release-notes/2.0.0.html">2.0.0</a></li> |
| <li><a href="../release-notes/2.0.1.html">2.0.1</a></li> |
| </ul></li> |
| <li><a href="../modules/index.html">Modules</a></li> |
| <li><a href="../tools/index.html">Tools</a></li> |
| <li class="nav-header">Documentation</li> |
| <li><a href="../docs/toc.html">Table of Contents</a></li> |
| <li><a href="../docs/installationguide.html">Installation Guide</a></li> |
| <li><a href="../docs/quickstartguide.html">QuickStart Guide</a></li> |
| <li><a href="../docs/userguide.html">User Guide</a></li> |
| <li><a href="../docs/jaxws-guide.html">JAXWS Guide</a></li> |
| <li><a href="../docs/pojoguide.html">POJO Guide</a></li> |
| <li><a href="../docs/spring.html">Spring Guide</a></li> |
| <li><a href="../docs/webadminguide.html">Web Administrator's Guide</a></li> |
| <li><a href="../docs/migration.html">Migration Guide (from Axis1)</a></li> |
| <li class="nav-header">Resources</li> |
| <li><a href="../faq.html">FAQ</a></li> |
| <li><a href="https://github.com/apache/axis-axis2-java-core" class="externalLink">Source Code</a></li> |
| <li class="nav-header">Get Involved</li> |
| <li><a href="../overview.html">Overview</a></li> |
| <li><a href="../mail-lists.html">Mailing Lists</a></li> |
| <li><a href="../release-process.html">Release Process</a></li> |
| <li><a href="../guidelines.html">Developer Guidelines</a></li> |
| <li><a href="../siteHowTo.html">Build the Site</a></li> |
| <li class="nav-header">Project Information</li> |
| <li><a href="https://github.com/apache/axis-axis2-java-core/graphs/contributors" class="externalLink">Contributors</a></li> |
| <li><a href="https://issues.apache.org/jira/projects/AXIS2/issues" class="externalLink">Issues</a></li> |
| <li class="nav-header">Apache</li> |
| <li><a href="https://www.apache.org/licenses/LICENSE-2.0.html" class="externalLink">License</a></li> |
| <li><a href="https://www.apache.org/foundation/sponsorship.html" class="externalLink">Sponsorship</a></li> |
| <li><a href="https://www.apache.org/foundation/thanks.html" class="externalLink">Thanks</a></li> |
| <li><a href="https://www.apache.org/security/" class="externalLink">Security</a></li> |
| </ul> |
| </nav> |
| <div class="well sidebar-nav"> |
| <div id="poweredBy"> |
| <div class="clear"></div> |
| <div class="clear"></div> |
| <a href="https://maven.apache.org/" class="builtBy" target="_blank"><img class="builtBy" alt="Built by Maven" src="../images/logos/maven-feather.png" /></a> |
| </div> |
| </div> |
| </header> |
| <main id="bodyColumn" class="span10"> |
| <html> |
| |
| |
| <section><a id="Apache_Axis2_MCP_Integration_Guide"></a> |
| <h1>Apache Axis2 MCP Integration Guide</h1> |
| |
| |
| <p><strong>Who should read this:</strong> Developers building MCP servers or clients |
| that target Axis2 JSON-RPC services and need to understand the auto-generated MCP tool |
| catalog, the required envelope format, authentication flow, and current limitations.</p> |
| |
| |
| <p><strong>Quick start:</strong> For step-by-step build, deploy, and test instructions |
| (including curl commands for every endpoint), see the sample READMEs: |
| <a href="https://github.com/apache/axis-axis2-java-core/blob/master/modules/samples/userguide/src/userguide/springbootdemo-tomcat11/README.md" class="externalLink">springbootdemo-tomcat11 README</a> (Tomcat 11) |
| and |
| <a href="https://github.com/apache/axis-axis2-java-core/blob/master/modules/samples/userguide/src/userguide/springbootdemo-wildfly/README.md" class="externalLink">springbootdemo-wildfly README</a> |
| (WildFly 32/39).</p> |
| |
| |
| <p><strong>In one sentence:</strong> Axis2 auto-generates an MCP tool catalog from its |
| deployed services, accessible at <code>/openapi-mcp.json</code>, that tells MCP clients |
| the exact JSON-RPC envelope format, auth requirements, and endpoint URL for every |
| deployed operation — no out-of-band documentation required.</p> |
| |
| |
| <section><a id="What_is_MCP.3F"></a> |
| <h2>What is MCP?</h2> |
| |
| |
| <p><strong>MCP (<a href="https://en.wikipedia.org/wiki/Model_Context_Protocol" class="externalLink">Model Context |
| Protocol</a>)</strong> is an open standard published at |
| <a href="https://modelcontextprotocol.io/" class="externalLink">modelcontextprotocol.io</a> |
| that defines how AI assistants (Claude, ChatGPT, |
| Cursor, etc.) discover and call external tools.</p> |
| |
| |
| <p><strong>Relationship to OpenAPI:</strong> |
| <a href="https://en.wikipedia.org/wiki/OpenAPI_Specification" class="externalLink">OpenAPI</a> |
| describes REST APIs for human developers and code generators. MCP describes |
| the same services as <em>tools</em> for AI assistants. Axis2 generates both |
| from the same deployed services — <code>/openapi.json</code> produces the |
| OpenAPI spec, <code>/openapi-mcp.json</code> produces the MCP tool catalog. |
| They are complementary: OpenAPI tells a developer how to call your API; |
| MCP tells an AI assistant how to call it.</p> |
| |
| |
| <p><strong>The core idea:</strong> An MCP server advertises a catalog of tools — each |
| with a name, a natural-language description, and a JSON Schema describing its |
| parameters. An AI assistant reads this catalog, decides which tool to call based on |
| the user's request, fills in the parameters as a JSON object, and sends a |
| <code>tools/call</code> request. The server executes the tool and returns the result |
| as JSON. The entire exchange is JSON — requests, responses, parameter schemas, |
| error messages. There is no XML anywhere in the protocol.</p> |
| |
| |
| <p><strong>Why MCP is JSON-only:</strong> MCP is built on |
| <a href="https://www.jsonrpc.org/specification" class="externalLink">JSON-RPC 2.0</a>, the same |
| lightweight RPC protocol used by language servers (LSP), cryptocurrency nodes |
| (Ethereum), and many other modern tools. AI assistants produce and consume JSON |
| natively — their training data is overwhelmingly JSON, their function-calling APIs |
| use JSON, and their tool-use formats are JSON Schema. XML/SOAP was never considered |
| for MCP because the entire AI tooling ecosystem is JSON-native.</p> |
| |
| |
| <p><strong>What this means for SOAP services:</strong> MCP cannot call SOAP endpoints |
| directly. A SOAP service returns XML envelopes with namespaces, and MCP clients |
| cannot parse them. To expose a SOAP service to AI agents, convert it to JSON-RPC |
| first — this is a configuration change in <code>services.xml</code> (swap message |
| receivers), not a code change. The service Java class is unchanged. See the |
| <a href="spring-boot-starter.html#soap_vs_json">Spring Boot Starter Guide</a> |
| for the <code>axis2.mode=json</code> setting.</p> |
| |
| |
| <p>MCP is to AI tool use what OpenAPI is to REST API discovery: a machine-readable |
| contract that eliminates guesswork. The protocol specification is at |
| <code>modelcontextprotocol.io</code>.</p> |
| |
| </section><section><a id="Axis2.3A_Three_Protocols_from_One_Service"></a> |
| <h2>Axis2: Three Protocols from One Service</h2> |
| |
| |
| <p>A single Axis2 service deployment simultaneously speaks three protocols from the |
| same Java class, with no code duplication and no wrapper layers:</p> |
| |
| |
| <table class="bodyTableBorder"> |
| |
| <tr class="a"> |
| <th>Protocol</th> |
| <th>What it serves</th> |
| <th>Who calls it</th></tr> |
| |
| <tr class="b"> |
| |
| <td><strong>JSON-RPC</strong></td> |
| |
| <td>Axis2's native wire format — <code>{"op":[{"arg0":{...}}]}</code> envelope over HTTP POST</td> |
| |
| <td>Existing enterprise callers, Node.js bridges, legacy integrations</td> |
| </tr> |
| |
| <tr class="a"> |
| |
| <td><strong>REST + OpenAPI</strong></td> |
| |
| <td>Auto-generated OpenAPI 3.0 spec at <code>/openapi.json</code>, interactive Swagger UI at <code>/swagger-ui</code></td> |
| |
| <td>React frontends, data API consumers, API gateways, developers exploring the service</td> |
| </tr> |
| |
| <tr class="b"> |
| |
| <td><strong>MCP</strong></td> |
| |
| <td>Auto-generated MCP tool catalog at <code>/openapi-mcp.json</code> with full <code>inputSchema</code>, auth hints, and payload templates</td> |
| |
| <td>AI assistants (Claude Desktop, Claude API, Cursor), any MCP-compatible agent</td> |
| </tr> |
| </table> |
| |
| |
| <p><strong>This is unique to Axis2.</strong> No other Java framework serves all three |
| protocols from the same service class in the same deployment:</p> |
| |
| |
| <ul> |
| |
| <li><strong>Spring Boot</strong> — excellent REST + OpenAPI via springdoc. MCP is available |
| via Spring AI, but as a separate server component with its own tool definitions — not |
| auto-generated from existing service classes. No native JSON-RPC support.</li> |
| |
| <li><strong>Apache CXF</strong> — SOAP + REST, but no JSON-RPC transport and no MCP support.</li> |
| |
| <li><strong>JAX-RS (Jersey, RESTEasy)</strong> — REST + OpenAPI only. No JSON-RPC, no MCP.</li> |
| |
| <li><strong>gRPC</strong> — its own binary protocol with REST bridging via grpc-gateway. |
| No JSON-RPC, no MCP.</li> |
| </ul> |
| |
| |
| <p>With Axis2, adding MCP to an existing JSON-RPC service is a configuration change in |
| <code>services.xml</code> — add <code>mcpDescription</code> and <code>mcpInputSchema</code> |
| parameters to each operation, and the MCP catalog appears automatically at |
| <code>/openapi-mcp.json</code>. The service Java class is unchanged.</p> |
| |
| |
| <ul> |
| |
| <li><a href="#mcp_catalog">1. The MCP Catalog Endpoint</a></li> |
| |
| <li><a href="#catalog_schema">2. Catalog Schema Reference</a></li> |
| |
| <li><a href="#envelope">3. The Axis2 JSON-RPC Envelope (Critical)</a></li> |
| |
| <li><a href="#auth">4. Authentication: Two-Phase Bearer Token Flow</a></li> |
| |
| <li><a href="#error_handling">5. Error Handling: Correlation ID Pattern</a></li> |
| |
| <li><a href="#not_implemented">6. Not Implemented / Limitations</a></li> |
| |
| <li><a href="#migration_path">7. Migration Path: JSON-RPC to REST</a></li> |
| |
| <li><a href="#python_compat">8. Python MCP Client Example</a></li> |
| </ul> |
| |
| |
| <a id="mcp_catalog"></a> |
| </section><section><a id="a1._The_MCP_Catalog_Endpoint"></a> |
| <h2>1. The MCP Catalog Endpoint</h2> |
| |
| |
| <p>Every Axis2 deployment exposes a machine-readable MCP tool catalog:</p> |
| |
| |
| <pre> |
| GET /axis2/openapi-mcp.json |
| Content-Type: application/json |
| Cache-Control: no-cache, no-store |
| </pre> |
| |
| |
| <p>The catalog is served by <code>SwaggerUIHandler.handleMcpCatalogRequest()</code> |
| alongside the existing <code>/swagger-ui</code> and <code>/openapi.json</code> endpoints. |
| <code>Cache-Control: no-cache, no-store</code> is intentional — the service list changes |
| on every deployment and a stale catalog causes MCP clients to call operations that no |
| longer exist.</p> |
| |
| |
| <p><strong>HTTP headers set on the catalog response:</strong></p> |
| |
| |
| <table class="bodyTableBorder"> |
| |
| <tr class="a"> |
| <th>Header</th> |
| <th>Value</th> |
| <th>Purpose</th></tr> |
| |
| <tr class="b"> |
| <td><code>Content-Type</code></td> |
| <td><code>application/json; charset=UTF-8</code></td> |
| <td>MCP client parsing</td></tr> |
| |
| <tr class="a"> |
| <td><code>Cache-Control</code></td> |
| <td><code>no-cache, no-store</code></td> |
| <td>Prevent stale tool lists</td></tr> |
| |
| <tr class="b"> |
| <td><code>Access-Control-Allow-Origin</code></td> |
| <td><code>*</code></td> |
| <td>MCP clients from any origin</td></tr> |
| |
| <tr class="a"> |
| <td><code>Access-Control-Allow-Methods</code></td> |
| <td><code>GET, OPTIONS</code></td> |
| <td>Catalog is GET-only (POST is on service endpoints)</td></tr> |
| |
| <tr class="b"> |
| <td><code>Access-Control-Allow-Headers</code></td> |
| <td><code>Content-Type, Authorization</code></td> |
| <td>Bearer token in pre-flight</td></tr> |
| |
| <tr class="a"> |
| <td><code>X-Content-Type-Options</code></td> |
| <td><code>nosniff</code></td> |
| <td>Security hardening</td></tr> |
| |
| <tr class="b"> |
| <td><code>X-Frame-Options</code></td> |
| <td><code>SAMEORIGIN</code></td> |
| <td>Clickjacking protection</td></tr> |
| </table> |
| |
| |
| <a id="catalog_schema"></a> |
| </section><section><a id="a2._Catalog_Schema_Reference"></a> |
| <h2>2. Catalog Schema Reference</h2> |
| |
| |
| <p>The catalog JSON has two top-level keys: <code>_meta</code> (transport contract, |
| constant across all services) and <code>tools</code> (one entry per deployed operation).</p> |
| |
| <section><a id="a2.1__meta_Block"></a> |
| <h3>2.1 _meta Block</h3> |
| |
| |
| <pre> |
| { |
| "_meta": { |
| "axis2JsonRpcFormat": "{\"<operationName>\":[{\"arg0\":{<params>}}]}", |
| "contentType": "application/json", |
| "authHeader": "Authorization: Bearer <token>", |
| "tokenEndpoint": "POST /services/loginService/doLogin", |
| "errorContract": { |
| "schemaRef": "#/components/schemas/ErrorResponse", |
| "fields": { |
| "error": "Error code: VALIDATION_ERROR | RATE_LIMITED | SERVICE_UNAVAILABLE | BAD_REQUEST | INTERNAL_ERROR", |
| "message": "Human-readable error message", |
| "errorRef": "UUID correlation ID — quote in support requests", |
| "timestamp": "ISO 8601 when the error occurred", |
| "retryAfter": "Seconds to wait before retrying (429/503 only, null otherwise)" |
| }, |
| "httpStatusMapping": { |
| "400": "BAD_REQUEST — malformed JSON or missing required fields", |
| "422": "VALIDATION_ERROR — valid JSON but fails business validation", |
| "429": "RATE_LIMITED — too many requests, check retryAfter", |
| "500": "INTERNAL_ERROR — server fault, errorRef logged server-side", |
| "503": "SERVICE_UNAVAILABLE — downstream dependency or overload" |
| } |
| } |
| }, |
| ... |
| } |
| </pre> |
| |
| |
| <p>The <code>_meta</code> block answers the three questions every MCP client must answer |
| before calling any tool:</p> |
| |
| <ul> |
| |
| <li><strong>What body structure does the server expect?</strong> See |
| <code>axis2JsonRpcFormat</code> — the Axis2 JSON-RPC envelope with |
| operation name as the top-level key.</li> |
| |
| <li><strong>How do I authenticate?</strong> See <code>authHeader</code> and |
| <code>tokenEndpoint</code> — call <code>loginService/doLogin</code> first, |
| then pass the returned token as a Bearer header.</li> |
| |
| <li><strong>What Content-Type?</strong> Always <code>application/json</code>.</li> |
| |
| <li><strong>What do errors look like?</strong> See <code>errorContract</code> — |
| structured JSON with error code, message, correlation ID (<code>errorRef</code>), |
| timestamp, and HTTP status mapping. Services throw |
| <code>JsonRpcFaultException</code> to produce these responses.</li> |
| </ul> |
| |
| </section><section><a id="a2.2_Per-Tool_Fields"></a> |
| <h3>2.2 Per-Tool Fields</h3> |
| |
| |
| <pre> |
| { |
| "tools": [ |
| { |
| "name": "doLogin", |
| "description": "loginService: doLogin", |
| "inputSchema": { |
| "type": "object", |
| "properties": {}, |
| "required": [] |
| }, |
| "endpoint": "POST /services/loginService/doLogin", |
| "x-axis2-payloadTemplate": "{\"doLogin\":[{\"arg0\":{}}]}", |
| "x-requiresAuth": false, |
| "annotations": { |
| "readOnlyHint": false, |
| "destructiveHint": false, |
| "idempotentHint": false, |
| "openWorldHint": false |
| } |
| }, |
| { |
| "name": "portfolioVariance", |
| "description": "Calculate portfolio variance using O(n^2) covariance matrix multiplication", |
| "inputSchema": { |
| "type": "object", |
| "required": ["nAssets", "weights", "covarianceMatrix"], |
| "properties": { |
| "nAssets": {"type": "integer", "minimum": 2, "maximum": 2000}, |
| "weights": {"type": "array", "items": {"type": "number"}}, |
| "covarianceMatrix": {"type": "array", "items": {"type": "array", "items": {"type": "number"}}}, |
| "normalizeWeights": {"type": "boolean", "default": false}, |
| "nPeriodsPerYear": {"type": "integer", "default": 252} |
| } |
| }, |
| "endpoint": "POST /services/FinancialBenchmarkService/portfolioVariance", |
| "x-axis2-payloadTemplate": "{\"portfolioVariance\":[{\"arg0\":{}}]}", |
| "x-requiresAuth": true, |
| "annotations": { |
| "readOnlyHint": true, |
| "destructiveHint": false, |
| "idempotentHint": true, |
| "openWorldHint": false |
| } |
| } |
| ] |
| } |
| </pre> |
| |
| |
| <p><strong>Field semantics:</strong></p> |
| |
| |
| <table class="bodyTableBorder"> |
| |
| <tr class="a"> |
| <th>Field</th> |
| <th>Type</th> |
| <th>Notes</th></tr> |
| |
| <tr class="b"> |
| <td><code>name</code></td> |
| <td>string</td> |
| <td>Axis2 operation name (local part of QName); use as tool name in MCP</td></tr> |
| |
| <tr class="a"> |
| <td><code>description</code></td> |
| <td>string</td> |
| <td>Auto-generated "ServiceName: operationName" — not a rich natural language description</td></tr> |
| |
| <tr class="b"> |
| <td><code>inputSchema</code></td> |
| <td>object</td> |
| <td>MCP-compliant JSON Schema; populated from <code>mcpInputSchema</code> parameter in services.xml when set, otherwise empty <code>{}</code></td></tr> |
| |
| <tr class="a"> |
| <td><code>endpoint</code></td> |
| <td>string</td> |
| <td>Full POST path for this operation</td></tr> |
| |
| <tr class="b"> |
| <td><code>x-axis2-payloadTemplate</code></td> |
| <td>string (JSON)</td> |
| <td>The exact body to send, with the operation's wrapper already filled in</td></tr> |
| |
| <tr class="a"> |
| <td><code>x-requiresAuth</code></td> |
| <td>boolean</td> |
| <td><code>false</code> only for <code>loginService</code> (case-insensitive); <code>true</code> for everything else</td></tr> |
| |
| <tr class="b"> |
| <td><code>annotations</code></td> |
| <td>object</td> |
| <td>MCP 2025-03-26 safety hints; all default to <code>false</code> (conservative)</td></tr> |
| </table> |
| |
| |
| <p><strong>MCP 2025-03-26 annotations</strong> (<code>readOnlyHint</code>, |
| <code>destructiveHint</code>, <code>idempotentHint</code>, <code>openWorldHint</code>) |
| are present for spec compliance but are all <code>false</code>. They are not tuned per |
| service. If your MCP host requires accurate hints, you must set them in a catalog |
| post-processor or in your Python MCP server layer.</p> |
| |
| |
| <a id="envelope"></a> |
| </section></section><section><a id="a3._The_Axis2_JSON-RPC_Envelope_.28Critical.29"></a> |
| <h2>3. The Axis2 JSON-RPC Envelope (Critical)</h2> |
| |
| |
| <p>Axis2's JSON-RPC layer requires every call to use a specific three-layer envelope. |
| This is the single biggest difference from conventional REST APIs. |
| Every MCP tool that calls an Axis2 service must use this format.</p> |
| |
| <section><a id="a3.1_Required_Envelope_Structure"></a> |
| <h3>3.1 Required Envelope Structure</h3> |
| |
| |
| <pre> |
| POST /services/{ServiceName}/{operationName} |
| Content-Type: application/json |
| Authorization: Bearer {token} |
| |
| { |
| "{operationName}": [ |
| { |
| "arg0": { |
| ... your parameters here ... |
| } |
| } |
| ] |
| } |
| </pre> |
| |
| |
| <p>The parsing sequence in <code>JsonUtils.invokeServiceClass()</code> is strict:</p> |
| |
| <ol style="list-style-type: decimal;"> |
| |
| <li><code>beginObject()</code> — outer <code>{}</code></li> |
| |
| <li><code>nextName()</code> — must equal the operation name</li> |
| |
| <li><code>beginArray()</code> — the <code>[...]</code> wrapper</li> |
| |
| <li><code>beginObject()</code> — the parameter object</li> |
| |
| <li><code>nextName()</code> → <code>fromJson()</code> — reads each parameter</li> |
| |
| <li><code>endObject()</code>, <code>endArray()</code>, <code>endObject()</code></li> |
| </ol> |
| |
| |
| <p>Any deviation — bare JSON object, missing array, wrong operation name — results in |
| <code>Bad Request [errorRef=<uuid>]</code>. There is no partial match or helpful |
| field-level error (see Section 5).</p> |
| |
| </section><section><a id="a3.2_Login_Payload_Example"></a> |
| <h3>3.2 Login Payload Example</h3> |
| |
| |
| <p>The exact payload for <code>loginService/doLogin</code>:</p> |
| |
| |
| <pre> |
| { |
| "doLogin": [ |
| { |
| "arg0": { |
| "email": "user@example.com", |
| "credentials": "password" |
| } |
| } |
| ] |
| } |
| </pre> |
| |
| |
| <p>This pattern applies to every Axis2 service. The <code>x-axis2-payloadTemplate</code> |
| field in the catalog pre-fills the operation name wrapper so MCP clients only need to |
| substitute parameters into <code>arg0</code>.</p> |
| |
| </section><section><a id="a3.3_enableJSONOnly_Mode"></a> |
| <h3>3.3 enableJSONOnly Mode</h3> |
| |
| |
| <p>When a service is deployed with <code>enableJSONOnly=true</code>, the outer operation |
| name wrapper is optional — the server dispatches by URL path alone. The payload reduces to:</p> |
| |
| |
| <pre> |
| [{ "arg0": { ... parameters ... } }] |
| </pre> |
| |
| |
| <p>Most production deployments use <code>enableJSONOnly=false</code> (the default), |
| which requires the full envelope. Check the catalog's <code>x-axis2-payloadTemplate</code> |
| to see which format a specific service expects.</p> |
| |
| |
| <a id="auth"></a> |
| </section></section><section><a id="a4._Authentication.3A_Two-Phase_Bearer_Token_Flow"></a> |
| <h2>4. Authentication: Two-Phase Bearer Token Flow</h2> |
| |
| |
| <p><strong>Note:</strong> Axis2 is a web services framework — it does not |
| impose any specific authentication mechanism. The two-phase Bearer token |
| flow described below is implemented by the |
| <a href="https://github.com/apache/axis-axis2-java-core/tree/master/modules/samples/userguide/src/userguide/springbootdemo-tomcat11" class="externalLink">sample application</a>'s |
| Spring Security configuration, not by Axis2 itself. Your application can |
| use any auth mechanism (OAuth2, API keys, mTLS, etc.) by configuring |
| Spring Security or your servlet container accordingly.</p> |
| |
| |
| <p>The sample application uses the following two-phase flow:</p> |
| |
| <section><a id="Phase_1_.E2.80.94_Obtain_Token_.28no_auth_required.29"></a> |
| <h3>Phase 1 — Obtain Token (no auth required)</h3> |
| |
| |
| <pre> |
| POST /services/loginService/doLogin |
| Content-Type: application/json |
| |
| { |
| "doLogin": [ |
| { |
| "arg0": { |
| "email": "user@example.com", |
| "credentials": "password" |
| } |
| } |
| ] |
| } |
| |
| Response: |
| { |
| "response": { |
| "token": "eyJhbGciOiJIUzI1NiJ9...", |
| "user": { ... } |
| } |
| } |
| </pre> |
| |
| |
| <p>Token storage is implementation-specific. A common pattern is to persist the token |
| as a JSON file with mode <code>0600</code> (user-only read), or pass it via an |
| environment variable.</p> |
| |
| </section><section><a id="Phase_2_.E2.80.94_Call_Protected_Services"></a> |
| <h3>Phase 2 — Call Protected Services</h3> |
| |
| |
| <pre> |
| POST /services/{ServiceName}/{operationName} |
| Content-Type: application/json |
| Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... |
| |
| { |
| "{operationName}": [{ "arg0": { ... } }] |
| } |
| </pre> |
| |
| |
| <p>All services except <code>loginService</code> require the Bearer header. |
| The catalog's <code>x-requiresAuth</code> field encodes this per-tool. The |
| <code>_meta.tokenEndpoint</code> tells MCP clients where to obtain the token |
| without hardcoding the service path.</p> |
| |
| |
| <a id="error_handling"></a> |
| </section></section><section><a id="a5._Error_Handling"></a> |
| <h2>5. Error Handling</h2> |
| |
| |
| <p>Axis2 JSON-RPC services support two error formats depending on where the |
| error originates:</p> |
| |
| <section><a id="a5.1_Structured_JSON_Errors_.28service-level_validation.29"></a> |
| <h3>5.1 Structured JSON Errors (service-level validation)</h3> |
| |
| |
| <p>Services that throw |
| <a href="https://github.com/apache/axis-axis2-java-core/blob/master/modules/json/src/org/apache/axis2/json/gson/rpc/JsonRpcFaultException.java" class="externalLink"><code>JsonRpcFaultException</code></a> |
| produce structured JSON error responses with proper HTTP status codes:</p> |
| |
| |
| <pre> |
| HTTP 422 |
| Content-Type: application/json |
| |
| { |
| "response": { |
| "status": "FAILED", |
| "error": "VALIDATION_ERROR", |
| "message": "initialValue must be > 0 (GBM is undefined for non-positive starting values).", |
| "errorRef": "a3f2c1d0-7b4e-4a2f-9c8d-1e6f3b5a2d7c", |
| "timestamp": "2026-05-15T14:30:00Z", |
| "retryAfter": null |
| } |
| } |
| </pre> |
| |
| |
| <p>The HTTP status codes map to error categories:</p> |
| |
| <table class="bodyTableBorder"> |
| |
| <tr class="a"> |
| <th>HTTP Status</th> |
| <th>Error Code</th> |
| <th>Meaning</th></tr> |
| |
| <tr class="b"> |
| <td>400</td> |
| <td>BAD_REQUEST</td> |
| <td>Malformed JSON or missing required fields</td></tr> |
| |
| <tr class="a"> |
| <td>422</td> |
| <td>VALIDATION_ERROR</td> |
| <td>Valid JSON but fails business validation</td></tr> |
| |
| <tr class="b"> |
| <td>429</td> |
| <td>RATE_LIMITED</td> |
| <td>Too many requests; check <code>retryAfter</code></td></tr> |
| |
| <tr class="a"> |
| <td>500</td> |
| <td>INTERNAL_ERROR</td> |
| <td>Server fault; <code>errorRef</code> logged server-side</td></tr> |
| |
| <tr class="b"> |
| <td>503</td> |
| <td>SERVICE_UNAVAILABLE</td> |
| <td>Downstream dependency or overload</td></tr> |
| </table> |
| |
| |
| <p>The <code>errorRef</code> UUID is logged server-side with full context (operation |
| name, exception message, stack trace). Clients should surface the UUID in |
| their error displays so users can quote it in support requests.</p> |
| |
| </section><section><a id="a5.2_SOAP_Fault_Fallback_.28parse-level_errors.29"></a> |
| <h3>5.2 SOAP Fault Fallback (parse-level errors)</h3> |
| |
| |
| <p>Requests that fail before reaching the service method (malformed JSON, |
| wrong operation name, missing array wrapper) produce a legacy SOAP fault |
| with a sanitized <code>Bad Request</code> message. This is a deliberate |
| security feature — no structural information leakage:</p> |
| |
| |
| <pre> |
| HTTP 500 |
| <soapenv:Fault> |
| <faultcode>soapenv:Server</faultcode> |
| <faultstring>Bad Request [errorRef=a3f2c1d0-7b4e-4a2f-9c8d-1e6f3b5a2d7c]</faultstring> |
| </soapenv:Fault> |
| </pre> |
| |
| </section><section><a id="a5.2_What_Triggers_This"></a> |
| <h3>5.2 What Triggers This</h3> |
| |
| |
| <table class="bodyTableBorder"> |
| |
| <tr class="a"> |
| <th>Bad Payload</th> |
| <th>Result</th></tr> |
| |
| <tr class="b"> |
| <td>Not valid JSON at all</td> |
| <td><code>Bad Request [errorRef=...]</code></td></tr> |
| |
| <tr class="a"> |
| <td>Valid JSON but missing outer array <code>[...]</code></td> |
| <td><code>Bad Request [errorRef=...]</code></td></tr> |
| |
| <tr class="b"> |
| <td>Operation name in body does not match URL path</td> |
| <td><code>Bad Request [errorRef=...]</code></td></tr> |
| |
| <tr class="a"> |
| <td>Parameters wrong type for service method</td> |
| <td><code>Bad Request [errorRef=...]</code></td></tr> |
| |
| <tr class="b"> |
| <td>Service reflection error (wrong method signature)</td> |
| <td><code>Internal Server Error [errorRef=...]</code></td></tr> |
| </table> |
| |
| </section><section><a id="a5.3_Python_MCP_Implications"></a> |
| <h3>5.3 Python MCP Implications</h3> |
| |
| |
| <p>MCP tools built against Axis2 services should surface the <code>errorRef</code> |
| UUID in their error responses so users can correlate with server logs. A recommended |
| pattern is to return a structured <code>ErrorResponse</code> with <code>error_type</code> |
| and <code>suggestions</code>:</p> |
| |
| |
| <pre> |
| from dataclasses import dataclass |
| |
| @dataclass |
| class ErrorResponse: |
| error: str # "Bad Request [errorRef=a3f2c1d0...]" |
| error_type: str # "axis2_payload_error" |
| suggestions: list[str] # ["Check x-axis2-payloadTemplate in catalog"] |
| </pre> |
| |
| |
| |
| <a id="not_implemented"></a> |
| </section></section><section><a id="a6._Not_Implemented_.2F_Limitations"></a> |
| <h2>6. Not Implemented / Limitations</h2> |
| |
| |
| <p>The following capabilities are <strong>not present</strong> in the Axis2 MCP catalog. |
| Each item notes whether the gap is architectural (won't be added to Axis2) or deferred |
| (could be added).</p> |
| |
| |
| <table class="bodyTableBorder"> |
| |
| <tr class="a"> |
| <th>Feature</th> |
| <th>Status</th> |
| <th>Notes</th></tr> |
| |
| <tr class="b"> |
| |
| <td>Rich <code>inputSchema</code> properties</td> |
| |
| <td><strong>Implemented</strong> — set <code>mcpInputSchema</code> parameter on <code><operation></code> in services.xml</td> |
| |
| <td>When <code>mcpInputSchema</code> is set, the catalog embeds the full JSON Schema (types, constraints, required fields). Falls back to empty <code>{}</code> when not set. All financial benchmark operations have full schemas. Automatic introspection from Java types is not yet implemented — schemas are hand-authored in services.xml.</td> |
| </tr> |
| |
| <tr class="a"> |
| |
| <td>Natural language tool descriptions</td> |
| |
| <td><strong>Implemented</strong> — set <code>mcpDescription</code> parameter on <code><operation></code> or <code><service></code> in services.xml</td> |
| |
| <td>Operation-level parameter takes precedence over service-level; falls back to auto-generated "ServiceName: operationName".</td> |
| </tr> |
| |
| <tr class="b"> |
| |
| <td>MCP Resources and Prompts</td> |
| |
| <td>Not implemented</td> |
| |
| <td>The catalog exposes only MCP "tools". The MCP protocol also defines "resources" (URIs for data blobs) and "prompts" (parameterized message templates). Axis2 services are operation-based and do not map to resources or prompts.</td> |
| </tr> |
| |
| <tr class="a"> |
| |
| <td>MCP 2025-03-26 annotation tuning</td> |
| |
| <td><strong>Implemented</strong> — set <code>mcpReadOnly</code>, <code>mcpIdempotent</code>, <code>mcpDestructive</code>, <code>mcpOpenWorld</code> on <code><operation></code> or <code><service></code> in services.xml</td> |
| |
| <td>Conservative <code>false</code> defaults preserved when parameters absent. Operation-level overrides service-level.</td> |
| </tr> |
| |
| <tr class="b"> |
| |
| <td>Streaming / SSE responses</td> |
| |
| <td>Not implemented — architectural gap</td> |
| |
| <td>Axis2 JSON-RPC is request/response only. The MCP protocol supports server-sent event streams for long-running operations. There is no streaming path.</td> |
| </tr> |
| |
| <tr class="a"> |
| |
| <td>Batch tool calls</td> |
| |
| <td>Not implemented</td> |
| |
| <td>Each Axis2 operation is a separate HTTP POST. There is no batch envelope.</td> |
| </tr> |
| |
| <tr class="b"> |
| |
| <td>Semantic query layer (filter/sort/fields)</td> |
| |
| <td>Not applicable to Axis2</td> |
| |
| <td>Axis2 JSON-RPC is operation-based — parameters are passed in <code>arg0</code>, not as query strings. A REST API layer could add uniform filter/sort/fields query semantics, but that is outside the scope of the Axis2 JSON-RPC transport.</td> |
| </tr> |
| |
| <tr class="a"> |
| |
| <td>Natural key resolution (ticker → assetId)</td> |
| |
| <td><strong>Implemented</strong> — set global parameter <code>mcpTickerResolveService</code> in axis2.xml</td> |
| |
| <td>When set to <code>ServiceName/operationName</code>, <code>_meta.tickerResolveEndpoint</code> is added to the catalog. Omitted entirely when not configured so deployments without a ticker service are unaffected.</td> |
| </tr> |
| |
| <tr class="b"> |
| |
| <td>Pagination</td> |
| |
| <td><strong>Offset/limit implemented</strong> — see <a href="json-pagination.html">Pagination Guide</a></td> |
| |
| <td>Axis2 provides <code>PaginatedResponse<T></code> and <code>PaginationRequest</code> |
| for offset/limit pagination with maxLimit clamping. |
| Cursor-based pagination is not implemented (offset/limit maps directly to JPA/Hibernate DAO patterns).</td> |
| </tr> |
| |
| <tr class="a"> |
| |
| <td>Structured error responses</td> |
| |
| <td><strong>Implemented</strong> — see <a href="#error_handling">Section 5</a></td> |
| |
| <td>Services throw <code>JsonRpcFaultException</code> to produce structured JSON errors |
| with HTTP status codes (422/429/503), error codes, correlation IDs, and timestamps. |
| Parse-level errors still produce legacy SOAP faults. Not RFC 7807 format, but |
| provides equivalent structured error information.</td> |
| </tr> |
| |
| <tr class="b"> |
| |
| <td>API key management</td> |
| |
| <td>Not applicable to Axis2</td> |
| |
| <td>Axis2 uses email/password → Bearer token via <code>loginService</code> only. API key + secret issuance with scopes and rotation would need to be implemented in a separate layer.</td> |
| </tr> |
| |
| <tr class="a"> |
| |
| <td>Write operations (CRUD mutations)</td> |
| |
| <td>Axis2 has write services; catalog supports them</td> |
| |
| <td>Axis2 write operations exist as services and will appear in the catalog with <code>x-requiresAuth: true</code>. If your deployment also exposes a REST API layer for writes, determine which path is canonical for your use case.</td> |
| </tr> |
| </table> |
| |
| |
| <a id="migration_path"></a> |
| </section><section><a id="a7._Migration_Path.3A_JSON-RPC_to_REST"></a> |
| <h2>7. Migration Path: JSON-RPC to REST</h2> |
| |
| |
| <p>Organizations that deploy both Axis2 JSON-RPC services and a modern REST API layer |
| will have two different MCP transport paths. Understanding which path a given MCP tool |
| uses determines what limitations apply:</p> |
| |
| |
| <table class="bodyTableBorder"> |
| |
| <tr class="a"> |
| <th>Aspect</th> |
| <th>Axis2 JSON-RPC</th> |
| <th>REST API Layer</th></tr> |
| |
| <tr class="b"> |
| <td>Protocol</td> |
| <td>Axis2 JSON-RPC envelope: <code>{"op":[{"arg0":{}}]}</code></td> |
| <td>Plain REST: <code>GET /api/v1/resources/{id}?filter=...</code></td></tr> |
| |
| <tr class="a"> |
| <td>Tool definitions</td> |
| <td>Auto-generated from deployed Axis2 services</td> |
| <td>Auto-generated from OpenAPI 3.1 (e.g., via springdoc-openapi)</td></tr> |
| |
| <tr class="b"> |
| <td>Auth</td> |
| <td>email + password → Bearer token (loginService)</td> |
| <td>API key + secret → scoped JWT</td></tr> |
| |
| <tr class="a"> |
| <td>Query semantics</td> |
| <td>Per-operation parameters in arg0</td> |
| <td>Uniform filter/sort/fields on every resource</td></tr> |
| |
| <tr class="b"> |
| <td>Error format</td> |
| <td>Structured JSON (JsonRpcFaultException) with correlation ID, HTTP status codes</td> |
| <td>RFC 7807 Problem Details (JSON, per-field)</td></tr> |
| |
| <tr class="a"> |
| <td>Pagination</td> |
| <td>Offset/limit (<a href="json-pagination.html">PaginatedResponse</a>)</td> |
| <td>Cursor-based</td></tr> |
| |
| <tr class="b"> |
| <td>inputSchema</td> |
| <td>Full JSON Schema via <code>mcpInputSchema</code> in services.xml (hand-authored)</td> |
| <td>Full JSON Schema from OpenAPI annotations (auto-generated)</td></tr> |
| </table> |
| |
| |
| <p>The Axis2 MCP catalog at <code>/openapi-mcp.json</code> provides MCP-ready tool |
| discovery for existing Axis2 services. If your organization is building a REST API |
| layer alongside Axis2, the catalog serves as a bridge — providing machine-readable |
| tool discovery during the transition period.</p> |
| |
| |
| <a id="python_compat"></a> |
| </section><section><a id="a8._Python_MCP_Client_Example"></a> |
| <h2>8. Python MCP Client Example</h2> |
| |
| |
| <p>The most common MCP integration path is a Python bridge — AI assistants |
| like Claude and ChatGPT use Python-based MCP servers to call external tools. |
| The template below shows how to authenticate, discover tools from the |
| Axis2 MCP catalog, and call services using the correct JSON-RPC envelope.</p> |
| |
| |
| <pre> |
| import httpx |
| import json |
| from mcp.server import Server |
| from mcp.server.stdio import stdio_server |
| from mcp.types import Tool, TextContent |
| |
| BASE_URL = "https://your-axis2-server/services" |
| server = Server("axis2-mcp") |
| _token = None |
| |
| async def login(email: str, password: str) -> str: |
| async with httpx.AsyncClient() as c: |
| r = await c.post( |
| f"{BASE_URL}/loginService/doLogin", |
| json={"doLogin": [{"arg0": {"email": email, "credentials": password}}]} |
| ) |
| return r.json()["response"]["token"] |
| |
| async def call_service(service: str, op: str, params: dict) -> dict: |
| async with httpx.AsyncClient() as c: |
| r = await c.post( |
| f"{BASE_URL}/{service}/{op}", |
| json={op: [{"arg0": params}]}, |
| headers={"Authorization": f"Bearer {_token}"} |
| ) |
| return r.json() |
| |
| @server.list_tools() |
| async def list_tools() -> list[Tool]: |
| # Fetch live from catalog — honors Cache-Control: no-cache |
| async with httpx.AsyncClient() as c: |
| catalog = (await c.get(f"{BASE_URL}/../openapi-mcp.json")).json() |
| return [ |
| Tool(name=t["name"], description=t["description"], |
| inputSchema=t["inputSchema"]) |
| for t in catalog["tools"] |
| ] |
| |
| @server.call_tool() |
| async def call_tool(name: str, arguments: dict) -> list[TextContent]: |
| # Resolve service name from catalog endpoint field |
| # then delegate to call_service() |
| ... |
| </pre> |
| |
| |
| <p><strong>Key points:</strong></p> |
| |
| <ul> |
| |
| <li>The catalog at <code>/openapi-mcp.json</code> has |
| <code>Cache-Control: no-cache</code>, so fetching it at |
| <code>list_tools()</code> time is correct — the tool list stays |
| current without restarting the MCP server.</li> |
| |
| <li>When <code>mcpInputSchema</code> is set in <code>services.xml</code>, |
| the catalog provides full JSON Schema for each tool. For services |
| without it, define the schema in your Python MCP server.</li> |
| |
| <li>The <code>_meta.axis2JsonRpcFormat</code> field documents the exact |
| envelope format your HTTP client must send.</li> |
| </ul> |
| |
| </section> |
| </html> </main> |
| </div> |
| </div> |
| <hr/> |
| <footer> |
| <div class="container-fluid"> |
| <div class="row-fluid"> |
| <p>© 2004–2026 |
| <a href="https://www.apache.org/">The Apache Software Foundation</a> |
| </p> |
| </div> |
| </div> |
| </footer> |
| </body> |
| </html> |