You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-commits@axis.apache.org by ro...@apache.org on 2026/04/07 14:04:23 UTC

(axis-axis2-java-core) 05/09: docs: add JSON-RPC MCP Integration Guide

This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit 71d451dfa6e47162d0082b0e160d4bc1a27d490f
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Tue Apr 7 01:56:33 2026 -1000

    docs: add JSON-RPC MCP Integration Guide
    
    New doc at src/site/xdoc/docs/json-rpc-mcp-guide.xml covering:
    
    - /openapi-mcp.json catalog endpoint (schema, HTTP headers, cache-control)
    - _meta block: axis2JsonRpcFormat, authHeader, tokenEndpoint
    - Per-tool fields: x-axis2-payloadTemplate, x-requiresAuth, MCP 2025 annotations
    - Axis2 JSON-RPC envelope format with parsing sequence explanation
    - enableJSONOnly mode
    - Two-phase Bearer token auth flow (matches pyRapi/rapi-mcp pattern)
    - Correlation ID error handling (Bad Request [errorRef=<uuid>])
    - Full unit test feature matrix: McpCatalogGeneratorTest (50+ tests),
      McpCatalogHandlerTest (17 tests), McpAxis2PayloadTest (30 tests),
      JSONRPCIntegrationTest (9 tests), pyRapi test_auth.py (18 tests)
    - Not-implemented table: empty inputSchema properties, no rich descriptions,
      no Resources/Prompts, conservative annotation defaults, no streaming,
      no batch calls, no semantic query layer, no pagination, SOAP fault format,
      no API key management, moneyball empty, gateway not wired to Axis2
    - Comparison table: Axis2 today vs Data API Phase 1 (Q2 2026)
    - Python compatibility notes per repo: rapi-mcp, pyRapi,
      internal-alpha-theory-mcp (3 servers), atds_research, hermes_at, moneyball
    - Minimal catalog-driven MCP server template
    
    Audience: Python MCP developers targeting AT RAPI services, and developers
    who have read DATA_API_VISION.md / DATA_API_INTERFACE_DESIGN.md.
    
    Add as section 23.4 in toc.xml under JSON Support.
    
    Co-Authored-By: Claude Sonnet 4.6 <no...@anthropic.com>
---
 src/site/xdoc/docs/json-rpc-mcp-guide.xml | 707 ++++++++++++++++++++++++++++++
 src/site/xdoc/docs/toc.xml                |  13 +
 2 files changed, 720 insertions(+)

