Widget callServerTool calls hardcode bare tool names, breaking under multi-server MCP hosts
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 68/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- api, full-stack
Research direction
Start by searching the affected examples under examples/ for app.callServerTool() calls and inspect getHostContext().toolInfo.tool.name. Update the listed interactive examples so callbacks resolve host-qualified names while retaining bare-name behavior, then verify the interactions through a multi-server MCP aggregator and a direct single-server connection.
Written by the indexing model from the issue text.
Description
Summary
Every example app that calls app.callServerTool() hardcodes a literal tool-name string (e.g. name: "get-time") instead of resolving it from getHostContext().toolInfo. This works fine when a client connects directly to a single MCP server, but breaks under any MCP host/proxy that aggregates multiple servers behind one session and qualifies tool names to avoid collisions (e.g. pdf-server-save_pdf instead of save_pdf). In that situation the tool that rendered the widget is reachable under its qualified name, but the widget's own callServerTool calls go out under the original bare name and fail to resolve — "tool not found" — even though the widget itself loaded and displayed correctly.
This is easy to miss because it never surfaces when testing a server standalone (1:1 with a client), which is presumably how these examples are normally exercised. It only shows up once a server is aggregated behind a multi-server hub/router.
Reproduction
Run any affected example (e.g. pdf-server) behind an MCP aggregator that qualifies tool names for multi-server sessions (we hit this with mcphub, which qualifies names as <server>-<tool> whenever a named route has 2+ connected servers). The entry tool (display_pdf → pdf-server-display_pdf) is called correctly by the host and the widget renders. But any follow-up interaction that goes through the widget's own callServerTool — paging, saving, polling — fails, because the widget calls the literal string save_pdf/read_pdf_bytes/etc., which doesn't exist under that name in the aggregated session; only pdf-server-save_pdf etc. do.
Fix
getHostContext().toolInfo.tool.name already exposes the actual, host-rewritten name of the tool that invoked the widget. A widget can recover whatever prefix the host applied and reapply it to any tool it calls back with, e.g.:
function resolveToolName(baseName: string): string {
const ctx = app.getHostContext();
const invokedName = ctx?.toolInfo?.tool?.name;
if (!invokedName) return baseName;
// this widget's own bare entry-tool names, in case the host mounts it from more than one
const entryTools = ["display_pdf"];
const ownToolName = entryTools.find((t) => invokedName.endsWith(t));
if (!ownToolName) return baseName;
const prefix = invokedName.slice(0, invokedName.length - ownToolName.length);
return prefix + baseName;
}
await app.callServerTool({ name: resolveToolName("save_pdf"), arguments: {...} });
falling back to the bare name on hosts that don't rewrite names at all. This is a small, self-contained change per example and doesn't require any protocol/SDK change — toolInfo is already available.
Affected examples
Checked every example under examples/ for callServerTool usage. 15 hardcode bare names:
basic-server-preact—get-timebasic-server-react—get-timebasic-server-solid—get-timebasic-server-svelte—get-timebasic-server-vanillajs—get-timebasic-server-vue—get-timequickstart—get-timeintegration-server—get-timecohort-heatmap-server—get-cohort-datacustomer-segmentation-server—get-customer-datadebug-server—debug-log,debug-tool,debug-refreshsystem-monitor-server—poll-system-statswiki-explorer-server—get-first-degree-linkslazy-auth-server—get_secret,revoke_auth_tokenpdf-server—submit_viewer_state,submit_page_data,save_pdf,read_pdf_bytes,submit_save_data,poll_pdf_commands
Not affected: threejs-server plumbs callServerTool through as an unused prop (no live call site in the example). map-server, qr-server, scenario-modeler-server, shadertoy-server, sheet-music-server, transcript-server, video-resource-server, budget-allocator-server are render-once and never call back to the server, so the pattern doesn't apply.
Why this matters beyond the 15
The basic-server-* examples are the templates most third-party MCP App authors will fork from first. Fixing the pattern there — even as a small shared helper documented once — sets the right precedent going forward, on top of fixing the specific interactive examples (pdf-server, debug-server, wiki-explorer-server, lazy-auth-server, etc.) directly.
- Dominant language
- TypeScript
- Stars
- 2.9k
- Forks
- 387
- Avg merge
- 3h 21m
- Merged PRs (30d)
- 6
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from modelcontextprotocol/ext-apps
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
modelcontextprotocol/ext-apps#767 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
modelcontextprotocol/ext-apps#742 · 1 comment ·
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
modelcontextprotocol/ext-apps#711 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
modelcontextprotocol/ext-apps#706 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
modelcontextprotocol/ext-apps#703 · 1 reaction ·
All issues in modelcontextprotocol/ext-apps
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
ontola/atomic-server#1625 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
melgarafael/DeskcommCRM#1451 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 82/100
-
bug via-triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bot:ai-assisted component:compact-js status:untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
midnightntwrk/midnight-sdk#403 ·