Building Model Context Protocol (MCP) Servers with stdio: A Complete Guide
The Model Context Protocol (MCP) is revolutionizing how AI applications interact with external tools and data sources. In this comprehensive guide, we'll walk through building MCP servers using the stdio (standard input/output) transport method, which is perfect for local development and testing.
What is MCP?
The Model Context Protocol is an open standard that enables AI assistants to securely connect to external data sources and tools. Think of it as a universal adapter that allows AI models to interact with databases, APIs, file systems, and custom tools in a standardized way.
Why Choose stdio Transport?
The stdio transport method offers several advantages:
- Simplicity: Easy to implement and debug
- Local Development: Perfect for testing and development
- Security: Runs in the same process space
- Performance: Low latency for local operations
Setting Up Your Environment
First, let's set up our development environment. We'll use Python with the FastMCP library for rapid development.
1. Project Structure
Create a new directory for your MCP server:
mkdir stdio_mcp_server
cd stdio_mcp_server
2. Dependencies
Create a pyproject.toml
file:
[project]
name = "stdio-mcp-server"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.9.2",
]
Install dependencies using uv (or pip):
uv sync
Building Your First MCP Server
Let's start with a simple calculator server that demonstrates the core concepts.
Simple Calculator Server
Create calc_two_numbers_mcp_server.py
:
from mcp.server.fastmcp import FastMCP
import asyncio
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 1. Create a FastMCP server instance
mcp = FastMCP(
name="sum_two_numbers_mcp_server",
)
# 2. Define a tool that sums two numbers
@mcp.tool(
name="sum_two_numbers",
description="A tool that sums two numbers.",
)
async def sum_two_numbers_tool(a: int, b: int) -> int:
"""A tool that sums two numbers."""
return f"The sum of {a} and {b} is {a + b} (Calculated by MCP server)."
if __name__ == "__main__":
# 3. Run the FastMCP server
asyncio.run(mcp.run())
Running Your Server
uv run python calc_two_numbers_mcp_server.py
That's it! You now have a working MCP server that can perform calculations.
Building a More Complex Server
Let's create a more sophisticated server that interacts with external APIs. We'll build a dev.to article search server.
Dev.to Search Server
Create dev_to_search_tool_mcp_server.py
:
from mcp.server.fastmcp import FastMCP
from typing import List, Dict, Any, Optional
import asyncio
import logging
import httpx
import click
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def serve(auth_token: str):
# Create FastMCP server instance
mcp = FastMCP(name="DevTo_Search_MCP_Server")
# Base URL for dev.to API
DEV_TO_BASE_URL = "https://dev.to/api"
@mcp.tool(
name="search_dev_to_articles",
description="""
Search for articles on dev.to by topic/keyword.
Args:
query (str): The search query/topic to search for
per_page (int, optional): Number of articles to return (default: 10)
page (int, optional): Page number for pagination (default: 1)
tag (str, optional): Filter by specific tag
username (str, optional): Filter by specific username
Returns:
List of article objects with title, description, URL, tags, etc.
""",
)
async def search_dev_to_articles(
query: str,
per_page: Optional[int] = 10,
page: Optional[int] = 1,
tag: Optional[str] = None,
username: Optional[str] = None,
) -> List[Dict[str, Any]]:
"""Search for articles on dev.to using the public API."""
try:
# Build query parameters
params = {
"per_page": min(per_page, 1000), # API limit
"page": page,
}
if query:
formatted_query = query.replace(" ", "+")
params["q"] = formatted_query
# Add optional filters
if tag:
params["tag"] = tag
if username:
params["username"] = username
# Make API request
async with httpx.AsyncClient() as client:
response = await client.get(
f"{DEV_TO_BASE_URL}/articles", params=params
)
response.raise_for_status()
return response.json()
except httpx.HTTPError as e:
logger.error(f"HTTP error occurred: {e}")
return {"error": f"Failed to fetch articles: {str(e)}"}
except Exception as e:
logger.error(f"Unexpected error occurred: {e}")
return {"error": f"An unexpected error occurred: {str(e)}"}
# Run the server
await mcp.run()
if __name__ == "__main__":
asyncio.run(serve("your-api-token"))
Key MCP Concepts
1. Tools
Tools are the core functionality your MCP server provides. They're defined using the @mcp.tool
decorator:
@mcp.tool(
name="tool_name",
description="Clear description of what the tool does",
)
async def tool_function(param1: type, param2: type) -> return_type:
"""Implementation of your tool"""
return result
2. Type Hints
Always use proper type hints for parameters and return values. This helps the AI understand how to use your tools correctly.
3. Error Handling
Implement robust error handling to provide meaningful feedback when things go wrong.
4. Logging
Use logging to help debug issues and monitor your server's performance.
Best Practices
1. Clear Descriptions
Write detailed descriptions for your tools. The AI uses these to understand when and how to use each tool.
2. Parameter Validation
Validate input parameters to prevent errors and provide helpful feedback.
3. Async/Await
Use async/await for I/O operations to maintain server responsiveness.
4. Resource Management
Properly manage resources like HTTP connections using context managers.
5. Configuration
Use environment variables or configuration files for sensitive data like API keys.
Testing Your MCP Server
1. Local Testing
Test your server locally using the MCP CLI tools or by integrating with compatible AI applications.
2. Integration Testing
Test with actual AI assistants that support MCP to ensure compatibility.
3. Error Scenarios
Test error conditions to ensure your server handles failures gracefully.
Deployment Considerations
1. Process Management
For production use, consider using process managers like systemd or supervisord.
2. Monitoring
Implement health checks and monitoring to ensure your server remains available.
3. Security
- Validate all inputs
- Use environment variables for secrets
- Implement rate limiting if needed
- Consider authentication for sensitive operations
Conclusion
MCP servers with stdio transport provide a powerful yet simple way to extend AI capabilities. Start with simple tools and gradually build more complex functionality as needed. The key is to focus on clear interfaces, robust error handling, and comprehensive documentation.
The examples we've built demonstrate both basic arithmetic operations and complex API interactions. Use these patterns as starting points for your own MCP servers, whether you're integrating with databases, file systems, or external services.
Happy building! 🚀
Resources
Have you built an MCP server? Share your experience in the comments below!
Top comments (0)