axi-cli 0.0.4__tar.gz → 0.0.7__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 (31) hide show
  1. axi_cli-0.0.7/CHANGELOG.md +101 -0
  2. {axi_cli-0.0.4 → axi_cli-0.0.7}/PKG-INFO +47 -8
  3. {axi_cli-0.0.4 → axi_cli-0.0.7}/README.md +43 -7
  4. {axi_cli-0.0.4 → axi_cli-0.0.7}/pyproject.toml +9 -1
  5. axi_cli-0.0.7/skills/axi-creator/SKILL.md +171 -0
  6. axi_cli-0.0.7/skills/axi-use/SKILL.md +117 -0
  7. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/__init__.py +2 -5
  8. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/cli.py +217 -53
  9. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/config.py +38 -1
  10. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/daemon/client.py +40 -16
  11. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/daemon/protocol.py +8 -4
  12. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/daemon/server.py +73 -27
  13. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/executor.py +18 -3
  14. axi_cli-0.0.7/src/axi/mcp_serve.py +335 -0
  15. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/models.py +20 -2
  16. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/providers/mcp.py +26 -66
  17. axi_cli-0.0.7/src/axi/providers/native.py +238 -0
  18. axi_cli-0.0.7/src/axi/registry.py +155 -0
  19. axi_cli-0.0.7/src/axi/search/__init__.py +6 -0
  20. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/search/cache.py +5 -2
  21. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/search/hybrid.py +2 -2
  22. axi_cli-0.0.4/CHANGELOG.md +0 -47
  23. axi_cli-0.0.4/src/axi/providers/native.py +0 -66
  24. axi_cli-0.0.4/src/axi/registry.py +0 -111
  25. axi_cli-0.0.4/src/axi/search/__init__.py +0 -7
  26. {axi_cli-0.0.4 → axi_cli-0.0.7}/.gitignore +0 -0
  27. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/providers/__init__.py +0 -0
  28. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/search/bm25.py +0 -0
  29. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/search/embedding.py +0 -0
  30. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/search/regex.py +0 -0
  31. {axi_cli-0.0.4 → axi_cli-0.0.7}/src/axi/search/tokenize.py +0 -0
