Skip to main content

OpenAPI documentation and helper decorators for FastMCP servers

Project description

fastmcp-openapi

fastmcp-openapi 为 FastMCP 服务提供 OpenAPI 文档生成、Swagger UI 页面、HTTP tool 代理路由,以及面向业务扩展的请求钩子封装。

LICENSE codecov CI pypi version pyversions Checked with mypy Code style: black

核心能力

  • 在注册 tool 时提取参数、响应和描述信息,生成 OpenAPI 3.1 schema。
  • 自动注册 /openapi.json/docs/api/tools 等文档路由。
  • 通过 FastMCPOpenAPI.tool() 注册 MCP tool,并自动为每个 tool 注册对应 HTTP 代理路由。
  • 支持 before_request / after_request 钩子,统一处理请求上下文与响应头。
  • 支持把 Pydantic BaseModel 参数展开为 MCP tool 入参,并在代理调用时自动回填模型。
  • 支持自定义 tool 代理异常处理与校验异常处理。

安装

uv add fastmcp-openapi

本仓库本地开发可直接同步依赖:

uv sync

快速开始

下面的示例来自仓库根目录的 examples/demo_server.py,展示了完整的工具注册、请求钩子、响应模型和本地启动方式。

import asyncio
import typing as t
import uuid

from fastmcp import Context, FastMCP
from pydantic import BaseModel, Field
from starlette.requests import Request
from starlette.responses import Response

from fastmcp_openapi import FastMCPOpenAPI

DataT = t.TypeVar("DataT")

openapi = FastMCPOpenAPI(
    FastMCP("demo"),
    title="Demo MCP Tools API",
    description="Demo FastMCP OpenAPI docs",
    base_url="http://127.0.0.1:8333",
)


class ItemAddInput(BaseModel):
    aid: str = Field(..., description="活动ID")
    itemIds: list[str] = Field(..., description="待添加商品的 num_id 列表")


class ActCount(BaseModel):
    dealing: int = Field(..., description="待处理的商品数")
    rendered: int = Field(..., description="渲染中的商品数")
    applied: int = Field(default=0, description="已应用的商品数")
    fail: int = Field(default=0, description="应用失败的商品数")
    removed: int = Field(default=0, description="已移除的商品数")


class ActInfo(BaseModel):
    id: str = Field(..., description="活动ID")
    name: str = Field(..., description="活动显示名称")
    count: ActCount = Field(..., description="活动占用的商品数")
    thumbnail: str = Field(default="", description="活动缩略图")


class ItemActData(BaseModel):
    act: ActInfo = Field(..., description="活动信息")


class ItemAddData(BaseModel):
    data: ItemActData = Field(..., description="添加商品后的活动信息")


class BaseResponse(BaseModel, t.Generic[DataT]):
    message: str = Field(..., description="接口错误信息")
    request_id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="请求ID")
    code: int = Field(..., description="接口错误码")
    data: DataT | None = Field(default=None, description="接口返回数据")


@openapi.before_request
def load_request_id(request: Request) -> None:
    request.state.request_id = request.headers.get("X-Request-Id", "")


@openapi.after_request
def add_request_id_header(request: Request, response: Response) -> Response:
    response.headers["X-Request-Id"] = request.state.request_id
    return response


@openapi.tool()
async def list_items(ctx: Context, title: str = "") -> BaseResponse[list[dict]]:
    items = [
        {"num_id": "123456", "title": "测试商品1", "price": 9.9},
        {"num_id": "234567", "title": "正式商品2", "price": 19.9},
    ]
    items = [item for item in items if not title or title in item["title"]]
    return BaseResponse[list[dict]](code=0, message="success", data=items)


@openapi.tool()
async def item_add(ctx: Context, param: ItemAddInput) -> BaseResponse[ItemAddData]:
    return BaseResponse[ItemAddData](
        code=0,
        message="success",
        data=ItemAddData.model_validate(
            {
                "data": {
                    "act": {
                        "id": param.aid,
                        "name": "test",
                        "count": {"dealing": 1, "rendered": 2},
                        "thumbnail": "http://example.com/thumbnail.jpg",
                    }
                }
            }
        ),
    )


