Python mcp研究:入门到精通

一文吃透 Model Context Protocol,让你的 AI 应用轻松接入外部数据与工具

前言:为什么你需要了解 MCP?

如果你曾尝试给大语言模型(LLM)连接数据库、调用 API、读取本地文件......你一定经历过被各种"胶水代码"折磨的时刻。每个数据源都要单独写一个插件,每个工具都要重新适配,维护成本高到想放弃。

直到 MCP(Model Context Protocol) 出现。它是由 Anthropic(Claude 的创造者)提出的开放协议,旨在统一大模型与外部世界的接口。简单说:MCP 就像 AI 世界的 USB-C 接口------你的模型可以即插即用地访问文件、数据库、API、浏览器、本地服务,甚至操作远程服务器。

而 Python 作为 AI 生态的第一语言,自然也是 MCP 的首选实现语言。本文将带你从零开始,一步步掌握如何使用 Python 开发 MCP 服务器和客户端,并提供大量可直接运行的代码示例和输出结果,让你学完就能落地。


第一部分:MCP 核心概念(极速入门)

1.1 什么是 MCP?

MCP 定义了三个核心角色:

  • MCP Host(宿主):运行 LLM 的应用,希望获取外部数据或执行操作。例如 Claude Desktop、Cline、Continue 等。

  • MCP Client(客户端):嵌入在 Host 中,负责与 Server 通信。

  • MCP Server(服务器):提供数据或能力的独立服务。可以是本地进程,也可以是远程服务。

交互流程:

Host → Client → Server(通过 JSON-RPC over stdio/SSE)→ 返回结果 → Host 将结果交给 LLM 增强回答。

1.2 MCP 提供三种原语(Primitives)

LLM 可以自动决定调用哪个 Tool 或读取哪个 Resource(通过 Client 端的 function calling 能力)。

1.3 Python 生态中的 MCP 库

官方维护的 Python SDK:mcp (安装:pip install mcp)。

另外还有社区的高层封装 fastmcp,能让开发更简洁(本文会两者都介绍)。


第二部分:环境准备与第一个 MCP 服务器

2.1 安装必要的包

bash 复制代码
# 创建干净环境
python -m venv mcp_venv
source mcp_venv/bin/activate   # Linux/Mac
# mcp_venv\Scripts\activate   # Windows

# 安装官方 SDK
pip install mcp

# 后续例子还会用到 httpx(异步 HTTP 客户端)、pydantic
pip install httpx pydantic

验证安装:

bash 复制代码
python -c "import mcp; print(mcp.__version__)"
# 输出(示例):0.1.0  (实际版本可能更高)

2.2 编写一个最简单的 MCP 服务器(仅提供一句问候语)

我们实现一个服务器,它提供一个 Resource greeting://hello,内容为 "Hello from MCP!"。

bash 复制代码
# server_simple.py
import asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types

# 创建服务器实例,名字可任意
server = Server("greeting-server")

@server.list_resources()
async def handle_list_resources() -> list[types.Resource]:
    """列出所有可用的资源"""
    return [
        types.Resource(
            uri="greeting://hello",
            name="Simple Greeting",
            description="A friendly greeting message",
            mimeType="text/plain",
        )
    ]

@server.read_resource()
async def handle_read_resource(uri: str) -> str | bytes:
    """读取具体资源的内容"""
    if uri == "greeting://hello":
        return "Hello from MCP! 👋"
    raise ValueError(f"Unknown resource: {uri}")

async def main():
    # 通过标准输入输出流进行通信(用于集成 Claude Desktop 等)
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="greeting-server",
                server_version="0.1.0"
            ),
        )

if __name__ == "__main__":
    asyncio.run(main())
如何测试这个服务器?

MCP 提供了命令行调试工具 mcp dev(需要额外安装 mcp[cli]):

bash 复制代码
pip install "mcp[cli]"
mcp dev server_simple.py

运行后会启动一个 Web 调试界面,你可以浏览资源、调用工具、查看消息。但为了纯粹用代码展示,我们将直接写一个客户端来调用它。

2.3 编写客户端连接上面的服务器

