gclluch/py-ast-mcp

GitHub: gclluch/py-ast-mcp

基于 Python ast 标准库的 MCP 服务器,为 AI 编程助手提供包含调用图、复杂度分析和死代码检测在内的 20 种源代码结构分析工具。

Stars: 0 | Forks: 0

# py-ast-mcp 一个用于对 **Python** 源代码进行深度结构分析的 MCP (Model Context Protocol) 服务器。 它是 [ts-ast-mcp](https://github.com/gclluch/ts-ast-mcp) 的 Python 对应版本,并在 Python 语义允许的范围内尽可能镜像了其 20 个工具接口。一切都建立在标准库 `ast` 模块之上——不需要编译、类型检查器或项目配置。`jedi` 是一个可选的额外依赖,仅用于跨文件语义解析,当未安装它时,每个工具都能优雅降级。 与 `grep` 不同,此服务器能理解实际的结构:函数签名、类层次结构、调用关系、圈复杂度以及未被引用的代码。 ## 功能 - **真正的 AST,而非正则**——带有注解和默认值的签名、装饰器、async 标志、仅限位置和仅限关键字参数、嵌套定义。 - **感知 Python 的分类**——dataclass、enum、`Protocol`、`TypedDict`、`NamedTuple`、ABC、exception 和 `TypeAlias` 被识别为不同的类型。 - **Mermaid 格式的调用图**——文件范围或包范围的流程图,以某个函数为根节点,并带有可选的外部调用边。 - **质量工具**——带有等级的圈复杂度,分数与 `radon`/`mccabe` 匹配,代码坏味道,以及 Python 特有的风险(可变默认参数、可变的 `@dataclass` 字段默认值、裸 `except`、不可达的 `except` 子句、闭包延迟绑定、对字面量使用 `is`、未等待的协程、使用 `assert` 进行验证)。 - **跨目录的死代码**——未被引用的私有和模块级符号,并在私有和公共名称之间进行置信度划分。 - **Protocol 一致性**——查找显式(基类,包括间接子类)和结构性(方法集匹配)的实现。 - **Docstring 解析**——Google 和 NumPy 风格被拆分为摘要 / 参数 / 返回值 / 引发异常。 - **结构性差异比对**——签名级别,而非文本级别:API 中实际发生了什么变化。 - **对模型友好的输出**——紧凑、易扫读的纯文本,到处都有行号,而不是原始的 JSON 转储。 - **遇到错误输入从不崩溃**——语法错误会作为包含行和列的可读错误返回,并标记为 `isError`,以便客户端能够区分失败和分析结果;服务器保持运行。 - **解析缓存**——模块按路径 + mtime + 大小进行缓存,因此一轮对话中重复的工具调用成本很低。 ## 安装说明 需要 Python 3.10+。建议使用你安装的最新版 Python:服务器只能解析其自身解释器能理解的语法(参见 [已知限制](#known-limitations))。 ``` git clone py-ast-mcp cd py-ast-mcp pip install -e . # 可选:跨文件语义解析 pip install -e ".[semantic]" # 开发(pytest + jedi) pip install -e ".[dev]" ``` 这会安装一个运行 stdio 服务器的 `py-ast-mcp` 命令行脚本。使用 `python -m py_ast_mcp` 也可以。 ## 工具 所有 `path` 参数都接受绝对路径或相对于服务器工作目录的相对路径。 ### 结构 | 工具 | 参数 | 描述 | | --- | --- | --- | | `analyze_file` | `path` | 每个符号的高级摘要:类(带有 dataclass/enum/Protocol/TypedDict 分类)、函数、async 函数、方法和模块级赋值。 | | `list_functions` | `path` | 所有函数和方法,包含完整的签名(注解、默认值、返回类型、装饰器、async 标志)以及行范围。 | | `get_function_body` | `path`, `name` | 函数或方法的完整带行号源码。支持 `Class.method` 和点号嵌套路径;在同一个文件中找不到时,回退到基类中查找。 | | `list_methods` | `path`, `type` | 类的所有方法:声明的方法、property、class/static 方法、类属性,以及从同一个文件中定义的基类继承的成员。 | | `get_type_definition` | `path`, `name` | 提取类 / `TypeAlias` / `Enum` / `Protocol` / `TypedDict` / `NamedTuple` 定义及其成员和源码。 | | `list_declarations` | `path` | 带有注解或推断类型的模块级赋值。 | | `list_exports` | `path` | 公共 API:存在 `__all__` 时遵循其定义,否则导出非下划线开头的模块级名称;标记重导出的导入以及 `__all__` 中未定义的名称。 | | `list_imports` | `path` | 所有导入,包含绑定的名称、模块路径、相对导入层级和别名,并按标准库 / 第三方 / 相对路径进行分组。 | | `find_usages` | `path`, `identifier`, `context` (默认 `1`) | 包含周围源码行的每次出现:读取、赋值、参数、属性访问、导入、`global`/`nonlocal`。当安装了 `jedi` 时,会添加项目范围内的引用。 | ### 调用分析 | 工具 | 参数 | 描述 | | --- | --- | --- | | `call_graph` | `path`, `function`, `direction` (`TD`/`TB`/`LR`/`RL`/`BT`, 默认 `TD`), `include_external` (默认 `false`), `scope` (`file`/`package`, 默认 `file`) | 调用关系的 Mermaid 流程图。`function` 将图根植于某一个函数;`scope="package"` 会遍历所在目录下的每一个 `.py` 文件。还会以文本形式列出边以及没有边的函数。 | | `get_callers` | `path`, `function`, `scope` (`file`/`package`, 默认 `file`) | 反向调用图:带有调用点的直接调用者、间接调用者,以及能到达该函数的入口点。 | ### 质量 | 工具 | 参数 | 描述 | | --- | --- | --- | | `code_complexity` | `path`, `function` | 每个函数的圈复杂度及 A-F 等级。统计 `if`/`elif`、`for`、`while`、`for`/`try` 的 `else`、`except`、推导式及其 `if` 子句、布尔运算符、三元表达式,以及除了不可辩驳的模式(`case _:` 或纯捕获——这是直通,不是分支)之外的 `match` case。`with` 和 `assert` 不计入(两者都不分支),嵌套的 `def` 会单独计分,而不是合并到父级中——因此分数与 `radon cc --no-assert` 完全匹配。传入 `function` 可获取决策点细分。 | | `code_smells` | `path`, `function` | 过长的函数、过深的嵌套、上帝类、过多的参数、可变默认参数、可变的 `@dataclass` 字段默认值、裸 `except:`、被遮蔽的内建函数、过高的复杂度。按严重程度分组,并带有建议的修复方案。 | | `find_errors` | `path`, `function` | Python 特有的风险:裸/宽泛的 `except`、`except: pass`、被前面的子句设为不可达的 `except` 子句、可变默认参数、可变的 `@dataclass` 字段默认值(导入时引发 `ValueError`)、未等待的协程调用(尽力而为)、使用 `assert` 进行运行时验证、在循环*或推导式*变量上的闭包延迟绑定、针对 `None`/`True`/`False` 的 `==`/`!=`、对字面量使用 `is`(镜像了 CPython 自己的 `SyntaxWarning`)、从不使用 `self` 的方法。 | | `dead_code` | `path`, `include_tests` (默认 `false`) | 跨目录的未被引用的私有和模块级符号。如果 `path` 是一个文件,则会扫描其所在目录,以便发现跨文件的引用。 | | `find_implementations` | `path`, `interface` | 实现 Protocol/ABC 的类——显式(直接或间接基类)和结构性(方法集匹配),以及近似匹配。参数名为 `interface` 而非 `protocol`,因此同样的调用也可用于 `ts-ast-mcp`。 | ### 文档与多文件 | 工具 | 参数 | 描述 | | --- | --- | --- | | `get_doc` | `path`, `name` | 为函数、`Class.method`、类、模块(`name="module"`)或已记录的模块常量提取 Docstring。Google 和 NumPy 风格被解析为 摘要 / 描述 / 参数 / 返回值 / 生成值 / 引发异常。 | | `analyze_package` | `path`, `include_tests` (默认 `false`) | 每一个 `.py` 文件的目录级摘要:行数统计、类/函数计数、docstring、逐文件的符号列表,以及任何无法解析的文件。 | | `diff_ast` | `old_path`, `new_path` | 结构性 diff:添加 / 删除 / 修改的函数、方法、类、基类、装饰器、模块变量、导入和 `__all__`。签名级别,并附带“潜在破坏性”的摘要。 | | `find_node_at_position` | `path`, `line` (从 1 开始), `column` (从 0 开始) | 位于光标位置处的 AST 节点、完整节点链以及封闭作用域链。可用时添加 jedi 解析的定义。 | ## 配置 ### Claude Code 在你的项目根目录下添加一个 `.mcp.json`(此文件会被自动读取): ``` { "mcpServers": { "py-ast": { "command": "py-ast-mcp", "args": [] } } } ``` 如果你安装在了虚拟环境中,请显式指定路径,以便服务器不依赖于你 shell 的 `PATH`: ``` { "mcpServers": { "py-ast": { "command": "/absolute/path/to/venv/bin/python", "args": ["-m", "py_ast_mcp"] } } } ``` 或者使用 `uv`,直接从检出的代码库运行而无需安装: ``` { "mcpServers": { "py-ast": { "command": "uvx", "args": ["--from", "/absolute/path/to/py-ast-mcp", "py-ast-mcp"] } } } ``` 你也可以从 CLI 注册它: ``` claude mcp add py-ast -- py-ast-mcp ``` ### Claude Desktop 编辑 `claude_desktop_config.json`: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` - Linux: `~/.config/Claude/claude_desktop_config.json` ``` { "mcpServers": { "py-ast": { "command": "py-ast-mcp", "args": [] } } } ``` 因为 Claude Desktop 不会继承你的 shell 环境,所以使用绝对路径通常更安全: ``` { "mcpServers": { "py-ast": { "command": "/absolute/path/to/venv/bin/py-ast-mcp", "args": [] } } } ``` 编辑文件后重启 Claude Desktop。 ### 直接从 GitHub 运行 无需检出,无需安装 —— `uv` 会拉取并运行它: ``` { "mcpServers": { "py-ast": { "command": "uvx", "args": ["--from", "git+https://github.com/gclluch/py-ast-mcp", "py-ast-mcp"] } } } ``` 在参数中添加 `--with jedi` 以启用跨文件语义解析。 ## 示例输出 针对一个小型包的 `call_graph`: ``` # call_graph (package) samplepkg/core.py [16 functions, 11 edges] ## mermaid ```mermaid flowchart TD n2_core_Engine_store["core.Engine.store L57"] n9_util_normalize["util.normalize L4"] n2_core_Engine_store --> n9_util_normalize ``` ## 边 - Engine.store -> validate @L58 - Engine.store -> normalize @L59 - run_pipeline -> Engine.store @L70 ``` `code_complexity` on the standard library's `argparse`: ``` # complexity /usr/lib/python3.11/argparse.py [138 functions, module total 350] average 3.7 max 46 functions over 10: 10 function lines cc rank len depth ArgumentParser._parse_known_args L1930-2187 46 F 258 6 HelpFormatter._format_actions_usage L406-519 28 D 114 5 ``` ## 开发 ```bash pip install -e ".[dev]" # 单元测试 pytest # 端到端:生成真实服务器并使用手写的 # JSON-RPC client 通过 stdio 驱动它(initialize -> tools/list -> tools/call) python scripts/stdio_smoke_test.py ``` 布局: ``` src/py_ast_mcp/ server.py MCP tool registration (FastMCP, stdio transport) parse.py shared parse + cache by path/mtime/size, AST navigation format.py shared output formatting analyze.py analyze_file, analyze_package, find_node_at_position functions.py list_functions, get_function_body, list_methods types.py get_type_definition, list_declarations imports.py list_imports, list_exports usages.py find_usages callgraph.py call_graph, get_callers complexity.py code_complexity smells.py code_smells errors.py find_errors deadcode.py dead_code protocols.py find_implementations doc.py get_doc diff.py diff_ast jedi_support.py optional cross-file resolution ``` ## 已知限制 这些是对语法树的启发式判断,而不是类型检查器: - **服务器使用自身解释器的语法进行解析。** `ast` 只能读取正在运行的 Python 能理解的语法,因此即使文件是有效的,运行在 3.11 上的服务器也会将 `PEP 695` 代码(`type X = ...`、`def f[T]()`)报告为语法错误。**无论目标项目针对哪个版本,请在你拥有的最新版 Python 上运行服务器**——当可能是解释器的原因时,解析错误会说明情况。 - **调用解析是基于名称的。** `obj.method()` 通过方法名匹配;当作用域内的多个类定义了相同的名称时,以第一个为准。`self.method()` 先在封闭类内解析,然后跨文件中的类解析。 - **跨模块调用边** 在 `scope="package"` 中仅通过 `import` 语句进行解析。动态分发、工厂和回调不会被追踪。 - **`dead_code` 按名称而非作用域匹配。** `getattr`、插件注册表、入口点以及来自扫描范围之外的重导出会产生误报;公共符号会作为较低的置信度单独报告。更大的风险在另一方面:任何属性访问或与符号名称相同的字符串字面量都会将其标记为活跃,因此该工具**存在漏报**。请将命中的结果视为需要确认的候选项。 - **`jedi` 解析范围仅限于检测到的项目根目录**,通过向上查找 `.` / `setup.py` / `requirements.txt` 来确定。该根目录之外的文件对 `find_usages` 的跨文件部分是不可见的。 - **`unawaited-coroutine` 尽力而为。** 它会标记对在同一个文件中声明的 `async def` 函数的调用,这些调用既没有被等待,也没有被包裹在可识别的 `asyncio` 助手中。 - **继承的成员** 仅针对在同一个文件中定义的基类进行解析;来自其他模块的基类会被列为未解析。 - **`list_declarations` 类型推断基于字面量形状**,而不是真正的推断引擎:它报告的是阅读者从右侧推断出的内容。 ## 许可证 MIT
标签:MCP, Python, SOC Prime, 代码审查, 开发工具, 无后门, 逆向工具, 错误基检测, 静态代码分析