MCP Learning Server
by somyabhadada
README.md
# š 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](#-project-overview)
- [Architecture](#-architecture)
- [Folder Structure](#-folder-structure)
- [Installation](#-installation)
- [Running the Project](#-running-the-project)
- [Available Tools](#-available-tools)
- [Example Execution](#-example-execution)
- [MCP Learning Notes](#-mcp-learning-notes)
- [Common Errors](#-common-errors)
- [Future Improvements](#-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+
- `uv` package 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](https://docs.astral.sh/uv/getting-started/installation/) package manager
### Setup
```bash
# 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:
```bash
uv run python -m client.client
```
### Option 2: Chat Interface
```bash
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):
```bash
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:
```bash
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
```python
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:
1. FastMCP inspects the function's type hints
2. Generates a JSON Schema for the inputs
3. Stores the tool in an internal registry
4. When `list_tools()` is called, returns all registered schemas
### How the Client Discovers Tools
```python
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:
```json
// 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:
1. **Business logic** ā Add a function to `server/tools.py`:
```python
def my_tool(input_param: str) -> dict[str, Any]:
"""Do something useful."""
return ToolResponse.ok(result="Hello!")
```
2. **Validation** (optional) ā Add a Pydantic model to `server/models.py`:
```python
class MyToolInput(BaseModel):
input_param: str = Field(..., min_length=1)
```
3. **Registration** ā Add a `@mcp.tool()` decorator in `server/server.py`:
```python
@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).*
TDQS
A4.4/5.0
Scored across 5 tools
Disambiguation5/5
Each tool serves a distinct purpose: calculator for arithmetic, current_time for time, random_quote for quotes, uuid_generator for UUIDs, and weather for mock weather data. There is no overlap or ambiguity.
Naming Consistency5/5
All tool names are lowercase, descriptive, and use underscores where needed. They follow a consistent pattern of terse but clear naming.
Tool Count5/5
With 5 tools, the set is well-scoped for a learning/demo server. Each tool provides a distinct utility without being overwhelming or too sparse.
Completeness4/5
The set covers several typical utility functions (math, time, random, identification, weather). A minor gap could be string manipulation or unit conversion, but for a learning server it is adequately complete.
Maintenance
ActivityInactive
ResponsivenessNo issues