MCP 实战——社区焦点

104 阅读7分钟

在上一章中,你迈入了高级服务器架构的领域:学会了将 MCP 服务器集成进标准 ASGI 应用,编写强大的中间件来拦截并处理 MCP 事件,甚至还能从 OpenAPI 规范自动生成整套工具。你已从构建独立服务器,升级为设计复杂、集成式的 MCP 系统。

到目前为止,我们主要从零开始构建工具,以掌握核心原理。但 MCP 最令人兴奋的一点,是它正在快速壮大的生态。全球开发者正在构建并开源各类 MCP 服务器来解决常见问题。与其重复造轮子,不如站在巨人的肩膀上

本章将带你游览这个蓬勃发展的生态。我们会聚焦三个由社区创建、可用于真实场景的强力 MCP 服务器。你将看到前面章节的模式如何落地解决实际问题:与本地文件系统交互、查询数据库、以及从 Web 获取内容。这是你观察 MCP 实战并学习如何把这些现成工具用于自己项目的好机会。

在本章结束时,你将能够:

  • 使用 Node.js 实现的 MCP 服务器,为 LLM 提供文件系统访问能力。
  • 利用另一个 Node.js 服务器,让 LLM 查询 SQLite 数据库。
  • 集成一个 Python 实现的 MCP 服务器,用于抓取与解析网页
  • 巩固对 STDIO 传输的理解(适用于 Python 与非 Python 服务器)。

让我们看看社区都造出了哪些好东西吧。

文件系统 MCP 服务器

你能赋予 AI 代理最基础、也最关键的能力之一,就是与文件系统交互——读写与管理文件。许多自治代理框架都以此为基石。modelcontextprotocol/servers 仓库就提供了一个即用型、功能强大的 MCP 服务器来完成这件事。

注意:这个 MCP 服务器是用 Node.js 写的,而非 Python。这正好体现了 MCP 的多语言特性。作为 Python 开发者,你可以无缝使用其他语言编写的工具——只要客户端与服务器都说 MCP 这门“通用语言”。

服务器安装

先获取服务器代码。你需要在系统中安装 Node.js 与 npm(从 nodejs.org/ 下载)。

在你本书使用的主项目目录打开终端,然后克隆仓库并安装依赖:

图 103. 设置文件系统服务器

$ git clone https://github.com/modelcontextprotocol/servers
$ cd servers
$ npm install
$ cd ..

执行后,你会在项目旁得到一个 servers 目录。现在我们来写 Python 客户端与之交互。接下来的示例中,我们会创建两个目录 fastmcpmcp 来存放各自的客户端脚本。

使用 fastmcp 连接文件系统服务器

先从高层的 fastmcp 库开始。创建并进入 fastmcp 目录。

图 104. 终端

$ mkdir fastmcp
$ cd fastmcp

在该目录内创建 filesystem_mcp_client.py

图 105. fastmcp/filesystem_mcp_client.py

import asyncio
from fastmcp import Client
import os
from fastmcp.client.transports import NodeStdioTransport

# 1. Path to the Node.js server script.
node_script = "../servers/src/filesystem/dist/index.js"

# 2. Configure the transport to run a Node.js script.
transport = NodeStdioTransport(
    script_path=node_script,
    node_cmd="node",
    args=["."]
)

client = Client(transport)

async def call_tool():
    async with client:
        content = "Startup MCP is a billion dollar idea. Please fund me!"
        filename = "mcp_startup_pitch.txt"

        # 3. Call the 'write_file' tool.
        await client.call_tool("write_file",
                               {"path": filename, "content": content})
        
        # 4. Verify the file was created and clean up.
        with open(filename, "r") as f:
            print(f.read())

        os.remove(filename)

asyncio.run(call_tool())

代码要点:

  • node_script:提供作为 MCP 服务器的已编译 JavaScript 文件路径。
  • NodeStdioTransport:关键所在。它专为通过 STDIO 启动并与 Node.js 进程通信而设计。我们指定命令 node 以及给脚本的参数(这里的 . 表示以当前目录为根目录)。
  • call_tool:调用服务器提供的 write_file 工具,传入目标路径与内容。
  • 验证:工具返回后,用 Python 文件 I/O 打开新文件、打印内容验证成功,再删除清理目录。

fastmcp 目录内运行客户端脚本:

图 106. 终端

> python .\filesystem_mcp_client.py
Startup MCP is a billion dollar idea. Please fund me!

成功!你的 Python 脚本通过 Node.js 的 MCP 服务器把文件写到了磁盘。

