canvas-design-mcp 0.9.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -8
- package/dist/course-types.d.ts +79 -0
- package/dist/course-types.d.ts.map +1 -0
- package/dist/course-types.js +45 -0
- package/dist/course-types.js.map +1 -0
- package/dist/index.js +230 -6
- package/dist/index.js.map +1 -1
- package/dist/tools/course-config.d.ts +4 -0
- package/dist/tools/course-config.d.ts.map +1 -0
- package/dist/tools/course-config.js +144 -0
- package/dist/tools/course-config.js.map +1 -0
- package/dist/tools/course-scaffold.d.ts +4 -0
- package/dist/tools/course-scaffold.d.ts.map +1 -0
- package/dist/tools/course-scaffold.js +298 -0
- package/dist/tools/course-scaffold.js.map +1 -0
- package/dist/tools/course-templates.d.ts +5 -0
- package/dist/tools/course-templates.d.ts.map +1 -0
- package/dist/tools/course-templates.js +393 -0
- package/dist/tools/course-templates.js.map +1 -0
- package/dist/tools/generate-course.d.ts +3 -0
- package/dist/tools/generate-course.d.ts.map +1 -0
- package/dist/tools/generate-course.js +40 -0
- package/dist/tools/generate-course.js.map +1 -0
- package/dist/tools/generate-page.d.ts +3 -0
- package/dist/tools/generate-page.d.ts.map +1 -0
- package/dist/tools/generate-page.js +52 -0
- package/dist/tools/generate-page.js.map +1 -0
- package/dist/tools/generate-week.d.ts +3 -0
- package/dist/tools/generate-week.d.ts.map +1 -0
- package/dist/tools/generate-week.js +37 -0
- package/dist/tools/generate-week.js.map +1 -0
- package/dist/tools/get-started.d.ts +2 -0
- package/dist/tools/get-started.d.ts.map +1 -0
- package/dist/tools/get-started.js +82 -0
- package/dist/tools/get-started.js.map +1 -0
- package/dist/tools/import-course.d.ts +13 -0
- package/dist/tools/import-course.d.ts.map +1 -0
- package/dist/tools/import-course.js +332 -0
- package/dist/tools/import-course.js.map +1 -0
- package/dist/tools/list-courses.d.ts.map +1 -1
- package/dist/tools/list-courses.js +52 -6
- package/dist/tools/list-courses.js.map +1 -1
- package/dist/tools/panopto.d.ts.map +1 -1
- package/dist/tools/panopto.js +25 -1
- package/dist/tools/panopto.js.map +1 -1
- package/dist/tools/publish.d.ts.map +1 -1
- package/dist/tools/publish.js +91 -10
- package/dist/tools/publish.js.map +1 -1
- package/dist/tools/setup-course.d.ts +16 -0
- package/dist/tools/setup-course.d.ts.map +1 -0
- package/dist/tools/setup-course.js +124 -0
- package/dist/tools/setup-course.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/errors.d.ts +10 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +22 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/wizard.d.ts +1 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +63 -20
- package/dist/wizard.js.map +1 -1
- package/docs/canvas-design-kb/00-meta/Changelog.md +29 -0
- package/docs/feature-roadmap.md +97 -2
- package/docs/installation.md +121 -13
- package/docs/npm-publishing.md +117 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -6,14 +6,19 @@ Works in Claude Code, VS Code, ChatGPT Codex, and any MCP-compatible host. Zero
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
9
13
|
## What It Does
|
|
10
14
|
|
|
11
15
|
| Tool | What it does |
|
|
12
16
|
|---|---|
|
|
17
|
+
| `get_started` | Get a tailored orientation based on your current config — what tools are active, what setup unlocks, quick-start prompts, and a Context7 hint. Call this at the start of any session. |
|
|
13
18
|
| `generate_canvas_page` | Turns a raw assignment brief into polished, Canvas-safe HTML with a hero banner, two-column layout, and brand colors |
|
|
14
19
|
| `validate_canvas_html` | Checks HTML against Canvas RCE sanitizer rules and WCAG 2.1 AA accessibility checks |
|
|
15
20
|
| `update_canvas_kb` | Fetches the current Canvas HTML allowlist directly from Canvas LMS source and reports any changes |
|
|
16
|
-
| `setup_institution` |
|
|
21
|
+
| `setup_institution` | Runs the setup wizard to save brand colors, Canvas URL, API token, brand standards URL, and Panopto config — returns a formatted setup summary when done |
|
|
17
22
|
| `list_canvas_courses` | Lists your Canvas courses with semester filtering, student counts, and favorite pinning to help choose the right one |
|
|
18
23
|
| `publish_to_canvas` | Validates and publishes generated HTML directly to a Canvas course page — with FERPA preflight and title collision protection |
|
|
19
24
|
| `critique_canvas_page` | Scores visual design quality (0–100) with 8 structural checks, strengths, and prioritized findings |
|
|
@@ -28,6 +33,11 @@ Works in Claude Code, VS Code, ChatGPT Codex, and any MCP-compatible host. Zero
|
|
|
28
33
|
| `generate_student_personas` | Generate 1–20 student personas using real probability distributions for race, disability, and 21 other dimensions |
|
|
29
34
|
| `load_canvas_page` | Read the most recently generated HTML page from `output/` back into context (or load a named file) |
|
|
30
35
|
| `save_canvas_page` | Write improved HTML back to `output/` — automatically backs up the previous version before overwriting |
|
|
36
|
+
| `setup_course` | Run once per course: choose page types from a checklist, set weeks, get a complete folder scaffold pre-filled with content prompts |
|
|
37
|
+
| `generate_page` | Generate or regenerate a single Canvas page from its `.md` source file |
|
|
38
|
+
| `generate_week` | Generate all pages for one week at once |
|
|
39
|
+
| `generate_course` | Batch generate the entire course in one command |
|
|
40
|
+
| `import_course` | Seed a course folder from a Canvas backup archive — extracts pages, assignments, quizzes, and discussions with `[NEEDS REVIEW]` placeholders for content that can't be auto-extracted |
|
|
31
41
|
|
|
32
42
|
---
|
|
33
43
|
|
|
@@ -36,7 +46,7 @@ Works in Claude Code, VS Code, ChatGPT Codex, and any MCP-compatible host. Zero
|
|
|
36
46
|
### Option A — npm global install
|
|
37
47
|
|
|
38
48
|
```bash
|
|
39
|
-
npm install -g
|
|
49
|
+
npm install -g canvas-design-mcp
|
|
40
50
|
```
|
|
41
51
|
|
|
42
52
|
Then add to your MCP client config:
|
|
@@ -57,11 +67,40 @@ Then add to your MCP client config:
|
|
|
57
67
|
|
|
58
68
|
### Option B — Docker (no Node.js required)
|
|
59
69
|
|
|
70
|
+
Docker has two steps:
|
|
71
|
+
|
|
72
|
+
1. **Download the image** so it exists on your computer.
|
|
73
|
+
2. **Tell your AI app how to start it** by adding the JSON config below to that app's MCP settings.
|
|
74
|
+
|
|
75
|
+
The JSON is not a PowerShell command. It is a configuration snippet for Claude Desktop, Cursor, LM Studio, and other MCP clients.
|
|
76
|
+
|
|
60
77
|
```bash
|
|
61
78
|
# Pull the image
|
|
62
79
|
docker pull ghcr.io/ryfter/canvas-design-studio:latest
|
|
63
80
|
```
|
|
64
81
|
|
|
82
|
+
Run the setup wizard once so Canvas Design Studio can save your school colors, Canvas URL, and optional API token.
|
|
83
|
+
|
|
84
|
+
Windows PowerShell:
|
|
85
|
+
|
|
86
|
+
```powershell
|
|
87
|
+
docker run -it --rm -v "$HOME\.canvas-design-mcp:/root/.canvas-design-mcp" ghcr.io/ryfter/canvas-design-studio:latest
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
macOS Terminal:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
docker run -it --rm -v "$HOME/.canvas-design-mcp:/root/.canvas-design-mcp" ghcr.io/ryfter/canvas-design-studio:latest
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Linux terminal:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
docker run -it --rm -v "$HOME/.canvas-design-mcp:/root/.canvas-design-mcp" ghcr.io/ryfter/canvas-design-studio:latest
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
After the wizard finishes, add this to your MCP client config:
|
|
103
|
+
|
|
65
104
|
```json
|
|
66
105
|
{
|
|
67
106
|
"mcpServers": {
|
|
@@ -77,28 +116,48 @@ docker pull ghcr.io/ryfter/canvas-design-studio:latest
|
|
|
77
116
|
}
|
|
78
117
|
```
|
|
79
118
|
|
|
80
|
-
|
|
119
|
+
That `-v` line is the important part: it lets the temporary Docker container read the saved config from your computer. Without that mount, the container starts fresh every time and will not remember your institution settings.
|
|
120
|
+
|
|
121
|
+
If your AI app does not expand `${HOME}` correctly, replace it with your full home folder path:
|
|
81
122
|
|
|
82
|
-
|
|
123
|
+
- Windows: `C:/Users/YOUR-USERNAME/.canvas-design-mcp:/root/.canvas-design-mcp`
|
|
124
|
+
- macOS: `/Users/YOUR-USERNAME/.canvas-design-mcp:/root/.canvas-design-mcp`
|
|
83
125
|
|
|
84
|
-
|
|
126
|
+
Common config locations:
|
|
127
|
+
|
|
128
|
+
- **Claude Desktop on Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
129
|
+
- **Claude Desktop on macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
130
|
+
- **Cursor:** `~/.cursor/mcp.json`
|
|
131
|
+
- **LM Studio:** `~/.lmstudio/mcp.json`
|
|
132
|
+
- **Codex CLI:** uses TOML instead of this JSON; see [docs/installation.md](docs/installation.md)
|
|
133
|
+
|
|
134
|
+
After saving the config, fully restart the AI app. The app will run Docker for you whenever it needs the Canvas Design Studio tools.
|
|
135
|
+
|
|
136
|
+
### What the Setup Wizard Asks
|
|
137
|
+
|
|
138
|
+
Whether you use npm or Docker, the first setup run asks:
|
|
85
139
|
|
|
86
140
|
```
|
|
87
141
|
╔═══════════════════════════════════════════════════════════╗
|
|
88
142
|
║ Canvas Design Studio — First Run Setup ║
|
|
89
143
|
╚═══════════════════════════════════════════════════════════╝
|
|
90
144
|
|
|
91
|
-
Institution name: (Boise State University)
|
|
145
|
+
Institution name (your college or university): (Boise State University)
|
|
146
|
+
Brand standards URL (optional — your AI fetches this to suggest your colors):
|
|
92
147
|
Primary brand color (#hex): (#0033A0)
|
|
93
148
|
Secondary / accent color (#hex): (#D64309)
|
|
94
|
-
Canvas base URL: (https://boisestate.instructure.com)
|
|
95
|
-
Canvas API token
|
|
149
|
+
Canvas base URL (no trailing slash): (https://boisestate.instructure.com)
|
|
150
|
+
Canvas API token — Account → Settings → Approved Integrations (optional):
|
|
96
151
|
Professor email for FERPA scan allowlist (optional):
|
|
97
152
|
Favorite Canvas course IDs, comma-separated (optional):
|
|
98
153
|
```
|
|
99
154
|
|
|
155
|
+
When the wizard finishes it prints a formatted setup summary showing your settings and what you can do right now. Ask your AI to save it as `my-canvas-setup.md` for reference.
|
|
156
|
+
|
|
100
157
|
Config saves to `~/.canvas-design-mcp/institution.json` — survives `npx` reinstalls.
|
|
101
158
|
|
|
159
|
+
**New to Canvas Design Studio?** See `docs/start_here.md` for a full orientation, or run `get_started` at the beginning of any session for a tailored overview of what's active.
|
|
160
|
+
|
|
102
161
|
---
|
|
103
162
|
|
|
104
163
|
## Generating a Page
|
|
@@ -242,6 +301,10 @@ Config file: `~/.canvas-design-mcp/institution.json`
|
|
|
242
301
|
- **v0.7** — Professor philosophy KB (steering context for every page Claude generates) ✓
|
|
243
302
|
- **v0.8** — Student persona review (statistically grounded audience feedback before publishing) ✓
|
|
244
303
|
- **v0.9** — Assignment improvement loop (load page from output/, apply critique, save back with backup) ✓
|
|
304
|
+
- **v0.9.6** — Course design foundation: `setup_course` wizard, `generate_page/week/course`, 15 page type templates ✓
|
|
305
|
+
- **v0.9.7** — Canvas backup import: `import_course` seeds a full course folder from a previous semester's archive ✓
|
|
306
|
+
- **v0.9.8** — Assignment type customization: `proj-assignment` and `tech-assignment` page types with `team` and `timeline` flags ✓
|
|
307
|
+
- **v1.0.0** — First-professor polish: `get_started` orientation tool, setup summary, brand URL extraction, wizard inline explanations, rich error messages with ChatGPT help links, and three orientation docs (`start_here`, `troubleshooting`, `setup-worksheet`) ✓
|
|
245
308
|
|
|
246
309
|
---
|
|
247
310
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export declare const PAGE_TYPES: readonly ["front-page", "overview", "resources", "slides", "videos", "assignment", "engage-assignment", "proj-assignment", "tech-assignment", "reading", "reading-quiz", "weekly-quiz", "lab", "discussion-board", "extra-credit", "custom"];
|
|
2
|
+
export type PageType = typeof PAGE_TYPES[number];
|
|
3
|
+
export declare const PAGE_TYPE_LABELS: Record<PageType, string>;
|
|
4
|
+
export declare const DEFAULT_PAGE_TYPES: PageType[];
|
|
5
|
+
export interface CourseColors {
|
|
6
|
+
primary: string;
|
|
7
|
+
primaryDark: string;
|
|
8
|
+
primaryLight: string;
|
|
9
|
+
secondary: string;
|
|
10
|
+
}
|
|
11
|
+
export interface WeekEntry {
|
|
12
|
+
week: number;
|
|
13
|
+
weekStr: string;
|
|
14
|
+
title: string;
|
|
15
|
+
topic: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CourseConfig {
|
|
18
|
+
institution: string;
|
|
19
|
+
courseName: string;
|
|
20
|
+
courseNumber: string;
|
|
21
|
+
professor: string;
|
|
22
|
+
semester: string;
|
|
23
|
+
weeks: number;
|
|
24
|
+
pageTypes: PageType[];
|
|
25
|
+
layoutFixed: boolean;
|
|
26
|
+
colors: CourseColors;
|
|
27
|
+
heroImages: Partial<Record<PageType, string>>;
|
|
28
|
+
weekOutline: WeekEntry[];
|
|
29
|
+
}
|
|
30
|
+
export interface PageFrontMatter {
|
|
31
|
+
week?: number;
|
|
32
|
+
title?: string;
|
|
33
|
+
heroImage?: string;
|
|
34
|
+
assignmentNumber?: string;
|
|
35
|
+
due?: string;
|
|
36
|
+
points?: number;
|
|
37
|
+
team?: boolean;
|
|
38
|
+
timeline?: boolean;
|
|
39
|
+
[key: string]: string | number | boolean | undefined;
|
|
40
|
+
}
|
|
41
|
+
export interface PageContent {
|
|
42
|
+
pageType: PageType;
|
|
43
|
+
frontMatter: PageFrontMatter;
|
|
44
|
+
sections: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
export interface GeneratePageInput {
|
|
47
|
+
mdPath: string;
|
|
48
|
+
courseDir?: string;
|
|
49
|
+
outputDir?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface GeneratePageResult {
|
|
52
|
+
html: string;
|
|
53
|
+
filename: string;
|
|
54
|
+
weekNumber: number;
|
|
55
|
+
pageType: PageType;
|
|
56
|
+
savedTo: string;
|
|
57
|
+
}
|
|
58
|
+
export interface GenerateWeekInput {
|
|
59
|
+
weekNumber: number;
|
|
60
|
+
courseDir?: string;
|
|
61
|
+
outputDir?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface GenerateWeekResult {
|
|
64
|
+
weekNumber: number;
|
|
65
|
+
pages: GeneratePageResult[];
|
|
66
|
+
outputDir: string;
|
|
67
|
+
warnings: string[];
|
|
68
|
+
}
|
|
69
|
+
export interface GenerateCourseInput {
|
|
70
|
+
courseDir?: string;
|
|
71
|
+
outputDir?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface GenerateCourseResult {
|
|
74
|
+
totalPages: number;
|
|
75
|
+
outputDir: string;
|
|
76
|
+
weekResults: GenerateWeekResult[];
|
|
77
|
+
warnings: string[];
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=course-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course-types.d.ts","sourceRoot":"","sources":["../src/course-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const PAGE_TYPES = [
|
|
2
|
+
'front-page',
|
|
3
|
+
'overview',
|
|
4
|
+
'resources',
|
|
5
|
+
'slides',
|
|
6
|
+
'videos',
|
|
7
|
+
'assignment',
|
|
8
|
+
'engage-assignment',
|
|
9
|
+
'proj-assignment',
|
|
10
|
+
'tech-assignment',
|
|
11
|
+
'reading',
|
|
12
|
+
'reading-quiz',
|
|
13
|
+
'weekly-quiz',
|
|
14
|
+
'lab',
|
|
15
|
+
'discussion-board',
|
|
16
|
+
'extra-credit',
|
|
17
|
+
'custom',
|
|
18
|
+
];
|
|
19
|
+
export const PAGE_TYPE_LABELS = {
|
|
20
|
+
'front-page': 'Front Page (course home)',
|
|
21
|
+
'overview': 'Overview (learning objectives, intro, activities)',
|
|
22
|
+
'resources': 'Resources (slides, videos, readings combined)',
|
|
23
|
+
'slides': 'Slides (dedicated slide deck page)',
|
|
24
|
+
'videos': 'Videos (dedicated Panopto video page)',
|
|
25
|
+
'assignment': 'Assignment',
|
|
26
|
+
'engage-assignment': 'Engage Assignment (short in-class activity)',
|
|
27
|
+
'proj-assignment': 'Project Assignment (multi-week deliverable)',
|
|
28
|
+
'tech-assignment': 'Technical Assignment (hands-on, tool-based)',
|
|
29
|
+
'reading': 'Reading',
|
|
30
|
+
'reading-quiz': 'Reading Quiz',
|
|
31
|
+
'weekly-quiz': 'Weekly Quiz',
|
|
32
|
+
'lab': 'Lab',
|
|
33
|
+
'discussion-board': 'Discussion Board',
|
|
34
|
+
'extra-credit': 'Extra Credit',
|
|
35
|
+
'custom': 'Custom (professor-defined sections)',
|
|
36
|
+
};
|
|
37
|
+
export const DEFAULT_PAGE_TYPES = [
|
|
38
|
+
'front-page',
|
|
39
|
+
'overview',
|
|
40
|
+
'resources',
|
|
41
|
+
'assignment',
|
|
42
|
+
'discussion-board',
|
|
43
|
+
'weekly-quiz',
|
|
44
|
+
];
|
|
45
|
+
//# sourceMappingURL=course-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course-types.js","sourceRoot":"","sources":["../src/course-types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,UAAU;IACV,WAAW;IACX,QAAQ;IACR,QAAQ;IACR,YAAY;IACZ,mBAAmB;IACnB,iBAAiB;IACjB,iBAAiB;IACjB,SAAS;IACT,cAAc;IACd,aAAa;IACb,KAAK;IACL,kBAAkB;IAClB,cAAc;IACd,QAAQ;CACA,CAAC;AAIX,MAAM,CAAC,MAAM,gBAAgB,GAA6B;IACxD,YAAY,EAAS,0BAA0B;IAC/C,UAAU,EAAW,mDAAmD;IACxE,WAAW,EAAU,+CAA+C;IACpE,QAAQ,EAAa,oCAAoC;IACzD,QAAQ,EAAa,uCAAuC;IAC5D,YAAY,EAAS,YAAY;IACjC,mBAAmB,EAAE,6CAA6C;IAClE,iBAAiB,EAAI,6CAA6C;IAClE,iBAAiB,EAAI,6CAA6C;IAClE,SAAS,EAAY,SAAS;IAC9B,cAAc,EAAO,cAAc;IACnC,aAAa,EAAQ,aAAa;IAClC,KAAK,EAAgB,KAAK;IAC1B,kBAAkB,EAAG,kBAAkB;IACvC,cAAc,EAAO,cAAc;IACnC,QAAQ,EAAa,qCAAqC;CAC3D,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC5C,YAAY;IACZ,UAAU;IACV,WAAW;IACX,YAAY;IACZ,kBAAkB;IAClB,aAAa;CACd,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,9 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { CanvasApiClient } from './canvas-api.js';
|
|
6
6
|
import { configExists, loadConfig, saveConfig } from './config.js';
|
|
7
|
-
import { runWizard } from './wizard.js';
|
|
7
|
+
import { runWizard, formatSetupSummary } from './wizard.js';
|
|
8
|
+
import { formatError } from './utils/errors.js';
|
|
9
|
+
import { getStarted } from './tools/get-started.js';
|
|
8
10
|
import { validateCanvasHtml } from './tools/validate.js';
|
|
9
11
|
import { generateCanvasPage } from './tools/generate.js';
|
|
10
12
|
import { updateCanvasKb } from './tools/update-kb.js';
|
|
@@ -18,6 +20,11 @@ import { ingestAssignmentFolder } from './tools/ingest.js';
|
|
|
18
20
|
import { getPhilosophyKb, updatePhilosophyKb } from './tools/philosophy.js';
|
|
19
21
|
import { generateStudentPersonas, getStudentPersonas } from './tools/personas.js';
|
|
20
22
|
import { loadCanvasPage, saveCanvasPage } from './tools/page-io.js';
|
|
23
|
+
import { generatePage } from './tools/generate-page.js';
|
|
24
|
+
import { generateWeek } from './tools/generate-week.js';
|
|
25
|
+
import { generateCourse } from './tools/generate-course.js';
|
|
26
|
+
import { runCourseWizard } from './tools/setup-course.js';
|
|
27
|
+
import { importCourse } from './tools/import-course.js';
|
|
21
28
|
async function main() {
|
|
22
29
|
if (!configExists()) {
|
|
23
30
|
if (!process.stdin.isTTY) {
|
|
@@ -35,6 +42,11 @@ async function main() {
|
|
|
35
42
|
const server = new Server({ name: 'canvas-design-studio', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
36
43
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
37
44
|
tools: [
|
|
45
|
+
{
|
|
46
|
+
name: 'get_started',
|
|
47
|
+
description: 'Get a tailored orientation based on your current config — what tools are active, what setup unlocks, quick-start prompts, and a Context7 hint for the latest docs. Call this at the start of any session.',
|
|
48
|
+
inputSchema: { type: 'object', properties: {} },
|
|
49
|
+
},
|
|
38
50
|
{
|
|
39
51
|
name: 'setup_institution',
|
|
40
52
|
description: 'Re-run the setup wizard to update institution config (brand colors, Canvas URL, API token). Run this to change institutions or rotate credentials.',
|
|
@@ -290,15 +302,91 @@ async function main() {
|
|
|
290
302
|
},
|
|
291
303
|
},
|
|
292
304
|
},
|
|
305
|
+
{
|
|
306
|
+
name: 'setup_course',
|
|
307
|
+
description: 'Run the course setup wizard to create a full course folder scaffold — course-config.md, all week folders, and pre-filled template .md files for each active page type. Run once per course. Supports color overrides and a checkbox page-type selector with recommendations.',
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: 'object',
|
|
310
|
+
properties: {
|
|
311
|
+
courseDir: { type: 'string', description: 'Directory to create the course scaffold in. Defaults to "course/" in the current project.' },
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: 'generate_page',
|
|
317
|
+
description: 'Generate one Canvas HTML page from a single .md content file. Finds course-config.md automatically by walking up from the file. Saves to output/week-NN/filename.html. Use for one-off pages and per-page tweaks.',
|
|
318
|
+
inputSchema: {
|
|
319
|
+
type: 'object',
|
|
320
|
+
required: ['mdPath'],
|
|
321
|
+
properties: {
|
|
322
|
+
mdPath: { type: 'string', description: 'Path to the .md content file (e.g. "course/week-03/assignment.md" or "course/front-page.md").' },
|
|
323
|
+
courseDir: { type: 'string', description: 'Directory containing course-config.md. Inferred from mdPath if omitted.' },
|
|
324
|
+
outputDir: { type: 'string', description: 'Output directory. Defaults to "output/" inside the course directory.' },
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: 'generate_week',
|
|
330
|
+
description: 'Generate all Canvas HTML pages for one week. Reads course-config.md for active page types and colors, then generates HTML for each .md file found in the week folder. Skips missing files with a warning.',
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: 'object',
|
|
333
|
+
required: ['weekNumber'],
|
|
334
|
+
properties: {
|
|
335
|
+
weekNumber: { type: 'number', description: 'Week number to generate (e.g. 3 for week-03).' },
|
|
336
|
+
courseDir: { type: 'string', description: 'Directory containing course-config.md. Defaults to "course/".' },
|
|
337
|
+
outputDir: { type: 'string', description: 'Output directory. Defaults to "output/" inside the course directory.' },
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'generate_course',
|
|
343
|
+
description: 'Batch generate all Canvas HTML pages for the entire course — front page plus all weeks. Reads course-config.md for the week count and active page types. Reports total pages generated and any warnings about missing .md files.',
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: 'object',
|
|
346
|
+
properties: {
|
|
347
|
+
courseDir: { type: 'string', description: 'Directory containing course-config.md. Defaults to "course/".' },
|
|
348
|
+
outputDir: { type: 'string', description: 'Output directory. Defaults to "output/" inside the course directory.' },
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'import_course',
|
|
354
|
+
description: 'Import a previous semester\'s course from a canvas-backup archive folder. Reads modules, pages, assignments, quizzes, and discussions and scaffolds a pre-filled course/ folder ready to update and regenerate. Works at three granularities: full course (omit weekNumber and assignmentName), one week (provide weekNumber), or one assignment (provide assignmentName). Content that cannot be cleanly extracted — quiz questions, LTI links, external tools — is written as [NEEDS REVIEW] placeholders.',
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: 'object',
|
|
357
|
+
required: ['archivePath'],
|
|
358
|
+
properties: {
|
|
359
|
+
archivePath: {
|
|
360
|
+
type: 'string',
|
|
361
|
+
description: 'Path to the canvas-backup archive folder for the course (e.g. "D:/CanvasArchive/2026/Spring/ITM370").',
|
|
362
|
+
},
|
|
363
|
+
outputDir: {
|
|
364
|
+
type: 'string',
|
|
365
|
+
description: 'Directory to write the imported course folder into. Defaults to "course/" in the current project.',
|
|
366
|
+
},
|
|
367
|
+
weekNumber: {
|
|
368
|
+
type: 'number',
|
|
369
|
+
description: 'Import only this week (1-based). Omit to import all weeks.',
|
|
370
|
+
},
|
|
371
|
+
assignmentName: {
|
|
372
|
+
type: 'string',
|
|
373
|
+
description: 'Import only this specific assignment by name. Omit to import all content.',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
},
|
|
293
378
|
],
|
|
294
379
|
}));
|
|
295
380
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
296
381
|
const { name, arguments: args } = request.params;
|
|
297
382
|
try {
|
|
383
|
+
if (name === 'get_started') {
|
|
384
|
+
return { content: [{ type: 'text', text: getStarted() }] };
|
|
385
|
+
}
|
|
298
386
|
if (name === 'setup_institution') {
|
|
299
|
-
await runWizard();
|
|
387
|
+
const config = await runWizard();
|
|
300
388
|
return {
|
|
301
|
-
content: [{ type: 'text', text:
|
|
389
|
+
content: [{ type: 'text', text: formatSetupSummary(config) }],
|
|
302
390
|
};
|
|
303
391
|
}
|
|
304
392
|
if (name === 'validate_canvas_html') {
|
|
@@ -319,6 +407,24 @@ async function main() {
|
|
|
319
407
|
};
|
|
320
408
|
}
|
|
321
409
|
if (name === 'generate_canvas_page') {
|
|
410
|
+
if (!configExists()) {
|
|
411
|
+
return {
|
|
412
|
+
content: [{
|
|
413
|
+
type: 'text',
|
|
414
|
+
text: formatError({
|
|
415
|
+
title: 'Setup Required',
|
|
416
|
+
message: 'Canvas Design Studio needs institution config before generating pages.',
|
|
417
|
+
cause: 'No institution.json found at ~/.canvas-design-mcp/institution.json.',
|
|
418
|
+
fix: [
|
|
419
|
+
'Run setup_institution to save your institution colors, Canvas URL, and optional API token',
|
|
420
|
+
'The wizard takes about 2 minutes and only needs to run once',
|
|
421
|
+
],
|
|
422
|
+
context: 'generate_canvas_page called with no institution config',
|
|
423
|
+
}),
|
|
424
|
+
}],
|
|
425
|
+
isError: true,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
322
428
|
const config = loadConfig();
|
|
323
429
|
const result = generateCanvasPage(args, config);
|
|
324
430
|
const response = [
|
|
@@ -422,7 +528,21 @@ async function main() {
|
|
|
422
528
|
const config = loadConfig();
|
|
423
529
|
if (!config.panopto) {
|
|
424
530
|
return {
|
|
425
|
-
content: [{
|
|
531
|
+
content: [{
|
|
532
|
+
type: 'text',
|
|
533
|
+
text: formatError({
|
|
534
|
+
title: 'Panopto — Not Configured',
|
|
535
|
+
message: 'Panopto video tools require a Panopto domain and API credentials.',
|
|
536
|
+
cause: 'No Panopto configuration is saved in your institution config.',
|
|
537
|
+
fix: [
|
|
538
|
+
'Run setup_institution',
|
|
539
|
+
'When prompted for Panopto domain, enter your institution\'s Panopto domain (e.g. bsu.hosted.panopto.com)',
|
|
540
|
+
'For video search and caption download, also provide a Panopto client ID and secret',
|
|
541
|
+
'embed_panopto_video works without API credentials — you only need the video ID',
|
|
542
|
+
],
|
|
543
|
+
context: 'Panopto not configured',
|
|
544
|
+
}),
|
|
545
|
+
}],
|
|
426
546
|
isError: true,
|
|
427
547
|
};
|
|
428
548
|
}
|
|
@@ -435,7 +555,21 @@ async function main() {
|
|
|
435
555
|
const config = loadConfig();
|
|
436
556
|
if (!config.panopto) {
|
|
437
557
|
return {
|
|
438
|
-
content: [{
|
|
558
|
+
content: [{
|
|
559
|
+
type: 'text',
|
|
560
|
+
text: formatError({
|
|
561
|
+
title: 'Panopto — Not Configured',
|
|
562
|
+
message: 'Panopto video tools require a Panopto domain and API credentials.',
|
|
563
|
+
cause: 'No Panopto configuration is saved in your institution config.',
|
|
564
|
+
fix: [
|
|
565
|
+
'Run setup_institution',
|
|
566
|
+
'When prompted for Panopto domain, enter your institution\'s Panopto domain (e.g. bsu.hosted.panopto.com)',
|
|
567
|
+
'For video search and caption download, also provide a Panopto client ID and secret',
|
|
568
|
+
'embed_panopto_video works without API credentials — you only need the video ID',
|
|
569
|
+
],
|
|
570
|
+
context: 'Panopto not configured',
|
|
571
|
+
}),
|
|
572
|
+
}],
|
|
439
573
|
isError: true,
|
|
440
574
|
};
|
|
441
575
|
}
|
|
@@ -452,7 +586,21 @@ async function main() {
|
|
|
452
586
|
const config = loadConfig();
|
|
453
587
|
if (!config.panopto) {
|
|
454
588
|
return {
|
|
455
|
-
content: [{
|
|
589
|
+
content: [{
|
|
590
|
+
type: 'text',
|
|
591
|
+
text: formatError({
|
|
592
|
+
title: 'Panopto — Not Configured',
|
|
593
|
+
message: 'Panopto video tools require a Panopto domain and API credentials.',
|
|
594
|
+
cause: 'No Panopto configuration is saved in your institution config.',
|
|
595
|
+
fix: [
|
|
596
|
+
'Run setup_institution',
|
|
597
|
+
'When prompted for Panopto domain, enter your institution\'s Panopto domain (e.g. bsu.hosted.panopto.com)',
|
|
598
|
+
'For video search and caption download, also provide a Panopto client ID and secret',
|
|
599
|
+
'embed_panopto_video works without API credentials — you only need the video ID',
|
|
600
|
+
],
|
|
601
|
+
context: 'Panopto not configured',
|
|
602
|
+
}),
|
|
603
|
+
}],
|
|
456
604
|
isError: true,
|
|
457
605
|
};
|
|
458
606
|
}
|
|
@@ -462,6 +610,24 @@ async function main() {
|
|
|
462
610
|
return { content: [{ type: 'text', text: result }], ...(isApiError ? { isError: true } : {}) };
|
|
463
611
|
}
|
|
464
612
|
if (name === 'ingest_assignment_folder') {
|
|
613
|
+
if (!configExists()) {
|
|
614
|
+
return {
|
|
615
|
+
content: [{
|
|
616
|
+
type: 'text',
|
|
617
|
+
text: formatError({
|
|
618
|
+
title: 'Setup Required',
|
|
619
|
+
message: 'Canvas Design Studio needs institution config before generating pages.',
|
|
620
|
+
cause: 'No institution.json found at ~/.canvas-design-mcp/institution.json.',
|
|
621
|
+
fix: [
|
|
622
|
+
'Run setup_institution to save your institution colors, Canvas URL, and optional API token',
|
|
623
|
+
'The wizard takes about 2 minutes and only needs to run once',
|
|
624
|
+
],
|
|
625
|
+
context: 'ingest_assignment_folder called with no institution config',
|
|
626
|
+
}),
|
|
627
|
+
}],
|
|
628
|
+
isError: true,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
465
631
|
const config = loadConfig();
|
|
466
632
|
const { folderPath } = (args ?? {});
|
|
467
633
|
const result = ingestAssignmentFolder({ folderPath }, config);
|
|
@@ -550,6 +716,64 @@ async function main() {
|
|
|
550
716
|
lines.push(` Backup created: ${result.backup}`);
|
|
551
717
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
552
718
|
}
|
|
719
|
+
if (name === 'setup_course') {
|
|
720
|
+
const { courseDir } = (args ?? {});
|
|
721
|
+
const created = await runCourseWizard(courseDir);
|
|
722
|
+
return {
|
|
723
|
+
content: [{ type: 'text', text: `✓ Course scaffold created.\n${created.length} files written:\n${created.map(f => ` • ${f}`).join('\n')}` }],
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
if (name === 'generate_page') {
|
|
727
|
+
const input = args;
|
|
728
|
+
const result = generatePage(input);
|
|
729
|
+
return {
|
|
730
|
+
content: [{ type: 'text', text: `✓ Generated ${result.pageType} page\n Week: ${result.weekNumber || 'N/A'}\n Saved: ${result.savedTo}` }],
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
if (name === 'generate_week') {
|
|
734
|
+
const input = args;
|
|
735
|
+
const result = generateWeek(input);
|
|
736
|
+
const lines = [`✓ Week ${result.weekNumber}: ${result.pages.length} page(s) generated`];
|
|
737
|
+
for (const p of result.pages)
|
|
738
|
+
lines.push(` • ${p.pageType} → ${p.savedTo}`);
|
|
739
|
+
if (result.warnings.length > 0)
|
|
740
|
+
lines.push(`\n⚠ Warnings:\n${result.warnings.map(w => ` • ${w}`).join('\n')}`);
|
|
741
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
742
|
+
}
|
|
743
|
+
if (name === 'generate_course') {
|
|
744
|
+
const input = (args ?? {});
|
|
745
|
+
const result = generateCourse(input);
|
|
746
|
+
const lines = [
|
|
747
|
+
`✓ Course generated: ${result.totalPages} page(s) across ${result.weekResults.length} week(s)`,
|
|
748
|
+
` Output: ${result.outputDir}`,
|
|
749
|
+
];
|
|
750
|
+
if (result.warnings.length > 0)
|
|
751
|
+
lines.push(`\n⚠ Warnings (${result.warnings.length}):\n${result.warnings.map(w => ` • ${w}`).join('\n')}`);
|
|
752
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
753
|
+
}
|
|
754
|
+
if (name === 'import_course') {
|
|
755
|
+
const { archivePath, outputDir, weekNumber, assignmentName } = (args ?? {});
|
|
756
|
+
const result = importCourse({
|
|
757
|
+
archivePath,
|
|
758
|
+
outputDir: outputDir ?? 'course',
|
|
759
|
+
weekNumber,
|
|
760
|
+
assignmentName,
|
|
761
|
+
});
|
|
762
|
+
const lines = [
|
|
763
|
+
`✓ Import complete`,
|
|
764
|
+
` Weeks imported: ${result.weeksImported}`,
|
|
765
|
+
` Files created: ${result.filesCreated}`,
|
|
766
|
+
];
|
|
767
|
+
if (result.warnings.length > 0) {
|
|
768
|
+
lines.push(`\n⚠ Warnings (${result.warnings.length}):`);
|
|
769
|
+
lines.push(...result.warnings.map(w => ` • ${w}`));
|
|
770
|
+
}
|
|
771
|
+
lines.push('\nNext steps:');
|
|
772
|
+
lines.push(' 1. Open course-config.md — update semester, professor name, and week topics');
|
|
773
|
+
lines.push(' 2. Search for [NEEDS REVIEW] in .md files and fill in missing content');
|
|
774
|
+
lines.push(' 3. Tell Claude: "Generate the course from the course/ folder"');
|
|
775
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
776
|
+
}
|
|
553
777
|
return {
|
|
554
778
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
555
779
|
isError: true,
|