WhyType
TypeScript debugging, from agentic to manual

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.

01 For agents — MCP server

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:

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.

without
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>
with whytype
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.

02 CLI

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.

03 Playground

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.

04 VS Code extension

A "Why does this fail?" quick fix on any TypeScript diagnostic renders the because-chain for your own project, in the editor. Marketplace →

05 Library

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.