使用 mcp 库连接文件系统服务器

现在用更底层的 mcp 库达成同样效果。退出 fastmcp 目录并进入新建的 mcp 目录。

图 107. 终端

$ cd ..
$ mkdir mcp
$ cd mcp

在该目录内创建 filesystem_mcp_client.py

图 108. mcp/filesystem_mcp_client.py

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio
import os

node_script = "../servers/src/filesystem/dist/index.js"
directory = "."

# 1. Define how to run the server process.
server_params = StdioServerParameters(
    command="node",
    args=[node_script, directory]
)

async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(
            read, write
        ) as session:
            await session.initialize()

            content = "Startup MCP is a billion dollar idea. Please fund me!"
            filename = "mcp_startup_pitch.txt"
            await session.call_tool("write_file",
                                    {"path": filename, "content": content})
            
            with open(filename, "r") as f:
                print(f.read())

            os.remove(filename)

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

这体现了 mcp 库更显式的理念:

  • StdioServerParameters 明确进程如何启动(命令 node)以及传给脚本的参数。这相当于 fastmcp 中 NodeStdioTransport 的低层等价物。

mcp 目录内运行客户端:

图 109. 终端

> python .\filesystem_mcp_client.py
Startup MCP is a billion dollar idea. Please fund me!

结果一致。你已经看到两种库都能轻松与非 Python 的 MCP 服务器通信。

SQLite3 MCP 服务器

接下来让 AI 能与数据库交互。社区项目 mcp-database-serverSQLite 数据库提供了 MCP 接口。

准备数据库与服务器

首先需要 SQLite3 命令行工具。Linux 与 macOS 通常预装;Windows 可从 www.sqlite.org/download.ht… 下载。

在主项目目录克隆仓库并安装依赖:

图 110. 设置数据库服务器

$ git clone https://github.com/executeautomation/mcp-database-server
$ cd mcp-database-server
$ npm install
$ cd ..

然后创建并填充一个数据库文件 db.sqlite3

图 111. 终端

> sqlite3 db.sqlite3

这会打开 SQLite 控制台。粘贴以下 SQL 以创建数据表并插入一些数据:

图 112. 创建并填充数据库

CREATE TABLE ai_startups (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    funding TEXT NOT NULL,
    type TEXT NOT NULL
);

INSERT INTO ai_startups (id, name, funding, type) VALUES
(1, 'Connecto MCP', '1000000', 'MCP'),
(2, 'Neural Vision Labs', '2500000', 'Computer Vision'),
(3, 'AutoFlow AI', '750000', 'Process Automation'),
(4, 'ChatBot Solutions', '1800000', 'Conversational AI'),
(5, 'DataMine Intelligence', '3200000', 'Data Analytics');

SELECT * FROM ai_startups;

你应当能看到刚插入的五行数据。输入 .exit 退出 SQLite 控制台。

现在我们有了可供 MCP 服务器使用的数据库文件。

用 fastmcp 查询数据库

回到 fastmcp 目录,创建 sqlite3_mcp_client.py

图 113. fastmcp/sqlite3_mcp_client.py

import asyncio
from fastmcp import Client
from fastmcp.client.transports import NodeStdioTransport

node_script = "../mcp-database-server/dist/src/index.js"

transport = NodeStdioTransport(
    script_path=node_script,
    node_cmd="node",
    args=["../db.sqlite3"] # Pass the database file as an argument
)

client = Client(transport)

async def call_tool():
    async with client:
        # Use a tool to write data
        insert_query = "insert into ai_startups (name, funding, type) values ('bamboo AI', '2000000', 'Gen AI')"
        await client.call_tool("write_query", {"query": insert_query})

        # Use a tool to read data
        result = await client.call_tool("read_query", {"query": "select * from ai_startups"})
        print(result)

        # Clean up the test data
        delete_query = "delete from ai_startups where name = 'bamboo AI'"
        await client.call_tool("write_query", {"query": delete_query})

asyncio.run(call_tool())

