# Qingflow MCP Server

基于用户态 `token` 与 `wsId` 的 Qingflow MCP 适配层，覆盖表单数据、文件、应用、流程、视图、报表、门户、导航，以及高层解决方案搭建能力。

## 特性

- 用户账号登录，复用现有 Qingflow 权限模型
- 支持直接注入用户 `token` 建立 MCP 会话
- 显式工作区选择，避免误用默认工作区
- 覆盖表单数据增查改删
- 覆盖内部成员 / 内部部门 / 外部联系人查询
- 覆盖应用 CRUD、表单结构读写、应用发布
- 覆盖应用包（Tag）CRUD
- 覆盖顶层 Navigation 导航配置
- 覆盖评论、通过、拒绝、回退、转交、改派、加签
- 覆盖门户 CRUD、门户发布
- 覆盖文件上传凭证获取与本地文件上传
- 覆盖流程设计与流程调试
- 覆盖视图设计与视图子类型配置
- 覆盖报表设计与图表排序/配置
- 覆盖 `solution_design_session` 任务级设计会话，支持分阶段 DSL 设计与 finalize
- 覆盖 staged 高层搭建工具与统一入口：`solution_schema_example`、`solution_build_app`、`solution_build_flow`、`solution_build_all`、`solution_build_views`、`solution_build_analytics_portal`、`solution_build_navigation`

## 不支持

- 外部部门查询
- 应用包分组管理
- 应用包内导航项配置
- 签名图片、eSign
- 外部成员候选查询（转交/加签）
- 短信验证码、多因素登录挑战
- OpenAPI `accessToken` / `serviceToken`

## 安装

```bash
cd <repo_root>/qingflow-support/mcp-server
python3 -m venv .venv
./.venv/bin/pip install -e '.[dev]'
```

## 本地智能体安装（npm）

如果你要给本地 agent / gateway 安装这套 MCP，而不是手动管理 Python 环境，可以直接用 npm：

```bash
cd <repo_root>/qingflow-support/mcp-server
npm install -g .
```

或只在当前目录安装：

```bash
cd <repo_root>/qingflow-support/mcp-server
npm install
```

如果你想把当前版本封装成可分发的 npm 安装包：

```bash
cd <repo_root>/qingflow-support/mcp-server
npm run pack:npm
```

产物会输出到：

```bash
dist/npm/josephyan-qingflow-mcp-<version>.tgz
```

另一台机器可直接安装这个 tgz：

```bash
npm install /absolute/path/to/dist/npm/josephyan-qingflow-mcp-<version>.tgz
```

安装时会自动创建 `.npm-python/`，并在里面执行 `pip install .`。

如果你是全局安装，完成后本机可直接使用这两个入口：

```bash
qingflow-app-user-mcp
qingflow-app-builder-mcp
```

如果你只是对当前源码 checkout 执行了 `npm install`，请直接运行：

```bash
node ./npm/bin/qingflow-app-user-mcp.mjs
node ./npm/bin/qingflow-app-builder-mcp.mjs
```

适合这类本地 stdio MCP 客户端：

- Claude Desktop
- Cline / Cursor
- OpenClaw 风格的本地 agent / gateway
- 其它支持 `command + args + env` 的本地智能体

详细说明见：

- `docs/local-agent-install.md`

## 运行

```bash
cd <repo_root>/qingflow-support/mcp-server
./qingflow-app-user-mcp
```

或：

```bash
qingflow-app-user-mcp
qingflow-app-builder-mcp
```

## 配置

- `QINGFLOW_MCP_DEFAULT_BASE_URL`：默认 Qingflow 后端地址
- `QINGFLOW_MCP_DEFAULT_QF_VERSION`：默认 `qfVersion` 路由值
- `QINGFLOW_MCP_HOME`：本地会话目录，默认 `~/.qingflow-mcp`
- `QINGFLOW_MCP_DESIGN_HOME`：`solution_design_session` 本地设计会话目录，默认 `~/.qingflow-mcp/design-sessions`
- `QINGFLOW_MCP_BUILD_HOME`：分阶段 build assembly 目录，默认 `~/.qingflow-mcp/build-assemblies`
- `QINGFLOW_MCP_BOOTSTRAP_HOME`：`solution_bootstrap` 运行记录目录，默认 `~/.qingflow-mcp/bootstrap-runs`
- `QINGFLOW_MCP_TIMEOUT_SECONDS`：HTTP 超时秒数，默认 `30`

