@upstash/context7-mcp v3.1.0 never sets isError — all errors return as successful text, agents cannot distinguish failure from docs
Reproduced behavior
@upstash/context7-mcp v3.1.0 (via npx -y @upstash/context7-mcp, stdio transport) never sets the isError field in its tools/call results. Per the MCP spec, isError defaults to false when absent — so every response looks like a success, even when the server is returning an error message.
Evidence
All probes below used JSON-RPC over stdio against a freshly spawned server process.
1. Validation error — empty string coerced to undefined
// request
{"method":"tools/call","params":{"name":"query-docs","arguments":{"libraryId":"","query":"hooks"}}}
// response.result
{"content":[{"type":"text","text":"Invalid input: expected string, received undefined"}]}
// isError field: ABSENT (not false — missing entirely)The libraryId value is "" (empty string), but the error says "received undefined" — revealing an internal coercion from empty string to undefined before validation.
2. Format validation error
// request
{"method":"tools/call","params":{"name":"query-docs","arguments":{"libraryId":" ","query":"hooks"}}}
// response.result
{"content":[{"type":"text","text":"Invalid library ID format: \" \". Expected format: /owner/repo or /owner/repo/version"}]}
// isError field: ABSENT3. Library not found
// request
{"method":"tools/call","params":{"name":"query-docs","arguments":{"libraryId":"/nonexistent/zzz-does-not-exist-12345","query":"setup"}}}
// response.result
{"content":[{"type":"text","text":"Library \"/nonexistent/zzz-does-not-exist-12345\" not found. Please check the library ID or your access permissions."}]}
// isError field: ABSENT4. Missing required input (both empty)
// request
{"method":"tools/call","params":{"name":"resolve-library-id","arguments":{"query":"","libraryName":""}}}
// response.result
{"content":[{"type":"text","text":"Library name is required"}]}
// isError field: ABSENT5. Successful response (for contrast)
// request
{"method":"tools/call","params":{"name":"query-docs","arguments":{"libraryId":"/vercel/next.js","query":"app router"}}}
// response.result
{"content":[{"type":"text","text":"### Create App Router Home and About Pages\n\nSource: https://..."}]}
// isError field: ALSO ABSENTImpact
An MCP client (or an agent orchestrating tool calls) has no structured way to detect that a Context7 call failed. The only option is to parse the English text for phrases like "Invalid input", "not found", "is required" — which is fragile, locale-dependent, and defeats the purpose of a structured protocol.
This is the same class of bug as the mcp-server-fetch pagination issue (in-band errors that contradict the protocol's isError field), but more systematic: it affects every error path in the server, not just one edge case.
Additional finding: empty string → undefined coercion
Passing libraryId: "" produces "expected string, received undefined". The empty string is being coerced to a falsy/undefined value before the schema validator runs. By contrast, libraryId: " " (single space) correctly reaches the format validator and produces a different, more specific error. This suggests the server uses a value || undefined pattern (or equivalent truthy check) that treats empty string as missing.
Reproduction
npx -y @upstash/context7-mcp # stdio, then send JSON-RPC init + tool callVersion: v3.1.0. No auth required. Tested 2026-06-10.