内容
# sangsik-gdscript-graph
一个针对GDScript/Godot项目的函数级调用图索引器,作为 [MCP](https://modelcontextprotocol.io) 服务器公开,以便AI编码助手可以回答诸如“什么调用了这个函数?”或“如果我更改这个信号,什么会发生?”之类的问题,而无需重新阅读整个代码库。
它使用 [gdtoolkit](https://github.com/Scony/godot-gdscript-toolkit) 的GDScript语法分析Godot项目中的每个 `.gd` 文件,解析调用和信号连接,跨文件(autoloads、`class_name`、继承、内部类、lambda、属性访问器、链式信号接收器等),并将结果存储在SQLite数据库中,MCP服务器按需查询。
## 功能
- **调用解析**:跨裸调用、`self.` 调用、`super.` 调用、autoloads、`class_name` 类型接收器和本地类型变量 — 包括继承链。
- **信号解析**:对于 `.connect(...)` 在信号端和处理端:裸信号/自身/继承信号,和链式接收器(`GameManager.card_drawn.connect(handler)`)。
- **作用域**:内部类、属性访问器(`get:`/`set(value):`)、lambda、静态函数都使用正确的作用域边界进行跟踪。
- **设计稳健**:病理性深的GDScript文件是隔离的(一个文件的解析/递归失败不会中止整个构建),构建是原子的(失败的重建永远不会破坏现有的数据库),并且已知ReDoS倾向于 `.tscn` 解析路径已加强。
- **传递影响分析**:BFS遍历调用图,以回答“所有调用(或被)此函数调用的函数,最大N跳”。
- **自动同步**:在服务器运行时,OS级文件监视器(FSEvents/inotify/ReadDirectoryChangesW)在编辑 `.gd`/`.tscn`/`project.godot` 文件后短暂的去抖动窗口内自动重建图 — 在正常编辑会话期间无需手动重建。
- **增量重建**:每次构建都缓存每个文件的解析树(按内容哈希键控);重建会为每个未更改的文件重用缓存,并且只重新解析实际更改的内容,输出与完整重建相同。 在一个433文件的测试项目中,这将重建时间减少了约3倍(解析本身,占主导地位的构建时间,减少了约4.5倍)。
- **启动时协调离线编辑**:实时文件监视器只能看到启动后所做的更改,因此每个服务器启动还会在后台运行一次协调重建(由于增量缓存而廉价),以赶上在没有服务器运行的情况下编辑的任何内容 — 例如,在两次会话之间在Godot编辑器中编辑项目后,MCP客户端会启动一个新的服务器。
## 安装
```bash
pip install -e .
```
需要Python 3.10+。
## 使用
### 1. 构建图
```bash
gdscript-graph build /path/to/godot/project -o graph.db
```
这会扫描每个 `.gd` 文件(以及 `project.godot` 文件,用于autoloads和 `.tscn` 文件,用于autoload场景),解析调用和信号连接,并将SQLite数据库写入 `graph.db`(如果省略 `-o`,则默认为 `<project>/.gdscript_graph.db`)。
### 2. 运行MCP服务器
```bash
gdscript-graph mcp graph.db
```
这会在stdio上启动一个MCP服务器,并且默认情况下,会监视项目目录(在构建时记录在数据库中)以检测更改 — 编辑 `.gd`/`.tscn`/`project.godot` 文件会在最后一次编辑后约2秒内触发自动重建,无需重启。传递 `--no-watch` 以禁用此功能,或传递 `--debounce-ms <n>` 以更改延迟。
将你的MCP客户端(Claude Code、Claude Desktop等)指向此命令,例如在Claude Code MCP配置中:
```json
{
"mcpServers": {
"gdscript-graph": {
"command": "gdscript-graph",
"args": ["mcp", "/path/to/graph.db"]
}
}
}
```
### 可用的MCP工具
| 工具 | 描述 |
|---|---|
| `search(query, limit=20)` | 函数、信号、变量、常量和枚举的 substring 搜索。 |
| `status()` | 索引健康/新鲜度:文件/符号/调用/信号计数、最后构建时间和是否正在等待或进行监视器重建。 |
| `node(name, file=None, scope=None, kind=None)` | 一个符号的逐字源代码(从磁盘重新读取)及其调用者/处理者,一次调用。 |
| `explore(names, max_path_depth=6)` | `node` 针对多个符号的详细信息,以及连接每个已解析对的实际调用路径(如果存在)。 |
| `files(prefix=None)` | 列出索引文件,包含 `class_name`/`extends`/`parse_error` 和符号计数分解;`prefix` 细化到子目录或文件。 |
| `callers(function_name, file=None, scope=None)` | 列出调用给定函数的调用站点。 |
| `callees(function_name, file=None, scope=None)` | 列出从给定函数内部调用的函数。 |
| `signal_handlers(signal_name, file=None, scope=None)` | 列出通过 `.connect(...)` 连接到信号的处理程序。 |
| `impact(function_name, file=None, scope=None, direction="callers", max_depth=5)` | 遍历调用图,以查找通过更改函数影响的所有内容。 |
`file`/`scope` 在多个声明共享名称时进行消歧义(不同文件或内部类中的同名函数)。
## 开发
```bash
pip install -e . pytest
pytest -q
```
MCP服务器在每次查询时都会重新读取DB文件,因此在服务器运行时进行的任何重建(无论是由文件监视器触发还是手动运行),都会立即被拾取,无需重启。