Skip to main content
Glama
amitsharma55

MCP Server + AI Agent

by amitsharma55
README.md
# MCP Server + AI Agent

A Python MCP server with JWT authentication and sample tools (weather + Bitcoin price), plus an AI agent that uses OpenAI to reason about and call those tools.

## Architecture

```
User prompt → OpenAI GPT-4o (decides tool calls) → MCP Client → MCP Server (localhost:8000)
                    ↑                                    ↓
                    └──────── tool results ──────────────┘
```

- **MCP Server**: `mcp` Python SDK (FastMCP) + Starlette, Streamable HTTP at `POST /mcp`
- **Auth**: HS256 JWT — self-signed tokens, validated by the server
- **Tools**: Auto-discovered via MCP protocol — add tools to the server, agent sees them automatically
- **Agent**: Interactive CLI that connects to the MCP server and uses OpenAI for reasoning

## Project Structure

```
mcp-server/
├── pyproject.toml
├── Dockerfile
├── .env.example
├── .env                        ← your secrets (git-ignored)
├── scripts/
│   └── generate_token.py       ← mint JWT tokens
├── src/                        ← MCP Server
│   ├── __init__.py
│   ├── config.py               ← loads env vars
│   ├── auth.py                 ← HS256 JWT verifier
│   ├── server.py               ← FastMCP + Starlette app
│   └── tools/
│       ├── __init__.py
│       ├── weather.py          ← Open-Meteo weather tool
│       └── crypto.py           ← CoinGecko Bitcoin price tool
└── agent/                      ← AI Agent
    ├── __init__.py
    ├── mcp_client.py           ← connects to MCP server
    ├── llm.py                  ← OpenAI integration
    └── main.py                 ← interactive CLI
```

---

## Prerequisites

| Tool | Install |
|---|---|
| Python 3.12+ | `brew install python@3.12` |
| uv (package manager) | `brew install uv` |
| Docker (optional) | Docker Desktop for Mac |

## Setup

### 1. Install dependencies

```bash
cd /path/to/mcp-server
uv sync
```

This installs all Python packages (`mcp`, `httpx`, `pyjwt`, `uvicorn`, `openai`, etc.) into a local `.venv`.

### 2. Create your `.env` file

```bash
cp .env.example .env
```

### 3. Generate a JWT secret

```bash
openssl rand -hex 32
```

Paste the output as `JWT_SECRET` in `.env`.

### 4. Generate a JWT token

```bash
uv run python scripts/generate_token.py
```

This prints a JWT token valid for 30 days. Paste it as `MCP_TOKEN` in `.env`.

Options:
```bash
uv run python scripts/generate_token.py --sub amit --days 60
```

### 5. Add your API keys to `.env`

```
JWT_SECRET=<from step 3>
MCP_SERVER_URL=http://localhost:8000/mcp
MCP_TOKEN=<from step 4>
OPENAI_API_KEY=<your OpenAI API key>
```

---

## MCP Server

### Start the server

```bash
uv run uvicorn src.server:app --port 8000
```

The server runs at `http://localhost:8000`. Press `Ctrl+C` to stop.

### Test the server

#### Health check

```bash
curl http://localhost:8000/health
```

Expected: `{"status":"ok"}`

#### Test unauthenticated access (should be blocked)

```bash
curl -s -w "\nHTTP %{http_code}\n" -X POST http://localhost:8000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
```

Expected: `401 Unauthorized`

#### Test authenticated access

```bash
TOKEN=$(uv run python scripts/generate_token.py)

# Initialize
curl -s -X POST http://localhost:8000/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | python3 -m json.tool
```

Expected: JSON with `protocolVersion`, `capabilities`, and `serverInfo`.

#### List tools

```bash
curl -s -X POST http://localhost:8000/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":2}' | python3 -m json.tool
```

Expected: Two tools — `get_weather` and `get_bitcoin_price` with their input schemas.

#### Call the Bitcoin price tool

```bash
curl -s -X POST http://localhost:8000/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"tools/call","id":3,"params":{"name":"get_bitcoin_price","arguments":{"currency":"usd"}}}' | python3 -m json.tool
```

