feat(mcp): add resources and prompts support - #1172
Conversation
will-lamerton
left a comment
There was a problem hiding this comment.
Nice work, and thanks for keeping sampling/elicitation out of scope. Verified locally: tsc clean, biome clean, knip clean, all 88 tests in the touched specs pass. A few things to fix before this can go in.
Blocking
1. The branch doesn't merge. Conflicts against current main in source/mcp/mcp-client.ts, source/mcp/mcp-client.spec.ts, and docs/configuration/mcp-configuration.md. Please rebase.
2. Resource mentions bypass the file-mention size guard. The new RESOURCE case in source/utils/prompt-processor.ts inlines the whole body unconditionally. The FILE case right above it deliberately doesn't - over FILE_MENTION_INLINE_MAX_LINES it emits a head preview plus a read_file(...) hint, with the comment "so a single @-mention can't flood the conversation". One @-mention of a large MCP resource dumps everything into context. Same cap should apply.
3. The server name gets stamped twice. source/components/user-input.tsx passes fileCompletions[selectedFileIndex]?.displayPath as the resourceName argument, but displayPath is `${resource.name} (${resource.serverName})`. So the chip reads [@api-docs (docs-server)] and the assembled header becomes === MCP Resource: api-docs (docs-server) (from docs-server) ===. The unit test passes a bare 'resource.txt' there, so it doesn't catch this. Pass resource.name through instead of the display string.
4. Multi-message prompts collapse into one user turn. getPrompt preserves role on each message, then mcp-prompt-handler.ts joins every message's text with \n\n and sends it as a single onHandleChatMessage. A few-shot prompt with assistant turns - a normal MCP prompt shape - loses its structure.
5. Capability gating is claimed but not implemented. The commit message and changeset both say discovery is "gated on the server's declared capabilities", but connectToServer calls listResources() / listPrompts() unconditionally and swallows the failure. That's two extra round trips per server that supports neither, and the "MCP server does not support resources" log line is a guess. client.getServerCapabilities() is available post-connect - either use it or fix the claim.
Non-blocking
handleResourceMentionreturnsnullon any read failure, so a server error, a timeout, and a missing resource all look identical: the mention silently does nothing. MirrorshandleFileMention, but a remote read failing silently is a worse trade than a local file not existing.- Positional args beyond the prompt's declared
argumentsare dropped with no warning, as are all args when the prompt declares none. - Issue #1162's phase 2 called for prompts to go through
source/commands/lazy-registry.ts; this intercepts inhandleSlashCommandinstead. Fine functionally (custom commands are correctly checked first), but worth a note on why. - No tests cover the
user-input.tsxwiring.encodeMCPResourcePath/decodeMCPResourcePathand the merged completion list are the riskiest new code here and are only exercised manually. getServerInfonow returnsresourceCount/promptCount, but neither/mcpnor/doctorshows them.docs/battlemap.mdis the parity claim this closes, and it isn't updated.
Solid
Server-scoped readResource / getPrompt rather than cross-server URI search, with a test proving two same-named prompts route correctly. Text vs blob discrimination by key presence rather than a nonexistent type field - correct per the MCP schema and directly tested. Binary blocks become a note instead of raw base64. Discovery failures can't break tool discovery. disconnect() clears the new maps. Changeset names the right package.
The MCP client implemented exactly two operations, listTools and callTool - no listResources, readResource, listPrompts, or getPrompt anywhere in source/mcp/. docs/battlemap.md claims client parity with Claude Code, which additionally surfaces MCP resources as @-mentions and MCP prompts as slash commands. MCPClient discovers a server's resources and prompts alongside its tools at connect time, gated on the server's declared capabilities and best-effort (a listResources/listPrompts failure logs and leaves that server with zero of that kind, the same as a server that never declared the capability - it must not fail tool discovery, which is the connection's real contract). readResource and getPrompt take an explicit serverName rather than searching every connected server by URI/name, so two servers that happen to expose the same URI or prompt name can never be confused with each other - the completion or command that triggers either already knows which server it came from. Resources join the file-mention system: `@` fuzzy-searches local filenames and connected servers' resources together (same completion list, same Tab-to-select, distinguished by an encoded path prefix so selection knows which reader to call), and a selected resource is read and inlined as a placeholder exactly the way a file mention already works. A binary content block becomes a short placeholder note instead of its raw base64 landing in the prompt. Prompts dispatch as `/mcp:<server>:<prompt>`, listed alongside custom commands in the `/` completion menu. Positional arguments fill the prompt's declared parameters in order (not all collapsing onto the first, and not silently swallowed past the first missing one - every missing required argument is named before anything is sent). Unlike a custom command's static template, the prompt is fetched fresh from its server on every invocation and the result is sent as the next chat turn via the same onHandleChatMessage path a custom command uses - not merely displayed, which is what makes it usable as a command rather than a lookup. Sampling, elicitation, and roots are out of scope for this change (the proposal itself calls for staging them separately, since they require the client to act as a server back toward the MCP host). Refs Nano-Collective#1162.
Blocking: - Gate resource/prompt discovery on the server's declared capabilities (getServerCapabilities()) instead of attempting listResources/listPrompts unconditionally and swallowing the failure. - Apply the same FILE_MENTION_INLINE_MAX_LINES guard to MCP resource mentions so a single large @-mention can't flood the conversation. - Stop double-stamping the server name on a resource mention chip - pass the bare resource name instead of the completion list's disambiguated displayPath. - Preserve per-message roles when a prompt returns multiple messages (e.g. a few-shot example) instead of flattening them into one user turn. handleChatMessage now accepts optional historyMessages spliced in ahead of the new user message. Non-blocking: - Surface a failed resource read via logError instead of failing silently, unlike a missing local file which the user can already see. - Warn instead of silently dropping positional args beyond what a prompt declares (including all args when it declares none). - Note why MCP prompts intercept in handleSlashCommand rather than going through the static lazy-registry. - Cover the user-input.tsx resource-mention wiring with a test. - Show resource/prompt counts and names in /mcp and /doctor. - Update docs/battlemap.md's MCP parity claim to call out resources and prompts specifically. Also rebases onto current main to resolve the branch's merge conflicts.
c57d330 to
049c2bb
Compare
|
Thanks @will-lamerton, here's everything addressed: Blocking
Non-blocking
|
Resolves the user-input.tsx conflict. main rewrote the component's render layer (new-ui: TitledBoxWithPreferences, promptWidth, the `?` shortcuts overlay, removal of the terminal-mouse selection-mode block), so main's version is taken wholesale and this branch's MCP-resource additions are re-applied on top: - getToolManager / fuzzyScoreFilePath / handleResourceMention imports - MCP_RESOURCE_PATH_PREFIX with encode/decode helpers - getMCPResourceCompletions, merged into the `@` completion list - fileCompletions state widened with displayPath / resourceName - mcp:<server>:<prompt> names in the slash-command completions - the resource-vs-file branch in handleFileSelection - displayPath rendering for resource rows in the suggestions list
will-lamerton
left a comment
There was a problem hiding this comment.
Rebase done — I merged current main in and resolved the user-input.tsx conflict myself (2e4991a). main had rewritten that component's render layer, so I took main's version wholesale and re-applied the seven MCP-resource additions on top; the branch now differs from main by exactly the 25 files and 2265/52 lines this PR intended, with main a clean ancestor.
Re-verified all five blocking items in the code rather than taking the reply at face value — the size guard, the bare resource.name, the capability gating via getServerCapabilities(), and the historyMessages splice are all genuinely there, and the non-user-terminal prompt case you handled on top of #4 is a nice catch I hadn't asked for. All seven non-blocking items addressed too.
Local on the merged head: tsc clean, biome clean, knip clean, 292 tests pass across the touched specs plus the user-input and app-util suites. Thanks for the thorough turnaround.
Unrelated to this PR's feature work, but main is red on `pnpm run test:format` and that blocks this branch from merging. Nano-Collective#1279 (126e76f) added the path-validators import and Nano-Collective#1289 (0648d75) added the inline-diff import. Each branch was internally sorted and green on its own; merge commit 9331255 interleaved the two lists in the wrong order, and no single PR's CI ever saw the combination. Pure import reorder from `biome check --write`, no behavior change.
|
Thanks @will-lamerton, Since this covered phases 1 and 2 of #1162, |
Closes #1162.
Description
Adds MCP resources and prompts support to
MCPClient, and wires both into the interactive UI: resources join the existing@-mention/file-completion system, prompts dispatch as/mcp:<server>:<prompt>slash commands that feed the model the same way a custom command does. Phases 1 and 2 of #1162; sampling/elicitation/roots (phase 3) are explicitly out of scope for this PR.Type of Change
Changeset
pnpm changeset) describing this change for the changelogTesting
Automated Tests
.spec.ts/tsxfilespnpm test:allcompletes successfully)Manual Testing
Checklist