大语言模型实战(十六)——MCP完整语法指南:从零掌握Model Context Protocol的所有语法和模式

《MCP完整语法指南:从零掌握Model Context Protocol的所有语法和模式》

目录

  1. MCP基础概念
  2. 通信架构
  3. Server端语法
  4. Client端语法
  5. 四大功能完整语法
  6. 高级模式
  7. 错误处理
  8. 完整工作流

1 MCP基础概念

1.1 MCP是什么?

MCP (Model Context Protocol) = 模型上下文协议

复制代码
MCP的核心目标:
┌─────────────────────────────────────────┐
│ 标准化AI应用之间的通信方式               │
│                                         │
│ Server(数据/工具提供方)                │
│        ↕ (JSON-RPC 2.0)                 │
│ Client(数据/工具消费方)                │
│        ↕ (stdio / HTTP)                 │
│ LLM (实际生成者)                        │
└─────────────────────────────────────────┘

1.2 MCP的四大功能

功能 符号 作用 使用场景
Resources 📁 提供被动数据 文档、配置、文件
Tools 🔧 执行主动操作 API调用、计算、搜索
Prompts 💬 提供提示词 提示词管理、LLM指引
Sampling 🤖 请求LLM采样 LLM调用、参数调整

1.3 MCP的通信协议

复制代码
┌─────────────────────────┐
│  JSON-RPC 2.0 协议      │
├─────────────────────────┤
│ method: "resources/list"│  【关键】固定的方法名
│ params: {...}           │  【关键】参数
│ id: 1                   │  【关键】请求ID
└─────────────────────────┘

2 通信架构

2.1 stdio通信方式(本地)

python 复制代码
# 【Server端】启动stdio服务
from mcp.server.stdio import stdio_server

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await app.run(read_stream, write_stream, app.create_initialization_options())

# 【Client端】连接stdio服务
from mcp.client.stdio import stdio_client, StdioServerParameters

params = StdioServerParameters(
    command=sys.executable,      # Python解释器
    args=["server.py"],          # Server脚本
    env=None                     # 环境变量
)

async with stdio_client(params) as (reader, writer):
    async with ClientSession(reader, writer) as session:
        await session.initialize()

2.2 完整的握手流程

复制代码
┌─ Client                                Server
│
├─ 1. 启动Server进程 (stdio)
│  └─ 建立stdin/stdout通道
│
├─ 2. Client初始化
│  ├─ async with ClientSession(reader, writer)
│  └─ await session.initialize()
│
├─ 3. Server响应初始化
│  └─ 返回初始化配置
│
├─ 4. 发送通知
│  ├─ await session.send_notification(
│  │      method="notifications/initialized"
│  │  )
│  └─ 握手完成 ✅
│
└─ 5. 开始通信
   ├─ list_resources()
   ├─ read_resource(uri)
   ├─ list_tools()
   ├─ call_tool(name, args)
   ├─ list_prompts()
   ├─ get_prompt(name, args)
   └─ 采样请求...

3 Server端语法

3.1 Server初始化

python 复制代码
from mcp.server import Server
import mcp.types as types
from mcp.server.stdio import stdio_server
import asyncio

# 【步骤1】创建Server实例
app = Server("my-server-name")

# 【步骤2】定义处理器(装饰器模式)
@app.list_resources()
async def list_resources() -> list[types.Resource]:
    pass

@app.read_resource()
async def read_resource(uri: str) -> str:
    pass

@app.list_tools()
async def list_tools() -> list[types.Tool]:
    pass

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    pass

@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
    pass

@app.get_prompt()
async def get_prompt(name: str, arguments: dict = None) -> types.GetPromptResult:
    pass

# 【步骤3】启动Server
async def main():
    async with stdio_server() as (read_stream, write_stream):
        await app.run(read_stream, write_stream, app.create_initialization_options())

if __name__ == "__main__":
    asyncio.run(main())

3.2 Server端的四个装饰器

3.2.1 Resources装饰器

