canvas-lms-mcp 1.27.7 → 1.28.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.
Files changed (36) hide show
  1. package/README.md +27 -0
  2. package/dist/canvas/index.cjs +3 -3
  3. package/dist/canvas/index.js +2 -2
  4. package/dist/{chunk-5ALUPLEK.cjs → chunk-BJFRBBXI.cjs} +114 -41
  5. package/dist/chunk-BJFRBBXI.cjs.map +1 -0
  6. package/dist/{chunk-XTYTTVRA.js → chunk-EY7MUK6A.js} +2 -2
  7. package/dist/{chunk-XTYTTVRA.js.map → chunk-EY7MUK6A.js.map} +1 -1
  8. package/dist/{chunk-U7JB6QE2.js → chunk-FF7TOBVJ.js} +2 -2
  9. package/dist/{chunk-SYYAP7AS.cjs → chunk-FXFEJU2S.cjs} +4 -4
  10. package/dist/{chunk-SYYAP7AS.cjs.map → chunk-FXFEJU2S.cjs.map} +1 -1
  11. package/dist/{chunk-3TMKZNKA.js → chunk-MZGRCYAP.js} +348 -275
  12. package/dist/chunk-MZGRCYAP.js.map +1 -0
  13. package/dist/{chunk-NRDHKYUE.cjs → chunk-V7NUQZO2.cjs} +2 -2
  14. package/dist/{chunk-NRDHKYUE.cjs.map → chunk-V7NUQZO2.cjs.map} +1 -1
  15. package/dist/discovery/manifests.d.ts +9 -1
  16. package/dist/discovery/manifests.d.ts.map +1 -1
  17. package/dist/http.cjs +5 -5
  18. package/dist/http.js +3 -3
  19. package/dist/init.cjs +3 -3
  20. package/dist/init.js +1 -1
  21. package/dist/server.cjs +4 -4
  22. package/dist/server.js +3 -3
  23. package/dist/stdio.cjs +4 -4
  24. package/dist/stdio.js +3 -3
  25. package/dist/tools/index.d.ts.map +1 -1
  26. package/dist/tools/output/contract.d.ts +76 -0
  27. package/dist/tools/output/contract.d.ts.map +1 -0
  28. package/dist/tools/output/entities.d.ts +46 -0
  29. package/dist/tools/output/entities.d.ts.map +1 -0
  30. package/dist/tools/pages.d.ts.map +1 -1
  31. package/dist/tools/types.d.ts +12 -0
  32. package/dist/tools/types.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/chunk-3TMKZNKA.js.map +0 -1
  35. package/dist/chunk-5ALUPLEK.cjs.map +0 -1
  36. /package/dist/{chunk-U7JB6QE2.js.map → chunk-FF7TOBVJ.js.map} +0 -0
