Skip to main content
Glama
jordanvalnet

code-dev-intel

by jordanvalnet

code-dev-intel

code-dev-intel is an npm package that exposes a self-hosted MCP and HTTP server for TypeScript code intelligence.

It gives AI agents and IDE assistants fast access to symbol definitions, references, implementations, file outlines, dependency graphs, structural search, text search, and duplicate detection without forcing the model to scan your whole repository every time.

What The Package Brings To A Project

  • Faster code navigation for AI agents in medium and large TypeScript repositories.

  • A local-first MCP server you can plug into IDEs, coding agents, and CLI assistants.

  • A stable automation entrypoint with ensure, so scripts and agents can start the server only when needed.

  • An HTTP API for tools and health checks, plus MCP JSON-RPC for clients that speak MCP directly.

  • A self-hosted alternative to remote code indexing for teams that want data to stay local.

Related MCP server: arcscope

Benchmark: does it actually change agent behavior?

On a large production TypeScript repository, with fresh-context agents free to choose any tool:

  • 11 of 11 agents adopted it on their own. On real tasks against a production codebase, every one of the 11 fresh-context agents chose code-intel over Grep/Read — with no instruction to do so.

  • Fewer tokens, scaling with task difficulty: roughly parity on simple grep-friendly lookups, but 13–34% fewer tokens (≈27% fewer on average) on debugging traces, large-file comprehension, and ambiguous-name searches — the tasks that dominate real work.

  • Type-checked precision: semantic results carry no grep false positives (the grep-only baseline had to manually enumerate and exclude ambiguous-name matches).

  • Compact, grep-beating output: findReferences/findDefinitions/findImplementations group matches by file as "line:col" positions. On a 43-reference symbol that's ~72% smaller than the old format and ~60% smaller than grep -n — so even raw text search no longer wins on tokens.

Full methodology and per-task numbers: the benchmark write-up on GitHub.

Good Use Cases

  • Refactoring a symbol safely across many files.

  • Reviewing a PR and tracing impact before commenting.

  • Understanding a codebase entrypoint without opening dozens of files.

  • Replacing repeated grep chains with semantic navigation.

  • Running local automation in CI, hooks, or agent workflows.

What The Package Exposes

Tools

Semantic (type-aware — no native grep/read equivalent):

  • findDefinitions — go-to-definition for a symbol

  • findReferences — every real usage, resolved by the type-checker (no comment/string/same-name false positives)

  • findImplementations — implementations of an interface, abstract class, or port

  • findSymbol — find a symbol by name alone (no file path needed)

  • findCallers / findCallees — incoming / outgoing call hierarchy

  • getSymbolContent — the source of one symbol, not the whole file

  • getFileOutline — a file's structure without reading it

  • dependencyGraph — a file's import graph (every static import form, imported assets included, resolved the way tsc resolves it, and it reports what it could not follow)

  • impactedFiles — blast radius of a set of changed files, code or asset (same resolution, same completeness report, cached between calls)

Search & analysis:

  • searchStruct — AST-shaped structural search (ast-grep)

  • searchText — plain text / regex search (ripgrep)

  • findDuplicates — copy-paste / clone detection