python 复制代码
# 【装饰器1】list_resources
@app.list_resources()
async def list_resources() -> list[types.Resource]:
    """
    【必须】返回所有可用资源的列表
    
    返回值格式:List[types.Resource]
    每个Resource包含:
    - uri: str              (唯一标识)
    - name: str             (显示名称)
    - description: str      (可选,描述)
    - mimeType: str         (可选,MIME类型)
    """
    return [
        types.Resource(
            uri="file:///path/to/file.txt",
            name="example.txt",
            description="Example file",
            mimeType="text/plain"
        )
    ]

# 【装饰器2】read_resource
@app.read_resource()
async def read_resource(uri: str) -> str:
    """
    【必须】读取指定URI的资源内容
    
    输入:uri (str)
    输出:资源内容 (str)
    """
    path = uri.replace("file://", "")
    with open(path, 'r') as f:
        return f.read()

3.2.2 Tools装饰器

python 复制代码
# 【装饰器3】list_tools
@app.list_tools()
async def list_tools() -> list[types.Tool]:
    """
    【必须】返回所有可用工具的列表
    
    每个Tool包含:
    - name: str                    (工具唯一标识)
    - description: str             (工具描述)
    - inputSchema: dict            (参数JSON Schema)
    """
    return [
        types.Tool(
            name="calculator",
            description="执行数学运算",
            inputSchema={
                "type": "object",
                "properties": {
                    "a": {"type": "number"},
                    "b": {"type": "number"},
                    "operation": {
                        "type": "string",
                        "enum": ["add", "subtract", "multiply", "divide"]
                    }
                },
                "required": ["a", "b", "operation"]
            }
        )
    ]

# 【装饰器4】call_tool
@app.call_tool()
async def call_tool(
    name: str,
    arguments: dict
) -> list[types.TextContent]:
    """
    【必须】执行指定工具
    
    输入:
    - name: 工具名称
    - arguments: 工具参数
    
    输出:List[types.TextContent]
    """
    if name == "calculator":
        a = arguments.get("a")
        b = arguments.get("b")
        operation = arguments.get("operation")
        
        results = {
            "add": a + b,
            "subtract": a - b,
            "multiply": a * b,
            "divide": a / b if b != 0 else "Error"
        }
        
        return [types.TextContent(
            type="text",
            text=f"结果: {results[operation]}"
        )]
    
    return [types.TextContent(
        type="text",
        text=f"未知工具: {name}"
    )]

3.2.3 Prompts装饰器

python 复制代码
# 【装饰器5】list_prompts
@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
    """
    【必须】返回所有可用提示词模板
    
    每个Prompt包含:
    - name: str                    (模板唯一标识)
    - description: str             (模板描述)
    - arguments: List[PromptArgument]  (参数列表)
    """
    return [
        types.Prompt(
            name="code-review",
            description="代码审查模板",
            arguments=[
                types.PromptArgument(
                    name="code",
                    description="需要审查的代码",
                    required=True
                ),
                types.PromptArgument(
                    name="language",
                    description="编程语言",
                    required=True
                ),
                types.PromptArgument(
                    name="focus",
                    description="审查重点",
                    required=False
                )
            ]
        )
    ]

# 【装饰器6】get_prompt
@app.get_prompt()
async def get_prompt(
    name: str,
    arguments: dict = None
) -> types.GetPromptResult:
    """
    【必须】根据参数生成具体的提示词
    
    返回值:GetPromptResult
    包含messages列表,每个message是PromptMessage
    """
    if name == "code-review":
        code = arguments.get("code") if arguments else ""
        language = arguments.get("language", "Unknown")
        focus = arguments.get("focus", "general")
        
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text=f"你是{language}代码审查专家,专注于{focus}方面。"
                    )
                ),
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text",
                        text=f"请审查以下代码:\n{code}"
                    )
                )
            ]
        )
    
    raise ValueError(f"未知模板: {name}")

4 Client端语法

4.1 Client初始化

python 复制代码
import sys
import asyncio
from mcp import ClientSession
from mcp.client.stdio import stdio_client, StdioServerParameters
from mcp.types import Notification

async def main():
    # 【步骤1】定义Server参数
    params = StdioServerParameters(
        command=sys.executable,      # Python解释器路径
        args=["server.py"],          # Server脚本路径
        env=None                     # 环境变量
    )
    
    # 【步骤2】建立通信通道
    async with stdio_client(params) as (reader, writer):
        # 【步骤3】创建Client会话
        async with ClientSession(reader, writer) as session:
            # 【步骤4】初始化握手
            await session.initialize()
            
            # 【步骤5】发送初始化通知
            notification = Notification(
                method="notifications/initialized",
                params={}
            )
            await session.send_notification(notification)
            
            # 【步骤6】现在可以调用各种方法
            # ...后续操作...

if __name__ == "__main__":
    asyncio.run(main())

4.2 Client调用Resources

python 复制代码
async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    # 【方法1】列出所有资源
    response = await session.list_resources()
    for resource in response.resources:
        print(f"资源: {resource.name}")
        print(f"URI: {resource.uri}")
        print(f"描述: {resource.description}")
    
    # 【方法2】读取指定资源
    content = await session.read_resource("file:///path/to/file.txt")
    print(f"内容: {content}")

4.3 Client调用Tools

python 复制代码
async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    # 【方法1】列出所有工具
    response = await session.list_tools()
    for tool in response.tools:
        print(f"工具: {tool.name}")
        print(f"描述: {tool.description}")
        print(f"参数: {tool.inputSchema}")
    
    # 【方法2】调用指定工具
    result = await session.call_tool(
        name="calculator",
        arguments={
            "a": 5,
            "b": 3,
            "operation": "add"
        }
    )
    print(f"结果: {result.content[0].text}")

4.4 Client调用Prompts

python 复制代码
async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    # 【方法1】列出所有提示词模板
    response = await session.list_prompts()
    for prompt in response.prompts:
        print(f"模板: {prompt.name}")
        print(f"描述: {prompt.description}")
        print(f"参数: {prompt.arguments}")
    
    # 【方法2】获取参数化的提示词
    result = await session.get_prompt(
        name="code-review",
        arguments={
            "code": "def hello(): pass",
            "language": "Python",
            "focus": "performance"
        }
    )
    
    # 【步骤3】处理返回的messages
    for message in result.messages:
        print(f"角色: {message.role}")
        print(f"内容: {message.content.text}")

5 四大功能完整语法

5.1 Resources完整示例

python 复制代码
# ═════════════════════════════════════════
# Server端
# ═════════════════════════════════════════
import os
from pathlib import Path

DOC_DIR = str(Path(__file__).parent / "docs")

@app.list_resources()
async def list_resources() -> list[types.Resource]:
    """列出docs目录下的所有文件"""
    files = [f for f in os.listdir(DOC_DIR) if f.endswith(".txt")]
    return [
        types.Resource(
            uri=f"file://{os.path.join(DOC_DIR, fname)}",
            name=fname,
            description=f"Documentation: {fname}",
            mimeType="text/plain"
        )
        for fname in files
    ]

@app.read_resource()
async def read_resource(uri: str) -> str:
    """读取指定文件内容"""
    from urllib.parse import unquote
    path = unquote(uri.replace("file://", ""))
    with open(path, 'r', encoding='utf-8') as f:
        return f.read()

# ═════════════════════════════════════════
# Client端
# ═════════════════════════════════════════
async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    # 发现资源
    resources = await session.list_resources()
    
    # 读取每个资源
    for res in resources.resources:
        content = await session.read_resource(res.uri)
        print(f"{res.name}: {len(content)} 字符")

5.2 Tools完整示例

