Skip to main content
Glama

MCP Screenshot Server

by batteryshark
AGENTS.md34.8 kB
## Python Coding Agent Rules These rules serve as a definitive guide when creating Python projects to favor minimal, human-verifiable outputs that are easy to reason about and simple to maintain. ### Environment - Always create a dedicated virtual environment with `uv`. - If a Python version is specified, use it: `uv venv --python 3.xx`. Otherwise, default to `uv venv --python 3.12`. - Create a new uv package via `uv init` - Install dependencies with `uv add ...`. - Call ALL python commands with `uv` first so we use the environemnt (e.g. `uv run python ...`) ### Package Management ### Dependencies and Research - Prefer the standard library. Add external dependencies only when strictly necessary or explicitly stated. - When using any non-built-in module, consult the Context7 MCP server for documentation and examples to accelerate development. ### Simplicity-First Delivery - Build only the minimum code required to achieve the goal. - Do not add extensive error handling, tests, or logging unless explicitly requested. - Keep the project structure shallow and obvious; avoid unnecessary frameworks or abstractions. ### Iteration and Verification - Work in small, focused steps that are easy for a human to run and verify. - After each step, be ready to incorporate corrections or design updates based on feedback or new insights. ### Temporary Experiments - You may create small test modules or scripts to validate ideas or isolate features. - Remove these once they have served their purpose to keep the repository clean. ### Code Style - Prefer clarity over cleverness: descriptive names, type hints, and small functions. - Avoid deep nesting and unnecessary indirection; keep control flow straightforward. ### Documentation and Commands - Provide only the essential commands to run or reproduce results. - Keep documentation brief and practical; include just enough context to onboard a mid-level developer quickly. ## Python MCP Development Rules These rules serve as a definitive guide when creating MCP (Model Context Protocol) servers and clients with Python, emphasizing simplicity, reliability, and agent-friendly patterns. **Follow the MVP approach**: start simple, add complexity only when your specific use case requires it. ### Environment and Dependencies - Always use Python 3.12 or newer for MCP projects. - For MCP servers requiring client functionality, use fastMCP 2.10 or newer (github.com/jlowin/fastmcp). - When data validation is required, use the most up-to-date version of `pydantic` 2.xx. - Always consult Context7 MCP for searchable, up-to-date documentation and code examples before deciding on an approach. ### MCP Configuration Format MCP servers are configured through JSON configuration files (typically `mcp.json`) that define how MCP Host applications connect to and launch servers. Understanding this format is essential for providing clear setup instructions with your Python MCP servers. #### Configuration File Structure The configuration uses a root `mcpServers` object where each key is a unique server identifier and each value contains the connection configuration: ```json { "mcpServers": { "my-server-name": { // Server configuration object } } } ``` **Server Identifier Guidelines:** - Use camelCase naming (e.g., "weatherTools", "databaseHelper") - Avoid whitespace and special characters - Make names descriptive of the server's purpose - Names appear in MCP Host UIs for user reference #### Local Server Configuration (stdio Transport) Local servers run as subprocesses and communicate via standard input/output. This is the most common configuration for Python MCP servers. **Required Fields:** - `type`: Must be `"stdio"` - `command`: Executable or command to launch the server **Optional Fields:** - `args`: Array of command-line arguments - `env`: Environment variables as key-value pairs - `cwd`: Working directory for command execution - `timeout`: Request timeout in seconds ```json { "mcpServers": { "pythonCalculator": { "type": "stdio", "command": "uv", "args": ["run", "--directory", "/path/to/project", "python", "-m", "calculator_server"] }, "localFileServer": { "type": "stdio", "command": "python", "args": ["/path/to/file_server.py"], "cwd": "/path/to/project", "env": { "LOG_LEVEL": "INFO", "DATA_PATH": "/path/to/data" } }, "dockerizedServer": { "type": "stdio", "command": "docker", "args": [ "run", "--rm", "-i", "my-mcp-server:latest" ] } } } ``` #### Remote Server Configuration (http/sse Transport) Remote servers run independently and are accessed via HTTP. This is ideal for shared services, cloud deployments, or servers requiring network access. **Required Fields:** - `type`: Either `"http"` or `"sse"` - `url`: Full URL to the server endpoint **Optional Fields:** - `headers`: HTTP headers (typically for authentication) - `timeout`: Network request timeout ```json { "mcpServers": { "cloudWeatherAPI": { "type": "http", "url": "https://my-mcp-server.herokuapp.com/mcp/", "headers": { "Authorization": "Bearer your-api-key-here", "X-Client-Version": "1.0" } }, "internalService": { "type": "http", "url": "http://localhost:8000/mcp/", "timeout": 30 } } } ``` #### Security Considerations **For Local Servers:** - The `command` and `args` fields enable arbitrary code execution - Only use trusted server implementations - Validate all paths and avoid dynamic argument construction - Be cautious with `env` variables containing secrets **For Remote Servers:** - Never hardcode API keys or secrets in the configuration file - Use environment variables or secure credential management - Ensure HTTPS for production deployments - Validate server certificates and origins #### Configuration Documentation Guidelines **Always analyze your server code first** to determine what configuration options it actually uses. Only include examples for features your server implements. **Steps to create server-specific configuration examples:** 1. **Examine environment variables** your server reads (e.g., `os.getenv()` calls) 2. **Check HTTP configuration** if your server supports HTTP transport (`HOST`, `PORT` variables) 3. **Identify authentication** if your server validates headers or credentials 4. **Review file paths** and working directory requirements **Example Analysis Process:** ```python # Server code analysis: user_managed = os.getenv("USER_MANAGED", "false") # → Include in env example mcp_host = os.getenv("HOST", "127.0.0.1") # → Server supports HTTP mcp_port = os.getenv("PORT", None) # → HTTP port configuration # No auth header checks → Don't include authorization # Results in configuration examples: # ✓ Basic stdio config # ✓ stdio config with USER_MANAGED env var # ✓ HTTP config (since server supports it) # ✗ Authorization headers (server doesn't use them) ``` **Configuration Template Structure:** ```python """ MCP Server Configuration Examples: === Local/stdio Configuration === { "mcpServers": { "serverName": { "type": "stdio", "command": "uv", "args": ["run", "--directory", "/path/to/project", "python", "server_file.py"] } } } [Include environment section only if server uses environment variables] === Local/stdio Configuration with Environment === { "mcpServers": { "serverName": { "type": "stdio", "command": "uv", "args": ["run", "--directory", "/path/to/project", "python", "server_file.py"], "env": { "ACTUAL_ENV_VAR": "value" } } } } [Include HTTP section only if server supports HTTP transport] === Remote/HTTP Configuration === { "mcpServers": { "serverName": { "type": "http", "url": "https://your-domain.com/mcp/" } } } [Include headers section only if server validates authentication] === Remote/HTTP Configuration with Authentication === { "mcpServers": { "serverName": { "type": "http", "url": "https://your-domain.com/mcp/", "headers": { "Authorization": "Bearer YOUR_API_KEY" } } } } Place this configuration in: - VS Code: .vscode/mcp.json (project) or user settings - Claude Desktop: claude_desktop_config.json - Cursor: .cursor/mcp.json (project) or ~/.cursor/mcp.json (user) - LM Studio: ~/.lmstudio/mcp.json """ ``` #### Common Configuration Patterns **Python Package Installation:** ```json { "mcpServers": { "packagedServer": { "type": "stdio", "command": "uvx", "args": ["my-mcp-package"] } } } ``` **Development with uv:** ```json { "mcpServers": { "devServer": { "type": "stdio", "command": "uv", "args": ["run", "--directory", "/path/to/project", "python", "server.py"] } } } ``` **Virtual Environment:** ```json { "mcpServers": { "venvServer": { "type": "stdio", "command": "/path/to/venv/bin/python", "args": ["/path/to/server.py"] } } } ``` ### Server Instructions - **Always include clear, concise instructions** when creating FastMCP servers to help agents understand purpose and usage. - Keep instructions brief - agents already understand tools through type annotations and docstrings. - Focus on **what the server does** and **when to use it**, not how individual tools work. - Use simple, direct language that explains the server's domain and typical workflows. ```python # Clear purpose and usage guidance mcp = FastMCP( name="FileAnalysisServer", instructions=""" Analyzes files and directories for insights. Use for code analysis, file statistics, and content extraction. Tools work with local file paths and common file formats. """ ) # Domain-specific context mcp = FastMCP( name="DatabaseServer", instructions=""" Manages SQLite database operations. Use for data queries, schema inspection, and basic CRUD operations. Automatically handles connection pooling and transaction safety. """ ) # Workflow-oriented guidance mcp = FastMCP( name="DocumentProcessor", instructions=""" Processes documents through analysis pipeline. Start with upload_document, then use analyze_content or extract_metadata. Supports PDF, DOCX, and plain text formats. """ ) ``` #### Instructions Best Practices - **Start with the server's core purpose** in one clear sentence. - **Mention primary use cases** or scenarios where this server is helpful. - **Note any important limitations** or requirements (file formats, authentication, etc.). - **Indicate common workflows** when tools have dependencies or suggested order. - **Avoid repeating tool documentation** - let type hints and docstrings handle specifics. ### Server Design Patterns #### Entrypoint Structure - Design FastMCP servers to support both stdio and streamable-http transport modes. - Keep the server module clean by isolating transport logic in the entrypoint. - Use this standard pattern for maximum flexibility: ```python from fastmcp import FastMCP import os # Create server with clear instructions mcp = FastMCP( name="CalculatorServer", instructions=""" Performs mathematical calculations and analysis. Use for arithmetic, statistical operations, and number formatting. All operations handle standard Python numeric types. """ ) @mcp.tool def add(a: float, b: float) -> float: """Add two numbers together.""" return a + b @mcp.tool def calculate_average(numbers: list[float]) -> dict: """Calculate average and statistics for a list of numbers.""" if not numbers: raise ValueError("Cannot calculate average of empty list") avg = sum(numbers) / len(numbers) return {"average": avg, "count": len(numbers), "sum": sum(numbers)} if __name__ == "__main__": mcp_host = os.getenv("HOST", "127.0.0.1") mcp_port = os.getenv("PORT", None) if mcp_port: mcp.run(port=int(mcp_port), host=mcp_host, transport="streamable-http") else: mcp.run() ``` ### Client Development - When creating MCP clients, prioritize using the MCPConfig structure from fastMCP. - This approach enables standard MCP configuration JSON format compatibility. - Maintain consistency with established MCP client patterns. ### Agent Integration - For MCP servers or clients that interact with agents, use the latest version of `pydantic-ai`. - Design agent interactions to be stateless and predictable. - Keep agent-facing interfaces simple and well-documented. ### Code Organization - Structure MCP projects with clear separation between transport, business logic, and data models. - Avoid deep nesting in tool definitions and resource handlers. - Use descriptive names for MCP tools and resources that clearly indicate their purpose. ### Session Management and Caching #### Session Identification (Keep It Simple) - FastMCP automatically provides session tracking through the `Context` object. - Access session identifiers only when you need to cache data or maintain state between tool calls. - **Start without session management** unless your specific use case requires it. ```python from fastmcp import FastMCP, Context mcp = FastMCP(name="SessionAwareServer") @mcp.tool def get_user_preferences(user_id: str, ctx: Context) -> dict: """Get user preferences with session awareness.""" # Access session identifiers when needed session_id = ctx.session_id # Persistent across tool calls in same session client_id = ctx.client_id # Identifies the client (may be None) request_id = ctx.request_id # Unique per tool call # Use session_id as cache key for persistent data cache_key = f"user_prefs:{session_id}:{user_id}" return {"preferences": "cached_data", "session": session_id} ``` #### Request-Level State Management - Use `ctx.set_state()` and `ctx.get_state()` for sharing data within a single request. - State is automatically isolated between requests - no cleanup needed. - This is perfect for middleware patterns and request-scoped caching. ```python @mcp.tool def initialize_user_session(user_id: str, ctx: Context) -> dict: """Initialize user data for subsequent tool calls.""" # Store data for other tools in this request ctx.set_state("current_user", user_id) ctx.set_state("user_permissions", ["read", "write"]) return {"user": user_id, "initialized": True} @mcp.tool def perform_user_action(action: str, ctx: Context) -> dict: """Perform action using previously stored user data.""" # Access data set by previous tool calls user_id = ctx.get_state("current_user") permissions = ctx.get_state("user_permissions") if not user_id: raise ValueError("User session not initialized") return {"action": action, "user": user_id, "allowed": action in permissions} ``` #### Session-Based Caching Patterns - Use `session_id` for data that should persist across multiple tool calls. - Combine with external storage (Redis, SQLite) only when needed. - Keep cache keys simple and predictable. ```python import sqlite3 from pathlib import Path # Simple session cache using SQLite (add only when needed) def get_session_cache(session_id: str, key: str) -> str | None: """Simple session-based cache getter.""" db_path = Path("session_cache.db") with sqlite3.connect(db_path) as conn: cursor = conn.execute( "SELECT value FROM cache WHERE session_id = ? AND key = ?", (session_id, key) ) result = cursor.fetchone() return result[0] if result else None def set_session_cache(session_id: str, key: str, value: str) -> None: """Simple session-based cache setter.""" db_path = Path("session_cache.db") with sqlite3.connect(db_path) as conn: conn.execute(""" CREATE TABLE IF NOT EXISTS cache ( session_id TEXT, key TEXT, value TEXT, PRIMARY KEY (session_id, key) ) """) conn.execute( "INSERT OR REPLACE INTO cache VALUES (?, ?, ?)", (session_id, key, value) ) @mcp.tool def remember_preference(setting: str, value: str, ctx: Context) -> dict: """Remember a user preference for this session.""" session_id = ctx.session_id set_session_cache(session_id, f"pref:{setting}", value) return {"saved": setting, "value": value, "session": session_id} @mcp.tool def get_preference(setting: str, ctx: Context) -> dict: """Retrieve a previously saved preference.""" session_id = ctx.session_id value = get_session_cache(session_id, f"pref:{setting}") return {"setting": setting, "value": value, "found": value is not None} ``` #### Command History and Reference Tracking - Use request_id to track individual command executions. - Store command results when you need to reference previous executions. - Keep history simple - store only what you actually need to reference. ```python @mcp.tool def execute_command(command: str, ctx: Context) -> dict: """Execute a command and track it for reference.""" request_id = ctx.request_id session_id = ctx.session_id # Execute the command (simplified) result = f"Output of: {command}" # Store for potential reference by other tools set_session_cache(session_id, f"cmd:{request_id}", command) set_session_cache(session_id, f"result:{request_id}", result) ctx.info(f"Command executed with ID: {request_id}") return { "command": command, "result": result, "request_id": request_id, "reference": f"Use request ID {request_id} to reference this command" } @mcp.tool def reference_previous_command(request_id: str, ctx: Context) -> dict: """Reference a previously executed command by its request ID.""" session_id = ctx.session_id command = get_session_cache(session_id, f"cmd:{request_id}") result = get_session_cache(session_id, f"result:{request_id}") if not command: return {"error": f"No command found for request ID: {request_id}"} return { "original_command": command, "original_result": result, "request_id": request_id } ``` #### Session Management Best Practices - **Start simple**: Use only request-level state (`ctx.set_state/get_state`) for most cases. - **Add session persistence** only when you need data to survive across multiple tool calls. - **Use external storage** (Redis/SQLite) only when you need persistence beyond the server lifetime. - **Keep identifiers accessible**: Log session_id and request_id in tool outputs when users need to reference them. - **Clean up when possible**: Implement session cleanup for long-running servers, but don't over-engineer it. ### Tool Development #### Tool Registration Approaches - **Start simple with `@mcp.tool` decorator** for straightforward tools with minimal logic. - For complex tools, separate business logic from MCP registration and add tools programmatically. - Use `mcp.tool(function)` method for registering existing functions without decorators. - Choose the approach that keeps your code clear and maintainable. ```python from fastmcp import FastMCP mcp = FastMCP(name="CalculatorServer") # Simple decorator approach for basic tools @mcp.tool def add(a: int, b: int) -> int: """Adds two integer numbers together.""" return a + b # Method 1: Register existing function without decorator def multiply(a: int, b: int) -> int: """Multiplies two numbers.""" return a * b # Register the function as a tool mcp.tool(multiply) # Method 2: Register with custom name def divide_numbers(a: float, b: float) -> float: """Divides two numbers.""" if b == 0: raise ValueError("Cannot divide by zero") return a / b # Register with custom name and options mcp.tool(divide_numbers, name="divide") # Method 3: Separate complex business logic (no wrapper needed) def process_complex_data(input_data: dict) -> dict: """Process complex data with business logic.""" # Heavy processing, multiple steps, etc. # This function is already properly annotated for MCP return {"processed": input_data, "status": "complete"} # Register directly - no wrapper needed mcp.tool(process_complex_data) ``` #### Async vs Sync Functions - **Start with sync functions** for simple tools unless you specifically need async I/O. - Use async functions when you have actual I/O operations (database, API calls, file access). - Don't add async complexity unless it's required for your use case. ```python from fastmcp import FastMCP mcp = FastMCP() # Start simple: Sync for basic operations @mcp.tool def calculate_factorial(n: int) -> int: """Calculate factorial of a number.""" return math.factorial(n) # Use async when you actually need I/O @mcp.tool async def fetch_user_data(user_id: str) -> dict: """Fetch user data from database.""" return await database.get_user(user_id) ``` #### Type Annotations (Essential) - **Always use basic type annotations** for parameters and return values. - Start with simple types (`str`, `int`, `dict`) and add complexity only when needed. - Type annotations enable automatic schema generation and validation. ```python # Start simple @mcp.tool def process_text(text: str, max_length: int = 100) -> dict: """Process text data.""" return {"result": text[:max_length], "length": len(text)} # Add complexity only when needed from typing import Literal @mcp.tool def process_with_format( text: str, format: Literal["json", "xml"] = "json" ) -> dict: """Process text with specific format.""" # Implementation... ``` #### Parameter Validation (When Needed) - Start without validation constraints unless your use case specifically requires them. - Add `Field` validation only when you need to enforce specific business rules. - Keep validation simple and focused on actual requirements. ```python # Simple approach (preferred) @mcp.tool def analyze_metrics(count: int, user_id: str) -> dict: """Analyze metrics.""" if count < 0 or count > 100: raise ValueError("Count must be between 0 and 100") return {"count": count, "user": user_id} # Add Field validation only when business rules require it from typing import Annotated from pydantic import Field @mcp.tool def validate_user_id( user_id: Annotated[str, Field(pattern=r"^[A-Z]{2}\d{4}$")] ) -> dict: """Validate user ID format when strict format required.""" return {"valid": True, "user_id": user_id} ``` #### Return Values and Structured Output - **Always annotate return types** to enable automatic output schema generation. - Leverage FastMCP 2.10+ structured output for machine-readable results. - Object-like returns (dict, Pydantic models) automatically become structured content. - Primitive returns get wrapped under a "result" key in structured output. ```python from dataclasses import dataclass from fastmcp import FastMCP mcp = FastMCP() @dataclass class UserProfile: name: str age: int email: str @mcp.tool def get_user_profile(user_id: str) -> UserProfile: """Get a user's profile information.""" return UserProfile(name="Alice", age=30, email="alice@example.com") @mcp.tool def calculate_sum(a: int, b: int) -> int: """Add two numbers (result wrapped automatically).""" return a + b # Returns {"result": 8} in structured output ``` #### Tool Annotations and Metadata - Use annotations to provide semantic hints about tool behavior. - Set `readOnlyHint=True` for tools that don't modify state. - Mark potentially destructive operations with appropriate annotations. - Add descriptive titles for better user interfaces. ```python @mcp.tool( name="calculate_sum", description="Add two numbers together safely", annotations={ "title": "Calculate Sum", "readOnlyHint": True, "idempotentHint": True, "openWorldHint": False }, tags={"math", "calculation"} ) def add_numbers(a: float, b: float) -> float: """Add two numbers together.""" return a + b ``` #### Context Access - Use `Context` parameter for accessing MCP features (logging, progress, resources). - Implement progress reporting for long-running operations. - Leverage context for resource access and LLM sampling capabilities. ```python from fastmcp import FastMCP, Context mcp = FastMCP(name="ContextDemo") @mcp.tool async def process_large_file(file_uri: str, ctx: Context) -> dict: """Process a large file with progress reporting.""" await ctx.info(f"Starting to process {file_uri}") # Read resource through context resource = await ctx.read_resource(file_uri) data = resource[0].content if resource else "" # Report progress await ctx.report_progress(progress=50, total=100) # Process data... result = {"size": len(data), "processed": True} await ctx.report_progress(progress=100, total=100) return result ``` #### Advanced Tool Features - Use `exclude_args` to hide runtime-injected parameters from tool schema. - Implement dynamic tool enabling/disabling for feature flags and maintenance. - Use `ToolResult` for complete control over content and structured output. - Add custom metadata with `meta` parameter for versioning and application-specific data. ```python from fastmcp.tools.tool import ToolResult from fastmcp import FastMCP mcp = FastMCP() # Use these features only when you actually need them @mcp.tool(exclude_args=["user_id"]) def get_user_profile(name: str, user_id: str = None) -> dict: """Get user profile (user_id injected by server).""" return {"name": name, "user_id": user_id} # Dynamic tool control for feature flags @mcp.tool(enabled=False) def maintenance_feature() -> str: """Feature under maintenance.""" return "Service unavailable" maintenance_feature.enable() # Enable when ready ``` #### Separation of Concerns and Startup Registration - **Design your functions to be MCP-ready from the start** with proper type annotations and docstrings. - Register tools programmatically at startup when you have complex initialization. - No wrapper functions needed if your business logic is already MCP-compatible. ```python # business_logic.py - MCP-ready business logic def process_document(content: str, format: str = "default") -> dict: """Process document content with specified format.""" # Heavy processing, multiple steps, validation, etc. processed = content.upper() # Simplified example return { "original_length": len(content), "processed_content": processed, "format_used": format } def analyze_sentiment(text: str) -> dict: """Analyze text sentiment using ML/NLP processing.""" # Complex ML/NLP processing return {"sentiment": "positive", "confidence": 0.85} # mcp_server.py - Clean MCP server registration from fastmcp import FastMCP from .business_logic import process_document, analyze_sentiment mcp = FastMCP(name="DocumentServer") def register_tools(): """Register all tools at startup time.""" # Register functions directly - no wrappers needed mcp.tool(process_document) mcp.tool(analyze_sentiment) if __name__ == "__main__": register_tools() # Set up tools at startup mcp.run() ``` ### Tool Response Format Policy #### String vs Structured Response Guidelines - **For text-only responses**: Return raw strings directly to avoid JSON wrapping overhead and string escaping - **For structured data**: Use dictionaries, dataclasses, or Pydantic models to enable machine-readable output - **Avoid primitive wrapping inefficiency**: Prefer returning structured objects over primitive types when the response contains meaningful data ```python from datetime import datetime from fastmcp import FastMCP from fastmcp.tools.tool import ToolResult from mcp.types import TextContent mcp = FastMCP(name="ResponseServer") # PREFERRED: Raw string for text-only responses @mcp.tool def generate_report(data: str) -> ToolResult: """Generate a human-readable report.""" report_text = f"Analysis Report:\n{data}\nGenerated on {datetime.now()}" # Return raw text without JSON wrapping - more efficient return ToolResult(content=[TextContent(type="text", text=report_text)]) # PREFERRED: Structured response for data that should be machine-readable @mcp.tool def analyze_metrics(data: str) -> dict: """Analyze data and return structured metrics.""" return { "word_count": len(data.split()), "char_count": len(data), "timestamp": datetime.now().isoformat(), "status": "completed" } # AVOID: Primitive returns that get auto-wrapped in {"result": value} @mcp.tool def bad_example(text: str) -> str: """This creates inefficient JSON wrapping.""" return f"Processed: {text}" # Becomes {"result": "Processed: text"} # BETTER: Use ToolResult for text-only output @mcp.tool def better_example(text: str) -> ToolResult: """This returns raw text efficiently.""" return ToolResult(content=[TextContent(type="text", text=f"Processed: {text}")]) ``` #### Response Format Decision Matrix | Response Type | Use Case | Return Type | Rationale | |---------------|----------|-------------|-----------| | **Text Report** | Human-readable content, logs, formatted output | `ToolResult` with `TextContent` | Avoids JSON escaping, more efficient for text | | **Status Message** | Simple success/error messages | `ToolResult` with `TextContent` | No need for structured parsing | | **Data Analysis** | Metrics, counts, structured results | `dict` or dataclass | Enables machine processing | | **Configuration** | Settings, parameters, key-value data | `dict` or Pydantic model | Clear structure, validation | | **Mixed Content** | Text + structured data | `ToolResult` with both content and structured_content | Best of both worlds | #### Implementation Guidelines 1. **Default to raw text**: If the primary use case is human consumption, use `ToolResult` with `TextContent` 2. **Structure when beneficial**: Use structured responses when clients need to programmatically process the data 3. **Avoid primitive auto-wrapping**: Never return bare strings, integers, or booleans unless you specifically want the `{"result": value}` wrapper 4. **Consider the consumer**: Think about whether agents/clients will parse the response or just display it This policy minimizes unnecessary JSON overhead while preserving structured output capabilities where they add value. ### Error Handling - **Start simple**: Let Python exceptions bubble up naturally for most cases. - Use `ToolError` only when you need specific error messages for agents. - Add error masking (`mask_error_details=True`) only in production when needed. ```python from fastmcp import FastMCP mcp = FastMCP(name="SimpleServer") @mcp.tool def divide(a: float, b: float) -> float: """Divide a by b.""" if b == 0: raise ValueError("Cannot divide by zero") return a / b # Use ToolError only when you need agent-specific messaging from fastmcp.exceptions import ToolError @mcp.tool def process_file(filename: str) -> dict: """Process a file.""" if not filename.endswith('.txt'): raise ToolError("Only .txt files are supported") # Process file... return {"status": "processed"} ``` ### Testing MCP Servers #### In-Memory Testing (Preferred) - **Always prefer in-memory testing** over subprocess/network-based testing for unit tests. - Pass your FastMCP server instance directly to the FastMCP Client for zero-overhead connections. - In-memory testing runs entirely within the same Python process, eliminating network complexity. - This approach enables debugger breakpoints in both test code and server handlers. ```python from fastmcp import FastMCP, Client import pytest @pytest.fixture def weather_server(): server = FastMCP("WeatherServer") @server.tool def get_temperature(city: str) -> dict: temps = {"NYC": 72, "LA": 85, "Chicago": 68} return {"city": city, "temp": temps.get(city, 70)} return server @pytest.mark.asyncio async def test_temperature_tool(weather_server): async with Client(weather_server) as client: result = await client.call_tool("get_temperature", {"city": "LA"}) assert result.data == {"city": "LA", "temp": 85} ``` #### Transport Inference for Testing - The FastMCP Client automatically infers transport based on input: - `FastMCP` instance → In-memory transport (ideal for testing) - File path ending in `.py` → Python Stdio transport - URL starting with `http://` or `https://` → HTTP transport #### Mocking External Dependencies - Use `unittest.mock.AsyncMock` for external services (databases, APIs) in tests. - This ensures tests are fast, deterministic, and don't require external infrastructure. ```python from unittest.mock import AsyncMock async def test_database_tool(): server = FastMCP("DataServer") # Mock the database mock_db = AsyncMock() mock_db.fetch_users.return_value = [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"} ] @server.tool async def list_users() -> list: return await mock_db.fetch_users() async with Client(server) as client: result = await client.call_tool("list_users", {}) assert len(result.data) == 2 assert result.data[0]["name"] == "Alice" mock_db.fetch_users.assert_called_once() ``` #### Testing Deployed Servers (When Necessary) - Use HTTP transport testing only for deployment validation, authentication testing, or network behavior verification. - Connect to running servers via their URL for integration tests. ```python async def test_deployed_server(): # Connect to a running server async with Client("http://localhost:8000/mcp/") as client: await client.ping() # Test with real network transport tools = await client.list_tools() assert len(tools) > 0 result = await client.call_tool("greet", {"name": "World"}) assert "Hello" in result.data ``` #### Key Testing Benefits - **Performance**: In-memory tests execute instantly without network overhead. - **Simplicity**: No server startup scripts, port management, or cleanup between tests. - **Debugging**: Full debugger support across client and server code. - **Reliability**: Eliminates network-related test flakiness and infrastructure dependencies.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/batteryshark/mcp-screenshot'

If you have feedback or need assistance with the MCP directory API, please join our Discord server