@@ -0,0 +1,101 @@
1
+ # Changelog
2
+
3
+ ## [0.0.7] - 2026-07-04
4
+
5
+ ### Added
6
+ - `axi doctor` 自检命令:一次性检查配置、daemon、MCP server 连接、embedding、native 工具来源;配置了 MCP server 时拉起 daemon 验证连接,有问题以非零码退出并在 `issues` 里给出可执行的下一步。`native_tools.from_entry_points` 列出所有经 Python entry_points 自动注入的工具及来源模块,便于审计
7
+ - daemon 请求超时可配置:`daemon.requestTimeout`(默认 120 秒,原硬编码 30 秒)+ `AXI_REQUEST_TIMEOUT` 环境变量(优先),超时信息指向调高方式
8
+ - native 工具加载记录来源日志(模块 / entry_point + server + 工具名),便于审计自动注入
9
+
10
+ ### Fixed
11
+ - 并发多个 axi 进程同时发现 daemon 未运行会各自拉起、互相 unlink socket / 覆盖 PID:改用启动文件锁串行化,只有持锁进程 spawn,其余复查后复用
12
+ - CLI `--key value` 解析忽略工具 schema,把 string 字段的 `false` / `42` / `null` 误转成布尔 / 数字 / 空值:现在按目标字段类型决定是否 JSON 解析(string 字段保留原文,其余照常解析)
13
+
14
+ ### Changed
15
+ - **Breaking**:搜索结果移除 `score` 字段——native 与 MCP 来自两套独立索引、各自归一化,跨源分数不可比,暴露只会误导 Agent;结果按相关性降序返回,顺序本身即信号
16
+ - 搜索栈(bm25s / jieba / numpy)延迟到首次 `search` / `grep` 才 import,`describe` / `run` / `list` 不再付这笔冷启动成本;各引擎按工具集版本号独立重建
17
+ - embedding 缓存从当前目录 `.axi/cache/embeddings.json` 移到 `~/.axi/cache/<配置路径哈希>.json`,按配置隔离、不再依赖 cwd
18
+ - `allowed_types` / `server_tool_counts` 抽为公共函数,消除 cli / daemon / registry 间的重复实现
19
+
20
+ ## [0.0.6] - 2026-07-03
21
+
22
+ ### Added
23
+ - `axi mcp` 命令:将 axi 工具导出为 MCP server,实现"写一次,CLI + MCP 两用"
24
+ - `--transport stdio|http`:stdio(默认,由 MCP 客户端拉起)或 streamable HTTP(`--port`/`--host`)
25
+ - `--server a,b`:只暴露指定 server 的工具(逗号分隔多个)
26
+ - 平铺模式:每个工具注册为独立 MCP tool(单 server 用裸名,多 server 加 `server__` 前缀);元工具模式:只暴露 `search`/`grep`/`describe`/`run` 四个工具,渐进式披露
27
+ - 缺省形态自动判定:指定 `--server` → 平铺,全量 → 元工具;`--flat`/`--meta` 强制覆盖
28
+ - `axi daemon status` 输出新增 `config_path` 字段
29
+ - 执行前参数校验:native 工具按 Pydantic 参数模型校验并转换(字符串数字 → int、嵌套 dict → BaseModel 实例、多余字段报错);MCP 工具由 daemon 按 `input_schema` 做 jsonschema 校验。非法参数返回 `{"status":"error","error":"Invalid params: ..."}`,不再触达工具本体
30
+
31
+ ### Fixed
32
+ - MCP 工具返回 `isError` 结果时被包成 `{"status":"success"}` 的问题:现在正确落进 error 信封,且工具级错误不再触发无意义的重连重试
33
+ - `axi mcp` 长驻进程在 daemon idle 超时后 MCP 工具永久不可用:执行路径统一走 `daemon_request`(内含自动重启)
34
+ - 两个模块注册同名 native 工具时相互遮蔽(静默执行错误的函数):函数/模型注册键改为 full_name
35
+ - `**kwargs` 签名的 `@tool` 函数在参数校验下不可调用:`*args`/`**kwargs` 不再进参数模型,有 `**kwargs` 时放行多余字段
36
+ - 参数校验与 CLI 类型猜测的冲突:native 侧启用 `coerce_numbers_to_str`(`--name 42` → "42"),daemon 侧按 schema 顶层类型做轻量转换(`--port 08080` → 8080)
37
+ - schema 含不可解析 `$ref` 时工具不可调用:校验兜底放宽为除 ValidationError 外一律放行
38
+ - 平铺模式下清洗后同名的工具静默覆盖:按注册顺序加 `_2`/`_3` 后缀
39
+ - `axi mcp --server` 把"配置了但没连上"的 MCP server 误报为"不存在":现在区分两种情况并指向 daemon 日志
40
+ - cwd 存在 `./axi.json` 但未设 `AXI_CONFIG` 时输出迁移提示(0.0.5 → 0.0.6 升级不再静默丢工具)
41
+ - uvicorn / starlette / httpx 从传递依赖转为显式声明
42
+
43
+ ### Changed
44
+ - **Breaking**:配置文件不再从当前目录读取 `./axi.json`;默认路径改为 `~/.axi/axi.json`,项目级配置通过 `AXI_CONFIG` 环境变量指定
45
+ - **Breaking**:daemon 按配置文件隔离,每份 `AXI_CONFIG` 对应独立 daemon;socket/pid/log 移至 `~/.axi/daemons/<配置路径哈希>.*`(原 `~/.axi/daemon.sock` 废弃)
46
+
47
+ ## [0.0.5] - 2026-04-23
48
+
49
+ ### Added
50
+ - Python entry_points 自动发现原生工具:扩展包在 `pyproject.toml` 里声明 `[project.entry-points."axi.native_tools"]` 即可被 axi 自动发现,无需 `axi.json` 登记
51
+ - `docs/configuration.md` 补充 entry_points 机制说明与合并规则(去重、同名 server collision、加载失败降级)
52
+
53
+ ### Changed
54
+ - `load_native_tool_modules` 从 `providers/mcp.py` 搬到 `providers/native.py`,职责回归原生工具 Provider
55
+ - 模块加载失败时不再占位,避免对后续合法包误报 server 名 collision
56
+
57
+ ## [0.0.4] - 2026-04-20
58
+
59
+ ### Added
60
+ - `AXI_CONFIG` 环境变量:自定义 `axi.json` 配置文件路径
61
+ - GitHub Actions workflow:打 `v*` tag 时自动通过 trusted publishing 发布到 PyPI
62
+ - `pyproject.toml` 新增 sdist 打包排除规则(`.github/`、`docs/`、`tests/`、`uv.lock` 等)
63
+ - README 补充 daemon 自动管理、生命周期、状态输出、文件位置等完整说明
64
+ - `docs/architecture.md` 补充 daemon 启动流程、idle watchdog、关闭流程、通信协议章节
65
+
66
+ ### Changed
67
+ - 包名由 `axi` 重命名为 `axi-cli`(PyPI 发布名)
68
+ - 安装方式从 `uv pip install -e .` 改为 `uv sync`
69
+
70
+ ## [0.0.3] - 2026-04-01
71
+
72
+ ### Added
73
+ - BM25 关键词搜索(bm25s + jieba 分词,支持中英文)
74
+ - Embedding 语义搜索(Jina/OpenAI,通过 LangChain 接入,可选)
75
+ - 混合搜索(BM25 + Embedding,RRF 融合排序,分数归一化 0-1)
76
+ - `axi grep` 命令:独立的正则表达式搜索
77
+ - `axi daemon status` 输出 JSON 格式状态信息(PID、运行时长、工具统计)
78
+ - Daemon idle 超时自动关闭机制
79
+ - `config.py` 统一配置模块(`axi.json` 解析 + Pydantic 模型)
80
+ - `docs/configuration.md` 配置参考文档
81
+ - Embedding 文件缓存(`.axi/` 目录)
82
+ - MCP 工具调用失败时自动重连一次
83
+
84
+ ### Changed
85
+ - `axi search` 从子串匹配改为 BM25 混合搜索
86
+ - 搜索结果新增 `score` 字段
87
+ - `ToolResolveError` 拆分为 `ToolNotFoundError` 和 `AmbiguousToolError`
88
+ - Daemon 启动不再需要 `--config` 参数,统一从 `axi.json` 读取
89
+ - MCP/原生工具配置统一通过 `config.py` 管理,移除分散的配置解析逻辑
90
+
91
+ ## [0.0.2] - 2026-03-30
92
+
93
+ ### Changed
94
+ - 原生工具 schema 提取改用 Pydantic `create_model`,支持 Literal、Optional、list[T]、嵌套 BaseModel、Annotated[..., Field()] 等高级类型
95
+ - 版本号从 0.1.0 调整为 0.0.2,反映项目早期阶段
96
+ - 代码格式化统一(ruff format)
97
+
98
+ ## [0.1.0] - 2026-03-29
99
+
100
+ ### Added
101
+ - 初始版本:原生工具注册、MCP 对接、daemon 模式
@@ -1,15 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axi-cli
3
- Version: 0.0.4
3
+ Version: 0.0.7
4
4
  Summary: Agent eXecution Interface - unified tool layer for AI Agents
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: bm25s>=0.3.3
7
7
  Requires-Dist: jieba>=0.42.1