python 复制代码
# ═════════════════════════════════════════
# Server端
# ═════════════════════════════════════════
@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="search",
            description="搜索知识库",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "搜索关键词"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "返回结果数量"
                    }
                },
                "required": ["query"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "search":
        query = arguments.get("query")
        limit = arguments.get("limit", 10)
        
        # 【实现搜索逻辑】
        results = search_knowledge_base(query, limit)
        
        return [types.TextContent(
            type="text",
            text=f"找到 {len(results)} 个结果:\n" + 
                 "\n".join(f"- {r['title']}" for r in results)
        )]
    
    return [types.TextContent(type="text", text="工具不存在")]

# ═════════════════════════════════════════
# Client端
# ═════════════════════════════════════════
async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    # 发现工具
    tools = await session.list_tools()
    
    # 使用工具
    result = await session.call_tool(
        name="search",
        arguments={"query": "Python", "limit": 5}
    )
    print(result.content[0].text)

5.3 Prompts完整示例

python 复制代码
# ═════════════════════════════════════════
# Server端
# ═════════════════════════════════════════
@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
    return [
        types.Prompt(
            name="summarize",
            description="总结文本内容",
            arguments=[
                types.PromptArgument(
                    name="text",
                    description="要总结的文本",
                    required=True
                ),
                types.PromptArgument(
                    name="style",
                    description="总结风格",
                    required=False
                )
            ]
        )
    ]

@app.get_prompt()
async def get_prompt(name: str, arguments: dict = None) -> types.GetPromptResult:
    if name == "summarize":
        text = arguments.get("text", "") if arguments else ""
        style = arguments.get("style", "bullet") if arguments else "bullet"
        
        style_guide = {
            "bullet": "用要点列表形式",
            "paragraph": "用段落形式",
            "one-line": "用一句话"
        }
        
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="system",
                    content=types.TextContent(
                        type="text",
                        text=f"你是一个内容总结专家。{style_guide.get(style)}"
                    )
                ),
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text",
                        text=f"请总结以下内容:\n{text}"
                    )
                )
            ]
        )
    
    raise ValueError(f"未知模板: {name}")

# ═════════════════════════════════════════
# Client端 + LLM集成
# ═════════════════════════════════════════
from openai import OpenAI

llm_client = OpenAI(api_key="your_key")

async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    # 获取提示词
    prompt_result = await session.get_prompt(
        name="summarize",
        arguments={
            "text": "长文本内容...",
            "style": "bullet"
        }
    )
    
    # 转换为LLM格式
    messages = [
        {
            "role": msg.role,
            "content": msg.content.text
        }
        for msg in prompt_result.messages
    ]
    
    # 调用LLM
    response = llm_client.chat.completions.create(
        model="gpt-4",
        messages=messages
    )
    
    print(response.choices[0].message.content)

5.4 Sampling完整示例

python 复制代码
# ═════════════════════════════════════════
# Server端
# ═════════════════════════════════════════
import json

@app.get_prompt()
async def get_prompt(name: str, arguments: dict = None) -> types.GetPromptResult:
    if name == "with-sampling":
        question = arguments.get("question", "") if arguments else ""
        
        # 【关键】构建采样请求
        sampling_request = {
            "method": "sampling/createMessage",
            "params": {
                "messages": [
                    {
                        "role": "user",
                        "content": {"type": "text", "text": question}
                    }
                ],
                "systemPrompt": "你是一个专业的助手",
                "temperature": 0.7,
                "maxTokens": 1000,
                "modelPreferences": {
                    "hints": [{"name": "gpt-4"}],
                    "intelligencePriority": 0.9
                }
            }
        }
        
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text=json.dumps(sampling_request, ensure_ascii=False)
                    )
                )
            ]
        )

# ═════════════════════════════════════════
# Client端
# ═════════════════════════════════════════
async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    # 获取采样请求
    prompt_result = await session.get_prompt(
        name="with-sampling",
        arguments={"question": "什么是MCP?"}
    )
    
    # 解析采样请求
    sampling_request = json.loads(prompt_result.messages[0].content.text)
    
    # 【可选】用户可以调整参数
    sampling_request["params"]["temperature"] = 0.3  # 更保守
    
    # 【关键】执行采样
    params = sampling_request["params"]
    response = llm_client.chat.completions.create(
        model="gpt-4",
        messages=params["messages"],
        temperature=params.get("temperature", 0.7),
        max_tokens=params.get("maxTokens", 1000)
    )
    
    print(response.choices[0].message.content)

6 高级模式

6.1 FastMCP简化语法

python 复制代码
# ═════════════════════════════════════════
# FastMCP (推荐用于简单场景)
# ═════════════════════════════════════════
from mcp.server.fastmcp import FastMCP

