From agents
Build MCP servers with FastMCP v3. Research, scaffold, implement, test, deploy. Use when creating MCP servers or integrating APIs via MCP. NOT for REST APIs, CLI tools, or non-MCP integrations.
npx claudepluginhub wyattowalsh/agents --plugin agentsThis skill uses the workspace's default tool permissions.
Build production-ready MCP servers with FastMCP v3 (3.0.0rc2). This skill guides through research, scaffolding, implementation, testing, and deployment. All first-party output follows this repo's conventions: `mcp/<name>/` directory, `fastmcp.json` config, exact `uv` workspace member, imperative voice, kebab-case naming. Reserve `mcp/servers/` for machine-local third-party MCP installs; it is g...
evals/server-scaffolding.jsonevals/tool-definition.jsonreferences/auth-and-security.mdreferences/common-errors.mdreferences/deployment.mdreferences/fastmcp-v3-api.mdreferences/quick-reference.mdreferences/resources-and-prompts.mdreferences/server-composition.mdreferences/testing.mdreferences/tool-design.mdtemplates/fastmcp.jsontemplates/pyproject.tomltemplates/server.pyCreates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Build production-ready MCP servers with FastMCP v3 (3.0.0rc2). This skill guides through research, scaffolding, implementation, testing, and deployment. All first-party output follows this repo's conventions: mcp/<name>/ directory, fastmcp.json config, exact uv workspace member, imperative voice, kebab-case naming. Reserve mcp/servers/ for machine-local third-party MCP installs; it is gitignored.
Target: FastMCP v3 rc2 — Provider/Transform architecture, 14 built-in middleware, OAuth 2.1, server composition, component versioning, structured output, background tasks, elicitation, sampling.
Input: $ARGUMENTS — the service, API, or capability to wrap as an MCP server.
Route $ARGUMENTS to the appropriate mode:
| $ARGUMENTS pattern | Mode | Start at |
|---|---|---|
| Service/API name (e.g., "GitHub", "Stripe") | New server | Phase 1 |
Path to existing first-party server (e.g., mcp/myserver/) | Extend | Phase 3 |
| OpenAPI spec URL or file path | Convert OpenAPI | Phase 2 (scaffold) then load references/server-composition.md §6 |
| FastAPI app to convert | Convert FastAPI | Phase 2 (scaffold) then load references/server-composition.md §7 |
| Error message or "debug" + description | Debug | Load references/common-errors.md, match symptom |
| "learn" or conceptual question | Learn | Load relevant reference file, explain |
| Empty | Gallery / help overview | Show available modes and example invocations |
Before implementation, fetch current FastMCP v3 docs. Bundled references capture rc2 — live docs may be newer.
resolve-library-id for "fastmcp", then query-docs for the topic.https://gofastmcp.com/llms-full.txt — comprehensive LLM-optimized docs.site:gofastmcp.com <topic> for specific topics.If live docs contradict bundled references, live docs win. Always fetch live docs first — API details shift between rc2 and stable.
Goal: Understand the target service and design the MCP server's tool/resource/prompt inventory.
Load: references/tool-design.md (naming, descriptions, parameter design, 8 tool patterns)
Answer before proceeding:
task=True, requires fastmcp[tasks])OpenAPIProvider or FastMCP.from_openapi())mount() + namespaces)Plan 5-15 tools per server. For each tool, define:
| Field | Requirement |
|---|---|
| Name | snake_case, verb_noun format, max 64 chars |
| Description | 3-5 sentences: WHAT, WHEN to use, WHEN NOT, WHAT it returns |
| Parameters | Each with type, description, constraints via Annotated[type, Field(...)] |
| Annotations | readOnlyHint, destructiveHint, idempotentHint, openWorldHint |
| Error cases | What ToolError messages to raise for expected failures |
references/fastmcp-v3-api.md §6-7 for URI patterns and prompt design.Decide on composition strategy:
FastMCP instance with all tools.mount() with namespaces.FileSystemProvider or custom Provider.OpenAPIProvider or FastMCP.from_openapi().See references/server-composition.md for patterns.
Produce a tool/resource/prompt inventory table before proceeding:
| Component | Type | Name | Description (brief) |
|-----------|------|------|---------------------|
| Tool | tool | search_issues | Search GitHub issues by query |
| Resource | resource | config://settings | Current server configuration |
| Prompt | prompt | summarize_pr | Summarize a pull request for review |
Goal: Create the project directory, tests, and configure dependencies.
Run wagents new mcp <name> to scaffold:
mcp/<name>/
├── server.py # FastMCP entry point
├── pyproject.toml # uv project config
├── fastmcp.json # FastMCP CLI config
└── tests/
├── conftest.py # Client fixture, mock Context
└── test_server.py # Automated test suite
Customize the scaffold:
server.py: mcp = FastMCP("name").pyproject.toml: name = "mcp-<name>".pyproject.toml.pyproject.toml and fastmcp.json.Add to pyproject.toml:
[tool.pytest.ini_options]
asyncio_mode = "auto"
[dependency-groups]
dev = ["pytest>=8", "pytest-asyncio>=0.25"]
Create tests/conftest.py — see references/testing.md §4 for the complete template.
pyproject.toml has [tool.uv.workspace] members = ["mcp/<name>"].cd mcp/<name> && uv sync to install dependencies.uv run python -c "from server import mcp; print(mcp.name)".Note: wagents validate validates skills/agents only, NOT MCP servers. Use the import check above for MCP server validation.
Goal: Build all tools, resources, and prompts from the Phase 1 inventory.
Load: references/fastmcp-v3-api.md (full API surface), references/tool-design.md (patterns)
from fastmcp import FastMCP, Context
mcp = FastMCP(
"server-name",
instructions="Description of what this server provides and when to use it.",
)
For shared resources (HTTP clients, DB connections), add a composable lifespan:
from fastmcp.server.lifespan import lifespan
@lifespan
async def http_lifespan(server):
import httpx
async with httpx.AsyncClient() as client:
yield {"http": client}
mcp = FastMCP("server-name", lifespan=http_lifespan)
# Access in tools: ctx.lifespan_context["http"]
Combine lifespans with |: mcp = FastMCP("name", lifespan=db_lifespan | cache_lifespan)
For each tool in the inventory, follow this pattern:
from typing import Annotated
from pydantic import Field
from fastmcp import Context
from fastmcp.exceptions import ToolError
@mcp.tool(
annotations={
"readOnlyHint": True,
"openWorldHint": True,
},
)
async def search_items(
query: Annotated[str, Field(description="Search term to find items.", min_length=1)],
limit: Annotated[int, Field(description="Max results to return.", ge=1, le=100)] = 10,
ctx: Context | None = None,
) -> dict:
"""Search for items matching a query.
Use this tool when you need to find items by keyword. Returns a list of
matching items with their IDs and titles. Use the limit parameter to
control result count. Does not search archived items.
Returns a dictionary with 'items' list and 'total' count.
"""
if ctx:
await ctx.info(f"Searching for: {query}")
try:
results = await do_search(query, limit)
return {"items": results, "total": len(results)}
except ServiceError as e:
raise ToolError(f"Search failed: {e}")
Key rules for every tool:
Annotated[type, Field(description=...)] on EVERY parameter.ToolError for expected failures (always visible to client).annotations dict on every tool — at minimum readOnlyHint.ctx: Context | None = None for testability without MCP runtime.See references/tool-design.md §9 for 8 complete tool patterns (sync, async+Context, stateful, external API, data processing, dependency-injected, sampling, elicitation).
import json
@mcp.resource("config://settings", mime_type="application/json")
async def get_settings() -> str:
"""Current server configuration."""
return json.dumps(settings)
@mcp.resource("users://{user_id}/profile")
async def get_user_profile(user_id: str) -> str:
"""User profile by ID."""
return json.dumps(await fetch_profile(user_id))
See references/fastmcp-v3-api.md §6 for URI templates, query params, wildcards, and class-based resources.
from fastmcp.prompts import Message
@mcp.prompt
def summarize_pr(pr_number: int, detail_level: str = "brief") -> list[Message]:
"""Generate a prompt to summarize a pull request."""
return [Message(
role="user",
content=f"Summarize PR #{pr_number} at {detail_level} detail level.",
)]
For large servers, split into domain modules and compose. See references/server-composition.md.
from fastmcp import FastMCP
from .issues import issues_server
from .repos import repos_server
mcp = FastMCP("github")
mcp.mount(issues_server, namespace="issues")
mcp.mount(repos_server, namespace="repos")
Load references/auth-and-security.md when implementing authentication.
JWTVerifier, GitHubProvider, or RemoteAuthProvider.@mcp.tool(auth=require_scopes("admin")).common.py with shared tools, separate auth/no-auth entry points.Goal: Verify all components work correctly with deterministic tests.
Load: references/testing.md (patterns, 18-item checklist)
Use the in-memory Client — no network, no subprocess:
import pytest
from fastmcp import Client
from server import mcp
@pytest.fixture
async def client():
async with Client(mcp) as c:
yield c
async def test_search_items(client):
result = await client.call_tool("search_items", {"query": "test"})
assert result.data is not None
assert not result.is_error
async def test_list_tools(client):
tools = await client.list_tools()
names = [t.name for t in tools]
assert "search_items" in names
Cover all 8 categories from references/testing.md:
list_tools(), list_resources(), list_prompts() return expected names.ToolError, not crashes.read_resource(uri) returns correct content and MIME type.get_prompt(name, args) returns expected messages.# MCP Inspector (browser-based)
fastmcp dev inspector mcp/<name>/server.py
# CLI testing
fastmcp list mcp/<name>/server.py
fastmcp call mcp/<name>/server.py search_items '{"query": "test"}'
cd mcp/<name> && uv run pytest -v
Goal: Make the server available to MCP clients.
Load: references/deployment.md (transports, client configs, Docker)
| Scenario | Transport | Command |
|---|---|---|
| Local / Claude Desktop | stdio | fastmcp run server.py |
| Remote / multi-client | Streamable HTTP | fastmcp run server.py --transport http --port 8000 |
| Development | Inspector | fastmcp dev inspector server.py |
Add to client config (Claude Desktop, Claude Code, Cursor):
{
"mcpServers": {
"server-name": {
"command": "uv",
"args": ["run", "--directory", "/path/to/mcp/<name>", "fastmcp", "run", "server.py"]
}
}
}
See references/deployment.md §7 for complete configs per client.
MCP server validation (wagents validate does NOT check MCP servers):
# Import check
uv run python -c "from server import mcp; print(mcp.name)"
# List registered components
fastmcp list mcp/<name>/server.py
# Interactive inspection
fastmcp dev inspector mcp/<name>/server.py
Before declaring the server complete:
Annotated + Field(description=...) on all parametersannotations (at minimum readOnlyHint)ToolError for expected failuresprint() or stdout writes in any tooluv run pytest -vfastmcp.json lists all required dependenciespyproject.toml has correct metadata and dependenciesmask_error_details=True set for production deploymentLoad these files on demand during the relevant phase. Do NOT load all at once.
| File | Content | Load during |
|---|---|---|
references/fastmcp-v3-api.md | Complete v3 API surface: constructor, decorators, Context, return types, resources, prompts, providers, transforms, 14 middleware, background tasks, visibility, v2→v3 changes | Phase 3 |
references/tool-design.md | LLM-optimized naming, descriptions, parameters, annotations, error handling, 8 tool patterns, structured output, response patterns, anti-patterns | Phase 1, 3 |
references/server-composition.md | mount(), import_server(), proxy, FileSystemProvider, OpenAPI (OpenAPIProvider + from_openapi), FastAPI conversion, custom providers, transforms, gateway pattern, DRY registration | Phase 1, 3 |
references/testing.md | In-memory Client, pytest setup, conftest.py template, 8 test categories, MCP Inspector, CLI testing, 18-item checklist | Phase 4 |
references/auth-and-security.md | OAuth 2.1, JWTVerifier, per-component auth, custom auth checks, session-based visibility, custom route auth bypass, SSRF prevention, dual-mode pattern, 15 security rules | Phase 3 |
references/deployment.md | Transports, FastMCP CLI, ASGI, custom routes, client configs, fastmcp.json schema, Docker, background task workers, production checklist | Phase 5 |
references/resources-and-prompts.md | Resources (static, dynamic, binary), prompts (single/multi-message), resource vs tool guidance, testing patterns | Phase 3 |
references/common-errors.md | 34 errors: symptom → cause → v3-updated fix, quick-fix lookup table | Debug mode |
references/quick-reference.md | Minimal examples: server, tool, resource, prompt, lifespan, test, run | Quick start |
These are non-negotiable. Violating any of these produces broken MCP servers.
No stdout. Never use print() or write to stdout in tools/resources/prompts. Stdout is the MCP transport. Use ctx.info(), ctx.warning(), ctx.error() for logging.
ToolError for expected failures. Always raise ToolError("message") for user-facing errors. Standard exceptions are masked by mask_error_details in production.
Verbose descriptions. Every tool needs a 3-5 sentence docstring. Every parameter needs Field(description=...). LLMs cannot use tools they don't understand.
Annotations on every tool. Set readOnlyHint, destructiveHint, idempotentHint, openWorldHint. Clients use these for confirmation flows and retry logic.
No *args or **kwargs. MCP requires a fixed JSON schema for tool inputs. Dynamic signatures break schema generation.
Async state access. In v3, ctx.get_state() and ctx.set_state() are async — always await them.
URI schemes required. Every resource URI must have a scheme (data://, config://, users://). Bare paths fail.
Test deterministically. Use in-memory Client(mcp), not manual prompting. Tests must be repeatable and automated.
Module-level mcp variable. The FastMCP instance must be importable at module level. fastmcp run imports server:mcp by default.
Secrets in env vars only. Never hardcode API keys. Never accept tokens as tool parameters. Load from environment, validate on startup.
Load references/quick-reference.md for the complete quick reference with minimal examples for server, tool, resource, prompt, lifespan, test, and run commands.
Use these terms consistently. Do not invent synonyms.
| Canonical term | Meaning | NOT |
|---|---|---|
| tool | A callable MCP function exposed to clients | "endpoint", "action", "command" |
| resource | URI-addressed read-only data exposed to clients | "asset", "file", "data source" |
| prompt | Reusable message template guiding LLM behavior | "instruction", "system message" |
| provider | Dynamic component source (e.g., FileSystemProvider, OpenAPIProvider) | "plugin", "adapter" |
| transform | Middleware that modifies components at mount time | "filter", "interceptor" |
| middleware | Request/response processing hook in the server pipeline | "handler", "decorator" |
| lifespan | Async context manager for shared server resources | "startup hook", "init" |
| mount | Attach a child server with a namespace prefix | "register", "include" |
| namespace | Prefix added to component names during mount | "scope", "prefix" |
| Context | Runtime object passed to tools for logging, state, sampling | "request", "session" |
| ToolError | Exception class for user-visible error messages | "raise Exception" |
| annotation | Tool metadata hints (readOnlyHint, destructiveHint, etc.) | "tag", "label" |
| transport | Communication layer: stdio or Streamable HTTP | "protocol", "channel" |