Skip to content

mcp over stdio typescript and javascript no network

DaiVELOPER

An MCP server that gives a coding model a code-structure-aware view of a repository instead of a generic filesystem.

Ask for a symbol, get the symbol. Ask for a repo map, get signatures rather than bodies. Read returns whole files and Grep returns unstructured lines, so a structural question is answered by pulling thousands of tokens of file text into context and having the model do the parsing. The parsing is cheap and deterministic; paying for it in model context is the waste.

Five runs, one proof of concept. Not a product. On twelve questions of its own choosing, over a fixture of its own construction, it delivered the same evidence as a scripted native-tool agent for 21% of the approximate tokens — reproducibly, and with no model in the loop.

That is not evidence that it generalises, and nothing here claims it is.

what is here

Six things, and the last one argues with the fourth

  • The filesystem interface is structure-blind

    Read returns whole files and Grep returns unstructured lines, so 'where is handleAuth defined and who calls it' is answered by pulling thousands of tokens of file text into context and having the model do the parsing.

  • Eight tools, each replacing a specific native move

    Six retrieval tools and two symbol-scoped edits. Every one of them names the native call it is meant to be reached for instead of, because a tool a model does not reach for is a tool that costs its description and returns nothing.

  • Every response says what it did not see

    A response states how many files it scanned and how many it could not index, scoped to the path searched, so a gap never reads as a confident 'not found'. On an unfamiliar repository that line read 120 files scanned, 127 unindexed.

  • Twelve questions, 3,486 tokens, and one scenario carrying it

    Against a scripted native-tool agent's 16,495 approximate tokens, the treatment arm answered all twelve on 3,486 — a 78.9% reduction with answer derivability held at 12/12 in both arms.

  • A write that would not parse is refused, not repaired

    replace_symbol and insert_symbol re-parse the prospective file in memory first and refuse if it does not parse, naming the line. Validation happens before the write, never after.

  • What it is not

    Five runs, one proof of concept. Not a product. No editor integration, no LSP, no remote service, no semantic search, no embeddings, and no network at runtime.

01 · structure-blind

The filesystem interface is structure-blind

Read returns whole files and Grep returns unstructured lines, so 'where is handleAuth defined and who calls it' is answered by pulling thousands of tokens of file text into context and having the model do the parsing.

The parsing is cheap and deterministic. Paying for it in model context is the waste, and it is the only thing this project is about.

Twelve lines of a source module drawn as blocks: three declarations at the margin, their indented bodies, and two closing braces. Every line is present, because this is what a Read returns. Twelve lines, and nine of them are bodies. A Read charges for all twelve; the question was which three declarations the file contains.
Two drawings of one twelve-line source fragment side by side. On the left, what Read returns: all twelve lines, indented bodies and closing braces included. On the right, what outline returns: the three declaration lines only, in the same positions, with the nine body lines blank. Nine of these twelve lines are bodies. On the benchmark's 605-line module the same substitution answers 'what is the last declaration in this file' for 91 approximate tokens, against the 1,068 the scripted native arm spent on the same question.

02 · eight-tools

Eight tools, each replacing a specific native move

Six retrieval tools and two symbol-scoped edits. Every one of them names the native call it is meant to be reached for instead of, because a tool a model does not reach for is a tool that costs its description and returns nothing.

The eight descriptions and schemas cost 2,000 approximate tokens, paid once per session — run 5 cut them 18% by tightening the wording and the schemas, merging and removing nothing. At the median per-scenario saving of 405 tokens, a session has to ask about five structural questions before that introduction has paid for itself.

claude mcp add -s user daiveloper -- \  node /abs/path/to/daiveloper/dist/server/main.js
  • outline
  • find_symbol
  • read_symbol
  • search_code
  • find_callers
  • find_references
  • replace_symbol — write
  • insert_symbol — write

03 · audit

Every response says what it did not see

A response states how many files it scanned and how many it could not index, scoped to the path searched, so a gap never reads as a confident 'not found'. On an unfamiliar repository that line read 120 files scanned, 127 unindexed.

That second number is Markdown, JSON, CSS and everything else the grammars do not cover. It is also the reminder that 'not found' from these tools means 'not found in TypeScript and JavaScript'. A bounded response says how many results exist and what offset fetches the rest; nothing truncates silently.