# 【步骤1】创建FastMCP实例
mcp = FastMCP("my-service")

# 【步骤2】用装饰器定义工具(自动生成JSON Schema)
@mcp.tool()
async def calculator(operation: str, a: float, b: float) -> str:
    """执行数学运算
    
    Args:
        operation: 运算类型 (add, subtract, multiply, divide)
        a: 第一个数字
        b: 第二个数字
    
    Returns:
        str: 计算结果
    """
    if operation == "add":
        return f"结果: {a + b}"
    elif operation == "divide":
        if b == 0:
            return "错误: 除数不能为零"
        return f"结果: {a / b}"
    # ...其他操作

# 【步骤3】启动
if __name__ == "__main__":
    mcp.run(transport="stdio")

6.2 错误处理模式

python 复制代码
# ═════════════════════════════════════════
# Server端错误处理
# ═════════════════════════════════════════
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    """【鲁棒】包含完整的错误处理"""
    
    try:
        # 【步骤1】验证工具存在
        if name not in AVAILABLE_TOOLS:
            raise ValueError(f"工具不存在: {name}")
        
        # 【步骤2】验证参数
        required_args = TOOLS_CONFIG[name].get("required", [])
        for arg in required_args:
            if arg not in arguments:
                raise ValueError(f"缺少必需参数: {arg}")
        
        # 【步骤3】类型验证和转换
        try:
            a = float(arguments.get("a", 0))
            b = float(arguments.get("b", 0))
        except (TypeError, ValueError):
            raise ValueError("参数必须是数字")
        
        # 【步骤4】执行工具
        result = execute_tool(name, a, b)
        
        return [types.TextContent(
            type="text",
            text=f"成功: {result}"
        )]
    
    except ValueError as e:
        # 【关键】返回错误消息而不是抛出异常
        return [types.TextContent(
            type="text",
            text=f"错误: {str(e)}"
        )]
    
    except Exception as e:
        # 【关键】捕获未预期的异常
        return [types.TextContent(
            type="text",
            text=f"内部错误: {str(e)}"
        )]

# ═════════════════════════════════════════
# Client端错误处理
# ═════════════════════════════════════════
async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    try:
        # 【步骤1】验证Server连接
        tools = await session.list_tools()
        if not tools.tools:
            print("⚠️ Server没有提供任何工具")
        
        # 【步骤2】调用工具
        result = await session.call_tool(
            name="calculator",
            arguments={"a": 5, "b": "invalid"}  # 故意传入错误类型
        )
        
        # 【步骤3】检查结果
        if result.content:
            text = result.content[0].text
            if "错误" in text:
                print(f"⚠️ Server返回错误: {text}")
            else:
                print(f"✅ 成功: {text}")
    
    except Exception as e:
        print(f"❌ 调用失败: {e}")

6.3 类型系统

python 复制代码
# ═════════════════════════════════════════
# MCP中的所有类型
# ═════════════════════════════════════════

# Resource相关
types.Resource(
    uri: str,                    # 【必须】资源唯一标识
    name: str,                   # 【必须】显示名称
    description: Optional[str],  # 【可选】描述
    mimeType: Optional[str]      # 【可选】MIME类型
)

# Tool相关
types.Tool(
    name: str,                   # 【必须】工具名称
    description: str,            # 【必须】工具描述
    inputSchema: dict            # 【必须】参数JSON Schema
)

# Prompt相关
types.Prompt(
    name: str,                   # 【必须】模板名称
    description: str,            # 【必须】模板描述
    arguments: List[PromptArgument]  # 【必须】参数列表
)

types.PromptArgument(
    name: str,                   # 【必须】参数名
    description: str,            # 【必须】参数描述
    required: bool               # 【必须】是否必需
)

# Message相关
types.PromptMessage(
    role: str,                   # 【必须】"assistant" 或 "user"
    content: TextContent         # 【必须】内容
)

types.TextContent(
    type: str,                   # 【必须】"text"
    text: str                    # 【必须】文本内容
)

# 结果相关
types.GetPromptResult(
    messages: List[PromptMessage]  # 【必须】消息列表
)

