canvas-parent-mcp 1.0.3 → 1.1.3
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/README.md +24 -0
- package/dist/bundle.js +373 -83
- package/dist/index.js +1 -1
- package/package.json +9 -9
- 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.
|
|
10
|
+
"version": "1.1.3"
|
|
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.1.3",
|
|
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.1.3",
|
|
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/README.md
CHANGED
|
@@ -108,3 +108,27 @@ This reuses the same SSO + OAuth endpoints the official Canvas Student/Parent ap
|
|
|
108
108
|
## Status
|
|
109
109
|
|
|
110
110
|
Unofficial — not affiliated with Instructure. AI-maintained.
|
|
111
|
+
|
|
112
|
+
## Acknowledgement of Terms
|
|
113
|
+
|
|
114
|
+
By using this MCP server, you acknowledge and agree to the following:
|
|
115
|
+
|
|
116
|
+
**1. This server accesses your own Canvas account via the official Canvas REST API.** Auth happens via your own personal access token, issued by your institution. It does not — and cannot — access anyone else's enrollments, grades, or messages.
|
|
117
|
+
|
|
118
|
+
**2. [Instructure's Canvas API Policy](https://www.instructure.com/policies/canvas-api-policy) governs your use of this server**, in addition to your institution's own acceptable-use policy. The clauses most relevant here:
|
|
119
|
+
|
|
120
|
+
> You may not use our APIs on behalf of any third-party… You may not use or access our APIs for competitive purposes… You may not interfere with our APIs, our systems, or other users… You may not circumvent any contractual usage limits.
|
|
121
|
+
|
|
122
|
+
On rate limits: *"limits are enforced per user access token… with dynamic throttling."* On data: *"Any user information retrieved through the API—including course enrollments, grades, and profile information—should be considered and treated as private information."*
|
|
123
|
+
|
|
124
|
+
You are agreeing to those terms — read by the maintainer 2026-05-23 — every time you invoke a tool in this server.
|
|
125
|
+
|
|
126
|
+
**3. Personal, observer/student/parent use only.** This project is not affiliated with, endorsed by, sponsored by, or in partnership with Instructure, Inc. or any school district. It is a personal automation tool for an authenticated Canvas user (typically a parent observer) to read their own (or their student's) enrollments, assignments, grades, and announcements. Do not use it to bulk-extract a district's course content, redistribute student data, or train AI models on student records.
|
|
127
|
+
|
|
128
|
+
**4. FERPA + your institution's AUP apply.** Student educational records are protected under the federal Family Educational Rights and Privacy Act (FERPA). Even though your token grants you lawful access, **how you store, redistribute, or feed that data into LLMs is regulated**. Treat any output (grades, assignments, comments, conversations) as confidential student data. Your institution's acceptable-use policy may add further restrictions on automated access — check before automating.
|
|
129
|
+
|
|
130
|
+
**5. Your token is yours alone.** **Do not commit `CANVAS_API_TOKEN` to git**, do not paste it in shared chats, and rotate it if it's ever exposed. A leaked token grants full Canvas access scoped to your user.
|
|
131
|
+
|
|
132
|
+
**6. You accept full responsibility** for any consequences of using this server in connection with your Canvas account — rate limiting (dynamic throttling kicks in well below documented limits when Canvas is under load), token revocation, account warnings, institution-admin investigations, or any enforcement action. If Instructure or your institution objects to your use, stop using this server.
|
|
133
|
+
|
|
134
|
+
This section is the maintainer's good-faith summary of the terms — it is not legal advice and does not modify or supersede Instructure's actual Canvas API Policy or your institution's policies.
|
package/dist/bundle.js
CHANGED
|
@@ -7572,6 +7572,10 @@ var require_receiver = __commonJS({
|
|
|
7572
7572
|
* extensions
|
|
7573
7573
|
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
|
|
7574
7574
|
* client or server mode
|
|
7575
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
7576
|
+
* buffered data chunks
|
|
7577
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
7578
|
+
* fragments
|
|
7575
7579
|
* @param {Number} [options.maxPayload=0] The maximum allowed message length
|
|
7576
7580
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
7577
7581
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -7582,6 +7586,8 @@ var require_receiver = __commonJS({
|
|
|
7582
7586
|
this._binaryType = options.binaryType || BINARY_TYPES[0];
|
|
7583
7587
|
this._extensions = options.extensions || {};
|
|
7584
7588
|
this._isServer = !!options.isServer;
|
|
7589
|
+
this._maxBufferedChunks = options.maxBufferedChunks | 0;
|
|
7590
|
+
this._maxFragments = options.maxFragments | 0;
|
|
7585
7591
|
this._maxPayload = options.maxPayload | 0;
|
|
7586
7592
|
this._skipUTF8Validation = !!options.skipUTF8Validation;
|
|
7587
7593
|
this[kWebSocket] = void 0;
|
|
@@ -7611,6 +7617,18 @@ var require_receiver = __commonJS({
|
|
|
7611
7617
|
*/
|
|
7612
7618
|
_write(chunk, encoding, cb) {
|
|
7613
7619
|
if (this._opcode === 8 && this._state == GET_INFO) return cb();
|
|
7620
|
+
if (this._maxBufferedChunks > 0 && this._buffers.length >= this._maxBufferedChunks) {
|
|
7621
|
+
cb(
|
|
7622
|
+
this.createError(
|
|
7623
|
+
RangeError,
|
|
7624
|
+
"Too many buffered chunks",
|
|
7625
|
+
false,
|
|
7626
|
+
1008,
|
|
7627
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
7628
|
+
)
|
|
7629
|
+
);
|
|
7630
|
+
return;
|
|
7631
|
+
}
|
|
7614
7632
|
this._bufferedBytes += chunk.length;
|
|
7615
7633
|
this._buffers.push(chunk);
|
|
7616
7634
|
this.startLoop(cb);
|
|
@@ -7940,6 +7958,17 @@ var require_receiver = __commonJS({
|
|
|
7940
7958
|
return;
|
|
7941
7959
|
}
|
|
7942
7960
|
if (data.length) {
|
|
7961
|
+
if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
|
|
7962
|
+
const error51 = this.createError(
|
|
7963
|
+
RangeError,
|
|
7964
|
+
"Too many message fragments",
|
|
7965
|
+
false,
|
|
7966
|
+
1008,
|
|
7967
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
7968
|
+
);
|
|
7969
|
+
cb(error51);
|
|
7970
|
+
return;
|
|
7971
|
+
}
|
|
7943
7972
|
this._messageLength = this._totalPayloadLength;
|
|
7944
7973
|
this._fragments.push(data);
|
|
7945
7974
|
}
|
|
@@ -7969,6 +7998,17 @@ var require_receiver = __commonJS({
|
|
|
7969
7998
|
cb(error51);
|
|
7970
7999
|
return;
|
|
7971
8000
|
}
|
|
8001
|
+
if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
|
|
8002
|
+
const error51 = this.createError(
|
|
8003
|
+
RangeError,
|
|
8004
|
+
"Too many message fragments",
|
|
8005
|
+
false,
|
|
8006
|
+
1008,
|
|
8007
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
8008
|
+
);
|
|
8009
|
+
cb(error51);
|
|
8010
|
+
return;
|
|
8011
|
+
}
|
|
7972
8012
|
this._fragments.push(buf);
|
|
7973
8013
|
}
|
|
7974
8014
|
this.dataMessage(cb);
|
|
@@ -9175,6 +9215,10 @@ var require_websocket = __commonJS({
|
|
|
9175
9215
|
* multiple times in the same tick
|
|
9176
9216
|
* @param {Function} [options.generateMask] The function used to generate the
|
|
9177
9217
|
* masking key
|
|
9218
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
9219
|
+
* buffered data chunks
|
|
9220
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
9221
|
+
* fragments
|
|
9178
9222
|
* @param {Number} [options.maxPayload=0] The maximum allowed message size
|
|
9179
9223
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
9180
9224
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -9186,6 +9230,8 @@ var require_websocket = __commonJS({
|
|
|
9186
9230
|
binaryType: this.binaryType,
|
|
9187
9231
|
extensions: this._extensions,
|
|
9188
9232
|
isServer: this._isServer,
|
|
9233
|
+
maxBufferedChunks: options.maxBufferedChunks,
|
|
9234
|
+
maxFragments: options.maxFragments,
|
|
9189
9235
|
maxPayload: options.maxPayload,
|
|
9190
9236
|
skipUTF8Validation: options.skipUTF8Validation
|
|
9191
9237
|
});
|
|
@@ -9485,6 +9531,8 @@ var require_websocket = __commonJS({
|
|
|
9485
9531
|
autoPong: true,
|
|
9486
9532
|
closeTimeout: CLOSE_TIMEOUT,
|
|
9487
9533
|
protocolVersion: protocolVersions[1],
|
|
9534
|
+
maxBufferedChunks: 1024 * 1024,
|
|
9535
|
+
maxFragments: 128 * 1024,
|
|
9488
9536
|
maxPayload: 100 * 1024 * 1024,
|
|
9489
9537
|
skipUTF8Validation: false,
|
|
9490
9538
|
perMessageDeflate: true,
|
|
@@ -9727,6 +9775,8 @@ var require_websocket = __commonJS({
|
|
|
9727
9775
|
websocket.setSocket(socket, head, {
|
|
9728
9776
|
allowSynchronousEvents: opts.allowSynchronousEvents,
|
|
9729
9777
|
generateMask: opts.generateMask,
|
|
9778
|
+
maxBufferedChunks: opts.maxBufferedChunks,
|
|
9779
|
+
maxFragments: opts.maxFragments,
|
|
9730
9780
|
maxPayload: opts.maxPayload,
|
|
9731
9781
|
skipUTF8Validation: opts.skipUTF8Validation
|
|
9732
9782
|
});
|
|
@@ -10069,6 +10119,10 @@ var require_websocket_server = __commonJS({
|
|
|
10069
10119
|
* called
|
|
10070
10120
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
10071
10121
|
* @param {String} [options.host] The hostname where to bind the server
|
|
10122
|
+
* @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
|
|
10123
|
+
* buffered data chunks
|
|
10124
|
+
* @param {Number} [options.maxFragments=131072] The maximum number of message
|
|
10125
|
+
* fragments
|
|
10072
10126
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
10073
10127
|
* size
|
|
10074
10128
|
* @param {Boolean} [options.noServer=false] Enable no server mode
|
|
@@ -10090,6 +10144,8 @@ var require_websocket_server = __commonJS({
|
|
|
10090
10144
|
options = {
|
|
10091
10145
|
allowSynchronousEvents: true,
|
|
10092
10146
|
autoPong: true,
|
|
10147
|
+
maxBufferedChunks: 1024 * 1024,
|
|
10148
|
+
maxFragments: 128 * 1024,
|
|
10093
10149
|
maxPayload: 100 * 1024 * 1024,
|
|
10094
10150
|
skipUTF8Validation: false,
|
|
10095
10151
|
perMessageDeflate: false,
|
|
@@ -10369,6 +10425,8 @@ var require_websocket_server = __commonJS({
|
|
|
10369
10425
|
socket.removeListener("error", socketOnError);
|
|
10370
10426
|
ws.setSocket(socket, head, {
|
|
10371
10427
|
allowSynchronousEvents: this.options.allowSynchronousEvents,
|
|
10428
|
+
maxBufferedChunks: this.options.maxBufferedChunks,
|
|
10429
|
+
maxFragments: this.options.maxFragments,
|
|
10372
10430
|
maxPayload: this.options.maxPayload,
|
|
10373
10431
|
skipUTF8Validation: this.options.skipUTF8Validation
|
|
10374
10432
|
});
|
|
@@ -34495,6 +34553,7 @@ var StdioServerTransport = class {
|
|
|
34495
34553
|
|
|
34496
34554
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
34497
34555
|
var PROTOCOL_VERSION = 2;
|
|
34556
|
+
var HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
|
|
34498
34557
|
var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
34499
34558
|
"fetch",
|
|
34500
34559
|
"read_cookies",
|
|
@@ -34569,7 +34628,7 @@ var ProtocolError = class extends Error {
|
|
|
34569
34628
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
34570
34629
|
var BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
34571
34630
|
var SCOPE_KEY_RE = /^[A-Za-z0-9_.\-]{1,256}$/;
|
|
34572
|
-
var SCOPE_KEY_GLOB_RE = /^[A-Za-z0-9_.\-]{1,
|
|
34631
|
+
var SCOPE_KEY_GLOB_RE = /^[A-Za-z0-9_.\-]{1,256}\*?$/;
|
|
34573
34632
|
var HEADER_NAME_RE = /^[A-Za-z0-9_\-]{1,128}$/;
|
|
34574
34633
|
var HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;
|
|
34575
34634
|
function assertObject(x, label) {
|
|
@@ -34698,7 +34757,7 @@ function assertStoragePointersArray(value, label, declaredKeys) {
|
|
|
34698
34757
|
if (entry.jsonPointer === void 0) {
|
|
34699
34758
|
throw new ProtocolError(`${label}[${i}].jsonPointer: missing`);
|
|
34700
34759
|
}
|
|
34701
|
-
if (typeof entry.key !== "string" || !
|
|
34760
|
+
if (typeof entry.key !== "string" || !SCOPE_KEY_RE.test(entry.key)) {
|
|
34702
34761
|
throw new ProtocolError(`${label}[${i}].key: invalid key ${JSON.stringify(entry.key)}`);
|
|
34703
34762
|
}
|
|
34704
34763
|
if (typeof entry.jsonPointer !== "string" || !isValidJsonPointer(entry.jsonPointer)) {
|
|
@@ -34797,6 +34856,8 @@ function validateFrame(raw) {
|
|
|
34797
34856
|
return validateReady(raw);
|
|
34798
34857
|
if (t === "frame")
|
|
34799
34858
|
return validateEncrypted(raw);
|
|
34859
|
+
if (t === "pair-pending")
|
|
34860
|
+
return validatePairPending(raw);
|
|
34800
34861
|
throw new ProtocolError(`unknown frame type: ${String(t)}`);
|
|
34801
34862
|
}
|
|
34802
34863
|
function validateHello(raw) {
|
|
@@ -34898,6 +34959,17 @@ function validateEncrypted(raw) {
|
|
|
34898
34959
|
assertBase64(raw.ciphertext, "frame.ciphertext");
|
|
34899
34960
|
return raw;
|
|
34900
34961
|
}
|
|
34962
|
+
var PAIR_CODE_RE = /^\d{3}-\d{3}$/;
|
|
34963
|
+
function validatePairPending(raw) {
|
|
34964
|
+
assertString(raw.mcpId, "pair-pending.mcpId");
|
|
34965
|
+
if (!isValidMcpId(raw.mcpId))
|
|
34966
|
+
throw new ProtocolError("pair-pending.mcpId: invalid format");
|
|
34967
|
+
assertString(raw.pairCode, "pair-pending.pairCode");
|
|
34968
|
+
if (!PAIR_CODE_RE.test(raw.pairCode)) {
|
|
34969
|
+
throw new ProtocolError(`pair-pending.pairCode: must match XXX-XXX, got ${String(raw.pairCode)}`);
|
|
34970
|
+
}
|
|
34971
|
+
return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
|
|
34972
|
+
}
|
|
34901
34973
|
function validateInnerFrame(raw) {
|
|
34902
34974
|
assertObject(raw, "inner");
|
|
34903
34975
|
const t = raw.type;
|
|
@@ -35480,15 +35552,22 @@ async function startHost(opts) {
|
|
|
35480
35552
|
let extensionWs = null;
|
|
35481
35553
|
const peers = /* @__PURE__ */ new Map();
|
|
35482
35554
|
const ownInnerListeners = [];
|
|
35555
|
+
const disconnectListeners = [];
|
|
35556
|
+
const pendingPairListeners = [];
|
|
35483
35557
|
let ownSession = null;
|
|
35558
|
+
let ownPendingPairCode = null;
|
|
35484
35559
|
let resolveOwnSession;
|
|
35485
35560
|
let rejectOwnSession;
|
|
35486
|
-
|
|
35487
|
-
|
|
35488
|
-
|
|
35489
|
-
|
|
35490
|
-
|
|
35491
|
-
|
|
35561
|
+
let ownSessionReady;
|
|
35562
|
+
function resetSessionPromise() {
|
|
35563
|
+
ownSessionReady = new Promise((resolve, reject) => {
|
|
35564
|
+
resolveOwnSession = resolve;
|
|
35565
|
+
rejectOwnSession = reject;
|
|
35566
|
+
});
|
|
35567
|
+
ownSessionReady.catch(() => {
|
|
35568
|
+
});
|
|
35569
|
+
}
|
|
35570
|
+
resetSessionPromise();
|
|
35492
35571
|
let extensionHello = null;
|
|
35493
35572
|
wss.on("connection", (ws) => {
|
|
35494
35573
|
let identified = null;
|
|
@@ -35557,11 +35636,12 @@ async function startHost(opts) {
|
|
|
35557
35636
|
}
|
|
35558
35637
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
35559
35638
|
const shared = await ecdhX25519(opts.ownIdentity.x25519Priv, extPub);
|
|
35560
|
-
const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(
|
|
35561
|
-
if (
|
|
35562
|
-
|
|
35563
|
-
|
|
35564
|
-
|
|
35639
|
+
const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(HKDF_SESSION_INFO), 32);
|
|
35640
|
+
if (extensionWs !== ws)
|
|
35641
|
+
return;
|
|
35642
|
+
ownSession = new SessionState(key);
|
|
35643
|
+
ownPendingPairCode = null;
|
|
35644
|
+
resolveOwnSession(ownSession);
|
|
35565
35645
|
} else {
|
|
35566
35646
|
const slot = peers.get(frame.mcpId);
|
|
35567
35647
|
if (slot)
|
|
@@ -35588,6 +35668,16 @@ async function startHost(opts) {
|
|
|
35588
35668
|
extensionWs.send(JSON.stringify(frame));
|
|
35589
35669
|
}
|
|
35590
35670
|
}
|
|
35671
|
+
if (frame.type === "pair-pending" && identified === "extension") {
|
|
35672
|
+
if (frame.mcpId === opts.ownMcpId) {
|
|
35673
|
+
ownPendingPairCode = frame.pairCode;
|
|
35674
|
+
pendingPairListeners.forEach((cb) => cb(frame.pairCode));
|
|
35675
|
+
} else {
|
|
35676
|
+
const slot = peers.get(frame.mcpId);
|
|
35677
|
+
if (slot)
|
|
35678
|
+
slot.ws.send(JSON.stringify(frame));
|
|
35679
|
+
}
|
|
35680
|
+
}
|
|
35591
35681
|
} catch (e) {
|
|
35592
35682
|
console.error("[fetchproxy] host: message handler error:", e);
|
|
35593
35683
|
try {
|
|
@@ -35603,6 +35693,9 @@ async function startHost(opts) {
|
|
|
35603
35693
|
if (!ownSession) {
|
|
35604
35694
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
35605
35695
|
}
|
|
35696
|
+
ownSession = null;
|
|
35697
|
+
resetSessionPromise();
|
|
35698
|
+
disconnectListeners.forEach((cb) => cb());
|
|
35606
35699
|
}
|
|
35607
35700
|
if (identified === "peer" && peerMcpId)
|
|
35608
35701
|
peers.delete(peerMcpId);
|
|
@@ -35627,7 +35720,14 @@ async function startHost(opts) {
|
|
|
35627
35720
|
},
|
|
35628
35721
|
onOwnInner: (cb) => {
|
|
35629
35722
|
ownInnerListeners.push(cb);
|
|
35630
|
-
}
|
|
35723
|
+
},
|
|
35724
|
+
onExtensionDisconnect: (cb) => {
|
|
35725
|
+
disconnectListeners.push(cb);
|
|
35726
|
+
},
|
|
35727
|
+
onPendingPair: (cb) => {
|
|
35728
|
+
pendingPairListeners.push(cb);
|
|
35729
|
+
},
|
|
35730
|
+
pendingPairCode: () => ownPendingPairCode
|
|
35631
35731
|
};
|
|
35632
35732
|
}
|
|
35633
35733
|
|
|
@@ -35657,36 +35757,57 @@ async function startPeer(opts) {
|
|
|
35657
35757
|
const sessionNonce = fromB64(hello.sessionNonce);
|
|
35658
35758
|
ws.send(JSON.stringify(hello));
|
|
35659
35759
|
const innerListeners = [];
|
|
35760
|
+
const renegotiateListeners = [];
|
|
35761
|
+
const pendingPairListeners = [];
|
|
35660
35762
|
let session = null;
|
|
35763
|
+
let pendingPairCode = null;
|
|
35764
|
+
let resolveFirstReady;
|
|
35765
|
+
let rejectFirstReady;
|
|
35661
35766
|
const sessionPromise = new Promise((resolve, reject) => {
|
|
35662
|
-
|
|
35663
|
-
|
|
35664
|
-
|
|
35665
|
-
|
|
35666
|
-
|
|
35667
|
-
|
|
35668
|
-
|
|
35669
|
-
|
|
35670
|
-
|
|
35671
|
-
|
|
35672
|
-
|
|
35767
|
+
resolveFirstReady = resolve;
|
|
35768
|
+
rejectFirstReady = reject;
|
|
35769
|
+
});
|
|
35770
|
+
const onMessage = async (data) => {
|
|
35771
|
+
try {
|
|
35772
|
+
const raw = JSON.parse(data.toString());
|
|
35773
|
+
const frame = validateFrame(raw);
|
|
35774
|
+
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
35775
|
+
const extPub = fromB64(frame.extensionSessionPub);
|
|
35776
|
+
const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
|
|
35777
|
+
const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
|
|
35778
|
+
const isRenegotiation = session !== null;
|
|
35779
|
+
session = new SessionState(sessionKey);
|
|
35780
|
+
pendingPairCode = null;
|
|
35781
|
+
if (isRenegotiation) {
|
|
35782
|
+
renegotiateListeners.forEach((cb) => cb());
|
|
35783
|
+
} else {
|
|
35784
|
+
resolveFirstReady(session);
|
|
35673
35785
|
}
|
|
35674
|
-
|
|
35675
|
-
|
|
35676
|
-
|
|
35677
|
-
|
|
35678
|
-
|
|
35786
|
+
return;
|
|
35787
|
+
}
|
|
35788
|
+
if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
|
|
35789
|
+
pendingPairCode = frame.pairCode;
|
|
35790
|
+
pendingPairListeners.forEach((cb) => cb(frame.pairCode));
|
|
35791
|
+
return;
|
|
35792
|
+
}
|
|
35793
|
+
if (frame.type === "frame" && frame.mcpId === opts.mcpId) {
|
|
35794
|
+
if (!session)
|
|
35795
|
+
return;
|
|
35796
|
+
if (!session.acceptInboundSeq(frame.seq))
|
|
35797
|
+
return;
|
|
35798
|
+
try {
|
|
35679
35799
|
const inner = await openEncryptedFrame(session.sessionKey, frame);
|
|
35680
35800
|
innerListeners.forEach((cb) => cb(inner));
|
|
35801
|
+
} catch {
|
|
35681
35802
|
}
|
|
35682
|
-
} catch (e) {
|
|
35683
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
35684
35803
|
}
|
|
35685
|
-
}
|
|
35686
|
-
|
|
35687
|
-
|
|
35688
|
-
|
|
35689
|
-
|
|
35804
|
+
} catch (e) {
|
|
35805
|
+
rejectFirstReady(e instanceof Error ? e : new Error(String(e)));
|
|
35806
|
+
}
|
|
35807
|
+
};
|
|
35808
|
+
ws.on("message", onMessage);
|
|
35809
|
+
ws.once("close", () => {
|
|
35810
|
+
rejectFirstReady(new Error("peer WS closed before ready"));
|
|
35690
35811
|
});
|
|
35691
35812
|
sessionPromise.catch(() => {
|
|
35692
35813
|
});
|
|
@@ -35694,13 +35815,21 @@ async function startPeer(opts) {
|
|
|
35694
35815
|
ws,
|
|
35695
35816
|
session: sessionPromise,
|
|
35696
35817
|
sendInner: async (inner) => {
|
|
35697
|
-
|
|
35818
|
+
await sessionPromise;
|
|
35819
|
+
const s = session;
|
|
35698
35820
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), inner);
|
|
35699
35821
|
ws.send(JSON.stringify(sealed));
|
|
35700
35822
|
},
|
|
35701
35823
|
onInner: (cb) => {
|
|
35702
35824
|
innerListeners.push(cb);
|
|
35703
35825
|
},
|
|
35826
|
+
onRenegotiate: (cb) => {
|
|
35827
|
+
renegotiateListeners.push(cb);
|
|
35828
|
+
},
|
|
35829
|
+
onPendingPair: (cb) => {
|
|
35830
|
+
pendingPairListeners.push(cb);
|
|
35831
|
+
},
|
|
35832
|
+
pendingPairCode: () => pendingPairCode,
|
|
35704
35833
|
close: () => ws.close()
|
|
35705
35834
|
};
|
|
35706
35835
|
return handle;
|
|
@@ -35757,6 +35886,32 @@ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
|
35757
35886
|
return id;
|
|
35758
35887
|
}
|
|
35759
35888
|
|
|
35889
|
+
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
35890
|
+
function classifyFetchError(error51) {
|
|
35891
|
+
if (/Could not establish connection/i.test(error51) || /Receiving end does not exist/i.test(error51)) {
|
|
35892
|
+
return "content_script_unreachable";
|
|
35893
|
+
}
|
|
35894
|
+
if (/^tab fetch failed:/.test(error51)) {
|
|
35895
|
+
return "tab_fetch_failed";
|
|
35896
|
+
}
|
|
35897
|
+
if (/^fetch threw:/.test(error51)) {
|
|
35898
|
+
return "tab_fetch_failed";
|
|
35899
|
+
}
|
|
35900
|
+
if (/^no tab matching /.test(error51)) {
|
|
35901
|
+
return "no_tab";
|
|
35902
|
+
}
|
|
35903
|
+
if (/not in domains \[/.test(error51)) {
|
|
35904
|
+
return "domain_denied";
|
|
35905
|
+
}
|
|
35906
|
+
if (/^capability .+ not granted/.test(error51)) {
|
|
35907
|
+
return "capability_denied";
|
|
35908
|
+
}
|
|
35909
|
+
if (/^(request|response) body too large:/.test(error51)) {
|
|
35910
|
+
return "body_too_large";
|
|
35911
|
+
}
|
|
35912
|
+
return "other";
|
|
35913
|
+
}
|
|
35914
|
+
|
|
35760
35915
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
35761
35916
|
var FetchproxyProtocolError = class extends Error {
|
|
35762
35917
|
constructor(message) {
|
|
@@ -35799,7 +35954,11 @@ function assertUrlInDomains(field, url2, domains) {
|
|
|
35799
35954
|
}
|
|
35800
35955
|
var DEFAULT_JSON_OK_STATUSES = [200, 201, 202, 204];
|
|
35801
35956
|
var FetchproxyServer = class {
|
|
35802
|
-
/**
|
|
35957
|
+
/**
|
|
35958
|
+
* Bridge role. `null` until the first verb call (or an explicit
|
|
35959
|
+
* `connect()`) — `listen()` no longer triggers the role election
|
|
35960
|
+
* as of 0.5.3+. Reset to `null` on `close()`.
|
|
35961
|
+
*/
|
|
35803
35962
|
role = null;
|
|
35804
35963
|
opts;
|
|
35805
35964
|
hostHandle = null;
|
|
@@ -35821,6 +35980,12 @@ var FetchproxyServer = class {
|
|
|
35821
35980
|
pendingIdb = /* @__PURE__ */ new Map();
|
|
35822
35981
|
mcpId = null;
|
|
35823
35982
|
identity = null;
|
|
35983
|
+
// 0.5.3+: in-flight role-election / handle-start promise. Set the
|
|
35984
|
+
// first time a verb call runs `ensureConnected`, awaited by concurrent
|
|
35985
|
+
// callers, cleared once the connection is up. Single source of truth
|
|
35986
|
+
// for "we're connecting right now" so two parallel first-calls don't
|
|
35987
|
+
// race the port bind.
|
|
35988
|
+
connectingPromise = null;
|
|
35824
35989
|
constructor(opts) {
|
|
35825
35990
|
if (!Array.isArray(opts.domains) || opts.domains.length === 0) {
|
|
35826
35991
|
throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
|
|
@@ -35872,23 +36037,87 @@ var FetchproxyServer = class {
|
|
|
35872
36037
|
};
|
|
35873
36038
|
}
|
|
35874
36039
|
/**
|
|
35875
|
-
*
|
|
35876
|
-
* from disk (creating it on first call)
|
|
35877
|
-
*
|
|
35878
|
-
*
|
|
35879
|
-
* `
|
|
35880
|
-
*
|
|
36040
|
+
* Prepare the bridge for use. Loads the long-term identity keypair
|
|
36041
|
+
* from disk (creating it on first call) and computes this instance's
|
|
36042
|
+
* `mcpId`. Does NOT bind the bridge port or dial any WebSocket — the
|
|
36043
|
+
* connection is established lazily on the first verb call (see
|
|
36044
|
+
* `ensureConnected` / `getOrConnect`).
|
|
36045
|
+
*
|
|
36046
|
+
* Pre-0.5.3 behavior: `listen()` also did role election and started
|
|
36047
|
+
* the host/peer immediately, which meant every configured-but-unused
|
|
36048
|
+
* MCP claimed bridge resources at MCP-client boot. Several MCPs
|
|
36049
|
+
* starting in parallel under Claude Desktop also produced noisy
|
|
36050
|
+
* `ERR_CONNECTION_REFUSED` errors in the extension if it raced ahead
|
|
36051
|
+
* of the first MCP's port bind. Deferring keeps boot quiet and
|
|
36052
|
+
* leaves the port unowned until something actually needs it.
|
|
36053
|
+
*
|
|
36054
|
+
* Calling `listen()` twice without an intervening `close()` is a
|
|
36055
|
+
* no-op (the second call's identity load is idempotent).
|
|
35881
36056
|
*/
|
|
35882
36057
|
async listen() {
|
|
35883
|
-
|
|
35884
|
-
|
|
36058
|
+
if (!this.identity) {
|
|
36059
|
+
this.identity = await loadOrCreateIdentity(this.opts.serverName, this.opts.identityDir);
|
|
36060
|
+
}
|
|
36061
|
+
if (!this.mcpId) {
|
|
36062
|
+
this.mcpId = generateMcpId(this.opts.serverName, this.opts.version);
|
|
36063
|
+
}
|
|
36064
|
+
}
|
|
36065
|
+
/**
|
|
36066
|
+
* Force an eager bridge connection (role-election + host/peer handle
|
|
36067
|
+
* start + listener wiring) without waiting for the first verb call.
|
|
36068
|
+
* Useful for callers that want to surface the role / connection
|
|
36069
|
+
* outcome at boot, or for tests whose harness dials a mock extension
|
|
36070
|
+
* immediately after server construction. Production MCPs that just
|
|
36071
|
+
* answer tool calls should NOT call this — the lazy connect via
|
|
36072
|
+
* `ensureConnected` will do the right thing on first use, keeping
|
|
36073
|
+
* boot cheap and avoiding port-bind contention for MCPs that never
|
|
36074
|
+
* actually get invoked.
|
|
36075
|
+
*
|
|
36076
|
+
* Idempotent: a second call after the first has resolved is a no-op
|
|
36077
|
+
* (the existing handle is reused). Throws if `listen()` was never
|
|
36078
|
+
* called.
|
|
36079
|
+
*/
|
|
36080
|
+
async connect() {
|
|
36081
|
+
await this.ensureConnected();
|
|
36082
|
+
}
|
|
36083
|
+
/**
|
|
36084
|
+
* Establish the bridge connection (role-election + host/peer handle
|
|
36085
|
+
* start + listener wiring) the first time a verb is invoked.
|
|
36086
|
+
* Idempotent after the connection is up; concurrent first-callers
|
|
36087
|
+
* share the same in-flight promise so only one election happens.
|
|
36088
|
+
*
|
|
36089
|
+
* Throws if `listen()` was never called — the contract is that the
|
|
36090
|
+
* MCP author still must wire `transport.start()` at boot to load
|
|
36091
|
+
* identity / set mcpId, even though the WS doesn't open until a
|
|
36092
|
+
* verb runs.
|
|
36093
|
+
*/
|
|
36094
|
+
async ensureConnected() {
|
|
36095
|
+
if (this.hostHandle || this.peerHandle)
|
|
36096
|
+
return;
|
|
36097
|
+
if (this.connectingPromise) {
|
|
36098
|
+
await this.connectingPromise;
|
|
36099
|
+
return;
|
|
36100
|
+
}
|
|
36101
|
+
if (!this.identity || !this.mcpId) {
|
|
36102
|
+
throw new Error("FetchproxyServer: ensureConnected called before listen() \u2014 call listen() at MCP boot to load identity");
|
|
36103
|
+
}
|
|
36104
|
+
this.connectingPromise = this.doConnect();
|
|
36105
|
+
try {
|
|
36106
|
+
await this.connectingPromise;
|
|
36107
|
+
} finally {
|
|
36108
|
+
this.connectingPromise = null;
|
|
36109
|
+
}
|
|
36110
|
+
}
|
|
36111
|
+
async doConnect() {
|
|
36112
|
+
const identity = this.identity;
|
|
36113
|
+
const mcpId = this.mcpId;
|
|
35885
36114
|
const el = await electRole({ host: this.opts.host, port: this.opts.port });
|
|
35886
36115
|
if (el.role === "host") {
|
|
35887
36116
|
this.role = "host";
|
|
35888
36117
|
this.hostHandle = await startHost({
|
|
35889
36118
|
httpServer: el.server,
|
|
35890
|
-
ownIdentity:
|
|
35891
|
-
ownMcpId:
|
|
36119
|
+
ownIdentity: identity,
|
|
36120
|
+
ownMcpId: mcpId,
|
|
35892
36121
|
ownServerName: this.opts.serverName,
|
|
35893
36122
|
ownVersion: this.opts.version,
|
|
35894
36123
|
ownDomains: this.opts.domains,
|
|
@@ -35903,13 +36132,17 @@ var FetchproxyServer = class {
|
|
|
35903
36132
|
onPairCode: this.opts.onPairCode
|
|
35904
36133
|
});
|
|
35905
36134
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
36135
|
+
this.hostHandle.onExtensionDisconnect(() => this.rejectAllPending());
|
|
36136
|
+
this.hostHandle.onPendingPair((code) => {
|
|
36137
|
+
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
36138
|
+
});
|
|
35906
36139
|
} else {
|
|
35907
36140
|
this.role = "peer";
|
|
35908
36141
|
this.peerHandle = await startPeer({
|
|
35909
36142
|
host: this.opts.host,
|
|
35910
36143
|
port: this.opts.port,
|
|
35911
|
-
identity
|
|
35912
|
-
mcpId
|
|
36144
|
+
identity,
|
|
36145
|
+
mcpId,
|
|
35913
36146
|
serverName: this.opts.serverName,
|
|
35914
36147
|
version: this.opts.version,
|
|
35915
36148
|
domains: this.opts.domains,
|
|
@@ -35923,8 +36156,19 @@ var FetchproxyServer = class {
|
|
|
35923
36156
|
sessionStoragePointers: this.opts.sessionStoragePointers
|
|
35924
36157
|
});
|
|
35925
36158
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
36159
|
+
this.peerHandle.onRenegotiate(() => this.rejectAllPending());
|
|
36160
|
+
this.peerHandle.onPendingPair((code) => {
|
|
36161
|
+
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
36162
|
+
});
|
|
36163
|
+
if (this.opts.onPairCode) {
|
|
36164
|
+
const cb = this.opts.onPairCode;
|
|
36165
|
+
this.peerHandle.onPendingPair((code) => cb(code));
|
|
36166
|
+
}
|
|
35926
36167
|
}
|
|
35927
36168
|
}
|
|
36169
|
+
pairingErrorMessage(code) {
|
|
36170
|
+
return `fetchproxy: pairing required for ${this.opts.serverName} \u2014 open the fetchproxy extension popup in Chrome and approve the pair request. Verify the pair code matches: ${code}`;
|
|
36171
|
+
}
|
|
35928
36172
|
/**
|
|
35929
36173
|
* Raw single-shot fetch through the bridge. Most callers should prefer
|
|
35930
36174
|
* the verb shortcuts (`get` / `post` / `getJson` / `postJson` / `getHtml`)
|
|
@@ -35940,8 +36184,11 @@ var FetchproxyServer = class {
|
|
|
35940
36184
|
* offline, etc.).
|
|
35941
36185
|
*/
|
|
35942
36186
|
async fetch(init) {
|
|
35943
|
-
|
|
35944
|
-
|
|
36187
|
+
await this.ensureConnected();
|
|
36188
|
+
const pendingCode = this.currentPendingPairCode();
|
|
36189
|
+
if (pendingCode !== null) {
|
|
36190
|
+
const error51 = this.pairingErrorMessage(pendingCode);
|
|
36191
|
+
return { ok: false, error: error51, kind: classifyFetchError(error51) };
|
|
35945
36192
|
}
|
|
35946
36193
|
const id = this.nextRequestId++;
|
|
35947
36194
|
const inner = { type: "request", id, op: "fetch", init };
|
|
@@ -36082,9 +36329,8 @@ var FetchproxyServer = class {
|
|
|
36082
36329
|
if (!this.opts.capabilities.includes("read_cookies")) {
|
|
36083
36330
|
throw new Error('FetchproxyServer.readCookies(): MCP did not declare "read_cookies" in capabilities \u2014 add it to FetchproxyServerOpts.capabilities to enable this verb');
|
|
36084
36331
|
}
|
|
36085
|
-
|
|
36086
|
-
|
|
36087
|
-
}
|
|
36332
|
+
await this.ensureConnected();
|
|
36333
|
+
this.throwIfPendingPair();
|
|
36088
36334
|
if (opts.subdomain !== void 0)
|
|
36089
36335
|
assertSubdomainLabel(opts.subdomain);
|
|
36090
36336
|
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
@@ -36141,9 +36387,8 @@ var FetchproxyServer = class {
|
|
|
36141
36387
|
if (!this.opts.capabilities.includes(op)) {
|
|
36142
36388
|
throw new Error(`FetchproxyServer.${op === "read_local_storage" ? "readLocalStorage" : "readSessionStorage"}(): MCP did not declare ${JSON.stringify(op)} in capabilities`);
|
|
36143
36389
|
}
|
|
36144
|
-
|
|
36145
|
-
|
|
36146
|
-
}
|
|
36390
|
+
await this.ensureConnected();
|
|
36391
|
+
this.throwIfPendingPair();
|
|
36147
36392
|
if (!Array.isArray(opts.keys) || opts.keys.length === 0) {
|
|
36148
36393
|
throw new Error(`FetchproxyServer.${op}: opts.keys must be a non-empty array`);
|
|
36149
36394
|
}
|
|
@@ -36198,9 +36443,8 @@ var FetchproxyServer = class {
|
|
|
36198
36443
|
if (!this.opts.capabilities.includes("capture_request_header")) {
|
|
36199
36444
|
throw new Error('FetchproxyServer.captureRequestHeader(): MCP did not declare "capture_request_header" in capabilities');
|
|
36200
36445
|
}
|
|
36201
|
-
|
|
36202
|
-
|
|
36203
|
-
}
|
|
36446
|
+
await this.ensureConnected();
|
|
36447
|
+
this.throwIfPendingPair();
|
|
36204
36448
|
const declared = this.opts.captureHeaders.find((d) => d.urlPattern === opts.urlPattern && d.headerName === opts.headerName);
|
|
36205
36449
|
if (!declared) {
|
|
36206
36450
|
throw new Error(`FetchproxyServer.captureRequestHeader: (urlPattern=${JSON.stringify(opts.urlPattern)}, headerName=${JSON.stringify(opts.headerName)}) not declared in captureHeaders`);
|
|
@@ -36241,9 +36485,8 @@ var FetchproxyServer = class {
|
|
|
36241
36485
|
if (!this.opts.capabilities.includes("read_indexed_db")) {
|
|
36242
36486
|
throw new Error('FetchproxyServer.readIndexedDb(): MCP did not declare "read_indexed_db" in capabilities');
|
|
36243
36487
|
}
|
|
36244
|
-
|
|
36245
|
-
|
|
36246
|
-
}
|
|
36488
|
+
await this.ensureConnected();
|
|
36489
|
+
this.throwIfPendingPair();
|
|
36247
36490
|
if (!Array.isArray(opts.keys) || opts.keys.length === 0) {
|
|
36248
36491
|
throw new Error("FetchproxyServer.readIndexedDb: opts.keys must be a non-empty array");
|
|
36249
36492
|
}
|
|
@@ -36321,10 +36564,11 @@ var FetchproxyServer = class {
|
|
|
36321
36564
|
if (inner.op === void 0 || inner.op === "fetch") {
|
|
36322
36565
|
fetchCb({ ok: true, status: inner.status, url: inner.url, body: inner.body });
|
|
36323
36566
|
} else {
|
|
36324
|
-
|
|
36567
|
+
const error51 = `unexpected ${inner.op} response on fetch awaiter`;
|
|
36568
|
+
fetchCb({ ok: false, error: error51, kind: classifyFetchError(error51) });
|
|
36325
36569
|
}
|
|
36326
36570
|
} else {
|
|
36327
|
-
fetchCb({ ok: false, error: inner.error });
|
|
36571
|
+
fetchCb({ ok: false, error: inner.error, kind: classifyFetchError(inner.error) });
|
|
36328
36572
|
}
|
|
36329
36573
|
return;
|
|
36330
36574
|
}
|
|
@@ -36391,6 +36635,52 @@ var FetchproxyServer = class {
|
|
|
36391
36635
|
}
|
|
36392
36636
|
}
|
|
36393
36637
|
}
|
|
36638
|
+
rejectAllPending(reason = "extension disconnected") {
|
|
36639
|
+
const err = new FetchproxyProtocolError(reason);
|
|
36640
|
+
for (const cb of this.pending.values()) {
|
|
36641
|
+
cb({ ok: false, error: err.message, kind: classifyFetchError(err.message) });
|
|
36642
|
+
}
|
|
36643
|
+
this.pending.clear();
|
|
36644
|
+
for (const cb of this.pendingReadCookies.values()) {
|
|
36645
|
+
cb({ ok: false, error: err.message });
|
|
36646
|
+
}
|
|
36647
|
+
this.pendingReadCookies.clear();
|
|
36648
|
+
for (const { reject } of this.pendingStorage.values())
|
|
36649
|
+
reject(err);
|
|
36650
|
+
this.pendingStorage.clear();
|
|
36651
|
+
for (const { reject } of this.pendingCapture.values())
|
|
36652
|
+
reject(err);
|
|
36653
|
+
this.pendingCapture.clear();
|
|
36654
|
+
for (const { reject } of this.pendingIdb.values())
|
|
36655
|
+
reject(err);
|
|
36656
|
+
this.pendingIdb.clear();
|
|
36657
|
+
}
|
|
36658
|
+
/**
|
|
36659
|
+
* 0.5.2+: read the current pair-pending pair code from whichever handle
|
|
36660
|
+
* is active, returning null when none is pending. Public verbs call this
|
|
36661
|
+
* at the top so that a tool invoked while the bridge is waiting on user
|
|
36662
|
+
* approval fails fast with the actionable error rather than hanging on a
|
|
36663
|
+
* sealed frame the extension will never process.
|
|
36664
|
+
*/
|
|
36665
|
+
currentPendingPairCode() {
|
|
36666
|
+
if (this.hostHandle)
|
|
36667
|
+
return this.hostHandle.pendingPairCode();
|
|
36668
|
+
if (this.peerHandle)
|
|
36669
|
+
return this.peerHandle.pendingPairCode();
|
|
36670
|
+
return null;
|
|
36671
|
+
}
|
|
36672
|
+
/**
|
|
36673
|
+
* 0.5.2+: throw `FetchproxyProtocolError` with the actionable pair-code
|
|
36674
|
+
* message if the bridge is waiting on user approval. Used by the verb
|
|
36675
|
+
* methods (readCookies, readLocalStorage, etc.) that surface errors via
|
|
36676
|
+
* thrown exceptions rather than `ok:false` discriminated unions.
|
|
36677
|
+
*/
|
|
36678
|
+
throwIfPendingPair() {
|
|
36679
|
+
const code = this.currentPendingPairCode();
|
|
36680
|
+
if (code !== null) {
|
|
36681
|
+
throw new FetchproxyProtocolError(this.pairingErrorMessage(code));
|
|
36682
|
+
}
|
|
36683
|
+
}
|
|
36394
36684
|
/**
|
|
36395
36685
|
* Shut down the bridge. Host: terminates the WebSocket server and any
|
|
36396
36686
|
* still-attached extension/peer clients. Peer: closes the upstream
|
|
@@ -36398,6 +36688,10 @@ var FetchproxyServer = class {
|
|
|
36398
36688
|
* twice in a row.
|
|
36399
36689
|
*/
|
|
36400
36690
|
async close() {
|
|
36691
|
+
this.rejectAllPending();
|
|
36692
|
+
if (this.connectingPromise) {
|
|
36693
|
+
await this.connectingPromise.catch(() => void 0);
|
|
36694
|
+
}
|
|
36401
36695
|
if (this.hostHandle)
|
|
36402
36696
|
await this.hostHandle.close();
|
|
36403
36697
|
if (this.peerHandle)
|
|
@@ -36405,6 +36699,7 @@ var FetchproxyServer = class {
|
|
|
36405
36699
|
this.hostHandle = null;
|
|
36406
36700
|
this.peerHandle = null;
|
|
36407
36701
|
this.role = null;
|
|
36702
|
+
this.connectingPromise = null;
|
|
36408
36703
|
}
|
|
36409
36704
|
};
|
|
36410
36705
|
|
|
@@ -36493,8 +36788,7 @@ async function bootstrap(opts) {
|
|
|
36493
36788
|
for (const p of localStoragePointers) {
|
|
36494
36789
|
pointers[p.outputKey] = { storageKey: p.storageKey, jsonPointer: p.jsonPointer };
|
|
36495
36790
|
}
|
|
36496
|
-
|
|
36497
|
-
localStorage = await stub.readLocalStorage({
|
|
36791
|
+
localStorage = await server2.readLocalStorage({
|
|
36498
36792
|
keys: allKeys,
|
|
36499
36793
|
...storageDomainOpts,
|
|
36500
36794
|
...localStoragePointers.length > 0 ? { pointers } : {}
|
|
@@ -36507,8 +36801,7 @@ async function bootstrap(opts) {
|
|
|
36507
36801
|
for (const p of sessionStoragePointers) {
|
|
36508
36802
|
pointers[p.outputKey] = { storageKey: p.storageKey, jsonPointer: p.jsonPointer };
|
|
36509
36803
|
}
|
|
36510
|
-
|
|
36511
|
-
sessionStorage = await stub.readSessionStorage({
|
|
36804
|
+
sessionStorage = await server2.readSessionStorage({
|
|
36512
36805
|
keys: allKeys,
|
|
36513
36806
|
...storageDomainOpts,
|
|
36514
36807
|
...sessionStoragePointers.length > 0 ? { pointers } : {}
|
|
@@ -36531,9 +36824,6 @@ async function bootstrap(opts) {
|
|
|
36531
36824
|
}
|
|
36532
36825
|
const indexedDbBucket = {};
|
|
36533
36826
|
for (const d of indexedDb) {
|
|
36534
|
-
if (!server2.readIndexedDb) {
|
|
36535
|
-
throw new Error("bootstrap: server factory does not implement readIndexedDb (declared indexedDb but server stub omits it)");
|
|
36536
|
-
}
|
|
36537
36827
|
const values = await server2.readIndexedDb({
|
|
36538
36828
|
database: d.database,
|
|
36539
36829
|
store: d.store,
|
|
@@ -36658,7 +36948,7 @@ function loadAccount(env = process.env) {
|
|
|
36658
36948
|
// package.json
|
|
36659
36949
|
var package_default = {
|
|
36660
36950
|
name: "canvas-parent-mcp",
|
|
36661
|
-
version: "1.
|
|
36951
|
+
version: "1.1.3",
|
|
36662
36952
|
mcpName: "io.github.chrischall/canvas-parent-mcp",
|
|
36663
36953
|
description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
|
|
36664
36954
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -36702,17 +36992,17 @@ var package_default = {
|
|
|
36702
36992
|
"test:watch": "vitest"
|
|
36703
36993
|
},
|
|
36704
36994
|
dependencies: {
|
|
36705
|
-
"@fetchproxy/bootstrap": "^0.
|
|
36995
|
+
"@fetchproxy/bootstrap": "^0.6.0",
|
|
36706
36996
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36707
|
-
dotenv: "^17.4.
|
|
36708
|
-
zod: "^4.3
|
|
36997
|
+
dotenv: "^17.4.2",
|
|
36998
|
+
zod: "^4.4.3"
|
|
36709
36999
|
},
|
|
36710
37000
|
devDependencies: {
|
|
36711
|
-
"@types/node": "^25.
|
|
36712
|
-
"@vitest/coverage-v8": "^4.1.
|
|
37001
|
+
"@types/node": "^25.9.1",
|
|
37002
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
36713
37003
|
esbuild: "^0.28.0",
|
|
36714
|
-
typescript: "^6.0.
|
|
36715
|
-
vitest: "^4.1.
|
|
37004
|
+
typescript: "^6.0.3",
|
|
37005
|
+
vitest: "^4.1.7"
|
|
36716
37006
|
}
|
|
36717
37007
|
};
|
|
36718
37008
|
|
|
@@ -37504,7 +37794,7 @@ try {
|
|
|
37504
37794
|
} catch (e) {
|
|
37505
37795
|
configError = e;
|
|
37506
37796
|
}
|
|
37507
|
-
var server = new McpServer({ name: "canvas", version: "1.
|
|
37797
|
+
var server = new McpServer({ name: "canvas", version: "1.1.3" });
|
|
37508
37798
|
if (resolved) {
|
|
37509
37799
|
const client = new CanvasClient(resolved.account, {
|
|
37510
37800
|
preloaded: resolved.preloaded
|
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ try {
|
|
|
44
44
|
catch (e) {
|
|
45
45
|
configError = e;
|
|
46
46
|
}
|
|
47
|
-
const server = new McpServer({ name: 'canvas', version: '1.
|
|
47
|
+
const server = new McpServer({ name: 'canvas', version: '1.1.3' }); // x-release-please-version
|
|
48
48
|
if (resolved) {
|
|
49
49
|
const client = new CanvasClient(resolved.account, {
|
|
50
50
|
preloaded: resolved.preloaded,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvas-parent-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"mcpName": "io.github.chrischall/canvas-parent-mcp",
|
|
5
|
-
"description": "Canvas LMS (Instructure) MCP server
|
|
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>",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
"test:watch": "vitest"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@fetchproxy/bootstrap": "^0.
|
|
47
|
+
"@fetchproxy/bootstrap": "^0.6.0",
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
|
-
"dotenv": "^17.4.
|
|
50
|
-
"zod": "^4.3
|
|
49
|
+
"dotenv": "^17.4.2",
|
|
50
|
+
"zod": "^4.4.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@types/node": "^25.
|
|
54
|
-
"@vitest/coverage-v8": "^4.1.
|
|
53
|
+
"@types/node": "^25.9.1",
|
|
54
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
55
55
|
"esbuild": "^0.28.0",
|
|
56
|
-
"typescript": "^6.0.
|
|
57
|
-
"vitest": "^4.1.
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"vitest": "^4.1.7"
|
|
58
58
|
}
|
|
59
59
|
}
|
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.
|
|
9
|
+
"version": "1.1.3",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "canvas-parent-mcp",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.1.3",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|