type: architecture created: 2026-04-06 last-verified: 2026-04-09 status: Active — Java A1/A2/A3 complete, C1 complete, mcpInputSchema deployed

MCP Support for Apache Axis2/C

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.


Why C Port Comes After Java

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:

  • Simpler wire protocol: JSON lines over stdio or HTTP. No binary framing, no HPACK compression, no stream multiplexing.
  • C already has the dispatch layer: 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.
  • stdio in C is trivial: fgets(stdin) / fprintf(stdout). In Java this requires thread management and stream handling. In C it is a 30-line loop.
  • No JVM startup: stdio-based MCP clients start MCP servers as subprocesses. JVM cold-start is 1–3 seconds. A C binary starts in under 100ms. For stdio MCP (process-per-client), this is visible every time the MCP client opens.
  • Port is mechanical once Java proves the protocol: After Axis2/Java validates the 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.


What the Two Runtimes Provide

Axis2/Java MCPAxis2/C MCP
TargetEnterprise service busEdge / embedded / mobile
HardwareWildFly, Tomcat, cloudRaspberry Pi, Android, IoT
JVM startup1–3 secondsN/A — native binary
Memory512MB–2GB JVM heap~30 MB total process
Tool catalogaxis2-openapi annotation reflectionStatic mcp_tools[] struct array
DemoClaude calling springbootdemo-tomcat11Claude calling financial calculations on a $20 phone
Apache contributionFirst Java framework with native MCP transportFirst C middleware with MCP — nothing else exists

Same MCP client. Same protocol. Full hardware spectrum: enterprise server to $20 Android phone.


Current State (2026-04-09)

What exists in Axis2/C today

ArtifactStatus
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

What exists in Axis2/Java (the reference)

ArtifactStatus
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.


Track C — C Implementation Plan

C1: stdio MCP Mode in financial_benchmark_service.c

When: ✅ 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.


C2: HTTP/SSE MCP Endpoint

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/sseContent-Type: text/event-stream; server pushes data: {...}\n\n per response

SSE 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.


C3: axis2-transport-mcp C Module (Long Term — Apache Graduation)

Full port of the Java axis2-transport-mcp module to C's transport interfaces:

  • Implements axis2_transport_receiver_t (TransportListener equivalent)
  • Implements axis2_transport_sender_t (TransportSender equivalent)
  • Translates MCP JSON-RPC 2.0 messages ↔ axis2_msg_ctx_t (MessageContext)
  • Deployable as a proper Axis2/C module (.mar file), not embedded in a service

This 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.


Feature Comparison: C vs Java MCP

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.

FeatureAxis2/CAxis2/Java
initialize / tools/list / tools/call
stdio transport✅ Native (fgets/fprintf)✅ Bridge JAR
inputSchemaStatic struct array (compile-time)Auto-generated from Java types + manual override
Startup time< 100 ms1–3 sec (JVM cold-start)
Memory footprint~30 MB512 MB–2 GB JVM heap
Monte Carlo throughput~150K sims/sec~73K sims/sec
HTTP/SSE transportPlanned (C2)Planned (A4)
Spring Boot integrationN/A✅ Starter with autoconfiguration
Auto-schema from typesN/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.


Key Design Decisions for C Port

Why static tool catalog, not services.xml reflection

C has no runtime reflection. Options are:

  1. Static struct array (chosen for C1/C2) — zero overhead, explicit, easily audited
  2. Parse services.xml at startup — more Axis2-idiomatic, allows adding tools without recompiling, required for C3 as a proper Axis2 module

C1 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.


Relationship to Java Reference Implementation

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.


Demo Deliverable

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.


Files to Create (C Port)

FilePurpose
samples/user_guide/financial-benchmark-service/src/finbench_mcp.cstdio loop, tools/list, initialize, dispatch
samples/user_guide/financial-benchmark-service/src/finbench_mcp.hTool catalog struct, handler type, public API
samples/user_guide/financial-benchmark-service/src/finbench_mcp_sse.cHTTP/SSE transport (C2)
modules/transport-mcp/Full Axis2/C transport module (C3)
docs/userguide/mcp-userguide.mdUser-facing setup guide