结论先说:MCP 的 Completions 是一套「服务端给候选值」的能力,它只覆盖 prompt 参数和 resource 模板变量,不覆盖工具入参。用对了能把用户的自由文本输入变成下拉选择,用错了(想拿它补全工具参数)会白忙一场。这是 MCP 里最容易被忽略、也最容易被误解的一个可选能力。
Completions 到底补全什么
按 MCP 规范,Completions 让服务器为「 prompt 的参数」和「 resource 模板的变量」提供补全建议。用户在填这两个东西的时候,客户端弹一个下拉,候选值由你的服务端实时给出。
它的引用类型只有两种:
| 类型 | 引用对象 | 示例 |
|---|---|---|
ref/prompt | 按名字引用一个 prompt | {"type":"ref/prompt","name":"code_review"} |
ref/resource | 按 URI 引用资源模板 | {"type":"ref/resource","uri":"file:///{path}"} |
注意这里没有 ref/tool。很多人以为 Completions 能补全工具入参,规范里并没有这一项——工具参数的约束要靠 JSON Schema 的 enum 、描述和校验来做。
协议长什么样
请求用的是 completion/complete 方法,参数结构非常小:
{
"jsonrpc": "2.0",
"id": 1,
"method": "completion/complete",
"params": {
"ref": { "type": "ref/prompt", "name": "code_review" },
"argument": { "name": "language", "value": "py" }
}
}
服务端回一组建议值,并可选带上总数和「还有更多」标记:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"completion": {
"values": ["python", "pytorch", "pyside"],
"total": 10,
"hasMore": true
}
}
}
三个返回字段的约束要记牢:values 单次最多 100 条;total 可以大于实际返回的条数;hasMore 表示还有没返回的候选,即使总数未知也能置 true 。
多参数联动:靠 context 传递已填值
当 prompt 有多个参数时,后一个参数的候选项往往依赖前一个。规范为此提供了 context.arguments,把用户已经填好的值一并带过来:
{
"ref": { "type": "ref/prompt", "name": "code_review" },
"argument": { "name": "framework", "value": "fla" },
"context": { "arguments": { "language": "python" } }
}
服务端据此就能只返回 python 生态下的框架名。但规范也明确说了:客户端不保证一定会带 context。所以服务端的处理原则是——context 缺失时返回空列表,绝不抛异常。
怎么声明这个能力
服务端必须在能力声明里带上 completions:
{
"capabilities": {
"completions": {}
}
}
不同 SDK 的封装程度不一样,这是选型时值得留意的一点:
- Python SDK:注册一个
@mcp.completion()处理函数即可,SDK 看到处理函数就自动声明completions能力,无需手动写。函数接收ref、argument、context三个参数,返回Completion(values=[...]);没有建议时返回None,SDK 会转成空数组。 - TypeScript SDK:用
completable()包裹 argsSchema 里的某个字段,第一个被包裹的字段会顺带注册处理函数并声明能力。资源模板则通过complete映射,按变量各挂一个回调。
Python 侧的最小实现大概是这样:
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference
mcp = MCPServer("GitHub Explorer")
LANGUAGES = ["go", "javascript", "python", "rust", "typescript"]
@mcp.prompt()
def review_code(language: str, code: str) -> str:
"""Review a snippet of code."""
return f"Review this {language} code:\n{code}"
@mcp.completion()
async def handle_completion(ref, argument, context):
if isinstance(ref, PromptReference) and argument.name == "language":
return Completion(values=[l for l in LANGUAGES if l.startswith(argument.value)])
return None
有个细节很容易踩:SDK 不会替你过滤。argument.value 只是用户已输入的前缀,返回什么就显示什么,前缀匹配要你自己写。
错误处理与限流
规范对 Completions 的错误码给了明确约定,照着实现即可:
| 场景 | 错误码 |
|---|---|
| 不支持该能力 | -32601 Method not found |
| prompt 名字非法 / 缺少必填参数 | -32602 Invalid params |
| 服务端内部错误 | -32603 Internal error |
服务端与客户端各自该做什么
规范把两边的最佳实践分得很清楚,落地时对着清单自查就行:
- 服务端:按相关度排序返回;合适时做模糊匹配;对补全请求限流;校验所有输入。
- 客户端:对高频输入做防抖;缓存补全结果;优雅处理缺失或部分结果。
这和我们在 MCP 工具注解(annotations) 里讲的思路一致——协议把「怎么描述」和「怎么使用」分开约定,服务端负责给足信息,客户端负责用得克制。
- 确认补全对象:只有 prompt 参数和 resource 模板变量能补全。如果要约束的是工具入参,改 JSON Schema,别用 Completions 。
- 写处理函数:按 SDK 方式注册(Python 用装饰器,TS 用 completable),分支处理 ref 类型和 argument.name 。
- 处理 context 缺失:context 为空时返回空列表,不要抛错,客户端可能根本不发。
- 加限流与截断:单次结果截断到 100 条,对请求做频率限制,避免补全接口被滥用为枚举通道。
用 TypeScript 让一个字段的候选项依赖另一个字段
async function completeBranch(value: string, context?: { arguments?: Record<string, string> }) {
const repo = context?.arguments?.repo;
if (!repo) return [];
return (branchesByRepo[repo] ?? []).filter(b => b.startsWith(value));
}
repo 已填为 typescript-sdk、 branch 输入 rel 时,返回该仓库下的 release 分支。 context 缺失时直接返回空数组。
和 prompts / resources 的关系
Completions 是依附于 prompts 和 resources 的增强能力,本身不独立存在。如果你还没理清这两个原语,建议先看 MCP 的 resources 与 prompts 指南,再回来做补全。服务端与客户端的整体开发流程可以参照 Python 开发 MCP 客户端 与 TypeScript 构建 MCP Server。还没入门的话,MCP 入门教程 是起点。
先搞懂 prompts 与 resourcesCompletions 能补全工具的入参吗?
需要手动声明 completions 能力吗?
一次最多能返回多少条候选?
context 一定会有吗?
小结
Completions 是个小而完整的能力:请求三个字段、响应三个字段,却能把「让用户猜该填什么」变成「让用户选」。记住它的边界(只管 prompt 和 resource)、写好 context 缺失的兜底、补上限流,这个能力就很稳。
资料来源:Model Context Protocol Specification:Completion、MCP Python SDK:Completions、MCP TypeScript SDK:Completion。










评论 (0)