codemode-lsp
Semantic code intelligence and refactoring for TypeScript/JavaScript agents:
an MCP server exposing a single execute tool backed by the language
server. The LLM writes one JavaScript script that chains semantic operations
(lsp.*) — filtering, looping, and aggregating in code instead of across many
tool-call round trips:
const refs = await lsp.findReferences("src/api.ts", "handleRequest");
const relevant = refs.filter((r) => r.context.includes("deprecated"));
for (const ref of relevant) {
await lsp.replaceSymbolBody(ref.file, ref.symbolPath, newImpl);
}
({ modified: relevant.length });
It shines on codebase-wide work that grep-based agents get subtly wrong: impact analysis, call graphs, usage audits, and atomic multi-file refactors. Names are resolved, not text-matched. Writes are transactional — nothing hits disk unless the whole script succeeds, and successful writes come back as reviewable unified diffs.
Setup
No install step — the package bundles its own typescript-language-server.
The workspace root is the directory the server is spawned from:
{
"mcpServers": {
"codemode-lsp": {
"command": "npx",
"args": ["-y", "codemode-lsp"]
}
}
}
Add "env": { "CODEMODE_READONLY": "1" } to try it on a repo it physically
cannot write to.
Operations
The tool returns { result, logs, changes }. Symbols are addressed as
(file, "MyClass/myMethod") pairs discovered via getSymbols. When a script
does something a cheaper call covers, the result (and logs) carries an
advisory [hint]. Full type definitions are embedded in the tool description
(generated from source); if a client truncates it, await lsp.help() returns
the complete reference and await lsp.docs("query") returns just the matching
op signatures, related types, and a worked example.
See PRD.md for the complete spec and decision log.
Configuration
Limitations
- TypeScript/JavaScript only (the architecture is language-agnostic).
- Per-file diagnostics cover session-touched files;
checkProject()is the whole-project check. On files created mid-script, per-file "Cannot find module" alias errors are taggedlikelyFalsePositive: true—checkProject()has no such false positives. getDependenciesis syntactic;moveSymbolmoves top-level symbols only.Reference.isWriteAccessis alwaysfalse(not exposed over standard LSP).
Development
bun run check # typecheck + lint + all tests — run before declaring done
bun run generate:types # regenerate src/lsp-types.generated.ts after API changes
bun run eval # LLM benchmark: headless Claude Code, execute tool only
The tool description's worked examples run verbatim as golden tests, and every eval task's reference solution runs in the normal suite — neither can rot.