DEV Community

Cover image for Understanding MCP: A Developer's Conversation πŸ’¬
Data Tech Bridge
Data Tech Bridge

Posted on

Understanding MCP: A Developer's Conversation πŸ’¬

Model Context Protocol explained through practical Q&A

Table of Contents

  1. The Problem MCP Solves
  2. Building Your First MCP Server
  3. The Client Side
  4. The Two-Terminal Misconception
  5. The Three Components
  6. Client vs Host - The Key Difference
  7. Why Three Components?
  8. Testing Your MCP Server
  9. Real-World Example: GitHub MCP Server
  10. MCP Transport: stdio vs SSE
  11. Common Patterns
  12. Debugging Tips
  13. Architecture Deep Dive
  14. Quick Start Summary
  15. Explore More MCP Servers

1. The Problem MCP Solves 🎯

Rahul (Developer): Man, I keep hearing about MCP everywhere. What problem does it actually solve?

Amit (Expert): Good question! Let me give you a real-world scenario. Before MCP, if you wanted Claude Desktop to access your GitHub repos, AWS resources, and company database, you'd need:

  • Claude team to build custom GitHub integration
  • Claude team to build custom AWS integration
  • Claude team to build custom database integration

Now multiply this by every AI app (VS Code, Cursor, ChatGPT, etc.). That's N apps Γ— M tools = NΓ—M custom integrations!

Rahul: That sounds like a nightmare.

Amit: Exactly! MCP solves this with a standard protocol. Build ONE GitHub MCP server, and it works everywhere - Claude, VS Code, Cursor, ChatGPT. Now it's just N apps + M tools = N+M integrations. Much cleaner!

Rahul: So it's like REST APIs for AI tools?

Amit: Similar concept, but different implementation. Let me show you the simplest MCP example - a math server with add and multiply tools.


2. Building Your First MCP Server πŸ› οΈ

Rahul: Alright, show me the code!

Amit: Sure! Here's a complete MCP server in 30 lines:

# mcp_server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import asyncio

server = Server("math-server")

@server.list_tools()
async def list_tools():
    return [
        Tool(name="add", description="Add two numbers", inputSchema={
            "type": "object",
            "properties": {
                "a": {"type": "number"},
                "b": {"type": "number"}
            },
            "required": ["a", "b"]
        }),
        Tool(name="multiply", description="Multiply two numbers", inputSchema={
            "type": "object",
            "properties": {
                "a": {"type": "number"},
                "b": {"type": "number"}
            },
            "required": ["a", "b"]
        })
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "add":
        result = arguments["a"] + arguments["b"]
    elif name == "multiply":
        result = arguments["a"] * arguments["b"]

    return [TextContent(type="text", text=str(result))]

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())

if __name__ == "__main__":
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

Rahul: That's it? Just decorators and async functions?

Amit: Yep! Just two key decorators:

  • @server.list_tools() - Tells clients what tools are available
  • @server.call_tool() - Handles tool execution

The MCP SDK handles all the JSON-RPC protocol details for you.


3. The Client Side πŸ”Œ

Rahul: How do I use this server?

Amit: Here's a client that connects to the server:

# mcp_client.py
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio
import sys

