chore: ai, public course and other ui improvements

This commit is contained in:
Rotimi Best
2026-05-09 06:05:03 +01:00
parent 5717dd8004
commit c31ad63955
318 changed files with 20668 additions and 4788 deletions
+749
View File
@@ -0,0 +1,749 @@
---
name: MCP Course Authoring from Structured Payloads
overview: Build an MCP-compatible authoring surface that lets external AI clients such as Claude send normalized course structures into ClassroomIO, save them as reviewable drafts, revise them, and publish them into real ClassroomIO courses.
todos:
- id: prd-review
content: Finalize PRD scope, product policy, and success metrics with stakeholders
status: completed
- id: auth-model
content: Define shared organization automation key system reused by MCP and Public API
status: completed
- id: import-schema
content: Finalize normalized course payload schema shared by prompt and PDF-derived flows
status: completed
- id: import-validation
content: Add validation module for structured draft create, update, and publish requests
status: completed
- id: import-data-model
content: Add draft persistence tables and query layer
status: completed
- id: import-api
content: Add organization-scoped draft and publish routes
status: completed
- id: mcp-package
content: Build MCP package with stdio transport and ClassroomIO tool adapters
status: completed
- id: publish-flow
content: Implement draft-to-course publish orchestration using existing course, section, lesson, and language services
status: completed
- id: idempotency
content: Add idempotency keys and duplicate publish guards for MCP-triggered imports
status: completed
- id: observability
content: Add structured logs, audit trail, and rate limits for MCP tools
status: completed
- id: docs
content: Document MCP tool contracts, auth setup, payload schema, and agent responsibilities
status: completed
isProject: false
---
# MCP Course Authoring from Structured Payloads
## Status
- Done (shipped)
## Date
- March 12, 2026
## Purpose
Enable external AI clients such as Claude to create and edit ClassroomIO courses through MCP by sending normalized course structures into ClassroomIO.
The key product outcome is:
1. Claude or another agent reads source material externally
2. Claude turns that material into structured course JSON
3. MCP sends that JSON to ClassroomIO
4. ClassroomIO stores it as a draft
5. the user reviews and explicitly publishes it
This supports prompts like:
- "Create a complete course on exponential functions in Classroom."
- "Read this PDF, extract the course structure, and publish it into Classroom."
with one important boundary:
**In v1, ClassroomIO does not parse or extract PDFs itself.**
## Problem Statement
ClassroomIO already has the core CRUD primitives needed to create courses and content, but it does not yet expose a clean automation layer for external AI agents.
Current limitations:
- external AI clients have no MCP-compatible tool surface
- there is no normalized course payload contract for external agents
- there is no first-class draft persistence model for AI-generated course structures
- there is no explicit review/publish workflow for machine-generated content
- there is no stable API contract for "here is a complete course structure, store it safely"
This means agents can only create ClassroomIO content through brittle, record-by-record automation instead of a deliberate authoring pipeline.
## Core Product Decision
**Claude or the external agent is responsible for extraction. ClassroomIO is responsible for validation, persistence, review, authorization, and publish.**
That means:
- if the source is a PDF, Claude reads it
- if the source is a website, Claude reads it
- if the source is a prompt, Claude expands it
- ClassroomIO only receives structured payloads
## Primary Use Cases
### Use Case 1: Prompt to Structured Draft
A teacher asks Claude to create a course on a topic. Claude reasons about the content and sends a normalized course payload to ClassroomIO through MCP.
### Use Case 2: PDF to Structured Draft
A teacher provides one or more PDFs to Claude. Claude extracts the material and converts it into a normalized course payload. MCP sends that payload to ClassroomIO.
### Use Case 3: Draft Revision
After initial creation, Claude updates the draft by:
1. renaming sections
2. rewriting lesson content
3. reordering lessons
4. adding or removing lessons
5. publishing when the teacher confirms
## Goals
1. Provide an MCP-compatible authoring surface for external AI clients.
2. Keep ClassroomIO as the source of truth for auth, permissions, validation, persistence, and publishing.
3. Standardize a normalized course payload that any external agent can submit.
4. Make draft review mandatory before publish in v1.
5. Ensure org-scoped, permission-safe authoring with no direct DB access from the MCP server.
6. Support both prompt-derived and PDF-derived content through one shared payload contract.
7. Preserve human control by returning warnings and requiring explicit publish.
## Non-Goals (v1)
- Student-facing AI workflows
- Direct DB writes from the MCP server
- Full autonomous publishing without a review step
- ClassroomIO-side PDF extraction, OCR, or parsing
- Async PDF extraction workers
- Cross-organization or cross-course content access
- Full LMS migration parity with Teachable/Thinkific/SCORM in this PRD
- Perfect fidelity for tables, diagrams, formulas, or source formatting
## Confirmed Product Decisions
1. MCP is an orchestration layer, not the persistence layer.
2. ClassroomIO API owns validation, authorization, draft storage, and publish.
3. Draft-first is required; AI-generated content is not published immediately in v1.
4. External agents submit structured payloads; ClassroomIO does not ingest raw PDFs in v1.
5. The MCP server should start as a thin tool adapter and not require its own model provider.
6. Lesson long-form content should be stored in lesson language content, not only `lesson.note`.
7. Publish remains an explicit second step.
## Current-State Audit
| Capability | Current State | Notes |
| --- | --- | --- |
| Course creation | Exists via `POST /course` | Creates course shell only |
| Section creation | Exists via `POST /course/:courseId/section` | One section at a time |
| Lesson creation | Exists via `POST /course/:courseId/lesson` | One lesson at a time |
| Lesson language content | Exists via lesson-language routes | Good fit for generated lesson body |
| Course content reorder | Exists via content/section/lesson reorder routes | Useful after generation |
| Document upload | Exists for lesson documents | Not needed for v1 MCP ingestion |
| AI course assistant | PRD exists | Focused on in-app chat, not MCP |
| Public API keys | PRD exists | Likely dependency for auth model |
| Draft persistence for AI-generated course structures | Does not exist yet | Must be added |
## Data Sources Checked
- [apps/api/src/routes/course/course.ts](/Users/rotimibest/_pros/classroomio/apps/api/src/routes/course/course.ts)
- [apps/api/src/routes/course/section.ts](/Users/rotimibest/_pros/classroomio/apps/api/src/routes/course/section.ts)
- [apps/api/src/routes/course/lesson.ts](/Users/rotimibest/_pros/classroomio/apps/api/src/routes/course/lesson.ts)
- [apps/api/src/routes/course/lesson-language.ts](/Users/rotimibest/_pros/classroomio/apps/api/src/routes/course/lesson-language.ts)
- [apps/api/src/services/course/course.ts](/Users/rotimibest/_pros/classroomio/apps/api/src/services/course/course.ts)
- [apps/api/src/services/course/section.ts](/Users/rotimibest/_pros/classroomio/apps/api/src/services/course/section.ts)
- [apps/api/src/services/lesson/lesson.ts](/Users/rotimibest/_pros/classroomio/apps/api/src/services/lesson/lesson.ts)
- [apps/api/src/services/lesson-language.ts](/Users/rotimibest/_pros/classroomio/apps/api/src/services/lesson-language.ts)
- [packages/utils/src/validation/course/course.ts](/Users/rotimibest/_pros/classroomio/packages/utils/src/validation/course/course.ts)
- [packages/utils/src/validation/lesson/lesson.ts](/Users/rotimibest/_pros/classroomio/packages/utils/src/validation/lesson/lesson.ts)
- [packages/utils/src/validation/lesson/language.ts](/Users/rotimibest/_pros/classroomio/packages/utils/src/validation/lesson/language.ts)
- [prd/ai-course-assistant [DONE]/README.md](/Users/rotimibest/_pros/classroomio/prd/ai-course-assistant%20[DONE]/README.md)
- [prd/public-api [DONE]/README.md](/Users/rotimibest/_pros/classroomio/prd/public-api%20[DONE]/README.md)
## User Personas
### Persona A: Solo Teacher
Wants to turn source material into a structured first course quickly, then lightly edit before publish.
### Persona B: Training Operator
Uses Claude to convert internal manuals or SOPs into structured staff training without rebuilding content manually.
### Persona C: Content Team
Uses Claude as an operator to draft or refactor course structures programmatically across multiple organizations and topics.
## Jobs To Be Done
1. Convert source intent or material into a usable draft course structure.
2. Persist AI-generated course structures safely before publish.
3. Review machine-generated content before it affects learner-facing content.
4. Keep content generation inside org permissions and audit boundaries.
5. Avoid brittle record-by-record automation from external agents.
## Product Principles
1. Draft before publish.
2. API-first, not DB-first.
3. One normalized course payload regardless of how the agent derived it.
4. External agents think; ClassroomIO validates and persists.
5. Warnings are first-class output, not hidden logs.
## Proposed Architecture
```mermaid
flowchart TB
subgraph client [AI Client]
Claude["Claude or other MCP client"]
Source["Prompt / PDF / Website / Notes"]
end
subgraph mcp [MCP Server]
Tools["MCP tools"]
Adapter["ClassroomIO API adapter"]
end
subgraph api [ClassroomIO API]
Auth["Org-scoped automation key auth"]
DraftRoutes["Draft routes"]
DraftService["Draft orchestration service"]
PublishService["Draft publish service"]
end
subgraph db [Data Layer]
DraftTable["course_import_draft"]
CourseTables["course / section / lesson / lesson_language"]
end
Source --> Claude
Claude --> Tools
Tools --> Adapter
Adapter --> Auth
Auth --> DraftRoutes
DraftRoutes --> DraftService
DraftService --> DraftTable
DraftService --> PublishService
PublishService --> CourseTables
```
## Scope Overview
### In Scope
- MCP tool surface for structured course authoring workflows
- organization-scoped authentication for MCP clients
- draft persistence and preview APIs
- publish flow from draft to real course content
- warnings, audit logging, and idempotency
### Out of Scope
- raw PDF ingestion by ClassroomIO
- OCR, parsing, or extraction jobs
- rich in-dashboard chat UX
- student preview or learner-side AI guidance
## Functional Requirements
### FR-1 MCP Tool Surface
The MCP server must expose a minimal, stable set of tools for AI clients:
- `create_course_draft`
- `get_course_draft`
- `update_course_draft`
- `publish_course_draft`
- `list_org_courses`
- `get_course_structure`
The MCP server must not write directly to the database.
### FR-2 Authentication and Scoping
All MCP actions must be authenticated and scoped to an organization.
Requirements:
- use organization-scoped automation keys
- reuse the same underlying key system as the Public API
- allow MCP to use a narrower scope preset than generic API access
- every request resolves a single org context
- mutations require org admin or authorized course team membership
- cross-org access must be rejected
### FR-3 Normalized Course Payload
All external agents must submit the same normalized course payload.
Minimum payload shape:
- course metadata
- sections
- lessons
- lesson content by locale
- optional exercises
- source references
- warnings
### FR-4 Draft Review
Generated content must remain in draft state until explicitly published.
The draft API must allow:
- fetching current draft
- patching draft metadata
- adding/removing/reordering sections and lessons
- updating lesson titles and language content
- inspecting warnings before publish
### FR-5 Structured Payload Ingestion
Users must be able to create a draft course by submitting a normalized course payload.
The API must not care whether the payload came from:
- a direct prompt
- a PDF processed by Claude
- a website processed by Claude
- manually authored JSON
### FR-6 Publish Flow
Publishing a draft must create or update real ClassroomIO content through the existing service layer.
Publish orchestration should:
1. create course shell
2. create sections
3. create lessons
4. create lesson-language content
5. optionally create exercises in later scope
6. return created IDs and publish summary
### FR-7 Idempotency and Retry Safety
MCP-triggered operations must be safe to retry.
Requirements:
- accept idempotency key on draft creation and publish
- prevent duplicate course creation on publish retries
### FR-8 Observability
The system must expose enough detail for debugging and support.
Requirements:
- structured logs per draft create/update/publish action
- audit trail for actor, org, tool, and created course IDs
- warning list available to the AI client and internal admins
- rate limits per org and per key
## Data Model
### New Table: `course_import_draft`
Fields:
- `id`
- `organizationId`
- `createdByProfileId`
- `sourceType`
- `status`
- `title`
- `locale`
- `idempotencyKey`
- `summary`
- `draft`
- `warnings`
- `sourceArtifacts`
- `publishedCourseId`
- timestamps
This table stores the normalized draft payload and the publish result.
## Normalized Payload Contract
```ts
type CourseDraft = {
course: {
title: string;
description: string;
type: 'LIVE_CLASS' | 'SELF_PACED';
locale: string;
metadata?: Record<string, unknown>;
};
sections: Array<{
externalId: string;
title: string;
order: number;
}>;
lessons: Array<{
externalId: string;
sectionExternalId: string;
title: string;
order: number;
isUnlocked?: boolean;
public?: boolean;
}>;
lessonLanguages: Array<{
lessonExternalId: string;
locale: string;
content: string;
}>;
exercises?: Array<Record<string, unknown>>;
sourceReferences?: Array<{
type: 'prompt' | 'pdf';
label: string;
pageStart?: number;
pageEnd?: number;
}>;
warnings: Array<{
code: string;
message: string;
severity: 'info' | 'warning' | 'error';
}>;
};
```
## Proposed API Design
### Validation
Add:
- `packages/utils/src/validation/course-import/course-import.ts`
Schemas:
- `ZCourseImportDraftCreate`
- `ZCourseImportDraftGetParam`
- `ZCourseImportDraftUpdate`
- `ZCourseImportDraftPublish`
### Routes
Add organization-scoped route group:
- `apps/api/src/routes/organization/course-import.ts`
Suggested endpoints:
| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/organization/course-import/drafts` | Create draft from normalized payload |
| `GET` | `/organization/course-import/drafts/:draftId` | Get draft |
| `PUT` | `/organization/course-import/drafts/:draftId` | Update draft |
| `POST` | `/organization/course-import/drafts/:draftId/publish` | Publish draft into course |
All routes must return a single response shape and follow existing route conventions.
### Services
Add:
- `apps/api/src/services/course-import/course-import.ts`
Responsibilities:
- draft persistence
- validation of structured payloads
- publish orchestration using current course/section/lesson/language services
## MCP Server Design
### Placement
Recommended:
- `apps/mcp`
or
- `packages/mcp`
Start with stdio transport for Claude Desktop and local agent usage.
### Responsibilities
- expose MCP tools
- validate tool input
- forward requests to ClassroomIO API
- format structured responses for the AI client
### Non-Responsibilities
- direct DB access
- authorization decisions independent of ClassroomIO
- content extraction from source materials
- PDF parsing or OCR
### Tool Contracts
#### `create_course_draft`
Input:
- `organizationId`
- normalized `draft`
- `sourceType`
- `idempotencyKey?`
- `summary?`
- `sourceArtifacts?`
Output:
- `draftId`
- `summary`
- `warnings`
#### `get_course_draft`
Input:
- `draftId`
Output:
- stored draft payload
- summary
- warnings
- publish status
#### `update_course_draft`
Input:
- `draftId`
- patch payload
Output:
- updated summary
- warnings
#### `publish_course_draft`
Input:
- `draftId`
- optional overrides for title/description/type/metadata
Output:
- `courseId`
- `createdSections`
- `createdLessons`
- `localeCount`
## Shared API Key Design
MCP and Public API should reuse the same underlying key infrastructure.
They should not reuse the same default secret for every use case.
Recommended model:
- one shared `organization_api_key` table
- one shared bearer-auth middleware
- different key presets by integration type
Recommended key fields:
- `id`
- `organizationId`
- `type`: `mcp | api | zapier`
- `label`
- `secretPrefix`
- `secretHash`
- `scopes`
- `createdByProfileId`
- `lastUsedAt`
- `expiresAt`
- `revokedAt`
- `createdAt`
- `updatedAt`
Recommended key handling:
- show raw secret once on creation
- store only the hash
- allow multiple active keys per organization
- encourage one key per integration or agent
Recommended preset policy:
- `mcp` preset:
- draft create/read/update
- draft publish
- `api` preset:
- broader route-specific scopes selected by admin
## Route Design
Shared management routes should live under organization automation, not a separate MCP-only surface.
Recommended management routes:
- `GET /organization/automation/keys`
- `POST /organization/automation/keys`
- `DELETE /organization/automation/keys/:keyId`
- `POST /organization/automation/keys/:keyId/rotate`
Recommended request fields for create:
- `type`
- `label`
- `preset`
- `scopes` optional for custom API keys
- `expiresAt` optional
Recommended auth flow for machine requests:
1. client sends `Authorization: Bearer <key>`
2. middleware hashes key
3. API resolves organization from stored key row
4. API attaches scopes and key metadata to context
5. route checks scope and org permissions
## Automation UI Design
The management UI should live in the org sidebar as `Automation`.
Tabs:
- `MCP`
- `Zapier` disabled, coming soon
- `API` disabled, coming soon until public API ships
MCP tab responsibilities:
- explain what MCP does
- generate org-scoped MCP keys
- list active MCP keys
- revoke or rotate keys
- show setup snippets for:
- Claude Code
- Codex
- Cursor
Permissions:
- org admins can create, rotate, revoke
- teachers can open the page but see the controls disabled
- all server mutations remain admin-only
## UX and Review Model
The review loop is:
1. external agent creates draft
2. user inspects summary and warnings
3. external agent revises draft if needed
4. user explicitly publishes
The AI client should never assume silent publish in v1.
## Failure Modes and Warnings
The system must capture and expose at least these issues:
- duplicate external IDs
- lessons referencing missing sections
- lesson-language entries referencing missing lessons
- empty or invalid titles/content
- invalid metadata shape
- duplicate publish attempts
Publishing should be blocked on validation errors. Informational warnings may still allow publish.
## Security Considerations
1. Reject cross-org access for all draft and publish routes.
2. Enforce org admin or equivalent authoring permissions.
3. Log key ID, key type, organization, and output course IDs for all MCP-triggered publishes.
4. Apply rate limits per org and per key.
5. Keep the MCP server outside the database trust boundary.
## Success Metrics
### Product Metrics
1. Time from agent output to first draft available
2. Draft-to-publish conversion rate
3. Average manual edits before publish
4. Percentage of generated drafts published within 24 hours
### Quality Metrics
1. Percentage of drafts requiring no structural edits
2. Publish failure rate
3. Duplicate publish rate
4. Support tickets related to malformed structure payloads
## Rollout Plan
### Phase 1: Draft and Publish API
Ship:
- normalized payload schema
- draft create endpoint
- draft update endpoint
- publish endpoint
### Phase 2: MCP Server
Ship:
- stdio MCP server
- auth wiring
- tool contracts for create/get/update/publish
### Phase 3: Hardening
Ship:
- idempotency keys
- richer warnings
- observability
- docs and examples
## Dependencies
1. Existing course, section, lesson, and lesson-language services remain the publish target.
2. Shared organization automation key infrastructure must be implemented.
3. External agent must generate normalized course payloads that match the schema.
## Open Questions
1. Should `publish_course_draft` remain a separate tool only, or support an explicit `publish: true` flag during draft creation?
2. Should exercise payloads be part of v1 or deferred until lesson drafting stabilizes?
3. What locale set should be supported by default?
4. Should cloud-only restrictions apply, similar to the AI assistant PRD?
5. Should the shared key-management routes ship before public `/v1/*` API routes, so MCP can adopt them first?
## Recommendation Summary
The correct v1 shape is:
1. thin MCP server
2. ClassroomIO-owned draft/publish API
3. external agent owns source extraction
4. explicit draft review before publish
5. publish via existing content services, not custom DB writes
That keeps the system small, matches your clarified boundary, and gives Claude a clean automation surface without forcing ClassroomIO to solve PDF extraction in v1.
@@ -0,0 +1,357 @@
# MCP Consumption Spec
## Purpose
Define exactly how an MCP client such as Claude Desktop or another agent runtime should consume the ClassroomIO MCP server.
This spec assumes:
1. the agent performs source extraction externally
2. the MCP server only accepts structured course payloads
3. ClassroomIO API is the system of record
## Consumption Model
```text
User prompt/source material
-> Claude or agent
-> MCP tool call
-> ClassroomIO API
-> ClassroomIO DB
```
The MCP server is consumed as a tool surface, not as a conversational model.
## Agent Responsibilities
The agent must:
1. read source material such as PDFs, notes, prompts, or websites
2. infer a structured course outline
3. construct a normalized payload that matches the draft schema
4. call MCP tools with that payload
5. handle validation errors and revise payloads when needed
6. only publish after explicit user confirmation
The agent must not:
1. write directly to the ClassroomIO database
2. assume ClassroomIO will parse raw PDFs
3. assume drafts are automatically published
## Authentication Model
### Target model
Use an organization-scoped MCP key generated from the org `Automation -> MCP` tab.
That key should come from the same shared key infrastructure later reused by the Public API.
The MCP server forwards that credential to ClassroomIO on every request.
Example environment:
```bash
CLASSROOMIO_API_URL=https://api.classroomio.example
CLASSROOMIO_API_KEY=cio_mcp_xxxxx
```
Recommended request header:
```text
Authorization: Bearer cio_mcp_xxxxx
```
### Current bootstrap implementation
Until the shared organization key system lands, the local package uses:
```text
Authorization: Bearer <PRIVATE_SERVER_KEY>
cio-org-id: <organization-id>
x-cio-actor-id: <profile-id>
```
That is an implementation bridge, not the intended long-term auth model.
## MCP Transport
### v1
- `stdio`
This is the best default for Claude Desktop and local agent environments.
### Future
- SSE or HTTP transport if remote/shared infrastructure is needed
## Tool Surface
### `create_course_draft`
Creates a reviewable draft from a normalized payload.
Input:
```json
{
"sourceType": "prompt",
"idempotencyKey": "optional-key",
"summary": {
"sourceLabel": "Exponential Functions PDF"
},
"sourceArtifacts": [
{
"type": "pdf",
"label": "chapter-1.pdf"
}
],
"draft": {
"course": {
"title": "Exponential Functions",
"description": "A structured introduction to exponential functions.",
"type": "SELF_PACED",
"locale": "en"
},
"sections": [
{
"externalId": "sec-1",
"title": "Foundations",
"order": 0
}
],
"lessons": [
{
"externalId": "lesson-1",
"sectionExternalId": "sec-1",
"title": "What Is an Exponential Function?",
"order": 0
}
],
"lessonLanguages": [
{
"lessonExternalId": "lesson-1",
"locale": "en",
"content": "<p>Lesson content here</p>"
}
],
"warnings": []
}
}
```
Output:
```json
{
"draftId": "uuid",
"status": "DRAFT",
"summary": {
"sectionCount": 1,
"lessonCount": 1,
"localeCount": 1,
"warningCount": 0
},
"warnings": []
}
```
### `get_course_draft`
Fetches the current stored draft.
Input:
```json
{
"draftId": "uuid"
}
```
Output:
```json
{
"id": "uuid",
"status": "DRAFT",
"draft": {
"...": "normalized payload"
},
"warnings": []
}
```
### `update_course_draft`
Updates a stored draft with revised structure or content.
Input:
```json
{
"draftId": "uuid",
"draft": {
"...": "updated normalized payload"
}
}
```
Output:
```json
{
"draftId": "uuid",
"status": "DRAFT",
"summary": {
"sectionCount": 3,
"lessonCount": 12,
"localeCount": 1,
"warningCount": 1
},
"warnings": [
{
"code": "LOW_CONFIDENCE_BOUNDARY",
"message": "Section split was inferred.",
"severity": "warning"
}
]
}
```
### `publish_course_draft`
Publishes a stored draft into real ClassroomIO entities.
Input:
```json
{
"draftId": "uuid",
"title": "Optional override",
"description": "Optional override",
"type": "SELF_PACED"
}
```
Output:
```json
{
"courseId": "uuid",
"createdSections": 3,
"createdLessons": 12,
"localeCount": 1
}
```
## Expected Agent Workflow
### Flow A: Prompt-Based Course Creation
1. User asks Claude to create a course.
2. Claude plans the structure.
3. Claude calls `create_course_draft`.
4. Claude presents the summary to the user.
5. User requests revisions if needed.
6. Claude calls `update_course_draft`.
7. User confirms publish.
8. Claude calls `publish_course_draft`.
### Flow B: PDF-Based Course Creation
1. User provides PDF to Claude.
2. Claude extracts and structures the content externally.
3. Claude builds normalized payload.
4. Claude calls `create_course_draft`.
5. Claude presents draft summary and warnings.
6. Claude revises if needed.
7. Claude publishes only after user confirmation.
## Draft Schema Rules
The agent must ensure:
1. `section.externalId` values are unique
2. `lesson.externalId` values are unique
3. every `lesson.sectionExternalId` references an existing section
4. every `lessonLanguage.lessonExternalId` references an existing lesson
5. all required text fields are non-empty
If these rules are violated, ClassroomIO should reject the request with validation errors.
## Error Handling Contract
### Validation Errors
The MCP client should expect 400-level errors when:
- the payload shape is invalid
- section or lesson references are broken
- required fields are missing
Expected behavior:
1. surface the error to the agent
2. revise payload
3. retry with corrected structure
### Authorization Errors
The MCP client should expect:
- `401` for missing or invalid auth
- `403` for valid auth but insufficient org permission
### Publish Errors
If publish fails:
1. do not assume partial success
2. inspect returned error
3. fetch the draft again if needed
4. retry only if the failure is clearly safe to retry
## Idempotency Guidance
The MCP client should send an `idempotencyKey` on draft creation when:
- the same source material may be retried
- the same user prompt may be replayed
- the client cannot guarantee exactly-once execution
Use one stable key per logical draft creation request.
## Recommended User Experience in Claude
The agent should present:
1. course title
2. section count
3. lesson count
4. warnings
5. a publish confirmation prompt before calling `publish_course_draft`
The agent should not auto-publish immediately after draft creation.
## Minimal v1 MCP Server Requirements
The MCP server should:
1. expose the draft/publish tools above
2. validate required inputs before forwarding
3. forward auth and org context to ClassroomIO
4. return structured JSON outputs to the client
5. avoid embedding model logic
## Future Extensions
Possible future tools:
- `list_course_drafts`
- `delete_course_draft`
- `clone_course_structure`
- `add_exercises_to_draft`
- `preview_publish_diff`
These are not required for v1.
@@ -0,0 +1,56 @@
# MCP Course Authoring from Structured Payloads Implementation Plan
## Scope Lock (v1)
1. The MCP server is a thin API adapter, not a persistence layer.
2. Draft review is mandatory before publish.
3. External agents submit normalized course payloads.
4. ClassroomIO does not parse PDFs or source material in v1.
5. Publish uses existing ClassroomIO services for course, section, lesson, and lesson language creation.
6. Authentication is org-scoped and must reject cross-org mutations.
## Delivery Phases
1. Phase A: Payload schema, validation, and draft persistence
2. Phase B: Draft create/get/update API
3. Phase C: Draft publish orchestration
4. Phase D: MCP server and tool contracts
5. Phase E: Observability, auth hardening, and idempotency
## Ticket Breakdown
| ID | Area | Task | Key Files | Dependencies | Done When |
| --- | --- | --- | --- | --- | --- |
| MCP-A1 | Validation | Add structured course payload validation module | `packages/utils/src/validation/course-import/course-import.ts` | None | Schemas exported and built |
| MCP-A2 | DB | Add draft persistence table | `packages/db/src/schema.ts` | None | Draft schema updated |
| MCP-A3 | DB | Add draft query module | `packages/db/src/queries/course-import/course-import.ts` | MCP-A2 | CRUD/query helpers exist |
| MCP-A4 | API | Add draft route skeleton | `apps/api/src/routes/organization/course-import.ts` | MCP-A1 | Route group registered |
| MCP-B1 | API | Implement draft create service | `apps/api/src/services/course-import/course-import.ts` | MCP-A4 | Structured payload creates draft |
| MCP-B2 | API | Implement draft fetch/update endpoints | `apps/api/src/routes/organization/course-import.ts` | MCP-B1 | Draft can be reviewed and edited |
| MCP-C1 | API | Implement draft publish service | `apps/api/src/services/course-import/course-import.ts` | MCP-B2 | Draft creates course, sections, lessons, languages |
| MCP-C2 | API | Add idempotent publish behavior | `apps/api/src/services/course-import/course-import.ts` | MCP-C1 | Retries do not duplicate courses |
| MCP-D1 | MCP | Scaffold MCP package | `apps/mcp/` or `packages/mcp/` | MCP-B2 | Stdio server runs locally |
| MCP-D2 | MCP | Add `create_course_draft` tool | MCP server tool files | MCP-D1, MCP-B1 | Claude can create drafts |
| MCP-D3 | MCP | Add `get_course_draft` and `update_course_draft` tools | MCP server tool files | MCP-B2 | Claude can inspect and revise drafts |
| MCP-D4 | MCP | Add `publish_course_draft` tool | MCP server tool files | MCP-C2 | Claude can publish reviewed drafts |
| MCP-E1 | Security | Enforce org-scoped auth and rate limits | middleware + routes | MCP-A4 | Unauthorized access returns 401/403 |
| MCP-E2 | Observability | Add structured logging and audit trail | services + middleware | MCP-C1 | Draft and publish actions are traceable |
| MCP-E3 | Docs | Document setup and consumption contract | `prd/mcp-course-authoring [DONE]/` and docs follow-up | MCP-D4 | Operator docs exist |
## Suggested First Sprint Slice
1. Draft schema and table
2. Draft create endpoint
3. Draft get/update endpoint
4. Draft publish endpoint
5. Minimal MCP server with:
- `create_course_draft`
- `get_course_draft`
- `publish_course_draft`
## Build and Verification Commands
1. `pnpm --filter @cio/utils build`
2. `pnpm --filter @cio/db build`
3. `pnpm --filter @cio/api build`
4. Build the MCP package/app once created