Expected: Current Bitcoin price in USD.

#### Call the weather tool (needs API key)

```bash
curl -s -X POST http://localhost:8000/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"tools/call","id":4,"params":{"name":"get_weather","arguments":{"city":"London"}}}' | python3 -m json.tool
```

The weather tool uses the free [Open-Meteo API](https://open-meteo.com/) — no API key required.

---

## AI Agent

The agent connects to the MCP server, auto-discovers tools, and uses OpenAI GPT-4o to decide when to call them.

### Run the agent

Make sure the MCP server is running first (in a separate terminal), then:

```bash
uv run python -m agent.main
```

Expected output:

```
Connecting to MCP server...
Connected to: MCP Server v1.26.0
Available tools: get_weather, get_bitcoin_price

Type your question (or 'quit' to exit):
```

### Example prompts

| Prompt | What happens |
|---|---|
| `What's the Bitcoin price?` | Agent calls `get_bitcoin_price` → returns price |
| `What is Bitcoin trading at in EUR?` | Agent calls `get_bitcoin_price(currency="eur")` |
| `What's the weather in London?` | Agent calls `get_weather(city="London")` (needs API key) |
| `What's 2 + 2?` | Agent answers directly — no tool call |
| `quit` | Exits the agent |

Tool calls are visible in real time:

```
You: What's the Bitcoin price?
  → Calling get_bitcoin_price({"currency": "usd"})
  ← Result received
Assistant: Bitcoin is at $70,487 USD, up 3.3% in the last 24h.
```

---

## MCP Inspector (Debugging UI)

[MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the official interactive debugging tool for MCP servers. It provides a web UI to connect, list tools, call them, and inspect raw JSON-RPC messages.

### Launch the Inspector

No install needed — run it directly with `npx`:

```bash
npx @modelcontextprotocol/inspector
```

This opens a web UI at `http://localhost:6274`.

### Connect to Your Server

1. **Transport Type**: Select **Streamable HTTP**
2. **URL**: `http://localhost:8000/mcp`
3. **Authentication**: Add a header:
   - **Header Name**: `Authorization`
   - **Header Value**: `Bearer <your MCP_TOKEN from .env>`

> **Important**: The token value must start with `Bearer ` (with a space) followed by your JWT token.

4. Click **Connect**

### What You Can Do

- **List Tools** — view all registered tools (`get_weather`, `get_bitcoin_price`, etc.)
- **Call Tools** — fill in arguments and execute tool calls interactively
- **Inspect Messages** — view raw JSON-RPC request/response traffic
- **Test Auth** — verify your JWT tokens are working correctly

### Stop the Inspector

Press `Ctrl+C` in the terminal where the inspector is running.

---

## Docker (Optional)

### Build and run

```bash
docker build -t mcp-server .
docker run -p 8000:8000 --env-file .env mcp-server
```

This builds a container image and runs the server on port 8000. The `--env-file .env` passes your secrets into the container.

### Test

Same curl commands as above — the server is at `http://localhost:8000`.

---

## Expose to Internet (Optional)

### Cloudflare Tunnel (no account needed)

```bash
brew install cloudflared
cloudflared tunnel --url http://localhost:8000
```

This gives you a public `https://*.trycloudflare.com` URL. No signup required. Stop with `Ctrl+C`.

### Cloud Run (Google Cloud)

```bash
gcloud run deploy mcp-server --source . --allow-unauthenticated
```

One command deploy. Tear down with:

```bash
gcloud run services delete mcp-server
```

---

## Adding New Tools

1. Create a new file in `src/tools/` (e.g. `src/tools/my_tool.py`)
2. Write an async function with type hints and a docstring:

```python
async def my_tool(param: str) -> dict:
    """Description shown to the LLM."""
    return {"result": "..."}
```

3. Register it in `src/server.py`:

```python
from src.tools.my_tool import my_tool
mcp.tool()(my_tool)
```

4. Restart the server — the agent auto-discovers the new tool on next startup.