canvas-parent-mcp 1.2.0 → 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 +1 -1
- package/dist/bundle.js +229 -49
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/server.json +2 -2
|
@@ -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.2.
|
|
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.2.
|
|
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.2.
|
|
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",
|
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,
|
|
@@ -34990,7 +34987,8 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
34990
34987
|
"capture_redirect",
|
|
34991
34988
|
"read_indexed_db",
|
|
34992
34989
|
"read_dom",
|
|
34993
|
-
"download"
|
|
34990
|
+
"download",
|
|
34991
|
+
"graphql"
|
|
34994
34992
|
]);
|
|
34995
34993
|
|
|
34996
34994
|
// node_modules/@fetchproxy/protocol/dist/mcp-id.js
|
|
@@ -35293,6 +35291,7 @@ function assertIndexedDbScopesArray(value, label) {
|
|
|
35293
35291
|
}
|
|
35294
35292
|
var DOM_SELECTOR_RE = /^[^-]{1,512}$/;
|
|
35295
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}$/;
|
|
35296
35295
|
function assertDomSelectorsArray(value, label) {
|
|
35297
35296
|
if (!Array.isArray(value)) {
|
|
35298
35297
|
throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
|
|
@@ -35329,6 +35328,37 @@ function assertDomSelectorsArray(value, label) {
|
|
|
35329
35328
|
}
|
|
35330
35329
|
}
|
|
35331
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
|
+
}
|
|
35332
35362
|
function validateFrame(raw) {
|
|
35333
35363
|
assertObject(raw, "frame");
|
|
35334
35364
|
const t = raw.type;
|
|
@@ -35407,6 +35437,9 @@ function validateHello(raw) {
|
|
|
35407
35437
|
if (raw.domSelectors !== void 0) {
|
|
35408
35438
|
assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
|
|
35409
35439
|
}
|
|
35440
|
+
if (raw.graphqlOps !== void 0) {
|
|
35441
|
+
assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
|
|
35442
|
+
}
|
|
35410
35443
|
assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
|
|
35411
35444
|
assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
|
|
35412
35445
|
assertBase64(raw.sessionNonce, "hello.sessionNonce");
|
|
@@ -35656,6 +35689,28 @@ function validateInnerRequest(raw) {
|
|
|
35656
35689
|
}
|
|
35657
35690
|
return raw;
|
|
35658
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
|
+
}
|
|
35659
35714
|
if (raw.op === "download") {
|
|
35660
35715
|
assertObject(raw.init, "inner.init");
|
|
35661
35716
|
if (raw.init.url === void 0) {
|
|
@@ -35681,7 +35736,7 @@ function validateInnerRequest(raw) {
|
|
|
35681
35736
|
}
|
|
35682
35737
|
return raw;
|
|
35683
35738
|
}
|
|
35684
|
-
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"; 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)}`);
|
|
35685
35740
|
}
|
|
35686
35741
|
function assertNonEmptyKeyArray(value, label) {
|
|
35687
35742
|
if (!Array.isArray(value)) {
|
|
@@ -35712,6 +35767,10 @@ function assertStringMap(value, label) {
|
|
|
35712
35767
|
}
|
|
35713
35768
|
}
|
|
35714
35769
|
}
|
|
35770
|
+
var KNOWN_RESPONSE_OPS = /* @__PURE__ */ new Set([
|
|
35771
|
+
...KNOWN_CAPABILITIES,
|
|
35772
|
+
"graphql_query"
|
|
35773
|
+
]);
|
|
35715
35774
|
function validateInnerResponse(raw) {
|
|
35716
35775
|
assertPositiveInt(raw.id, "inner.id");
|
|
35717
35776
|
if (raw.ok === true) {
|
|
@@ -35773,6 +35832,13 @@ function validateInnerResponse(raw) {
|
|
|
35773
35832
|
assertStringMap(raw.values, "inner.values");
|
|
35774
35833
|
return raw;
|
|
35775
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
|
+
}
|
|
35776
35842
|
if (op === "download") {
|
|
35777
35843
|
assertObject(raw.value, "inner.value");
|
|
35778
35844
|
assertString(raw.value.path, "inner.value.path");
|
|
@@ -35797,7 +35863,7 @@ function validateInnerResponse(raw) {
|
|
|
35797
35863
|
if (raw.ok === false) {
|
|
35798
35864
|
assertString(raw.error, "inner.error");
|
|
35799
35865
|
if (raw.op !== void 0) {
|
|
35800
|
-
if (typeof raw.op !== "string" || !
|
|
35866
|
+
if (typeof raw.op !== "string" || !KNOWN_RESPONSE_OPS.has(raw.op)) {
|
|
35801
35867
|
throw new ProtocolError(`inner.op: unknown response op ${JSON.stringify(raw.op)}`);
|
|
35802
35868
|
}
|
|
35803
35869
|
}
|
|
@@ -35989,11 +36055,33 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner) {
|
|
|
35989
36055
|
};
|
|
35990
36056
|
}
|
|
35991
36057
|
async function openEncryptedFrame(sessionKey, frame) {
|
|
35992
|
-
const
|
|
35993
|
-
|
|
35994
|
-
|
|
35995
|
-
|
|
35996
|
-
|
|
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
|
+
}
|
|
35997
36085
|
}
|
|
35998
36086
|
|
|
35999
36087
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
@@ -36119,6 +36207,12 @@ async function buildServerHello(opts) {
|
|
|
36119
36207
|
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
36120
36208
|
}));
|
|
36121
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
|
+
}
|
|
36122
36216
|
return hello;
|
|
36123
36217
|
}
|
|
36124
36218
|
|
|
@@ -36209,7 +36303,8 @@ async function startHost(opts) {
|
|
|
36209
36303
|
indexedDbScopes: opts.ownIndexedDbScopes,
|
|
36210
36304
|
localStoragePointers: opts.ownLocalStoragePointers,
|
|
36211
36305
|
sessionStoragePointers: opts.ownSessionStoragePointers,
|
|
36212
|
-
domSelectors: opts.ownDomSelectors
|
|
36306
|
+
domSelectors: opts.ownDomSelectors,
|
|
36307
|
+
graphqlOps: opts.ownGraphqlOps
|
|
36213
36308
|
});
|
|
36214
36309
|
const ownSessionNonce = fromB64(ownHello.sessionNonce);
|
|
36215
36310
|
let extensionWs = null;
|
|
@@ -36446,7 +36541,8 @@ async function startPeer(opts) {
|
|
|
36446
36541
|
indexedDbScopes: opts.indexedDbScopes,
|
|
36447
36542
|
domSelectors: opts.domSelectors,
|
|
36448
36543
|
localStoragePointers: opts.localStoragePointers,
|
|
36449
|
-
sessionStoragePointers: opts.sessionStoragePointers
|
|
36544
|
+
sessionStoragePointers: opts.sessionStoragePointers,
|
|
36545
|
+
graphqlOps: opts.graphqlOps
|
|
36450
36546
|
});
|
|
36451
36547
|
const sessionNonce = fromB64(hello.sessionNonce);
|
|
36452
36548
|
ws.send(JSON.stringify(hello));
|
|
@@ -36490,10 +36586,20 @@ async function startPeer(opts) {
|
|
|
36490
36586
|
return;
|
|
36491
36587
|
if (!session.acceptInboundSeq(frame.seq))
|
|
36492
36588
|
return;
|
|
36493
|
-
|
|
36494
|
-
|
|
36495
|
-
innerListeners.forEach((cb) => cb(inner));
|
|
36496
|
-
}
|
|
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
|
+
}
|
|
36497
36603
|
}
|
|
36498
36604
|
}
|
|
36499
36605
|
} catch (e) {
|
|
@@ -36770,6 +36876,10 @@ var FetchproxyServer = class {
|
|
|
36770
36876
|
pendingIdb = /* @__PURE__ */ new Map();
|
|
36771
36877
|
// download awaiters resolve the saved-file metadata (path + size + mime).
|
|
36772
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();
|
|
36773
36883
|
mcpId = null;
|
|
36774
36884
|
identity = null;
|
|
36775
36885
|
// 0.5.3+: in-flight role-election / handle-start promise. Set the
|
|
@@ -36857,6 +36967,10 @@ var FetchproxyServer = class {
|
|
|
36857
36967
|
selector: d.selector,
|
|
36858
36968
|
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
36859
36969
|
})),
|
|
36970
|
+
graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
|
|
36971
|
+
name: d.name,
|
|
36972
|
+
operationName: d.operationName
|
|
36973
|
+
})),
|
|
36860
36974
|
// 0.8.0+: timer + lazy-revive default to ON. Every realty MCP
|
|
36861
36975
|
// adapter was about to set these to the same numbers anyway; the
|
|
36862
36976
|
// back-door is `0` (explicit opt-out) if a caller genuinely wants
|
|
@@ -36978,6 +37092,7 @@ var FetchproxyServer = class {
|
|
|
36978
37092
|
ownLocalStoragePointers: this.opts.localStoragePointers,
|
|
36979
37093
|
ownSessionStoragePointers: this.opts.sessionStoragePointers,
|
|
36980
37094
|
ownDomSelectors: this.opts.domSelectors,
|
|
37095
|
+
ownGraphqlOps: this.opts.graphqlOps,
|
|
36981
37096
|
onPairCode: this.opts.onPairCode
|
|
36982
37097
|
});
|
|
36983
37098
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
@@ -37006,7 +37121,8 @@ var FetchproxyServer = class {
|
|
|
37006
37121
|
indexedDbScopes: this.opts.indexedDbScopes,
|
|
37007
37122
|
localStoragePointers: this.opts.localStoragePointers,
|
|
37008
37123
|
sessionStoragePointers: this.opts.sessionStoragePointers,
|
|
37009
|
-
domSelectors: this.opts.domSelectors
|
|
37124
|
+
domSelectors: this.opts.domSelectors,
|
|
37125
|
+
graphqlOps: this.opts.graphqlOps
|
|
37010
37126
|
});
|
|
37011
37127
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
37012
37128
|
this.peerHandle.onRenegotiate(() => {
|
|
@@ -37212,6 +37328,7 @@ var FetchproxyServer = class {
|
|
|
37212
37328
|
this.pendingRedirect.delete(id);
|
|
37213
37329
|
this.pendingDownload.delete(id);
|
|
37214
37330
|
this.pendingIdb.delete(id);
|
|
37331
|
+
this.pendingGraphql.delete(id);
|
|
37215
37332
|
}
|
|
37216
37333
|
throw err;
|
|
37217
37334
|
}
|
|
@@ -38021,6 +38138,52 @@ var FetchproxyServer = class {
|
|
|
38021
38138
|
await this.sendInnerFrame(inner);
|
|
38022
38139
|
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
38023
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
|
+
}
|
|
38024
38187
|
assertScopeSubset(requested, declared, label) {
|
|
38025
38188
|
const undeclared = undeclaredKeys(requested, declared);
|
|
38026
38189
|
if (undeclared.length > 0) {
|
|
@@ -38158,6 +38321,20 @@ var FetchproxyServer = class {
|
|
|
38158
38321
|
}
|
|
38159
38322
|
return;
|
|
38160
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
|
+
}
|
|
38161
38338
|
const cookiesCb = this.pendingReadCookies.get(inner.id);
|
|
38162
38339
|
if (cookiesCb) {
|
|
38163
38340
|
this.pendingReadCookies.delete(inner.id);
|
|
@@ -38209,6 +38386,9 @@ var FetchproxyServer = class {
|
|
|
38209
38386
|
for (const { reject } of this.pendingDownload.values())
|
|
38210
38387
|
reject(err);
|
|
38211
38388
|
this.pendingDownload.clear();
|
|
38389
|
+
for (const { reject } of this.pendingGraphql.values())
|
|
38390
|
+
reject(err);
|
|
38391
|
+
this.pendingGraphql.clear();
|
|
38212
38392
|
}
|
|
38213
38393
|
/**
|
|
38214
38394
|
* 0.5.2+: read the current pair-pending pair code from whichever handle
|
|
@@ -38499,7 +38679,7 @@ function loadAccount(env = process.env) {
|
|
|
38499
38679
|
// package.json
|
|
38500
38680
|
var package_default = {
|
|
38501
38681
|
name: "canvas-parent-mcp",
|
|
38502
|
-
version: "1.2.
|
|
38682
|
+
version: "1.2.2",
|
|
38503
38683
|
mcpName: "io.github.chrischall/canvas-parent-mcp",
|
|
38504
38684
|
description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
|
|
38505
38685
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38543,9 +38723,9 @@ var package_default = {
|
|
|
38543
38723
|
"test:watch": "vitest"
|
|
38544
38724
|
},
|
|
38545
38725
|
dependencies: {
|
|
38546
|
-
"@chrischall/mcp-utils": "^0.
|
|
38547
|
-
"@fetchproxy/bootstrap": "^1.
|
|
38548
|
-
"@fetchproxy/server": "^1.
|
|
38726
|
+
"@chrischall/mcp-utils": "^0.14.0",
|
|
38727
|
+
"@fetchproxy/bootstrap": "^1.7.0",
|
|
38728
|
+
"@fetchproxy/server": "^1.7.0",
|
|
38549
38729
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38550
38730
|
dotenv: "^17.4.2",
|
|
38551
38731
|
zod: "^4.4.3"
|
|
@@ -39475,7 +39655,7 @@ try {
|
|
|
39475
39655
|
}
|
|
39476
39656
|
await runMcp({
|
|
39477
39657
|
name: "canvas",
|
|
39478
|
-
version: "1.2.
|
|
39658
|
+
version: "1.2.2",
|
|
39479
39659
|
// x-release-please-version
|
|
39480
39660
|
tools: [
|
|
39481
39661
|
(server) => {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvas-parent-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"mcpName": "io.github.chrischall/canvas-parent-mcp",
|
|
5
5
|
"description": "Canvas LMS (Instructure) MCP server — student/observer read access with optional file download",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"test:watch": "vitest"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chrischall/mcp-utils": "^0.
|
|
48
|
-
"@fetchproxy/bootstrap": "^1.
|
|
49
|
-
"@fetchproxy/server": "^1.
|
|
47
|
+
"@chrischall/mcp-utils": "^0.14.0",
|
|
48
|
+
"@fetchproxy/bootstrap": "^1.7.0",
|
|
49
|
+
"@fetchproxy/server": "^1.7.0",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
51
|
"dotenv": "^17.4.2",
|
|
52
52
|
"zod": "^4.4.3"
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/canvas-parent-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "canvas-parent-mcp",
|
|
14
|
-
"version": "1.2.
|
|
14
|
+
"version": "1.2.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|