会话元数据写入 `~/.qingflow-mcp/profiles.json`。

配置文件会按下面的顺序查找第一个存在的文件：

1. `QINGFLOW_MCP_CONFIG_PATH`
2. 当前工作目录 `qingflow-mcp.config.json`
3. `~/.qingflow-mcp/config.json`
4. `/etc/qingflow-mcp/config.json`

`auth_login` / `auth_use_token` 的路由优先级是：

1. 工具参数里显式传入的 `base_url` / `qf_version`
2. 全局默认 `QINGFLOW_MCP_DEFAULT_BASE_URL` / `QINGFLOW_MCP_DEFAULT_QF_VERSION` 或配置文件里的 `default_base_url` / `default_qf_version`（同层里环境变量优先）

敏感 token 优先写入系统 keychain；如果当前环境没有可用 keychain，会自动退回仅内存会话。

示例配置文件：

```json
{
  "default_base_url": "https://qingflow.com/api",
  "default_qf_version": "canary"
}
```

## 会话模型

1. 调用 `auth_login` 或 `auth_use_token`
2. 调用 `workspace_list`
3. 调用 `workspace_select`
4. 再调用业务工具

未选择工作区前，除 `auth_*` 和 `workspace_*` 外的工具都会报错。

说明：
- `auth_use_token` 是 MCP 工具，不是额外的 HTTP 请求头。业务请求真正发送的是 `token`、`wsId`，以及必要时的 `Cookie: qfVersion=...`
- 生产环境如果 `/user` 响应里没有返回版本路由，`workspace_select` 会用工作区的 `systemVersion` 兜底到当前会话；因此不要跳过 `workspace_select`

## 工具清单

### 认证与工作区

- `auth_login`
- `auth_use_token`
- `auth_whoami`
- `auth_logout`
- `workspace_list`
- `workspace_select`
- `workspace_set_plugin_status`

### 表单数据

- `record_field_resolve`
- `record_query_plan`
- `record_write_plan`
- `record_query`
- `record_aggregate`
- `record_create`
- `record_get`
- `record_update`
- `record_delete`

说明：
- `record_query` 是推荐的统一读入口，按 `query_mode` 路由到列表、单条详情或汇总分析
- `record_query(list)` 默认返回扁平宽表行，适合浏览、导出和分析；当选列超过后端单次字段上限时，工具会自动分批查询并按 `apply_id` 合并；单条完整详情请用 `record_get`
- `record_write_plan` 用于复杂写入前的静态预检，支持 `fields` 这种更友好的写法；每个字段都会返回 `write_format.support_level`，区分 `full`、`restricted`、`unsupported`
- `record_create` 和 `record_update` 都支持 `verify_write=true`，会在写入后读回记录并检测字段是否被静默丢弃、清空或数量不足
- 子表字段现在支持行对象写法，例如 `{"明细": [{"产品名称": "企业版", "数量": 2}]}`；更新既有子表行时可额外传 `rowId` / `row_id` / `__row_id__`
- `record_get` / `record_create` / `record_update` 继续保留，用于精确详情读取和明确写入

### 文件

- `file_get_upload_info`
- `file_upload_local`

### 应用

- `package_list`
- `package_get`
- `package_get_base`
- `package_create`
- `package_update`
- `package_delete`

- `navigation_list_published`
- `navigation_list_draft_page`
- `navigation_list_draft_all`
- `navigation_get_detail`
- `navigation_get_status`
- `navigation_set_visible`
- `navigation_create`
- `navigation_update`
- `navigation_delete`
- `navigation_publish`
- `navigation_reorder`

说明：
- `workspace_set_plugin_status` 会调用工作区插件安装接口，可用于安装或卸载指定插件。
- staged 高层 build 在创建导航时如果遇到 `50004 插件未安装`，会先尝试自动安装导航插件，再重试一次导航创建。

