@teachinglabstudio/scm-mcp
v0.3.0
Published
MCP server exposing the SCM REST API to Claude Desktop and other MCP clients.
Maintainers
Readme
@teachinglabstudio/scm-mcp
MCP server that exposes the SCM REST API to Claude Desktop (and other MCP clients) as tools. Lives next to the SCM web/api packages so it ships in lockstep with their DTOs and routes.
Your Mac Cloud
|
Claude Desktop ── stdio JSON-RPC ── scm-mcp ── HTTPS ──────▶ SCM API
(Anthropic app) (Node) Bearer scma_… (your Railway/wherever)What you get
70 tools total (v0.3.0). Every call goes through SCM's existing scope + role guards — Claude sees exactly what the human admin sees, nothing more.
Reads (31):
me,list_my_capabilities,list_my_integrations,list_oauth_clientslist_districts,get_district,list_schools,get_school,list_classrooms,get_classroomlist_staff,get_staff,list_students,get_studentlist_materials,get_material,list_lessons,get_lesson,list_kcs,get_kclist_assignments,get_assignment,list_student_groups,get_student_group,list_goals,get_goallist_lesson_progress,get_lesson_progress,get_lesson_progress_dashboard,list_kc_progress,get_kc_progress
Goal + Group lifecycle (7): create_goal, update_goal, approve_goal, create_student_group, add_group_member, approve_student_group, generate_group_recommendations.
Admin / curriculum / progress writes (19): create+update for districts, schools, classrooms, students, materials, lessons, KCs; update_staff; create_assignment; update_student_group; update_lesson_progress; update_kc_progress. ADMIN-only at SCM (group/progress mutations TEACHER+).
Destructive ops (13): delete_district, delete_school, delete_classroom, delete_student, delete_staff, delete_material, delete_lesson, delete_kc, delete_goal, delete_student_group (all soft — set deletedAt); delete_assignment, remove_group_member (hard — recoverable by re-creating); disconnect_integration (revoke an agent token). Every description starts with DESTRUCTIVE — only call when explicitly told for LLM steering. ADMIN-only (group member removal TEACHER+; integration revoke owner-only). Every call audit-logged with actorKind=agent. See DEC-035.
Deliberately not exposed: create_staff (requires upstream Supabase Auth user) and connect_integration (would let an agent provision its own bearer). The full RFC OAuth flow endpoints (/oauth/authorize, /oauth/token, etc.) aren't tools either — they're for external client wallets, not for an agent acting as a user.
Install
Once published to npm
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"scm": {
"command": "npx",
"args": ["-y", "@teachinglabstudio/scm-mcp"],
"env": {
"SCM_API_URL": "https://scm.example.com",
"SCM_TOKEN": "scma_replace-me"
}
}
}
}Restart Claude Desktop. The scm server should appear in Claude's tool drawer.
Before npm publish (local-only smoke)
git clone [email protected]:TeachingLabHQ/SCM-SCHEMA.git
cd SCM-SCHEMA
pnpm install
pnpm --filter @teachinglabstudio/scm-mcp buildThen point Claude Desktop at the built file:
{
"mcpServers": {
"scm": {
"command": "node",
"args": ["/absolute/path/to/SCM-SCHEMA/mcp/dist/scm-mcp.mjs"],
"env": {
"SCM_API_URL": "http://localhost:3001",
"SCM_TOKEN": "scma_replace-me"
}
}
}
}Get a token
- Sign in to the SCM web app as yourself.
- Go to /integrations.
- Click Connect Claude.
- Optionally label it (e.g. "MacBook Claude Desktop").
- Click Connect — a one-time bearer token (
scma_…) appears. - Copy it into the
SCM_TOKENfield above. It is shown only once. - Restart Claude Desktop so it picks up the new env var.
Tokens default to a 90-day lifetime. When yours expires you'll see a friendly message in Claude. Mint a new one at /integrations and update your config.
Troubleshooting
"missing required env vars"
You forgot SCM_API_URL or SCM_TOKEN in your config. Edit, save, fully quit Claude Desktop (not just close the window), and relaunch.
"SCM_TOKEN must be a SCM agent token (starts with scma_)"
You probably pasted a Supabase JWT instead of an integration token. Tokens that work here come from /integrations, not from the browser session.
"Your SCM token is invalid, expired, or has been revoked" Two paths: (a) it's been more than 90 days since you minted, or (b) someone (you?) hit Disconnect on /integrations. Mint a new one and update your config.
"Out of scope for your role" You hit a row outside your district/school scope. SCM enforces scope server-side; this is working as designed. Pivot to a row you have access to.
Nothing shows up in Claude Desktop Three things to check:
- Is the process actually running? Tail Claude Desktop's MCP logs (
~/Library/Logs/Claude/mcp-server-scm.logor similar). - Did you fully restart Claude Desktop after editing the config? File watchers don't pick up MCP server changes.
- Run the binary by hand:
SCM_API_URL=http://localhost:3001 SCM_TOKEN=scma_xxx node /path/to/scm-mcp.mjs. If it doesn't print a JSON line on stderr and wait for stdin, the install is broken.
Development
cd mcp
pnpm install
pnpm test # 46 tests across factory + client + config + tool integration
pnpm typecheck
pnpm build # esbuild → dist/scm-mcp.mjs (single file, deps inlined)
pnpm dev # tsx-driven watch mode for hacking on toolsAdding a new tool
src/tools/reads.ts (or writes.ts) — add a ToolSpec:
export const list_widgets: ToolSpec<typeof shape> = {
name: 'list_widgets',
description: 'What this does + when to call it.',
inputSchema: { schoolId: z.string().uuid().optional(), cursor, limit },
method: 'GET',
pathTemplate: '/widgets',
queryArgs: ['schoolId', 'cursor', 'limit'],
};Then add it to the READ_TOOLS (or WRITE_TOOLS) array. That's it — defineTool handles fetching, error mapping, and result framing.
Architecture in 60 seconds
src/index.ts — boot, env validation, server setup, register every tool spec.
src/define-tool.ts — factory that turns a declarative spec into an McpServer.registerTool call.
src/scm-client.ts — fetch wrapper that attaches the bearer, masks the token in logs, maps HTTP status codes to user-facing messages.
src/config.ts — env reader; fails loudly if SCM_API_URL or SCM_TOKEN is missing or malformed.
src/logger.ts — structured stderr logger. Stdout is the protocol channel — never write user-facing output there.
src/tools/reads.ts — 30 read-only tool specs.
