mcp-ctags
v1.0.0
Published
MCP server for fast code navigation using Universal Ctags and ripgrep — find symbol definitions and references across any codebase
Maintainers
Readme
mcp-ctags
An MCP (Model Context Protocol) server that gives AI coding agents fast, precise code navigation across any codebase using Universal Ctags and ripgrep.
Exposes three tools to agents:
| Tool | Answers | How |
|---|---|---|
| find_symbol | Where is X defined? | Pre-built ctags index (in-memory, instant) |
| find_references | Where is X called/used? | Live ripgrep search (authoritative) |
| refresh_tags | Rebuild the index | Runs ctags -R with your exclusion config |
Prerequisites
Both tools must be available on your system PATH:
Universal Ctags
macOS
brew install universal-ctagsUbuntu / Debian
sudo apt-get install universal-ctagsWindows
Download the latest release from the universal-ctags GitHub releases, extract the zip, and add the folder to your system PATH.
Or via Scoop:
scoop install universal-ctagsOr via Chocolatey:
choco install universal-ctagsripgrep
macOS
brew install ripgrepUbuntu / Debian
sudo apt-get install ripgrepWindows
Via Scoop:
scoop install ripgrepVia Chocolatey:
choco install ripgrepOr download the .zip from the ripgrep GitHub releases and add to your PATH.
Verify installation
macOS / Linux
ctags --version # Should show "Universal Ctags"
rg --version # Should show ripgrep 13+Windows (PowerShell)
ctags --version # Should show "Universal Ctags"
rg --version # Should show ripgrep 13+Installation
Via npx (recommended — no global install needed):
npx mcp-ctagsGlobal install:
npm install -g mcp-ctagsQuick Start
Step 1 — Generate the tags index for your project:
ctags -R \
--languages=JavaScript,TypeScript,Python,Go,SQL \
--exclude=node_modules --exclude=.git --exclude=dist \
-f /path/to/your/project/.tags \
/path/to/your/projectStep 2 — Add to your MCP config (~/.vscode-server/data/User/mcp.json for VS Code, or your agent's config file):
{
"servers": {
"ctags": {
"command": "npx",
"args": ["-y", "mcp-ctags"],
"env": {
"CTAGS_FILE": "/absolute/path/to/your/project/.tags",
"CTAGS_ROOT": "/absolute/path/to/your/project"
}
}
}
}Step 3 — Restart your agent / MCP host. The server is ready.
Configuration
All configuration is done through environment variables in the env block of your MCP config. No config files to manage.
| Variable | Required | Default | Description |
|---|---|---|---|
| CTAGS_FILE | Yes | ../.tags relative to package | Absolute path to the .tags file |
| CTAGS_ROOT | Yes | Directory of CTAGS_FILE | Absolute root path of the codebase — used to strip prefixes from results and scope ripgrep searches |
| CTAGS_EXCLUDE_DIRS | No | node_modules,.git,dist,build,build_*,coverage,logs,.cache,vendor,__pycache__,target,.next | Comma-separated list of directory names/patterns to exclude from both indexing and reference search |
| CTAGS_EXCLUDE_FILES | No | *.min.js | Comma-separated list of file glob patterns to exclude |
| CTAGS_STALE_HOURS | No | 24 | Hours after which find_symbol appends a staleness warning. Set lower (e.g. 4) for fast-moving codebases. |
Example: Monorepo with custom exclusions
"env": {
"CTAGS_FILE": "/home/me/myproject/.tags",
"CTAGS_ROOT": "/home/me/myproject",
"CTAGS_EXCLUDE_DIRS": "node_modules,.git,dist,build,coverage,vendor,docs/generated",
"CTAGS_EXCLUDE_FILES": "*.min.js,*bundle*.js,*swagger*.json,*.pb.go"
}Excluding a specific file
Use the full relative path from CTAGS_ROOT in CTAGS_EXCLUDE_FILES:
"CTAGS_EXCLUDE_FILES": "*.min.js,path/to/specific/generated-file.js"Tools Reference
find_symbol
Find where a function, class, variable, or method is defined.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | Symbol name or partial name (case-insensitive) |
| exact | boolean | No | If true, match exact name only. Default: false |
| kind | string | No | Filter by symbol kind: function, class, method, variable, property, module, constant |
| scope | string | No | Restrict results to files under this path prefix, e.g. "src" or "lib" |
| limit | number | No | Max results to return. Default: 30 |
Examples:
find_symbol("processOrder")
→ processOrder [function] → src/orders/orderService.js:42
find_symbol("Order", { kind: "class" })
→ OrderModel [class] → src/models/Order.js:5
find_symbol("getOrder", { exact: true, scope: "src" })
→ getOrder [function] → src/orders/orderController.js:78
find_symbol("user", { kind: "function", limit: 5 })
→ getUser [function] → src/users/userController.js:12
→ createUser [function] → src/users/userController.js:34
→ updateUser [function] → src/users/userController.js:67
→ deleteUser [function] → src/users/userController.js:89
→ getUserById [function] → src/users/userService.js:21Staleness warning: If the index is more than 24 hours old, results include a reminder to run refresh_tags.
find_references
Find all call sites and usages of a symbol across the codebase. Uses live ripgrep — always current, not limited by the index age.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| symbol | string | Yes | Exact symbol name (whole-word match) |
| glob | string | No | Restrict search to files matching this glob, e.g. "*.js" or "api/**" |
| limit | number | No | Max results to return. Default: 40 |
Examples:
find_references("processOrder")
→ src/orders/orderController.js:105 const result = await processOrder(payload);
→ src/jobs/syncWorker.js:78 processOrder(req.body);
→ tests/order.test.js:34 expect(processOrder(mock)).resolves.toEqual(...);
find_references("OrderModel", { glob: "src/**" })
→ src/orders/orderController.js:8 const OrderModel = require('../models/order');
→ src/orders/orderController.js:45 const doc = await OrderModel.findOne({ id });
find_references("createUser", { limit: 5 })
→ src/users/userRoutes.js:12 router.post('/user', createUser);
→ tests/user.test.js:22 await createUser({ name: 'Alice' });refresh_tags
Regenerate the ctags index using the current exclusion configuration from your env vars.
No parameters.
When to run:
- After pulling significant upstream changes
- After adding new files or renaming functions
- After switching branches with substantial file changes
- When
find_symbolreports a symbol you know exists but can't find
Example output:
Tags refreshed. 94,213 lines indexed.
Excluded dirs: node_modules, .git, dist, build, coverage
Excluded files: *.min.js, *bundle*.js, *swagger*.jsonHow It Works
mcp.json env config
│
▼
index.js (MCP server, stdio transport)
│
├─ find_symbol ──► reads .tags file into memory (cached)
│ searches symbol column only (anchored regex)
│ returns: symbol, file, line, kind
│
├─ find_references ► spawnSync('rg', [...]) with --glob exclusions
│ whole-word match, JSON output
│ returns: file, line, matching code
│
└─ refresh_tags ──► spawnSync('ctags', ['-R', ...excludes])
invalidates in-memory cache
confirms count of symbols indexedWhy two tools for navigation?
find_symbolreads a pre-built static index — it's instant (O(n) scan of in-memory lines, no disk I/O after first load). Best for "where is X defined?"find_referencesuses ripgrep live — it catches dynamic patterns and usages that ctags doesn't index (require calls, string references, config files). Best for "where is X used?"
Together they cover what Ctrl+Click and Find All References give you in an IDE — but accessible to any AI agent over MCP.
Troubleshooting
"Tags file not found"
You haven't generated the index yet, or CTAGS_FILE points to the wrong path. Run:
ctags -R --exclude=node_modules --exclude=.git -f /your/project/.tags /your/projectOr call refresh_tags from your agent — it will run this for you.
find_symbol returns no results for a symbol I know exists
- The index may be stale (check the age note in the response). Call
refresh_tags. - The file containing the symbol may be excluded. Check
CTAGS_EXCLUDE_DIRSandCTAGS_EXCLUDE_FILES. - The language may not be indexed.
refresh_tagscoversJavaScript,TypeScript,Python,Go,SQLby default.
find_references is slow
Your CTAGS_EXCLUDE_DIRS is missing large directories. Add them:
"CTAGS_EXCLUDE_DIRS": "node_modules,.git,dist,build,vendor,generated"Results contain noise from bundled/generated files
Add the directory or file pattern to exclusions:
"CTAGS_EXCLUDE_DIRS": "...,docs/website",
"CTAGS_EXCLUDE_FILES": "*.min.js,*bundle*.js,*swagger*.json"Then call refresh_tags to rebuild the index.
Development
git clone https://github.com/vishalkumar14/mcp-ctags.git
cd mcp-ctags
npm install
npm testTests use Jest and a fixture tags file — no live ctags or ripgrep required to run the test suite.
test/
lib.test.js # Unit tests for all pure functions (27 tests)
fixtures/
sample.tags # Minimal ctags fixture file for testingLicense
MIT
