DEV Community

Elsayed Kamal
Elsayed Kamal

Posted on

Building Model Context Protocol (MCP) Servers with stdio: A Complete Guide

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
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

Install dependencies using uv (or pip):

uv sync
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

Running Your Server

uv run python calc_two_numbers_mcp_server.py
Enter fullscreen mode Exit fullscreen mode

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"))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)