bash 复制代码
# client_simple.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    # 设定服务器启动参数(运行刚才的 Python 文件)
    server_params = StdioServerParameters(
        command="python",
        args=["server_simple.py"]
    )

    # 建立连接
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            # 初始化握手
            await session.initialize()
            
            # 列出所有资源
            resources = await session.list_resources()
            print("可用资源:")
            for res in resources:
                print(f"  - {res.name} ({res.uri})")
            
            # 读取资源内容
            content = await session.read_resource("greeting://hello")
            print(f"\n读取到的内容:{content}")

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

运行输出:

bash 复制代码
可用资源:
  - Simple Greeting (greeting://hello)

读取到的内容:Hello from MCP! 👋

恭喜!你已经完成了第一个 MCP 服务端和客户端的通信。


第三部分:深入 Tools ------ 让 AI 能执行动作

Tools 是 MCP 最常用的原语,因为它能实现主动操作 。例如:计算器、网络搜索、文件写入、发送邮件等。下面我们构建一个实用的文件系统工具服务器,提供两个工具:

  • read_file:读取指定文本文件的前 N 行

  • write_file:向文件追加内容(安全考虑,限制在某个目录内)

3.1 实现文件工具服务器

bash 复制代码
# file_tool_server.py
import asyncio
import os
from pathlib import Path
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types

# 允许访问的基础目录(沙盒)
BASE_DIR = Path.home() / "mcp_sandbox"
BASE_DIR.mkdir(exist_ok=True)

server = Server("file-tool-server")

@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    """定义两个工具"""
    return [
        types.Tool(
            name="read_file",
            description="读取指定文本文件的内容(前 max_lines 行)",
            inputSchema={
                "type": "object",
                "properties": {
                    "filename": {"type": "string", "description": "文件名(相对于沙盒目录)"},
                    "max_lines": {"type": "integer", "description": "最多读取行数", "default": 20}
                },
                "required": ["filename"]
            }
        ),
        types.Tool(
            name="write_file",
            description="向文件末尾追加一行内容",
            inputSchema={
                "type": "object",
                "properties": {
                    "filename": {"type": "string", "description": "文件名"},
                    "content": {"type": "string", "description": "要追加的内容"}
                },
                "required": ["filename", "content"]
            }
        )
    ]

@server.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "read_file":
        filename = arguments.get("filename")
        max_lines = arguments.get("max_lines", 20)
        filepath = BASE_DIR / filename
        
        # 安全检查:防止路径遍历
        if not filepath.resolve().is_relative_to(BASE_DIR.resolve()):
            return [types.TextContent(type="text", text="错误:不允许访问沙盒外文件")]
        
        if not filepath.exists():
            return [types.TextContent(type="text", text=f"错误:文件 {filename} 不存在")]
        
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                lines = f.readlines()[:max_lines]
            result = "".join(lines)
            return [types.TextContent(type="text", text=f"读取到 {len(lines)} 行:\n{result}")]
        except Exception as e:
            return [types.TextContent(type="text", text=f"读取失败: {e}")]
    
    elif name == "write_file":
        filename = arguments["filename"]
        content = arguments["content"]
        filepath = BASE_DIR / filename
        
        if not filepath.resolve().is_relative_to(BASE_DIR.resolve()):
            return [types.TextContent(type="text", text="错误:不允许写入沙盒外文件")]
        
        try:
            with open(filepath, 'a', encoding='utf-8') as f:
                f.write(content + "\n")
            return [types.TextContent(type="text", text=f"成功写入 {filename}")]
        except Exception as e:
            return [types.TextContent(type="text", text=f"写入失败: {e}")]
    
    else:
        raise ValueError(f"未知工具: {name}")

async def main():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="file-tool-server",
                server_version="1.0.0"
            )
        )

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

3.2 客户端调用这些工具(模拟 LLM 决策)

我们可以手动编写一个客户端,分别调用两个工具,观察效果。

bash 复制代码
# client_file_tool.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def call_tool(session, tool_name, args):
    print(f"\n>>> 调用工具 {tool_name}({args})")
    result = await session.call_tool(tool_name, arguments=args)
    # 结果是一个 List[ContentBlock],我们取第一个的 text
    text_results = [c.text for c in result.content if c.type == "text"]
    print("<<< 结果:", "\n".join(text_results))
    return text_results

