| <!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'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 — 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>✅ 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>✅ 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>✅ 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>✅ 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>✅ Done</td> |
| <td>Tomcat 8443, <code>certificateVerification="required"</code>, IoT CA pattern</td></tr> |
| <tr class="a"> |
| <td>X.509 Spring Security</td> |
| <td>✅ Done</td> |
| <td><code>X509AuthenticationFilter</code> at <code>@Order(2)</code>, CN → <code>ROLE_X509_CLIENT</code></td></tr> |
| <tr class="b"> |
| <td>A3 end-to-end validation</td> |
| <td>✅ Done</td> |
| <td><code>Claude Desktop → bridge → mTLS 8443 → BigDataH2Service</code> confirmed</td></tr> |
| <tr class="a"> |
| <td><code>axis2-spring-boot-starter</code></td> |
| <td>❌ Not started</td> |
| <td>Phase 1 of modernization plan</td></tr> |
| <tr class="b"> |
| <td><code>axis2-transport-mcp</code> native</td> |
| <td>❌ Not started</td> |
| <td>Track B — 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">{"processBigDataSet":[{"request":{"datasetId":"test-dataset-001","datasetSize":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 — 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>—</td></tr> |
| <tr class="a"> |
| <td><code>ca-truststore.p12</code></td> |
| <td>Tomcat truststore (CA cert only)</td> |
| <td>—</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>—</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"><Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" |
| maxThreads="150" SSLEnabled="true"> |
| <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> |
| <SSLHostConfig certificateVerification="required" |
| truststoreFile="conf/ca-truststore.p12" |
| truststorePassword="changeit" |
| truststoreType="PKCS12" |
| protocols="TLSv1.2+"> |
| <Certificate certificateKeystoreFile="conf/server-keystore.p12" |
| certificateKeystorePassword="changeit" |
| certificateKeystoreType="PKCS12" |
| type="RSA" /> |
| </SSLHostConfig> |
| </Connector> |
| </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 → Tomcat TLS handshake (certificateVerification=required) |
| → Only CA-signed certs pass |
| → Tomcat writes cert chain to jakarta.servlet.request.X509Certificate attribute |
| → X509AuthenticationFilter.doFilter() |
| → Extract CN (e.g., "axis2-mcp-bridge") |
| → SecurityContextHolder.getContext().setAuthentication(token) |
| → FilterSecurityInterceptor: authenticated → passes |
| → Service handler executes |
| </code></pre><hr /></section></section><section><a id="Track_A_.E2.80.94_OpenAPI-Driven_MCP_Bridge"></a> |
| <h2>Track A — OpenAPI-Driven MCP Bridge</h2><section><a id="A1_.E2.80.94_.2Fopenapi-mcp.json_endpoint_.E2.9C.85_Done"></a> |
| <h3>A1 — <code>/openapi-mcp.json</code> endpoint ✅ 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">{ |
| "tools": [ |
| { |
| "name": "portfolioVariance", |
| "description": "Calculate portfolio variance using O(n²) covariance matrix...", |
| "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" |
| }, |
| { |
| "name": "monteCarlo", |
| "description": "Monte Carlo VaR simulation using Geometric Brownian Motion or Merton jump-diffusion...", |
| "inputSchema": { |
| "type": "object", |
| "required": [], |
| "properties": { |
| "nSimulations": {"type": "integer", "default": 10000, "maximum": 1000000}, |
| "nPeriods": {"type": "integer", "default": 252}, |
| "initialValue": {"type": "number", "default": 1000000}, |
| "expectedReturn": {"type": "number", "default": 0.08}, |
| "volatility": {"type": "number", "default": 0.20}, |
| "model": {"type": "string", "default": "gbm", "enum": ["gbm", "merton"]}, |
| "jumpIntensity": {"type": "number", "default": 1.0}, |
| "jumpMean": {"type": "number", "default": -0.03}, |
| "jumpVol": {"type": "number", "default": 0.05}, |
| "nPeriodsPerYear": {"type": "integer", "default": 252}, |
| "randomSeed": {"type": "integer", "default": 0}, |
| "percentiles": {"type": "array", "default": [0.01, 0.05]} |
| } |
| }, |
| "endpoint": "POST /services/FinancialBenchmarkService/monteCarlo" |
| } |
| ] |
| } |
| </code></pre> |
| <p>Tool schemas are populated via <code>mcpInputSchema</code> parameters in |
| <code>services.xml</code> — parsed by <code>generateMcpCatalogJson()</code> at runtime.</p> |
| <p><strong>Routing</strong>: <code>OpenApiServlet.java</code> dispatches <code>uri.endsWith("/openapi-mcp.json")</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 — <code>axis2-mcp-bridge</code> stdio JAR ✅ 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 — 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> — 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> — GETs <code>{baseUrl}/openapi-mcp.json</code> at startup, builds <code>List<McpTool></code> and <code>Map<String,McpTool></code></li> |
| <li><code>McpStdioServer</code> — blocking stdin read loop, JSON-RPC 2.0 dispatch</li> |
| <li><code>McpTool</code> — 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>{"nAssets":5, "weights":[...]}</code>); the bridge wraps them into the envelope that |
| Axis2's <code>JsonRpcMessageReceiver</code> expects: <code>{"operationName":[{arguments}]}</code>. The |
| array wrapper and operation-name-as-key pattern are artifacts of Axis2's SOAP/XML |
| heritage — 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>{"operationName":[{"arg0":{...}}]}</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>"2024-11-05"</code></p> |
| <p><strong>Claude Desktop config</strong> (<code>~/.config/claude/claude_desktop_config.json</code>):</p> |
| |
| <pre class="prettyprint"><code class="language-json">{ |
| "mcpServers": { |
| "axis2-demo": { |
| "command": "java", |
| "args": ["-jar", "/path/to/axis2-mcp-bridge-2.0.1-SNAPSHOT-exe.jar", |
| "--base-url", "https://localhost:8443/axis2-json-api", |
| "--keystore", "${project.basedir}/certs/client-keystore.p12", |
| "--truststore", "${project.basedir}/certs/ca-truststore.p12"] |
| } |
| } |
| } |
| </code></pre></section><section><a id="A3_.E2.80.94_End-to-end_validation_.E2.9C.85_Done"></a> |
| <h3>A3 — End-to-end validation ✅ Done</h3> |
| <p>Full chain confirmed working:</p> |
| |
| <pre class="prettyprint"><code class="nohighlight nocode">Claude Desktop → axis2-mcp-bridge stdio → HTTPS+mTLS port 8443 |
| → Tomcat TLS handshake (client cert CN=axis2-mcp-bridge) |
| → X509AuthenticationFilter (authenticated, ROLE_X509_CLIENT) |
| → BigDataH2Service.processBigDataSet() |
| → 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 — Native MCP Transport (<code>axis2-transport-mcp</code>)</h2> |
| <p><strong>When</strong>: After Track A is demonstrated. This is the Apache contribution — 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) |
| ↓ |
| axis2-transport-mcp |
| ↓ |
| Axis2 MessageContext (service name + operation name + payload) |
| ↓ |
| Service implementation (same Java class as JSON-RPC and REST callers) |
| ↓ |
| Axis2 MessageContext (response payload) |
| ↓ |
| axis2-transport-mcp |
| ↓ |
| 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 → MCP (axis2-transport-mcp, native) |
| ↓ |
| REST clients → REST (planned, Phase 3) → Axis2 Service |
| ↑ (one Java class) |
| Existing JSON-RPC callers → 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 — 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 — 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 — this is a |
| development/demo CA, not a production CA.</p> |
| <p><strong>Why <code>certificateVerification="required"</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 — 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>✅ 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> — new module scaffolding</li> |
| <li>stdio transport (B1) — validates JSON-RPC 2.0 ↔ 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>✅</td> |
| <td>✅</td> |
| <td>Validated</td></tr> |
| <tr class="a"> |
| <td>WildFly 39</td> |
| <td>OpenJDK 25</td> |
| <td>✅</td> |
| <td>✅</td> |
| <td>Validated</td></tr> |
| <tr class="b"> |
| <td>Tomcat 11</td> |
| <td>OpenJDK 21</td> |
| <td>✅</td> |
| <td>✅</td> |
| <td>Validated</td></tr> |
| <tr class="a"> |
| <td>Tomcat 11</td> |
| <td>OpenJDK 25</td> |
| <td>✅</td> |
| <td>✅</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 — 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 ←stdio→ axis2-mcp-bridge ←blocking HTTP POST→ 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 — 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 μs vs Java's 660 μs (see <a href="mcp-examples.html#full-performance-summary">performance comparison</a>). |
| Both implementations expose identical MCP tool schemas — 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> — the class is loaded directly from the |
| classpath. Works immediately on the first catalog request.</li> |
| <li><strong><code>SpringBeanName</code> parameter</strong> — 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> → <code>integer</code>, <code>double</code>/<code>float</code> → <code>number</code>, |
| <code>boolean</code> → <code>boolean</code>, <code>String</code> → <code>string</code>, arrays (including nested |
| <code>double[][]</code>), <code>List<T></code>, and POJOs → <code>object</code>.</p> |
| <p>Explicit <code>mcpInputSchema</code> in <code>services.xml</code> always takes precedence — |
| 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 — 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 — 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> |