canvas-parent-mcp 1.1.8 → 1.2.2
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/dist/bundle.js +351 -51
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/server.json +2 -2
- package/skills/canvas-parent/SKILL.md +92 -0
- package/skills/canvas-parent-api/SKILL.md +138 -0
- package/skills/canvas-parent-api/references/canvas-endpoints.md +223 -0
- package/skills/canvas/SKILL.md +0 -202
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Canvas LMS (Instructure) — courses, grades, assignments, announcements, planner, conversations, files",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.2.2"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Canvas LMS",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Canvas LMS (Instructure) MCP server for Claude — student/observer access via natural language",
|
|
18
|
-
"version": "1.
|
|
18
|
+
"version": "1.2.2",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvas-parent-mcp",
|
|
3
3
|
"displayName": "Canvas LMS",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.2",
|
|
5
5
|
"description": "Canvas LMS (Instructure) MCP server for Claude — courses, grades, assignments, announcements, planner, conversations, files",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
|
@@ -20,6 +20,6 @@
|
|
|
20
20
|
"education",
|
|
21
21
|
"mcp"
|
|
22
22
|
],
|
|
23
|
-
"skills": "./
|
|
23
|
+
"skills": "./skills/",
|
|
24
24
|
"mcp": "./.mcp.json"
|
|
25
25
|
}
|
package/dist/bundle.js
CHANGED
|
@@ -3776,6 +3776,7 @@ var require_fast_uri = __commonJS({
|
|
|
3776
3776
|
return uriTokens.join("");
|
|
3777
3777
|
}
|
|
3778
3778
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3779
|
+
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3779
3780
|
function getParseError(parsed, matches) {
|
|
3780
3781
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3781
3782
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3805,6 +3806,11 @@ var require_fast_uri = __commonJS({
|
|
|
3805
3806
|
uri = "//" + uri;
|
|
3806
3807
|
}
|
|
3807
3808
|
}
|
|
3809
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX);
|
|
3810
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf("\\") !== -1) {
|
|
3811
|
+
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3812
|
+
malformedAuthorityOrPort = true;
|
|
3813
|
+
}
|
|
3808
3814
|
const matches = uri.match(URI_PARSE);
|
|
3809
3815
|
if (matches) {
|
|
3810
3816
|
parsed.scheme = matches[1];
|
|
@@ -3848,7 +3854,7 @@ var require_fast_uri = __commonJS({
|
|
|
3848
3854
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
3849
3855
|
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
3850
3856
|
try {
|
|
3851
|
-
parsed.host = URL
|
|
3857
|
+
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
3852
3858
|
} catch (e) {
|
|
3853
3859
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
3854
3860
|
}
|
|
@@ -7693,6 +7699,7 @@ var require_receiver = __commonJS({
|
|
|
7693
7699
|
this._opcode = 0;
|
|
7694
7700
|
this._totalPayloadLength = 0;
|
|
7695
7701
|
this._messageLength = 0;
|
|
7702
|
+
this._numFragments = 0;
|
|
7696
7703
|
this._fragments = [];
|
|
7697
7704
|
this._errored = false;
|
|
7698
7705
|
this._loop = false;
|
|
@@ -8043,23 +8050,23 @@ var require_receiver = __commonJS({
|
|
|
8043
8050
|
this.controlMessage(data, cb);
|
|
8044
8051
|
return;
|
|
8045
8052
|
}
|
|
8053
|
+
if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
|
|
8054
|
+
const error51 = this.createError(
|
|
8055
|
+
RangeError,
|
|
8056
|
+
"Too many message fragments",
|
|
8057
|
+
false,
|
|
8058
|
+
1008,
|
|
8059
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
8060
|
+
);
|
|
8061
|
+
cb(error51);
|
|
8062
|
+
return;
|
|
8063
|
+
}
|
|
8046
8064
|
if (this._compressed) {
|
|
8047
8065
|
this._state = INFLATING;
|
|
8048
8066
|
this.decompress(data, cb);
|
|
8049
8067
|
return;
|
|
8050
8068
|
}
|
|
8051
8069
|
if (data.length) {
|
|
8052
|
-
if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
|
|
8053
|
-
const error51 = this.createError(
|
|
8054
|
-
RangeError,
|
|
8055
|
-
"Too many message fragments",
|
|
8056
|
-
false,
|
|
8057
|
-
1008,
|
|
8058
|
-
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
8059
|
-
);
|
|
8060
|
-
cb(error51);
|
|
8061
|
-
return;
|
|
8062
|
-
}
|
|
8063
8070
|
this._messageLength = this._totalPayloadLength;
|
|
8064
8071
|
this._fragments.push(data);
|
|
8065
8072
|
}
|
|
@@ -8089,17 +8096,6 @@ var require_receiver = __commonJS({
|
|
|
8089
8096
|
cb(error51);
|
|
8090
8097
|
return;
|
|
8091
8098
|
}
|
|
8092
|
-
if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
|
|
8093
|
-
const error51 = this.createError(
|
|
8094
|
-
RangeError,
|
|
8095
|
-
"Too many message fragments",
|
|
8096
|
-
false,
|
|
8097
|
-
1008,
|
|
8098
|
-
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
8099
|
-
);
|
|
8100
|
-
cb(error51);
|
|
8101
|
-
return;
|
|
8102
|
-
}
|
|
8103
8099
|
this._fragments.push(buf);
|
|
8104
8100
|
}
|
|
8105
8101
|
this.dataMessage(cb);
|
|
@@ -8122,6 +8118,7 @@ var require_receiver = __commonJS({
|
|
|
8122
8118
|
this._totalPayloadLength = 0;
|
|
8123
8119
|
this._messageLength = 0;
|
|
8124
8120
|
this._fragmented = 0;
|
|
8121
|
+
this._numFragments = 0;
|
|
8125
8122
|
this._fragments = [];
|
|
8126
8123
|
if (this._opcode === 2) {
|
|
8127
8124
|
let data;
|
|
@@ -9622,8 +9619,8 @@ var require_websocket = __commonJS({
|
|
|
9622
9619
|
autoPong: true,
|
|
9623
9620
|
closeTimeout: CLOSE_TIMEOUT,
|
|
9624
9621
|
protocolVersion: protocolVersions[1],
|
|
9625
|
-
maxBufferedChunks:
|
|
9626
|
-
maxFragments:
|
|
9622
|
+
maxBufferedChunks: 256 * 1024,
|
|
9623
|
+
maxFragments: 16 * 1024,
|
|
9627
9624
|
maxPayload: 100 * 1024 * 1024,
|
|
9628
9625
|
skipUTF8Validation: false,
|
|
9629
9626
|
perMessageDeflate: true,
|
|
@@ -10210,9 +10207,9 @@ var require_websocket_server = __commonJS({
|
|
|
10210
10207
|
* called
|
|
10211
10208
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
10212
10209
|
* @param {String} [options.host] The hostname where to bind the server
|
|
10213
|
-
* @param {Number} [options.maxBufferedChunks=
|
|
10210
|
+
* @param {Number} [options.maxBufferedChunks=262144] The maximum number of
|
|
10214
10211
|
* buffered data chunks
|
|
10215
|
-
* @param {Number} [options.maxFragments=
|
|
10212
|
+
* @param {Number} [options.maxFragments=16384] The maximum number of message
|
|
10216
10213
|
* fragments
|
|
10217
10214
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
10218
10215
|
* size
|
|
@@ -10235,8 +10232,8 @@ var require_websocket_server = __commonJS({
|
|
|
10235
10232
|
options = {
|
|
10236
10233
|
allowSynchronousEvents: true,
|
|
10237
10234
|
autoPong: true,
|
|
10238
|
-
maxBufferedChunks:
|
|
10239
|
-
maxFragments:
|
|
10235
|
+
maxBufferedChunks: 256 * 1024,
|
|
10236
|
+
maxFragments: 16 * 1024,
|
|
10240
10237
|
maxPayload: 100 * 1024 * 1024,
|
|
10241
10238
|
skipUTF8Validation: false,
|
|
10242
10239
|
perMessageDeflate: false,
|
|
@@ -34989,7 +34986,9 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
34989
34986
|
"capture_request_header",
|
|
34990
34987
|
"capture_redirect",
|
|
34991
34988
|
"read_indexed_db",
|
|
34992
|
-
"
|
|
34989
|
+
"read_dom",
|
|
34990
|
+
"download",
|
|
34991
|
+
"graphql"
|
|
34993
34992
|
]);
|
|
34994
34993
|
|
|
34995
34994
|
// node_modules/@fetchproxy/protocol/dist/mcp-id.js
|
|
@@ -35290,6 +35289,76 @@ function assertIndexedDbScopesArray(value, label) {
|
|
|
35290
35289
|
}
|
|
35291
35290
|
}
|
|
35292
35291
|
}
|
|
35292
|
+
var DOM_SELECTOR_RE = /^[^-]{1,512}$/;
|
|
35293
|
+
var DOM_ATTRIBUTE_RE = /^[A-Za-z_:][A-Za-z0-9_:.\-]{0,127}$/;
|
|
35294
|
+
var GRAPHQL_OP_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]{0,127}$/;
|
|
35295
|
+
function assertDomSelectorsArray(value, label) {
|
|
35296
|
+
if (!Array.isArray(value)) {
|
|
35297
|
+
throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
|
|
35298
|
+
}
|
|
35299
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35300
|
+
for (let i = 0; i < value.length; i++) {
|
|
35301
|
+
const entry = value[i];
|
|
35302
|
+
assertObject(entry, `${label}[${i}]`);
|
|
35303
|
+
if (entry.name === void 0) {
|
|
35304
|
+
throw new ProtocolError(`${label}[${i}].name: missing`);
|
|
35305
|
+
}
|
|
35306
|
+
if (entry.selector === void 0) {
|
|
35307
|
+
throw new ProtocolError(`${label}[${i}].selector: missing`);
|
|
35308
|
+
}
|
|
35309
|
+
if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
|
|
35310
|
+
throw new ProtocolError(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
|
|
35311
|
+
}
|
|
35312
|
+
if (typeof entry.selector !== "string" || !DOM_SELECTOR_RE.test(entry.selector)) {
|
|
35313
|
+
throw new ProtocolError(`${label}[${i}].selector: invalid ${JSON.stringify(entry.selector)}`);
|
|
35314
|
+
}
|
|
35315
|
+
if (entry.attribute !== void 0) {
|
|
35316
|
+
if (typeof entry.attribute !== "string" || !DOM_ATTRIBUTE_RE.test(entry.attribute)) {
|
|
35317
|
+
throw new ProtocolError(`${label}[${i}].attribute: invalid ${JSON.stringify(entry.attribute)}`);
|
|
35318
|
+
}
|
|
35319
|
+
}
|
|
35320
|
+
if (seen.has(entry.name)) {
|
|
35321
|
+
throw new ProtocolError(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
|
|
35322
|
+
}
|
|
35323
|
+
seen.add(entry.name);
|
|
35324
|
+
for (const k of Object.keys(entry)) {
|
|
35325
|
+
if (k !== "name" && k !== "selector" && k !== "attribute") {
|
|
35326
|
+
throw new ProtocolError(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
35327
|
+
}
|
|
35328
|
+
}
|
|
35329
|
+
}
|
|
35330
|
+
}
|
|
35331
|
+
function assertGraphqlOpsArray(value, label) {
|
|
35332
|
+
if (!Array.isArray(value)) {
|
|
35333
|
+
throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
|
|
35334
|
+
}
|
|
35335
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35336
|
+
for (let i = 0; i < value.length; i++) {
|
|
35337
|
+
const entry = value[i];
|
|
35338
|
+
assertObject(entry, `${label}[${i}]`);
|
|
35339
|
+
if (entry.name === void 0) {
|
|
35340
|
+
throw new ProtocolError(`${label}[${i}].name: missing`);
|
|
35341
|
+
}
|
|
35342
|
+
if (entry.operationName === void 0) {
|
|
35343
|
+
throw new ProtocolError(`${label}[${i}].operationName: missing`);
|
|
35344
|
+
}
|
|
35345
|
+
if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
|
|
35346
|
+
throw new ProtocolError(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
|
|
35347
|
+
}
|
|
35348
|
+
if (typeof entry.operationName !== "string" || !GRAPHQL_OP_NAME_RE.test(entry.operationName)) {
|
|
35349
|
+
throw new ProtocolError(`${label}[${i}].operationName: invalid ${JSON.stringify(entry.operationName)}`);
|
|
35350
|
+
}
|
|
35351
|
+
if (seen.has(entry.name)) {
|
|
35352
|
+
throw new ProtocolError(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
|
|
35353
|
+
}
|
|
35354
|
+
seen.add(entry.name);
|
|
35355
|
+
for (const k of Object.keys(entry)) {
|
|
35356
|
+
if (k !== "name" && k !== "operationName") {
|
|
35357
|
+
throw new ProtocolError(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
35358
|
+
}
|
|
35359
|
+
}
|
|
35360
|
+
}
|
|
35361
|
+
}
|
|
35293
35362
|
function validateFrame(raw) {
|
|
35294
35363
|
assertObject(raw, "frame");
|
|
35295
35364
|
const t = raw.type;
|
|
@@ -35365,6 +35434,12 @@ function validateHello(raw) {
|
|
|
35365
35434
|
if (raw.sessionStoragePointers !== void 0) {
|
|
35366
35435
|
assertStoragePointersArray(raw.sessionStoragePointers, "hello.sessionStoragePointers", raw.sessionStorageKeys);
|
|
35367
35436
|
}
|
|
35437
|
+
if (raw.domSelectors !== void 0) {
|
|
35438
|
+
assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
|
|
35439
|
+
}
|
|
35440
|
+
if (raw.graphqlOps !== void 0) {
|
|
35441
|
+
assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
|
|
35442
|
+
}
|
|
35368
35443
|
assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
|
|
35369
35444
|
assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
|
|
35370
35445
|
assertBase64(raw.sessionNonce, "hello.sessionNonce");
|
|
@@ -35599,6 +35674,43 @@ function validateInnerRequest(raw) {
|
|
|
35599
35674
|
}
|
|
35600
35675
|
return raw;
|
|
35601
35676
|
}
|
|
35677
|
+
if (raw.op === "read_dom") {
|
|
35678
|
+
assertObject(raw.init, "inner.init");
|
|
35679
|
+
if (raw.init.origin === void 0)
|
|
35680
|
+
throw new ProtocolError("inner.init.origin: missing");
|
|
35681
|
+
if (raw.init.names === void 0)
|
|
35682
|
+
throw new ProtocolError("inner.init.names: missing");
|
|
35683
|
+
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
35684
|
+
assertNonEmptyKeyArray(raw.init.names, "inner.init.names");
|
|
35685
|
+
for (const k of Object.keys(raw.init)) {
|
|
35686
|
+
if (k !== "origin" && k !== "names") {
|
|
35687
|
+
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on read_dom`);
|
|
35688
|
+
}
|
|
35689
|
+
}
|
|
35690
|
+
return raw;
|
|
35691
|
+
}
|
|
35692
|
+
if (raw.op === "graphql_query") {
|
|
35693
|
+
assertObject(raw.init, "inner.init");
|
|
35694
|
+
if (raw.init.name === void 0)
|
|
35695
|
+
throw new ProtocolError("inner.init.name: missing");
|
|
35696
|
+
if (raw.init.variables === void 0) {
|
|
35697
|
+
throw new ProtocolError("inner.init.variables: missing");
|
|
35698
|
+
}
|
|
35699
|
+
assertString(raw.init.name, "inner.init.name");
|
|
35700
|
+
if (raw.init.name.length === 0) {
|
|
35701
|
+
throw new ProtocolError("inner.init.name: must be non-empty");
|
|
35702
|
+
}
|
|
35703
|
+
assertObject(raw.init.variables, "inner.init.variables");
|
|
35704
|
+
if (raw.init.tabUrl !== void 0) {
|
|
35705
|
+
assertString(raw.init.tabUrl, "inner.init.tabUrl");
|
|
35706
|
+
}
|
|
35707
|
+
for (const k of Object.keys(raw.init)) {
|
|
35708
|
+
if (k !== "name" && k !== "variables" && k !== "tabUrl") {
|
|
35709
|
+
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on graphql_query`);
|
|
35710
|
+
}
|
|
35711
|
+
}
|
|
35712
|
+
return raw;
|
|
35713
|
+
}
|
|
35602
35714
|
if (raw.op === "download") {
|
|
35603
35715
|
assertObject(raw.init, "inner.init");
|
|
35604
35716
|
if (raw.init.url === void 0) {
|
|
@@ -35624,7 +35736,7 @@ function validateInnerRequest(raw) {
|
|
|
35624
35736
|
}
|
|
35625
35737
|
return raw;
|
|
35626
35738
|
}
|
|
35627
|
-
throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "download"; got ${JSON.stringify(raw.op)}`);
|
|
35739
|
+
throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query"; got ${JSON.stringify(raw.op)}`);
|
|
35628
35740
|
}
|
|
35629
35741
|
function assertNonEmptyKeyArray(value, label) {
|
|
35630
35742
|
if (!Array.isArray(value)) {
|
|
@@ -35655,6 +35767,10 @@ function assertStringMap(value, label) {
|
|
|
35655
35767
|
}
|
|
35656
35768
|
}
|
|
35657
35769
|
}
|
|
35770
|
+
var KNOWN_RESPONSE_OPS = /* @__PURE__ */ new Set([
|
|
35771
|
+
...KNOWN_CAPABILITIES,
|
|
35772
|
+
"graphql_query"
|
|
35773
|
+
]);
|
|
35658
35774
|
function validateInnerResponse(raw) {
|
|
35659
35775
|
assertPositiveInt(raw.id, "inner.id");
|
|
35660
35776
|
if (raw.ok === true) {
|
|
@@ -35709,6 +35825,20 @@ function validateInnerResponse(raw) {
|
|
|
35709
35825
|
assertObject(raw.values, "inner.values");
|
|
35710
35826
|
return raw;
|
|
35711
35827
|
}
|
|
35828
|
+
if (op === "read_dom") {
|
|
35829
|
+
if (raw.values === void 0) {
|
|
35830
|
+
throw new ProtocolError("inner.values: missing on read_dom response");
|
|
35831
|
+
}
|
|
35832
|
+
assertStringMap(raw.values, "inner.values");
|
|
35833
|
+
return raw;
|
|
35834
|
+
}
|
|
35835
|
+
if (op === "graphql_query") {
|
|
35836
|
+
if (raw.data === void 0) {
|
|
35837
|
+
throw new ProtocolError("inner.data: missing on graphql_query response");
|
|
35838
|
+
}
|
|
35839
|
+
assertObject(raw.data, "inner.data");
|
|
35840
|
+
return raw;
|
|
35841
|
+
}
|
|
35712
35842
|
if (op === "download") {
|
|
35713
35843
|
assertObject(raw.value, "inner.value");
|
|
35714
35844
|
assertString(raw.value.path, "inner.value.path");
|
|
@@ -35733,7 +35863,7 @@ function validateInnerResponse(raw) {
|
|
|
35733
35863
|
if (raw.ok === false) {
|
|
35734
35864
|
assertString(raw.error, "inner.error");
|
|
35735
35865
|
if (raw.op !== void 0) {
|
|
35736
|
-
if (typeof raw.op !== "string" || !
|
|
35866
|
+
if (typeof raw.op !== "string" || !KNOWN_RESPONSE_OPS.has(raw.op)) {
|
|
35737
35867
|
throw new ProtocolError(`inner.op: unknown response op ${JSON.stringify(raw.op)}`);
|
|
35738
35868
|
}
|
|
35739
35869
|
}
|
|
@@ -35925,11 +36055,33 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner) {
|
|
|
35925
36055
|
};
|
|
35926
36056
|
}
|
|
35927
36057
|
async function openEncryptedFrame(sessionKey, frame) {
|
|
35928
|
-
const
|
|
35929
|
-
|
|
35930
|
-
|
|
35931
|
-
|
|
35932
|
-
|
|
36058
|
+
const result = await openEncryptedFrameDetailed(sessionKey, frame);
|
|
36059
|
+
if (result.stage === "ok")
|
|
36060
|
+
return result.inner;
|
|
36061
|
+
throw result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
36062
|
+
}
|
|
36063
|
+
async function openEncryptedFrameDetailed(sessionKey, frame) {
|
|
36064
|
+
let pt;
|
|
36065
|
+
try {
|
|
36066
|
+
const iv = fromB64(frame.iv);
|
|
36067
|
+
const ct = fromB64(frame.ciphertext);
|
|
36068
|
+
pt = await aesGcmOpen(sessionKey, iv, ct);
|
|
36069
|
+
} catch (error51) {
|
|
36070
|
+
return { stage: "decrypt-failed", error: error51 };
|
|
36071
|
+
}
|
|
36072
|
+
let parsed;
|
|
36073
|
+
try {
|
|
36074
|
+
parsed = JSON.parse(dec.decode(pt));
|
|
36075
|
+
} catch (error51) {
|
|
36076
|
+
return { stage: "validation-failed", error: error51, recoveredId: void 0 };
|
|
36077
|
+
}
|
|
36078
|
+
try {
|
|
36079
|
+
const inner = validateInnerFrame(parsed);
|
|
36080
|
+
return { stage: "ok", inner };
|
|
36081
|
+
} catch (error51) {
|
|
36082
|
+
const recoveredId = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.id === "number" && Number.isInteger(parsed.id) && parsed.id > 0 ? parsed.id : void 0;
|
|
36083
|
+
return { stage: "validation-failed", error: error51, recoveredId };
|
|
36084
|
+
}
|
|
35933
36085
|
}
|
|
35934
36086
|
|
|
35935
36087
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
@@ -36048,6 +36200,19 @@ async function buildServerHello(opts) {
|
|
|
36048
36200
|
jsonPointer: d.jsonPointer
|
|
36049
36201
|
}));
|
|
36050
36202
|
}
|
|
36203
|
+
if (opts.domSelectors && opts.domSelectors.length > 0) {
|
|
36204
|
+
hello.domSelectors = opts.domSelectors.map((d) => ({
|
|
36205
|
+
name: d.name,
|
|
36206
|
+
selector: d.selector,
|
|
36207
|
+
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
36208
|
+
}));
|
|
36209
|
+
}
|
|
36210
|
+
if (opts.graphqlOps && opts.graphqlOps.length > 0) {
|
|
36211
|
+
hello.graphqlOps = opts.graphqlOps.map((d) => ({
|
|
36212
|
+
name: d.name,
|
|
36213
|
+
operationName: d.operationName
|
|
36214
|
+
}));
|
|
36215
|
+
}
|
|
36051
36216
|
return hello;
|
|
36052
36217
|
}
|
|
36053
36218
|
|
|
@@ -36137,7 +36302,9 @@ async function startHost(opts) {
|
|
|
36137
36302
|
captureHeaders: opts.ownCaptureHeaders,
|
|
36138
36303
|
indexedDbScopes: opts.ownIndexedDbScopes,
|
|
36139
36304
|
localStoragePointers: opts.ownLocalStoragePointers,
|
|
36140
|
-
sessionStoragePointers: opts.ownSessionStoragePointers
|
|
36305
|
+
sessionStoragePointers: opts.ownSessionStoragePointers,
|
|
36306
|
+
domSelectors: opts.ownDomSelectors,
|
|
36307
|
+
graphqlOps: opts.ownGraphqlOps
|
|
36141
36308
|
});
|
|
36142
36309
|
const ownSessionNonce = fromB64(ownHello.sessionNonce);
|
|
36143
36310
|
let extensionWs = null;
|
|
@@ -36372,8 +36539,10 @@ async function startPeer(opts) {
|
|
|
36372
36539
|
sessionStorageKeys: opts.sessionStorageKeys,
|
|
36373
36540
|
captureHeaders: opts.captureHeaders,
|
|
36374
36541
|
indexedDbScopes: opts.indexedDbScopes,
|
|
36542
|
+
domSelectors: opts.domSelectors,
|
|
36375
36543
|
localStoragePointers: opts.localStoragePointers,
|
|
36376
|
-
sessionStoragePointers: opts.sessionStoragePointers
|
|
36544
|
+
sessionStoragePointers: opts.sessionStoragePointers,
|
|
36545
|
+
graphqlOps: opts.graphqlOps
|
|
36377
36546
|
});
|
|
36378
36547
|
const sessionNonce = fromB64(hello.sessionNonce);
|
|
36379
36548
|
ws.send(JSON.stringify(hello));
|
|
@@ -36417,10 +36586,20 @@ async function startPeer(opts) {
|
|
|
36417
36586
|
return;
|
|
36418
36587
|
if (!session.acceptInboundSeq(frame.seq))
|
|
36419
36588
|
return;
|
|
36420
|
-
|
|
36421
|
-
|
|
36422
|
-
innerListeners.forEach((cb) => cb(inner));
|
|
36423
|
-
}
|
|
36589
|
+
const result = await openEncryptedFrameDetailed(session.sessionKey, frame);
|
|
36590
|
+
if (result.stage === "ok") {
|
|
36591
|
+
innerListeners.forEach((cb) => cb(result.inner));
|
|
36592
|
+
} else if (result.stage === "decrypt-failed") {
|
|
36593
|
+
} else {
|
|
36594
|
+
console.error("[fetchproxy] peer: received a frame that decrypted OK but failed validation:", result.error);
|
|
36595
|
+
if (result.recoveredId !== void 0) {
|
|
36596
|
+
innerListeners.forEach((cb) => cb({
|
|
36597
|
+
type: "response",
|
|
36598
|
+
id: result.recoveredId,
|
|
36599
|
+
ok: false,
|
|
36600
|
+
error: `malformed response failed protocol validation: ${String(result.error)}`
|
|
36601
|
+
}));
|
|
36602
|
+
}
|
|
36424
36603
|
}
|
|
36425
36604
|
}
|
|
36426
36605
|
} catch (e) {
|
|
@@ -36697,6 +36876,10 @@ var FetchproxyServer = class {
|
|
|
36697
36876
|
pendingIdb = /* @__PURE__ */ new Map();
|
|
36698
36877
|
// download awaiters resolve the saved-file metadata (path + size + mime).
|
|
36699
36878
|
pendingDownload = /* @__PURE__ */ new Map();
|
|
36879
|
+
// 1.x+: graphql_query awaiters resolve the GraphQL `data` object. Its
|
|
36880
|
+
// shape is operation-specific, so the awaiter resolves `unknown` and the
|
|
36881
|
+
// caller narrows.
|
|
36882
|
+
pendingGraphql = /* @__PURE__ */ new Map();
|
|
36700
36883
|
mcpId = null;
|
|
36701
36884
|
identity = null;
|
|
36702
36885
|
// 0.5.3+: in-flight role-election / handle-start promise. Set the
|
|
@@ -36779,6 +36962,15 @@ var FetchproxyServer = class {
|
|
|
36779
36962
|
key: d.key,
|
|
36780
36963
|
jsonPointer: d.jsonPointer
|
|
36781
36964
|
})),
|
|
36965
|
+
domSelectors: (opts.domSelectors ?? []).map((d) => ({
|
|
36966
|
+
name: d.name,
|
|
36967
|
+
selector: d.selector,
|
|
36968
|
+
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
36969
|
+
})),
|
|
36970
|
+
graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
|
|
36971
|
+
name: d.name,
|
|
36972
|
+
operationName: d.operationName
|
|
36973
|
+
})),
|
|
36782
36974
|
// 0.8.0+: timer + lazy-revive default to ON. Every realty MCP
|
|
36783
36975
|
// adapter was about to set these to the same numbers anyway; the
|
|
36784
36976
|
// back-door is `0` (explicit opt-out) if a caller genuinely wants
|
|
@@ -36899,6 +37091,8 @@ var FetchproxyServer = class {
|
|
|
36899
37091
|
ownIndexedDbScopes: this.opts.indexedDbScopes,
|
|
36900
37092
|
ownLocalStoragePointers: this.opts.localStoragePointers,
|
|
36901
37093
|
ownSessionStoragePointers: this.opts.sessionStoragePointers,
|
|
37094
|
+
ownDomSelectors: this.opts.domSelectors,
|
|
37095
|
+
ownGraphqlOps: this.opts.graphqlOps,
|
|
36902
37096
|
onPairCode: this.opts.onPairCode
|
|
36903
37097
|
});
|
|
36904
37098
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
@@ -36926,7 +37120,9 @@ var FetchproxyServer = class {
|
|
|
36926
37120
|
captureHeaders: this.opts.captureHeaders,
|
|
36927
37121
|
indexedDbScopes: this.opts.indexedDbScopes,
|
|
36928
37122
|
localStoragePointers: this.opts.localStoragePointers,
|
|
36929
|
-
sessionStoragePointers: this.opts.sessionStoragePointers
|
|
37123
|
+
sessionStoragePointers: this.opts.sessionStoragePointers,
|
|
37124
|
+
domSelectors: this.opts.domSelectors,
|
|
37125
|
+
graphqlOps: this.opts.graphqlOps
|
|
36930
37126
|
});
|
|
36931
37127
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
36932
37128
|
this.peerHandle.onRenegotiate(() => {
|
|
@@ -37132,6 +37328,7 @@ var FetchproxyServer = class {
|
|
|
37132
37328
|
this.pendingRedirect.delete(id);
|
|
37133
37329
|
this.pendingDownload.delete(id);
|
|
37134
37330
|
this.pendingIdb.delete(id);
|
|
37331
|
+
this.pendingGraphql.delete(id);
|
|
37135
37332
|
}
|
|
37136
37333
|
throw err;
|
|
37137
37334
|
}
|
|
@@ -37901,6 +38098,92 @@ var FetchproxyServer = class {
|
|
|
37901
38098
|
await this.sendInnerFrame(inner);
|
|
37902
38099
|
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
37903
38100
|
}
|
|
38101
|
+
/**
|
|
38102
|
+
* 1.4.0+: read declared DOM values from the user's signed-in tab.
|
|
38103
|
+
* Requires `'read_dom'` in capabilities AND every requested `name` to
|
|
38104
|
+
* match a declared `domSelectors` entry. The extension reads each
|
|
38105
|
+
* declared selector from the matched tab's DOM (isolated-world
|
|
38106
|
+
* `querySelector`, value or attribute) — no page-JS execution.
|
|
38107
|
+
*
|
|
38108
|
+
* Returns a `Record<string, string>` of `name → value`, with names
|
|
38109
|
+
* whose element (or attribute) was absent omitted. Throws
|
|
38110
|
+
* `FetchproxyProtocolError` on bridge failures and a plain `Error` on
|
|
38111
|
+
* developer mistakes (undeclared capability, undeclared name).
|
|
38112
|
+
*/
|
|
38113
|
+
async readDom(opts) {
|
|
38114
|
+
if (!this.opts.capabilities.includes("read_dom")) {
|
|
38115
|
+
throw new Error('FetchproxyServer.readDom(): MCP did not declare "read_dom" in capabilities');
|
|
38116
|
+
}
|
|
38117
|
+
await this.ensureConnected();
|
|
38118
|
+
this.throwIfPendingPair();
|
|
38119
|
+
if (!Array.isArray(opts.names) || opts.names.length === 0) {
|
|
38120
|
+
throw new Error("FetchproxyServer.readDom: opts.names must be a non-empty array");
|
|
38121
|
+
}
|
|
38122
|
+
this.assertScopeSubset(opts.names, this.opts.domSelectors.map((d) => d.name), "domSelectors");
|
|
38123
|
+
if (opts.subdomain !== void 0)
|
|
38124
|
+
assertSubdomainLabel(opts.subdomain);
|
|
38125
|
+
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
38126
|
+
const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
38127
|
+
const origin = `https://${host}`;
|
|
38128
|
+
const id = this.nextRequestId++;
|
|
38129
|
+
const inner = {
|
|
38130
|
+
type: "request",
|
|
38131
|
+
id,
|
|
38132
|
+
op: "read_dom",
|
|
38133
|
+
init: { origin, names: [...opts.names] }
|
|
38134
|
+
};
|
|
38135
|
+
const pending = new Promise((resolve, reject) => {
|
|
38136
|
+
this.pendingStorage.set(id, { resolve, reject });
|
|
38137
|
+
});
|
|
38138
|
+
await this.sendInnerFrame(inner);
|
|
38139
|
+
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
38140
|
+
}
|
|
38141
|
+
/**
|
|
38142
|
+
* 1.x+: run a declared GraphQL operation through the page's own Apollo
|
|
38143
|
+
* client (`window.__APOLLO_CLIENT__`) in the signed-in tab's MAIN world.
|
|
38144
|
+
* Requires `'graphql'` in capabilities AND `name` to match a declared
|
|
38145
|
+
* `graphqlOps` entry. The extension resolves `name` → `operationName` →
|
|
38146
|
+
* the live DocumentNode the page already observed, then invokes
|
|
38147
|
+
* `client.query({ query, variables })` — the site's own request path, so
|
|
38148
|
+
* per-request bot telemetry (Akamai etc.) runs automatically.
|
|
38149
|
+
*
|
|
38150
|
+
* Returns the GraphQL `data` object on success (shape is
|
|
38151
|
+
* operation-specific; the caller narrows). Throws a plain `Error` on
|
|
38152
|
+
* developer mistakes (undeclared capability, undeclared name) and a
|
|
38153
|
+
* descriptive `Error` on the `ok:false` bridge path — which includes the
|
|
38154
|
+
* typed "operation not yet observed on this tab" case (open the site's
|
|
38155
|
+
* page and retry).
|
|
38156
|
+
*/
|
|
38157
|
+
async graphqlQuery(opts) {
|
|
38158
|
+
if (!this.opts.capabilities.includes("graphql")) {
|
|
38159
|
+
throw new Error('FetchproxyServer.graphqlQuery(): MCP did not declare "graphql" in capabilities');
|
|
38160
|
+
}
|
|
38161
|
+
if (typeof opts.name !== "string" || opts.name.length === 0) {
|
|
38162
|
+
throw new Error("FetchproxyServer.graphqlQuery: opts.name must be a non-empty string");
|
|
38163
|
+
}
|
|
38164
|
+
const declaredNames = this.opts.graphqlOps.map((d) => d.name);
|
|
38165
|
+
if (!declaredNames.includes(opts.name)) {
|
|
38166
|
+
throw new Error(`FetchproxyServer.graphqlQuery: operation ${JSON.stringify(opts.name)} not in declared graphqlOps [${declaredNames.map((n) => JSON.stringify(n)).join(", ")}]`);
|
|
38167
|
+
}
|
|
38168
|
+
await this.ensureConnected();
|
|
38169
|
+
this.throwIfPendingPair();
|
|
38170
|
+
const id = this.nextRequestId++;
|
|
38171
|
+
const inner = {
|
|
38172
|
+
type: "request",
|
|
38173
|
+
id,
|
|
38174
|
+
op: "graphql_query",
|
|
38175
|
+
init: {
|
|
38176
|
+
name: opts.name,
|
|
38177
|
+
variables: opts.variables,
|
|
38178
|
+
...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
|
|
38179
|
+
}
|
|
38180
|
+
};
|
|
38181
|
+
const pending = new Promise((resolve, reject) => {
|
|
38182
|
+
this.pendingGraphql.set(id, { resolve, reject });
|
|
38183
|
+
});
|
|
38184
|
+
await this.sendInnerFrame(inner);
|
|
38185
|
+
return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
|
|
38186
|
+
}
|
|
37904
38187
|
assertScopeSubset(requested, declared, label) {
|
|
37905
38188
|
const undeclared = undeclaredKeys(requested, declared);
|
|
37906
38189
|
if (undeclared.length > 0) {
|
|
@@ -37972,7 +38255,7 @@ var FetchproxyServer = class {
|
|
|
37972
38255
|
if (storageCb) {
|
|
37973
38256
|
this.pendingStorage.delete(inner.id);
|
|
37974
38257
|
if (inner.ok) {
|
|
37975
|
-
if ((inner.op === "read_local_storage" || inner.op === "read_session_storage") && inner.values) {
|
|
38258
|
+
if ((inner.op === "read_local_storage" || inner.op === "read_session_storage" || inner.op === "read_dom") && inner.values) {
|
|
37976
38259
|
storageCb.resolve({ ...inner.values });
|
|
37977
38260
|
} else {
|
|
37978
38261
|
storageCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on storage awaiter`));
|
|
@@ -38038,6 +38321,20 @@ var FetchproxyServer = class {
|
|
|
38038
38321
|
}
|
|
38039
38322
|
return;
|
|
38040
38323
|
}
|
|
38324
|
+
const graphqlCb = this.pendingGraphql.get(inner.id);
|
|
38325
|
+
if (graphqlCb) {
|
|
38326
|
+
this.pendingGraphql.delete(inner.id);
|
|
38327
|
+
if (inner.ok) {
|
|
38328
|
+
if (inner.op === "graphql_query") {
|
|
38329
|
+
graphqlCb.resolve(inner.data);
|
|
38330
|
+
} else {
|
|
38331
|
+
graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
|
|
38332
|
+
}
|
|
38333
|
+
} else {
|
|
38334
|
+
graphqlCb.reject(new FetchproxyProtocolError(inner.error));
|
|
38335
|
+
}
|
|
38336
|
+
return;
|
|
38337
|
+
}
|
|
38041
38338
|
const cookiesCb = this.pendingReadCookies.get(inner.id);
|
|
38042
38339
|
if (cookiesCb) {
|
|
38043
38340
|
this.pendingReadCookies.delete(inner.id);
|
|
@@ -38089,6 +38386,9 @@ var FetchproxyServer = class {
|
|
|
38089
38386
|
for (const { reject } of this.pendingDownload.values())
|
|
38090
38387
|
reject(err);
|
|
38091
38388
|
this.pendingDownload.clear();
|
|
38389
|
+
for (const { reject } of this.pendingGraphql.values())
|
|
38390
|
+
reject(err);
|
|
38391
|
+
this.pendingGraphql.clear();
|
|
38092
38392
|
}
|
|
38093
38393
|
/**
|
|
38094
38394
|
* 0.5.2+: read the current pair-pending pair code from whichever handle
|
|
@@ -38379,7 +38679,7 @@ function loadAccount(env = process.env) {
|
|
|
38379
38679
|
// package.json
|
|
38380
38680
|
var package_default = {
|
|
38381
38681
|
name: "canvas-parent-mcp",
|
|
38382
|
-
version: "1.
|
|
38682
|
+
version: "1.2.2",
|
|
38383
38683
|
mcpName: "io.github.chrischall/canvas-parent-mcp",
|
|
38384
38684
|
description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
|
|
38385
38685
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38423,9 +38723,9 @@ var package_default = {
|
|
|
38423
38723
|
"test:watch": "vitest"
|
|
38424
38724
|
},
|
|
38425
38725
|
dependencies: {
|
|
38426
|
-
"@chrischall/mcp-utils": "^0.
|
|
38427
|
-
"@fetchproxy/bootstrap": "^1.
|
|
38428
|
-
"@fetchproxy/server": "^1.
|
|
38726
|
+
"@chrischall/mcp-utils": "^0.14.0",
|
|
38727
|
+
"@fetchproxy/bootstrap": "^1.7.0",
|
|
38728
|
+
"@fetchproxy/server": "^1.7.0",
|
|
38429
38729
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38430
38730
|
dotenv: "^17.4.2",
|
|
38431
38731
|
zod: "^4.4.3"
|
|
@@ -38434,7 +38734,7 @@ var package_default = {
|
|
|
38434
38734
|
"@types/node": "^26.0.0",
|
|
38435
38735
|
"@vitest/coverage-v8": "^4.1.7",
|
|
38436
38736
|
esbuild: "^0.28.0",
|
|
38437
|
-
typescript: "^
|
|
38737
|
+
typescript: "^7.0.2",
|
|
38438
38738
|
vitest: "^4.1.7"
|
|
38439
38739
|
}
|
|
38440
38740
|
};
|
|
@@ -39355,7 +39655,7 @@ try {
|
|
|
39355
39655
|
}
|
|
39356
39656
|
await runMcp({
|
|
39357
39657
|
name: "canvas",
|
|
39358
|
-
version: "1.
|
|
39658
|
+
version: "1.2.2",
|
|
39359
39659
|
// x-release-please-version
|
|
39360
39660
|
tools: [
|
|
39361
39661
|
(server) => {
|