ast-mcp 0.2.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.
- ast_mcp-0.2.0/.gitignore +17 -0
- ast_mcp-0.2.0/LICENSE +21 -0
- ast_mcp-0.2.0/PKG-INFO +195 -0
- ast_mcp-0.2.0/README.md +162 -0
- ast_mcp-0.2.0/ast_mcp/__init__.py +26 -0
- ast_mcp-0.2.0/ast_mcp/__main__.py +10 -0
- ast_mcp-0.2.0/ast_mcp/cli.py +459 -0
- ast_mcp-0.2.0/ast_mcp/extract.py +428 -0
- ast_mcp-0.2.0/ast_mcp/extract_doc.py +360 -0
- ast_mcp-0.2.0/ast_mcp/extract_schema.py +469 -0
- ast_mcp-0.2.0/ast_mcp/index.py +451 -0
- ast_mcp-0.2.0/ast_mcp/languages.py +158 -0
- ast_mcp-0.2.0/ast_mcp/main.py +139 -0
- ast_mcp-0.2.0/ast_mcp/parser.py +118 -0
- ast_mcp-0.2.0/ast_mcp/queries/core/go.scm +9 -0
- ast_mcp-0.2.0/ast_mcp/queries/core/javascript.scm +12 -0
- ast_mcp-0.2.0/ast_mcp/queries/core/lua.scm +28 -0
- ast_mcp-0.2.0/ast_mcp/queries/core/python.scm +9 -0
- ast_mcp-0.2.0/ast_mcp/queries/core/tsx.scm +18 -0
- ast_mcp-0.2.0/ast_mcp/queries/core/typescript.scm +18 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/bash.scm +6 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/css.scm +5 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/dockerfile.scm +5 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/graphql.scm +6 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/perl.scm +4 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/proto.scm +6 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/r.scm +5 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/ruby.scm +4 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/scss.scm +6 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/sql.scm +4 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/terraform.scm +4 -0
- ast_mcp-0.2.0/ast_mcp/queries/defs/zsh.scm +6 -0
- ast_mcp-0.2.0/ast_mcp/render.py +113 -0
- ast_mcp-0.2.0/ast_mcp/tools.py +543 -0
- ast_mcp-0.2.0/pyproject.toml +57 -0
- ast_mcp-0.2.0/tests/__init__.py +0 -0
- ast_mcp-0.2.0/tests/fixtures/core/sample.go +29 -0
- ast_mcp-0.2.0/tests/fixtures/core/sample.js +18 -0
- ast_mcp-0.2.0/tests/fixtures/core/sample.lua +42 -0
- ast_mcp-0.2.0/tests/fixtures/core/sample.py +24 -0
- ast_mcp-0.2.0/tests/fixtures/core/sample.ts +18 -0
- ast_mcp-0.2.0/tests/fixtures/core/sample.tsx +9 -0
- ast_mcp-0.2.0/tests/fixtures/data/big.csv +5001 -0
- ast_mcp-0.2.0/tests/fixtures/data/sample.json +15 -0
- ast_mcp-0.2.0/tests/fixtures/data/sample.toml +12 -0
- ast_mcp-0.2.0/tests/fixtures/data/sample.xml +5 -0
- ast_mcp-0.2.0/tests/fixtures/data/sample.yaml +10 -0
- ast_mcp-0.2.0/tests/fixtures/data/schema.graphql +22 -0
- ast_mcp-0.2.0/tests/fixtures/data/schema.proto +17 -0
- ast_mcp-0.2.0/tests/fixtures/data/schema.sql +9 -0
- ast_mcp-0.2.0/tests/fixtures/data/small.csv +4 -0
- ast_mcp-0.2.0/tests/fixtures/devops/Dockerfile +6 -0
- ast_mcp-0.2.0/tests/fixtures/devops/main.tf +16 -0
- ast_mcp-0.2.0/tests/fixtures/docs/sample.md +27 -0
- ast_mcp-0.2.0/tests/fixtures/scripting/sample.R +9 -0
- ast_mcp-0.2.0/tests/fixtures/scripting/sample.pl +12 -0
- ast_mcp-0.2.0/tests/fixtures/scripting/sample.rb +14 -0
- ast_mcp-0.2.0/tests/fixtures/scripting/sample.sh +9 -0
- ast_mcp-0.2.0/tests/fixtures/scripting/sample.zsh +9 -0
- ast_mcp-0.2.0/tests/fixtures/web/sample.css +14 -0
- ast_mcp-0.2.0/tests/fixtures/web/sample.html +17 -0
- ast_mcp-0.2.0/tests/fixtures/web/sample.scss +12 -0
- ast_mcp-0.2.0/tests/test_cli.py +298 -0
- ast_mcp-0.2.0/tests/test_extract.py +195 -0
- ast_mcp-0.2.0/tests/test_extract_defs.py +145 -0
- ast_mcp-0.2.0/tests/test_extract_doc.py +111 -0
- ast_mcp-0.2.0/tests/test_extract_schema.py +151 -0
- ast_mcp-0.2.0/tests/test_golden.py +164 -0
- ast_mcp-0.2.0/tests/test_index.py +224 -0
- ast_mcp-0.2.0/tests/test_languages.py +106 -0
- ast_mcp-0.2.0/tests/test_parser.py +96 -0
- ast_mcp-0.2.0/tests/test_server.py +56 -0
- ast_mcp-0.2.0/tests/test_tools.py +354 -0
ast_mcp-0.2.0/.gitignore
ADDED
ast_mcp-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 artemOP
|
|
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.
|
ast_mcp-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ast-mcp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Structural code retrieval over MCP — tree-sitter symbols instead of whole files
|
|
5
|
+
Project-URL: Homepage, https://github.com/matthew-brough/AST_MCP
|
|
6
|
+
Project-URL: Repository, https://github.com/matthew-brough/AST_MCP
|
|
7
|
+
Project-URL: Issues, https://github.com/matthew-brough/AST_MCP/issues
|
|
8
|
+
Author-email: artemOP <matthew.brough123890@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ast,claude,code-search,llm,mcp,tree-sitter
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: mcp[cli]>=2.1.1
|
|
25
|
+
Requires-Dist: tree-sitter-go>=0.25.0
|
|
26
|
+
Requires-Dist: tree-sitter-javascript>=0.25.0
|
|
27
|
+
Requires-Dist: tree-sitter-language-pack>=1.16.2
|
|
28
|
+
Requires-Dist: tree-sitter-lua>=0.5.0
|
|
29
|
+
Requires-Dist: tree-sitter-python>=0.25.0
|
|
30
|
+
Requires-Dist: tree-sitter-typescript>=0.23.2
|
|
31
|
+
Requires-Dist: tree-sitter>=0.26.0
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# AST_MCP
|
|
35
|
+
|
|
36
|
+
Structural code retrieval over MCP. Parses source with tree-sitter and serves
|
|
37
|
+
**symbols** instead of files, so an agent asking "what does `parse_config` do"
|
|
38
|
+
gets 40 lines rather than 2000.
|
|
39
|
+
|
|
40
|
+
Read-only. The server never writes to your source — the only file it writes is
|
|
41
|
+
its own index at `.ast_mcp/index.db`.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv tool install ast-mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or run it without installing anything:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uvx ast-mcp --version
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Requires Python 3.11 or newer.
|
|
56
|
+
|
|
57
|
+
## Wire it into Claude Code
|
|
58
|
+
|
|
59
|
+
From the repository you want indexed:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
ast-mcp init
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
That writes an `ast-mcp` stanza into `<repo>/.mcp.json`, adds `.ast_mcp/` to
|
|
66
|
+
`.gitignore`, and builds the index. Claude Code reads `.mcp.json` at startup,
|
|
67
|
+
so the next session in that directory has the tools already connected — no
|
|
68
|
+
per-session step.
|
|
69
|
+
|
|
70
|
+
`init` merges. Every other server in `.mcp.json` is left exactly as it was,
|
|
71
|
+
and a file it cannot parse is reported rather than overwritten. Re-running it
|
|
72
|
+
is a no-op. Pass `--dry-run` to see the change first.
|
|
73
|
+
|
|
74
|
+
The stanza it writes carries no absolute paths, so it is safe to commit:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"mcpServers": {
|
|
79
|
+
"ast-mcp": { "command": "ast-mcp", "args": ["serve"] }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If `ast-mcp` is not permanently on `PATH` it records `uvx ast-mcp serve`
|
|
85
|
+
instead. Override either with `--command "uv run ast-mcp serve"`.
|
|
86
|
+
|
|
87
|
+
## CLI
|
|
88
|
+
|
|
89
|
+
| command | what it does |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `ast-mcp init` | register in `.mcp.json`, ignore the index dir, build the index |
|
|
92
|
+
| `ast-mcp index [--rebuild]` | build or refresh the index; `--rebuild` discards it first |
|
|
93
|
+
| `ast-mcp status [--json]` | file/symbol counts, index size, freshness, registration |
|
|
94
|
+
| `ast-mcp languages [--group G]` | the language registry — 26 rows, their extensions and profiles |
|
|
95
|
+
| `ast-mcp serve` | the MCP server over stdio; what Claude Code launches |
|
|
96
|
+
|
|
97
|
+
Every command takes `--root PATH`. Root resolution is `--root`, else
|
|
98
|
+
`AST_MCP_ROOT`, else the working directory. Bare `ast-mcp` means `ast-mcp
|
|
99
|
+
serve`.
|
|
100
|
+
|
|
101
|
+
Only `init` writes anything outside `.ast_mcp/`, and only `.mcp.json` and
|
|
102
|
+
`.gitignore`. The server itself never writes to your source.
|
|
103
|
+
|
|
104
|
+
## Not every language gets the same treatment
|
|
105
|
+
|
|
106
|
+
A `.py` file has functions with signatures and docstrings. A
|
|
107
|
+
`docker-compose.yml` has none of that — it has a *shape*. A `README.md` has a
|
|
108
|
+
heading tree. Forcing all three through one symbol model produces garbage for
|
|
109
|
+
two of them, so there are four **extraction profiles**:
|
|
110
|
+
|
|
111
|
+
| profile | payload key | what you get | languages |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| `symbols` | `symbols` | signature, docstring, nesting, imports | python, javascript, typescript, tsx, go, lua |
|
|
114
|
+
| `defs` | `symbols` | same shape, weaker guarantees — docstrings often `null` | ruby, perl, r, bash, zsh, css, scss, sql, graphql, proto, terraform, dockerfile |
|
|
115
|
+
| `schema` | `schema` | key paths + inferred value types, **not** funcdefs | json, json5, yaml, toml, xml, csv |
|
|
116
|
+
| `outline` | `outline` | heading / section tree | markdown, html |
|
|
117
|
+
|
|
118
|
+
26 languages across six groups: `core`, `web`, `scripting`, `data`, `devops`,
|
|
119
|
+
`docs`. Every response declares its `profile` and `group` **before** the
|
|
120
|
+
payload — read that field, don't assume `symbols` exists.
|
|
121
|
+
|
|
122
|
+
Kinds never lie about fidelity. A `schema` node's kind is a value type
|
|
123
|
+
(`object`, `array`, `string`); an `outline` node's kind is a document structure
|
|
124
|
+
(`heading`, `code_block`). Nothing outside the `symbols`/`defs` profiles ever
|
|
125
|
+
claims to be a function or a class.
|
|
126
|
+
|
|
127
|
+
## Tools
|
|
128
|
+
|
|
129
|
+
| tool | use it for |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `file_outline(path, max_depth, include_docstrings)` | the `Read` replacement — a file's shape, bodies elided |
|
|
132
|
+
| `get_symbol(name, path, mode)` | one definition. `name` is a symbol name, a key path (`services.web.ports`), or a heading slug |
|
|
133
|
+
| `search_symbols(query, kind, lang, group, path_glob, limit)` | find things by name across the repo |
|
|
134
|
+
| `get_docstrings(path \| symbols)` | docs without bodies |
|
|
135
|
+
| `list_imports(path)` | dependency edges out of a file, plus exports where the language has them |
|
|
136
|
+
| `ast_query(path, query, captures)` | raw tree-sitter S-expression — the escape hatch |
|
|
137
|
+
|
|
138
|
+
Every tool takes `max_tokens` (default 4000). An over-budget response is
|
|
139
|
+
trimmed, flagged `truncated: true`, and tells you which argument narrows it.
|
|
140
|
+
Nothing is dropped silently.
|
|
141
|
+
|
|
142
|
+
`get_symbol` **never guesses**. A name matching several symbols returns
|
|
143
|
+
`ambiguous: true` with candidates; pass `path` or a qualified name to resolve.
|
|
144
|
+
|
|
145
|
+
## When to use this vs CCE `context_search`
|
|
146
|
+
|
|
147
|
+
They answer different questions and both stay.
|
|
148
|
+
|
|
149
|
+
- **`context_search` (CCE)** — fuzzy semantic retrieval over embedded chunks.
|
|
150
|
+
Use for *"how does auth work?"*, *"where is rate limiting handled?"*, and
|
|
151
|
+
anything where you know the concept but not the name.
|
|
152
|
+
- **AST_MCP** — exact structural retrieval by name, kind and range. Use for
|
|
153
|
+
*"show me `TokenStore.refresh`"*, *"what's in this config file?"*, *"list
|
|
154
|
+
every `CREATE TABLE` in the repo"*, and anything where you know the name but
|
|
155
|
+
not the location.
|
|
156
|
+
|
|
157
|
+
Rough rule: describing behaviour → `context_search`. Naming a thing →
|
|
158
|
+
AST_MCP.
|
|
159
|
+
|
|
160
|
+
## What it costs
|
|
161
|
+
|
|
162
|
+
The saving grows with file size — the response envelope is fixed cost, so
|
|
163
|
+
small files benefit least. Measured on this codebase:
|
|
164
|
+
|
|
165
|
+
| file | lines | full read | outline | saving |
|
|
166
|
+
|---|---|---|---|---|
|
|
167
|
+
| `ast_mcp/render.py` | 94 | 789 tok | 352 tok | 2.2x |
|
|
168
|
+
| `ast_mcp/index.py` | 428 | 3994 tok | 967 tok | 4.1x |
|
|
169
|
+
| `ast_mcp/tools.py` | 457 | 3882 tok | 690 tok | 5.6x |
|
|
170
|
+
|
|
171
|
+
Below roughly 100 lines it is about break-even against `Read`. Above that it
|
|
172
|
+
pays, and `get_symbol` on a single definition pays regardless.
|
|
173
|
+
|
|
174
|
+
A 5000-row CSV or JSON array costs the same as a 3-row one: homogeneous
|
|
175
|
+
repeats collapse to one node carrying `children_count`.
|
|
176
|
+
|
|
177
|
+
## Freshness
|
|
178
|
+
|
|
179
|
+
The SQLite index is a cache, never an oracle. Every path in a response was
|
|
180
|
+
`stat`-checked against its recorded `(mtime_ns, size)` during that same call,
|
|
181
|
+
and reparsed if it moved. There is no watcher daemon and no stale window.
|
|
182
|
+
|
|
183
|
+
## Development
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
git clone https://github.com/matthew-brough/AST_MCP && cd AST_MCP
|
|
187
|
+
uv sync --all-groups
|
|
188
|
+
uv run --group dev pytest -q
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The suite runs on 3.11 through 3.14. `uv build` produces the wheel; the
|
|
192
|
+
`.scm` query files ship inside it, and CI fails the build if any are missing.
|
|
193
|
+
|
|
194
|
+
`SPEC.md` is the contract: §V invariants, §I interfaces, §T tasks, §B the log
|
|
195
|
+
of what went wrong and what changed because of it.
|
ast_mcp-0.2.0/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# AST_MCP
|
|
2
|
+
|
|
3
|
+
Structural code retrieval over MCP. Parses source with tree-sitter and serves
|
|
4
|
+
**symbols** instead of files, so an agent asking "what does `parse_config` do"
|
|
5
|
+
gets 40 lines rather than 2000.
|
|
6
|
+
|
|
7
|
+
Read-only. The server never writes to your source — the only file it writes is
|
|
8
|
+
its own index at `.ast_mcp/index.db`.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv tool install ast-mcp
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or run it without installing anything:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uvx ast-mcp --version
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Requires Python 3.11 or newer.
|
|
23
|
+
|
|
24
|
+
## Wire it into Claude Code
|
|
25
|
+
|
|
26
|
+
From the repository you want indexed:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
ast-mcp init
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That writes an `ast-mcp` stanza into `<repo>/.mcp.json`, adds `.ast_mcp/` to
|
|
33
|
+
`.gitignore`, and builds the index. Claude Code reads `.mcp.json` at startup,
|
|
34
|
+
so the next session in that directory has the tools already connected — no
|
|
35
|
+
per-session step.
|
|
36
|
+
|
|
37
|
+
`init` merges. Every other server in `.mcp.json` is left exactly as it was,
|
|
38
|
+
and a file it cannot parse is reported rather than overwritten. Re-running it
|
|
39
|
+
is a no-op. Pass `--dry-run` to see the change first.
|
|
40
|
+
|
|
41
|
+
The stanza it writes carries no absolute paths, so it is safe to commit:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"ast-mcp": { "command": "ast-mcp", "args": ["serve"] }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If `ast-mcp` is not permanently on `PATH` it records `uvx ast-mcp serve`
|
|
52
|
+
instead. Override either with `--command "uv run ast-mcp serve"`.
|
|
53
|
+
|
|
54
|
+
## CLI
|
|
55
|
+
|
|
56
|
+
| command | what it does |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `ast-mcp init` | register in `.mcp.json`, ignore the index dir, build the index |
|
|
59
|
+
| `ast-mcp index [--rebuild]` | build or refresh the index; `--rebuild` discards it first |
|
|
60
|
+
| `ast-mcp status [--json]` | file/symbol counts, index size, freshness, registration |
|
|
61
|
+
| `ast-mcp languages [--group G]` | the language registry — 26 rows, their extensions and profiles |
|
|
62
|
+
| `ast-mcp serve` | the MCP server over stdio; what Claude Code launches |
|
|
63
|
+
|
|
64
|
+
Every command takes `--root PATH`. Root resolution is `--root`, else
|
|
65
|
+
`AST_MCP_ROOT`, else the working directory. Bare `ast-mcp` means `ast-mcp
|
|
66
|
+
serve`.
|
|
67
|
+
|
|
68
|
+
Only `init` writes anything outside `.ast_mcp/`, and only `.mcp.json` and
|
|
69
|
+
`.gitignore`. The server itself never writes to your source.
|
|
70
|
+
|
|
71
|
+
## Not every language gets the same treatment
|
|
72
|
+
|
|
73
|
+
A `.py` file has functions with signatures and docstrings. A
|
|
74
|
+
`docker-compose.yml` has none of that — it has a *shape*. A `README.md` has a
|
|
75
|
+
heading tree. Forcing all three through one symbol model produces garbage for
|
|
76
|
+
two of them, so there are four **extraction profiles**:
|
|
77
|
+
|
|
78
|
+
| profile | payload key | what you get | languages |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| `symbols` | `symbols` | signature, docstring, nesting, imports | python, javascript, typescript, tsx, go, lua |
|
|
81
|
+
| `defs` | `symbols` | same shape, weaker guarantees — docstrings often `null` | ruby, perl, r, bash, zsh, css, scss, sql, graphql, proto, terraform, dockerfile |
|
|
82
|
+
| `schema` | `schema` | key paths + inferred value types, **not** funcdefs | json, json5, yaml, toml, xml, csv |
|
|
83
|
+
| `outline` | `outline` | heading / section tree | markdown, html |
|
|
84
|
+
|
|
85
|
+
26 languages across six groups: `core`, `web`, `scripting`, `data`, `devops`,
|
|
86
|
+
`docs`. Every response declares its `profile` and `group` **before** the
|
|
87
|
+
payload — read that field, don't assume `symbols` exists.
|
|
88
|
+
|
|
89
|
+
Kinds never lie about fidelity. A `schema` node's kind is a value type
|
|
90
|
+
(`object`, `array`, `string`); an `outline` node's kind is a document structure
|
|
91
|
+
(`heading`, `code_block`). Nothing outside the `symbols`/`defs` profiles ever
|
|
92
|
+
claims to be a function or a class.
|
|
93
|
+
|
|
94
|
+
## Tools
|
|
95
|
+
|
|
96
|
+
| tool | use it for |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `file_outline(path, max_depth, include_docstrings)` | the `Read` replacement — a file's shape, bodies elided |
|
|
99
|
+
| `get_symbol(name, path, mode)` | one definition. `name` is a symbol name, a key path (`services.web.ports`), or a heading slug |
|
|
100
|
+
| `search_symbols(query, kind, lang, group, path_glob, limit)` | find things by name across the repo |
|
|
101
|
+
| `get_docstrings(path \| symbols)` | docs without bodies |
|
|
102
|
+
| `list_imports(path)` | dependency edges out of a file, plus exports where the language has them |
|
|
103
|
+
| `ast_query(path, query, captures)` | raw tree-sitter S-expression — the escape hatch |
|
|
104
|
+
|
|
105
|
+
Every tool takes `max_tokens` (default 4000). An over-budget response is
|
|
106
|
+
trimmed, flagged `truncated: true`, and tells you which argument narrows it.
|
|
107
|
+
Nothing is dropped silently.
|
|
108
|
+
|
|
109
|
+
`get_symbol` **never guesses**. A name matching several symbols returns
|
|
110
|
+
`ambiguous: true` with candidates; pass `path` or a qualified name to resolve.
|
|
111
|
+
|
|
112
|
+
## When to use this vs CCE `context_search`
|
|
113
|
+
|
|
114
|
+
They answer different questions and both stay.
|
|
115
|
+
|
|
116
|
+
- **`context_search` (CCE)** — fuzzy semantic retrieval over embedded chunks.
|
|
117
|
+
Use for *"how does auth work?"*, *"where is rate limiting handled?"*, and
|
|
118
|
+
anything where you know the concept but not the name.
|
|
119
|
+
- **AST_MCP** — exact structural retrieval by name, kind and range. Use for
|
|
120
|
+
*"show me `TokenStore.refresh`"*, *"what's in this config file?"*, *"list
|
|
121
|
+
every `CREATE TABLE` in the repo"*, and anything where you know the name but
|
|
122
|
+
not the location.
|
|
123
|
+
|
|
124
|
+
Rough rule: describing behaviour → `context_search`. Naming a thing →
|
|
125
|
+
AST_MCP.
|
|
126
|
+
|
|
127
|
+
## What it costs
|
|
128
|
+
|
|
129
|
+
The saving grows with file size — the response envelope is fixed cost, so
|
|
130
|
+
small files benefit least. Measured on this codebase:
|
|
131
|
+
|
|
132
|
+
| file | lines | full read | outline | saving |
|
|
133
|
+
|---|---|---|---|---|
|
|
134
|
+
| `ast_mcp/render.py` | 94 | 789 tok | 352 tok | 2.2x |
|
|
135
|
+
| `ast_mcp/index.py` | 428 | 3994 tok | 967 tok | 4.1x |
|
|
136
|
+
| `ast_mcp/tools.py` | 457 | 3882 tok | 690 tok | 5.6x |
|
|
137
|
+
|
|
138
|
+
Below roughly 100 lines it is about break-even against `Read`. Above that it
|
|
139
|
+
pays, and `get_symbol` on a single definition pays regardless.
|
|
140
|
+
|
|
141
|
+
A 5000-row CSV or JSON array costs the same as a 3-row one: homogeneous
|
|
142
|
+
repeats collapse to one node carrying `children_count`.
|
|
143
|
+
|
|
144
|
+
## Freshness
|
|
145
|
+
|
|
146
|
+
The SQLite index is a cache, never an oracle. Every path in a response was
|
|
147
|
+
`stat`-checked against its recorded `(mtime_ns, size)` during that same call,
|
|
148
|
+
and reparsed if it moved. There is no watcher daemon and no stale window.
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
git clone https://github.com/matthew-brough/AST_MCP && cd AST_MCP
|
|
154
|
+
uv sync --all-groups
|
|
155
|
+
uv run --group dev pytest -q
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The suite runs on 3.11 through 3.14. `uv build` produces the wheel; the
|
|
159
|
+
`.scm` query files ship inside it, and CI fails the build if any are missing.
|
|
160
|
+
|
|
161
|
+
`SPEC.md` is the contract: §V invariants, §I interfaces, §T tasks, §B the log
|
|
162
|
+
of what went wrong and what changed because of it.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""AST_MCP — structural code retrieval over tree-sitter.
|
|
2
|
+
|
|
3
|
+
Package-level payload records shared by every layer.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
__version__ = "0.2.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class Error:
|
|
15
|
+
"""A non-fatal problem, carried in a response instead of raised.
|
|
16
|
+
|
|
17
|
+
SPEC §V.5: a tool never propagates an exception to the agent. Every
|
|
18
|
+
failure mode becomes one of these and the payload stays valid.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
code: str
|
|
22
|
+
path: str | None = None
|
|
23
|
+
detail: str | None = None
|
|
24
|
+
|
|
25
|
+
def as_dict(self) -> dict[str, str | None]:
|
|
26
|
+
return {"code": self.code, "path": self.path, "detail": self.detail}
|