8
+ Requires-Dist: jsonschema>=4.26.0
8
9
  Requires-Dist: langchain-community>=0.4.1
9
10
  Requires-Dist: langchain-openai>=1.1.12
10
11
  Requires-Dist: mcp>=1.26.0
11
12
  Requires-Dist: pydantic>=2.12.5
13
+ Requires-Dist: starlette>=1.0.0
12
14
  Requires-Dist: typer>=0.24.1
15
+ Requires-Dist: uvicorn>=0.42.0
13
16
  Description-Content-Type: text/markdown
14
17
 
15
18
  # axi
@@ -48,7 +51,7 @@ axi run jina/jina_search --query "hello world" --count 3
48
51
 
49
52
  ## 配置
50
53
 
51
- 在项目根目录创建 `axi.json`(参考 `axi.json.example`):
54
+ 配置文件默认为 `~/.axi/axi.json`;项目级配置通过 `AXI_CONFIG` 环境变量指定(如 `export AXI_CONFIG=/path/to/project/axi.json`,或写进 direnv 的 `.envrc`)。每份配置对应一个独立的 daemon,多项目互不干扰。格式参考 `axi.json.example`:
52
55
 
53
56
  ```json
54
57
  {
@@ -93,6 +96,8 @@ axi run jina/jina_search --query "hello world" --count 3
93
96
  | `axi grep <pattern>` | 正则表达式搜索工具(支持 `--limit/-l`) |
94
97
  | `axi describe <tool>` | 查看工具完整 schema |
95
98
  | `axi run <tool> --key value` | 执行工具(也支持 `-j '{...}'` 传 JSON) |
99
+ | `axi mcp` | 将 axi 工具导出为 MCP server(stdio / HTTP) |
100
+ | `axi doctor` | 自检:配置、daemon、MCP 连接、embedding、native 工具来源;有问题非零退出 |
96
101
  | `axi daemon start` | 手动启动 daemon(通常无需手动,CLI 会自动拉起) |
97
102
  | `axi daemon status` | 查看 daemon 状态(PID、运行时长、空闲时长、工具数量等) |
98
103
  | `axi daemon stop` | 手动停止 daemon |
@@ -127,6 +132,37 @@ search = tool("jina/jina_search")
127
132
  results = search(query="python async", count=5)
128
133
  ```
129
134
 
