| <!-- | |
| ~ Licensed to the Apache Software Foundation (ASF) under one | |
| ~ or more contributor license agreements. See the NOTICE file | |
| ~ distributed with this work for additional information | |
| ~ regarding copyright ownership. The ASF licenses this file | |
| ~ to you under the Apache License, Version 2.0 (the | |
| ~ "License"); you may not use this file except in compliance | |
| ~ with the License. You may obtain a copy of the License at | |
| ~ | |
| ~ http://www.apache.org/licenses/LICENSE-2.0 | |
| ~ | |
| ~ Unless required by applicable law or agreed to in writing, | |
| ~ software distributed under the License is distributed on an | |
| ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| ~ KIND, either express or implied. See the License for the | |
| ~ specific language governing permissions and limitations | |
| ~ under the License. | |
| --> | |
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" | |
| "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> | |
| <html> | |
| <head> | |
| <meta http-equiv="content-type" content=""/> | |
| <title>Apache Axis2 MCP Integration Guide</title> | |
| </head> | |
| <body dir="ltr" lang="en-US"> | |
| <h1 align="center">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">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">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> | |
| <!-- ============================================================ --> | |
| <h2>What is MCP?</h2> | |
| <p><strong>MCP (<a href="https://en.wikipedia.org/wiki/Model_Context_Protocol">Model Context | |
| Protocol</a>)</strong> is an open standard published at | |
| <a href="https://modelcontextprotocol.io/">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">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">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> | |
| <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 border="1"> | |
| <tr><th>Protocol</th><th>What it serves</th><th>Who calls it</th></tr> | |
| <tr> | |
| <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> | |
| <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> | |
| <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 name="mcp_catalog"/> | |
| <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 border="1"> | |
| <tr><th>Header</th><th>Value</th><th>Purpose</th></tr> | |
| <tr><td><code>Content-Type</code></td><td><code>application/json; charset=UTF-8</code></td><td>MCP client parsing</td></tr> | |
| <tr><td><code>Cache-Control</code></td><td><code>no-cache, no-store</code></td><td>Prevent stale tool lists</td></tr> | |
| <tr><td><code>Access-Control-Allow-Origin</code></td><td><code>*</code></td><td>MCP clients from any origin</td></tr> | |
| <tr><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><td><code>Access-Control-Allow-Headers</code></td><td><code>Content-Type, Authorization</code></td><td>Bearer token in pre-flight</td></tr> | |
| <tr><td><code>X-Content-Type-Options</code></td><td><code>nosniff</code></td><td>Security hardening</td></tr> | |
| <tr><td><code>X-Frame-Options</code></td><td><code>SAMEORIGIN</code></td><td>Clickjacking protection</td></tr> | |
| </table> | |
| <!-- ============================================================ --> | |
| <a name="catalog_schema"/> | |
| <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> | |
| <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> | |
| <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 border="1"> | |
| <tr><th>Field</th><th>Type</th><th>Notes</th></tr> | |
| <tr><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><td><code>description</code></td><td>string</td><td>Auto-generated "ServiceName: operationName" — not a rich natural language description</td></tr> | |
| <tr><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><td><code>endpoint</code></td><td>string</td><td>Full POST path for this operation</td></tr> | |
| <tr><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><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><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 name="envelope"/> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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 name="auth"/> | |
| <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">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> | |
| <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> | |
| <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 name="error_handling"/> | |
| <h2>5. Error Handling</h2> | |
| <p>Axis2 JSON-RPC services support two error formats depending on where the | |
| error originates:</p> | |
| <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"><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 border="1"> | |
| <tr><th>HTTP Status</th><th>Error Code</th><th>Meaning</th></tr> | |
| <tr><td>400</td><td>BAD_REQUEST</td><td>Malformed JSON or missing required fields</td></tr> | |
| <tr><td>422</td><td>VALIDATION_ERROR</td><td>Valid JSON but fails business validation</td></tr> | |
| <tr><td>429</td><td>RATE_LIMITED</td><td>Too many requests; check <code>retryAfter</code></td></tr> | |
| <tr><td>500</td><td>INTERNAL_ERROR</td><td>Server fault; <code>errorRef</code> logged server-side</td></tr> | |
| <tr><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> | |
| <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> | |
| <h3>5.2 What Triggers This</h3> | |
| <table border="1"> | |
| <tr><th>Bad Payload</th><th>Result</th></tr> | |
| <tr><td>Not valid JSON at all</td><td><code>Bad Request [errorRef=...]</code></td></tr> | |
| <tr><td>Valid JSON but missing outer array <code>[...]</code></td><td><code>Bad Request [errorRef=...]</code></td></tr> | |
| <tr><td>Operation name in body does not match URL path</td><td><code>Bad Request [errorRef=...]</code></td></tr> | |
| <tr><td>Parameters wrong type for service method</td><td><code>Bad Request [errorRef=...]</code></td></tr> | |
| <tr><td>Service reflection error (wrong method signature)</td><td><code>Internal Server Error [errorRef=...]</code></td></tr> | |
| </table> | |
| <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 name="not_implemented"/> | |
| <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 border="1"> | |
| <tr><th>Feature</th><th>Status</th><th>Notes</th></tr> | |
| <tr> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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> | |
| <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 name="migration_path"/> | |
| <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 border="1"> | |
| <tr><th>Aspect</th><th>Axis2 JSON-RPC</th><th>REST API Layer</th></tr> | |
| <tr><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><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><td>Auth</td><td>email + password → Bearer token (loginService)</td><td>API key + secret → scoped JWT</td></tr> | |
| <tr><td>Query semantics</td><td>Per-operation parameters in arg0</td><td>Uniform filter/sort/fields on every resource</td></tr> | |
| <tr><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><td>Pagination</td><td>Offset/limit (<a href="json-pagination.html">PaginatedResponse</a>)</td><td>Cursor-based</td></tr> | |
| <tr><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 name="python_compat"/> | |
| <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> | |
| </body> | |
| </html> |