types.ListResourcesResult(
    resources: List[Resource]      # 【必须】资源列表
)

types.ListToolsResult(
    tools: List[Tool]              # 【必须】工具列表
)

types.ListPromptsResult(
    prompts: List[Prompt]          # 【必须】提示词列表
)

# 内容相关
types.ToolResult(
    type: str,                     # "text" 或 "image"
    text: Optional[str],           # 文本内容
    image: Optional[dict]          # 图片数据
)

7 错误处理

7.1 常见错误及解决方案

python 复制代码
# ═════════════════════════════════════════
# 【错误1】装饰器名称错误
# ═════════════════════════════════════════

# ❌ 错误
@app.list_tool()  # 应该是 list_tools()
async def list_tool():
    pass

# ✅ 正确
@app.list_tools()
async def list_tools():
    pass

# ═════════════════════════════════════════
# 【错误2】参数类型错误
# ═════════════════════════════════════════

# ❌ 错误
@app.call_tool()
async def call_tool(name, arguments):  # 缺少类型注解
    pass

# ✅ 正确
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    pass

# ═════════════════════════════════════════
# 【错误3】返回值格式错误
# ═════════════════════════════════════════

# ❌ 错误
return "直接返回字符串"  # 应该返回list[types.TextContent]

# ✅ 正确
return [types.TextContent(type="text", text="结果")]

# ═════════════════════════════════════════
# 【错误4】JSON Schema格式错误
# ═════════════════════════════════════════

# ❌ 错误
inputSchema={
    "type": "object",
    "properties": {
        "a": "number"  # ❌ 应该是 {"type": "number"}
    }
}

# ✅ 正确
inputSchema={
    "type": "object",
    "properties": {
        "a": {"type": "number"}
    },
    "required": ["a"]
}

# ═════════════════════════════════════════
# 【错误5】消息格式错误
# ═════════════════════════════════════════

# ❌ 错误
types.PromptMessage(
    role="user",
    content="直接传字符串"  # ❌ 应该是TextContent对象
)

# ✅ 正确
types.PromptMessage(
    role="user",
    content=types.TextContent(
        type="text",
        text="实际内容"
    )
)

8 完整工作流

8.1 端到端示例

python 复制代码
# ═══════════════════════════════════════════════
# 完整的代码审查系统示例
# ═══════════════════════════════════════════════

# ───────────────────────────────────────────────
# 第1部分:Server端定义
# ───────────────────────────────────────────────
from mcp.server import Server
import mcp.types as types
from mcp.server.stdio import stdio_server
import asyncio
import json

app = Server("code-review-server")

# 1️⃣ 定义Resources - 提供审查规则
REVIEW_RULES = {
    "file:///rules/python.md": "Python代码审查规则",
    "file:///rules/javascript.md": "JavaScript代码审查规则"
}

@app.list_resources()
async def list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri=uri,
            name=uri.split("/")[-1],
            description=desc,
            mimeType="text/markdown"
        )
        for uri, desc in REVIEW_RULES.items()
    ]

@app.read_resource()
async def read_resource(uri: str) -> str:
    if uri == "file:///rules/python.md":
        return "# Python代码审查规则\n1. 遵循PEP8\n2. 类型注解..."
    return "规则不存在"

