Skip to main content
Glama
zhouweico

mcp-yearning

by zhouweico
README.md
# mcp-yearning

Yearning MCP Server - 让 AI 助手能够查询和管理 [Yearning](https://github.com/cookieY/Yearning) SQL 审核平台的工单与查询:浏览数据源与表结构、提交并审核 SQL 工单、执行只读查询等。

基于 Yearning REST / WebSocket API(`/api/v2`,JWT Bearer 认证);列表类与查询执行走 WebSocket,其余为 REST。

## 特性

- **多协议传输**:`stdio`(默认)、`sse`、`streamable-http`,一套代码适配本地与远程场景
- **接口认证**:HTTP 传输支持 Bearer Token 保护,未授权请求返回 `401`
- **原生账号密码登录**:用户名/密码登录换取 JWT,`401` 时自动重新登录并重放请求,无需手工维护 Token
- **JWT 自动管理**:登录态缓存并在 8 小时过期前(提前 0.5h)主动续登,全程无感
- **REST + WebSocket 统一封装**:列表类接口(我的工单、审核列表、评论)与查询执行走 WebSocket,已封装为「连接→发一帧→收一帧→关闭」的伪 REST;查询执行额外用 msgpack 编解码
- **信封解析**:Yearning 恒返回 HTTP 200 + 信封 `{payload, code:1200, text}`;非 1200 视为业务错误
- **Yearning 原生概念**:直接以 `数据源 / 库 / 表 / SQL 工单 / 审核流程` 组织操作,读写一体
- **危险操作防护**:审核工单(agree)等高危操作带 `destructiveHint` 注解,撤回工单带 `idempotentHint` 注解,均需明确动作参数
- **破坏性操作 MRTR 确认**:同意工单(agree)等高危操作通过 MCP 2.0 Elicitation 机制弹出确认表单,需用户明确同意后才执行
- **MCP Resources**:以 `yearning://` URI 暴露用户信息、数据源列表等只读元数据,客户端可直接读取
- **Stateless HTTP**:支持无状态 HTTP 模式,每次请求独立处理、无会话状态,适合 Serverless / 多副本部署
- **灵活部署**:`uvx` 免安装运行、Docker 构建即用

## 前置准备

准备一个可访问的 Yearning 实例。你需要准备:

- Yearning 地址(如 `http://localhost:8000`)
- 登录用户名 / 密码(常规账号或 LDAP 账号)

> 账号在 Yearning 内的角色决定你能看到哪些数据源、能提交/审核哪些工单;MCP Server 本身不做任何鉴权,仅原样转发请求。

## 快速开始

### MCP 客户端(stdio,本地)

以 Claude Code 为例,在项目 `.mcp.json` 或全局 `~/.claude.json` 中添加:

```json
{
  "mcpServers": {
    "yearning": {
      "type": "stdio",
      "command": "uvx",
      "args": ["mcp-yearning"],
      "env": {
        "YEARNING_URL": "http://localhost:8000",
        "YEARNING_USERNAME": "your-user",
        "YEARNING_PASSWORD": "your-password",
        "YEARNING_LOGIN_TYPE": "general",
        "YEARNING_READ_ONLY": "false"
      }
    }
  }
}
```

> Cursor、OpenCode、Claude Desktop 等客户端的配置格式相同,核心均为 `command: uvx` + `args: ["mcp-yearning"]`,按各客户端语法填入 `YEARNING_*` 环境变量即可。

### Docker(公开镜像,免构建)

已发布公开镜像 `ghcr.io/zhouweico/mcp-yearning:latest`,无需本地构建。下面以 Claude Code 为例,说明如何用 `docker` 命令运行并配置 mcp-yearning。

**方式一:stdio(由客户端拉起容器,适合本地集成)**

在 Claude Code 的 `.mcp.json` 中直接用 `docker` 作为启动命令,客户端会以 stdio 管道与容器内服务通信:

```json
{
  "mcpServers": {
    "yearning": {
      "type": "stdio",
      "command": "docker",
      "args": ["run", "-i", "--rm", "ghcr.io/zhouweico/mcp-yearning:latest"],
      "env": {
        "YEARNING_URL": "http://your-yearning:8000",
        "YEARNING_USERNAME": "your-user",
        "YEARNING_PASSWORD": "your-password"
      }
    }
  }
}
```

> 必须带 `-i`(保持 stdin 管道),否则容器内的 stdio 服务无法与客户端通信。

**方式二:HTTP + 认证(容器独立运行,客户端远程连接,适合多客户端共享)**

先启动容器:

```bash
docker run -d -p 8080:8080 \
  -e MCP_TRANSPORT=streamable-http \
  -e MCP_AUTH_TOKEN=your-strong-token \
  -e YEARNING_URL=http://your-yearning:8000 \
  -e YEARNING_USERNAME=your-user \
  -e YEARNING_PASSWORD=your-password \
  ghcr.io/zhouweico/mcp-yearning:latest
```

再在 Claude Code 的 `.mcp.json` 中通过 HTTP 连接:

```json
{
  "mcpServers": {
    "yearning": {
      "type": "streamable-http",
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer your-strong-token"
      }
    }
  }
}
```

## 可用工具

### 只读工具(14 个)

按业务域分组排列:元数据 → SQL 工单 → 查询与评论。

| 工具 | 分组 | 说明 | 对应 API |
|------|------|------|----------|
| `yearning_user_info` | 元数据 | 当前用户信息、可查询数据源 | `GET /api/v2/fetch/userinfo` |
| `yearning_list_sources` | 元数据 | 列出有权限的数据源 | `GET /api/v2/fetch/source` |
| `yearning_list_databases` | 元数据 | 数据源下的库列表 | `GET /api/v2/fetch/base` |
| `yearning_list_tables` | 元数据 | 库下的表列表 | `GET /api/v2/fetch/table` |
| `yearning_table_fields` | 元数据 | 表结构(字段 + 索引) | `GET /api/v2/fetch/fields` |
| `yearning_sql_check` | SQL 工单 | 提交前 SQL 审核检测(`order_type`: ddl / dml) | `PUT /api/v2/fetch/test` |
| `yearning_my_orders` | SQL 工单 | 我的工单列表(分页/状态/关键字过滤) | `WS /api/v2/common/list` |
| `yearning_order_detail` | SQL 工单 | 工单详情(SQL 明细 + 完整 SQL) | `GET /api/v2/fetch/detail` + `/fetch/sql` |
| `yearning_order_timeline` | SQL 工单 | 工单审核时间线与流程步骤 | `GET /api/v2/fetch/timeline` + `/fetch/steps` |
| `yearning_rollback_sql` | SQL 工单 | 获取工单回滚 SQL | `GET /api/v2/fetch/roll` |
| `yearning_audit_orders` | SQL 工单 | 待我审核的工单列表 | `WS /api/v2/audit/order/list` |
| `yearning_query_status` | 查询与评论 | 查询审核开关 + 我的查询工单状态 | `GET /api/v2/fetch/is_query` + `/fetch/query_status` |
| `yearning_run_query` | 查询与评论 | 执行只读 SELECT 查询(不修改数据;Yearning 留存查询审计记录) | `WS /api/v2/query/results`(msgpack) |
| `yearning_order_comments` | 查询与评论 | 读取工单评论 | `WS /api/v2/fetch/comment` |

### 写工具(5 个)

按工单生命周期排列:提交 → 撤回 → 审核 → 查询申请 → 评论。

| 工具 | 分组 | 说明 | 对应 API |
|------|------|------|----------|
| `yearning_submit_order` | SQL 工单 | 提交 SQL 工单(DDL/DML) | `POST /api/v2/common/post` |
| `yearning_undo_order` | SQL 工单 | 撤回自己未执行的工单 | `GET /api/v2/fetch/undo` |
| `yearning_audit_order` | SQL 工单 | 审核工单:agree/reject/undo(agree 即批准变更落地,高危) | `POST /api/v2/audit/order/state` |
| `yearning_submit_query_order` | 查询与评论 | 提交数据查询申请 | `POST /api/v2/query/post` |
| `yearning_post_comment` | 查询与评论 | 发表工单评论 | `POST /api/v2/fetch/comment` |

> **未提供的操作**:管理端(admin)能力(用户 / 数据源 / 规则管理)与 AI 辅助(text2sql / advisor)为后续 Phase,未包含在本期;此类操作请通过 Yearning 控制台人工执行。
>
> **只读 / 写的区别**:上表「只读工具」在 `YEARNING_READ_ONLY=true` 下**仍然可用**;「写工具」在该模式下会被**完全排除**——不出现在 `tools/list` 中,Agent 既看不到也无法调用(注册期排除,非运行期拦截)。这样生产环境开启只读后,Agent 只能查询、绝无意外变更工单的风险。
>
> **审核/撤回为危险操作**:`yearning_audit_order`(agree)会批准一条 DDL/DML 变更在数据源执行,`yearning_undo_order` 会撤回工单;调用时务必明确动作参数,避免对话中的误操作直接落到生产。
>
> **MRTR 确认**:`yearning_audit_order`(agree)为破坏性操作,执行前会通过 MCP 2.0 Elicitation 弹出确认表单,需用户明确同意后才执行。若客户端不支持 Elicitation(如 stdio 模式),则降级为直接执行。

## 配置

### 环境变量

**MCP 传输与认证**

| 变量 | 说明 | 默认值 |
|------|------|--------|
| `MCP_TRANSPORT` | 传输协议:`stdio` / `sse` / `streamable-http` | `stdio` |
| `MCP_HOST` | HTTP 传输监听地址(stdio 忽略),默认仅本地回环;对外暴露需显式设置并务必配置 `MCP_AUTH_TOKEN` | `127.0.0.1` |
| `MCP_PORT` | HTTP 传输监听端口(stdio 忽略) | `8080` |
| `MCP_AUTH_TOKEN` | 设置后启用 Bearer Token 认证,保护 HTTP 接口 | -(不鉴权) |
| `MCP_STATELESS_HTTP` | 启用无状态 HTTP 模式,适合 Serverless 部署(详见下方说明) | `false` |
| `MCP_LOG_LEVEL` | 日志级别:`debug`/`info`/`warning`/`error` | `info` |

**Yearning 连接**

| 变量 | 说明 | 默认值 |
|------|------|--------|
| `YEARNING_URL` | Yearning 地址 | `http://localhost:8000` |
| `YEARNING_USERNAME` | 登录用户名(必填) | - |
| `YEARNING_PASSWORD` | 登录密码(必填) | - |
| `YEARNING_LOGIN_TYPE` | 登录类型:`general` / `ldap` | `general` |
| `YEARNING_TIMEOUT` | 请求超时(秒) | `30` |
| `YEARNING_READ_ONLY` | 只读模式,排除全部写工具(适合生产环境) | `false` |
| `YEARNING_INSECURE` | 跳过 TLS 证书验证,用于自签名证书环境(详见下方说明) | `false` |

> 认证凭证只需用户名/密码:客户端首次请求时自动调用 `POST /api/v2/login`(或 `ldap`)换取 JWT 并缓存;收到 `401` 时先尝试重新登录、失败则重放原请求(最多一次),全程无需人工干预。
>
> 注意区分两类凭证:`MCP_AUTH_TOKEN` 保护本 MCP Server 的 HTTP 接口;`YEARNING_USERNAME` / `YEARNING_PASSWORD` 用于登录 Yearning,两者互不相关。

### Yearning 概念说明

Yearning 的 SQL 审核组织层级为:**数据源(source)> 库(database)> 表(table)> SQL 工单(order)> 审核流程(audit)**。

- 提交一条 SQL 变更需先经 `yearning_sql_check` 检测,再用 `yearning_submit_order` 提单;工单按配置的审核流流转,审核人用 `yearning_audit_order` 放行/驳回。
- 线上查询走 `yearning_run_query`(仅 SELECT),需具备查询权限;部分环境开启查询审核后,查询也需先 `yearning_submit_query_order` 申请。
- 工单状态:0 已驳回 / 1 已同意待执行 / 2 待审核 / 3 已完成 / 4 已终止 / 5 执行中 / 6 已撤回。

### 只读模式

设置 `YEARNING_READ_ONLY=true` 可排除全部写工具,仅允许查询,适合生产环境使用:

```json
{
  "env": {
    "YEARNING_READ_ONLY": "true"
  }
}
```

### TLS 证书验证

本服务基于 httpx2 发起 HTTPS 请求,**默认会验证 TLS 证书**(行为与 httpx 一致)。

- 在使用自签名证书或内部 CA 的环境中,HTTPS 请求会因证书校验失败而报错。此时可设置环境变量 `YEARNING_INSECURE=true` 跳过 TLS 证书验证。
- 该选项适用于开发、测试等使用自签名证书的环境。

```json
{
  "env": {
    "YEARNING_INSECURE": "true"
  }
}
```

> **安全警告**:禁用 TLS 证书验证是不安全的,会使得 HTTPS 连接容易受到中间人攻击。**请勿在生产环境中使用**,生产环境应使用受信任的 CA 签发的有效证书。

## 多协议传输

通过 `MCP_TRANSPORT` 选择传输协议:

- **`stdio`(默认)**:标准输入输出,适合 Claude Code、Cursor 等本地 AI 客户端集成。
- **`sse`**:Server-Sent Events,HTTP 传输,端点 `http://<host>:<port>/sse`。
- **`streamable-http`**:Streamable HTTP,端点 `http://<host>:<port>/mcp`。

以 `streamable-http` 启动示例:

```bash
MCP_TRANSPORT=streamable-http \
MCP_HOST=0.0.0.0 MCP_PORT=8080 \
MCP_AUTH_TOKEN=your-strong-token \
mcp-yearning
```

## 接口认证

设置 `MCP_AUTH_TOKEN` 后,所有 HTTP 请求必须携带正确 Token,否则返回 `401`:

```
Authorization: Bearer <MCP_AUTH_TOKEN>
```

也兼容 `X-Auth-Token` / `X-MCP-Token` 请求头。健康检查端点 `GET /health` 免鉴权,返回 `{"status":"ok"}`,用于容器探活。

> `stdio` 传输为本地进程通信,不涉及网络,无需也不会进行 Token 认证。未设置 `MCP_AUTH_TOKEN` 时 HTTP 接口不鉴权,生产环境请务必配置。
>
> 注意区分两类凭证:`MCP_AUTH_TOKEN` 保护本 MCP Server 的 HTTP 接口;`YEARNING_USERNAME` / `YEARNING_PASSWORD` 用于登录 Yearning,两者互不相关。

## MCP Resources

本服务以 MCP 2.0 Resources 暴露只读元数据,客户端可直接通过 URI 读取,无需调用工具:

| Resource URI | 说明 |
|---|---|
| `yearning://user-info` | 当前登录用户信息(含可查询数据源) |
| `yearning://sources` | 数据源列表 |

> Resources 仅暴露只读数据,不涉及任何写操作。

## Stateless HTTP 模式

设置 `MCP_STATELESS_HTTP=true` 可启用无状态 HTTP 模式,每次请求独立处理、不保留会话状态,适合 Serverless 平台(如 AWS Lambda、阿里云函数计算)或多副本无状态部署:

```bash
MCP_TRANSPORT=streamable-http \
MCP_STATELESS_HTTP=true \
MCP_PORT=8080 \
mcp-yearning
```

> Stateless 模式下不支持流式响应(SSE stream),每个 HTTP 请求独立完成工具调用后返回。适合短时、无状态的工具调用场景。

## 容器化部署

### 本地构建(Docker)

```bash
# 构建镜像
docker build -t mcp-yearning:latest .

# 以 streamable-http 运行并启用认证
docker run -d --name mcp-yearning -p 8080:8080 \
  -e MCP_TRANSPORT=streamable-http \
  -e MCP_AUTH_TOKEN=your-strong-token \
  -e YEARNING_URL=http://your-yearning:8000 \
  -e YEARNING_USERNAME=your-user \
  -e YEARNING_PASSWORD=your-password \
  mcp-yearning:latest
```

### Docker Compose

复制 `.env.example` 为 `.env` 并按需修改,然后:

```bash
cp .env.example .env
docker compose up -d
```

`docker-compose.yml` 已内置 `build`(基于本地 `Dockerfile` 构建并标记为 `mcp-yearning:latest`)和健康检查(探测 `/health`),以非 root 用户运行,适合本地开发部署。

## 使用场景示例

配置好后,你可以这样和 AI 对话(每条示例后括注主要涉及的工具):

### 数据源与表结构探查

```
连上 Yearning,告诉我我有哪些数据源可用
```
(`yearning_user_info` + `yearning_list_sources`)

```
看看 order_db 这个数据源下有哪些库,再列出 user 表有哪些字段和索引
```
(`yearning_list_databases` → `yearning_list_tables` → `yearning_table_fields`)

### 提交并跟踪 SQL 变更

```
把这条建表语句在 dev 环境提个工单,先帮我做个 SQL 检测看看有没有问题:
CREATE TABLE t_demo (id INT PRIMARY KEY, name VARCHAR(64));
```
(`yearning_sql_check` → `yearning_submit_order`)

```
我刚提的工单到哪一步了?把审核时间线和当前步骤给我
```
(`yearning_my_orders` → `yearning_order_timeline`)

```
这个工单如果执行出错,回滚 SQL 是什么
```
(`yearning_rollback_sql`)

### 审核人视角

```
列出待我审核的工单
```
(`yearning_audit_orders`)

```
工单 ORD-2026-0001 没问题,帮我通过
```
(`yearning_audit_order`,`tp=agree`)

### 线上只读查询

```
在 order_db 的 user 库里查一下最近 7 天注册的账号,前 100 条
```
(`yearning_run_query`)

```
这个环境的查询为什么被拦了?看看查询审核开关和我的查询工单状态
```
(`yearning_query_status`)

### 协作与审计

```
把 ORD-2026-0001 这个工单的评论都拉出来看看
```
(`yearning_order_comments`)

```
在 ORD-2026-0001 工单下留言:已确认索引已存在,可放行
```
(`yearning_post_comment`)

> 审核 / 撤回等危险操作需明确动作参数;`yearning_audit_order(agree)` 会批准变更在生产数据源执行,请确认无误后再调用。管理端(admin)能力与 AI 辅助工具为后续 Phase,未包含在本期。

## 已知限制

以下为当前实现与 Yearning 接口交互中的已知边界,使用前请留意:

- **WebSocket 工具共 4 个**:`yearning_my_orders`、`yearning_audit_orders`、`yearning_order_comments`(JSON)与 `yearning_run_query`(msgpack)。它们依赖将裸 JWT 放入 `Sec-WebSocket-Protocol` 头完成鉴权;若 Yearning 前置了不透传该请求头、或会校验 subprotocol 合法性的反向代理 / 网关,WebSocket 鉴权会失败。直连 Yearning 不受影响。
- **每次调用新建一条 WebSocket 连接,无复用**:列表类接口被高频调用时存在握手开销(TCP + WS + JWT 解析每回重来)。功能无误,但高并发场景延迟偏高。
- **查询审核开启时 `yearning_run_query` 需先审批**:若 Yearning 开启了查询审核(数据源需先有 `status=2` 的已批准查询工单),未审批前执行查询不会返回结果;本服务会识别该情况并明确提示「请先经 `yearning_submit_query_order` 提交查询申请并审批」,而非返回空结果误导。
- **权限 / token 类失败已转译为友好错误**:无对应数据源权限、token 失效、查询审核未批准、或传入参数不合法时,Yearning 会**不回帧直接关闭连接**。本服务的 WebSocket 封装已增加接收超时,并把底层连接中断转成 `YearningApiError`(说明可能原因),不再向调用方抛出底层栈信息。
- **`yearning_run_query` 请求字段与 Yearning 结构体绑定**:查询请求以 msgpack 编码,键名(`type` / `sql` / `schema`)对应 Yearning `QueryDeal.Ref` 的 Go 字段;若 Yearning 改动了该结构体或引入 msgpack tag,需同步更新 `clients/ws.py` 的打包逻辑。
- **`yearning_sql_check` 仅接受 DDL / DML**:Yearning 的检测接口会拒绝 SELECT 等非工单 SQL(返回「请提交DML语句」)。这是平台设计而非缺陷——SELECT 不走工单流程,请直接使用 `yearning_run_query`。
- **登录类型仅支持 `general` / `ldap`**:受 Yearning 平台限制,OIDC 等第三方 SSO 为浏览器跳转流程,无头客户端无法用账号密码完成,当前未实现(详见配置说明)。
- **部分只读接口使用 `GET` + JSON body**:Yearning 的 `/fetch/*` 等接口(对应 `yearning_list_*`、`yearning_order_detail` 等工具)通过 `GET` 请求携带 JSON body 传参(服务端 `c.Bind` 只读 body,不读 query string)。这不符合常见 HTTP 语义,若前置的反向代理 / 网关 / CDN 会丢弃 GET 请求的 body,这些工具会静默返回空结果(信封仍为 `code==1200`)。透传 body 的代理(如默认配置的 Nginx)不受影响,已在真实部署环境实测正常;如遇列表恒为空,请优先排查代理是否吞掉了 GET body。

## 开发

```bash
pip install -e ".[dev]"
pytest          # respx mock 测试,无需真实 Yearning 环境
ruff check src tests
```

### 实现注记

- 列表类接口(我的工单、审核列表、评论)为 WebSocket(`/common/list`、`/audit/order/list`、`/fetch/comment`),客户端封装为「连接→发一帧→收一帧→关闭」的伪 REST
- 查询执行(`/query/results`)走 WebSocket 且用 msgpack 编解码:请求为 `msgpack.packb({"type": 0, "sql": sql, "schema": schema})`,响应含 `results` / `query_time` / `error` / `status` 等
- HTTP 请求统一 `Authorization: Bearer <JWT>`;WebSocket 将 JWT 放入 `Sec-WebSocket-Protocol`(裸 token,无 Bearer 前缀)
- 响应信封恒为 HTTP 200,结构 `{payload, code, text}`;成功 `code==1200`,非 1200 视为业务错误;未知 `tp` 返回裸字符串 `"Illegal"`

## License

MIT

TDQS

A3.7/5.0

Scored across 14 tools

Disambiguation5/5

Each tool targets a distinct resource and action: user info, source listing, database/table/field metadata, SQL check, order retrieval (my orders, audit orders), order details (detail, timeline, rollback, comments), and query execution. The two order-listing tools (my_orders vs audit_orders) are clearly differentiated by user vs auditor perspective.

Naming Consistency4/5

All tools share the 'yearning_' prefix and use snake_case. Most follow a verb_noun pattern (list_sources, list_tables, run_query) while some are noun-based (table_fields, order_detail). This is a minor deviation but overall predictable and readable.

Tool Count5/5

14 tools is well within the typical range for a domain-specific server. The count matches the platform's complexity and each tool covers a distinct aspect of the SQL audit and query workflow.

Completeness2/5

The tools heavily favor read/query operations but lack critical actions. The description in yearning_sql_check references a 'submit_order' tool that is absent, so the core workflow of submitting SQL for approval is incomplete. Additionally, there is no tool for approving/rejecting orders from the auditor perspective, only listing them. This leaves significant functional gaps.

Maintenance

ActivityStale
ResponsivenessNo issues