package/README.md CHANGED
@@ -165,6 +165,33 @@ Canvas applies rate limits per-user. When creating many New Quizzes items (e.g.,
165
165
  | Course Syllabus | `canvas://course/{courseId}/syllabus` | text/html |
166
166
  | Assignment Description | `canvas://course/{courseId}/assignment/{assignmentId}/description` | text/html |
167
167
 
168
+ ### Structured output
169
+
170
+ Some tools return machine-readable `structuredContent` alongside their text content, validated against an `outputSchema` the server advertises in `tools/list`. Migration is **per tool**, so the two surfaces coexist:
171
+
172
+ | Client behaviour | Migrated tool | Unmigrated tool |
173
+ |---|---|---|
174
+ | Reads `content[0].text` | Works, byte-identical to before | Works |
175
+ | Reads `structuredContent` | Works | Field is absent |
176
+ | Validates against `outputSchema` | Works | No schema advertised |
177
+
178
+ Three guarantees hold for every migrated tool:
179
+
180
+ - **The text content is unchanged.** `content[0].text` carries exactly the bytes it did before migration. `structuredContent` is added alongside it, never in place of it, so text-only clients and the interactive widgets are unaffected.
181
+ - **Canvas fields we do not declare are passed through, not stripped or rejected.** Entity schemas are open, because Canvas ships new fields continuously and a closed schema would turn each one into a failed tool call. Only the envelopes this server authors itself are closed.
182
+ - **Errors are never structured.** A tool failure returns `isError: true` with plain text, exactly as before.
183
+
184
+ A tool returning a list wraps it under a single plural key, since MCP requires an output schema to be an object:
185
+
186
+ ```jsonc
187
+ {
188
+ "content": [{ "type": "text", "text": "[ ... unchanged JSON ... ]" }],
189
+ "structuredContent": { "pages": [ /* the same value */ ] }
190
+ }
191
+ ```
192
+
193
+ Which tools are migrated is recorded per tool as `structuredOutput` in [`docs/generated/tool-manifest.json`](docs/generated/tool-manifest.json) (manifest schema 1.1). Currently: the five `pages` tools.
194
+
168
195
  ### Interactive widgets
169
196
 
170
197
  `view_course_structure` is an [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) tool: hosts that support the spec render an interactive tree explorer (collapsible modules, type-filter chips, title search, published/unpublished badges, links open in a new tab); hosts that don't fall back transparently to the same JSON payload that `get_course_structure` returns. The widget is self-contained — no external scripts, fonts, or network calls — and is shipped inline with the tool definition.
@@ -1,17 +1,17 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkSYYAP7AScjs = require('../chunk-SYYAP7AS.cjs');
3
+ var _chunkFXFEJU2Scjs = require('../chunk-FXFEJU2S.cjs');
4
4
 
5
5
 
6
6
 
7
7
 
8
8
 
9
- var _chunkNRDHKYUEcjs = require('../chunk-NRDHKYUE.cjs');
9
+ var _chunkV7NUQZO2cjs = require('../chunk-V7NUQZO2.cjs');
10
10
 
11
11
 
12
12
 
13
13
 
14
14
 
15
15
 
16
- exports.CanvasApiError = _chunkNRDHKYUEcjs.CanvasApiError; exports.CanvasClient = _chunkSYYAP7AScjs.CanvasClient; exports.CanvasHttpClient = _chunkNRDHKYUEcjs.CanvasHttpClient; exports.appendCanvasQuery = _chunkNRDHKYUEcjs.appendCanvasQuery; exports.toCanvasQuery = _chunkNRDHKYUEcjs.toCanvasQuery;
16
+ exports.CanvasApiError = _chunkV7NUQZO2cjs.CanvasApiError; exports.CanvasClient = _chunkFXFEJU2Scjs.CanvasClient; exports.CanvasHttpClient = _chunkV7NUQZO2cjs.CanvasHttpClient; exports.appendCanvasQuery = _chunkV7NUQZO2cjs.appendCanvasQuery; exports.toCanvasQuery = _chunkV7NUQZO2cjs.toCanvasQuery;
17
17
  //# sourceMappingURL=index.cjs.map
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  CanvasClient
3
- } from "../chunk-U7JB6QE2.js";
3
+ } from "../chunk-FF7TOBVJ.js";
4
4
  import {
5
5
  CanvasApiError,
6
6
  CanvasHttpClient,
7
7
  appendCanvasQuery,
8
8
  toCanvasQuery
9
- } from "../chunk-XTYTTVRA.js";
9
+ } from "../chunk-EY7MUK6A.js";
10
10
  export {
11
11
  CanvasApiError,
12
12
  CanvasClient,
@@ -15,11 +15,11 @@ var _chunkOOP4MJA7cjs = require('./chunk-OOP4MJA7.cjs');
15
15
 
16
16
 
17
17
 
18
- var _chunkSYYAP7AScjs = require('./chunk-SYYAP7AS.cjs');
18
+ var _chunkFXFEJU2Scjs = require('./chunk-FXFEJU2S.cjs');
19
19
 
20
20
 
21
21
 
22
- var _chunkNRDHKYUEcjs = require('./chunk-NRDHKYUE.cjs');
22
+ var _chunkV7NUQZO2cjs = require('./chunk-V7NUQZO2.cjs');
23
23
 
24
24
  // src/server.ts
25
25
  var _mcpjs = require('@modelcontextprotocol/sdk/server/mcp.js');
@@ -921,7 +921,7 @@ function assignmentSubmissionTools(canvas) {
921
921
 
922
922
  // src/tools/errors.ts
923
923
  function formatError(error) {
924
- if (error instanceof _chunkNRDHKYUEcjs.CanvasApiError) {
924
+ if (error instanceof _chunkV7NUQZO2cjs.CanvasApiError) {
925
925
  const status = error.status;
926
926
  const message = error.message;
927
927
  const ltiMarkers = ["Tool not configured", "tool is not configured"];
@@ -975,7 +975,7 @@ function analyticsTools(canvas) {
975
975
  inputSchema: {
976
976
  course_id: _zod.z.number().describe("The Canvas course ID"),
977
977
  search_term: _zod.z.string().describe("The keyword or phrase to search for"),
978
- content_types: _zod.z.array(_zod.z.enum(_chunkSYYAP7AScjs.SEARCH_CONTENT_TYPES)).optional().describe(
978
+ content_types: _zod.z.array(_zod.z.enum(_chunkFXFEJU2Scjs.SEARCH_CONTENT_TYPES)).optional().describe(
979
979
  "Content types to search. Defaults to all types: pages, discussions, assignments, announcements."
980
980
  )
981
981
  },
@@ -987,7 +987,7 @@ function analyticsTools(canvas) {
987
987
  const courseId = params.course_id;
988
988
  const searchTerm = params.search_term;
989
989
  const types = _nullishCoalesce(params.content_types, () => ( [
990
- ..._chunkSYYAP7AScjs.SEARCH_CONTENT_TYPES
990
+ ..._chunkFXFEJU2Scjs.SEARCH_CONTENT_TYPES
991
991
  ]));
992
992
  if (types.length === 0) return { results: [] };
993
993
  const settled = await Promise.allSettled(
@@ -1240,7 +1240,7 @@ function attentionTools(canvas, pseudonymizer) {
1240
1240
  summaryMap.set(s.id, s);
1241
1241
  }
1242
1242
  } catch (err) {
1243
- if (err instanceof _chunkNRDHKYUEcjs.CanvasApiError && err.status === 404) {
1243
+ if (err instanceof _chunkV7NUQZO2cjs.CanvasApiError && err.status === 404) {
1244
1244
  analyticsAvailable = false;
1245
1245
  } else {
1246
1246
  throw err;
@@ -1584,7 +1584,7 @@ async function fanOut(config) {
1584
1584
  skipped.push(outcome.result);
1585
1585
  }
1586
1586
  } catch (err) {
1587
- if (!(err instanceof _chunkNRDHKYUEcjs.CanvasApiError)) {
1587
+ if (!(err instanceof _chunkV7NUQZO2cjs.CanvasApiError)) {
1588
1588
  console.error(`Unexpected error ${config.errorContext(item)}:`, err);
1589
1589
  }
1590
1590
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -3885,9 +3885,9 @@ function gradingPolicyTools(canvas) {
3885
3885
  latePolicySource = "api";
3886
3886
  } else {
3887
3887
  const err = latePolicyResult.reason;
3888
- if (err instanceof _chunkNRDHKYUEcjs.CanvasApiError && err.status === 403) {
3888
+ if (err instanceof _chunkV7NUQZO2cjs.CanvasApiError && err.status === 403) {
3889
3889
  policyUnavailable = true;
3890
- } else if (err instanceof _chunkNRDHKYUEcjs.CanvasApiError && err.status === 404) {
3890
+ } else if (err instanceof _chunkV7NUQZO2cjs.CanvasApiError && err.status === 404) {
3891
3891
  latePolicySource = "default";
3892
3892
  } else {
3893
3893
  throw err;
@@ -3917,7 +3917,7 @@ function gradingPolicyTools(canvas) {
3917
3917
  }
3918
3918
  }
3919
3919
  } catch (err) {
3920
- if (!(err instanceof _chunkNRDHKYUEcjs.CanvasApiError)) throw err;
3920
+ if (!(err instanceof _chunkV7NUQZO2cjs.CanvasApiError)) throw err;
3921
3921
  if (err.status !== 403 && err.status !== 404) throw err;
3922
3922
  }
3923
3923
  if (standardTitle === null) {
@@ -4047,7 +4047,7 @@ function gradingStandardsTools(canvas) {
4047
4047
  }
4048
4048
  throw new Error("Provide either course_id or account_id.");
4049
4049
  } catch (error) {
4050
- if (error instanceof _chunkNRDHKYUEcjs.CanvasApiError && error.status === 403 && courseId === void 0 && accountId !== void 0 && !error.message.toLowerCase().includes("rate limit exceeded")) {
4050
+ if (error instanceof _chunkV7NUQZO2cjs.CanvasApiError && error.status === 403 && courseId === void 0 && accountId !== void 0 && !error.message.toLowerCase().includes("rate limit exceeded")) {
4051
4051
  throw new Error(
4052
4052
  "Creating grading standards at the account level requires Canvas admin permissions. Try creating the standard in a course context instead (use course_id).",
4053
4053
  { cause: error }
@@ -4358,7 +4358,7 @@ function outcomeTools(canvas, pseudonymizer) {
4358
4358
  title: "Get Root Outcome Group",
4359
4359
  description: "Get the root outcome group for an account or course context.",
4360
4360
  inputSchema: {
4361
- context_type: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_CONTEXT_TYPES).describe("Whether to read outcomes from an account or course context."),
4361
+ context_type: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_CONTEXT_TYPES).describe("Whether to read outcomes from an account or course context."),
4362
4362
  context_id: _zod.z.number().describe("The Canvas account ID or course ID for the context.")
4363
4363
  },
4364
4364
  annotations: { readOnlyHint: true, openWorldHint: true },
@@ -4372,7 +4372,7 @@ function outcomeTools(canvas, pseudonymizer) {
4372
4372
  title: "List Outcome Groups",
4373
4373
  description: "List all outcome groups for an account or course context.",
4374
4374
  inputSchema: {
4375
- context_type: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_CONTEXT_TYPES).describe("Whether to read outcome groups from an account or course context."),
4375
+ context_type: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_CONTEXT_TYPES).describe("Whether to read outcome groups from an account or course context."),
4376
4376
  context_id: _zod.z.number().describe("The Canvas account ID or course ID for the context.")
4377
4377
  },
4378
4378
  annotations: { readOnlyHint: true, openWorldHint: true },
@@ -4386,10 +4386,10 @@ function outcomeTools(canvas, pseudonymizer) {
4386
4386
  title: "List Outcome Group Links",
4387
4387
  description: "List all outcome links in an account or course context.",
4388
4388
  inputSchema: {
4389
- context_type: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_CONTEXT_TYPES).describe("Whether to read outcome links from an account or course context."),
4389
+ context_type: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_CONTEXT_TYPES).describe("Whether to read outcome links from an account or course context."),
4390
4390
  context_id: _zod.z.number().describe("The Canvas account ID or course ID for the context."),
4391
- outcome_style: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_DETAIL_LEVELS).optional().describe('Outcome detail level. Use "full" to include expanded outcome fields.'),
4392
- outcome_group_style: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_DETAIL_LEVELS).optional().describe(
4391
+ outcome_style: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_DETAIL_LEVELS).optional().describe('Outcome detail level. Use "full" to include expanded outcome fields.'),
4392
+ outcome_group_style: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_DETAIL_LEVELS).optional().describe(
4393
4393
  'Outcome group detail level. Use "full" to include expanded outcome group fields.'
4394
4394
  )
4395
4395
  },
@@ -4408,7 +4408,7 @@ function outcomeTools(canvas, pseudonymizer) {
4408
4408
  title: "Get Outcome Group",
4409
4409
  description: "Get details for a specific outcome group in an account or course context.",
4410
4410
  inputSchema: {
4411
- context_type: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_CONTEXT_TYPES).describe("The outcome group context type."),
4411
+ context_type: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_CONTEXT_TYPES).describe("The outcome group context type."),
4412
4412
  context_id: _zod.z.number().describe("The Canvas account ID or course ID for the context."),
4413
4413
  outcome_group_id: _zod.z.number().describe("The Canvas outcome group ID.")
4414
4414
  },
@@ -4424,10 +4424,10 @@ function outcomeTools(canvas, pseudonymizer) {
4424
4424
  title: "List Outcome Group Outcomes",
4425
4425
  description: "List the linked outcomes directly under a specific outcome group.",
4426
4426
  inputSchema: {
4427
- context_type: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_CONTEXT_TYPES).describe("The outcome group context type."),
4427
+ context_type: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_CONTEXT_TYPES).describe("The outcome group context type."),
4428
4428
  context_id: _zod.z.number().describe("The Canvas account ID or course ID for the context."),
4429
4429
  outcome_group_id: _zod.z.number().describe("The Canvas outcome group ID."),
4430
- outcome_style: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_DETAIL_LEVELS).optional().describe('Outcome detail level. Use "full" to include expanded outcome fields.')
4430
+ outcome_style: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_DETAIL_LEVELS).optional().describe('Outcome detail level. Use "full" to include expanded outcome fields.')
4431
4431
  },
4432
4432
  annotations: { readOnlyHint: true, openWorldHint: true },
4433
4433
  handler: async (params) => canvas.outcomes.listGroupOutcomes(
@@ -4444,7 +4444,7 @@ function outcomeTools(canvas, pseudonymizer) {
4444
4444
  title: "List Outcome Group Subgroups",
4445
4445
  description: "List the immediate child outcome groups under a specific outcome group.",
4446
4446
  inputSchema: {
4447
- context_type: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_CONTEXT_TYPES).describe("The outcome group context type."),
4447
+ context_type: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_CONTEXT_TYPES).describe("The outcome group context type."),
4448
4448
  context_id: _zod.z.number().describe("The Canvas account ID or course ID for the context."),
4449
4449
  outcome_group_id: _zod.z.number().describe("The Canvas outcome group ID.")
4450
4450
  },
@@ -4514,14 +4514,14 @@ function outcomeTools(canvas, pseudonymizer) {
4514
4514
  inputSchema: {
4515
4515
  course_id: _zod.z.number().describe("The Canvas course ID."),
4516
4516
  aggregate: _zod.z.enum(["course"]).optional().describe("Aggregate all student rollups into a single course-level rollup."),
4517
- aggregate_stat: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_ROLLUP_AGGREGATE_STATS).optional().describe('Statistic to use when aggregate="course".'),
4517
+ aggregate_stat: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_ROLLUP_AGGREGATE_STATS).optional().describe('Statistic to use when aggregate="course".'),
4518
4518
  user_ids: _zod.z.array(_zod.z.union([_zod.z.number(), _zod.z.string()])).optional().describe('Optional Canvas user IDs or SIS user IDs prefixed with "sis_user_id:".'),
4519
4519
  outcome_ids: _zod.z.array(_zod.z.number()).optional().describe("Optional outcome IDs to restrict the rollups."),
4520
4520
  include_courses: _zod.z.boolean().optional().describe("Include linked course details in the response payload."),
4521
- exclude: _zod.z.array(_zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_EXCLUDE_OPTIONS)).optional().describe("Optional rollup exclusions for missing users or missing outcome results."),
4522
- sort_by: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_ROLLUP_SORT_BY).optional().describe("Sort rollups by student name or by a specific outcome score."),
4521
+ exclude: _zod.z.array(_zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_EXCLUDE_OPTIONS)).optional().describe("Optional rollup exclusions for missing users or missing outcome results."),
4522
+ sort_by: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_ROLLUP_SORT_BY).optional().describe("Sort rollups by student name or by a specific outcome score."),
4523
4523
  sort_outcome_id: _zod.z.number().optional().describe('Outcome ID to sort by when sort_by="outcome".'),
4524
- sort_order: _zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_SORT_ORDER).optional().describe("Sort order to apply when sorting rollups."),
4524
+ sort_order: _zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_SORT_ORDER).optional().describe("Sort order to apply when sorting rollups."),
4525
4525
  add_defaults: _zod.z.boolean().optional().describe("Include default mastery colors and levels when Canvas supports it.")
4526
4526
  },