if __name__ == "__main__":
    asyncio.run(openapi.setup())
    openapi.mcp.run(transport="http", host="0.0.0.0", port=8333, path="/mcp", stateless_http=True)

启动后可访问:

  • http://127.0.0.1:8333/docs
  • http://127.0.0.1:8333/openapi.json
  • http://127.0.0.1:8333/api/tools
  • http://127.0.0.1:8333/call/list_items
  • http://127.0.0.1:8333/call/item_add

自动注册的路由

调用 await openapi.setup() 后会注册文档路由;每次使用 @openapi.tool() 注册 tool 时,也会注册对应代理路由。

文档路由

  • GET /openapi.json:返回 OpenAPI schema。
  • GET /docs:返回 Swagger UI 页面。
  • GET /api/tools:返回内部 registry 中提取出的 tool 信息。
  • GET /favicon.svg:默认文档图标。

tool 代理路由

  • GET /call/{tool_name}:从 query 参数读取入参。
  • POST /call/{tool_name}:从 JSON body 读取入参。
  • GET|POST /status:默认健康检查路由,返回 OK

说明:

  • 只会为已注册的 tool 生成精确路径,不会开放任意 tool 名称的通配调用。
  • BaseModel 类型入参会在注册阶段展开为字段级 schema,在运行时再组装回模型实例。
  • tool 调用抛出 ValidationError 时默认返回 422;其他异常默认返回 500

常见配置

from fastmcp_openapi import FastMCPOpenAPI, FastMCPOpenAPIConfig

config = FastMCPOpenAPIConfig(
    title="Demo MCP Tools API",
    version="1.0.0",
    description="Demo FastMCP OpenAPI docs",
    base_url="http://127.0.0.1:8333",
    openapi_route="/openapi.json",
    docs_ui_route="/docs",
    api_tools_route="/api/tools",
    api_base="/call",
    status_route="/healthz",
    enable_status_route=True,
    enable_cors=True,
)

openapi = FastMCPOpenAPI(FastMCP("demo"), config=config)

如果需要关闭状态路由:

config = FastMCPOpenAPIConfig(enable_status_route=False)

Hook 机制

before_requestafter_request 都支持同步函数与异步函数,并按注册顺序执行。

  • before_request 返回值会被忽略。
  • after_request 返回 Response 时会替换当前响应。
  • after_request 返回 None 时沿用当前响应。

文档导航

源码布局

src/fastmcp_openapi/
├── __init__.py
├── config.py
├── decorators.py
├── extractor.py
├── routes.py
└── templates.py

本地开发

uv sync
uv run poe lint
uv run poe test

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastmcp_openapi-0.0.6.tar.gz (143.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastmcp_openapi-0.0.6-py3-none-any.whl (20.5 kB view details)

Uploaded Python 3

File details

Details for the file fastmcp_openapi-0.0.6.tar.gz.

File metadata

  • Download URL: fastmcp_openapi-0.0.6.tar.gz
  • Upload date:
  • Size: 143.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastmcp_openapi-0.0.6.tar.gz
Algorithm Hash digest
SHA256 2010ed99b07e1700d1d6c3a88f30a1619f15412d242271a9451e1802b3671137
MD5 96dea7b7b4609b9d94e7d3998bf59202
BLAKE2b-256 094af35b757f4549a1f629d126104a9dbd12aedc805ec868a9889eba80fb6fed

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmcp_openapi-0.0.6.tar.gz:

Publisher: release.yml on seekplum/fastmcp-openapi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmcp_openapi-0.0.6-py3-none-any.whl.

File metadata

  • Download URL: fastmcp_openapi-0.0.6-py3-none-any.whl
  • Upload date:
  • Size: 20.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastmcp_openapi-0.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 49b2601dea2a037c28fe9bdc179d2bdb01933504c9235aea69f0dfc45bfd47cf
MD5 5630c171ad745e349f935ab2bfec151f
BLAKE2b-256 9b0b34f59b4a68e4cbc0adbb0ae77d73c929b88625f38a9f70f299f0f42a4135

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmcp_openapi-0.0.6-py3-none-any.whl:

Publisher: release.yml on seekplum/fastmcp-openapi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page