MCP协议atdio协议规范及例子
时间:2025-4-30 15:13 作者:六度科技 分类: cursor&vscode
Model Context Protocol (MCP) 协议规范
文档整理自 https://modelcontextprotocol.io/ 及实际开发经验
MCP (Model Context Protocol) 是一个标准化的协议,用于大型语言模型(LLMs)与外部应用程序进行交互。它允许LLM模型通过标准化的方式访问数据和执行功能,类似于API但专为LLM交互设计。
核心概念
MCP协议基于以下几个核心概念:
- 资源 (Resources) - 提供数据给LLM,类似于GET端点
- 工具 (Tools) - 允许LLM执行操作,类似于POST端点
- 提示 (Prompts) - 可重用的交互模板
- 通知 (Notifications) - 无需响应的事件通知
通信方式
MCP支持多种通信方式:
- 标准输入/输出 (stdio) - 命令行界面通信
- Server-sent events (SSE) - Web应用程序通信
- WebSocket - 双向通信
本文主要介绍通过stdio进行通信的标准流程。
基本通信格式
MCP使用JSON-RPC 2.0协议进行通信,每条消息都必须是有效的JSON格式,并以换行符(\n
)结尾。
JSON-RPC 2.0基本格式
请求:
{
"jsonrpc": "2.0",
"id": 1,
"method": "方法名称",
"params": { /* 参数对象 */ }
}
响应:
{
"jsonrpc": "2.0",
"id": 1,
"result": { /* 结果对象 */ }
}
错误响应:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "错误描述"
}
}
通知(无需响应):
{
"jsonrpc": "2.0",
"method": "notifications/某类通知",
"params": { /* 参数对象 */ }
}
完整通信流程
1. 服务启动握手
服务端输出: {}\n
说明: 服务启动时发送一个空的JSON对象,告诉客户端服务已准备好接收请求。这是必须的第一步。
2. 初始化请求
客户端输入: {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"tools":true,"prompts":false,"resources":true,"logging":false,"roots":{"listChanged":false}},"clientInfo":{"name":"client-name","version":"1.0.0"}},"jsonrpc":"2.0","id":0}\n
说明:
protocolVersion
- 协议版本,如"2024-11-05"capabilities
- 客户端支持的功能clientInfo
- 客户端信息
3. 初始化响应
服务端输出: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"server-name","version":"1.0.0"}}}\n
说明:
protocolVersion
- 服务端支持的协议版本capabilities
- 服务端支持的功能serverInfo
- 服务端信息
4. 初始化通知
客户端输入: {"method":"notifications/initialized","params":{},"jsonrpc":"2.0"}\n
说明: 客户端通知服务端初始化已完成,可以开始正常通信。
5. 工具列表请求
客户端输入: {"method":"tools/list","params":{},"jsonrpc":"2.0","id":1}\n
说明: 客户端请求获取可用工具列表。
6. 工具列表响应
服务端输出: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"toolName","description":"工具描述","inputSchema":{"type":"object","properties":{},"required":[]},"outputSchema":{"type":"object","properties":{"content":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["text"]},"text":{"type":"string"}},"required":["type","text"]}}]},"required":["content"]}}]}}\n
说明:
name
- 工具名称description
- 工具描述inputSchema
- JSON Schema格式的输入参数定义outputSchema
- JSON Schema格式的输出结果定义
重要:outputSchema
必须准确定义工具的返回格式,客户端会严格按照这个格式解析结果。
7. 工具调用请求
客户端输入: {"method":"tools/call","params":{"name":"toolName","arguments":{}},"jsonrpc":"2.0","id":2}\n
说明:
name
- 要调用的工具名称arguments
- 符合inputSchema的参数对象
8. 工具调用响应(符合MCP v2的标准格式)
服务端输出: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"工具返回的内容"}]}}\n
说明:
content
- 包含结果内容的数组- 每个内容项必须有
type
字段,目前标准支持的类型有:text
- 文本内容image
- 图片内容(base64编码)resource
- 嵌入资源
注意:响应格式必须严格匹配在工具列表中声明的outputSchema
。
错误处理
MCP使用标准JSON-RPC 2.0错误处理机制:
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32603,
"message": "调用工具时发生错误:详细错误信息"
}
}
常见错误代码:
-32700
- 解析错误-32600
- 无效请求-32601
- 方法不存在-32602
- 无效参数-32603
- 内部错误
高级功能
进度通知
对于长时间运行的请求,服务端可以发送进度通知:
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "token-from-request",
"progress": 0.5,
"total": 1.0
}
}
资源功能
MCP还支持资源读取功能:
客户端输入: {"method":"resources/list","params":{},"jsonrpc":"2.0","id":3}\n
客户端输入: {"method":"resources/read","params":{"uri":"resource://path"},"jsonrpc":"2.0","id":4}\n
实现最佳实践
工具响应格式
工具返回格式必须符合以下结构:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "返回的文本内容"
}
]
}
}
对于多种返回类型的混合内容:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "文本内容"
},
{
"type": "image",
"data": "base64编码的图像数据",
"mimeType": "image/png"
}
]
}
}
常见错误与调试
- 工具列表与工具调用格式不匹配:确保
outputSchema
与实际返回格式一致 - JSON解析错误:检查JSON格式是否有效,特别是引号、逗号等
- 缺少换行符:每条消息必须以
\n
结尾 - ID不匹配:响应ID必须与请求ID一致
日志记录
建议实现详细的日志记录,包括:
- 收到的每条请求
- 发送的每条响应
- 错误和异常
- 工具调用的输入和输出
示例实现
以下是一个PowerShell实现MCP协议的基本框架:
PowerShell脚本需要注意事项
1. 编写脚本
新建txt文件,编写脚本
另存为后缀名为".ps1"的文件,保存类型选择所有文件,编码类型选择 "UTF-8 BOM"
2. 设置脚本运行权限(只需一次)
打开 PowerShell(以管理员身份运行),输入:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
然后输入 Y 确认。
3. 如何运行脚本
右键点击 “.ps1” → 使用 PowerShell 运行。
4. 恢复原设置(可选)
如果你想恢复默认脚本安全策略:
Set-ExecutionPolicy Restricted -Scope CurrentUser
# MCP协议通信服务 - PowerShell实现
# 使用PowerShell实现MCP协议通信,提供百度热点数据
# 设置参数
param(
[switch]$Test
)
# 调试变量设置
$EnableLogging = 1 # 设置为1时才会生成输出日志
$DetailedLogging = 1 # 在$EnableLogging为1的前提下,设置为1时才会记录详细内容
$UseMockData = 0 # 设置为1时返回测试数据,0时返回真实API数据
# 设置UTF-8编码
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$PSDefaultParameterValues['*:Encoding'] = 'utf8'
# 日志文件设置(只有在启用日志时才会实际创建文件)
$scriptPath = $PSScriptRoot
$logFile = Join-Path -Path $scriptPath -ChildPath "log.txt"
# 如果启用日志并且文件不存在,则创建一个空文件
if ($EnableLogging -eq 1) {
if (-not (Test-Path -Path $logFile)) {
$null = New-Item -Path $logFile -ItemType File -Force
}
# 初始日志记录
"MCP服务启动 - PowerShell版本 $(Get-Date)" | Out-File -FilePath $logFile -Encoding utf8
}
# 日志函数,根据调试变量决定是否记录日志
function Write-Log {
param(
[string]$Message,
[switch]$Detailed,
[switch]$Force
)
# 只有在启用日志或强制记录时才记录日志
if (($EnableLogging -eq 1) -or $Force) {
if ($Force -and ($EnableLogging -eq 0)) {
# 强制记录且日志未启用,确保文件存在
if (-not (Test-Path -Path $logFile)) {
$null = New-Item -Path $logFile -ItemType File -Force
}
}
# 如果是详细日志,检查$DetailedLogging
if ($Detailed) {
if ($DetailedLogging -eq 1) {
$Message | Out-File -FilePath $logFile -Append -Encoding utf8
}
}
else {
# 非详细日志,直接记录
$Message | Out-File -FilePath $logFile -Append -Encoding utf8
}
}
}
# 定义标准JSON响应模板 - 确保完全符合MCP协议规范
$initResponseTemplate = @'
{"jsonrpc":"2.0","id":REPLACE_ID,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"baidu_hotspot_ps","version":"1.0.0"}}}
'@
# 旧版工具列表响应模板(不再使用)
# $toolsListResponseTemplate = @'
# {"jsonrpc":"2.0","id":REPLACE_ID,"result":{"tools":[{"name":"getBaiduHotspot","description":"获取百度热点新闻列表","inputSchema":{"type":"object","properties":{},"required":[]},"outputSchema":{"type":"object","properties":{"content":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"hot":{"type":"string"},"url":{"type":"string"},"index":{"type":"integer"}},"required":["title","hot","url","index"]}},"success":{"type":"boolean"},"name":{"type":"string"},"subtitle":{"type":"string"},"update_time":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"hot":{"type":"string"},"url":{"type":"string"},"mobil_url":{"type":"string"},"index":{"type":"integer"}},"required":["title","hot","url","index"]}}},"required":["content"]}}]}}
# '@
# 定义MCP工具列表获取函数
function Get-MCP-ToolList {
param()
# 注意: 这里的工具列表定义要和实际输出格式完全匹配
# 修改后的工具定义,输出架构改为包含content字段
$toolList = @{
tools = @(
@{
name = "getBaiduHotspot"
description = "Get Baidu Hot News List"
inputSchema = @{
type = "object"
properties = @{}
required = @()
}
outputSchema = @{
type = "object"
properties = @{
content = @{
type = "array"
items = @{
type = "object"
properties = @{
type = @{
type = "string"
enum = @("text")
}
text = @{
type = "string"
}
}
required = @("type", "text")
}
}
}
required = @("content")
}
}
)
}
return $toolList
}
# 定义获取百度热点数据的函数
function Get-BaiduHotspot {
param(
[switch]$ReturnError
)
try {
# 检查是否使用测试数据
if ($UseMockData -eq 1) {
# 构造简单的测试数据结构
$mockData = @{
success = $true
name = "百度热点"
subtitle = "指数"
update_time = "2025-04-25 16:36:39"
data = @(
@{
title = "热点标题1"
hot = "780.9万"
url = "https://www.baidu.com/"
index = 1
},
@{
title = "热点标题2"
hot = "771.2万"
url = "https://www.baidu.com/"
index = 2
}
)
}
Write-Log -Message "使用测试数据,返回 $($mockData.data.Count) 条记录"
return $mockData
}
# 使用WebClient代替Invoke-RestMethod,确保UTF-8编码处理正确
$apiUrl = "https://api.vvhan.com/api/hotlist/baiduRD"
# 创建WebClient实例并设置UTF-8编码 注意这里是个坑,如果使用Invoke-RestMethod,中文会乱码
$webClient = New-Object System.Net.WebClient
$webClient.Encoding = [System.Text.Encoding]::UTF8
# 获取API数据
$jsonData = $webClient.DownloadString($apiUrl)
$rawData = $jsonData | ConvertFrom-Json
Write-Log -Message "API调用成功: $apiUrl"
return $rawData
}
catch {
$errorMsg = "API调用失败: $($_.Exception.Message)"
Write-Log -Message $errorMsg -Force
if ($ReturnError) {
return @{
error = $true
code = -32603
message = "Failed to call Baidu Hotspot API: $($_.Exception.Message)"
}
}
else {
throw $errorMsg
}
}
}
$errorResponseTemplate = @'
{"jsonrpc":"2.0","error":{"code":-32601,"message":"方法不存在或不支持"},"id":REPLACE_ID}
'@
$parseErrorResponseTemplate = @'
{"jsonrpc":"2.0","error":{"code":-32700,"message":"解析错误"},"id":REPLACE_ID}
'@
# API通用错误响应模板
$apiErrorTemplate = @'
{"jsonrpc":"2.0","error":{"code":-32603,"message":"REPLACE_MESSAGE"},"id":REPLACE_ID}
'@
# 测试模式
if ($Test) {
Write-Host "测试模式:" -ForegroundColor Yellow
Write-Host "EnableLogging = $EnableLogging, DetailedLogging = $DetailedLogging" -ForegroundColor Cyan
if ($EnableLogging -eq 1) {
Write-Host "日志文件将创建在: $logFile" -ForegroundColor Cyan
}
else {
Write-Host "日志记录已禁用,不会创建日志文件" -ForegroundColor Cyan
}
Write-Host "{}" -ForegroundColor Green
Write-Host ($initResponseTemplate -replace "REPLACE_ID", "0") -ForegroundColor Green
# 显示工具列表响应
$toolList = Get-MCP-ToolList
$toolResponse = @{
jsonrpc = "2.0"
id = 1
result = $toolList
}
$toolResponseString = $toolResponse | ConvertTo-Json -Depth 10 -Compress
Write-Host $toolResponseString -ForegroundColor Green
# 测试API调用
try {
$testData = Get-BaiduHotspot
# 生成标准MCP格式响应,确保符合协议规范
$testResponse = ConvertTo-Json -InputObject @{
jsonrpc = "2.0"
id = 2
result = @{
content = @(
@{
type = "text"
text = ($testData | ConvertTo-Json -Depth 10 -Compress)
}
)
}
} -Depth 20 -Compress
Write-Host "API调用成功响应示例:" -ForegroundColor Cyan
Write-Host $testResponse -ForegroundColor Green
# 显示数据预览
Write-Host "数据预览 (前3条热点数据):" -ForegroundColor Cyan
foreach ($item in ($testData.data | Select-Object -First 3)) {
Write-Host " [$($item.title)] - $($item.hot)" -ForegroundColor Magenta
}
}
catch {
$errorResponse = $apiErrorTemplate -replace "REPLACE_MESSAGE", "调用百度热点API失败: $($_.Exception.Message)" -replace "REPLACE_ID", "2"
Write-Host "API调用失败响应示例:" -ForegroundColor Red
Write-Host $errorResponse -ForegroundColor Yellow
}
Write-Host "错误响应示例:" -ForegroundColor Cyan
Write-Host ($errorResponseTemplate -replace "REPLACE_ID", "null") -ForegroundColor Green
exit
}
# 1. 服务启动握手 - 发送空对象
Write-Output "{}"
Write-Log -Message "发送初始握手: {}"
# 主处理循环
try {
# 使用更稳定的方式读取标准输入
$stdin = [System.Console]::In
while ($true) {
try {
# 读取一行输入
$inputLine = $stdin.ReadLine()
# 如果输入为null或空,表示已到达输入流末尾,退出循环
if ($null -eq $inputLine) {
Write-Log -Message "收到null输入,可能是流已关闭,退出循环" -Force
break
}
if ([string]::IsNullOrWhiteSpace($inputLine)) {
continue
}
# 记录接收到的输入
Write-Log -Message "接收: $inputLine" -Detailed:($DetailedLogging -eq 1)
# 解析JSON请求
try {
$jsonRequest = $inputLine | ConvertFrom-Json
# 检查消息类型并响应
if ($jsonRequest.method -eq "initialize") {
# 3. 初始化响应 - 使用动态ID
$responseId = $jsonRequest.id
$initResponse = $initResponseTemplate -replace "REPLACE_ID", $responseId
Write-Output $initResponse
Write-Log -Message "发送初始化响应 (ID: $responseId)"
Write-Log -Message "响应内容: $initResponse" -Detailed
}
elseif ($jsonRequest.method -eq "tools/list") {
# 使用Get-MCP-ToolList函数获取简化的工具列表
$responseId = $jsonRequest.id # 获取请求的实际ID
$toolList = Get-MCP-ToolList
# 构造工具列表响应
$toolResponse = @{
jsonrpc = "2.0"
id = $responseId
result = $toolList
}
# 将工具列表转换为JSON
$toolResponseString = $toolResponse | ConvertTo-Json -Depth 10 -Compress
# 输出响应
Write-Output $toolResponseString
Write-Log -Message "发送工具列表 (ID: $responseId)"
Write-Log -Message "响应内容: $toolResponseString" -Detailed
}
elseif ($jsonRequest.method -eq "tools/call") {
# 提取ID
$responseId = $jsonRequest.id
try {
# 获取百度热点实时数据
$hotspotData = Get-BaiduHotspot
# 构造符合新schema的响应格式
$response = @{
jsonrpc = "2.0"
id = $responseId
result = @{
content = @(
@{
type = "text"
text = ($hotspotData | ConvertTo-Json -Depth 10 -Compress)
}
)
}
}
# 将响应转换为JSON
$responseString = $response | ConvertTo-Json -Depth 10 -Compress
# 发送热点数据响应
Write-Output $responseString
Write-Log -Message "发送热点数据 (ID: $responseId)"
Write-Log -Message "响应内容: $responseString" -Detailed
# 输出数据细节,便于调试
if ($DetailedLogging -eq 1) {
Write-Log -Message "热点数据详情:"
try {
Write-Log -Message "热点数据 (限制5条):"
foreach ($item in $hotspotData.data | Select-Object -First 5) {
Write-Log -Message " - [$($item.title)] - $($item.hot)"
}
}
catch {
Write-Log -Message " 解析热点数据失败: $_" -Force
}
}
}
catch {
# API调用失败,返回错误响应
$errorMessage = "调用百度热点API失败: $($_.Exception.Message)"
$apiError = $apiErrorTemplate -replace "REPLACE_MESSAGE", $errorMessage -replace "REPLACE_ID", $responseId
Write-Output $apiError
Write-Log -Message "发送API错误响应 (ID: $responseId)" -Force
Write-Log -Message "错误内容: $apiError" -Detailed
}
}
elseif ($jsonRequest.method -like "notifications/*") {
# 通知类消息不需要响应
Write-Log -Message "收到通知消息,不响应: $($jsonRequest.method)"
}
else {
# 未知方法,返回错误
# 使用请求的ID生成错误响应
if ($null -ne $jsonRequest.id) {
$errorResponse = $errorResponseTemplate -replace "REPLACE_ID", $jsonRequest.id
Write-Output $errorResponse
Write-Log -Message "发送错误响应 (ID: $($jsonRequest.id))"
Write-Log -Message "响应内容: $errorResponse" -Detailed
}
else {
$errorResponse = $errorResponseTemplate -replace "REPLACE_ID", "null"
Write-Output $errorResponse
Write-Log -Message "发送错误响应 (ID: null)"
Write-Log -Message "响应内容: $errorResponse" -Detailed
}
}
}
catch {
# JSON解析错误
Write-Log -Message "JSON解析错误: $_" -Force
# 返回JSON-RPC解析错误,无法获取ID,使用null
$parseErrorResponse = $parseErrorResponseTemplate -replace "REPLACE_ID", "null"
Write-Output $parseErrorResponse
Write-Log -Message "发送解析错误响应"
Write-Log -Message "响应内容: $parseErrorResponse" -Detailed
}
}
catch {
# 读取输入错误
Write-Log -Message "读取输入错误: $_" -Force
continue
}
}
}
catch {
Write-Log -Message "严重错误: $_" -Force
}
finally {
Write-Log -Message "MCP服务已停止 $(Get-Date)" -Force
}
示例配置cursor
{
"mcpServers": {
"baidu_hotspot": {
"timeout": 180,
"command": "powershell.exe",
"args": ["-ExecutionPolicy", "Bypass", "-NoProfile", "-File", "D:\\cursor\\test\\baidu_test.ps1"],
"description": "百度热点搜索服务"
}
}
}
结语
MCP协议为LLM应用开发提供了标准化的接口,使开发者能够以一致的方式为AI模型提供数据和功能。无论使用何种编程语言实现,只要遵循上述规范,就能确保与支持MCP的应用程序正确交互。