135
+ ## 导出为 MCP server
136
+
137
+ 用 axi 封装的工具写一次,既能 CLI 调用,也能一条命令变成 MCP server 给任何 MCP 客户端(Claude Desktop、Claude Code 等)使用:
138
+
139
+ ```bash
140
+ axi mcp # 全部工具,元工具模式(search/grep/describe/run)
141
+ axi mcp --server mytools # 只暴露一组工具,平铺模式
142
+ axi mcp --server a,b --transport http --port 8321 # HTTP 长驻服务
143
+ ```
144
+
145
+ 两种暴露形态(互斥):
146
+
147
+ - **平铺**(指定 `--server` 时默认)— 每个工具注册为独立 MCP tool,对端直接看到并调用。单 server 用裸工具名,多 server 加 `server__` 前缀
148
+ - **元工具**(全量导出时默认)— 只暴露 `search` / `grep` / `describe` / `run` 四个工具,对端以渐进式披露的方式发现和调用,上下文占用恒定,工具再多也不吃 context
149
+
150
+ `--flat` / `--meta` 可强制覆盖默认判定。
151
+
152
+ MCP 客户端配置示例(stdio 由客户端拉起,用 `AXI_CONFIG` 锚定项目):
153
+
154
+ ```json
155
+ {
156
+ "mcpServers": {
157
+ "my-tools": {
158
+ "command": "axi",
159
+ "args": ["mcp", "--server", "mytools"],
160
+ "env": { "AXI_CONFIG": "/path/to/project/axi.json" }
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
130
166
  ## 搜索
131
167
 
132
168
  axi 提供两种搜索方式:
@@ -134,7 +170,7 @@ axi 提供两种搜索方式:
134
170
  - **`axi search`** — 混合搜索,默认 BM25 关键词搜索(bm25s + jieba 分词,支持中英文)。配置 `search.embedding` 后启用 BM25 + Embedding 混合搜索(RRF 融合排序,分数归一化 0-1)
135
171
  - **`axi grep`** — 正则表达式搜索,按工具名和描述匹配
136
172
 
137
- 搜索结果包含 `score` 字段,便于 Agent 判断相关性。
173
+ 结果按相关性排序返回,顺序本身即信号(不含分数:native MCP 来自两套索引、分数不可比)。
138
174
 
139
175
  ## Daemon 模式
140
176
 
@@ -154,7 +190,7 @@ axi CLI ──(Unix socket)──> daemon ──(stdio)──> MCP server A
154
190
  | 阶段 | 行为 |
155
191
  |------|------|
156
192
  | **启动** | 加载 `axi.json` → 连接所有 MCP server → 创建 Unix socket → 写入 PID 文件 → 启动 idle watchdog |
157
- | **运行** | 通过 `~/.axi/daemon.sock` 监听请求,路由到对应 MCP server 执行 |
193
+ | **运行** | 通过 `~/.axi/daemons/<hash>.sock` 监听请求,路由到对应 MCP server 执行 |
158
194
  | **空闲关闭** | 默认 30 分钟无活动自动关闭(每 60 秒检查一次;`status` 和 `shutdown` 请求不重置计时器) |
159
195
  | **手动关闭** | `axi daemon stop` 发送关闭指令,daemon 优雅退出 |
160
196
  | **信号关闭** | 收到 SIGTERM / SIGINT 时优雅关闭(清理 socket 和 PID 文件) |
@@ -179,6 +215,7 @@ axi daemon stop # 手动停止
179
215
  {
180
216
  "status": "running",
181
217
  "pid": 12345,
218
+ "config_path": "/path/to/project/axi.json",
182
219
  "uptime_seconds": 1800,
183
220
  "idle_seconds": 120,
184
221
  "idle_timeout_seconds": 1800,
@@ -190,13 +227,15 @@ axi daemon stop # 手动停止
190
227
 
191
228
  ### 文件位置
192
229
 
193
- daemon 运行时文件位于 `~/.axi/`:
230
+ daemon 按配置文件隔离:每份 `AXI_CONFIG` 对应一个独立 daemon,运行时文件位于 `~/.axi/daemons/`,文件名为配置文件绝对路径的哈希:
194
231
 
195
232
  | 文件 | 说明 |
196
233
  |------|------|
197
- | `daemon.sock` | Unix socket,CLI 与 daemon 的通信通道 |
198
- | `daemon.pid` | daemon 进程 PID,用于检测是否存活 |
199
- | `daemon.log` | daemon 启动和错误日志 |
234
+ | `<hash>.sock` | Unix socket,CLI 与 daemon 的通信通道 |
235
+ | `<hash>.pid` | daemon 进程 PID,用于检测是否存活 |
236
+ | `<hash>.log` | daemon 启动和错误日志 |
237
+
238
+ `axi daemon status` 的 `config_path` 字段显示当前 daemon 服务的配置文件。
200
239
 
201
240
  ## 技术栈
202
241
 
@@ -34,7 +34,7 @@ axi run jina/jina_search --query "hello world" --count 3
34
34
 
35
35
  ## 配置
36
36
 
37
- 在项目根目录创建 `axi.json`(参考 `axi.json.example`):
37
+ 配置文件默认为 `~/.axi/axi.json`;项目级配置通过 `AXI_CONFIG` 环境变量指定(如 `export AXI_CONFIG=/path/to/project/axi.json`,或写进 direnv 的 `.envrc`)。每份配置对应一个独立的 daemon,多项目互不干扰。格式参考 `axi.json.example`:
38
38
 
39
39
  ```json
