Summary
#4592 isolates per-tool schema failures so one unrepresentable tool no longer drops every Desktop capability. But the isolation validates each tool independently; the final registration bundles all surviving tools into offers and is sent as a single frame via ClientCapabilityChannel.replace(). The protocol enforces aggregate limits that per-tool validation cannot see, so a large (but entirely valid) MCP configuration still fails the whole registration and takes Browser, Computer Use, Client settings, Rive and MCP down together — the same outage shape as #4591.
Reachable outage (reproduced on this branch)
desktop_mcp is a single offer holding all MCP tools (apps/desktop/src/main/runtime-host-boot.ts). 65 completely valid MCP tools → the offer has 65 tools → decodeClientCapabilityReplaceInput throws Invalid Client Capability offer tools (per-offer cap CLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER = 64) → the whole frame is rejected in replace() → on first registration every Desktop capability disappears.
- Repro:
offer tool counts [["desktop_browser",1],["desktop_mcp",65]] → rejected: Invalid Client Capability offer tools.
- Total-tool cap
CLIENT_CAPABILITY_MAX_TOOLS = 256 and manifest cap CLIENT_CAPABILITY_MAX_MANIFEST_BYTES = 56 KiB are likewise unguarded by per-tool probing: several mid-size schemas (each ≤ 32 KiB and individually legal) can exceed the aggregate manifest.
- MCP discovery permits up to 1000 tools (
packages/mcp/src/tool-discovery.ts), so >64 is realistic with a few servers.
Limits: packages/runtime-host/src/protocol/client-capability.ts (CLIENT_CAPABILITY_MAX_OFFERS = 32, _MAX_TOOLS_PER_OFFER = 64, _MAX_TOOLS = 256, _MAX_MANIFEST_BYTES = 56 * 1024). Assembly + isolation: apps/desktop/src/main/runtime-host-native-capabilities.ts.
Proposed
- Split the MCP capability into multiple offers of ≤ 64 tools (
desktop_mcp, desktop_mcp_2, …; offerId must match [A-Za-z0-9_-]{1,128}).
- Deterministically trim tools that exceed the total-count / manifest-byte budget, warning about what was dropped (no silent caps).
- Keep the core capabilities (Browser, Computer Use, Client settings, Rive) prioritized so MCP overflow can never evict them.
- Run a final
decodeClientCapabilityReplaceInput on the fully assembled manifest so provider.offers() always decodes — the registration can never fail the whole frame.
Acceptance
- A workspace with > 64 MCP tools registers (serving up to the protocol caps) instead of failing the whole frame; the non-MCP capabilities always register.
- Tests cover the 65-tool split and aggregate byte/count trimming.
Follow-up to #4592. Related: #4591, #4614.
Summary
#4592 isolates per-tool schema failures so one unrepresentable tool no longer drops every Desktop capability. But the isolation validates each tool independently; the final registration bundles all surviving tools into offers and is sent as a single frame via
ClientCapabilityChannel.replace(). The protocol enforces aggregate limits that per-tool validation cannot see, so a large (but entirely valid) MCP configuration still fails the whole registration and takes Browser, Computer Use, Client settings, Rive and MCP down together — the same outage shape as #4591.Reachable outage (reproduced on this branch)
desktop_mcpis a single offer holding all MCP tools (apps/desktop/src/main/runtime-host-boot.ts). 65 completely valid MCP tools → the offer has 65 tools →decodeClientCapabilityReplaceInputthrowsInvalid Client Capability offer tools(per-offer capCLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER = 64) → the whole frame is rejected inreplace()→ on first registration every Desktop capability disappears.offer tool counts [["desktop_browser",1],["desktop_mcp",65]] → rejected: Invalid Client Capability offer tools.CLIENT_CAPABILITY_MAX_TOOLS = 256and manifest capCLIENT_CAPABILITY_MAX_MANIFEST_BYTES = 56 KiBare likewise unguarded by per-tool probing: several mid-size schemas (each ≤ 32 KiB and individually legal) can exceed the aggregate manifest.packages/mcp/src/tool-discovery.ts), so >64 is realistic with a few servers.Limits:
packages/runtime-host/src/protocol/client-capability.ts(CLIENT_CAPABILITY_MAX_OFFERS = 32,_MAX_TOOLS_PER_OFFER = 64,_MAX_TOOLS = 256,_MAX_MANIFEST_BYTES = 56 * 1024). Assembly + isolation:apps/desktop/src/main/runtime-host-native-capabilities.ts.Proposed
desktop_mcp,desktop_mcp_2, …;offerIdmust match[A-Za-z0-9_-]{1,128}).decodeClientCapabilityReplaceInputon the fully assembled manifest soprovider.offers()always decodes — the registration can never fail the whole frame.Acceptance
Follow-up to #4592. Related: #4591, #4614.