Summary: Axis2/C gains MCP (Model Context Protocol) support by porting from the Axis2/Java reference implementation — the same pattern used successfully for HTTP/2. The C implementation targets hardware where Java cannot run: edge devices, embedded systems, Android phones. One MCP client (stdio or HTTP) connects to both runtimes via identical protocol. One AI protocol, the full hardware spectrum.
MCP is JSON-RPC 2.0. Three required methods: initialize, tools/list, tools/call. Two transports: stdio (simple, process-per-client) and HTTP/SSE (persistent server). Existing Axis2/C infrastructure — json-c, Apache httpd, the service dispatch table — covers everything needed. No new dependencies.
The HTTP/2 feature followed this sequence: Java reference implementation → C port. It worked because the wire protocol is well-specified and both runtimes share the same transport abstraction (TransportListener/TransportSender in Java; axis2_transport_receiver_t/axis2_transport_sender_t in C).
MCP is a better candidate than HTTP/2 was:
route_operation() in financial_benchmark_service.c already does what tools/call needs — parse action name, dispatch to handler, return JSON. The JSON-RPC 2.0 wrapper is the only addition.fgets(stdin) / fprintf(stdout). In Java this requires thread management and stream handling. In C it is a 30-line loop.initialize/tools/list/tools/call handshake against a compliant MCP client, the C port translates proven behavior rather than discovering it.Estimated new C code: ~500 lines for stdio, ~800 for HTTP/SSE. The HTTP/2 port was ~5,000 lines. MCP is lighter because 80% of the dispatch work already exists.
| Axis2/Java MCP | Axis2/C MCP | |
|---|---|---|
| Target | Enterprise service bus | Edge / embedded / mobile |
| Hardware | WildFly, Tomcat, cloud | Raspberry Pi, Android, IoT |
| JVM startup | 1–3 seconds | N/A — native binary |
| Memory | 512MB–2GB JVM heap | ~30 MB total process |
| Tool catalog | axis2-openapi annotation reflection | Static mcp_tools[] struct array |
| Demo | Claude calling springbootdemo-tomcat11 | Claude calling financial calculations on a $20 phone |
| Apache contribution | First Java framework with native MCP transport | First C middleware with MCP — nothing else exists |
Same MCP client. Same protocol. Full hardware spectrum: enterprise server to $20 Android phone.
| Artifact | Status |
|---|---|
| json-c integration | ✅ In use throughout financial benchmark service |
HTTP/2 transport (modules/transport-h2) | ✅ Working, tested |
financial_benchmark_service.c | ✅ portfolioVariance, monteCarlo VaR, scenarioAnalysis — all validated |
Service dispatch table (route_operation()) | ✅ Reusable for MCP tools/call |
| Apache httpd handler | ✅ Running, mod_axis2 loaded |
axis2-transport-mcp C module | ❌ Not started |
| stdio MCP mode | ✅ Done (C1) — finbench_mcp.c + finbench_mcp_main.c |
| HTTP/SSE MCP endpoint | ❌ Not started |
| Artifact | Status |
|---|---|
axis2-openapi module | ✅ Serves /openapi.json, /openapi.yaml, /swagger-ui |
springbootdemo-tomcat11 | ✅ Reference implementation, Java 25 + Tomcat 11 |
/openapi-mcp.json endpoint | ✅ Done (A1) |
axis2-mcp-bridge stdio JAR | ✅ Done (A2) — with mTLS (port 8443, IoT CA) |
| A3 end-to-end validation | ✅ Done — MCP client → bridge → mTLS → BigDataH2Service confirmed |
axis2-transport-mcp Java native | ❌ Not started (B1) |
C1 is now unblocked. Java A2 was the gate: the JSON-RPC 2.0 handshake (initialize / tools/list / tools/call) is validated against a compliant MCP client. The C port translates proven behavior rather than discovering it.
The Java bridge uses no MCP SDK — just Jackson + Java stdlib HttpClient. The C port similarly needs no library beyond json-c, which is already in use throughout financial_benchmark_service.c.
financial_benchmark_service.cWhen: ✅ Done.
How it works: The binary detects whether stdin is a pipe (MCP client attached) or a tty (interactive/httpd mode) and switches behavior accordingly:
/* In main entry point or service init */ if (!isatty(fileno(stdin))) { finbench_run_mcp_stdio(env); return AXIS2_SUCCESS; }
The stdio loop is JSON-RPC 2.0 framing around the existing dispatch:
static void finbench_run_mcp_stdio(const axutil_env_t *env) { char line_buf[65536]; while (fgets(line_buf, sizeof(line_buf), stdin)) { json_object *req = json_tokener_parse(line_buf); if (!req) continue; const char *method = /* json_object_get_string(req["method"]) */; json_object *id_obj = /* req["id"] */; if (strcmp(method, "initialize") == 0) finbench_mcp_write_result(id_obj, finbench_mcp_initialize()); else if (strcmp(method, "tools/list") == 0) finbench_mcp_write_result(id_obj, finbench_mcp_tools_list()); else if (strcmp(method, "tools/call") == 0) finbench_mcp_write_result(id_obj, finbench_mcp_dispatch(env, req)); else finbench_mcp_write_error(id_obj, -32601, "Method not found"); json_object_put(req); } }
Tool catalog: Static struct array — no reflection, no overhead. Derived from the same operation names as the HTTP/2 JSON dispatch:
typedef axis2_char_t* (*finbench_mcp_handler_t)( const axutil_env_t *env, json_object *arguments); typedef struct { const char *name; const char *description; const char *input_schema_json; /* JSON string, inlined or from services.xml */ finbench_mcp_handler_t handler; } finbench_mcp_tool_t; static const finbench_mcp_tool_t finbench_mcp_tools[] = { { "portfolioVariance", "Calculate portfolio variance using O(n^2) covariance matrix " "multiplication. Returns variance, volatility, annualized volatility, " "and timing in microseconds.", "{\"type\":\"object\",\"properties\":{" "\"n_assets\":{\"type\":\"integer\"}," "\"weights\":{\"type\":\"array\",\"items\":{\"type\":\"number\"}}," "\"covariance_matrix\":{\"type\":\"array\",\"items\":{\"type\":\"number\"}}" "},\"required\":[\"n_assets\",\"weights\",\"covariance_matrix\"]}", finbench_portfolio_variance_mcp_handler }, { "monteCarlo", "Monte Carlo Value at Risk simulation using Geometric Brownian Motion. " "Returns VaR at 95%% and 99%% confidence, max drawdown, probability of " "profit, and simulations-per-second throughput.", "{\"type\":\"object\",\"properties\":{" "\"n_simulations\":{\"type\":\"integer\"}," "\"n_periods\":{\"type\":\"integer\"}," "\"initial_value\":{\"type\":\"number\"}," "\"expected_return\":{\"type\":\"number\"}," "\"volatility\":{\"type\":\"number\"}" "},\"required\":[]}", finbench_monte_carlo_mcp_handler }, { "scenarioAnalysis", "Scenario analysis with probability-weighted expected return, " "upside/downside calculation, and O(n) vs O(1) hash lookup benchmark. " "Validates that scenario probabilities sum to 1.0.", "{\"type\":\"object\",\"properties\":{" "\"assets\":{\"type\":\"array\"}" "},\"required\":[\"assets\"]}", finbench_scenario_mcp_handler }, { NULL, NULL, NULL, NULL } /* sentinel */ };
Each _mcp_handler is a thin wrapper: extract arguments from json_object *, call the existing finbench_*_json_only() function, return the result string.
Example MCP client config (stdio transport; the demo deliverable):
{ "mcpServers": { "axis2c-financial": { "command": "/usr/local/axis2c/bin/financial-benchmark-mcp", "args": [] } } }
Effort: ~500 lines. Two to three days after Java A2 is validated.
When: After C1 stdio works. Enables multiple Claude sessions sharing one persistent C server over HTTP/2.
Transport: Reuses the existing Apache httpd handler. New routes:
POST /mcp — JSON-RPC 2.0 request (same dispatch as stdio, different framing)GET /mcp/sse — Content-Type: text/event-stream; server pushes data: {...}\n\n per responseSSE in C via Apache httpd:
/* Handler for GET /mcp/sse */ ap_set_content_type(r, "text/event-stream"); apr_table_set(r->headers_out, "Cache-Control", "no-cache"); apr_table_set(r->headers_out, "Connection", "keep-alive"); ap_rflush(r); /* Response loop — write MCP responses as SSE events */ while (session_active) { json_object *mcp_response = /* next result */; const char *json_str = json_object_to_json_string(mcp_response); ap_rprintf(r, "data: %s\n\n", json_str); ap_rflush(r); json_object_put(mcp_response); }
Effort: ~800 lines including session management. One week after C1.
axis2-transport-mcp C Module (Long Term — Apache Graduation)Full port of the Java axis2-transport-mcp module to C's transport interfaces:
axis2_transport_receiver_t (TransportListener equivalent)axis2_transport_sender_t (TransportSender equivalent)axis2_msg_ctx_t (MessageContext).mar file), not embedded in a serviceThis is the Apache-grade contribution — the same architectural position in C as axis2-transport-mcp in Java. Targets the Axis2/C release after the Java transport is accepted by the Apache community.
Both implementations cover the MCP essentials — the three required methods (initialize, tools/list, tools/call) over stdio with full inputSchema. An AI assistant configured with either backend gets identical capabilities.
| Feature | Axis2/C | Axis2/Java |
|---|---|---|
initialize / tools/list / tools/call | ✅ | ✅ |
| stdio transport | ✅ Native (fgets/fprintf) | ✅ Bridge JAR |
| inputSchema | Static struct array (compile-time) | Auto-generated from Java types + manual override |
| Startup time | < 100 ms | 1–3 sec (JVM cold-start) |
| Memory footprint | ~30 MB | 512 MB–2 GB JVM heap |
| Monte Carlo throughput | ~150K sims/sec | ~73K sims/sec |
| HTTP/SSE transport | Planned (C2) | Planned (A4) |
| Spring Boot integration | N/A | ✅ Starter with autoconfiguration |
| Auto-schema from types | N/A (C has no reflection) | ✅ Java POJO introspection |
| Android / edge / IoT | ✅ Native binary | ❌ JVM required |
The approaches differ by design, not by omission. Java‘s auto-schema generation uses runtime reflection — a language feature C does not have. C’s static tool catalog is the idiomatic equivalent: explicit, zero-overhead, compile-time checked. Both produce the same MCP catalog JSON that AI assistants consume.
Where the implementations compete is on the deployment edge: C runs where Java cannot (Android phones, IoT gateways, embedded systems) and starts instantly for stdio MCP (where the MCP client spawns a subprocess per session). Java runs where enterprise infrastructure already exists (WildFly, Tomcat, Spring Boot) with richer integration tooling.
Why static tool catalog, not services.xml reflection
C has no runtime reflection. Options are:
services.xml at startup — more Axis2-idiomatic, allows adding tools without recompiling, required for C3 as a proper Axis2 moduleC1 and C2 use option 1 because the financial benchmark service is a self-contained demo. C3 uses option 2 because a general-purpose transport module must discover services dynamically.
Why isatty() for mode detection
The same binary serves both HTTP/2 JSON (via Apache httpd as a shared library) and MCP stdio (as a subprocess launched by an MCP client). The isatty() check is the POSIX-standard way to detect whether stdin is connected to an interactive terminal or a pipe. When an MCP client launches the binary, stdin is a pipe — MCP mode. When httpd loads the shared library, stdin is not opened at all.
In production the binary and the shared library would be separate build targets from the same source. The isatty() check handles the development/demo case where one binary does both.
Why input_schema_json is inlined as a string
Avoids a JSON-within-JSON parse at startup and keeps the tool definition co-located with its handler. For C3, the schema would be loaded from services.xml or a companion .json file in the service directory, following the pattern the Axis2/C service registry already uses for WSDL.
Java A1 (/openapi-mcp.json) ✅ done — defines MCP tool schema format
↓
Java A2 (axis2-mcp-bridge stdio) ✅ done — validates JSON-RPC 2.0 handshake
↓
Java B1 (axis2-transport-mcp) ← proves native transport pattern
↓
C1 (stdio in financial-benchmark) ✅ done — finbench_mcp.c, same protocol, static tool catalog
↓
C2 (HTTP/SSE endpoint) ← port: same as Java A4/B2
↓
C3 (axis2-transport-mcp C module) ← port: same transport abstraction in C
The Java schemas defined in axis2-openapi (/openapi-mcp.json) are the canonical inputSchema for each tool. The C static struct array uses the same JSON schema strings, ensuring Claude sees identical tool definitions regardless of which runtime is serving them.
When C1 and Java B1 are both complete, the demonstration is:
MCP client (stdio)
├── axis2c-financial (stdio, $20 Android phone)
│ portfolioVariance — 500 assets in 5ms, 30MB memory
│ monteCarlo VaR — 10,000 GBM simulations in 100ms
│ scenarioAnalysis — O(n) vs O(1) hash, 2,000 assets
│
└── axis2-java-services (HTTPS/HTTP2, enterprise server)
FinancialBenchmarkService — same calculations, JVM runtime
BigDataH2Service — HTTP/2 streaming, large payloads
Same Claude session. Same MCP protocol. One C binary on a $20 phone. One Java service on an enterprise server. The protocol unifies them.
| File | Purpose |
|---|---|
samples/user_guide/financial-benchmark-service/src/finbench_mcp.c | stdio loop, tools/list, initialize, dispatch |
samples/user_guide/financial-benchmark-service/src/finbench_mcp.h | Tool catalog struct, handler type, public API |
samples/user_guide/financial-benchmark-service/src/finbench_mcp_sse.c | HTTP/SSE transport (C2) |
modules/transport-mcp/ | Full Axis2/C transport module (C3) |
docs/userguide/mcp-userguide.md | User-facing setup guide |