| type | architecture |
|---|---|
| created | 2026-04-06 |
| last-verified | 2026-04-06 |
| status | Active — A1/A2/A3 complete, mTLS validated |
BLUF: 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
/openapi-mcp.json and proxies MCP tools/call to Axis2 over HTTPS+mTLS. Phase B (native,
novel Apache contribution) implements axis2-transport-mcp so Axis2 speaks MCP
directly — no wrapper. One service deployment, three protocols: JSON-RPC, REST, MCP.
MCP is JSON-RPC 2.0. The three required methods are initialize, tools/list, and
tools/call. Everything else (transport: stdio or HTTP/SSE, tool schema format,
capability negotiation) is specified by the MCP protocol document at
modelcontextprotocol.io.
| Artifact | Status | Notes |
|---|---|---|
springbootdemo-tomcat11 |
✅ Working | Spring Boot 3.x + Axis2 + Tomcat 11 + Java 25 |
axis2-openapi module |
✅ Working | Serves /openapi.json, /openapi.yaml, /swagger-ui |
/openapi-mcp.json endpoint |
✅ Done | OpenApiSpecGenerator.generateMcpCatalogJson() + SwaggerUIHandler.handleMcpCatalogRequest() |
axis2-mcp-bridge stdio JAR |
✅ Done | modules/mcp-bridge/, produces *-exe.jar uber-jar |
| mTLS transport | ✅ Done | Tomcat 8443, certificateVerification="required", IoT CA pattern |
| X.509 Spring Security | ✅ Done | X509AuthenticationFilter at @Order(2), CN → ROLE_X509_CLIENT |
| A3 end-to-end validation | ✅ Done | Claude Desktop → bridge → mTLS 8443 → BigDataH2Service confirmed |
axis2-spring-boot-starter |
❌ Not started | Phase 1 of modernization plan |
| A4 HTTP/SSE transport | ❌ Not started | Post-demo, deferred |
axis2-transport-mcp native |
❌ Not started | Track B — novel Apache contribution |
springbootdemo-tomcat11 base URL: https://localhost:8443/axis2-json-api
Services deployed:
- LoginService (auth, port 8080 only)
- BigDataH2Service (streaming/multiplexing demo, accessible via mTLS on 8443)
BigDataH2Service request format (confirmed working via MCP bridge):
{"processBigDataSet":[{"request":{"datasetId":"test-dataset-001","datasetSize":1048576}}]}Certificates live in /home/robert/repos/axis-axis2-java-core/certs/. The CA follows
the same pattern as the Kanaha camera project — RSA 4096 CA with RSA 2048 leaf certs,
appropriate for IoT/embedded where certificate management is manual.
| File | Contents | Validity |
|---|---|---|
ca.key / ca.crt |
Root CA, CN=Axis2 CA, O=Apache Axis2, OU=IoT Services |
10 years |
server.key / server.crt |
Server cert, CN=localhost, SAN: DNS:localhost, IP:127.0.0.1 |
2 years |
server-keystore.p12 |
Tomcat server keystore (server cert + key + CA chain) | — |
ca-truststore.p12 |
Tomcat truststore (CA cert only) | — |
client.key / client.crt |
Client cert, CN=axis2-mcp-bridge, extendedKeyUsage=clientAuth |
2 years |
client-keystore.p12 |
Bridge client keystore (client cert + key + CA chain) | — |
Keystores are also copied to /home/robert/apache-tomcat-11.0.20/conf/.
Password for all PKCS12 files: changeit
server.xml connector in /home/robert/apache-tomcat-11.0.20/conf/server.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>Plain HTTP port 8081 is commented out. All traffic goes through 8443.
The filter chains in Axis2Application.java are ordered:
| Order | Chain | Matcher | Auth |
|---|---|---|---|
| 1 | springSecurityFilterChain (default) |
Everything | JWT |
| 2 | springSecurityFilterChainMtls |
Port 8443 (MtlsRequestMatcher) |
X.509 cert |
| 3 | springSecurityFilterChainOpenApi |
/openapi.json, /openapi.yaml, /swagger-ui, /openapi-mcp.json |
None |
| 4 | springSecurityFilterChainLogin |
/services/LoginService/** |
None |
The @Order(2) mTLS chain intercepts all 8443 requests before the JWT chain.
X509AuthenticationFilter reads jakarta.servlet.request.X509Certificate (set by
Tomcat after the TLS handshake), extracts the CN, and creates an
UsernamePasswordAuthenticationToken with ROLE_X509_CLIENT. The existing
GenericAccessDecisionManager.decide() is a no-op, so any authenticated principal
passes FilterSecurityInterceptor.
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
Implementation: OpenApiSpecGenerator.generateMcpCatalogJson(HttpServletRequest) iterates
AxisConfiguration.getServices() using the same isSystemService() / shouldIncludeService() /
shouldIncludeOperation() filters as the existing OpenAPI path generation. Output:
{
"tools": [
{
"name": "processBigDataSet",
"description": "BigDataH2Service: processBigDataSet",
"inputSchema": { "type": "object", "properties": {}, "required": [] },
"endpoint": "POST /services/BigDataH2Service/processBigDataSet"
}
]
}Routing: OpenApiServlet.java dispatches uri.endsWith("/openapi-mcp.json") to
handler.handleMcpCatalogRequest(). Axis2WebAppInitializer.java maps the path.
Axis2Application.java OPENAPI_PATHS array includes /openapi-mcp.json so the
OpenAPI filter chain (@Order(3)) handles it without auth.
Location: modules/mcp-bridge/
Key decision: 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 HttpClient. The three-method
handshake is straightforward enough to hand-roll correctly.
Classes:
McpBridgeMain— entry point, parses--base-url,--keystore,--truststoreargs, buildsSSLContext, starts registry + serverToolRegistry— GETs{baseUrl}/openapi-mcp.jsonat startup, buildsList<McpTool>andMap<String,McpTool>McpStdioServer— blocking stdin read loop, JSON-RPC 2.0 dispatchMcpTool— data class: name, description, inputSchema (JsonNode), endpoint, path
Build: maven-shade-plugin 3.6.0 produces axis2-mcp-bridge-2.0.1-SNAPSHOT-exe.jar
(classifier: exe) with MainClass=McpBridgeMain.
Axis2 JSON-RPC envelope: tools/call wraps arguments as {toolName: [arguments]}
before POSTing to the Axis2 endpoint, matching the existing JSON-RPC convention.
Notifications: MCP notifications/initialized (no id field) is silently consumed
with no response, as required by JSON-RPC 2.0.
Protocol version: "2024-11-05"
Claude Desktop config (~/.config/claude/claude_desktop_config.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", "/home/robert/repos/axis-axis2-java-core/certs/client-keystore.p12",
"--truststore", "/home/robert/repos/axis-axis2-java-core/certs/ca-truststore.p12"]
}
}
}Full chain confirmed working:
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
Tomcat log confirmation:
X509AuthenticationFilter: authenticated CN=axis2-mcp-bridge on port 8443
Adds persistent server mode (multiple Claude sessions sharing one bridge). Required for production. Additive — no changes to Axis2 side or tool catalog format.
POST /mcp → JSON-RPC request
GET /mcp/sse → SSE stream for server-initiated messages
When: After Track A is demonstrated. This is the Apache contribution — no other Java framework has native MCP transport.
Module location: modules/transport-mcp/
Interface: Axis2's TransportListener + TransportSender.
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)
- stdio first — simpler, no connection management, validates the JSON-RPC 2.0 ↔ MessageContext translation layer end-to-end
- HTTP/SSE second — reuses Axis2's existing HTTP infrastructure, adds SSE for progress notifications on long-running service operations
Populated from axis2-openapi Phase 2 output. initialize response includes
capabilities.tools derived from deployed services and their @McpTool annotations.
axis2.transport.mcp.enabled=true
axis2.transport.mcp.transport=stdio # or http
axis2.transport.mcp.path=/mcp # only for http transportClaude Desktop / AI agent → MCP (axis2-transport-mcp, native)
↓
REST clients → REST (@RestMapping, Phase 3) → Axis2 Service
↑ (one Java class)
Existing JSON-RPC callers → JSON-RPC (unchanged)
Why stdio first for both tracks: Simplest MCP transport, zero port conflicts, works immediately with Claude Desktop and Cursor. Validates the translation layer before adding HTTP connection management complexity.
Why OpenAPI as the bridge, not direct Axis2 introspection: /openapi-mcp.json
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.
Why no MCP Java SDK: Apache 2.0 license constraint. Jackson (Apache 2.0) + Java
stdlib HttpClient 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.
Why IoT CA pattern: RSA 4096 CA (10 years) + RSA 2048 leaf certs (2 years) matches the Kanaha camera project 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.
Why certificateVerification="required" at Tomcat, not Spring Security: Tomcat
enforces the TLS handshake before any HTTP processing. Invalid client certs are rejected
at the TCP layer — Spring Security never sees them. X509AuthenticationFilter only
needs to extract identity from an already-verified cert, not verify it.
Why not JAX-RS instead of @RestMapping: JAX-RS brings a second framework
dependency and its own lifecycle. @RestMapping is a thin annotation processed by
Axis2's existing REST dispatcher — no container dependency, backwards compatible,
opt-in per-operation.
finbench_mcp.c + finbench_mcp_main.c committed to axis-axis2-c-core.
Builds with build_financial_service.sh; installs to /usr/local/axis2c/bin/financial-benchmark-mcp.
See docs/MCP.md in the axis-axis2-c-core repo for the full C implementation plan.
| Step | Work | Notes |
|---|---|---|
@McpTool annotation |
Richer inputSchema in /openapi-mcp.json |
Currently all tools return empty properties {} |
| A4 HTTP/SSE | Persistent bridge server mode | Required for production, additive |
modules/transport-mcp/— new module scaffolding- stdio transport first (B1) — validates JSON-RPC 2.0 ↔ MessageContext translation
- HTTP/SSE transport (B2) — reuses Axis2 HTTP infrastructure
build_financial_service.sh in axis-axis2-c-core needs to be run on the Penguin
host after Axis2/C is installed. The service is committed and compiles clean — the
script is the only remaining deployment step.
Track A (axis2-mcp-bridge) requires:
axis2-openapimodule (for/openapi-mcp.json)com.fasterxml.jackson.core:jackson-databind:2.21.1(Apache 2.0)- Java 21+ (HttpClient is standard library)
- No Axis2 core dependency — bridge is a separate process
Track B (axis2-transport-mcp) requires:
axis2-core/axis2-kernel(TransportListener interface)axis2-openapi(tool schema generation)- No MCP SDK — same Jackson-only approach as A2