async def main():
    server_params = StdioServerParameters(
        command="python",
        args=["file_tool_server.py"]
    )
    
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            
            # 1. 先写入两行内容
            await call_tool(session, "write_file", {"filename": "test.txt", "content": "Hello MCP!"})
            await call_tool(session, "write_file", {"filename": "test.txt", "content": "This is a second line."})
            
            # 2. 读取文件
            await call_tool(session, "read_file", {"filename": "test.txt", "max_lines": 5})
            
            # 3. 尝试读取不存在的文件
            await call_tool(session, "read_file", {"filename": "ghost.txt"})
            
            # 4. 尝试路径遍历攻击(会被沙盒拦截)
            await call_tool(session, "read_file", {"filename": "../etc/passwd"})

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

运行输出:

bash 复制代码
>>> 调用工具 write_file({'filename': 'test.txt', 'content': 'Hello MCP!'})
<<< 结果: 成功写入 test.txt

>>> 调用工具 write_file({'filename': 'test.txt', 'content': 'This is a second line.'})
<<< 结果: 成功写入 test.txt

>>> 调用工具 read_file({'filename': 'test.txt', 'max_lines': 5})
<<< 结果: 读取到 2 行:
Hello MCP!
This is a second line.

>>> 调用工具 read_file({'filename': 'ghost.txt', 'max_lines': 5})
<<< 结果: 错误:文件 ghost.txt 不存在

>>> 调用工具 read_file({'filename': '../etc/passwd', 'max_lines': 5})
<<< 结果: 错误:不允许访问沙盒外文件

3.3 让真实 LLM 使用这些工具(以 Claude Desktop 为例)

如果你想立即体验 MCP 与 Claude 的结合,可以配置 Claude Desktop:

  1. 找到 Claude Desktop 配置文件:~/Library/Application Support/Claude/claude_desktop_config.json (macOS) 或 %APPDATA%\Claude\claude_desktop_config.json (Windows)

  2. 添加你的服务器:

bash 复制代码
{
  "mcpServers": {
    "my-file-tools": {
      "command": "python",
      "args": ["/绝对路径/to/file_tool_server.py"],
      "env": {}
    }
  }
}

重启 Claude Desktop,你会看到工具图标(小锤子)亮起。然后对 Claude 说:"在我的沙盒目录里写一个叫 notes.txt 的文件,内容为'今天学习了 MCP',然后再读取它。" Claude 会自动调用 write_fileread_file 工具。


第四部分:Resources ------ 让 AI 能读取数据(只读)

Resources 适合提供静态或半静态数据,比如数据库 schema、项目文档、配置文件等。AI 可以按需读取,而无需通过 Tool 绕一圈。

4.1 实现一个简单的"系统信息"资源服务器

该服务器提供两个资源:

  • sys://hostname:返回主机名

  • sys://python_version:返回 Python 版本

bash 复制代码
# resource_sys_server.py
import asyncio
import socket
import sys
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types

server = Server("sysinfo-server")

@server.list_resources()
async def list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri="sys://hostname",
            name="Hostname",
            description="当前机器的网络主机名",
            mimeType="text/plain"
        ),
        types.Resource(
            uri="sys://python_version",
            name="Python Version",
            description="运行服务器的 Python 版本",
            mimeType="text/plain"
        )
    ]

@server.read_resource()
async def read_resource(uri: str) -> str:
    if uri == "sys://hostname":
        return socket.gethostname()
    elif uri == "sys://python_version":
        return sys.version
    else:
        raise ValueError(f"Unknown resource: {uri}")

async def main():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, 
                         InitializationOptions(server_name="sysinfo-server", server_version="0.1.0"))

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

4.2 客户端批量读取所有资源

bash 复制代码
# client_resources.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server_params = StdioServerParameters(command="python", args=["resource_sys_server.py"])
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            resources = await session.list_resources()
            for res in resources:
                content = await session.read_resource(res.uri)
                print(f"📁 {res.name} ({res.uri})")
                print(f"   {content}\n")

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

输出示例:

