--- name: langchain4j-mcp-client description: "Use when reviewing LangChain4j MCP client integration details (transport, lifecycle, McpToolProvider mapping, protocol semantics, errors, observability). Do not invoke for general LangChain4j AI-service review (use langchain4j-review), MCP server design/implementation (use langchain4j-mcp-server), or greenfield agent architecture (use langchain4j-agent-design)." allowed-tools: Read, Grep, Glob, Bash(git diff *), Bash(git log *), Bash(git show *) --- # LangChain4j MCP Client Review Review Git-tracked changes (or the specified file/directory) across **8 dimensions**. For each finding, cite the file path and line number, classify severity (critical / warning / suggestion), and provide the fix. > **Version baseline**: LangChain4j `1.18.1` / `1.18.1-beta28`, MCP protocol `2025-11-25`, Java 17+ ## Scope If `$ARGUMENTS` is provided, review only those files. Otherwise, review recent changes: ``` !`git diff --name-only HEAD -- '*.java' '*.kt' 2>/dev/null | head -20 || echo "no changes"` ``` ## Review Dimensions ### 1. Transport Selection & Configuration - **`StreamableHttpMcpTransport`** for production remote servers — not the deprecated `HttpMcpTransport` - `HttpMcpTransport` is deprecated since `1.4.0-beta10` (uses OkHttp/SSE) — migrate to Streamable HTTP - `StdioMcpTransport` for local subprocess servers only — no reconnection support - `WebSocketMcpTransport` only for Quarkus MCP Server compatibility — not guaranteed with other servers - `subsidiaryChannel(true)` on Streamable HTTP for server-initiated notifications (tool list changes, log messages) - `sslContext(...)` for TLS — never skip TLS in production for HTTP/WebSocket transports - `McpHeadersSupplier` for per-call dynamic headers (auth tokens) — not static `customHeaders` when tokens rotate - `logRequests(true)` / `logResponses(true)` for debugging — disable in production (sensitive data in tool arguments) - `setHttpVersion1_1()` only when HTTP/2 is unsupported by the server or intermediate proxy - Stdio transport: MCP server must log to `System.err`, **never `System.out`** — stdout corrupts JSON-RPC - **`DockerMcpTransport`** (module: `langchain4j-mcp-docker`, since 1.13.0) — container-isolated execution (safer than `StdioMcpTransport` in restricted environments); supports Podman, Docker Desktop, remote Docker sockets; review criteria: verify Docker/Podman daemon available in runtime environment and container image is accessible - Review question: when `DockerMcpTransport` is used, confirm Docker/Podman is available in the deployment environment - `StreamableHttpMcpTransport` exposes the negotiated session ID in 1.14.0; treat it as sensitive correlation metadata and do not log it with PII/secrets ### 2. Client Lifecycle & Builder - Always set `key("descriptive-name")` — default random UUID makes filtering, logging, and `get_resource` difficult - Set meaningful `toolExecutionTimeout` — zero disables timeout entirely, risking indefinite blocking - Default timeouts are sensible: `initializationTimeout(30s)`, `toolExecutionTimeout(60s)`, `pingTimeout(10s)` - `autoHealthCheck(true)` (default) with `autoHealthCheckInterval(30s)` — disable only for short-lived clients - `reconnectInterval(Duration)` controls delay before reconnection after transport failure - `cacheToolList(true)` (default) is correct for stable servers; set `false` when tools change without `notifications/tools/list_changed` - `evictToolListCache()` for manual cache invalidation when needed - **Always close `McpClient`** — implements `AutoCloseable`. Leaks subprocesses (stdio), HTTP connections, or WebSocket connections - Use try-with-resources or `@PreDestroy` / `DisposableBean` in Spring Boot - **`_meta` field propagation (1.13.0+):** configure `metaSupplier` when trace context propagation is needed; verify `McpMetaSupplier` returns current trace/span IDs, not stale or static values ### 3. Tool Execution Chain (structuredContent vs content) - **Critical**: LangChain4j always prefers `structuredContent` over `content` when both are present - When `structuredContent` exists: `resultText()` = `JSON.stringify(structuredContent)`, `content` is completely ignored - When only `content` exists: `resultText()` = concatenated `content[].text` joined by `\n` - Only `type: "text"` content blocks are supported — image, audio, resource link blocks throw `RuntimeException` - The LLM receives `ToolExecutionResultMessage.text()` which is populated from `resultText()` - `ToolExecutionResult.result()` (parsed Object) is only populated for `structuredContent` paths — `null` for content-only - `ToolExecutionResult.attributes()` are for `ChatMemory` persistence only — **not sent to the LLM** - Large `structuredContent` payloads waste tokens — design schemas to be concise or intercept with `toolWrapper` ### 4. McpToolProvider Configuration - `mcpClients(...)` accepts varargs or List — multiple MCP clients are queried in order - `toolNameMapper((client, spec) -> client.key() + "_" + spec.name())` — prevent name collisions from multiple servers - `toolNameMapper` and `toolSpecificationMapper` are mutually exclusive — setting both throws `IllegalArgumentException` - `filterToolNames(...)` for simple include-list; `filter(BiPredicate)` for complex filtering logic - **Filter is applied before mapping** — filter predicates see the original server tool name - `toolWrapper(executor -> ...)` to intercept tool execution for tracing, metrics, content transformation, or LLM input control - 1.14.0 adds an MCP tool result extractor hook; prefer it over ad hoc parsing when controlling what reaches the LLM - `failIfOneServerFails(false)` (default) for resilience — set `true` only when all servers are critical - `alwaysVisibleToolNames(...)` for tools that must remain in the LLM context when `ToolSearchStrategy` is active - `resourcesAsToolsPresenter(...)` to expose MCP resources as synthetic `list_resources` / `get_resource` tools - Dynamic management: `addMcpClient()`, `removeMcpClient()`, `addFilter()`, `setFilter()`, `resetFilters()` — all thread-safe ### 5. Resources & Prompts - `listResources()` / `listResourceTemplates()` for discovery — templates use URI templates (RFC 6570) - `readResource(uri)` returns `McpReadResourceResult` — handle both `McpTextResourceContents` and `McpBlobResourceContents` - Use `DefaultMcpResourcesAsToolsPresenter` to give the LLM autonomous resource discovery — avoids manual orchestration - `toChatMessage()` on `McpPromptMessage` — throws for `assistant` role with non-text content - Binary blob content conversion is unsupported regardless of role - `listPrompts()` / `getPrompt(name, arguments)` for prompt templates - **Resource Subscriptions (1.13.0+):** check whether `subscribeToResource()` / `unsubscribeFromResource()` is used for dynamic resources; verify `McpClientListener.onResourceUpdated()` / `onResourceListChanged()` callbacks are implemented; anti-pattern: polling resources repeatedly instead of subscribing ### 6. Error Handling - **Layer 1 — JSON-RPC protocol errors**: error code `-32602` → `ToolArgumentsException`; all others → `ToolExecutionException` - **Layer 2 — Tool execution errors**: `isError: true` in result → `ToolExecutionException` (default path) or `ToolExecutionResult.isError() == true` (listener path) - **Layer 3 — Transport failures**: timeout → `McpCancellationNotification` sent + timeout message returned; `ExecutionException` → wrapped; process exit → all futures completed exceptionally - Catch `ToolArgumentsException` separately when argument validation errors need different handling - Don't swallow `isError` in custom `toolWrapper` — propagate error signals - `toolExecutionTimeoutErrorMessage(...)` customizes the message the LLM receives on timeout ### 7. Observability - `McpClientListener` for tool/resource/prompt lifecycle hooks: `beforeExecuteTool`, `afterExecuteTool`, `onExecuteToolError` - 1.14.0 supports multiple listeners and additional event types; keep metrics, audit, and cache invalidation listeners separate when possible - `afterExecuteTool` receives both `ToolExecutionResult` and `rawResult` (full JSON-RPC `Map`) — use `rawResult` for custom `structuredContent` vs `content` inspection - `McpCallContext` contains `invocationContext()` (chat memory ID) and `message()` (the JSON-RPC request) — correlate MCP calls with AI Service invocations - `McpLogMessageHandler` for server-side log messages — `DefaultMcpLogMessageHandler` forwards to SLF4J - Custom `logHandler(...)` on `DefaultMcpClient.builder()` for alerting on server errors - `logRequests` / `logResponses` on transports for JSON-RPC traffic debugging ### 8. Protocol Compliance - Default protocol version `2025-11-25` — the latest MCP spec - Client advertises `roots` capability with `listChanged: true` — no sampling/elicitation support yet - Server capabilities from `initialize` response are logged at `DEBUG` but not used for feature gating - `setRoots(List)` sends `notifications/roots/list_changed` to the server - Server's `notifications/tools/list_changed` triggers automatic cache eviction when `cacheToolList(true)` - 1.14.0 fixes resource-list cache behavior; still verify servers emit list-changed notifications when resources are dynamic - `McpOperationHandler` handles incoming `ping`, `roots/list`, `notifications/message`, and `notifications/tools/list_changed` - MCP Registry client (`DefaultMcpRegistryClient`) is read-only — verify trust before running discovered servers - **Schema compliance:** Verify `$ref` / `$defs` and `anyOf` + object schemas; the 1.18.1 line fixes the latter MCP tool-schema mapping edge case. ## Anti-Patterns (quick reference) - Using `StdioMcpTransport` in environments that prohibit host process spawning → use `DockerMcpTransport` - Not subscribing to resource updates when using frequently-changing resources → causes stale data - Missing `McpClientListener` callbacks for resource/prompt list changes → silent cache invalidation ## Output Format ```markdown ## Review Summary - **Files reviewed**: N - **Critical**: N | **Warning**: N | **Suggestion**: N ## Findings ### [CRITICAL] Dimension — Short title **File**: `path/to/file.java:42` **Issue**: Description of the problem **Fix**: ```java // corrected code ``` ``` For detailed rules, code examples, and anti-patterns tables, see [reference.md](reference.md).