feat(cli): match MCP resources globally on bare @ and show full references - #5774
Conversation
…ences Bare `@<partial>` with no `<server>:` prefix now matches resource URIs and friendly names across all connected MCP servers, surfaced alongside the file results and injected as the canonical `@server:uri` reference — so a user can pull up a resource by a memorable fragment without first recalling which server exposes it. In the @-mention dropdown, resource rows keep the full `server:uri` reference intact on a single line, aligned, with the description column truncating instead — so resources that share a long URI prefix stay distinguishable.
|
Thanks for the PR! Template looks good ✓ On direction: solid alignment with the On approach: the scope feels right — two tightly related UX improvements that share test fixtures and rendering context, so splitting would create more overhead than it saves. The refactoring in One minor question for the author to consider (not blocking): the global scan runs on every keystroke for bare Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 方向:与 方案:范围合理——两个紧密相关的 UX 改进,共享测试夹具和渲染上下文,拆开反而增加开销。 一个供作者思考的小问题(不阻塞):全局扫描在裸 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal (before reading the diff): I would have added a Comparison: the PR matches this proposal almost exactly, and does it well. The key design choices are sound:
Reuse check: the new No blockers found. The diff is focused and minimal for the stated goals. Unit TestsThe new tests cover: global URI matching, global name/title matching, resource-before-file ordering, empty Real-Scenario Testing (tmux)Started the bundled CLI with The MCP server ( The CLI starts, renders the TUI correctly, and file 中文说明代码审查独立方案(读 diff 之前):我会新增一个 对比: PR 与该方案高度一致,且实现质量高。关键设计选择合理:
复用检查: 新的 未发现阻塞问题。 Diff 聚焦且对目标来说是最小改动。 单测新测试覆盖:全局 URI 匹配、全局 name/title 匹配、资源优先于文件排序、空 真实场景测试 (tmux)使用 MCP 服务器( CLI 启动正常、TUI 渲染正确、文件 — Qwen Code · qwen3.7-max |
|
Stepping back to look at the whole picture: this is a clean, well-motivated PR that solves two real UX problems with MCP resource The implementation matches what I would have done independently — extract the shared ranking logic, add a global scan over The test coverage is thorough — 6 new unit tests covering the key behavioral contracts (global matching by URI and name, ordering relative to files, bare The one gap in my review is that I couldn't get a real MCP server connected in the CI environment to visually verify the dropdown behavior. The unit tests cover the logic comprehensively though, and the display test validates the rendering contract. The author reports macOS testing with screenshots showing the expected before/after behavior. Minor note for the maintainer: the global scan runs on every keystroke for bare Approval guardrail: cross-repo PR but Approving. ✅ 中文说明从全局来看:这是一个干净、动机明确的 PR,解决了 MCP 资源 实现与我独立方案高度一致——提取共享排序逻辑、新增对 测试覆盖充分——6 个新单测覆盖关键行为合约(全局 URI/name 匹配、相对文件的排序、裸 审查中唯一的缺口是我没能在 CI 环境中让真实 MCP 服务器连上来做可视化验证。不过单测全面覆盖了逻辑,展示测试验证了渲染合约。作者报告了 macOS 上的测试并附了前后对比截图。 给维护者的小提示:全局扫描在每次裸 审批守卫:跨仓库 PR 但类型为 批准 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Direction looks right — I hit the same two issues from the bare- A few test cases from my version that might be worth folding in — each exercises a matching path the current global test doesn't:
Also verified live in tmux against a real stdio MCP server ( |
| * Resource → suggestion input shape. Structurally satisfied by core's | ||
| * `DiscoveredMCPResource` (typed locally to avoid a core import / rebuild). | ||
| */ | ||
| type CompletableResource = { |
There was a problem hiding this comment.
[Suggestion] The local CompletableResource type is structurally weaker than core's DiscoveredMCPResource (Resource & { serverName }). If core ever adds a required field (e.g. mimeType) or an always-present field the picker should surface, this local shape silently accepts the drift — no compile-time canary.
The rationale in the doc comment ("avoid a core import / rebuild") is already undermined: lines 7-8 import Config, FileSearch, FileSearchFactory, and escapePath from @qwen-code/qwen-code-core, and core's barrel (packages/core/src/index.ts) re-exports * from './tools/mcp-client.js', so DiscoveredMCPResource is reachable from the same package without an extra rebuild trigger.
| type CompletableResource = { | |
| import type { | |
| Config, | |
| DiscoveredMCPResource, | |
| FileSearch, | |
| } from '@qwen-code/qwen-code-core'; | |
| import { FileSearchFactory, escapePath } from '@qwen-code/qwen-code-core'; | |
| import type { Suggestion } from '../components/SuggestionsDisplay.js'; | |
| import { MAX_SUGGESTIONS_TO_SHOW } from '../components/SuggestionsDisplay.js'; | |
| import { matchMcpServerPrefix, buildMcpResourceRef } from './mcpResourceRef.js'; | |
| import { t } from '../../i18n/index.js'; | |
| type CompletableResource = DiscoveredMCPResource; |
(Or drop the alias entirely and use DiscoveredMCPResource at the two callsites.)
— qwen3.7-max via Qwen Code /review
| }) | ||
| .filter((m) => m.rank !== Infinity) | ||
| .sort((a, b) => a.rank - b.rank || a.ref.localeCompare(b.ref)) | ||
| .slice(0, MAX_SUGGESTIONS_TO_SHOW * 3) |
There was a problem hiding this comment.
[Suggestion] The * 3 headroom on MAX_SUGGESTIONS_TO_SHOW is undocumented. The reason (these candidates merge with file results downstream, and only 8 rows render, so 3× gives ~24 candidates of material) is non-obvious — a future maintainer could trim it to * 1 as an obvious-looking waste and silently degrade merge quality.
| .slice(0, MAX_SUGGESTIONS_TO_SHOW * 3) | |
| // 3× headroom: these candidates merge with file results; only | |
| // `MAX_SUGGESTIONS_TO_SHOW` rows render, so the extra material keeps the | |
| // merged top-N useful even when a file flood dominates. | |
| .slice(0, MAX_SUGGESTIONS_TO_SHOW * 3) |
— qwen3.7-max via Qwen Code /review
| pattern: string, | ||
| ): Suggestion[] { | ||
| if (!config) return []; | ||
| if (config.isTrustedFolder?.() === false) return []; |
There was a problem hiding this comment.
[Suggestion] The isTrustedFolder === false early-return in the new global path is a security boundary (prevents leaking server resource existence in untrusted folders), but unlike the equivalent gate in getMcpResourceSuggestions (lines 104-107), it carries no comment explaining the rationale. The per-server path documents it as: "Don't surface resource URIs in an untrusted folder: the read path is blocked there, so completing them would both mislead and leak the existence of a server's resources."
Mirror that comment here — otherwise a future maintainer removing this check for "consistency" with another code path would inadvertently create a resource-existence leak.
| if (config.isTrustedFolder?.() === false) return []; | |
| if (!config) return []; | |
| // Don't surface resource URIs in an untrusted folder: the read path | |
| // (`ToolRegistry.readMcpResource`) is blocked there, so completing them | |
| // would both mislead and leak the existence of a server's resources. | |
| if (config.isTrustedFolder?.() === false) return []; | |
| if (pattern.length === 0) return []; |
— qwen3.7-max via Qwen Code /review
yiliang114
left a comment
There was a problem hiding this comment.
Approving — built this branch and ran the full @ completion acceptance live in tmux against a real stdio MCP server (/mcp ✓ connected, 120-col). Everything behaves as intended, including the cases I flagged earlier.
What I verified on this branch's build:
| Input | Result |
|---|---|
@asys-mcp |
server entry asys-mcp-http: on one line, colon intact (no wrap) |
@analyze |
matches …/analyze_ppu_op + …/analyze_ppu_bubble by mid-URI substring |
@asight |
all asight:// resources by scheme, mixed with files |
@skill |
URI-skills resources + local .qwen/skills/ files together |
@bubble |
tail-only substring → single resource |
@schema |
name-only match on a db:// resource |
@communication |
80-char ref shown in full and its description still rendered — the MIN_DESCRIPTION_WIDTH floor holds |
Real capture:
> @analyze
asys-mcp-http:asight://skills/analyze_ppu_bubble Analyze PPU bubble (idle time) ...
asys-mcp-http:asight://skills/analyze_ppu_op Analyze PPU operator performance ...
> @asys-mcp
asys-mcp-http: MCP resource server <- colon on one line
> @communication
asys-mcp-http:asight://skills/analyze_ppu_communication_overlap_and_pipeline_bubble_v2 Long URI ...
Two nice touches confirmed while testing: descriptions align into a column across described rows, and the min-description-width floor keeps the description visible even for an 80-char reference. Accepting a suggestion injects the canonical @server:uri.
Tested on macOS (🍏). Windows/Linux not exercised locally — left to CI.
What this PR does
Two related improvements to
@-mention completion for MCP resources.Global resource matching on a bare
@. Previously a resource URI only completed after you typed its server name and a colon (@myserver:…). Now a bare@<partial>with no<server>:prefix matches the partial (case-insensitively) against every discovered resource's URI and friendly name across all connected servers, surfaced alongside the file results and injected as the canonical@server:urireference. Server-name discovery (@<partial>→@server:) and the per-server path are unchanged; global matches are prepended to the file list so they stay visible without hiding files, and the bare@(empty partial) remains a files-only view.Full, aligned resource references in the dropdown. An MCP resource row now keeps its complete
server:urireference on a single line; the description column yields the width and truncates instead. References that share a long prefix (e.g. severalserver:scheme://skills/…URIs) are no longer cut down to an identical…, so they stay distinguishable and their descriptions line up.Why it's needed
Resource references are long (
server+scheme://path), and the dropdown previously let the description column dominate the row width, wrapping or truncating the reference so that resources sharing a prefix rendered identically and couldn't be told apart. Separately, requiring the exact server name before any resource would complete made resources hard to discover when you remember a fragment of the URI but not which server hosts it.Reviewer Test Plan
How to verify
Configure any MCP server that exposes resources — e.g. a small stdio server whose
resources/listreturns a few entries with long shared-prefix URIs and descriptions. Then in the input box:@<servername>:— every resource is listed with its fullserver:urireference intact and the descriptions aligned.@<fragment>that matches a resource URI/name but is not a server name (e.g. the URI scheme) — the resources appear (prepended above files) and insert as@server:uri.@<path>still lists files, and a bare@alone still shows files only.Expected: full references are shown (not truncated to
…) and the description truncates instead; the bare fragment surfaces matching resources across servers.Evidence (Before & After)
Server
asys-mcp-httpexposingasight://skills/…resources.Before — the
@server:reference is forced to wrap onto a second line, and a bare@asightmatched no resources at all:After — full references on one line with aligned descriptions, and a bare
@asight(no server prefix) matches globally:Selecting a row inserts
@asys-mcp-http:asight://skills/hbm_bandwidth_utilization.Tested on
Environment (optional)
Built bundle (
npm run bundle) run asnode --expose-gc dist/cli.js, against a throwaway stdio MCP server, driven in tmux. Logic covered by vitest unit tests.Risk & Scope
@<partial>keystroke and prepends matches before file results, so a very generic short fragment can surface several resources above files. Resource counts are typically small. Ordering (resources before files) and substring-vs-prefix matching are isolated and easy to tune.@server:path and slash-command completion are behavior-unchanged.@server:uri) is unchanged.Linked Issues
Follows #5733. Refs #5601.
中文说明
这个 PR 做了什么
对 MCP 资源的
@提及补全做了两处相关改进。裸
@的全局资源匹配。 之前资源 URI 只有在先打出服务器名加冒号(@myserver:…)之后才会补全。现在没有<server>:前缀的裸@<partial>会把这段(不区分大小写)匹配到所有已连接服务器中每个已发现资源的 URI 和友好名称,与文件结果一起呈现,并以规范的@server:uri引用注入。服务器名发现(@<partial>→@server:)与按服务器路径都保持不变;全局匹配前插到文件列表之前以保证可见、且不隐藏文件,裸@(空 partial)仍然是只看文件的视图。下拉里完整、对齐的资源引用。 MCP 资源行现在把完整的
server:uri引用保持在一行;改由描述列让出宽度并截断。共享长前缀的引用(例如多个server:scheme://skills/…URI)不再被切成一模一样的…,因此可以区分,描述也对齐。为什么需要
资源引用很长(
server+scheme://path),而下拉之前让描述列占据了行宽,把引用换行或截断,导致共享前缀的资源渲染得一模一样、无法区分。另外,要求先打出准确的服务器名才能补全任何资源,使得"只记得 URI 的一个片段、却不知道哪个服务器托管它"时很难发现资源。评审测试计划
如何验证
配置任意一个暴露资源的 MCP 服务器——例如一个小的 stdio 服务器,其
resources/list返回几条带长共享前缀 URI 和描述的条目。然后在输入框:@<servername>:—— 每个资源都以完整的server:uri引用列出,描述对齐。@<fragment>(例如 URI scheme)—— 资源出现(前插在文件之前)并以@server:uri注入。@<path>仍然列文件,单独一个裸@仍然只显示文件。预期:显示完整引用(不被截成
…),改为描述截断;裸片段能跨服务器召出匹配的资源。证据(前后对比)
服务器
asys-mcp-http暴露asight://skills/…资源。之前 ——
@server:引用被迫换行到第二行,且裸@asight完全匹配不到资源:之后 —— 完整引用一行显示、描述对齐,且裸
@asight(无服务器前缀)能全局匹配:选中某一行会插入
@asys-mcp-http:asight://skills/hbm_bandwidth_utilization。测试平台
环境(可选)
构建后的 bundle(
npm run bundle)以node --expose-gc dist/cli.js运行,对接一个临时 stdio MCP 服务器,在 tmux 中驱动。逻辑由 vitest 单测覆盖。风险与范围
@<partial>键入时扫描所有已发现资源(内存内,沿用按服务器路径已有的 slice 上限),并把匹配前插到文件之前,因此非常宽泛的短片段可能把若干资源排到文件上方。资源数量通常很少。顺序(资源排在文件前)与子串/前缀匹配都是隔离的、易于调整。@server:路径与斜杠命令补全行为不变。@server:uri)不变。关联 Issue
Follows #5733. Refs #5601.