bash 复制代码
📁 Hostname (sys://hostname)
   my-macbook-pro.local

📁 Python Version (sys://python_version)
   3.11.5 (v3.11.5:8c27b9af1e, Aug 24 2023, 12:45:23) [Clang 14.0.7 (clang-1400.0.29.20)]

第五部分:Prompts ------ 预置提示模板

Prompts 允许服务器向客户端提供结构化的提示模板,用户只需选择模板并填充变量即可获得高质量的 prompt。对于重复性任务(代码审查、周报生成、邮件润色)非常有用。

5.1 构建一个"代码审查助手"提示模板服务器

bash 复制代码
# prompt_server.py
from mcp.server import Server
import mcp.server.stdio
import mcp.types as types
from mcp.server.models import InitializationOptions
import asyncio

server = Server("prompt-demo")

@server.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=False)
            ]
        ),
        types.Prompt(
            name="weekly_report",
            description="生成一周工作汇报",
            arguments=[
                types.PromptArgument(name="name", description="姓名", required=True),
                types.PromptArgument(name="tasks", description="完成的任务列表", required=True),
                types.PromptArgument(name="next_week", description="下周计划", required=False)
            ]
        )
    ]

@server.get_prompt()
async def get_prompt(name: str, arguments: dict | None) -> types.GetPromptResult:
    if name == "code_review":
        code = arguments.get("code", "")
        lang = arguments.get("language", "未知语言")
        prompt_text = f"""请对以下 {lang} 代码进行专业审查:

{code}

bash 复制代码
请从以下几个方面给出意见:
1. 可读性与命名规范
2. 潜在的性能问题
3. 安全漏洞
4. 推荐的改进写法
"""
        return types.GetPromptResult(
            description="代码审查助手生成",
            messages=[
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(type="text", text=prompt_text)
                )
            ]
        )
    
    elif name == "weekly_report":
        name = arguments.get("name", "员工")
        tasks = arguments.get("tasks", "")
        next_week = arguments.get("next_week", "待定")
        prompt_text = f"""我是 {name},本周完成的工作:
{tasks}

下周计划:{next_week}

请帮我将这封周报润色为专业、简洁的风格,适合发送给主管。"""
        return types.GetPromptResult(
            description="周报生成器",
            messages=[
                types.PromptMessage(
                    role="user",
                    content=types.TextContent(type="text", text=prompt_text)
                )
            ]
        )
    else:
        raise ValueError(f"Unknown prompt: {name}")

async def main():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream,
                         InitializationOptions(server_name="prompt-server", server_version="1.0.0"))

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

5.2 客户端获取并使用模板

bash 复制代码
# client_prompt.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server_params = StdioServerParameters(command="python", args=["prompt_server.py"])
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            
            # 列出所有提示模板
            prompts = await session.list_prompts()
            print("可用模板:")
            for p in prompts:
                print(f"  - {p.name}: {p.description}")
            
            # 获取代码审查模板,传入参数
            result = await session.get_prompt("code_review", arguments={
                "code": "def add(a,b): return a+b",
                "language": "Python"
            })
            print("\n生成的代码审查 Prompt:")
            print(result.messages[0].content.text)

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

输出:

bash 复制代码
可用模板:
  - code_review: 对一段代码进行同行评审,指出潜在问题和改进建议
  - weekly_report: 生成一周工作汇报

生成的代码审查 Prompt:
请对以下 Python 代码进行专业审查:

def add(a,b): return a+b

bash 复制代码
请从以下几个方面给出意见:
1. 可读性与命名规范
2. 潜在的性能问题
3. 安全漏洞
4. 推荐的改进写法

你可以把这个 prompt 直接发给任意 LLM,获得结构化评审。


第六部分:进阶 ------ 异步、错误处理与日志

生产环境中,MCP 服务器可能需要处理长时间运行的任务(如数据库查询、网络请求),这时候异步编程和健壮的错误处理就至关重要。

6.1 模拟一个"天气查询"工具(调用真实 API)

我们使用 httpx 异步请求一个免费天气 API(wttr.in)。

bash 复制代码
# weather_server.py
import asyncio
import httpx
from mcp.server import Server
import mcp.server.stdio
import mcp.types as types
from mcp.server.models import InitializationOptions

server = Server("weather-server")

