Provides TypeScript inferred types, completions, and type errors to AI coding agents via an MCP server.
Also known as clockblocker/typeprobe
- Stars
- 1
- Tools
- 12
- Downloads, 30 days
- —
- Latest version
- 4.0.0
- Last commit
- Oct 10, 2026
- License
- MIT
What is typeprobe MCP?
typeprobe provides TypeScript inferred types, completions, and type errors to AI coding agents via an MCP server. It exposes 12 tools, such as hover_by_name, hover, batch_hover.
Summary generated from the project's README
About typeprobe MCP server
Providing TypeScript inferred types, completions, and type errors to AI coding agents is what typeprobe does. It allows developers to give their AI clients the actual intelligence of the TypeScript compiler so that agents do not have to guess about code structures. The server works via stdio and can be integrated into various environments including Claude Code, Cursor, VS Code, and the Gemini CLI through simple setup commands.
Users can access specific tools like hover_by_name to find the type of a symbol, batch_hover for multiple lookups at once, and completions to see available members or union values at a cursor position. It also provides diagnostics to check for errors in a specific file and annotations to identify redundant or widening type declarations. For testing, the typeprobe/testing library allows developers to lock in inferred types using snapshot tests in runners like Vitest or Jest.
What you can do with it
- 1Verify symbol typesAn AI agent can determine the exact type of a variable or function by its name within a specific file.
- 2Get code completionsAn AI can retrieve TypeScript completions, such as string-literal union members, at a specific cursor position.
- 3Prevent type regressionsDevelopers can use the testing utility to ensure that refactoring does not unintentionally change inferred types.
Written from the project's README. Every use case is checked against a quote from it.
How to install typeprobe MCP
Formats verified 10 Nov 2026Add to CursorAdd to GooseInstall typeprobe MCP in Claude Code
$ claude mcp add typeprobe -- npx -y typeprobeInstall typeprobe MCP in Cursor
~/.cursor/mcp.json
{
"mcpServers": {
"typeprobe": {
"command": "npx",
"args": [
"-y",
"typeprobe"
]
}
}
}Install typeprobe MCP in VS Code
.vscode/mcp.json
{
"servers": {
"typeprobe": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"typeprobe"
]
}
}
}Install typeprobe MCP in VS Code CLI
$ code --add-mcp '{"name":"typeprobe","type":"stdio","command":"npx","args":["-y","typeprobe"]}'Install typeprobe MCP in Claude Desktop
claude_desktop_config.json
{
"mcpServers": {
"typeprobe": {
"command": "npx",
"args": [
"-y",
"typeprobe"
]
}
}
}Install typeprobe MCP in Codex
$ codex mcp add typeprobe -- npx -y typeprobeInstall typeprobe MCP in Codex config.toml
~/.codex/config.toml
[mcp_servers.typeprobe] command = "npx" args = ["-y", "typeprobe"]
Install typeprobe MCP in Gemini CLI
$ gemini mcp add -s user typeprobe npx -- -y typeprobeInstall typeprobe MCP in Gemini settings.json
~/.gemini/settings.json
{
"mcpServers": {
"typeprobe": {
"command": "npx",
"args": [
"-y",
"typeprobe"
]
}
}
}typeprobe MCP tools 12
Detected from the READMEhover_by_namehoverbatch_hovercompletionsdiagnosticsannotationsbackendsignaturedisplaykindcostcompiler
| hover_by_name | — | |
| hover | — | |
| batch_hover | — | |
| completions | — | |
| diagnostics | — | |
| annotations | — | |
| backend | — | |
| signature | — | |
| display | — | |
| kind | — | |
| cost | — | |
| compiler | — |
Configuration
TYPEPROBE_BACKENDBackend for the hover and diagnostics tools. Use typescript6 if the native TypeScript 7 backend fails in your project.
TYPEPROBE_COMPILERWhich TypeScript compilers to use: typeprobe's bundled ones, the project's own (typescript 5.0 to 6.x; typescript 7 or @typescript/native-preview), or auto: the project's when supported, else bundled.
| Name | Description | |
|---|---|---|
| TYPEPROBE_BACKEND | Backend for the hover and diagnostics tools. Use typescript6 if the native TypeScript 7 backend fails in your project. | |
| TYPEPROBE_COMPILER | Which TypeScript compilers to use: typeprobe's bundled ones, the project's own (typescript 5.0 to 6.x; typescript 7 or @typescript/native-preview), or auto: the project's when supported, else bundled. |
Automated checks
Ran Nov 14, 2026Full report →Passed our automated checks. That is not a security audit: read the code and give the server the narrowest access it needs. How checks work
- Source repository linked and reachableclockblocker/typeprobe is public and reachablegithub.com/clockblocker/typeprobe ↗Pass
- Package names this repository as its sourcetypeprobe names clockblocker/typeprobe as sourcenpmjs.com/package/typeprobe ↗Pass
- Build provenance (Sigstore attestation)No build attestation published for this releaseNot checked
- Open source license (OSI)MITspdx.org/licenses/MIT.html ↗Pass
- Commit or release in the last yearLast commit or release 2026-10-10github.com/clockblocker/typeprobe ↗Pass
- OpenSSF ScorecardNot checked
- Package name not close to a popular packageNo popular package one edit away from typeprobePass
- No critical advisory in the package or its dependencies26 resolved dependencies checked; no critical advisoryosv.dev ↗Pass
- No hidden instructions to the model12 tool descriptions and the README read; no override phrases, credential paths or hidden charactersPass
- Destructive tools are labelledNo tool named like delete, drop or execPass
- Remote endpoint answers the MCP handshakeNot checked
README
View on GitHubtypeprobe
typeprobe asks the TypeScript compiler what it infers (types, completions, type errors) so that AI coding agents and tests don't have to guess. It comes as:
- an MCP server for coding agents (Claude Code, Codex, Cursor, VS Code, Gemini CLI, any stdio client),
typeprobe/testing, which returns inferred types as strings for snapshot tests,- a CLI and a synchronous library API.
typeprobe was called prinfer until 4.0.0. See Migrating from prinfer.
Quick start
Give your agent the compiler:
npx -y typeprobe setup claude # or codex, cursor, vscode, gemini; see Install
The agent can now call hover_by_name(file: "src/utils.ts", name: "names") and get Type: string[] back, or diagnostics(file) after an edit.
Lock the types your API infers in a test (Vitest, Jest, Bun, or any runner with snapshots):
import { expect, test } from "vitest"; // or "bun:test"
import { inferredType } from "typeprobe/testing";
import { groupBy, type User } from "../src/users";
const users: User[] = [{ name: "Ada", role: "admin" }];
const byRole = groupBy(users, (user) => user.role);
test("groupBy keys the result by the callback's return type", () => {
expect(inferredType(import.meta.url, { name: "byRole" }))
.toMatchInlineSnapshot(`"Record<Role, User[]>"`);
});
Write the matcher empty (toMatchInlineSnapshot()) and the runner fills it in. A refactor that changes the type fails the test.
Install
Every command runs through npx, so nothing needs installing first. After npm i -g typeprobe, drop the npx -y prefix: setup then registers the typeprobe-mcp binary, which skips npx's package check on each launch.
Claude Code. The plugin bundles the MCP server and a skill that tells Claude when to use it:
/plugin marketplace add clockblocker/typeprobe
/plugin install typeprobe@typeprobe
Other clients, or Claude Code without the plugin:
| Client | Command | Writes |
|---|---|---|
| Claude Code | npx -y typeprobe setup claude |
claude mcp add; --scope project for a shared .mcp.json |
| Codex | npx -y typeprobe setup codex |
codex mcp add (~/.codex/config.toml) |
| Cursor | npx -y typeprobe setup cursor |
~/.cursor/mcp.json; --scope project: .cursor/mcp.json |
| VS Code | npx -y typeprobe setup vscode |
code --add-mcp; --scope project: .vscode/mcp.json |
| Gemini CLI | npx -y typeprobe setup gemini |
~/.gemini/settings.json; --scope project: .gemini/settings.json |
Any other MCP client: typeprobe is a stdio server started with npx -y typeprobe mcp.
{
"mcpServers": {
"typeprobe": { "command": "npx", "args": ["-y", "typeprobe", "mcp"] }
}
}
It is also listed in the MCP Registry as io.github.clockblocker/typeprobe and on Smithery.
Setup options:
--printshows the command or config change without applying it.--npxregistersnpx -y typeprobe mcpeven whentypeprobe-mcpis installed. Use it when the client can't findtypeprobe-mcp; editors started outside a shell often miss nvm, fnm, or volta paths.- Re-running setup updates the
typeprobeentry's command and leaves other servers alone. JSON configs keep keys you added to the entry, such asenv. A JSON config that doesn't parse is left untouched, and setup prints the entry to add by hand. - A
prinferentry from before the rename is replaced rather than kept next to the new one:claudeandcodexremove it, and JSON configs swap it fortypeprobein place, keeping its other keys (withPRINFER_*env names renamed toTYPEPROBE_*). - On Windows the server is registered as
cmd /c npx -y typeprobe mcp(orcmd /c typeprobe-mcp), because clients launch it without a shell and can't run npm's.cmdshims.
Tell the agent when to use it
Tool descriptions only go so far; agents still reach for tsc or write annotations by hand. Add a short usage block to your instructions file (the Claude Code plugin ships the same guidance as a skill):
npx -y typeprobe setup agents-md # ./AGENTS.md
npx -y typeprobe setup agents-md --file CLAUDE.md
The block sits between <!-- typeprobe:start --> and <!-- typeprobe:end -->; re-running updates it in place, and replaces a block prinfer wrote (<!-- prinfer:start -->).
MCP tools
| Tool | Use it for |
|---|---|
hover_by_name |
The type of a named symbol. Start here. |
hover |
The type of a token on a line: callback parameters, expressions, repeated names. |
batch_hover |
Up to 100 lookups, across files, in one call. |
completions |
What TypeScript offers at a cursor, such as string-literal union members. |
diagnostics |
Type errors in one file, without checking the whole project. |
annotations |
Annotations TypeScript would infer anyway. |
Shared parameters:
fileis absolute or relative to the server's working directory. Lines and columns are 1-based.projectis atsconfig.jsonpath; the default is the nearest one above the file.backend(typescript7by default, ortypescript6) on the hover tools anddiagnostics. If a call fails or looks wrong on TypeScript 7, retry it withtypescript6. See Backends and compilers.- Hover tools take
include_docs(JSDoc),include_cost(see Type cost budgets),full, andmax_chars. Type text is capped at 4000 characters per type, and a cut type ends with a line like… truncated: 187 chars total, union of 4 members. Pass max_chars: N to see more (0 for no limit).full: trueturns off TypeScript's own truncation ({ ...; },... 12 more ...) and lists every overload. Structured content is never cut.
hover_by_name
hover_by_name(file: "src/utils.ts", name: "names")
hover_by_name(file: "src/utils.ts", name: "names", line: 11) # line picks among same-named symbols
Type: string[]
Name: names
Kind: const
Position: 11:14
When a name is declared more than once, the lookup prefers declarations and says which it picked: Matched line 8 of 2 declarations (also 12); pass line to choose. The structured result lists the others as alternatives: [{ line, column, kind }]. A line that matches none fails with SYMBOL_NOT_FOUND and lists the declaration lines in declaredAt. A line where the name is used rather than declared, such as box.value in an if, gives the type there, narrowed by the code around it.
Overloaded functions show the first signature, then up to three more (full: true lists all; structured overloads always has every one):
Type: (value: string): number (+1 overload)
Overloads:
(value: number): string
Returns: number
Name: parse
Kind: function
Position: 1:17
hover
Pass text copied from the line and typeprobe finds the column. Whole identifiers match first: on users.map((user) => user.name), text: "user" skips users and hits the callback parameter, and occurrence: 2 picks the next user. Only when the line has no whole-identifier match is text matched as a substring. column works in place of text. Generic calls show their instantiated types, as in an editor.
hover(file: "src/utils.ts", line: 11, text: "user")
Type: { id: number; name: string; }
Name: user
Kind: parameter
Position: 11:33
Target: "user" at 11:33
If the text isn't on the line, the error quotes the line:
Error [SYMBOL_NOT_FOUND]: Text "nope" not found on line 11 of /project/src/utils.ts
Nearby identifiers: name, names, map, user, users
Suggestion: Line 11 reads: "export const names = users.map((user) => user.name);". Copy text exactly from it, or give a column instead.
batch_hover
Each item is {name, line?}, {line, text, occurrence?}, or {line, column}, with an optional file that overrides the shared top-level file. Failures are per item, including a missing file, so one bad lookup doesn't lose the rest.
batch_hover(file: "src/utils.ts", positions: [
{name: "users"},
{line: 11, text: "user"},
{file: "src/missing.ts", line: 1, column: 1}
])
Batch hover results: 2 succeeded, 1 failed
--- src/utils.ts:users:6 ---
Type: { id: number; name: string; }[]
...
--- src/missing.ts:1:1 ---
Error [FILE_NOT_FOUND]: File not found: /project/src/missing.ts
Suggestion: Check the path. Relative paths resolve against the MCP server's working directory (/project); pass an absolute path to be sure.
A line or column outside the file is an INVALID_ARGUMENT error that gives the valid range.
Hover results
Every hover returns the same fields on every backend and surface:
signature: the type text alone, on one line, without the declaration keyword or name an editor hover starts with.string[]for a variable,(value: string): numberfor a function, the instantiated signature for a call. Type aliases keep their name and type parameters (type Event = { kind: "open"; ... } | ...); interfaces and classes are their name (Box<T>). Optional members read astscwrites them in declarations:digits?: number, with| undefinedonly where the source wrote it or the type isn't the annotation's (an instantiated generic, aPartial<T>).display: the editor's hover text (const names: string[]). Only the TypeScript 7 language server reports it.kind: the editor's label (function,method,const,parameter,property,type,interface,class,enum, ...), pluscallfor the callee of a call.returnType,documentation,overloads,unionMembers, andalternativeswhen they apply.cost:{ instantiations, types }withinclude_cost: true.compiler:{ name, version, source }, the compiler that answered.
completions
The cursor sits before the character at column; for a string union, put it just inside the opening quote.
completions(file: "src/utils.ts", line: 14, column: 30)
completions(file: "src/utils.ts", line: 20, column: 1, prefix: "use", limit: 20)
Entries come in TypeScript's ranking: locals, members, and literal values first, then globals (keywords after other entries of the same rank), then auto-imports. At most limit come back (default 50, up to 500); when more match, the text ends with … 947 more; pass prefix to narrow, or raise limit and the structured result has total and truncated: true.
prefix keeps names that start with it, ignoring case. Without it, the text typed left of the cursor filters the list as in an editor (after use in useSt, only use… names); prefix: "" turns that off. At a key of an object literal that accepts any key (Record<string, number>), typeprobe returns no entries and a note saying so, instead of every global. completions always runs on TypeScript 6.
diagnostics
Run it on each file you edited; the edit is done when none reports an error.
diagnostics(file: "src/utils.ts")
diagnostics(file: "src/utils.ts", include_suggestions: true) # also TS6133 unused variables etc.
src/utils.ts:16:14 error TS2322: Type 'string' is not assignable to type 'number'.
1 error, 0 warnings.
A clean file returns No type errors.
annotations
annotations(file: "src/utils.ts")
src/utils.ts:2:55 redundant return type of format: declared string, inferred string (exported)
src/utils.ts:14:19 widening drink: declared Drink, inferred "tea" (exported)
2 redundant, 2 widening (5 annotations checked).
redundant: delete the annotation; the type stays the same.
widening: the annotation is wider than the inferred type; keep it if the wider type is intended.
A redundant annotation can be deleted without changing the type. A widening one is often deliberate (a variable that must accept any Drink later, an exported contract), so treat it as a question. Only annotations with an initializer or body to infer from are checked. Each structured finding has the range of the : Type text, declared, inferred, exported, and a suggestion. annotations always runs on TypeScript 6.
Errors
Every tool returns text plus structured content: { version: 1, ok: true, result } or { version: 1, ok: false, error }. The text of a failure carries the code, message, and recovery hints, because many clients show the model only the text:
Error [SYMBOL_NOT_FOUND]: No symbol named "nmes" at line 11 found in src/utils.ts
Did you mean: names, name?
Suggestion: Check the spelling against candidates, pass line to pick the match on a known line, or call hover with that line and text copied from it.
error has code (INVALID_ARGUMENT, FILE_NOT_FOUND, SYMBOL_NOT_FOUND, TYPESCRIPT_ERROR, or INTERNAL_ERROR), message, suggestion, and, where they apply, file, line, column, project, candidates (close identifiers from the file, or those near the line), and declaredAt. Each tool's outputSchema covers both outcomes in one envelope and lists the error fields an agent recovers with; the others are still sent. The CLI's --json output and the exported zod schemas (hoverSuccessSchema, contractErrorResponseSchema, ...) use the same contract.
Type tests
typeprobe/testing returns the type as the editor displays it, so the snapshot is never written by hand, unlike expectTypeOf or tsd, and any change shows up, including a literal union widening to string. Update snapshots after an intended change with the runner's update flag (vitest -u, bun test --update-snapshots). The lookup helpers are synchronous on both backends, so there is no promise to forget to await.
import { inferredCompletions, inferredType } from "typeprobe/testing";
// Another module: resolve it against the test file.
expect(inferredType(new URL("../src/users.ts", import.meta.url), { name: "groupBy" }))
.toMatchInlineSnapshot(`"<T, K extends PropertyKey>(items: readonly T[], key: (item: T) => K): Record<K, T[]>"`);
// Line 7 of this test file reads: const fallback: Role = "member";
expect(inferredCompletions(import.meta.url, { line: 7, text: '"' }))
.toMatchInlineSnapshot(`
[
"admin",
"member",
]
`);
| Helper | Returns |
|---|---|
inferredType(file, selector) |
The printed type (signature). |
inferredTypeInfo(file, selector) |
The full hover result: name, kind, return type, docs, cost. |
inferredCompletions(file, selector) |
Every completion name at a cursor, on TypeScript 7, with no filter or limit. |
inferredTypeCost(file, selector) |
{ instantiations, types }; see Type cost budgets. |
inferredTypeIssues(file, selector) |
Readability issues; see Readability checks. |
typeReadabilityIssues(text, rules?) |
Readability issues in any printed type text. |
expectType(file, selector) |
Checks text, cost, and readability at once; see expectType. |
expectTypes(file, selector) |
expectType for several targets, plus a budget for all of them; see expectTypes. |
closeTestingSessions() |
Stops the TypeScript 7 compilers early; see TypeScript 7 in tests. |
The file is import.meta.url for the test file itself, or a URL resolved against it. Plain string paths resolve against process.cwd(). The file is read through its nearest tsconfig.json, or project.
The selector picks the target:
{ name, line? }: a declaration by name;linepicks among repeats.{ line, text, occurrence? }: the token wheretextstarts on that line, matched like thehovertool'stext. ForinferredCompletionsthe cursor goes right after the match, sotext: "user."lists members andtext: '"'lists string-literal members;cursor: "start"puts it before.{ line, column }: a 1-based position (for completions, the cursor sits before that column).
Options go in the same object, e.g. { name: "byRole", backend: "typescript7" }. A third argument throws.
| Option | Default | Effect |
|---|---|---|
backend |
"typescript6" |
"typescript7" prints TypeScript 7's output. inferredCompletions always uses TypeScript 7. |
full |
true |
false gives the editor's shortened form ({ ...; }). |
sort_unions |
false |
Prints unions in a fixed order; see below. |
include_docs |
false |
Adds documentation to inferredTypeInfo. |
include_cost |
false |
Adds cost to inferredTypeInfo (TypeScript 6 only). |
project |
nearest tsconfig.json |
The tsconfig to read the file with. |
compiler |
TYPEPROBE_COMPILER, else "bundled" |
Whose TypeScript to use; see Bundled or project compilers. |
timeout |
60000 |
Milliseconds a TypeScript 7 call may block before it throws. |
strict |
false |
Throws on unknown keys, naming the closest valid one. Test runners don't type-check test files, so without it a camelCase sortUnions does nothing. |
Failed lookups throw with the fix in the message: an unknown name lists the closest declarations, missing text quotes the line, and a missing relative path explains how to resolve it against the test file.
typeprobe/vitest is a deprecated alias for typeprobe/testing.
Stable union order
TypeScript 6 and TypeScript 7 print union members in different orders ("idle" | "error" on one, "error" | "idle" on the other), so a snapshot written on one backend fails on the other. sort_unions: true prints every union, at any depth, in the same order on both: members sorted by their text (UTF-16 code units, so 1 | 10 | 2), with null and undefined last.
expect(inferredType(import.meta.url, { name: "status", sort_unions: true }))
.toMatchInlineSnapshot(`""error" | "idle" | "loading" | null"`);
Only union members move. Property order can still differ: cond ? { ok: true, value: 1 } : { ok: false, error: "e" } prints { ok: false; error: string; value?: undefined; } on TypeScript 6 and { value?: undefined; ok: false; error: string; } on TypeScript 7, and a mapped type over a union of keys lists its properties in each backend's union order. The library's hover options and the CLI (--sort-unions) take the option too.
Types of values you don't have
To pin what a function infers for an argument you have no value for, declare the argument in a fixture file. A declare const in the test file itself has no runtime value, so the test throws a ReferenceError when it runs.
// test/groupBy.fixture.ts, read for types only, never run
import { groupBy, type User } from "../src/users";
declare const input: User[];
export const byName = groupBy(input, (user) => user.name);
// test/users.test.ts
expect(inferredType(new URL("./groupBy.fixture.ts", import.meta.url), { name: "byName" }))
.toMatchInlineSnapshot(`"Record<string, User[]>"`);
Readability checks
A type can be right and still print in a form a reader has to work out. inferredTypeIssues(file, selector) returns those places in a target's printed type (an empty array means none), and typeReadabilityIssues(text, rules?) in any type text. The default rules:
utility-type: an unresolved utility type with type arguments, at any depth:Omit,Pick,Partial,Required,Readonly,Exclude,Extract,NonNullable,ReturnType,Parameters,ConstructorParameters,InstanceType,Awaited,ThisParameterType,OmitThisParameter(exported asDEFAULT_UTILITY_TYPES). Only the outermost of nested ones is reported. One over a type parameter of the printed signature (Omit<T, "id">in<T>(value: T) => Omit<T, "id">) is not, since nothing can resolve it beforeTis known.Record,Promise,Array, and other generic types are not flagged.object-intersection: an intersection with an object type, such asUser & { id: string; }or a mapped type.string & {}, which keeps literal suggestions, is not flagged.truncation:... 3 more ...,{ ...; }, a...placeholder, and text cut at the length limit. The helpers print untruncated types unlessfull: false, so this mostly catchesfull: falseand text from elsewhere.
The text is read with the TypeScript scanner and parser, so a string literal type like "Omit<" or "..." is not flagged, nor is a rest parameter or spread tuple. Each issue is { rule, text, offset, message }. Text that doesn't parse even with truncation markers removed (cut at the length limit) gets truncation issues only.
rules takes utilityTypes (the names to flag, e.g. [...DEFAULT_UTILITY_TYPES, "DeepPartial"], or false), objectIntersections and truncation (false turns them off), and allow, fragments that are fine as printed:
expect(
inferredTypeIssues(import.meta.url, { name: "userId", rules: { allow: ['string & { readonly __brand: "UserId"; }'] } }),
).toEqual([]);
typeprobe MCP questions
How do I add typeprobe MCP to Claude Code?
Run claude mcp add typeprobe -- npx -y typeprobe in your terminal. Add --scope user to make it available in every project.
How do I add typeprobe MCP to Cursor?
Open ~/.cursor/mcp.json and add the server under mcpServers, using the Cursor config shown in the install section above.
What tools does typeprobe MCP provide?
It exposes 12 tools, including hover_by_name, hover, batch_hover, completions, diagnostics.
What can typeprobe do for my AI agent?
It allows an agent to query the TypeScript compiler for inferred types, completions, and diagnostics so it doesn't have to guess.
How do I install typeprobe for Claude Code?
You can add it using the plugin marketplace commands: /plugin marketplace add clockblocker/typeprobe and /plugin install typeprobe@typeprobe.