canvas-parent-mcp 2.1.7 → 2.1.9
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +7 -1
- package/dist/auth.js +8 -5
- package/dist/bundle.js +380 -136
- package/dist/client.js +84 -18
- package/dist/index.js +12 -8
- package/dist/qr-login.js +3 -1
- package/dist/session-cache.js +12 -3
- package/dist/session-login.js +3 -0
- package/dist/signal.js +16 -0
- package/dist/tools/announcements.js +1 -1
- package/dist/tools/assignments.js +9 -4
- package/dist/tools/calendar.js +2 -2
- package/dist/tools/conversations.js +2 -2
- package/dist/tools/courses.js +2 -2
- package/dist/tools/discussions.js +1 -1
- package/dist/tools/files.js +2 -2
- package/dist/tools/grades.js +1 -1
- package/dist/tools/healthcheck.js +11 -7
- package/dist/tools/observees.js +1 -1
- package/dist/tools/planner.js +1 -1
- package/dist/tools/profile.js +1 -1
- package/dist/tools/submissions.js +2 -2
- package/package.json +4 -4
- package/server.json +32 -2
- package/skills/canvas-parent/SKILL.md +2 -2
package/dist/bundle.js
CHANGED
|
@@ -27002,6 +27002,17 @@ var storage = new AsyncLocalStorage();
|
|
|
27002
27002
|
function withCallSignal(signal, fn, request) {
|
|
27003
27003
|
return signal ? storage.run({ signal, ...request ? { request } : {} }, fn) : fn();
|
|
27004
27004
|
}
|
|
27005
|
+
function currentCallSignal() {
|
|
27006
|
+
return storage.getStore()?.signal;
|
|
27007
|
+
}
|
|
27008
|
+
function withAmbientCancellation(own2) {
|
|
27009
|
+
const ambient = currentCallSignal();
|
|
27010
|
+
if (!ambient)
|
|
27011
|
+
return own2;
|
|
27012
|
+
if (!own2)
|
|
27013
|
+
return ambient;
|
|
27014
|
+
return AbortSignal.any([own2, ambient]);
|
|
27015
|
+
}
|
|
27005
27016
|
|
|
27006
27017
|
// node_modules/@chrischall/mcp-utils/dist/caller/index.js
|
|
27007
27018
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
@@ -41783,17 +41794,62 @@ var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
|
|
|
41783
41794
|
|
|
41784
41795
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
41785
41796
|
var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
41797
|
+
var MCP_TOOL_ERROR_KINDS = /* @__PURE__ */ new Set([
|
|
41798
|
+
"no_credential",
|
|
41799
|
+
"credential_rejected",
|
|
41800
|
+
"edge_blocked",
|
|
41801
|
+
"session_expired",
|
|
41802
|
+
"verification_pending",
|
|
41803
|
+
"timeout",
|
|
41804
|
+
"http",
|
|
41805
|
+
"transport",
|
|
41806
|
+
"too_large",
|
|
41807
|
+
"unknown"
|
|
41808
|
+
]);
|
|
41786
41809
|
var McpToolError = class extends Error {
|
|
41787
41810
|
/** Actionable remediation text, when one applies. */
|
|
41788
41811
|
hint;
|
|
41812
|
+
/** The upstream HTTP status this failure came from, when known. */
|
|
41813
|
+
status;
|
|
41814
|
+
/** What kind of failure this is, when the thrower knows. */
|
|
41815
|
+
kind;
|
|
41789
41816
|
constructor(message, opts) {
|
|
41790
41817
|
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
41791
41818
|
this.name = "McpToolError";
|
|
41792
41819
|
if (opts?.hint !== void 0)
|
|
41793
41820
|
this.hint = opts.hint;
|
|
41821
|
+
if (opts?.status !== void 0)
|
|
41822
|
+
this.status = opts.status;
|
|
41823
|
+
if (opts?.kind !== void 0)
|
|
41824
|
+
this.kind = opts.kind;
|
|
41794
41825
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
41795
41826
|
}
|
|
41796
41827
|
};
|
|
41828
|
+
var DECLARATION_CAUSE_DEPTH = 4;
|
|
41829
|
+
function firstDeclared(err, read) {
|
|
41830
|
+
let cur = err;
|
|
41831
|
+
for (let depth = 0; depth < DECLARATION_CAUSE_DEPTH && cur !== null && typeof cur === "object"; depth++) {
|
|
41832
|
+
const found = read(cur);
|
|
41833
|
+
if (found !== void 0)
|
|
41834
|
+
return found;
|
|
41835
|
+
cur = cur.cause;
|
|
41836
|
+
}
|
|
41837
|
+
return void 0;
|
|
41838
|
+
}
|
|
41839
|
+
function errorStatusOf(err) {
|
|
41840
|
+
return firstDeclared(err, (link2) => {
|
|
41841
|
+
const e = link2;
|
|
41842
|
+
if (typeof e.status === "number")
|
|
41843
|
+
return e.status;
|
|
41844
|
+
return typeof e.statusCode === "number" ? e.statusCode : void 0;
|
|
41845
|
+
});
|
|
41846
|
+
}
|
|
41847
|
+
function errorKindOf(err) {
|
|
41848
|
+
return firstDeclared(err, (link2) => {
|
|
41849
|
+
const k = link2.kind;
|
|
41850
|
+
return typeof k === "string" && MCP_TOOL_ERROR_KINDS.has(k) ? k : void 0;
|
|
41851
|
+
});
|
|
41852
|
+
}
|
|
41797
41853
|
function createHelpfulError(message, opts) {
|
|
41798
41854
|
return new McpToolError(message, opts);
|
|
41799
41855
|
}
|
|
@@ -41818,7 +41874,7 @@ var API_KEY_RE = new RegExp([
|
|
|
41818
41874
|
// webhook signing secret (Stripe-style)
|
|
41819
41875
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
41820
41876
|
var GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
41821
|
-
var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
41877
|
+
var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
41822
41878
|
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
41823
41879
|
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
41824
41880
|
var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -41829,6 +41885,8 @@ var JSON_SECRET_KEYS = [
|
|
|
41829
41885
|
"api[_-]?secret",
|
|
41830
41886
|
"(?:x[_-])?api[_-]?key",
|
|
41831
41887
|
"private[_-]?key",
|
|
41888
|
+
"code[_-]?verifier",
|
|
41889
|
+
// PKCE; a bare `verifier` key is too generic for JSON
|
|
41832
41890
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
41833
41891
|
// bounded like HEADER_SECRET_RE
|
|
41834
41892
|
"(?:proxy-)?authorization",
|
|
@@ -61660,7 +61718,7 @@ function readEnvVar(key, opts = {}) {
|
|
|
61660
61718
|
if (typeof raw === "string") {
|
|
61661
61719
|
const trimmed = raw.trim();
|
|
61662
61720
|
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
61663
|
-
return trimmed;
|
|
61721
|
+
return opts.trim === false ? raw : trimmed;
|
|
61664
61722
|
}
|
|
61665
61723
|
}
|
|
61666
61724
|
return opts.default;
|
|
@@ -61714,12 +61772,14 @@ var SERVER_PROTOCOL_VERSIONS = Object.freeze([
|
|
|
61714
61772
|
...SUPPORTED_PROTOCOL_VERSIONS
|
|
61715
61773
|
]);
|
|
61716
61774
|
function hintResultOrRethrow(err) {
|
|
61775
|
+
if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
|
|
61776
|
+
throw err;
|
|
61717
61777
|
if (err instanceof McpToolError && err.hint) {
|
|
61718
61778
|
return errorResult(`${err.message}
|
|
61719
61779
|
|
|
61720
61780
|
Hint: ${err.hint}`);
|
|
61721
61781
|
}
|
|
61722
|
-
|
|
61782
|
+
return errorResult(messageOf(err));
|
|
61723
61783
|
}
|
|
61724
61784
|
function mcpRequestFrom(args) {
|
|
61725
61785
|
const ctx = args.at(-1);
|
|
@@ -61949,6 +62009,18 @@ function isCloudflareChallenge(html) {
|
|
|
61949
62009
|
}
|
|
61950
62010
|
|
|
61951
62011
|
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
62012
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
62013
|
+
var RequestTimeoutError = class extends Error {
|
|
62014
|
+
timeoutMs;
|
|
62015
|
+
/** Always `'timeout'` unless a subclass says otherwise. */
|
|
62016
|
+
kind = "timeout";
|
|
62017
|
+
constructor(service, timeoutMs) {
|
|
62018
|
+
super(`Request to ${service} timed out after ${timeoutMs}ms.`);
|
|
62019
|
+
this.name = "RequestTimeoutError";
|
|
62020
|
+
this.timeoutMs = timeoutMs;
|
|
62021
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
62022
|
+
}
|
|
62023
|
+
};
|
|
61952
62024
|
var ApiError = class extends Error {
|
|
61953
62025
|
status;
|
|
61954
62026
|
constructor(status, message) {
|
|
@@ -61961,6 +62033,8 @@ var ApiError = class extends Error {
|
|
|
61961
62033
|
var EdgeBlockedError = class extends ApiError {
|
|
61962
62034
|
/** Which edge refused the request, e.g. `'CloudFront'` or `'Cloudflare'`. */
|
|
61963
62035
|
vendor;
|
|
62036
|
+
/** Always `'edge_blocked'`: the credential was never judged. */
|
|
62037
|
+
kind = "edge_blocked";
|
|
61964
62038
|
/**
|
|
61965
62039
|
* @param where The service the request was for, and — when the thrower knows
|
|
61966
62040
|
* it — the method and path, which the message names.
|
|
@@ -62140,7 +62214,7 @@ var PositiveInt = external_exports.number().int().positive();
|
|
|
62140
62214
|
var NonNegInt = external_exports.number().int().nonnegative();
|
|
62141
62215
|
var NonEmptyString = external_exports.string().min(1);
|
|
62142
62216
|
var IsoDate = external_exports.iso.date();
|
|
62143
|
-
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
|
|
62217
|
+
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30").overwrite((v) => v.padStart(5, "0"));
|
|
62144
62218
|
var NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
|
|
62145
62219
|
var SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
|
|
62146
62220
|
var schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
|
|
@@ -62158,7 +62232,11 @@ var pageSchema = {
|
|
|
62158
62232
|
var OAuth2RefreshError = class extends McpToolError {
|
|
62159
62233
|
status;
|
|
62160
62234
|
constructor(status, message, opts) {
|
|
62161
|
-
super(message,
|
|
62235
|
+
super(message, {
|
|
62236
|
+
...opts,
|
|
62237
|
+
status,
|
|
62238
|
+
kind: status >= 400 && status < 500 && status !== 408 && status !== 429 ? "credential_rejected" : "http"
|
|
62239
|
+
});
|
|
62162
62240
|
this.name = "OAuth2RefreshError";
|
|
62163
62241
|
this.status = status;
|
|
62164
62242
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
@@ -62190,6 +62268,7 @@ function createOAuth2Refresher(opts) {
|
|
|
62190
62268
|
const grantType = opts.grantType ?? "refresh_token";
|
|
62191
62269
|
const maxRetries = opts.retry?.count ?? 0;
|
|
62192
62270
|
const retryDelayMs = opts.retry?.delayMs ?? 0;
|
|
62271
|
+
const timeoutMs = opts.timeout === false ? 0 : opts.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
62193
62272
|
let inFlight = null;
|
|
62194
62273
|
let currentRefreshToken = opts.refreshToken;
|
|
62195
62274
|
const spent = /* @__PURE__ */ new Set();
|
|
@@ -62200,22 +62279,57 @@ function createOAuth2Refresher(opts) {
|
|
|
62200
62279
|
refresh_token: currentRefreshToken,
|
|
62201
62280
|
...opts.params
|
|
62202
62281
|
}).toString();
|
|
62203
|
-
const
|
|
62204
|
-
|
|
62205
|
-
|
|
62206
|
-
|
|
62207
|
-
|
|
62208
|
-
|
|
62209
|
-
|
|
62282
|
+
const controller = timeoutMs > 0 ? new AbortController() : void 0;
|
|
62283
|
+
const timedOut = () => new RequestTimeoutError(endpointWhere(opts.endpoint).service, timeoutMs);
|
|
62284
|
+
let timer;
|
|
62285
|
+
const expired = controller ? new Promise((_resolve, reject) => {
|
|
62286
|
+
timer = setTimeout(() => {
|
|
62287
|
+
controller.abort();
|
|
62288
|
+
reject(timedOut());
|
|
62289
|
+
}, timeoutMs);
|
|
62290
|
+
}) : void 0;
|
|
62291
|
+
expired?.catch(() => {
|
|
62210
62292
|
});
|
|
62293
|
+
const bounded = (p) => expired ? Promise.race([p, expired]) : p;
|
|
62294
|
+
try {
|
|
62295
|
+
return await exchangeBounded(body, controller?.signal, bounded, timedOut);
|
|
62296
|
+
} finally {
|
|
62297
|
+
clearTimeout(timer);
|
|
62298
|
+
}
|
|
62299
|
+
}
|
|
62300
|
+
async function exchangeBounded(body, signal, bounded, timedOut) {
|
|
62301
|
+
let res;
|
|
62302
|
+
try {
|
|
62303
|
+
res = await bounded(doFetch(opts.endpoint, {
|
|
62304
|
+
method: "POST",
|
|
62305
|
+
headers: {
|
|
62306
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
62307
|
+
Accept: "application/json"
|
|
62308
|
+
},
|
|
62309
|
+
body,
|
|
62310
|
+
...signal ? { signal } : {}
|
|
62311
|
+
}));
|
|
62312
|
+
} catch (err) {
|
|
62313
|
+
if (signal?.aborted && !(err instanceof RequestTimeoutError))
|
|
62314
|
+
throw timedOut();
|
|
62315
|
+
throw err;
|
|
62316
|
+
}
|
|
62317
|
+
const keepTimeout = (fallback) => (err) => {
|
|
62318
|
+
if (err instanceof RequestTimeoutError) {
|
|
62319
|
+
res.body?.cancel().catch(() => {
|
|
62320
|
+
});
|
|
62321
|
+
throw err;
|
|
62322
|
+
}
|
|
62323
|
+
return fallback;
|
|
62324
|
+
};
|
|
62211
62325
|
if (!res.ok) {
|
|
62212
|
-
const errText = await res.text().catch((
|
|
62326
|
+
const errText = await bounded(res.text()).catch(keepTimeout(""));
|
|
62213
62327
|
const edge = detectEdgeBlock({ body: errText, headers: res.headers, status: res.status });
|
|
62214
62328
|
if (edge)
|
|
62215
62329
|
throw new EdgeBlockedError(res.status, edge.vendor, endpointWhere(opts.endpoint));
|
|
62216
62330
|
throw new OAuth2RefreshError(res.status, `OAuth2 token refresh failed: ${res.status} ${res.statusText}: ${truncateErrorMessage(errText, 200)}`, { hint: "The refresh token may be expired or revoked \u2014 re-authenticate." });
|
|
62217
62331
|
}
|
|
62218
|
-
const data = await res.json().catch((
|
|
62332
|
+
const data = await bounded(res.json()).catch(keepTimeout(null));
|
|
62219
62333
|
const accessToken = data?.access_token;
|
|
62220
62334
|
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
62221
62335
|
throw createHelpfulError("OAuth2 token refresh returned no access_token.", {
|
|
@@ -62253,6 +62367,14 @@ function createOAuth2Refresher(opts) {
|
|
|
62253
62367
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
62254
62368
|
}
|
|
62255
62369
|
return function refresh(refreshToken) {
|
|
62370
|
+
const signal = currentCallSignal();
|
|
62371
|
+
if (!signal)
|
|
62372
|
+
return shared(refreshToken);
|
|
62373
|
+
if (signal.aborted)
|
|
62374
|
+
return Promise.reject(abortReasonOf(signal));
|
|
62375
|
+
return releaseOnAbort(shared(refreshToken), signal);
|
|
62376
|
+
};
|
|
62377
|
+
function shared(refreshToken) {
|
|
62256
62378
|
if (inFlight)
|
|
62257
62379
|
return inFlight;
|
|
62258
62380
|
if (typeof refreshToken === "string" && refreshToken.length > 0 && !spent.has(refreshToken)) {
|
|
@@ -62275,7 +62397,18 @@ function createOAuth2Refresher(opts) {
|
|
|
62275
62397
|
});
|
|
62276
62398
|
inFlight = p;
|
|
62277
62399
|
return p;
|
|
62278
|
-
}
|
|
62400
|
+
}
|
|
62401
|
+
}
|
|
62402
|
+
function releaseOnAbort(p, signal) {
|
|
62403
|
+
return new Promise((resolve5, reject) => {
|
|
62404
|
+
const onAbort = () => reject(abortReasonOf(signal));
|
|
62405
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
62406
|
+
p.then(resolve5, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
62407
|
+
});
|
|
62408
|
+
}
|
|
62409
|
+
function abortReasonOf(signal) {
|
|
62410
|
+
const reason = signal.reason;
|
|
62411
|
+
return reason instanceof Error ? reason : new Error(String(reason));
|
|
62279
62412
|
}
|
|
62280
62413
|
function endpointWhere(endpoint) {
|
|
62281
62414
|
try {
|
|
@@ -62493,6 +62626,17 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
62493
62626
|
]);
|
|
62494
62627
|
var ACCOUNT_KEY_FRAME = "account-key";
|
|
62495
62628
|
var ACCOUNT_ATTEST_FRAME = "account-attest";
|
|
62629
|
+
var BRIDGE_ROLE_FRAME = "bridge-role";
|
|
62630
|
+
var BRIDGE_SERVE_FRAME = "bridge-serve";
|
|
62631
|
+
var ROOM_PING_FRAME = "room-ping";
|
|
62632
|
+
var ROOM_PONG_FRAME = "room-pong";
|
|
62633
|
+
var BRIDGE_ROLE_LABEL_MAX = 64;
|
|
62634
|
+
var ROOM_FRAME_ACCEPTS = Object.freeze({
|
|
62635
|
+
[BRIDGE_ROLE_FRAME]: BRIDGE_ROLE_FRAME,
|
|
62636
|
+
[BRIDGE_SERVE_FRAME]: BRIDGE_SERVE_FRAME,
|
|
62637
|
+
[ROOM_PING_FRAME]: ROOM_PING_FRAME,
|
|
62638
|
+
[ROOM_PONG_FRAME]: ROOM_PING_FRAME
|
|
62639
|
+
});
|
|
62496
62640
|
|
|
62497
62641
|
// node_modules/@fetchproxy/protocol/dist/mcp-id.js
|
|
62498
62642
|
var ID_RE = /^([^:]+):([^:]+):([0-9a-f]{16})$/;
|
|
@@ -63158,6 +63302,12 @@ function validateFrame(raw) {
|
|
|
63158
63302
|
return validateAccountKey(raw);
|
|
63159
63303
|
if (t === ACCOUNT_ATTEST_FRAME)
|
|
63160
63304
|
return validateAccountAttest(raw);
|
|
63305
|
+
if (t === BRIDGE_ROLE_FRAME)
|
|
63306
|
+
return validateBridgeRole(raw);
|
|
63307
|
+
if (t === BRIDGE_SERVE_FRAME || t === ROOM_PING_FRAME || t === ROOM_PONG_FRAME) {
|
|
63308
|
+
assertExactFields(raw, TYPE_ONLY_FIELDS, t);
|
|
63309
|
+
return { type: t };
|
|
63310
|
+
}
|
|
63161
63311
|
throw new ProtocolError3(`unknown frame type: ${String(t)}`);
|
|
63162
63312
|
}
|
|
63163
63313
|
function validateHello(raw) {
|
|
@@ -63483,6 +63633,43 @@ function validateAccountAttest(raw) {
|
|
|
63483
63633
|
sig: raw.sig
|
|
63484
63634
|
};
|
|
63485
63635
|
}
|
|
63636
|
+
var TYPE_ONLY_FIELDS = ["type"];
|
|
63637
|
+
var BRIDGE_ROLE_FIELDS = ["type", "role", "canServe", "serving"];
|
|
63638
|
+
var BRIDGE_ROLE_SERVING_FIELDS = ["label", "since"];
|
|
63639
|
+
function validateBridgeRole(raw) {
|
|
63640
|
+
const L = BRIDGE_ROLE_FRAME;
|
|
63641
|
+
const role = raw.role;
|
|
63642
|
+
if (role !== "serving" && role !== "standby") {
|
|
63643
|
+
throw new ProtocolError3(`${L}.role: must be one of serving, standby`);
|
|
63644
|
+
}
|
|
63645
|
+
const canServe = raw.canServe;
|
|
63646
|
+
assertBoolean(canServe, `${L}.canServe`);
|
|
63647
|
+
assertExactFields(raw, BRIDGE_ROLE_FIELDS, L);
|
|
63648
|
+
const serving = raw.serving;
|
|
63649
|
+
if (role === "serving") {
|
|
63650
|
+
if (serving !== void 0) {
|
|
63651
|
+
throw new ProtocolError3(`${L}.serving: must be absent when role is serving`);
|
|
63652
|
+
}
|
|
63653
|
+
return { type: BRIDGE_ROLE_FRAME, role, canServe };
|
|
63654
|
+
}
|
|
63655
|
+
if (serving === void 0) {
|
|
63656
|
+
throw new ProtocolError3(`${L}.serving: required when role is standby`);
|
|
63657
|
+
}
|
|
63658
|
+
assertObject(serving, `${L}.serving`);
|
|
63659
|
+
const label = serving.label;
|
|
63660
|
+
assertDisplayString(label, `${L}.serving.label`, 1, BRIDGE_ROLE_LABEL_MAX);
|
|
63661
|
+
const since = serving.since;
|
|
63662
|
+
if (typeof since !== "number" || !Number.isSafeInteger(since) || since < 0) {
|
|
63663
|
+
throw new ProtocolError3(`${L}.serving.since: expected non-negative safe integer (Unix ms)`);
|
|
63664
|
+
}
|
|
63665
|
+
assertExactFields(serving, BRIDGE_ROLE_SERVING_FIELDS, `${L}.serving`);
|
|
63666
|
+
return {
|
|
63667
|
+
type: BRIDGE_ROLE_FRAME,
|
|
63668
|
+
role,
|
|
63669
|
+
canServe,
|
|
63670
|
+
serving: { label, since }
|
|
63671
|
+
};
|
|
63672
|
+
}
|
|
63486
63673
|
function validateInnerFrame(raw) {
|
|
63487
63674
|
assertObject(raw, "inner");
|
|
63488
63675
|
const t = raw.type;
|
|
@@ -65425,9 +65612,9 @@ async function startHost(opts) {
|
|
|
65425
65612
|
});
|
|
65426
65613
|
return {
|
|
65427
65614
|
close: () => new Promise((resolve5) => {
|
|
65428
|
-
for (const
|
|
65615
|
+
for (const client2 of wss.clients) {
|
|
65429
65616
|
try {
|
|
65430
|
-
|
|
65617
|
+
client2.terminate();
|
|
65431
65618
|
} catch {
|
|
65432
65619
|
}
|
|
65433
65620
|
}
|
|
@@ -68517,7 +68704,7 @@ function loadAccount(env = process.env) {
|
|
|
68517
68704
|
// package.json
|
|
68518
68705
|
var package_default = {
|
|
68519
68706
|
name: "canvas-parent-mcp",
|
|
68520
|
-
version: "2.1.
|
|
68707
|
+
version: "2.1.9",
|
|
68521
68708
|
mcpName: "io.github.chrischall/canvas-parent-mcp",
|
|
68522
68709
|
description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
|
|
68523
68710
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -68564,9 +68751,9 @@ var package_default = {
|
|
|
68564
68751
|
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
68565
68752
|
},
|
|
68566
68753
|
dependencies: {
|
|
68567
|
-
"@chrischall/mcp-utils": "^
|
|
68568
|
-
"@fetchproxy/bootstrap": "^3.
|
|
68569
|
-
"@fetchproxy/server": "^3.
|
|
68754
|
+
"@chrischall/mcp-utils": "^3.0.0",
|
|
68755
|
+
"@fetchproxy/bootstrap": "^3.6.0",
|
|
68756
|
+
"@fetchproxy/server": "^3.6.0",
|
|
68570
68757
|
"@modelcontextprotocol/server": "^2.3.0",
|
|
68571
68758
|
dotenv: "^18.0.0",
|
|
68572
68759
|
zod: "^4.6.5"
|
|
@@ -68724,12 +68911,6 @@ async function responseEdgeBlock(res) {
|
|
|
68724
68911
|
}
|
|
68725
68912
|
|
|
68726
68913
|
// node_modules/@chrischall/mcp-utils/dist/healthcheck/index.js
|
|
68727
|
-
function statusOf2(err) {
|
|
68728
|
-
if (typeof err !== "object" || err === null)
|
|
68729
|
-
return void 0;
|
|
68730
|
-
const s = err.status ?? err.statusCode;
|
|
68731
|
-
return typeof s === "number" ? s : void 0;
|
|
68732
|
-
}
|
|
68733
68914
|
var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
|
|
68734
68915
|
"ok",
|
|
68735
68916
|
"no_credential",
|
|
@@ -68745,6 +68926,9 @@ var CREDENTIAL_ARMS = /* @__PURE__ */ new Set([
|
|
|
68745
68926
|
function isArm(kind) {
|
|
68746
68927
|
return kind !== void 0 && CREDENTIAL_ARMS.has(kind);
|
|
68747
68928
|
}
|
|
68929
|
+
function fromSource(source) {
|
|
68930
|
+
return source !== null ? ` from '${source}'` : "";
|
|
68931
|
+
}
|
|
68748
68932
|
function credentialHint(arm, prefix, hostLabel, source) {
|
|
68749
68933
|
switch (arm) {
|
|
68750
68934
|
case "ok":
|
|
@@ -68752,15 +68936,15 @@ function credentialHint(arm, prefix, hostLabel, source) {
|
|
|
68752
68936
|
case "no_credential":
|
|
68753
68937
|
return `No credential resolved. Nothing was available to authenticate with \u2014 sign in and reconnect the connector so ${prefix} receives a token, or set the documented environment variable.`;
|
|
68754
68938
|
case "credential_rejected":
|
|
68755
|
-
return `${hostLabel} rejected the credential
|
|
68939
|
+
return `${hostLabel} rejected the credential${fromSource(source)}. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
|
|
68756
68940
|
case "edge_blocked":
|
|
68757
68941
|
return `${hostLabel} refused the request at its CDN/WAF before it reached the API, so the credential${source !== null ? ` from '${source}'` : ""} was never judged \u2014 re-signing in or rotating it will not help. This is usually a block on this host's IP address or request fingerprint: route through the ContextMint Bridge if this connector supports it, or retry later from a different network.`;
|
|
68758
68942
|
case "session_expired":
|
|
68759
|
-
return `The credential
|
|
68943
|
+
return `The credential${fromSource(source)} is configured, but no session is live \u2014 ${hostLabel} served a sign-in page rather than the data. Sign in again; a cookie-session portal expires these on its own, so this recurs between uses.`;
|
|
68760
68944
|
case "verification_pending":
|
|
68761
68945
|
return `${hostLabel} is holding the sign-in on a second factor rather than refusing it. Supply the verification code the ACCOUNT HOLDER received \u2014 the credential itself is not the problem, so changing it will not help.`;
|
|
68762
68946
|
case "timeout":
|
|
68763
|
-
return `The credential from '${source}' resolved, but ${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
|
|
68947
|
+
return `${source !== null ? `The credential from '${source}' resolved, but ` : ""}${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
|
|
68764
68948
|
case "http":
|
|
68765
68949
|
return `${hostLabel} answered with an error status that is not an auth rejection. That is USUALLY a ${hostLabel}-side problem rather than an auth one \u2014 but a 404 here more often means the probe path is wrong than that ${hostLabel} is broken, so check error.message and probe.url before concluding anything about the credential.`;
|
|
68766
68950
|
case "transport":
|
|
@@ -68781,7 +68965,8 @@ async function runCredentialHealthcheck(args) {
|
|
|
68781
68965
|
state = await resolveCredential();
|
|
68782
68966
|
} catch (e) {
|
|
68783
68967
|
const edge = edgeBlockOf(e);
|
|
68784
|
-
const
|
|
68968
|
+
const declared = errorKindOf(e);
|
|
68969
|
+
const classified = classifyThrown?.(e) ?? (edge !== null ? { kind: "edge_blocked", detail: { vendor: edge.vendor } } : declared !== void 0 ? { kind: declared } : void 0);
|
|
68785
68970
|
const result2 = {
|
|
68786
68971
|
ok: false,
|
|
68787
68972
|
// Still false, and still no source: a classification explains WHY
|
|
@@ -68828,10 +69013,11 @@ async function runCredentialHealthcheck(args) {
|
|
|
68828
69013
|
try {
|
|
68829
69014
|
await probeFn();
|
|
68830
69015
|
} catch (e) {
|
|
68831
|
-
status =
|
|
69016
|
+
status = errorStatusOf(e);
|
|
68832
69017
|
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
68833
69018
|
const edge = edgeBlockOf(e);
|
|
68834
|
-
|
|
69019
|
+
const declared = errorKindOf(e);
|
|
69020
|
+
arm = edge !== null ? "edge_blocked" : declared !== void 0 ? declared : status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
|
|
68835
69021
|
let kind = arm;
|
|
68836
69022
|
let detail = edge !== null ? { vendor: edge.vendor } : void 0;
|
|
68837
69023
|
const custom2 = classifyThrown?.(e);
|
|
@@ -68877,9 +69063,35 @@ function registerCredentialHealthcheckTool(args) {
|
|
|
68877
69063
|
}, async () => runCredentialHealthcheck(args));
|
|
68878
69064
|
}
|
|
68879
69065
|
|
|
69066
|
+
// src/tools/healthcheck.ts
|
|
69067
|
+
function registerHealthcheckTools(server, state) {
|
|
69068
|
+
registerCredentialHealthcheckTool({
|
|
69069
|
+
server,
|
|
69070
|
+
prefix: "canvas",
|
|
69071
|
+
hostLabel: "canvas",
|
|
69072
|
+
probePath: "/api/v1/users/self/profile",
|
|
69073
|
+
resolveCredential: async () => {
|
|
69074
|
+
if (!state.resolved) {
|
|
69075
|
+
throw state.configError ?? new Error("Canvas auth is not configured.");
|
|
69076
|
+
}
|
|
69077
|
+
return {
|
|
69078
|
+
source: state.resolved.source,
|
|
69079
|
+
detail: {
|
|
69080
|
+
base_url: state.resolved.account.baseUrl,
|
|
69081
|
+
mode: state.resolved.account.mode
|
|
69082
|
+
}
|
|
69083
|
+
};
|
|
69084
|
+
},
|
|
69085
|
+
probeFn: async () => {
|
|
69086
|
+
if (!state.client) throw new Error("Canvas client is not initialised.");
|
|
69087
|
+
return state.client.request("/api/v1/users/self/profile");
|
|
69088
|
+
}
|
|
69089
|
+
});
|
|
69090
|
+
}
|
|
69091
|
+
|
|
68880
69092
|
// src/client.ts
|
|
68881
69093
|
import { constants as fsConstants3 } from "fs";
|
|
68882
|
-
import { lstat as lstat2, open as open2, stat } from "fs/promises";
|
|
69094
|
+
import { lstat as lstat2, open as open2, rm as rm3, stat } from "fs/promises";
|
|
68883
69095
|
|
|
68884
69096
|
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
68885
69097
|
import { existsSync as existsSync4, readFileSync as readFileSync2, mkdirSync as mkdirSync3, chmodSync as chmodSync2, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "node:fs";
|
|
@@ -69434,6 +69646,12 @@ function reportCacheWriteFailure(err) {
|
|
|
69434
69646
|
import { dirname as dirname4, isAbsolute as isAbsolute5, join as join6, resolve as resolve4 } from "path";
|
|
69435
69647
|
import { homedir as homedir4 } from "os";
|
|
69436
69648
|
|
|
69649
|
+
// src/signal.ts
|
|
69650
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
69651
|
+
function requestSignal(timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
69652
|
+
return withAmbientCancellation(AbortSignal.timeout(timeoutMs));
|
|
69653
|
+
}
|
|
69654
|
+
|
|
69437
69655
|
// src/session-login.ts
|
|
69438
69656
|
var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36";
|
|
69439
69657
|
var SessionLoginError = class extends Error {
|
|
@@ -69465,6 +69683,7 @@ async function sessionLogin(opts) {
|
|
|
69465
69683
|
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
69466
69684
|
const loginUrl = `${baseUrl}/login/canvas`;
|
|
69467
69685
|
const getRes = await fetch(loginUrl, {
|
|
69686
|
+
signal: requestSignal(),
|
|
69468
69687
|
headers: {
|
|
69469
69688
|
"User-Agent": USER_AGENT,
|
|
69470
69689
|
Accept: "text/html,application/xhtml+xml"
|
|
@@ -69495,6 +69714,7 @@ async function sessionLogin(opts) {
|
|
|
69495
69714
|
body.append("pseudonym_session[remember_me]", "1");
|
|
69496
69715
|
const postRes = await fetch(loginUrl, {
|
|
69497
69716
|
method: "POST",
|
|
69717
|
+
signal: requestSignal(),
|
|
69498
69718
|
redirect: "manual",
|
|
69499
69719
|
headers: {
|
|
69500
69720
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
@@ -69526,6 +69746,8 @@ async function sessionLogin(opts) {
|
|
|
69526
69746
|
// src/client.ts
|
|
69527
69747
|
var DEFAULT_PER_PAGE = 100;
|
|
69528
69748
|
var DEFAULT_MAX_PAGES = 50;
|
|
69749
|
+
var DOWNLOAD_TIMEOUT_MS = 10 * 6e4;
|
|
69750
|
+
var MAX_DOWNLOAD_BYTES = 1024 ** 3;
|
|
69529
69751
|
var CanvasClient = class {
|
|
69530
69752
|
account;
|
|
69531
69753
|
sessionLoginFn;
|
|
@@ -69536,6 +69758,7 @@ var CanvasClient = class {
|
|
|
69536
69758
|
refreshSession;
|
|
69537
69759
|
/** Explicit download root; when unset, resolved per call (see {@link downloadRoot}). */
|
|
69538
69760
|
outputDir;
|
|
69761
|
+
maxDownloadBytes;
|
|
69539
69762
|
auth;
|
|
69540
69763
|
/** Lazily-built shared refresh_token exchanger (oauth mode only). */
|
|
69541
69764
|
oauthRefresh = null;
|
|
@@ -69552,6 +69775,7 @@ var CanvasClient = class {
|
|
|
69552
69775
|
constructor(account, opts = {}) {
|
|
69553
69776
|
this.account = account;
|
|
69554
69777
|
this.outputDir = opts.outputDir;
|
|
69778
|
+
this.maxDownloadBytes = opts.maxDownloadBytes ?? MAX_DOWNLOAD_BYTES;
|
|
69555
69779
|
this.sessionLoginFn = opts.sessionLogin ?? sessionLogin;
|
|
69556
69780
|
this.refreshSession = opts.refreshSession ?? null;
|
|
69557
69781
|
this.auth = new CookieSessionManager({
|
|
@@ -69607,6 +69831,10 @@ var CanvasClient = class {
|
|
|
69607
69831
|
* - `destinationPath` must lie inside the download root (see
|
|
69608
69832
|
* {@link downloadRoot}), checked through symlinks; a relative path is
|
|
69609
69833
|
* resolved against that root.
|
|
69834
|
+
*
|
|
69835
|
+
* The body is streamed to disk (never buffered whole) and capped at
|
|
69836
|
+
* `maxDownloadBytes`; a transfer that fails or overruns part-way leaves no
|
|
69837
|
+
* partial file behind.
|
|
69610
69838
|
*/
|
|
69611
69839
|
async download(path, destinationPath, opts = {}) {
|
|
69612
69840
|
const url4 = this.pinDownloadUrl(path);
|
|
@@ -69625,16 +69853,25 @@ var CanvasClient = class {
|
|
|
69625
69853
|
} catch {
|
|
69626
69854
|
throw new ParentDirectoryMissingError(parent);
|
|
69627
69855
|
}
|
|
69628
|
-
const res = await this.authedFetch(url4, {});
|
|
69856
|
+
const res = await this.authedFetch(url4, {}, DOWNLOAD_TIMEOUT_MS);
|
|
69629
69857
|
await throwIfEdgeBlocked(res, "GET", path);
|
|
69630
|
-
if (res.status === 401) throw
|
|
69631
|
-
if (res.status === 404) throw new Error(`Canvas download 404 for ${path}`);
|
|
69632
|
-
if (!res.ok) throw new Error(`Canvas download ${res.status} for ${path}`);
|
|
69633
|
-
|
|
69634
|
-
|
|
69858
|
+
if (res.status === 401) throw this.expiredError();
|
|
69859
|
+
if (res.status === 404) throw new Error(`Canvas download 404 for ${redactPath(path)}`);
|
|
69860
|
+
if (!res.ok) throw new Error(`Canvas download ${res.status} for ${redactPath(path)}`);
|
|
69861
|
+
if (Number(res.headers.get("content-length")) > this.maxDownloadBytes) {
|
|
69862
|
+
await res.body?.cancel();
|
|
69863
|
+
throw new DownloadTooLargeError(this.maxDownloadBytes);
|
|
69864
|
+
}
|
|
69865
|
+
const bytes = await writeConfined(
|
|
69866
|
+
dest,
|
|
69867
|
+
destinationPath,
|
|
69868
|
+
res.body,
|
|
69869
|
+
opts.overwrite === true,
|
|
69870
|
+
this.maxDownloadBytes
|
|
69871
|
+
);
|
|
69635
69872
|
return {
|
|
69636
69873
|
path: dest,
|
|
69637
|
-
bytes
|
|
69874
|
+
bytes,
|
|
69638
69875
|
contentType: res.headers.get("content-type") ?? "application/octet-stream"
|
|
69639
69876
|
};
|
|
69640
69877
|
}
|
|
@@ -69682,10 +69919,10 @@ var CanvasClient = class {
|
|
|
69682
69919
|
body: opts.body
|
|
69683
69920
|
});
|
|
69684
69921
|
await throwIfEdgeBlocked(res, opts.method ?? "GET", path);
|
|
69685
|
-
if (res.status === 401) throw
|
|
69686
|
-
if (res.status === 404) throw new Error(`Canvas 404 ${path}`);
|
|
69922
|
+
if (res.status === 401) throw this.expiredError();
|
|
69923
|
+
if (res.status === 404) throw new Error(`Canvas 404 ${redactPath(path)}`);
|
|
69687
69924
|
if (res.status >= 500) throw new CanvasUnreachableError(res.status);
|
|
69688
|
-
if (!res.ok) throw withBodyPreview(new Error(`Canvas ${res.status} ${res.statusText} for ${path}`), await res.text());
|
|
69925
|
+
if (!res.ok) throw withBodyPreview(new Error(`Canvas ${res.status} ${res.statusText} for ${redactPath(path)}`), await res.text());
|
|
69689
69926
|
return res;
|
|
69690
69927
|
}
|
|
69691
69928
|
/**
|
|
@@ -69697,12 +69934,14 @@ var CanvasClient = class {
|
|
|
69697
69934
|
* download callers map that to a {@link TokenExpiredError}. Used by both API
|
|
69698
69935
|
* requests and file downloads.
|
|
69699
69936
|
*/
|
|
69700
|
-
async authedFetch(url4, init) {
|
|
69937
|
+
async authedFetch(url4, init, timeoutMs) {
|
|
69701
69938
|
this.proactivelyExpire();
|
|
69702
69939
|
return this.auth.withSession(
|
|
69703
69940
|
async (state) => fetch(url4, {
|
|
69704
69941
|
...init,
|
|
69705
|
-
headers: { ...state.headers, ...init.headers }
|
|
69942
|
+
headers: { ...state.headers, ...init.headers },
|
|
69943
|
+
// Fresh per attempt, so a 401 replay gets its own full timeout.
|
|
69944
|
+
signal: requestSignal(timeoutMs)
|
|
69706
69945
|
})
|
|
69707
69946
|
);
|
|
69708
69947
|
}
|
|
@@ -69720,6 +69959,14 @@ var CanvasClient = class {
|
|
|
69720
69959
|
this.auth.invalidate();
|
|
69721
69960
|
}
|
|
69722
69961
|
}
|
|
69962
|
+
/**
|
|
69963
|
+
* The error for a 401 that survived any re-auth. On the fetchproxy path the
|
|
69964
|
+
* session came from the browser and the user never set CANVAS_USERNAME /
|
|
69965
|
+
* CANVAS_PASSWORD, so the message must point at the browser instead.
|
|
69966
|
+
*/
|
|
69967
|
+
expiredError() {
|
|
69968
|
+
return new TokenExpiredError(this.account.mode, void 0, { browser: this.refreshSession !== null });
|
|
69969
|
+
}
|
|
69723
69970
|
/** Whether a 401 in the current mode can be recovered by re-running login(). */
|
|
69724
69971
|
canReauth() {
|
|
69725
69972
|
const acct = this.account;
|
|
@@ -69762,7 +70009,8 @@ var CanvasClient = class {
|
|
|
69762
70009
|
this.oauthRefresh ??= createOAuth2Refresher({
|
|
69763
70010
|
endpoint: `${acct.baseUrl}/login/oauth2/token`,
|
|
69764
70011
|
refreshToken: acct.refreshToken,
|
|
69765
|
-
params: { client_id: acct.clientId, client_secret: acct.clientSecret }
|
|
70012
|
+
params: { client_id: acct.clientId, client_secret: acct.clientSecret },
|
|
70013
|
+
fetchImpl: (input2, init) => fetch(input2, { ...init, signal: requestSignal() })
|
|
69766
70014
|
});
|
|
69767
70015
|
let accessToken;
|
|
69768
70016
|
let expiresIn;
|
|
@@ -69806,8 +70054,12 @@ function injectPerPage(pathOrUrl, perPage) {
|
|
|
69806
70054
|
return `${pathOrUrl}${sep2}per_page=${perPage}`;
|
|
69807
70055
|
}
|
|
69808
70056
|
var TokenExpiredError = class extends Error {
|
|
69809
|
-
|
|
69810
|
-
|
|
70057
|
+
/**
|
|
70058
|
+
* `opts.browser`: the session was lifted from the user's signed-in browser
|
|
70059
|
+
* tab (the fetchproxy path), not minted from CANVAS_USERNAME/PASSWORD.
|
|
70060
|
+
*/
|
|
70061
|
+
constructor(mode, detail, opts = {}) {
|
|
70062
|
+
const base = mode === "token" ? "Canvas access token rejected (401). Check CANVAS_TOKEN \u2014 it may be expired or revoked." : mode === "session" ? opts.browser ? "Canvas rejected the session read from your browser (401). Sign back into Canvas in your browser (the tab the ContextMint Bridge extension reads), then retry." : "Canvas session login failed (401). Check CANVAS_USERNAME / CANVAS_PASSWORD \u2014 they may have changed, or the account may be locked or behind SSO." : "Canvas OAuth refresh failed. Check CANVAS_CLIENT_ID, CANVAS_CLIENT_SECRET, CANVAS_REFRESH_TOKEN.";
|
|
69811
70063
|
super(detail ? `${base} (${detail})` : base);
|
|
69812
70064
|
this.mode = mode;
|
|
69813
70065
|
this.detail = detail;
|
|
@@ -69832,6 +70084,14 @@ var DownloadUrlRejectedError = class extends Error {
|
|
|
69832
70084
|
}
|
|
69833
70085
|
url;
|
|
69834
70086
|
};
|
|
70087
|
+
var DownloadTooLargeError = class extends Error {
|
|
70088
|
+
constructor(maxBytes) {
|
|
70089
|
+
super(`DownloadTooLarge: the file is larger than the ${maxBytes} bytes canvas_download_file accepts.`);
|
|
70090
|
+
this.maxBytes = maxBytes;
|
|
70091
|
+
this.name = "DownloadTooLargeError";
|
|
70092
|
+
}
|
|
70093
|
+
maxBytes;
|
|
70094
|
+
};
|
|
69835
70095
|
var DownloadDestinationRejectedError = class extends Error {
|
|
69836
70096
|
constructor(path, root) {
|
|
69837
70097
|
super(
|
|
@@ -69844,7 +70104,7 @@ var DownloadDestinationRejectedError = class extends Error {
|
|
|
69844
70104
|
path;
|
|
69845
70105
|
root;
|
|
69846
70106
|
};
|
|
69847
|
-
async function writeConfined(dest, requested,
|
|
70107
|
+
async function writeConfined(dest, requested, body, overwrite, maxBytes) {
|
|
69848
70108
|
const { O_WRONLY: O_WRONLY2, O_CREAT: O_CREAT2, O_TRUNC: O_TRUNC2, O_EXCL: O_EXCL2, O_NOFOLLOW: O_NOFOLLOW3 } = fsConstants3;
|
|
69849
70109
|
const flags = O_WRONLY2 | O_CREAT2 | O_NOFOLLOW3 | (overwrite ? O_TRUNC2 : O_EXCL2);
|
|
69850
70110
|
let handle;
|
|
@@ -69857,11 +70117,22 @@ async function writeConfined(dest, requested, buf, overwrite) {
|
|
|
69857
70117
|
if (code === "EISDIR") throw new InvalidPathError(requested);
|
|
69858
70118
|
throw e;
|
|
69859
70119
|
}
|
|
70120
|
+
let bytes = 0;
|
|
69860
70121
|
try {
|
|
69861
|
-
|
|
69862
|
-
|
|
70122
|
+
if (body) {
|
|
70123
|
+
for await (const chunk2 of body) {
|
|
70124
|
+
bytes += chunk2.byteLength;
|
|
70125
|
+
if (bytes > maxBytes) throw new DownloadTooLargeError(maxBytes);
|
|
70126
|
+
await handle.write(chunk2);
|
|
70127
|
+
}
|
|
70128
|
+
}
|
|
70129
|
+
} catch (e) {
|
|
69863
70130
|
await handle.close();
|
|
70131
|
+
await rm3(dest, { force: true });
|
|
70132
|
+
throw e;
|
|
69864
70133
|
}
|
|
70134
|
+
await handle.close();
|
|
70135
|
+
return bytes;
|
|
69865
70136
|
}
|
|
69866
70137
|
var InvalidPathError = class extends Error {
|
|
69867
70138
|
constructor(path, reason = "must be a filename, not a directory") {
|
|
@@ -69888,34 +70159,6 @@ var FileExistsError = class extends Error {
|
|
|
69888
70159
|
path;
|
|
69889
70160
|
};
|
|
69890
70161
|
|
|
69891
|
-
// src/tools/healthcheck.ts
|
|
69892
|
-
function registerHealthcheckTools(server, state) {
|
|
69893
|
-
registerCredentialHealthcheckTool({
|
|
69894
|
-
server,
|
|
69895
|
-
prefix: "canvas",
|
|
69896
|
-
hostLabel: "canvas",
|
|
69897
|
-
probePath: "/api/v1/users/self/profile",
|
|
69898
|
-
resolveCredential: async () => {
|
|
69899
|
-
if (!state.resolved) {
|
|
69900
|
-
throw state.configError ?? new Error("Canvas auth is not configured.");
|
|
69901
|
-
}
|
|
69902
|
-
return {
|
|
69903
|
-
source: state.resolved.source,
|
|
69904
|
-
detail: {
|
|
69905
|
-
base_url: state.resolved.account.baseUrl,
|
|
69906
|
-
mode: state.resolved.account.mode
|
|
69907
|
-
}
|
|
69908
|
-
};
|
|
69909
|
-
},
|
|
69910
|
-
probeFn: async () => {
|
|
69911
|
-
const client = new CanvasClient(state.resolved.account, {
|
|
69912
|
-
refreshSession: state.resolved.refresh
|
|
69913
|
-
});
|
|
69914
|
-
return client.request("/api/v1/users/self/profile");
|
|
69915
|
-
}
|
|
69916
|
-
});
|
|
69917
|
-
}
|
|
69918
|
-
|
|
69919
70162
|
// src/view.ts
|
|
69920
70163
|
var CV_VIEWS = ["compact", "full"];
|
|
69921
70164
|
var NOTE = `compact removes the avatar URLs Canvas attaches to user objects (avatar_url, avatar_image_url); "full" returns Canvas's payload untouched. Link fields (url, html_url, preview_url) survive both rungs. No field projection: this server passes Canvas's payload through verbatim and has no verified record of which of its fields a caller needs.`;
|
|
@@ -69931,14 +70174,14 @@ function viewResponse(view, data) {
|
|
|
69931
70174
|
var argsSchema = external_exports.object({
|
|
69932
70175
|
view: viewArg()
|
|
69933
70176
|
});
|
|
69934
|
-
function registerProfileTools(server,
|
|
70177
|
+
function registerProfileTools(server, client2) {
|
|
69935
70178
|
server.registerTool("canvas_get_profile", {
|
|
69936
70179
|
description: "Get the logged-in user's Canvas profile (id, name, primary_email, login_id, locale, time_zone). Useful first call to confirm credentials.",
|
|
69937
|
-
annotations: { readOnlyHint: true },
|
|
70180
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
69938
70181
|
inputSchema: argsSchema
|
|
69939
70182
|
}, async (rawArgs) => {
|
|
69940
70183
|
const args = argsSchema.parse(rawArgs);
|
|
69941
|
-
const data = await
|
|
70184
|
+
const data = await client2.request("/api/v1/users/self/profile");
|
|
69942
70185
|
return viewResponse(args.view, data);
|
|
69943
70186
|
});
|
|
69944
70187
|
}
|
|
@@ -69947,14 +70190,14 @@ function registerProfileTools(server, client) {
|
|
|
69947
70190
|
var argsSchema2 = external_exports.object({
|
|
69948
70191
|
view: viewArg()
|
|
69949
70192
|
});
|
|
69950
|
-
function registerObserveeTools(server,
|
|
70193
|
+
function registerObserveeTools(server, client2) {
|
|
69951
70194
|
server.registerTool("canvas_list_observees", {
|
|
69952
70195
|
description: "List students linked to your Canvas observer account. Returns an empty array for plain student tokens.",
|
|
69953
|
-
annotations: { readOnlyHint: true },
|
|
70196
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
69954
70197
|
inputSchema: argsSchema2
|
|
69955
70198
|
}, async (rawArgs) => {
|
|
69956
70199
|
const args = argsSchema2.parse(rawArgs);
|
|
69957
|
-
const data = await
|
|
70200
|
+
const data = await client2.requestPaginated("/api/v1/users/self/observees?include[]=avatar_url");
|
|
69958
70201
|
return viewResponse(args.view, data);
|
|
69959
70202
|
});
|
|
69960
70203
|
}
|
|
@@ -69976,10 +70219,10 @@ var getArgs = external_exports.object({
|
|
|
69976
70219
|
courseId: external_exports.string(),
|
|
69977
70220
|
view: viewArg()
|
|
69978
70221
|
});
|
|
69979
|
-
function registerCourseTools(server,
|
|
70222
|
+
function registerCourseTools(server, client2) {
|
|
69980
70223
|
server.registerTool("canvas_list_courses", {
|
|
69981
70224
|
description: "List active Canvas courses (with course-level grades, total scores, current grading period scores, and term).",
|
|
69982
|
-
annotations: { readOnlyHint: true },
|
|
70225
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
69983
70226
|
inputSchema: listArgs
|
|
69984
70227
|
}, async (rawArgs) => {
|
|
69985
70228
|
const args = listArgs.parse(rawArgs);
|
|
@@ -69988,19 +70231,19 @@ function registerCourseTools(server, client) {
|
|
|
69988
70231
|
"state[]": "available",
|
|
69989
70232
|
"include[]": ["total_scores", "current_grading_period_scores", "term"]
|
|
69990
70233
|
});
|
|
69991
|
-
const data = await
|
|
70234
|
+
const data = await client2.requestPaginated(path);
|
|
69992
70235
|
return textContent(data);
|
|
69993
70236
|
});
|
|
69994
70237
|
server.registerTool("canvas_get_course", {
|
|
69995
70238
|
description: "Get a single Canvas course with its syllabus, teachers, and term.",
|
|
69996
|
-
annotations: { readOnlyHint: true },
|
|
70239
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
69997
70240
|
inputSchema: getArgs
|
|
69998
70241
|
}, async (rawArgs) => {
|
|
69999
70242
|
const args = getArgs.parse(rawArgs);
|
|
70000
70243
|
const path = buildPath(`/api/v1/courses/${encodeURIComponent(args.courseId)}`, {
|
|
70001
70244
|
"include[]": ["syllabus_body", "teachers", "term"]
|
|
70002
70245
|
});
|
|
70003
|
-
const data = await
|
|
70246
|
+
const data = await client2.request(path);
|
|
70004
70247
|
return viewResponse(args.view, data);
|
|
70005
70248
|
});
|
|
70006
70249
|
}
|
|
@@ -70011,13 +70254,13 @@ var listArgs2 = external_exports.object({
|
|
|
70011
70254
|
bucket: external_exports.enum(["past", "overdue", "undated", "ungraded", "unsubmitted", "upcoming", "future"]).optional().describe("Optional Canvas-side filter.")
|
|
70012
70255
|
});
|
|
70013
70256
|
var missingArgs = external_exports.object({
|
|
70014
|
-
observeeId: external_exports.string().optional(),
|
|
70015
|
-
courseIds: external_exports.array(external_exports.string()).optional().describe("Required when observeeId is set.")
|
|
70257
|
+
observeeId: external_exports.string().optional().describe("A linked observee's user id. When set, courseIds is required."),
|
|
70258
|
+
courseIds: external_exports.array(external_exports.string()).optional().describe("Required (non-empty) when observeeId is set; optional otherwise.")
|
|
70016
70259
|
});
|
|
70017
|
-
function registerAssignmentTools(server,
|
|
70260
|
+
function registerAssignmentTools(server, client2) {
|
|
70018
70261
|
server.registerTool("canvas_list_assignments", {
|
|
70019
70262
|
description: "List a course's assignments (with the user's submission inline). Supports the standard Canvas `bucket` filter.",
|
|
70020
|
-
annotations: { readOnlyHint: true },
|
|
70263
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70021
70264
|
inputSchema: listArgs2
|
|
70022
70265
|
}, async (rawArgs) => {
|
|
70023
70266
|
const args = listArgs2.parse(rawArgs);
|
|
@@ -70026,21 +70269,24 @@ function registerAssignmentTools(server, client) {
|
|
|
70026
70269
|
bucket: args.bucket,
|
|
70027
70270
|
order_by: "due_at"
|
|
70028
70271
|
});
|
|
70029
|
-
const data = await
|
|
70272
|
+
const data = await client2.requestPaginated(path);
|
|
70030
70273
|
return textContent(data);
|
|
70031
70274
|
});
|
|
70032
70275
|
server.registerTool("canvas_list_missing_submissions", {
|
|
70033
70276
|
description: "List past-due unsubmitted assignments for the user (or a linked observee). For an observee, courseIds is required.",
|
|
70034
|
-
annotations: { readOnlyHint: true },
|
|
70277
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70035
70278
|
inputSchema: missingArgs
|
|
70036
70279
|
}, async (rawArgs) => {
|
|
70037
70280
|
const args = missingArgs.parse(rawArgs);
|
|
70281
|
+
if (args.observeeId !== void 0 && !args.courseIds?.length) {
|
|
70282
|
+
throw new Error("courseIds is required when observeeId is set (list the observee's courses with canvas_list_courses).");
|
|
70283
|
+
}
|
|
70038
70284
|
const path = buildPath(`/api/v1/${userSegment(args.observeeId)}/missing_submissions`, {
|
|
70039
70285
|
"include[]": ["planner_overrides", "course"],
|
|
70040
70286
|
"filter[]": "submittable",
|
|
70041
70287
|
"course_ids[]": args.courseIds
|
|
70042
70288
|
});
|
|
70043
|
-
const data = await
|
|
70289
|
+
const data = await client2.requestPaginated(path);
|
|
70044
70290
|
return textContent(data);
|
|
70045
70291
|
});
|
|
70046
70292
|
}
|
|
@@ -70058,10 +70304,10 @@ var recentArgs = external_exports.object({
|
|
|
70058
70304
|
since: external_exports.string().optional().describe("ISO 8601 timestamp; defaults to 14 days ago."),
|
|
70059
70305
|
view: viewArg()
|
|
70060
70306
|
});
|
|
70061
|
-
function registerSubmissionTools(server,
|
|
70307
|
+
function registerSubmissionTools(server, client2) {
|
|
70062
70308
|
server.registerTool("canvas_get_submission", {
|
|
70063
70309
|
description: "Get a single submission with rubric assessment and grader comments. userId defaults to 'self'.",
|
|
70064
|
-
annotations: { readOnlyHint: true },
|
|
70310
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70065
70311
|
inputSchema: getArgs2
|
|
70066
70312
|
}, async (rawArgs) => {
|
|
70067
70313
|
const args = getArgs2.parse(rawArgs);
|
|
@@ -70070,12 +70316,12 @@ function registerSubmissionTools(server, client) {
|
|
|
70070
70316
|
`/api/v1/courses/${encodeURIComponent(args.courseId)}/assignments/${encodeURIComponent(args.assignmentId)}/submissions/${encodeURIComponent(userId)}`,
|
|
70071
70317
|
{ "include[]": ["submission_comments", "rubric_assessment", "assignment"] }
|
|
70072
70318
|
);
|
|
70073
|
-
const data = await
|
|
70319
|
+
const data = await client2.request(path);
|
|
70074
70320
|
return viewResponse(args.view, data);
|
|
70075
70321
|
});
|
|
70076
70322
|
server.registerTool("canvas_list_recent_submissions", {
|
|
70077
70323
|
description: "List recently graded submissions in a course. Defaults to a 14-day window for the calling user.",
|
|
70078
|
-
annotations: { readOnlyHint: true },
|
|
70324
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70079
70325
|
inputSchema: recentArgs
|
|
70080
70326
|
}, async (rawArgs) => {
|
|
70081
70327
|
const args = recentArgs.parse(rawArgs);
|
|
@@ -70087,7 +70333,7 @@ function registerSubmissionTools(server, client) {
|
|
|
70087
70333
|
graded_since: since,
|
|
70088
70334
|
"include[]": ["assignment", "submission_comments"]
|
|
70089
70335
|
});
|
|
70090
|
-
const data = await
|
|
70336
|
+
const data = await client2.requestPaginated(path);
|
|
70091
70337
|
return viewResponse(args.view, data);
|
|
70092
70338
|
});
|
|
70093
70339
|
}
|
|
@@ -70096,10 +70342,10 @@ function registerSubmissionTools(server, client) {
|
|
|
70096
70342
|
var argsSchema3 = external_exports.object({
|
|
70097
70343
|
observeeId: external_exports.string().optional()
|
|
70098
70344
|
});
|
|
70099
|
-
function registerGradeTools(server,
|
|
70345
|
+
function registerGradeTools(server, client2) {
|
|
70100
70346
|
server.registerTool("canvas_list_enrollments", {
|
|
70101
70347
|
description: "List active student enrollments with per-course grades (current_score, final_score, current_grade, final_grade, current grading period info).",
|
|
70102
|
-
annotations: { readOnlyHint: true },
|
|
70348
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70103
70349
|
inputSchema: argsSchema3
|
|
70104
70350
|
}, async (rawArgs) => {
|
|
70105
70351
|
const args = argsSchema3.parse(rawArgs);
|
|
@@ -70108,7 +70354,7 @@ function registerGradeTools(server, client) {
|
|
|
70108
70354
|
"type[]": "StudentEnrollment",
|
|
70109
70355
|
"include[]": ["current_points", "grades"]
|
|
70110
70356
|
});
|
|
70111
|
-
const data = await
|
|
70357
|
+
const data = await client2.requestPaginated(path);
|
|
70112
70358
|
return textContent(data);
|
|
70113
70359
|
});
|
|
70114
70360
|
}
|
|
@@ -70121,10 +70367,10 @@ var eventsArgs = external_exports.object({
|
|
|
70121
70367
|
endDate: external_exports.string().optional(),
|
|
70122
70368
|
allEvents: external_exports.boolean().optional()
|
|
70123
70369
|
});
|
|
70124
|
-
function registerCalendarTools(server,
|
|
70370
|
+
function registerCalendarTools(server, client2) {
|
|
70125
70371
|
server.registerTool("canvas_list_calendar_events", {
|
|
70126
70372
|
description: "List Canvas calendar events or assignments across selected contexts (courses/users).",
|
|
70127
|
-
annotations: { readOnlyHint: true },
|
|
70373
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70128
70374
|
inputSchema: eventsArgs
|
|
70129
70375
|
}, async (rawArgs) => {
|
|
70130
70376
|
const args = eventsArgs.parse(rawArgs);
|
|
@@ -70135,14 +70381,14 @@ function registerCalendarTools(server, client) {
|
|
|
70135
70381
|
"context_codes[]": args.contextCodes,
|
|
70136
70382
|
all_events: args.allEvents
|
|
70137
70383
|
});
|
|
70138
|
-
const data = await
|
|
70384
|
+
const data = await client2.requestPaginated(path);
|
|
70139
70385
|
return textContent(data);
|
|
70140
70386
|
});
|
|
70141
70387
|
server.registerTool("canvas_list_upcoming_events", {
|
|
70142
70388
|
description: "List the calling user's upcoming events (Canvas's curated next-7-days view).",
|
|
70143
|
-
annotations: { readOnlyHint: true }
|
|
70389
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
70144
70390
|
}, async () => {
|
|
70145
|
-
const data = await
|
|
70391
|
+
const data = await client2.request("/api/v1/users/self/upcoming_events");
|
|
70146
70392
|
return textContent(data);
|
|
70147
70393
|
});
|
|
70148
70394
|
}
|
|
@@ -70155,10 +70401,10 @@ var argsSchema4 = external_exports.object({
|
|
|
70155
70401
|
contextCodes: external_exports.array(external_exports.string()).optional(),
|
|
70156
70402
|
filter: external_exports.enum(["new_activity"]).optional()
|
|
70157
70403
|
});
|
|
70158
|
-
function registerPlannerTools(server,
|
|
70404
|
+
function registerPlannerTools(server, client2) {
|
|
70159
70405
|
server.registerTool("canvas_list_planner_items", {
|
|
70160
70406
|
description: "List planner items (assignments + announcements + planner notes + calendar events) for the user or a linked observee.",
|
|
70161
|
-
annotations: { readOnlyHint: true },
|
|
70407
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70162
70408
|
inputSchema: argsSchema4
|
|
70163
70409
|
}, async (rawArgs) => {
|
|
70164
70410
|
const args = argsSchema4.parse(rawArgs);
|
|
@@ -70169,7 +70415,7 @@ function registerPlannerTools(server, client) {
|
|
|
70169
70415
|
"context_codes[]": args.contextCodes,
|
|
70170
70416
|
filter: args.filter
|
|
70171
70417
|
});
|
|
70172
|
-
const data = await
|
|
70418
|
+
const data = await client2.requestPaginated(path);
|
|
70173
70419
|
return textContent(data);
|
|
70174
70420
|
});
|
|
70175
70421
|
}
|
|
@@ -70182,10 +70428,10 @@ var argsSchema5 = external_exports.object({
|
|
|
70182
70428
|
activeOnly: external_exports.boolean().optional(),
|
|
70183
70429
|
view: viewArg()
|
|
70184
70430
|
});
|
|
70185
|
-
function registerAnnouncementTools(server,
|
|
70431
|
+
function registerAnnouncementTools(server, client2) {
|
|
70186
70432
|
server.registerTool("canvas_list_announcements", {
|
|
70187
70433
|
description: 'List announcements across one or more courses. `contextCodes` is required (e.g. ["course_123"]). Defaults to active-only.',
|
|
70188
|
-
annotations: { readOnlyHint: true },
|
|
70434
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70189
70435
|
inputSchema: argsSchema5
|
|
70190
70436
|
}, async (rawArgs) => {
|
|
70191
70437
|
const args = argsSchema5.parse(rawArgs);
|
|
@@ -70195,7 +70441,7 @@ function registerAnnouncementTools(server, client) {
|
|
|
70195
70441
|
end_date: args.endDate,
|
|
70196
70442
|
active_only: args.activeOnly ?? true
|
|
70197
70443
|
});
|
|
70198
|
-
const data = await
|
|
70444
|
+
const data = await client2.requestPaginated(path);
|
|
70199
70445
|
return viewResponse(args.view, data);
|
|
70200
70446
|
});
|
|
70201
70447
|
}
|
|
@@ -70210,10 +70456,10 @@ var getArgs3 = external_exports.object({
|
|
|
70210
70456
|
id: external_exports.string(),
|
|
70211
70457
|
view: viewArg()
|
|
70212
70458
|
});
|
|
70213
|
-
function registerConversationTools(server,
|
|
70459
|
+
function registerConversationTools(server, client2) {
|
|
70214
70460
|
server.registerTool("canvas_list_conversations", {
|
|
70215
70461
|
description: "List Canvas inbox conversations. Optional `scope` (unread/starred/archived/sent) and `filter` (array of context codes).",
|
|
70216
|
-
annotations: { readOnlyHint: true },
|
|
70462
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70217
70463
|
inputSchema: listArgs3
|
|
70218
70464
|
}, async (rawArgs) => {
|
|
70219
70465
|
const args = listArgs3.parse(rawArgs);
|
|
@@ -70222,12 +70468,12 @@ function registerConversationTools(server, client) {
|
|
|
70222
70468
|
"filter[]": args.filter,
|
|
70223
70469
|
"include[]": ["participant_avatars"]
|
|
70224
70470
|
});
|
|
70225
|
-
const data = await
|
|
70471
|
+
const data = await client2.requestPaginated(path);
|
|
70226
70472
|
return viewResponse(args.view, data);
|
|
70227
70473
|
});
|
|
70228
70474
|
server.registerTool("canvas_get_conversation", {
|
|
70229
70475
|
description: "Get a full Canvas conversation thread with all messages. Read-only: does not mark the conversation as read.",
|
|
70230
|
-
annotations: { readOnlyHint: true },
|
|
70476
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70231
70477
|
inputSchema: getArgs3
|
|
70232
70478
|
}, async (rawArgs) => {
|
|
70233
70479
|
const args = getArgs3.parse(rawArgs);
|
|
@@ -70235,7 +70481,7 @@ function registerConversationTools(server, client) {
|
|
|
70235
70481
|
auto_mark_as_read: false,
|
|
70236
70482
|
"include[]": ["participant_avatars"]
|
|
70237
70483
|
});
|
|
70238
|
-
const data = await
|
|
70484
|
+
const data = await client2.request(path);
|
|
70239
70485
|
return viewResponse(args.view, data);
|
|
70240
70486
|
});
|
|
70241
70487
|
}
|
|
@@ -70247,10 +70493,10 @@ var argsSchema6 = external_exports.object({
|
|
|
70247
70493
|
orderBy: external_exports.enum(["position", "recent_activity", "title"]).optional(),
|
|
70248
70494
|
view: viewArg()
|
|
70249
70495
|
});
|
|
70250
|
-
function registerDiscussionTools(server,
|
|
70496
|
+
function registerDiscussionTools(server, client2) {
|
|
70251
70497
|
server.registerTool("canvas_list_discussion_topics", {
|
|
70252
70498
|
description: "List discussion topics for a course (read-only).",
|
|
70253
|
-
annotations: { readOnlyHint: true },
|
|
70499
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70254
70500
|
inputSchema: argsSchema6
|
|
70255
70501
|
}, async (rawArgs) => {
|
|
70256
70502
|
const args = argsSchema6.parse(rawArgs);
|
|
@@ -70258,7 +70504,7 @@ function registerDiscussionTools(server, client) {
|
|
|
70258
70504
|
only_announcements: args.onlyAnnouncements ?? false,
|
|
70259
70505
|
order_by: args.orderBy
|
|
70260
70506
|
});
|
|
70261
|
-
const data = await
|
|
70507
|
+
const data = await client2.requestPaginated(path);
|
|
70262
70508
|
return viewResponse(args.view, data);
|
|
70263
70509
|
});
|
|
70264
70510
|
}
|
|
@@ -70274,10 +70520,10 @@ var downloadArgs = external_exports.object({
|
|
|
70274
70520
|
destinationPath: external_exports.string().describe("Where to write the file. Must be inside the download directory (CANVAS_OUTPUT_DIR, default ~/Downloads); a relative path is resolved against it."),
|
|
70275
70521
|
overwrite: external_exports.boolean().optional()
|
|
70276
70522
|
});
|
|
70277
|
-
function registerFileTools(server,
|
|
70523
|
+
function registerFileTools(server, client2) {
|
|
70278
70524
|
server.registerTool("canvas_list_course_files", {
|
|
70279
70525
|
description: "List a course's files (metadata only \u2014 use canvas_download_file with the `url` field).",
|
|
70280
|
-
annotations: { readOnlyHint: true },
|
|
70526
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
70281
70527
|
inputSchema: listArgs4
|
|
70282
70528
|
}, async (rawArgs) => {
|
|
70283
70529
|
const args = listArgs4.parse(rawArgs);
|
|
@@ -70285,16 +70531,16 @@ function registerFileTools(server, client) {
|
|
|
70285
70531
|
search_term: args.searchTerm,
|
|
70286
70532
|
"content_types[]": args.contentTypes
|
|
70287
70533
|
});
|
|
70288
|
-
const data = await
|
|
70534
|
+
const data = await client2.requestPaginated(path);
|
|
70289
70535
|
return textContent(data);
|
|
70290
70536
|
});
|
|
70291
70537
|
server.registerTool("canvas_download_file", {
|
|
70292
70538
|
description: "Download a Canvas file to disk. `url` is the absolute URL from canvas_list_course_files (only file URLs on the configured Canvas host are accepted); `destinationPath` is required and must be inside the download directory (CANVAS_OUTPUT_DIR, default ~/Downloads).",
|
|
70293
|
-
annotations: { destructiveHint: true, openWorldHint: true },
|
|
70539
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
70294
70540
|
inputSchema: downloadArgs
|
|
70295
70541
|
}, async (rawArgs) => {
|
|
70296
70542
|
const args = downloadArgs.parse(rawArgs);
|
|
70297
|
-
const meta3 = await
|
|
70543
|
+
const meta3 = await client2.download(args.url, args.destinationPath, {
|
|
70298
70544
|
overwrite: args.overwrite ?? false
|
|
70299
70545
|
});
|
|
70300
70546
|
return textContent(meta3);
|
|
@@ -70311,19 +70557,17 @@ try {
|
|
|
70311
70557
|
} catch (e) {
|
|
70312
70558
|
configError = e;
|
|
70313
70559
|
}
|
|
70560
|
+
var client = resolved ? new CanvasClient(resolved.account, { refreshSession: resolved.refresh }) : null;
|
|
70314
70561
|
await runMcp({
|
|
70315
70562
|
name: "canvas",
|
|
70316
|
-
version: "2.1.
|
|
70563
|
+
version: "2.1.9",
|
|
70317
70564
|
// x-release-please-version
|
|
70318
70565
|
tools: [
|
|
70319
70566
|
// Outside the `if (resolved)` on purpose: an unconfigured server must
|
|
70320
70567
|
// still expose something that can say WHY (see the tool's docblock).
|
|
70321
|
-
(server) => registerHealthcheckTools(server, { resolved, configError }),
|
|
70568
|
+
(server) => registerHealthcheckTools(server, { resolved, configError, client }),
|
|
70322
70569
|
(server) => {
|
|
70323
|
-
if (resolved) {
|
|
70324
|
-
const client = new CanvasClient(resolved.account, {
|
|
70325
|
-
refreshSession: resolved.refresh
|
|
70326
|
-
});
|
|
70570
|
+
if (resolved && client) {
|
|
70327
70571
|
registerProfileTools(server, client);
|
|
70328
70572
|
registerObserveeTools(server, client);
|
|
70329
70573
|
registerCourseTools(server, client);
|