Use when creating or modifying Model Context Protocol (MCP) servers with FastMCP framework - guides through tools, resources, prompts, authentication, Codex Desktop integration, and production deployment with Python and TypeScript examples
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Use when creating or modifying Model Context Protocol (MCP) servers with FastMCP framework - guides through tools, resources, prompts, authentication, Codex Desktop integration, and production deployment with Python and TypeScript examples
*CRITICAL* Add the following steps to your Todo list using TodoWrite:
Determine server purpose and required components (tools, resources, prompts)
Ask: What functionality does this MCP server provide? What external systems will it integrate with?
Create FastMCP server file with basic structure
Use Quick Start template below. Choose Python or TypeScript based on project requirements.
Implement tools for LLM-executable functions
Follow Tools section. Include type hints/annotations, validation, error handling.
Add resources if data access needed
Follow Resources section. Use URI templates for dynamic resources. Include security validation.
Add prompts if workflow guidance needed
Follow Prompts section. Use for multi-step workflows, best practices, templates.
Configure Codex Desktop integration
Follow Codex Desktop Integration section. Use fastmcp CLI or manual config. Handle environment variables.
Test server locally
Run server in STDIO mode. Test with FastMCP client or Codex Desktop locally.
Add authentication for production
Follow Authentication section. Use OAuth for enterprise, token verification for custom auth.
Deploy using appropriate transport
STDIO for local tools, HTTP/SSE for network access. Follow Deployment section.
Verify integration end-to-end
Test in Codex Desktop. Verify tools appear, resources load, prompts work.
When to Use This Skill
Use this skill when:
Creating new MCP servers with FastMCP
Adding tools, resources, or prompts to existing servers
Integrating MCP servers with Codex Desktop
Implementing authentication for production MCP servers
Deploying MCP servers via STDIO, HTTP, or SSE transports
Migrating from FastMCP v2 to v3
Creating custom domain-specific MCP integrations
Do NOT use this skill when:
Building MCP servers in languages other than Python or TypeScript (use official SDK)
You need maximum control over MCP protocol implementation (use official SDK)
Creating simple command-line tools without LLM integration (FastMCP is overkill)
Quick Start
Minimal Python Example
from fastmcp import FastMCP
mcp = FastMCP("Demo Server 🚀")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers together"""
return a + b
@mcp.resource("greeting://hello")
def get_greeting() -> str:
"""Get a friendly greeting"""
return "Hello from FastMCP!"
if __name__ == "__main__":
mcp.run()
Run it:
python server.py
Minimal TypeScript Example
import { FastMCP } from "@fastmcp/server";
const mcp = new FastMCP("Demo Server 🚀");
mcp.tool({
name: "add",
description: "Add two numbers together",
parameters: {
a: { type: "number", description: "First number" },
b: { type: "number", description: "Second number" }
},
execute: async ({ a, b }) => a + b
});
mcp.resource({
uri: "greeting://hello",
name: "Greeting",
description: "Get a friendly greeting",
read: async () => "Hello from FastMCP!"
});
mcp.run();
Run it:
npm install @fastmcp/server
node server.js
Codex Desktop Installation
Using fastmcp CLI (Recommended):
fastmcp install Codex-desktop server.py
Manual config (~/Library/Application Support/Codex/claude_desktop_config.json on macOS):
What are prompts? Message templates that help LLMs generate structured, purposeful responses - "best practices encoded into your server."
Basic Python Prompt
from fastmcp import FastMCP
from fastmcp.prompts import Message, PromptResult
mcp = FastMCP("Prompt Server")
@mcp.prompt()
def ask_about_topic(topic: str) -> str:
"""Generate a user message asking for explanation"""
return f"Can you please explain the concept of '{topic}' in simple terms?"
Advanced Python Prompt with Multi-Message Conversation
@mcp.prompt(
name="code_review_workflow",
description="Complete code review with security analysis",
tags={"security", "code-quality"}
)
def code_review(code: str, language: str = "python") -> PromptResult:
"""Security-focused code review workflow"""
return PromptResult(
messages=[
Message(
role="user",
content=f"Review this {language} code for security issues:\n```{language}\n{code}\n```"
),
Message(
role="assistant",
content="I'll analyze this systematically for security vulnerabilities."
),
Message(
role="user",
content="Focus especially on SQL injection, XSS, and authentication bypass."
)
],
description="Security-focused code review with systematic analysis",
meta={"priority": "high", "review_type": "security"}
)
TypeScript Prompt Implementation
mcp.prompt({
name: "code_review_workflow",
description: "Complete code review with security analysis",
parameters: {
code: { type: "string", description: "Code to review" },
language: { type: "string", default: "python" }
},
execute: async ({ code, language }) => {
return {
messages: [
{
role: "user",
content: `Review this ${language} code for security issues:\n\`\`\`${language}\n${code}\n\`\`\``
},
{
role: "assistant",
content: "I'll analyze this systematically for security vulnerabilities."
},
{
role: "user",
content: "Focus especially on SQL injection, XSS, and authentication bypass."
}
],
description: "Security-focused code review",
meta: { priority: "high", review_type: "security" }
};
}
});
Context - MCP Capabilities Access
What is Context? Dependency-injected object providing access to logging, progress tracking, resource/prompt management, LLM operations, and request metadata.
Context Capabilities
Category
Methods
Purpose
Logging
debug(), info(), warning(), error()
Send log messages to clients
Progress
report_progress(progress, total)
Update clients on long-running ops
Resources
list_resources(), read_resource(uri)
Access other resources
Prompts
list_prompts(), get_prompt(name, args)
Retrieve prompt templates
LLM
sample(prompt, temperature)
Request client LLM generation
User Input
elicit(question, response_type)
Request structured user input
State
set_state(), get_state(), delete_state()
Persist data across requests
Metadata
request_id, client_id, session_id
Request context information
Python Context Example
from fastmcp import FastMCP, Context
from fastmcp.dependencies import CurrentContext
@mcp.tool()
async def process_data(data_uri: str, ctx: Context = CurrentContext()) -> dict:
"""Process data with full context capabilities"""
await ctx.info(f"Processing {data_uri}")
# Read another resource
resources = await ctx.read_resource(data_uri)
content = resources[0].text
# Report progress
await ctx.report_progress(progress=25, total=100)
# Use LLM for analysis
summary = await ctx.sample(f"Summarize this data concisely: {content[:500]}")
await ctx.report_progress(progress=75, total=100)
# Store state for next request
await ctx.set_state("last_processed", data_uri)
await ctx.report_progress(progress=100, total=100)
return {
"result": summary.text,
"request_id": ctx.request_id,
"timestamp": ctx.timestamp
}
TypeScript Context Example
mcp.tool({
name: "process_data",
description: "Process data with full context capabilities",
parameters: {
data_uri: { type: "string", description: "URI of data to process" }
},
execute: async ({ data_uri }, ctx) => {
await ctx.info(`Processing ${data_uri}`);
// Read resource
const resources = await ctx.readResource(data_uri);
const content = resources[0].text;
// Report progress
await ctx.reportProgress(25, 100);
// Use LLM
const summary = await ctx.sample(`Summarize this data: ${content.slice(0, 500)}`);
await ctx.reportProgress(75, 100);
// Store state
await ctx.setState("last_processed", data_uri);
await ctx.reportProgress(100, 100);
return {
result: summary.text,
request_id: ctx.requestId,
timestamp: ctx.timestamp
};
}
});
Critical: Context is scoped to a single request. State persists across requests, but context object itself is recreated per request.
from fastmcp.server import create_proxy
from fastmcp.auth import BearerAuth
# Create proxy to remote MCP server
proxy = create_proxy(
"https://api.example.com/mcp/sse",
name="Remote Server Proxy",
auth=BearerAuth(token="your-api-token")
)
if __name__ == "__main__":
proxy.run()
Dynamic Component Management
# Add/remove components at runtime
mcp.add_tool(my_function)
mcp.remove_tool("tool_name")
# Control visibility
mcp.disable(tags={"admin"}) # Hide admin tools
mcp.enable(tags={"public"}, only=True) # Allowlist mode
# Clients automatically notified via notifications/tools/list_changed
Testing with Client
import asyncio
from fastmcp import FastMCP, Client
mcp = FastMCP("Test Server")
@mcp.tool()
def add(a: int, b: int) -> int:
return a + b
# Test your server
async def test_server():
async with Client(mcp) as client:
result = await client.call_tool("add", {"a": 5, "b": 3})
assert result == 8
print("✅ Test passed")
asyncio.run(test_server())
Common Pitfalls
1. Environment Isolation in Codex Desktop
Problem: Server can't find API keys or dependencies
# ❌ BAD: Relies on shell environment
api_key = os.getenv("API_KEY") # Will be None in Codex Desktop!
Solution: Explicitly pass environment variables in config
from fastmcp import FastMCP, Context, ToolError
@mcp.tool(
description="[What this tool does]",
annotations={"readOnlyHint": True} # or destructiveHint, etc.
)
async def tool_name(
param1: str, # Required parameter
param2: int = 10, # Optional with default
ctx: Context = None # Context injection
) -> dict:
"""[Detailed docstring for LLM]"""
try:
# 1. Validate inputs
if not param1:
raise ToolError("param1 is required")
# 2. Log operation
await ctx.info(f"Processing: {param1}")
# 3. Report progress for long operations
await ctx.report_progress(50, 100)
# 4. Execute logic
result = do_something(param1, param2)
# 5. Return structured data
return {"result": result, "param1": param1}
except Exception as e:
await ctx.error(f"Tool failed: {str(e)}")
raise ToolError(f"Operation failed: {str(e)}")
Basic Resource Template (Python)
from fastmcp import ResourceError
@mcp.resource("namespace://{param}/path")
async def resource_name(param: str, ctx: Context) -> str:
"""[Docstring describing what data this returns]"""
try:
# 1. Validate parameters
if not is_valid(param):
raise ResourceError(f"Invalid parameter: {param}")
# 2. Security checks
if not has_permission(param):
raise ResourceError("Access denied")
# 3. Fetch data
data = fetch_data(param)
# 4. Return as string (JSON for structured data)
return json.dumps(data)
except Exception as e:
raise ResourceError(f"Failed to fetch data: {str(e)}")
Production Server Template (Python)
#!/usr/bin/env python3
from fastmcp import FastMCP, Context, ToolError
from fastmcp.auth import GoogleOAuth
import logging
import os
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Create secure server
mcp = FastMCP(
name=os.getenv("SERVER_NAME", "Production Server"),
auth=GoogleOAuth(
client_id=os.getenv("GOOGLE_CLIENT_ID"),
client_secret=os.getenv("GOOGLE_CLIENT_SECRET")
),
mask_error_details=True,
rate_limit={"requests_per_minute": int(os.getenv("RATE_LIMIT", "100"))},
)
# Add your tools, resources, prompts here
if __name__ == "__main__":
port = int(os.getenv("PORT", "8443"))
transport = os.getenv("TRANSPORT", "http")
logger.info(f"Starting server on {transport}:{port}")
mcp.run(
transport=transport,
host="0.0.0.0",
port=port,
ssl_certfile=os.getenv("SSL_CERT"),
ssl_keyfile=os.getenv("SSL_KEY")
)