typescript-native-lsp
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Warn
- fs module — File system access in .github/workflows/ci.yml
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
TypeScript 7 native language server plugin for Claude Code, with TypeScript 6 fallback
typescript-native-lsp
TypeScript/JavaScript language server for Claude Code that runs TypeScript 7's native LSP (tsc --lsp) and falls back to typescript-language-server for TypeScript 6 and older, providing code intelligence features like go-to-definition, find references, type information, and type errors reported after edits.
What you get
- Type errors after edits, without running a typecheck. When Claude edits a file, that file's errors reach the conversation on its next tool call, so mistakes are caught while Claude is still on the file rather than after a full
tscrun. - Answers that match the files on disk. Files changed by shell commands, git, formatters or codegen are picked up before the next request, and renamed or deleted files leave the program instead of lingering as ghosts. The official plugin, like any server Claude Code talks to directly, keeps serving the version Claude last edited.
- Compiler-backed navigation instead of grep. Go to definition, find references, implementations, call hierarchy, document and workspace symbols, and the resolved type and documentation of any symbol.
- TypeScript 7's speed. The native server starts and loads large projects far faster than
tsserver, and it is the same compiler that typechecks your build.
Why this plugin exists
The official typescript-lsp plugin launches typescript-language-server, which wraps the classic tsserver. TypeScript 7 is the native Go port of the compiler and ships no tsserver.js, so on a TypeScript 7 project that plugin fails every request with:
Could not find a valid TypeScript installation. Please ensure that the "typescript"
dependency is installed in the workspace or that a valid `tsserver.path` is specified.
TypeScript 7 serves LSP itself instead, via tsc --lsp --stdio. This plugin launches that server when the project uses TypeScript 7 or newer, and launches typescript-language-server when it uses TypeScript 6 or older, so one plugin covers both. It is a drop-in replacement for typescript-lsp; the two must not be enabled together (see Installation).
Supported Extensions
.ts, .tsx, .js, .jsx, .mts, .cts, .mjs, .cjs
Installation
claude plugin marketplace add rubberduck-studio/typescript-native-lsp
claude plugin install typescript-native-lsp@typescript-native-lsp
claude plugin disable typescript-lsp@claude-plugins-official
The last line matters. When two enabled plugins claim the same file extension, Claude Code starts only the first one it registers and never starts the other; the /plugin interface shows a warning naming the active one. Which plugin registers first is not something you control, so disable typescript-lsp rather than relying on the order. Restart Claude Code or run /reload-plugins afterwards.
Requirements
- Claude Code 2.1.50 or newer, the first version that accepts the
startupTimeoutsetting the plugin uses; on Windows 2.1.74 or newer, which fixed file URIs. Cloud sessions never start plugin language servers, so the plugin only works in local sessions. - Node.js 22.18 or newer on
PATH. Claude Code spawns the launcher asnode, and the plugin is written in TypeScript that Node runs directly by stripping the types, which Node does by default from 22.18 on. The fallback for TypeScript 6 and older runs typescript-language-server 6, which requires Node 22.22 or newer. - For TypeScript 7 projects: nothing else. The project's own
typescriptdependency provides the server. - For TypeScript 6 and older: typescript-language-server, either in the project (
npm install -D typescript-language-server) or globally (npm install -g typescript-language-server). It locates TypeScript itself, as the project'stypescriptdependency, which must be 6 or older and a full install, not an API-only package. Do not install a globaltypescriptfor this: on a fresh machine that resolves to TypeScript 7, which has no tsserver and cannot serve TypeScript 6 projects. - Projects without TypeScript: a global TypeScript 7 or newer, from
npm install -g typescripton any platform, or on macOS and Linux anytsconPATHsuch asbrew install typescript.
How it works
On every start the launcher decides which server to run for the session's project directory, in this order:
TYPESCRIPT_NATIVE_LSP_TSDK, if set, as the path of atypescriptpackage directory of version 7 or newer.- Every typescript package installed under
node_modules, walking up from the project directory to the nearest lockfile or git root. A package counts by the name in its own package.json, not by its directory, so an alias such as"@typescript/native": "npm:typescript@7"is found wherever the package manager hoisted it. The highest version wins, so an aliased TypeScript 7 next to a TypeScript 6 API shim is picked up. This works the same under npm, pnpm, yarn and bun. - Workspace packages under
packages/*andapps/*of that root, same rule. - A
typescriptpackage of version 7 or newer under the global npm root (TYPESCRIPT_NATIVE_LSP_GLOBAL_ROOTSoverrides where to look, as aPATH-style list). - On macOS and Linux,
tscortsgoonPATH, if--versionreports 7 or newer. - For TypeScript 6 and older: typescript-language-server, project-local, then under the global npm root, then on macOS and Linux on
PATH. It locates TypeScript on its own, as the firstnode_modules/typescriptabove the workspace, so the launcher only checks that this will succeed and fails early with an explanation when it will not.
TypeScript 7 or newer runs as the native binary from its platform package, tsc --lsp --stdio. TypeScript 6 or older runs typescript-language-server with --stdio through node on its entry file. Either way the launcher stays in front of the server as a small proxy that works around gaps in Claude Code's LSP client, described in the next two sections. With both of its features switched off, the launcher replaces itself with the server on macOS and Linux. Nothing is ever run through a shell, and .cmd shims are never executed, which is why global installs are resolved as packages rather than found on PATH on Windows.
The launcher makes no network requests and sends no telemetry. The only processes it spawns besides the server are tsc --version and tsgo --version, on macOS and Linux only, when probing a binary on PATH. Everything it logs goes to stderr and shows up in claude --debug output prefixed with [typescript-native-lsp].
Troubleshooting
Run the launcher's resolve mode from the project directory to see what it would start and why. The installed copy lives in Claude Code's plugin cache; claude plugin list --json prints its location as installPath:
claude plugin list --json # note the installPath of typescript-native-lsp
node <installPath>/scripts/launch.mjs --resolve
A clone of this repository works the same way: node scripts/launch.mjs --resolve from inside the project directory.
If a project's TypeScript 7 is somewhere the launcher does not look, point TYPESCRIPT_NATIVE_LSP_TSDK at the package directory, for example /path/to/node_modules/typescript, in the shell that starts Claude Code. The override applies to TypeScript 7 or newer; typescript-language-server finds TypeScript 6 and older on its own.
Inside Claude Code:
/pluginlists installed plugins and shows start failures in its Errors tab, including anExecutable not found in $PATHfor a missingnode.claude --debuglogsLoaded 1 LSP server(s) from plugin: typescript-native-lspandTotal LSP servers loaded: Nat startup, then the launcher's[typescript-native-lsp]lines when the server starts, including the resolved project directory, TypeScript and command./reload-pluginspicks up plugin changes without restarting the session.
Files changed outside Claude Code
Claude Code tells the language server only about files its own Edit and Write tools touch, and it never closes a document. Servers treat an open document as authoritative over disk, so once Claude has touched a file, changes made to it by shell commands, git, formatters or codegen stay invisible for the rest of the session, and a renamed or deleted file lingers in the program. Answers then point into files that no longer exist, and diagnostics report conflicts with them. This affects every LSP plugin, including the official one (anthropics/claude-code#76870).
Claude Code has no unsaved buffers, so disk is always the truth. Before every request the proxy compares each document Claude has opened with its file: a changed file is sent to the server with its current content, a deleted file is closed and its diagnostics cleared, and a file that reappears is opened again. Checking costs a file-status call per open document, well under a millisecond per request. It works the same for TypeScript 7 and TypeScript 6 and older.
This is an interim measure too, and it will be removed once Claude Code itself sends the new content of open files that change on disk, or closes them (anthropics/claude-code#76870, anthropics/claude-code#93104). It stays on regardless of what the client advertises: since 2.1.288 Claude Code advertises file watching but never sends file-change events, and such events would not refresh open documents anyway, because servers treat an open document as the client's. When disk and server agree it sends nothing. Set TYPESCRIPT_NATIVE_LSP_DOCUMENT_SYNC=0 to run without it.
Diagnostics
Claude Code attaches a file's type errors to the conversation after an edit, and it learns about them only through pushed textDocument/publishDiagnostics notifications. TypeScript 7's native server never pushes per-file diagnostics; it answers textDocument/diagnostic requests instead. To close that gap the proxy bridges the two for the native server: it forwards all traffic unchanged and, after each didOpen, didChange or didSave, requests the file's diagnostics from the server and publishes the result to Claude Code. The effect is the same as with typescript-language-server on TypeScript 6 and older, which pushes on its own and needs no bridge.
The bridge is an interim measure. It switches itself off once the client requests diagnostics itself, rather than when it merely advertises support, and it will be removed once TypeScript pushes for such clients (microsoft/TypeScript#63921) or Claude Code pulls (anthropics/claude-code#40282). Set TYPESCRIPT_NATIVE_LSP_DIAGNOSTICS=0 to run the native server directly without it; TYPESCRIPT_NATIVE_LSP_DEBUG=1 logs each request and publish to stderr.
Git worktrees
Claude Code roots the server at the directory the session started in and keeps it there when Claude enters a git worktree later: ${CLAUDE_PROJECT_DIR} and the server's working directory stay at the original checkout, so the launcher resolves TypeScript from that checkout. Requests on worktree files are still answered correctly, because the native server resolves each opened file's own tsconfig.json: a type lookup finds symbols that exist only in the worktree, and references for a worktree file return worktree paths only. This holds whether the server started before or after Claude entered the worktree. A session launched inside a worktree resolves TypeScript from the worktree itself.
Two consequences. The TypeScript that runs the server is the one installed where the session started, so a worktree branch that changes the TypeScript version is served by the start directory's version; launch the session inside the worktree or set TYPESCRIPT_NATIVE_LSP_TSDK if that matters. And a session launched inside a worktree needs that worktree to have its own node_modules: resolution stops at the worktree's .git file and does not fall through to the main checkout, on purpose, since the main checkout may hold different dependency versions.
Limitations
- Diagnostics arrive one tool call late, and only for files Claude edits. Claude Code does not wait for a language server's diagnostics after an edit; it attaches whatever arrived by the next tool call, and drops diagnostics for a file that the very next call edits again. It also opens a file with the server only when it edits it, so an edit that breaks a different file goes unnoticed until a full typecheck. Both are client behaviour, identical for every LSP plugin (anthropics/claude-code#93321).
- Windows has no real-session report yet. The launcher is written for it (no
.cmdspawning, npm shim parsing, noexecve) and CI completes the initialize handshake with both servers on Windows, but nobody has used it from an interactive Claude Code session on Windows so far. Reports welcome. - Monorepos with built package outputs. When packages import each other through built declaration files (
dist/*.d.ts), references from consuming packages resolve to the declaration files, not the source, so find-references on a source symbol will not list them. Any TypeScript server behaves this way. Claude Code additionally drops results in gitignored paths. - Linux file watching for files Claude has not opened. Files Claude has opened are kept in sync with disk on every platform (see above). Other files are the server's own business: the native server watches them only on macOS and Windows, so on Linux a file TypeScript 7 loaded as a dependency and that later changed outside Claude Code is not picked up until Claude opens it.
Development
This plugin was built for and with the help of Claude Code and is maintained by rubberduck studio. Every change is reviewed by a human before it is merged. Behaviour was verified in real Claude Code sessions on macOS, through CI on Linux, macOS and Windows, and in git worktree sessions; see Limitations for what is known not to work.
More Information
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found