mcp-ctags
v1.0.1
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.
Security Model
mcp-ctags executes local rg and ctags binaries on the host machine to search code and rebuild tag indexes. It invokes those binaries through Node.js spawnSync() with argument arrays and shell: false, so user input is not interpolated into a shell command string.
Binary selection can be configured with RG_BIN and CTAGS_BIN. These values must be a binary name or executable path only; extra arguments and obvious shell metacharacters are rejected during startup.
The first-party package code does not make outbound network requests. npm ownership and publisher hygiene are handled separately through the maintainer release process.
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_BIN | No | ctags | Binary name or absolute path for Universal Ctags. Must not include extra arguments |
| RG_BIN | No | rg | Binary name or absolute path for ripgrep. Must not include extra arguments |
| 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_BIN": "/usr/local/bin/ctags",
"RG_BIN": "/usr/local/bin/rg",
"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
