One engine extracts the compiler's actual reasoning — diagnostics as because-chains, generic inference bindings, and conditional-type traces with real checker verdicts. Five doors into it, pick yours: an MCP server for agents, a CLI, an in-browser playground, a VS Code extension, and a library.
An agent debugging TypeScript sees what tsc prints: the flattened
summary of a forty-line elaboration — "Type X is not assignable to type Y."
The actual cause is a declaration three levels down the compiler's reasoning,
often in another file, and it never reaches the context window. So the agent
guesses: a cast, an any, an edit at the use site. Each wrong guess
costs a full edit–compile–fail loop. whytype_explain returns the
compiler's own reasoning chain and the related declaration site as markdown —
the agent fixes the cause on the first loop.
# the project needs its own typescript (>=5 <7) npm i -D whytype npx whytype init # writes .mcp.json + Cursor config + CLAUDE.md block
init is idempotent and never overwrites config it didn't write
(--dry-run previews). Manual routes still work:
claude mcp add whytype -- npx whytype mcp, or for Cursor, Windsurf, and
any other MCP client:
{ "mcpServers": { "whytype": { "command": "npx", "args": ["whytype", "mcp"] } } }
Three tools appear:
whytype_diagnostics — compact error list: file, line, column, code — with a typescript X — tsconfig.json header; offset/limit paginate, project targets a monorepo package.whytype_explain — the full reasoning at a location: nested because-chain, inferred type arguments, which conditional branch fired and why.whytype_snippet — explain a pasted snippet, no project needed.
Hooks — zero tool calls: npx whytype init --hooks wires a Claude
Code PostToolUse hook: after every edit, whytype hook
re-checks the edited file and feeds new errors back as because-chains
automatically. Each hook run is a cold compile (seconds on big projects) — hooks
surface regressions, the MCP tools are the fast investigation loop.
tsc> Type 'string' is not assignable to type 'number'. (line 4) agent> casts the value — as number tsc> error moved to the consumer agent> widens the field — any tsc> clean, bug shipped agent> …
agent> whytype_explain(src/main.ts:4:30) whytype> ## error TS2322 — src/main.ts:4:30 Type 'string' is not assignable to type 'number'. ``` 2 | 3 | // Cross-file mismatch: `port` is declared in shapes.ts. > 4 | export const cfg: Config = { port: "80", host: "localhost" }; | ^^^^ 5 | 6 | // Generic inference at a call site. ``` Related: - src/shapes.ts:2:3 — The expected type comes from property 'port' which is declared here on type 'Config' (TS6500) **`port`** : `string` _typescript 6.0.3_ agent> fixes the declaration site. one loop.
The "with whytype" block is real, unedited whytype_explain output
(the fixture lives in the repo at spike/fixtures/demo-project). Deeper
mismatches arrive as a nested Because: bullet chain, one bullet per
elaboration step.
npx whytype src/app.ts:42:7 # explain one location npx whytype check # every project error as a because-tree npx whytype check --json # stable wire types for scripts
tsconfig.json is found upward from cwd (--project to override). Exit
codes: 0 clean, 1 errors found, 2 usage/environment. Runs on your project's own
typescript install.
whytype.dev — paste code, click an error, read the reasoning. Runs entirely in your browser: the compiler lives in a web worker, nothing leaves the tab. Share buttons encode code into the URL fragment.
A "Why does this fail?" quick fix on any TypeScript diagnostic renders the because-chain for your own project, in the editor. Marketplace →
npm i whytype
// snippet engine (browser or Node) import { analyze, initEngine, inspect } from "whytype"; // real-project mode + markdown rendering (Node) import { createProject } from "whytype/node"; const project = createProject({ rootDir: process.cwd() }); console.log(project.explainMarkdown({ file: "src/app.ts" }));
Inference bindings read one internal checker field, validated against the pinned TS 6 — on other 5.x/6.x versions they are best-effort and degrade to absent, never wrong. Everything else uses public compiler API only.