- `record_comment_add`
- `record_comment_list`
- `record_comment_mention_candidates`
- `record_comment_mark_read`
- `record_comment_stats`
- `task_statistics`
- `task_list`
- `task_list_grouped`
- `task_urge`
- `task_approve`
- `task_reject`
- `task_rollback_candidates`
- `task_rollback`
- `task_transfer`
- `task_transfer_candidates`

- `app_list`
- `app_get_base`
- `app_update_base`
- `app_get_form_schema`
- `app_create`
- `app_delete`
- `app_publish`

说明：
- `app_get_base` 和 `app_get_form_schema` 默认返回紧凑摘要，适合智能体消费；如果需要完整原始 `baseInfo/form schema`，显式传 `include_raw=true`
- 原始表单 schema 写入接口 `app_update_form_schema` 已不再对外暴露；对外应优先使用 `solution_*` 或更高层的 builder 流程，而不是直接拼接原生 schema

### 流程

- `workflow_list_nodes`
- `workflow_get_node_detail`
- `workflow_get_global_settings`
- `workflow_get_future_nodes`
- `workflow_get_future_nodes_app`
- `workflow_get_qsource_active`
- `workflow_get_qsource_passive`
- `workflow_get_editable_question_ids`
- `workflow_get_print_nodes`

说明：
- `workflow_*` 的低层写接口已不再对外暴露；对外应优先使用 `solution_build_flow`

### 视图

- `view_list`
- `view_list_flat`
- `view_get_config`
- `view_get_base_info`
- `view_list_questions`
- `view_list_associations`
- `view_board_get_lane_base_info`
- `view_get_future_nodes`
- `view_get_workflow_status`

说明：
- `view_*` 的低层写接口已不再对外暴露；对外应优先使用 `solution_build_views`

### 报表

- `qingbi_report_list`
- `qingbi_report_get_base`
- `qingbi_report_get_config`
- `qingbi_report_get_data`
- `qingbi_report_delete`
- `qingbi_report_reorder`
- `qingbi_report_list_fields`
- `qingbi_report_list_field_options`

说明：
- `qingbi_report_*` 的原始创建和配置写入接口已不再对外暴露；对外应优先使用 `solution_build_analytics_portal`

### 门户

- `portal_list`
- `portal_get`
- `portal_create`
- `portal_update`
- `portal_delete`
- `portal_publish`

### 通讯录

- `directory_search`
- `directory_list_internal_users`
- `directory_list_all_internal_users`
- `directory_list_internal_departments`
- `directory_list_all_departments`
- `directory_list_sub_departments`
- `directory_list_external_members`

### 解决方案编排

- `solution_design_session`
- `solution_schema_example`
- `solution_build_all`
- `solution_build_app`
- `solution_build_flow`
- `solution_build_views`
- `solution_build_analytics_portal`
- `solution_build_navigation`
- `solution_build_status`

`solution_design_session` 用来承接上层 AI 的分阶段设计过程：

- `start`：创建或读取一个 task-scoped 设计会话；未传 `session_id` 时会自动生成并返回
- `submit_stage`：提交 `discover/design/experience` 阶段 patch，返回当前 gate 状态和缺失项
- `get`：读取当前设计会话快照
- `finalize`：把分阶段 patch 合并成完整 `SolutionSpec`，并返回可执行 `execution_plan`
- 这是纯本地设计会话工具，不调用 Qingflow 后端，也不会影响智能体处理其他任务

公开高层搭建入口已经改成 staged build：

- `solution_schema_example`
  - 返回指定 stage 的精确 DSL 示例、必填字段、常见错误和建议的下一次调用
  - 复杂 payload 生成前应先调用

- `solution_build_all`
  - 负责把完整 `SolutionSpec` 自动拆分到 `app_flow/views/analytics_portal/navigation`
  - 支持 `preflight/plan/apply/repair`
  - `preflight/plan` 未传 `build_id` 时会自动生成，并返回 `build_id` 与 `suggested_next_call`
  - `verify=true` 时会在成功后自动做回读校验
  - `repair` 时如果未传 `solution_spec`，会优先复用 `build_id` 已落盘的 manifest

