Skip to main content
Glama
snyderandrew35

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.