Skip to main content
Glama
README.md
# Schedule MCP · 个人项目排程管理工具

极简风格的个人项目排程管理工具。一个 Python 服务同时承担两种角色:

1. **MCP Server**:通过 [Verdure MCP Platform](https://github.com/maker-community/verdure-mcp-for-xiaozhi) / imcp.pro 等平台接入**小智 AI**,让小智通过语音/对话轻松读写你的排程数据,并基于规则给出事务提醒(剩余的自然语言组织交给小智服务端的 DeepSeek v4 模型)。
2. **REST API + PC 前端**:PC 端浏览器直接访问部署服务的 `/app` 页面(或本地打开 `web/index.html` 并指向部署地址),提供日历 / 甘特图(可编辑)/ 多项目管理 / 待办计划(近期/远期)的直观查看与编辑。

数据存储使用 **SQLite**(零额外依赖),部署后即开即用,无需本地跑任何后端。

参考项目:[shiikun-cn/tarot-mcp](https://github.com/shiikun-cn/tarot-mcp)(MCP-over-HTTP 兼容模式已验证可跑通)。

---

## 功能一览

| 能力 | 说明 |
|---|---|
| 多项目管理 | 项目名称/说明/状态(**规划中/进行中/已归档** 三态)/优先级/起止日期/颜色 |
| 日程排程(甘特) | 项目下任务,支持开始/结束区间、截止日、状态(待办/进行中/完成);**甘特页可直接编辑**:拖拽条块边缘调起止日期、点击条块改属性、新增项目/任务、统一保存 |
| 自动任务状态 | 设置中开启后,按当前日期自动将任务置为 待办(未开始)/进行中/完成(已过期),不再累积"已逾期" |
| 待办计划 | 记录**还没确定的项目/事**:分近期/远期,带优先级(**无日期属性**,不参与提醒) |
| 事务提醒 | 规则引擎实时计算:**已逾期 / 今天到期 / 即将到期(默认未来7天) / 项目临近结束**(以任务/项目为主) |
| 日程查询 | 新增 `get_schedule` 工具:`range=today/week/month` 精简日程,AI 问"今天要做什么"优先用它 |
| 星期定义 | 设置中可选 **周一 / 周日** 开始,统一日历月视图、概览本周周历、"本周"统计口径 |
| PC 前端 | 概览 / 日历 / 甘特 / 项目 / 待办计划 / 提醒 六个视图,单 HTML 文件,无需构建 |
| 数据备份 | `/api/backup` 导出全量 JSON(含设置),`/api/restore` 恢复 |

---

## 项目结构

```
schedule-mcp/
├── app.py                     # Flask 主服务:MCP JSON-RPC 兼容层 + REST API + 前端托管
├── db.py                      # SQLite 数据层 + 设置 + 自动任务状态 + 提醒规则引擎 + 备份/恢复
├── requirements.txt           # 仅 Flask + gunicorn(SQLite 用标准库)
├── Dockerfile                 # 容器镜像(gunicorn 生产启动,PORT 环境变量)
├── .gitignore
├── .github/workflows/
│   └── keep-alive.yml         # 服务保活(Render 免费版必须,详见下文说明)
├── web/
│   └── index.html             # PC 端前端(单文件,无外部依赖)
├── smoke-test.mjs             # 端到端冒烟测试(35 项断言)
├── seed-demo.mjs              # 演示数据播种(日期相对今天)
└── data/                      # SQLite 数据库文件(data/schedule.db,不入库)
```

---

## 数据模型

| 表 | 字段 |
|---|---|
| `projects` | id, name, description, color, status(**planned/active/archived**), priority(1-5), start_date, end_date, created_at, updated_at |
| `tasks` | id, project_id(外键,级联删除), title, description, status(todo/doing/done), priority, start_date, end_date, due_date |
| `todos` | id, text, bucket(soon/later), **completed**(0/1), completed_at, project_id, **priority**(1-5)(无日期属性) |
| `settings` | key-value:`auto_task_status`(0/1)、`week_start`(mon/sun)、`last_auto_date` |

所有日期格式:`YYYY-MM-DD`。提醒**不落库**,由规则引擎实时计算。

**注意**:旧版本的 `paused/completed` 项目状态会在启动时自动迁移为 `planned/archived`;`progress`(任务进度)、`due_date`(待办截止日)字段已在产品层移除(数据库列保留以兼容旧备份,读写与展示均不再使用)。

---

## 自动任务状态规则(设置中开启后生效)

- 今天 **早于** 开始日期 → `todo`(待办)
- 今天 **在** 开始日期 ~ 结束/截止日期之间 → `doing`(进行中)
- 今天 **晚于** 结束/截止日期 → `done`(完成,避免累积"已逾期")
- 任务没有任何日期属性 → 保持原状态
- **新建/更新任务时即时归类**(不受当天幂等限制);全量执行同一天只跑一次(`last_auto_date` 幂等),设置面板"立即执行"会强制运行
- 每次查询任务/日程时也会自动触发(若开启)

---

## MCP 工具清单(16 个,供小智 AI 调用)

| 工具 | 作用 | 典型问题示例 |
|---|---|---|
| `list_projects` | 列出项目(按状态过滤) | "我有哪些项目?" |
| `create_project` | 新建项目 | "帮我建个'装修'项目,11月到12月,最高优先级" |
| `update_project` | 更新项目(状态/时间/优先级…) | "把XX项目标为进行中/已归档" |
| `delete_project` | 删除项目(级联删任务) | "删掉XX项目" |
| `list_tasks` | 列出任务(按项目/状态过滤) | "XX项目有哪些任务?" |
| `create_task` | 项目下新建任务 | "给XX加个任务,明天截止" |
| `update_task` | 更新任务(状态/时间…) | "把XX任务标为完成" |
| `delete_task` | 删除任务 | "删掉XX任务" |
| `list_todos` | 列出待办计划(近期/远期) | "我有哪些待办?" |
| `create_todo` | 新建待办计划(无日期,带优先级) | "记个待办:买机票" |
| `update_todo` | 更新待办计划(完成/改类/优先级) | "把XX划掉" |
| `delete_todo` | 删除待办计划 | "删掉XX" |
| `get_reminders` | 事务提醒(逾期/今天/即将/项目临近结束) | "有什么要提醒我的?" |
| **`get_schedule`** | **精简日程:range=today/week/month(默认 today)** | **"今天要做什么?"优先用这个** |
| `get_schedule_summary` | 日程总览(今天/本周/本月 + 统计) | "整体进度如何?" |
| `get_gantt_data` | 项目排程区间(已归档项目不出现) | "XX项目的时间安排?" |

**返回给 AI 的数据已精简**:所有工具的 metadata **不包含** `color`、`created_at`、`updated_at`、`project_color`;任务不含 `progress`、待办计划不含 `due_date`。`content[0].text` 为中文易读文本,metadata 为结构化数据,二者字段一致。

### 如何注释掉 / 修改工具(重要)

工具的**定义全部集中在 `app.py` 的 `TOOLS` 列表**(约第 260-560 行),一个工具对应列表中的一个大括号对象,包含 `name` / `description` / `inputSchema` / `handler` 四个字段。`tools/list` 清单由 `TOOLS` 自动生成,**注释掉即对 AI 隐藏,无需改其他代码**,保存后重启服务生效。

**示例 1:注释掉 `create_project` 工具(不让小智新建项目)**

```python
# app.py 中 TOOLS 列表里,把整个对象包进注释:
    # {
    #     "name": "create_project",
    #     "description": "新建一个项目。…",
    #     "inputSchema": {…},
    #     "handler": tool_create_project,
    # },
```

**示例 2:只修改工具描述(引导 AI 何时使用)**

```python
    {
        "name": "get_reminders",
        "description": "获取事务提醒…。建议每天早上询问用户时优先调用。",
        # ↑ 只改 description 字符串即可
        …
    },
```

**示例 3:只保留"任务提醒"相关工具**

把 `TOOLS` 列表中非提醒类工具(如 `create_project`、`list_tasks`、`create_todo` 等)整体注释掉,只保留 `get_reminders`、`get_schedule`、`get_schedule_summary` 即可。小智端在 Verdure 平台重新探测后会只看到剩余工具。

> 提示:注释后建议在 Verdure 平台**重新保存/刷新**该 MCP 服务器配置以触发重新探测。每次修改 `TOOLS` 后,可先本地重启服务并用 `node smoke-test.mjs` 确认工具清单变化符合预期(断言里写死了 16 个,若注释工具请同步修改该脚本的 `expected` 数组)。

---

## REST API(PC 前端直连,CORS 全开)

| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/health` | 健康检查(keep-alive 探测用) |
| GET/POST | `/api/projects` | 项目列表 / 新建 |
| GET/PUT/DELETE | `/api/projects/<id>` | 项目详情 / 更新 / 删除 |
| GET/POST | `/api/tasks` | 任务列表(`?project_id=&status=`) / 新建 |
| GET/PUT/DELETE | `/api/tasks/<id>` | 任务详情 / 更新 / 删除 |
| GET/POST | `/api/todos` | 待办计划列表(`?bucket=&completed=`) / 新建 |
| GET/PUT/DELETE | `/api/todos/<id>` | 待办计划详情 / 更新 / 删除 |
| GET | `/api/reminders?window_days=7` | 事务提醒 |
| GET | `/api/summary` | 日程总览(含 week_start) |
| GET | `/api/gantt` | 甘特排程数据(不含已归档项目) |
| GET/PUT | `/api/settings` | 读取/修改设置(auto_task_status / week_start) |
| POST | `/api/settings/apply-auto` | 立即执行一次自动任务状态刷新 |
| GET | `/api/backup` | 导出全量备份 JSON(含设置) |
| POST | `/api/restore` | 从备份 JSON 恢复 |
| GET | `/app` | PC 端排程面板(前端页面) |

统一返回 `{"code":0,"data":…}`;错误返回 `{"code":4xx/5xx,"error":"…"}`。可选鉴权:设置环境变量 `API_KEY` 后,所有请求需携带请求头 `X-API-KEY`(`/health` 与 `/app` 除外,保证保活与页面访问不受影响)。

---

## 本地快速运行(验证)

```bash
cd schedule-mcp
python -m venv .venv
# Windows: .venv\Scripts\activate     macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt
python app.py        # 默认监听 0.0.0.0:8080,可用环境变量 PORT 修改
```

> 💡 **gunicorn 仅 Linux 可用**:gunicorn 依赖 Unix 的 `fcntl`,Windows 本地无法直接运行(部署到 Render/Docker 的 Linux 容器不受影响)。**本地调试一律用 `python app.py`**(Flask 开发服务器,功能一致),生产环境由 Dockerfile 使用 gunicorn。

验证:

```bash
# 1) 健康检查
curl http://127.0.0.1:8080/health

# 2) MCP 握手
curl -X POST http://127.0.0.1:8080/ -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'

# 3) 列出工具(应 16 个)
curl -X POST http://127.0.0.1:8080/ -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

# 4) 今日日程(新工具)
curl -X POST http://127.0.0.1:8080/ -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_schedule","arguments":{"range":"today"}}}'

# 5) 事务提醒
curl -X POST http://127.0.0.1:8080/ -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_reminders","arguments":{}}}'

# 6) 设置:星期定义 / 自动任务状态
curl http://127.0.0.1:8080/api/settings
curl -X PUT http://127.0.0.1:8080/api/settings -H "Content-Type: application/json" \
  -d '{"week_start":"sun","auto_task_status":"1"}'
```

浏览器打开 `http://127.0.0.1:8080/app` 即可使用 PC 端面板(同源直连,无需配置);若直接双击打开 `web/index.html`,需在 ⚙ 设置中把 API 地址填为 `http://127.0.0.1:8080`。

附带自测/演示脚本:

```bash
node smoke-test.mjs            # 端到端冒烟测试(35 项断言,含自动状态/设置/字段精简)
node seed-demo.mjs             # 播种演示数据(一个项目+4任务+4待办,日期相对今天)
```

> 重置数据:删除 `data/schedule.db` 后重启服务即可(仓库默认已 gitignore 数据库文件)。

---

## 部署步骤(GitHub → Render → Verdure → 小智AI)

### 第 1 步:上传 GitHub

1. 在 GitHub 新建仓库(如 `schedule-mcp`,Private 或 Public 均可)。
2. 推送代码(**不要**提交 `data/*.db`,.gitignore 已排除;也建议不提交 `.venv/`):

```bash
cd schedule-mcp
git init
git add .
git commit -m "feat: schedule-mcp 个人排程 MCP 服务"
git branch -M main
git remote add origin https://github.com/<你的用户名>/schedule-mcp.git
git push -u origin main
```

### 第 2 步:部署到 Render(免费)

1. 打开 [render.com](https://render.com) → New → **Web Service** → 连接你的 GitHub 仓库。
2. 配置:
   - **Name**:`schedule-mcp`(任意)
   - **Environment**:`Python 3`(或 Docker,若选 Docker 则无需下面 Build/Start)
   - **Build Command**:`pip install -r requirements.txt`
   - **Start Command**:`gunicorn -w 2 -b 0.0.0.0:${PORT:-8080} app:app --timeout 30`
   - **Instance Type**:Free
   - **Environment Variables**:`API_KEY=你的随机密钥`(可选,设置了则 MCP/REST 请求都需带 `X-API-KEY`);`TZ=Asia/Shanghai`(必填,否则时区不正确)
3. Deploy,等待构建完成。得到服务地址:`https://schedule-mcp.onrender.com`。
4. 验证:浏览器打开 `https://schedule-mcp.onrender.com/health` 应返回 `{"code":0,"status":"ok",…}`。

> ⚠️ Render 免费版注意事项:
> - 免费实例约 15 分钟无流量会**休眠**,下次请求需冷启动(首次慢约 5-30 秒)。→ 必须开启 keep-alive(见下)。
> - 免费版文件系统是**临时的**:重新部署/重启会清空 SQLite 数据。个人自用建议在 ⚙ 设置里定期「导出备份」,或接受重建后重新录入(对日常短期排程影响不大)。

### 第 3 步:开启 keep-alive 保活(Render 免费版必须)

1. 编辑 `.github/workflows/keep-alive.yml`,把 `URL` 改为你的服务地址(如 `https://schedule-mcp.onrender.com/health`)。
2. (可选)若设置了 `API_KEY`,在 GitHub 仓库 **Settings → Secrets and variables → Actions → New repository secret** 添加 `SERVICE_API_KEY`,并在 workflow 中启用该行。
3. 推送修改。Actions 将每 10 分钟 ping 一次 `/health`,服务保持活跃。
4. 特别地,若 workflow 保活失效:使用cron-job.org 向服务地址发送 GET 请求。将访问频率设置为 每 10 到 14 分钟 一次。

### 第 4 步:在 Verdure MCP Platform 添加 MCP 服务器

1. 登录 Verdure MCP Platform(若还没有账号,用与参考项目一致的方式注册;平台对接的是小智 / 涂鸦等助手端点)。
2. 进入「添加 / 管理 MCP 服务器」页面,选择 **HTTP 类型**(Verdure 探测的是 `POST /` 的 JSON-RPC,本服务已按此实现,与 tarot-mcp 完全一致)。
3. 填写:
   - **服务地址(URL)**:`https://schedule-mcp.onrender.com/`(根路径即可,服务在 `/` 处理 MCP JSON-RPC)
   - 若设置了鉴权,填写 `X-API-KEY` 对应的密钥(或按平台要求填 header)
4. 保存后平台会自动 `initialize` + `tools/list` 探测,应能看到上方 16 个工具。如果工具列表为空,检查服务 `/health` 是否可访问、地址末尾是否误加了 `/api` 等路径。
5. 将添加的 MCP 服务器**绑定到你的小智 AI 助手**(平台内选择助手 → 关联该服务器)。

### 第 5 步:小智 AI 语音验证

对小智说类似以下指令,测试读 / 写:

- "今天要做什么?" / "今天有什么安排?" → 触发 `get_schedule`(优先)
- "有什么需要提醒我的" → 触发 `get_reminders`
- "这周有什么安排?" / "这个月有什么安排?" → 触发 `get_schedule(range=week/month)`
- "记个待办计划:买机票,高优先级" → 触发 `create_todo`
- "新建一个项目叫装修,11 月到 12 月,优先级最高" → 触发 `create_project`
- "给装修项目加个任务:确定设计方案,12 月 1 日截止" → 触发 `create_task`
- "我有哪些项目?XX项目进度怎么样" → 触发 `list_projects` / `list_tasks` / `get_gantt_data`
- "把XX任务标为完成" → 触发 `update_task`

---

## keep-alive 说明(研究结论)

**结论:keep-alive 不是 MCP 协议要求,也不是 Verdure MCP Platform 的要求;它只在"服务部署于无流量即休眠的免费托管(如 Render Free / Railway 免费额度)"时才必须。**

推理依据:

1. 参考项目 `tarot-mcp` 中的 `keep-alive.yml` 是 **GitHub Actions** 定时任务,每 10 分钟用 `curl` GET 一次 `/health`,其 URL 指向 `https://tarot-mcp.onrender.com/health` —— 这是针对 **Render 免费实例约 15 分钟无流量即休眠**的保活手段。
2. MCP 协议本身(initialize / tools/list / tools/call)没有任何保活要求;Verdure 平台是"对接 + 远程注入"的管理层,它负责在**你的服务在线时**把工具注入小智,不会替你启动休眠中的外部服务。
3. 因此:
   - 部署在 **Render Free / Railway 免费额度**等会休眠的平台 → **必须保留 keep-alive**(否则小智调用工具时冷启动慢或超时,体验很差);
   - 部署在 **付费常驻实例**(Render Starter+、云服务器等)或 **Verdure 自带托管**(如平台支持)→ 可删除 `.github/workflows/keep-alive.yml`,无任何副作用。
4. 本项目默认**保留并适配** keep-alive.yml(URL 需改成你的服务地址),因为参考链路用的是 Render 免费版,与你的部署方式一致。

---

## 常见问题

**Q:部署后小智说"没有可用的工具"?**
A:先确认 `https://<你的服务>/health` 可访问;再在 Verdure 平台重新保存/刷新服务器配置触发重新探测。注意地址填根路径(`/`),不要带 `/api`。

**Q:怎么让 AI 只做任务提醒,少一些增删改工具?**
A:见上文「如何注释掉 / 修改工具」——在 `app.py` 的 `TOOLS` 列表里注释掉不需要的工具即可,注释后重启服务、并在 Verdure 重新探测。

**Q:自动任务状态会把我手动标的完成状态改掉吗?**
A:开启后,任务状态完全由日期推导(未开始→待办、进行中→进行中、已过期→完成)。若想手动控制,关掉设置里的开关即可,关闭后仅保留手动修改。

**Q:Render 免费版重启后数据丢了?**
A:免费实例文件系统是临时的。在 ⚙ 设置 → 导出备份 定期保存 JSON;下次重建后用「从备份恢复」导入即可(备份含设置)。

**Q:怎么加 API 鉴权?**
A:Render 环境变量加 `API_KEY=xxx`。此后 MCP(POST /)与 REST(/api/*)都要求 `X-API-KEY` 头;前端在 ⚙ 设置里填 Key,keep-alive workflow 配置 secret。

**Q:可以在别处部署吗?**
A:可以,任何能跑 Python 的托管都行(Railway / Fly.io / 云服务器 / 内网 NAS)。SQLite 文件在 `data/`,挂持久卷可避免数据丢失。

**Q:如何查看小智到底调用了哪些工具?**
A:Render 日志(Logs)中会打印每次 tools/call 的参数与结果;也可以在 `/` 的 GET 响应里看工具清单。

---

## 技术要点(与 tarot-mcp 保持一致的兼容性)

- MCP 走 **JSON-RPC over HTTP**(单端点 `POST /`),实现 `initialize`(protocolVersion 2024-11-05)、`notifications/initialized`、`tools/list`、`tools/call`、`ping`,未知方法返回空结果,与 tarot-mcp 兼容补丁一致,已被 Verdure / imcp.pro 类平台验证可用。
- CORS 全开(`Access-Control-Allow-Origin: *`),PC 端 HTML 可跨域直连 REST API。
- SQLite 使用 WAL 模式 + 每请求独立连接,配合 gunicorn 多 worker 安全读写。
- 工具返回 `content[0].text`(中文易读)+ `metadata`(结构化 JSON,已剔除 color/created_at/updated_at/project_color 等 AI 无关字段),兼顾 AI 理解与机器解析。
- 设置(自动任务状态 / 星期定义)存于服务端 `settings` 表,前端面板与 MCP 查询共用同一配置;"本周"统计与前端日历、周历全部遵循该星期定义。