一键导入
learn-api
External API evaluation and MCP server wrapper generation. Load when integrating a REST/GraphQL API as a workspace tool
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
External API evaluation and MCP server wrapper generation. Load when integrating a REST/GraphQL API as a workspace tool
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Capability-based provider system patterns. Load when creating providers or wiring module-provider integration
Full-stack WebSocket subscription development. Load when adding WS routes, topic services, or debugging WS data flows
Systematic Python type error resolution (mypy/pyright). Load when fixing backend type check failures
Type-first Python with Pydantic, Protocol, and discriminated unions. Load when writing models or enforcing typing
Systematic type error resolution for Python (mypy/pyright) and TypeScript (vue-tsc). Use when fixing type errors, resolving type check failures, or optimizing type imports.
Terminal command safety, delegation routing, and guarded execution. Load when running shell commands or delegating to Bash subagents
| name | learn-api |
| description | External API evaluation and MCP server wrapper generation. Load when integrating a REST/GraphQL API as a workspace tool |
| user-invocable | false |
Evaluate external APIs encountered during work and, when they provide recurring value, wrap them as MCP servers so they become persistent workspace tools. Covers API assessment, spec discovery, server generation with FastMCP, route filtering, custom tool augmentation, path encoding pitfalls, VS Code stdio configuration, and common errors with solutions.
Before building anything, evaluate whether the API is worth wrapping as a persistent MCP tool.
Assessment criteria:
| Factor | Worth Wrapping | Not Worth Wrapping |
|---|---|---|
| Usage frequency | Recurring across tasks/sessions | One-off lookup |
| Scope | Multiple useful endpoints | Single narrow query |
| Spec availability | OpenAPI/Swagger spec exists | No spec, undocumented |
| Auth complexity | Public or simple API key | Complex OAuth flows requiring user interaction |
| Data freshness | Live data matters (registries, status) | Static/archival data |
Decision:
When proceeding, present to user: "This API ({name}) would be useful as a permanent workspace tool. Want me to create an MCP server for it?"
| Scenario | Approach | Effort |
|---|---|---|
| REST API with an OpenAPI spec available | FastMCP.from_openapi() auto-generation | Low — minutes |
| API without spec, or highly custom tool logic | Hand-crafted @mcp.tool() decorators | Medium |
| Hybrid: spec-based + custom overrides | from_openapi() + @server.tool() additions | Low-Medium |
| Existing FastAPI application | FastMCP.from_fastapi(app) conversion | Low |
Decision rule: If the API has an OpenAPI spec → always start with from_openapi(). Add custom tools only where auto-generation falls short (path encoding, response transformation, multi-step orchestration).
Install dependencies:
pip install fastmcp httpx
# Add pyyaml if spec is YAML format
pip install pyyaml
Minimal hand-crafted server:
from fastmcp import FastMCP
mcp = FastMCP("My Server")
@mcp.tool()
async def hello(name: str) -> str:
"""Say hello to someone."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
OpenAPI auto-generated server:
from __future__ import annotations
import httpx
import yaml # or json
from fastmcp import FastMCP
from fastmcp.server.openapi import MCPType, RouteMap
def _load_spec() -> dict:
"""Fetch and parse the OpenAPI spec."""
resp = httpx.get("https://api.example.com/openapi.yaml", timeout=30.0)
resp.raise_for_status()
return yaml.safe_load(resp.text) # or resp.json() for JSON specs
def _create_client() -> httpx.AsyncClient:
"""Create the shared async HTTP client."""
return httpx.AsyncClient(base_url="https://api.example.com", timeout=30.0)
def create_server() -> FastMCP:
spec = _load_spec()
client = _create_client()
server = FastMCP.from_openapi(
openapi_spec=spec,
client=client,
name="My API Server",
route_maps=[
# All GET endpoints → tools (default behavior)
RouteMap(methods=["GET"], mcp_type=MCPType.TOOL),
],
)
return server
# Module-level export — REQUIRED for stdio transport and FastMCP Cloud
mcp = create_server()
if __name__ == "__main__":
mcp.run()
RouteMap controls which OpenAPI endpoints become MCP tools, resources, or are excluded. Patterns are evaluated in order — first match wins.
Route filtering patterns:
route_maps=[
# 1. Exclude auth/admin endpoints (security)
RouteMap(pattern=r".*/auth/.*", mcp_type=MCPType.EXCLUDE),
RouteMap(pattern=r".*/admin/.*", mcp_type=MCPType.EXCLUDE),
# 2. Exclude write operations (read-only server)
RouteMap(methods=["PUT", "POST", "DELETE", "PATCH"], mcp_type=MCPType.EXCLUDE),
# 3. Exclude duplicate API versions
RouteMap(pattern=r"/v0\.1/.*", mcp_type=MCPType.EXCLUDE),
# 4. Exclude endpoints you'll replace with custom tools
RouteMap(pattern=r".*/items/\{itemId\}.*", mcp_type=MCPType.EXCLUDE),
# 5. Catch-all: remaining GET → tools
RouteMap(methods=["GET"], mcp_type=MCPType.TOOL),
]
MCPType options:
| Type | Effect |
|---|---|
MCPType.TOOL | Expose as callable tool |
MCPType.RESOURCE | Expose as readable resource |
MCPType.RESOURCE_TEMPLATE | Expose as parameterized resource template |
MCPType.EXCLUDE | Skip entirely |
After from_openapi(), you can add custom @server.tool() decorators to the same server. This is essential when:
/ in identifiers)server = FastMCP.from_openapi(openapi_spec=spec, client=client, name="API")
# Add custom tool alongside auto-generated ones
@server.tool()
async def get_item_details(item_name: str) -> dict:
"""Get details for an item whose name may contain slashes.
Args:
item_name: Full item name (e.g. 'com.example/my-item')
"""
from urllib.parse import quote
encoded = quote(item_name, safe="")
resp = await client.get(f"/v1/items/{encoded}")
resp.raise_for_status()
return resp.json()
Add a server entry to the workspace MCP configuration:
// .vscode/mcp.json
{
"servers": {
"my-server": {
"type": "stdio",
"command": "python3",
"args": ["${workspaceFolder}/mcp-servers/my-server/server.py"]
}
}
}
Verify the server works:
# Quick smoke test — should print tool list to stderr and exit
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}' | python3 server.py
Problem: REST APIs with path parameters containing / (e.g., reverse-DNS identifiers like io.github.user/my-server) break when FastMCP auto-generates tool calls — the slash is treated as a path separator.
Solution: Exclude these endpoints from auto-generation and create custom tools with explicit URL encoding:
from urllib.parse import quote
# Exclude the auto-generated endpoint
RouteMap(pattern=r".*/servers/\{serverName\}.*", mcp_type=MCPType.EXCLUDE),
# Replace with custom tool
@server.tool()
async def get_server(server_name: str) -> dict:
"""Get server details. Name uses reverse-DNS format (e.g. 'io.github.user/repo')."""
encoded = quote(server_name, safe="") # safe="" encodes ALL special chars including /
resp = await client.get(f"/v0/servers/{encoded}")
resp.raise_for_status()
return resp.json()
For servers that hold connections or caches, use the lifespan pattern:
from contextlib import asynccontextmanager
from dataclasses import dataclass
@dataclass
class ServerContext:
client: httpx.AsyncClient
@asynccontextmanager
async def server_lifespan(server: FastMCP):
client = httpx.AsyncClient(base_url="https://api.example.com", timeout=30.0)
try:
yield ServerContext(client=client)
finally:
await client.aclose()
mcp = FastMCP("Server", lifespan=server_lifespan)
The server object must be assigned at module level for stdio transport to work:
# ✅ CORRECT
def create_server() -> FastMCP:
...
return server
mcp = create_server() # Module-level assignment
# ❌ WRONG — server only exists inside function scope
def create_server():
server = FastMCP("Server")
server.run() # Won't be found by stdio transport
| Category | Error | Fix |
|---|---|---|
| Startup | ModuleNotFoundError: fastmcp | pip install fastmcp[openapi] |
| Startup | ImportError: httpx | pip install httpx |
| OpenAPI | Path params not encoded | Custom tool with manual URL construction |
| OpenAPI | Missing routes after filter | Check RouteMap keys match spec paths exactly |
| Runtime | ConnectionError on tool call | Verify base URL + API key env var |
| Runtime | Timeout on large responses | Add timeout param to httpx client |
| VS Code | Server not appearing | Restart VS Code, check settings.json MCP config |
| VS Code | "spawn ENOENT" | Use absolute path to Python in command array |
from_openapi() without RouteMap filtering exposes write/auth endpoints as tools. Always exclude dangerous operations./ in path params silently breaks routing.httpx.AsyncClient use default (5s) timeout. Set explicit timeout=30.0 or higher.httpx.AsyncClient inside each tool function. Create once and share via lifespan or module scope.from_openapi() for the bulk, add @server.tool() for edge cases requiring custom logic.Args: sections.Complete server templates and configuration examples: see ./TEMPLATES.md
| Task | Command / Code |
|---|---|
| Install FastMCP | pip install fastmcp |
| Install with YAML support | pip install fastmcp httpx pyyaml |
| Run server standalone | python3 server.py |
| Run with inspector | fastmcp dev server.py |
| Install to Claude Desktop | fastmcp install server.py |
| Debug logging | FASTMCP_LOG_LEVEL=DEBUG python3 server.py |
| FastMCP Version | Key Feature |
|---|---|
| v2.14.0 | Background tasks (task=True), breaking changes (see docs) |
| v2.14.1 | Sampling with tools, Python 3.13 support |
| v2.14.2 | MCP SDK pinned <2.x, OpenAPI 3.1 support |
| v3.0.0 (beta) | Provider architecture, transforms, component versioning |