# 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 <工具名> '' # 示例 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 服务不做核价计算,只做客户与商务数据管理。