blob: b4efb11dbe61d207b21f7823617219c3c2e38635 [file]
<!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&apos;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 &#x2014; 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 &#x2014; <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 &#x2014; 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 &#x2014; 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 &#x2014; 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 &#x2014; 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 &#x2014; <code>{&quot;op&quot;:[{&quot;arg0&quot;:{...}}]}</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> &#x2014; excellent REST + OpenAPI via springdoc. MCP is available
via Spring AI, but as a separate server component with its own tool definitions &#x2014; not
auto-generated from existing service classes. No native JSON-RPC support.</li>
<li><strong>Apache CXF</strong> &#x2014; SOAP + REST, but no JSON-RPC transport and no MCP support.</li>
<li><strong>JAX-RS (Jersey, RESTEasy)</strong> &#x2014; REST + OpenAPI only. No JSON-RPC, no MCP.</li>
<li><strong>gRPC</strong> &#x2014; 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> &#x2014; 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 &#x2014; 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>
{
&quot;_meta&quot;: {
&quot;axis2JsonRpcFormat&quot;: &quot;{\&quot;&lt;operationName&gt;\&quot;:[{\&quot;arg0\&quot;:{&lt;params&gt;}}]}&quot;,
&quot;contentType&quot;: &quot;application/json&quot;,
&quot;authHeader&quot;: &quot;Authorization: Bearer &lt;token&gt;&quot;,
&quot;tokenEndpoint&quot;: &quot;POST /services/loginService/doLogin&quot;,
&quot;errorContract&quot;: {
&quot;schemaRef&quot;: &quot;#/components/schemas/ErrorResponse&quot;,
&quot;fields&quot;: {
&quot;error&quot;: &quot;Error code: VALIDATION_ERROR | RATE_LIMITED | SERVICE_UNAVAILABLE | BAD_REQUEST | INTERNAL_ERROR&quot;,
&quot;message&quot;: &quot;Human-readable error message&quot;,
&quot;errorRef&quot;: &quot;UUID correlation ID &#x2014; quote in support requests&quot;,
&quot;timestamp&quot;: &quot;ISO 8601 when the error occurred&quot;,
&quot;retryAfter&quot;: &quot;Seconds to wait before retrying (429/503 only, null otherwise)&quot;
},
&quot;httpStatusMapping&quot;: {
&quot;400&quot;: &quot;BAD_REQUEST &#x2014; malformed JSON or missing required fields&quot;,
&quot;422&quot;: &quot;VALIDATION_ERROR &#x2014; valid JSON but fails business validation&quot;,
&quot;429&quot;: &quot;RATE_LIMITED &#x2014; too many requests, check retryAfter&quot;,
&quot;500&quot;: &quot;INTERNAL_ERROR &#x2014; server fault, errorRef logged server-side&quot;,
&quot;503&quot;: &quot;SERVICE_UNAVAILABLE &#x2014; downstream dependency or overload&quot;
}
}
},
...
}
</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> &#x2014; 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> &#x2014; 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> &#x2014;
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>
{
&quot;tools&quot;: [
{
&quot;name&quot;: &quot;doLogin&quot;,
&quot;description&quot;: &quot;loginService: doLogin&quot;,
&quot;inputSchema&quot;: {
&quot;type&quot;: &quot;object&quot;,
&quot;properties&quot;: {},
&quot;required&quot;: []
},
&quot;endpoint&quot;: &quot;POST /services/loginService/doLogin&quot;,
&quot;x-axis2-payloadTemplate&quot;: &quot;{\&quot;doLogin\&quot;:[{\&quot;arg0\&quot;:{}}]}&quot;,
&quot;x-requiresAuth&quot;: false,
&quot;annotations&quot;: {
&quot;readOnlyHint&quot;: false,
&quot;destructiveHint&quot;: false,
&quot;idempotentHint&quot;: false,
&quot;openWorldHint&quot;: false
}
},
{
&quot;name&quot;: &quot;portfolioVariance&quot;,
&quot;description&quot;: &quot;Calculate portfolio variance using O(n^2) covariance matrix multiplication&quot;,
&quot;inputSchema&quot;: {
&quot;type&quot;: &quot;object&quot;,
&quot;required&quot;: [&quot;nAssets&quot;, &quot;weights&quot;, &quot;covarianceMatrix&quot;],
&quot;properties&quot;: {
&quot;nAssets&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;minimum&quot;: 2, &quot;maximum&quot;: 2000},
&quot;weights&quot;: {&quot;type&quot;: &quot;array&quot;, &quot;items&quot;: {&quot;type&quot;: &quot;number&quot;}},
&quot;covarianceMatrix&quot;: {&quot;type&quot;: &quot;array&quot;, &quot;items&quot;: {&quot;type&quot;: &quot;array&quot;, &quot;items&quot;: {&quot;type&quot;: &quot;number&quot;}}},
&quot;normalizeWeights&quot;: {&quot;type&quot;: &quot;boolean&quot;, &quot;default&quot;: false},
&quot;nPeriodsPerYear&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;default&quot;: 252}
}
},
&quot;endpoint&quot;: &quot;POST /services/FinancialBenchmarkService/portfolioVariance&quot;,
&quot;x-axis2-payloadTemplate&quot;: &quot;{\&quot;portfolioVariance\&quot;:[{\&quot;arg0\&quot;:{}}]}&quot;,
&quot;x-requiresAuth&quot;: true,
&quot;annotations&quot;: {
&quot;readOnlyHint&quot;: true,
&quot;destructiveHint&quot;: false,
&quot;idempotentHint&quot;: true,
&quot;openWorldHint&quot;: 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 &quot;ServiceName: operationName&quot; &#x2014; 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}
{
&quot;{operationName}&quot;: [
{
&quot;arg0&quot;: {
... 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> &#x2014; outer <code>{}</code></li>
<li><code>nextName()</code> &#x2014; must equal the operation name</li>
<li><code>beginArray()</code> &#x2014; the <code>[...]</code> wrapper</li>
<li><code>beginObject()</code> &#x2014; the parameter object</li>
<li><code>nextName()</code> &#x2192; <code>fromJson()</code> &#x2014; reads each parameter</li>
<li><code>endObject()</code>, <code>endArray()</code>, <code>endObject()</code></li>
</ol>
<p>Any deviation &#x2014; bare JSON object, missing array, wrong operation name &#x2014; results in
<code>Bad Request [errorRef=&lt;uuid&gt;]</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>
{
&quot;doLogin&quot;: [
{
&quot;arg0&quot;: {
&quot;email&quot;: &quot;user@example.com&quot;,
&quot;credentials&quot;: &quot;password&quot;
}
}
]
}
</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 &#x2014; the server dispatches by URL path alone. The payload reduces to:</p>
<pre>
[{ &quot;arg0&quot;: { ... 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 &#x2014; 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 &#x2014; Obtain Token (no auth required)</h3>
<pre>
POST /services/loginService/doLogin
Content-Type: application/json
{
&quot;doLogin&quot;: [
{
&quot;arg0&quot;: {
&quot;email&quot;: &quot;user@example.com&quot;,
&quot;credentials&quot;: &quot;password&quot;
}
}
]
}
Response:
{
&quot;response&quot;: {
&quot;token&quot;: &quot;eyJhbGciOiJIUzI1NiJ9...&quot;,
&quot;user&quot;: { ... }
}
}
</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 &#x2014; Call Protected Services</h3>
<pre>
POST /services/{ServiceName}/{operationName}
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
{
&quot;{operationName}&quot;: [{ &quot;arg0&quot;: { ... } }]
}
</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
{
&quot;response&quot;: {
&quot;status&quot;: &quot;FAILED&quot;,
&quot;error&quot;: &quot;VALIDATION_ERROR&quot;,
&quot;message&quot;: &quot;initialValue must be &gt; 0 (GBM is undefined for non-positive starting values).&quot;,
&quot;errorRef&quot;: &quot;a3f2c1d0-7b4e-4a2f-9c8d-1e6f3b5a2d7c&quot;,
&quot;timestamp&quot;: &quot;2026-05-15T14:30:00Z&quot;,
&quot;retryAfter&quot;: 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 &#x2014; no structural information leakage:</p>
<pre>
HTTP 500
&lt;soapenv:Fault&gt;
&lt;faultcode&gt;soapenv:Server&lt;/faultcode&gt;
&lt;faultstring&gt;Bad Request [errorRef=a3f2c1d0-7b4e-4a2f-9c8d-1e6f3b5a2d7c]&lt;/faultstring&gt;
&lt;/soapenv:Fault&gt;
</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 # &quot;Bad Request [errorRef=a3f2c1d0...]&quot;
error_type: str # &quot;axis2_payload_error&quot;
suggestions: list[str] # [&quot;Check x-axis2-payloadTemplate in catalog&quot;]
</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> &#x2014; set <code>mcpInputSchema</code> parameter on <code>&lt;operation&gt;</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 &#x2014; schemas are hand-authored in services.xml.</td>
</tr>
<tr class="a">
<td>Natural language tool descriptions</td>
<td><strong>Implemented</strong> &#x2014; set <code>mcpDescription</code> parameter on <code>&lt;operation&gt;</code> or <code>&lt;service&gt;</code> in services.xml</td>
<td>Operation-level parameter takes precedence over service-level; falls back to auto-generated &quot;ServiceName: operationName&quot;.</td>
</tr>
<tr class="b">
<td>MCP Resources and Prompts</td>
<td>Not implemented</td>
<td>The catalog exposes only MCP &quot;tools&quot;. The MCP protocol also defines &quot;resources&quot; (URIs for data blobs) and &quot;prompts&quot; (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> &#x2014; set <code>mcpReadOnly</code>, <code>mcpIdempotent</code>, <code>mcpDestructive</code>, <code>mcpOpenWorld</code> on <code>&lt;operation&gt;</code> or <code>&lt;service&gt;</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 &#x2014; 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 &#x2014; 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 &#x2192; assetId)</td>
<td><strong>Implemented</strong> &#x2014; 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> &#x2014; see <a href="json-pagination.html">Pagination Guide</a></td>
<td>Axis2 provides <code>PaginatedResponse&lt;T&gt;</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> &#x2014; 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 &#x2192; 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>{&quot;op&quot;:[{&quot;arg0&quot;:{}}]}</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 &#x2192; Bearer token (loginService)</td>
<td>API key + secret &#x2192; 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 &#x2014; 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 &#x2014; 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 = &quot;https://your-axis2-server/services&quot;
server = Server(&quot;axis2-mcp&quot;)
_token = None
async def login(email: str, password: str) -&gt; str:
async with httpx.AsyncClient() as c:
r = await c.post(
f&quot;{BASE_URL}/loginService/doLogin&quot;,
json={&quot;doLogin&quot;: [{&quot;arg0&quot;: {&quot;email&quot;: email, &quot;credentials&quot;: password}}]}
)
return r.json()[&quot;response&quot;][&quot;token&quot;]
async def call_service(service: str, op: str, params: dict) -&gt; dict:
async with httpx.AsyncClient() as c:
r = await c.post(
f&quot;{BASE_URL}/{service}/{op}&quot;,
json={op: [{&quot;arg0&quot;: params}]},
headers={&quot;Authorization&quot;: f&quot;Bearer {_token}&quot;}
)
return r.json()
@server.list_tools()
async def list_tools() -&gt; list[Tool]:
# Fetch live from catalog &#x2014; honors Cache-Control: no-cache
async with httpx.AsyncClient() as c:
catalog = (await c.get(f&quot;{BASE_URL}/../openapi-mcp.json&quot;)).json()
return [
Tool(name=t[&quot;name&quot;], description=t[&quot;description&quot;],
inputSchema=t[&quot;inputSchema&quot;])
for t in catalog[&quot;tools&quot;]
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -&gt; 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 &#x2014; 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>