getio0909/toolclash
GitHub: getio0909/toolclash
一款离线的确定性 Go 命令行工具,通过分析 JSON Schema 的交集,在部署前和日志回放中证明 AI Agent 工具目录是否存在参数混淆与冲突。
Stars: 0 | Forks: 0
# ToolClash
[](LICENSE)
[](go.mod)
[](https://github.com/getio0909/toolclash/actions/workflows/ci.yml)
AI agent 越来越多地从 MCP 服务器、应用程序代码和模型提供商 API 接收数十个函数。在模型看来,两个工具可能具有互换性,并且即使它们具有不同的效果,也可能接受相同的调用。仅针对名称的 linter 会遗漏这种重叠;而 schema 验证器只会单独检查每个工具。
ToolClash 将目录作为集合进行分析。其 `scan` 命令首先查找具有易混淆名称或描述的工具,然后询问它们的 JSON Schema 输入语言是否存在交集。当它报告已证实的重叠时,会包含一个具体的 JSON 值,该值已独立通过两个 schema 的验证。
它的 `replay` 命令回答了另一个问题:给定一个已经发生的工具调用,目录中还有哪些其他工具会接受完全相同的参数?Replay 会首先验证所选工具,然后在不进行词法过滤的情况下,根据整个目录检查规范参数。它不会重复模型请求或调用任何工具。
```
similar name or description
│
▼
bounded schema intersection ── disjoint ──▶ no collision finding
│
┌─────┴─────┐
▼ ▼
overlap unknown
+ witness fail closed
```
ToolClash 是离线的、确定性的,采用 MIT 许可证,并且除了 Go 标准库之外没有其他运行时依赖。
## 有何不同
- **产生证据的发现。** `overlap` 始终包含一个满足两个输入 schema 的具体见证。
- **三种可靠的结果。** 一对工具的结果为 `overlap`、`disjoint` 或 `unknown`。不支持和超出预算的分析永远不会变成猜测的肯定结果。
- **目录感知风险。** 词法相似性会缩小候选范围,而显式的 MCP 效果注解可以提升已证实冲突的严重性。
- **观察到的调用回放。** 导入已完成的提供商追踪,并证明哪些其他工具接受相同的真实参数对象,即使它们的名称和描述毫不相关。
- **提供商中立的输入。** 将 MCP、OpenAI Responses、OpenAI Chat Completions、Anthropic 或纯工具数组放在一起扫描;从这些提供商或混合的 JSONL 中回放已完成的调用。
- **适配 CI 的输出。** 呈现紧凑的终端输出、确定性的 JSON 或 SARIF 2.1.0。
ToolClash 并不声称发明了 JSON Schema 见证生成。它将保守的见证引擎应用于跨提供商 AI 工具目录歧义这一特定问题。见证证明两个 schema 共享一个接受的输入;它并不预测特定模型会选择错误的工具。
## 安装
需要 Go 1.24 或更高版本。
```
go install github.com/getio0909/toolclash/cmd/toolclash@latest
```
或者从源码构建:
```
git clone https://github.com/getio0909/toolclash.git
cd toolclash
go build -o toolclash ./cmd/toolclash
```
在 Windows 上,使用 `go build -o toolclash.exe ./cmd/toolclash`。
## 快速开始
扫描一个包含故意混淆工具对的目录:
```
toolclash scan examples/collision-mcp.json
```
将多个提供商作为一个组合目录进行扫描:
```
toolclash scan \
examples/collision-mcp.json \
examples/safe-openai-responses.json \
examples/anthropic-tools.json
```
输出机器可读的内容,并在出现警告或错误时让 CI 失败:
```
toolclash scan --format json --fail-on warning catalogs/*.json
toolclash scan --format sarif --output toolclash.sarif catalogs/*.json
```
使用一次 `-` 从标准输入读取目录:
```
toolclash scan - < examples/safe-openai-responses.json
```
PowerShell:
```
Get-Content -Raw examples/safe-openai-responses.json | toolclash scan -
```
针对 MCP 目录回放观察到的 OpenAI Responses 调用:
```
toolclash replay \
--catalog examples/replay-catalog-mcp.json \
examples/replay-openai-responses.json
```
该示例选择了 `lookup_customer`,但相同的参数也满足 `erase_account_record`。因为它们显式的 MCP 效果提示发生冲突,replay 会同时报告 `TC006` 和 `TC007`。报告中包含参数摘要,而不包含追踪中的客户标识符。
## 扫描还是回放?
| 命令 | 证据 | 候选范围 | 最佳用途 |
| --- | --- | --- | --- |
| `scan` | 由两个 schema 独立接受的生成见证 | 词法上看似合理的对,加上精确和规范化后的名称冲突 | 在部署前审查目录 |
| `replay` | 来自观察到的已选调用的规范参数 | 每个导入的目录工具;无词法过滤器 | 审计实际工具流量或调查事件 |
这两个命令都是确定性和离线的。`scan` 可以在任何调用发生之前发现潜在的冲突。`replay` 可以揭示静态词法选择有意不去比较的结构兼容的替代方案。这两个命令都不预测模型行为或验证工具实现的真实副作用。
## 它能检测到什么
| 规则 | 命令 | 默认严重性 | 含义 |
| --- | --- | --- | --- |
| `TC001` | `scan` | error | 两个目录条目具有相同的工具名称。 |
| `TC002` | `scan` | warning | 分隔符和大小写规范化后名称发生冲突。 |
| `TC003` | `scan` | warning | 易混淆的名称/描述存在已证实的 schema 重叠。 |
| `TC004` | `scan` | error | 已证实的易混淆对具有矛盾的显式效果提示,例如只读与破坏性。 |
| `TC005` | `scan` | note | 词法上易混淆的对无法安全判定。 |
| `TC006` | `replay` | warning | 多个目录工具接受观察到的参数。 |
| `TC007` | `replay` | error | 接受的工具具有矛盾的显式效果提示。 |
| `TC008` | `replay` | error | 所选工具不存在,或者每个匹配的 schema 都明确拒绝观察到的参数。 |
| `TC009` | `replay` | note | 回放不完整,因为 schema 无效、不受支持或有界限。 |
`TC005` 和 `TC009` 是刻意设计的。它们告诉你求解器在哪里拒绝近似 schema 特性,或者在资源边界处停止。当不完整的分析应使命令失败时使用 `--strict`;严格渲染会将不完整的发现提升为 warning 严重性。
## 支持的目录结构
ToolClash 自动检测以下结构。每个导入的工具都会规范化为相同的内部模型,同时保留其源文件和目录索引。
### MCP
MCP 工具对象使用 `inputSchema`。接受纯 `tools` 响应和 JSON-RPC `result.tools` 响应。
```
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "read_file",
"description": "Read a UTF-8 file",
"inputSchema": {
"type": "object",
"properties": {
"path": { "type": "string", "minLength": 1 }
},
"required": ["path"],
"additionalProperties": false
},
"annotations": {
"readOnlyHint": true,
"destructiveHint": false
}
}
]
}
}
```
### OpenAI Responses
Responses API 函数工具使用带 `parameters` 的扁平对象。
```
{
"tools": [
{
"type": "function",
"name": "read_file",
"description": "Read a UTF-8 file",
"parameters": {
"type": "object",
"properties": {
"path": { "type": "string" }
},
"required": ["path"],
"additionalProperties": false
},
"strict": true
}
]
}
```
### OpenAI Chat Completions
Chat Completions 函数工具将相同字段封装在 `function` 内部。
```
{
"tools": [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read a UTF-8 file",
"parameters": {
"type": "object",
"properties": {
"path": { "type": "string" }
},
"required": ["path"],
"additionalProperties": false
}
}
}
]
}
```
### Anthropic
Anthropic 工具对象使用 `input_schema`。
```
{
"tools": [
{
"name": "read_file",
"description": "Read a UTF-8 file",
"input_schema": {
"type": "object",
"properties": {
"path": { "type": "string" }
},
"required": ["path"],
"additionalProperties": false
}
}
]
}
```
也接受包含任何受支持工具对象结构的纯 JSON 数组。
输入是只读的;ToolClash 绝不会重写源目录。
## 支持的回放追踪
`replay` 从这些提供商的通信结构中导入已完成的工具调用:
- [OpenAI Responses function-call 输出项](https://developers.openai.com/api/docs/guides/function-calling#handling-function-calls):带有 `type: "function_call"`、`name` 和 JSON 编码 `arguments` 的 `output[]` 条目;
- [OpenAI Chat Completions 函数调用](https://developers.openai.com/api/docs/guides/function-calling#handling-function-calls):带有 `name` 和 JSON 编码 `arguments` 的 `choices[].message.tool_calls[].function`;
- 带有 `name` 和 `input` 对象的 Anthropic `content[]` 条目:标准 [`tool_use` 块](https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls)和 MCP 连接器 [`mcp_tool_use` 块](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector);
- [MCP `tools/call` 请求](https://modelcontextprotocol.io/specification/2025-11-25/schema):带有 `params.name` 和可选 `params.arguments` 对象的 JSON-RPC 请求。
追踪文件也可以是包含这些完整提供商对象混合的 JSONL。流式增量、部分参数片段、工具结果、任意应用程序日志信封以及特定于提供商的事件包装器不会被重构。在回放之前提取完整的受支持调用对象。
工具参数必须解码为 JSON 对象。默认情况下,每个参数对象限制为 1 MiB。OpenAI 和 Anthropic `tool_use` 名称限制为 64 个 ASCII 字节;MCP 和 Anthropic `mcp_tool_use` 名称限制为 128 个 ASCII 字节,并且还可以包含 `.`。
追踪文件可能包含生产数据。ToolClash 不渲染原始参数或提供商调用 ID。报告通过追踪源、从零开始的调用索引、所选工具名称、接受和未知计数,以及规范参数对象的 SHA-256 摘要来识别。该摘要仍然可以关联跨报告的相同输入,并且参数在进行验证时存在于进程内存中;请将追踪和报告作为机密工件加以保护。
## CLI
```
toolclash scan [flags] [ ...]
Scan flags:
--format human|json|sarif output format (default: human)
--output write output to a file instead of stdout
--fail-on warning|error|never finding threshold (default: error)
--threshold lexical threshold in (0,1] (default: 0.62)
--strict fail when analysis is incomplete
--max-tools imported-tool limit (default: 1024)
--max-candidates schema-proof limit (default: 2048)
toolclash replay --catalog [--catalog ...] [flags] \
[ ...]
Replay flags:
--format human|json|sarif output format (default: human)
--output write output to a file instead of stdout
--fail-on warning|error|never finding threshold (default: error)
--strict fail when replay analysis is incomplete
--max-tools imported-tool limit (default: 1024)
--max-calls observed-call limit (default: 4096)
--max-validations call-by-tool validation limit (default: 50000)
--max-trace-bytes aggregate trace-byte limit (default: 33554432)
toolclash version
```
退出状态 `0` 表示未达到选定的失败阈值。退出状态 `1` 表示发现结果达到了该阈值,或者严格模式遇到了不完整的分析。退出状态 `2` 表示参数无效、输入格式错误或操作错误。
## 理解证明
假设两个易混淆的工具都接受带有 `path` 的对象,但在操作是否具有破坏性上存在分歧。ToolClash 可以发出如下的见证:
```
{ "path": "a" }
```
引擎从交集约束中构建候选对象,然后运行两个独立的验证:
```
Validate(schema A, witness) == success
Validate(schema B, witness) == success
```
只有这样才能将该对报告为 `overlap`。如果约束相互矛盾,则结果为 `disjoint`。如果支持的子集无法可靠地判定该对,则结果为 `unknown`。
提供商工具参数是 JSON 对象,因此目录分析也会将两个 schema 与对象实例类型相交。宽松的 `{}` 输入 schema 不会使标量值成为有效的工具参数。
Replay 使用相同的独立验证器,但不构建见证。对于每个观察到的调用,它会:
1. 解析所有名称与所选名称完全匹配的目录条目;
2. 要求至少有一个匹配的 schema 接受参数,当名称不存在或每个匹配的 schema 都拒绝时报告 `TC008`;
3. 根据每个目录 schema 验证相同的参数;
4. 当多个工具接受时报告 `TC006`,当这些接受的工具具有矛盾的显式效果提示时报告 `TC007`;
5. 当不受支持、无效或有界限的 schema 语义导致结果不完整时,报告 `TC009`。
MCP 的 `readOnlyHint`、`destructiveHint`、`idempotentHint` 和 `openWorldHint` 是不受信任的元数据。ToolClash 报告显式的不匹配;它不使用这些提示作为授权或作为现实世界行为的证明。根据 MCP 语义,`destructiveHint` 和 `idempotentHint` 仅在同一工具显式声明 `readOnlyHint: false` 时才会参与;省略的值不会被规范默认值替换。
## JSON Schema 覆盖范围
求解器有意实现了一个有边界的子集,而不是假装支持所有的 JSON Schema 词汇。没有 `$schema` 的 schema 被解释为 JSON Schema 2020-12;显式方言必须是 2020-12 meta-schema URI,否则分析返回 `unknown`。
- 布尔 schema;`type`、`enum` 和 `const`
- `allOf`、`anyOf` 和 `oneOf`
- 带有 `$defs` 和 `definitions` 的本地 JSON Pointer `$ref`
- 对象:`properties`、`required`、`additionalProperties`、`minProperties` 和 `maxProperties`
- 数组:单一 schema 的 `items`、`minItems` 和 `maxItems`
- 字符串:`minLength` 和 `maxLength`
- 数字:`minimum`、`maximum`、`exclusiveMinimum`、`exclusiveMaximum` 和 `multipleOf`
- 不影响验证的标准注解关键字
布尔 schema 在证明引擎和嵌套 schema 中受支持。
提供商目录的 `inputSchema`/`parameters` 字段本身必须是 JSON Schema 对象,以匹配提供商的通信格式。
此集合之外的功能——例如正则表达式模式、格式、条件、`not`、元组验证和递归引用——在出现在候选 schema 中时会产生 `unknown`。在 replay 期间,这种 schema 会促成 `TC009`,而不是被视为接受或拒绝调用。在实现资源感知解析之前,嵌套的 `$id` 资源也会产生 `unknown`。针对不受信任的输入,解析、引用展开、分支、深度、节点计数、候选对、序列化见证大小、追踪调用和按工具调用验证都设有边界。
请参阅[架构说明](docs/architecture.md)以了解不变性、权衡和确切的分析管道。
## CI 示例
```
name: tool-catalog
on:
pull_request:
push:
branches: [main]
jobs:
toolclash:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- run: go run ./cmd/toolclash scan --fail-on warning catalogs/*.json
- run: >-
go run ./cmd/toolclash replay
--catalog catalogs/production.json
--fail-on warning
traces/redacted.jsonl
```
对于 GitHub 代码扫描,渲染 SARIF 并使用官方的 `github/codeql-action/upload-sarif` action 上传它。
## 设计边界
- 无模型调用、embedding、遥测或网络访问
- 不声称仅凭词法相似性就能证明冲突
- 不声称 schema 见证能预测模型行为
- 不执行目录描述的工具
- 不重放模型请求或重构流式参数增量
- 报告中没有原始回放参数或提供商调用 ID
- 不自动重写名称、描述或 schema
这些边界使得分析具有可重现性,并且可以在本地 hook 和 CI 中安全运行。
## 贡献与安全
在提交更改之前,请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。通过 [GitHub 私密漏洞](https://github.com/getio0909/toolclash/security/advisories/new)报告漏洞,而不是通过公开 issue;详情请参阅 [SECURITY.md](SECURITY.md)。
## 许可证
ToolClash 在 [MIT 许可证](LICENSE)下提供。
标签:AI智能体, EVTX分析, Go, JSON Schema, MCP协议, Ruby工具, 云安全监控, 冲突检测, 日志审计, 静态分析