archgraph-mcp 1.0.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.
- archgraph_mcp-1.0.0/LICENSE +21 -0
- archgraph_mcp-1.0.0/PKG-INFO +27 -0
- archgraph_mcp-1.0.0/README.md +394 -0
- archgraph_mcp-1.0.0/pyproject.toml +79 -0
- archgraph_mcp-1.0.0/setup.cfg +4 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/__init__.py +3 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/__main__.py +144 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/enums.py +27 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/graph/__init__.py +0 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/graph/builder.py +142 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/graph/query_engine.py +211 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/logging_config.py +27 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/models.py +56 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/parser/__init__.py +0 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/parser/base.py +61 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/parser/java.py +182 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/parser/kotlin.py +226 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/parser/typescript.py +192 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/semantic/__init__.py +1 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/semantic/build.py +29 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/semantic/embeddings.py +170 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/semantic/vector_index.py +98 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/server/__init__.py +0 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/server/mcp_server.py +339 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/storage/__init__.py +0 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/storage/kuzu_store.py +246 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/utils/__init__.py +0 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp/utils/scanner.py +89 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp.egg-info/PKG-INFO +27 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp.egg-info/SOURCES.txt +40 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp.egg-info/dependency_links.txt +1 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp.egg-info/entry_points.txt +2 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp.egg-info/requires.txt +22 -0
- archgraph_mcp-1.0.0/src/archgraph_mcp.egg-info/top_level.txt +1 -0
- archgraph_mcp-1.0.0/tests/test_embeddings.py +73 -0
- archgraph_mcp-1.0.0/tests/test_graph_builder.py +32 -0
- archgraph_mcp-1.0.0/tests/test_graph_ui.py +27 -0
- archgraph_mcp-1.0.0/tests/test_kuzu_store.py +45 -0
- archgraph_mcp-1.0.0/tests/test_mcp_server.py +49 -0
- archgraph_mcp-1.0.0/tests/test_parser.py +91 -0
- archgraph_mcp-1.0.0/tests/test_query_engine.py +51 -0
- archgraph_mcp-1.0.0/tests/test_scanner.py +42 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ArchGraph MCP Contributors
|
|
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,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: archgraph-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server that builds a knowledge graph of a software codebase
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Dist: pydantic>=2.13.4
|
|
8
|
+
Requires-Dist: networkx>=3.6.1
|
|
9
|
+
Requires-Dist: mcp<2.0,>=1.28.1
|
|
10
|
+
Requires-Dist: uvicorn>=0.51.0
|
|
11
|
+
Requires-Dist: starlette>=1.3.1
|
|
12
|
+
Requires-Dist: tree-sitter>=0.26.0
|
|
13
|
+
Requires-Dist: tree-sitter-typescript>=0.23.2
|
|
14
|
+
Requires-Dist: tree-sitter-java>=0.23.5
|
|
15
|
+
Requires-Dist: tree-sitter-kotlin>=1.1.0
|
|
16
|
+
Requires-Dist: kuzu>=0.11.3
|
|
17
|
+
Provides-Extra: semantic
|
|
18
|
+
Requires-Dist: numpy>=2.5.1; extra == "semantic"
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=9.1.1; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=1.4.0; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff>=0.15.22; extra == "dev"
|
|
23
|
+
Requires-Dist: mypy>=2.3.0; extra == "dev"
|
|
24
|
+
Requires-Dist: types-networkx>=3.6.1.20260624; extra == "dev"
|
|
25
|
+
Requires-Dist: mdformat>=1.0; extra == "dev"
|
|
26
|
+
Requires-Dist: mdformat-gfm>=1.0; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# ArchGraph MCP
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mustafa-zidan/archgraph-mcp/actions/workflows/test.yml)
|
|
4
|
+
[](https://pypi.org/project/archgraph-mcp/)
|
|
5
|
+
[](https://pypi.org/project/archgraph-mcp/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
ArchGraph MCP turns a repository into a queryable graph. It parses TypeScript, Java, and Kotlin source with
|
|
9
|
+
[Tree-sitter](https://tree-sitter.github.io/), builds a directed graph of files, symbols, and dependencies, and exposes
|
|
10
|
+
that graph to AI coding agents over the [Model Context Protocol](https://modelcontextprotocol.io/). Instead of grepping
|
|
11
|
+
for call sites, an agent can ask what a function depends on, what breaks if it changes, or how two nodes connect.
|
|
12
|
+
|
|
13
|
+
## How a repository becomes a queryable graph
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Repository
|
|
17
|
+
↓
|
|
18
|
+
File Scanner (lazy, generator-based)
|
|
19
|
+
↓
|
|
20
|
+
Parser Layer (Tree-sitter: TypeScript, Java, Kotlin)
|
|
21
|
+
↓
|
|
22
|
+
Graph Builder (NetworkX DiGraph)
|
|
23
|
+
↓
|
|
24
|
+
Kuzu Storage (embedded graph DB + full-text search)
|
|
25
|
+
↓
|
|
26
|
+
Query Engine (BFS, shortest path, impact analysis)
|
|
27
|
+
↓
|
|
28
|
+
MCP Server (FastMCP: stdio, sse, or streamable-http transport)
|
|
29
|
+
↓
|
|
30
|
+
AI Agent (Cursor, Windsurf, Claude Code, etc.)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The graph itself lives in **NetworkX** at query time; traversals (BFS, shortest path, impact analysis) run in memory
|
|
34
|
+
against that structure. **Kuzu**'s job is persistence and lexical search: it stores nodes and edges across restarts and
|
|
35
|
+
powers BM25-style `search_nodes`, falling back to substring matching when full-text search finds nothing. Semantic
|
|
36
|
+
search is a separate, optional layer: embedding vectors live in NumPy files next to the Kuzu path rather than inside the
|
|
37
|
+
graph database itself.
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
This README covers installation, usage, and the reference tables. Three guides go deeper:
|
|
42
|
+
|
|
43
|
+
- [Setup and MCP](docs/setup-and-mcp.md): install, first-time analyze, environment variables, stdio vs HTTP, Claude
|
|
44
|
+
Desktop, Cursor, VS Code, remote URLs, Docker, troubleshooting.
|
|
45
|
+
- [Local build and semantic](docs/local-build-and-semantic.md): developing from a clone, `analyze --semantic-index`,
|
|
46
|
+
embedding backends, and serving with the vector index.
|
|
47
|
+
- [Release cycle](docs/release-cycle.md): versioning and PyPI releases for maintainers.
|
|
48
|
+
|
|
49
|
+
## Installing it
|
|
50
|
+
|
|
51
|
+
`uvx archgraph-mcp` works once the package is on PyPI. Until the first release ships, install from a clone.
|
|
52
|
+
|
|
53
|
+
### From PyPI, with uvx
|
|
54
|
+
|
|
55
|
+
[`uvx`](https://docs.astral.sh/uv/guides/tools/) runs the published tool in an isolated environment, no global install
|
|
56
|
+
needed:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uvx archgraph-mcp --help
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The optional **`[semantic]`** extra pulls in NumPy for `search_nodes_semantic` and the vector index. Pass it with
|
|
63
|
+
`--with` so the tool environment includes it:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uvx --with "archgraph-mcp[semantic]" archgraph-mcp --help
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### From a git clone
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/mustafa-zidan/archgraph-mcp.git
|
|
73
|
+
cd archgraph-mcp
|
|
74
|
+
uv sync --extra dev --extra semantic
|
|
75
|
+
uv run archgraph-mcp --help
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Drop `--extra semantic` if you don't need vector search; the core graph and full-text search work without it.
|
|
79
|
+
|
|
80
|
+
## Analyzing and serving a repository
|
|
81
|
+
|
|
82
|
+
Examples below use `uvx` (PyPI). From a clone, replace `uvx …` with `uv run …`.
|
|
83
|
+
|
|
84
|
+
### Analyze
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uvx archgraph-mcp analyze ./your-repo
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This scans the repo, parses it, and writes a Kuzu database (`archgraph.kuzu` by default). Add `--semantic-index` to also
|
|
91
|
+
build the vector index, which needs `[semantic]` in the environment:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uvx --with "archgraph-mcp[semantic]" archgraph-mcp analyze ./your-repo --semantic-index
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Serve over stdio (local)
|
|
98
|
+
|
|
99
|
+
Most desktop agents spawn the process and talk over stdin/stdout:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
uvx archgraph-mcp serve ./your-repo
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
With semantic tooling available to the server process:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uvx --with "archgraph-mcp[semantic]" archgraph-mcp serve ./your-repo
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Serve over HTTP (remote)
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uvx archgraph-mcp serve ./your-repo --transport streamable-http --port 3847
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Graph viewer (HTTP transports only)
|
|
118
|
+
|
|
119
|
+
`--graph-ui` (or `GRAPH_UI=1`) exposes a [vis-network](https://visjs.github.io/vis-network/docs/network/) view at
|
|
120
|
+
`/graph` and raw JSON at `/api/graph` (optional `limit` query param, default `500`, caps node count for responsiveness).
|
|
121
|
+
It's ignored under `stdio`, since there's no HTTP server to attach it to.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
uvx archgraph-mcp serve ./your-repo --transport streamable-http --port 3847 --graph-ui
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Wiring it into an MCP client
|
|
128
|
+
|
|
129
|
+
Full walkthroughs for Claude Desktop, Cursor, VS Code, and remote URLs are in
|
|
130
|
+
[docs/setup-and-mcp.md](docs/setup-and-mcp.md). Quick reference below; it requires `uv` on `PATH` so `uvx` resolves.
|
|
131
|
+
|
|
132
|
+
**Core** (lexical `search_nodes` only):
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"mcpServers": {
|
|
137
|
+
"archgraph": {
|
|
138
|
+
"command": "uvx",
|
|
139
|
+
"args": [
|
|
140
|
+
"archgraph-mcp",
|
|
141
|
+
"serve",
|
|
142
|
+
"/absolute/path/to/your/repo"
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**With `[semantic]`** (NumPy + `search_nodes_semantic`, once an index exists):
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"mcpServers": {
|
|
154
|
+
"archgraph": {
|
|
155
|
+
"command": "uvx",
|
|
156
|
+
"args": [
|
|
157
|
+
"--with",
|
|
158
|
+
"archgraph-mcp[semantic]",
|
|
159
|
+
"archgraph-mcp",
|
|
160
|
+
"serve",
|
|
161
|
+
"/absolute/path/to/your/repo"
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**From a git clone** (before a PyPI release, or for development):
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"mcpServers": {
|
|
173
|
+
"archgraph": {
|
|
174
|
+
"command": "uv",
|
|
175
|
+
"args": [
|
|
176
|
+
"run",
|
|
177
|
+
"--directory",
|
|
178
|
+
"/absolute/path/to/archgraph-mcp",
|
|
179
|
+
"archgraph-mcp",
|
|
180
|
+
"serve",
|
|
181
|
+
"/absolute/path/to/your/repo"
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Run `uv sync --extra dev --extra semantic` in that clone before starting the MCP client.
|
|
189
|
+
|
|
190
|
+
Optional env, e.g. a custom Kuzu path:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"mcpServers": {
|
|
195
|
+
"archgraph": {
|
|
196
|
+
"command": "uvx",
|
|
197
|
+
"args": [
|
|
198
|
+
"archgraph-mcp",
|
|
199
|
+
"serve",
|
|
200
|
+
"/path/to/repo"
|
|
201
|
+
],
|
|
202
|
+
"env": {
|
|
203
|
+
"ARCHGRAPH_STORE": "/path/to/repo/.archgraph/archgraph.kuzu"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Remote server** (streamable-http / SSE):
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"mcpServers": {
|
|
215
|
+
"archgraph": {
|
|
216
|
+
"url": "http://your-server:3847/mcp"
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Start the process itself with `uvx archgraph-mcp serve /repo --transport streamable-http --port 3847` (Docker
|
|
223
|
+
instructions below).
|
|
224
|
+
|
|
225
|
+
## The tools it exposes
|
|
226
|
+
|
|
227
|
+
| Tool | Description |
|
|
228
|
+
| ----------------------- | ------------------------------------------------- |
|
|
229
|
+
| `search_nodes` | Lexical search (FTS + substring fallback) by type |
|
|
230
|
+
| `search_nodes_semantic` | Cosine similarity (requires `[semantic]` + index) |
|
|
231
|
+
| `trace_dependencies` | What does this node depend on? |
|
|
232
|
+
| `trace_dependents` | What depends on this node? |
|
|
233
|
+
| `impact_analysis` | What breaks if this node changes? |
|
|
234
|
+
| `trace_path` | Shortest path between two nodes |
|
|
235
|
+
| `architecture_summary` | High-level graph summary |
|
|
236
|
+
|
|
237
|
+
Node ids follow a `type:identifier` shape, e.g. `function:auth.loginUser` or `file:src/auth.ts`. Run `search_nodes`
|
|
238
|
+
first if you don't know the exact id.
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
search_nodes(query="login", node_type="function")
|
|
242
|
+
impact_analysis(node_id="function:auth.loginUser")
|
|
243
|
+
trace_path(source_id="file:src/auth.ts", target_id="database:users")
|
|
244
|
+
architecture_summary()
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Semantic search
|
|
248
|
+
|
|
249
|
+
Lexical search (`search_nodes`) matches names and text. Semantic search (`search_nodes_semantic`) matches meaning, at
|
|
250
|
+
the cost of an embedding backend and a build step.
|
|
251
|
+
|
|
252
|
+
1. Install `[semantic]` into the tool environment:
|
|
253
|
+
`uvx --with "archgraph-mcp[semantic]" archgraph-mcp analyze ./repo --semantic-index` (or `uv sync --extra semantic`
|
|
254
|
+
from a clone).
|
|
255
|
+
2. Pick a backend via `ARCHGRAPH_EMBED_BACKEND`:
|
|
256
|
+
- `openai` (default): `POST {OPENAI_BASE_URL}/v1/embeddings`. Works against LM Studio, Ollama's OpenAI-compatible
|
|
257
|
+
mode, or OpenAI itself.
|
|
258
|
+
- `local`: in-process models via `sentence-transformers`, installed separately (
|
|
259
|
+
`pip install sentence-transformers`).
|
|
260
|
+
3. Build the index during analyze, with the flag or the env var: `archgraph-mcp analyze ./repo --semantic-index` or
|
|
261
|
+
`ARCHGRAPH_BUILD_SEMANTIC_INDEX=1`. This writes `archgraph.vectors.npz` and `archgraph.embeddings.json` next to the
|
|
262
|
+
Kuzu file (`--store` / `ARCHGRAPH_STORE`).
|
|
263
|
+
|
|
264
|
+
| Variable | Purpose |
|
|
265
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
266
|
+
| `ARCHGRAPH_EMBED_BACKEND` | `openai` or `local` |
|
|
267
|
+
| `ARCHGRAPH_EMBED_BATCH_SIZE` | OpenAI HTTP backend: inputs per request (default `64`). If the server returns fewer vectors than inputs, the client retries one string per request automatically; set `1` to skip the slow failed-batch attempt. |
|
|
268
|
+
| `OPENAI_BASE_URL` | e.g. `http://127.0.0.1:1234/v1` for LM Studio |
|
|
269
|
+
| `OPENAI_API_KEY` | Bearer token (dummy value is fine if the server ignores it) |
|
|
270
|
+
| `OPENAI_EMBEDDING_MODEL` | Model id for `/v1/embeddings` |
|
|
271
|
+
| `ARCHGRAPH_LOCAL_EMBED_MODEL` | Sentence-transformers model id when backend is `local` |
|
|
272
|
+
|
|
273
|
+
The index build and the query-time call must agree on backend and model; a mismatch produces vectors that don't line up,
|
|
274
|
+
not an error.
|
|
275
|
+
|
|
276
|
+
## Supported languages
|
|
277
|
+
|
|
278
|
+
- TypeScript / TSX
|
|
279
|
+
- Java
|
|
280
|
+
- Kotlin (`.kt`, `.kts`)
|
|
281
|
+
|
|
282
|
+
## Version support
|
|
283
|
+
|
|
284
|
+
Requires Python 3.12+ (`requires-python` in [`pyproject.toml`](pyproject.toml)). CI
|
|
285
|
+
([`.github/workflows/test.yml`](.github/workflows/test.yml)) runs this matrix on every push:
|
|
286
|
+
|
|
287
|
+
| OS | 3.12 | 3.13 | 3.14 |
|
|
288
|
+
| ------- | ---- | ---- | ---------------------- |
|
|
289
|
+
| Linux | ✅ | ✅ | ✅ |
|
|
290
|
+
| macOS | ✅ | ✅ | ✅ (built from source) |
|
|
291
|
+
| Windows | ✅ | ✅ | ❌ see note below |
|
|
292
|
+
|
|
293
|
+
Windows + Python 3.14 is excluded from CI. [`kuzu`](https://pypi.org/project/kuzu/#files) 0.11.3 ships a `cp314` wheel
|
|
294
|
+
for Linux but not for Windows or macOS, so both fall back to a source build. macOS's build succeeds because Xcode's
|
|
295
|
+
command line tools provide `make`; the Windows runner has no such toolchain, so the same fallback fails there. It's an
|
|
296
|
+
upstream packaging gap, not something this project controls. Docker sidesteps it entirely, since the container is
|
|
297
|
+
Linux-based regardless of host OS.
|
|
298
|
+
|
|
299
|
+
## Running it as a service
|
|
300
|
+
|
|
301
|
+
### Docker
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
docker build -t archgraph-mcp .
|
|
305
|
+
docker run -p 3847:3847 -v /path/to/repo:/repo archgraph-mcp
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Railway
|
|
309
|
+
|
|
310
|
+
1. Connect the GitHub repo at [railway.app](https://railway.app).
|
|
311
|
+
2. Set the environment variable `REPO_PATH=/repo`.
|
|
312
|
+
3. Deploy; Railway picks up `railway.json` automatically.
|
|
313
|
+
|
|
314
|
+
### Fly.io
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
fly launch
|
|
318
|
+
fly deploy
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Environment variables
|
|
322
|
+
|
|
323
|
+
| Variable | Default | Description |
|
|
324
|
+
| -------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------- |
|
|
325
|
+
| `REPO_PATH` | `.` | Path to the repository to analyze |
|
|
326
|
+
| `ARCHGRAPH_STORE` | `archgraph.kuzu` | Kuzu database path (overrides the default when the CLI does not pass `--store`) |
|
|
327
|
+
| `PORT` | `3847` | Port for SSE transport |
|
|
328
|
+
| `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio`, `sse`, or `streamable-http` |
|
|
329
|
+
| `GRAPH_UI` | unset | Set to `1` / `true` to enable `/graph` and `/api/graph` (same as `--graph-ui`; HTTP transports only) |
|
|
330
|
+
| `ARCHGRAPH_BUILD_SEMANTIC_INDEX` | unset | Set to `1` / `true` to build the vector index when serving triggers a full analyze (optional) |
|
|
331
|
+
|
|
332
|
+
## Developing on it
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
uv sync --extra dev
|
|
336
|
+
# or: pip install -e ".[dev]"
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Lint and format:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
ruff check src tests
|
|
343
|
+
ruff format src tests
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Markdown (see [`.mdformat.toml`](.mdformat.toml); [GFM](https://github.github.com/gfm/) tables and wrapping):
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
mdformat README.md CHANGELOG.md docs/
|
|
350
|
+
mdformat --check README.md CHANGELOG.md docs/
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Static typing:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
mypy src
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Tests:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
pytest tests/ -v
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Optional [pre-commit](https://pre-commit.com/) hooks run Ruff lint and format on commit:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
pip install pre-commit
|
|
369
|
+
pre-commit install
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Releasing to PyPI
|
|
373
|
+
|
|
374
|
+
[`.github/workflows/test.yml`](.github/workflows/test.yml) runs on every push and PR to `main`, `master`, or `develop`:
|
|
375
|
+
`uv sync --extra dev`, Ruff, mdformat, mypy, and pytest, across Ubuntu, macOS, and Windows on Python 3.12, 3.13, and
|
|
376
|
+
3.14.
|
|
377
|
+
|
|
378
|
+
[`.github/workflows/release.yml`](.github/workflows/release.yml) is manual (`workflow_dispatch`) and publishes to
|
|
379
|
+
[PyPI](https://pypi.org/p/archgraph-mcp) via [trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC, no
|
|
380
|
+
long-lived token). To cut a release:
|
|
381
|
+
|
|
382
|
+
1. Bump `version` in [`pyproject.toml`](pyproject.toml) and add a matching section to [`CHANGELOG.md`](CHANGELOG.md),
|
|
383
|
+
then merge to the default branch.
|
|
384
|
+
2. One-time setup: on PyPI, add a trusted publisher for this repo (workflow `release.yml`, environment `pypi`); on
|
|
385
|
+
GitHub, create an environment named `pypi`.
|
|
386
|
+
3. Run **Actions → Release → Run workflow**, entering the same version string as in `pyproject.toml`.
|
|
387
|
+
|
|
388
|
+
The workflow tags `vX.Y.Z`, builds with `uv build`, publishes to PyPI, signs the artifacts with Sigstore, and creates a
|
|
389
|
+
GitHub Release with notes pulled from the changelog. Full maintainer guide:
|
|
390
|
+
[docs/release-cycle.md](docs/release-cycle.md).
|
|
391
|
+
|
|
392
|
+
## License
|
|
393
|
+
|
|
394
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=80.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "archgraph-mcp"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "MCP server that builds a knowledge graph of a software codebase"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"pydantic>=2.13.4",
|
|
12
|
+
"networkx>=3.6.1",
|
|
13
|
+
"mcp>=1.28.1,<2.0",
|
|
14
|
+
"uvicorn>=0.51.0",
|
|
15
|
+
"starlette>=1.3.1",
|
|
16
|
+
"tree-sitter>=0.26.0",
|
|
17
|
+
"tree-sitter-typescript>=0.23.2",
|
|
18
|
+
"tree-sitter-java>=0.23.5",
|
|
19
|
+
"tree-sitter-kotlin>=1.1.0",
|
|
20
|
+
"kuzu>=0.11.3",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
semantic = [
|
|
25
|
+
"numpy>=2.5.1",
|
|
26
|
+
]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest>=9.1.1",
|
|
29
|
+
"pytest-asyncio>=1.4.0",
|
|
30
|
+
"ruff>=0.15.22",
|
|
31
|
+
"mypy>=2.3.0",
|
|
32
|
+
"types-networkx>=3.6.1.20260624",
|
|
33
|
+
"mdformat>=1.0",
|
|
34
|
+
"mdformat-gfm>=1.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
archgraph-mcp = "archgraph_mcp.__main__:main"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
pythonpath = ["src"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
target-version = "py312"
|
|
48
|
+
line-length = 120
|
|
49
|
+
src = ["src", "tests"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint.isort]
|
|
55
|
+
known-first-party = ["archgraph_mcp"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.format]
|
|
58
|
+
quote-style = "double"
|
|
59
|
+
|
|
60
|
+
[tool.mypy]
|
|
61
|
+
python_version = "3.12"
|
|
62
|
+
mypy_path = "src"
|
|
63
|
+
packages = ["archgraph_mcp"]
|
|
64
|
+
warn_return_any = false
|
|
65
|
+
warn_unused_configs = true
|
|
66
|
+
disallow_untyped_defs = false
|
|
67
|
+
check_untyped_defs = true
|
|
68
|
+
|
|
69
|
+
[[tool.mypy.overrides]]
|
|
70
|
+
module = [
|
|
71
|
+
"tree_sitter.*",
|
|
72
|
+
"tree_sitter_typescript.*",
|
|
73
|
+
"tree_sitter_java.*",
|
|
74
|
+
"tree_sitter_kotlin.*",
|
|
75
|
+
"kuzu.*",
|
|
76
|
+
"numpy.*",
|
|
77
|
+
"sentence_transformers.*",
|
|
78
|
+
]
|
|
79
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""CLI entry-point for ArchGraph MCP.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python -m archgraph_mcp analyze ./my_repo
|
|
5
|
+
python -m archgraph_mcp serve ./my_repo
|
|
6
|
+
python -m archgraph_mcp serve ./my_repo --transport sse --port 3847
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Literal, cast
|
|
17
|
+
|
|
18
|
+
from archgraph_mcp.graph.builder import GraphBuilder
|
|
19
|
+
from archgraph_mcp.graph.query_engine import QueryEngine
|
|
20
|
+
from archgraph_mcp.logging_config import setup_logging
|
|
21
|
+
from archgraph_mcp.storage.kuzu_store import KuzuStore
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _default_store_path() -> str:
|
|
25
|
+
return os.environ.get("ARCHGRAPH_STORE") or "archgraph.kuzu"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main(argv: list[str] | None = None) -> None:
|
|
29
|
+
setup_logging()
|
|
30
|
+
parser = argparse.ArgumentParser(
|
|
31
|
+
prog="archgraph-mcp",
|
|
32
|
+
description="Build and query a knowledge graph of your codebase.",
|
|
33
|
+
)
|
|
34
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
35
|
+
|
|
36
|
+
# ---- analyze ----
|
|
37
|
+
p_analyze = sub.add_parser("analyze", help="Analyze a repository and print summary")
|
|
38
|
+
p_analyze.add_argument(
|
|
39
|
+
"repo", type=Path, nargs="?", default=Path(os.environ.get("REPO_PATH", ".")), help="Path to the repository root"
|
|
40
|
+
)
|
|
41
|
+
p_analyze.add_argument(
|
|
42
|
+
"--store",
|
|
43
|
+
type=str,
|
|
44
|
+
default=_default_store_path(),
|
|
45
|
+
help="Kuzu database path (default: archgraph.kuzu or ARCHGRAPH_STORE)",
|
|
46
|
+
)
|
|
47
|
+
p_analyze.add_argument(
|
|
48
|
+
"--semantic-index",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="Build embedding vector index (requires pip install archgraph-mcp[semantic])",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# ---- serve ----
|
|
54
|
+
p_serve = sub.add_parser("serve", help="Start the MCP server")
|
|
55
|
+
p_serve.add_argument(
|
|
56
|
+
"repo", type=Path, nargs="?", default=Path(os.environ.get("REPO_PATH", ".")), help="Path to the repository root"
|
|
57
|
+
)
|
|
58
|
+
p_serve.add_argument(
|
|
59
|
+
"--store",
|
|
60
|
+
type=str,
|
|
61
|
+
default=_default_store_path(),
|
|
62
|
+
help="Kuzu database path (default: archgraph.kuzu or ARCHGRAPH_STORE)",
|
|
63
|
+
)
|
|
64
|
+
p_serve.add_argument(
|
|
65
|
+
"--transport",
|
|
66
|
+
type=str,
|
|
67
|
+
default=os.environ.get("MCP_TRANSPORT", "stdio"),
|
|
68
|
+
choices=["stdio", "sse", "streamable-http"],
|
|
69
|
+
help="MCP transport: stdio (local), sse, or streamable-http (remote)",
|
|
70
|
+
)
|
|
71
|
+
p_serve.add_argument(
|
|
72
|
+
"--port",
|
|
73
|
+
type=int,
|
|
74
|
+
default=int(os.environ.get("PORT", "3847")),
|
|
75
|
+
help="Port for HTTP transports (default 3847, or PORT env)",
|
|
76
|
+
)
|
|
77
|
+
p_serve.add_argument(
|
|
78
|
+
"--graph-ui",
|
|
79
|
+
action="store_true",
|
|
80
|
+
help="Expose GET /graph and /api/graph (HTTP transports only; not stdio)",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
args = parser.parse_args(argv)
|
|
84
|
+
|
|
85
|
+
if args.command == "analyze":
|
|
86
|
+
_run_analyze(args.repo, args.store, semantic_index=args.semantic_index)
|
|
87
|
+
elif args.command == "serve":
|
|
88
|
+
graph_ui_flag = args.graph_ui or os.environ.get("GRAPH_UI", "").strip().lower() in (
|
|
89
|
+
"1",
|
|
90
|
+
"true",
|
|
91
|
+
"yes",
|
|
92
|
+
)
|
|
93
|
+
_run_serve(args.repo, args.store, args.transport, args.port, graph_ui=graph_ui_flag)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _run_analyze(repo: Path, store_path: str, *, semantic_index: bool = False) -> None:
|
|
97
|
+
if semantic_index:
|
|
98
|
+
os.environ["ARCHGRAPH_BUILD_SEMANTIC_INDEX"] = "1"
|
|
99
|
+
|
|
100
|
+
builder = GraphBuilder()
|
|
101
|
+
builder.build_from_repository(repo.resolve())
|
|
102
|
+
|
|
103
|
+
resolved = str(Path(store_path).resolve())
|
|
104
|
+
store = KuzuStore(resolved)
|
|
105
|
+
store.save_graph(builder.all_nodes(), builder.all_edges())
|
|
106
|
+
|
|
107
|
+
from archgraph_mcp.semantic.build import maybe_build_semantic_index
|
|
108
|
+
|
|
109
|
+
maybe_build_semantic_index(resolved, builder.all_nodes())
|
|
110
|
+
|
|
111
|
+
engine = QueryEngine(builder.graph, builder._node_index, fts_store=store)
|
|
112
|
+
summary = engine.architecture_summary()
|
|
113
|
+
store.close()
|
|
114
|
+
print(json.dumps(summary.model_dump(), indent=2))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _run_serve(
|
|
118
|
+
repo: Path,
|
|
119
|
+
store_path: str,
|
|
120
|
+
transport: str,
|
|
121
|
+
port: int,
|
|
122
|
+
*,
|
|
123
|
+
graph_ui: bool = False,
|
|
124
|
+
) -> None:
|
|
125
|
+
# FastMCP binds ``self.settings.port`` from values captured when ``mcp`` is constructed
|
|
126
|
+
# (import time). Set these *before* importing ``mcp_server`` so ``--port`` is honored.
|
|
127
|
+
os.environ["PORT"] = str(port)
|
|
128
|
+
os.environ["FASTMCP_PORT"] = str(port)
|
|
129
|
+
|
|
130
|
+
from archgraph_mcp.server.mcp_server import initialize, mcp
|
|
131
|
+
|
|
132
|
+
log = logging.getLogger("archgraph_mcp")
|
|
133
|
+
graph_ui_effective = graph_ui and transport != "stdio"
|
|
134
|
+
if graph_ui and transport == "stdio":
|
|
135
|
+
log.warning(
|
|
136
|
+
"--graph-ui is ignored for stdio transport (no HTTP server). "
|
|
137
|
+
"Use --transport sse or streamable-http to enable /graph.",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Build or load the graph
|
|
141
|
+
initialize(str(repo), store_path, graph_ui=graph_ui_effective)
|
|
142
|
+
|
|
143
|
+
# Let FastMCP handle server startup
|
|
144
|
+
mcp.run(transport=cast(Literal["stdio", "sse", "streamable-http"], transport))
|