diff --git a/src/site/xdoc/docs/json-rpc-mcp-guide.xml b/src/site/xdoc/docs/json-rpc-mcp-guide.xml
new file mode 100644
index 0000000000..516d3ea70e
--- /dev/null
+++ b/src/site/xdoc/docs/json-rpc-mcp-guide.xml
@@ -0,0 +1,707 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements. See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership. The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License. You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied. See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html>
+<head>
+  <meta http-equiv="content-type" content=""/>
+  <title>Apache Axis2 MCP Integration Guide</title>
+</head>
+
+<body dir="ltr" lang="en-US">
+
+<h1 align="center">Apache Axis2 MCP Integration Guide</h1>
+
+<p><strong>Who should read this:</strong> Developers who have written Python MCP servers
+targeting AT's RAPI/Axis2 services (rapi-mcp, alpha-knowledge-mcp, alpha-collector-mcp),
+or who have read the Data API Vision and Interface Design documents and need to understand
+what MCP capabilities are available today via Axis2 vs. what is planned for the Data API
+Phase 1 REST layer.</p>
+
+<p><strong>In one sentence:</strong> Axis2 auto-generates an MCP tool catalog from its
+deployed services, accessible at <code>/openapi-mcp.json</code>, that tells MCP clients
+the exact JSON-RPC envelope format, auth requirements, and endpoint URL for every
+deployed operation — no out-of-band documentation required.</p>
+
+<ul>
+<li><a href="#mcp_catalog">1. The MCP Catalog Endpoint</a></li>
+<li><a href="#catalog_schema">2. Catalog Schema Reference</a></li>
+<li><a href="#envelope">3. The Axis2 JSON-RPC Envelope (Critical)</a></li>
+<li><a href="#auth">4. Authentication: Two-Phase Bearer Token Flow</a></li>
+<li><a href="#error_handling">5. Error Handling: Correlation ID Pattern</a></li>
+<li><a href="#tested_features">6. Features Covered by Unit Tests</a></li>
+<li><a href="#not_implemented">7. Not Implemented / Limitations</a></li>
+<li><a href="#data_api_relationship">8. Relationship to the Data API Vision</a></li>
+<li><a href="#python_compat">9. Python MCP Compatibility Notes</a></li>
+</ul>
+
+<!-- ============================================================ -->
+<a name="mcp_catalog"/>
+<h2>1. The MCP Catalog Endpoint</h2>
+
+<p>Every Axis2 deployment exposes a machine-readable MCP tool catalog:</p>
+
+<pre>
+GET /axis2/openapi-mcp.json
+Content-Type: application/json
+Cache-Control: no-cache, no-store
+</pre>
+
+<p>The catalog is served by <code>SwaggerUIHandler.handleMcpCatalogRequest()</code>
+alongside the existing <code>/swagger-ui</code> and <code>/openapi.json</code> endpoints.
+<code>Cache-Control: no-cache, no-store</code> is intentional — the service list changes
+on every deployment and a stale catalog causes MCP clients to call operations that no
+longer exist.</p>
+
+<p><strong>HTTP headers set on the catalog response:</strong></p>
+
+<table border="1">
+<tr><th>Header</th><th>Value</th><th>Purpose</th></tr>
+<tr><td><code>Content-Type</code></td><td><code>application/json; charset=UTF-8</code></td><td>MCP client parsing</td></tr>
+<tr><td><code>Cache-Control</code></td><td><code>no-cache, no-store</code></td><td>Prevent stale tool lists</td></tr>
+<tr><td><code>Access-Control-Allow-Origin</code></td><td><code>*</code></td><td>MCP clients from any origin</td></tr>
+<tr><td><code>Access-Control-Allow-Methods</code></td><td><code>GET, OPTIONS</code></td><td>Catalog is GET-only (POST is on service endpoints)</td></tr>
+<tr><td><code>Access-Control-Allow-Headers</code></td><td><code>Content-Type, Authorization</code></td><td>Bearer token in pre-flight</td></tr>
+<tr><td><code>X-Content-Type-Options</code></td><td><code>nosniff</code></td><td>Security hardening</td></tr>
+<tr><td><code>X-Frame-Options</code></td><td><code>SAMEORIGIN</code></td><td>Clickjacking protection</td></tr>
+</table>
+
+<!-- ============================================================ -->
+<a name="catalog_schema"/>
+<h2>2. Catalog Schema Reference</h2>
+
+<p>The catalog JSON has two top-level keys: <code>_meta</code> (transport contract,
+constant across all services) and <code>tools</code> (one entry per deployed operation).</p>
+
+<h3>2.1 _meta Block</h3>
+
+<pre>
+{
+  "_meta": {
+    "axis2JsonRpcFormat": "{\"&lt;operationName&gt;\":[{\"arg0\":{&lt;params&gt;}}]}",
+    "contentType":        "application/json",
+    "authHeader":         "Authorization: Bearer &lt;token&gt;",
+    "tokenEndpoint":      "POST /services/loginService/doLogin"
+  },
+  ...
+}
+</pre>
+
+<p>The <code>_meta</code> block answers the three questions every MCP client must answer
+before calling any tool:</p>
+<ul>
+<li><strong>What body structure does the server expect?</strong> See
+    <code>axis2JsonRpcFormat</code> — the Axis2 JSON-RPC envelope with
+    operation name as the top-level key.</li>
+<li><strong>How do I authenticate?</strong> See <code>authHeader</code> and
+    <code>tokenEndpoint</code> — call <code>loginService/doLogin</code> first,
+    then pass the returned token as a Bearer header.</li>
+<li><strong>What Content-Type?</strong> Always <code>application/json</code>.</li>
+</ul>
+
+<h3>2.2 Per-Tool Fields</h3>
+
+<pre>
+{
+  "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":        "getAssetCalculations",
+      "description": "GetAssetCalculationsService: getAssetCalculations",
+      "inputSchema": { "type": "object", "properties": {}, "required": [] },
+      "endpoint":                "POST /services/GetAssetCalculationsService/getAssetCalculations",
+      "x-axis2-payloadTemplate": "{\"getAssetCalculations\":[{\"arg0\":{}}]}",
+      "x-requiresAuth":          true,
+      "annotations": {
+        "readOnlyHint":    false,
+        "destructiveHint": false,
+        "idempotentHint":  false,
+        "openWorldHint":   false
+      }
+    }
+  ]
+}
+</pre>
+
+<p><strong>Field semantics:</strong></p>
+
+<table border="1">
+<tr><th>Field</th><th>Type</th><th>Notes</th></tr>
+<tr><td><code>name</code></td><td>string</td><td>Axis2 operation name (local part of QName); use as tool name in MCP</td></tr>
+<tr><td><code>description</code></td><td>string</td><td>Auto-generated "ServiceName: operationName" — not a rich natural language description</td></tr>
+<tr><td><code>inputSchema</code></td><td>object</td><td>MCP-compliant JSON Schema wrapper; <strong>properties is always empty</strong> (see Section 7)</td></tr>
+<tr><td><code>endpoint</code></td><td>string</td><td>Full POST path for this operation</td></tr>
+<tr><td><code>x-axis2-payloadTemplate</code></td><td>string (JSON)</td><td>The exact body to send, with the operation's wrapper already filled in</td></tr>
+<tr><td><code>x-requiresAuth</code></td><td>boolean</td><td><code>false</code> only for <code>loginService</code> (case-insensitive); <code>true</code> for everything else</td></tr>
+<tr><td><code>annotations</code></td><td>object</td><td>MCP 2025-03-26 safety hints; all default to <code>false</code> (conservative)</td></tr>
+</table>
+
+<p><strong>MCP 2025-03-26 annotations</strong> (<code>readOnlyHint</code>,
+<code>destructiveHint</code>, <code>idempotentHint</code>, <code>openWorldHint</code>)
+are present for spec compliance but are all <code>false</code>. They are not tuned per
+service. If your MCP host requires accurate hints, you must set them in a catalog
+post-processor or in your Python MCP server layer.</p>
+
+<!-- ============================================================ -->
+<a name="envelope"/>
+<h2>3. The Axis2 JSON-RPC Envelope (Critical)</h2>
+
+<p>Axis2's JSON-RPC layer requires every call to use a specific three-layer envelope.
+This is the single biggest difference from conventional REST and from the Data API
+Phase 1 contract. Every Python MCP tool that calls an Axis2 service must use this format.</p>
+
+<h3>3.1 Required Envelope Structure</h3>
+
+<pre>
+POST /services/{ServiceName}/{operationName}
+Content-Type: application/json
+Authorization: Bearer {token}
+
+{
+  "{operationName}": [
+    {
+      "arg0": {
+        ... your parameters here ...
+      }
+    }
+  ]
+}
+</pre>
+
+<p>The parsing sequence in <code>JsonUtils.invokeServiceClass()</code> is strict:</p>
+<ol>
+<li><code>beginObject()</code> — outer <code>{}</code></li>
+<li><code>nextName()</code> — must equal the operation name</li>
+<li><code>beginArray()</code> — the <code>[...]</code> wrapper</li>
+<li><code>beginObject()</code> — the parameter object</li>
+<li><code>nextName()</code> → <code>fromJson()</code> — reads each parameter</li>
+<li><code>endObject()</code>, <code>endArray()</code>, <code>endObject()</code></li>
+</ol>
+
+<p>Any deviation — bare JSON object, missing array, wrong operation name — results in
+<code>Bad Request [errorRef=&lt;uuid&gt;]</code>. There is no partial match or helpful
+field-level error (see Section 5).</p>
+
+<h3>3.2 pyRapi Reference Implementation</h3>
+
+<p>The canonical Python reference for this format is
+<code>pyRapi/tests/test_auth.py::TestDoLogin::test_login_sends_correct_payload</code>,
+which asserts the exact payload sent to <code>loginTokenizerService</code>:</p>
+
+<pre>
+{
+  "doLogin": [
+    {
+      "arg0": {
+        "email":       "user@example.com",
+        "credentials": "password"
+      }
+    }
+  ]
+}
+</pre>
+
+<p>This pattern applies to every Axis2 service. The <code>x-axis2-payloadTemplate</code>
+field in the catalog pre-fills the operation name wrapper so MCP clients only need to
+substitute parameters into <code>arg0</code>.</p>
+
+<h3>3.3 enableJSONOnly Mode</h3>
+
+<p>When a service is deployed with <code>enableJSONOnly=true</code>, the outer operation
+name wrapper is optional — the server dispatches by URL path alone. The payload reduces to:</p>
+
+<pre>
+[{ "arg0": { ... parameters ... } }]
+</pre>
+
+<p>Most AT production services use <code>enableJSONOnly=false</code> (the default),
+which requires the full envelope. Check the catalog's <code>x-axis2-payloadTemplate</code>
+to see which format a specific service expects.</p>
+
+<!-- ============================================================ -->
+<a name="auth"/>
+<h2>4. Authentication: Two-Phase Bearer Token Flow</h2>
+
+<p>Axis2 services use a two-phase auth flow. This matches the pattern in
+<code>rapi-mcp</code>'s <code>RAPIClient</code> and <code>pyRapi/auth.py</code>.</p>
+
+<h3>Phase 1 — Obtain Token (no auth required)</h3>
+
+<pre>
+POST /services/loginService/doLogin
+Content-Type: application/json
+
+{
+  "doLogin": [
+    {
+      "arg0": {
+        "email":       "user@example.com",
+        "credentials": "password"
+      }
+    }
+  ]
+}
+
+Response:
+{
+  "response": {
+    "token": "eyJhbGciOiJIUzI1NiJ9...",
+    "user": { ... }
+  }
+}
+</pre>
+
+<p>Token storage follows the pyRapi convention: JSON file at
+<code>~/.pyrapi/auth_token</code> with mode <code>0600</code>, or environment variable
+<code>AT_RAPI_MCP_AUTH_TOKEN</code>. Both patterns are tested in
+<code>pyRapi/tests/test_auth.py</code>.</p>
+
+<h3>Phase 2 — Call Protected Services</h3>
+
+<pre>
+POST /services/{ServiceName}/{operationName}
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
+
+{
+  "{operationName}": [{ "arg0": { ... } }]
+}
+</pre>
+
+<p>All services except <code>loginService</code> require the Bearer header.
+The catalog's <code>x-requiresAuth</code> field encodes this per-tool. The
+<code>_meta.tokenEndpoint</code> tells MCP clients where to obtain the token
+without hardcoding the service path.</p>
+
+<!-- ============================================================ -->
+<a name="error_handling"/>
+<h2>5. Error Handling: Correlation ID Pattern</h2>
+
+<p>Malformed JSON-RPC bodies return a sanitized fault message containing only
+an opaque correlation ID. This is a deliberate security feature — the bare
+<code>Bad Request</code> message passes penetration testing with no structural
+information leakage.</p>
+
+<h3>5.1 What Clients Receive</h3>
+
+<pre>
+HTTP 500
+&lt;soapenv:Fault&gt;
+  &lt;faultcode&gt;soapenv:Server&lt;/faultcode&gt;
+  &lt;faultstring&gt;Bad Request [errorRef=a3f2c1d0-7b4e-4a2f-9c8d-1e6f3b5a2d7c]&lt;/faultstring&gt;
+&lt;/soapenv:Fault&gt;
+</pre>
+
+<p>The <code>errorRef</code> UUID is logged server-side with full context (operation
+name, exception message, stack trace). Developers grep the UUID; the client response
+contains no class names, field paths, or stack trace elements.</p>
+
+<h3>5.2 What Triggers This</h3>
+
+<table border="1">
+<tr><th>Bad Payload</th><th>Result</th></tr>
+<tr><td>Not valid JSON at all</td><td><code>Bad Request [errorRef=...]</code></td></tr>
+<tr><td>Valid JSON but missing outer array <code>[...]</code></td><td><code>Bad Request [errorRef=...]</code></td></tr>
+<tr><td>Operation name in body does not match URL path</td><td><code>Bad Request [errorRef=...]</code></td></tr>
+<tr><td>Parameters wrong type for service method</td><td><code>Bad Request [errorRef=...]</code></td></tr>
+<tr><td>Service reflection error (wrong method signature)</td><td><code>Internal Server Error [errorRef=...]</code></td></tr>
+</table>
+
+<h3>5.3 Python MCP Implications</h3>
+
+<p>MCP tools built against Axis2 services should surface the <code>errorRef</code>
+UUID in their error responses so users can correlate with server logs. Follow the
+<code>internal-alpha-theory-mcp</code> pattern of returning an <code>ErrorResponse</code>
+with <code>error_type</code> and <code>suggestions</code>:</p>
+
+<pre>
+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"]
+</pre>
+
+<!-- ============================================================ -->
+<a name="tested_features"/>
+<h2>6. Features Covered by Unit Tests</h2>
+
+<p>The following capabilities are covered by unit and integration tests. Use this as a
+conformance checklist when extending the catalog or adding new MCP client code.</p>
+
+<h3>6.1 MCP Catalog Generator — McpCatalogGeneratorTest (50+ tests)</h3>
+
+<table border="1">
+<tr><th>Test</th><th>Feature Verified</th></tr>
+<tr><td><code>testCatalogIsValidJson</code></td><td>Catalog output is parseable JSON</td></tr>
+<tr><td><code>testCatalogRootHasToolsArray</code></td><td>Root has "tools" array key</td></tr>
+<tr><td><code>testEmptyConfigurationProducesEmptyToolsArray</code></td><td>No services → empty tools array (no crash)</td></tr>
+<tr><td><code>testToolNameMatchesOperationName</code></td><td>tool.name equals Axis2 operation name</td></tr>
+<tr><td><code>testToolDescriptionContainsServiceAndOperationName</code></td><td>Description is "ServiceName: operationName"</td></tr>
+<tr><td><code>testToolEndpointFormat</code></td><td>Endpoint is "POST /services/ServiceName/operationName"</td></tr>
+<tr><td><code>testInputSchemaTypeIsObject</code></td><td>inputSchema.type = "object"</td></tr>
+<tr><td><code>testInputSchemaHasPropertiesField</code></td><td>inputSchema.properties field present</td></tr>
+<tr><td><code>testInputSchemaHasRequiredField</code></td><td>inputSchema.required array present</td></tr>
+<tr><td><code>testTwoServicesEachWithOneOperationProduceTwoTools</code></td><td>Tool count matches deployed operations</td></tr>
+<tr><td><code>testOperationNameWithQuoteIsEscaped</code></td><td>Special characters are JSON-escaped in all fields</td></tr>
+<tr><td><code>testGenerateMcpCatalogWithNullRequestDoesNotThrow</code></td><td>null HttpServletRequest → graceful empty catalog</td></tr>
+<tr><td><code>testCatalogHasMetaObject</code></td><td>_meta object present at catalog root</td></tr>
+<tr><td><code>testMetaHasAxis2JsonRpcFormat</code></td><td>_meta.axis2JsonRpcFormat contains "operationName" and "arg0" placeholders</td></tr>
+<tr><td><code>testMetaHasContentType</code></td><td>_meta.contentType = "application/json"</td></tr>
+<tr><td><code>testMetaHasAuthHeaderField</code></td><td>_meta.authHeader describes Bearer scheme</td></tr>
+<tr><td><code>testMetaHasTokenEndpoint</code></td><td>_meta.tokenEndpoint references loginService, starts with "POST "</td></tr>
+<tr><td><code>testToolHasPayloadTemplateField</code></td><td>x-axis2-payloadTemplate present on every tool</td></tr>
+<tr><td><code>testPayloadTemplateIsValidJson</code></td><td>Template string is parseable JSON</td></tr>
+<tr><td><code>testPayloadTemplateOperationNameIsTopLevelKey</code></td><td>Template has {operationName: ...} at root</td></tr>
+<tr><td><code>testPayloadTemplateValueIsArray</code></td><td>Template value is an array</td></tr>
+<tr><td><code>testPayloadTemplateArrayHasArg0Object</code></td><td>Array element has "arg0" key</td></tr>
+<tr><td><code>testPayloadTemplatesDistinctAcrossOperations</code></td><td>Each operation has a unique template</td></tr>
+<tr><td><code>testNonLoginServiceRequiresAuth</code></td><td>Regular services have x-requiresAuth: true</td></tr>
+<tr><td><code>testLoginServiceDoesNotRequireAuth</code></td><td>loginService has x-requiresAuth: false</td></tr>
+<tr><td><code>testLoginServiceCaseInsensitive</code></td><td>"LoginService" (capital L) also no-auth</td></tr>
+<tr><td><code>testToolHasAnnotationsField</code></td><td>annotations object present on every tool</td></tr>
+<tr><td><code>testAnnotationsHasReadOnlyHint</code></td><td>readOnlyHint boolean present</td></tr>
+<tr><td><code>testAllAnnotationHintsAreBooleans</code></td><td>All MCP 2025-03-26 hints are booleans, not strings</td></tr>
+<tr><td><code>testMcpToolsMatchOpenApiPaths</code></td><td>Every MCP tool endpoint has a matching OpenAPI path</td></tr>
+</table>
+
+<h3>6.2 Catalog HTTP Handler — McpCatalogHandlerTest (17 tests)</h3>
+
+<table border="1">
+<tr><th>Test</th><th>Feature Verified</th></tr>
+<tr><td><code>testMcpCatalogReturnsHttp200</code></td><td>Endpoint responds successfully</td></tr>
+<tr><td><code>testMcpCatalogContentTypeIsJson</code></td><td>application/json Content-Type</td></tr>
+<tr><td><code>testMcpCatalogContentTypeIncludesUtf8</code></td><td>UTF-8 charset declared</td></tr>
+<tr><td><code>testMcpCatalogHasCorsOriginHeader</code></td><td>Access-Control-Allow-Origin: *</td></tr>
+<tr><td><code>testMcpCatalogHasCacheControlNoCache</code></td><td>Cache-Control contains no-cache or no-store</td></tr>
+<tr><td><code>testMcpCatalogHasXContentTypeOptionsNoSniff</code></td><td>X-Content-Type-Options: nosniff</td></tr>
+<tr><td><code>testMcpCatalogCorsMethodsIncludesGet</code></td><td>CORS allows GET (catalog is read-only)</td></tr>
+<tr><td><code>testMcpCatalogReflectsRegisteredService</code></td><td>Deployed services appear in tools array</td></tr>
+<tr><td><code>testMcpCatalogBodyHasMetaObject</code></td><td>_meta present in handler response</td></tr>
+<tr><td><code>testMcpCatalogMetaDocumentsAxis2Format</code></td><td>_meta.axis2JsonRpcFormat contains "arg0"</td></tr>
+<tr><td><code>testMcpCatalogToolsHavePayloadTemplateAndAuth</code></td><td>Tools carry x-axis2-payloadTemplate, x-requiresAuth, annotations end-to-end</td></tr>
+</table>
+
+<h3>6.3 Axis2 JSON-RPC Payload — McpAxis2PayloadTest (30 tests)</h3>
+
+<table border="1">
+<tr><th>Test</th><th>Feature Verified</th></tr>
+<tr><td><code>testPayloadTemplateHasSingleTopLevelKey</code></td><td>Template has exactly one root key</td></tr>
+<tr><td><code>testPayloadTemplateValueIsArray</code></td><td>Root value is array (not object)</td></tr>
+<tr><td><code>testPayloadTemplateArrayHasExactlyOneElement</code></td><td>Array has exactly one element</td></tr>
+<tr><td><code>testPayloadTemplateArrayElementHasArg0Key</code></td><td>Element has "arg0" key</td></tr>
+<tr><td><code>testPayloadTemplateArg0IsEmptyObject</code></td><td>arg0 is <code>{}</code> in template (params substituted by caller)</td></tr>
+<tr><td><code>testLoginServicePayloadTemplateHasDoLoginKey</code></td><td>loginService template has "doLogin" as root key</td></tr>
+<tr><td><code>testLoginServicePayloadTemplateCompatibleWithPyRapiFormat</code></td><td>Template structure matches pyRapi test_auth.py expected payload</td></tr>
+<tr><td><code>testTwoPhaseAuthFlowDocumentedInCatalog</code></td><td>loginService is public; all others require auth; both have templates</td></tr>
+<tr><td><code>testAllToolPayloadTemplatesMatchToolName</code></td><td>Every template's root key equals the tool's name field</td></tr>
+<tr><td><code>testAllNonLoginToolsRequireAuth</code></td><td>No service accidentally marked public</td></tr>
+<tr><td><code>testAllToolsHaveAnnotations</code></td><td>MCP 2025 annotations present on every tool</td></tr>
+<tr><td><code>testMetaTokenEndpointPointsToLoginService</code></td><td>_meta.tokenEndpoint contains "loginService"</td></tr>
+<tr><td><code>testMetaAxis2FormatHintContainsArg0</code></td><td>Format hint is instructive (contains "arg0")</td></tr>
+<tr><td><code>testMetaAuthHeaderDocumentsBearerScheme</code></td><td>authHeader contains "Bearer"</td></tr>
+</table>
+
+<h3>6.4 JSON-RPC Error Hardening — Gson JSONRPCIntegrationTest (9 tests)</h3>
+
+<table border="1">
+<tr><th>Test</th><th>Feature Verified</th></tr>
+<tr><td><code>testJsonRpcMessageReceiver</code></td><td>Correct envelope → successful round-trip response</td></tr>
+<tr><td><code>testJsonInOnlyRPCMessageReceiver</code></td><td>Fire-and-forget (InOnly) receives empty response on success</td></tr>
+<tr><td><code>testMalformedJsonBodyReturnsBadRequest</code></td><td>Non-JSON body returns "Bad Request" (no exception class leaked)</td></tr>
+<tr><td><code>testMalformedJsonBodyIncludesCorrelationId</code></td><td>Fault contains "errorRef=" UUID</td></tr>
+<tr><td><code>testMalformedJsonBodyCorrelationIdIsUuid</code></td><td>errorRef matches 8-4-4-4-12 hex UUID format</td></tr>
+<tr><td><code>testMissingOuterArrayReturnsBadRequestWithCorrelationId</code></td><td>Valid JSON but wrong envelope structure → correlated Bad Request</td></tr>
+<tr><td><code>testMalformedJsonBodyDoesNotLeakExceptionClassName</code></td><td>No "MalformedJsonException", "IOException", or "at org.apache" in response</td></tr>
+<tr><td><code>testInOnlyMalformedJsonBodyReturnsBadRequestWithCorrelationId</code></td><td>InOnly receiver applies same error hardening</td></tr>
+</table>
+
+<h3>6.5 pyRapi Authentication — test_auth.py (18 tests)</h3>
+
+<p>These Python tests document the auth contract from the client side. Any Axis2-targeting
+MCP server must satisfy the same protocol:</p>
+
+<table border="1">
+<tr><th>Test Class</th><th>Test</th><th>Feature Verified</th></tr>
+<tr><td>TestSaveToken</td><td><code>test_saves_token_to_file</code></td><td>Token persisted to <code>~/.pyrapi/auth_token</code></td></tr>
+<tr><td>TestSaveToken</td><td><code>test_file_permissions_are_600</code></td><td>Token file mode 0600 (user-only read)</td></tr>
+<tr><td>TestSaveToken</td><td><code>test_creates_directory_if_missing</code></td><td>Token directory auto-created</td></tr>
+<tr><td>TestLoadToken</td><td><code>test_returns_none_when_no_file</code></td><td>Missing token file → None (no crash)</td></tr>
+<tr><td>TestLoadToken</td><td><code>test_returns_none_on_corrupt_json</code></td><td>Corrupt token file → None (graceful)</td></tr>
+<tr><td>TestDoLogin</td><td><code>test_login_sends_correct_payload</code></td><td>Payload is <code>{"doLogin":[{"arg0":{"email":"...","credentials":"..."}}]}</code></td></tr>
+<tr><td>TestDoLogin</td><td><code>test_login_200_but_no_token_in_response</code></td><td>200 with missing token field → False (not a crash)</td></tr>
+<tr><td>TestDoLogin</td><td><code>test_login_401_invalid_credentials</code></td><td>401 → False with no token saved</td></tr>
+<tr><td>TestDoLogin</td><td><code>test_login_500_server_error</code></td><td>500 → False (server errors don't break client)</td></tr>
+</table>
+
+<!-- ============================================================ -->
+<a name="not_implemented"/>
+<h2>7. Not Implemented / Limitations</h2>
+
+<p>The following capabilities are <strong>not present</strong> in the Axis2 MCP catalog.
+Each item notes whether the gap is architectural (won't be added to Axis2) or deferred
+(could be added).</p>
+
+<table border="1">
+<tr><th>Feature</th><th>Status</th><th>Notes</th></tr>
+<tr>
+  <td>Rich <code>inputSchema</code> properties</td>
+  <td>Not implemented — architectural gap</td>
+  <td><code>inputSchema.properties</code> is always <code>{}</code>. Axis2 does not introspect Java parameter types into JSON Schema. The rapi-mcp and alpha-knowledge-mcp servers define full Pydantic-sourced schemas by hand. MCP clients that need parameter validation must document their schemas in the Python layer.</td>
+</tr>
+<tr>
+  <td>Natural language tool descriptions</td>
+  <td>Not implemented — architectural gap</td>
+  <td><code>description</code> is auto-generated as "ServiceName: operationName". Compare rapi-mcp's curated descriptions: "Get all assets in a fund with optional calculation data". If Claude or another LLM uses the catalog for tool selection, it will have less context. Workaround: add descriptions via an OpenAPI customizer.</td>
+</tr>
+<tr>
+  <td>MCP Resources and Prompts</td>
+  <td>Not implemented</td>
+  <td>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.</td>
+</tr>
+<tr>
+  <td>MCP 2025-03-26 annotation tuning</td>
+  <td>Deferred</td>
+  <td>All annotations (<code>readOnlyHint</code>, <code>destructiveHint</code>, <code>idempotentHint</code>, <code>openWorldHint</code>) default to <code>false</code>. Read-only services like <code>GetAssetCalculationsService</code> should have <code>readOnlyHint: true</code>. Requires per-service configuration or introspection of service annotations.</td>
+</tr>
+<tr>
+  <td>Streaming / SSE responses</td>
+  <td>Not implemented — architectural gap</td>
+  <td>Axis2 JSON-RPC is request/response only. The MCP protocol supports server-sent event streams for long-running operations. There is no streaming path.</td>
+</tr>
+<tr>
+  <td>Batch tool calls</td>
+  <td>Not implemented</td>
+  <td>Each Axis2 operation is a separate HTTP POST. There is no batch envelope.</td>
+</tr>
+<tr>
+  <td>Semantic query layer (filter/sort/fields)</td>
+  <td>Not applicable to Axis2</td>
+  <td>The Data API Interface Design describes a Tier 1 query semantic (<code>?filter=ops&gt;0.05&amp;sort=-ops&amp;fields=ticker,ops</code>). Axis2 JSON-RPC is operation-based — parameters are passed in <code>arg0</code>, not as query strings. <code>GetAssetCalculationsService</code> has its own parameter object but does not implement the Data API query syntax.</td>
+</tr>
+<tr>
+  <td>Natural key resolution (ticker → assetId)</td>
+  <td>Not in catalog — available via service</td>
+  <td>Ticker resolution exists as an Axis2 service (used by pyRapi's <code>search_assets</code> tool) but the catalog does not expose a dedicated resolution endpoint. Callers must know fund IDs and asset IDs or call the appropriate lookup service separately.</td>
+</tr>
+<tr>
+  <td>Cursor-based pagination</td>
+  <td>Not implemented</td>
+  <td>Axis2 operations return their full result sets. The Data API Interface Design describes cursor-based pagination stable across concurrent mutations; this is a Phase 1 Data API feature, not present in Axis2.</td>
+</tr>
+<tr>
+  <td>RFC 7807 Problem Details error format</td>
+  <td>Not implemented — architectural gap</td>
+  <td>Axis2 returns SOAP faults (XML envelope) even for JSON-RPC errors. The Data API Interface Design specifies RFC 7807 JSON problem details with per-field validation errors. The correlation ID in Axis2 faults is the only structured information in the error response.</td>
+</tr>
+<tr>
+  <td>API key management</td>
+  <td>Not applicable to Axis2</td>
+  <td>The Data API Vision describes API key + secret issuance with scopes, IP restrictions, and rotation. Axis2 uses email/password → Bearer token via <code>loginService</code> only. No API key model exists in Axis2.</td>
+</tr>
+<tr>
+  <td>Write operations (CRUD mutations)</td>
+  <td>Axis2 has write services; catalog supports them — but see notes</td>
+  <td>Axis2 write operations (position updates, scenario changes) exist as services and will appear in the catalog with <code>x-requiresAuth: true</code>. However, the Data API Vision explicitly reserves write operations for Phase 2 REST endpoints. MCP clients should treat Axis2 write operations as experimental until the canonical write path is available.</td>
+</tr>
+<tr>
+  <td>moneyball MCP integration</td>
+  <td>Not implemented</td>
+  <td><code>/home/robert/repos/moneyball/app/mcp/</code> contains only an empty <code>__init__.py</code>. No tools, no server, not connected to Axis2.</td>
+</tr>
+<tr>
+  <td>alpha-gateway-mcp routing to Axis2</td>
+  <td>Not implemented</td>
+  <td>The alpha-gateway-mcp routes intent to alpha-knowledge-mcp and alpha-collector-mcp. It does not route to Axis2 services. Adding Axis2 tools to the gateway's tool registry would enable natural language routing to RAPI operations.</td>
+</tr>
+</table>
+
+<!-- ============================================================ -->
+<a name="data_api_relationship"/>
+<h2>8. Relationship to the Data API Vision</h2>
+
+<p>The Data API Vision document (Carlos Carneiro) distinguishes two different MCP
+transport paths. Understanding which path a given MCP tool uses determines what
+limitations apply:</p>
+
+<table border="1">
+<tr><th>Aspect</th><th>Axis2 JSON-RPC (today)</th><th>Data API Phase 1 REST (Q2 2026)</th></tr>
+<tr><td>Protocol</td><td>Axis2 JSON-RPC envelope: <code>{"op":[{"arg0":{}}]}</code></td><td>Plain REST: <code>GET /api/v1/funds/{id}/assets?filter=...</code></td></tr>
+<tr><td>Tool definitions</td><td>Auto-generated from deployed Axis2 services</td><td>Auto-generated from OpenAPI 3.1 via springdoc-openapi</td></tr>
+<tr><td>Auth</td><td>email + password → Bearer token (loginService)</td><td>API key + secret → scoped JWT (new endpoint)</td></tr>
+<tr><td>Query semantics</td><td>Per-operation parameters in arg0</td><td>Tier 1: filter/sort/fields on every resource; Tier 2: /analytics/* endpoints</td></tr>
+<tr><td>Error format</td><td>SOAP fault with correlation ID UUID</td><td>RFC 7807 Problem Details (JSON, per-field)</td></tr>
+<tr><td>Pagination</td><td>Full result sets only</td><td>Cursor-based</td></tr>
+<tr><td>inputSchema</td><td>Always empty properties</td><td>Full JSON Schema from OpenAPI annotations</td></tr>
+<tr><td>Write support</td><td>Experimental (Axis2 write services exist)</td><td>Phase 2 (Q3 2026), after service extraction</td></tr>
+<tr><td>Calculations</td><td>Via RAPI Axis2 services (GetAssetCalculationsService)</td><td>Via RAPI proxy (Phase 1) or direct Spring injection (Phase 2)</td></tr>
+<tr><td>Field discovery</td><td>950+ fields via GetAssetFieldsService</td><td>Via /api/v1/fields endpoint (Phase 1)</td></tr>
+</table>
+
+<p><strong>Key architectural quote</strong> from the Data API Vision:</p>
+
+<blockquote>
+<p>"The Data API bypasses Axis2 for calculation reads. RAPI's Axis2 JSON-RPC was built
+for internal server-to-server calculation orchestration — and it continues serving that
+role for existing callers. The Data API consumes the same Spring service beans
+(AssetCalculationsOperations) directly via dependency injection, avoiding the Axis2
+protocol layer entirely."</p>
+</blockquote>
+
+<p>This means:</p>
+<ul>
+<li><strong>Build read MCP tools now</strong> — Portfolio screening, field discovery,
+    asset lookup, and historical reads all work today against Axis2 RAPI services. The
+    rapi-mcp server's 13 tools demonstrate the pattern at production scale.</li>
+<li><strong>Write MCP tools wait</strong> — No canonical REST write path exists yet.
+    Do not build write tools against Axis2 write operations as their behaviour will
+    diverge from the Data API write contract in Phase 2.</li>
+<li><strong>The Axis2 catalog bridges the gap</strong> — The <code>/openapi-mcp.json</code>
+    endpoint provides MCP-ready tool discovery for existing RAPI services during the
+    period before the Data API REST layer is available. It is not a replacement for the
+    Data API tool catalog; it is a stopgap for current Axis2 services.</li>
+</ul>
+
+<!-- ============================================================ -->
+<a name="python_compat"/>
+<h2>9. Python MCP Compatibility Notes</h2>
+
+<p>Quick reference for each Python project and how it interacts with Axis2:</p>
+
+<h3>rapi-mcp (13 tools — /home/robert/repos/modelcontextprotocol/servers/rapi-mcp/)</h3>
+<ul>
+<li>Calls Axis2 RAPI services via <code>httpx</code> with Bearer token auth.</li>
+<li>Defines full Pydantic <code>inputSchema</code> per tool (Axis2 catalog cannot do this).</li>
+<li>Uses curated natural language descriptions (Axis2 catalog uses "ServiceName: opName").</li>
+<li>Field optimization: 24 of 184 fields selected by default (<code>get_fund_assets</code>).</li>
+<li><strong>TODO in models.py</strong>: Hardcoded auth and fund selection values — needs env-var wiring.</li>
+<li>The Axis2 MCP catalog's <code>_meta.axis2JsonRpcFormat</code> matches exactly what this
+    server's <code>RAPIClient</code> sends.</li>
+</ul>
+
+<h3>pyRapi (/home/robert/repos/pyRapi/)</h3>
+<ul>
+<li>Reference implementation of the Axis2 auth flow. <code>test_auth.py</code> is the
+    canonical source of truth for the payload format (<code>{"doLogin":[{"arg0":{...}}]}</code>).</li>
+<li>Token stored in <code>~/.pyrapi/auth_token</code> with mode 0600; or via
+    <code>AT_RAPI_MCP_AUTH_TOKEN</code> env var.</li>
+<li>The Axis2 MCP catalog's two-phase auth documentation mirrors this exactly.</li>
+</ul>
+
+<h3>internal-alpha-theory-mcp (3 servers — /home/robert/repos/internal-alpha-theory-mcp/)</h3>
+<ul>
+<li><strong>alpha-knowledge-mcp</strong>: 27 tools, SQLite backend, glossary/client/product/lifecycle data.
+    Does not call Axis2. Candidate for gateway routing to Axis2 for live portfolio data.</li>
+<li><strong>alpha-collector-mcp</strong>: 9 tools, hybrid SQLite + S3 object store with taxonomy paths.
+    Does not call Axis2.</li>
+<li><strong>alpha-gateway-mcp</strong>: Intent routing to knowledge and collector MCPs.
+    Does not currently route to Axis2. Adding Axis2 tools to its <code>RegisteredTool</code>
+    registry would enable natural language routing to RAPI portfolio operations.</li>
+<li>Response pattern: <code>ToolResponse(success, data, metadata, message)</code> and
+    <code>ErrorResponse(error, error_type, suggestions)</code>. Recommended for any new
+    MCP server that wraps Axis2.</li>
+</ul>
+
+<h3>atds_research (/home/robert/repos/atds_research/) and hermes_at (/home/robert/repos/hermes_at/)</h3>
+<ul>
+<li>Research/prototype repos. MCP integration not found in these repositories as of the
+    time this document was written.</li>
+</ul>
+
+<h3>moneyball (/home/robert/repos/moneyball/)</h3>
+<ul>
+<li>MCP directory exists (<code>app/mcp/__init__.py</code>) but is empty.
+    No tools implemented.</li>
+</ul>
+
+<h3>Minimal Axis2-Aware MCP Server Template</h3>
+
+<pre>
+import httpx
+import json
+from mcp.server import Server
+from mcp.server.stdio import stdio_server
+from mcp.types import Tool, TextContent
+
+BASE_URL = "https://your-axis2-server/services"
+server  = Server("axis2-mcp")
+_token  = None
+
+async def login(email: str, password: str) -&gt; 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) -&gt; 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() -&gt; 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) -&gt; list[TextContent]:
+    # Resolve service name from catalog endpoint field
+    # then delegate to call_service()
+    ...
+</pre>
+
+<p><strong>Note on catalog-driven tool lists:</strong> Fetching the catalog at
+<code>list_tools()</code> time is correct because the catalog has
+<code>Cache-Control: no-cache</code>. The tool list is always current without requiring
+an MCP server restart on Axis2 redeployment.</p>
+
+</body>
+</html>
diff --git a/src/site/xdoc/docs/toc.xml b/src/site/xdoc/docs/toc.xml
index 3415f8743c..480f7f43ba 100644
--- a/src/site/xdoc/docs/toc.xml
+++ b/src/site/xdoc/docs/toc.xml
@@ -145,6 +145,19 @@ Support</a></li>
         <li><a href="openapi-rest-advanced-http2-userguide.html">HTTP/2 Performance Integration</a></li>
     </ul>
     </li>
+    <li><strong>23.4 <a href="json-rpc-mcp-guide.html">JSON-RPC MCP Integration Guide</a></strong>
+    <ul>
+        <li><a href="json-rpc-mcp-guide.html#mcp_catalog">MCP Catalog Endpoint (/openapi-mcp.json)</a></li>
+        <li><a href="json-rpc-mcp-guide.html#catalog_schema">Catalog Schema: _meta and per-tool fields</a></li>
+        <li><a href="json-rpc-mcp-guide.html#envelope">Axis2 JSON-RPC Envelope Format</a></li>
+        <li><a href="json-rpc-mcp-guide.html#auth">Two-Phase Bearer Token Authentication</a></li>
+        <li><a href="json-rpc-mcp-guide.html#error_handling">Error Handling: Correlation ID Pattern</a></li>
+        <li><a href="json-rpc-mcp-guide.html#tested_features">Unit Test Feature Coverage</a></li>
+        <li><a href="json-rpc-mcp-guide.html#not_implemented">Not Implemented / Limitations</a></li>
+        <li><a href="json-rpc-mcp-guide.html#data_api_relationship">Relationship to Data API Vision</a></li>
+        <li><a href="json-rpc-mcp-guide.html#python_compat">Python MCP Compatibility Notes</a></li>
+    </ul>
+    </li>
 </ul>
 </li>
 <li><a href="corba-deployer.html">Exposing CORBA Services as Web Services</a></li>