在上一章中,你已经掌握了将 MCP 服务器推进到生产环境的要点:你学会了用 JWT 进行身份验证来保护工具,并将应用部署到现代的无服务器平台和传统虚拟机上。你已经构建、加固并部署了独立的 MCP 服务器。
但如果你的 MCP 服务器不需要 独立存在呢?如果你想把可由 AI 调用的工具 添加到现有 Web 应用 里呢?如果你想在每一次工具调用 时进行拦截,以添加日志或自定义授权逻辑呢?又或者,你能否在不手写任何工具 的情况下,直接为整套 REST API 生成一个 MCP 接口?
本章将超越独立服务器,深入高级架构 的世界。你将学会如何把 MCP 无缝融入 Python Web 开发的更大生态中。我们不再把 MCP 服务器当作孤立小岛,而是把它作为更大系统中的强力组件。
在本章结束时,你将能够:
- 使用 Starlette 将 MCP 服务器集成到标准 ASGI Web 应用中。
- 编写强大的中间件(Middleware) ,用来检查与处理 MCP 消息。
- 从 OpenAPI 规范 自动生成完整的 MCP 工具套件。
你已经建好了工坊。现在,让我们把它融入城市。
集成 ASGI:让你的 MCP 成为"公民"
到目前为止,当你运行 mcp.run(transport="http")
时,你启用的是一个完整且自包含 的 Web 服务器。但现代 Python Web 应用通常基于 ASGI(Asynchronous Server Gateway Interface) 标准构建。ASGI 是让 Web 服务器(如 Uvicorn)与 Web 框架(如 Starlette、FastAPI、Django)对话的通用语言。
如果你想让同一个 Web 应用 既能在 /
提供常规网页,又能在 /api/mcp
提供 MCP 服务,该怎么办?答案是挂载(Mounting) 。挂载允许你将一个完整的 ASGI 应用"插入"到另一应用的某个 URL 路径下。
为演示这一点,我们使用 Starlette ------ 一个轻量且强大的 ASGI 框架,非常适合构建高性能的异步服务。
你的第一个 Starlette 应用
首先,为项目添加 Starlette 与 Uvicorn 服务器。
图 81. 终端
csharp
> uv add starlette uvicorn httpx
接着创建一个最小可用的 Starlette 应用。它在 /
暴露一个返回 JSON 的端点。新建 hello_starlette.py
。
图 82. hello_starlette.py
python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
async def homepage(request):
return JSONResponse({'hello': 'world'})
app = Starlette(debug=True, routes=[
Route('/', homepage),
])
这段代码定义了一个简单的 Web 应用。app
是主要的 ASGI 应用对象。它有一条 /
路由,由 homepage
函数处理。
运行时不直接执行该 Python 文件,而是使用 ASGI 服务器(如 Uvicorn),并告诉它去哪里找到 app
对象。
图 83. 终端
vbnet
> uvicorn hello_starlette:app --port 9000
INFO: Started server process [34804]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:9000 (Press CTRL+C to quit)
hello_starlette:app
的语法告诉 Uvicorn:"在 hello_starlette.py
文件中寻找名为 app
的变量。"
可以用一个简单客户端进行测试。创建 starlette_client.py
。
图 84. starlette_client.py
vbscript
import httpx
response = httpx.get("http://127.0.0.1:9000/")
print("Received response:")
print(response.json())
在 Uvicorn 服务器运行的情况下,打开新的终端执行客户端:
图 85. 终端
css
> python .\starlette_client.py
Received response:
{'hello': 'world'}
很好!你已经成功运行了一个标准的 ASGI Web 应用。现在,让我们给它加上 MCP。
挂载 MCP 服务器:fastmcp 方式
fastmcp 让你能非常轻松地从 MCP 服务器实例获取一个兼容 ASGI 的应用对象。
创建一个新文件,将 Starlette 应用与 MCP 服务器结合起来。
图 86. mount_asgi_server_mcp_server.py(fastmcp)
python
from fastmcp import FastMCP
from starlette.applications import Starlette
from starlette.routing import Mount, Route
from starlette.responses import JSONResponse
# 1. Create your MCP server as usual.
mcp = FastMCP("My App")
@mcp.tool
async def call_api_somewhere() -> str:
print("Calling API...")
return "Done!"
# 2. A standard Starlette route handler.
async def homepage(request):
return JSONResponse({'hello': 'world'})
# 3. Get the ASGI application for your MCP server.
mcp_asgi_app = mcp.http_app()
# 4. Create the main Starlette app.
app = Starlette(
debug=True,
# 5. Hook up the MCP server's lifespan to the main app.
lifespan=mcp_asgi_app.lifespan,
routes=[
# 6. Define the routes for the combined application.
Route('/', homepage),
Mount('/secret_mcp_server', app=mcp_asgi_app),
]
)
要点解析:
- MCP 服务器 :像往常一样定义
mcp
实例与工具。 mcp.http_app()
:关键点。该方法返回一个完整、自包含的 ASGI 应用,代表你的 MCP 服务器。lifespan
:MCP 服务器需要管理后台任务与会话。lifespan
处理启动与关闭事件。把 MCP 应用的lifespan
传给主 Starlette 应用,可确保 MCP 的会话管理器正确启动/停止。- 挂载(Mount) :Starlette 的
Mount
用于把一个应用插到另一个应用里。我们告诉 Starlette:"凡是以/secret_mcp_server
开头的请求,都交给mcp_asgi_app
处理。"
用 Uvicorn 运行这个组合服务器。
图 87. 终端
vbnet
> uvicorn mount_asgi_server_mcp_server:app --port 9000
INFO: Started server process [32980]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:9000 (Press CTRL+C to quit)
现在,单一的服务器进程监听 9000 端口,既能处理常规 HTTP 请求,也能处理 MCP 请求。用一个新的 MCP 客户端来验证:
图 88. mount_asgi_server_mcp_client.py(fastmcp)
python
import asyncio
from fastmcp import Client
from fastmcp.client.transports import (
StreamableHttpTransport,
)
# The URL now points to the mounted path.
client = Client(transport=StreamableHttpTransport("http://localhost:9000/secret_mcp_server/mcp"))
async def main():
async with client:
data = await client.call_tool("call_api_somewhere")
print(data)
asyncio.run(main())
客户端唯一的变化是 URL :它包含了我们挂载 MCP 应用的路径 /secret_mcp_server
,后接 fastmcp 使用的标准端点 /mcp
。
运行客户端:
图 89. 终端
python
> python .\mount_asgi_server_mcp_client.py
CallToolResult(content=[TextContent(type='text', text='Done!', annotations=None, meta=None)], structured_content={'result': 'Done!'}, data='Done!', is_error=False)
成功!你已经把 MCP 服务器集成进了更大的 Web 应用。
挂载 MCP 服务器:mcp 库方式
使用 mcp 库的流程非常相似,但一如既往会更显式一些。
图 90. mount_asgi_server_mcp_server.py(mcp)
python
from starlette.applications import Starlette
from starlette.routing import Mount, Route
from mcp.server.fastmcp import FastMCP
import contextlib
from collections.abc import AsyncIterator
from starlette.responses import JSONResponse
mcp = FastMCP("My App")
@mcp.tool()
async def call_api_somewhere() -> str:
print("Calling API...")
return "Done!"
# This is an alternative, more explicit way to define the lifespan.
@contextlib.asynccontextmanager
async def mcp_lifespan(app: Starlette) -> AsyncIterator[None]:
async with mcp.session_manager.run():
print("MCP Session Manager started.")
yield
print("MCP Session Manager stopped.")
async def homepage(request):
return JSONResponse({'hello': 'world'})
# The method to get the ASGI app is named differently.
mcp_asgi_app = mcp.streamable_http_app()
app = Starlette(
debug=True,
# You can pass the lifespan context directly from the MCP app.
lifespan=mcp_asgi_app.router.lifespan_context,
routes=[
Route('/', homepage),
Mount('/secret_mcp_server', app=mcp_asgi_app),
]
)
核心概念相同,但有少许差异:
- 获取 ASGI 应用的方法名为
mcp.streamable_http_app()
。 lifespan
通过mcp_asgi_app.router.lifespan_context
获取。示例中也展示了如何用@contextlib.asynccontextmanager
手动定义,以获得更多控制力。
停止之前的服务器,运行新的:
图 91. 终端
vbnet
> uvicorn mount_asgi_server_mcp_server:app --port 9000
INFO: Started server process [10656]
INFO: Waiting for application startup.
[07/14/25 22:07:35] INFO StreamableHTTP session manager started streamable_http_manager.py:112
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:9000 (Press CTRL+C to quit)
客户端代码也与独立版本非常相似。
图 92. mount_asgi_server_mcp_client.py(mcp)
python
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
# Note the updated URL.
MCP_SERVER_URL = "http://127.0.0.1:9000/secret_mcp_server/mcp"
async def run():
async with streamablehttp_client(MCP_SERVER_URL) as (read, write, _):
async with ClientSession(
read,
write
) as session:
await session.initialize()
result = await session.call_tool("call_api_somewhere")
print(result)
if __name__ == "__main__":
asyncio.run(run())
运行客户端,你会得到预期结果。
图 93. 终端
python
> python .\mount_asgi_server_mcp_client.py
meta=None content=[TextContent(type='text', text='Done!', annotations=None, meta=None)] structuredContent={'result': 'Done!'} isError=False
到这里,你已经成为 ASGI 小能手,能把 MCP 功能编织进任何现代 Python Web 框架。
用中间件为服务器"增压"
**中间件(Middleware)**位于请求/响应链路上,包裹你的主业务逻辑。它是处理"横切关注点"的强大模式,例如:
- 记录每个入站请求;
- 校验认证令牌;
- 给每个响应添加自定义头;
- 统计请求处理耗时。
fastmcp 为 MCP 服务器提供了一个简洁优雅 的中间件系统。我们来写一个中间件,在工具被调用前记录其细节。
注:本节聚焦 fastmcp 的中间件系统。虽然你也可以用标准 ASGI 中间件模式为基础 mcp 库实现中间件,但 fastmcp 为此提供了更高层、更方便的 API。
创建 middleware_mcp_server.py
。
图 94. middleware_mcp_server.py
python
from fastmcp import FastMCP
from fastmcp.server.middleware import Middleware, MiddlewareContext
# 1. Define a custom middleware class.
class ToolMiddleware(Middleware):
# 2. Implement the hook for the 'call_tool' event.
async def on_call_tool(self, context: MiddlewareContext, call_next):
if context.fastmcp_context:
# 3. Access information from the context.
tool = await context.fastmcp_context.fastmcp.get_tool(context.message.name)
print(tool)
# 4. Pass control to the next middleware or the tool itself.
return await call_next(context)
# 5. Create the server and add the middleware.
mcp = FastMCP("MyServer")
mcp.add_middleware(ToolMiddleware())
@mcp.tool
async def call_api_somewhere() -> str:
print("Calling API...")
return "Done!"
if __name__ == "__main__":
mcp.run(transport="http", port=9000)
逐点剖析:
- 中间件类 :继承自
fastmcp.server.middleware.Middleware
。 - 事件钩子 :该基类提供多个
on_*
方法可重写,如on_notification
、on_call_tool
、on_read_resource
等。我们要拦截工具调用,因此实现on_call_tool
。 MiddlewareContext
:包含当前请求的大量信息:原始消息、服务器实例等。这里我们用它根据工具名获取工具定义。call_next
:至关重要。必须调用它才能将控制权向下传递 。如果不调用,请求处理会被短路,实际的工具函数将不会执行(例如授权失败时可短路)。add_middleware()
:将自定义中间件注册到FastMCP
实例上。
以独立脚本方式启动该服务器:
图 95. 终端
markdown
> python .\middleware_mcp_server.py
你会看到熟悉的 fastmcp 启动横幅。接着是客户端。它只是一个标准的 fastmcp 客户端,并不知道服务器上跑着中间件。
图 96. middleware_mcp_client.py
python
import asyncio
from fastmcp import Client
from fastmcp.client.transports import (
StreamableHttpTransport,
)
client = Client(transport=StreamableHttpTransport("http://localhost:9000/mcp"))
async def main():
async with client:
data = await client.call_tool("call_api_somewhere")
print(data)
asyncio.run(main())
运行客户端:
图 97. 终端
python
> python .\middleware_mcp_client.py
CallToolResult(content=[TextContent(type='text', text='Done!', annotations=None, meta=None)], structured_content={'result': 'Done!'}, data='Done!', is_error=False)
客户端如期获得响应。现在看看服务器终端窗口的输出:
图 98. 服务器日志
sql
...
INFO: 127.0.0.1:57437 - "POST /mcp/ HTTP/1.1" 200 OK
name='call_api_somewhere' title=None description=None tags=set() enabled=True parameters={'properties': {}, 'type': 'object'} output_schema={'properties': {'result': {'title': 'Result', 'type': 'string'}}, 'required': ['result'], 'title': '_WrappedResult', 'type': 'object', 'x-fastmcp-wrap-result': True} annotations=None serializer=None fn=<function call_api_somewhere at 0x0000023A50DAB400>
Calling API...
...
看到了吗!就在工具打印 Calling API...
之前,你看到了工具对象 的详细信息。我们的中间件成功拦截了调用、执行了自定义逻辑,然后把控制权传递给了工具本身。
一步将 OpenAPI 变成 MCP
你已经学会了把 MCP 集成进现有应用,并用中间件扩展它的功能。接下来是一个能为你节省数小时甚至数天 工作的特性:从 OpenAPI 规范自动生成 MCP 工具。
OpenAPI 是定义 REST API 的行业标准。许多服务会发布一个 openapi.json
文件,以可编程的方式描述其所有可用的端点、参数与响应。
fastmcp 提供了一个相当惊艳的功能:它可以读取这些文件,并即时 创建一个对应的 MCP 服务器------为每个 API 端点生成一个工具 ,而无需你手写哪怕一个 @mcp.tool
装饰器。
我们来用经典示例 Swagger Petstore API 试试。交互式文档在:petstore3.swagger.io/。
该 API 拥有管理宠物、订单与用户的数十个端点。我们只用几行代码就把它们全部变成 MCP 工具。
创建文件 openapi_mcp_server.py
。
图 99. openapi_mcp_server.py
ini
import httpx
from fastmcp import FastMCP
# 1. Create an HTTP client to talk to the real API.
client = httpx.AsyncClient(base_url="https://petstore3.swagger.io/api/v3")
# 2. Fetch the OpenAPI specification file.
openapi_spec = httpx.get("https://petstore3.swagger.io/api/v3/openapi.json").json()
# 3. Create the MCP server from the specification.
mcp = FastMCP.from_openapi(
openapi_spec=openapi_spec,
client=client,
name="My API MCP Server"
)
if __name__ == "__main__":
mcp.run(transport="http", port=9000)
这段代码很简洁,作用如下:
httpx.AsyncClient
:创建一个异步 HTTP 客户端;MCP 服务器会用它在底层向 Petstore API 发起真实请求。- 获取规范 :用普通的 GET 请求下载
openapi.json
。 FastMCP.from_openapi()
:本章"主角"。把 JSON 规范和httpx
客户端传进去,它会完成解析规范、为每个端点创建工具,并正确连线 HTTP 调用的全部繁琐工作。
运行该服务器。
图 100. 终端
markdown
> python .\openapi_mcp_server.py
你的 MCP 服务器现在已经运行,充当 Petstore API 的"智能代理"。我们写个客户端来调用自动生成的工具。查看 OpenAPI 规范,有一个端点叫 findPetsByStatus
。fastmcp 会把它生成为同名工具。
图 101. openapi_mcp_client.py
python
import asyncio
from fastmcp import Client
from fastmcp.client.transports import (
StreamableHttpTransport,
)
client = Client(transport=StreamableHttpTransport("http://localhost:9000/mcp"))
async def main():
async with client:
# Call the auto-generated tool.
pets = await client.call_tool("findPetsByStatus", {"status": "available"})
print("One of the pets:\n", pets.content[0].text)
asyncio.run(main())
运行客户端。
图 102. 终端
css
> python .\openapi_mcp_client.py
One of the pets:
{
"result": [
{
"id": 4,
"category": {
"id": 1,
"name": "Dogs"
},
"name": "Dog 1",
"photoUrls": [
"url1",
"url2"
],
...
{
"id": 5612,
"name": "EAMBkLajcZsGvWYDRU"
}
],
"status": "available"
}
]
}
完美运行 !你的客户端调用了本地 MCP 服务器上的 findPetsByStatus
工具;服务器随后使用其 httpx
客户端向
https://petstore3.swagger.io/api/v3/pet/findByStatus?status=available
发起 GET 请求,收到 JSON 响应后,作为工具结果回传给客户端。
你刚刚让 AI 具备了与一个复杂 REST API 交互的能力,且没有手写任何一个工具 。仅此一项功能,就能成倍加速为既有服务"工具化"的过程。
关键要点(Key Takeaways)
本章把你的 MCP 技能提升到新的复杂度层级,展示了如何把你的工作融入更广阔的 Web 开发世界。
- MCP 是 ASGI 生态的一等公民 :MCP 服务器不必是独立个体。你可以把它们挂载进任意 ASGI 兼容框架(如 Starlette、FastAPI),构建同时服务传统 Web 流量 与 MCP 工具的统一应用。
- 中间件带来"超能力" :你可以通过中间件拦截 MCP 事件,为服务器优雅地添加日志、认证、缓存等横切关注点。
- OpenAPI 是巨大的捷径 :
FastMCP.from_openapi
是真正的游戏规则改变者。对于任何公开 OpenAPI 规范的 REST API,你都能一键生成 MCP 接口,省去海量样板代码。
你不再只是构建 MCP 服务器,而是在设计 MCP 架构。
下一章 我们将把这些技能用于实战:走进一系列真实世界、开源的 MCP 服务器 Community Spotlights 。你会看到开发者如何利用这些先进技巧构建与文件系统 、数据库 交互,甚至从网页抓取内容的强大工具。是时候看看,当社区用这些强力工具开工后,究竟能创造出什么了。