目录
简介
本文档详细讲解FastMCP框架中custom_route装饰器的使用方法,说明如何在MCP服务器中集成自定义HTTP端点。通过分析auth/routes.py中的认证端点实现,阐述自定义路由与MCP核心协议的协同工作模式,并提供OAuth回调、健康检查、管理API等典型应用场景的实现示例。
本节来源
- server.py
custom_route装饰器详解
custom_route装饰器是FastMCP服务器提供的核心功能之一,用于在MCP协议之外注册自定义的HTTP路由。该装饰器允许开发者添加任意HTTP端点,适用于OAuth回调、健康检查、管理API等场景。
装饰器接受以下参数:
- path: 路由的URL路径(如"/oauth/callback")
- methods: 支持的HTTP方法列表(如[“GET”, “POST”])
- name: 路由的可选名称(用于Starlette的反向URL查找功能)
- include_in_schema: 是否包含在OpenAPI架构中,默认为True
使用custom_route装饰器时,处理函数必须是异步函数,接收Starlette的Request对象并返回Response对象。
本节来源
- server.py
Route对象创建与请求处理
当使用custom_route装饰器时,系统会创建一个Route对象并将其添加到服务器的自定义路由列表中。Route对象包含路径、端点处理函数、支持的方法、名称和架构包含设置等信息。
请求处理函数的签名必须严格遵循async def handler(request: Request) -> Response的模式。处理函数可以访问请求的完整信息,包括查询参数、请求体、头部等,并可以返回各种类型的响应,如JSON响应、文本响应或自定义响应。
#mermaid-svg-9vO3JgzC8E0i6Xaq {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-9vO3JgzC8E0i6Xaq .error-icon{fill:#552222;}#mermaid-svg-9vO3JgzC8E0i6Xaq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9vO3JgzC8E0i6Xaq .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-9vO3JgzC8E0i6Xaq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9vO3JgzC8E0i6Xaq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9vO3JgzC8E0i6Xaq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9vO3JgzC8E0i6Xaq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9vO3JgzC8E0i6Xaq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9vO3JgzC8E0i6Xaq .marker.cross{stroke:#333333;}#mermaid-svg-9vO3JgzC8E0i6Xaq svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9vO3JgzC8E0i6Xaq .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-9vO3JgzC8E0i6Xaq .cluster-label text{fill:#333;}#mermaid-svg-9vO3JgzC8E0i6Xaq .cluster-label span{color:#333;}#mermaid-svg-9vO3JgzC8E0i6Xaq .label text,#mermaid-svg-9vO3JgzC8E0i6Xaq span{fill:#333;color:#333;}#mermaid-svg-9vO3JgzC8E0i6Xaq .node rect,#mermaid-svg-9vO3JgzC8E0i6Xaq .node circle,#mermaid-svg-9vO3JgzC8E0i6Xaq .node ellipse,#mermaid-svg-9vO3JgzC8E0i6Xaq .node polygon,#mermaid-svg-9vO3JgzC8E0i6Xaq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9vO3JgzC8E0i6Xaq .node .label{text-align:center;}#mermaid-svg-9vO3JgzC8E0i6Xaq .node.clickable{cursor:pointer;}#mermaid-svg-9vO3JgzC8E0i6Xaq .arrowheadPath{fill:#333333;}#mermaid-svg-9vO3JgzC8E0i6Xaq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9vO3JgzC8E0i6Xaq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9vO3JgzC8E0i6Xaq .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-9vO3JgzC8E0i6Xaq .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-9vO3JgzC8E0i6Xaq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9vO3JgzC8E0i6Xaq .cluster text{fill:#333;}#mermaid-svg-9vO3JgzC8E0i6Xaq .cluster span{color:#333;}#mermaid-svg-9vO3JgzC8E0i6Xaq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9vO3JgzC8E0i6Xaq :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
匹配自定义路由
不匹配
HTTP请求
路径匹配
调用处理函数
处理请求逻辑
生成响应
返回Response对象
继续其他路由匹配
图源
- server.py
本节来源
- server.py
典型应用场景实现
OAuth回调端点
在OAuth认证流程中,custom_route装饰器可用于实现回调端点,处理授权服务器重定向回来的请求。
#mermaid-svg-X0y78ZtOpVmOqGEZ {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-X0y78ZtOpVmOqGEZ .error-icon{fill:#552222;}#mermaid-svg-X0y78ZtOpVmOqGEZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-X0y78ZtOpVmOqGEZ .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-X0y78ZtOpVmOqGEZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-X0y78ZtOpVmOqGEZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-X0y78ZtOpVmOqGEZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-X0y78ZtOpVmOqGEZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-X0y78ZtOpVmOqGEZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-X0y78ZtOpVmOqGEZ .marker.cross{stroke:#333333;}#mermaid-svg-X0y78ZtOpVmOqGEZ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-X0y78ZtOpVmOqGEZ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-X0y78ZtOpVmOqGEZ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-X0y78ZtOpVmOqGEZ .actor-line{stroke:grey;}#mermaid-svg-X0y78ZtOpVmOqGEZ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-X0y78ZtOpVmOqGEZ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-X0y78ZtOpVmOqGEZ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-X0y78ZtOpVmOqGEZ .sequenceNumber{fill:white;}#mermaid-svg-X0y78ZtOpVmOqGEZ #sequencenumber{fill:#333;}#mermaid-svg-X0y78ZtOpVmOqGEZ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-X0y78ZtOpVmOqGEZ .messageText{fill:#333;stroke:#333;}#mermaid-svg-X0y78ZtOpVmOqGEZ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-X0y78ZtOpVmOqGEZ .labelText,#mermaid-svg-X0y78ZtOpVmOqGEZ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-X0y78ZtOpVmOqGEZ .loopText,#mermaid-svg-X0y78ZtOpVmOqGEZ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-X0y78ZtOpVmOqGEZ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-X0y78ZtOpVmOqGEZ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-X0y78ZtOpVmOqGEZ .noteText,#mermaid-svg-X0y78ZtOpVmOqGEZ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-X0y78ZtOpVmOqGEZ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-X0y78ZtOpVmOqGEZ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-X0y78ZtOpVmOqGEZ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-X0y78ZtOpVmOqGEZ .actorPopupMenu{position:absolute;}#mermaid-svg-X0y78ZtOpVmOqGEZ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-X0y78ZtOpVmOqGEZ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-X0y78ZtOpVmOqGEZ .actor-man circle,#mermaid-svg-X0y78ZtOpVmOqGEZ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-X0y78ZtOpVmOqGEZ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
"客户端应用"
"MCP服务器"
"授权服务器"
重定向到授权端点
显示登录页面
提交凭证
重定向到回调URL
GET /oauth/callback
处理回调请求
重定向到应用
"客户端应用"
"MCP服务器"
"授权服务器"
图源
- auth_server.py
健康检查端点
健康检查是微服务架构中的重要组成部分,custom_route装饰器可以轻松实现健康检查端点。
@server.custom_route("/health", methods=["GET"])
async def health_check(request: Request) –> Response:
return JSONResponse({"status": "ok"})
管理API端点
管理API通常需要特殊的访问控制,custom_route装饰器可以与中间件结合使用,实现安全的管理接口。
本节来源
- auth_server.py
- server.py
与MCP核心协议的协同工作
自定义路由与MCP核心协议可以协同工作,通过auth/routes.py中的认证端点实现可以看出,自定义路由可以与MCP的认证机制无缝集成。
在create_auth_routes函数中,多个认证相关的路由被创建,包括授权、令牌、注册和撤销端点。这些端点使用CORS中间件处理跨域请求,并与OAuth授权服务器提供者集成。
#mermaid-svg-yGVZnnRgCUyO5JEd {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-yGVZnnRgCUyO5JEd .error-icon{fill:#552222;}#mermaid-svg-yGVZnnRgCUyO5JEd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yGVZnnRgCUyO5JEd .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-yGVZnnRgCUyO5JEd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yGVZnnRgCUyO5JEd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yGVZnnRgCUyO5JEd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yGVZnnRgCUyO5JEd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yGVZnnRgCUyO5JEd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yGVZnnRgCUyO5JEd .marker.cross{stroke:#333333;}#mermaid-svg-yGVZnnRgCUyO5JEd svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yGVZnnRgCUyO5JEd g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-yGVZnnRgCUyO5JEd g.classGroup text .title{font-weight:bolder;}#mermaid-svg-yGVZnnRgCUyO5JEd .nodeLabel,#mermaid-svg-yGVZnnRgCUyO5JEd .edgeLabel{color:#131300;}#mermaid-svg-yGVZnnRgCUyO5JEd .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-yGVZnnRgCUyO5JEd .label text{fill:#131300;}#mermaid-svg-yGVZnnRgCUyO5JEd .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-yGVZnnRgCUyO5JEd .classTitle{font-weight:bolder;}#mermaid-svg-yGVZnnRgCUyO5JEd .node rect,#mermaid-svg-yGVZnnRgCUyO5JEd .node circle,#mermaid-svg-yGVZnnRgCUyO5JEd .node ellipse,#mermaid-svg-yGVZnnRgCUyO5JEd .node polygon,#mermaid-svg-yGVZnnRgCUyO5JEd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yGVZnnRgCUyO5JEd .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-yGVZnnRgCUyO5JEd g.clickable{cursor:pointer;}#mermaid-svg-yGVZnnRgCUyO5JEd g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-yGVZnnRgCUyO5JEd g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-yGVZnnRgCUyO5JEd .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-yGVZnnRgCUyO5JEd .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-yGVZnnRgCUyO5JEd .dashed-line{stroke-dasharray:3;}#mermaid-svg-yGVZnnRgCUyO5JEd #compositionStart,#mermaid-svg-yGVZnnRgCUyO5JEd .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd #compositionEnd,#mermaid-svg-yGVZnnRgCUyO5JEd .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd #dependencyStart,#mermaid-svg-yGVZnnRgCUyO5JEd .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd #dependencyStart,#mermaid-svg-yGVZnnRgCUyO5JEd .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd #extensionStart,#mermaid-svg-yGVZnnRgCUyO5JEd .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd #extensionEnd,#mermaid-svg-yGVZnnRgCUyO5JEd .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd #aggregationStart,#mermaid-svg-yGVZnnRgCUyO5JEd .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd #aggregationEnd,#mermaid-svg-yGVZnnRgCUyO5JEd .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-yGVZnnRgCUyO5JEd .edgeTerminals{font-size:11px;}#mermaid-svg-yGVZnnRgCUyO5JEd :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
"创建"
"接收"
"返回"
FastMCP
+_custom_starlette_routes : list[Route]
+custom_route(path, methods, name, include_in_schema)
Route
+path : str
+endpoint : Callable
+methods : list[str]
+name : str | None
+include_in_schema : bool
Request
+query_params : dict
+headers : dict
+body : bytes
Response
+status_code : int
+headers : dict
+body : bytes
图源
- server.py
- routes.py
本节来源
- routes.py
- server.py
API文档生成控制
include_in_schema参数控制自定义路由是否包含在OpenAPI架构文档中。这对于管理API或内部端点特别有用,可以选择性地将其从公开的API文档中隐藏。
当include_in_schema设置为False时,端点仍然可用,但不会出现在自动生成的API文档中,这为API的安全性和隐私性提供了额外的控制层。
本节来源
- server.py





