你能找到的大多数模型上下文协议教程都是用 TypeScript 或 Python 编写的,因为这些是拥有官方 SDK 的语言。这让我们中的很多人——那些维护着存储多年业务数据的 PHP 应用的人——不禁思考,这个协议是否对我们可用。
它是可用的。MCP 是一种有线协议,而非库。如果你的语言能从标准输入读取一行并写回 JSON,你就可以用它实现一个服务器。这篇文章将带你了解协议实际要求什么、PHP 实现大致是什么样的,以及——我低估的那部分——当你将内部系统暴露给模型时会发生什么变化。
MCP 究竟是什么
模型上下文协议是一个开放标准,用于将 AI 助手连接到外部系统:你的数据、你的工具、你的 API。它解决的是组合问题。在标准出现之前,每个助手都需要与每个数据源进行定制集成。MCP 定义了一个接口,使得任何合规的客户端都能与任何合规的服务器通信。
在底层,它是 JSON-RPC 2.0。请求携带 jsonrpc 版本、method、params 对象和 id;响应携带匹配的 id 以及 result 或 error。通知是没有 id 的请求,不需要回复。如果你之前实现过 JSON-RPC 服务,你已经知道了 80% 的传输故事。
一个服务器暴露三种原始类型:
- 工具 — 模型可以调用的操作。每个都有名称、描述和描述其输入的 JSON Schema。这是真正做事的原语:查询数据库、创建记录、发送请求。模型选择何时调用它们。
- 资源 — 模型可以读取的数据,通过 URI 寻址。文件、记录、生成的文档。这些用于上下文,而非操作,客户端通常决定拉取什么。
- 提示 — 用户可以主动调用的可重用提示模板,通常在客户端 UI 中显示为斜杠命令或菜单项。
工具和资源之间的区别比初看时更重要。一个粗略的规则是:工具由模型控制,资源由应用程序控制。 如果模型应该决定是否获取某物,就把它做成工具。如果用户或主机应用程序决定,就把它做成资源。
两种传输方式
MCP 定义了两种标准传输方式,选择正确的方式是第一个架构决策。
stdio。 客户端将你的服务器作为子进程启动,并通过标准输入输出与其通信——换行符分隔的 JSON,每行一条消息。这是最简单的设置:没有端口、没有 HTTP 服务器、没有认证层,因为唯一能与你的进程通信的是启动它的父进程。这对于在与客户端同一台机器上运行的任何东西来说都是正确的选择。
使用 stdio 有两条规则,两者都容易在 PHP 中违反:
-
永远不要向 stdout 写入协议消息以外的任何内容。 一个意外的
echo、调试时留下的var_dump,或打印到 stdout 的 PHP 警告都会破坏消息流,客户端将无法解析它。相反,将诊断信息发送到 stderr,客户端通常会将其转发到日志。 - 关闭输出缓冲并在每次写入后刷新,否则你的响应会停留在缓冲区中,而客户端在等待。
可流式传输的 HTTP。 服务器作为普通的 HTTP 端点运行。客户端向其 POST JSON-RPC 消息;服务器回复单个 JSON 响应,或在需要为一个请求推送多条消息时回复服务器发送事件流。这是在用户机器以外某处运行的服务器的传输方式——对于大多数 PHP 商店来说,这是我们已经知道如何操作的有趣案例,因为这是我们已经知道如何操作的部署模型。
(在规范的早期版本中存在一个较旧的 HTTP+SSE 传输方式。新的工作应该针对可流式传输的 HTTP。)
握手
无论你选择哪种传输方式,对话都以相同的方式开始。客户端发送 initialize,包含它所使用的协议版本和支持的功能。你的服务器回复其自己的协议版本、功能以及其名称和版本。客户端然后发送 initialized 通知,正常操作开始。
功能是双方协商的方式。如果你的服务器没有实现资源,你就不会公布资源功能,一个行为良好的客户端就不会调用 resources/list。不要公布你尚未构建的功能。
握手后,重要的方法是可预测的:
| 方法 | 作用 |
|---|---|
tools/list |
返回你暴露的工具,附带描述和输入模式 |
tools/call |
使用给定参数执行一个工具,返回其结果 |
resources/list |
返回可用资源及其 URI |
resources/read |
返回一个资源的内容 |
prompts/list |
返回可用提示模板 |
prompts/get |
返回一个填充好的提示 |
一个最小但真正有用的服务器是 initialize 加 tools/list 加 tools/call。其他一切都是可选的。
PHP 端的外观
与传输方式无关的结构形状:
read a JSON-RPC message
→ dispatch on `method`
→ build a result (or an error)
→ write the response with the same `id`
Enter fullscreen mode Exit fullscreen mode
对于 stdio 来说,这是一个在 fgets(STDIN)、json_decode、对方法名的 match 以及 fwrite(STDOUT, json_encode($response) . "\n") 上的循环。对于可流式传输的 HTTP 来说,它是一个解码请求体并返回编码响应的单一端点。中间的调度层是相同的;只有读写端发生变化。从一开始就这样编写,你就可以从一个代码库支持两者。
在开始之前,值得了解的三个 PHP 特定事项:
工具模式。 每个工具都需要其输入的 JSON Schema。快速地将它们作为嵌套数组手动编写会变得乏味。从你已经维护的东西中派生它们——一个验证规则集、一个 DTO、一组通过反射读取的类型化构造函数参数——可以防止模式和实际实现分离。模式漂移是“模型不断错误调用工具”的最常见原因。
错误处理。 区分两种故障。格式错误的请求或未知方法是 协议错误:返回一个 JSON-RPC error 对象。运行但失败的工具——未找到记录、验证拒绝输入——是 工具错误:返回一个设置了错误标志和人类可读消息的正常结果。这种区别很重要,因为第二种错误会返回给模型,模型可以读取消息并进行调整。协议错误只是告诉它出了问题。在模型可能合理地从中恢复的故障处,将 PHP 异常转换为第二种。
长时间运行的工作。 PHP 的每个请求一个进程模型非常适合 stdio(进程的生命周期与会话一样长),但如果一个工具需要几分钟,对于 HTTP 来说就有点尴尬了。保持工具调用简短。如果一个工具启动了缓慢的操作,立即返回一个作业标识符,并暴露一个报告状态的第二个工具。模型能很好地处理这种模式;它们能很好地处理超时的请求。
连接到 Claude
一旦服务器运行,有三种广泛的方式可以访问它。
本地,通过 stdio。 桌面和 CLI 客户端——Claude Desktop、Claude Code 以及各种 IDE 集成——允许你通过指定要运行的命令及其参数(php,加上你的服务器脚本路径)来注册服务器,它们为你管理子进程。这是从“它响应 tools/list”到“我正在使用它”的最快方式。
远程,通过 HTTP。 部署在 URL 上的可流式传输 HTTP 服务器可以与支持远程连接的客户端注册。Claude API 也有一个 MCP 连接器:你在请求中声明服务器的 URL,API 在服务器端建立连接,这样模型就可以调用你的工具,而无需你编写客户端循环。请注意,托管的 MCP 服务器通常使用 OAuth 持有者令牌而不是服务自己的原生 API 密钥进行身份验证——这些是不同的身份验证系统,假设后者有效是一个常见的早期错误。
从你自己的代码。 如果你正在构建一个代理而不是使用现有的客户端,大多数 AI SDK 都可以将 MCP 工具定义转换为其原生工具格式,因此 MCP 服务器成为你控制的循环的工具来源。
无论哪种方式,首先通过 stdio 进行调试。故障模式更简单:没有 TLS、没有身份验证、没有代理、没有 CORS。先把协议弄对,然后再移到 HTTP。
我低估的部分:暴露
当你在现有系统前放置一个 MCP 服务器时,情况会发生变化。你暴露的每个工具都是对至少部分由从外部世界读取的文本引导的模型授予的功能。如果模型被说服调用你的工具,你的工具就会运行。
实际后果:
工具范围要窄。 一个接受任意 SQL 的通用 run_query 工具是最方便构建的,也是最糟糕的发布方式。优先选择具有类型化参数的特定工具——find_customer_by_email、list_orders_in_range——并在服务器端验证每个参数,就像它来自匿名 HTTP 请求一样。它实际上就是这样。
读和写是不同的风险类别。 只读工具有披露风险。写入工具有这实际上发生了的风险。将它们分开,并将任何破坏性或不可逆转的操作置于主机应用程序中的明确确认之后,而不是相信模型会小心。
描述是安全表面的一部分。 工具描述是模型读取的指令。模糊的描述会诱使模型在你不打算的情况下调用工具。明确规定何时应该使用工具——而不仅仅是它做什么——可以显著提高正确性和安全性。
不要泄露你不打算暴露的内容。 返回任务所需的字段,而不是整个记录。不应该出现在面向客户的答案中的内部注释、成本价格或个人电话号码也不应该出现在工具结果中。在服务器端过滤,而不是在提示中过滤。
记录每个调用。 工具名称、参数、调用者、结果。当有人问“为什么它这样做”时,日志是你唯一能得到的答案。
这些都不是异乎寻常的——这是你应用于公共 API 端点的相同纪律。不同之处在于调用者是语言模型,而不是阅读你文档的开发者,因此歧义会以你未曾预料的方式解决,而不是作为支持票浮出水面。
值得吗?
对于一个建立在多年积累的业务数据之上的 PHP 应用程序,MCP 是我发现的连接这些数据和能够真正对其进行推理的助手的最便宜的桥梁。没有 SDK 可以等待。它是通过管道或 HTTP 端点的 JSON-RPC——这两件事 PHP 已经做了二十年。
从三个只读工具开始,这些工具回答组织中某人每周都会问的问题。通过 stdio 将其发布给一个人。看看他们实际要求什么。这比你预先设计的任何规范都要好得多。
如果你在没有官方 SDK 的语言中构建了 MCP 服务器,我很想知道是什么让你遇到了困难——是传输、模式还是范围界定。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.