Protocols

  • MCP over stdio

  • MCP over JSON-RPC via POST /mcp

  • Plain HTTP tool endpoints under /tools/*

  • Health and discovery endpoints via /health and /tools/describe

Installation

pnpm add -D code-dev-intel.ts

Requirements:

  • Node.js >=18

  • pnpm >=10

  • A TypeScript or JavaScript repository where semantic navigation is useful

Quick Start

pnpm exec code-dev-intel ensure --workspaceRoot=. --port=4545

This is the recommended command for agents, scripts, hooks, and CI because it:

  • starts the service only if needed

  • waits until the server is healthy

  • exits successfully when the server is already running

Other CLI commands

pnpm exec code-dev-intel start --workspaceRoot=. --port=4545
pnpm exec code-dev-intel status --port=4545
pnpm exec code-dev-intel ensure --workspaceRoot=. --port=4545 --timeout=15000 --verbose

How To Use It In A Project

HTTP example

curl -X POST http://127.0.0.1:4545/tools/findReferences \
	-H "Content-Type: application/json" \
	-d '{
		"workspaceRoot": "/absolute/path/to/project",
		"filePath": "src/feature/use-case.ts",
		"symbol": "runFeature"
	}'

MCP JSON-RPC example

{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "tools/call",
	"params": {
		"name": "getFileOutline",
		"arguments": {
			"workspaceRoot": "/absolute/path/to/project",
			"filePath": "src/feature/use-case.ts",
			"options": {
				"symbolKinds": ["function", "class"]
			}
		}
	}
}

Typical agent workflow

  1. Call getFileOutline to understand file structure.

  2. Call getSymbolContent for the exact function, class, or type you need.

  3. Call findReferences or findDefinitions to trace impact.

  4. Use dependencyGraph when imports and module flow matter.

  5. Use searchStruct for AST-shaped code patterns.

  6. Fall back to searchText for literal strings, comments, or error messages.

Tool Guide

findDefinitions

Use when you know the symbol name and want the canonical declaration site.

findReferences

Use when you want call sites, usages, and cross-file impact.

findImplementations

Use for interfaces, abstract contracts, and implementation discovery.

findSymbol

Use when you know the symbol name but not the file it lives in — this removes the "grep for the name, then read the file" round trip. Takes workspaceRoot and symbol; no filePath.

The query is matched exactly first; if nothing matches exactly the fuzzy (substring / camelCase) hits are returned instead, so a partial name still finds something. Matches inside node_modules and ambient *.d.ts files are excluded. Each match carries name, kind, filePath, the line/column range, and containerName when the symbol is nested.

findCallers / findCallees

Call-hierarchy questions: findCallers answers "who calls this", findCallees answers "what does this call". Both take workspaceRoot, filePath, and symbol, and resolve through the type-checker rather than by name matching.

Prefer these over findReferences when the call graph is what you actually want: references also include imports, type positions, and re-exports, while the call hierarchy returns real call sites with the caller/callee symbol next to each one.

getFileOutline

Use before reading a large file. This is the fastest way to understand the file structure.

Options:

  • symbolKinds: string[] — keep only the listed kinds (e.g. ["function", "class"]).

  • summaryOnly: boolean (default false) — omit the signature field for each symbol. Use this on large schema files to keep the response under MCP token limits.

getSymbolContent

Use when you want the full declaration body of one symbol instead of the entire file.

Options:

  • maxLines: number (default unlimited) — truncate the returned content after N lines. The result includes truncated: boolean and (when truncated) truncatedAtLine: number.

dependencyGraph

Use when you need import relationships and transitive module dependencies.

Options:

  • maxDepth: number (default 5) — how many import hops to expand from the root file.

  • includeExternal: boolean (default false) — also report package and node-builtin imports. Internal dependencies come back as repo-relative paths; external ones as the raw specifier.

  • includeAssets: boolean (default true) — follow imports of non-code files. See Assets as leaves.

Every static form is followed:

  • import / import type clauses, export … from, export * from, export * as ns from

  • import x = require('…')

  • import() / require() calls with a string literal anywhere in the file — a nested closure, a class method, a declare module body

  • import() in a type position: type X = import('./m').Y, typeof import('./m'), a generic argument, a mapped type, a declare global member. A dependency named only in a type position still moves when its target moves.

  • JSDoc @type {import('./m').T} in JavaScript files, where JSDoc is the type system. In a .ts file the checker ignores JSDoc types, so neither does this.

Resolution is TypeScript's own (ts.resolveModuleName), using the options of the nearest tsconfig.json / jsconfig.json walking up from the importing file — not just the workspace root's. That covers relative specifiers, compilerOptions.paths and baseUrl aliases with TypeScript's precedence, extends chains, package main / exports and directory imports, declaration-only modules, the .mts / .cts / .mjs / .cjs family, and workspace packages symlinked into node_modules (reported as the real file inside the workspace, not as a package). If a nested project redefines paths without re-declaring an alias the root config maps, the root config is tried as a fallback so the edge is not lost. Targets that resolve outside workspaceRoot are never followed. On a case-insensitive filesystem a mis-cased import (./notifications/sender for Sender.ts) resolves to the file's real on-disk name, so its importer joins the graph instead of pointing at a node nothing walked.

Completeness is reported, not assumed. Whatever the graph could not follow comes back in unresolved (up to 20 entries of { from, specifier, reason }) with the full tally in unresolvedCount:

  • not-found — a specifier that names a file and finds none: relative, absolute, alias-shaped, or baseUrl-shaped (billing/invoice when a billing/ directory sits under baseUrl). A deleted module or a broken alias.

  • unsupported-file-type — the file is there, and this graph does not read that kind: ./Widget.vue, ./engine.wasm, ./Page.mdx. A real dependency, reported as one, instead of being called missing.

  • outside-workspace — the target exists but lies outside workspaceRoot.

  • dynamic-specifier — a non-literal import() / require() argument, e.g. import(`./locales/${lang}`); the source text is quoted back.

Packages and node: builtins are external dependencies, never "unresolved", so unresolvedCount: 0 means the graph is complete.

impactedFiles

Use to scope a refactor, a review, or a test run: given the files you changed, it returns every file transitively impacted (direct and indirect importers), including the changed files themselves.

Options:

  • changedFiles: string[] (required) — repo-relative paths of the changed files. A changed file may be an asset: pass a stylesheet, a JSON fixture or an icon and you get the code that imports it.

  • changedSymbolsByFile: Record<string, string[]> (optional) — e.g. { "src/a.ts": ["ChangedExport"] }, to keep only the importers that use one of those exports.

  • includeAssets: boolean (default true) — see Assets as leaves.

changedSymbolsByFile narrows the answer without truncating it:

  • A re-export carries the change on. export * from './impl' passes foo through as foo; export { foo as publicFoo } from './impl' passes it on as publicFoo. So a barrel is transparent, and the consumers behind it stay in the answer.

  • Anything else is opaque. A file that uses a changed symbol in its own code may have changed in any way, so all of its exports count as changed from there on.

  • A file listed in changedFiles with no entry counts as changed in full — no list means you did not narrow it down, not that nothing in it changed.

  • Assets and modules with no readable exports are never filtered out. An asset has no exports; a CommonJS module's module.exports is not on the parse tree. Missing knowledge is not evidence that nothing matches.

The import graph behind it is built by the same extractor and resolver as dependencyGraph, so the two tools cannot disagree about the same repository. The result carries the same completeness signal: unresolvedCount plus an unresolvedSample of up to 10 entries, counted across the whole workspace rather than only inside the impact set — every one is a specifier that could have been a missing importer.

Assets as leaves

Both module-graph tools follow imports of non-code files, under the same option name and the same rules. The extensions that count:

.css .scss .sass .less .json .svg .png .jpg .jpeg .gif .webp .avif .woff .woff2 .graphql .gql .md .txt .yaml .yml

Only the last extension matters, so Button.module.css is a .css asset.

  • Resolved by exact filename, and nothing else. No extension guessing, no directory index, no node_modules walk — ./button.css resolves if and only if button.css sits next to the importer. Path aliases and baseUrl are substituted (so @shared/icons/logo.svg works), and a bundler query suffix is ignored (./logo.svg?react resolves logo.svg), but every candidate still has to be a file that exists. The graph never invents an asset edge. The single exception is .json, the one extension Node and every bundler append by themselves: ./data/fixture resolves fixture.json if that file is there. ./button never resolves button.css.

  • Always a leaf. Assets are never read, never parsed and never a source of edges, so a .md file whose fenced examples contain import statements contributes nothing. They do not appear in the workspace graph's files list either — only as the target of an edge.

  • A changed asset lists its importers. That is the point of the default: with includeAssets: false, impactedFiles for a changed stylesheet answers with the stylesheet alone, which is a confidently wrong answer rather than an incomplete one.

  • Excluding them excludes them from the unresolved report too. With includeAssets: false you asked for a code graph, so an asset import the graph did not follow is not a gap in what you asked for. With assets on, an asset import that names no file is reported as not-found, because it is genuinely broken.

Cost, measured on a 4,000-file synthetic workspace with ~3,600 asset imports: turning assets on adds nothing to the build (they are resolved either way; the option filters the answer), adds ~0.6% to an impactedFiles response for a code change, and ~21% to a dependencyGraph response for a file that actually imports assets. Resolving them by exact filename is cheaper than leaving them to TypeScript's module resolution, which probes a dozen candidate paths per asset specifier and fails.

Workspace graph cache

impactedFiles builds a graph of the whole workspace, which on a few thousand files takes seconds. That graph is cached per workspace root — in memory for the life of the process, and on disk between processes — and every call re-walks the directory tree to decide what to reuse:

  • Files are stamped with (mtime, size) at parse time; only files whose stamp moved are re-read. A rename is detected as a removal plus an addition with the same stamp, and the parse moves across once the content confirms it — a stamp is a hint (an archive restored with its timestamps produces the same one), so the bytes decide.

  • Resolutions carry their evidence. Every specifier records what its answer actually depended on: the file it landed on, every path TypeScript probed and did not find, the manifests TypeScript read, and any directory whose existence decided the verdict. When the walk reports files appearing or vanishing, a file is resolved again if and only if the change intersects that record — the rule a language server uses to decide which resolutions a file event invalidates. Adding a file to a 4,000-file workspace re-resolves one file instead of four thousand.

    • The walk tracks directories as well as files, because billing/gone is an uninstalled package when nothing called billing exists under baseUrl and a lost workspace edge when something does — and an empty directory appearing or being removed is a change no file event reports.

    • What the walk cannot watch is never reused. The walk does not enter node_modules, dist, coverage, .next, dot-directories or nested checkouts, and does not follow symlinks; the resolver reads all of them. A resolution that landed in, or probed inside, one of those places — a workspace package built into its own dist/ after the first index — is therefore taken again on every call, as is any edge naming a file the walk does not report. unwatchableFiles in the cache stats is how many such files a workspace has (zero here).

  • A config edit still redoes every resolution. compilerOptions.paths moves every aliased edge at once, so a content change to any tsconfig.json / jsconfig.json / package.json re-resolves the workspace. Package manifests carry their own provenance and could in principle be narrowed the same way; they are not, because TypeScript records only the manifests a lookup actually read, and being wrong there would move edges in silence.

  • Recently written files are always re-read. Filesystem timestamps are quantized — measured here at 0.5–18.7 ms, with 358 of 500 same-length rewrites indistinguishable by (mtime, size) — so any file whose recorded mtime is within 2 s of when it was cached is read again rather than trusted. Edit a file and ask immediately, and you get the file you just saved.

  • The graph survives the process. Every editor or agent session spawns a fresh stdio server, so the cold build used to be paid once per session. After a call that changed anything, the workspace's graph — stamps, module facts, resolutions and their provenance — is written to the OS user cache directory (%LOCALAPPDATA%\code-dev-intel\graph-cache\ on Windows, $XDG_CACHE_HOME or ~/.cache/code-dev-intel/graph-cache/ elsewhere), one file per workspace named by a digest of its root path. The next process loads it, then runs the same walk diff: stamps that still match are reused, everything else follows the rules above.

    • Never inside the workspace. A cache file decides which files import which, so a repository that could ship one could plant edges an agent then acts on. It lives in the user's own cache directory, and it is read as untrusted input anyway — versions, root, and every field validated, every path required to be workspace-relative. Anything unexpected is a cold build, never a wrong graph.

    • Never a failure. A read-only home directory or a full disk logs nothing into the tool call; the write is skipped and the process carries on. Writes are atomic (temp file plus rename), so a process killed mid-write leaves either the old file or the new one.

    • It stores no source. Workspace-relative paths, (mtime, size) and a content digest per file, the specifiers each file imports and the names it exports, where each specifier resolved and the evidence behind it. Nothing in it can reconstruct a line of code.

    • CODE_INTEL_CACHE_DIR moves the directory; CODE_INTEL_GRAPH_CACHE=off (or false, 0, no) switches persistence off entirely. Cache files are invalidated by the package version, the TypeScript version and a schema version, so an upgrade never reuses answers the new resolver would not give — and the directory is swept on write (nothing older than 30 days, at most the 32 most recent workspaces), so it cannot grow one file per abandoned worktree forever.

  • Bounded: at most 50,000 tracked files across at most 16 workspace roots, least-recently-used roots evicted first.

dependencyGraph shares the same per-file parse cache, so the two tools never parse the same file twice and still cannot disagree.

Measured on a large private consumer codebase (~4,200 files) and on a 4,000-file synthetic workspace. The real repository is the one that matters: a cold build there costs about 11 s, of which ~9 s is TypeScript's own module resolution, and the first call of a new process now reads that graph back in ~1.1 s instead of building it. Every editor or agent session spawns a fresh server, so that was the larger of the two costs.

The synthetic workspace is where the per-event gains are visible, medians of 5 fresh processes each, both columns measured in one session on one (busy) machine — pre-T-020 build → this one:

before

after

cold build

8,069 ms

8,342 ms

unchanged workspace

399 ms

380 ms

one file edited

417 ms

454 ms

one file added

5,035 ms

462 ms

one file deleted

4,449 ms

502 ms

one file renamed

6,244 ms

384 ms

one asset added

5,535 ms

415 ms

tsconfig alias edited

5,055 ms

5,843 ms

first call of a new process

8,342 ms (no such thing existed)

1,113 ms

The four bold rows in the middle are the common events of an agent's editing loop, and they were the most expensive ones — a file appearing or vanishing cost as much as a cold build. The tsconfig row stays a full re-resolution on purpose: an alias edit really does move every edge. The cold row is unchanged, so recording the evidence is free. The graph is identical in every row (3,986 files, 17,954 edges, 327 unresolved — the numbers the previous engine produced).

Absolute timings depend heavily on what else the machine is doing: the same code measured 3.5 s for that cold build on an idle machine and 8.3 s here, so read the ratios rather than the milliseconds. The persisted file is 6.9 MB for the synthetic workspace (7.7 MB for the ~4,200-file repository, 89 KB for this one), read and validated field by field in 163–281 ms, and written in 94–411 ms on the calls that changed something — never on the calls that did not. Resident memory for the 4,000-file workspace is 213 MB, or 205 MB for a process that loads the graph instead of building it. On this repository (76 files): ~800 ms cold, 12–55 ms warm, 20–44 ms for the first call of a new process, and dependencyGraph at 18–24 ms per call. The directory walk is most of every warm call.

searchStruct

Use when you need structural matching with ast-grep patterns instead of plain text.

Example:

{
	"workspaceRoot": "/absolute/path/to/project",
	"query": "export interface $NAME { $$$BODY }",
	"options": {
		"language": "ts"
	}
}

searchText

Use for literal strings, comments, log messages, config keys, or partial identifiers.

The ripgrep binary is bundled via @vscode/ripgrep, so the tool works out of the box on Windows, macOS, and Linux without rg on the PATH. Override with CODE_INTEL_RIPGREP_PATH if needed.

The result includes engine: 'ripgrep' | 'node-fallback' and (when falling back) engineFallbackReason: string so clients can debug why ripgrep was not used.

CODE_INTEL_SPAWN_TIMEOUT (milliseconds, default 15000) bounds the ripgrep run; a search that exceeds it is killed and degrades to the node engine with engineFallbackReason set, so a pathological pattern costs a slower response rather than a hung tool call.

findDefinitions / findReferences / findImplementations filtering

By default the resolver hides matches inside node_modules/** and *.d.ts ambient files. Opt back in with options:

  • includeNodeModules: boolean (default false)

  • includeDeclarationFiles: boolean (default false)

findDuplicates

Use when you want code duplication clusters and optional markdown reporting.

Prompting Recommendations For AI Agents

As of v0.3.0 the server describes itself: every tool ships a "use this instead of grep/read, and why" description, and the MCP initialize response returns an instructions block that routes symbol-level intents to the right tool. Clients that surface instructions (e.g. recent Claude Code) inject it into the model's context automatically — so you usually need no prompt forcing at all. The benchmark above measured 100% spontaneous adoption with zero consumer-side instructions.

For clients that do not surface server instructions, a short system-prompt snippet still helps. Keep it minimal — long MCP instructions are easy for clients to truncate, and the per-tool descriptions already carry the detail.

Minimal system prompt snippet

Use code-dev-intel for non-trivial TypeScript exploration before falling back to grep or full-file reads.
Prefer:
- getFileOutline for large files
- getSymbolContent for targeted reads
- findDefinitions/findReferences for symbol tracing
- dependencyGraph for module flow
- searchStruct for AST-shaped patterns
- searchText only for literal text queries

Review-focused prompt snippet

Before reviewing TypeScript changes, use code-dev-intel to trace definitions, references, implementations, and dependency impact.
Do not rely only on text search when checking refactors or behavioral regressions.

Refactor-focused prompt snippet

When planning a refactor, first inspect file outlines and symbol content, then trace references and dependency impact.
Use structural search only for syntax-shaped patterns and plain text search only for literals.

Practical guidance for prompt authors

  • Tell the model that this server is for TypeScript semantic navigation.

  • State when it should be preferred over grep.

  • Mention the high-value tools by name so the model knows what to search for.

  • Keep the instructions short and concrete. Long MCP instructions are easier for clients to truncate.

IDE And Agent Configuration

The package can be exposed either as:

  • a local stdio MCP server

  • a local HTTP server with /mcp and /tools/*

Configuration notes (all clients)

  • --workspaceRoot=. is passed once at startup. Do not pass workspaceRoot in individual tool calls — the startup value is applied automatically. (For VS Code, use ${workspaceFolder}.)

  • Deferred tools: some clients load MCP tool schemas on demand. If the mcp__code-intel__* tools aren't visible yet, have the agent load them first (in Claude Code, via ToolSearch); the server's initialize.instructions also prompt their use.

  • First call is slow, the rest are fast: the first semantic call builds the TypeScript program (a few seconds on a large repo), then it's cached for the session. The module graph behind impactedFiles and dependencyGraph outlives the session: the first call of a new server reads the graph the last one left in your user cache directory, which on a 4,200-file repository is ~1 s instead of ~11 s.

  • The module-graph cache lives outside the repository, one file per workspace root, named by a digest of that path: %LOCALAPPDATA%\code-dev-intel\graph-cache\ on Windows, $XDG_CACHE_HOME/code-dev-intel/graph-cache/ (or ~/.cache/…) elsewhere.

    • CODE_INTEL_CACHE_DIR=<dir> puts it somewhere else — a scratch directory in CI, for instance.

    • CODE_INTEL_GRAPH_CACHE=off (also false, 0, no) turns it off: nothing is read, nothing is written, no directory is created. Every call then rebuilds from the process's own memory as before.

    • To clear it, delete the directory (or the one .json file for your workspace); the next call rebuilds it. Nothing has to be told about it. Files older than 30 days, and everything past the 32 most recent workspaces, are swept on the next write.

  • Reconnect after upgrading: when you change the version or config, reconnect/restart the MCP server so the client reloads the tools and instructions.

  • Working in a git worktree? By default a request workspaceRoot must stay within the startup --workspaceRoot. To authorize a path outside it (e.g. a sibling git worktree), pass --allowed-workspace-root=<glob> (repeatable) or set CODE_INTEL_ALLOWED_WORKSPACE_ROOTS (comma/semicolon-separated globs) — e.g. --workspaceRoot=. --allowed-workspace-root="/repos/myapp*". Patterns are matched against the canonical (realpath-resolved) path, so ../symlink escapes stay blocked; with no patterns configured, the strict default-boundary behavior is unchanged.

VS Code / GitHub Copilot

Create .vscode/mcp.json:

{
	"servers": {
		"codeIntel": {
			"type": "stdio",
			"command": "pnpm",
			"args": [
				"exec",
				"code-dev-intel",
				"--stdio",
				"--workspaceRoot=${workspaceFolder}"
			]
		}
	}
}

Useful VS Code commands:

  • MCP: List Servers

  • MCP: Reset Cached Tools

  • MCP: Reset Trust

  • MCP: Open Workspace Folder MCP Configuration

Notes:

  • VS Code expects .vscode/mcp.json with top-level servers.

  • If tools do not appear after an update, reset cached tools and reload the window.

Claude Code

Project-shared config in .mcp.json:

{
	"mcpServers": {
		"code-intel": {
			"command": "pnpm",
			"args": [
				"exec",
				"code-dev-intel",
				"--stdio",
				"--workspaceRoot=."
			]
		}
	}
}

CLI setup example:

claude mcp add --transport stdio --scope project code-intel -- \
	pnpm exec code-dev-intel --stdio --workspaceRoot=.

On native Windows, Claude Code may require cmd /c for npx. With pnpm, the direct command usually remains cleaner.

Useful commands:

  • claude mcp list

  • claude mcp get code-intel

  • /mcp

Windsurf

Add the server in ~/.codeium/windsurf/mcp_config.json:

{
	"mcpServers": {
		"code-intel": {
			"command": "pnpm",
			"args": [
				"exec",
				"code-dev-intel",
				"--stdio",
				"--workspaceRoot=."
			]
		}
	}
}

Windsurf also supports remote HTTP MCP configuration if you prefer starting the server separately and pointing the client to /mcp.

Generic MCP clients

Many clients and IDE agents can consume the same server even if their exact UI differs.

Use this stdio shape when the client expects a command-based MCP server:

{
	"mcpServers": {
		"code-intel": {
			"command": "pnpm",
			"args": [
				"exec",
				"code-dev-intel",
				"--stdio",
				"--workspaceRoot=."
			]
		}
	}
}

Use this HTTP shape when the client expects a remote MCP endpoint:

{
	"mcpServers": {
		"code-intel": {
			"type": "http",
			"url": "http://127.0.0.1:4545/mcp"
		}
	}
}

This generic approach usually applies to MCP-capable assistants and IDEs such as Cursor-like, Cline-like, Roo-like, or custom internal agent shells that can launch stdio or HTTP MCP servers.

Non-MCP automation

If your tool does not support MCP yet, start the server with ensure and call the HTTP endpoints directly.

Examples:

  • internal scripts

  • CI jobs

  • review bots

  • custom agent frameworks

  • editor plugins that can call HTTP but not MCP

For local development

pnpm exec code-dev-intel ensure --workspaceRoot=. --port=4545

For CI or hooks

pnpm exec code-dev-intel ensure --workspaceRoot=. --port=4545 --timeout=15000

For containerized local isolation

pnpm docker:core:up
curl http://127.0.0.1:4545/health

Security Notes

  • The default host is 127.0.0.1.

  • If you bind to a non-local host, configure CODE_INTEL_API_KEY.

  • Browser requests: a POST that carries an Origin header is only accepted when the origin is the server itself (http://127.0.0.1:<port>, http://localhost:<port>) or listed in CODE_INTEL_ALLOWED_ORIGINS (comma/semicolon-separated origins, or *). This blocks CSRF / DNS-rebinding pages from driving the local server; IDEs, agents, curl and Node clients send no Origin and are unaffected.

  • All user-supplied paths are normalized and validated against workspace boundaries.

  • The server is designed for local-first use. If you expose it remotely, put it behind your normal network controls.

Package Validation

Before publishing a new version, these commands are the main confidence checks:

pnpm build
pnpm test:all
pnpm mcp:self-test
pnpm release:smoke
pnpm audit --audit-level=high

release:smoke is the most useful final check for the npm package because it validates the published package shape from a temporary consumer project.

Contributor Docs

If you are working on the package itself rather than consuming it, start here:

  • docs/ai/00-context.md

  • services/code-intel-mcp/README.md

  • services/indexer/README.md

  • docker/README.md

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    A TypeScript-aware MCP server that provides coding agents with repository discovery, code intelligence, and web project context for local codebases. It enables deep symbol navigation, diagnostic reporting, and structural analysis of monorepos without requiring full IDE integration.
    7
    5 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A local MCP server that gives AI coding agents symbol definitions, dependency graphs, and a live architecture vocabulary for TypeScript/JavaScript repos, with no network or embeddings.
    5 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    An MCP server that provides TypeScript 7 native language server capabilities (go to definition, find references, hover types, diagnostics) to coding agents, using the Go-based tsc compiler for fast and accurate semantic analysis.
    31 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server for semantic codebase navigation that builds an AST index of symbols, imports, and exports, providing AI agents with tools to search, explore, and understand code.
    MIT