mcp-creator
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.
mcp-creator
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.
Quick Start
Install:
npx skills add github:wyattowalsh/agents --skill mcp-creator -y -g --agent antigravity --agent claude-code --agent codex --agent crush --agent cursor --agent gemini-cli --agent github-copilot --agent grok --agent opencodeUse: /mcp-creator <service or API to integrate>
Works with Claude Code, Gemini CLI, OpenCode, and other agentskills.io-compatible agents.
| $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 |
Critical Rules
Section titled “Critical Rules”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. Usectx.info(),ctx.warning(),ctx.error()for logging. -
ToolError for expected failures. Always
raise ToolError("message")for user-facing errors. Standard exceptions are masked bymask_error_detailsin 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
*argsor**kwargs. MCP requires a fixed JSON schema for tool inputs. Dynamic signatures break schema generation. -
Async state access. In v3,
ctx.get_state()andctx.set_state()are async — alwaysawaitthem. -
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
mcpvariable. TheFastMCPinstance must be importable at module level.fastmcp runimportsserver:mcpby default. -
Secrets in env vars only. Never hardcode API keys. Never accept tokens as tool parameters. Load from environment, validate on startup.
Consult Live Documentation
Section titled “Consult Live Documentation”Before implementation, fetch current FastMCP v3 docs. Bundled references capture rc2 — live docs may be newer.
- Context7 —
resolve-library-idfor “fastmcp”, thenquery-docsfor the topic. - WebFetch —
https://gofastmcp.com/llms-full.txt— comprehensive LLM-optimized docs. - WebSearch fallback —
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.
Phase 1: Research & Plan
Section titled “Phase 1: Research & Plan”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)
1.1 Understand the Target
Section titled “1.1 Understand the Target”- Read any documentation, SDK, or API reference for the target service.
- Identify the core operations users need (CRUD, search, status, config).
- Note authentication requirements (API keys, OAuth, tokens).
- Check for existing MCP servers for this service — avoid duplicating work.
1.2 Architecture Checklist
Section titled “1.2 Architecture Checklist”Answer before proceeding:
- Auth needed? (API key → env var, OAuth → HTTP transport, none → stdio)
- Transport? (stdio for local, Streamable HTTP for remote/multi-client)
- Background tasks? (long-running operations →
task=True, requiresfastmcp[tasks]) - OpenAPI spec available? (→
OpenAPIProviderorFastMCP.from_openapi()) - Multiple domains? (→ composed servers with
mount()+ namespaces)
1.3 Design Tool Inventory
Section titled “1.3 Design Tool Inventory”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 |
1.4 Design Resources and Prompts
Section titled “1.4 Design Resources and Prompts”- Resources for static/slow-changing data (config, schemas, status). URI-addressed.
- Prompts for reusable message templates that guide LLM behavior.
- See
references/fastmcp-v3-api.md§6-7 for URI patterns and prompt design.
1.5 Plan Architecture
Section titled “1.5 Plan Architecture”Decide on composition strategy:
- Single server — Most cases. One
FastMCPinstance with all tools. - Composed servers — Large APIs. Domain servers mounted via
mount()with namespaces. - Provider-based — Dynamic tool registration.
FileSystemProvideror customProvider. - OpenAPI conversion — Auto-generate tools from OpenAPI spec.
OpenAPIProviderorFastMCP.from_openapi().
See references/server-composition.md for patterns.
1.6 Deliverable
Section titled “1.6 Deliverable”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 |Phase 2: Scaffold
Section titled “Phase 2: Scaffold”Goal: Create the project directory, tests, and configure dependencies.
2.1 Create Project Structure
Section titled “2.1 Create Project Structure”Run python scripts/scaffold_mcp.py <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 suiteCustomize the scaffold:
- Set server name in
server.py:mcp = FastMCP("name"). - Set package name in
pyproject.toml:name = "mcp-<name>". - Update description in
pyproject.toml. - Add service-specific dependencies to both
pyproject.tomlandfastmcp.json.
2.2 Add Test Configuration
Section titled “2.2 Add Test Configuration”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.
2.3 Configure and Verify
Section titled “2.3 Configure and Verify”- Ensure root
pyproject.tomlhas[tool.uv.workspace] members = ["mcp/<name>"]. - Run
cd mcp/<name> && uv syncto install dependencies. - Verify:
uv run python -c "from server import mcp; print(mcp.name)".
Note: python scripts/check.py validates this skill only, NOT MCP servers. Use the import check above for MCP server validation.
Phase 3: Implement
Section titled “Phase 3: Implement”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)
3.1 Server Setup
Section titled “3.1 Server Setup”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
@lifespanasync 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)
3.2 Implement Tools
Section titled “3.2 Implement Tools”For each tool in the inventory, follow this pattern:
from typing import Annotatedfrom pydantic import Fieldfrom fastmcp import Contextfrom 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.- Verbose docstring: WHAT, WHEN to use, WHEN NOT, WHAT it returns.
ToolErrorfor expected failures (always visible to client).annotationsdict on every tool — at minimumreadOnlyHint.ctx: Context \| None = Nonefor 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).
3.3 Implement Resources
Section titled “3.3 Implement Resources”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.
3.4 Implement Prompts
Section titled “3.4 Implement Prompts”from fastmcp.prompts import Message
@mcp.promptdef 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.", )]3.5 Composition (if applicable)
Section titled “3.5 Composition (if applicable)”For large servers, split into domain modules and compose. See references/server-composition.md.
from fastmcp import FastMCPfrom .issues import issues_serverfrom .repos import repos_server
mcp = FastMCP("github")mcp.mount(issues_server, namespace="issues")mcp.mount(repos_server, namespace="repos")3.6 Auth (if applicable)
Section titled “3.6 Auth (if applicable)”Load references/auth-and-security.md when implementing authentication.
- stdio transport: No MCP-level auth. Use env vars for backend API keys.
- HTTP transport: OAuth 2.1 via
JWTVerifier,GitHubProvider, orRemoteAuthProvider. - Per-tool auth:
@mcp.tool(auth=require_scopes("admin")). - Dual-mode pattern:
common.pywith shared tools, separate auth/no-auth entry points.
Phase 4: Test
Section titled “Phase 4: Test”Goal: Verify all components work correctly with deterministic tests.
Load: references/testing.md (patterns, 18-item checklist)
4.1 Write Tests
Section titled “4.1 Write Tests”Use the in-memory Client — no network, no subprocess:
import pytestfrom fastmcp import Clientfrom server import mcp
@pytest.fixtureasync 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 names4.2 Test Categories
Section titled “4.2 Test Categories”Cover all 8 categories from references/testing.md:
- Discovery —
list_tools(),list_resources(),list_prompts()return expected names. - Happy path — Each tool with valid input returns expected output.
- Error handling — Invalid input produces
ToolError, not crashes. - Edge cases — Empty strings, boundary values, Unicode, large inputs.
- Resources —
read_resource(uri)returns correct content and MIME type. - Prompts —
get_prompt(name, args)returns expected messages. - Integration — Tool chains, lifespan setup/teardown.
- Concurrent — Multiple simultaneous calls don’t interfere.
4.3 Interactive Testing
Section titled “4.3 Interactive Testing”# MCP Inspector (browser-based)fastmcp dev inspector mcp/<name>/server.py
# CLI testingfastmcp list mcp/<name>/server.pyfastmcp call mcp/<name>/server.py search_items '{"query": "test"}'4.4 Run Tests
Section titled “4.4 Run Tests”cd mcp/<name> && uv run pytest -vPhase 5: Deploy & Configure
Section titled “Phase 5: Deploy & Configure”Goal: Make the server available to MCP clients.
Load: references/deployment.md (transports, client configs, Docker)
5.1 Select Transport
Section titled “5.1 Select Transport”| 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 |
5.2 Generate Client Config
Section titled “5.2 Generate Client Config”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.
5.3 Validate
Section titled “5.3 Validate”MCP server validation (skill check.py does NOT check MCP servers):
# Import checkuv run python -c "from server import mcp; print(mcp.name)"
# List registered componentsfastmcp list mcp/<name>/server.py
# Interactive inspectionfastmcp dev inspector mcp/<name>/server.py5.4 Quality Checklist
Section titled “5.4 Quality Checklist”Before declaring the server complete:
- Every tool has
Annotated+Field(description=...)on all parameters - Every tool has a verbose docstring (WHAT, WHEN, WHEN NOT, RETURNS)
- Every tool has
annotations(at minimumreadOnlyHint) - Every tool uses
ToolErrorfor expected failures - No
print()orstdoutwrites in any tool - Resources have correct URI schemes and MIME types
- Tests pass:
uv run pytest -v - MCP Inspector shows all components correctly
-
fastmcp.jsonlists all required dependencies -
pyproject.tomlhas correct metadata and dependencies - No deprecated constructor kwargs (removed in rc1)
- Custom routes have manual auth if sensitive
-
mask_error_details=Trueset for production deployment
Quick Reference
Section titled “Quick Reference”Load references/quick-reference.md for the complete quick reference with minimal examples for server, tool, resource, prompt, lifespan, test, and run commands.
Canonical Vocabulary
Section titled “Canonical Vocabulary”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” |
Validation Contract
Section titled “Validation Contract”Before declaring changes to this skill complete, run:
python skills/mcp-creator/scripts/check.pyCompletion criteria:
scripts/check.pyexits 0.- MCP scaffold smoke-check:
python scripts/scaffold_mcp.py --dry-run example-server.
| Field | Value |
|---|---|
| Source Type | repo-owned |
| Display Source | github:wyattowalsh/agents |
| Source Kind | repo |
| Installability | portable command |
| Review State | reviewed |
| Target Agents | antigravity, claude-code, codex, crush, cursor, gemini-cli, github-copilot, grok, opencode |
| Field | Value |
|---|---|
| Name | mcp-creator |
| License | MIT |
| Version | 2.0.0 |
| Author | wyattowalsh |
| Field | Value |
|---|---|
| Model | opus |
| Argument Hint | [service or API to integrate] |
Related Skills
Section titled “Related Skills”Full SKILL.md
---name: mcp-creatordescription: >- 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.license: MITargument-hint: "<service or API to integrate>"model: opusmetadata: author: wyattowalsh version: "2.0.0"---# MCP Creator — FastMCP v3
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.
---
## Dispatch
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 |
---
## Consult Live Documentation
Before implementation, fetch current FastMCP v3 docs. Bundled references capture rc2 — live docs may be newer.
1. **Context7** — `resolve-library-id` for "fastmcp", then `query-docs` for the topic.2. **WebFetch** — `https://gofastmcp.com/llms-full.txt` — comprehensive LLM-optimized docs.3. **WebSearch fallback** — `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.
---
## Phase 1: Research & Plan
**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)
### 1.1 Understand the Target
- Read any documentation, SDK, or API reference for the target service.- Identify the core operations users need (CRUD, search, status, config).- Note authentication requirements (API keys, OAuth, tokens).- Check for existing MCP servers for this service — avoid duplicating work.
### 1.2 Architecture Checklist
Answer before proceeding:
- [ ] Auth needed? (API key → env var, OAuth → HTTP transport, none → stdio)- [ ] Transport? (stdio for local, Streamable HTTP for remote/multi-client)- [ ] Background tasks? (long-running operations → `task=True`, requires `fastmcp[tasks]`)- [ ] OpenAPI spec available? (→ `OpenAPIProvider` or `FastMCP.from_openapi()`)- [ ] Multiple domains? (→ composed servers with `mount()` + namespaces)
### 1.3 Design Tool Inventory
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 |
### 1.4 Design Resources and Prompts
- **Resources** for static/slow-changing data (config, schemas, status). URI-addressed.- **Prompts** for reusable message templates that guide LLM behavior.- See `references/fastmcp-v3-api.md` §6-7 for URI patterns and prompt design.
### 1.5 Plan Architecture
Decide on composition strategy:
- **Single server** — Most cases. One `FastMCP` instance with all tools.- **Composed servers** — Large APIs. Domain servers mounted via `mount()` with namespaces.- **Provider-based** — Dynamic tool registration. `FileSystemProvider` or custom `Provider`.- **OpenAPI conversion** — Auto-generate tools from OpenAPI spec. `OpenAPIProvider` or `FastMCP.from_openapi()`.
See `references/server-composition.md` for patterns.
### 1.6 Deliverable
Produce a tool/resource/prompt inventory table before proceeding:
```markdown| 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 |```
---
## Phase 2: Scaffold
**Goal:** Create the project directory, tests, and configure dependencies.
### 2.1 Create Project Structure
Run `python scripts/scaffold_mcp.py <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:- Set server name in `server.py`: `mcp = FastMCP("name")`.- Set package name in `pyproject.toml`: `name = "mcp-<name>"`.- Update description in `pyproject.toml`.- Add service-specific dependencies to both `pyproject.toml` and `fastmcp.json`.
### 2.2 Add Test Configuration
Add to `pyproject.toml`:
```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.
### 2.3 Configure and Verify
- Ensure root `pyproject.toml` has `[tool.uv.workspace] members = ["mcp/<name>"]`.- Run `cd mcp/<name> && uv sync` to install dependencies.- Verify: `uv run python -c "from server import mcp; print(mcp.name)"`.
Note: `python scripts/check.py` validates this skill only, NOT MCP servers. Use the import check above for MCP server validation.
---
## Phase 3: Implement
**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)
### 3.1 Server Setup
```pythonfrom 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:
```pythonfrom fastmcp.server.lifespan import lifespan
@lifespanasync 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)`
### 3.2 Implement Tools
For each tool in the inventory, follow this pattern:
```pythonfrom typing import Annotatedfrom pydantic import Fieldfrom fastmcp import Contextfrom 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.- Verbose docstring: WHAT, WHEN to use, WHEN NOT, WHAT it returns.- `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).
### 3.3 Implement Resources
```pythonimport 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.
### 3.4 Implement Prompts
```pythonfrom fastmcp.prompts import Message
@mcp.promptdef 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.", )]```
### 3.5 Composition (if applicable)
For large servers, split into domain modules and compose. See `references/server-composition.md`.
```pythonfrom fastmcp import FastMCPfrom .issues import issues_serverfrom .repos import repos_server
mcp = FastMCP("github")mcp.mount(issues_server, namespace="issues")mcp.mount(repos_server, namespace="repos")```
### 3.6 Auth (if applicable)
Load `references/auth-and-security.md` when implementing authentication.
- **stdio transport:** No MCP-level auth. Use env vars for backend API keys.- **HTTP transport:** OAuth 2.1 via `JWTVerifier`, `GitHubProvider`, or `RemoteAuthProvider`.- **Per-tool auth:** `@mcp.tool(auth=require_scopes("admin"))`.- **Dual-mode pattern:** `common.py` with shared tools, separate auth/no-auth entry points.
---
## Phase 4: Test
**Goal:** Verify all components work correctly with deterministic tests.
**Load:** `references/testing.md` (patterns, 18-item checklist)
### 4.1 Write Tests
Use the in-memory `Client` — no network, no subprocess:
```pythonimport pytestfrom fastmcp import Clientfrom server import mcp
@pytest.fixtureasync 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```
### 4.2 Test Categories
Cover all 8 categories from `references/testing.md`:
1. **Discovery** — `list_tools()`, `list_resources()`, `list_prompts()` return expected names.2. **Happy path** — Each tool with valid input returns expected output.3. **Error handling** — Invalid input produces `ToolError`, not crashes.4. **Edge cases** — Empty strings, boundary values, Unicode, large inputs.5. **Resources** — `read_resource(uri)` returns correct content and MIME type.6. **Prompts** — `get_prompt(name, args)` returns expected messages.7. **Integration** — Tool chains, lifespan setup/teardown.8. **Concurrent** — Multiple simultaneous calls don't interfere.
### 4.3 Interactive Testing
```bash# MCP Inspector (browser-based)fastmcp dev inspector mcp/<name>/server.py
# CLI testingfastmcp list mcp/<name>/server.pyfastmcp call mcp/<name>/server.py search_items '{"query": "test"}'```
### 4.4 Run Tests
```bashcd mcp/<name> && uv run pytest -v```
---
## Phase 5: Deploy & Configure
**Goal:** Make the server available to MCP clients.
**Load:** `references/deployment.md` (transports, client configs, Docker)
### 5.1 Select Transport
| 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` |
### 5.2 Generate Client Config
Add to client config (Claude Desktop, Claude Code, Cursor):
```json{ "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.
### 5.3 Validate
MCP server validation (skill check.py does NOT check MCP servers):
```bash# Import checkuv run python -c "from server import mcp; print(mcp.name)"
# List registered componentsfastmcp list mcp/<name>/server.py
# Interactive inspectionfastmcp dev inspector mcp/<name>/server.py```
### 5.4 Quality Checklist
Before declaring the server complete:
- [ ] Every tool has `Annotated` + `Field(description=...)` on all parameters- [ ] Every tool has a verbose docstring (WHAT, WHEN, WHEN NOT, RETURNS)- [ ] Every tool has `annotations` (at minimum `readOnlyHint`)- [ ] Every tool uses `ToolError` for expected failures- [ ] No `print()` or `stdout` writes in any tool- [ ] Resources have correct URI schemes and MIME types- [ ] Tests pass: `uv run pytest -v`- [ ] MCP Inspector shows all components correctly- [ ] `fastmcp.json` lists all required dependencies- [ ] `pyproject.toml` has correct metadata and dependencies- [ ] No deprecated constructor kwargs (removed in rc1)- [ ] Custom routes have manual auth if sensitive- [ ] `mask_error_details=True` set for production deployment
---
## Reference File Index
Load 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 |
---
## Critical Rules
These are non-negotiable. Violating any of these produces broken MCP servers.
1. **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.
2. **ToolError for expected failures.** Always `raise ToolError("message")` for user-facing errors. Standard exceptions are masked by `mask_error_details` in production.
3. **Verbose descriptions.** Every tool needs a 3-5 sentence docstring. Every parameter needs `Field(description=...)`. LLMs cannot use tools they don't understand.
4. **Annotations on every tool.** Set `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`. Clients use these for confirmation flows and retry logic.
5. **No `*args` or `**kwargs`.** MCP requires a fixed JSON schema for tool inputs. Dynamic signatures break schema generation.
6. **Async state access.** In v3, `ctx.get_state()` and `ctx.set_state()` are async — always `await` them.
7. **URI schemes required.** Every resource URI must have a scheme (`data://`, `config://`, `users://`). Bare paths fail.
8. **Test deterministically.** Use in-memory `Client(mcp)`, not manual prompting. Tests must be repeatable and automated.
9. **Module-level `mcp` variable.** The `FastMCP` instance must be importable at module level. `fastmcp run` imports `server:mcp` by default.
10. **Secrets in env vars only.** Never hardcode API keys. Never accept tokens as tool parameters. Load from environment, validate on startup.
---
## Quick Reference
Load `references/quick-reference.md` for the complete quick reference with minimal examples for server, tool, resource, prompt, lifespan, test, and run commands.
---
## Canonical Vocabulary
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" |
## Validation Contract
Before declaring changes to this skill complete, run:
```bashpython scripts/check.py```
Completion criteria:
1. `scripts/check.py` exits 0.2. MCP scaffold smoke-check: `python scripts/scaffold_mcp.py --dry-run example-server`.