使用 OpenCode 进行 AI 辅助编程时,内置的工具集已经覆盖了文件读写、代码搜索、终端执行等常见场景。但在实际项目中,我们常常需要与内部 API、私有数据库、特定命令行工具或自定义工作流交互。这时,OpenCode 的 Custom Tools(自定义工具)功能就派上了用场。
自定义工具允许你定义 OpenCode Agent 可以调用的外部工具,从而扩展其能力边界。本文将深入讲解 Custom Tools 的配置方法、开发技巧和实战案例,帮助你将 OpenCode 打造成适配团队工作流的专属开发助手。
Custom Tools 是 OpenCode 中一种可扩展机制,允许你在 opencode.json 配置文件中定义新的工具,让 Agent 在对话中自动调用。每个自定义工具本质上是一个可执行命令的封装——Agent 决定何时调用、传入参数,然后获取命令的输出来辅助决策。
与 MCP(Model Context Protocol)服务器不同,Custom Tools 更轻量,无需启动独立服务进程,适合封装简单的脚本或命令行操作。
自定义工具在 opencode.json 的 customTools 字段中定义。来看一个最小示例:
{
"customTools": {
"weather": {
"displayName": "天气查询",
"description": "查询指定城市的当前天气信息",
"command": "python scripts/weather.py"
}
}
}
每个工具的核心字段:
weather):工具的唯一标识,Agent 通过它引用工具大多数工具需要接收参数。通过 args 字段定义参数结构,Agent 会理解这些参数并自动填充:
{
"customTools": {
"deploy": {
"displayName": "部署服务",
"description": "将指定分支部署到目标环境(staging/production)",
"command": "bash scripts/deploy.sh",
"args": {
"branch": {
"description": "要部署的分支名称",
"required": true
},
"env": {
"description": "目标环境:staging 或 production",
"required": true,
"enum": ["staging", "production"]
},
"force": {
"description": "是否强制部署(跳过检查)",
"required": false
}
}
}
}
}
参数通过命令行位置或环境变量传递给脚本。OpenCode 会按参数定义的顺序拼接命令。
更灵活的做法是通过 stdin 或文件传递参数,在脚本内部解析:
{
"customTools": {
"db_query": {
"displayName": "数据库查询",
"description": "对项目数据库执行只读 SQL 查询并返回结果",
"command": "python scripts/query_db.py",
"args": {
"sql": {
"description": "要执行的 SQL 查询语句",
"required": true
}
}
}
}
}
对应的 Python 脚本 scripts/query_db.py:
import sys
import json
import sqlite3
input_data = json.loads(sys.stdin.read())
sql = input_data.get("sql", "")
conn = sqlite3.connect("database.sqlite")
cursor = conn.cursor()
cursor.execute(sql)
rows = cursor.fetchall()
columns = [desc[0] for desc in cursor.description]
result = {"columns": columns, "rows": rows}
print(json.dumps(result, ensure_ascii=False))
conn.close()
在团队协作中,经常需要执行 Git 操作。创建一个自定义工具来简化分支合并和发布流程:
{
"customTools": {
"git_merge_check": {
"displayName": "合并冲突检查",
"description": "检查两个分支之间是否存在合并冲突,返回冲突文件列表",
"command": "bash .opencode/scripts/merge_check.sh",
"args": {
"source": {
"description": "源分支名称",
"required": true
},
"target": {
"description": "目标分支名称",
"required": true
}
}
}
}
}
对应的 Shell 脚本 .opencode/scripts/merge_check.sh:
#!/bin/bash source=$1 target=$2 git fetch origin "$source" "$target" 2>/dev/null if git merge-base --is-ancestor "origin/$source" "origin/$target"; then echo "无冲突:$source 已完全包含在 $target 中" exit 0 fi conflicts=$(git merge-tree $(git merge-base "origin/$source" "origin/$target") "origin/$source" "origin/$target" | grep -A 3 "^changed in both" || true) if [ -z "$conflicts" ]; then echo "无冲突:两个分支可以自动合并" else echo "发现潜在冲突:" echo "$conflicts" fi
Agent 可以在执行合并操作前自动调用此工具检查冲突风险,极大减少意外合并事故。
在 Laravel 或 Rails 项目中,数据库迁移是一个日常操作。创建一个工具来预览未执行的迁移及其影响:
{
"customTools": {
"pending_migrations": {
"displayName": "待执行迁移",
"description": "列出项目中所有未执行的数据库迁移文件及其内容摘要",
"command": "php artisan migrate:status --json",
"args": {}
},
"migration_preview": {
"displayName": "迁移预览",
"description": "预览指定迁移文件的 SQL 变更内容",
"command": "bash .opencode/scripts/preview_migration.sh",
"args": {
"migration": {
"description": "迁移文件名(如 2026_01_01_000001_create_users_table)",
"required": true
}
}
}
}
}
企业中常用内部 API 管理服务状态。以下工具封装了调用内部部署平台 API 的操作:
{
"customTools": {
"internal_api": {
"displayName": "内部 API 调用",
"description": "调用公司内部管理 API,获取服务状态或执行操作。支持 GET/POST 方法。",
"command": "python .opencode/scripts/internal_api.py",
"args": {
"endpoint": {
"description": "API 路径,例如 /api/services/status",
"required": true
},
"method": {
"description": "HTTP 方法",
"required": false,
"default": "GET",
"enum": ["GET", "POST"]
},
"data": {
"description": "POST 请求的 JSON 数据体",
"required": false
}
}
}
}
}
Python 脚本 .opencode/scripts/internal_api.py:
import sys, json, os, requests
API_BASE = os.environ.get("INTERNAL_API_URL", "http://localhost:8080")
API_TOKEN = os.environ.get("INTERNAL_API_TOKEN", "")
input_data = json.loads(sys.stdin.read())
endpoint = input_data["endpoint"]
method = input_data.get("method", "GET").upper()
data = input_data.get("data")
headers = {"Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json"}
try:
if method == "GET":
resp = requests.get(f"{API_BASE}{endpoint}", headers=headers, timeout=10)
else:
payload = json.loads(data) if isinstance(data, str) else data
resp = requests.post(f"{API_BASE}{endpoint}", headers=headers, json=payload, timeout=10)
result = {
"status_code": resp.status_code,
"body": resp.json() if resp.text else {}
}
except Exception as e:
result = {"error": str(e)}
print(json.dumps(result, ensure_ascii=False))
通过在 Shell 中提前设置 INTERNAL_API_URL 和 INTERNAL_API_TOKEN 环境变量,既能实现自动化,又避免在配置文件中明文存储密钥。
Agent 的决策完全依赖 description 字段。对比以下两个描述:
描述中应包含:工具的用途、输入输出格式、触发场景。
Agent 解析的是标准输出,推荐使用 JSON 格式返回结构化数据,便于 Agent 提取关键信息。
print(json.dumps({"success": true, "message": "部署完成", "url": "https://staging.example.com"}))
脚本应捕获所有异常,返回明确错误信息而非堆栈跟踪:
try:
result = do_something()
print(json.dumps({"success": True, "data": result}))
except Exception as e:
print(json.dumps({"success": False, "error": str(e)}))
sys.exit(1)
Agent 在遇到非零退出码时会认为工具执行失败,并尝试其他策略或向用户报告。
推荐将自定义脚本放在项目内的 .opencode/scripts/ 目录下,这样既与项目绑定,又不会污染根目录。使用相对于工作目录的路径:
"command": "bash .opencode/scripts/deploy.sh"
permissions 字段限制工具的调用范围开发自定义工具时,可以先在终端手动测试:
echo '{"sql": "SELECT COUNT(*) FROM users"}' | python scripts/query_db.py
确认输出格式正确后,再集成到 OpenCode 中。在对话中使用时,Agent 调用工具的输入输出会显示在界面中,便于观察和调试。
OpenCode 的 Custom Tools 机制将 AI Agent 的能力边界从通用编程扩展到了团队专属的业务场景。通过编写简单的脚本并定义清晰的参数结构,我们可以让 OpenCode 理解内部 API、执行复杂的 Git 操作、查询数据库、触发 CI/CD 流程,真正做到"一个 Agent 搞定开发全流程"。
本文从基础配置讲到了三个实战案例,涵盖 Git 自动化、数据库操作和内部 API 集成。关键要点是:精确的描述、结构化的输出、完善的错误处理,以及合理的安全实践。
当你下次遇到重复性操作时,不妨想一想:能否把它封装成一个 Custom Tool,让 OpenCode 来代劳?