
Mcp Gtags Server
io.github.harshithsunku由 GitHub 账号 harshithsunku 发布v2.0.0更新于 Oct 24, 2026
Indexed C/C++ code navigation for AI agents — GNU Global (gtags) lookups over MCP, not grep scans.
概览
面向 AI 代理的 C/C++ 索引式代码导航:通过 MCP 调用 GNU Global(gtags)查询,而非 grep 扫描。
- 可以做什么
- 查找 C/C++ 符号的定义与引用,附带 #ifdef 条件栈,并可按 .config 过滤
- 读取单个符号的源码正文,或列出某个文件定义的全部符号
- 梳理调用关系:谁调用了某函数、该函数调用了什么、两个函数间最短静态调用链
- 在编辑后刷新或重建仓库索引
- 以紧凑文本或 JSON 返回结果,支持分页,并对高频符号给出按文件汇总
- 适用场景
- 适合助手处理大型 C/C++ 代码库(包括内核规模或当前无法编译的代码树),且 grep 结果太慢或塞满上下文窗口时。也适用于没有 sudo 权限或缺少 LSP 工具的机器。
- 运行要求
- 以 stdio 在本地运行,通常通过 uvx 启动(需要 uv)。首次使用时会把 GNU Global、universal-ctags 和 Pygments 安装到用户主目录,必要时改为从源码编译 GNU Global。每个仓库在首次查询时自动建立索引;项目根目录来自工具参数、GTAGS_MCP_ROOT、客户端工作区根目录或工作目录。
安装
在 SourceWeft 中
- 打开 控制台中的 Mcp Gtags Server,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
mcp-gtags-server
Stop letting your AI agent grep. Give it an index.
[PyPI] [CI] [Kernel eval] [Python 3.10+] [License: MIT] [Docs] [MCP] [Powered by GNU Global] [Install in Cursor]
mcp-name: io.github.harshithsunku/mcp-gtags-server
One user-level config entry, no sudo, no pre-installed anything — the whole toolchain installs itself into user space on first use, and every repo you open is served automatically.
📖 Full documentation — install for every client, the tool reference, output format, kernel features, configuration and troubleshooting.
Every AI coding agent — Claude Code, Cursor, Codex, you name it — answers "where is this function defined?" the same way: grep the entire tree. On a million-line C/C++ codebase that's a full scan per question, and the output is a firehose: every comment, string literal, and unrelated match, dumped straight into the model's context window.
mcp-gtags-server replaces those scans with indexed lookups powered by GNU Global (gtags) — the same tags engine kernel and systems developers have trusted for decades — exposed to agents over the Model Context Protocol. Built for the codebases LSP-based tools can't handle: kernel-scale C/C++, trees that don't currently compile, machines you can't sudo on.
- ~100× faster per query — milliseconds instead of seconds, at any codebase size
- Radically less noise — the definition, not 7,873 lines of matches
- Speaks kernel —
#ifdefguard stacks with.configfiltering, macro-generated symbols (sys_read→ itsSYSCALL_DEFINE3site) that no other tagging tool resolves, and definitions the parser misses recovered from theirEXPORT_SYMBOLsite via ctags - Zero index management — first query builds the index, every query auto-refreshes it
- Correctness measured in CI — a 64-case golden eval covering all 8 tools against a pinned kernel: 100% recall, 100% precision@1 (docs/capability.md)
- Works everywhere MCP does — Claude Code, Claude Desktop, Cursor, any MCP client
The numbers (real Linux kernel, not a toy)
Measured on a full Linux kernel checkout — 65,163 C/C++ files, 37.1 million lines — warm page cache:
One-time index build: 66 s for the whole kernel. Incremental refresh after edits: well under a second. Reproduce it yourself with scripts/benchmark.sh:
The speed is nice. The real win is precision: an agent that gets 5 exact lines instead of 7,873 noisy ones keeps its context window for actual reasoning.
And the answers are measured, not assumed: CI runs a 64-case golden eval against pinned kernel v6.16 on every push — currently 100% recall, 100% precision@1 across definitions, macro resolution, export recovery, references, callers, callees, definition bodies, #ifdef guards, and reachability, covering all 8 tools. The full methodology, numbers, and honest limitations live in docs/capability.md.
Quick start (60 seconds)
Option A — one config entry (recommended)
Add the server once, at user level, and you're done — no installer, no pre-installed gtags, no per-repo setup. The only prerequisite is uv (or use Option B, which installs it for you).
Or click the Cursor one-click install badge at the top of this page.
On the very first tool call the server bootstraps everything else by itself: GNU Global (prebuilt user-space binaries — no compiler, no sudo), universal-ctags, and Pygments, all into ~/.gtags-mcp. While that one-time install runs (a few seconds on most platforms), tool calls return a "toolchain is being installed — retry shortly" status instead of failing. Opt out with --no-auto-setup or GTAGS_MCP_AUTO_SETUP=0.
The prebuilt Linux binaries run on any distro with glibc ≥ 2.28 (RHEL/Rocky 8+, Ubuntu 18.10+, Debian 10+). On older hosts — or if a downloaded binary fails its post-install execution check — setup automatically compiles GNU Global from source instead, which only needs make and a C compiler.
Option B — install the plugin (server + skill + slash commands)
The plugin bundles this server, a "C/C++ code navigation" skill that tells
the agent which tool answers which question, and two slash commands
(/gtags:impact, /gtags:explain). Installing it registers the MCP server for
you — there is no config file to edit.
Install one way, not both. A plugin and a manual entry run two servers, and the agent sees every tool twice. Switching to the plugin? Remove the manual entry first (
claude mcp remove gtags).
Option C — one-line installer (shared background server)
One command. No sudo. Works everywhere — restricted corporate machines, containers, build servers:
Everything lands in your home directory — the server (via uv), GNU Global, universal-ctags, and Pygments (in ~/.gtags-mcp). When it finishes, a shared background HTTP server is running that every client and IDE window on the machine can point at (http://127.0.0.1:8383/mcp), and the exact client configuration is printed to your console.
Re-run the same command any time:
- Up to date? → "Already installed and up to date — nothing to install", and the config is printed again.
- New release on GitHub/PyPI? → the package updates, an outdated gtags toolchain is wiped and reinstalled automatically, and the background server restarts on the new version.
Slash commands (MCP prompts)
Available with any install, in clients that support MCP prompts (Claude Code, Cursor). They cost no tool-schema context:
One config, many repos
However you install it, one user-level entry serves every repo you open — 20 repos need zero extra installs and zero extra config. Each tool call resolves its project root down this ladder:
project_rootargument on the tool call (agents pass this to target any tree)--rootflag /GTAGS_MCP_ROOTenv varrootin a config file (note: pinning a root here defeats multi-repo)- The client's workspace roots (MCP roots protocol) — IDEs that advertise their open folders get the right repo automatically, even on the shared HTTP server; with several folders open, agents are asked to pass
project_root. Roots exist only for clients on protocol revisions up to 2025-11-25: the 2026-07-28 revision deprecated them, so those clients skip this step (stdio servers still resolve through step 5; on the shared HTTP server, agents passproject_root) - Walk up from the server's working directory to the nearest
.git/GTAGS— this is why stdio servers spawned by Claude Code/Cursor inside a repo just work
Step 5 can be switched off with --no-cwd-fallback / GTAGS_MCP_CWD_FALLBACK=0
(the plugin sets it): the tools then ask for project_root instead of indexing
whatever directory the server happens to run in. Plugin clients launch the
server from the plugin's own folder, which is never your project.
That's it. No indexing step, no configuration. Ask your agent "who calls tcp_v4_rcv?" — the first query in any repo builds that repo's index automatically, and every query after that is answered in milliseconds. Run mcp-gtags-server doctor any time to see what the server detects, or mcp-gtags-server config to re-print the client configuration.
Background server details (network access, port, lifecycle)
The installer runs mcp-gtags-server --transport http --host 127.0.0.1 --port 8383 in the background (pid: ~/.gtags-mcp/server.pid, log: ~/.gtags-mcp/server.log). Environment overrides for the installer:
Security note: the HTTP endpoint is unauthenticated. It binds localhost by default; only bind 0.0.0.0 on networks you trust.
Manual install (prefer system packages, or already have Global)
The server finds binaries in this order: --bin-dir/GTAGS_MCP_BIN_DIR/config bin_dir → ~/.gtags-mcp/bin → PATH → ~/.local/bin.
Claude Desktop — extension bundle or manual config
Easiest: download mcp-gtags-server.mcpb from the latest release, drag it into Claude Desktop's Settings → Extensions, and pick your project folder in the setup screen. The gtags toolchain installs itself on first use.
Or add to claude_desktop_config.json manually (pin the project since Desktop doesn't launch in your repo):
Pin a project root explicitly
By default the server uses the client's advertised workspace root (MCP roots protocol) or auto-detects the project root by walking up from its working directory to the nearest .git or existing GTAGS — so queries from anywhere inside a monorepo resolve to the repo root. Override with --root /path, the GTAGS_MCP_ROOT env var, or root in a config file — or pass project_root on any individual tool call to query a different tree. (See "One config, many repos" for the full resolution order.)
Config files — per-project and per-user defaults
Every setting can also live in a TOML file, so teams share defaults through the repo (like .editorconfig):
- Project:
.gtags-mcp.tomlat the project root - User:
~/.config/gtags-mcp/config.toml
Precedence: tool-call argument > CLI flag > environment variable > project config > user config > built-in default.
Tools
Eight tools, all read-only except update_index. Each takes project_root and
format ("text" default, "json"); list-shaped tools also take limit/offset.
Full parameter reference: docs → Tools.
- find_definition — Go to definition: every definition with its
#ifdefguard stack and ctags kind/signature, plus a usage summary (reference/file counts, hottest files,EXPORT_SYMBOL*status). Resolves macro-generated names (sys_read→SYSCALL_DEFINE3). - find_references — Every usage site with its guard stack; above 200 references it returns a per-file distribution, and
path_prefixnarrows it to one directory. - get_symbol_body — The source of one definition (function, struct, or multi-line macro), not the whole file.
- find_callers — Who calls this function: references mapped to their enclosing functions, deduplicated, with call counts and call lines.
- find_callees — What this function calls: call sites taken from its body and checked against the index, split into in-tree (with locations) and external.
- reachability — The shortest static call chain from function A to function B, with the file:line of every hop, or "no static path".
- list_file_symbols — Every symbol a file defines (its API surface).
- update_index — Synchronous index refresh after edits;
full=truerebuilds from scratch. The only tool that writes.
Symbol-level tools — the noise killers
Give the agent the symbol, not the file.
Core lookups
Every query tool supports limit/offset pagination, long-line truncation, and (where it makes sense) case_insensitive — output is engineered to never flood a context window.
Every tool also declares MCP tool annotations: all are readOnlyHint: true except update_index, and none reach outside your machine (openWorldHint: false). Clients use these hints to auto-approve and parallelize read-only calls. Each response carries the envelope exactly once, as text content, with no duplicate structuredContent copy.
The flow that saves your context window
A few hundred lines of context total — versus tens of thousands for the grep-and-read-files equivalent.
Output and kernel smarts
Output: compact text by default, JSON on request
Since v2.0.0 every tool answers with grep-shaped text — the format agents read best, and 2–3× cheaper in tokens than the same answer as JSON:
path:line: source rows carry a tag with what the symbol is and the
#if/#ifdef stack it lives under. Paginated results end with a footer like
[1-100 of 5290 · offset=100 for more].
Against v1.x's JSON default the same seven kernel questions cost 20% fewer
tokens overall — up to 68% on individual lookups. find_callers is the one
call that got bigger (about 9%), on purpose: it now includes each call site's
source line, which is exactly what agents used to spend an extra grep to see.
Pass format="json" for the machine-readable envelope — the same data, rendered
from one source of truth:
- Symbol locations always use the stable record schema
{symbol, path, line, col, kind, typeref, scope, signature, guard, snippet}with repo-relative paths. Keys are only ever added, never renamed or removed within a major version. kind/typeref/scope/signaturesay what a symbol is (since v0.8.1): function vs. macro vs. struct vs. typedef vs. enum constant, its return/target type, its enclosing scope (enum:color,struct:item), and its parameter list — extracted per file by universal-ctags with no build and no compile database, cached, and filled on definition-shaped results (find_definition,list_file_symbols). When universal-ctags isn't available the fields are simplynull; disable explicitly with--no-enrich,GTAGS_MCP_ENRICH=0, orenrich = falsein.gtags-mcp.toml.guardsays when a symbol exists (since v0.9.0): the enclosing#if/#ifdefstack, outermost first ([]= unconditional,null= scanning disabled or file unreadable). See the next section — this is the headline feature.resolved_viasays how a symbol was found when it took macro-family resolution rather than a literal index match ("macro:SYSCALL_DEFINE","fuzzy:vfs_read") — see Macro-generated symbols.next_toolstells the agent the highest-value follow-up call for what was (or wasn't) found.total/offset/truncateddrive pagination; failures keep the envelope with anerrorfield and are flaggedisErroron the protocol so the agent can self-correct.- Composite tools return tool-shaped
results(e.g.find_callees{in_tree, external},reachabilitya hop chain) inside the same envelope. - Every tool declares MCP tool annotations: all are
readOnlyHint: trueexceptupdate_index, and none reach outside your machine (openWorldHint: false) — clients use these to auto-approve and parallelize calls.
Upgrading from v1.x? Pass format="json" explicitly wherever you parsed the
old default. symbol_info is now part of find_definition,
summarize_references is find_references (it groups by file automatically),
and blast_radius is the /gtags:impact prompt — git diff, then
find_callers on each changed function.
#ifdef-aware: know which definition your config actually compiles
Kernel and firmware code defines the same symbol multiple times and lets the
build configuration pick one. Every other no-build tool returns a flat,
unexplained list — an agent happily reads the no-op stub of kmap and
reasons its way to a wrong answer. This server reads the preprocessor
conditionals (pure scanning — still no build, no compile_commands.json):
Pass active_config — a kernel .config path or a macro list like
"CONFIG_SMP,BITS_PER_LONG=64,!CONFIG_DEBUG" — to find_definition,
or find_references, and definitions whose guard stack is
definitely false under it are dropped (the envelope reports the count as
config_filtered). Filtering is deliberately conservative: a .config is a
closed world for CONFIG_* macros (kbuild semantics, including
=m → CONFIG_X_MODULE and IS_ENABLED/IS_BUILTIN/IS_MODULE), but
anything unknown (__ASSEMBLY__, ARCH_HAS_*, arithmetic it can't decide)
never drops a result.
The details are handled so the output stays clean: classic include guards
(#ifndef FOO_H) are detected and suppressed, #elif chains compose into
explicit conditions (!CONFIG_X86_64 && CONFIG_X86_32), comments on
directives are ignored (they lie), and broken/partial files never fail a
query. Disable with --no-guards, GTAGS_MCP_GUARDS=0, or guards = false
in .gtags-mcp.toml.
Macro-generated symbols resolve too (sys_read → SYSCALL_DEFINE3)
The best-known gap of every tagging tool on the kernel: sys_read,
trace_sched_switch, and css_set_lock have no literal definition
anywhere — they are minted by token-pasting macros, and a plain lookup
comes back empty (or worse, returns a same-named test helper). This server
resolves them from the index alone, no preprocessor and no build:
Covered families: SYSCALL_DEFINE0..6 / COMPAT_SYSCALL_DEFINE* (incl.
__x64_/__ia32_/__arm64_/__se_/__do_ wrapper spellings), tracepoints
(TRACE_EVENT, DEFINE_EVENT, DECLARE_TRACE, ...), and the bare-name
definers (DEFINE_SPINLOCK, DEFINE_MUTEX, DEFINE_PER_CPU*,
DECLARE_BITMAP, module_param*, any DEFINE_/DECLARE_-shaped macro) —
plus a last-resort fuzzy tier that tries underscore-variant spellings.
Resolved results are flagged with resolved_via in the envelope and ranked
ahead of same-named textual shadows; DEFINE_* sites rank above their
DECLARE_* counterparts. find_definition additionally reports the
EXPORT_SYMBOL / EXPORT_SYMBOL_GPL variant a kernel symbol is exported
with, in an exported field. Costs nothing when a symbol resolves normally
(only family-shaped names like sys_*/trace_* get the extra indexed
lookups); disable with --no-macro-resolve, GTAGS_MCP_MACRO_RESOLVE=0, or
macro_resolve = false.
What gets indexed (junk stays out)
Indexing feeds gtags an explicit file list instead of letting it walk the tree:
- In a git repository the list comes from
git ls-files—.gitignoreis respected exactly, so build output, vendored blobs, and generated files never pollute the index. Disable withrespect_gitignore = falsein.gtags-mcp.toml. - Outside git, the tree is walked minus well-known junk directories (
.git,node_modules,build,dist,.venv, ...). skip_globsin.gtags-mcp.tomldrops anything else you never want indexed.
Incremental refreshes recollect the list, so newly ignored files drop out of the index and new files appear — automatically.
Multi-language projects (C + Python + more)
Real projects mix languages — a C core with Python tooling, JS frontends, Go services. The server handles this automatically:
- Native languages (C, C++, Java, PHP, Yacc, assembly) use GNU Global's fast built-in parser.
- Everything else (Python, Go, Rust, JavaScript, TypeScript, Ruby, ... ~150 languages) is indexed through Global's ctags + Pygments plugin parsers — same index, same tools, same queries.
The one-line installer (and mcp-gtags-server setup) enables this automatically — it installs universal-ctags and Pygments into user space, and the server switches to the native-pygments parser label on its own. Prefer system packages? Those work too:
Now find_definition("py_util"), get_symbol_body (indentation-aware for Python), find_callees, find_callers — all work across every language in the tree, in one index.
Force a specific parser label with --label, GTAGS_MCP_LABEL, or label in .gtags-mcp.toml (e.g. default for native-only, pygments for plugin-everything).
Honest caveats: for plugin-parsed languages, definitions are as accurate as ctags, but references are token-based — every occurrence of the name counts, without C-grade semantic reference tracking or local-scope awareness. For C/C++ nothing changes: the native parser still does that part.
How it works
Architecture
One long-lived server process per IDE window (stdio) — or one shared HTTP server for the whole machine. Everything heavy lives on disk and is shared: the toolchain installs itself once per machine, the index builds itself once per repo.
A tool call, end to end
The binary is not re-executed per call — the server process stays alive; each call is one JSON-RPC message plus one millisecond-scale global subprocess:
- First query on a tree? The index is built automatically (the only operation that ever blocks — and only once).
- Where do the index files go? Into a single
.gtags-mcp/folder at the project root — never loose files next to your code. The folder ships its own.gitignore, sogit statusstays clean without touching yours. A pre-existing root-levelGTAGS(from older versions, or your own gtags runs) keeps being used as-is.mcp-gtags-server doctorshows the location. - Files changed? A debounced incremental refresh runs in the background: queries always answer instantly from the current index while
gtags -icatches up behind the scenes. Measured on the kernel: queries return in 0.02s while the 25s freshness check runs invisibly. Staleness is bounded by the debounce window; callupdate_indexfor a synchronous, guaranteed-fresh barrier right after edits. - Huge result? Pagination footers tell the agent exactly how to fetch the next page — or the tool itself suggests a narrower one (
find_callerson a symbol used in 500+ files points tofind_references, which groups by file).
FAQ
Why gtags instead of a language server (LSP)?
LSP servers give richer semantics but need a working build configuration, per-editor setup, and serious warm-up time on large trees. gtags indexes 37M lines in about a minute with zero configuration, handles the kernel-scale codebases LSPs choke on, and its fuzzy parser doesn't care whether the code currently compiles. For C/C++ navigation questions — definition, references, callers — it's the pragmatic sweet spot. (Wrapping clangd for the compile-DB case was considered and deliberately rejected: users who have a working compile_commands.json already have clangd and its ecosystem — see ROADMAP.md.)
What languages? C, C++, Yacc, Java, PHP, and assembly natively — plus Python, Go, Rust, JS/TS, Ruby, and ~150 others via the ctags/Pygments plugin parsers (see Multi-language projects).
Does the agent have to manage the index?
No. That's the point. Build-on-first-query, background refresh with adaptive debounce, zero blocking — queries never wait for index maintenance. The explicit update_index tool exists only as an escape hatch: a synchronous freshness barrier after edits, and a from-scratch rebuild with full=true.
Where does the index live? Can I delete it?
In .gtags-mcp/ at the project root (self-gitignored). Delete it freely any time — the next query rebuilds it from scratch.
Will it fight my agent's built-in tools? The tool descriptions are written to steer the model: they say when to use indexed lookups instead of grep. In practice agents pick the faster, narrower tool naturally.
ModuleNotFoundError: No module named 'mcp.server.fastmcp' on startup?
Releases up to v1.4.2 declared an uncapped mcp dependency, so installs made after the MCP Python SDK 2.0 release resolve an SDK they can't import. v1.4.3 fixes it by capping the SDK below 2.0, and v1.5.0+ runs natively on SDK 2.x (mcp>=2.2,<3), speaking every protocol revision from 2024-11-05 through 2026-07-28. Pick up the fix with uvx --refresh mcp-gtags-server --help (or uvx mcp-gtags-server@latest), or re-run the installer. If you must stay on an older release, pin the SDK yourself: uvx --with 'mcp<2' [email protected].
Development
Tests build a real C project in a temp dir and exercise auto-indexing, auto-refresh, caller mapping, body extraction, pagination, user-space binary discovery, and config layering end-to-end.
Correctness is also measured, not assumed: mcp-gtags-server eval --golden evals/golden.jsonl --root <kernel-tree> runs a 64-case golden set covering all 8 tools (definitions, macro resolution, references, callers, callees, bodies, guards, reachability, maintenance) against a real kernel and prints recall / precision@1 — CI does this weekly against a pinned tag. See docs/capability.md for the current numbers.
Release flow: bump version in pyproject.toml, tag vX.Y.Z, push — CI publishes to PyPI and users pick the update up on their next installer re-run. Prebuilt GNU Global binaries are rebuilt by tagging global-v<version> (or gh workflow run release-binaries.yml -f version=<version> to replace the assets of an existing release in place); Linux builds run inside manylinux_2_28 containers so they work on any glibc ≥ 2.28 host, enforced by a CI symbol-ceiling check.
Roadmap
See ROADMAP.md — structured JSON output landed in v0.8.0, ctags
metadata enrichment (kind/signature/scope) in v0.8.1, #ifdef/config-guard
awareness (the headline capability for kernel and firmware trees) in v0.9.0,
then, all shipped in v1.0.0: macro-family symbol resolution (sys_read → its
SYSCALL_DEFINE3 site), the agent workflow tools (reachability,
diff-impact analysis), automatic recovery from corrupted index databases, and the
correctness eval harness — a 64-case golden set against pinned kernel v6.16
scoring 100% recall / 100% precision@1 in CI, with the measured writeup in
docs/capability.md. Every technical milestone is done;
what remains is distribution (MCP registry, directories, the writeup post).
Contributions welcome — open an issue or PR.
License
MIT © Harshith Sunku
来源:README.md,提交 fb6bd0b
工具
0版本历史
3- v2.0.0最新Sep 17, 2026
- v1.5.0Sep 17, 2026
- v1.4.2Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分