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 +1 -1
- package/dist/canvas/index.cjs +3 -3
- package/dist/canvas/index.js +2 -2
- package/dist/{chunk-32RJEARZ.js → chunk-FJT5UMRL.js} +2 -2
- package/dist/{chunk-ZB3IRQKC.cjs → chunk-NQQL7WH6.cjs} +2 -2
- package/dist/{chunk-ZB3IRQKC.cjs.map → chunk-NQQL7WH6.cjs.map} +1 -1
- package/dist/{chunk-FFK6TAPL.js → chunk-RD54R26V.js} +41 -12
- package/dist/chunk-RD54R26V.js.map +1 -0
- package/dist/{chunk-NG6KBHE4.cjs → chunk-U5YBMWJ7.cjs} +3 -3
- package/dist/{chunk-NG6KBHE4.cjs.map → chunk-U5YBMWJ7.cjs.map} +1 -1
- package/dist/{chunk-5YQQB6LB.cjs → chunk-YWDVJPDY.cjs} +77 -48
- package/dist/chunk-YWDVJPDY.cjs.map +1 -0
- package/dist/{chunk-HZU2YDP4.js → chunk-Z635XMKW.js} +2 -2
- package/dist/{chunk-HZU2YDP4.js.map → chunk-Z635XMKW.js.map} +1 -1
- package/dist/http.cjs +5 -5
- package/dist/http.js +3 -3
- package/dist/init.cjs +3 -3
- package/dist/init.js +1 -1
- package/dist/server.cjs +4 -4
- package/dist/server.js +3 -3
- package/dist/stdio.cjs +4 -4
- package/dist/stdio.js +3 -3
- package/dist/tools/assignments.d.ts.map +1 -1
- package/dist/tools/courses.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-5YQQB6LB.cjs.map +0 -1
- package/dist/chunk-FFK6TAPL.js.map +0 -1
- /package/dist/{chunk-32RJEARZ.js.map → chunk-FJT5UMRL.js.map} +0 -0
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
|
|
package/dist/canvas/index.cjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkU5YBMWJ7cjs = require('../chunk-U5YBMWJ7.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
var
|
|
9
|
+
var _chunkNQQL7WH6cjs = require('../chunk-NQQL7WH6.cjs');
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
exports.CanvasApiError =
|
|
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
|
package/dist/canvas/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CanvasClient
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-FJT5UMRL.js";
|
|
4
4
|
import {
|
|
5
5
|
CanvasApiError,
|
|
6
6
|
CanvasHttpClient,
|
|
7
7
|
appendCanvasQuery,
|
|
8
8
|
toCanvasQuery
|
|
9
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
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.
|
|
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-
|
|
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-
|
|
10
|
+
} from "./chunk-FJT5UMRL.js";
|
|
11
11
|
import {
|
|
12
12
|
CanvasApiError,
|
|
13
13
|
version
|
|
14
|
-
} from "./chunk-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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-
|
|
3561
|
+
//# sourceMappingURL=chunk-RD54R26V.js.map
|