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 5530655908e389a8e5a1aed59969d5bb0f695186 Author: Robert Lazarski <[email protected]> AuthorDate: Sat Apr 11 11:48:37 2026 -1000 Document MCP limitations: progress, stdio-only, auto-schema Adds "Known Limitations" section to mcp-architecture.md: - Progress notifications: blocked by bridge's blocking HTTP proxy architecture, not by stdio transport. Documents the three possible approaches (polling, streaming, callbacks) and notes that financial benchmarks complete within interactive time budgets (~1.4s for 100K Monte Carlo). Points to Axis2/C (2-3x faster) for latency-critical workloads with identical MCP tool schemas. - Stdio-only transport: HTTP/SSE (A4) deferred. Covers Claude Desktop, Cursor, and Claude Code use cases. Contributions welcome. - Auto-schema ServiceClass limitation: introspection requires explicit ServiceClass parameter. Spring-bean-only services need hand-written mcpInputSchema. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --- .../apache/axis2/openapi/OpenApiSpecGenerator.java | 52 ++++++++++++++++- src/site/markdown/docs/mcp-architecture.md | 67 ++++++++++++++++++++++ 2 files changed, 116 insertions(+), 3 deletions(-) diff --git a/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java b/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java index 5c6fbc25ab..18d69ae815 100644 --- a/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java +++ b/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java @@ -604,11 +604,57 @@ public class OpenApiSpecGenerator { */ private com.fasterxml.jackson.databind.node.ObjectNode generateSchemaFromServiceClass( AxisService service, String operationName) { + return generateSchemaFromServiceClass(service, operationName, null); + } + + private com.fasterxml.jackson.databind.node.ObjectNode generateSchemaFromServiceClass( + AxisService service, String operationName, HttpServletRequest request) { try { + Class<?> serviceClass = null; + + // Try 1: explicit ServiceClass parameter String className = getServiceClassName(service); - if (className == null) return null; + if (className != null) { + serviceClass = Thread.currentThread().getContextClassLoader().loadClass(className); + } + + // Try 2: resolve Spring bean class from SpringBeanName via WebApplicationContext. + // Uses reflection to avoid a compile-time dependency on Spring Framework — + // the openapi module must work without Spring on the classpath. + // Mirrors the lookup in SpringServletContextObjectSupplier which uses + // WebApplicationContextUtils.getWebApplicationContext(servletContext). + if (serviceClass == null && request != null) { + try { + String beanName = null; + if (service.getParameter("SpringBeanName") != null) { + beanName = (String) service.getParameter("SpringBeanName").getValue(); + } + if (beanName != null) { + jakarta.servlet.ServletContext sc = request.getServletContext(); + // Call WebApplicationContextUtils.getWebApplicationContext(sc) via reflection + Class<?> wacUtils = Class.forName( + "org.springframework.web.context.support.WebApplicationContextUtils"); + java.lang.reflect.Method getWac = wacUtils.getMethod( + "getWebApplicationContext", jakarta.servlet.ServletContext.class); + Object ctx = getWac.invoke(null, sc); + if (ctx != null) { + java.lang.reflect.Method getBean = ctx.getClass().getMethod( + "getBean", String.class); + Object bean = getBean.invoke(ctx, beanName); + if (bean != null) { + serviceClass = bean.getClass(); + log.debug("[MCP] Resolved Spring bean '" + beanName + + "' -> " + serviceClass.getName()); + } + } + } + } catch (Exception springEx) { + log.debug("[MCP] Could not resolve Spring bean for " + + service.getName() + ": " + springEx.getMessage()); + } + } - Class<?> serviceClass = Thread.currentThread().getContextClassLoader().loadClass(className); + if (serviceClass == null) return null; java.lang.reflect.Method targetMethod = null; for (java.lang.reflect.Method m : serviceClass.getMethods()) { if (m.getName().equals(operationName) && m.getParameterCount() == 1) { @@ -938,7 +984,7 @@ public class OpenApiSpecGenerator { // if introspection fails (e.g., no ServiceClass parameter, // method not found, or primitive parameters). com.fasterxml.jackson.databind.node.ObjectNode schema = - generateSchemaFromServiceClass(service, opName); + generateSchemaFromServiceClass(service, opName, request); if (schema != null) { toolNode.set("inputSchema", schema); log.debug("[MCP] Auto-generated inputSchema for " diff --git a/src/site/markdown/docs/mcp-architecture.md b/src/site/markdown/docs/mcp-architecture.md index d6ef60edd0..f460aa0463 100644 --- a/src/site/markdown/docs/mcp-architecture.md +++ b/src/site/markdown/docs/mcp-architecture.md @@ -360,6 +360,73 @@ MCP and OpenAPI support needs validation across the full container/JDK matrix: --- +## Known Limitations + +### No progress notifications during long-running operations + +The MCP spec supports progress notifications — JSON-RPC messages sent from the +server to the client while a tool call is executing. This is useful for +operations like Monte Carlo simulations (100K+ paths can take 1-14 seconds) +where the AI assistant could display incremental status. + +**The limitation is architectural, not transport-related.** The MCP stdio +transport supports progress notifications natively (they are regular JSON-RPC +notifications on stdout). The constraint is the bridge's HTTP proxy pattern: + +``` +Claude Desktop ←stdio→ axis2-mcp-bridge ←blocking HTTP POST→ Axis2 service +``` + +The bridge sends one HTTP POST to Axis2 and blocks until the full response +arrives. During a long computation, the bridge has no way to obtain intermediate +status from the service. Adding progress support would require one of: + +- A polling side-channel (bridge polls a status endpoint while the main call runs) +- HTTP chunked/streaming responses from Axis2 +- A callback mechanism from the service to the bridge + +These are non-trivial changes to the Axis2 response pipeline and the bridge +architecture. + +**Practical impact:** The financial benchmark services complete well within +interactive time budgets — portfolio variance in under 1 ms, Monte Carlo +100K paths in ~1.4 seconds on Java. For workloads where even this latency +is a concern, the same financial benchmark operations are available on +[Axis2/C](http://axis.apache.org/axis2/c/), which runs 2-3x faster: +Monte Carlo 100K paths in ~0.7 seconds, 500-asset portfolio variance in +232 μs vs Java's 660 μs (see [performance comparison](mcp-examples.md#full-performance-summary)). +Both implementations expose identical MCP tool schemas — an AI assistant +configured with either backend gets the same financial capabilities. + +### Stdio transport only (HTTP/SSE deferred) + +The MCP bridge currently supports stdio transport only (Claude Desktop +subprocess model). HTTP/SSE transport (A4) — which would enable Claude +API tool use, multi-user bridge sharing, and remote MCP clients — is +deferred. Contributions welcome. + +### Auto-generated inputSchema from Java types + +When `mcpInputSchema` is not set in `services.xml`, the MCP catalog +generator auto-generates a JSON Schema by introspecting the Java service +method's parameter type. Two resolution strategies are used: + +1. **`ServiceClass` parameter** — the class is loaded directly from the + classpath. Works immediately on the first catalog request. +2. **`SpringBeanName` parameter** — the bean is resolved from the Spring + `WebApplicationContext` via reflection (no compile-time Spring dependency + in the OpenAPI module). Works after Spring initialization is complete. + +Supported types: `int`/`long` → `integer`, `double`/`float` → `number`, +`boolean` → `boolean`, `String` → `string`, arrays (including nested +`double[][]`), `List<T>`, and POJOs → `object`. + +Explicit `mcpInputSchema` in `services.xml` always takes precedence — +use it when you need `required` fields, `minimum`/`maximum` constraints, +`default` values, or `description` text that reflection cannot provide. + +--- + ## Dependencies and Build Track A (`axis2-mcp-bridge`) requires:
