wicker-study-mcp 2.6.0 → 2.7.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 +14 -2
- package/package.json +1 -1
- package/server.mjs +30 -3
package/README.md
CHANGED
|
@@ -9,6 +9,17 @@ Runs from anywhere. Nothing here needs a checkout of the application.
|
|
|
9
9
|
|
|
10
10
|
## Use it
|
|
11
11
|
|
|
12
|
+
The authenticated **Docs** page in Wicker Study can mint a scoped key and place it directly inside
|
|
13
|
+
one copy-ready Codex or Claude Code installation block. There is no separate credential field to
|
|
14
|
+
copy. The first line stores the embedded key with owner-only permissions and the second registers
|
|
15
|
+
the MCP server.
|
|
16
|
+
|
|
17
|
+
For a manually supplied key, the same secure bootstrap is available as:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
WICKER_STUDY_URL='https://study.wicker.life' WICKER_STUDY_API_KEY='wsk_…' npx -y wicker-study-mcp@2.7.0 configure
|
|
21
|
+
```
|
|
22
|
+
|
|
12
23
|
```jsonc
|
|
13
24
|
// Claude Desktop / Claude Code / Cursor MCP config
|
|
14
25
|
{
|
|
@@ -59,10 +70,11 @@ they are how a key is obtained. Everything else needs one.
|
|
|
59
70
|
- **Reading** — `list_courses`, `get_course`, `get_chapter`, `get_course_outline`, `search_course`,
|
|
60
71
|
`list_materials`, `list_questions`, `get_practice_queue`, `get_progress`, `list_flashcards`,
|
|
61
72
|
`list_due_cards`, `list_mistakes`, `list_mock_sessions`, `get_mock_session`, `get_academic_plan`,
|
|
62
|
-
`list_known_programmes`, `get_calendar`, `get_activity`,
|
|
73
|
+
`get_planning_context`, `list_known_programmes`, `get_calendar`, `get_activity`,
|
|
74
|
+
`get_account_summary`, `whoami`
|
|
63
75
|
- **Studying** — `submit_answer`, `set_mastery`, `review_card`, `add_to_deck`, `create_flashcard`,
|
|
64
76
|
`review_flashcard`, `resolve_mistake`, `record_chapter_read`, `save_academic_plan`,
|
|
65
|
-
`set_course_visibility`, `join_programme`
|
|
77
|
+
`update_planning_objective`, `set_course_visibility`, `join_programme`
|
|
66
78
|
- **Documents and calendars** — `analyze_documents`, `apply_changes`, `preview_calendar`,
|
|
67
79
|
`save_calendar_link`, `sync_calendar_link`, `remove_calendar_link`
|
|
68
80
|
- **Canvas** — `canvas_connect`, `canvas_updates` (announcements, assignments with
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wicker-study-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "MCP server for Wicker Study: read course material and a student's academic record, study on their behalf, collect a private Canvas course snapshot, and \u2014 with an admin key \u2014 run the editorial workflow.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
package/server.mjs
CHANGED
|
@@ -25,6 +25,24 @@ import { getSavedCanvasAccessToken, promptForLocalCanvasImport, saveCanvasAccess
|
|
|
25
25
|
import { beginAuthorization } from './authorize.mjs'
|
|
26
26
|
import { configPath, forgetApiKey, listSavedServers, normaliseServerUrl, resolveApiKey, saveApiKey } from './config.mjs'
|
|
27
27
|
|
|
28
|
+
// A copy-ready installation block can provision the credential before an MCP
|
|
29
|
+
// client launches the stdio server. Persist it through the same hardened
|
|
30
|
+
// config writer as browser authorization (0700 directory, 0600 file), then
|
|
31
|
+
// discard the environment value with this short-lived process.
|
|
32
|
+
if (process.argv[2] === 'configure') {
|
|
33
|
+
try {
|
|
34
|
+
const serverUrl = normaliseServerUrl(process.env.WICKER_STUDY_URL || 'https://study.wicker.life')
|
|
35
|
+
const apiKey = String(process.env.WICKER_STUDY_API_KEY || '').trim()
|
|
36
|
+
if (!apiKey) throw new Error('WICKER_STUDY_API_KEY is required for configuration.')
|
|
37
|
+
const saved = await saveApiKey(serverUrl, apiKey, { name: 'Terminal installation', scopes: ['read', 'write'] })
|
|
38
|
+
process.stdout.write(`Wicker Study access saved securely for ${saved.server} at ${saved.path}.\n`)
|
|
39
|
+
process.exit(0)
|
|
40
|
+
} catch (error) {
|
|
41
|
+
process.stderr.write(`${error instanceof Error ? error.message : 'Wicker Study could not be configured.'}\n`)
|
|
42
|
+
process.exit(1)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
28
46
|
const baseUrl = normaliseServerUrl(process.env.WICKER_STUDY_URL || 'https://study.wicker.life')
|
|
29
47
|
const DEFAULT_CANVAS_URL = process.env.WICKER_CANVAS_URL || 'https://canvas.maastrichtuniversity.nl'
|
|
30
48
|
|
|
@@ -71,7 +89,7 @@ const json = (value) => ({ content: [{ type: 'text', text: typeof value === 'str
|
|
|
71
89
|
const failed = (error) => ({ isError: true, content: [{ type: 'text', text: error.message }] })
|
|
72
90
|
const run = (fn) => async (args) => { try { return json(await fn(args)) } catch (error) { return failed(error) } }
|
|
73
91
|
|
|
74
|
-
const server = new McpServer({ name: 'wicker-study', version: '2.
|
|
92
|
+
const server = new McpServer({ name: 'wicker-study', version: '2.7.0' })
|
|
75
93
|
const courseId = z.string().describe('Course id (e.g. "sec"). Use list_courses to discover ids.')
|
|
76
94
|
const chapterId = z.string().describe('Chapter id (e.g. "02").')
|
|
77
95
|
|
|
@@ -417,7 +435,7 @@ server.tool('wicker_sign_out',
|
|
|
417
435
|
stillConnected: Boolean(credential.apiKey),
|
|
418
436
|
note: credential.apiKey
|
|
419
437
|
? 'WICKER_STUDY_API_KEY is set in this process’s environment and still applies; unset it to disconnect fully.'
|
|
420
|
-
: `Revoke the key itself at ${baseUrl}/app/
|
|
438
|
+
: `Revoke the key itself at ${baseUrl}/app/settings?tab=api if it should stop working everywhere.`
|
|
421
439
|
}
|
|
422
440
|
}))
|
|
423
441
|
|
|
@@ -426,7 +444,7 @@ server.tool('canvas_connect',
|
|
|
426
444
|
{ canvasUrl: z.string().optional().describe(`Canvas origin. Default ${DEFAULT_CANVAS_URL}.`) },
|
|
427
445
|
run(async ({ canvasUrl }) => {
|
|
428
446
|
const origin = new URL(canvasUrl || DEFAULT_CANVAS_URL).origin
|
|
429
|
-
const settings = `${baseUrl}/app/
|
|
447
|
+
const settings = `${baseUrl}/app/settings?tab=connections`
|
|
430
448
|
const connections = (await api('/api/account/integrations/canvas')).connections || []
|
|
431
449
|
const match = connections.find((connection) => connection.origin === origin) || null
|
|
432
450
|
if (match) {
|
|
@@ -489,6 +507,7 @@ server.tool('list_mistakes', 'Mistake bank.', { open: z.boolean().optional().des
|
|
|
489
507
|
server.tool('list_mock_sessions', 'Completed mock sessions.', {}, run(() => api('/api/mocks')))
|
|
490
508
|
server.tool('get_mock_session', 'One mock session with every answer and correction.', { sessionId: z.string() }, run(({ sessionId }) => api(`/api/mocks/${encodeURIComponent(sessionId)}`)))
|
|
491
509
|
server.tool('get_academic_plan', 'Active academic programme: courses, attempts, exam dates, events, gates, summary.', {}, run(() => api('/api/academics')))
|
|
510
|
+
server.tool('get_planning_context', 'Read the student’s saved exam scenario as a compact planning model. Recorded attempts and grades are explicitly separated from private choices such as a resit, following-year deferral, expected grade, or what-if outcome. Actual academic-calendar records are grouped into dated examination windows, so one window can contain a period’s primary exams and another period’s resits. Each course includes allowed session ids and course-specific roles derived from its teaching period, calendar, transcript fallback, and verified resit rules. Returns stable session ids and a revision; call this before suggesting or changing the plan.', {}, run(() => api('/api/planning/context')))
|
|
492
511
|
server.tool('list_known_programmes', 'The catalogue of known bachelor programmes.', {}, run(() => api('/api/editorial-programmes')))
|
|
493
512
|
server.tool('get_calendar', 'Unified calendar in one call: exam attempts, personal events, registration windows, the institution calendar, saved timetable feeds (lectures, tutorials, labs), and — when Canvas is connected — Canvas assignment deadlines and Canvas course events. This is the tool for "when is my next lecture", "where do I need to be", and "what is due this week". Events carry `category`, `courseCode`, and for Canvas items a `canvasStatus`; `problems` names any source that could not be read, which is how you tell an empty week from a missing timetable feed.', { from: z.string().optional().describe('ISO date; omit for everything'), to: z.string().optional() },
|
|
494
513
|
run(async ({ from, to }) => { const data = await api('/api/calendar/events'); const events = data.events.filter((e) => (!from || String(e.start) >= from) && (!to || String(e.start) <= to)); return { ...data, events } }))
|
|
@@ -518,6 +537,14 @@ server.tool('record_chapter_read', 'Record that the student read a chapter.', {
|
|
|
518
537
|
run(({ courseId, chapterId, label }) => api('/api/activity', { method: 'POST', body: { type: 'read', courseId, chapterId, label } })))
|
|
519
538
|
server.tool('save_academic_plan', 'Save the active academic programme workspace. Pass the revision you read to avoid overwriting concurrent edits.', { workspace: z.record(z.any()), expectedRevision: z.number().int() },
|
|
520
539
|
run(({ workspace, expectedRevision }) => api('/api/academics', { method: 'PUT', body: { workspace, expectedRevision } })))
|
|
540
|
+
server.tool('update_planning_objective', 'Update one course in the student’s private exam scenario without replacing the rest of the academic record. Call get_planning_context first, use a session id from that course’s planningRules.allowedSessionIds, inspect allowedDestinations for whether the shared window is a primary or resit route for that course, explain the exact change to the student, and pass the revision you read. Invalid sittings and stale revisions are rejected.', {
|
|
541
|
+
courseId: z.string(),
|
|
542
|
+
expectedRevision: z.number().int(),
|
|
543
|
+
mode: z.enum(['current', 'resit', 'none']).optional(),
|
|
544
|
+
targetSession: z.string().max(140).nullable().optional(),
|
|
545
|
+
expectedGrade: z.number().min(0).max(100).nullable().optional(),
|
|
546
|
+
outcome: z.enum(['actual', 'pass', 'fail']).optional()
|
|
547
|
+
}, run(({ courseId: id, expectedRevision, ...objective }) => api(`/api/planning/objectives/${encodeURIComponent(id)}`, { method: 'PATCH', body: { objective, expectedRevision } })))
|
|
521
548
|
server.tool('set_course_visibility', 'Archive/unarchive or reorder a course for the student.', { courseId, archived: z.boolean().optional(), order: z.number().int().optional() },
|
|
522
549
|
run(({ courseId, archived, order }) => api(`/api/courses/${encodeURIComponent(courseId)}`, { method: 'PATCH', body: { archived, order } })))
|
|
523
550
|
|