Two answers to the question 'where is formatCurrency defined'. A grep over the fixture returns 64 matching lines across 21 of 48 files, of which three are declarations and two are unlabelled decoys, for 1,567 approximate tokens. A find_symbol call returns two definitions and one name-contains match marked with a tilde, states that it scanned 45 files and could not index 3, and costs 132 approximate tokens. 132 against 1,567, for the same three declarations. The 45-scanned-3-unindexed line is the part a grep has no equivalent of: it is what stops a gap in the index reading as a confident 'not found'.

04 · measured

Twelve questions, 3,486 tokens, and one scenario carrying it

Against a scripted native-tool agent's 16,495 approximate tokens, the treatment arm answered all twelve on 3,486 — a 78.9% reduction with answer derivability held at 12/12 in both arms.

And 95.2% of the margin over the cheapest possible native sequence comes from one scenario of the twelve. Remove it and the remaining eleven total 2,529 approximate tokens against that floor's 2,826: under it before the session preamble, and above it once the preamble is charged. The aggregate is real and it hides its own composition.

Approximate tokens ingested across twelve benchmark scenarios, one bar per arm. A scripted native-tool baseline costs 16,495; the cheapest possible native sequence, given the answer in advance, costs 9,677; DaiVELOPER costs 3,486, or 5,486 once the once-per-session tool preamble is charged. All four arms answered 12 of 12, checked by re-reading the fixture rather than taken from a tool's own claim. The preamble row is the same treatment with the 2,000 tokens of tool descriptions a session pays once, which the native arms never pay.

05 · writes

A write that would not parse is refused, not repaired

replace_symbol and insert_symbol re-parse the prospective file in memory first and refuse if it does not parse, naming the line. Validation happens before the write, never after.

The write boundary is checked both lexically and against realpath, so neither a .. nor a symlink escapes the served root. All three refusal paths were exercised from a cold start and all three refused without writing.

Four outcomes for a symbol-scoped write. Source that does not parse is refused with the offending line named. Source that parses but declares nothing is refused with nothing written. A path that escapes the served root is refused with the resolved path named. Source that parses and declares is written, and the next read_symbol in the same session sees it. Validation happens before the write, never after, so a refusal leaves the file exactly as it was. All four paths were exercised from a cold start against a copy of the fixture the server had never seen.

Still no refactoring and no formatting. A write tool that reshapes code it was not asked to reshape is a tool nobody can review the output of, and neither of these two moves a line it was not pointed at.

06 · not

What it is not

Five runs, one proof of concept. Not a product. No editor integration, no LSP, no remote service, no semantic search, no embeddings, and no network at runtime.

TypeScript and JavaScript only. Twelve scenarios written by the same project they measure, over a fixture built by it, with no held-out set and no model in the loop — RESULTS.md names that as the largest threat to its own result and does not resolve it.

How the 6,191-token margin over the cheapest native sequence divides. One scenario, exports-of-long-file, accounts for 95.2% of it; the remaining eleven scenarios together account for 4.8%. Removing that one scenario leaves the eleven at 2,529 approximate tokens against the native floor's 2,826 — under it, and above it once the session preamble is charged. The question that scenario asks is 'list every export of this long file with its return type', and no native tool can do it at all. That is a real capability difference and it is also one question out of twelve.

Twelve questions, written by the people building the thing that answers them, against a fixture written by the same people and built to be hard to grep. There is no held-out set and no third party wrote a single question. RESULTS.md calls that its own largest threat and does not resolve it.

start here

Clone it, build it, register it

git clone <this repo> daiveloper && cd daivelopernpm install --include=devnpm run build

Node 22 or later. --include=dev matters if NODE_ENV=production is set, and the build is optional — the server also runs from source under node --import tsx.

npm run typechecknpm testnpm run bench

npm test includes a real stdio round trip against the server, and npm run bench prints the table on this site and writes the run to bench/results/.

claude mcp add -s user daiveloper -- \  node /abs/path/to/daiveloper/dist/server/main.js

Use an absolute path: Claude Code launches the command from the project directory, not from the checkout. -s project writes a .mcp.json shared with anyone who clones the repository instead. Confirm with claude mcp list, then /mcp inside a session.

stdout belongs to the JSON-RPC transport. Every diagnostic goes to stderr — including the one line the server prints when it cannot create its cache directory and falls back to the OS temp directory — or the transport breaks.

The startup line on stderr also names the root the server resolved and why: --root if one was passed, otherwise the git repository above the directory it was launched in. Since run 5, root is optional on every tool — a call that names one still wins.