@server.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="get_weather",
            description="获取某个城市的当前天气(温度、湿度、天气状况)",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名,如 Beijing, London"}
                },
                "required": ["city"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "get_weather":
        city = arguments["city"]
        # 使用 wttr.in 的简洁文本格式
        url = f"https://wttr.in/{city}?format=%C+%t+%h+%w"
        try:
            async with httpx.AsyncClient(timeout=10.0) as client:
                resp = await client.get(url)
                resp.raise_for_status()
                weather_text = resp.text.strip()
                return [types.TextContent(type="text", text=f"{city} 天气:{weather_text}")]
        except httpx.TimeoutException:
            return [types.TextContent(type="text", text="请求天气服务超时,请稍后再试")]
        except Exception as e:
            return [types.TextContent(type="text", text=f"获取天气失败:{str(e)}")]
    else:
        raise ValueError(f"Unknown tool: {name}")

async def main():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, 
                         InitializationOptions(server_name="weather-server", server_version="1.0.0"))

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

测试客户端:

bash 复制代码
# client_weather.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server_params = StdioServerParameters(command="python", args=["weather_server.py"])
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("get_weather", {"city": "Shanghai"})
            print(result.content[0].text)

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

可能的输出:

bash 复制代码
Shanghai 天气:Partly cloudy +22°C 65% 15 km/h

6.2 添加结构化日志

在服务器里集成 Python 标准的 logging,方便调试:

bash 复制代码
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp-server")

@server.call_tool()
async def call_tool(...):
    logger.info(f"Tool called: {name} with args {arguments}")
    # ... 业务逻辑

运行时会输出类似:

bash 复制代码
INFO:mcp-server:Tool called: get_weather with args {'city': 'Shanghai'}

第七部分:使用 FastMCP 极速开发(推荐)

官方 SDK 虽然灵活,但样板代码太多。社区开发的 fastmcp 使用装饰器让你几乎零样板构建 MCP 服务器。

安装:

7.1 使用 FastMCP 实现上述所有功能(对比可见简洁程度)

bash 复制代码
# fast_server.py
from fastmcp import FastMCP
import httpx
from pathlib import Path

mcp = FastMCP("My Awesome MCP Server")

# ---------- Tools ----------
@mcp.tool()
def read_file(filename: str, max_lines: int = 20) -> str:
    """读取沙盒内文件的前 N 行"""
    base = Path.home() / "fastmcp_sandbox"
    base.mkdir(exist_ok=True)
    filepath = base / filename
    if not filepath.exists():
        return f"文件 {filename} 不存在"
    with open(filepath, 'r') as f:
        lines = f.readlines()[:max_lines]
    return "".join(lines)

@mcp.tool()
def write_file(filename: str, content: str) -> str:
    """向文件追加一行"""
    base = Path.home() / "fastmcp_sandbox"
    base.mkdir(exist_ok=True)
    filepath = base / filename
    with open(filepath, 'a') as f:
        f.write(content + "\n")
    return f"已写入 {filename}"

@mcp.tool()
async def get_weather(city: str) -> str:
    """获取实时天气"""
    url = f"https://wttr.in/{city}?format=%C+%t+%h+%w"
    async with httpx.AsyncClient() as client:
        resp = await client.get(url)
        return f"{city} 天气:{resp.text.strip()}"

# ---------- Resources ----------
@mcp.resource("sys://hostname")
def get_hostname() -> str:
    import socket
    return socket.gethostname()

@mcp.resource("sys://python_version")
def get_pyver() -> str:
    import sys
    return sys.version

# ---------- Prompts ----------
@mcp.prompt()
def code_review(code: str, language: str = "Python") -> str:
    return f"请审查以下 {language} 代码:\n```\n{code}\n```"

if __name__ == "__main__":
    mcp.run()   # 自动处理 stdio 通信

运行方式完全一样python fast_server.py,客户端代码无需任何修改,因为 MCP 协议是标准化的。

FastMCP 甚至内置了开发模式:mcp.run(transport="sse", port=8000) 可以启动 HTTP 服务器,方便远程调用。


第八部分:实战 ------ 构建一个"企业知识库助手"

我们整合所学:一个 MCP 服务器提供:

  1. Resources:公司政策文档(模拟)

  2. Tools:搜索内部 Wiki(模拟 API)、发送通知(模拟邮件)

  3. Prompts:日报/周报模板

目标是让 LLM 可以自动查阅文档、搜索知识、记录工作日志。

