Tutor Lesson-Script & Transcript Retrieval MCP
README.md
# Tutor Lesson-Script & Transcript Retrieval MCP Server
A minimal remote [MCP](https://modelcontextprotocol.io) server exposing **one tool**,
`get_lesson_script`, that returns the markdown tutor script for a
lesson from `ps_lessons.script` on your Supabase project.
Retrieval only — no scoring or rubric logic.
## The tool
`get_lesson_script(lesson: str) -> str`
Accepts a lesson **code** (`"401.1"`, `"413.2"`), a **concept** (`"Suffix s"`, `"sh"`),
or a **name** (`"Orientation"`, `"Unit 1 Recap"`, `"Unit 3 Passwords and Play"`).
It is scoped to `curriculum_id = your-curriculum-uuid`.
Behavior:
- **One match** → returns that lesson's markdown script.
- **No match** → a short message with the closest lesson names.
- **Several matches** (e.g. `"401"` → `401.1` and `401.2`) → lists them and asks you to be specific.
- **Match but empty script** → says the lesson exists but isn't populated yet.
## Environment variables
| Variable | Purpose |
|---|---|
| `SUPABASE_URL` | `https://your-project-ref.supabase.co` |
| `SUPABASE_SERVICE_ROLE_KEY` | Supabase → Project Settings → API → `service_role` key. Server-side only. |
| `CURRICULUM_ID` | The curriculum UUID this tool is scoped to (set your own). |
| `CACHE_TTL` | Optional; seconds to cache lesson rows (default 60). |
Copy `.env.example` to `.env` and fill in the key for local runs.
## Run locally
```bash
pip install -r requirements.txt
export $(grep -v '^#' .env | xargs) # or set the vars however you prefer
python server.py # serves Streamable HTTP at http://localhost:8000/mcp
```
Test it with the MCP Inspector (no code needed):
```bash
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP URL: http://localhost:8000/mcp
# then call get_lesson_script with {"lesson": "401.1"}
```
(Opening `/mcp` in a browser returns HTTP 406 — that's expected; it needs MCP headers.)
## Deploy on Render
1. Push this repo to GitHub.
2. Render dashboard → **New → Blueprint**, point at the repo (uses `render.yaml`).
Or **New → Web Service** with build `pip install -r requirements.txt` and start `python server.py`.
3. Set env vars `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, and `CURRICULUM_ID` in
the Render dashboard.
4. Deploy. Your endpoint is **`https://<service-name>.onrender.com/mcp`**.
> Free Render instances sleep after inactivity and cold-start in ~30–60s. Hit the
> endpoint once to warm it before a live demo, or use a paid instance.
## Add as a Claude custom connector
In Claude → **Customize → Connectors → + → Add custom connector**:
- **Name:** Tutor Lesson Scripts
- **URL:** `https://<service-name>.onrender.com/mcp`
- Leave OAuth blank (this server is unauthenticated — see note below).
Then enable it per chat via the **+** button → **Connectors**, and ask, e.g.,
*"Use Tutor Lesson Scripts to get the script for lesson 401.1."*
(On Team/Enterprise plans an Owner adds the connector under Organization Settings →
Connectors first, then members connect.)
## Demo suggestion
Warm the endpoint, then in a fresh chat: *"Get me the tutor script for 418.1."* →
Claude returns the markdown (spell step reads hills → jets → tan → call, etc.).
Pick and rehearse one lesson beforehand so the demo is smooth.
## Security note
This server is unauthenticated for a dev demo: anyone with the URL can read lesson
scripts, and the service-role key stays on the server (never sent to clients).
Before anything beyond dev, put it behind OAuth (Claude custom connectors support an
OAuth Client ID/Secret) or a gateway, and/or use a restricted key.
---
# Phase 3: Transcript retrieval + Google auth
Two read-only PencilSpaces tools, and Google OAuth restricting the **whole server**
(all three tools) to `@yourdomain.org`.
## New tools
- `list_recordings(start_date, end_date)` — recordings in a date range, each labeled
with its lesson name (space title). Dedupes space lookups. Returns structured rows:
`recordingId, spaceId, lessonName, date, durationMinutes, hostUserIds`.
- `get_transcript(recording_id, space_id=None)` — the diarized transcript plus lesson
name and host userId(s). `status` is one of `ready | processing | empty | legacy |
invalid_id | not_authorized | error`. Segments are diarized by opaque `userId`
(no name resolution — the eval skill corrects PS misattributions).
Flow: `list_recordings` → user picks one → `get_transcript` → the `tutor-eval`
skill calls `get_lesson_script`. Pass `spaceId` from the list into `get_transcript`
so it doesn't re-fetch the space.
## Two auth layers
- **User → server:** Google OAuth, gated to `@yourdomain.org` (below).
- **Server → PencilSpaces:** `PENCIL_SPACES_TOKEN` Bearer secret. The token's account
must be a **host** of the spaces, or the endpoints 401.
## New environment variables
`SERVER_BASE_URL` (this server's public HTTPS URL), `GOOGLE_OAUTH_CLIENT_ID`,
`GOOGLE_OAUTH_CLIENT_SECRET`, `ALLOWED_EMAIL_DOMAIN` (e.g. `yourdomain.org`),
`JWT_SIGNING_KEY` (random, stable), `PENCIL_API_BASE`, `PENCIL_SPACES_TOKEN`.
## Google Cloud OAuth setup
1. In the **yourdomain.org Google Workspace**, create/select a Google Cloud project
(must be under that org so "Internal" is available).
2. **APIs & Services → OAuth consent screen** → **Internal** (this is the primary
domain gate — only `@yourdomain.org` accounts can complete sign-in). Add scopes
`openid` and `.../auth/userinfo.email`.
3. **APIs & Services → Credentials → Create credentials → OAuth client ID** →
**Web application**. Set:
- Authorized JavaScript origins: `https://your-service-name.onrender.com`
- Authorized redirect URI: `https://your-service-name.onrender.com/auth/callback`
(must match exactly; `/auth/callback` is FastMCP's default).
4. Copy the Client ID (`…apps.googleusercontent.com`) and Secret (`GOCSPX-…`) into
Render env vars. The server allows Claude's callback
(`https://claude.ai/api/mcp/auth_callback`) as a client redirect.
## PencilSpaces
Set `PENCIL_API_BASE` (confirm the exact API host) and `PENCIL_SPACES_TOKEN`
(a host account's Bearer token) as Render secrets. Read-only; never writes.
## Connector re-connection
Turning on auth changes the existing connector: users must **re-add / re-authorize**
it in Claude and sign in with Google. That's expected — it's the gate for handling
real transcripts.
## Testing with auth on
Use the MCP Inspector's OAuth flow, or the FastMCP client:
`Client("https://…onrender.com/mcp", auth="oauth")`. First connect opens Google login.
## Verify against a real recording (one-time)
The PencilSpaces response field names in `pencilspaces.py` follow the phase spec but
were not verified against a live payload. On the first real recording, confirm:
- the recordings list key (`recordings`) and fields (`recordingId`, `spaceId`,
`startingTime`, `duration`);
- transcript state markers (`code`/`status` == `TRY_AGAIN_LATER`; `segments` null vs `[]`);
- that the host `userId` from the space's `hosts` array matches the `userId` used in
transcript segments (this overlap is what the role-tagging rests on). If the host
never spoke or there are multiple hosts, note it — don't try to solve it now.
## Privacy (by design)
`userId` pass-through, no name resolution — names never enter Claude. Real student
transcripts: run evaluations in your own account and delete prototype conversations
when done; no shared/retained transcript stores.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues