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.
Files changed (73) hide show
  1. ast_mcp-0.2.0/.gitignore +17 -0
  2. ast_mcp-0.2.0/LICENSE +21 -0
  3. ast_mcp-0.2.0/PKG-INFO +195 -0
  4. ast_mcp-0.2.0/README.md +162 -0
  5. ast_mcp-0.2.0/ast_mcp/__init__.py +26 -0
  6. ast_mcp-0.2.0/ast_mcp/__main__.py +10 -0
  7. ast_mcp-0.2.0/ast_mcp/cli.py +459 -0
  8. ast_mcp-0.2.0/ast_mcp/extract.py +428 -0
  9. ast_mcp-0.2.0/ast_mcp/extract_doc.py +360 -0
  10. ast_mcp-0.2.0/ast_mcp/extract_schema.py +469 -0
  11. ast_mcp-0.2.0/ast_mcp/index.py +451 -0
  12. ast_mcp-0.2.0/ast_mcp/languages.py +158 -0
  13. ast_mcp-0.2.0/ast_mcp/main.py +139 -0
  14. ast_mcp-0.2.0/ast_mcp/parser.py +118 -0
  15. ast_mcp-0.2.0/ast_mcp/queries/core/go.scm +9 -0
  16. ast_mcp-0.2.0/ast_mcp/queries/core/javascript.scm +12 -0
  17. ast_mcp-0.2.0/ast_mcp/queries/core/lua.scm +28 -0
  18. ast_mcp-0.2.0/ast_mcp/queries/core/python.scm +9 -0
  19. ast_mcp-0.2.0/ast_mcp/queries/core/tsx.scm +18 -0
  20. ast_mcp-0.2.0/ast_mcp/queries/core/typescript.scm +18 -0
  21. ast_mcp-0.2.0/ast_mcp/queries/defs/bash.scm +6 -0
  22. ast_mcp-0.2.0/ast_mcp/queries/defs/css.scm +5 -0
  23. ast_mcp-0.2.0/ast_mcp/queries/defs/dockerfile.scm +5 -0
  24. ast_mcp-0.2.0/ast_mcp/queries/defs/graphql.scm +6 -0
  25. ast_mcp-0.2.0/ast_mcp/queries/defs/perl.scm +4 -0
  26. ast_mcp-0.2.0/ast_mcp/queries/defs/proto.scm +6 -0
  27. ast_mcp-0.2.0/ast_mcp/queries/defs/r.scm +5 -0
  28. ast_mcp-0.2.0/ast_mcp/queries/defs/ruby.scm +4 -0
  29. ast_mcp-0.2.0/ast_mcp/queries/defs/scss.scm +6 -0
  30. ast_mcp-0.2.0/ast_mcp/queries/defs/sql.scm +4 -0
  31. ast_mcp-0.2.0/ast_mcp/queries/defs/terraform.scm +4 -0
  32. ast_mcp-0.2.0/ast_mcp/queries/defs/zsh.scm +6 -0
  33. ast_mcp-0.2.0/ast_mcp/render.py +113 -0
  34. ast_mcp-0.2.0/ast_mcp/tools.py +543 -0
  35. ast_mcp-0.2.0/pyproject.toml +57 -0
  36. ast_mcp-0.2.0/tests/__init__.py +0 -0
  37. ast_mcp-0.2.0/tests/fixtures/core/sample.go +29 -0
  38. ast_mcp-0.2.0/tests/fixtures/core/sample.js +18 -0
  39. ast_mcp-0.2.0/tests/fixtures/core/sample.lua +42 -0
  40. ast_mcp-0.2.0/tests/fixtures/core/sample.py +24 -0
  41. ast_mcp-0.2.0/tests/fixtures/core/sample.ts +18 -0
  42. ast_mcp-0.2.0/tests/fixtures/core/sample.tsx +9 -0
  43. ast_mcp-0.2.0/tests/fixtures/data/big.csv +5001 -0
  44. ast_mcp-0.2.0/tests/fixtures/data/sample.json +15 -0
  45. ast_mcp-0.2.0/tests/fixtures/data/sample.toml +12 -0
  46. ast_mcp-0.2.0/tests/fixtures/data/sample.xml +5 -0
  47. ast_mcp-0.2.0/tests/fixtures/data/sample.yaml +10 -0
  48. ast_mcp-0.2.0/tests/fixtures/data/schema.graphql +22 -0
  49. ast_mcp-0.2.0/tests/fixtures/data/schema.proto +17 -0
  50. ast_mcp-0.2.0/tests/fixtures/data/schema.sql +9 -0
  51. ast_mcp-0.2.0/tests/fixtures/data/small.csv +4 -0
  52. ast_mcp-0.2.0/tests/fixtures/devops/Dockerfile +6 -0
  53. ast_mcp-0.2.0/tests/fixtures/devops/main.tf +16 -0
  54. ast_mcp-0.2.0/tests/fixtures/docs/sample.md +27 -0
  55. ast_mcp-0.2.0/tests/fixtures/scripting/sample.R +9 -0
  56. ast_mcp-0.2.0/tests/fixtures/scripting/sample.pl +12 -0
  57. ast_mcp-0.2.0/tests/fixtures/scripting/sample.rb +14 -0
  58. ast_mcp-0.2.0/tests/fixtures/scripting/sample.sh +9 -0
  59. ast_mcp-0.2.0/tests/fixtures/scripting/sample.zsh +9 -0
  60. ast_mcp-0.2.0/tests/fixtures/web/sample.css +14 -0
  61. ast_mcp-0.2.0/tests/fixtures/web/sample.html +17 -0
  62. ast_mcp-0.2.0/tests/fixtures/web/sample.scss +12 -0
  63. ast_mcp-0.2.0/tests/test_cli.py +298 -0
  64. ast_mcp-0.2.0/tests/test_extract.py +195 -0
  65. ast_mcp-0.2.0/tests/test_extract_defs.py +145 -0
  66. ast_mcp-0.2.0/tests/test_extract_doc.py +111 -0
  67. ast_mcp-0.2.0/tests/test_extract_schema.py +151 -0
  68. ast_mcp-0.2.0/tests/test_golden.py +164 -0
  69. ast_mcp-0.2.0/tests/test_index.py +224 -0
  70. ast_mcp-0.2.0/tests/test_languages.py +106 -0
  71. ast_mcp-0.2.0/tests/test_parser.py +96 -0
  72. ast_mcp-0.2.0/tests/test_server.py +56 -0
  73. ast_mcp-0.2.0/tests/test_tools.py +354 -0
@@ -0,0 +1,17 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ .claude
13
+ CLAUDE.md
14
+ .mcp.json
15
+
16
+ # AST_MCP symbol index
17
+ .ast_mcp/
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.
@@ -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}
@@ -0,0 +1,10 @@
1
+ """`python -m ast_mcp` — same entry as the `ast-mcp` console script."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from ast_mcp.cli import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())