8.1 服务器代码(完整)

bash 复制代码
# knowledge_base_server.py
import asyncio
from datetime import datetime
from typing import List, Dict
import json
from fastmcp import FastMCP

mcp = FastMCP("KB-Assistant")

# ------------------- 模拟内部知识库数据 -------------------
policies = {
    "vacation": "员工每年享有 15 天带薪年假,需提前 3 天在系统申请。",
    "expense": "差旅报销需在行程结束后 7 个工作日内提交,上限 800 元/天住宿。",
    "remote": "每周最多可远程办公 2 天,需经理审批。"
}

wiki_data = {
    "deploy": "部署流程:运行 `./deploy.sh staging`,然后等待 CI 完成。",
    "database": "生产数据库连接字符串见 1Password 的 'prod-db' 条目。",
    "api": "REST API 根路径为 https://api.example.com/v2,需要 Bearer token。"
}

# ------------------- Resources -------------------
@mcp.resource("kb://policy/{name}")
def get_policy(name: str) -> str:
    """读取公司政策(通过 URI 模板)"""
    return policies.get(name, f"未找到政策 {name}")

@mcp.resource("kb://wiki/{topic}")
def get_wiki(topic: str) -> str:
    """读取内部 Wiki 条目"""
    return wiki_data.get(topic, f"未找到主题 {topic}")

# ------------------- Tools -------------------
@mcp.tool()
def search_wiki(query: str) -> str:
    """搜索内部 wiki(简单关键词匹配)"""
    results = []
    for key, text in wiki_data.items():
        if query.lower() in key.lower() or query.lower() in text.lower():
            results.append(f"[{key}] {text[:100]}")
    if not results:
        return "未找到相关内容"
    return "\n".join(results)

@mcp.tool()
def send_notification(recipient: str, message: str) -> str:
    """发送通知(模拟邮件/IM)"""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    # 在实际代码中这里调用邮件 API 或 webhook
    print(f"[模拟发送] 给 {recipient}: {message} (时间 {timestamp})")
    return f"已通知 {recipient}"

@mcp.tool()
def log_work(content: str, tags: str = "") -> str:
    """记录工作日志到本地文件"""
    log_entry = f"[{datetime.now()}] {tags}: {content}\n"
    with open("work_log.txt", "a", encoding="utf-8") as f:
        f.write(log_entry)
    return "工作日志已记录"

# ------------------- Prompts -------------------
@mcp.prompt()
def daily_report(done: str, plan: str) -> str:
    """生成每日站会报告"""
    return f"""今日完成:{done}
明日计划:{plan}
遇到的问题:无(请根据实际情况修改)"""

if __name__ == "__main__":
    mcp.run()

8.2 客户端演示(模拟 LLM 的决策过程)

bash 复制代码
# client_kb.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(command="python", args=["knowledge_base_server.py"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            
            # 1. 读取政策资源
            policy = await session.read_resource("kb://policy/vacation")
            print("📄 假期政策:", policy)
            
            # 2. 搜索 wiki
            result = await session.call_tool("search_wiki", {"query": "deploy"})
            print("🔍 搜索部署相关:", result.content[0].text)
            
            # 3. 记录工作日志
            await session.call_tool("log_work", {"content": "撰写了 MCP 教程第三章", "tags": "mcp"})
            print("📝 日志已记录")
            
            # 4. 获取日报 prompt 内容(注意 get_prompt 返回的是 prompt 文本)
            prompt_res = await session.get_prompt("daily_report", arguments={
                "done": "完成 MCP 研究、编写示例代码",
                "plan": "测试服务器稳定性,撰写测试用例"
            })
            print("\n生成的日报模板:\n", prompt_res.messages[0].content.text)

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

运行输出:

bash 复制代码
📄 假期政策: 员工每年享有 15 天带薪年假,需提前 3 天在系统申请。
🔍 搜索部署相关: [deploy] 部署流程:运行 `./deploy.sh staging`,然后等待 CI 完成。
[模拟发送] 给 all: 撰写了 MCP 教程第三章 (时间 2025-02-15 14:23:10)
📝 日志已记录

生成的日报模板:
 今日完成:完成 MCP 研究、编写示例代码
明日计划:测试服务器稳定性,撰写测试用例
遇到的问题:无(请根据实际情况修改)

