Files
mcp-server-demo/docs/crm/MCP工具说明文档.md

556 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MCP for CRM — 工具说明文档
> 服务对象:汽车零部件(小型阀体外壳)智能报价智能体
> 服务定位:CRM 侧客户/询价/报价/商机数据服务(业务写入 + 只读回读 + 受限清理)
> 版本:v1.1(2026-08-09)
---
## 1 服务概览
| 项 | 值 |
|----|----|
| 服务名 | `mcp-for-crm-auto` |
| 框架 | MCPServer(mcp 2.0.0 内置高级框架),`@app.tool()` 装饰器注册 |
| 传输协议 | Streamable HTTP(JSON-RPC 2.0) |
| 远程端点 | `https://crm-data.dongsk.top/mcp` |
| 本地端点 | `http://0.0.0.0:8002/mcp`(`cd src && python3 server.py`) |
| 数据库 | PostgreSQL 15+,库 `smart_quotation_auto`(仅服务层内部经 `db.py` 连接池访问) |
| 工具数 | **10 个** = 6 个业务工具 + 4 个验证/管理工具 |
### 工具一览
| # | 工具 | 功能 | 类型 | 报价环节 |
|---|------|------|------|----------|
| 1 | `get_customer_info` | 查询客户信用/折扣/账期 | 只读 | Step 2 客户查询 |
| 2 | `create_inquiry` | 创建询价单 | 写入 | Step 7 存档 |
| 3 | `save_quotation` | 保存报价单(自动版本+1) | 写入(事务) | Step 7 存档 |
| 4 | `get_quotation_history` | 历史报价/版本链查询 | 只读 | Step 7 前置检查 |
| 5 | `update_opportunity` | 创建/更新商机 | 写入 | Step 7 存档 |
| 6 | `get_inquiry_info` | 询价单整行回读 | 只读 | 存档后验证 |
| 7 | `get_opportunity_info` | 商机回读 | 只读 | 存档后验证 |
| 8 | `get_record_counts` | 三表行数统计 | 只读 | 数据卫生检查 |
| 9 | `purge_test_records` | 受限测试数据清理 | 写入(带防护) | 回归测试清理 |
| 10 | `save_quotation_mock` | 报价写入 CRM(Mock 模拟) | 写入(独立 mock 表) | 模拟报价写入 CRM |
### 单号与状态约定
| 实体 | 单号规则 | 状态机 |
|------|----------|--------|
| 询价单 | `INQ-2026-NNNN`(自增) | `pending` → `quoted`(首次报价自动推进) |
| 报价单 | `QUO-2026-NNNN`(自增) | `draft` → `superseded`(被新版替代);`won` / `lost` 为终态,受保护 |
| 商机 | `OPP-2026-NNNN`(自增) | `lead` → `quoted` → `won` / `lost` |
---
## 2 调用约定
### 2.1 客户端注册(mcpServers)
```json
{
"mcpServers": {
"crm": { "url": "https://crm-data.dongsk.top/mcp" }
}
}
```
### 2.2 兜底命令行客户端(纯标准库)
```bash
python3 scripts/mcp_http_client.py crm <工具名> '<JSON参数>'
# 示例
python3 scripts/mcp_http_client.py crm get_customer_info '{"customer_code":"OEM-2024-003"}'
```
### 2.3 返回通用约定
- 所有工具返回 **JSON 字符串**(`ensure_ascii=False`,中文不转义)。
- 查询类:`{"total": N, "<集合名>": [...]}`;写入/操作类:`{"success": true|false, ...}`。
- 失败统一形态:`{"success": false, "message": "<原因>"}`。
- 金额字段(decimal 列)在 JSON 中为字符串,如 `"180000.00"`;日期 `YYYY-MM-DD`。
- 日期入参格式必须为 `YYYY-MM-DD`,否则返回格式错误。
---
## 3 业务工具详解(6 个)
### 3.1 get_customer_info — 查询客户信息
**功能**:按客户编码精确查询,或按名称、OEM 层级模糊搜索。返回信用等级、折扣率、账期、历史订单统计,用于报价前的客户校验与商务条款确定。
**入参**(全部可选;全部不传 = 返回全表):
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `customer_code` | string | 否 | — | 客户编码,精确匹配,如 `OEM-2024-003` |
| `name` | string | 否 | — | 客户名称,`%模糊%` 匹配 |
| `oem_tier` | string | 否 | — | 层级精确匹配:`OEM` / `Tier1` / `Tier2` |
**返回字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `total` | int | 命中条数 |
| `customers[].customer_code` | string | 客户编码 |
| `customers[].name` | string | 客户名称 |
| `customers[].oem_tier` | string | OEM / Tier1 / Tier2 |
| `customers[].credit_level` | string | 信用等级 A/B/C/D 或「无评级」 |
| `customers[].discount_rate` | decimal | 折扣率(字符串,如 `"0.040"` = 4%) |
| `customers[].payment_days` | int | 账期天数(月结 N 天) |
| `customers[].contact_person` / `phone` / `email` | string | 联系人信息 |
| `customers[].industry` / `region` | string | 行业 / 区域 |
| `customers[].total_orders` | int | 历史订单数 |
| `customers[].total_amount` | decimal | 历史订单总额 |
| `customers[].notes` | string | 备注 |
**返回示例**:
```json
{
"total": 1,
"customers": [
{
"customer_code": "OEM-2024-003",
"name": "某新能源主机厂",
"oem_tier": "OEM",
"credit_level": "A",
"discount_rate": "0.040",
"payment_days": 60,
"contact_person": "刘工",
"phone": "0551-2222-0005",
"email": "liu@oem-nev.example.com",
"industry": "汽车",
"region": "合肥",
"total_orders": 22,
"total_amount": "9800000.00",
"notes": "新能源三电系统配套,增长快",
"created_at": "2026-08-07 17:31:56.244396"
}
]
}
```
---
### 3.2 create_inquiry — 创建询价记录
**功能**:客户提交询价后建档。校验客户存在性,自动生成 `INQ-2026-NNNN` 单号,初始状态 `pending`。
**入参**:
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `customer_code` | string | **是** | — | 客户编码(不存在则失败) |
| `drawing_number` | string | 否 | null | 图纸号 |
| `part_number` | string | 否 | null | 零件号 |
| `annual_volume` | int | 否 | null | 年用量(件/年) |
| `target_price` | float | 否 | null | 客户目标价 ¥ |
| `notes` | string | 否 | null | 备注 |
**返回字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 成功标记 |
| `inquiry_id` | string | 生成的询价单号 |
| `customer_name` | string | 客户名称 |
| `status` | string | 固定 `pending` |
| `message` | string | 提示信息 |
**失败返回**:`{"success": false, "message": "未找到客户 XXX"}`
**返回示例**:
```json
{
"success": true,
"inquiry_id": "INQ-2026-0028",
"customer_name": "某新能源主机厂",
"status": "pending",
"message": "询价记录 INQ-2026-0028 已创建"
}
```
**业务规则**:同一询价单可多次创建(每次生成新单号);「同一询价单多次报价」通过 3.3 `save_quotation` 的版本机制实现,不重复建档。
---
### 3.3 save_quotation — 保存报价单(多版本核心)
**功能**:报价存档核心工具。同一询价单**不限报价次数**,版本号自动 MAX+1;旧版非终态自动置 `superseded`;首报自动推询价单 `pending → quoted`;`won` 终态保护(仍存档但带 warning)。事务内完成:作废旧版 → 推询价状态 → 写报价主表 → 写成本明细表。
**入参**:
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `inquiry_id` | string | **是** | — | 询价单号(不存在则失败并提示先建档) |
| `customer_code` | string | **是** | — | 客户编码(不存在则失败) |
| `mold_cost` | float | **是** | — | 模具费 ¥(一次性) |
| `unit_price` | float | **是** | — | 单价 ¥ |
| `annual_volume` | int | **是** | — | 年用量 |
| `material_cost` | float | **是** | — | 材料成本 ¥/件 |
| `casting_cost` | float | **是** | — | 压铸成本 ¥/件 |
| `machining_cost` | float | **是** | — | 机加工成本 ¥/件 |
| `post_process_cost` | float | **是** | — | 后处理成本 ¥/件 |
| `overhead_rate` | float | 否 | 0.12 | 管理费率 |
| `profit_rate` | float | 否 | 0.15 | 利润率 |
| `tax_rate` | float | 否 | 0.13 | 税率 |
| `payment_terms` | string | 否 | `月结30天` | 付款条款 |
| `delivery_terms` | string | 否 | `含税含运` | 交付条款 |
| `valid_days` | int | 否 | 30 | 报价有效天数(自当日起) |
| `remarks` | string | 否 | null | 备注 |
**返回字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 成功标记 |
| `quotation_id` | string | 生成的报价单号 |
| `status` | string | 固定 `draft`(草稿,待人工审核后发出) |
| `version` | int | 本次版本号(自动递增) |
| `previous_quotation_id` | string\|null | 上一版报价单号(首报为 null) |
| `price_change_pct` | float\|null | 较上版价差百分比(首报为 null) |
| `summary.mold_cost` / `unit_price` / `annual_volume` | float/int | 回显 |
| `summary.total_annual` | float | 年总额 = 单价×年用量 |
| `summary.tax_rate` | string | 税率百分比字符串,如 `"13%"` |
| `summary.tax_amount` | float | 税额 |
| `summary.total_amount` | float | 含税总额 |
| `summary.currency` | string | 固定 `CNY` |
| `valid_until` | string | 有效期至 `YYYY-MM-DD` |
| `warning` | string(可选) | 存在中标旧版时的提示 |
| `message` | string | 提示信息 |
**返回示例**(第 2 版议价):
```json
{
"success": true,
"quotation_id": "QUO-2026-0029",
"status": "draft",
"version": 2,
"previous_quotation_id": "QUO-2026-0028",
"price_change_pct": -3.0,
"summary": {
"mold_cost": 180000,
"unit_price": 42.7,
"annual_volume": 80000,
"total_annual": 3416000.0,
"tax_rate": "13%",
"tax_amount": 444080.0,
"total_amount": 3860080.0,
"currency": "CNY"
},
"valid_until": "2026-09-08",
"message": "报价单 QUO-2026-0029(第 2 版)已保存为草稿,待人工审核后发出"
}
```
**业务规则**:
- 版本链查询用 3.4;`won`/`lost` 终态永不被 superseded 覆盖。
- 并发安全由数据库 `(inquiry_id, version)` 唯一约束兜底。
- 草稿态报价单在 HTML 上带「草稿·待人工审核」水印,人工审核后才可发出。
---
### 3.4 get_quotation_history — 查询历史报价
**功能**:历史报价与版本链查询,用于再报价前置检查(有没有报过、上一版多少钱)与价格一致性校验。同一询价单按版本号降序返回,并逐行标记 `is_latest`。
**入参**(全部可选):
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `customer_code` | string | 否 | — | 按客户筛选 |
| `inquiry_id` | string | 否 | — | 按询价单筛选 |
| `start_date` | string | 否 | — | 报价日期起,`YYYY-MM-DD` |
| `end_date` | string | 否 | — | 报价日期止,`YYYY-MM-DD` |
| `status` | string | 否 | — | 状态筛选:draft/superseded/won/lost |
| `latest_only` | bool | 否 | false | true 时每个询价单仅返回最新一版 |
**返回字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `total` | int | 命中条数 |
| `quotations[]` | array | 报价行(quotation 全字段 + `customer_name`) |
| `quotations[].version` | int | 版本号 |
| `quotations[].status` | string | 状态 |
| `quotations[].is_latest` | bool | 是否为该询价单最新版本(全表口径,不受筛选影响) |
| `quotations[].cost_detail` | object | 成本明细(四项成本 + 费率 + total_cost + unit_price) |
**返回示例**(INQ-2026-0019 三版议价链,latest_only=true):
```json
{
"total": 1,
"quotations": [
{
"quotation_id": "QUO-2026-0018",
"inquiry_id": "INQ-2026-0019",
"customer_code": "OEM-2024-002",
"quotation_date": "2026-08-07",
"valid_until": "2026-09-06",
"version": 3,
"status": "draft",
"mold_cost": "180000.00",
"unit_price": "41.50",
"annual_volume": 80000,
"customer_name": "某商用车主机厂",
"is_latest": true,
"cost_detail": { "...": "四项成本与费率" }
}
]
}
```
**注意**:日期参数格式错误(非 `YYYY-MM-DD`)会抛出解析异常,调用方应保证格式。
---
### 3.5 update_opportunity — 创建/更新商机
**功能**:双模式。传 `opportunity_id` = 更新已有商机的阶段/金额/概率等字段(仅更新非 null 字段);不传 `opportunity_id` = 按 `customer_code + title` 新建商机(默认 stage=lead、probability=20、expected_amount=0,source=智能报价自动生成)。
**入参**:
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `opportunity_id` | string | 否 | — | 商机号(传入则走更新模式) |
| `customer_code` | string | 新建时必填 | — | 客户编码 |
| `title` | string | 新建时必填 | — | 商机标题 |
| `stage` | string | 否 | 新建时 `lead` | 阶段:lead/quoted/won/lost |
| `expected_amount` | float | 否 | 新建时 0 | 预期金额 ¥ |
| `probability` | int | 否 | 新建时 20 | 成交概率 % |
| `oem_program` | string | 否 | — | OEM 项目/平台 |
| `expected_close_date` | string | 否 | — | 预期关单日期 `YYYY-MM-DD` |
| `notes` | string | 否 | — | 备注(保留参数) |
**返回字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 成功标记 |
| `opportunity_id` | string | 商机号(更新时为传入值,新建时为生成值) |
| `message` | string | 「已更新」或「已创建」 |
**失败返回**:
- `{"success": false, "message": "expected_close_date 格式错误:...,应为 YYYY-MM-DD"}`
- `{"success": false, "message": "未找到商机 OPP-2026-XXXX"}`
- `{"success": false, "message": "新建商机需要提供 customer_code 和 title"}`
- `{"success": false, "message": "未找到客户 XXX"}`
**返回示例**(新建):
```json
{
"success": true,
"opportunity_id": "OPP-2026-0019",
"message": "商机 OPP-2026-0019 已创建"
}
```
---
### 3.6 save_quotation_mock — 报价写入 CRM(Mock 模拟)
**用途**:模拟「报价写入 CRM」动作。写入独立 mock 表 `quotation_mock`,不校验客户/询价单/商机,与其他工具无任何关联,适合演示与联调场景单独调用。
**入参**:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `part_name` | string | ✅ | — | 零件名称 |
| `unit_price` | number | ✅ | — | 报价单价 |
| `customer_name` | string | — | null | 客户名称(自由文本,不做存在性校验) |
| `part_number` | string | — | null | 零件号 |
| `material_grade` | string | — | null | 材质牌号 |
| `mold_cost` | number | — | 0.0 | 模具费 |
| `annual_volume` | integer | — | null | 年用量(提供时自动计算 `total_annual = unit_price × annual_volume`) |
| `currency` | string | — | CNY | 币种 |
| `remarks` | string | — | null | 备注 |
**返回示例**:
```json
{
"success": true,
"quotation_id": "QUO-MOCK-0001",
"status": "draft",
"summary": {
"part_name": "变速箱阀体外壳", "unit_price": 41.66, "mold_cost": 180000.0,
"annual_volume": 300000, "total_annual": 12498000.0, "currency": "CNY"
},
"message": "报价单 QUO-MOCK-0001 已写入 CRM mock 表(quotation_mock)"
}
```
**关键说明**:
- 单号 `QUO-MOCK-####` 自增,独立于正式 `QUO-2026-####` 序列。
- `quotation_mock` 表无外键,写入不影响正式三表(inquiry/quotation/opportunity),`get_record_counts` 也不统计该表。
- 无版本管理、无状态流转:每次调用新增一行,状态固定 `draft`。
## 4 验证/管理工具详解(4 个)
> 用途:**MCP 读写分离交叉验证**——写工具的返回值与只读回读工具交叉核对;测试数据清理经受限工具完成。
> 原则:验证/清理环节禁止直连数据库;`db.py` 仅为服务层内部连接池。
### 4.1 get_inquiry_info — 询价单回读(只读)
**功能**:按询价单号回读询价单整行,用于存档后验证(状态、图号、零件号、年用量、目标价、创建时间)。
**入参**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `inquiry_id` | string | **是** | 询价单号 |
**返回**:命中 `{"success": true, "inquiry": {<整行字段>}}`;未命中 `{"success": false, "message": "未找到询价单 XXX"}`。
**示例**:
```json
{
"success": true,
"inquiry": {
"inquiry_id": "INQ-2026-0025",
"customer_code": "T1-2025-002",
"inquiry_date": "2026-08-08",
"drawing_number": "DWG-VHB-MN-Rev1",
"part_number": "P-VHB-009",
"annual_volume": 5000,
"target_price": "30.00",
"status": "quoted",
"notes": "TC-05生产报价:微型泵阀体壳 ADC12 96.3cc 160T一模两腔...",
"created_at": "2026-08-08 13:34:11.945841"
}
}
```
---
### 4.2 get_opportunity_info — 商机回读(只读)
**功能**:按商机号或客户编码回读商机,用于存档后验证(stage/probability/expected_amount/更新时间)。两参数二选一,`opportunity_id` 优先。
**入参**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `opportunity_id` | string | 二选一 | 商机号(精确单条) |
| `customer_code` | string | 二选一 | 客户编码(该客户全部商机,按创建时间倒序) |
**返回**:`{"total": N, "opportunities": [<整行字段>]}`;两参数都未提供时 `{"success": false, "message": "需提供 opportunity_id 或 customer_code"}`。
**示例**:
```json
{
"total": 1,
"opportunities": [
{
"opportunity_id": "OPP-2026-0018",
"customer_code": "T1-2025-002",
"title": "微型泵阀体壳 ADC12(P-VHB-009)首单",
"stage": "quoted",
"expected_amount": "40300.00",
"probability": 50,
"oem_program": "大陆制动系统配套",
"expected_close_date": "2026-09-30",
"source": "智能报价自动生成",
"created_by": "AI智能报价",
"updated_at": "2026-08-08"
}
]
}
```
---
### 4.3 get_record_counts — 三表行数统计(只读)
**功能**:返回 inquiry/quotation/opportunity 三表行数,用于数据卫生检查(如回归前后行数一致性核对)。
**入参**:无。
**返回示例**:
```json
{
"success": true,
"counts": { "inquiry": 24, "quotation": 27, "opportunity": 18 }
}
```
---
### 4.4 purge_test_records — 受限测试数据清理(管理)
**功能**:清理 TC-03 回归测试写入的数据。**带硬性防护**:仅当报价单 `remarks`、询价单 `notes` 含「TC-03回归测试」,或商机 `title` 以「TC-03」开头时才允许删除;其余一律进入 `rejected` 列表,防止误删演示/生产数据。级联删除子表(quotation_cost_detail / inquiry_item)。
**入参**(至少传一组;均为 ID 数组):
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `quotation_ids` | array[string] | 否 | 待清理报价单号列表 |
| `inquiry_ids` | array[string] | 否 | 待清理询价单号列表 |
| `opportunity_ids` | array[string] | 否 | 待清理商机号列表 |
**返回字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 固定 true(单条拒绝不影响整体) |
| `deleted.quotations` / `deleted.inquiries` / `deleted.opportunities` | int | 实际删除条数 |
| `deleted.rejected` | array[string] | 被防护拒绝的 ID 列表(非测试标记或不存在) |
**示例一:正常清理(回归测试自动调用)**
```json
{
"success": true,
"deleted": { "quotations": 3, "inquiries": 3, "opportunities": 1, "rejected": [] }
}
```
**示例二:防护生效(对生产数据调用)**
```json
{
"success": true,
"deleted": {
"quotations": 0, "inquiries": 0, "opportunities": 0,
"rejected": ["QUO-2026-0027", "INQ-2026-0025", "OPP-2026-0018"]
}
}
```
---
## 5 附录
### 5.1 依赖数据表
| 表 | 用途 |
|----|------|
| `customer` | 客户主数据(信用/折扣/账期/历史统计) |
| `inquiry` | 询价单(pending→quoted) |
| `quotation` | 报价单主表(版本链/状态机/金额) |
| `quotation_cost_detail` | 报价成本明细(四项成本+费率) |
| `opportunity` | 商机(阶段/金额/概率/关单日期) |
### 5.2 标准存档链路(智能体 Step 7)
```
get_quotation_history(inquiry_id) ← 前置:查有无历史版本
├─ 无 → create_inquiry(...) ← 建询价单
└─ 有 → 复用原 inquiry_id
save_quotation(...) ← 存报价(版本自动+1,事务)
update_opportunity(...) ← 商机同步最新版年总额(stage=quoted)
get_inquiry_info / get_opportunity_info ← 可选:存档后交叉验证
```
### 5.3 与 ERP 服务的协作
核价计算(材质对照/压铸参数/机加工工时/汇总核价)由 `mcp-for-erp` 承担,见《MCP for ERP — 工具说明文档》;CRM 服务不做核价计算,只做客户与商务数据管理。