async def main():
    server_params = StdioServerParameters(
        command=sys.executable,
        args=["mcp_server.py"]
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            # Initialize
            await session.initialize()

            # List tools
            tools = await session.list_tools()
            print("Tools:", [t.name for t in tools.tools])

            # Call add tool
            result = await session.call_tool("add", arguments={"a": 7, "b": 3})
            print("7 + 3 =", result.content[0].text)

            # Call multiply tool
            result = await session.call_tool("multiply", arguments={"a": 4, "b": 6})
            print("4 * 6 =", result.content[0].text)

if __name__ == "__main__":
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

Rahul: Wait, where do I start the server? Do I run it in another terminal?

Amit: Nope! That's the key difference with MCP. Check out this line:

StdioServerParameters(command=sys.executable, args=["mcp_server.py"])
Enter fullscreen mode Exit fullscreen mode

The client spawns the server as a subprocess. You never manually start the server.


4. The Two-Terminal Misconception ❌

Rahul: But normally I start the server first, then connect clients. Why can't I do that here?

Amit: Good question! Let me show you why:

Traditional HTTP/TCP:

# Terminal 1
python http_server.py  # Listens on port 8000

# Terminal 2
curl http://localhost:8000  # Connects to Terminal 1 βœ“
Enter fullscreen mode Exit fullscreen mode

MCP stdio:

# Terminal 1
python mcp_server.py  # Waits on stdin (no port)

# Terminal 2
python mcp_client.py  # Spawns its OWN server βœ—
Enter fullscreen mode Exit fullscreen mode

Rahul: So they can't talk to each other?

Amit: Right. MCP uses stdio pipes (stdin/stdout), not network ports. Each terminal has separate stdin/stdout streams. The client spawns the server as a subprocess and communicates via pipes:

Client Process                     Server Process
    |                                  |
    |-- subprocess.Popen ------------->| (spawned)
    |                                  |
    |-- JSON via stdin --------------->|
    |                                  |-- execute tool
    |<-- JSON via stdout --------------|-- return result
    |                                  |
    |-- terminate ------------------->| (killed)
Enter fullscreen mode Exit fullscreen mode

Rahul: So the client controls the server's lifecycle?

Amit: Exactly! Here's why that's good:

  • No port conflicts
  • No manual server management
  • Process isolation (each client gets own server)
  • Automatic cleanup (server dies when client exits)

5. The Three Components 🎭

Rahul: OK, I see Server and Client. But what's this "Host" everyone talks about?

Amit: Let me break down the three roles:

  1. Server - Provides tools (your math server)
  2. Client - MCP protocol handler (connects to server)
  3. Host - Application that uses servers (Claude Desktop, VS Code, AI agents)

Rahul: Got an example?

Amit: Sure! Here's an AI agent (Host) using our math server:

# mcp_host.py
from mcp import stdio_client, StdioServerParameters
from strands import Agent
from strands.tools.mcp import MCPClient
import sys

# Host application that uses MCP server
mcp_client = MCPClient(lambda: stdio_client(
    StdioServerParameters(
        command=sys.executable,
        args=["mcp_server.py"]
    )
))

with mcp_client:
    tools = mcp_client.list_tools_sync()
    print("Available tools:", len(tools), "tools")

    agent = Agent(tools=tools)
    response = agent("What is 15 + 27? Then multiply that result by 3")
    print(response)
Enter fullscreen mode Exit fullscreen mode

6. Client vs Host - The Key Difference πŸ”‘

Rahul: Hold on! What's the difference between Client and Host? They look the same to me. Both connect to the server, right?

Amit: I know, they do look similar! But they serve different purposes. Let me show you:

Client (mcp_client.py) - Low-level MCP protocol:

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    async with stdio_client(StdioServerParameters(...)) as (read, write):
        async with ClientSession(read, write) as session:
            # You manually call each tool
            result = await session.call_tool("add", {"a": 7, "b": 3})
            print(result.content[0].text)  # Output: 10
Enter fullscreen mode Exit fullscreen mode

Host (mcp_host.py) - Application using MCP:

from strands import Agent
from strands.tools.mcp import MCPClient

mcp_client = MCPClient(lambda: stdio_client(...))

with mcp_client:
    tools = mcp_client.list_tools_sync()
    agent = Agent(tools=tools)
    # AI agent decides which tools to call
    response = agent("What is 15 + 27? Then multiply that result by 3")
    # Agent automatically: add(15,27) β†’ multiply(42,3) β†’ answer
Enter fullscreen mode Exit fullscreen mode

Rahul: I'm still not seeing it. Both use MCP client, right?

Amit: Yeah, but check out how they're used:

Client:

  • Direct MCP protocol usage
  • You explicitly call each tool
  • You control the flow
  • Good for: Testing, debugging, simple scripts

Host:

  • Application built on top of MCP
  • AI/logic decides which tools to call
  • Autonomous decision-making
  • Good for: AI assistants, complex workflows

Rahul: Can you show them side-by-side?

Amit: Absolutely!

Scenario: Calculate (10 + 5) * 3

Client approach:

# You write the logic
result1 = await session.call_tool("add", {"a": 10, "b": 5})
intermediate = int(result1.content[0].text)  # 15

result2 = await session.call_tool("multiply", {"a": intermediate, "b": 3})
final = result2.content[0].text  # 45
Enter fullscreen mode Exit fullscreen mode

Host approach:

# Agent figures out the logic
response = agent("Calculate 10 + 5, then multiply by 3")
# Agent automatically:
# 1. Calls add(10, 5) β†’ 15
# 2. Calls multiply(15, 3) β†’ 45
# 3. Returns answer
Enter fullscreen mode Exit fullscreen mode

Rahul: Ah! So Client is like using a library directly, and Host is building an app with that library?

Amit: Exactly! Here's another way to think about it:

Client = MCP Protocol Handler

  • Raw access to MCP servers
  • Manual tool invocation
  • You write the orchestration logic

Host = Application Using MCP

  • Built on top of MCP client
  • Autonomous tool usage (AI, rules, workflows)
  • Application-specific features (UI, auth, logging)

7. Why Three Components? πŸ€”

Rahul: Why not just Server + Client? Why do we need Host?

Amit: Good question! Here's why:

Why not just Server?

  • Apps need a standard way to discover and call tools
  • Client provides the MCP protocol API

Why not just Server + Client?

  • Host manages lifecycle (start/stop servers)
  • Host handles multiple servers simultaneously
  • Host provides app-specific features (UI, auth, logging)

Real-world example:

Claude Desktop (Host)
    ↓ manages
GitHub MCP Server + AWS MCP Server + Database MCP Server
    ↓ via
MCP Client (protocol handler)
Enter fullscreen mode Exit fullscreen mode

8. Testing Your MCP Server πŸ§ͺ

Rahul: How do I test my server without writing a client?

Amit: Use MCP Inspector! It's a visual debugging tool that makes life easier:

# Install
npm install -g @modelcontextprotocol/inspector

# Run
mcp-inspector python mcp_server.py

# Open browser
http://localhost:5173
Enter fullscreen mode Exit fullscreen mode

Rahul: What can I do with it?

Amit: Lots of stuff:

  1. See all available tools
  2. Test tools with custom arguments
  3. View JSON-RPC messages
  4. Debug responses in real-time

For example:

  1. Click "Tools" tab β†’ see add and multiply
  2. Click add β†’ enter {"a": 10, "b": 5}
  3. Click "Execute" β†’ see result 15
  4. View raw JSON-RPC in Logs tab

Rahul: Nice! Way easier than writing test clients!


9. Real-World Example: GitHub MCP Server πŸ™

Rahul: You mentioned GitHub earlier. How's that work?

Amit: GitHub MCP Server is just like our math server, but it provides GitHub operations:

# Any app can use it
mcp_client = MCPClient(lambda: stdio_client(
    StdioServerParameters(
        command="npx",
        args=["-y", "@modelcontextprotocol/server-github"]
    )
))

# Now you have tools like:
# - create_issue
# - search_repositories
# - read_file
# - etc.
Enter fullscreen mode Exit fullscreen mode

Rahul: So it's stdio transport, not HTTP?

Amit: Correct! It runs as a local subprocess:

Claude Desktop (Host)
    ↓ spawns subprocess
GitHub MCP Server (Node.js script)
    ↓ provides tools via stdio
Claude can now:
- Create GitHub issues
- Search repositories  
- Read file contents
Enter fullscreen mode Exit fullscreen mode

10. MCP Transport: stdio vs SSE πŸ”„

Rahul: You mentioned stdio transport. Are there other ways to implement MCP?

Amit: Yes! MCP supports two transport mechanisms:

1. stdio (stdin/stdout) - What we've been using

  • Local subprocess communication
  • No network ports
  • Most common for local development
  • What we covered in this tutorial

2. SSE (Server-Sent Events) - HTTP-based

  • Web-based communication
  • Uses HTTP with Server-Sent Events
  • For remote/web access
  • Can be accessed via localhost URL

Rahul: Can you show me SSE implementation?

Amit: Sure! Here's the same math server using SSE:

Server with SSE:

from mcp.server import Server
from mcp.server.sse import sse_server
from starlette.applications import Starlette
from starlette.routing import Route
import uvicorn

server = Server("math-server")

# Same @server.list_tools() and @server.call_tool() as before

app = Starlette(
    routes=[
        Route("/sse", endpoint=sse_server(server), methods=["GET", "POST"])
    ]
)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
Enter fullscreen mode Exit fullscreen mode

Client with SSE:

from mcp.client.sse import sse_client
from mcp import ClientSession
import asyncio

async def main():
    async with sse_client("http://localhost:8000/sse") as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            result = await session.call_tool("add", arguments={"a": 5, "b": 3})
            print("5 + 3 =", result.content[0].text)

if __name__ == "__main__":
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

Rahul: When should I use each?

Amit: Here's the breakdown:

Use stdio when:

  • Building local tools
  • Desktop applications
  • CLI tools
  • Development/testing
  • No remote access needed

Use SSE when:

  • Web applications
  • Remote server access
  • Cloud deployments
  • Multiple clients from different machines
  • Browser-based clients

Rahul: Got it! So stdio for local, SSE for web.

Amit: Exactly! Most MCP servers start with stdio for simplicity, then add SSE if remote access is needed.


11. Common Patterns πŸ“‹

Rahul: What are the typical use cases?

Amit: Here are the common patterns:

1. Data Access:

# Database MCP Server
@server.call_tool()
async def call_tool(name, arguments):
    if name == "query_database":
        return execute_sql(arguments["query"])
Enter fullscreen mode Exit fullscreen mode

2. API Integration:

# Slack MCP Server
@server.call_tool()
async def call_tool(name, arguments):
    if name == "send_message":
        return slack_api.post_message(arguments["channel"], arguments["text"])
Enter fullscreen mode Exit fullscreen mode

3. File Operations:

# Filesystem MCP Server
@server.call_tool()
async def call_tool(name, arguments):
    if name == "read_file":
        return open(arguments["path"]).read()
Enter fullscreen mode Exit fullscreen mode

Rahul: So basically any operation can be an MCP tool?

Amit: Yep! If you can code it, you can expose it via MCP.


12. Debugging Tips πŸ›

Rahul: What if my server isn't working?

Amit: Here's a quick verification checklist:

1. Server Startup Check:

python mcp_server.py
Enter fullscreen mode Exit fullscreen mode
  • No errors = server code is valid βœ“
  • Waits for input = ready to accept connections βœ“
  • Ctrl+C to exit

2. Client Test:

python mcp_client.py
Enter fullscreen mode Exit fullscreen mode

Expected output:

Tools: ['add', 'multiply']  βœ“ Server responding
7 + 3 = 10                  βœ“ Tools working
4 * 6 = 24                  βœ“ All tests passed
Enter fullscreen mode Exit fullscreen mode

3. Inspector Test:

mcp-inspector python mcp_server.py
Enter fullscreen mode Exit fullscreen mode

Visual confirmation of tools and responses.

Common issues:

Server won't start:

pip install mcp  # Missing dependency
Enter fullscreen mode Exit fullscreen mode

Tools not showing:
Check @server.list_tools() decorator exists

Tool execution fails:
Check @server.call_tool() handles the tool name


13. Architecture Deep Dive πŸ—οΈ

Rahul: Can you walk me through the full architecture one more time?

Amit: Sure! Here's the complete flow:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Host Application (Claude Desktop, VS Code)     β”‚
β”‚                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚ MCP Client (Protocol Handler)         β”‚    β”‚
β”‚  β”‚                                        β”‚    β”‚
β”‚  β”‚  - Spawns server subprocess           β”‚    β”‚
β”‚  β”‚  - Manages stdio pipes                β”‚    β”‚
β”‚  β”‚  - Sends JSON-RPC requests            β”‚    β”‚
β”‚  β”‚  - Receives JSON-RPC responses        β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚ subprocess.Popen
                   β”‚ stdin/stdout pipes
                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ MCP Server (Tool Provider)                      β”‚
β”‚                                                 β”‚
β”‚  @server.list_tools()                          β”‚
β”‚  - Returns available tools                      β”‚
β”‚                                                 β”‚
β”‚  @server.call_tool()                           β”‚
β”‚  - Executes tool logic                         β”‚
β”‚  - Returns results                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Communication:

  1. Host spawns server via subprocess.Popen
  2. Host writes JSON to server's stdin
  3. Server reads from stdin, processes request
  4. Server writes JSON to stdout
  5. Host reads from server's stdout
  6. Host terminates server when done

Rahul: And this all happens automatically?

Amit: Yep! The MCP SDK handles all the plumbing. You just write:

Server side:

@server.list_tools()  # What tools do you have?
@server.call_tool()   # Execute this tool
Enter fullscreen mode Exit fullscreen mode

Client side:

session.list_tools()           # Get available tools
session.call_tool(name, args)  # Call a tool
Enter fullscreen mode Exit fullscreen mode

14. Quick Start Summary ⚑

Rahul: Give me the quick version to get started.

Amit: Here you go:

# Install
pip install mcp

# Create server (mcp_server.py)
# - Use @server.list_tools() to register tools
# - Use @server.call_tool() to handle execution

# Create client (mcp_client.py)
# - Use StdioServerParameters to spawn server
# - Use ClientSession to call tools

# Test
python mcp_client.py

# Debug
npm install -g @modelcontextprotocol/inspector
mcp-inspector python mcp_server.py
Enter fullscreen mode Exit fullscreen mode

Rahul: That's it?

Amit: That's it! Now you understand:

  • βœ“ What MCP solves (N+M vs NΓ—M integrations)
  • βœ“ Three components (Server, Client, Host)
  • βœ“ stdio transport (subprocess, not network)
  • βœ“ Why not two terminals (pipes, not ports)
  • βœ“ How to build servers (decorators)
  • βœ“ How to test (Inspector)
  • βœ“ Real-world usage (GitHub, databases, APIs)

Rahul: Awesome! Time to build my first MCP server.

Amit: Go for it! Remember: any tool you build will work in every AI app that supports MCP. That's the power of standards.


15. Explore More MCP Servers 🌐

Rahul: This is cool! Are there more MCP servers I can explore?

Amit: Absolutely! Check out these resources:

Official MCP Servers:

Community Resources:

  • 🌍 mcp.so

    • Curated list of MCP servers
    • Community contributions
    • Search by category
  • πŸ“š mcpservers.org

    • Comprehensive directory
    • Server ratings and reviews
    • Installation guides

Popular MCP Servers:

  • GitHub (create issues, search repos)
  • AWS (manage cloud resources)
  • PostgreSQL (database queries)
  • Filesystem (file operations)
  • Slack (send messages)
  • Google Drive (file management)
  • And many more!

Rahul: Awesome! Time to explore and build.

Amit: Go for it! Remember: any tool you build will work in every AI app that supports MCP. That's the power of standards.


Run the demos:

pip install mcp strands
python mcp_client.py  # See client in action
python mcp_host.py    # See AI agent using MCP
mcp-inspector python mcp_server.py  # Visual debugging
Enter fullscreen mode Exit fullscreen mode

Happy MCP building! πŸš€

Top comments (1)

Collapse
 
datatechbridge profile image
Data Tech Bridge

Please give any feedback and share with others if this helps understanding MCP better. πŸ™ŒπŸ™