MCP Learning Server
A learning-focused MCP server that provides tools for current time, arithmetic, random quotes, mock weather, and UUID generation, demonstrating the Model Context Protocol end-to-end.
README
š 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
- Architecture
- Folder Structure
- Installation
- Running the Project
- Available Tools
- Example Execution
- MCP Learning Notes
- Common Errors
- Future Improvements
šÆ 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 manager- Official 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 rules
Why Each File Exists
| File | Purpose |
|---|---|
server/server.py |
MCP registration layer ā connects business logic to the MCP protocol using @mcp.tool() decorators |
server/tools.py |
Business logic ā pure Python functions that can be tested without MCP |
server/models.py |
Validation ā Pydantic models that enforce type safety at the input boundary |
server/quotes.py |
Data ā separates data from logic for clean architecture |
client/client.py |
Interactive client ā demonstrates the full MCP client workflow |
client/chat.py |
Chat interface ā shows how natural language maps to tool calls |
client/ui.py |
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.client
Option 2: Chat Interface
uv run python -m client.chat
Option 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 | {"success": true, "data": {"current_time": "2026-07-01T12:00:00Z"}} |
2. calculator
Performs arithmetic operations on two numbers.
| Input | {"a": 10, "b": 20, "operation": "add"} |
| Operations | add, subtract, multiply, divide |
| Output | {"success": true, "data": {"result": 30.0}} |
| Division by zero | {"success": false, "error": "Division by zero is not allowed"} |
3. random_quote
Returns a random inspirational quote from an internal list of 25 quotes.
| Input | None |
| Output | {"success": true, "data": {"quote": "Stay hungry, stay foolish. ā Steve Jobs"}} |
4. weather
Returns mock weather data for Indian cities.
| Input | {"city": "Jaipur"} |
| Supported Cities | Jaipur, Delhi, Mumbai, Bangalore, Chennai, Kolkata, Hyderabad, Pune |
| Output | {"success": true, "data": {"city": "Jaipur", "temperature": "35°C", "condition": "Sunny"}} |
| Unknown city | {"success": false, "error": "City 'London' not found. Available cities: ..."} |
5. uuid_generator
Generates a random UUID (version 4).
| Input | None |
| Output | {"success": true, "data": {"uuid": "550e8400-e29b-41d4-a716-446655440000"}} |
š» 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 | list_tools() returns schemas dynamically |
| 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 |
|---|---|---|
Server script not found |
Running from wrong directory | cd to project root |
ModuleNotFoundError: server |
Missing uv sync or wrong Python |
Run uv sync first |
ConnectionError |
Server failed to start | Check logs/app.log for details |
Invalid operation |
Typo in operation name | Use: add, subtract, multiply, divide |
Division by zero |
b=0 with divide operation | Expected ā handled gracefully |
City not found |
City not in mock data | Use: Jaipur, Delhi, Mumbai, Bangalore, Chennai, Kolkata |
Broken pipe / garbled output |
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).
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.