与文件系统服务器非常相似:我们用 NodeStdioTransport 启动服务器脚本,但这次把 db.sqlite3 的路径作为参数传入。该服务器主要提供两个工具:

  • write_query(用于 INSERT/UPDATE/DELETE
  • read_query(用于 SELECT

运行客户端:

图 114. 终端

> python .\sqlite3_mcp_client.py
CallToolResult(content=[TextContent(type='text', text='[\n  {\n    "id": 1,\n    "name": "Connecto MCP",\n    "funding": "1000000",\n    "type": "MCP"\n  },\n  {\n    "id": 2,\n    "name": "Neural Vision Labs",\n    "funding": "2500000",\n    "type": "Computer Vision"\n  },\n  {\n    "id": 3,\n    "name": "AutoFlow AI",\n    "funding": "750000",\n    "type": "Process Automation"\n  },\n  {\n    "id": 4,\n    "name": "ChatBot Solutions",\n    "funding": "1800000",\n    "type": "Conversational AI"\n  },\n  {\n    "id": 5,\n    "name": "DataMine Intelligence",\n    "funding": "3200000",\n    "type": "Data Analytics"\n  },\n  {\n    "id": 6,\n    "name": "bamboo AI",\n    "funding": "2000000",\n    "type": "Gen AI"\n  }\n]', annotations=None, meta=None)], structured_content=None, data=None, is_error=False)

输出是一个 CallToolResult 对象。你可以在其 content 中看到数据库查询结果的 JSON 表示,包括我们临时插入的 “bamboo AI”。

用 mcp 库查询数据库

mcp 目录创建 sqlite3_mcp_client.py

图 115. mcp/sqlite3_mcp_client.py

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio

node_script = "../mcp-database-server/dist/src/index.js"
db_file = "../db.sqlite3"

server_params = StdioServerParameters(
    command="node",
    args=[node_script, db_file]
)

async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(
            read, write
        ) as session:
            await session.initialize()

            insert_query = "insert into ai_startups (name, funding, type) values ('bamboo AI', '2000000', 'Gen AI')"
            await session.call_tool("write_query", {"query": insert_query})

            result = await session.call_tool("read_query", {"query": "select * from ai_startups"})
            print(result)

            delete_query = "delete from ai_startups where name = 'bamboo AI'"
            await session.call_tool("write_query", {"query": delete_query})

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

同样用 StdioServerParameters 显式定义如何运行 Node.js 服务器及其参数。逻辑与 fastmcp 版本完全一致

运行客户端:

图 116. 终端

> python .\sqlite3_mcp_client.py
meta=None content=[TextContent(type='text', text='[\n  {\n    "id": 1,\n    "name": "Connecto MCP",\n    "funding": "1000000",\n    "type": "MCP"\n  },\n  {\n    "id": 2,\n    "name": "Neural Vision Labs",\n    "funding": "2500000",\n    "type": "Computer Vision"\n  },\n  {\n    "id": 3,\n    "name": "AutoFlow AI",\n    "funding": "750000",\n    "type": "Process Automation"\n  },\n  {\n    "id": 4,\n    "name": "ChatBot Solutions",\n    "funding": "1800000",\n    "type": "Conversational AI"\n  },\n  {\n    "id": 5,\n    "name": "DataMine Intelligence",\n    "funding": "3200000",\n    "type": "Data Analytics"\n  },\n  {\n    "id": 6,\n    "name": "bamboo AI",\n    "funding": "2000000",\n    "type": "Gen AI"\n  }\n]', annotations=None, meta=None)] structuredContent=None isError=False

输出格式略有不同,但核心结果一致。你已经成功为 LLM 提供了查询在线数据库的工具。

抓取(Fetching)HTML 的 MCP 服务器

最后一个“聚光灯”示例,是一个让 LLM 浏览网页的服务器。主 servers 仓库中的 fetch 工具用 Python 编写,并使用了诸如 readabilipy 之类的优秀库来抓取 URL、剔除杂质(如广告与导航菜单),并返回干净、可读的内容。

安装依赖

该服务器用 Python 编写,因此可通过 uv 管理其依赖。

图 117. 终端

$ uv add markdownify readabilipy protego

使用 fastmcp 抓取内容

回到你的 fastmcp 目录,创建 fetch_mcp_client.py

图 118. fastmcp/fetch_mcp_client.py

import asyncio
from fastmcp import Client
from fastmcp.client.transports import StdioTransport

# 1. Configure a transport to run a Python module.
transport = StdioTransport(
    "python",
    cwd="../servers/src/fetch/src",
    args=["-m", "mcp_server_fetch"]
)

client = Client(transport)

async def call_tool():
    async with client:
        # 2. Call the 'fetch' tool with a URL.
        html_result = await client.call_tool("fetch", {"url": "https://github.com/arjunaskykok"})
        print(html_result)

asyncio.run(call_tool())

来看这里的关键差异:

  • StdioTransport:因为服务器是一个 Python 模块,我们可以使用通用的 StdioTransport。它会运行 python 命令;args 指定以模块方式(-m)运行名为 mcp_server_fetch 的模块。关键是设置 cwd(当前工作目录)为服务器的源码目录,这样服务器才能找到需要的本地文件。
  • call_tool:直接调用 fetch 工具,并传入要抓取的 URL。

注意:需要一个小改动

在运行 fetch 服务器示例之前,需要对从 modelcontextprotocol/servers 仓库克隆的源码做一个小调整。默认代码配置了代理,但在我们的简单示例中并不会提供代理。这可能会在某些版本的 httpx 中引发 AsyncClient.__init__() got an unexpected keyword argument 'proxies' 错误。

为修复该问题,打开文件 servers/src/fetch/src/mcp_server_fetch/server.py,在 两处初始化 AsyncClient 的位置删除 proxies=proxy_url 参数。

为便于理解,以下是 diff 形式的更改:

--- a/src/fetch/src/mcp_server_fetch/server.py
+++ b/src/fetch/src/mcp_server_fetch/server.py
@@ -72,7 +72,7 @@
 
     robot_txt_url = get_robots_txt_url(url)
 
-    async with AsyncClient(proxies=proxy_url) as client:
+    async with AsyncClient() as client:
         try:
             response = await client.get(
                 robot_txt_url,
@@ -116,7 +116,7 @@
     """
     from httpx import AsyncClient, HTTPError
 
-    async with AsyncClient(proxies=proxy_url) as client:
+    async with AsyncClient() as client:
         try:
             response = await client.get(
                 url,

运行客户端。

图 119. 终端

> python .\fetch_mcp_client.py
CallToolResult(content=[TextContent(type='text', text='Contents of https://github.com/arjunaskykok:\narjunaskykok (Arjuna Sky Kok) · GitHub\n\nSkip to content\n\n## Navigation Menu\n\nSign in\n\nAppearance settings\n\n# Search code, repositories, users, issues, pull requests...\n...', annotations=None, meta=None)], structured_content=None, data=None, is_error=False)

你将得到一段很长但干净、基于文本的页面内容表示。

使用 mcp 库抓取内容

最后,再用 mcp 库实现相同的任务。进入 mcp 目录并创建 fetch_mcp_client.py

图 120. mcp/fetch_mcp_client.py

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio

# 1. Define how to run the Python module server.
server_params = StdioServerParameters(
    command="python",
    args=["-m", "mcp_server_fetch"],
    cwd="../servers/src/fetch/src",
    env=None,
)

async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(
            read, write
        ) as session:
            await session.initialize()

            result = await session.call_tool("fetch", {"url": "https://github.com/arjunaskykok"})
            print(result.content)

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

与之前一样,StdioServerParameters 提供了显式、底层的子进程运行配置。commandargscwd 的设置与 fastmcp 示例中的 StdioTransport 一一对应。

运行客户端。

图 121. 终端

> python .\fetch_mcp_client.py
[TextContent(type='text', text='Contents of https://github.com/arjunaskykok:\narjunaskykok (Arjuna Sky Kok) · GitHub\n\nSkip to content\n\n## Navigation Menu\n\nSign in\n\nAppearance settings\n\n# Search code, repositories, users, issues, pull requests...\n...', annotations=None, meta=None)]

结果同样是该网页干净的抓取内容

关键要点(Key Takeaways)

本章带你实操巡礼 MCP 生态,展示如何利用社区构建的工具,快速为你的 AI 代理加入强大能力。

  • MCP 是多语言(Polyglot)的:你亲眼看到 Python 客户端可以无缝使用 Node.js 编写的 MCP 服务器。协议才是契约,而非编程语言本身。
  • STDIO 十分万能:你已经掌握了通过 STDIONode.js 服务器(NodeStdioTransport 或显式的 StdioServerParameters)以及 Python 服务器通信。
  • 社区工具很强大:并非所有东西都要从零构建。现在你可以自信地查找、搭建、使用开源 MCP 服务器来完成常见任务:文件系统操作、数据库查询与网页抓取。

你已经探索了基石、掌握了高级架构,并看到社区在构建什么。你所需的技能已经齐备。

最后一章,我们将把一切融会贯通。你会着手一个综合压轴项目:编排多个 MCP 服务器来解决一个复杂的多步骤问题。这将是对你技能的终极考验,把整本书所学汇聚成一个令人印象深刻的应用。让我们一起打造点酷炫的东西吧。