npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

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-ctags

Ubuntu / Debian

sudo apt-get install universal-ctags

Windows 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-ctags

Or via Chocolatey:

choco install universal-ctags

ripgrep

macOS

brew install ripgrep

Ubuntu / Debian

sudo apt-get install ripgrep

Windows

Via Scoop:

scoop install ripgrep

Via Chocolatey:

choco install ripgrep

Or 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-ctags

Global install:

npm install -g mcp-ctags

Quick 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/project

Step 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:21

Staleness 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_symbol reports 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*.json

How 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 indexed

Why two tools for navigation?

  • find_symbol reads 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_references uses 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/project

Or call refresh_tags from your agent — it will run this for you.

find_symbol returns no results for a symbol I know exists

  1. The index may be stale (check the age note in the response). Call refresh_tags.
  2. The file containing the symbol may be excluded. Check CTAGS_EXCLUDE_DIRS and CTAGS_EXCLUDE_FILES.
  3. The language may not be indexed. refresh_tags covers JavaScript, TypeScript, Python, Go, SQL by 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 test

Tests 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 testing

License

MIT