40
40
  {
@@ -79,6 +79,8 @@ axi run jina/jina_search --query "hello world" --count 3
79
79
  | `axi grep <pattern>` | 正则表达式搜索工具(支持 `--limit/-l`) |
80
80
  | `axi describe <tool>` | 查看工具完整 schema |
81
81
  | `axi run <tool> --key value` | 执行工具(也支持 `-j '{...}'` 传 JSON) |
82
+ | `axi mcp` | 将 axi 工具导出为 MCP server(stdio / HTTP) |
83
+ | `axi doctor` | 自检:配置、daemon、MCP 连接、embedding、native 工具来源;有问题非零退出 |
82
84
  | `axi daemon start` | 手动启动 daemon(通常无需手动,CLI 会自动拉起) |
83
85
  | `axi daemon status` | 查看 daemon 状态(PID、运行时长、空闲时长、工具数量等) |
84
86
  | `axi daemon stop` | 手动停止 daemon |
@@ -113,6 +115,37 @@ search = tool("jina/jina_search")
113
115
  results = search(query="python async", count=5)
114
116
  ```
115
117
 
118
+ ## 导出为 MCP server
119
+
120
+ 用 axi 封装的工具写一次,既能 CLI 调用,也能一条命令变成 MCP server 给任何 MCP 客户端(Claude Desktop、Claude Code 等)使用:
121
+
122
+ ```bash
123
+ axi mcp # 全部工具,元工具模式(search/grep/describe/run)
124
+ axi mcp --server mytools # 只暴露一组工具,平铺模式
125
+ axi mcp --server a,b --transport http --port 8321 # HTTP 长驻服务
126
+ ```
127
+
128
+ 两种暴露形态(互斥):
129
+
130
+ - **平铺**(指定 `--server` 时默认)— 每个工具注册为独立 MCP tool,对端直接看到并调用。单 server 用裸工具名,多 server 加 `server__` 前缀
131
+ - **元工具**(全量导出时默认)— 只暴露 `search` / `grep` / `describe` / `run` 四个工具,对端以渐进式披露的方式发现和调用,上下文占用恒定,工具再多也不吃 context
132
+
133
+ `--flat` / `--meta` 可强制覆盖默认判定。
134
+
135
+ MCP 客户端配置示例(stdio 由客户端拉起,用 `AXI_CONFIG` 锚定项目):
136
+
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "my-tools": {
141
+ "command": "axi",
142
+ "args": ["mcp", "--server", "mytools"],
143
+ "env": { "AXI_CONFIG": "/path/to/project/axi.json" }
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
116
149
  ## 搜索
117
150
 
118
151
  axi 提供两种搜索方式:
@@ -120,7 +153,7 @@ axi 提供两种搜索方式:
120
153
  - **`axi search`** — 混合搜索,默认 BM25 关键词搜索(bm25s + jieba 分词,支持中英文)。配置 `search.embedding` 后启用 BM25 + Embedding 混合搜索(RRF 融合排序,分数归一化 0-1)
121
154
  - **`axi grep`** — 正则表达式搜索,按工具名和描述匹配
122
155
 
123
- 搜索结果包含 `score` 字段,便于 Agent 判断相关性。
156
+ 结果按相关性排序返回,顺序本身即信号(不含分数:native MCP 来自两套索引、分数不可比)。
124
157
 
125
158
  ## Daemon 模式
126
159
 
@@ -140,7 +173,7 @@ axi CLI ──(Unix socket)──> daemon ──(stdio)──> MCP server A
140
173
  | 阶段 | 行为 |
141
174
  |------|------|
142
175
  | **启动** | 加载 `axi.json` → 连接所有 MCP server → 创建 Unix socket → 写入 PID 文件 → 启动 idle watchdog |
143
- | **运行** | 通过 `~/.axi/daemon.sock` 监听请求,路由到对应 MCP server 执行 |
176
+ | **运行** | 通过 `~/.axi/daemons/<hash>.sock` 监听请求,路由到对应 MCP server 执行 |
144
177
  | **空闲关闭** | 默认 30 分钟无活动自动关闭(每 60 秒检查一次;`status` 和 `shutdown` 请求不重置计时器) |
145
178
  | **手动关闭** | `axi daemon stop` 发送关闭指令,daemon 优雅退出 |
146
179
  | **信号关闭** | 收到 SIGTERM / SIGINT 时优雅关闭(清理 socket 和 PID 文件) |
@@ -165,6 +198,7 @@ axi daemon stop # 手动停止
165
198
  {
166
199
  "status": "running",
167
200
  "pid": 12345,
201
+ "config_path": "/path/to/project/axi.json",
168
202
  "uptime_seconds": 1800,
169
203
  "idle_seconds": 120,
170
204
  "idle_timeout_seconds": 1800,
@@ -176,13 +210,15 @@ axi daemon stop # 手动停止
176
210
 
177
211
  ### 文件位置
178
212
 
179
- daemon 运行时文件位于 `~/.axi/`:
213
+ daemon 按配置文件隔离:每份 `AXI_CONFIG` 对应一个独立 daemon,运行时文件位于 `~/.axi/daemons/`,文件名为配置文件绝对路径的哈希:
180
214
 
181
215
  | 文件 | 说明 |
182
216
  |------|------|
183
- | `daemon.sock` | Unix socket,CLI 与 daemon 的通信通道 |
184
- | `daemon.pid` | daemon 进程 PID,用于检测是否存活 |
185
- | `daemon.log` | daemon 启动和错误日志 |
217
+ | `<hash>.sock` | Unix socket,CLI 与 daemon 的通信通道 |
218
+ | `<hash>.pid` | daemon 进程 PID,用于检测是否存活 |
219
+ | `<hash>.log` | daemon 启动和错误日志 |
220
+
221
+ `axi daemon status` 的 `config_path` 字段显示当前 daemon 服务的配置文件。
186
222
 
187
223
  ## 技术栈
188
224
 
@@ -1,17 +1,20 @@
1
1
  [project]
2
2
  name = "axi-cli"
3
- version = "0.0.4"
3
+ version = "0.0.7"
4
4
  description = "Agent eXecution Interface - unified tool layer for AI Agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
7
7
  dependencies = [
8
8
  "bm25s>=0.3.3",
9
9
  "jieba>=0.42.1",
10
+ "jsonschema>=4.26.0",
10
11
  "langchain-community>=0.4.1",
11
12
  "langchain-openai>=1.1.12",
12
13
  "mcp>=1.26.0",
13
14
  "pydantic>=2.12.5",
15
+ "starlette>=1.0.0",
14
16
  "typer>=0.24.1",
17
+ "uvicorn>=0.42.0",
15
18
  ]
16
19
 
17
20
  [project.scripts]
@@ -37,7 +40,12 @@ exclude = [
37
40
 
38
41
  [dependency-groups]
39
42
  dev = [
43
+ "httpx>=0.28.1",
40
44
  "pytest>=9.0.2",
41
45
  "pytest-asyncio>=1.3.0",
42
46
  "ruff>=0.15.8",
43
47
  ]
48
+
49
+ [tool.pytest.ini_options]
50
+ # 只从 tests/ 收集,隔离本地 scratch 的 test/(已 gitignore,不随包分发)
51
+ testpaths = ["tests"]
@@ -0,0 +1,171 @@
1
+ ---
2
+ name: axi-creator
3
+ description: Expose an existing capability (Python function, HTTP API, third-party SDK, or MCP server) as an axi tool, or export registered axi tools as an MCP server. Use when the user wants to "add a tool to axi", "wrap an API for axi", "register a function", "make X callable via `axi run`", "expose these tools over MCP / hook them to Claude Desktop" (`axi mcp`), or needs to build a new `@tool` / configure `mcpServers` / publish a native-tool package. Covers decorator usage, schema quality via type hints, registration via `axi.json` vs `pyproject.toml` entry_points, MCP export, and end-to-end self-verification. Use `axi-use` instead when the task is to call existing tools rather than register new ones.
4
+ ---
5
+
6
+ # axi — 工具接入手册
7
+
8
+ 把一个能力变成 axi 工具,**先选路径,再写注册,最后自测**。
9
+
10
+ ## 选路径(按起点决定)
11
+
12
+ | 起点 | 走哪条 | 工作量 |
13
+ |---|---|---|
14
+ | 已有 MCP server(能跑命令或暴露 HTTP 端点) | **Path B:改 `axi.json` 的 `mcpServers`** | 1 行 JSON |
15
+ | 有 Python 函数 / 想包装 HTTP API 或第三方 SDK | **Path A:写 `@tool` 装饰器** | 函数 + 几行注册 |
16
+ | 要把工具以 pip 包分发给别人 | Path A + entry_points | 加 1 段 pyproject.toml |
17
+
18
+ ---
19
+
20
+ ## Path A:`@tool` 装饰器
21
+
22
+ 最小示例(放进任意 Python 文件,比如 `my_tools.py`):
23
+
24
+ ```python
25
+ from axi import tool
26
+
27
+ @tool(
28
+ name="query_orders", # 可选,缺省用函数名
29
+ description="按区域查询订单", # 可选,缺省用 docstring
30
+ output_example={"orders": [...], "total": 1}, # 可选,但强烈建议写
31
+ )
32
+ def query_orders(region: str, limit: int = 10) -> dict:
33
+ return {"orders": [...], "total": 1}
34
+ ```
35
+
36
+ **就这一步就够了——函数被 import 时自动注册。** `input_schema` 由 axi 从 type hints 提取(用 Pydantic `create_model`),不用自己写 JSON Schema。
37
+
38
+ ### 写出好 schema(决定 Agent 用起来的难度)
39
+
40
+ axi 支持 Pydantic 能支持的一切。**越严格,Agent 传错参数的概率越低**:
41
+
42
+ ```python
43
+ from typing import Annotated, Literal
44
+ from pydantic import BaseModel, Field
45
+
46
+ class TimeRange(BaseModel):
47
+ start: str
48
+ end: str
49
+
50
+ @tool(description="查询设备的 CAN 数据")
51
+ def get_can_data(
52
+ device_id: Annotated[str, Field(description="设备唯一 ID", min_length=8)],
53
+ region: Literal["cn", "jp", "us"], # 枚举 → agent 不会猜错
54
+ range: TimeRange, # 嵌套 object schema
55
+ fields: list[str] | None = None, # 可选 list
56
+ limit: Annotated[int, Field(ge=1, le=1000)] = 100,
57
+ ) -> dict: ...
58
+ ```
59
+
60
+ 要点:
61
+ - **必传参数不给默认值**;可选参数一律给默认值(Agent 会省略)
62
+ - 用 `Literal[...]` 表达枚举,比 `str` + 文档说明更可靠
63
+ - 用 `Annotated[T, Field(description=...)]` 在 schema 里塞字段说明;**`description` 文本会被 BM25/Embedding 搜到**——写清楚利己利人
64
+ - 复杂输入用 `BaseModel` 嵌套;axi 会把它展开成 object schema
65
+ - **填 `output_example`**:MCP 协议没有 output schema,原生工具是 axi 独有的优势——Agent 能在 `axi describe` 里直接看到返回形状,不用试跑一遍
66
+
67
+ ### 注册:两种方式,二选一
68
+
69
+ **方式 1:本项目内用 → `axi.json` 的 `nativeTools`**
70
+
71
+ ```json
72
+ {
73
+ "nativeTools": [
74
+ { "module": "./tools/my_tools.py" }, // 文件路径,server 名自动推导为 my_tools
75
+ { "module": "my_pkg.tools", "name": "my" } // 模块路径,显式指定 server
76
+ ]
77
+ }
78
+ ```
79
+
80
+ `axi.json` 是 `AXI_CONFIG` 环境变量指向的文件(缺省 `~/.axi/axi.json`,**不会从当前目录发现**)。项目级配置在项目里放一份 axi.json 并 `export AXI_CONFIG=/path/to/axi.json`(direnv 最省事)。
81
+
82
+ **方式 2:分发给别人用 → pip 包 + `pyproject.toml` entry_points**
83
+
84
+ ```toml
85
+ [project.entry-points."axi.native_tools"]
86
+ smartlink = "smartlink_axi.tools"
87
+ ```
88
+
89
+ 装完 `pip install <你的包>` 后,**无需 axi.json** 也能 `axi list` 看到;group 名必须是 `axi.native_tools`,value 必须是模块路径(不要写 `.py` 文件)。
90
+
91
+ 合并规则:同模块两边都声明时 `axi.json` 赢;不同模块声明同一个 server 名会 log warning 但照常挂载(工具合并)。
92
+
93
+ ---
94
+
95
+ ## Path B:接入已有 MCP server
96
+
97
+ 编辑 `AXI_CONFIG` 指向的 `axi.json`(缺省 `~/.axi/axi.json`):
98
+
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "myserver": { // key 就是 server 名
103
+ "command": "npx", "args": ["some-mcp-tool"], // 本地进程
104
+ "env": {"API_KEY": "xxx"}
105
+ },
106
+ "remote": {
107
+ "url": "http://localhost:8000/mcp" // 或 HTTP streaming
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ `command` / `url` 二选一。保存后直接 `axi list` 就能看到新工具——daemon 会按需自动拉起连接。
114
+
115
+ ---
116
+
117
+ ## 自测(三步,必做)
118
+
119
+ 注册完后走一遍 Agent 视角的发现→调用路径,验证 schema 生成正确:
120
+
121
+ ```bash
122
+ axi list # 新 server / 工具出现了?
123
+ axi search "你的工具描述里的关键词" # 能被语义搜到?搜不到 → description 要改
124
+ axi describe <server>/<tool> # input_schema 字段、类型、required 对不对?
125
+ axi run <server>/<tool> --key value # 跑通,返回 {"status":"success","data":...}
126
+ ```
127
+
128
+ **搜不到比跑不通更常见**——description 太短、只写了类名或方法名都会导致 BM25/Embedding 召回差。用几个真实查询跑 `axi search`,看你的工具有没有出现、排得够不够靠前。
129
+
130
+ ---
131
+
132
+ ## 导出为 MCP server(写一次,CLI + MCP 两用)
133
+
134
+ 注册进 axi 的工具(native 和 MCP 都行)可以一条命令变成 MCP server,挂给 Claude Desktop / Claude Code 等任何 MCP 客户端:
135
+
136
+ ```bash
137
+ axi mcp --server my_tools # 平铺:每个工具是独立 MCP tool(单 server 用裸名,多 server 加 server__ 前缀)
138
+ axi mcp # 元工具:只暴露 search/grep/describe/run 四个工具,工具再多也不吃对端 context
139
+ axi mcp --server a,b --transport http --port 8321 # HTTP 长驻服务
140
+ ```
141
+
142
+ 缺省形态自动判定:指定了 `--server` → 平铺,全量 → 元工具;`--flat` / `--meta` 强制覆盖。
143
+
144
+ 客户端配置示例——stdio 进程由客户端拉起、cwd 不可控,**必须在 env 里用 `AXI_CONFIG` 锚定配置**:
145
+
146
+ ```json
147
+ {
148
+ "mcpServers": {
149
+ "my-tools": {
150
+ "command": "axi",
151
+ "args": ["mcp", "--server", "my_tools"],
152
+ "env": { "AXI_CONFIG": "/path/to/axi.json" }
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 反模式
161
+
162
+ - 函数签名用 `**kwargs` 或 `*args` — schema 提取不到参数,Agent 看到空 schema 会乱传
163
+ - 所有参数都写 `str` — 失去类型约束和枚举提示,Agent 要靠 describe 里的自然语言猜
164
+ - `description` 只写一个词("搜索"、"查询")— BM25 召回差、Embedding 语义稀薄
165
+ - 返回不稳定结构(同一工具有时返 list 有时返 dict)— 下游 Agent 没法写聚合逻辑
166
+ - 把敏感配置(API Key)硬编码在 `@tool` 函数里 — 走环境变量或 `mcpServers[...].env`
167
+ - entry_points 的 value 写成文件路径 `"pkg/tools.py"` — 必须是可 import 的模块路径 `"pkg.tools"`
168
+
169
+ ---
170
+
171
+ 需要更多配置细节(`search` embedding、`daemon` idle 超时、`AXI_CONFIG` 环境变量改 axi.json 路径等)时,直接读源码:axi 包里的 `config.py` 是 Pydantic 模型,字段和默认值一目了然;`axi --help` 查命令参数。
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: axi-use
3
+ description: 通过 `axi` CLI(或在 Python 中使用 `from axi import tool`)发现并调用工具。当任务需要执行外部工具(当前拥有<changeme>)请使用此skill。核心工作流程为:`axi search <query>` → `axi describe <tool>` → `axi run <tool> --key value`;当任务涉及多次调用之间的循环、聚合或后处理时,请优先使用 Python 进行处理。
4
+ ---
5
+
6
+ # axi — Agent 使用手册
7
+
8
+ axi 是当前环境里的工具调用层。有工具需求时,**先 `axi search`,再 `axi describe`,最后 `axi run`**,不要去翻代码或自己实现。
9
+
10
+ ## 何时用
11
+
12
+ - 任务需要外部能力:web 搜索、知识库检索、注册过的 Python 函数、任何 MCP server 暴露的工具
13
+ - 用户让你"调用某工具 / 搜索某工具 / 查某工具怎么用"
14
+
15
+ 工具可以来自三种渠道(对使用者透明,直接 `axi search` 就能找到):`axi.json` 的 `mcpServers` / `nativeTools`,或 Python 包通过 entry_points 自动注册。配置文件位置由 `AXI_CONFIG` 环境变量指定,缺省 `~/.axi/axi.json`——**不会从当前目录自动发现**,项目级配置需要 `export AXI_CONFIG=/path/to/axi.json`。没有配置文件也不影响使用。
16
+
17
+ ## 三步工作流
18
+
19
+ ### 1. 发现工具 — `axi search` 或 `axi grep`
20
+
21
+ 两个命令覆盖两种场景,**按你当前知道什么来选**:
22
+
23
+ | 你掌握的线索 | 用哪个 | 为什么 |
24
+ |---|---|---|
25
+ | 只有意图 / 能力描述("web 搜索"、"查订单") | `axi search` | BM25 + embedding 混合,支持中英文自然语言语义匹配 |
26
+ | 已知工具名或 server 名的片段 / 模式 | `axi grep` | 正则直匹,精确、无语义噪声 |
27
+ | 想看某个 server 下所有工具 | `axi list [server]` | 纯枚举,不做匹配(最后查找不到的兜底手段) |
28
+
29
+ ```bash
30
+ axi search "web" --top-k 3
31
+ # [{"name":"jina/jina_search","description":"Search the web. ...","source":"mcp"},
32
+ # {"name":"jina/jina_reader","description":"Read and extract content from web page.","source":"mcp"}]
33
+ # 结果按相关性排序(越靠前越相关),不含分数
34
+
35
+ axi grep "^jina/" --limit 5
36
+ # 精确列出 jina server 下所有工具
37
+
38
+ axi list jina
39
+ # [{"server":"jina","tools":["jina_reader","jina_search"]}]
40
+ ```
41
+
42
+ ### 2. 查看参数 — `axi describe`
43
+
44
+ 拿到候选后,用完整名(`server/tool` 或原生工具的短名)查 schema:
45
+
46
+ ```bash
47
+ axi describe jina/jina_search
48
+ ```
49
+
50
+ 返回:
51
+
52
+ ```json
53
+ {"name":"jina_search","server":"jina","description":"...",
54
+ "input_schema":{"type":"object",
55
+ "properties":{"query":{"type":"string","minLength":1},
56
+ "count":{"type":"number","default":5},
57
+ "siteFilter":{"type":"string"}},
58
+ "required":["query"]},
59
+ "source":"mcp"}
60
+ ```
61
+
62
+ **只看 `input_schema.required` 就知道必传什么**;可选参数用默认值即可。
63
+
64
+ ### 3. 执行 — `axi run`
65
+
66
+ 两种传参方式,**优先用 `--key value`**:
67
+
68
+ ```bash
69
+ # 推荐:键值对,自动 JSON 解析数字 / 布尔 / JSON 字面量(`--count 3` 就是 int,别加引号)
70
+ axi run jina/jina_search --query "python async" --count 3
71
+
72
+ # 复杂嵌套参数时:整体 JSON
73
+ axi run jina/jina_search --json '{"query":"python async","count":3}'
74
+ ```
75
+
76
+ 返回统一信封:
77
+
78
+ ```json
79
+ {"status":"success","data": ...}
80
+ {"status":"error","error":"..."}
81
+ ```
82
+
83
+ **`status` 字段决定分支**,不要靠退出码判断业务错误。
84
+
85
+ ## 易踩坑
86
+
87
+ | 坑 | 正确做法 |
88
+ |---|---|
89
+ | `axi daemon start`(手动启动) | **不要。** MCP 工具首次调用自动拉起 daemon,空闲 30 分钟自动回收 |
90
+ | MCP 工具没有 `output_example` | 批量处理前先 `axi run` 探一次真实返回结构,再写后续代码 |
91
+ | 工具完整名搞错 | 直接用 `axi search` 返回的 `name` 字段作为完整名,不要自己拼接;所有工具(含原生)都有 `server/tool` 形式 |
92
+ | MCP server 名和 npm 包名混淆 | 以 `axi.json` 里 `mcpServers` 的 **key** 为准(例如 key 是 `jina`,不是 `jina-mcp-tools`) |
93
+
94
+ ## 何时切 Python
95
+
96
+ **单次调用走 CLI;需要循环 / 聚合 / 跨调用后处理时走 Python。** 别在 Python 里 `subprocess` 调 CLI(冷启进程 + 反复 JSON 序列化,既慢又费 token)——直接 import:
97
+
98
+ ```python
99
+ from axi import tool
100
+
101
+ search = tool("jina/jina_search") # 返回可调用句柄,参数跟 `axi run --key value` 相同
102
+ results = search(query="python best practices", count=3)
103
+ ```
104
+
105
+ ## 命令速查
106
+
107
+ | 命令 | 用途 | 典型例子 |
108
+ |---|---|---|
109
+ | `axi search <q> [--top-k N]` | 按语义 / 意图找工具 | `axi search "订单" -k 5` |
110
+ | `axi grep <regex> [--limit N]` | 按工具名 / 描述字面模式找工具 | `axi grep "retrieval"` |
111
+ | `axi list [server]` | 枚举工具 | `axi list jina` |
112
+ | `axi describe <tool>` | 查 input_schema | `axi describe jina/jina_search` |
113
+ | `axi run <tool> --k v` 或 `--json '…'` | 执行 | `axi run jina/jina_search --query hi` |
114
+ | `axi daemon status` | 看 daemon 是否在跑 | 调试 MCP 卡顿时用 |
115
+ | `axi --help` / `axi <cmd> --help` | 查任何命令的完整参数 | 拿不准选项时首选 |
116
+
117
+ > **下游项目**:如果你的仓库基于 axi 构建且有业务契约(时区、字段语义、输出结构约定等),在自己仓库再写一份项目级 SKILL.md 补这些——本 skill 只覆盖 axi 自身的调用接口。
@@ -64,14 +64,11 @@ def tool(
64
64
 
65
65
  def _make_daemon_caller(tool_name: str) -> Callable:
66
66
  """创建通过 daemon 调用 MCP 工具的 PTC 函数。"""
67
- from axi.daemon.client import ensure_daemon, send_request
67
+ from axi.daemon.client import daemon_request
68
68
  from axi.daemon.protocol import DaemonRequest
69
69
 
70
70
  def _daemon_caller(**kwargs: Any) -> Any:
71
- if not ensure_daemon():
72
- raise RuntimeError("Daemon is not running. Start it with: axi daemon start")
73
-
74
- resp = send_request(
71
+ resp = daemon_request(
75
72
  DaemonRequest(method="call_tool", tool_name=tool_name, params=kwargs)
76
73
  )
77
74
  if resp.status == "error":