4527
4527
  annotations: { readOnlyHint: true, openWorldHint: true },
@@ -4571,7 +4571,7 @@ function outcomeTools(canvas, pseudonymizer) {
4571
4571
  description: "Get mastery distribution analytics for outcomes in a course, optionally filtered by students or outcomes.",
4572
4572
  inputSchema: {
4573
4573
  course_id: _zod.z.number().describe("The Canvas course ID."),
4574
- exclude: _zod.z.array(_zod.z.enum(_chunkSYYAP7AScjs.OUTCOME_EXCLUDE_OPTIONS)).optional().describe("Optional exclusions for missing users or missing outcome results."),
4574
+ exclude: _zod.z.array(_zod.z.enum(_chunkFXFEJU2Scjs.OUTCOME_EXCLUDE_OPTIONS)).optional().describe("Optional exclusions for missing users or missing outcome results."),
4575
4575
  outcome_ids: _zod.z.array(_zod.z.number()).optional().describe("Optional outcome IDs to restrict the distribution results."),
4576
4576
  student_ids: _zod.z.array(_zod.z.union([_zod.z.number(), _zod.z.string()])).optional().describe('Optional Canvas student IDs or SIS user IDs prefixed with "sis_user_id:".'),
4577
4577
  include_alignment_distributions: _zod.z.boolean().optional().describe("Include contributing score distributions for alignments."),
@@ -4595,6 +4595,60 @@ function outcomeTools(canvas, pseudonymizer) {
4595
4595
 
4596
4596
  // src/tools/pages.ts
4597
4597
 
4598
+
4599
+ // src/tools/output/contract.ts
4600
+
4601
+ var ACK_MESSAGE = "Operation completed successfully.";
4602
+ function objectOutput(schema) {
4603
+ return { kind: "object", schema };
4604
+ }
4605
+ function listOutput(key, entity) {
4606
+ return { kind: "list", key, schema: _zod.z.strictObject({ [key]: _zod.z.array(entity) }) };
4607
+ }
4608
+ function buildEnvelope(contract, value) {
4609
+ switch (contract.kind) {
4610
+ case "list":
4611
+ return { [contract.key]: value };
4612
+ case "ack":
4613
+ return { ok: true, message: ACK_MESSAGE };
4614
+ case "object":
4615
+ return value;
4616
+ }
4617
+ }
4618
+ function validateEnvelope(contract, envelope) {
4619
+ const parsed = contract.schema.safeParse(envelope);
4620
+ if (parsed.success) return { ok: true };
4621
+ return {
4622
+ ok: false,
4623
+ issues: parsed.error.issues.map((issue) => {
4624
+ const path = issue.path.join(".");
4625
+ return path === "" ? issue.message : `${path}: ${issue.message}`;
4626
+ })
4627
+ };
4628
+ }
4629
+ function outputContractError(toolName) {
4630
+ return `Canvas returned data this tool could not describe (${toolName}). This is a canvas-lms-mcp bug, not a Canvas permission problem \u2014 please report it. Re-run with CANVAS_LOG_LEVEL=debug for detail.`;
4631
+ }
4632
+
4633
+ // src/tools/output/entities.ts
4634
+
4635
+ var canvasPageSchema = _zod.z.looseObject({
4636
+ page_id: _zod.z.number(),
4637
+ url: _zod.z.string(),
4638
+ title: _zod.z.string(),
4639
+ // Absent on the paginated list endpoint, which returns stubs without a body.
4640
+ body: _zod.z.string().nullish(),
4641
+ published: _zod.z.boolean().nullish(),
4642
+ created_at: _zod.z.string().nullish(),
4643
+ updated_at: _zod.z.string().nullish(),
4644
+ editing_roles: _zod.z.string().nullish()
4645
+ });
4646
+ var pageDeletionSchema = _zod.z.strictObject({
4647
+ deleted: _zod.z.literal(true),
4648
+ page_url: _zod.z.string()
4649
+ });
4650
+
4651
+ // src/tools/pages.ts
4598
4652
  function pageTools(canvas) {
4599
4653
  return [
4600
4654
  {
@@ -4605,6 +4659,7 @@ function pageTools(canvas) {
4605
4659
  inputSchema: {
4606
4660
  course_id: _zod.z.number().describe("The Canvas course ID")
4607
4661
  },
4662
+ output: listOutput("pages", canvasPageSchema),
4608
4663
  annotations: {
4609
4664
  readOnlyHint: true,
4610
4665
  openWorldHint: true
@@ -4623,6 +4678,7 @@ function pageTools(canvas) {
4623
4678
  course_id: _zod.z.number().describe("The Canvas course ID"),
4624
4679
  page_url: _zod.z.string().describe('The page URL slug (e.g. "welcome-page")')
4625
4680
  },
4681
+ output: objectOutput(canvasPageSchema),
4626
4682
  annotations: {
4627
4683
  readOnlyHint: true,
4628
4684
  openWorldHint: true
@@ -4644,6 +4700,7 @@ function pageTools(canvas) {
4644
4700
  published: _zod.z.boolean().optional().describe("Whether the page is published"),
4645
4701
  editing_roles: _zod.z.string().optional().describe('Who can edit: "teachers", "students", "members", or "public"')
4646
4702
  },
4703
+ output: objectOutput(canvasPageSchema),
4647
4704
  annotations: {
4648
4705
  destructiveHint: true,
4649
4706
  openWorldHint: true
@@ -4670,6 +4727,7 @@ function pageTools(canvas) {
4670
4727
  published: _zod.z.boolean().optional().describe("Whether the page is published"),
4671
4728
  editing_roles: _zod.z.string().optional().describe('Who can edit: "teachers", "students", "members", or "public"')
4672
4729
  },
4730
+ output: objectOutput(canvasPageSchema),
4673
4731
  annotations: {
4674
4732
  destructiveHint: true,
4675
4733
  openWorldHint: true
@@ -4693,6 +4751,7 @@ function pageTools(canvas) {
4693
4751
  course_id: _zod.z.number().describe("The Canvas course ID"),
4694
4752
  page_url: _zod.z.string().describe("The page URL slug to delete")
4695
4753
  },
4754
+ output: objectOutput(pageDeletionSchema),
4696
4755
  annotations: {
4697
4756
  destructiveHint: true,
4698
4757
  openWorldHint: true
@@ -5292,7 +5351,7 @@ function newQuizAccommodationTools(canvas) {
5292
5351
  extra_attempts: record.extra_attempts
5293
5352
  });
5294
5353
  } catch (err) {
5295
- const message = err instanceof _chunkNRDHKYUEcjs.CanvasApiError ? err.message : "Unknown error";
5354
+ const message = err instanceof _chunkV7NUQZO2cjs.CanvasApiError ? err.message : "Unknown error";
5296
5355
  failed.push({
5297
5356
  assignment_id: assignmentId,
5298
5357
  time_multiplier: null,
@@ -5832,8 +5891,8 @@ function studentTools(canvas, pseudonymizer) {
5832
5891
  } else {
5833
5892
  coursesFailed.push({
5834
5893
  course_id: result.courseId,
5835
- status: result.err instanceof _chunkNRDHKYUEcjs.CanvasApiError ? result.err.status : null,
5836
- message: result.err instanceof _chunkNRDHKYUEcjs.CanvasApiError ? result.err.message : String(result.err)
5894
+ status: result.err instanceof _chunkV7NUQZO2cjs.CanvasApiError ? result.err.status : null,
5895
+ message: result.err instanceof _chunkV7NUQZO2cjs.CanvasApiError ? result.err.message : String(result.err)
5837
5896
  });
5838
5897
  }
5839
5898
  }
@@ -5973,7 +6032,7 @@ function studentSearchTools(canvas, pseudonymizer) {
5973
6032
  const coursesToScan = truncated ? sorted.slice(0, maxCourses) : sorted;
5974
6033
  const coursesFailed = [];
5975
6034
  const perCourseMatches = [];
5976
- const scanResults = await _chunkSYYAP7AScjs.mapWithConcurrency.call(void 0,
6035
+ const scanResults = await _chunkFXFEJU2Scjs.mapWithConcurrency.call(void 0,
5977
6036
  coursesToScan,
5978
6037
  CONCURRENT_COURSE_LIMIT,
5979
6038
  async (course) => {
@@ -5996,8 +6055,8 @@ function studentSearchTools(canvas, pseudonymizer) {
5996
6055
  } else {
5997
6056
  coursesFailed.push({
5998
6057
  course_id: r.course.id,
5999
- status: r.err instanceof _chunkNRDHKYUEcjs.CanvasApiError ? r.err.status : null,
6000
- message: r.err instanceof _chunkNRDHKYUEcjs.CanvasApiError ? r.err.message : String(r.err)
6058
+ status: r.err instanceof _chunkV7NUQZO2cjs.CanvasApiError ? r.err.status : null,
6059
+ message: r.err instanceof _chunkV7NUQZO2cjs.CanvasApiError ? r.err.message : String(r.err)
6001
6060
  });
6002
6061
  }
6003
6062
  }
@@ -6675,7 +6734,7 @@ async function scanQuizzes(canvas, courseId, warnings) {
6675
6734
  canvas.assignments.list(courseId)
6676
6735
  ]);
6677
6736
  const classicQuizzes = quizzes.filter((quiz) => CLASSIC_QUIZ_TYPES3.has(quiz.quiz_type));
6678
- const classicFindings = await _chunkSYYAP7AScjs.mapWithConcurrency.call(void 0,
6737
+ const classicFindings = await _chunkFXFEJU2Scjs.mapWithConcurrency.call(void 0,
6679
6738
  classicQuizzes,
6680
6739
  QUIZ_SCAN_CONCURRENCY_LIMIT,
6681
6740
  async (quiz) => {
@@ -6701,7 +6760,7 @@ async function scanQuizzes(canvas, courseId, warnings) {
6701
6760
  }
6702
6761
  );
6703
6762
  const newQuizAssignments = assignments.filter((a) => a.is_quiz_lti_assignment === true);
6704
- const newQuizFindings = await _chunkSYYAP7AScjs.mapWithConcurrency.call(void 0,
6763
+ const newQuizFindings = await _chunkFXFEJU2Scjs.mapWithConcurrency.call(void 0,
6705
6764
  newQuizAssignments,
6706
6765
  QUIZ_SCAN_CONCURRENCY_LIMIT,
6707
6766
  async (assignment) => {
@@ -7051,7 +7110,7 @@ async function scanQuizzesAccessibility(canvas, courseId, warnings) {
7051
7110
  canvas.assignments.list(courseId)
7052
7111
  ]);
7053
7112
  const classicQuizzes = quizzes.filter((quiz) => CLASSIC_QUIZ_TYPES4.has(quiz.quiz_type));
7054
- const classicFindings = await _chunkSYYAP7AScjs.mapWithConcurrency.call(void 0,
7113
+ const classicFindings = await _chunkFXFEJU2Scjs.mapWithConcurrency.call(void 0,
7055
7114
  classicQuizzes,
7056
7115
  QUIZ_SCAN_CONCURRENCY_LIMIT2,
7057
7116
  async (quiz) => {
@@ -7076,7 +7135,7 @@ async function scanQuizzesAccessibility(canvas, courseId, warnings) {
7076
7135
  }
7077
7136
  );
7078
7137
  const newQuizAssignments = assignments.filter((a) => a.is_quiz_lti_assignment === true);
7079
- const newQuizFindings = await _chunkSYYAP7AScjs.mapWithConcurrency.call(void 0,
7138
+ const newQuizFindings = await _chunkFXFEJU2Scjs.mapWithConcurrency.call(void 0,
7080
7139
  newQuizAssignments,
7081
7140
  QUIZ_SCAN_CONCURRENCY_LIMIT2,
7082
7141
  async (assignment) => {
@@ -7662,6 +7721,18 @@ function buildHandler(tool, pseudonymizer) {
7662
7721
  const response = {
7663
7722
  content: [{ type: "text", text }]
7664
7723
  };
7724
+ if (tool.output) {
7725
+ const structured = buildEnvelope(tool.output, value);
7726
+ const validation = validateEnvelope(tool.output, structured);
7727
+ if (!validation.ok) {
7728
+ console.error(`Output contract violation in "${tool.name}":`, validation.issues);
7729
+ return {
7730
+ content: [{ type: "text", text: outputContractError(tool.name) }],
7731
+ isError: true
7732
+ };
7733
+ }
7734
+ response.structuredContent = structured;
7735
+ }
7665
7736
  if (_optionalChain([pseudonymizer, 'optionalAccess', _144 => _144.isEnabled, 'call', _145 => _145()])) {
7666
7737
  response._meta = { pseudonymized: true, note: PSEUDONYM_META_NOTE };
7667
7738
  }
@@ -7673,7 +7744,7 @@ function buildHandler(tool, pseudonymizer) {
7673
7744
  }
7674
7745
  return response;
7675
7746
  } catch (error) {
7676
- if (!(error instanceof _chunkNRDHKYUEcjs.CanvasApiError)) {
7747
+ if (!(error instanceof _chunkV7NUQZO2cjs.CanvasApiError)) {
7677
7748
  console.error(`Unexpected error in tool "${tool.name}":`, error);
7678
7749
  }
7679
7750
  return {
@@ -7695,6 +7766,7 @@ function registerAllTools(server, canvas, pseudonymizer, role, features) {
7695
7766
  title: tool.title,
7696
7767
  description: tool.description,
7697
7768
  inputSchema: tool.inputSchema,
7769
+ ...tool.output ? { outputSchema: tool.output.schema } : {},
7698
7770
  annotations: tool.annotations,
7699
7771
  _meta: { ui: { resourceUri: tool.ui.resourceUri } }
7700
7772
  },
@@ -7707,6 +7779,7 @@ function registerAllTools(server, canvas, pseudonymizer, role, features) {
7707
7779
  title: tool.title,
7708
7780
  description: tool.description,
7709
7781
  inputSchema: tool.inputSchema,
7782
+ ...tool.output ? { outputSchema: tool.output.schema } : {},
7710
7783
  annotations: tool.annotations
7711
7784
  },
7712
7785
  handler
@@ -7758,7 +7831,7 @@ function registerSyllabusResource(server, canvas) {
7758
7831
  ]
7759
7832
  };
7760
7833
  } catch (error) {
7761
- if (!(error instanceof _chunkNRDHKYUEcjs.CanvasApiError)) {
7834
+ if (!(error instanceof _chunkV7NUQZO2cjs.CanvasApiError)) {
7762
7835
  console.error("Unexpected error in syllabus resource:", error);
7763
7836
  }
7764
7837
  return {
@@ -7812,7 +7885,7 @@ function registerAssignmentDescriptionResource(server, canvas) {
7812
7885
  ]
7813
7886
  };
7814
7887
  } catch (error) {
7815
- if (!(error instanceof _chunkNRDHKYUEcjs.CanvasApiError)) {
7888
+ if (!(error instanceof _chunkV7NUQZO2cjs.CanvasApiError)) {
7816
7889
  console.error("Unexpected error in assignment-description resource:", error);
7817
7890
  }
7818
7891
  return {
@@ -8864,14 +8937,14 @@ function registerAllResources(server, canvas) {
8864
8937
 
8865
8938
  // src/server.ts
8866
8939
  function createCanvasMCPServer(config) {
8867
- const canvas = new (0, _chunkSYYAP7AScjs.CanvasClient)({
8940
+ const canvas = new (0, _chunkFXFEJU2Scjs.CanvasClient)({
8868
8941
  token: config.token,
8869
8942
  baseUrl: config.baseUrl
8870
8943
  });
8871
8944
  const pseudonymizer = _nullishCoalesce(config.pseudonymizer, () => ( new Pseudonymizer({ baseUrl: config.baseUrl })));
8872
8945
  const server = new (0, _mcpjs.McpServer)({
8873
8946
  name: "canvas-lms-mcp",
8874
- version: _chunkNRDHKYUEcjs.version
8947
+ version: _chunkV7NUQZO2cjs.version
8875
8948
  });
8876
8949
  const features = {
8877
8950
  assignmentSubmission: config.enableAssignmentSubmission
@@ -8885,4 +8958,4 @@ function createCanvasMCPServer(config) {
8885
8958
 
8886
8959
 
8887
8960
  exports.Pseudonymizer = Pseudonymizer; exports.createCanvasMCPServer = createCanvasMCPServer;
8888
- //# sourceMappingURL=chunk-5ALUPLEK.cjs.map
8961
+ //# sourceMappingURL=chunk-BJFRBBXI.cjs.map