Skip to main content
Glama
Riteshpcs1994

MCP Production

README.md
# MCP Production — Model Context Protocol + OpenAI

Production-ready MCP server with FastAPI, Redis session memory, streaming, retries, rate limiting, and structured logging. Managed with **uv**.

---

## Project Structure

```
mcp_production/
├── app/
│   ├── main.py                  # FastAPI app factory
│   ├── config.py                # Centralised settings (pydantic-settings)
│   ├── logger.py                # Structured JSON logging (structlog)
│   ├── api/
│   │   ├── routes.py            # All route handlers
│   │   └── schemas.py           # Pydantic request/response models
│   ├── core/
│   │   ├── mcp_loop.py          # Agentic loop (blocking + streaming)
│   │   └── openai_client.py     # OpenAI client with retry
│   ├── tools/
│   │   ├── base.py              # BaseTool + ToolRegistry
│   │   ├── weather.py           # get_weather tool
│   │   ├── calculator.py        # calculate tool (sympy)
│   │   └── wiki.py              # search_wiki tool
│   └── memory/
│       └── session.py           # Redis-backed session memory
├── tests/
│   ├── test_tools.py            # Tool unit tests
│   └── test_api.py              # API integration tests
├── scripts/
│   ├── run_dev.sh               # Dev server (hot reload)
│   ├── run_prod.sh              # Production server (multi-worker)
│   └── test.sh                  # Run test suite
├── pyproject.toml               # uv project + dependencies
├── .env.example                 # Environment variable template
├── docker-compose.yml           # Local dev stack (app + Redis)
└── Dockerfile                   # Multi-stage Docker build
```

---

## Quick Start

### 1. Install uv

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

### 2. Clone and set up

```bash
git clone <repo>
cd mcp_production

# Install all dependencies
uv sync

# Copy and fill in env vars
cp .env.example .env
# Edit .env — add OPENAI_API_KEY at minimum
```

### 3. Start Redis

```bash
# Option A: Docker Compose (recommended)
docker-compose up redis -d

# Option B: Local Redis
brew install redis && redis-server
```

### 4. Run the server

```bash
# Development (hot reload)
bash scripts/run_dev.sh

# Or directly:
uv run uvicorn app.main:app --reload
```

Server starts at **http://localhost:8000**
API docs at **http://localhost:8000/docs**

---

## API Endpoints

### `POST /api/v1/chat` — Blocking

```bash
curl -X POST http://localhost:8000/api/v1/chat \
  -H "Content-Type: application/json" \
  -d '{
    "message": "What is the weather in Tokyo and calculate 17 * 4?",
    "session_id": "user-123"
  }'
```

Response:
```json
{
  "answer": "The weather in Tokyo is 22°C, sunny. And 17 * 4 = 68.",
  "session_id": "user-123",
  "turns": 2,
  "tools_called": ["get_weather", "calculate"],
  "total_tokens": 312
}
```

### `POST /api/v1/chat/stream` — Streaming SSE

```bash
curl -N -X POST http://localhost:8000/api/v1/chat/stream \
  -H "Content-Type: application/json" \
  -d '{"message": "Search Wikipedia for Python", "session_id": "user-123"}'
```

Events:
```
data: {"type": "tool_call",   "name": "search_wiki", "args": {"query": "Python"}}
data: {"type": "tool_result", "name": "search_wiki", "content": "Python is..."}
data: {"type": "token",       "content": "Python "}
data: {"type": "token",       "content": "is a..."}
data: {"type": "done",        "turns": 2, "tools": ["search_wiki"]}
```

### `DELETE /api/v1/session/{session_id}` — Clear History

```bash
curl -X DELETE http://localhost:8000/api/v1/session/user-123
```

### `GET /api/v1/tools` — List Tools

```bash
curl http://localhost:8000/api/v1/tools
```

### `GET /api/v1/health` — Health Check

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

---

## Adding a New Tool

1. Create `app/tools/my_tool.py`:

```python
from app.tools.base import BaseTool

class MyTool(BaseTool):
    name = "my_tool"

    def schema(self) -> dict:
        return {
            "type": "function",
            "function": {
                "name": self.name,
                "description": "Does something useful.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "input": {"type": "string", "description": "Input value"}
                    },
                    "required": ["input"]
                }
            }
        }

    async def execute(self, input: str) -> str:
        return f"Result for: {input}"
```

2. Register in `app/tools/__init__.py`:

```python
from app.tools.my_tool import MyTool
registry.register(MyTool())
```

That's it — the tool is automatically included in all API calls.

---

## Running Tests

```bash
bash scripts/test.sh

# Or with uv directly:
uv run pytest tests/ -v
```

---

## Docker (Full Stack)

```bash
docker-compose up --build
```

---

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `OPENAI_API_KEY` | required | Your OpenAI API key |
| `OPENAI_MODEL` | `gpt-4o-mini` | Model to use |
| `REDIS_URL` | `redis://localhost:6379` | Redis connection URL |
| `SESSION_TTL_SECONDS` | `3600` | Session memory TTL |
| `APP_ENV` | `development` | `development` or `production` |
| `RATE_LIMIT_PER_MINUTE` | `20` | Requests per minute per IP |
| `OPENWEATHER_API_KEY` | _(mock used)_ | Real weather API key |