«

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协议基于以下几个核心概念:

  1. 资源 (Resources) - 提供数据给LLM,类似于GET端点
  2. 工具 (Tools) - 允许LLM执行操作,类似于POST端点
  3. 提示 (Prompts) - 可重用的交互模板
  4. 通知 (Notifications) - 无需响应的事件通知

通信方式

MCP支持多种通信方式:

  1. 标准输入/输出 (stdio) - 命令行界面通信
  2. Server-sent events (SSE) - Web应用程序通信
  3. 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

说明:

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

说明:

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

说明:

重要outputSchema必须准确定义工具的返回格式,客户端会严格按照这个格式解析结果。

7. 工具调用请求

客户端输入: {"method":"tools/call","params":{"name":"toolName","arguments":{}},"jsonrpc":"2.0","id":2}\n

说明:

8. 工具调用响应(符合MCP v2的标准格式)

服务端输出: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"工具返回的内容"}]}}\n

说明:

注意:响应格式必须严格匹配在工具列表中声明的outputSchema

错误处理

MCP使用标准JSON-RPC 2.0错误处理机制:

{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32603,
    "message": "调用工具时发生错误:详细错误信息"
  }
}

常见错误代码:

高级功能

进度通知

对于长时间运行的请求,服务端可以发送进度通知:

{
  "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"
      }
    ]
  }
}

常见错误与调试

  1. 工具列表与工具调用格式不匹配:确保outputSchema与实际返回格式一致
  2. JSON解析错误:检查JSON格式是否有效,特别是引号、逗号等
  3. 缺少换行符:每条消息必须以\n结尾
  4. 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的应用程序正确交互。

更多详细信息,请参考官方文档规范