MCP Learning Server
š MCP Learning Project
A complete educational project demonstrating the Model Context Protocol (MCP) end-to-end.
This project is designed for developers who are completely new to MCP and want to understand:
What an MCP Server is
What an MCP Client is
How they communicate
How tools are registered and discovered
How requests flow and responses return
š Table of Contents
šÆ Project Overview
This project implements:
Component | Description |
MCP Server | A FastMCP server exposing 5 tools via stdio transport |
Interactive Client | A Rich-powered CLI that discovers and calls tools |
Chat Interface | A natural language chatbot that maps queries to tools |
Pydantic Validation | Type-safe input validation with friendly errors |
Logging | Structured logs with INFO/WARNING/ERROR levels |
Architecture Diagrams | Mermaid diagrams explaining every component |
Tech Stack
Python 3.11+
uvpackage managerOfficial MCP Python SDK (
mcp[cli]with FastMCP)asyncio
Pydantic v2
Rich library
šļø Architecture
High-Level Architecture
āāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāā
ā MCP Client ā stdio ā MCP Server ā
ā āāāāāāāāāāāŗā ā
ā ⢠Rich CLI/Chat ā JSON- ā ⢠FastMCP Router ā
ā ⢠ClientSession ā RPC 2.0 ā ⢠5 Registered ā
ā ⢠Tool Discovery ā ā Tools ā
āāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāRequest Flow
User
ā
Client UI (Rich terminal)
ā
ClientSession.call_tool(name, arguments)
ā
JSON-RPC 2.0 Request (serialized)
ā
stdio Transport (stdin pipe to server subprocess)
ā
FastMCP Server (deserializes, routes by tool name)
ā
@mcp.tool() Function ā Pydantic validation ā Business logic
ā
JSON Response (consistent format)
ā
stdio Transport (stdout pipe back to client)
ā
ClientSession (deserializes response)
ā
Rich UI (pretty-prints with syntax highlighting)
ā
User sees the result ā
Tool Discovery Flow
Client Server
| |
|āāāā initialize() āāāāāāāāāāāāŗ|
|āāāāā capabilities āāāāāāāāāā|
| |
|āāāā list_tools() āāāāāāāāāāāāŗ|
|āāāāā tool schemas āāāāāāāāāā|
| (name, description, |
| inputSchema) |
| |
| Client now knows all tools! |š Key MCP advantage: The client doesn't need hardcoded endpoints. It discovers tools dynamically!
š Folder Structure
mcp-learning-project/
ā
āāā server/ # MCP Server (the "backend")
ā āāā __init__.py # Package marker
ā āāā server.py # FastMCP setup + tool registration
ā āāā tools.py # Pure business logic (no MCP dependency)
ā āāā models.py # Pydantic validation models
ā āāā quotes.py # Hardcoded quote data
ā
āāā client/ # MCP Client (the "frontend")
ā āāā __init__.py # Package marker
ā āāā client.py # Interactive CLI client
ā āāā chat.py # Natural language chat interface
ā āāā ui.py # Rich terminal UI helpers
ā
āāā diagrams/ # Documentation
ā āāā architecture.md # Mermaid architecture diagrams
ā
āāā logs/ # Generated at runtime
ā āāā app.log # Structured application logs
ā
āāā screenshots/ # Screenshots placeholder
ā
āāā README.md # This file
āāā pyproject.toml # Project configuration
āāā uv.lock # Dependency lock file (auto-generated)
āāā .gitignore # Git ignore rulesWhy Each File Exists
File | Purpose |
| MCP registration layer ā connects business logic to the MCP protocol using |
| Business logic ā pure Python functions that can be tested without MCP |
| Validation ā Pydantic models that enforce type safety at the input boundary |
| Data ā separates data from logic for clean architecture |
| Interactive client ā demonstrates the full MCP client workflow |
| Chat interface ā shows how natural language maps to tool calls |
| Presentation ā all Rich formatting in one place |
š Installation
Prerequisites
Python 3.11 or higher
uv package manager
Setup
# 1. Clone the repository
git clone <repository-url>
cd mcp-learning-project
# 2. Create a virtual environment and install dependencies
uv sync
# This will:
# - Create a .venv/ directory
# - Install mcp[cli], pydantic, and rich
# - Generate uv.lock for reproducibilityā¶ļø Running the Project
Option 1: Interactive Client (Recommended)
The client automatically starts the server as a subprocess ā you only need one terminal:
uv run python -m client.clientOption 2: Chat Interface
uv run python -m client.chatOption 3: Server Only (for debugging)
If you want to run the server independently (e.g., to test with MCP Inspector):
uv run python -m server.serverā ļø The server uses stdio transport, so it reads from stdin and writes to stdout. Running it directly will wait for JSON-RPC input ā this is expected behavior.
Testing with MCP Inspector
The MCP Inspector is a visual debugging tool:
npx -y @modelcontextprotocol/inspector uv run python -m server.serverš§° Available Tools
1. current_time
Returns the current UTC time in ISO 8601 format.
Input | None |
Output |
|
2. calculator
Performs arithmetic operations on two numbers.
Input |
|
Operations |
|
Output |
|
Division by zero |
|
3. random_quote
Returns a random inspirational quote from an internal list of 25 quotes.
Input | None |
Output |
|
4. weather
Returns mock weather data for Indian cities.
Input |
|
Supported Cities | Jaipur, Delhi, Mumbai, Bangalore, Chennai, Kolkata, Hyderabad, Pune |
Output |
|
Unknown city |
|
5. uuid_generator
Generates a random UUID (version 4).
Input | None |
Output |
|
š» Example Execution
Interactive Client
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®
ā š MCP Learning Client ā
ā Connected to MCP Server via stdio ā
ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā š Available Tools ā
āāāāāā¬āāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāā¤
ā # ā Tool Name ā Description ā
āāāāāā¼āāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāā¤
ā 1 ā current_time ā Get the current UTC ā
ā ā ā time in ISO 8601 ā
āāāāāā¼āāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāā¤
ā 2 ā calculator ā Perform arithmetic ā
ā ā ā operations ā
āāāāāā¼āāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāā¤
ā 3 ā random_quote ā Get a random ā
ā ā ā inspirational quote ā
āāāāāā¼āāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāā¤
ā 4 ā weather ā Get mock weather data ā
āāāāāā¼āāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāā¤
ā 5 ā uuid_generator ā Generate a random UUID ā
āāāāāā“āāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāā
>>> 2
Enter calculator inputs:
First number (a): 45
Second number (b): 87
Operation (add/subtract/multiply/divide): multiply
ā³ Calling calculator...
āāāā ā
Success āāāā®
ā { ā
ā "result": 3915 ā
ā } ā
ā°āāāāāāāāāāāāāāāāāāāÆ
ā± Completed in 0.023s
>>> info weather
š weather
Get mock weather data for an Indian city.
š„ Input Schema:
{
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
>>> exit
š Goodbye!Chat Interface
š¤ MCP Chat Interface
Ask me about weather, calculations, time, quotes, or UUIDs.
Type 'exit' to quit.
You: What is the weather in Jaipur?
ā³ Calling weather tool...
āāāā š¤ Assistant āāāā®
ā š¤ļø Weather in ā
ā Jaipur: 35°C, Sunny ā
ā°āāāāāāāāāāāāāāāāāāāāāāÆ
You: Multiply 45 and 87
ā³ Calling calculator tool...
āāāā š¤ Assistant āāāā®
ā š¢ The answer is: ā
ā 3915.0 ā
ā°āāāāāāāāāāāāāāāāāāāāāāÆ
You: Give me an inspirational quote
ā³ Calling random_quote tool...
āāāāāā š¤ Assistant āāāāāā®
ā š¬ "Stay hungry, stay ā
ā foolish. ā Steve Jobs" ā
ā°āāāāāāāāāāāāāāāāāāāāāāāāāāÆExample Log Output (logs/app.log)
2026-07-08 10:30:15 | INFO | mcp.server | FastMCP server instance created: 'MCP Learning Server'
2026-07-08 10:30:15 | INFO | mcp.server | Starting MCP Learning Server (stdio transport)...
2026-07-08 10:30:16 | INFO | mcp.client | Client starting ā connecting to MCP server...
2026-07-08 10:30:16 | INFO | mcp.client | stdio transport established
2026-07-08 10:30:16 | INFO | mcp.client | MCP session initialized
2026-07-08 10:30:16 | INFO | mcp.client | Discovered 5 tools: ['current_time', 'calculator', ...]
2026-07-08 10:30:20 | INFO | mcp.client | Calling tool: calculator with args: {'a': 45, 'b': 87, 'operation': 'multiply'}
2026-07-08 10:30:20 | INFO | mcp.server | Tool called: calculator(a=45.0, b=87.0, op=multiply)
2026-07-08 10:30:20 | INFO | mcp.server | Tool result: calculator ā {'success': True, 'data': {'result': 3915.0}}
2026-07-08 10:30:20 | INFO | mcp.client | Tool calculator completed in 0.023sš MCP Learning Notes
What is MCP?
The Model Context Protocol (MCP) is a standardized protocol for connecting AI models to external tools and data sources. Think of it as a "USB-C for AI" ā a universal interface that lets any AI assistant use any tool without custom integrations.
How MCP Registers Tools
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My Server")
@mcp.tool(
name="calculator",
description="Perform arithmetic operations"
)
def calculator(a: float, b: float, operation: str) -> str:
# FastMCP auto-generates a JSON Schema from the function signature:
# {
# "type": "object",
# "properties": {
# "a": {"type": "number"},
# "b": {"type": "number"},
# "operation": {"type": "string"}
# },
# "required": ["a", "b", "operation"]
# }
...When @mcp.tool() is called:
FastMCP inspects the function's type hints
Generates a JSON Schema for the inputs
Stores the tool in an internal registry
When
list_tools()is called, returns all registered schemas
How the Client Discovers Tools
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
# 1. Define how to launch the server
server_params = StdioServerParameters(
command="uv",
args=["run", "python", "-m", "server.server"]
)
# 2. Connect via stdio
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 3. Initialize the session (handshake)
await session.initialize()
# 4. Discover all tools
tools = await session.list_tools()
# tools.tools ā list of Tool objects with name, description, inputSchema
# 5. Call a specific tool
result = await session.call_tool("calculator", {"a": 10, "b": 20, "operation": "add"})How Requests are Serialized
MCP uses JSON-RPC 2.0 over the stdio transport:
// Client ā Server (request)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "calculator",
"arguments": {"a": 10, "b": 20, "operation": "add"}
}
}
// Server ā Client (response)
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\"success\": true, \"data\": {\"result\": 30.0}}"
}
]
}
}How Transport Works
This project uses stdio transport:
The client spawns the server as a subprocess
JSON-RPC messages flow through the subprocess's stdin (requests) and stdout (responses)
This is why we never
print()to stdout in the server ā it would corrupt the protocol!
Other MCP transports include:
SSE (Server-Sent Events): HTTP-based, good for remote servers
Streamable HTTP: Newer HTTP transport for production use
How MCP Differs from REST
Aspect | REST API | MCP |
Discovery | You need documentation/OpenAPI spec |
|
Protocol | HTTP request/response | JSON-RPC 2.0 (bidirectional) |
Schema | OpenAPI (optional) | JSON Schema (built-in) |
Transport | Always HTTP | stdio, SSE, Streamable HTTP |
Standardization | Varies per API | One protocol for all tools |
AI Integration | Custom per model | Universal ā any model, any tool |
How to Add a New Tool
Adding a tool takes 3 steps:
Business logic ā Add a function to
server/tools.py:
def my_tool(input_param: str) -> dict[str, Any]:
"""Do something useful."""
return ToolResponse.ok(result="Hello!")Validation (optional) ā Add a Pydantic model to
server/models.py:
class MyToolInput(BaseModel):
input_param: str = Field(..., min_length=1)Registration ā Add a
@mcp.tool()decorator inserver/server.py:
@mcp.tool(name="my_tool", description="Does something useful")
def tool_my_tool(input_param: str) -> str:
return json.dumps(my_tool(input_param))That's it! The client will auto-discover the new tool on next connection.
ā ļø Common Errors
Error | Cause | Solution |
| Running from wrong directory |
|
| Missing | Run |
| Server failed to start | Check |
| Typo in operation name | Use: add, subtract, multiply, divide |
| b=0 with divide operation | Expected ā handled gracefully |
| City not in mock data | Use: Jaipur, Delhi, Mumbai, Bangalore, Chennai, Kolkata |
| Printing to stdout in server | Always use logging or stderr |
š® Future Improvements
Add LLM integration for intelligent intent parsing
Implement SSE transport for remote server access
Add tool caching and rate limiting
Build a web-based UI alongside the CLI
Add unit tests for each tool
Implement MCP Resources and Prompts (beyond Tools)
Add authentication and authorization
Create a tool that chains multiple other tools
š License
MIT License ā Feel free to use this for learning and education.
Built as an educational project to understand the Model Context Protocol (MCP).