Apache Axis2 MCP Integration Guide
Who should read this: 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.
Quick start: For step-by-step build, deploy, and test instructions (including curl commands for every endpoint), see the sample READMEs: springbootdemo-tomcat11 README (Tomcat 11) and springbootdemo-wildfly README (WildFly 32/39).
In one sentence: Axis2 auto-generates an MCP tool catalog from its
deployed services, accessible at /openapi-mcp.json, that tells MCP clients
the exact JSON-RPC envelope format, auth requirements, and endpoint URL for every
deployed operation — no out-of-band documentation required.
What is MCP?
MCP (Model Context Protocol) is an open standard published at modelcontextprotocol.io that defines how AI assistants (Claude, ChatGPT, Cursor, etc.) discover and call external tools.
Relationship to OpenAPI:
OpenAPI
describes REST APIs for human developers and code generators. MCP describes
the same services as tools for AI assistants. Axis2 generates both
from the same deployed services — /openapi.json produces the
OpenAPI spec, /openapi-mcp.json 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.
The core idea: An MCP server advertises a catalog of tools — each
with a name, a natural-language description, and a JSON Schema describing its
parameters. An AI assistant reads this catalog, decides which tool to call based on
the user's request, fills in the parameters as a JSON object, and sends a
tools/call request. The server executes the tool and returns the result
as JSON. The entire exchange is JSON — requests, responses, parameter schemas,
error messages. There is no XML anywhere in the protocol.
Why MCP is JSON-only: MCP is built on JSON-RPC 2.0, the same lightweight RPC protocol used by language servers (LSP), cryptocurrency nodes (Ethereum), and many other modern tools. AI assistants produce and consume JSON natively — their training data is overwhelmingly JSON, their function-calling APIs use JSON, and their tool-use formats are JSON Schema. XML/SOAP was never considered for MCP because the entire AI tooling ecosystem is JSON-native.
What this means for SOAP services: MCP cannot call SOAP endpoints
directly. A SOAP service returns XML envelopes with namespaces, and MCP clients
cannot parse them. To expose a SOAP service to AI agents, convert it to JSON-RPC
first — this is a configuration change in services.xml (swap message
receivers), not a code change. The service Java class is unchanged. See the
Spring Boot Starter Guide
for the axis2.mode=json setting.
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
modelcontextprotocol.io.
Axis2: Three Protocols from One Service
A single Axis2 service deployment simultaneously speaks three protocols from the same Java class, with no code duplication and no wrapper layers:
| Protocol | What it serves | Who calls it |
|---|---|---|
| JSON-RPC | Axis2's native wire format — {"op":[{"arg0":{...}}]} envelope over HTTP POST |
Existing enterprise callers, Node.js bridges, legacy integrations |
| REST + OpenAPI | Auto-generated OpenAPI 3.0 spec at /openapi.json, interactive Swagger UI at /swagger-ui |
React frontends, data API consumers, API gateways, developers exploring the service |
| MCP | Auto-generated MCP tool catalog at /openapi-mcp.json with full inputSchema, auth hints, and payload templates |
AI assistants (Claude Desktop, Claude API, Cursor), any MCP-compatible agent |
This is unique to Axis2. No other Java framework serves all three protocols from the same service class in the same deployment:
- Spring Boot — excellent REST + OpenAPI via springdoc. MCP is available via Spring AI, but as a separate server component with its own tool definitions — not auto-generated from existing service classes. No native JSON-RPC support.
- Apache CXF — SOAP + REST, but no JSON-RPC transport and no MCP support.
- JAX-RS (Jersey, RESTEasy) — REST + OpenAPI only. No JSON-RPC, no MCP.
- gRPC — its own binary protocol with REST bridging via grpc-gateway. No JSON-RPC, no MCP.
With Axis2, adding MCP to an existing JSON-RPC service is a configuration change in
services.xml — add mcpDescription and mcpInputSchema
parameters to each operation, and the MCP catalog appears automatically at
/openapi-mcp.json. The service Java class is unchanged.
- 1. The MCP Catalog Endpoint
- 2. Catalog Schema Reference
- 3. The Axis2 JSON-RPC Envelope (Critical)
- 4. Authentication: Two-Phase Bearer Token Flow
- 5. Error Handling: Correlation ID Pattern
- 6. Not Implemented / Limitations
- 7. Migration Path: JSON-RPC to REST
- 8. Python MCP Client Example
1. The MCP Catalog Endpoint
Every Axis2 deployment exposes a machine-readable MCP tool catalog:
GET /axis2/openapi-mcp.json Content-Type: application/json Cache-Control: no-cache, no-store
The catalog is served by SwaggerUIHandler.handleMcpCatalogRequest()
alongside the existing /swagger-ui and /openapi.json endpoints.
Cache-Control: no-cache, no-store is intentional — the service list changes
on every deployment and a stale catalog causes MCP clients to call operations that no
longer exist.
HTTP headers set on the catalog response:
| Header | Value | Purpose |
|---|---|---|
Content-Type |
application/json; charset=UTF-8 |
MCP client parsing |
Cache-Control |
no-cache, no-store |
Prevent stale tool lists |
Access-Control-Allow-Origin |
* |
MCP clients from any origin |
Access-Control-Allow-Methods |
GET, OPTIONS |
Catalog is GET-only (POST is on service endpoints) |
Access-Control-Allow-Headers |
Content-Type, Authorization |
Bearer token in pre-flight |
X-Content-Type-Options |
nosniff |
Security hardening |
X-Frame-Options |
SAMEORIGIN |
Clickjacking protection |
2. Catalog Schema Reference
The catalog JSON has two top-level keys: _meta (transport contract,
constant across all services) and tools (one entry per deployed operation).
2.1 _meta Block
{
"_meta": {
"axis2JsonRpcFormat": "{\"<operationName>\":[{\"arg0\":{<params>}}]}",
"contentType": "application/json",
"authHeader": "Authorization: Bearer <token>",
"tokenEndpoint": "POST /services/loginService/doLogin",
"errorContract": {
"schemaRef": "#/components/schemas/ErrorResponse",
"fields": {
"error": "Error code: VALIDATION_ERROR | RATE_LIMITED | SERVICE_UNAVAILABLE | BAD_REQUEST | INTERNAL_ERROR",
"message": "Human-readable error message",
"errorRef": "UUID correlation ID — quote in support requests",
"timestamp": "ISO 8601 when the error occurred",
"retryAfter": "Seconds to wait before retrying (429/503 only, null otherwise)"
},
"httpStatusMapping": {
"400": "BAD_REQUEST — malformed JSON or missing required fields",
"422": "VALIDATION_ERROR — valid JSON but fails business validation",
"429": "RATE_LIMITED — too many requests, check retryAfter",
"500": "INTERNAL_ERROR — server fault, errorRef logged server-side",
"503": "SERVICE_UNAVAILABLE — downstream dependency or overload"
}
}
},
...
}
The _meta block answers the three questions every MCP client must answer
before calling any tool:
- What body structure does the server expect? See
axis2JsonRpcFormat— the Axis2 JSON-RPC envelope with operation name as the top-level key. - How do I authenticate? See
authHeaderandtokenEndpoint— callloginService/doLoginfirst, then pass the returned token as a Bearer header. - What Content-Type? Always
application/json. - What do errors look like? See
errorContract— structured JSON with error code, message, correlation ID (errorRef), timestamp, and HTTP status mapping. Services throwJsonRpcFaultExceptionto produce these responses.
2.2 Per-Tool Fields
{
"tools": [
{
"name": "doLogin",
"description": "loginService: doLogin",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
},
"endpoint": "POST /services/loginService/doLogin",
"x-axis2-payloadTemplate": "{\"doLogin\":[{\"arg0\":{}}]}",
"x-requiresAuth": false,
"annotations": {
"readOnlyHint": false,
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": false
}
},
{
"name": "portfolioVariance",
"description": "Calculate portfolio variance using O(n^2) covariance matrix multiplication",
"inputSchema": {
"type": "object",
"required": ["nAssets", "weights", "covarianceMatrix"],
"properties": {
"nAssets": {"type": "integer", "minimum": 2, "maximum": 2000},
"weights": {"type": "array", "items": {"type": "number"}},
"covarianceMatrix": {"type": "array", "items": {"type": "array", "items": {"type": "number"}}},
"normalizeWeights": {"type": "boolean", "default": false},
"nPeriodsPerYear": {"type": "integer", "default": 252}
}
},
"endpoint": "POST /services/FinancialBenchmarkService/portfolioVariance",
"x-axis2-payloadTemplate": "{\"portfolioVariance\":[{\"arg0\":{}}]}",
"x-requiresAuth": true,
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}
}
]
}
Field semantics:
| Field | Type | Notes |
|---|---|---|
name |
string | Axis2 operation name (local part of QName); use as tool name in MCP |
description |
string | Auto-generated "ServiceName: operationName" — not a rich natural language description |
inputSchema |
object | MCP-compliant JSON Schema; populated from mcpInputSchema parameter in services.xml when set, otherwise empty {} |
endpoint |
string | Full POST path for this operation |
x-axis2-payloadTemplate |
string (JSON) | The exact body to send, with the operation's wrapper already filled in |
x-requiresAuth |
boolean | false only for loginService (case-insensitive); true for everything else |
annotations |
object | MCP 2025-03-26 safety hints; all default to false (conservative) |
MCP 2025-03-26 annotations (readOnlyHint,
destructiveHint, idempotentHint, openWorldHint)
are present for spec compliance but are all false. 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.
3. The Axis2 JSON-RPC Envelope (Critical)
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.
3.1 Required Envelope Structure
POST /services/{ServiceName}/{operationName}
Content-Type: application/json
Authorization: Bearer {token}
{
"{operationName}": [
{
"arg0": {
... your parameters here ...
}
}
]
}
The parsing sequence in JsonUtils.invokeServiceClass() is strict:
beginObject()— outer{}nextName()— must equal the operation namebeginArray()— the[...]wrapperbeginObject()— the parameter objectnextName()→fromJson()— reads each parameterendObject(),endArray(),endObject()
Any deviation — bare JSON object, missing array, wrong operation name — results in
Bad Request [errorRef=<uuid>]. There is no partial match or helpful
field-level error (see Section 5).
3.2 Login Payload Example
The exact payload for loginService/doLogin:
{
"doLogin": [
{
"arg0": {
"email": "user@example.com",
"credentials": "password"
}
}
]
}
This pattern applies to every Axis2 service. The x-axis2-payloadTemplate
field in the catalog pre-fills the operation name wrapper so MCP clients only need to
substitute parameters into arg0.
3.3 enableJSONOnly Mode
When a service is deployed with enableJSONOnly=true, the outer operation
name wrapper is optional — the server dispatches by URL path alone. The payload reduces to:
[{ "arg0": { ... parameters ... } }]
Most production deployments use enableJSONOnly=false (the default),
which requires the full envelope. Check the catalog's x-axis2-payloadTemplate
to see which format a specific service expects.
4. Authentication: Two-Phase Bearer Token Flow
Note: Axis2 is a web services framework — it does not impose any specific authentication mechanism. The two-phase Bearer token flow described below is implemented by the sample application'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.
The sample application uses the following two-phase flow:
Phase 1 — Obtain Token (no auth required)
POST /services/loginService/doLogin
Content-Type: application/json
{
"doLogin": [
{
"arg0": {
"email": "user@example.com",
"credentials": "password"
}
}
]
}
Response:
{
"response": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"user": { ... }
}
}
Token storage is implementation-specific. A common pattern is to persist the token
as a JSON file with mode 0600 (user-only read), or pass it via an
environment variable.
Phase 2 — Call Protected Services
POST /services/{ServiceName}/{operationName}
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
{
"{operationName}": [{ "arg0": { ... } }]
}
All services except loginService require the Bearer header.
The catalog's x-requiresAuth field encodes this per-tool. The
_meta.tokenEndpoint tells MCP clients where to obtain the token
without hardcoding the service path.
5. Error Handling
Axis2 JSON-RPC services support two error formats depending on where the error originates:
5.1 Structured JSON Errors (service-level validation)
Services that throw
JsonRpcFaultException
produce structured JSON error responses with proper HTTP status codes:
HTTP 422
Content-Type: application/json
{
"response": {
"status": "FAILED",
"error": "VALIDATION_ERROR",
"message": "initialValue must be > 0 (GBM is undefined for non-positive starting values).",
"errorRef": "a3f2c1d0-7b4e-4a2f-9c8d-1e6f3b5a2d7c",
"timestamp": "2026-05-15T14:30:00Z",
"retryAfter": null
}
}
The HTTP status codes map to error categories:
| HTTP Status | Error Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | Malformed JSON or missing required fields |
| 422 | VALIDATION_ERROR | Valid JSON but fails business validation |
| 429 | RATE_LIMITED | Too many requests; check retryAfter |
| 500 | INTERNAL_ERROR | Server fault; errorRef logged server-side |
| 503 | SERVICE_UNAVAILABLE | Downstream dependency or overload |
The errorRef 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.
5.2 SOAP Fault Fallback (parse-level errors)
Requests that fail before reaching the service method (malformed JSON,
wrong operation name, missing array wrapper) produce a legacy SOAP fault
with a sanitized Bad Request message. This is a deliberate
security feature — no structural information leakage:
HTTP 500 <soapenv:Fault> <faultcode>soapenv:Server</faultcode> <faultstring>Bad Request [errorRef=a3f2c1d0-7b4e-4a2f-9c8d-1e6f3b5a2d7c]</faultstring> </soapenv:Fault>
5.2 What Triggers This
| Bad Payload | Result |
|---|---|
| Not valid JSON at all | Bad Request [errorRef=...] |
Valid JSON but missing outer array [...] |
Bad Request [errorRef=...] |
| Operation name in body does not match URL path | Bad Request [errorRef=...] |
| Parameters wrong type for service method | Bad Request [errorRef=...] |
| Service reflection error (wrong method signature) | Internal Server Error [errorRef=...] |
5.3 Python MCP Implications
MCP tools built against Axis2 services should surface the errorRef
UUID in their error responses so users can correlate with server logs. A recommended
pattern is to return a structured ErrorResponse with error_type
and suggestions:
from dataclasses import dataclass
@dataclass
class ErrorResponse:
error: str # "Bad Request [errorRef=a3f2c1d0...]"
error_type: str # "axis2_payload_error"
suggestions: list[str] # ["Check x-axis2-payloadTemplate in catalog"]
6. Not Implemented / Limitations
The following capabilities are not present in the Axis2 MCP catalog. Each item notes whether the gap is architectural (won't be added to Axis2) or deferred (could be added).
| Feature | Status | Notes |
|---|---|---|
Rich inputSchema properties |
Implemented — set mcpInputSchema parameter on <operation> in services.xml |
When mcpInputSchema is set, the catalog embeds the full JSON Schema (types, constraints, required fields). Falls back to empty {} when not set. All financial benchmark operations have full schemas. Automatic introspection from Java types is not yet implemented — schemas are hand-authored in services.xml. |
| Natural language tool descriptions | Implemented — set mcpDescription parameter on <operation> or <service> in services.xml |
Operation-level parameter takes precedence over service-level; falls back to auto-generated "ServiceName: operationName". |
| MCP Resources and Prompts | Not implemented | The catalog exposes only MCP "tools". The MCP protocol also defines "resources" (URIs for data blobs) and "prompts" (parameterized message templates). Axis2 services are operation-based and do not map to resources or prompts. |
| MCP 2025-03-26 annotation tuning | Implemented — set mcpReadOnly, mcpIdempotent, mcpDestructive, mcpOpenWorld on <operation> or <service> in services.xml |
Conservative false defaults preserved when parameters absent. Operation-level overrides service-level. |
| Streaming / SSE responses | Not implemented — architectural gap | Axis2 JSON-RPC is request/response only. The MCP protocol supports server-sent event streams for long-running operations. There is no streaming path. |
| Batch tool calls | Not implemented | Each Axis2 operation is a separate HTTP POST. There is no batch envelope. |
| Semantic query layer (filter/sort/fields) | Not applicable to Axis2 | Axis2 JSON-RPC is operation-based — parameters are passed in arg0, 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. |
| Natural key resolution (ticker → assetId) | Implemented — set global parameter mcpTickerResolveService in axis2.xml |
When set to ServiceName/operationName, _meta.tickerResolveEndpoint is added to the catalog. Omitted entirely when not configured so deployments without a ticker service are unaffected. |
| Pagination | Offset/limit implemented — see Pagination Guide | Axis2 provides PaginatedResponse<T> and PaginationRequest
for offset/limit pagination with maxLimit clamping.
Cursor-based pagination is not implemented (offset/limit maps directly to JPA/Hibernate DAO patterns). |
| Structured error responses | Implemented — see Section 5 | Services throw JsonRpcFaultException 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. |
| API key management | Not applicable to Axis2 | Axis2 uses email/password → Bearer token via loginService only. API key + secret issuance with scopes and rotation would need to be implemented in a separate layer. |
| Write operations (CRUD mutations) | Axis2 has write services; catalog supports them | Axis2 write operations exist as services and will appear in the catalog with x-requiresAuth: true. If your deployment also exposes a REST API layer for writes, determine which path is canonical for your use case. |
7. Migration Path: JSON-RPC to REST
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:
| Aspect | Axis2 JSON-RPC | REST API Layer |
|---|---|---|
| Protocol | Axis2 JSON-RPC envelope: {"op":[{"arg0":{}}]} |
Plain REST: GET /api/v1/resources/{id}?filter=... |
| Tool definitions | Auto-generated from deployed Axis2 services | Auto-generated from OpenAPI 3.1 (e.g., via springdoc-openapi) |
| Auth | email + password → Bearer token (loginService) | API key + secret → scoped JWT |
| Query semantics | Per-operation parameters in arg0 | Uniform filter/sort/fields on every resource |
| Error format | Structured JSON (JsonRpcFaultException) with correlation ID, HTTP status codes | RFC 7807 Problem Details (JSON, per-field) |
| Pagination | Offset/limit (PaginatedResponse) | Cursor-based |
| inputSchema | Full JSON Schema via mcpInputSchema in services.xml (hand-authored) |
Full JSON Schema from OpenAPI annotations (auto-generated) |
The Axis2 MCP catalog at /openapi-mcp.json provides MCP-ready tool
discovery for existing Axis2 services. If your organization is building a REST API
layer alongside Axis2, the catalog serves as a bridge — providing machine-readable
tool discovery during the transition period.
8. Python MCP Client Example
The most common MCP integration path is a Python bridge — AI assistants like Claude and ChatGPT use Python-based MCP servers to call external tools. The template below shows how to authenticate, discover tools from the Axis2 MCP catalog, and call services using the correct JSON-RPC envelope.
import httpx
import json
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
BASE_URL = "https://your-axis2-server/services"
server = Server("axis2-mcp")
_token = None
async def login(email: str, password: str) -> str:
async with httpx.AsyncClient() as c:
r = await c.post(
f"{BASE_URL}/loginService/doLogin",
json={"doLogin": [{"arg0": {"email": email, "credentials": password}}]}
)
return r.json()["response"]["token"]
async def call_service(service: str, op: str, params: dict) -> dict:
async with httpx.AsyncClient() as c:
r = await c.post(
f"{BASE_URL}/{service}/{op}",
json={op: [{"arg0": params}]},
headers={"Authorization": f"Bearer {_token}"}
)
return r.json()
@server.list_tools()
async def list_tools() -> list[Tool]:
# Fetch live from catalog — honors Cache-Control: no-cache
async with httpx.AsyncClient() as c:
catalog = (await c.get(f"{BASE_URL}/../openapi-mcp.json")).json()
return [
Tool(name=t["name"], description=t["description"],
inputSchema=t["inputSchema"])
for t in catalog["tools"]
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
# Resolve service name from catalog endpoint field
# then delegate to call_service()
...
Key points:
- The catalog at
/openapi-mcp.jsonhasCache-Control: no-cache, so fetching it atlist_tools()time is correct — the tool list stays current without restarting the MCP server. - When
mcpInputSchemais set inservices.xml, the catalog provides full JSON Schema for each tool. For services without it, define the schema in your Python MCP server. - The
_meta.axis2JsonRpcFormatfield documents the exact envelope format your HTTP client must send.
Apache Axis2
