Content
# My Custom MCP Server
A hand-built **MCP (Model Context Protocol) server** written in pure Python — no frameworks, no magic. It exposes tools (like sending emails and doing math) that any MCP-compatible client can call.
---
## What is MCP?
**MCP = Model Context Protocol.**
Think of it like a USB standard, but for AI tools.
- An **AI assistant** (like Claude) is the client.
- Your **MCP server** is a plugin that gives the AI new abilities (tools).
- The AI talks to your server using a standard format called **JSON-RPC over stdio** (basically sending JSON messages back and forth through the terminal).
```
You (or an AI)
|
| "Hey, send an email to bob@example.com"
v
[ MCP Client ] <----> [ MCP Server ] <----> Gmail API
JSON-RPC OAuth 2.0
```
---
## What This Project Does
This project has **two folders**:
| Folder | What it is |
|--------|-----------|
| `real_mcp_server/` | The server — exposes tools via MCP |
| `mcp_client/` | The client — connects to the server and calls tools |
---
## The Server (`real_mcp_server/`)
### Tools Available
| Tool | What it does |
|------|-------------|
| `add` | Adds two numbers (e.g. 5 + 3 = 8) |
| `multiply` | Multiplies two numbers (e.g. 4 × 6 = 24) |
| `send_email` | Sends a real email via your Gmail account |
### How the Server Works — File by File
```
real_mcp_server/
│
├── main.py # Entry point. Runs an infinite loop reading JSON from stdin
├── transport.py # Reads/writes JSON messages over stdin and stdout
├── dispatcher.py # Receives a request and passes it to the handler
├── handlers.py # Decides what to do based on the method name
│ (initialize, tools/list, tools/call)
├── registry.py # The list of all available tools and their descriptions
├── tools.py # The actual math functions (add, multiply)
├── gmail_tool.py # The Gmail send function with OAuth 2.0 authentication
├── jsonrpc.py # Helper to build JSON-RPC success/error responses
├── constants.py # Server name, version, protocol version
├── credentials.json # Google OAuth credentials (downloaded from Google Cloud)
└── token.json # Saved Gmail login token (auto-created on first login)
```
### How a Request Flows Through the Server
```
stdin (JSON message)
└─> transport.py reads the raw JSON line
└─> main.py passes it to dispatcher
└─> dispatcher.py calls handle()
└─> handlers.py checks the method:
├── "initialize" → returns server info
├── "tools/list" → returns list of all tools
└── "tools/call" → looks up the tool in registry
calls the function
returns the result
└─> transport.py writes the JSON response back to stdout
```
---
## The Client (`mcp_client/`)
### Files
```
mcp_client/
├── client.py # MCPClient class — handles the connection to the server
├── test.py # Original test (calls add tool)
└── test_gmail.py # Interactive test — lets you pick any tool and send emails
```
### How the Client Works
1. **Spawns** the server as a subprocess (using the server's Python venv)
2. **Sends** `initialize` → gets back server info
3. **Sends** `notifications/initialized` → handshake complete
4. **Sends** `tools/list` → gets back all available tools
5. **Lets you pick** which tool to call
6. **Sends** `tools/call` with your arguments → gets back the result
---
## The Gmail Tool — How It Works
The `send_email` tool uses the **Gmail API** with **OAuth 2.0**.
### First Time Setup (one-time)
1. You download `credentials.json` from Google Cloud Console
2. On first use, a browser opens asking you to log in to Google
3. You grant permission → a `token.json` file is saved locally
4. From now on, the server uses `token.json` silently — no browser needed
### How an Email Gets Sent
```
send_email("bob@gmail.com", "Hello", "This is a test")
└─> Loads token.json (or opens browser if missing)
└─> Builds the email in MIME format
└─> Base64-encodes it (Gmail API requirement)
└─> Calls Gmail API: users.messages.send
└─> Returns "Email sent successfully. Message ID: xxxx"
```
---
## How to Run
### Prerequisites
- Python 3.x
- Google Cloud project with Gmail API enabled
- `credentials.json` placed in `real_mcp_server/`
- Dependencies installed in `real_mcp_server/venv/`
### Install dependencies (first time only)
```powershell
cd "real_mcp_server"
python -m venv venv
.\venv\Scripts\activate
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
```
### Run the interactive test client
```powershell
cd mcp_client
python test_gmail.py
```
You'll see a menu:
```
[1] add -- Adds two integers.
[2] multiply -- Multiplies two integers.
[3] send_email -- Sends an email via Gmail API.
Your choice: 3
to (required): someone@gmail.com
subject (required): Hello from MCP!
body (required): This was sent by my custom MCP server.
[OK] Email sent successfully. Message ID: 18f4a3c9d2b1e0a7
```
---
## Key Concepts Learned
| Concept | What you built |
|---------|---------------|
| **MCP Protocol** | A server that speaks JSON-RPC over stdin/stdout |
| **Tool Registry** | A dictionary mapping tool names to functions + schemas |
| **OAuth 2.0** | Browser-based Google login that saves a reusable token |
| **Gmail API** | Sending real emails programmatically |
| **Subprocess communication** | A client that spawns and talks to a server process |
| **JSON-RPC** | A standard request/response format used by MCP |
---
## Why stdio (stdin/stdout)?
The MCP protocol uses stdio instead of HTTP because:
- **No port management** — the client just pipes data in and reads data out
- **Process isolation** — the server runs as a separate process; crashes don't affect the client
- **Easy integration** — any MCP host (Claude Desktop, Cursor, VS Code) can launch your server the same way: just run `python main.py`
---
## Deep Dive: OAuth, Gmail API & How the Email Actually Gets Sent
### The Problem — Why Can't We Just Use a Password?
Imagine you want your Python script to send emails from your Gmail account.
The naive way would be: give the script your Gmail password. But that's terrible because:
- Your password gives access to **everything** — read emails, delete emails, change your password, etc.
- If the script leaks, your entire account is compromised
- Google actually **blocks** plain password login for third-party apps
So Google built the **Gmail API + OAuth 2.0** as the safe alternative.
---
### What is the Gmail API?
The **Gmail API** is Google's official way for programs to interact with Gmail.
Instead of logging into Gmail like a human (browser → username → password), your code talks directly to Google's servers using a structured API:
```
Your Python code --> Google's servers --> Your Gmail inbox
(via HTTPS requests)
```
Things the Gmail API can do:
- Send emails
- Read emails
- Search emails
- Create drafts
- Manage labels
In this project, we only use **one feature: send email**.
---
### What is OAuth 2.0?
**OAuth 2.0** is a system that lets you give a specific app **limited, temporary access** to your Google account — without ever sharing your password.
Think of it like a **hotel key card**:
- The key card lets you into your room only
- It doesn't give you access to the whole hotel
- It expires after your stay
- The hotel (Google) issued it — not you
In our case:
- The "room" = permission to **send emails only** (not read, not delete)
- The "key card" = a file called `token.json`
- The "hotel" = Google
---
### Why OAuth Instead of a Password?
| | Password approach | OAuth approach |
|--|--|--|
| What access is given | Full account access | Only what you ask for (send email only) |
| How it's stored | Plain text — dangerous | An encrypted token — safer |
| If it leaks | Attacker owns your account | Attacker can only send emails (until you revoke) |
| Expiry | Never (until you change password) | Access token expires in 1 hour; refresh token is long-lived |
| Revocable | No easy way | Yes — one click in your Google account settings |
This is why we used OAuth. It's the **safe, standard, Google-approved** way.
---
### The Full Process — Step by Step
#### Step 1: Create credentials in Google Cloud Console
Before anything, you go to **Google Cloud Console** and:
1. Create a project ("MCP Gmail Demo")
2. Enable the Gmail API for that project
3. Create **OAuth 2.0 credentials** (Client ID + Client Secret)
4. Download these as `credentials.json`
`credentials.json` is like your app's **identity card** — it tells Google *which app* is asking for permission.
```json
{
"client_id": "123456.apps.googleusercontent.com",
"client_secret": "abc123...",
...
}
```
---
#### Step 2: First Run — Browser Login (one-time only)
When `send_email` is called for the first time and there's no `token.json`:
```
Your script runs
└─> Reads credentials.json (your app's identity)
└─> Opens a browser tab automatically
└─> Browser shows: "MCP Gmail Demo wants to send emails on your behalf"
└─> You click "Allow"
└─> Google gives back an authorization code
└─> Your script exchanges that code for two tokens:
- Access Token (valid for ~1 hour — used to make API calls)
- Refresh Token (valid forever — used to get new access tokens)
└─> Both tokens are saved to token.json
```
After this step, the browser never opens again.
---
#### Step 3: Later Runs — Silent Authentication
When `send_email` is called again:
```
Your script runs
└─> Reads token.json (already exists)
└─> Checks: is the access token still valid?
├── YES → use it directly
└── NO (expired after ~1 hour)
└─> Uses the refresh token to silently get a new access token
└─> Updates token.json
└─> No browser, no user interaction needed
```
---
#### Step 4: Building and Sending the Email
Once authenticated, here's how the email is actually sent:
```
send_email(to="bob@gmail.com", subject="Hi", body="Hello!")
│
├─> 1. Build the email in MIME format
│ MIME = a standard email format that includes headers (To, Subject)
│ and the body text
│
├─> 2. Base64 encode it
│ The Gmail API requires the email to be encoded in Base64
│ (a way to convert binary data into safe text characters)
│
├─> 3. Call Gmail API
│ POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
│ Body: { "raw": "<base64 encoded email>" }
│
└─> 4. Google sends the email from your Gmail account
Returns: { "id": "18f4a3c9d2b1e0a7" } ← Gmail message ID
```
---
### The Complete Picture
```
First run:
─────────────────────────────────────────────────────────────────
MCP Client MCP Server Gmail API Google OAuth
│ │ │ │
│── tools/call ────>│ │ │
│ send_email │ │ │
│ │── open browser ──────────────────>│
│ │<─ authorization code ─────────────│
│ │── exchange code ──────────────────>│
│ │<─ access token + refresh token ───│
│ │ (saved to token.json) │
│ │── send email ─────────────────────>│
│ │<─ message ID ──────────────────────│
│<── result ────────│ │ │
Later runs (token.json exists):
─────────────────────────────────────────────────────────────────
MCP Client MCP Server Gmail API Google OAuth
│ │ │ │
│── tools/call ────>│ │ │
│ send_email │── load token.json │ │
│ │ (no browser) │ │
│ │── send email ─────> │
│ │<─ message ID ───── │
│<── result ────────│ │ │
```
---
## Common Issues & Fixes
| Error | Fix |
|-------|-----|
| `Error 403: access_denied` | Add your Gmail to **Test users** in Google Cloud Console → APIs & Services → OAuth consent screen → Audience |
| `FileNotFoundError: credentials.json` | Download it from Google Cloud Console → APIs & Services → Credentials |
| `JSONDecodeError` during OAuth | Fixed — stdout is redirected to stderr during the OAuth flow so it doesn't interfere with JSON-RPC |
| Browser doesn't open | Delete `token.json` and run again |
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers
Time
A Model Context Protocol server for time and timezone conversions.