mcp-gtags-server 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install GNU Global (for end-to-end tests)
18
+ run: sudo apt-get update && sudo apt-get install -y global exuberant-ctags python3-pygments
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - name: Run tests
26
+ run: uv run --extra dev pytest -v
27
+
28
+ - name: Shellcheck installer
29
+ run: bash -n scripts/install.sh
@@ -0,0 +1,63 @@
1
+ name: Publish to PyPI
2
+
3
+ # Release flow (the installer's update channel):
4
+ # 1. Bump `version` in pyproject.toml on main.
5
+ # 2. Tag it: git tag v0.6.0 && git push origin v0.6.0
6
+ # 3. This workflow builds and publishes to PyPI and creates the GitHub
7
+ # release. Every user re-running install.sh (or `uv tool install
8
+ # --upgrade mcp-gtags-server`) then picks the update up automatically.
9
+ #
10
+ # Requires PyPI *trusted publishing* configured once for this repository:
11
+ # https://docs.pypi.org/trusted-publishers/ (project: mcp-gtags-server,
12
+ # workflow: publish.yml, environment: pypi).
13
+
14
+ on:
15
+ push:
16
+ tags:
17
+ - "v*"
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+
28
+ - name: Run tests first
29
+ run: |
30
+ sudo apt-get update && sudo apt-get install -y global
31
+ uv run --extra dev pytest -q
32
+
33
+ - name: Build sdist and wheel
34
+ run: uv build
35
+
36
+ - uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish:
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment: pypi
45
+ permissions:
46
+ id-token: write # PyPI trusted publishing
47
+ contents: write # GitHub release
48
+ steps:
49
+ - uses: actions/download-artifact@v4
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+
54
+ - name: Publish to PyPI
55
+ uses: pypa/gh-action-pypi-publish@release/v1
56
+
57
+ - name: Create GitHub release
58
+ env:
59
+ GH_TOKEN: ${{ github.token }}
60
+ run: |
61
+ gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" \
62
+ --title "mcp-gtags-server $GITHUB_REF_NAME" \
63
+ --generate-notes dist/*
@@ -0,0 +1,122 @@
1
+ name: Build prebuilt GNU Global binaries
2
+
3
+ # Builds relocatable GNU Global binaries with a long placeholder prefix that
4
+ # `gtags-mcp setup` patches to the user's ~/.gtags-mcp (conda-style
5
+ # relocation). Assets are attached to the release for tag `global-v<version>`
6
+ # and picked up automatically by setup on every user machine.
7
+ #
8
+ # Trigger by pushing a tag like `global-v6.6.15`, or manually.
9
+
10
+ on:
11
+ push:
12
+ tags:
13
+ - "global-v*"
14
+ workflow_dispatch:
15
+ inputs:
16
+ version:
17
+ description: "GNU Global version to build (e.g. 6.6.15)"
18
+ required: true
19
+ default: "6.6.15"
20
+
21
+ permissions:
22
+ contents: write
23
+
24
+ env:
25
+ GLOBAL_SHA256: cf0937cb3ed521b2ab1acfa7aff45103040b860bb642c4c2f094ac3a3fe86024
26
+
27
+ jobs:
28
+ build:
29
+ strategy:
30
+ matrix:
31
+ include:
32
+ - runner: ubuntu-22.04
33
+ platform: linux-x86_64
34
+ - runner: ubuntu-22.04-arm
35
+ platform: linux-aarch64
36
+ - runner: macos-13
37
+ platform: macos-x86_64
38
+ - runner: macos-latest
39
+ platform: macos-aarch64
40
+ runs-on: ${{ matrix.runner }}
41
+ steps:
42
+ - name: Resolve version
43
+ id: ver
44
+ run: |
45
+ if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
46
+ echo "version=${GITHUB_REF_NAME#global-v}" >> "$GITHUB_OUTPUT"
47
+ else
48
+ echo "version=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT"
49
+ fi
50
+
51
+ - name: Download and verify source
52
+ run: |
53
+ curl -fsSLO "https://ftp.gnu.org/gnu/global/global-${{ steps.ver.outputs.version }}.tar.gz"
54
+ echo "${GLOBAL_SHA256} global-${{ steps.ver.outputs.version }}.tar.gz" | shasum -a 256 -c -
55
+ tar xzf "global-${{ steps.ver.outputs.version }}.tar.gz"
56
+
57
+ - name: Build with placeholder prefix
58
+ run: |
59
+ # Must match PLACEHOLDER_PREFIX in src/gtags_mcp/toolchain.py:
60
+ # "/opt/gtags-mcp-placeholder-prefix" + 167 underscores (200 chars).
61
+ PREFIX="/opt/gtags-mcp-placeholder-prefix$(printf '_%.0s' $(seq 1 167))"
62
+ sudo mkdir -p "$PREFIX"
63
+ sudo chown "$(id -u)" "$PREFIX"
64
+ cd "global-${{ steps.ver.outputs.version }}"
65
+ ./configure --prefix="$PREFIX" --disable-gtagscscope
66
+ make -j"$(getconf _NPROCESSORS_ONLN)"
67
+ make install
68
+ echo "PREFIX=$PREFIX" >> "$GITHUB_ENV"
69
+
70
+ - name: Smoke test
71
+ run: |
72
+ mkdir smoke && cd smoke
73
+ printf 'int foo(int x){return x;}\nint main(void){return foo(1);}\n' > a.c
74
+ "$PREFIX/bin/gtags"
75
+ "$PREFIX/bin/global" -x foo | grep -q a.c
76
+
77
+ - name: Package
78
+ run: |
79
+ STAGE="global-${{ steps.ver.outputs.version }}-${{ matrix.platform }}"
80
+ mkdir "$STAGE"
81
+ cp -R "$PREFIX/bin" "$PREFIX/lib" "$PREFIX/share" "$STAGE/"
82
+ tar czf "$STAGE.tar.gz" "$STAGE"
83
+ shasum -a 256 "$STAGE.tar.gz"
84
+
85
+ - name: Upload artifact
86
+ uses: actions/upload-artifact@v4
87
+ with:
88
+ name: global-${{ steps.ver.outputs.version }}-${{ matrix.platform }}
89
+ path: global-${{ steps.ver.outputs.version }}-${{ matrix.platform }}.tar.gz
90
+
91
+ release:
92
+ needs: build
93
+ runs-on: ubuntu-latest
94
+ steps:
95
+ - name: Resolve version
96
+ id: ver
97
+ run: |
98
+ if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
99
+ echo "version=${GITHUB_REF_NAME#global-v}" >> "$GITHUB_OUTPUT"
100
+ else
101
+ echo "version=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT"
102
+ fi
103
+
104
+ - name: Download all artifacts
105
+ uses: actions/download-artifact@v4
106
+ with:
107
+ merge-multiple: true
108
+
109
+ - name: Generate checksums
110
+ run: sha256sum global-*.tar.gz > checksums.txt && cat checksums.txt
111
+
112
+ - name: Create or update release
113
+ env:
114
+ GH_TOKEN: ${{ github.token }}
115
+ run: |
116
+ TAG="global-v${{ steps.ver.outputs.version }}"
117
+ gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 || \
118
+ gh release create "$TAG" --repo "$GITHUB_REPOSITORY" \
119
+ --title "GNU Global ${{ steps.ver.outputs.version }} prebuilt binaries" \
120
+ --notes "Relocatable user-space GNU Global binaries consumed by \`gtags-mcp setup\`. Not for direct use."
121
+ gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
122
+ global-*.tar.gz checksums.txt
@@ -0,0 +1,20 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+
11
+ # gtags index files (never commit an index)
12
+ GTAGS
13
+ GRTAGS
14
+ GPATH
15
+ GSYMS
16
+
17
+ # editors
18
+ .idea/
19
+ .vscode/
20
+ *.swp
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Harshith Sunku
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,342 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-gtags-server
3
+ Version: 0.6.0
4
+ Summary: Indexed code navigation for AI coding agents β€” replace grep scans with GNU Global (gtags) lookups over MCP. ~100x faster and radically less noise on million-line C/C++ codebases.
5
+ Project-URL: Homepage, https://github.com/harshithsunku/mcp-gtags-server
6
+ Project-URL: Repository, https://github.com/harshithsunku/mcp-gtags-server
7
+ Project-URL: Issues, https://github.com/harshithsunku/mcp-gtags-server/issues
8
+ Author: Harshith Sunku
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Harshith Sunku
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: ai-agents,c,c++,claude,code-navigation,code-search,gnu-global,gtags,mcp,model-context-protocol
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Software Development
41
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
42
+ Requires-Python: >=3.10
43
+ Requires-Dist: mcp>=1.8.0
44
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
45
+ Provides-Extra: dev
46
+ Requires-Dist: pytest>=8.0; extra == 'dev'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # ⚑ mcp-gtags-server
50
+
51
+ > **Stop letting your AI agent grep. Give it an index.**
52
+
53
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
54
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
55
+ [![MCP](https://img.shields.io/badge/protocol-MCP-8A2BE2)](https://modelcontextprotocol.io/)
56
+ [![Powered by GNU Global](https://img.shields.io/badge/powered%20by-GNU%20Global-orange)](https://www.gnu.org/software/global/)
57
+
58
+ 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.
59
+
60
+ **mcp-gtags-server** replaces those scans with indexed lookups powered by [GNU Global (gtags)](https://www.gnu.org/software/global/) β€” the same tags engine kernel and systems developers have trusted for decades β€” exposed to agents over the [Model Context Protocol](https://modelcontextprotocol.io/).
61
+
62
+ - πŸš€ **~100Γ— faster per query** β€” milliseconds instead of seconds, at any codebase size
63
+ - 🎯 **Radically less noise** β€” the definition, not 7,873 lines of matches
64
+ - 🧠 **Zero index management** β€” first query builds the index, every query auto-refreshes it
65
+ - πŸ”Œ **Works everywhere MCP does** β€” Claude Code, Claude Desktop, Cursor, any MCP client
66
+
67
+ ## πŸ“Š The numbers (real Linux kernel, not a toy)
68
+
69
+ Measured on a full Linux kernel checkout β€” **65,163 C/C++ files, 37.1 million lines** β€” warm page cache:
70
+
71
+ | Question an agent asks | `grep -rn` | gtags (this server) | Context consumed |
72
+ |---|---|---|---|
73
+ | Where is `tcp_v4_rcv` defined? | 1.40 s | **0.01 s** | 8 lines β†’ **1 line** |
74
+ | Where is `kmalloc` defined? | 1.62 s | **0.01 s** | 7,873 lines β†’ **5 lines** |
75
+ | Who references `kmalloc`? | 1.62 s | **0.10 s** | 7,873 noisy lines β†’ 2,744 real sites (or a **ranked per-file summary**) |
76
+ | Show me `tcp_v4_rcv`'s implementation | *read a 3,500-line file* | **`get_symbol_body`** | **exactly the 271-line function** |
77
+ | Who calls `ext4_mark_inode_dirty`? | 245 raw match lines | **`find_callers`** | **62 deduped caller functions, with counts** |
78
+
79
+ 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`](scripts/benchmark.sh):
80
+
81
+ ```bash
82
+ ./scripts/benchmark.sh /path/to/linux tcp_v4_rcv kmalloc ext4_readdir
83
+ ```
84
+
85
+ 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.
86
+
87
+ ## πŸš€ Quick start (60 seconds)
88
+
89
+ **One command. No sudo. Works everywhere** β€” restricted corporate machines, containers, build servers:
90
+
91
+ ```bash
92
+ curl -fsSL https://raw.githubusercontent.com/harshithsunku/mcp-gtags-server/main/scripts/install.sh | bash
93
+ ```
94
+
95
+ Everything lands in your home directory β€” the server (via `uv`), GNU Global, universal-ctags, and Pygments (in `~/.gtags-mcp`). When it finishes, an MCP server is **already running in the background** and the exact client configuration is printed to your console:
96
+
97
+ ```text
98
+ ==> All set! Connect your tools with the configuration below:
99
+
100
+ MCP client configuration (HTTP transport):
101
+
102
+ Claude Code (once per device, all repos):
103
+ claude mcp add --scope user --transport http gtags http://127.0.0.1:8383/mcp
104
+
105
+ Cursor / any MCP client β€” global settings or .mcp.json:
106
+ {
107
+ "mcpServers": {
108
+ "gtags": { "url": "http://127.0.0.1:8383/mcp" }
109
+ }
110
+ }
111
+ ```
112
+
113
+ **Re-run the same command any time:**
114
+
115
+ - Up to date? β†’ *"Already installed and up to date β€” nothing to install"*, and the config is printed again.
116
+ - 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.
117
+
118
+ Prefer stdio (client-launched processes) over a background server? That works too β€” `GTAGS_MCP_NO_SERVER=1` skips the server, and any client can use:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "gtags": {
124
+ "command": "gtags-mcp"
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ That's it. No indexing step, no configuration, **no per-repo setup** β€” 20 repos need zero extra installs. 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 `gtags-mcp doctor` any time to see what the server detects, or `gtags-mcp config` to re-print the client configuration.
131
+
132
+ <details>
133
+ <summary><b>Background server details</b> (network access, port, lifecycle)</summary>
134
+
135
+ The installer runs `gtags-mcp --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:
136
+
137
+ | Variable | Default | Meaning |
138
+ |---|---|---|
139
+ | `GTAGS_MCP_PORT` | `8383` | HTTP port |
140
+ | `GTAGS_MCP_HOST` | `127.0.0.1` | Bind address β€” set `0.0.0.0` to reach the server from other devices at `http://<machine-ip>:8383/mcp` |
141
+ | `GTAGS_MCP_NO_SERVER` | unset | `1` = don't start a background server |
142
+
143
+ **Security note:** the HTTP endpoint is unauthenticated. It binds localhost by default; only bind `0.0.0.0` on networks you trust.
144
+
145
+ </details>
146
+
147
+ <details>
148
+ <summary><b>Manual install</b> (prefer system packages, or already have Global)</summary>
149
+
150
+ ```bash
151
+ # 1. GNU Global β€” EITHER user-space (no sudo):
152
+ gtags-mcp setup
153
+ # OR a system package:
154
+ sudo apt install global # Debian/Ubuntu
155
+ sudo dnf install global # Fedora
156
+ brew install global # macOS
157
+
158
+ # 2. The server:
159
+ uv tool install mcp-gtags-server # or: pip install mcp-gtags-server
160
+ ```
161
+
162
+ The server finds binaries in this order: `--bin-dir`/`GTAGS_MCP_BIN_DIR`/config `bin_dir` β†’ `~/.gtags-mcp/bin` β†’ `PATH` β†’ `~/.local/bin`.
163
+
164
+ </details>
165
+
166
+ <details>
167
+ <summary><b>Claude Desktop</b> config</summary>
168
+
169
+ Add to `claude_desktop_config.json` (pin the project since Desktop doesn't launch in your repo):
170
+
171
+ ```json
172
+ {
173
+ "mcpServers": {
174
+ "gtags": {
175
+ "command": "gtags-mcp",
176
+ "args": ["--root", "/absolute/path/to/your/project"]
177
+ }
178
+ }
179
+ }
180
+ ```
181
+
182
+ </details>
183
+
184
+ <details>
185
+ <summary><b>Pin a project root</b> explicitly</summary>
186
+
187
+ The default project root is the server's working directory. 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.
188
+
189
+ </details>
190
+
191
+ <details>
192
+ <summary><b>Config files</b> β€” per-project and per-user defaults</summary>
193
+
194
+ Every setting can also live in a TOML file, so teams share defaults through the repo (like `.editorconfig`):
195
+
196
+ - **Project**: `.gtags-mcp.toml` at the project root
197
+ - **User**: `~/.config/gtags-mcp/config.toml`
198
+
199
+ ```toml
200
+ # .gtags-mcp.toml
201
+ label = "native-pygments" # force a GTAGSLABEL parser label
202
+ bin_dir = "/opt/tools/bin" # extra directory searched for gtags/global/ctags
203
+ # root = "/abs/path" # default project root (user config)
204
+ ```
205
+
206
+ Precedence: tool-call argument > CLI flag > environment variable > project config > user config > built-in default.
207
+
208
+ </details>
209
+
210
+ ## 🧰 The tools
211
+
212
+ ### Symbol-level tools β€” the noise killers
213
+
214
+ The pattern proven out by semantic-code servers like [Serena](https://github.com/oraios/serena): **give the agent the symbol, not the file.**
215
+
216
+ | Tool | What the agent gets |
217
+ |---|---|
218
+ | `symbol_info` | **A one-shot overview card** β€” definitions, reference count, hottest files, and which tool to use next. The best first query for any unfamiliar symbol. |
219
+ | `get_symbol_body` | **Just the source of a definition.** The 271-line `tcp_v4_rcv` function β€” not the 3,500-line file it lives in. Handles functions, structs, and multi-line macros. |
220
+ | `find_callers` | **The call graph, deduplicated.** Every reference mapped to its enclosing function with call counts: 245 raw lines for `ext4_mark_inode_dirty` collapse to 62 callers. |
221
+ | `call_hierarchy` | **Multi-level impact analysis.** Who calls X, who calls *those*, up to 5 levels β€” a cycle-safe, capped tree instead of N rounds of grep. |
222
+ | `find_callees` | **The outgoing call graph.** What does this function call? Body-extracted call sites, each verified against the index, split into in-tree (with locations) and external. |
223
+ | `summarize_references` | **A ranked per-file count.** The cheap first move for hot symbols β€” `kmalloc`'s 2,744 references become one screen of "where usage concentrates". |
224
+ | `project_overview` | **Orientation in an unfamiliar repo** β€” file counts by top-level directory and language, straight from the index. |
225
+ | `find_dead_symbols` | **Dead-code candidates** β€” every symbol a file defines that nothing references. |
226
+ | `find_includers` | **Header blast radius** β€” every file that `#include`s a header, matched by basename. |
227
+
228
+ A two-level `call_hierarchy` on the kernel's `ext4_mark_inode_dirty` β€” 87 compact lines instead of dozens of grep rounds:
229
+
230
+ ```text
231
+ ext4_mark_inode_dirty (definition: fs/ext4/ext4_jbd2.h:138)
232
+ β”œβ”€ ext4_rename fs/ext4/namei.c (6 sites)
233
+ β”‚ └─ ext4_rename2 fs/ext4/namei.c (1 site)
234
+ β”œβ”€ swap_inode_boot_loader fs/ext4/ioctl.c (5 sites)
235
+ β”‚ └─ __ext4_ioctl fs/ext4/ioctl.c (1 site)
236
+ β”œβ”€ ext4_mkdir fs/ext4/namei.c (3 sites)
237
+ β”‚ └─ ext4_rename2 fs/ext4/namei.c (1 site)
238
+ ...
239
+ ```
240
+
241
+ ### Core lookups
242
+
243
+ | Tool | What it does | Underlying command |
244
+ |---|---|---|
245
+ | `find_definition` | Where is this symbol defined? | `global -x` |
246
+ | `find_references` | Raw reference lines for a symbol | `global -rx` |
247
+ | `find_symbol_usages` | Usages of symbols with no in-tree definition (libc calls etc.) | `global -sx` |
248
+ | `grep_project` | Regex search across indexed files | `global -gx` |
249
+ | `list_file_symbols` | A file's API surface β€” every symbol it defines | `global -fx` |
250
+ | `complete_symbol` | Symbols starting with a prefix | `global -c` |
251
+ | `find_files` | Indexed files whose path matches a regex | `global -P` |
252
+ | `index_project` / `update_index` | Force rebuild / refresh (rarely needed β€” it's automatic) | `gtags` / `global -u` |
253
+
254
+ Every query tool supports `limit`/`offset` pagination with a continuation footer, long-line truncation, and (where it makes sense) `case_insensitive` β€” output is *engineered* to never flood a context window.
255
+
256
+ ### The flow that saves your context window
257
+
258
+ ```text
259
+ 0. project_overview() β†’ orient in an unfamiliar repo (12 lines)
260
+ 1. symbol_info("kmalloc") β†’ definitions + usage spread + next step (12 lines)
261
+ 2. call_hierarchy("ext4_mark_inode_dirty") β†’ multi-level impact tree (1 line/caller)
262
+ 3. get_symbol_body("tcp_v4_rcv") β†’ read the ONE function that matters
263
+ 4. find_callees("tcp_v4_rcv") β†’ what it depends on, with locations
264
+ ```
265
+
266
+ A few hundred lines of context total β€” versus tens of thousands for the grep-and-read-files equivalent.
267
+
268
+ ## 🌍 Multi-language projects (C + Python + more)
269
+
270
+ Real projects mix languages β€” a C core with Python tooling, JS frontends, Go services. The server handles this automatically:
271
+
272
+ - **Native languages** (C, C++, Java, PHP, Yacc, assembly) use GNU Global's fast built-in parser.
273
+ - **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.
274
+
275
+ The one-line installer (and `gtags-mcp 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:
276
+
277
+ ```bash
278
+ sudo apt install exuberant-ctags python3-pygments # Debian/Ubuntu
279
+ sudo dnf install ctags python3-pygments # Fedora
280
+ brew install ctags && pip install pygments # macOS
281
+ ```
282
+
283
+ Now `find_definition("py_util")`, `get_symbol_body` (indentation-aware for Python), `find_callees`, `call_hierarchy` β€” all work across every language in the tree, in one index.
284
+
285
+ 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).
286
+
287
+ **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.
288
+
289
+ ## βš™οΈ How it works
290
+
291
+ ```text
292
+ agent question ──► MCP tool ──► GTAGS index (built once, ~66s for the kernel)
293
+ β”‚
294
+ background auto-refresh (global -u, adaptive debounce)
295
+ β”‚
296
+ narrow answer ──► agent context
297
+ ```
298
+
299
+ - **First query on a tree?** The index is built automatically (the only operation that ever blocks β€” and only once).
300
+ - **Files changed?** A debounced incremental refresh runs **in the background**: queries always answer instantly from the current index while `global -u` catches 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; call `update_index` for a synchronous, guaranteed-fresh barrier right after edits.
301
+ - **Huge result?** Pagination footers tell the agent exactly how to fetch the next page β€” or the tool itself suggests a narrower one (`find_callers` on a symbol used in 500+ files points to `summarize_references`).
302
+
303
+ ## ❓ FAQ
304
+
305
+ **Why gtags instead of a language server (LSP)?**
306
+ 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.
307
+
308
+ **What languages?**
309
+ 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](#-multi-language-projects-c--python--more)).
310
+
311
+ **Does the agent have to manage the index?**
312
+ No. That's the point. Build-on-first-query, background refresh with adaptive debounce, zero blocking β€” queries never wait for index maintenance. The explicit `index_project`/`update_index` tools exist only as escape hatches (`update_index` doubles as a synchronous freshness barrier after edits).
313
+
314
+ **Will it fight my agent's built-in tools?**
315
+ 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.
316
+
317
+ ## πŸ› οΈ Development
318
+
319
+ ```bash
320
+ git clone https://github.com/harshithsunku/mcp-gtags-server
321
+ cd mcp-gtags-server
322
+ uv venv && uv pip install -e ".[dev]"
323
+ pytest # 21 tests; auto-skip if GNU Global is absent
324
+ npx @modelcontextprotocol/inspector gtags-mcp # poke at it interactively
325
+ ```
326
+
327
+ Tests build a real C project in a temp dir and exercise auto-indexing, auto-refresh, caller mapping, body extraction, and pagination end-to-end.
328
+
329
+ ## πŸ—ΊοΈ Roadmap
330
+
331
+ - [x] Multi-level call hierarchy (`call_hierarchy`, depth 1–5) for transitive impact analysis
332
+ - [x] Outgoing call graph (`find_callees`), symbol overview cards (`symbol_info`), project orientation (`project_overview`)
333
+ - [x] Dead-code candidates (`find_dead_symbols`) and header blast radius (`find_includers`)
334
+ - [x] Multi-language projects (Python, Go, Rust, JS, ... via ctags/Pygments plugin parsers, auto-detected)
335
+ - [ ] Structured (JSON) result variants for machine-readable output
336
+ - [ ] Published benchmarks vs LSP-based MCP servers
337
+
338
+ Contributions welcome β€” open an issue or PR.
339
+
340
+ ## πŸ“„ License
341
+
342
+ [MIT](LICENSE) Β© Harshith Sunku