- `solution_build_app`
  - 负责应用包、应用、字段、表单布局、角色、模拟数据
  - 支持 `preflight/plan/apply/repair`
  - `preflight/plan` 未传 `build_id` 时会自动生成，并返回 `build_id` 与 `suggested_next_call`
  - `target.package_tag_id` 可选；传入后会复用已有应用包

- `solution_build_flow`
  - 负责流程、生命周期阶段与相关角色
  - 支持 `preflight/plan/apply/repair`
  - `preflight/plan` 未传 `build_id` 时会自动生成，并返回 `build_id` 与 `suggested_next_call`
- `solution_build_views`
  - 负责视图创建、排序、子类型配置、应用发布
  - 支持 `preflight/plan/apply/repair`
  - `preflight/plan` 未传 `build_id` 时会自动生成，并返回 `build_id` 与 `suggested_next_call`
- `solution_build_analytics_portal`
  - 负责 Qingbi 报表、门户布局、筛选器、宫格入口、发布
  - 支持 `preflight/plan/apply/repair`
  - `preflight/plan` 未传 `build_id` 时会自动生成，并返回 `build_id` 与 `suggested_next_call`
- `solution_build_navigation`
  - 负责导航创建、排序与发布
  - 支持 `preflight/plan/apply/repair`
  - `preflight/plan` 未传 `build_id` 时会自动生成，并返回 `build_id` 与 `suggested_next_call`
- `solution_build_status`
  - 纯本地读取 `build_id` 对应的 assembly 状态
  - 返回 `stage_statuses`、`ready_stages`、`next_recommended_stage`、`missing_prerequisites`

设计边界：

- AI 负责生成每个阶段的显式 DSL
- MCP 只做校验、归一化、原生接口转译和执行
- 支持 staged DSL，也支持通过 `solution_build_all` 直接提交完整 `SolutionSpec`

## 示例 MCP client 配置

见 `examples/claude_desktop_config.json`。

如果通过 npm 安装到本机，也可以直接配成：

```json
{
  "mcpServers": {
    "qingflow": {
      "command": "qingflow-app-user-mcp",
      "args": [],
      "env": {
        "QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api"
      }
    }
  }
}
```

## 示例调用顺序

见 `examples/transcript.md`。

## 设计文档

- 工具矩阵与后续扩展方案：`docs/tool-matrix.md`

## 典型用法

### 1. 登录

```json
{
  "profile": "default",
  "base_url": "https://qingflow.example.com",
  "email": "user@example.com",
  "password": "******",
  "persist": true
}
```

### 1b. 直接注入 token

```json
{
  "profile": "default",
  "base_url": "https://qingflow.example.com/api",
  "token": "<user-token>",
  "ws_id": 10001,
  "persist": false
}
```

### 2. 查询工作区

```json
{
  "profile": "default",
  "page_num": 1,
  "page_size": 20,
  "include_external": false
}
```

### 3. 选择工作区

```json
{
  "profile": "default",
  "ws_id": 10001
}
```

### 4. 新增记录

```json
{
  "profile": "default",
  "app_key": "APP_xxx",
  "submit_type": 1,
  "answers": [
    {
      "queId": 1001,
      "queType": 2,
      "values": [{ "value": "hello" }],
      "tableValues": []
    }
  ]
}
```

### 5. 搜索内部成员

```json
{
  "profile": "default",
  "keyword": "张",
  "page_num": 1,
  "page_size": 20,
  "contain_disable": false
}
```

### 6. 拉取全部内部成员

```json
{
  "profile": "default",
  "page_size": 200,
  "max_pages": 100,
  "contain_disable": false
}
```

### 7. 拉取全部部门树

```json
{
  "profile": "default",
  "parent_dept_id": null,
  "max_depth": 20,
  "max_items": 2000
}
```

## 返回约定

- 成功时返回结构化 JSON
- Qingflow 后端报错会原样透出主要错误信息
- token 失效时会自动清除当前 profile 的本地会话，并提示重新登录
