fix(cli): match @ MCP resources by any URL substring, and stop label wrapping - #5776
fix(cli): match @ MCP resources by any URL substring, and stop label wrapping#5776yiliang114 wants to merge 2 commits into
Conversation
…iption In reverse (`@`) completion mode, a suggestion row that carries a description (MCP server/resource entries) let the description column shrink the label box, so the label wrapped mid-string — the trailing `:` of `server:` landed on its own line. Pin the label (`flexShrink: 0`) when the row has a description so the description column absorbs the shrink and truncates instead. Description-less rows (plain files) keep the previous behaviour.
…ll servers Before this change, a bare `@<partial>` only matched MCP *server names* by prefix; a resource was reachable only after typing the full `@server:` prefix. Typing a fragment of a resource URI or name (e.g. `@analyze`) returned no match even when a resource URI contained it. Add cross-server resource discovery: at the bare `@<partial>` stage, match the partial case-insensitively as a substring against every resource's canonical `@server:uri` reference and its friendly name/title, across all configured servers, and surface those alongside files. Reuses the existing `ResourceRegistry.getAllResources()`; ranks prefix above substring and reference above name.
There was a problem hiding this comment.
Pull request overview
This PR improves the CLI’s reverse (@) completion UX for MCP resources by (1) enabling cross-server substring matching of resources before a <server>: is typed, and (2) fixing reverse-mode suggestion row layout so labels don’t wrap mid-string when a description is present.
Changes:
- Add cross-server MCP resource “discovery” suggestions for bare
@<partial>using case-insensitive substring matching against<server>:<uri>and friendly name/title, mixed alongside file suggestions. - Adjust reverse-mode suggestion row flex behavior so description rows don’t squeeze/wrap the label.
- Add/extend unit tests covering resource discovery behavior and the reverse-mode label wrapping regression.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/cli/src/ui/hooks/useAtCompletion.ts | Adds cross-server MCP resource discovery suggestions for bare @<partial> and integrates them into the search flow. |
| packages/cli/src/ui/hooks/useAtCompletion.test.ts | Adds tests for the new bare-@<partial> MCP resource discovery behavior (substring/name match, cross-server, files mixed in, trust gating). |
| packages/cli/src/ui/components/SuggestionsDisplay.tsx | Prevents reverse-mode labels from shrinking/wrapping when a row includes a description by pinning label shrink behavior. |
| packages/cli/src/ui/components/SuggestionsDisplay.test.tsx | Adds a regression test ensuring reverse-mode label + description stays on one line. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return (registry.getAllResources?.() ?? []) | ||
| .map((resource) => { | ||
| const ref = buildMcpResourceRef(resource.serverName, resource.uri); | ||
| const friendly = resource.title || resource.name || ''; | ||
| return { | ||
| resource, | ||
| ref, | ||
| friendly, | ||
| rank: rankOf(ref.toLowerCase(), friendly.toLowerCase()), | ||
| }; | ||
| }) | ||
| .filter((m) => m.rank !== Infinity) | ||
| .sort((a, b) => a.rank - b.rank || a.ref.localeCompare(b.ref)) | ||
| .slice(0, MAX_SUGGESTIONS_TO_SHOW * 3) |
| const output = lastFrame() ?? ''; | ||
| // The whole label (colon included) sits on a single line, before the desc. | ||
| expect(output).toContain('asys-mcp-http: MCP resource server'); | ||
| // And the label is not split across lines. | ||
| expect(output).not.toMatch(/asys-mcp-http\n/); | ||
| expect(output.split('\n').length).toBe(1); | ||
| }); |
|
Closing as a duplicate of #5774, which opened earlier and is the more complete implementation (shared ranking helper + a minimum description-width floor). Extra test cases moved over as a comment there. |
|
Thanks for the PR, @yiliang114! Template looks good ✓ — all required sections filled in, bilingual, test plan with evidence. On direction: this is a clear-cut UX fix. Users remembering a fragment of a resource URI (the common case) getting "no match" is a real usability gap. Extending On approach: the scope is tight — exactly two focused fixes, +272/-12 (mostly tests), 4 files, no drive-by refactors or scope creep. The ranking scheme (ref prefix > name prefix > ref substring > name substring) is sensible and produces intuitive ordering. The bare- Moving on to code review. 🔍 中文说明感谢 @yiliang114 的 PR! 模板完整 ✓ — 所有章节齐全,双语,测试计划附带证据。 方向:这是一个明确的 UX 修复。用户只记得资源 URI 的片段(最常见的场景)却得到"无匹配",是真实的可用性问题。将 方案:范围精确——恰好两个聚焦的修复,+272/-12(主要是测试),4 个文件,没有顺手重构或范围膨胀。排序机制(引用前缀 > 名称前缀 > 引用子串 > 名称子串)合理且产生直觉排序。空 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewClean implementation. I read the diff against my own proposal (add a A few things worth noting:
No correctness bugs, security issues, or AGENTS.md violations found. TestingUnit TestsAll 38 tests pass across the two affected suites: New test coverage includes: substring matching across servers, name/title matching, mixed-with-files, empty- Build + Headless SmokeTmux Interactive TestLaunched the CLI interactively in tmux at The CLI starts cleanly and renders the TUI. Note: 中文说明代码审查实现干净。我将 diff 与自己的方案(添加一个遍历 值得注意的几点:
未发现正确性 bug、安全问题或 AGENTS.md 违规。 测试所有 38 个测试通过(33 个 — Qwen Code · qwen3.7-max |
|
This is a good PR and I'm confident recommending it for merge. Going back to my independent proposal: I would have done essentially the same thing — add a substring-matching function over The code is straightforward — no over-engineering, no speculative abstractions. The The test suite is strong: 6 new unit tests covering substring matching, cross-server discovery, name/title matching, mixed-with-files, empty-trigger guard, and untrusted-folder guard, plus the layout regression test. The build is clean and the CLI works. The only limitation in my verification is that I couldn't exercise MCP resource completion end-to-end in the TUI (no MCP server in CI), but the 33 unit tests covering all MCP discovery paths give high confidence. Approving. ✅ 中文说明这是一个好的 PR,我很有信心推荐合并。 回到我的独立方案:我本质上会做同样的事——在 代码直截了当——没有过度工程化,没有投机性抽象。 测试套件扎实:6 个新单测覆盖子串匹配、跨 server 发现、名称/标题匹配、与文件混排、空触发守卫和不信任目录守卫,加上布局回归测试。构建干净,CLI 正常工作。 验证的唯一限制是无法在 CI 的 TUI 中端到端验证 MCP 资源补全(无 MCP server),但 33 个覆盖所有 MCP 发现路径的单测提供了充分信心。 批准合并 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
What this PR does
Two fixes to
@completion for MCP resources, both surfaced by #5733.The main behavior change: typing a bare
@<fragment>now matches MCP resources by any substring of their@server:urireference or friendly name, across all configured servers — not just server names by prefix. So@analyze,@asight,@bubbleall reach a resource whose URI contains that fragment, mixed in with file results, without first typing the full@server:prefix.The second fix is layout: in
@mode a suggestion row that has a description (MCP server/resource entries) no longer wraps its label mid-string.Why it's needed
#5733 added "match MCP resource completions by name and discover servers", but discovery before the colon only matched server names by prefix. A user who remembers a fragment of a resource URI (the common case) got nothing:
@analyzereturned "no match" even thoughasys-mcp-http:asight://skills/analyze_ppu_opexists. Reaching a resource required knowing and typing the exact server name first.Separately, #5733 was the first time
@suggestions carried a description. That exposed a layout bug: the description column claimed nearly the full row width, squeezing the label so it wrapped — the:of aserver:entry rendered on its own line.Reviewer Test Plan
How to verify
Configure any MCP server that exposes resources whose URIs contain a recognizable mid-string (e.g.
asight://skills/analyze_ppu_op), then in the TUI:@analyze— Before: "no match". After: the matching resources appear (asys-mcp-http:asight://skills/analyze_ppu_op,…/analyze_ppu_bubble).@asys-mcp— Before: theasys-mcp-http:server entry's:wraps to the next line. After: label + description on one line, resources also listed directly.@asight(scheme),@skill(mid-URI, mixes with.qwen/skills/files),@bubble(tail),@schema(a differentdb://resource).@server:urireference.Evidence (Before & After)
Verified live in tmux against a real stdio MCP server (
asys-mcp-http,/mcpshows ✓ connected), 120-col terminal.Before (from the original report):
@asys-mcp→ server entry rendered with the:wrapped onto a second line.@analyze→无匹配/ no match.After:
Unit coverage:
useAtCompletion.test.ts(substring across servers, name/title match, mixed-with-files, empty-@stays files-only, untrusted folder) andSuggestionsDisplay.test.tsx(reverse-mode label stays on one line). 38 passing across the two suites; lint + typecheck clean.Tested on
Environment (optional)
Local build (
npm run build), runningpackages/cli/dist/index.jsagainst a stdio MCP server. Windows/Linux not exercised locally — left to CI.Risk & Scope
@keystroke. It is synchronous over the in-memoryResourceRegistryand capped atMAX_SUGGESTIONS_TO_SHOW * 3, so it is cheap; very large registries would do more substring work per keystroke.getMcpResourceSuggestionsbehavior and avoid a new dependency. A very long reference in a very narrow terminal may overflow rather than wrap (mirrors the prior long-label truncation path).@stays files-only; untrusted folders still surface no resources; the@server:drill-in path is unchanged.Linked Issues
No linked issue — reported directly. Follows up on #5733, which introduced
@MCP resource completion.中文说明
这个 PR 做了什么
针对 MCP resource 的
@补全修两个由 #5733 暴露的问题。主要行为变化:现在输入裸
@<片段>,会用该片段去子串匹配所有已配置 server 的资源(匹配其@server:uri全引用或友好名),而不再只按前缀匹配 server 名。于是@analyze、@asight、@bubble都能匹配到 URI 含该片段的资源,并与文件结果混排,不需要先打出完整的@server:前缀。第二个修复是布局:
@模式下带 description 的建议行(MCP server/resource 项)不再把标签从中间折行。为什么需要
#5733 加了「按名匹配 MCP 资源补全并发现 server」,但冒号之前的发现只按前缀匹配 server 名字。只记得资源 URI 片段的用户(最常见的情况)什么都得不到:
@analyze返回「无匹配」,尽管asys-mcp-http:asight://skills/analyze_ppu_op是存在的。要够到资源必须先知道并打出准确的 server 名。另外,#5733 是
@建议第一次带 description,由此暴露一个布局 bug:description 列几乎吃满整行宽度,把标签挤到折行 ——server:项的:被甩到了下一行。Reviewer Test Plan
如何验证
配置任意一个暴露资源、且资源 URI 含可辨识中段(如
asight://skills/analyze_ppu_op)的 MCP server,然后在 TUI 中:@analyze—— 修复前:「无匹配」。**修复后:**命中相关资源。@asys-mcp—— 修复前:asys-mcp-http:server 项的:折到下一行。修复后: 标签和描述同一行,资源也直接列出。@asight(scheme)、@skill(URI 中段,且与.qwen/skills/文件混排)、@bubble(末尾)、@schema(另一个db://资源)。@server:uri引用。证据(前 / 后)
在真实 stdio MCP server(
asys-mcp-http,/mcp显示 ✓ 已连接)、120 列终端下 tmux 实测。英文正文中的终端输出即为「修复后」实况;「修复前」为原始报告中的两种坏状态(冒号折行、@analyze无匹配)。单测覆盖:
useAtCompletion.test.ts(跨 server 子串、名/标题匹配、与文件混排、空@仍只出文件、不信任目录不出资源)与SuggestionsDisplay.test.tsx(reverse 模式标签不折行)。两套件 38 项通过;lint + typecheck 干净。风险与范围
@按键时执行,但它是对内存中ResourceRegistry的同步遍历,并按MAX_SUGGESTIONS_TO_SHOW * 3截断,开销很小。getMcpResourceSuggestions行为一致,且不引入新依赖。极窄终端下的超长引用可能溢出而非折行(沿用原有长标签截断路径)。@仍只出文件;不信任目录仍不出资源;@server:下钻路径不变。