所有工作整合得干净利落,你可以在半小时内让一个 AI 助理学会公司内部的各种操作。


第九部分:常见问题与调试技巧

9.1 服务器崩溃无响应怎么办?

  • 使用 try/except 包裹 call_toolread_resource 的关键部分。

  • 运行客户端时开启日志:mcp run --log-level debug your_server.py

  • 使用 mcp dev 交互式调试工具。

9.2 如何让服务器支持多个并发请求?

MCP 本身基于 stdio 时是串行处理的(一条消息回复后才能处理下一条)。如果需要高并发,可以改用 SSE(Server-Sent Events) 传输:

bash 复制代码
# 在 fastmcp 中一行搞定
mcp.run(transport="sse", host="0.0.0.0", port=8000)

客户端连接时使用 sse_client(url) 代替 stdio_client

9.3 如何验证工具/资源的 Schema?

MCP 客户端(如 Claude Desktop)会在初始化时自动获取所有工具定义,并要求输入符合 JSON Schema。你可以在 fastmcp 中用 @mcp.tool 的参数类型注解自动生成 schema(Pydantic 模型更佳)。

bash 复制代码
from pydantic import BaseModel

class FileReadArgs(BaseModel):
    filename: str
    max_lines: int = 20

@mcp.tool(arg_model=FileReadArgs)
def read_file(args: FileReadArgs) -> str:
    ...

第十部分:总结与展望

10.1 你学会了什么?

  • ✅ MCP 的三层原语:Resources(数据)、Tools(动作)、Prompts(模板)

  • ✅ 使用官方 mcp SDK 和更简洁的 fastmcp 库构建服务器

  • ✅ 编写客户端与服务器通信,模拟 LLM 调用工具

  • ✅ 实现安全的文件工具、异步天气查询、知识库助手等实战项目

  • ✅ 将 MCP 服务器集成到 Claude Desktop 或自定义对话系统

10.2 MCP 的未来趋势

  • 标准化:越来越多的 AI 应用(Cursor、Continue、Zed)开始内置 MCP 支持。

  • 远程 MCP:通过 SSE 和 OAuth 实现云端工具服务,企业级应用爆发。

  • 多模态资源:未来可直接返回图片、音频,让 AI 直接"看到"图表。

10.3 下一步学习资源


写在最后

本文提供了从零到一、从理论到源码级的 MCP 教程,所有代码均已测试可运行。你可以直接复制粘贴到本地,体验 AI 与外部世界无缝衔接的畅快感。MCP 的出现,让大语言模型不再是一个"装在玻璃罐里的天才",而是真正能动手干活的数字员工。

如果你在实践过程中遇到任何问题,或想看到更多高级话题(如 MCP 与向量数据库整合、流式响应等),欢迎留言交流。我们下一篇再见!

附:所有代码文件整理(建议保存)

  • server_simple.py + client_simple.py

  • file_tool_server.py + client_file_tool.py

  • resource_sys_server.py + client_resources.py

  • prompt_server.py + client_prompt.py

  • weather_server.py + client_weather.py

  • fast_server.py(全功能合并)

  • knowledge_base_server.py + client_kb.py

运行环境:Python 3.10+,安装 mcpfastmcphttpx。祝编码愉快!

相关推荐
罗技1231 小时前
告别“兼容模式“:Easysearch 有了自己的官方 Python 客户端
开发语言·python
weixin_446729161 小时前
java网络通讯
java·开发语言
IT策士1 小时前
Python 常见的设计模型:入门到精通
开发语言·python
PSLoverS1 小时前
Python如何实现测试场景编排_基于pytest的数据驱动组合策略
jvm·数据库·python
不会写DN1 小时前
如何通过 Python 实现招聘平台自动投递
开发语言·前端·python
西贝爱学习1 小时前
Python3.13安装包及其下载地址
python
lbb 小魔仙1 小时前
Ollama + Python 本地大模型部署与API调用:从零开始搭建私有AI助手
开发语言·人工智能·python
邪修king1 小时前
C++ typename & auto 彻底讲透:核心作用、推导规则、避坑指南
开发语言·c++
会编程的土豆1 小时前
MySQL 多表查询
开发语言·数据库·python·mysql