blob: 12795f67c16516c375341050e3ef6b43ff4bd43a [file]
<!DOCTYPE html>
<!--
| Generated by Apache Maven Doxia Site Renderer 2.0.0 from src/site/markdown/docs/mcp-architecture.md 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>MCP Support for Apache Axis2/Java – 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>
</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">MCP Support for Apache Axis2/Java</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">
<section><a id="MCP_Support_for_Apache_Axis2.2FJava"></a>
<h1>MCP Support for Apache Axis2/Java</h1>
<p><strong>Summary</strong>: Axis2/Java gains MCP (Model Context Protocol) support in two phases. Phase A
(practical, immediate) wraps an existing Axis2 deployment with a bridge that reads
<code>/openapi-mcp.json</code> and proxies MCP <code>tools/call</code> to Axis2 over HTTPS+mTLS. Phase B (native,
novel Apache contribution) implements <code>axis2-transport-mcp</code> so Axis2 speaks MCP
directly &#x2014; no wrapper. One service deployment, three protocols: JSON-RPC, REST, MCP.</p>
<p>MCP is JSON-RPC 2.0. The three required methods are <code>initialize</code>, <code>tools/list</code>, and
<code>tools/call</code>. Everything else (transport: stdio, tool schema format,
capability negotiation) is specified by the MCP protocol document at
modelcontextprotocol.io.</p><hr /><section><a id="Current_State_.282026-05-16.29"></a>
<h2>Current State (2026-05-16)</h2><section><a id="What_exists_today"></a>
<h3>What exists today</h3>
<table class="table table-striped">
<thead>
<tr class="a">
<th>Artifact</th>
<th>Status</th>
<th>Notes</th></tr></thead><tbody>
<tr class="b">
<td><code>springbootdemo-tomcat11</code></td>
<td>&#x2705; Working</td>
<td>Spring Boot 3.x + Axis2 + Tomcat 11 + Java 25</td></tr>
<tr class="a">
<td><code>axis2-openapi</code> module</td>
<td>&#x2705; Working</td>
<td>Serves <code>/openapi.json</code>, <code>/openapi.yaml</code>, <code>/swagger-ui</code></td></tr>
<tr class="b">
<td><code>/openapi-mcp.json</code> endpoint</td>
<td>&#x2705; Done</td>
<td><code>OpenApiSpecGenerator.generateMcpCatalogJson()</code> + <code>SwaggerUIHandler.handleMcpCatalogRequest()</code></td></tr>
<tr class="a">
<td><code>axis2-mcp-bridge</code> stdio JAR</td>
<td>&#x2705; Done</td>
<td><code>modules/mcp-bridge/</code>, produces <code>*-exe.jar</code> uber-jar</td></tr>
<tr class="b">
<td>mTLS transport</td>
<td>&#x2705; Done</td>
<td>Tomcat 8443, <code>certificateVerification=&quot;required&quot;</code>, IoT CA pattern</td></tr>
<tr class="a">
<td>X.509 Spring Security</td>
<td>&#x2705; Done</td>
<td><code>X509AuthenticationFilter</code> at <code>@Order(2)</code>, CN &#x2192; <code>ROLE_X509_CLIENT</code></td></tr>
<tr class="b">
<td>A3 end-to-end validation</td>
<td>&#x2705; Done</td>
<td><code>Claude Desktop &#x2192; bridge &#x2192; mTLS 8443 &#x2192; BigDataH2Service</code> confirmed</td></tr>
<tr class="a">
<td><code>axis2-spring-boot-starter</code></td>
<td>&#x274c; Not started</td>
<td>Phase 1 of modernization plan</td></tr>
<tr class="b">
<td><code>axis2-transport-mcp</code> native</td>
<td>&#x274c; Not started</td>
<td>Track B &#x2014; novel Apache contribution</td></tr></tbody>
</table>
</section><section><a id="Reference_implementations"></a>
<h3>Reference implementations</h3>
<p>Build, deploy, and test instructions for each container are in the sample READMEs:</p>
<ul>
<li><strong>Tomcat 11</strong>: <code>modules/samples/userguide/src/userguide/springbootdemo-tomcat11/README.md</code></li>
<li><strong>WildFly 32/39</strong>: <code>modules/samples/userguide/src/userguide/springbootdemo-wildfly/README.md</code></li>
</ul>
<pre class="prettyprint"><code class="nohighlight nocode">springbootdemo-tomcat11 base URL: https://localhost:8443/axis2-json-api
- LoginService (auth, port 8080 only)
- BigDataH2Service (streaming/multiplexing demo, accessible via mTLS on 8443)
springbootdemo-wildfly base URL: https://localhost:8443/axis2-json-api
- LoginService (JWT auth)
- FinancialBenchmarkService (portfolioVariance, monteCarlo VaR with Merton jump-diffusion, scenarioAnalysis)
- BigDataH2Service (HTTP/2 streaming)
Deployed and validated on WildFly 32 and WildFly 39
</code></pre>
<p><code>BigDataH2Service</code> request format (confirmed working via MCP bridge):</p>
<pre class="prettyprint"><code class="language-json">{&quot;processBigDataSet&quot;:[{&quot;request&quot;:{&quot;datasetId&quot;:&quot;test-dataset-001&quot;,&quot;datasetSize&quot;:1048576}}]}
</code></pre><hr /></section></section><section><a id="Security_Architecture"></a>
<h2>Security Architecture</h2><section><a id="PKI_.28IoT_CA_Pattern.29"></a>
<h3>PKI (IoT CA Pattern)</h3>
<p>Certificates live in <code>${project.basedir}/certs/</code>. The CA follows
a standard IoT CA pattern &#x2014; RSA 4096 CA with RSA 2048 leaf certs,
appropriate for IoT/embedded where certificate management is manual.</p>
<table class="table table-striped">
<thead>
<tr class="a">
<th>File</th>
<th>Contents</th>
<th>Validity</th></tr></thead><tbody>
<tr class="b">
<td><code>ca.key</code> / <code>ca.crt</code></td>
<td>Root CA, <code>CN=Axis2 CA, O=Apache Axis2, OU=IoT Services</code></td>
<td>10 years</td></tr>
<tr class="a">
<td><code>server.key</code> / <code>server.crt</code></td>
<td>Server cert, <code>CN=localhost</code>, SAN: <code>DNS:localhost, IP:127.0.0.1</code></td>
<td>2 years</td></tr>
<tr class="b">
<td><code>server-keystore.p12</code></td>
<td>Tomcat server keystore (server cert + key + CA chain)</td>
<td>&#x2014;</td></tr>
<tr class="a">
<td><code>ca-truststore.p12</code></td>
<td>Tomcat truststore (CA cert only)</td>
<td>&#x2014;</td></tr>
<tr class="b">
<td><code>client.key</code> / <code>client.crt</code></td>
<td>Client cert, <code>CN=axis2-mcp-bridge</code>, <code>extendedKeyUsage=clientAuth</code></td>
<td>2 years</td></tr>
<tr class="a">
<td><code>client-keystore.p12</code></td>
<td>Bridge client keystore (client cert + key + CA chain)</td>
<td>&#x2014;</td></tr></tbody>
</table>
<p>Keystores are also copied to <code>${CATALINA_HOME}/conf/</code>.</p>
<p>Password for all PKCS12 files: <code>changeit</code></p></section><section><a id="Tomcat_mTLS_Connector_.28port_8443.29"></a>
<h3>Tomcat mTLS Connector (port 8443)</h3>
<p><code>server.xml</code> connector in <code>${CATALINA_HOME}/conf/server.xml</code>:</p>
<pre class="prettyprint"><code class="language-xml">&lt;Connector port=&quot;8443&quot; protocol=&quot;org.apache.coyote.http11.Http11NioProtocol&quot;
maxThreads=&quot;150&quot; SSLEnabled=&quot;true&quot;&gt;
&lt;UpgradeProtocol className=&quot;org.apache.coyote.http2.Http2Protocol&quot; /&gt;
&lt;SSLHostConfig certificateVerification=&quot;required&quot;
truststoreFile=&quot;conf/ca-truststore.p12&quot;
truststorePassword=&quot;changeit&quot;
truststoreType=&quot;PKCS12&quot;
protocols=&quot;TLSv1.2+&quot;&gt;
&lt;Certificate certificateKeystoreFile=&quot;conf/server-keystore.p12&quot;
certificateKeystorePassword=&quot;changeit&quot;
certificateKeystoreType=&quot;PKCS12&quot;
type=&quot;RSA&quot; /&gt;
&lt;/SSLHostConfig&gt;
&lt;/Connector&gt;
</code></pre>
<p>Plain HTTP port 8081 is commented out. All traffic goes through 8443.</p></section><section><a id="Spring_Security_Filter_Chain"></a>
<h3>Spring Security Filter Chain</h3>
<p>The filter chains in <code>Axis2Application.java</code> are ordered:</p>
<table class="table table-striped">
<thead>
<tr class="a">
<th>Order</th>
<th>Chain</th>
<th>Matcher</th>
<th>Auth</th></tr></thead><tbody>
<tr class="b">
<td>1</td>
<td><code>springSecurityFilterChain</code> (default)</td>
<td>Everything</td>
<td>JWT</td></tr>
<tr class="a">
<td>2</td>
<td><code>springSecurityFilterChainMtls</code></td>
<td>Port 8443 (<code>MtlsRequestMatcher</code>)</td>
<td>X.509 cert</td></tr>
<tr class="b">
<td>3</td>
<td><code>springSecurityFilterChainOpenApi</code></td>
<td><code>/openapi.json</code>, <code>/openapi.yaml</code>, <code>/swagger-ui</code>, <code>/openapi-mcp.json</code></td>
<td>None</td></tr>
<tr class="a">
<td>4</td>
<td><code>springSecurityFilterChainLogin</code></td>
<td><code>/services/LoginService/**</code></td>
<td>None</td></tr></tbody>
</table>
<p>The <code>@Order(2)</code> mTLS chain intercepts all 8443 requests before the JWT chain.
<code>X509AuthenticationFilter</code> reads <code>jakarta.servlet.request.X509Certificate</code> (set by
Tomcat after the TLS handshake), extracts the CN, and creates an
<code>UsernamePasswordAuthenticationToken</code> with <code>ROLE_X509_CLIENT</code>. The existing
<code>GenericAccessDecisionManager.decide()</code> is a no-op, so any authenticated principal
passes <code>FilterSecurityInterceptor</code>.</p></section><section><a id="X.509_Authentication_Flow"></a>
<h3>X.509 Authentication Flow</h3>
<pre class="prettyprint"><code class="nohighlight nocode">Client presents cert &#x2192; Tomcat TLS handshake (certificateVerification=required)
&#x2192; Only CA-signed certs pass
&#x2192; Tomcat writes cert chain to jakarta.servlet.request.X509Certificate attribute
&#x2192; X509AuthenticationFilter.doFilter()
&#x2192; Extract CN (e.g., &quot;axis2-mcp-bridge&quot;)
&#x2192; SecurityContextHolder.getContext().setAuthentication(token)
&#x2192; FilterSecurityInterceptor: authenticated &#x2192; passes
&#x2192; Service handler executes
</code></pre><hr /></section></section><section><a id="Track_A_.E2.80.94_OpenAPI-Driven_MCP_Bridge"></a>
<h2>Track A &#x2014; OpenAPI-Driven MCP Bridge</h2><section><a id="A1_.E2.80.94_.2Fopenapi-mcp.json_endpoint_.E2.9C.85_Done"></a>
<h3>A1 &#x2014; <code>/openapi-mcp.json</code> endpoint &#x2705; Done</h3>
<p><strong>Implementation</strong>: <code>OpenApiSpecGenerator.generateMcpCatalogJson(HttpServletRequest)</code> iterates
<code>AxisConfiguration.getServices()</code> using the same <code>isSystemService()</code> / <code>shouldIncludeService()</code> /
<code>shouldIncludeOperation()</code> filters as the existing OpenAPI path generation. Output:</p>
<pre class="prettyprint"><code class="language-json">{
&quot;tools&quot;: [
{
&quot;name&quot;: &quot;portfolioVariance&quot;,
&quot;description&quot;: &quot;Calculate portfolio variance using O(n&#xb2;) covariance matrix...&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;name&quot;: &quot;monteCarlo&quot;,
&quot;description&quot;: &quot;Monte Carlo VaR simulation using Geometric Brownian Motion or Merton jump-diffusion...&quot;,
&quot;inputSchema&quot;: {
&quot;type&quot;: &quot;object&quot;,
&quot;required&quot;: [],
&quot;properties&quot;: {
&quot;nSimulations&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;default&quot;: 10000, &quot;maximum&quot;: 1000000},
&quot;nPeriods&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;default&quot;: 252},
&quot;initialValue&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;default&quot;: 1000000},
&quot;expectedReturn&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;default&quot;: 0.08},
&quot;volatility&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;default&quot;: 0.20},
&quot;model&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;default&quot;: &quot;gbm&quot;, &quot;enum&quot;: [&quot;gbm&quot;, &quot;merton&quot;]},
&quot;jumpIntensity&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;default&quot;: 1.0},
&quot;jumpMean&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;default&quot;: -0.03},
&quot;jumpVol&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;default&quot;: 0.05},
&quot;nPeriodsPerYear&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;default&quot;: 252},
&quot;randomSeed&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;default&quot;: 0},
&quot;percentiles&quot;: {&quot;type&quot;: &quot;array&quot;, &quot;default&quot;: [0.01, 0.05]}
}
},
&quot;endpoint&quot;: &quot;POST /services/FinancialBenchmarkService/monteCarlo&quot;
}
]
}
</code></pre>
<p>Tool schemas are populated via <code>mcpInputSchema</code> parameters in
<code>services.xml</code> &#x2014; parsed by <code>generateMcpCatalogJson()</code> at runtime.</p>
<p><strong>Routing</strong>: <code>OpenApiServlet.java</code> dispatches <code>uri.endsWith(&quot;/openapi-mcp.json&quot;)</code> to
<code>handler.handleMcpCatalogRequest()</code>. <code>Axis2WebAppInitializer.java</code> maps the path.
<code>Axis2Application.java</code> <code>OPENAPI_PATHS</code> array includes <code>/openapi-mcp.json</code> so the
OpenAPI filter chain (<code>@Order(3)</code>) handles it without auth.</p></section><section><a id="A2_.E2.80.94_axis2-mcp-bridge_stdio_JAR_.E2.9C.85_Done"></a>
<h3>A2 &#x2014; <code>axis2-mcp-bridge</code> stdio JAR &#x2705; Done</h3>
<p><strong>Location</strong>: <code>modules/mcp-bridge/</code></p>
<p><strong>Key decision</strong>: No MCP Java SDK (Apache 2.0 license constraint &#x2014; SDK license
uncertain at implementation time). JSON-RPC 2.0 is implemented directly using
Jackson 2.21.1 (Apache 2.0) + Java stdlib <code>HttpClient</code>. The three-method
handshake is straightforward enough to hand-roll correctly.</p>
<p><strong>Classes</strong>:</p>
<ul>
<li><code>McpBridgeMain</code> &#x2014; entry point, parses <code>--base-url</code>, <code>--keystore</code>, <code>--truststore</code> args, builds <code>SSLContext</code>, starts registry + server</li>
<li><code>ToolRegistry</code> &#x2014; GETs <code>{baseUrl}/openapi-mcp.json</code> at startup, builds <code>List&lt;McpTool&gt;</code> and <code>Map&lt;String,McpTool&gt;</code></li>
<li><code>McpStdioServer</code> &#x2014; blocking stdin read loop, JSON-RPC 2.0 dispatch</li>
<li><code>McpTool</code> &#x2014; data class: name, description, inputSchema (JsonNode), endpoint, path</li>
</ul>
<p><strong>Build</strong>: maven-shade-plugin 3.6.0 produces <code>axis2-mcp-bridge-2.0.1-SNAPSHOT-exe.jar</code>
(classifier: <code>exe</code>) with <code>MainClass=McpBridgeMain</code>.</p>
<p><strong>Axis2 JSON envelope translation</strong>: The bridge translates between standard MCP
JSON-RPC 2.0 and Axis2's internal JSON convention. MCP sends clean named parameters
(<code>{&quot;nAssets&quot;:5, &quot;weights&quot;:[...]}</code>); the bridge wraps them into the envelope that
Axis2's <code>JsonRpcMessageReceiver</code> expects: <code>{&quot;operationName&quot;:[{arguments}]}</code>. The
array wrapper and operation-name-as-key pattern are artifacts of Axis2's SOAP/XML
heritage &#x2014; the JSON formatter maps the request body to an Axiom <code>OMElement</code> tree
where the operation name is the root element and each array entry corresponds to
a Java method parameter. When the service method takes a single POJO argument,
callers see <code>{&quot;operationName&quot;:[{&quot;arg0&quot;:{...}}]}</code> where <code>arg0</code> is the default WSDL
parameter name. The bridge hides this from AI clients so they see only standard
JSON-RPC 2.0.</p>
<p><strong>Notifications</strong>: MCP <code>notifications/initialized</code> (no <code>id</code> field) is silently consumed
with no response, as required by JSON-RPC 2.0.</p>
<p><strong>Protocol version</strong>: <code>&quot;2024-11-05&quot;</code></p>
<p><strong>Claude Desktop config</strong> (<code>~/.config/claude/claude_desktop_config.json</code>):</p>
<pre class="prettyprint"><code class="language-json">{
&quot;mcpServers&quot;: {
&quot;axis2-demo&quot;: {
&quot;command&quot;: &quot;java&quot;,
&quot;args&quot;: [&quot;-jar&quot;, &quot;/path/to/axis2-mcp-bridge-2.0.1-SNAPSHOT-exe.jar&quot;,
&quot;--base-url&quot;, &quot;https://localhost:8443/axis2-json-api&quot;,
&quot;--keystore&quot;, &quot;${project.basedir}/certs/client-keystore.p12&quot;,
&quot;--truststore&quot;, &quot;${project.basedir}/certs/ca-truststore.p12&quot;]
}
}
}
</code></pre></section><section><a id="A3_.E2.80.94_End-to-end_validation_.E2.9C.85_Done"></a>
<h3>A3 &#x2014; End-to-end validation &#x2705; Done</h3>
<p>Full chain confirmed working:</p>
<pre class="prettyprint"><code class="nohighlight nocode">Claude Desktop &#x2192; axis2-mcp-bridge stdio &#x2192; HTTPS+mTLS port 8443
&#x2192; Tomcat TLS handshake (client cert CN=axis2-mcp-bridge)
&#x2192; X509AuthenticationFilter (authenticated, ROLE_X509_CLIENT)
&#x2192; BigDataH2Service.processBigDataSet()
&#x2192; real response returned to Claude
</code></pre>
<p>Tomcat log confirmation:</p>
<pre class="prettyprint"><code class="nohighlight nocode">X509AuthenticationFilter: authenticated CN=axis2-mcp-bridge on port 8443
</code></pre><hr /></section></section><section><a id="Track_B_.E2.80.94_Native_MCP_Transport_.28axis2-transport-mcp.29"></a>
<h2>Track B &#x2014; Native MCP Transport (<code>axis2-transport-mcp</code>)</h2>
<p><strong>When</strong>: After Track A is demonstrated. This is the Apache contribution &#x2014; no other
Java framework has native MCP transport.</p>
<p><strong>Module location</strong>: <code>modules/transport-mcp/</code></p>
<p><strong>Interface</strong>: Axis2's <code>TransportListener</code> + <code>TransportSender</code>.</p><section><a id="Protocol_translation"></a>
<h3>Protocol translation</h3>
<pre class="prettyprint"><code class="nohighlight nocode">MCP tools/call (JSON-RPC 2.0)
&#x2193;
axis2-transport-mcp
&#x2193;
Axis2 MessageContext (service name + operation name + payload)
&#x2193;
Service implementation (same Java class as JSON-RPC and REST callers)
&#x2193;
Axis2 MessageContext (response payload)
&#x2193;
axis2-transport-mcp
&#x2193;
MCP tools/call result (JSON-RPC 2.0)
</code></pre></section><section><a id="Tool_schema_generation"></a>
<h3>Tool schema generation</h3>
<p>Populated from <code>axis2-openapi</code> Phase 2 output. <code>initialize</code> response includes
<code>capabilities.tools</code> derived from deployed services and their <code>@McpTool</code> annotations.</p></section><section><a id="Starter_integration"></a>
<h3>Starter integration</h3>
<pre class="prettyprint"><code class="language-properties">axis2.transport.mcp.enabled=true
axis2.transport.mcp.transport=stdio
</code></pre></section><section><a id="End_state"></a>
<h3>End state</h3>
<pre class="prettyprint"><code class="nohighlight nocode">Claude Desktop / AI agent &#x2192; MCP (axis2-transport-mcp, native)
&#x2193;
REST clients &#x2192; REST (planned, Phase 3) &#x2192; Axis2 Service
&#x2191; (one Java class)
Existing JSON-RPC callers &#x2192; JSON-RPC (unchanged)
</code></pre><hr /></section></section><section><a id="Key_Design_Decisions"></a>
<h2>Key Design Decisions</h2>
<p><strong>Why stdio transport</strong>: Simplest MCP transport, zero port conflicts,
works immediately with Claude Desktop and Cursor. No market demand yet for
HTTP/SSE transport &#x2014; stdio covers all current use cases.</p>
<p><strong>Why OpenAPI as the bridge, not direct Axis2 introspection</strong>: <code>/openapi-mcp.json</code>
decouples the bridge from Axis2 internals. The bridge works against any HTTP service
that serves this format &#x2014; not just Axis2. This is useful for the Apache community
beyond the Axis2 user base.</p>
<p><strong>Why no MCP Java SDK</strong>: Apache 2.0 license constraint. Jackson (Apache 2.0) + Java
stdlib <code>HttpClient</code> implement the three-method JSON-RPC 2.0 protocol without external
dependencies whose license compatibility is uncertain. The protocol is well-specified
enough to hand-roll correctly.</p>
<p><strong>Why IoT CA pattern</strong>: RSA 4096 CA (10 years) + RSA 2048 leaf certs (2 years) matches
a standard IoT CA pattern. Appropriate for environments where certificate
management is manual and infrequent. The CA is only on one machine &#x2014; this is a
development/demo CA, not a production CA.</p>
<p><strong>Why <code>certificateVerification=&quot;required&quot;</code> at Tomcat, not Spring Security</strong>: Tomcat
enforces the TLS handshake before any HTTP processing. Invalid client certs are rejected
at the TCP layer &#x2014; Spring Security never sees them. <code>X509AuthenticationFilter</code> only
needs to extract identity from an already-verified cert, not verify it.</p><hr /></section><section><a id="Next_Steps"></a>
<h2>Next Steps</h2><section><a id="Track_A_remaining"></a>
<h3>Track A remaining</h3>
<table class="table table-striped">
<thead>
<tr class="a">
<th>Step</th>
<th>Work</th>
<th>Notes</th></tr></thead><tbody>
<tr class="b">
<td><code>mcpInputSchema</code> in services.xml</td>
<td>&#x2705; Done</td>
<td>All financial benchmark tools + login have full parameter schemas</td></tr></tbody>
</table>
</section><section><a id="Track_B"></a>
<h3>Track B</h3>
<ol style="list-style-type: decimal;">
<li><code>modules/transport-mcp/</code> &#x2014; new module scaffolding</li>
<li>stdio transport (B1) &#x2014; validates JSON-RPC 2.0 &#x2194; MessageContext translation</li>
</ol></section><section><a id="Testing_matrix"></a>
<h3>Testing matrix</h3>
<p>MCP and OpenAPI support needs validation across the full container/JDK matrix:</p>
<table class="table table-striped">
<thead>
<tr class="a">
<th>Container</th>
<th>JDK</th>
<th>MCP</th>
<th>OpenAPI</th>
<th>Status</th></tr></thead><tbody>
<tr class="b">
<td>WildFly 32</td>
<td>OpenJDK 21</td>
<td>&#x2705;</td>
<td>&#x2705;</td>
<td>Validated</td></tr>
<tr class="a">
<td>WildFly 39</td>
<td>OpenJDK 25</td>
<td>&#x2705;</td>
<td>&#x2705;</td>
<td>Validated</td></tr>
<tr class="b">
<td>Tomcat 11</td>
<td>OpenJDK 21</td>
<td>&#x2705;</td>
<td>&#x2705;</td>
<td>Validated</td></tr>
<tr class="a">
<td>Tomcat 11</td>
<td>OpenJDK 25</td>
<td>&#x2705;</td>
<td>&#x2705;</td>
<td>Validated</td></tr></tbody>
</table>
<hr /></section></section><section><a id="Known_Limitations"></a>
<h2>Known Limitations</h2><section><a id="No_progress_notifications_during_long-running_operations"></a>
<h3>No progress notifications during long-running operations</h3>
<p>The MCP spec supports progress notifications &#x2014; JSON-RPC messages sent from the
server to the client while a tool call is executing. This is useful for
operations like Monte Carlo simulations (100K+ paths can take 1-14 seconds)
where the AI assistant could display incremental status.</p>
<p><strong>The limitation is architectural, not transport-related.</strong> The MCP stdio
transport supports progress notifications natively (they are regular JSON-RPC
notifications on stdout). The constraint is the bridge's HTTP proxy pattern:</p>
<pre class="prettyprint"><code class="nohighlight nocode">Claude Desktop &#x2190;stdio&#x2192; axis2-mcp-bridge &#x2190;blocking HTTP POST&#x2192; Axis2 service
</code></pre>
<p>The bridge sends one HTTP POST to Axis2 and blocks until the full response
arrives. During a long computation, the bridge has no way to obtain intermediate
status from the service. Adding progress support would require one of:</p>
<ul>
<li>A polling side-channel (bridge polls a status endpoint while the main call runs)</li>
<li>HTTP chunked/streaming responses from Axis2</li>
<li>A callback mechanism from the service to the bridge</li>
</ul>
<p>These are non-trivial changes to the Axis2 response pipeline and the bridge
architecture.</p>
<p><strong>Practical impact:</strong> The financial benchmark services complete well within
interactive time budgets &#x2014; portfolio variance in under 1 ms, Monte Carlo
100K paths in ~1.4 seconds on Java. For workloads where even this latency
is a concern, the same financial benchmark operations are available on
<a href="https://axis.apache.org/axis2/c/core/" class="externalLink">Axis2/C</a>, which runs 2-3x faster:
Monte Carlo 100K paths in ~0.7 seconds, 500-asset portfolio variance in
232 &#x3bc;s vs Java's 660 &#x3bc;s (see <a href="mcp-examples.html#full-performance-summary">performance comparison</a>).
Both implementations expose identical MCP tool schemas &#x2014; an AI assistant
configured with either backend gets the same financial capabilities.</p></section><section><a id="Auto-generated_inputSchema_from_Java_types"></a>
<h3>Auto-generated inputSchema from Java types</h3>
<p>When <code>mcpInputSchema</code> is not set in <code>services.xml</code>, the MCP catalog
generator auto-generates a JSON Schema by introspecting the Java service
method's parameter type. Two resolution strategies are used:</p>
<ol style="list-style-type: decimal;">
<li><strong><code>ServiceClass</code> parameter</strong> &#x2014; the class is loaded directly from the
classpath. Works immediately on the first catalog request.</li>
<li><strong><code>SpringBeanName</code> parameter</strong> &#x2014; the bean is resolved from the Spring
<code>WebApplicationContext</code> via reflection (no compile-time Spring dependency
in the OpenAPI module). Works after Spring initialization is complete.</li>
</ol>
<p>Supported types: <code>int</code>/<code>long</code> &#x2192; <code>integer</code>, <code>double</code>/<code>float</code> &#x2192; <code>number</code>,
<code>boolean</code> &#x2192; <code>boolean</code>, <code>String</code> &#x2192; <code>string</code>, arrays (including nested
<code>double[][]</code>), <code>List&lt;T&gt;</code>, and POJOs &#x2192; <code>object</code>.</p>
<p>Explicit <code>mcpInputSchema</code> in <code>services.xml</code> always takes precedence &#x2014;
use it when you need <code>required</code> fields, <code>minimum</code>/<code>maximum</code> constraints,
<code>default</code> values, or <code>description</code> text that reflection cannot provide.</p><hr /></section></section><section><a id="Dependencies_and_Build"></a>
<h2>Dependencies and Build</h2>
<p>Track A (<code>axis2-mcp-bridge</code>) requires:</p>
<ul>
<li><code>axis2-openapi</code> module (for <code>/openapi-mcp.json</code>)</li>
<li><code>com.fasterxml.jackson.core:jackson-databind:2.21.1</code> (Apache 2.0)</li>
<li>Java 21+ (HttpClient is standard library)</li>
<li>No Axis2 core dependency &#x2014; bridge is a separate process</li>
</ul>
<p>Track B (<code>axis2-transport-mcp</code>) requires:</p>
<ul>
<li><code>axis2-core</code> / <code>axis2-kernel</code> (TransportListener interface)</li>
<li><code>axis2-openapi</code> (tool schema generation)</li>
<li>No MCP SDK &#x2014; same Jackson-only approach as A2</li>
</ul></section></section> </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>