Summary
The wizard's agent runs with the user's own MCP servers merged into the request — project .mcp.json,
user-level config, plugins. Neither query() call site sets strictMcpConfig, and the Anthropic API
rejects the entire request if any single tool schema is invalid. For a user who has one bad
third-party tool anywhere in their MCP config, the wizard is 100% broken, terminally, with no hint that
their local MCP setup is the cause:
API Error: 400 tools.25.custom.input_schema.properties: Property keys should match pattern '^[a-zA-Z0-9_.-]{1,64}$'
Surfaced by a customer support ticket (wizard v2.62.0, tools.679 — i.e. 680+ tool definitions on the
wire; the wizard's own surface is ~15–20 tools).
Telemetry (90 days, project 2)
wizard: agent api error where error_message contains input_schema.properties: 4 events, 3
distinct users, tool index varying per user (679 / 236 / 97) across self-driving-setup, nextjs,
fastapi. Rare, but terminal for whoever hits it, and the class is unbounded: any malformed schema in
any MCP server a user happens to have installed takes the wizard down.
Root cause — replicated, not inferred
settingSources: ['project'] does not gate this. Per the SDK's own typings
(@anthropic-ai/claude-agent-sdk@0.3.169, the exact pin), settingSources governs filesystem
settings.json only; the sole gate on foreign MCP servers is:
strictMcpConfig — Only use MCP servers passed via the mcpServers option (and servers declared
by explicitly-passed agent definitions in agents), ignoring all other MCP configurations: project
.mcp.json, user settings, plugins, and on-disk agent frontmatter.
Repro (node:22, SDK 0.3.169, fake endpoint on ANTHROPIC_BASE_URL applying Anthropic's documented
property-key rule; a planted .mcp.json server with property key "bad key"; one wizard-style server
passed in code via mcpServers; options mirroring the wizard: settingSources: ['project'],
permissionMode: 'acceptEdits'):
| Scenario |
.mcp.json |
strictMcpConfig |
tools sent |
foreign tool on wire |
API |
| A — wizard as-is |
yes |
unset |
27 |
yes |
400 |
| B — with fix |
yes |
true |
26 |
no |
200 |
| C — control |
no |
unset |
26 |
no |
200 |
B ≡ C: the flag removes exactly the inherited server; in-code mcpServers survive untouched.
Affected call sites
src/lib/agent/agent-interface.ts:882 (options object; settingSources at :908)
src/lib/agent/mcp-prompt-streaming.ts:254 — allowedTools: ['mcp__posthog-wizard__*'] gates
invocation, not which tool definitions are serialized, so this site is vulnerable too
Non-regression: the general-purpose subagent's mcpServers: inheritedMcpServerNames is
Object.keys(agentConfig.mcpServers) (:852) — our own names, exempt per the doc. Svelte
additionalMcpServers merge into the same in-code map. skills: 'all' / CLAUDE.md are a different
option. The repo's own .mcp.json (wizard-ci) is consumed by the developer session driving the
wizard, not by the wizard's subprocess.
Two adjacent defects (fire in 100% of these cases)
- The error prints twice, identically, same
request_id. One HTTP request; the SDK surfaces the
text in both an assistant block and the terminal result. AgentOutputSignals.push() retains both
and apiErrorMessage() joins every match (output-signals.ts:86-91). Fix: dedupe there
([...new Set(m)]) — not in push(), since one copy arrives bare and one wrapped in
Claude Code returned an error result: …, so line-level dedupe misses it.
- Raw provider JSON + "report this to wizard@posthog.com" is the whole UX. Only 401/429 get
triage today. A tools.<n>. 400 is deterministic and self-diagnosable — the user should be told
their MCP config is implicated and that moving .mcp.json aside unblocks them.
Proposed fix
Prior art / related
Summary
The wizard's agent runs with the user's own MCP servers merged into the request — project
.mcp.json,user-level config, plugins. Neither
query()call site setsstrictMcpConfig, and the Anthropic APIrejects the entire request if any single tool schema is invalid. For a user who has one bad
third-party tool anywhere in their MCP config, the wizard is 100% broken, terminally, with no hint that
their local MCP setup is the cause:
Surfaced by a customer support ticket (wizard v2.62.0,
tools.679— i.e. 680+ tool definitions on thewire; the wizard's own surface is ~15–20 tools).
Telemetry (90 days, project 2)
wizard: agent api errorwhereerror_messagecontainsinput_schema.properties: 4 events, 3distinct users, tool index varying per user (679 / 236 / 97) across
self-driving-setup,nextjs,fastapi. Rare, but terminal for whoever hits it, and the class is unbounded: any malformed schema inany MCP server a user happens to have installed takes the wizard down.
Root cause — replicated, not inferred
settingSources: ['project']does not gate this. Per the SDK's own typings(
@anthropic-ai/claude-agent-sdk@0.3.169, the exact pin),settingSourcesgoverns filesystemsettings.jsononly; the sole gate on foreign MCP servers is:Repro (node:22, SDK 0.3.169, fake endpoint on
ANTHROPIC_BASE_URLapplying Anthropic's documentedproperty-key rule; a planted
.mcp.jsonserver with property key"bad key"; one wizard-style serverpassed in code via
mcpServers; options mirroring the wizard:settingSources: ['project'],permissionMode: 'acceptEdits'):.mcp.jsonstrictMcpConfigtrueB ≡ C: the flag removes exactly the inherited server; in-code
mcpServerssurvive untouched.Affected call sites
src/lib/agent/agent-interface.ts:882(options object;settingSourcesat :908)src/lib/agent/mcp-prompt-streaming.ts:254—allowedTools: ['mcp__posthog-wizard__*']gatesinvocation, not which tool definitions are serialized, so this site is vulnerable too
Non-regression: the
general-purposesubagent'smcpServers: inheritedMcpServerNamesisObject.keys(agentConfig.mcpServers)(:852) — our own names, exempt per the doc. SvelteadditionalMcpServersmerge into the same in-code map.skills: 'all'/ CLAUDE.md are a differentoption. The repo's own
.mcp.json(wizard-ci) is consumed by the developer session driving thewizard, not by the wizard's subprocess.
Two adjacent defects (fire in 100% of these cases)
request_id. One HTTP request; the SDK surfaces thetext in both an assistant block and the terminal
result.AgentOutputSignals.push()retains bothand
apiErrorMessage()joins every match (output-signals.ts:86-91). Fix: dedupe there(
[...new Set(m)]) — not inpush(), since one copy arrives bare and one wrapped inClaude Code returned an error result: …, so line-level dedupe misses it.triage today. A
tools.<n>.400 is deterministic and self-diagnosable — the user should be toldtheir MCP config is implicated and that moving
.mcp.jsonaside unblocks them.Proposed fix
strictMcpConfig: trueat both call sitesoptions.strictMcpConfig === trueon the wire —query()is loaded viaan untyped dynamic import at both sites (
agent-interface.ts:63-67,mcp-prompt-streaming.ts:28), so a typo silently no-ops through build and testsapiErrorMessage()TOOL_SCHEMA_REJECTEDtriage + honest copy (follow the existing 401 pattern; keep theagent api errorevent name, varyerror_type)Prior art / related
.mcp.jsoncannot auto-spawnservers"), closed unmerged after a stamphog ESCALATE (needs owning-team review — plan for that here)
anyOf),open since June: with inheritance unfixed, the wizard installing the PostHog MCP server at
--scope user(clients/claude-code.ts:135) means we can break our own next runmcp__<name>__*for every mounted server is only safe once inheritance is off;strictMcpConfigis effectively its prerequisiterunner/harness/pi/mcp.ts:73