Mods: `$.tool.call` type-checking cost grows with the number of connected MCP tools (8M instantiations, 1.4 GB for one mod)

Status Open
Reported on v2.1.300
Maintainer reply None cached
Activity 1 comment · opened Oct 15, 2026

What happens
tsc --noEmit on a mod with 20 $.tool.call(...) sites (tests included) takes 5–9 s and 1.4 GB (8.0M instantiations). The generated claude-code-mcp types declare 437 MCP tools (all servers connected in the session that wrote them); the mod calls one of them.

Measurements (TypeScript 7.0.2, Claude Code 2.1.300, same mod, --extendedDiagnostics):

| McpToolInputs entries | Instantiations | Check time |
|---|---|---|
| 437 (as generated) | 8.0M | 4–9 s, 1.4 GB |
| 49 (one server only) | 0.63M | 0.8 s |
| 0 | 0.28M | 0.2 s, 75 MB |

Roughly 12k instantiations per MCP tool, multiplied by the number of call sites.

Cause (as far as I can tell)
The first ToolCallOverloads signature checks the argument against ToolCallArgs & ToolNamed<T>, where ToolCallArgs distributes Omit<…> over the whole builtin + MCP union. Every call site compares against every tool. Replacing that signature, on a copy of the generated types, with one indexed by the tool name (<T extends …>(input: { tool: T } & InputFor<T>)) and dropping the wide fallback brought the same mod to 1.9M instantiations / 2.2 s with no errors. The remaining cost is in on(event, { tool: "…" }, handler) filters (ToolNamed<T> over the same union). Dropping the fallback breaks non-literal tool names, so this is a pointer, not a patch.

Side effect
Because claude-code-mcp is a snapshot of the writing session's connected servers, the same mod type-checks differently depending on which session last loaded it (e.g. a server that timed out on connect is missing).

Expected
Per-call cost independent of the number of declared tools (index by name), and/or a way for a mod to declare only the MCP tools it uses.

Related: #100736, #99771.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