Toddle Timetable MCP Server
README.md
# Toddle Timetable MCP Server
A minimal [Model Context Protocol](https://modelcontextprotocol.io) server that lets Claude
look up a student's daily class schedule. It speaks the **Streamable HTTP** transport, so it
can be added to Claude as a custom connector.
Ships with a built-in mock timetable — no API credentials needed to try it.
## Tool
| Tool | Input | Output |
| --- | --- | --- |
| `get_daily_timetable` | `date` — a day formatted `YYYY-MM-DD` | Markdown summary, one line per period (`09:00 — Mathematics (Room 2B, Ms. Rao)`) |
Weekends and dates listed in `DATE_OVERRIDES` return a "no classes scheduled" message.
A malformed date returns a hint rather than raising.
## Run locally
Requires **Python 3.10+** (FastMCP does not support 3.9).
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python server.py
```
The server listens on `http://0.0.0.0:8000/mcp`. Override with the `HOST` and `PORT`
environment variables. `GET /` returns a small JSON health payload.
> **Use the endpoint URL with no trailing slash.** `/mcp/` answers with a `307` redirect to
> `/mcp`; some clients will not replay a POST body across a redirect. Always give clients
> exactly `.../mcp`.
## Authentication
There is none, by design. No auth provider is configured, so the server never returns `401`,
never sends a `WWW-Authenticate` header, and serves no OAuth metadata — the
`/.well-known/oauth-*` paths all return `404`. Any MCP client can connect anonymously.
That also means anyone with the URL can call the tool. Fine for mock data; add
[FastMCP auth](https://gofastmcp.com/servers/auth/authentication) before wiring in real
student records.
### Verify it's up
```bash
npx @modelcontextprotocol/inspector
```
Point the inspector at `http://localhost:8000/mcp`, transport **Streamable HTTP**, then call
`get_daily_timetable` with `{"date": "2026-08-10"}`.
### Connect to Claude
Settings → Connectors → Add custom connector → URL `http://localhost:8000/mcp`. For Claude on
the web the URL must be publicly reachable — deploy it (below) or tunnel with `ngrok http 8000`.
## Swapping in real data
Everything mock-specific lives in two places in `server.py`:
- `WEEKLY_TIMETABLE` / `DATE_OVERRIDES` — the sample data.
- `fetch_timetable(day)` — the single seam the tool calls.
Replace the body of `fetch_timetable` with your API call and keep the return shape: a list of
`{"time", "subject", "room", "teacher"}` dicts. Read credentials from the environment, never
hard-code them:
```python
import os, requests
def fetch_timetable(day):
resp = requests.get(
"https://api.example.com/timetable",
params={"date": day.isoformat()},
headers={"Authorization": f"Bearer {os.environ['TODDLE_API_TOKEN']}"},
timeout=10,
)
resp.raise_for_status()
return sorted(resp.json()["entries"], key=lambda e: e["time"])
```
## Deploy
The server reads `PORT` from the environment and binds `0.0.0.0`, which is what most PaaS
providers expect.
### Render
New → Web Service → point at this repo, then:
- **Build command:** `pip install -r requirements.txt`
- **Start command:** `python server.py`
Render injects `PORT` automatically. A `Procfile` is included for platforms that use one
(Railway, Heroku). Your connector URL is `https://<your-service>.onrender.com/mcp`.
### Fly.io / containers
```dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server.py .
CMD ["python", "server.py"]
```
### A note on Vercel
Vercel's Python runtime is serverless and request-scoped, which doesn't fit a long-lived
Streamable HTTP MCP session cleanly. Prefer Render, Railway, or Fly.io. If you must use Vercel,
use their `mcp-handler` adapter rather than `server.py` as-is.
## Project layout
```
server.py # MCP server, mock provider, and the get_daily_timetable tool
requirements.txt # fastmcp, requests, uvicorn
Procfile # start command for PaaS deploys
```
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues