🚀 Claude Runner - 带作业调度功能的MCP服务器
Claude Runner是一个单文件MCP服务器,它通过cron表达式来调度和执行Claude Code CLI任务。该服务器具备Web仪表盘、Webhook支持、动态MCP服务器创建以及令牌/成本跟踪等功能。
🚀 快速开始
前提条件
操作步骤
git clone https://github.com/floriansmeyers/SFLOW-AIRunner-MCP-PRD.git
cd SFLOW-AIRunner-MCP-PRD
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python server.py
首次运行时,服务器将执行以下操作:
- 创建包含所有表的
jobs.db SQLite数据库。
- 自动生成OAuth凭证(会打印到标准错误输出,请保存这些凭证!)。
- 启动调度器循环(每60秒检查一次)。
💻 运行方式
本地运行(stdio传输 - 适用于Claude Desktop)
python server.py
将以下配置添加到Claude Desktop的配置文件中(在macOS系统上为~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"claude-runner": {
"command": "/path/to/SFLOW-AIagents-MCP-Spinner/.venv/bin/python",
"args": ["/path/to/SFLOW-AIagents-MCP-Spinner/server.py"]
}
}
}
重要提示:请使用.venv内Python解释器的完整路径,以确保依赖项可用。
远程运行(SSE传输 - 适用于Web仪表盘)
MCP_TRANSPORT=sse python server.py
同时使用两种传输方式
MCP_TRANSPORT=both python server.py
⚙️ 环境变量
| 变量 |
是否必需 |
默认值 |
描述 |
ANTHROPIC_API_KEY |
是 |
- |
从console.anthropic.com获取的API密钥,通过Claude Agent SDK执行作业时必需。请勿使用个人Max/Pro订阅。 |
MCP_TRANSPORT |
否 |
both |
传输模式:stdio、sse或both |
OAUTH_CLIENT_ID |
否 |
自动生成 |
OAuth客户端ID |
OAUTH_CLIENT_SECRET |
否 |
自动生成 |
OAuth客户端密钥 |
OAUTH_SECRET_KEY |
否 |
自动生成 |
用于签署JWT令牌的密钥 |
DASHBOARD_USERNAME |
否 |
- |
仪表盘的基本认证用户名 |
DASHBOARD_PASSWORD |
否 |
- |
仪表盘的基本认证密码 |
NGROK_AUTHTOKEN |
否 |
- |
用于远程隧道的ngrok认证令牌 |
PUBLIC_URL |
否 |
- |
覆盖OAuth回调的基础URL(例如https://your-domain.com) |
你可以创建一个.env文件来持久化这些变量:
ANTHROPIC_API_KEY=sk-ant-...
OAUTH_CLIENT_ID=your_client_id
OAUTH_CLIENT_SECRET=your_client_secret
OAUTH_SECRET_KEY=your_secret_key
DASHBOARD_USERNAME=admin
DASHBOARD_PASSWORD=secure_password
NGROK_AUTHTOKEN=your_ngrok_token
🔐 身份验证(OAuth 2.1)
SSE传输需要OAuth 2.1身份验证。首次启动时,服务器会自动生成凭证并将其打印到标准错误输出:
============================================================
OAUTH CREDENTIALS FOR CLAUDE CONNECTOR
============================================================
Client ID: abc123...
Client Secret: xyz789...
Save these to your .env file:
OAUTH_CLIENT_ID=abc123...
OAUTH_CLIENT_SECRET=xyz789...
============================================================
重要提示:请立即保存这些凭证。密钥只会显示一次,且无法恢复。
OAuth端点
| 端点 |
描述 |
/.well-known/oauth-authorization-server |
服务器元数据 |
/.well-known/oauth-protected-resource |
受保护资源元数据 |
/oauth/authorize |
授权端点 |
/oauth/token |
令牌端点 |
重新生成凭证
如果你丢失了凭证,可以从数据库中删除客户端:
sqlite3 jobs.db "DELETE FROM oauth_clients WHERE client_name = 'Claude (auto)'"
然后重新启动服务器以生成新的凭证。
📁 目录结构
SFLOW-AIRunner-MCP-PRD/
├── server.py # 主MCP服务器
├── requirements.txt # Python依赖项
├── jobs.db # SQLite数据库(自动创建)
├── .env # 环境变量(可选)
├── fixed-servers/ # 内置MCP服务器
│ └── email/
│ ├── server.py
│ └── metadata.json
├── dynamic_servers/ # 用户创建的MCP服务器(自动创建)
│ ├── cat-facts/
│ └── r2-images/
├── deploy/ # 部署配置(systemd、nginx、部署脚本)
│ ├── deploy.sh
│ ├── mcpserver.service
│ ├── nginx.conf
│ └── README-HETZNER.md
└── claude_playground/ # 作业执行的工作目录(自动创建)
💾 数据库
SQLite数据库位于./jobs.db,包含以下表:
表结构
| 表名 |
描述 |
jobs |
带有cron表达式的调度任务 |
runs |
包含输出、令牌和成本的执行历史记录 |
settings |
配置信息(允许的工具、MCP服务器、凭证) |
webhooks |
触发提示的HTTP端点 |
oauth_clients |
OAuth客户端凭证 |
oauth_codes |
OAuth授权码 |
oauth_refresh_tokens |
OAuth刷新令牌 |
默认设置
首次运行时,会创建以下默认设置:
| 键 |
默认值 |
allowed_tools |
["WebSearch", "WebFetch", "Read", "Write", "Edit", "Bash"] |
mcp_servers |
[] |
mcp_env_vars |
{} |
webhook_base_url |
"http://localhost:8080" |
sandbox_mode |
true |
完全重置数据库
若要完全重置数据库,请执行以下操作:
rm jobs.db
python server.py
🛠️ 可用的MCP工具
作业管理
| 工具 |
描述 |
list_jobs |
列出所有调度作业 |
get_job(job_id) |
获取特定作业 |
create_job(name, cron, prompt, ...) |
创建新的调度作业 |
update_job(job_id, ...) |
更新现有作业 |
delete_job(job_id) |
删除作业及其运行记录 |
trigger_job(job_id) |
手动立即触发作业 |
运行管理
| 工具 |
描述 |
list_runs(job_id?, limit?) |
列出最近的运行记录 |
get_run(run_id) |
获取完整的运行详情,包括输出 |
kill_run(run_id) |
取消待处理/正在运行的运行 |
Webhook管理
| 工具 |
描述 |
create_webhook(name, prompt_template) |
创建Webhook端点 |
list_webhooks |
列出所有Webhook |
get_webhook(webhook_id) |
获取Webhook详情 |
update_webhook(webhook_id, ...) |
更新Webhook |
delete_webhook(webhook_id) |
删除Webhook |
动态MCP服务器管理
| 工具 |
描述 |
create_mcp_server(name, code, ...) |
创建自定义MCP服务器 |
list_dynamic_mcp_servers |
列出用户创建的服务器 |
get_dynamic_mcp_server(name) |
获取服务器详情 |
update_mcp_server(name, code, ...) |
更新动态服务器的代码/描述 |
delete_mcp_server(name) |
删除动态服务器 |
enable_mcp_server(name) |
启用动态MCP服务器 |
disable_mcp_server(name) |
禁用动态MCP服务器 |
固定MCP服务器管理
| 工具 |
描述 |
list_fixed_mcp_servers |
列出内置服务器 |
enable_fixed_server(name) |
启用固定服务器 |
disable_fixed_server(name) |
禁用固定服务器 |
内部MCP工具
| 工具 |
描述 |
invoke_internal_mcp_tool(tool, payload) |
在内部MCP服务器上调用工具 |
凭证管理
| 工具 |
描述 |
set_server_credential(server, key, value) |
设置凭证 |
get_server_credentials(server) |
查看凭证(掩码显示) |
list_required_credentials(server) |
查看所需凭证 |
get_unconfigured_servers |
查找缺少凭证的服务器 |
delete_server_credential(server, key) |
删除凭证 |
💻 通过claude.ai使用
连接成功后,你可以完全通过自然语言与服务器进行交互。Claude会在幕后将你的请求转换为相应的MCP工具调用。
重要提示:作业通过Claude Agent SDK执行,该SDK在底层运行本地Claude Code CLI。你需要在运行服务器的机器上安装并认证Claude Code(claude必须在系统PATH中可用并已登录)。
以下是主要的工作流程:
创建自定义工具
你可以让Claude动态构建新的MCP工具服务器。服务器代码会保存到dynamic_servers/<name>/,每个服务器都会有一个DATA_DIR路径变量用于本地文件存储。代码中通过os.environ.get()或os.getenv()引用的环境变量会被自动检测并标记为所需凭证。
你:创建一个工具,使用OpenWeatherMap API获取给定城市的当前天气。
Claude:[调用create_mcp_server — 将服务器保存到dynamic_servers/weather/,从代码中检测到OPENWEATHERMAP_API_KEY]
完成!我创建了一个带有get_weather(city)工具的“weather”服务器。
它需要一个API密钥 —— 现在要我保存你的密钥吗?
你:是的,密钥是abc123
Claude:[调用set_server_credential("weather", "OPENWEATHERMAP_API_KEY", "abc123")]
凭证已保存。天气服务器已准备好使用。
调度定期作业
作业使用Claude Agent SDK按cron计划运行。你可以指定作业允许使用的工具(空数组[]表示允许使用所有工具)。
你:创建一个作业,在每个工作日的上午9点运行,总结我们的Azure DevOps冲刺情况。
Claude:[调用create_job,参数如下:]
名称:"Daily Sprint Summary"
cron表达式:"0 9 * * 1-5"
提示:"连接到Azure DevOps并总结当前冲刺情况..."
工具:["mcp__azure-devops__wit_my_work_items", "mcp__email__send_email"]
作业已创建(ID:a1b2c3d4)。下次运行时间:明天上午9:00。
你:显示最近的5次运行记录
Claude:[调用list_runs,限制为5条记录]
以下是最近的运行记录 —— 4次成功完成,1次出错...
设置Webhook
Webhook允许外部服务通过HTTP POST触发提示。prompt_template支持{{payload}}表示完整的请求体,或{{payload.field.subfield}}表示嵌套值。
你:创建一个Webhook,每当创建新的Azure DevOps工作项时发送Slack摘要。POST请求体中的工作项标题位于resource.fields.System.Title。
Claude:[调用create_webhook,参数如下:]
名称:"New Work Item Notifier"
提示模板:"创建了一个新的工作项:{{payload.resource.fields.System.Title}}。总结它并发布到Slack。"
Webhook已创建!使用以下URL发送POST请求以触发它:
https://your-server.com/webhook/abc123def456...
你:测试一下 —— 手动触发Webhook
Claude:我无法通过MCP触发Webhook,但你可以使用curl进行测试:
curl -X POST https://your-server.com/webhook/abc123def456... \
-H "Content-Type: application/json" \
-d '{"resource":{"fields":{"System":{"Title":"Bug: login broken"}}}}'
直接调用工具
使用invoke_internal_mcp_tool可以立即在任何启用的MCP服务器上调用工具,而无需创建作业。
你:使用天气工具检查柏林的天气预报
Claude:[调用invoke_internal_mcp_tool,参数如下:]
工具:"mcp__weather__get_weather"
负载:{"city": "Berlin"}
柏林当前温度为12°C,天气多云,预计下午有雨。
管理服务器和凭证
你可以启用、禁用和更新内置(固定)和用户创建(动态)的服务器,并管理它们的凭证。
你:有哪些可用的服务器?
Claude:[调用list_fixed_mcp_servers和list_dynamic_mcp_servers]
固定服务器:email(已启用),playwright(已禁用)
动态服务器:weather(已启用),url-shortener(已禁用)
你:启用playwright并禁用天气服务器
Claude:[调用enable_fixed_server("playwright")和disable_mcp_server("weather")]
完成 —— playwright已启用,天气服务器已禁用。
你:哪些服务器缺少凭证?
Claude:[调用get_unconfigured_servers]
电子邮件服务器需要SENDGRID_API_KEY和SENDER_EMAIL。
临时提示
对于一次性任务,你可以使用Web仪表盘(SSE模式)中的快速运行功能立即执行提示,而无需创建作业。从claude.ai,你也可以手动触发任何现有作业:
你:立即运行“Daily Sprint Summary”作业
Claude:[调用trigger_job("a1b2c3d4")]
运行已排队(运行ID:e5f6g7h8)。我会回来查看结果。
⏰ Cron表达式示例
| 表达式 |
描述 |
0 9 * * 1-5 |
工作日上午9:00 |
0 */2 * * * |
每2小时 |
0 9 * * 1 |
周一上午9:00 |
*/30 * * * * |
每30分钟 |
0 0 1 * * |
每月1日午夜 |
🛠️ 故障排除
ModuleNotFoundError: No module named 'dotenv'
Claude Desktop使用的是系统Python,而不是你的虚拟环境。请更新配置以使用虚拟环境的完整路径:
{
"command": "/full/path/to/.venv/bin/python",
"args": ["/full/path/to/server.py"]
}
OAuth凭证丢失
删除自动生成的客户端并重新启动:
sqlite3 jobs.db "DELETE FROM oauth_clients WHERE client_name = 'Claude (auto)'"
python server.py
作业未运行
- 检查调度器是否正在运行(在日志中查找“Scheduler loop started”)。
- 验证作业是否已启用:
sqlite3 jobs.db "SELECT enabled FROM jobs"。
- 检查cron表达式是否有效。
- 在运行记录中查找错误:
sqlite3 jobs.db "SELECT error FROM runs ORDER BY started_at DESC LIMIT 1"。
作业中MCP工具不可用
如果作业报告“我无法访问[MCP服务器]工具”,请检查工具名称格式:
正确格式:mcp__<server>__<tool>(双下划线)
- 示例:
mcp__email__send_email
- 示例:
mcp__playwright__navigate
自动规范化格式:以下格式会自动转换:
email:send_email → mcp__email__send_email
email.send_email → mcp__email__send_email
提示:使用空工具数组[]允许使用所有可用工具,或检查响应中的tool_warnings以查看是否有工具被规范化。
服务器立即断开连接
检查标准错误输出以查找错误。常见原因包括:
- 缺少依赖项(运行
pip install -r requirements.txt)
- Python版本低于3.11
- 数据库被其他进程锁定
Git "dubious ownership"错误
如果自动部署失败并出现“fatal: detected dubious ownership in repository”错误,请执行以下操作:
git config --system --add safe.directory /opt/mcpserver/app
当仓库的所有者与运行部署脚本的用户不同时,会出现此错误。
Playwright浏览器未找到
如果Playwright因找不到浏览器而失败,请手动安装Chromium:
apt install -y libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 libcups2t64 \
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 \
libpango-1.0-0 libcairo2 libasound2t64
su - mcpuser
cd /opt/mcpserver/app
source venv/bin/activate
playwright install chromium
验证是否正常工作:
python -c "from playwright.sync_api import sync_playwright; p = sync_playwright().start(); b = p.chromium.launch(); print('OK'); b.close(); p.stop()"
🏗️ 架构
┌─────────────────────────────────────────────────────────────┐
│ MCP Server │
│ ┌───────────┐ ┌───────────────┐ ┌───────────────────┐ │
│ │ Job CRUD │ │ Scheduler │ │ Run Processor │ │
│ │ Tools │ │ (60s loop) │ │ (async) │ │
│ └─────┬─────┘ └───────┬───────┘ └─────────┬─────────┘ │
│ │ │ │ │
│ └────────────────┼────────────────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ SQLite │ │
│ │ (jobs.db) │ │
│ └────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Claude Agent │ │
│ │ SDK │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘
服务器运行三个并发的异步组件:
- MCP服务器 - 通过FastMCP公开25多个工具。
- 调度器循环 - 每60秒检查一次是否有到期的作业。
- 运行处理器循环 - 处理待处理的运行并执行它们。
📦 关键依赖项
fastmcp - MCP协议实现
claude-agent-sdk - 运行作业的核心执行引擎
croniter - Cron表达式解析
sendgrid, requests - 电子邮件提供商集成
uvicorn - 用于SSE传输的ASGI服务器
pyngrok - 用于从claude.ai进行远程访问的ngrok隧道
python-dotenv - .env文件加载
PyJWT - 用于OAuth的JWT令牌处理
beautifulsoup4 - 网页抓取
boto3 - AWS集成
playwright - 浏览器自动化