# 2️⃣ 定义Tools - 执行审查操作
@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="lint",
            description="执行静态检查",
            inputSchema={
                "type": "object",
                "properties": {
                    "code": {"type": "string"},
                    "language": {"type": "string"}
                },
                "required": ["code", "language"]
            }
        ),
        types.Tool(
            name="test-coverage",
            description="检查测试覆盖率",
            inputSchema={
                "type": "object",
                "properties": {
                    "code": {"type": "string"}
                },
                "required": ["code"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "lint":
        code = arguments.get("code", "")
        language = arguments.get("language", "")
        # 【实现】调用linter
        issues = lint_code(code, language)
        return [types.TextContent(
            type="text",
            text=f"发现 {len(issues)} 个问题:\n" + 
                 "\n".join(f"- {issue}" for issue in issues)
        )]
    
    elif name == "test-coverage":
        code = arguments.get("code", "")
        coverage = calculate_coverage(code)
        return [types.TextContent(
            type="text",
            text=f"测试覆盖率: {coverage}%"
        )]
    
    return [types.TextContent(type="text", text="工具不存在")]

# 3️⃣ 定义Prompts - 提供审查提示词
@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
    return [
        types.Prompt(
            name="code-review",
            description="代码审查",
            arguments=[
                types.PromptArgument(name="code", description="代码", required=True),
                types.PromptArgument(name="language", description="语言", required=True),
                types.PromptArgument(name="focus", description="重点", required=False)
            ]
        )
    ]

@app.get_prompt()
async def get_prompt(name: str, arguments: dict = None) -> types.GetPromptResult:
    if name == "code-review":
        code = arguments.get("code", "") if arguments else ""
        language = arguments.get("language", "Python") if arguments else "Python"
        focus = arguments.get("focus", "quality") if arguments else "quality"
        
        return types.GetPromptResult(
            messages=[
                types.PromptMessage(
                    role="assistant",
                    content=types.TextContent(
                        type="text",
                        text=f"你是一个资深的{language}代码审查专家,专注于{focus}。"
                    )
                ),
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(
                        type="text",
                        text=f"请审查以下代码:\n{code}"
                    )
                )
            ]
        )
    
    raise ValueError(f"未知模板: {name}")

# 启动Server
async def main():
    async with stdio_server() as (read_stream, write_stream):
        await app.run(read_stream, write_stream, app.create_initialization_options())

if __name__ == "__main__":
    asyncio.run(main())

# ───────────────────────────────────────────────
# 第2部分:Client端调用
# ───────────────────────────────────────────────
import sys
from mcp import ClientSession
from mcp.client.stdio import stdio_client, StdioServerParameters
from openai import OpenAI

class CodeReviewClient:
    def __init__(self):
        self.session = None
        self.llm = OpenAI(api_key="sk-...")
    
    async def connect(self, server_script):
        """连接Server"""
        params = StdioServerParameters(
            command=sys.executable,
            args=[server_script]
        )
        
        async with stdio_client(params) as (reader, writer):
            self.session = await ClientSession(reader, writer).__aenter__()
            await self.session.initialize()
    
    async def review_code(self, code: str, language: str):
        """执行代码审查流程"""
        
        # 【步骤1】获取审查规则(Resource)
        resources = await self.session.list_resources()
        relevant_rule = [r for r in resources.resources 
                        if language.lower() in r.name.lower()]
        
        if relevant_rule:
            rule_content = await self.session.read_resource(relevant_rule[0].uri)
            print(f"📋 审查规则:\n{rule_content[:200]}...")
        
        # 【步骤2】执行静态检查(Tool)
        lint_result = await self.session.call_tool(
            name="lint",
            arguments={"code": code, "language": language}
        )
        print(f"🔍 静态检查:\n{lint_result.content[0].text}")
        
        # 【步骤3】检查测试覆盖率(Tool)
        coverage_result = await self.session.call_tool(
            name="test-coverage",
            arguments={"code": code}
        )
        print(f"📊 测试覆盖率:\n{coverage_result.content[0].text}")
        
        # 【步骤4】使用Prompt获取LLM指引
        prompt_result = await self.session.get_prompt(
            name="code-review",
            arguments={"code": code, "language": language, "focus": "quality"}
        )
        
        # 【步骤5】调用LLM进行审查
        messages = [
            {
                "role": msg.role,
                "content": msg.content.text
            }
            for msg in prompt_result.messages
        ]
        
        # 添加前面的检查结果
        messages.append({
            "role": "user",
            "content": f"前面的检查结果:\n{lint_result.content[0].text}\n{coverage_result.content[0].text}"
        })
        
        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=messages
        )
        
        print(f"✨ LLM审查意见:\n{response.choices[0].message.content}")

# 使用
async def main():
    client = CodeReviewClient()
    await client.connect("server.py")
    
    sample_code = """
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)
    """
    
    await client.review_code(sample_code, "Python")

if __name__ == "__main__":
    asyncio.run(main())

8.2 调试工作流

python 复制代码
# ═══════════════════════════════════════════════
# 调试技巧
# ═══════════════════════════════════════════════

# 1️⃣ 打印所有可用的功能
async with ClientSession(reader, writer) as session:
    await session.initialize()
    
    print("【Resources】")
    resources = await session.list_resources()
    for r in resources.resources:
        print(f"  - {r.name}")
    
    print("\n【Tools】")
    tools = await session.list_tools()
    for t in tools.tools:
        print(f"  - {t.name}")
    
    print("\n【Prompts】")
    prompts = await session.list_prompts()
    for p in prompts.prompts:
        print(f"  - {p.name}")

# 2️⃣ 验证返回值
result = await session.call_tool(name="test", arguments={})
print(f"类型: {type(result)}")
print(f"内容: {result.content}")
for item in result.content:
    print(f"  - 类型: {item.type}")
    print(f"  - 文本: {item.text}")

# 3️⃣ 打印JSON Schema
tools = await session.list_tools()
for tool in tools.tools:
    print(f"\n工具: {tool.name}")
    print(f"Schema: {json.dumps(tool.inputSchema, indent=2, ensure_ascii=False)}")

# 4️⃣ 测试参数验证
import json
tool_schema = tools.tools[0].inputSchema
properties = tool_schema.get("properties", {})
required = tool_schema.get("required", [])

for param in required:
    print(f"【必需参数】 {param}: {properties[param]}")

for param in properties:
    if param not in required:
        print(f"【可选参数】 {param}: {properties[param]}")

总结

语法速查表

功能 Server方法 Client方法 返回类型
Resources @app.list_resources() session.list_resources() List[Resource]
Resources @app.read_resource() session.read_resource(uri) str
Tools @app.list_tools() session.list_tools() List[Tool]
Tools @app.call_tool() session.call_tool(name, args) List[TextContent]
Prompts @app.list_prompts() session.list_prompts() List[Prompt]
Prompts @app.get_prompt() session.get_prompt(name, args) GetPromptResult
Sampling 返回采样请求 解析并执行采样 LLM回复

最佳实践

  1. 始终使用类型注解 - 帮助IDE和类型检查
  2. 返回正确的类型 - Server必须返回List[TextContent]
  3. 完整的JSON Schema - 帮助Client验证参数
  4. 清晰的描述 - 让用户理解功能
  5. 错误处理 - 返回错误信息而不是抛异常
  6. 使用sys.executable - 跨环境兼容性
  7. 异步编程 - 所有I/O操作使用await

相关推荐
向量引擎19 小时前
【万字硬核】解密GPT-5.2-Pro与Sora2底层架构:从Transformer到世界模型,手撸一个高并发AI中台(附Python源码+压测报告)
人工智能·gpt·ai·aigc·ai编程·ai写作·api调用
while(awake) code19 小时前
L1 书生大模型提示词实践
人工智能
俊哥V19 小时前
[笔记.AI]谷歌Gemini-Opal上手初探
人工智能·ai·gemini·opal
code bean19 小时前
【AI】AI大模型之流式传输(前后端技术实现)
人工智能·ai·大模型·流式传输
黑客思维者19 小时前
二次函数模型完整训练实战教程,理解非线性模型的拟合逻辑(超详细,零基础可懂)
人工智能·语言模型·非线性拟合·二次函数模型
小途软件19 小时前
ssm607家政公司服务平台的设计与实现+vue
java·人工智能·pytorch·python·深度学习·语言模型
WJSKad123519 小时前
传送带物体检测识别_基于YOLO11与RGCSPELAN改进算法_工业视觉检测系统
人工智能·算法·视觉检测
富唯智能19 小时前
重新定义“自动化搬运项目”:15分钟部署的复合机器人如何革新柔性生产
人工智能·机器人·自动化
zxy284722530119 小时前
利用C#对接BotSharp本地大模型AI Agent示例(2)
人工智能·c#·api·ai agent·botsharp
初次攀爬者19 小时前
RAG知识库核心优化|基于语义的智能文本切片方案(对比字符串长度分割)
人工智能·后端