fix(binding-mcp): honor south exits' real capabilities in route resolution - #2529
Merged
Conversation
…ution McpBindingConfig.resolve()/resolveAll() decided whether a route serves a given capability (tools/prompts/resources) using only the route's static when: condition, never consulting realCapabilitiesByRoute -- the capabilities a south exit actually declares over its own KIND_LIFECYCLE handshake. Since an unrestricted when: condition (e.g. selecting only on toolkit:) is treated as serving every capability, a route in front of a tools-only south exit was still probed for prompts/resources, surfacing as continuous BINDING_MCP_HYDRATE_FAILED ... ROUTE_FAILED during cache hydration and the same gap in live request routing. McpBindingConfig now intersects a route's static capability set with its recorded real capabilities once that exit's lifecycle handshake has reported them, at both call sites that share this resolution path: cache hydration (resolveAll(traceId, kind)) and live request routing (resolve/resolveAll on McpBeginExFW). Before that handshake completes, no real capabilities are recorded yet and the static set is used as-is, so this narrows an over-broad match rather than gating on wire timing. mcp-kafka is tools-only by construction (its route condition schema rejects prompt:/resource: selectors), so its KIND_LIFECYCLE reply now always declares SERVER_TOOLS instead of echoing back whatever capabilities bits the connecting north forwarded for its own client's elicitation support. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Px3kTeX1XXiUpEk4pLwcjq
…orth's mcp-kafka-connect and mcp-schema-registry are pure byte-transparent relays (McpKafkaConnectClientFactory / a like factory) into an internally-composed mcp-openapi binding, which is itself a transparent relay into a further internally-composed mcp-http binding -- the actual KIND_LIFECYCLE handshake for all three of these south binding types terminates in McpHttpProxyFactory, not in their own factories. McpHttpProxyFactory's KIND_LIFECYCLE reply echoed back whatever capabilities bits the connecting north forwarded verbatim, with no real declaration at all -- the same class of bug already fixed for mcp-kafka, but mcp-http is not tools-only: McpOpenapiCompositeGenerator routes some OpenAPI operations to tools and others to resources depending on the spec, so a fixed constant would be wrong here (and would misdeclare capabilities for anyone configuring mcp-http/mcp-openapi directly with resource routes). McpHttpBindingConfig.serverCapabilities() now computes real capabilities from what's actually configured (SERVER_TOOLS when tools() is non-empty, SERVER_RESOURCES when resources() is non-empty), and McpHttpProxyFactory's lifecycle reply declares that instead of the client-forwarded value. This closes the same routing gap from the prior commit for mcp-kafka-connect and mcp-schema-registry (both of which, via the composite chain, only ever generate tool routes into mcp-http in practice) as well as for mcp-openapi and mcp-http used directly with resource routes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Px3kTeX1XXiUpEk4pLwcjq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
McpBindingConfig.resolve()/resolveAll()— the shared route-resolution path used by both live request routing andMcpProxyCacheHydrater's cache hydration — decided whether a route serves a given capability (tools/prompts/resources) using only the route's staticwhen:condition, never consultingrealCapabilitiesByRoute, the mechanism that actually learns a south route's real, live capabilities from its ownKIND_LIFECYCLEhandshake. Since an unrestrictedwhen:condition (e.g. selecting only ontoolkit:) is treated as serving every capability, a route in front of a tools-only south exit (mcp-kafka) was still probed for prompts/resources, surfacing as continuousBINDING_MCP_HYDRATE_FAILED ... ROUTE_FAILEDduring cache hydration, plus the same gap in live request routing.Tracing the two other binding types named in the issue (
mcp-kafka-connect,mcp-schema-registry) further: both are pure byte-transparent relays into an internally-compositedmcp-openapibinding, itself a transparent relay into a further-compositedmcp-httpbinding — the actualKIND_LIFECYCLEhandshake for all three terminates inMcpHttpProxyFactory, which had the same bug in a more severe form (no fallback at all, unconditional echo).Changes
binding-mcp—McpBindingConfignow intersects a route's static capability set with its recorded real capabilities once that exit's lifecycle handshake has reported them, at both call sites sharing this resolution path: cache hydration (resolveAll(traceId, kind)) and live request routing (resolve/resolveAllonMcpBeginExFW). Before that handshake completes, no real capabilities are recorded yet and the static set is used as-is, so this narrows an over-broad match rather than gating on wire timing.binding-mcp-kafka— tools-only by construction (its route condition schema rejectsprompt:/resource:selectors), so itsKIND_LIFECYCLEreply now always declaresSERVER_TOOLSinstead of echoing back whatever capabilities bits the connecting north forwarded for its own client's elicitation support.binding-mcp-http— not tools-only (McpOpenapiCompositeGeneratorroutes some OpenAPI operations to tools and others to resources depending on the spec), so a fixed constant would be wrong here.McpHttpBindingConfig.serverCapabilities()now computes real capabilities from what's actually configured (SERVER_TOOLSwhentools()is non-empty,SERVER_RESOURCESwhenresources()is non-empty), andMcpHttpProxyFactory's lifecycle reply declares that instead of the client-forwarded value. This transitively fixesmcp-kafka-connectandmcp-schema-registrytoo (both only ever generate tool routes through the composite chain in practice), as well asmcp-openapi/mcp-httpused directly with resource-only routes (confirmed schema-valid: neither binding's schema requires at least one tool route).Testing
McpBindingConfigTest: unit tests proving a route with an unrestricted condition is excluded frompromptsresolution once real capabilities are recorded as tools-only, and still included beforehand (before the lifecycle handshake completes).McpKafkaProxyFactoryTest: proves the lifecycle reply always declares tools-only regardless of what the client forwarded.McpHttpBindingConfigTest: provesserverCapabilities()returns the correct bits for tools-only, resources-only, both, and neither configured.Build verification caveat:
./mvnw verifycould not be run in the sandbox this was authored in —flyweight-maven-pluginand other internal artifacts require resolving frommaven.packages.aklivity.io, which redirects to an auth flow unavailable there. Changes were reviewed carefully by hand (types, imports, checkstyle conventions, generated-flyweight API shapes cross-checked against existing working code) but CI is the first real compile/checkstyle/test confirmation.Fixes #2524
🤖 Generated with Claude Code
Generated by Claude Code