我终于搞懂了 MCP:从 HTTP API 到 ERP MCP Server 的完整入门
MCP(Model Context Protocol)是一套让 Agent 统一发现、理解和调用外部能力的协议。
本文主要用一个 ERP 的“查询库存”场景,解释 MCP 是什么、MCP Server 是什么、怎么开发,以及 MCP 和普通 HTTP API 到底是什么关系。
1. 先搞懂 MCP、MCP Server、Tool 分别是什么
假设 ERP 原来有一个普通 HTTP 接口:
GET /api/inventory?materialCode=ABC001
现在希望以后可以直接对 Codex 说:
帮我查一下物料 ABC001 还有多少库存
那就可以在 MCP Server 中提供一个 Tool:
query_inventory(material_code)
整个关系是:
用户
↓
Codex / Agent
↓
MCP Tool:query_inventory
↓
MCP Server
↓
ERP HTTP API / ERP 内部业务代码
↓
ERP
简单理解:
Tool
= Agent 能调用的一个具体能力
MCP Server
= 把这些能力按照 MCP 标准提供出去的程序
MCP
= Agent 与 MCP Server 之间遵守的通信协议
2. 图里的“MCP”到底是什么意思
经常会看到这种图:
Codex / Agent
↓
MCP
↓
ERP MCP Server
↓
ERP
这里中间的 MCP 不是一个单独的软件,也不是还要再部署一个叫“MCP”的服务。
它表示的是:
Codex 和 ERP MCP Server 之间,按照 MCP 协议通信。
更准确一点可以画成:
Codex / Agent
↓
MCP Client
↓
MCP 协议
↓
ERP MCP Server
↓
ERP API / 业务代码
可以类比成:
浏览器
↓
HTTP
↓
Web Server
这里 HTTP 是协议,Web Server 才是真正运行的程序。
所以:
MCP 是通信规则,MCP Server 才是你真正需要开发和运行的程序。
3. MCP 和普通 HTTP API 有什么区别
可以先这样理解:
HTTP API
=
给普通程序调用的业务接口
MCP
=
给 Agent 使用的一套标准化能力协议
例如 ERP 原来有:
GET /api/inventory?materialCode=ABC001
MCP 可以把它包装成一个 Tool:
Tool 名称:
query_inventory
作用:
查询指定物料库存
参数:
material_code:物料编码
返回:
库存数量、可用数量、仓库信息
Agent 通过 MCP 可以知道:
这个 Tool 是干什么的
什么时候适合调用
需要哪些参数
参数是什么类型
所以:
普通 HTTP API 不会因为“接口文档写得很清楚”就自动变成 MCP。
4. 开发一个 ERP MCP Server,要做哪些事情
假设 ERP 有很多功能:
库存管理
订单管理
供应商管理
采购管理
财务管理
……
不要一上来把整个 ERP 全部暴露给 Agent。
先挑一个最简单的功能:
查询库存
我们最终希望得到一个 MCP Tool:
query_inventory(material_code)
整个开发过程可以简单分成 7 步:
1. 安装 MCP SDK
2. 创建 MCP Server
3. 定义 Tool
4. 先用假数据测试
5. Tool 内接 ERP 业务能力
6. 启动 MCP Server
7. 在 Codex 中添加这个 MCP Server
5. 第一步:准备 Python 环境
这里以 Python 为例。
安装 MCP Python SDK:
pip install "mcp[cli]"
如果后面需要调用 ERP HTTP API,再安装:
pip install httpx
项目结构可以先很简单:
erp-mcp/
└── server.py
6. 第二步:创建 MCP Server
在 server.py 中创建一个 MCP Server:
from mcp.server import MCPServer
mcp = MCPServer("ERP MCP Server")
可以简单理解成:
创建了一个名字叫
ERP MCP Server的 MCP 服务。
目前它还没有任何业务能力。
7. 第三步:增加一个 Tool
我们增加一个查询库存的 Tool:
from mcp.server import MCPServer
mcp = MCPServer("ERP MCP Server")
@mcp.tool()
def query_inventory(material_code: str) -> dict:
"""查询指定物料的库存。"""
return {
"material_code": material_code,
"quantity": 120,
"available_quantity": 100
}
if __name__ == "__main__":
mcp.run()
这里最关键的是:
@mcp.tool()
可以简单理解成:
把下面这个 Python 函数注册成一个 MCP Tool。
Codex 等 MCP Client 连接以后,就能发现:
query_inventory
以及它的说明和参数。
8. 第四步:先用假数据测试
先不要急着连真实 ERP。
例如调用:
query_inventory("ABC001")
返回:
{
"material_code": "ABC001",
"quantity": 120,
"available_quantity": 100
}
如果这一步能跑通,说明:
MCP Server 和 Tool 这一层基本正常。
下一步再接真实 ERP。
9. 第五步:把 Tool 接到 ERP HTTP API
假设 ERP 已经有接口:
GET http://erp.company.local/api/inventory
参数:
materialCode=ABC001
那么 MCP Tool 内部可以调用这个 API:
import httpx
from mcp.server import MCPServer
mcp = MCPServer("ERP MCP Server")
@mcp.tool()
def query_inventory(material_code: str) -> dict:
"""查询 ERP 中指定物料的库存。"""
response = httpx.get(
"http://erp.company.local/api/inventory",
params={
"materialCode": material_code
},
timeout=10
)
response.raise_for_status()
data = response.json()
return {
"material_code": material_code,
"quantity": data["quantity"],
"available_quantity": data["availableQuantity"]
}
if __name__ == "__main__":
mcp.run()
这时候链路就变成:
Codex
↓
query_inventory
↓
ERP MCP Server
↓
GET /api/inventory
↓
ERP
↓
返回真实库存
需要注意:
ERP 原来的 HTTP API 并没有变成 MCP。
只是我们新增了一个 MCP Server,把原来的业务能力包装成 MCP Tool。
10. MCP 和 HTTP API 能不能放在同一个服务、同一个端口
可以。
这是最容易让人困惑的地方。
假设 ERP 服务监听:
8000 端口
完全可以同时提供:
http://erp.company.com:8000/api/inventory
↑
普通 HTTP API
http://erp.company.com:8000/mcp
↑
MCP 入口
结构可以理解成:
ERP Service
监听 8000 端口
│
┌────────────┴────────────┐
│ │
↓ ↓
/api/inventory /mcp
普通 HTTP API MCP 入口
│ │
└────────────┬────────────┘
↓
ERP 业务代码
↓
数据库
所以:
同一个程序,可以用同一个端口,通过不同 URL 路径同时提供普通 HTTP API 和 MCP。
11. 同一个服务时,不需要 MCP 再绕一圈调用自己的 HTTP API
如果 MCP 和 HTTP API 本来就在同一个程序里,更合理的做法是:
ERP 业务代码
query_inventory()
↑ ↑
│ │
HTTP API MCP Tool
│ │
/api/inventory query_inventory
也就是:
普通 HTTP API 和 MCP Tool 共用同一套业务代码。
没必要:
MCP Tool
↓
再调用自己的 HTTP API
↓
再进入业务代码
这样反而多绕了一层。
12. 那什么时候应该单独部署 MCP Server
如果 ERP 已经运行很多年,例如:
ERP
Java 开发
监听 8080
已经有完整 HTTP API
不想修改原系统
这时候最省事的是:
Codex
↓
ERP MCP Server :3001
↓
ERP HTTP API :8080
↓
ERP
例如:
ERP:
http://erp-server:8080/api/inventory
MCP Server:
http://mcp-server:3001/mcp
这种方式的好处是:
原 ERP 基本不用修改。
13. 两个独立程序能不能使用同一个端口
一般不行。
例如:
ERP 进程
想监听 8000
MCP Server 进程
也想监听 8000
在同一个 IP 上,两个独立进程通常不能同时占用:
同一个 IP + 同一个端口
所以如果拆成两个独立服务,一般会是:
ERP:8080
MCP Server:3001
14. 为什么线上看起来又可以都是 443 端口
因为生产环境通常还有:
Nginx / 网关
例如对外统一:
https://erp.company.com
都是 HTTPS 443。
但是网关可以按照路径分流:
https://erp.company.com/api/*
↓
ERP :8080
https://erp.company.com/mcp
↓
MCP Server :3001
结构就是:
用户 / Codex
↓
https://xxx:443
↓
Nginx
┌──────┴──────┐
↓ ↓
/api /mcp
↓ ↓
ERP:8080 MCP:3001
所以:
外面看起来是同一个 443 端口,不代表里面一定是同一个程序。
15. 第六步:启动 MCP Server
MCP Server 可以运行在本地,也可以通过网络提供服务。
可以先简单理解成:
本地使用:
Codex
↓
本地 MCP Server
服务端使用:
Codex
↓
网络
↓
MCP Server
MCP 可以使用不同的传输方式,例如本地进程通信或基于 HTTP 的网络传输。
需要注意:
即使底层使用 HTTP 进行网络传输,Agent 和 MCP Server 之间遵守的仍然是 MCP 协议。
所以:
HTTP
= 可以作为传输通道
MCP
= Agent 与 MCP Server 之间的能力调用协议
16. 第七步:在 Codex 中添加 MCP Server
MCP Server 启动后,就可以在 Codex 中配置。
例如添加:
ERP MCP Server
连接成功以后,Codex 就可以发现:
query_inventory
以后用户只需要说:
帮我查一下物料 ABC001 的库存
执行过程可能是:
用户提问
↓
Codex 判断需要查询 ERP
↓
调用 query_inventory
↓
ERP MCP Server 收到请求
↓
调用 ERP API / 业务代码
↓
ERP 返回库存
↓
MCP Server 返回 Tool Result
↓
Codex 整理成自然语言
↓
告诉用户结果
17. 再增加几个 ERP Tool
查询库存跑通以后,可以继续增加:
query_order
query_supplier
create_purchase_request
最终形成:
ERP MCP Server
│
├── query_inventory
├── query_order
├── query_supplier
└── create_purchase_request
这样 Agent 就拥有了部分 ERP 能力。
18. 一个完整例子
用户说:
查一下 ABC001 的库存,
如果少于 100,就帮我创建一个采购 500 个的申请。
Agent 可以这样执行:
第 1 步
调用 query_inventory
↓
返回:
库存 60
↓
第 2 步
模型判断:
60 < 100
↓
第 3 步
调用 create_purchase_request
↓
ERP 创建采购申请
↓
第 4 步
Codex 返回最终结果
这就是 MCP 的价值:
让 Agent 不只是回答问题,而是真正能够调用企业系统完成事情。
19. MCP Server 不只是 Tool
MCP Server 还可以提供:
Tools
Resources
Prompts
小白阶段先重点理解 Tool 就够了。
可以简单理解:
Tool
= 让 Agent 执行动作
Resource
= 给 Agent 提供可以读取的数据
Prompt
= 可复用的提示词模板
对于 ERP 接入,最常见的还是:
把查询和操作能力做成 Tool。
20. 企业开发一定要考虑权限
不要这样:
Codex
↓
MCP Server
↓
超级管理员账号
↓
ERP 全部数据
更合理的是:
用户
↓
Codex
↓
MCP Server
↓
识别当前用户
↓
权限校验
↓
ERP
真正上线时,至少要考虑:
身份认证
权限控制
数据范围
操作审计
参数校验
敏感操作确认
日志
超时和异常处理
权限不能只靠 Prompt。
21. 模型是怎么知道每个 MCP Tool 是干什么的
这是理解 MCP 时非常关键的一点。
很多人会以为:
还需要单独写一个 Markdown 文档,把每个接口的用途告诉模型。
其实通常不需要额外写一个 md 文档给模型看。
MCP Tool 本身就会把这些信息提供出去:
Tool 名称
Tool 说明
输入参数
参数类型
返回结构
例如:
@mcp.tool()
def query_inventory(
material_code: str,
warehouse: str | None = None
) -> dict:
"""
查询指定物料在 ERP 中的库存信息。
material_code:物料编码
warehouse:仓库,可选
"""
...
这里:
函数名
query_inventory
↓
告诉模型:
这个 Tool 叫什么
docstring
查询指定物料在 ERP 中的库存信息
↓
告诉模型:
这个 Tool 是干什么的
参数类型
material_code: str
warehouse: str | None
↓
告诉模型:
需要传哪些参数、参数是什么类型
MCP Server 会把这些内容整理成类似下面的 Tool 描述:
{
"name": "query_inventory",
"description": "查询指定物料在 ERP 中的库存信息。",
"inputSchema": {
"type": "object",
"properties": {
"material_code": {
"type": "string"
},
"warehouse": {
"type": ["string", "null"]
}
},
"required": ["material_code"]
}
}
Codex 连接 MCP Server 后,会先获取这个 Server 提供了哪些 Tool。
然后模型就能看到:
query_inventory
= 查询 ERP 库存
query_order
= 查询 ERP 订单
create_purchase_request
= 创建采购申请
所以当用户说:
帮我查一下 ABC001 的库存
模型就能判断:
应该调用 query_inventory
需要单独维护 Tool 文档吗
MCP 协议本身不要求必须额外写 Markdown 文档。
但是企业项目里,仍然建议额外维护一份开发文档,例如:
erp-mcp/
├── server.py
├── README.md
└── docs/
└── tools.md
tools.md 可以给开发人员看:
Tool:query_inventory
用途:
查询 ERP 库存
底层接口:
GET /api/inventory
权限:
只能查询当前用户有权限的仓库
注意:
禁止返回采购成本字段
要注意:
这份 Markdown 主要是给开发人员维护和查看的,不是 MCP 必须提供给模型的。
模型真正依赖的核心信息,还是 MCP Tool 自己提供的:
name
description
inputSchema
outputSchema(可选)
annotations(可选)
一句话记住:
模型不是靠额外的 md 文档认识 Tool,而是靠 MCP Server 提供的 Tool 名称、说明和参数 Schema。
22. Codex 里的 MCP 是全局的,还是可以按项目配置
Codex 里的 MCP 不一定只能全局配置。
可以简单分成两层:
全局 MCP
=
所有项目都可能使用
项目级 MCP
=
只针对某个项目使用
全局 MCP
用户级配置通常放在:
~/.codex/config.toml
例如:
[mcp_servers.github]
url = "https://example.com/github-mcp"
enabled = true
这种 MCP 更像:
Codex 这个用户的通用工具。
适合放:
GitHub
通用搜索
通用文档工具
项目级 MCP
如果某个项目只需要自己专属的 MCP,可以在项目目录里配置:
my-project/
├── .codex/
│ └── config.toml
├── src/
└── ...
例如这个项目只需要 ERP MCP:
[mcp_servers.erp]
url = "https://erp.company.com/mcp"
enabled = true
可以理解成:
项目 A
└── ERP MCP
项目 B
└── OA MCP
项目 C
└── 没有专属 MCP
这样就不会把所有 MCP 都塞给所有项目。
为什么项目级 MCP 很重要
假设你有很多项目:
项目 A:ERP
项目 B:OA
项目 C:网站
项目 D:运维
如果所有 MCP 都全局配置:
ERP MCP
OA MCP
数据库 MCP
运维 MCP
……
那么每个项目都可能看到一堆自己根本用不到的 Tool。
这样会带来两个问题:
1. 工具越来越多,模型选择 Tool 时更容易受到干扰
2. MCP 初始化和 Tool 描述也会占用一定 Context / 运行资源
所以更推荐:
全局 MCP
=
真正通用的能力
项目级 MCP
=
当前项目专属能力
可以类比成:
全局 MCP
= 员工随身携带的通用工具
项目级 MCP
= 进入这个项目后才发给他的专用工具
23. 最后重新看 MCP、Tool、MCP Server、HTTP API
可以用下面这张关系记住:
用户
↓
Agent
↓
MCP Tool
↓
MCP 协议
↓
MCP Server
↓
HTTP API / 内部业务代码
↓
ERP
再翻译成人话:
Agent
= 使用能力干活的人
Tool
= Agent 可以调用的具体能力
MCP
= Agent 与外部能力之间的统一协议
MCP Server
= 真正实现并提供这些 Tool 的程序
HTTP API
= ERP 原来已有的普通业务接口
最后总结
对于一个已经存在的 ERP,最容易落地的路线通常是:
先选一个 ERP 功能
↓
定义一个 MCP Tool
↓
开发 MCP Server
↓
Tool 内调用 ERP HTTP API
↓
测试 Tool
↓
部署 MCP Server
↓
添加到 Codex
↓
用自然语言调用 ERP
关于部署,再记住:
同一个程序
→ 可以同一个端口
→ /api 提供普通 HTTP API
→ /mcp 提供 MCP
两个独立程序
→ 一般使用不同端口
→ ERP 8080
→ MCP Server 3001
生产环境
→ 可以再通过 Nginx / 网关
→ 统一对外暴露 443
关于 Tool 和项目配置,再记住:
模型怎么知道 Tool 是干什么的
→ 看 MCP Tool 的 name / description / inputSchema
→ 不需要额外给模型准备一个 md 文档
Codex 的 MCP 怎么配置
→ 通用 MCP 放全局配置
→ 项目专属 MCP 放项目 .codex/config.toml
一句话总结:
HTTP API 是业务系统原来的接口,MCP 是给 Agent 用的统一能力协议,MCP Server 则负责把业务能力按照 MCP 标准提供给 Agent;至于 HTTP API 和 MCP 是否同端口,取决于它们是不是运行在同一个程序里。
浙公网安备 33010602011771号