canvas-lms-mcp 1.19.2 → 1.21.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 CHANGED
@@ -17,7 +17,7 @@ MCP server for [Canvas LMS](https://www.instructure.com/canvas). Read courses, a
17
17
 
18
18
  1. **[Download `canvas-lms-mcp.mcpb`](https://github.com/bruchris/canvas-lms-mcp/releases/latest/download/canvas-lms-mcp.mcpb)** from the latest release.
19
19
  2. Double-click the file (or drag it into Claude Desktop's Extensions settings).
20
- 3. When prompted, paste your Canvas API token and Canvas API base URL (e.g. `https://school.instructure.com/api/v1`).
20
+ 3. When prompted, paste your Canvas API token and Canvas API base URL (e.g. `https://school.instructure.com/api/v1`). Teachers and staff handling student data can also flip **FERPA mode — pseudonymize students** on in the same dialog ([what it does](#ferpa-mode-student-pseudonymization)).
21
21
 
22
22
  No terminal, no Node.js install, no config-file editing — Claude Desktop bundles the runtime and handles config for you. The same `.mcpb` works in Claude Code and MCP for Windows.
23
23
 
@@ -1,17 +1,17 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkNG6KBHE4cjs = require('../chunk-NG6KBHE4.cjs');
3
+ var _chunkU5YBMWJ7cjs = require('../chunk-U5YBMWJ7.cjs');
4
4
 
5
5
 
6
6
 
7
7
 
8
8
 
9
- var _chunkZB3IRQKCcjs = require('../chunk-ZB3IRQKC.cjs');
9
+ var _chunkNQQL7WH6cjs = require('../chunk-NQQL7WH6.cjs');
10
10
 
11
11
 
12
12
 
13
13
 
14
14
 
15
15
 
16
- exports.CanvasApiError = _chunkZB3IRQKCcjs.CanvasApiError; exports.CanvasClient = _chunkNG6KBHE4cjs.CanvasClient; exports.CanvasHttpClient = _chunkZB3IRQKCcjs.CanvasHttpClient; exports.appendCanvasQuery = _chunkZB3IRQKCcjs.appendCanvasQuery; exports.toCanvasQuery = _chunkZB3IRQKCcjs.toCanvasQuery;
16
+ exports.CanvasApiError = _chunkNQQL7WH6cjs.CanvasApiError; exports.CanvasClient = _chunkU5YBMWJ7cjs.CanvasClient; exports.CanvasHttpClient = _chunkNQQL7WH6cjs.CanvasHttpClient; exports.appendCanvasQuery = _chunkNQQL7WH6cjs.appendCanvasQuery; exports.toCanvasQuery = _chunkNQQL7WH6cjs.toCanvasQuery;
17
17
  //# sourceMappingURL=index.cjs.map
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  CanvasClient
3
- } from "../chunk-32RJEARZ.js";
3
+ } from "../chunk-FJT5UMRL.js";
4
4
  import {
5
5
  CanvasApiError,
6
6
  CanvasHttpClient,
7
7
  appendCanvasQuery,
8
8
  toCanvasQuery
9
- } from "../chunk-HZU2YDP4.js";
9
+ } from "../chunk-Z635XMKW.js";
10
10
  export {
11
11
  CanvasApiError,
12
12
  CanvasClient,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CanvasHttpClient
3
- } from "./chunk-HZU2YDP4.js";
3
+ } from "./chunk-Z635XMKW.js";
4
4
 
5
5
  // src/canvas/courses.ts
6
6
  var DEFAULT_LIST_INCLUDE = ["term"];
@@ -1395,4 +1395,4 @@ export {
1395
1395
  OUTCOME_EXCLUDE_OPTIONS,
1396
1396
  CanvasClient
1397
1397
  };
1398
- //# sourceMappingURL=chunk-32RJEARZ.js.map
1398
+ //# sourceMappingURL=chunk-FJT5UMRL.js.map
@@ -1,5 +1,5 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// package.json
2
- var version = "1.19.2";
2
+ var version = "1.21.0";
3
3
 
4
4
  // src/canvas/query.ts
5
5
  function appendCanvasQuery(target, params) {
@@ -163,4 +163,4 @@ var CanvasHttpClient = class {
163
163
 
164
164
 
165
165
  exports.version = version; exports.appendCanvasQuery = appendCanvasQuery; exports.toCanvasQuery = toCanvasQuery; exports.CanvasApiError = CanvasApiError; exports.CanvasHttpClient = CanvasHttpClient;
166
- //# sourceMappingURL=chunk-ZB3IRQKC.cjs.map
166
+ //# sourceMappingURL=chunk-NQQL7WH6.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-ZB3IRQKC.cjs","../package.json","../src/canvas/query.ts","../src/canvas/client.ts"],"names":[],"mappings":"AAAA;ACEE,IAAA,QAAA,EAAW,QAAA;ADAb;AACA;AEGO,SAAS,iBAAA,CAAkB,MAAA,EAAyB,MAAA,EAAkC;AAC3F,EAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,MAAA;AACb,EAAA,IAAA,CAAA,MAAW,CAAC,GAAA,EAAK,KAAK,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjD,IAAA,GAAA,CAAI,MAAA,IAAU,KAAA,EAAA,GAAa,MAAA,IAAU,IAAA,EAAM,QAAA;AAC3C,IAAA,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,MAAA,GAAA,CAAI,KAAA,CAAM,OAAA,IAAW,CAAA,EAAG,QAAA;AACxB,MAAA,MAAM,SAAA,EAAW,GAAA,CAAI,QAAA,CAAS,IAAI,EAAA,EAAI,IAAA,EAAM,CAAA,EAAA;AAClB,MAAA;AACiB,QAAA;AACL,QAAA;AACtC,MAAA;AACK,IAAA;AACwB,MAAA;AAC/B,IAAA;AACF,EAAA;AACF;AAE2E;AACtC,EAAA;AACH,EAAA;AACzB,EAAA;AACT;AFFmD;AACA;AGtBT;AACxC,EAAA;AACA,EAAA;AAE+D,EAAA;AAChD,IAAA;AACD,IAAA;AACE,IAAA;AACE,IAAA;AAClB,EAAA;AACF;AAEqC;AACO;AAWd;AACpB,EAAA;AACA,EAAA;AACA,EAAA;AAEgC,EAAA;AAClB,IAAA;AAC2B,IAAA;AACd,IAAA;AACnC,EAAA;AAEsB,EAAA;AACR,IAAA;AACd,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOmF,EAAA;AACtD,IAAA;AACa,IAAA;AAC7B,IAAA;AACiB,MAAA;AACkB,MAAA;AACtB,MAAA;AACxB,IAAA;AAEsC,IAAA;AACQ,IAAA;AAClC,MAAA;AACR,QAAA;AACF,MAAA;AACF,IAAA;AAEwC,IAAA;AACH,MAAA;AACrB,MAAA;AAChB,IAAA;AACe,IAAA;AACa,MAAA;AAC5B,IAAA;AAEkC,IAAA;AAC7B,MAAA;AACM,MAAA;AACJ,QAAA;AACK,QAAA;AACV,MAAA;AACD,IAAA;AAEiB,IAAA;AAC0B,MAAA;AAEX,MAAA;AACY,MAAA;AAC7C,IAAA;AAE6B,IAAA;AACpB,MAAA;AACT,IAAA;AAEqB,IAAA;AACvB,EAAA;AAE8E,EAAA;AAC7B,IAAA;AACR,IAAA;AACC,MAAA;AACxC,IAAA;AAC0C,IAAA;AAEpB,IAAA;AACoB,IAAA;AAC9B,IAAA;AAEmB,IAAA;AACS,MAAA;AAC3B,QAAA;AAC4B,UAAA;AACrB,UAAA;AAChB,QAAA;AACD,MAAA;AAEiB,MAAA;AAC0B,QAAA;AAEtB,QAAA;AACuB,QAAA;AAC7C,MAAA;AAEkC,MAAA;AACd,MAAA;AACpB,MAAA;AAEsC,MAAA;AACxC,IAAA;AAEO,IAAA;AACT,EAAA;AAKE,EAAA;AAE+C,IAAA;AACR,IAAA;AACC,MAAA;AACxC,IAAA;AAC0C,IAAA;AAEpB,IAAA;AACoB,IAAA;AAC9B,IAAA;AAEmB,IAAA;AACS,MAAA;AAC3B,QAAA;AAC4B,UAAA;AACrB,UAAA;AAChB,QAAA;AACD,MAAA;AAEiB,MAAA;AAC0B,QAAA;AAEtB,QAAA;AACuB,QAAA;AAC7C,MAAA;AAEkC,MAAA;AACC,MAAA;AACf,MAAA;AACpB,MAAA;AAEsC,MAAA;AACxC,IAAA;AAEO,IAAA;AACT,EAAA;AAEgE,EAAA;AACtC,IAAA;AACO,IAAA;AACV,IAAA;AACvB,EAAA;AACF;AHnBmD;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-ZB3IRQKC.cjs","sourcesContent":[null,"{\n \"name\": \"canvas-lms-mcp\",\n \"version\": \"1.19.2\",\n \"mcpName\": \"io.github.bruchris/canvas-lms-mcp\",\n \"description\": \"TypeScript MCP 1.x server for Canvas LMS — 116 tools across Canvas courses, assignments, grades, gradebook history, quizzes, New Quizzes (LTI), outcomes, discussions, admin workflows, and more.\",\n \"type\": \"module\",\n \"types\": \"./dist/server.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\",\n \"require\": \"./dist/server.cjs\"\n },\n \"./stdio\": {\n \"types\": \"./dist/stdio.d.ts\",\n \"import\": \"./dist/stdio.js\",\n \"require\": \"./dist/stdio.cjs\"\n },\n \"./http\": {\n \"types\": \"./dist/http.d.ts\",\n \"import\": \"./dist/http.js\",\n \"require\": \"./dist/http.cjs\"\n },\n \"./canvas\": {\n \"types\": \"./dist/canvas/index.d.ts\",\n \"import\": \"./dist/canvas/index.js\",\n \"require\": \"./dist/canvas/index.cjs\"\n }\n },\n \"bin\": {\n \"canvas-lms-mcp\": \"bin/canvas-lms-mcp.js\"\n },\n \"files\": [\n \"bin/\",\n \"dist/\"\n ],\n \"scripts\": {\n \"dev\": \"tsup --watch\",\n \"build\": \"tsup && tsc -p tsconfig.build.json\",\n \"generate:manifests\": \"tsx scripts/generate-manifests.ts\",\n \"mcpb:pack\": \"node scripts/pack-mcpb.mjs\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"lint\": \"eslint src/ tests/ && prettier --check src/ tests/\",\n \"lint:fix\": \"eslint src/ tests/ --fix && prettier --write src/ tests/\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"keywords\": [\n \"canvas\",\n \"canvas-lms\",\n \"lms\",\n \"mcp\",\n \"model-context-protocol\",\n \"education\",\n \"grading\",\n \"ai-agent\",\n \"ai-tools\",\n \"claude-desktop\",\n \"cursor\",\n \"vscode\",\n \"instructure\",\n \"education-technology\",\n \"edtech\",\n \"teacher\",\n \"student\",\n \"grading-automation\",\n \"typescript\"\n ],\n \"author\": \"Christian Bru\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/bruchris/canvas-lms-mcp.git\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@10.11.0\",\n \"pnpm\": {\n \"overrides\": {\n \"hono\": \"^4.12.16\",\n \"ip-address\": \"^10.1.1\",\n \"fast-uri\": \"^3.1.2\",\n \"brace-expansion\": \"^5.0.6\",\n \"qs\": \"^6.15.2\"\n }\n },\n \"dependencies\": {\n \"@iarna/toml\": \"^2.2.5\",\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"prompts\": \"^2.4.2\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@types/node\": \"^25.9.1\",\n \"@types/prompts\": \"^2.4.9\",\n \"@typescript-eslint/eslint-plugin\": \"^8.60.0\",\n \"@typescript-eslint/parser\": \"^8.60.0\",\n \"@vitest/coverage-v8\": \"^4.1.7\",\n \"eslint\": \"^10.4.0\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-prettier\": \"^5.5.6\",\n \"prettier\": \"^3.8.3\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.22.3\",\n \"typescript\": \"^6.0.3\",\n \"typescript-eslint\": \"^8.60.0\",\n \"vitest\": \"^4.1.7\"\n }\n}\n","export type CanvasQueryPrimitive = string | number | boolean\n\nexport type CanvasQueryValue = CanvasQueryPrimitive | ReadonlyArray<CanvasQueryPrimitive>\n\nexport type CanvasQueryParams = Record<string, CanvasQueryValue | undefined | null>\n\nexport function appendCanvasQuery(target: URLSearchParams, params?: CanvasQueryParams): void {\n if (!params) return\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n const arrayKey = key.endsWith('[]') ? key : `${key}[]`\n for (const item of value) {\n if (item === undefined || item === null) continue\n target.append(arrayKey, String(item))\n }\n } else {\n target.set(key, String(value))\n }\n }\n}\n\nexport function toCanvasQuery(params?: CanvasQueryParams): URLSearchParams {\n const search = new URLSearchParams()\n appendCanvasQuery(search, params)\n return search\n}\n","import { version } from '../../package.json'\nimport { appendCanvasQuery, type CanvasQueryParams } from './query'\nimport type { CanvasClientConfig, CanvasErrorResponse } from './types'\n\nexport class CanvasApiError extends Error {\n status: number\n endpoint: string\n\n constructor(message: string, status: number, endpoint: string) {\n super(message)\n this.name = 'CanvasApiError'\n this.status = status\n this.endpoint = endpoint\n }\n}\n\nconst DEFAULT_MAX_PAGINATION_PAGES = 1000\nconst USER_AGENT = `canvas-lms-mcp/${version}`\n\nexport interface CanvasRequestOptions extends RequestInit {\n /**\n * Query params to append to the endpoint URL. Arrays produce repeated\n * `key[]=v1&key[]=v2` entries (the convention Canvas uses for `include[]`).\n * Preserves any query string already present on the endpoint.\n */\n query?: CanvasQueryParams\n}\n\nexport class CanvasHttpClient {\n private token: string\n private _baseUrl: string\n private maxPaginationPages: number\n\n constructor(config: CanvasClientConfig) {\n this.token = config.token\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '')\n this.maxPaginationPages = config.maxPaginationPages ?? DEFAULT_MAX_PAGINATION_PAGES\n }\n\n get baseUrl(): string {\n return this._baseUrl\n }\n\n /**\n * Makes a single authenticated request to the Canvas API.\n * Returns `undefined` (typed as `T`) when Canvas responds with 204 No Content,\n * which is the expected response for DELETE operations.\n */\n async request<T>(endpoint: string, options: CanvasRequestOptions = {}): Promise<T> {\n const { query, ...init } = options\n let url = endpoint.startsWith('http') ? endpoint : `${this._baseUrl}${endpoint}`\n if (query) {\n const parsed = new URL(url)\n appendCanvasQuery(parsed.searchParams, query)\n url = parsed.toString()\n }\n\n const method = (init.method ?? 'GET').toUpperCase()\n if (init.body != null && (method === 'GET' || method === 'HEAD')) {\n throw new Error(\n `GET requests must not include a body (Canvas CloudFront CDN rejects them with 403): ${endpoint}`,\n )\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n }\n if (init.body) {\n headers['Content-Type'] = 'application/json'\n }\n\n const response = await fetch(url, {\n ...init,\n headers: {\n ...headers,\n ...init.headers,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json() as Promise<T>\n }\n\n async paginate<T>(endpoint: string, params?: CanvasQueryParams): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', '100')\n }\n appendCanvasQuery(url.searchParams, params)\n\n const results: T[] = []\n let nextUrl: string | null = url.toString()\n let pages = 0\n\n while (nextUrl && pages < this.maxPaginationPages) {\n const response = await fetch(nextUrl, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n const data = (await response.json()) as T[]\n results.push(...data)\n pages++\n\n nextUrl = this.parseNextLink(response.headers.get('Link'))\n }\n\n return results\n }\n\n async paginateEnvelope<T>(\n endpoint: string,\n envelopeKey: string,\n params?: CanvasQueryParams,\n ): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', '100')\n }\n appendCanvasQuery(url.searchParams, params)\n\n const results: T[] = []\n let nextUrl: string | null = url.toString()\n let pages = 0\n\n while (nextUrl && pages < this.maxPaginationPages) {\n const response = await fetch(nextUrl, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n const body = (await response.json()) as Record<string, T[]>\n const data = body[envelopeKey] ?? []\n results.push(...data)\n pages++\n\n nextUrl = this.parseNextLink(response.headers.get('Link'))\n }\n\n return results\n }\n\n private parseNextLink(linkHeader: string | null): string | null {\n if (!linkHeader) return null\n const match = linkHeader.match(/<([^>]+)>;\\s*rel=\"next\"/)\n return match?.[1] ?? null\n }\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-NQQL7WH6.cjs","../package.json","../src/canvas/query.ts","../src/canvas/client.ts"],"names":[],"mappings":"AAAA;ACEE,IAAA,QAAA,EAAW,QAAA;ADAb;AACA;AEGO,SAAS,iBAAA,CAAkB,MAAA,EAAyB,MAAA,EAAkC;AAC3F,EAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,MAAA;AACb,EAAA,IAAA,CAAA,MAAW,CAAC,GAAA,EAAK,KAAK,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjD,IAAA,GAAA,CAAI,MAAA,IAAU,KAAA,EAAA,GAAa,MAAA,IAAU,IAAA,EAAM,QAAA;AAC3C,IAAA,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,MAAA,GAAA,CAAI,KAAA,CAAM,OAAA,IAAW,CAAA,EAAG,QAAA;AACxB,MAAA,MAAM,SAAA,EAAW,GAAA,CAAI,QAAA,CAAS,IAAI,EAAA,EAAI,IAAA,EAAM,CAAA,EAAA;AAClB,MAAA;AACiB,QAAA;AACL,QAAA;AACtC,MAAA;AACK,IAAA;AACwB,MAAA;AAC/B,IAAA;AACF,EAAA;AACF;AAE2E;AACtC,EAAA;AACH,EAAA;AACzB,EAAA;AACT;AFFmD;AACA;AGtBT;AACxC,EAAA;AACA,EAAA;AAE+D,EAAA;AAChD,IAAA;AACD,IAAA;AACE,IAAA;AACE,IAAA;AAClB,EAAA;AACF;AAEqC;AACO;AAWd;AACpB,EAAA;AACA,EAAA;AACA,EAAA;AAEgC,EAAA;AAClB,IAAA;AAC2B,IAAA;AACd,IAAA;AACnC,EAAA;AAEsB,EAAA;AACR,IAAA;AACd,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOmF,EAAA;AACtD,IAAA;AACa,IAAA;AAC7B,IAAA;AACiB,MAAA;AACkB,MAAA;AACtB,MAAA;AACxB,IAAA;AAEsC,IAAA;AACQ,IAAA;AAClC,MAAA;AACR,QAAA;AACF,MAAA;AACF,IAAA;AAEwC,IAAA;AACH,MAAA;AACrB,MAAA;AAChB,IAAA;AACe,IAAA;AACa,MAAA;AAC5B,IAAA;AAEkC,IAAA;AAC7B,MAAA;AACM,MAAA;AACJ,QAAA;AACK,QAAA;AACV,MAAA;AACD,IAAA;AAEiB,IAAA;AAC0B,MAAA;AAEX,MAAA;AACY,MAAA;AAC7C,IAAA;AAE6B,IAAA;AACpB,MAAA;AACT,IAAA;AAEqB,IAAA;AACvB,EAAA;AAE8E,EAAA;AAC7B,IAAA;AACR,IAAA;AACC,MAAA;AACxC,IAAA;AAC0C,IAAA;AAEpB,IAAA;AACoB,IAAA;AAC9B,IAAA;AAEmB,IAAA;AACS,MAAA;AAC3B,QAAA;AAC4B,UAAA;AACrB,UAAA;AAChB,QAAA;AACD,MAAA;AAEiB,MAAA;AAC0B,QAAA;AAEtB,QAAA;AACuB,QAAA;AAC7C,MAAA;AAEkC,MAAA;AACd,MAAA;AACpB,MAAA;AAEsC,MAAA;AACxC,IAAA;AAEO,IAAA;AACT,EAAA;AAKE,EAAA;AAE+C,IAAA;AACR,IAAA;AACC,MAAA;AACxC,IAAA;AAC0C,IAAA;AAEpB,IAAA;AACoB,IAAA;AAC9B,IAAA;AAEmB,IAAA;AACS,MAAA;AAC3B,QAAA;AAC4B,UAAA;AACrB,UAAA;AAChB,QAAA;AACD,MAAA;AAEiB,MAAA;AAC0B,QAAA;AAEtB,QAAA;AACuB,QAAA;AAC7C,MAAA;AAEkC,MAAA;AACC,MAAA;AACf,MAAA;AACpB,MAAA;AAEsC,MAAA;AACxC,IAAA;AAEO,IAAA;AACT,EAAA;AAEgE,EAAA;AACtC,IAAA;AACO,IAAA;AACV,IAAA;AACvB,EAAA;AACF;AHnBmD;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-NQQL7WH6.cjs","sourcesContent":[null,"{\n \"name\": \"canvas-lms-mcp\",\n \"version\": \"1.21.0\",\n \"mcpName\": \"io.github.bruchris/canvas-lms-mcp\",\n \"description\": \"TypeScript MCP 1.x server for Canvas LMS — 116 tools across Canvas courses, assignments, grades, gradebook history, quizzes, New Quizzes (LTI), outcomes, discussions, admin workflows, and more.\",\n \"type\": \"module\",\n \"types\": \"./dist/server.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\",\n \"require\": \"./dist/server.cjs\"\n },\n \"./stdio\": {\n \"types\": \"./dist/stdio.d.ts\",\n \"import\": \"./dist/stdio.js\",\n \"require\": \"./dist/stdio.cjs\"\n },\n \"./http\": {\n \"types\": \"./dist/http.d.ts\",\n \"import\": \"./dist/http.js\",\n \"require\": \"./dist/http.cjs\"\n },\n \"./canvas\": {\n \"types\": \"./dist/canvas/index.d.ts\",\n \"import\": \"./dist/canvas/index.js\",\n \"require\": \"./dist/canvas/index.cjs\"\n }\n },\n \"bin\": {\n \"canvas-lms-mcp\": \"bin/canvas-lms-mcp.js\"\n },\n \"files\": [\n \"bin/\",\n \"dist/\"\n ],\n \"scripts\": {\n \"dev\": \"tsup --watch\",\n \"build\": \"tsup && tsc -p tsconfig.build.json\",\n \"generate:manifests\": \"tsx scripts/generate-manifests.ts\",\n \"mcpb:pack\": \"node scripts/pack-mcpb.mjs\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"lint\": \"eslint src/ tests/ && prettier --check src/ tests/\",\n \"lint:fix\": \"eslint src/ tests/ --fix && prettier --write src/ tests/\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"keywords\": [\n \"canvas\",\n \"canvas-lms\",\n \"lms\",\n \"mcp\",\n \"model-context-protocol\",\n \"education\",\n \"grading\",\n \"ai-agent\",\n \"ai-tools\",\n \"claude-desktop\",\n \"cursor\",\n \"vscode\",\n \"instructure\",\n \"education-technology\",\n \"edtech\",\n \"teacher\",\n \"student\",\n \"grading-automation\",\n \"typescript\"\n ],\n \"author\": \"Christian Bru\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/bruchris/canvas-lms-mcp.git\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@10.11.0\",\n \"pnpm\": {\n \"overrides\": {\n \"hono\": \"^4.12.16\",\n \"ip-address\": \"^10.1.1\",\n \"fast-uri\": \"^3.1.2\",\n \"brace-expansion\": \"^5.0.6\",\n \"qs\": \"^6.15.2\"\n }\n },\n \"dependencies\": {\n \"@iarna/toml\": \"^2.2.5\",\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"prompts\": \"^2.4.2\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@types/node\": \"^25.9.1\",\n \"@types/prompts\": \"^2.4.9\",\n \"@typescript-eslint/eslint-plugin\": \"^8.60.0\",\n \"@typescript-eslint/parser\": \"^8.60.0\",\n \"@vitest/coverage-v8\": \"^4.1.7\",\n \"eslint\": \"^10.4.0\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-prettier\": \"^5.5.6\",\n \"prettier\": \"^3.8.3\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.22.3\",\n \"typescript\": \"^6.0.3\",\n \"typescript-eslint\": \"^8.60.0\",\n \"vitest\": \"^4.1.7\"\n }\n}\n","export type CanvasQueryPrimitive = string | number | boolean\n\nexport type CanvasQueryValue = CanvasQueryPrimitive | ReadonlyArray<CanvasQueryPrimitive>\n\nexport type CanvasQueryParams = Record<string, CanvasQueryValue | undefined | null>\n\nexport function appendCanvasQuery(target: URLSearchParams, params?: CanvasQueryParams): void {\n if (!params) return\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n const arrayKey = key.endsWith('[]') ? key : `${key}[]`\n for (const item of value) {\n if (item === undefined || item === null) continue\n target.append(arrayKey, String(item))\n }\n } else {\n target.set(key, String(value))\n }\n }\n}\n\nexport function toCanvasQuery(params?: CanvasQueryParams): URLSearchParams {\n const search = new URLSearchParams()\n appendCanvasQuery(search, params)\n return search\n}\n","import { version } from '../../package.json'\nimport { appendCanvasQuery, type CanvasQueryParams } from './query'\nimport type { CanvasClientConfig, CanvasErrorResponse } from './types'\n\nexport class CanvasApiError extends Error {\n status: number\n endpoint: string\n\n constructor(message: string, status: number, endpoint: string) {\n super(message)\n this.name = 'CanvasApiError'\n this.status = status\n this.endpoint = endpoint\n }\n}\n\nconst DEFAULT_MAX_PAGINATION_PAGES = 1000\nconst USER_AGENT = `canvas-lms-mcp/${version}`\n\nexport interface CanvasRequestOptions extends RequestInit {\n /**\n * Query params to append to the endpoint URL. Arrays produce repeated\n * `key[]=v1&key[]=v2` entries (the convention Canvas uses for `include[]`).\n * Preserves any query string already present on the endpoint.\n */\n query?: CanvasQueryParams\n}\n\nexport class CanvasHttpClient {\n private token: string\n private _baseUrl: string\n private maxPaginationPages: number\n\n constructor(config: CanvasClientConfig) {\n this.token = config.token\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '')\n this.maxPaginationPages = config.maxPaginationPages ?? DEFAULT_MAX_PAGINATION_PAGES\n }\n\n get baseUrl(): string {\n return this._baseUrl\n }\n\n /**\n * Makes a single authenticated request to the Canvas API.\n * Returns `undefined` (typed as `T`) when Canvas responds with 204 No Content,\n * which is the expected response for DELETE operations.\n */\n async request<T>(endpoint: string, options: CanvasRequestOptions = {}): Promise<T> {\n const { query, ...init } = options\n let url = endpoint.startsWith('http') ? endpoint : `${this._baseUrl}${endpoint}`\n if (query) {\n const parsed = new URL(url)\n appendCanvasQuery(parsed.searchParams, query)\n url = parsed.toString()\n }\n\n const method = (init.method ?? 'GET').toUpperCase()\n if (init.body != null && (method === 'GET' || method === 'HEAD')) {\n throw new Error(\n `GET requests must not include a body (Canvas CloudFront CDN rejects them with 403): ${endpoint}`,\n )\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n }\n if (init.body) {\n headers['Content-Type'] = 'application/json'\n }\n\n const response = await fetch(url, {\n ...init,\n headers: {\n ...headers,\n ...init.headers,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json() as Promise<T>\n }\n\n async paginate<T>(endpoint: string, params?: CanvasQueryParams): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', '100')\n }\n appendCanvasQuery(url.searchParams, params)\n\n const results: T[] = []\n let nextUrl: string | null = url.toString()\n let pages = 0\n\n while (nextUrl && pages < this.maxPaginationPages) {\n const response = await fetch(nextUrl, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n const data = (await response.json()) as T[]\n results.push(...data)\n pages++\n\n nextUrl = this.parseNextLink(response.headers.get('Link'))\n }\n\n return results\n }\n\n async paginateEnvelope<T>(\n endpoint: string,\n envelopeKey: string,\n params?: CanvasQueryParams,\n ): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', '100')\n }\n appendCanvasQuery(url.searchParams, params)\n\n const results: T[] = []\n let nextUrl: string | null = url.toString()\n let pages = 0\n\n while (nextUrl && pages < this.maxPaginationPages) {\n const response = await fetch(nextUrl, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n const body = (await response.json()) as Record<string, T[]>\n const data = body[envelopeKey] ?? []\n results.push(...data)\n pages++\n\n nextUrl = this.parseNextLink(response.headers.get('Link'))\n }\n\n return results\n }\n\n private parseNextLink(linkHeader: string | null): string | null {\n if (!linkHeader) return null\n const match = linkHeader.match(/<([^>]+)>;\\s*rel=\"next\"/)\n return match?.[1] ?? null\n }\n}\n"]}
@@ -7,11 +7,11 @@ import {
7
7
  OUTCOME_ROLLUP_SORT_BY,
8
8
  OUTCOME_SORT_ORDER,
9
9
  SEARCH_CONTENT_TYPES
10
- } from "./chunk-32RJEARZ.js";
10
+ } from "./chunk-FJT5UMRL.js";
11
11
  import {
12
12
  CanvasApiError,
13
13
  version
14
- } from "./chunk-HZU2YDP4.js";
14
+ } from "./chunk-Z635XMKW.js";
15
15
 
16
16
  // src/server.ts
17
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -729,10 +729,15 @@ function assignmentTools(canvas) {
729
729
  return [
730
730
  {
731
731
  name: "list_assignments",
732
- description: "List all assignments in a course. Use `include` to request submission, all_dates, overrides, score_statistics, etc. Use `bucket` to filter by past/upcoming/overdue/etc. Other filters: `search_term`, `assignment_ids`, `order_by`.",
732
+ description: 'List all assignments in a course. Use fields="slim" when enumerating assignments for selection; switch to "full" once you\'ve identified the target. slim returns id, name, due_at, points_possible, published, and course_id only. "full" (default) includes all Canvas fields plus `include` extras (submission, all_dates, overrides, score_statistics, etc.). Use `bucket` to filter by past/upcoming/overdue/etc. Other filters: `search_term`, `assignment_ids`, `order_by`.',
733
733
  inputSchema: {
734
734
  course_id: z3.number().describe("The Canvas course ID"),
735
- include: z3.array(z3.enum(ASSIGNMENT_LIST_INCLUDE)).optional().describe("Extra fields to include on each assignment (Canvas include[] param)"),
735
+ fields: z3.enum(["slim", "full"]).optional().describe(
736
+ 'Projection mode: "slim" returns id/name/due_at/points_possible/published/course_id only; "full" (default) returns all fields'
737
+ ),
738
+ include: z3.array(z3.enum(ASSIGNMENT_LIST_INCLUDE)).optional().describe(
739
+ 'Extra fields to include on each assignment (Canvas include[] param); ignored when fields="slim"'
740
+ ),
736
741
  search_term: z3.string().optional().describe("Partial name substring to filter assignments by"),
737
742
  bucket: z3.enum(ASSIGNMENT_BUCKET).optional().describe("Only include assignments in the given bucket"),
738
743
  assignment_ids: z3.array(z3.number()).optional().describe("Restrict to the given list of assignment IDs"),
@@ -748,8 +753,9 @@ function assignmentTools(canvas) {
748
753
  openWorldHint: true
749
754
  },
750
755
  handler: async (params) => {
756
+ const slim = params.fields === "slim";
751
757
  const opts = {};
752
- if (params.include !== void 0)
758
+ if (!slim && params.include !== void 0)
753
759
  opts.include = params.include;
754
760
  if (params.search_term !== void 0) opts.search_term = params.search_term;
755
761
  if (params.bucket !== void 0) opts.bucket = params.bucket;
@@ -761,7 +767,16 @@ function assignmentTools(canvas) {
761
767
  if (params.needs_grading_count_by_section !== void 0)
762
768
  opts.needs_grading_count_by_section = params.needs_grading_count_by_section;
763
769
  if (params.post_to_sis !== void 0) opts.post_to_sis = params.post_to_sis;
764
- return canvas.assignments.list(params.course_id, opts);
770
+ const assignments = await canvas.assignments.list(params.course_id, opts);
771
+ if (!slim) return assignments;
772
+ return assignments.map((a) => ({
773
+ id: a.id,
774
+ name: a.name,
775
+ due_at: a.due_at,
776
+ points_possible: a.points_possible,
777
+ published: a.published,
778
+ course_id: a.course_id
779
+ }));
765
780
  }
766
781
  },
767
782
  {
@@ -1072,11 +1087,16 @@ function courseTools(canvas) {
1072
1087
  return [
1073
1088
  {
1074
1089
  name: "list_courses",
1075
- description: "List courses for the authenticated user. `include` adds optional fields (teachers, total_students, term, syllabus_body, etc.). `state[]` narrows by course workflow state; `enrollment_state` narrows by the caller\u2019s enrollment state.",
1090
+ description: 'List courses for the authenticated user. Use fields="slim" when enumerating courses for selection; switch to "full" once you have identified the target. slim returns id, name, course_code, term name, and workflow_state only. "full" (default) includes all Canvas fields plus `include` extras (teachers, total_students, syllabus_body, etc.). `state[]` narrows by course workflow state; `enrollment_state` narrows by the caller\'s enrollment state.',
1076
1091
  inputSchema: {
1077
- enrollment_state: z6.enum(COURSE_ENROLLMENT_STATE).optional().describe("Filter courses by the caller\u2019s enrollment state"),
1092
+ fields: z6.enum(["slim", "full"]).optional().describe(
1093
+ 'Projection mode: "slim" returns id/name/course_code/term/workflow_state only; "full" (default) returns all fields'
1094
+ ),
1095
+ enrollment_state: z6.enum(COURSE_ENROLLMENT_STATE).optional().describe("Filter courses by the caller's enrollment state"),
1078
1096
  state: z6.array(z6.enum(COURSE_WORKFLOW_STATE)).optional().describe("Filter by course workflow state"),
1079
- include: z6.array(z6.enum(COURSE_LIST_INCLUDE)).optional().describe("Extra fields to include on each course (Canvas include[] param)"),
1097
+ include: z6.array(z6.enum(COURSE_LIST_INCLUDE)).optional().describe(
1098
+ 'Extra fields to include on each course (Canvas include[] param); ignored when fields="slim"'
1099
+ ),
1080
1100
  exclude_blueprint_courses: z6.boolean().optional().describe("Exclude blueprint courses from the results")
1081
1101
  },
1082
1102
  annotations: {
@@ -1084,16 +1104,25 @@ function courseTools(canvas) {
1084
1104
  openWorldHint: true
1085
1105
  },
1086
1106
  handler: async (params) => {
1107
+ const slim = params.fields === "slim";
1087
1108
  const opts = {};
1088
1109
  if (params.enrollment_state !== void 0)
1089
1110
  opts.enrollment_state = params.enrollment_state;
1090
1111
  if (params.state !== void 0)
1091
1112
  opts.state = params.state;
1092
- if (params.include !== void 0)
1113
+ if (!slim && params.include !== void 0)
1093
1114
  opts.include = params.include;
1094
1115
  if (params.exclude_blueprint_courses !== void 0)
1095
1116
  opts.exclude_blueprint_courses = params.exclude_blueprint_courses;
1096
- return canvas.courses.list(opts);
1117
+ const courses = await canvas.courses.list(opts);
1118
+ if (!slim) return courses;
1119
+ return courses.map((c) => ({
1120
+ id: c.id,
1121
+ name: c.name,
1122
+ course_code: c.course_code,
1123
+ term: c.term?.name ?? null,
1124
+ workflow_state: c.workflow_state
1125
+ }));
1097
1126
  }
1098
1127
  },
1099
1128
  {
@@ -3529,4 +3558,4 @@ export {
3529
3558
  Pseudonymizer,
3530
3559
  createCanvasMCPServer
3531
3560
  };
3532
- //# sourceMappingURL=chunk-FFK6TAPL.js.map
3561
+ //# sourceMappingURL=chunk-RD54R26V.js.map