moodle-cli 0.7.0-alpha.4 → 0.7.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/SKILL.md +1 -1
- package/agents/openai.yaml +2 -2
- package/dist/moodle.js +578 -65
- package/dist/worker/worker.js +1255 -839
- package/package.json +1 -1
- package/references/downloads.md +5 -1
package/dist/worker/worker.js
CHANGED
|
@@ -3573,7 +3573,7 @@ var require_parse = __commonJS({
|
|
|
3573
3573
|
var whitespace = /* @__PURE__ */ new Set([9, 10, 12, 13, 32]);
|
|
3574
3574
|
var ZERO = "0".charCodeAt(0);
|
|
3575
3575
|
var NINE = "9".charCodeAt(0);
|
|
3576
|
-
function
|
|
3576
|
+
function parse7(formula) {
|
|
3577
3577
|
formula = formula.trim().toLowerCase();
|
|
3578
3578
|
if (formula === "even") {
|
|
3579
3579
|
return [2, 0];
|
|
@@ -3625,7 +3625,7 @@ var require_parse = __commonJS({
|
|
|
3625
3625
|
}
|
|
3626
3626
|
}
|
|
3627
3627
|
}
|
|
3628
|
-
exports.parse =
|
|
3628
|
+
exports.parse = parse7;
|
|
3629
3629
|
}
|
|
3630
3630
|
});
|
|
3631
3631
|
|
|
@@ -5075,7 +5075,7 @@ var require_html = __commonJS({
|
|
|
5075
5075
|
}).join("");
|
|
5076
5076
|
}
|
|
5077
5077
|
set innerHTML(content) {
|
|
5078
|
-
const r =
|
|
5078
|
+
const r = parse7(content, this._parseOptions);
|
|
5079
5079
|
const nodes = r.childNodes.length ? r.childNodes : [new text_1.default(content, this)];
|
|
5080
5080
|
resetParent(nodes, this);
|
|
5081
5081
|
resetParent(this.childNodes, null);
|
|
@@ -5086,7 +5086,7 @@ var require_html = __commonJS({
|
|
|
5086
5086
|
content = [content];
|
|
5087
5087
|
} else if (typeof content == "string") {
|
|
5088
5088
|
options = Object.assign(Object.assign({}, this._parseOptions), options);
|
|
5089
|
-
const r =
|
|
5089
|
+
const r = parse7(content, options);
|
|
5090
5090
|
content = r.childNodes.length ? r.childNodes : [new text_1.default(r.innerHTML, this)];
|
|
5091
5091
|
}
|
|
5092
5092
|
resetParent(this.childNodes, null);
|
|
@@ -5100,7 +5100,7 @@ var require_html = __commonJS({
|
|
|
5100
5100
|
if (node instanceof node_1.default) {
|
|
5101
5101
|
return [node];
|
|
5102
5102
|
} else if (typeof node == "string") {
|
|
5103
|
-
const r =
|
|
5103
|
+
const r = parse7(node, this._parseOptions);
|
|
5104
5104
|
return r.childNodes.length ? r.childNodes : [new text_1.default(node, this)];
|
|
5105
5105
|
}
|
|
5106
5106
|
return [];
|
|
@@ -5484,7 +5484,7 @@ var require_html = __commonJS({
|
|
|
5484
5484
|
if (arguments.length < 2) {
|
|
5485
5485
|
throw new Error("2 arguments required");
|
|
5486
5486
|
}
|
|
5487
|
-
const p =
|
|
5487
|
+
const p = parse7(html, this._parseOptions);
|
|
5488
5488
|
if (where === "afterend") {
|
|
5489
5489
|
this.after(...p.childNodes);
|
|
5490
5490
|
} else if (where === "afterbegin") {
|
|
@@ -5630,7 +5630,7 @@ var require_html = __commonJS({
|
|
|
5630
5630
|
}
|
|
5631
5631
|
/** Clone this Node */
|
|
5632
5632
|
clone() {
|
|
5633
|
-
return
|
|
5633
|
+
return parse7(this.toString(), this._parseOptions).firstChild;
|
|
5634
5634
|
}
|
|
5635
5635
|
};
|
|
5636
5636
|
exports.default = HTMLElement2;
|
|
@@ -5832,7 +5832,7 @@ var require_html = __commonJS({
|
|
|
5832
5832
|
return stack;
|
|
5833
5833
|
}
|
|
5834
5834
|
exports.base_parse = base_parse;
|
|
5835
|
-
function
|
|
5835
|
+
function parse7(data, options = {}) {
|
|
5836
5836
|
const stack = base_parse(data, options);
|
|
5837
5837
|
const [root] = stack;
|
|
5838
5838
|
while (stack.length > 1) {
|
|
@@ -5860,7 +5860,7 @@ var require_html = __commonJS({
|
|
|
5860
5860
|
}
|
|
5861
5861
|
return root;
|
|
5862
5862
|
}
|
|
5863
|
-
exports.parse =
|
|
5863
|
+
exports.parse = parse7;
|
|
5864
5864
|
function resolveInsertable(insertable) {
|
|
5865
5865
|
return insertable.map((val) => {
|
|
5866
5866
|
if (typeof val === "string") {
|
|
@@ -5928,18 +5928,18 @@ var require_dist = __commonJS({
|
|
|
5928
5928
|
var parse_1 = __importDefault(require_parse2());
|
|
5929
5929
|
var valid_1 = __importDefault(require_valid());
|
|
5930
5930
|
exports.valid = valid_1.default;
|
|
5931
|
-
function
|
|
5931
|
+
function parse7(data, options = {}) {
|
|
5932
5932
|
return (0, parse_1.default)(data, options);
|
|
5933
5933
|
}
|
|
5934
|
-
exports.default =
|
|
5935
|
-
exports.parse =
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5934
|
+
exports.default = parse7;
|
|
5935
|
+
exports.parse = parse7;
|
|
5936
|
+
parse7.parse = parse_1.default;
|
|
5937
|
+
parse7.HTMLElement = html_1.default;
|
|
5938
|
+
parse7.CommentNode = comment_1.default;
|
|
5939
|
+
parse7.valid = valid_1.default;
|
|
5940
|
+
parse7.Node = node_1.default;
|
|
5941
|
+
parse7.TextNode = text_1.default;
|
|
5942
|
+
parse7.NodeType = type_1.default;
|
|
5943
5943
|
}
|
|
5944
5944
|
});
|
|
5945
5945
|
|
|
@@ -5995,300 +5995,6 @@ function problemResponse(status, code, title, detail, headers) {
|
|
|
5995
5995
|
);
|
|
5996
5996
|
}
|
|
5997
5997
|
|
|
5998
|
-
// src/version.ts
|
|
5999
|
-
var VERSION = "0.7.0-alpha.4";
|
|
6000
|
-
|
|
6001
|
-
// src/worker/http.ts
|
|
6002
|
-
var HEALTH_PATH = "/healthz";
|
|
6003
|
-
var READY_PATH = "/readyz";
|
|
6004
|
-
var MCP_PATH = "/mcp";
|
|
6005
|
-
var SESSION_PATH = "/session";
|
|
6006
|
-
var SESSION_TOUCH_PATH = "/session/touch";
|
|
6007
|
-
var WORKER_SERVICE_ID = "moodle-mcp";
|
|
6008
|
-
var WORKER_SERVICE_VERSION = VERSION;
|
|
6009
|
-
function createWorkerHandler(dependencies) {
|
|
6010
|
-
return {
|
|
6011
|
-
async fetch(request, env) {
|
|
6012
|
-
const url2 = new URL(request.url);
|
|
6013
|
-
const authorityProblem = validateRequestAuthority(request, url2, env.EXPECTED_HOST);
|
|
6014
|
-
if (authorityProblem) return authorityProblem;
|
|
6015
|
-
if (hasQueryCredential(url2)) return unauthorized("Bearer credentials are not accepted in the query string.");
|
|
6016
|
-
if (url2.pathname === HEALTH_PATH && request.method === "GET") {
|
|
6017
|
-
return Response.json(
|
|
6018
|
-
{ status: "pass", serviceId: WORKER_SERVICE_ID, version: WORKER_SERVICE_VERSION },
|
|
6019
|
-
{ headers: { "content-type": "application/health+json; charset=utf-8" } }
|
|
6020
|
-
);
|
|
6021
|
-
}
|
|
6022
|
-
if (url2.pathname === MCP_PATH && request.method !== "POST") {
|
|
6023
|
-
return problemResponse(405, "METHOD_NOT_ALLOWED", "Method Not Allowed", "The MCP transport accepts POST requests only.", {
|
|
6024
|
-
allow: "POST"
|
|
6025
|
-
});
|
|
6026
|
-
}
|
|
6027
|
-
if (url2.pathname === MCP_PATH) {
|
|
6028
|
-
if (!await verifyBearerToken(request.headers.get("authorization"), [
|
|
6029
|
-
env.MCP_ACCESS_TOKEN_DIGEST,
|
|
6030
|
-
activePreviousDigest(env.MCP_ACCESS_TOKEN_PREVIOUS_DIGEST, env.TOKEN_OVERLAP_EXPIRES_AT)
|
|
6031
|
-
])) {
|
|
6032
|
-
return unauthorized();
|
|
6033
|
-
}
|
|
6034
|
-
const server = typeof dependencies.mcpServer === "function" ? dependencies.mcpServer(env) : dependencies.mcpServer;
|
|
6035
|
-
return handleMcpRequest(request, server);
|
|
6036
|
-
}
|
|
6037
|
-
if (url2.pathname === READY_PATH && request.method === "GET") {
|
|
6038
|
-
if (!await authorizeSessionSync(request, env)) return unauthorized();
|
|
6039
|
-
const broker = resolveBroker(dependencies, env);
|
|
6040
|
-
if (broker instanceof Response) return broker;
|
|
6041
|
-
return broker.ready();
|
|
6042
|
-
}
|
|
6043
|
-
if (url2.pathname === SESSION_PATH && request.method === "PUT") {
|
|
6044
|
-
if (!await authorizeSessionSync(request, env)) return unauthorized();
|
|
6045
|
-
const broker = resolveBroker(dependencies, env);
|
|
6046
|
-
if (broker instanceof Response) return broker;
|
|
6047
|
-
return broker.replaceSession(request);
|
|
6048
|
-
}
|
|
6049
|
-
if (url2.pathname === SESSION_TOUCH_PATH && request.method === "POST") {
|
|
6050
|
-
if (!await authorizeSessionSync(request, env)) return unauthorized();
|
|
6051
|
-
const broker = resolveBroker(dependencies, env);
|
|
6052
|
-
if (broker instanceof Response) return broker;
|
|
6053
|
-
return broker.touch();
|
|
6054
|
-
}
|
|
6055
|
-
if (url2.pathname === READY_PATH || url2.pathname === SESSION_PATH || url2.pathname === SESSION_TOUCH_PATH) {
|
|
6056
|
-
const method = url2.pathname === READY_PATH ? "GET" : url2.pathname === SESSION_PATH ? "PUT" : "POST";
|
|
6057
|
-
return problemResponse(405, "METHOD_NOT_ALLOWED", "Method Not Allowed", "The session route does not accept this method.", {
|
|
6058
|
-
allow: method
|
|
6059
|
-
});
|
|
6060
|
-
}
|
|
6061
|
-
return problemResponse(404, "NOT_FOUND", "Not Found", "The requested route does not exist.");
|
|
6062
|
-
}
|
|
6063
|
-
};
|
|
6064
|
-
}
|
|
6065
|
-
function createDurableObjectBrokerApi(namespace) {
|
|
6066
|
-
const stub = namespace.get(namespace.idFromName("primary"));
|
|
6067
|
-
return {
|
|
6068
|
-
async handleMcp(body, context) {
|
|
6069
|
-
const response = await stub.fetch(new Request("https://session-broker/mcp", {
|
|
6070
|
-
method: "POST",
|
|
6071
|
-
headers: { "content-type": "application/json" },
|
|
6072
|
-
body: JSON.stringify({ request: body, context })
|
|
6073
|
-
}));
|
|
6074
|
-
if (!response.ok) {
|
|
6075
|
-
const problem = await safeProblem(response);
|
|
6076
|
-
throw new SessionBrokerMcpError(response.status, problem?.code);
|
|
6077
|
-
}
|
|
6078
|
-
const envelope = await response.json();
|
|
6079
|
-
return envelope.response ?? null;
|
|
6080
|
-
},
|
|
6081
|
-
ready: () => stub.fetch(new Request("https://session-broker/readyz")),
|
|
6082
|
-
async replaceSession(request) {
|
|
6083
|
-
return stub.fetch(new Request("https://session-broker/session", {
|
|
6084
|
-
method: "PUT",
|
|
6085
|
-
headers: { "content-type": request.headers.get("content-type") ?? "application/json" },
|
|
6086
|
-
body: await request.arrayBuffer()
|
|
6087
|
-
}));
|
|
6088
|
-
},
|
|
6089
|
-
touch: () => stub.fetch(new Request("https://session-broker/session/touch", { method: "POST" }))
|
|
6090
|
-
};
|
|
6091
|
-
}
|
|
6092
|
-
var SessionBrokerMcpError = class extends Error {
|
|
6093
|
-
constructor(status, code) {
|
|
6094
|
-
super("The session broker could not complete the MCP request.");
|
|
6095
|
-
this.status = status;
|
|
6096
|
-
this.code = code;
|
|
6097
|
-
this.name = "SessionBrokerMcpError";
|
|
6098
|
-
}
|
|
6099
|
-
status;
|
|
6100
|
-
code;
|
|
6101
|
-
};
|
|
6102
|
-
function resolveBroker(dependencies, env) {
|
|
6103
|
-
if (dependencies.broker) return dependencies.broker(env);
|
|
6104
|
-
if (env.SESSION_BROKER) return createDurableObjectBrokerApi(env.SESSION_BROKER);
|
|
6105
|
-
return problemResponse(503, "SESSION_BROKER_UNAVAILABLE", "Service Unavailable", "The session broker is unavailable.");
|
|
6106
|
-
}
|
|
6107
|
-
function validateRequestAuthority(request, url2, configuredHost) {
|
|
6108
|
-
const expectedHost = (configuredHost ?? url2.host).toLowerCase();
|
|
6109
|
-
const requestHost = (request.headers.get("host") ?? url2.host).toLowerCase();
|
|
6110
|
-
if (requestHost !== expectedHost) {
|
|
6111
|
-
return problemResponse(403, "INVALID_HOST", "Forbidden", "The request Host is not allowed.");
|
|
6112
|
-
}
|
|
6113
|
-
const origin = request.headers.get("origin");
|
|
6114
|
-
if (!origin) return null;
|
|
6115
|
-
try {
|
|
6116
|
-
const parsed = new URL(origin);
|
|
6117
|
-
if (parsed.origin !== url2.origin || parsed.host.toLowerCase() !== expectedHost) {
|
|
6118
|
-
return problemResponse(403, "INVALID_ORIGIN", "Forbidden", "The request Origin is not allowed.");
|
|
6119
|
-
}
|
|
6120
|
-
} catch {
|
|
6121
|
-
return problemResponse(403, "INVALID_ORIGIN", "Forbidden", "The request Origin is not allowed.");
|
|
6122
|
-
}
|
|
6123
|
-
return null;
|
|
6124
|
-
}
|
|
6125
|
-
function unauthorized(detail = "A valid Bearer token is required.") {
|
|
6126
|
-
return problemResponse(401, "INVALID_BEARER_TOKEN", "Unauthorized", detail, {
|
|
6127
|
-
"www-authenticate": 'Bearer realm="moodle-mcp"'
|
|
6128
|
-
});
|
|
6129
|
-
}
|
|
6130
|
-
async function handleMcpRequest(request, server) {
|
|
6131
|
-
if (!request.headers.get("content-type")?.toLowerCase().startsWith("application/json")) {
|
|
6132
|
-
return problemResponse(415, "UNSUPPORTED_MEDIA_TYPE", "Unsupported Media Type", "MCP requests must use application/json.");
|
|
6133
|
-
}
|
|
6134
|
-
let body;
|
|
6135
|
-
try {
|
|
6136
|
-
body = await request.json();
|
|
6137
|
-
} catch {
|
|
6138
|
-
return problemResponse(400, "INVALID_JSON", "Bad Request", "The request body is not valid JSON.");
|
|
6139
|
-
}
|
|
6140
|
-
const protocolVersion = request.headers.get("mcp-protocol-version");
|
|
6141
|
-
const metadataProblem = validateProtocolMetadata(request.headers, body, protocolVersion);
|
|
6142
|
-
if (metadataProblem) return metadataProblem;
|
|
6143
|
-
try {
|
|
6144
|
-
const response = await server.handle(body, {
|
|
6145
|
-
protocolVersion,
|
|
6146
|
-
method: request.headers.get("mcp-method") ?? void 0,
|
|
6147
|
-
toolName: request.headers.get("mcp-name") ?? void 0
|
|
6148
|
-
});
|
|
6149
|
-
if (response === null) return new Response(null, { status: 202 });
|
|
6150
|
-
if (request.headers.get("accept")?.toLowerCase().includes("text/event-stream")) {
|
|
6151
|
-
return new Response(`event: message
|
|
6152
|
-
data: ${JSON.stringify(response)}
|
|
6153
|
-
|
|
6154
|
-
`, {
|
|
6155
|
-
headers: {
|
|
6156
|
-
"cache-control": "private, no-store",
|
|
6157
|
-
"content-type": "text/event-stream; charset=utf-8"
|
|
6158
|
-
}
|
|
6159
|
-
});
|
|
6160
|
-
}
|
|
6161
|
-
return Response.json(response, { headers: { "cache-control": "private, no-store" } });
|
|
6162
|
-
} catch (error51) {
|
|
6163
|
-
if (error51 instanceof SessionBrokerMcpError) {
|
|
6164
|
-
const status = error51.status === 503 ? 503 : 500;
|
|
6165
|
-
const code = error51.code === "SESSION_MISSING" || error51.code === "SESSION_EXPIRED" ? error51.code : "SESSION_UNAVAILABLE";
|
|
6166
|
-
return problemResponse(status, code, "Service Unavailable", "The Moodle session is not ready.");
|
|
6167
|
-
}
|
|
6168
|
-
if (error51 instanceof Error && error51.name === "UnsupportedProtocolVersionError") {
|
|
6169
|
-
return problemResponse(400, "UNSUPPORTED_PROTOCOL_VERSION", "Unsupported Protocol Version", "The requested MCP protocol version is not supported.");
|
|
6170
|
-
}
|
|
6171
|
-
return problemResponse(500, "MCP_REQUEST_FAILED", "Internal Server Error", "The MCP request could not be completed.");
|
|
6172
|
-
}
|
|
6173
|
-
}
|
|
6174
|
-
async function safeProblem(response) {
|
|
6175
|
-
try {
|
|
6176
|
-
const value = await response.json();
|
|
6177
|
-
return isRecord(value) && (value.code === void 0 || typeof value.code === "string") ? value : null;
|
|
6178
|
-
} catch {
|
|
6179
|
-
return null;
|
|
6180
|
-
}
|
|
6181
|
-
}
|
|
6182
|
-
function validateProtocolMetadata(headers, body, protocolVersion) {
|
|
6183
|
-
if (!protocolVersion) {
|
|
6184
|
-
return problemResponse(400, "MCP_PROTOCOL_METADATA_INVALID", "Bad Request", "MCP-Protocol-Version is required.");
|
|
6185
|
-
}
|
|
6186
|
-
const params = isRecord(body.params) ? body.params : void 0;
|
|
6187
|
-
const metadata = params && isRecord(params._meta) ? params._meta : void 0;
|
|
6188
|
-
const bodyVersion = metadata?.["io.modelcontextprotocol/protocolVersion"];
|
|
6189
|
-
if (bodyVersion !== void 0 && bodyVersion !== protocolVersion) {
|
|
6190
|
-
return problemResponse(400, "MCP_PROTOCOL_METADATA_MISMATCH", "Bad Request", "MCP protocol metadata does not match the HTTP headers.");
|
|
6191
|
-
}
|
|
6192
|
-
if (typeof body.method !== "string") {
|
|
6193
|
-
return problemResponse(400, "MCP_PROTOCOL_METADATA_INVALID", "Bad Request", "The JSON-RPC method is required.");
|
|
6194
|
-
}
|
|
6195
|
-
const headerMethod = headers.get("mcp-method");
|
|
6196
|
-
const headerName = headers.get("mcp-name") ?? void 0;
|
|
6197
|
-
const paramsName = params && typeof params.name === "string" ? params.name : void 0;
|
|
6198
|
-
if (headerMethod !== body.method || headerName !== paramsName) {
|
|
6199
|
-
return problemResponse(400, "MCP_PROTOCOL_METADATA_MISMATCH", "Bad Request", "MCP method metadata does not match the JSON-RPC request.");
|
|
6200
|
-
}
|
|
6201
|
-
return null;
|
|
6202
|
-
}
|
|
6203
|
-
function authorizeSessionSync(request, env) {
|
|
6204
|
-
return verifyBearerToken(request.headers.get("authorization"), [
|
|
6205
|
-
env.SESSION_SYNC_TOKEN_DIGEST,
|
|
6206
|
-
activePreviousDigest(env.SESSION_SYNC_TOKEN_PREVIOUS_DIGEST, env.TOKEN_OVERLAP_EXPIRES_AT)
|
|
6207
|
-
]);
|
|
6208
|
-
}
|
|
6209
|
-
function activePreviousDigest(digest, expiresAt2) {
|
|
6210
|
-
if (!digest || !expiresAt2) return void 0;
|
|
6211
|
-
const expiration = Number(expiresAt2);
|
|
6212
|
-
return Number.isFinite(expiration) && Date.now() < expiration ? digest : void 0;
|
|
6213
|
-
}
|
|
6214
|
-
function isRecord(value) {
|
|
6215
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6216
|
-
}
|
|
6217
|
-
|
|
6218
|
-
// src/worker/crypto.ts
|
|
6219
|
-
async function createEncryptionKeyring(currentSecret, previousSecret) {
|
|
6220
|
-
if (!currentSecret) throw new Error("SESSION_ENCRYPTION_KEY is required.");
|
|
6221
|
-
const current = await deriveEncryptionKey(currentSecret);
|
|
6222
|
-
const keys = /* @__PURE__ */ new Map([[current.id, current.key]]);
|
|
6223
|
-
if (previousSecret) {
|
|
6224
|
-
const previous = await deriveEncryptionKey(previousSecret);
|
|
6225
|
-
keys.set(previous.id, previous.key);
|
|
6226
|
-
}
|
|
6227
|
-
return { current, keys };
|
|
6228
|
-
}
|
|
6229
|
-
async function encryptValue(value, keyring) {
|
|
6230
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
6231
|
-
const ciphertext = await crypto.subtle.encrypt(
|
|
6232
|
-
{ name: "AES-GCM", iv },
|
|
6233
|
-
keyring.current.key,
|
|
6234
|
-
new TextEncoder().encode(value)
|
|
6235
|
-
);
|
|
6236
|
-
return JSON.stringify({
|
|
6237
|
-
version: 1,
|
|
6238
|
-
keyId: keyring.current.id,
|
|
6239
|
-
iv: toBase64Url(iv),
|
|
6240
|
-
ciphertext: toBase64Url(new Uint8Array(ciphertext))
|
|
6241
|
-
});
|
|
6242
|
-
}
|
|
6243
|
-
async function decryptValue(encrypted, keyring) {
|
|
6244
|
-
const envelope = parseEnvelope(encrypted);
|
|
6245
|
-
const key = keyring.keys.get(envelope.keyId);
|
|
6246
|
-
if (!key) throw new Error("The session encryption key is unavailable.");
|
|
6247
|
-
const plaintext = await crypto.subtle.decrypt(
|
|
6248
|
-
{ name: "AES-GCM", iv: fromBase64Url(envelope.iv) },
|
|
6249
|
-
key,
|
|
6250
|
-
fromBase64Url(envelope.ciphertext)
|
|
6251
|
-
);
|
|
6252
|
-
return {
|
|
6253
|
-
value: new TextDecoder().decode(plaintext),
|
|
6254
|
-
keyId: envelope.keyId,
|
|
6255
|
-
needsRotation: envelope.keyId !== keyring.current.id
|
|
6256
|
-
};
|
|
6257
|
-
}
|
|
6258
|
-
async function deriveEncryptionKey(secret) {
|
|
6259
|
-
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(secret)));
|
|
6260
|
-
const key = await crypto.subtle.importKey("raw", digest, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
6261
|
-
const id = Array.from(digest.slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
6262
|
-
return { id, key };
|
|
6263
|
-
}
|
|
6264
|
-
function parseEnvelope(value) {
|
|
6265
|
-
let parsed;
|
|
6266
|
-
try {
|
|
6267
|
-
parsed = JSON.parse(value);
|
|
6268
|
-
} catch {
|
|
6269
|
-
throw new Error("The encrypted session record is invalid.");
|
|
6270
|
-
}
|
|
6271
|
-
if (!isRecord2(parsed) || parsed.version !== 1 || typeof parsed.keyId !== "string" || typeof parsed.iv !== "string" || typeof parsed.ciphertext !== "string") {
|
|
6272
|
-
throw new Error("The encrypted session record is invalid.");
|
|
6273
|
-
}
|
|
6274
|
-
return parsed;
|
|
6275
|
-
}
|
|
6276
|
-
function toBase64Url(bytes) {
|
|
6277
|
-
let binary = "";
|
|
6278
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
6279
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
6280
|
-
}
|
|
6281
|
-
function fromBase64Url(value) {
|
|
6282
|
-
const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
6283
|
-
const binary = atob(normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="));
|
|
6284
|
-
const bytes = new Uint8Array(binary.length);
|
|
6285
|
-
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
6286
|
-
return bytes;
|
|
6287
|
-
}
|
|
6288
|
-
function isRecord2(value) {
|
|
6289
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6290
|
-
}
|
|
6291
|
-
|
|
6292
5998
|
// node_modules/zod/v4/classic/external.js
|
|
6293
5999
|
var external_exports = {};
|
|
6294
6000
|
__export(external_exports, {
|
|
@@ -20383,426 +20089,848 @@ function detectVersion(schema2, defaultTarget) {
|
|
|
20383
20089
|
if ($schema === "http://json-schema.org/draft-04/schema#") {
|
|
20384
20090
|
return "draft-4";
|
|
20385
20091
|
}
|
|
20386
|
-
return defaultTarget ?? "draft-2020-12";
|
|
20092
|
+
return defaultTarget ?? "draft-2020-12";
|
|
20093
|
+
}
|
|
20094
|
+
function resolveRef(ref, ctx) {
|
|
20095
|
+
if (!ref.startsWith("#")) {
|
|
20096
|
+
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
20097
|
+
}
|
|
20098
|
+
const path = ref.slice(1).split("/").filter(Boolean);
|
|
20099
|
+
if (path.length === 0) {
|
|
20100
|
+
return ctx.rootSchema;
|
|
20101
|
+
}
|
|
20102
|
+
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
20103
|
+
if (path[0] === defsKey) {
|
|
20104
|
+
const key = path[1];
|
|
20105
|
+
if (!key || !ctx.defs[key]) {
|
|
20106
|
+
throw new Error(`Reference not found: ${ref}`);
|
|
20107
|
+
}
|
|
20108
|
+
return ctx.defs[key];
|
|
20109
|
+
}
|
|
20110
|
+
throw new Error(`Reference not found: ${ref}`);
|
|
20111
|
+
}
|
|
20112
|
+
function convertBaseSchema(schema2, ctx) {
|
|
20113
|
+
if (schema2.not !== void 0) {
|
|
20114
|
+
if (typeof schema2.not === "object" && Object.keys(schema2.not).length === 0) {
|
|
20115
|
+
return z.never();
|
|
20116
|
+
}
|
|
20117
|
+
throw new Error("not is not supported in Zod (except { not: {} } for never)");
|
|
20118
|
+
}
|
|
20119
|
+
if (schema2.unevaluatedItems !== void 0) {
|
|
20120
|
+
throw new Error("unevaluatedItems is not supported");
|
|
20121
|
+
}
|
|
20122
|
+
if (schema2.unevaluatedProperties !== void 0) {
|
|
20123
|
+
throw new Error("unevaluatedProperties is not supported");
|
|
20124
|
+
}
|
|
20125
|
+
if (schema2.if !== void 0 || schema2.then !== void 0 || schema2.else !== void 0) {
|
|
20126
|
+
throw new Error("Conditional schemas (if/then/else) are not supported");
|
|
20127
|
+
}
|
|
20128
|
+
if (schema2.dependentSchemas !== void 0 || schema2.dependentRequired !== void 0) {
|
|
20129
|
+
throw new Error("dependentSchemas and dependentRequired are not supported");
|
|
20130
|
+
}
|
|
20131
|
+
if (schema2.$ref) {
|
|
20132
|
+
const refPath = schema2.$ref;
|
|
20133
|
+
if (ctx.refs.has(refPath)) {
|
|
20134
|
+
return ctx.refs.get(refPath);
|
|
20135
|
+
}
|
|
20136
|
+
if (ctx.processing.has(refPath)) {
|
|
20137
|
+
return z.lazy(() => {
|
|
20138
|
+
if (!ctx.refs.has(refPath)) {
|
|
20139
|
+
throw new Error(`Circular reference not resolved: ${refPath}`);
|
|
20140
|
+
}
|
|
20141
|
+
return ctx.refs.get(refPath);
|
|
20142
|
+
});
|
|
20143
|
+
}
|
|
20144
|
+
ctx.processing.add(refPath);
|
|
20145
|
+
const resolved = resolveRef(refPath, ctx);
|
|
20146
|
+
const zodSchema2 = convertSchema(resolved, ctx);
|
|
20147
|
+
ctx.refs.set(refPath, zodSchema2);
|
|
20148
|
+
ctx.processing.delete(refPath);
|
|
20149
|
+
return zodSchema2;
|
|
20150
|
+
}
|
|
20151
|
+
if (schema2.enum !== void 0) {
|
|
20152
|
+
const enumValues = schema2.enum;
|
|
20153
|
+
if (ctx.version === "openapi-3.0" && schema2.nullable === true && enumValues.length === 1 && enumValues[0] === null) {
|
|
20154
|
+
return z.null();
|
|
20155
|
+
}
|
|
20156
|
+
if (enumValues.length === 0) {
|
|
20157
|
+
return z.never();
|
|
20158
|
+
}
|
|
20159
|
+
if (enumValues.length === 1) {
|
|
20160
|
+
return z.literal(enumValues[0]);
|
|
20161
|
+
}
|
|
20162
|
+
if (enumValues.every((v) => typeof v === "string")) {
|
|
20163
|
+
return z.enum(enumValues);
|
|
20164
|
+
}
|
|
20165
|
+
const literalSchemas = enumValues.map((v) => z.literal(v));
|
|
20166
|
+
if (literalSchemas.length < 2) {
|
|
20167
|
+
return literalSchemas[0];
|
|
20168
|
+
}
|
|
20169
|
+
return z.union([literalSchemas[0], literalSchemas[1], ...literalSchemas.slice(2)]);
|
|
20170
|
+
}
|
|
20171
|
+
if (schema2.const !== void 0) {
|
|
20172
|
+
return z.literal(schema2.const);
|
|
20173
|
+
}
|
|
20174
|
+
const type = schema2.type;
|
|
20175
|
+
if (Array.isArray(type)) {
|
|
20176
|
+
const typeSchemas = type.map((t) => {
|
|
20177
|
+
const typeSchema = { ...schema2, type: t };
|
|
20178
|
+
return convertBaseSchema(typeSchema, ctx);
|
|
20179
|
+
});
|
|
20180
|
+
if (typeSchemas.length === 0) {
|
|
20181
|
+
return z.never();
|
|
20182
|
+
}
|
|
20183
|
+
if (typeSchemas.length === 1) {
|
|
20184
|
+
return typeSchemas[0];
|
|
20185
|
+
}
|
|
20186
|
+
return z.union(typeSchemas);
|
|
20187
|
+
}
|
|
20188
|
+
if (!type) {
|
|
20189
|
+
return z.any();
|
|
20190
|
+
}
|
|
20191
|
+
let zodSchema;
|
|
20192
|
+
switch (type) {
|
|
20193
|
+
case "string": {
|
|
20194
|
+
let stringSchema = z.string();
|
|
20195
|
+
if (schema2.format) {
|
|
20196
|
+
const format = schema2.format;
|
|
20197
|
+
if (format === "email") {
|
|
20198
|
+
stringSchema = stringSchema.check(z.email());
|
|
20199
|
+
} else if (format === "uri" || format === "uri-reference") {
|
|
20200
|
+
stringSchema = stringSchema.check(z.url());
|
|
20201
|
+
} else if (format === "uuid" || format === "guid") {
|
|
20202
|
+
stringSchema = stringSchema.check(z.uuid());
|
|
20203
|
+
} else if (format === "date-time") {
|
|
20204
|
+
stringSchema = stringSchema.check(z.iso.datetime());
|
|
20205
|
+
} else if (format === "date") {
|
|
20206
|
+
stringSchema = stringSchema.check(z.iso.date());
|
|
20207
|
+
} else if (format === "time") {
|
|
20208
|
+
stringSchema = stringSchema.check(z.iso.time());
|
|
20209
|
+
} else if (format === "duration") {
|
|
20210
|
+
stringSchema = stringSchema.check(z.iso.duration());
|
|
20211
|
+
} else if (format === "ipv4") {
|
|
20212
|
+
stringSchema = stringSchema.check(z.ipv4());
|
|
20213
|
+
} else if (format === "ipv6") {
|
|
20214
|
+
stringSchema = stringSchema.check(z.ipv6());
|
|
20215
|
+
} else if (format === "mac") {
|
|
20216
|
+
stringSchema = stringSchema.check(z.mac());
|
|
20217
|
+
} else if (format === "cidr") {
|
|
20218
|
+
stringSchema = stringSchema.check(z.cidrv4());
|
|
20219
|
+
} else if (format === "cidr-v6") {
|
|
20220
|
+
stringSchema = stringSchema.check(z.cidrv6());
|
|
20221
|
+
} else if (format === "base64") {
|
|
20222
|
+
stringSchema = stringSchema.check(z.base64());
|
|
20223
|
+
} else if (format === "base64url") {
|
|
20224
|
+
stringSchema = stringSchema.check(z.base64url());
|
|
20225
|
+
} else if (format === "e164") {
|
|
20226
|
+
stringSchema = stringSchema.check(z.e164());
|
|
20227
|
+
} else if (format === "jwt") {
|
|
20228
|
+
stringSchema = stringSchema.check(z.jwt());
|
|
20229
|
+
} else if (format === "emoji") {
|
|
20230
|
+
stringSchema = stringSchema.check(z.emoji());
|
|
20231
|
+
} else if (format === "nanoid") {
|
|
20232
|
+
stringSchema = stringSchema.check(z.nanoid());
|
|
20233
|
+
} else if (format === "cuid") {
|
|
20234
|
+
stringSchema = stringSchema.check(z.cuid());
|
|
20235
|
+
} else if (format === "cuid2") {
|
|
20236
|
+
stringSchema = stringSchema.check(z.cuid2());
|
|
20237
|
+
} else if (format === "ulid") {
|
|
20238
|
+
stringSchema = stringSchema.check(z.ulid());
|
|
20239
|
+
} else if (format === "xid") {
|
|
20240
|
+
stringSchema = stringSchema.check(z.xid());
|
|
20241
|
+
} else if (format === "ksuid") {
|
|
20242
|
+
stringSchema = stringSchema.check(z.ksuid());
|
|
20243
|
+
}
|
|
20244
|
+
}
|
|
20245
|
+
if (typeof schema2.minLength === "number") {
|
|
20246
|
+
stringSchema = stringSchema.min(schema2.minLength);
|
|
20247
|
+
}
|
|
20248
|
+
if (typeof schema2.maxLength === "number") {
|
|
20249
|
+
stringSchema = stringSchema.max(schema2.maxLength);
|
|
20250
|
+
}
|
|
20251
|
+
if (schema2.pattern) {
|
|
20252
|
+
stringSchema = stringSchema.regex(new RegExp(schema2.pattern));
|
|
20253
|
+
}
|
|
20254
|
+
zodSchema = stringSchema;
|
|
20255
|
+
break;
|
|
20256
|
+
}
|
|
20257
|
+
case "number":
|
|
20258
|
+
case "integer": {
|
|
20259
|
+
let numberSchema = type === "integer" ? z.number().int() : z.number();
|
|
20260
|
+
if (typeof schema2.minimum === "number") {
|
|
20261
|
+
numberSchema = numberSchema.min(schema2.minimum);
|
|
20262
|
+
}
|
|
20263
|
+
if (typeof schema2.maximum === "number") {
|
|
20264
|
+
numberSchema = numberSchema.max(schema2.maximum);
|
|
20265
|
+
}
|
|
20266
|
+
if (typeof schema2.exclusiveMinimum === "number") {
|
|
20267
|
+
numberSchema = numberSchema.gt(schema2.exclusiveMinimum);
|
|
20268
|
+
} else if (schema2.exclusiveMinimum === true && typeof schema2.minimum === "number") {
|
|
20269
|
+
numberSchema = numberSchema.gt(schema2.minimum);
|
|
20270
|
+
}
|
|
20271
|
+
if (typeof schema2.exclusiveMaximum === "number") {
|
|
20272
|
+
numberSchema = numberSchema.lt(schema2.exclusiveMaximum);
|
|
20273
|
+
} else if (schema2.exclusiveMaximum === true && typeof schema2.maximum === "number") {
|
|
20274
|
+
numberSchema = numberSchema.lt(schema2.maximum);
|
|
20275
|
+
}
|
|
20276
|
+
if (typeof schema2.multipleOf === "number") {
|
|
20277
|
+
numberSchema = numberSchema.multipleOf(schema2.multipleOf);
|
|
20278
|
+
}
|
|
20279
|
+
zodSchema = numberSchema;
|
|
20280
|
+
break;
|
|
20281
|
+
}
|
|
20282
|
+
case "boolean": {
|
|
20283
|
+
zodSchema = z.boolean();
|
|
20284
|
+
break;
|
|
20285
|
+
}
|
|
20286
|
+
case "null": {
|
|
20287
|
+
zodSchema = z.null();
|
|
20288
|
+
break;
|
|
20289
|
+
}
|
|
20290
|
+
case "object": {
|
|
20291
|
+
const shape = {};
|
|
20292
|
+
const properties = schema2.properties || {};
|
|
20293
|
+
const requiredSet = new Set(schema2.required || []);
|
|
20294
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
20295
|
+
const propZodSchema = convertSchema(propSchema, ctx);
|
|
20296
|
+
shape[key] = requiredSet.has(key) ? propZodSchema : propZodSchema.optional();
|
|
20297
|
+
}
|
|
20298
|
+
if (schema2.propertyNames) {
|
|
20299
|
+
const keySchema = convertSchema(schema2.propertyNames, ctx);
|
|
20300
|
+
const valueSchema = schema2.additionalProperties && typeof schema2.additionalProperties === "object" ? convertSchema(schema2.additionalProperties, ctx) : z.any();
|
|
20301
|
+
if (Object.keys(shape).length === 0) {
|
|
20302
|
+
zodSchema = z.record(keySchema, valueSchema);
|
|
20303
|
+
break;
|
|
20304
|
+
}
|
|
20305
|
+
const objectSchema2 = z.object(shape).passthrough();
|
|
20306
|
+
const recordSchema = z.looseRecord(keySchema, valueSchema);
|
|
20307
|
+
zodSchema = z.intersection(objectSchema2, recordSchema);
|
|
20308
|
+
break;
|
|
20309
|
+
}
|
|
20310
|
+
if (schema2.patternProperties) {
|
|
20311
|
+
const patternProps = schema2.patternProperties;
|
|
20312
|
+
const patternKeys = Object.keys(patternProps);
|
|
20313
|
+
const looseRecords = [];
|
|
20314
|
+
for (const pattern of patternKeys) {
|
|
20315
|
+
const patternValue = convertSchema(patternProps[pattern], ctx);
|
|
20316
|
+
const keySchema = z.string().regex(new RegExp(pattern));
|
|
20317
|
+
looseRecords.push(z.looseRecord(keySchema, patternValue));
|
|
20318
|
+
}
|
|
20319
|
+
const schemasToIntersect = [];
|
|
20320
|
+
if (Object.keys(shape).length > 0) {
|
|
20321
|
+
schemasToIntersect.push(z.object(shape).passthrough());
|
|
20322
|
+
}
|
|
20323
|
+
schemasToIntersect.push(...looseRecords);
|
|
20324
|
+
if (schemasToIntersect.length === 0) {
|
|
20325
|
+
zodSchema = z.object({}).passthrough();
|
|
20326
|
+
} else if (schemasToIntersect.length === 1) {
|
|
20327
|
+
zodSchema = schemasToIntersect[0];
|
|
20328
|
+
} else {
|
|
20329
|
+
let result = z.intersection(schemasToIntersect[0], schemasToIntersect[1]);
|
|
20330
|
+
for (let i = 2; i < schemasToIntersect.length; i++) {
|
|
20331
|
+
result = z.intersection(result, schemasToIntersect[i]);
|
|
20332
|
+
}
|
|
20333
|
+
zodSchema = result;
|
|
20334
|
+
}
|
|
20335
|
+
break;
|
|
20336
|
+
}
|
|
20337
|
+
const objectSchema = z.object(shape);
|
|
20338
|
+
if (schema2.additionalProperties === false) {
|
|
20339
|
+
zodSchema = objectSchema.strict();
|
|
20340
|
+
} else if (typeof schema2.additionalProperties === "object") {
|
|
20341
|
+
zodSchema = objectSchema.catchall(convertSchema(schema2.additionalProperties, ctx));
|
|
20342
|
+
} else {
|
|
20343
|
+
zodSchema = objectSchema.passthrough();
|
|
20344
|
+
}
|
|
20345
|
+
break;
|
|
20346
|
+
}
|
|
20347
|
+
case "array": {
|
|
20348
|
+
const prefixItems = schema2.prefixItems;
|
|
20349
|
+
const items = schema2.items;
|
|
20350
|
+
if (prefixItems && Array.isArray(prefixItems)) {
|
|
20351
|
+
const tupleItems = prefixItems.map((item) => convertSchema(item, ctx));
|
|
20352
|
+
const rest = items && typeof items === "object" && !Array.isArray(items) ? convertSchema(items, ctx) : void 0;
|
|
20353
|
+
if (rest) {
|
|
20354
|
+
zodSchema = z.tuple(tupleItems).rest(rest);
|
|
20355
|
+
} else {
|
|
20356
|
+
zodSchema = z.tuple(tupleItems);
|
|
20357
|
+
}
|
|
20358
|
+
if (typeof schema2.minItems === "number") {
|
|
20359
|
+
zodSchema = zodSchema.check(z.minLength(schema2.minItems));
|
|
20360
|
+
}
|
|
20361
|
+
if (typeof schema2.maxItems === "number") {
|
|
20362
|
+
zodSchema = zodSchema.check(z.maxLength(schema2.maxItems));
|
|
20363
|
+
}
|
|
20364
|
+
} else if (Array.isArray(items)) {
|
|
20365
|
+
const tupleItems = items.map((item) => convertSchema(item, ctx));
|
|
20366
|
+
const rest = schema2.additionalItems && typeof schema2.additionalItems === "object" ? convertSchema(schema2.additionalItems, ctx) : void 0;
|
|
20367
|
+
if (rest) {
|
|
20368
|
+
zodSchema = z.tuple(tupleItems).rest(rest);
|
|
20369
|
+
} else {
|
|
20370
|
+
zodSchema = z.tuple(tupleItems);
|
|
20371
|
+
}
|
|
20372
|
+
if (typeof schema2.minItems === "number") {
|
|
20373
|
+
zodSchema = zodSchema.check(z.minLength(schema2.minItems));
|
|
20374
|
+
}
|
|
20375
|
+
if (typeof schema2.maxItems === "number") {
|
|
20376
|
+
zodSchema = zodSchema.check(z.maxLength(schema2.maxItems));
|
|
20377
|
+
}
|
|
20378
|
+
} else if (items !== void 0) {
|
|
20379
|
+
const element = convertSchema(items, ctx);
|
|
20380
|
+
let arraySchema = z.array(element);
|
|
20381
|
+
if (typeof schema2.minItems === "number") {
|
|
20382
|
+
arraySchema = arraySchema.min(schema2.minItems);
|
|
20383
|
+
}
|
|
20384
|
+
if (typeof schema2.maxItems === "number") {
|
|
20385
|
+
arraySchema = arraySchema.max(schema2.maxItems);
|
|
20386
|
+
}
|
|
20387
|
+
zodSchema = arraySchema;
|
|
20388
|
+
} else {
|
|
20389
|
+
zodSchema = z.array(z.any());
|
|
20390
|
+
}
|
|
20391
|
+
break;
|
|
20392
|
+
}
|
|
20393
|
+
default:
|
|
20394
|
+
throw new Error(`Unsupported type: ${type}`);
|
|
20395
|
+
}
|
|
20396
|
+
return zodSchema;
|
|
20387
20397
|
}
|
|
20388
|
-
function
|
|
20389
|
-
if (
|
|
20390
|
-
|
|
20398
|
+
function convertSchema(schema2, ctx) {
|
|
20399
|
+
if (typeof schema2 === "boolean") {
|
|
20400
|
+
return schema2 ? z.any() : z.never();
|
|
20391
20401
|
}
|
|
20392
|
-
|
|
20393
|
-
|
|
20394
|
-
|
|
20402
|
+
let baseSchema = convertBaseSchema(schema2, ctx);
|
|
20403
|
+
const hasExplicitType = schema2.type || schema2.enum !== void 0 || schema2.const !== void 0;
|
|
20404
|
+
if (schema2.anyOf && Array.isArray(schema2.anyOf)) {
|
|
20405
|
+
const options = schema2.anyOf.map((s) => convertSchema(s, ctx));
|
|
20406
|
+
const anyOfUnion = z.union(options);
|
|
20407
|
+
baseSchema = hasExplicitType ? z.intersection(baseSchema, anyOfUnion) : anyOfUnion;
|
|
20395
20408
|
}
|
|
20396
|
-
|
|
20397
|
-
|
|
20398
|
-
const
|
|
20399
|
-
|
|
20400
|
-
throw new Error(`Reference not found: ${ref}`);
|
|
20401
|
-
}
|
|
20402
|
-
return ctx.defs[key];
|
|
20409
|
+
if (schema2.oneOf && Array.isArray(schema2.oneOf)) {
|
|
20410
|
+
const options = schema2.oneOf.map((s) => convertSchema(s, ctx));
|
|
20411
|
+
const oneOfUnion = z.xor(options);
|
|
20412
|
+
baseSchema = hasExplicitType ? z.intersection(baseSchema, oneOfUnion) : oneOfUnion;
|
|
20403
20413
|
}
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20414
|
+
if (schema2.allOf && Array.isArray(schema2.allOf)) {
|
|
20415
|
+
if (schema2.allOf.length === 0) {
|
|
20416
|
+
baseSchema = hasExplicitType ? baseSchema : z.any();
|
|
20417
|
+
} else {
|
|
20418
|
+
let result = hasExplicitType ? baseSchema : convertSchema(schema2.allOf[0], ctx);
|
|
20419
|
+
const startIdx = hasExplicitType ? 0 : 1;
|
|
20420
|
+
for (let i = startIdx; i < schema2.allOf.length; i++) {
|
|
20421
|
+
result = z.intersection(result, convertSchema(schema2.allOf[i], ctx));
|
|
20422
|
+
}
|
|
20423
|
+
baseSchema = result;
|
|
20410
20424
|
}
|
|
20411
|
-
throw new Error("not is not supported in Zod (except { not: {} } for never)");
|
|
20412
20425
|
}
|
|
20413
|
-
if (schema2.
|
|
20414
|
-
|
|
20426
|
+
if (schema2.nullable === true && ctx.version === "openapi-3.0") {
|
|
20427
|
+
baseSchema = z.nullable(baseSchema);
|
|
20415
20428
|
}
|
|
20416
|
-
if (schema2.
|
|
20417
|
-
|
|
20429
|
+
if (schema2.readOnly === true) {
|
|
20430
|
+
baseSchema = z.readonly(baseSchema);
|
|
20418
20431
|
}
|
|
20419
|
-
if (schema2.
|
|
20420
|
-
|
|
20432
|
+
if (schema2.default !== void 0) {
|
|
20433
|
+
baseSchema = baseSchema.default(schema2.default);
|
|
20421
20434
|
}
|
|
20422
|
-
|
|
20423
|
-
|
|
20435
|
+
const extraMeta = {};
|
|
20436
|
+
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
|
|
20437
|
+
for (const key of coreMetadataKeys) {
|
|
20438
|
+
if (key in schema2) {
|
|
20439
|
+
extraMeta[key] = schema2[key];
|
|
20440
|
+
}
|
|
20424
20441
|
}
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
if (
|
|
20428
|
-
|
|
20442
|
+
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
|
|
20443
|
+
for (const key of contentMetadataKeys) {
|
|
20444
|
+
if (key in schema2) {
|
|
20445
|
+
extraMeta[key] = schema2[key];
|
|
20429
20446
|
}
|
|
20430
|
-
|
|
20431
|
-
|
|
20432
|
-
|
|
20433
|
-
|
|
20434
|
-
}
|
|
20435
|
-
return ctx.refs.get(refPath);
|
|
20436
|
-
});
|
|
20447
|
+
}
|
|
20448
|
+
for (const key of Object.keys(schema2)) {
|
|
20449
|
+
if (!RECOGNIZED_KEYS.has(key)) {
|
|
20450
|
+
extraMeta[key] = schema2[key];
|
|
20437
20451
|
}
|
|
20438
|
-
ctx.processing.add(refPath);
|
|
20439
|
-
const resolved = resolveRef(refPath, ctx);
|
|
20440
|
-
const zodSchema2 = convertSchema(resolved, ctx);
|
|
20441
|
-
ctx.refs.set(refPath, zodSchema2);
|
|
20442
|
-
ctx.processing.delete(refPath);
|
|
20443
|
-
return zodSchema2;
|
|
20444
20452
|
}
|
|
20445
|
-
if (
|
|
20446
|
-
|
|
20447
|
-
|
|
20448
|
-
|
|
20449
|
-
|
|
20450
|
-
|
|
20451
|
-
|
|
20452
|
-
|
|
20453
|
-
|
|
20454
|
-
|
|
20455
|
-
|
|
20456
|
-
|
|
20457
|
-
|
|
20458
|
-
|
|
20459
|
-
|
|
20460
|
-
|
|
20461
|
-
|
|
20453
|
+
if (Object.keys(extraMeta).length > 0) {
|
|
20454
|
+
ctx.registry.add(baseSchema, extraMeta);
|
|
20455
|
+
}
|
|
20456
|
+
if (schema2.description) {
|
|
20457
|
+
baseSchema = baseSchema.describe(schema2.description);
|
|
20458
|
+
}
|
|
20459
|
+
return baseSchema;
|
|
20460
|
+
}
|
|
20461
|
+
function fromJSONSchema(schema2, params) {
|
|
20462
|
+
if (typeof schema2 === "boolean") {
|
|
20463
|
+
return schema2 ? z.any() : z.never();
|
|
20464
|
+
}
|
|
20465
|
+
let normalized;
|
|
20466
|
+
try {
|
|
20467
|
+
normalized = JSON.parse(JSON.stringify(schema2));
|
|
20468
|
+
} catch {
|
|
20469
|
+
throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
|
|
20470
|
+
}
|
|
20471
|
+
const version2 = detectVersion(normalized, params?.defaultTarget);
|
|
20472
|
+
const defs = normalized.$defs || normalized.definitions || {};
|
|
20473
|
+
const ctx = {
|
|
20474
|
+
version: version2,
|
|
20475
|
+
defs,
|
|
20476
|
+
refs: /* @__PURE__ */ new Map(),
|
|
20477
|
+
processing: /* @__PURE__ */ new Set(),
|
|
20478
|
+
rootSchema: normalized,
|
|
20479
|
+
registry: params?.registry ?? globalRegistry
|
|
20480
|
+
};
|
|
20481
|
+
return convertSchema(normalized, ctx);
|
|
20482
|
+
}
|
|
20483
|
+
|
|
20484
|
+
// node_modules/zod/v4/classic/coerce.js
|
|
20485
|
+
var coerce_exports = {};
|
|
20486
|
+
__export(coerce_exports, {
|
|
20487
|
+
bigint: () => bigint3,
|
|
20488
|
+
boolean: () => boolean3,
|
|
20489
|
+
date: () => date4,
|
|
20490
|
+
number: () => number3,
|
|
20491
|
+
string: () => string3
|
|
20492
|
+
});
|
|
20493
|
+
function string3(params) {
|
|
20494
|
+
return _coercedString(ZodString, params);
|
|
20495
|
+
}
|
|
20496
|
+
function number3(params) {
|
|
20497
|
+
return _coercedNumber(ZodNumber, params);
|
|
20498
|
+
}
|
|
20499
|
+
function boolean3(params) {
|
|
20500
|
+
return _coercedBoolean(ZodBoolean, params);
|
|
20501
|
+
}
|
|
20502
|
+
function bigint3(params) {
|
|
20503
|
+
return _coercedBigint(ZodBigInt, params);
|
|
20504
|
+
}
|
|
20505
|
+
function date4(params) {
|
|
20506
|
+
return _coercedDate(ZodDate, params);
|
|
20507
|
+
}
|
|
20508
|
+
|
|
20509
|
+
// node_modules/zod/v4/classic/external.js
|
|
20510
|
+
config(en_default());
|
|
20511
|
+
|
|
20512
|
+
// src/mcp/protocol.ts
|
|
20513
|
+
var MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
20514
|
+
var LEGACY_PROTOCOL_VERSION = "2025-11-25";
|
|
20515
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [MODERN_PROTOCOL_VERSION, LEGACY_PROTOCOL_VERSION];
|
|
20516
|
+
var JsonRpcRequestSchema = external_exports.object({
|
|
20517
|
+
jsonrpc: external_exports.literal("2.0"),
|
|
20518
|
+
id: external_exports.union([external_exports.string(), external_exports.number(), external_exports.null()]).optional(),
|
|
20519
|
+
method: external_exports.string().trim().min(1),
|
|
20520
|
+
params: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
20521
|
+
}).strict();
|
|
20522
|
+
var ModernClientMetadataSchema = external_exports.object({
|
|
20523
|
+
"io.modelcontextprotocol/clientCapabilities": external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
20524
|
+
"io.modelcontextprotocol/clientInfo": external_exports.object({
|
|
20525
|
+
name: external_exports.string().trim().min(1),
|
|
20526
|
+
version: external_exports.string().trim().min(1)
|
|
20527
|
+
}).passthrough(),
|
|
20528
|
+
"io.modelcontextprotocol/protocolVersion": external_exports.literal(MODERN_PROTOCOL_VERSION)
|
|
20529
|
+
}).passthrough();
|
|
20530
|
+
var UnsupportedProtocolVersionError = class extends Error {
|
|
20531
|
+
protocolVersion;
|
|
20532
|
+
supportedVersions = SUPPORTED_PROTOCOL_VERSIONS;
|
|
20533
|
+
constructor(protocolVersion) {
|
|
20534
|
+
super(`Unsupported MCP protocol version: ${protocolVersion}`);
|
|
20535
|
+
this.name = "UnsupportedProtocolVersionError";
|
|
20536
|
+
this.protocolVersion = protocolVersion;
|
|
20537
|
+
}
|
|
20538
|
+
};
|
|
20539
|
+
var RequestMetadataMismatchError = class extends Error {
|
|
20540
|
+
field;
|
|
20541
|
+
expected;
|
|
20542
|
+
actual;
|
|
20543
|
+
constructor(field, expected, actual) {
|
|
20544
|
+
super(`MCP request metadata mismatch for ${field}.`);
|
|
20545
|
+
this.name = "RequestMetadataMismatchError";
|
|
20546
|
+
this.field = field;
|
|
20547
|
+
this.expected = expected;
|
|
20548
|
+
this.actual = actual;
|
|
20549
|
+
}
|
|
20550
|
+
};
|
|
20551
|
+
function parseJsonRpcRequest(input) {
|
|
20552
|
+
return JsonRpcRequestSchema.parse(input);
|
|
20553
|
+
}
|
|
20554
|
+
function resolveProtocolVersion(request, context = {}) {
|
|
20555
|
+
const meta3 = isRecord(request.params?._meta) ? request.params._meta : void 0;
|
|
20556
|
+
const initializeVersion = request.method === "initialize" && typeof request.params?.protocolVersion === "string" ? request.params.protocolVersion : void 0;
|
|
20557
|
+
const requested = context.protocolVersion ?? stringValue(meta3?.["io.modelcontextprotocol/protocolVersion"]) ?? initializeVersion ?? MODERN_PROTOCOL_VERSION;
|
|
20558
|
+
if (!isSupportedProtocolVersion(requested)) {
|
|
20559
|
+
throw new UnsupportedProtocolVersionError(requested);
|
|
20560
|
+
}
|
|
20561
|
+
return requested;
|
|
20562
|
+
}
|
|
20563
|
+
function assertRequestMetadata(request, protocolVersion, context = {}) {
|
|
20564
|
+
const meta3 = isRecord(request.params?._meta) ? request.params._meta : void 0;
|
|
20565
|
+
const metaVersion = stringValue(meta3?.["io.modelcontextprotocol/protocolVersion"]);
|
|
20566
|
+
if (protocolVersion === MODERN_PROTOCOL_VERSION) {
|
|
20567
|
+
const parsed = ModernClientMetadataSchema.safeParse(meta3);
|
|
20568
|
+
if (!parsed.success) {
|
|
20569
|
+
throw new RequestMetadataMismatchError("_meta", "valid modern client metadata");
|
|
20462
20570
|
}
|
|
20463
|
-
return z.union([literalSchemas[0], literalSchemas[1], ...literalSchemas.slice(2)]);
|
|
20464
20571
|
}
|
|
20465
|
-
if (
|
|
20466
|
-
|
|
20572
|
+
if (context.protocolVersion && metaVersion && context.protocolVersion !== metaVersion) {
|
|
20573
|
+
throw new RequestMetadataMismatchError("protocolVersion", context.protocolVersion, metaVersion);
|
|
20467
20574
|
}
|
|
20468
|
-
|
|
20469
|
-
|
|
20470
|
-
const typeSchemas = type.map((t) => {
|
|
20471
|
-
const typeSchema = { ...schema2, type: t };
|
|
20472
|
-
return convertBaseSchema(typeSchema, ctx);
|
|
20473
|
-
});
|
|
20474
|
-
if (typeSchemas.length === 0) {
|
|
20475
|
-
return z.never();
|
|
20476
|
-
}
|
|
20477
|
-
if (typeSchemas.length === 1) {
|
|
20478
|
-
return typeSchemas[0];
|
|
20479
|
-
}
|
|
20480
|
-
return z.union(typeSchemas);
|
|
20575
|
+
if (context.method && context.method !== request.method) {
|
|
20576
|
+
throw new RequestMetadataMismatchError("method", context.method, request.method);
|
|
20481
20577
|
}
|
|
20482
|
-
|
|
20483
|
-
|
|
20578
|
+
const toolName = typeof request.params?.name === "string" ? request.params.name : void 0;
|
|
20579
|
+
if (context.toolName && context.toolName !== toolName) {
|
|
20580
|
+
throw new RequestMetadataMismatchError("name", context.toolName, toolName);
|
|
20484
20581
|
}
|
|
20485
|
-
|
|
20486
|
-
|
|
20487
|
-
|
|
20488
|
-
|
|
20489
|
-
|
|
20490
|
-
|
|
20491
|
-
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
|
|
20498
|
-
|
|
20499
|
-
|
|
20500
|
-
|
|
20501
|
-
|
|
20502
|
-
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
|
|
20508
|
-
|
|
20509
|
-
|
|
20510
|
-
|
|
20511
|
-
|
|
20512
|
-
|
|
20513
|
-
|
|
20514
|
-
|
|
20515
|
-
|
|
20516
|
-
|
|
20517
|
-
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
} else if (format === "nanoid") {
|
|
20526
|
-
stringSchema = stringSchema.check(z.nanoid());
|
|
20527
|
-
} else if (format === "cuid") {
|
|
20528
|
-
stringSchema = stringSchema.check(z.cuid());
|
|
20529
|
-
} else if (format === "cuid2") {
|
|
20530
|
-
stringSchema = stringSchema.check(z.cuid2());
|
|
20531
|
-
} else if (format === "ulid") {
|
|
20532
|
-
stringSchema = stringSchema.check(z.ulid());
|
|
20533
|
-
} else if (format === "xid") {
|
|
20534
|
-
stringSchema = stringSchema.check(z.xid());
|
|
20535
|
-
} else if (format === "ksuid") {
|
|
20536
|
-
stringSchema = stringSchema.check(z.ksuid());
|
|
20537
|
-
}
|
|
20538
|
-
}
|
|
20539
|
-
if (typeof schema2.minLength === "number") {
|
|
20540
|
-
stringSchema = stringSchema.min(schema2.minLength);
|
|
20541
|
-
}
|
|
20542
|
-
if (typeof schema2.maxLength === "number") {
|
|
20543
|
-
stringSchema = stringSchema.max(schema2.maxLength);
|
|
20544
|
-
}
|
|
20545
|
-
if (schema2.pattern) {
|
|
20546
|
-
stringSchema = stringSchema.regex(new RegExp(schema2.pattern));
|
|
20547
|
-
}
|
|
20548
|
-
zodSchema = stringSchema;
|
|
20549
|
-
break;
|
|
20550
|
-
}
|
|
20551
|
-
case "number":
|
|
20552
|
-
case "integer": {
|
|
20553
|
-
let numberSchema = type === "integer" ? z.number().int() : z.number();
|
|
20554
|
-
if (typeof schema2.minimum === "number") {
|
|
20555
|
-
numberSchema = numberSchema.min(schema2.minimum);
|
|
20556
|
-
}
|
|
20557
|
-
if (typeof schema2.maximum === "number") {
|
|
20558
|
-
numberSchema = numberSchema.max(schema2.maximum);
|
|
20559
|
-
}
|
|
20560
|
-
if (typeof schema2.exclusiveMinimum === "number") {
|
|
20561
|
-
numberSchema = numberSchema.gt(schema2.exclusiveMinimum);
|
|
20562
|
-
} else if (schema2.exclusiveMinimum === true && typeof schema2.minimum === "number") {
|
|
20563
|
-
numberSchema = numberSchema.gt(schema2.minimum);
|
|
20564
|
-
}
|
|
20565
|
-
if (typeof schema2.exclusiveMaximum === "number") {
|
|
20566
|
-
numberSchema = numberSchema.lt(schema2.exclusiveMaximum);
|
|
20567
|
-
} else if (schema2.exclusiveMaximum === true && typeof schema2.maximum === "number") {
|
|
20568
|
-
numberSchema = numberSchema.lt(schema2.maximum);
|
|
20569
|
-
}
|
|
20570
|
-
if (typeof schema2.multipleOf === "number") {
|
|
20571
|
-
numberSchema = numberSchema.multipleOf(schema2.multipleOf);
|
|
20582
|
+
}
|
|
20583
|
+
function isSupportedProtocolVersion(value) {
|
|
20584
|
+
return SUPPORTED_PROTOCOL_VERSIONS.some((version2) => version2 === value);
|
|
20585
|
+
}
|
|
20586
|
+
function jsonRpcSuccess(id, result) {
|
|
20587
|
+
return { jsonrpc: "2.0", id, result };
|
|
20588
|
+
}
|
|
20589
|
+
function jsonRpcFailure(id, error51) {
|
|
20590
|
+
return { jsonrpc: "2.0", id, error: error51 };
|
|
20591
|
+
}
|
|
20592
|
+
function isRecord(value) {
|
|
20593
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20594
|
+
}
|
|
20595
|
+
function stringValue(value) {
|
|
20596
|
+
return typeof value === "string" ? value : void 0;
|
|
20597
|
+
}
|
|
20598
|
+
|
|
20599
|
+
// src/version.ts
|
|
20600
|
+
var VERSION = "0.7.0-alpha.6";
|
|
20601
|
+
|
|
20602
|
+
// src/worker/http.ts
|
|
20603
|
+
var HEALTH_PATH = "/healthz";
|
|
20604
|
+
var READY_PATH = "/readyz";
|
|
20605
|
+
var MCP_PATH = "/mcp";
|
|
20606
|
+
var SESSION_PATH = "/session";
|
|
20607
|
+
var SESSION_TOUCH_PATH = "/session/touch";
|
|
20608
|
+
var WORKER_SERVICE_ID = "moodle-mcp";
|
|
20609
|
+
var WORKER_SERVICE_VERSION = VERSION;
|
|
20610
|
+
function createWorkerHandler(dependencies) {
|
|
20611
|
+
return {
|
|
20612
|
+
async fetch(request, env) {
|
|
20613
|
+
const url2 = new URL(request.url);
|
|
20614
|
+
const authorityProblem = validateRequestAuthority(request, url2, env.EXPECTED_HOSTS ?? env.EXPECTED_HOST);
|
|
20615
|
+
if (authorityProblem) return authorityProblem;
|
|
20616
|
+
if (hasQueryCredential(url2)) return unauthorized("Bearer credentials are not accepted in the query string.");
|
|
20617
|
+
if (url2.pathname === HEALTH_PATH && request.method === "GET") {
|
|
20618
|
+
return Response.json(
|
|
20619
|
+
{ status: "pass", serviceId: WORKER_SERVICE_ID, version: WORKER_SERVICE_VERSION },
|
|
20620
|
+
{ headers: { "content-type": "application/health+json; charset=utf-8" } }
|
|
20621
|
+
);
|
|
20572
20622
|
}
|
|
20573
|
-
|
|
20574
|
-
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
zodSchema = z.boolean();
|
|
20578
|
-
break;
|
|
20579
|
-
}
|
|
20580
|
-
case "null": {
|
|
20581
|
-
zodSchema = z.null();
|
|
20582
|
-
break;
|
|
20583
|
-
}
|
|
20584
|
-
case "object": {
|
|
20585
|
-
const shape = {};
|
|
20586
|
-
const properties = schema2.properties || {};
|
|
20587
|
-
const requiredSet = new Set(schema2.required || []);
|
|
20588
|
-
for (const [key, propSchema] of Object.entries(properties)) {
|
|
20589
|
-
const propZodSchema = convertSchema(propSchema, ctx);
|
|
20590
|
-
shape[key] = requiredSet.has(key) ? propZodSchema : propZodSchema.optional();
|
|
20623
|
+
if (url2.pathname === MCP_PATH && request.method !== "POST") {
|
|
20624
|
+
return problemResponse(405, "METHOD_NOT_ALLOWED", "Method Not Allowed", "The MCP transport accepts POST requests only.", {
|
|
20625
|
+
allow: "POST"
|
|
20626
|
+
});
|
|
20591
20627
|
}
|
|
20592
|
-
if (
|
|
20593
|
-
|
|
20594
|
-
|
|
20595
|
-
|
|
20596
|
-
|
|
20597
|
-
|
|
20628
|
+
if (url2.pathname === MCP_PATH) {
|
|
20629
|
+
if (!await verifyBearerToken(request.headers.get("authorization"), [
|
|
20630
|
+
env.MCP_ACCESS_TOKEN_DIGEST,
|
|
20631
|
+
activePreviousDigest(env.MCP_ACCESS_TOKEN_PREVIOUS_DIGEST, env.TOKEN_OVERLAP_EXPIRES_AT)
|
|
20632
|
+
])) {
|
|
20633
|
+
return unauthorized();
|
|
20598
20634
|
}
|
|
20599
|
-
const
|
|
20600
|
-
|
|
20601
|
-
zodSchema = z.intersection(objectSchema2, recordSchema);
|
|
20602
|
-
break;
|
|
20635
|
+
const server = typeof dependencies.mcpServer === "function" ? dependencies.mcpServer(env) : dependencies.mcpServer;
|
|
20636
|
+
return handleMcpRequest(request, server);
|
|
20603
20637
|
}
|
|
20604
|
-
if (
|
|
20605
|
-
|
|
20606
|
-
const
|
|
20607
|
-
|
|
20608
|
-
|
|
20609
|
-
|
|
20610
|
-
|
|
20611
|
-
|
|
20612
|
-
|
|
20613
|
-
|
|
20614
|
-
|
|
20615
|
-
schemasToIntersect.push(z.object(shape).passthrough());
|
|
20616
|
-
}
|
|
20617
|
-
schemasToIntersect.push(...looseRecords);
|
|
20618
|
-
if (schemasToIntersect.length === 0) {
|
|
20619
|
-
zodSchema = z.object({}).passthrough();
|
|
20620
|
-
} else if (schemasToIntersect.length === 1) {
|
|
20621
|
-
zodSchema = schemasToIntersect[0];
|
|
20622
|
-
} else {
|
|
20623
|
-
let result = z.intersection(schemasToIntersect[0], schemasToIntersect[1]);
|
|
20624
|
-
for (let i = 2; i < schemasToIntersect.length; i++) {
|
|
20625
|
-
result = z.intersection(result, schemasToIntersect[i]);
|
|
20626
|
-
}
|
|
20627
|
-
zodSchema = result;
|
|
20628
|
-
}
|
|
20629
|
-
break;
|
|
20638
|
+
if (url2.pathname === READY_PATH && request.method === "GET") {
|
|
20639
|
+
if (!await authorizeSessionSync(request, env)) return unauthorized();
|
|
20640
|
+
const broker = resolveBroker(dependencies, env);
|
|
20641
|
+
if (broker instanceof Response) return broker;
|
|
20642
|
+
return broker.ready();
|
|
20643
|
+
}
|
|
20644
|
+
if (url2.pathname === SESSION_PATH && request.method === "PUT") {
|
|
20645
|
+
if (!await authorizeSessionSync(request, env)) return unauthorized();
|
|
20646
|
+
const broker = resolveBroker(dependencies, env);
|
|
20647
|
+
if (broker instanceof Response) return broker;
|
|
20648
|
+
return broker.replaceSession(request);
|
|
20630
20649
|
}
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
20636
|
-
} else {
|
|
20637
|
-
zodSchema = objectSchema.passthrough();
|
|
20650
|
+
if (url2.pathname === SESSION_TOUCH_PATH && request.method === "POST") {
|
|
20651
|
+
if (!await authorizeSessionSync(request, env)) return unauthorized();
|
|
20652
|
+
const broker = resolveBroker(dependencies, env);
|
|
20653
|
+
if (broker instanceof Response) return broker;
|
|
20654
|
+
return broker.touch();
|
|
20638
20655
|
}
|
|
20639
|
-
|
|
20640
|
-
|
|
20641
|
-
|
|
20642
|
-
|
|
20643
|
-
|
|
20644
|
-
if (prefixItems && Array.isArray(prefixItems)) {
|
|
20645
|
-
const tupleItems = prefixItems.map((item) => convertSchema(item, ctx));
|
|
20646
|
-
const rest = items && typeof items === "object" && !Array.isArray(items) ? convertSchema(items, ctx) : void 0;
|
|
20647
|
-
if (rest) {
|
|
20648
|
-
zodSchema = z.tuple(tupleItems).rest(rest);
|
|
20649
|
-
} else {
|
|
20650
|
-
zodSchema = z.tuple(tupleItems);
|
|
20651
|
-
}
|
|
20652
|
-
if (typeof schema2.minItems === "number") {
|
|
20653
|
-
zodSchema = zodSchema.check(z.minLength(schema2.minItems));
|
|
20654
|
-
}
|
|
20655
|
-
if (typeof schema2.maxItems === "number") {
|
|
20656
|
-
zodSchema = zodSchema.check(z.maxLength(schema2.maxItems));
|
|
20657
|
-
}
|
|
20658
|
-
} else if (Array.isArray(items)) {
|
|
20659
|
-
const tupleItems = items.map((item) => convertSchema(item, ctx));
|
|
20660
|
-
const rest = schema2.additionalItems && typeof schema2.additionalItems === "object" ? convertSchema(schema2.additionalItems, ctx) : void 0;
|
|
20661
|
-
if (rest) {
|
|
20662
|
-
zodSchema = z.tuple(tupleItems).rest(rest);
|
|
20663
|
-
} else {
|
|
20664
|
-
zodSchema = z.tuple(tupleItems);
|
|
20665
|
-
}
|
|
20666
|
-
if (typeof schema2.minItems === "number") {
|
|
20667
|
-
zodSchema = zodSchema.check(z.minLength(schema2.minItems));
|
|
20668
|
-
}
|
|
20669
|
-
if (typeof schema2.maxItems === "number") {
|
|
20670
|
-
zodSchema = zodSchema.check(z.maxLength(schema2.maxItems));
|
|
20671
|
-
}
|
|
20672
|
-
} else if (items !== void 0) {
|
|
20673
|
-
const element = convertSchema(items, ctx);
|
|
20674
|
-
let arraySchema = z.array(element);
|
|
20675
|
-
if (typeof schema2.minItems === "number") {
|
|
20676
|
-
arraySchema = arraySchema.min(schema2.minItems);
|
|
20677
|
-
}
|
|
20678
|
-
if (typeof schema2.maxItems === "number") {
|
|
20679
|
-
arraySchema = arraySchema.max(schema2.maxItems);
|
|
20680
|
-
}
|
|
20681
|
-
zodSchema = arraySchema;
|
|
20682
|
-
} else {
|
|
20683
|
-
zodSchema = z.array(z.any());
|
|
20656
|
+
if (url2.pathname === READY_PATH || url2.pathname === SESSION_PATH || url2.pathname === SESSION_TOUCH_PATH) {
|
|
20657
|
+
const method = url2.pathname === READY_PATH ? "GET" : url2.pathname === SESSION_PATH ? "PUT" : "POST";
|
|
20658
|
+
return problemResponse(405, "METHOD_NOT_ALLOWED", "Method Not Allowed", "The session route does not accept this method.", {
|
|
20659
|
+
allow: method
|
|
20660
|
+
});
|
|
20684
20661
|
}
|
|
20685
|
-
|
|
20662
|
+
return problemResponse(404, "NOT_FOUND", "Not Found", "The requested route does not exist.");
|
|
20686
20663
|
}
|
|
20687
|
-
|
|
20688
|
-
throw new Error(`Unsupported type: ${type}`);
|
|
20689
|
-
}
|
|
20690
|
-
return zodSchema;
|
|
20664
|
+
};
|
|
20691
20665
|
}
|
|
20692
|
-
function
|
|
20693
|
-
|
|
20694
|
-
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
|
|
20698
|
-
|
|
20699
|
-
|
|
20700
|
-
|
|
20701
|
-
|
|
20666
|
+
function createDurableObjectBrokerApi(namespace) {
|
|
20667
|
+
const stub = namespace.get(namespace.idFromName("primary"));
|
|
20668
|
+
return {
|
|
20669
|
+
async handleMcp(body, context) {
|
|
20670
|
+
const response = await stub.fetch(new Request("https://session-broker/mcp", {
|
|
20671
|
+
method: "POST",
|
|
20672
|
+
headers: { "content-type": "application/json" },
|
|
20673
|
+
body: JSON.stringify({ request: body, context })
|
|
20674
|
+
}));
|
|
20675
|
+
if (!response.ok) {
|
|
20676
|
+
const problem = await safeProblem(response);
|
|
20677
|
+
throw new SessionBrokerMcpError(response.status, problem?.code);
|
|
20678
|
+
}
|
|
20679
|
+
const envelope = await response.json();
|
|
20680
|
+
return envelope.response ?? null;
|
|
20681
|
+
},
|
|
20682
|
+
ready: () => stub.fetch(new Request("https://session-broker/readyz")),
|
|
20683
|
+
async replaceSession(request) {
|
|
20684
|
+
return stub.fetch(new Request("https://session-broker/session", {
|
|
20685
|
+
method: "PUT",
|
|
20686
|
+
headers: { "content-type": request.headers.get("content-type") ?? "application/json" },
|
|
20687
|
+
body: await request.arrayBuffer()
|
|
20688
|
+
}));
|
|
20689
|
+
},
|
|
20690
|
+
touch: () => stub.fetch(new Request("https://session-broker/session/touch", { method: "POST" }))
|
|
20691
|
+
};
|
|
20692
|
+
}
|
|
20693
|
+
var SessionBrokerMcpError = class extends Error {
|
|
20694
|
+
constructor(status, code) {
|
|
20695
|
+
super("The session broker could not complete the MCP request.");
|
|
20696
|
+
this.status = status;
|
|
20697
|
+
this.code = code;
|
|
20698
|
+
this.name = "SessionBrokerMcpError";
|
|
20702
20699
|
}
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20700
|
+
status;
|
|
20701
|
+
code;
|
|
20702
|
+
};
|
|
20703
|
+
function resolveBroker(dependencies, env) {
|
|
20704
|
+
if (dependencies.broker) return dependencies.broker(env);
|
|
20705
|
+
if (env.SESSION_BROKER) return createDurableObjectBrokerApi(env.SESSION_BROKER);
|
|
20706
|
+
return problemResponse(503, "SESSION_BROKER_UNAVAILABLE", "Service Unavailable", "The session broker is unavailable.");
|
|
20707
|
+
}
|
|
20708
|
+
function validateRequestAuthority(request, url2, configuredHosts) {
|
|
20709
|
+
const expectedHosts = (configuredHosts ?? url2.host).split(",").map((host) => host.trim().toLowerCase()).filter(Boolean);
|
|
20710
|
+
const requestHost = (request.headers.get("host") ?? url2.host).toLowerCase();
|
|
20711
|
+
if (requestHost !== url2.host.toLowerCase() || !expectedHosts.includes(requestHost)) {
|
|
20712
|
+
return problemResponse(403, "INVALID_HOST", "Forbidden", "The request Host is not allowed.");
|
|
20707
20713
|
}
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
|
|
20713
|
-
|
|
20714
|
-
for (let i = startIdx; i < schema2.allOf.length; i++) {
|
|
20715
|
-
result = z.intersection(result, convertSchema(schema2.allOf[i], ctx));
|
|
20716
|
-
}
|
|
20717
|
-
baseSchema = result;
|
|
20714
|
+
const origin = request.headers.get("origin");
|
|
20715
|
+
if (!origin) return null;
|
|
20716
|
+
try {
|
|
20717
|
+
const parsed = new URL(origin);
|
|
20718
|
+
if (parsed.origin !== url2.origin || !expectedHosts.includes(parsed.host.toLowerCase())) {
|
|
20719
|
+
return problemResponse(403, "INVALID_ORIGIN", "Forbidden", "The request Origin is not allowed.");
|
|
20718
20720
|
}
|
|
20721
|
+
} catch {
|
|
20722
|
+
return problemResponse(403, "INVALID_ORIGIN", "Forbidden", "The request Origin is not allowed.");
|
|
20719
20723
|
}
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20724
|
+
return null;
|
|
20725
|
+
}
|
|
20726
|
+
function unauthorized(detail = "A valid Bearer token is required.") {
|
|
20727
|
+
return problemResponse(401, "INVALID_BEARER_TOKEN", "Unauthorized", detail, {
|
|
20728
|
+
"www-authenticate": 'Bearer realm="moodle-mcp"'
|
|
20729
|
+
});
|
|
20730
|
+
}
|
|
20731
|
+
async function handleMcpRequest(request, server) {
|
|
20732
|
+
if (!request.headers.get("content-type")?.toLowerCase().startsWith("application/json")) {
|
|
20733
|
+
return problemResponse(415, "UNSUPPORTED_MEDIA_TYPE", "Unsupported Media Type", "MCP requests must use application/json.");
|
|
20725
20734
|
}
|
|
20726
|
-
|
|
20727
|
-
|
|
20735
|
+
let body;
|
|
20736
|
+
try {
|
|
20737
|
+
body = await request.json();
|
|
20738
|
+
} catch {
|
|
20739
|
+
return problemResponse(400, "INVALID_JSON", "Bad Request", "The request body is not valid JSON.");
|
|
20728
20740
|
}
|
|
20729
|
-
const
|
|
20730
|
-
const
|
|
20731
|
-
|
|
20732
|
-
|
|
20733
|
-
|
|
20741
|
+
const protocolVersion = request.headers.get("mcp-protocol-version");
|
|
20742
|
+
const metadataProblem = validateProtocolMetadata(request.headers, body, protocolVersion);
|
|
20743
|
+
if (metadataProblem) return metadataProblem;
|
|
20744
|
+
try {
|
|
20745
|
+
const response = await server.handle(body, {
|
|
20746
|
+
protocolVersion,
|
|
20747
|
+
method: request.headers.get("mcp-method") ?? void 0,
|
|
20748
|
+
toolName: request.headers.get("mcp-name") ?? void 0
|
|
20749
|
+
});
|
|
20750
|
+
if (response === null) return new Response(null, { status: 202 });
|
|
20751
|
+
if (isUnsupportedProtocolVersionResponse(response)) {
|
|
20752
|
+
return Response.json(response, {
|
|
20753
|
+
status: 400,
|
|
20754
|
+
headers: { "cache-control": "private, no-store" }
|
|
20755
|
+
});
|
|
20734
20756
|
}
|
|
20735
|
-
|
|
20736
|
-
|
|
20737
|
-
|
|
20738
|
-
|
|
20739
|
-
|
|
20757
|
+
if (request.headers.get("accept")?.toLowerCase().includes("text/event-stream")) {
|
|
20758
|
+
return new Response(`event: message
|
|
20759
|
+
data: ${JSON.stringify(response)}
|
|
20760
|
+
|
|
20761
|
+
`, {
|
|
20762
|
+
headers: {
|
|
20763
|
+
"cache-control": "private, no-store",
|
|
20764
|
+
"content-type": "text/event-stream; charset=utf-8"
|
|
20765
|
+
}
|
|
20766
|
+
});
|
|
20740
20767
|
}
|
|
20741
|
-
|
|
20742
|
-
|
|
20743
|
-
if (
|
|
20744
|
-
|
|
20768
|
+
return Response.json(response, { headers: { "cache-control": "private, no-store" } });
|
|
20769
|
+
} catch (error51) {
|
|
20770
|
+
if (error51 instanceof SessionBrokerMcpError) {
|
|
20771
|
+
const status = error51.status === 503 ? 503 : 500;
|
|
20772
|
+
const code = error51.code === "SESSION_MISSING" || error51.code === "SESSION_EXPIRED" ? error51.code : "SESSION_UNAVAILABLE";
|
|
20773
|
+
return problemResponse(status, code, "Service Unavailable", "The Moodle session is not ready.");
|
|
20774
|
+
}
|
|
20775
|
+
if (error51 instanceof Error && error51.name === "UnsupportedProtocolVersionError") {
|
|
20776
|
+
const requested = "protocolVersion" in error51 && typeof error51.protocolVersion === "string" ? error51.protocolVersion : protocolVersion ?? "unknown";
|
|
20777
|
+
return Response.json({
|
|
20778
|
+
jsonrpc: "2.0",
|
|
20779
|
+
id: jsonRpcRequestId(body),
|
|
20780
|
+
error: {
|
|
20781
|
+
code: -32022,
|
|
20782
|
+
message: "Unsupported protocol version",
|
|
20783
|
+
data: { supported: [...SUPPORTED_PROTOCOL_VERSIONS], requested }
|
|
20784
|
+
}
|
|
20785
|
+
}, {
|
|
20786
|
+
status: 400,
|
|
20787
|
+
headers: { "cache-control": "private, no-store" }
|
|
20788
|
+
});
|
|
20745
20789
|
}
|
|
20790
|
+
return problemResponse(500, "MCP_REQUEST_FAILED", "Internal Server Error", "The MCP request could not be completed.");
|
|
20746
20791
|
}
|
|
20747
|
-
|
|
20748
|
-
|
|
20792
|
+
}
|
|
20793
|
+
async function safeProblem(response) {
|
|
20794
|
+
try {
|
|
20795
|
+
const value = await response.json();
|
|
20796
|
+
return isRecord2(value) && (value.code === void 0 || typeof value.code === "string") ? value : null;
|
|
20797
|
+
} catch {
|
|
20798
|
+
return null;
|
|
20749
20799
|
}
|
|
20750
|
-
|
|
20751
|
-
|
|
20800
|
+
}
|
|
20801
|
+
function validateProtocolMetadata(headers, body, protocolVersion) {
|
|
20802
|
+
if (!protocolVersion) {
|
|
20803
|
+
return headerMismatchResponse(body, "MCP-Protocol-Version is required.");
|
|
20752
20804
|
}
|
|
20753
|
-
|
|
20805
|
+
const params = isRecord2(body.params) ? body.params : void 0;
|
|
20806
|
+
const metadata = params && isRecord2(params._meta) ? params._meta : void 0;
|
|
20807
|
+
const bodyVersion = metadata?.["io.modelcontextprotocol/protocolVersion"];
|
|
20808
|
+
if (bodyVersion !== void 0 && bodyVersion !== protocolVersion) {
|
|
20809
|
+
return headerMismatchResponse(body, "MCP protocol metadata does not match the HTTP headers.");
|
|
20810
|
+
}
|
|
20811
|
+
if (typeof body.method !== "string") {
|
|
20812
|
+
return problemResponse(400, "MCP_PROTOCOL_METADATA_INVALID", "Bad Request", "The JSON-RPC method is required.");
|
|
20813
|
+
}
|
|
20814
|
+
const headerMethod = headers.get("mcp-method");
|
|
20815
|
+
const headerName = headers.get("mcp-name") ?? void 0;
|
|
20816
|
+
const paramsName = params && typeof params.name === "string" ? params.name : void 0;
|
|
20817
|
+
const requiresModernHeaders = protocolVersion !== LEGACY_PROTOCOL_VERSION;
|
|
20818
|
+
const hasModernHeaders = headerMethod !== null || headerName !== void 0;
|
|
20819
|
+
if ((requiresModernHeaders || hasModernHeaders) && (headerMethod !== body.method || headerName !== paramsName)) {
|
|
20820
|
+
return headerMismatchResponse(body, "MCP method metadata does not match the JSON-RPC request.");
|
|
20821
|
+
}
|
|
20822
|
+
return null;
|
|
20823
|
+
}
|
|
20824
|
+
function headerMismatchResponse(body, detail) {
|
|
20825
|
+
return Response.json({
|
|
20826
|
+
jsonrpc: "2.0",
|
|
20827
|
+
id: jsonRpcRequestId(body),
|
|
20828
|
+
error: {
|
|
20829
|
+
code: -32020,
|
|
20830
|
+
message: "HeaderMismatch",
|
|
20831
|
+
data: { detail }
|
|
20832
|
+
}
|
|
20833
|
+
}, {
|
|
20834
|
+
status: 400,
|
|
20835
|
+
headers: { "cache-control": "private, no-store" }
|
|
20836
|
+
});
|
|
20837
|
+
}
|
|
20838
|
+
function jsonRpcRequestId(body) {
|
|
20839
|
+
return typeof body.id === "string" || typeof body.id === "number" || body.id === null ? body.id : null;
|
|
20840
|
+
}
|
|
20841
|
+
function isUnsupportedProtocolVersionResponse(value) {
|
|
20842
|
+
if (!isRecord2(value) || !isRecord2(value.error)) return false;
|
|
20843
|
+
return value.jsonrpc === "2.0" && value.error.code === -32022;
|
|
20844
|
+
}
|
|
20845
|
+
function authorizeSessionSync(request, env) {
|
|
20846
|
+
return verifyBearerToken(request.headers.get("authorization"), [
|
|
20847
|
+
env.SESSION_SYNC_TOKEN_DIGEST,
|
|
20848
|
+
activePreviousDigest(env.SESSION_SYNC_TOKEN_PREVIOUS_DIGEST, env.TOKEN_OVERLAP_EXPIRES_AT)
|
|
20849
|
+
]);
|
|
20850
|
+
}
|
|
20851
|
+
function activePreviousDigest(digest, expiresAt2) {
|
|
20852
|
+
if (!digest || !expiresAt2) return void 0;
|
|
20853
|
+
const expiration = Number(expiresAt2);
|
|
20854
|
+
return Number.isFinite(expiration) && Date.now() < expiration ? digest : void 0;
|
|
20754
20855
|
}
|
|
20755
|
-
function
|
|
20756
|
-
|
|
20757
|
-
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20763
|
-
|
|
20856
|
+
function isRecord2(value) {
|
|
20857
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20858
|
+
}
|
|
20859
|
+
|
|
20860
|
+
// src/worker/crypto.ts
|
|
20861
|
+
async function createEncryptionKeyring(currentSecret, previousSecret) {
|
|
20862
|
+
if (!currentSecret) throw new Error("SESSION_ENCRYPTION_KEY is required.");
|
|
20863
|
+
const current = await deriveEncryptionKey(currentSecret);
|
|
20864
|
+
const keys = /* @__PURE__ */ new Map([[current.id, current.key]]);
|
|
20865
|
+
if (previousSecret) {
|
|
20866
|
+
const previous = await deriveEncryptionKey(previousSecret);
|
|
20867
|
+
keys.set(previous.id, previous.key);
|
|
20764
20868
|
}
|
|
20765
|
-
|
|
20766
|
-
|
|
20767
|
-
|
|
20768
|
-
|
|
20769
|
-
|
|
20770
|
-
|
|
20771
|
-
|
|
20772
|
-
|
|
20773
|
-
|
|
20869
|
+
return { current, keys };
|
|
20870
|
+
}
|
|
20871
|
+
async function encryptValue(value, keyring) {
|
|
20872
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
20873
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
20874
|
+
{ name: "AES-GCM", iv },
|
|
20875
|
+
keyring.current.key,
|
|
20876
|
+
new TextEncoder().encode(value)
|
|
20877
|
+
);
|
|
20878
|
+
return JSON.stringify({
|
|
20879
|
+
version: 1,
|
|
20880
|
+
keyId: keyring.current.id,
|
|
20881
|
+
iv: toBase64Url(iv),
|
|
20882
|
+
ciphertext: toBase64Url(new Uint8Array(ciphertext))
|
|
20883
|
+
});
|
|
20884
|
+
}
|
|
20885
|
+
async function decryptValue(encrypted, keyring) {
|
|
20886
|
+
const envelope = parseEnvelope(encrypted);
|
|
20887
|
+
const key = keyring.keys.get(envelope.keyId);
|
|
20888
|
+
if (!key) throw new Error("The session encryption key is unavailable.");
|
|
20889
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
20890
|
+
{ name: "AES-GCM", iv: fromBase64Url(envelope.iv) },
|
|
20891
|
+
key,
|
|
20892
|
+
fromBase64Url(envelope.ciphertext)
|
|
20893
|
+
);
|
|
20894
|
+
return {
|
|
20895
|
+
value: new TextDecoder().decode(plaintext),
|
|
20896
|
+
keyId: envelope.keyId,
|
|
20897
|
+
needsRotation: envelope.keyId !== keyring.current.id
|
|
20774
20898
|
};
|
|
20775
|
-
return convertSchema(normalized, ctx);
|
|
20776
20899
|
}
|
|
20777
|
-
|
|
20778
|
-
|
|
20779
|
-
|
|
20780
|
-
|
|
20781
|
-
|
|
20782
|
-
boolean: () => boolean3,
|
|
20783
|
-
date: () => date4,
|
|
20784
|
-
number: () => number3,
|
|
20785
|
-
string: () => string3
|
|
20786
|
-
});
|
|
20787
|
-
function string3(params) {
|
|
20788
|
-
return _coercedString(ZodString, params);
|
|
20900
|
+
async function deriveEncryptionKey(secret) {
|
|
20901
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(secret)));
|
|
20902
|
+
const key = await crypto.subtle.importKey("raw", digest, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
20903
|
+
const id = Array.from(digest.slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
20904
|
+
return { id, key };
|
|
20789
20905
|
}
|
|
20790
|
-
function
|
|
20791
|
-
|
|
20906
|
+
function parseEnvelope(value) {
|
|
20907
|
+
let parsed;
|
|
20908
|
+
try {
|
|
20909
|
+
parsed = JSON.parse(value);
|
|
20910
|
+
} catch {
|
|
20911
|
+
throw new Error("The encrypted session record is invalid.");
|
|
20912
|
+
}
|
|
20913
|
+
if (!isRecord3(parsed) || parsed.version !== 1 || typeof parsed.keyId !== "string" || typeof parsed.iv !== "string" || typeof parsed.ciphertext !== "string") {
|
|
20914
|
+
throw new Error("The encrypted session record is invalid.");
|
|
20915
|
+
}
|
|
20916
|
+
return parsed;
|
|
20792
20917
|
}
|
|
20793
|
-
function
|
|
20794
|
-
|
|
20918
|
+
function toBase64Url(bytes) {
|
|
20919
|
+
let binary = "";
|
|
20920
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
20921
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
20795
20922
|
}
|
|
20796
|
-
function
|
|
20797
|
-
|
|
20923
|
+
function fromBase64Url(value) {
|
|
20924
|
+
const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
20925
|
+
const binary = atob(normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="));
|
|
20926
|
+
const bytes = new Uint8Array(binary.length);
|
|
20927
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
20928
|
+
return bytes;
|
|
20798
20929
|
}
|
|
20799
|
-
function
|
|
20800
|
-
return
|
|
20930
|
+
function isRecord3(value) {
|
|
20931
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20801
20932
|
}
|
|
20802
20933
|
|
|
20803
|
-
// node_modules/zod/v4/classic/external.js
|
|
20804
|
-
config(en_default());
|
|
20805
|
-
|
|
20806
20934
|
// src/constants.ts
|
|
20807
20935
|
var PACKAGE_NAME = "moodle-cli";
|
|
20808
20936
|
var NPM_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -20917,19 +21045,19 @@ function parseUserInfo(value) {
|
|
|
20917
21045
|
const data = asRecord(value);
|
|
20918
21046
|
return {
|
|
20919
21047
|
userid: numberValue(data.userid),
|
|
20920
|
-
username:
|
|
20921
|
-
fullname:
|
|
20922
|
-
sitename:
|
|
20923
|
-
siteurl:
|
|
20924
|
-
lang:
|
|
21048
|
+
username: stringValue2(data.username),
|
|
21049
|
+
fullname: stringValue2(data.fullname),
|
|
21050
|
+
sitename: stringValue2(data.sitename),
|
|
21051
|
+
siteurl: stringValue2(data.siteurl),
|
|
21052
|
+
lang: stringValue2(data.lang)
|
|
20925
21053
|
};
|
|
20926
21054
|
}
|
|
20927
21055
|
function parseCourse(value, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
20928
21056
|
const data = asRecord(value);
|
|
20929
21057
|
const course = {
|
|
20930
21058
|
id: numberValue(data.id),
|
|
20931
|
-
shortname:
|
|
20932
|
-
fullname:
|
|
21059
|
+
shortname: stringValue2(data.shortname),
|
|
21060
|
+
fullname: stringValue2(data.fullname),
|
|
20933
21061
|
category: numberValue(data.category),
|
|
20934
21062
|
visible: booleanValue(data.visible, true),
|
|
20935
21063
|
startdate: numberValue(data.startdate)
|
|
@@ -20947,21 +21075,21 @@ function parseActivity(value) {
|
|
|
20947
21075
|
const data = asRecord(value);
|
|
20948
21076
|
return {
|
|
20949
21077
|
id: numberValue(data.id),
|
|
20950
|
-
name:
|
|
20951
|
-
modname:
|
|
20952
|
-
url:
|
|
21078
|
+
name: stringValue2(data.name),
|
|
21079
|
+
modname: stringValue2(data.modname),
|
|
21080
|
+
url: stringValue2(data.url),
|
|
20953
21081
|
visible: booleanValue(data.visible, true),
|
|
20954
|
-
description:
|
|
21082
|
+
description: stringValue2(data.description)
|
|
20955
21083
|
};
|
|
20956
21084
|
}
|
|
20957
21085
|
function parseSection(value) {
|
|
20958
21086
|
const data = asRecord(value);
|
|
20959
21087
|
return {
|
|
20960
21088
|
id: numberValue(data.id),
|
|
20961
|
-
name:
|
|
21089
|
+
name: stringValue2(data.name),
|
|
20962
21090
|
section: numberValue(data.section),
|
|
20963
21091
|
visible: booleanValue(data.visible, true),
|
|
20964
|
-
summary:
|
|
21092
|
+
summary: stringValue2(data.summary),
|
|
20965
21093
|
activities: asArray(data.modules).map((item) => parseActivity(item))
|
|
20966
21094
|
};
|
|
20967
21095
|
}
|
|
@@ -20975,13 +21103,13 @@ function parseCourseFormatState(value, baseUrl) {
|
|
|
20975
21103
|
for (const item of asArray(state.cm)) {
|
|
20976
21104
|
const data = asRecord(item);
|
|
20977
21105
|
const id = numberValue(data.id);
|
|
20978
|
-
const sectionId =
|
|
20979
|
-
const module =
|
|
21106
|
+
const sectionId = stringValue2(data.sectionid);
|
|
21107
|
+
const module = stringValue2(data.module) || stringValue2(data.plugin).replace(/^mod_/u, "") || stringValue2(data.modname).toLowerCase();
|
|
20980
21108
|
const activity = {
|
|
20981
21109
|
id,
|
|
20982
21110
|
name: htmlText(data.name, baseUrl),
|
|
20983
21111
|
modname: module.toLowerCase(),
|
|
20984
|
-
url:
|
|
21112
|
+
url: stringValue2(data.url) ? resolveUrl(baseUrl, stringValue2(data.url)) : "",
|
|
20985
21113
|
visible: booleanValue(data.visible, true) && booleanValue(data.uservisible, true) && !booleanValue(data.stealth),
|
|
20986
21114
|
description: htmlText(data.content ?? data.description, baseUrl)
|
|
20987
21115
|
};
|
|
@@ -20994,7 +21122,7 @@ function parseCourseFormatState(value, baseUrl) {
|
|
|
20994
21122
|
const data = asRecord(item);
|
|
20995
21123
|
const id = numberValue(data.id);
|
|
20996
21124
|
const hasActivityList = Array.isArray(data.cmlist);
|
|
20997
|
-
const listedActivities = asArray(data.cmlist).map((activityId) => activities.get(
|
|
21125
|
+
const listedActivities = asArray(data.cmlist).map((activityId) => activities.get(stringValue2(activityId))).filter((activity) => activity !== void 0);
|
|
20998
21126
|
return {
|
|
20999
21127
|
id,
|
|
21000
21128
|
name: htmlText(data.title || data.rawtitle, baseUrl),
|
|
@@ -21012,18 +21140,18 @@ function parseTodoItem(value) {
|
|
|
21012
21140
|
const progress = course.progress;
|
|
21013
21141
|
return {
|
|
21014
21142
|
id: numberValue(data.id),
|
|
21015
|
-
name:
|
|
21016
|
-
activity_name:
|
|
21017
|
-
modname:
|
|
21143
|
+
name: stringValue2(data.name),
|
|
21144
|
+
activity_name: stringValue2(data.activityname),
|
|
21145
|
+
modname: stringValue2(data.modulename),
|
|
21018
21146
|
course_id: numberValue(course.id),
|
|
21019
|
-
course_name:
|
|
21147
|
+
course_name: stringValue2(course.fullname),
|
|
21020
21148
|
due_at: numberValue(data.timesort) || numberValue(data.timestart),
|
|
21021
21149
|
overdue: booleanValue(data.overdue),
|
|
21022
21150
|
actionable: booleanValue(action.actionable),
|
|
21023
|
-
action_name:
|
|
21024
|
-
action_url:
|
|
21025
|
-
url:
|
|
21026
|
-
event_type:
|
|
21151
|
+
action_name: stringValue2(action.name),
|
|
21152
|
+
action_url: stringValue2(action.url),
|
|
21153
|
+
url: stringValue2(data.url),
|
|
21154
|
+
event_type: stringValue2(data.eventtype),
|
|
21027
21155
|
course_progress: typeof progress === "number" ? progress : void 0
|
|
21028
21156
|
};
|
|
21029
21157
|
}
|
|
@@ -21034,15 +21162,15 @@ function parseAlertNotification(value) {
|
|
|
21034
21162
|
const data = asRecord(value);
|
|
21035
21163
|
return {
|
|
21036
21164
|
id: numberValue(data.id),
|
|
21037
|
-
subject:
|
|
21038
|
-
short_subject:
|
|
21039
|
-
event_type:
|
|
21040
|
-
component:
|
|
21165
|
+
subject: stringValue2(data.subject),
|
|
21166
|
+
short_subject: stringValue2(data.shortenedsubject),
|
|
21167
|
+
event_type: stringValue2(data.eventtype),
|
|
21168
|
+
component: stringValue2(data.component),
|
|
21041
21169
|
created_at: numberValue(data.timecreated),
|
|
21042
|
-
created_pretty:
|
|
21170
|
+
created_pretty: stringValue2(data.timecreatedpretty),
|
|
21043
21171
|
read: booleanValue(data.read),
|
|
21044
|
-
context_url:
|
|
21045
|
-
context_name:
|
|
21172
|
+
context_url: stringValue2(data.contexturl),
|
|
21173
|
+
context_name: stringValue2(data.contexturlname)
|
|
21046
21174
|
};
|
|
21047
21175
|
}
|
|
21048
21176
|
function parseAlertSummary(notificationsData, countsData, unreadCountsData) {
|
|
@@ -21071,20 +21199,20 @@ function parseForumPostAuthor(value) {
|
|
|
21071
21199
|
const urls = asRecord(data.urls);
|
|
21072
21200
|
return {
|
|
21073
21201
|
id: numberValue(data.id),
|
|
21074
|
-
fullname:
|
|
21075
|
-
profile_url:
|
|
21076
|
-
profile_image_url:
|
|
21202
|
+
fullname: stringValue2(data.fullname),
|
|
21203
|
+
profile_url: stringValue2(urls.profile),
|
|
21204
|
+
profile_image_url: stringValue2(urls.profileimage)
|
|
21077
21205
|
};
|
|
21078
21206
|
}
|
|
21079
21207
|
function parseForumPost(value, baseUrl = "") {
|
|
21080
21208
|
const data = asRecord(value);
|
|
21081
21209
|
const urls = asRecord(data.urls);
|
|
21082
|
-
const messageHtml =
|
|
21083
|
-
const structured = htmlToStructuredContent(messageHtml,
|
|
21210
|
+
const messageHtml = stringValue2(data.message);
|
|
21211
|
+
const structured = htmlToStructuredContent(messageHtml, stringValue2(urls.view || urls.discuss) || baseUrl);
|
|
21084
21212
|
return {
|
|
21085
21213
|
id: numberValue(data.id),
|
|
21086
21214
|
discussion_id: numberValue(data.discussionid),
|
|
21087
|
-
subject:
|
|
21215
|
+
subject: stringValue2(data.subject),
|
|
21088
21216
|
message_html: messageHtml,
|
|
21089
21217
|
message_text: structured.text,
|
|
21090
21218
|
image_urls: structured.image_urls,
|
|
@@ -21098,8 +21226,8 @@ function parseForumPost(value, baseUrl = "") {
|
|
|
21098
21226
|
unread: booleanValue(data.unread),
|
|
21099
21227
|
is_deleted: booleanValue(data.isdeleted),
|
|
21100
21228
|
is_private_reply: booleanValue(data.isprivatereply),
|
|
21101
|
-
url:
|
|
21102
|
-
reply_url:
|
|
21229
|
+
url: stringValue2(urls.view || urls.viewisolated),
|
|
21230
|
+
reply_url: stringValue2(urls.reply)
|
|
21103
21231
|
};
|
|
21104
21232
|
}
|
|
21105
21233
|
function parseForumDiscussion(value, discussionId, baseUrl = "") {
|
|
@@ -21111,7 +21239,7 @@ function parseForumDiscussion(value, discussionId, baseUrl = "") {
|
|
|
21111
21239
|
course_id: numberValue(data.courseid),
|
|
21112
21240
|
forum_id: numberValue(data.forumid),
|
|
21113
21241
|
group_id: numberValue(data.groupid),
|
|
21114
|
-
group_name:
|
|
21242
|
+
group_name: stringValue2(data.groupname),
|
|
21115
21243
|
url: posts[0]?.url ? posts[0].url.split("#", 1)[0] : "",
|
|
21116
21244
|
posts
|
|
21117
21245
|
};
|
|
@@ -21122,7 +21250,7 @@ function asRecord(value) {
|
|
|
21122
21250
|
function asArray(value) {
|
|
21123
21251
|
return Array.isArray(value) ? value : [];
|
|
21124
21252
|
}
|
|
21125
|
-
function
|
|
21253
|
+
function stringValue2(value) {
|
|
21126
21254
|
return typeof value === "string" ? value : value == null ? "" : String(value);
|
|
21127
21255
|
}
|
|
21128
21256
|
function numberValue(value) {
|
|
@@ -21149,7 +21277,7 @@ function parseJsonValue(value) {
|
|
|
21149
21277
|
}
|
|
21150
21278
|
}
|
|
21151
21279
|
function htmlText(value, baseUrl) {
|
|
21152
|
-
return htmlToStructuredContent(
|
|
21280
|
+
return htmlToStructuredContent(stringValue2(value), baseUrl).text;
|
|
21153
21281
|
}
|
|
21154
21282
|
|
|
21155
21283
|
// src/scraper.ts
|
|
@@ -21183,7 +21311,7 @@ function parseMoodleErrorHtml(html) {
|
|
|
21183
21311
|
function parsePageContext(html, baseUrl) {
|
|
21184
21312
|
const root = (0, import_node_html_parser2.parse)(html);
|
|
21185
21313
|
const config2 = parseMoodleConfig(html);
|
|
21186
|
-
const sesskey =
|
|
21314
|
+
const sesskey = stringValue3(config2.sesskey).trim();
|
|
21187
21315
|
const userid = numberValue2(config2.userId) || numberValue2(root.querySelector("[data-user-id]")?.getAttribute("data-user-id"));
|
|
21188
21316
|
if (!sesskey || !userid) {
|
|
21189
21317
|
throw new Error("Session appears invalid: could not load authenticated Moodle context");
|
|
@@ -21196,7 +21324,7 @@ function parsePageContext(html, baseUrl) {
|
|
|
21196
21324
|
fullname: cleanNodeText(root.querySelector(".userfullname")),
|
|
21197
21325
|
sitename: extractSitename(root),
|
|
21198
21326
|
siteurl: baseUrl,
|
|
21199
|
-
lang:
|
|
21327
|
+
lang: stringValue3(config2.language) || root.querySelector("html")?.getAttribute("lang") || ""
|
|
21200
21328
|
}
|
|
21201
21329
|
};
|
|
21202
21330
|
}
|
|
@@ -21266,9 +21394,9 @@ function parseCourseSectionNumbers(html, courseId) {
|
|
|
21266
21394
|
for (const href of hrefs) {
|
|
21267
21395
|
const url2 = parseMaybeUrl(href.replace(/&/gu, "&"), "https://moodle.invalid");
|
|
21268
21396
|
const id = url2?.searchParams.get("id");
|
|
21269
|
-
const
|
|
21270
|
-
if (id === String(courseId) &&
|
|
21271
|
-
const section = Number(
|
|
21397
|
+
const sectionValue2 = url2?.searchParams.get("section");
|
|
21398
|
+
if (id === String(courseId) && sectionValue2 && /^\d+$/.test(sectionValue2)) {
|
|
21399
|
+
const section = Number(sectionValue2);
|
|
21272
21400
|
if (!sections.includes(section)) {
|
|
21273
21401
|
sections.push(section);
|
|
21274
21402
|
}
|
|
@@ -21743,7 +21871,7 @@ function numberValue2(value) {
|
|
|
21743
21871
|
}
|
|
21744
21872
|
return 0;
|
|
21745
21873
|
}
|
|
21746
|
-
function
|
|
21874
|
+
function stringValue3(value) {
|
|
21747
21875
|
return typeof value === "string" ? value : value == null ? "" : String(value);
|
|
21748
21876
|
}
|
|
21749
21877
|
function fileEntry(name, url2, baseUrl) {
|
|
@@ -21870,13 +21998,13 @@ var ForumModule = class {
|
|
|
21870
21998
|
}
|
|
21871
21999
|
};
|
|
21872
22000
|
function shouldFallbackForumAjax(error51) {
|
|
21873
|
-
if (!
|
|
22001
|
+
if (!isRecord4(error51)) {
|
|
21874
22002
|
return false;
|
|
21875
22003
|
}
|
|
21876
22004
|
const code = typeof error51.moodleErrorCode === "string" ? error51.moodleErrorCode : "";
|
|
21877
22005
|
return code === "servicenotavailable" || code === "accessexception" || error51 instanceof Error && error51.message.includes("Web service is not available");
|
|
21878
22006
|
}
|
|
21879
|
-
function
|
|
22007
|
+
function isRecord4(value) {
|
|
21880
22008
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21881
22009
|
}
|
|
21882
22010
|
|
|
@@ -22177,7 +22305,7 @@ var MoodleClientCore = class {
|
|
|
22177
22305
|
await this.ensureSession();
|
|
22178
22306
|
try {
|
|
22179
22307
|
const data = await this.call(FUNC_GET_SITE_INFO);
|
|
22180
|
-
if (
|
|
22308
|
+
if (isRecord5(data) && "userid" in data) {
|
|
22181
22309
|
const info = parseUserInfo(data);
|
|
22182
22310
|
this.sesskey = typeof data.sesskey === "string" ? data.sesskey : this.sesskey;
|
|
22183
22311
|
this.userid = info.userid;
|
|
@@ -22262,9 +22390,9 @@ var MoodleClientCore = class {
|
|
|
22262
22390
|
let type = "";
|
|
22263
22391
|
try {
|
|
22264
22392
|
const data = await this.call(FUNC_GET_COURSE_MODULE, { cmid: id });
|
|
22265
|
-
const module =
|
|
22266
|
-
type =
|
|
22267
|
-
courseId =
|
|
22393
|
+
const module = isRecord5(data) && isRecord5(data.cm) ? data.cm : data;
|
|
22394
|
+
type = isRecord5(module) && typeof module.modname === "string" ? module.modname : "";
|
|
22395
|
+
courseId = isRecord5(module) && typeof module.course === "number" ? module.course : void 0;
|
|
22268
22396
|
} catch (error51) {
|
|
22269
22397
|
if (!this.errors.isApi(error51) || error51.moodleErrorCode !== "servicenotavailable") {
|
|
22270
22398
|
throw error51;
|
|
@@ -22297,7 +22425,7 @@ var MoodleClientCore = class {
|
|
|
22297
22425
|
aftereventid: 0,
|
|
22298
22426
|
limittononsuspendedevents: true
|
|
22299
22427
|
});
|
|
22300
|
-
const events =
|
|
22428
|
+
const events = isRecord5(data) && Array.isArray(data.events) ? data.events : [];
|
|
22301
22429
|
return parseTodoItems(events);
|
|
22302
22430
|
}
|
|
22303
22431
|
async getAlerts(limit = 20) {
|
|
@@ -22542,8 +22670,8 @@ var MoodleClientCore = class {
|
|
|
22542
22670
|
}
|
|
22543
22671
|
if (!response.ok) {
|
|
22544
22672
|
const context = `HTTP ${response.status} loading ${safeUrl(url2)}`;
|
|
22545
|
-
const
|
|
22546
|
-
if (
|
|
22673
|
+
const contentType2 = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
22674
|
+
if (contentType2.includes("text/html") || contentType2.includes("application/xhtml+xml")) {
|
|
22547
22675
|
const moodleError = await response.text().then(parseMoodleErrorHtml).catch(() => null);
|
|
22548
22676
|
if (moodleError) {
|
|
22549
22677
|
throw this.errors.api(`${moodleError.message} (${context})`, moodleError.code);
|
|
@@ -22558,7 +22686,7 @@ var MoodleClientCore = class {
|
|
|
22558
22686
|
let offset = 0;
|
|
22559
22687
|
while (true) {
|
|
22560
22688
|
const data = await this.call(FUNC_GET_COURSES_BY_TIMELINE, { classification: "all", limit: 100, offset });
|
|
22561
|
-
if (!
|
|
22689
|
+
if (!isRecord5(data) || !Array.isArray(data.courses) || !data.courses.length) {
|
|
22562
22690
|
break;
|
|
22563
22691
|
}
|
|
22564
22692
|
courses.push(...data.courses);
|
|
@@ -22723,7 +22851,7 @@ function queryMatches(text, query) {
|
|
|
22723
22851
|
return needle ? haystack.includes(needle) || needle.split(" ").every((token) => haystack.includes(token)) : true;
|
|
22724
22852
|
}
|
|
22725
22853
|
function parseTodoPayload(value) {
|
|
22726
|
-
return parseTodoItems(
|
|
22854
|
+
return parseTodoItems(isRecord5(value) && Array.isArray(value.events) ? value.events : []);
|
|
22727
22855
|
}
|
|
22728
22856
|
function errorMessage(value) {
|
|
22729
22857
|
return value instanceof Error ? value.message : "Unknown Moodle error";
|
|
@@ -22735,7 +22863,7 @@ function chunks(values, size) {
|
|
|
22735
22863
|
}
|
|
22736
22864
|
return result;
|
|
22737
22865
|
}
|
|
22738
|
-
function
|
|
22866
|
+
function isRecord5(value) {
|
|
22739
22867
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
22740
22868
|
}
|
|
22741
22869
|
function isLoginErrorCode(code) {
|
|
@@ -22758,6 +22886,8 @@ function safeUrl(value) {
|
|
|
22758
22886
|
}
|
|
22759
22887
|
|
|
22760
22888
|
// src/mcp/gateway.ts
|
|
22889
|
+
var import_node_html_parser3 = __toESM(require_dist(), 1);
|
|
22890
|
+
var MAX_MCP_FILE_BYTES = 16 * 1024 * 1024;
|
|
22761
22891
|
var MoodleGatewayError = class extends Error {
|
|
22762
22892
|
code;
|
|
22763
22893
|
constructor(code, message) {
|
|
@@ -22793,95 +22923,211 @@ function createMoodleGateway(client) {
|
|
|
22793
22923
|
return limit === void 0 ? forums : forums.slice(0, limit);
|
|
22794
22924
|
},
|
|
22795
22925
|
searchForums: ({ forumId, ...input }) => client.searchForumContent({ ...input, forumCmid: forumId }),
|
|
22796
|
-
getThread: ({ discussionId }) => client.getForumDiscussion(discussionId)
|
|
22926
|
+
getThread: ({ discussionId }) => client.getForumDiscussion(discussionId),
|
|
22927
|
+
async getFile({ source }) {
|
|
22928
|
+
let target = await resolveFileTarget(client, source);
|
|
22929
|
+
let response = await client.requestAbsolute(target.url);
|
|
22930
|
+
if (isHtml(response)) {
|
|
22931
|
+
const html = await response.text();
|
|
22932
|
+
if (looksLikeLoginPage(html)) throw fileAuthenticationRequired();
|
|
22933
|
+
const links = resourceLinks(html, target.url);
|
|
22934
|
+
if (links.length !== 1) {
|
|
22935
|
+
const code = links.length ? "MOODLE_FILE_SOURCE_AMBIGUOUS" : "MOODLE_FILE_NOT_FOUND";
|
|
22936
|
+
throw new MoodleGatewayError(code, "The Moodle resource did not resolve to exactly one file.");
|
|
22937
|
+
}
|
|
22938
|
+
const resolved = await resolveFileTarget(client, links[0].url);
|
|
22939
|
+
target = { ...resolved, name: target.name || links[0].name || resolved.name };
|
|
22940
|
+
response = await client.requestAbsolute(target.url);
|
|
22941
|
+
if (isHtml(response)) {
|
|
22942
|
+
if (looksLikeLoginPage(await response.text())) throw fileAuthenticationRequired();
|
|
22943
|
+
throw new MoodleGatewayError("MOODLE_FILE_NOT_FOUND", "Moodle did not return a downloadable file.");
|
|
22944
|
+
}
|
|
22945
|
+
}
|
|
22946
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
22947
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_MCP_FILE_BYTES) {
|
|
22948
|
+
throw fileTooLarge();
|
|
22949
|
+
}
|
|
22950
|
+
const content = await readBoundedBody(response, MAX_MCP_FILE_BYTES);
|
|
22951
|
+
const name = safeFilename(contentDispositionFilename(response.headers.get("content-disposition"))) ?? safeFilename(target.name) ?? safeFilename(urlFilename(response.url || target.url));
|
|
22952
|
+
if (!name) {
|
|
22953
|
+
throw new MoodleGatewayError("MOODLE_FILE_NAME_MISSING", "Moodle did not provide a safe filename.");
|
|
22954
|
+
}
|
|
22955
|
+
return {
|
|
22956
|
+
name,
|
|
22957
|
+
mimeType: contentType(response),
|
|
22958
|
+
bytes: content.byteLength,
|
|
22959
|
+
uri: publicFileUrl(response.url || target.url),
|
|
22960
|
+
blob: encodeBase64(content)
|
|
22961
|
+
};
|
|
22962
|
+
}
|
|
22797
22963
|
};
|
|
22798
22964
|
}
|
|
22799
|
-
|
|
22800
|
-
|
|
22801
|
-
|
|
22802
|
-
|
|
22803
|
-
|
|
22804
|
-
|
|
22805
|
-
jsonrpc: external_exports.literal("2.0"),
|
|
22806
|
-
id: external_exports.union([external_exports.string(), external_exports.number(), external_exports.null()]).optional(),
|
|
22807
|
-
method: external_exports.string().trim().min(1),
|
|
22808
|
-
params: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
22809
|
-
}).strict();
|
|
22810
|
-
var ModernClientMetadataSchema = external_exports.object({
|
|
22811
|
-
"io.modelcontextprotocol/clientCapabilities": external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
22812
|
-
"io.modelcontextprotocol/clientInfo": external_exports.object({
|
|
22813
|
-
name: external_exports.string().trim().min(1),
|
|
22814
|
-
version: external_exports.string().trim().min(1)
|
|
22815
|
-
}).passthrough(),
|
|
22816
|
-
"io.modelcontextprotocol/protocolVersion": external_exports.literal(MODERN_PROTOCOL_VERSION)
|
|
22817
|
-
}).passthrough();
|
|
22818
|
-
var UnsupportedProtocolVersionError = class extends Error {
|
|
22819
|
-
protocolVersion;
|
|
22820
|
-
supportedVersions = SUPPORTED_PROTOCOL_VERSIONS;
|
|
22821
|
-
constructor(protocolVersion) {
|
|
22822
|
-
super(`Unsupported MCP protocol version: ${protocolVersion}`);
|
|
22823
|
-
this.name = "UnsupportedProtocolVersionError";
|
|
22824
|
-
this.protocolVersion = protocolVersion;
|
|
22965
|
+
async function resolveFileTarget(client, rawSource) {
|
|
22966
|
+
const source = String(rawSource).trim();
|
|
22967
|
+
if (/^\d+$/u.test(source)) {
|
|
22968
|
+
const activityId = Number(source);
|
|
22969
|
+
if (!Number.isSafeInteger(activityId) || activityId < 1) throw invalidFileSource();
|
|
22970
|
+
return validateFileTarget(client, await fileFromActivity(client, activityId));
|
|
22825
22971
|
}
|
|
22826
|
-
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
constructor(field, expected, actual) {
|
|
22832
|
-
super(`MCP request metadata mismatch for ${field}.`);
|
|
22833
|
-
this.name = "RequestMetadataMismatchError";
|
|
22834
|
-
this.field = field;
|
|
22835
|
-
this.expected = expected;
|
|
22836
|
-
this.actual = actual;
|
|
22972
|
+
let url2;
|
|
22973
|
+
try {
|
|
22974
|
+
url2 = new URL(source);
|
|
22975
|
+
} catch {
|
|
22976
|
+
throw invalidFileSource();
|
|
22837
22977
|
}
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
|
|
22841
|
-
}
|
|
22842
|
-
|
|
22843
|
-
|
|
22844
|
-
|
|
22845
|
-
const requested = context.protocolVersion ?? stringValue3(meta3?.["io.modelcontextprotocol/protocolVersion"]) ?? initializeVersion ?? MODERN_PROTOCOL_VERSION;
|
|
22846
|
-
if (!isSupportedProtocolVersion(requested)) {
|
|
22847
|
-
throw new UnsupportedProtocolVersionError(requested);
|
|
22978
|
+
const site = new URL(client.baseUrl);
|
|
22979
|
+
const sitePath = site.pathname.replace(/\/$/u, "");
|
|
22980
|
+
if (url2.origin !== site.origin) throw invalidFileSource();
|
|
22981
|
+
if (url2.pathname === `${sitePath}/mod/resource/view.php`) {
|
|
22982
|
+
const activityId = Number(url2.searchParams.get("id"));
|
|
22983
|
+
if (!Number.isSafeInteger(activityId) || activityId < 1) throw invalidFileSource();
|
|
22984
|
+
return validateFileTarget(client, await fileFromActivity(client, activityId));
|
|
22848
22985
|
}
|
|
22849
|
-
|
|
22986
|
+
if (!url2.pathname.startsWith(`${sitePath}/pluginfile.php/`)) throw invalidFileSource();
|
|
22987
|
+
return { url: url2.toString(), name: urlFilename(url2.toString()) };
|
|
22850
22988
|
}
|
|
22851
|
-
function
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
|
|
22855
|
-
|
|
22856
|
-
|
|
22857
|
-
|
|
22858
|
-
|
|
22859
|
-
|
|
22860
|
-
|
|
22861
|
-
|
|
22862
|
-
|
|
22863
|
-
|
|
22864
|
-
|
|
22989
|
+
function validateFileTarget(client, target) {
|
|
22990
|
+
let url2;
|
|
22991
|
+
try {
|
|
22992
|
+
url2 = new URL(target.url);
|
|
22993
|
+
} catch {
|
|
22994
|
+
throw invalidFileSource();
|
|
22995
|
+
}
|
|
22996
|
+
const site = new URL(client.baseUrl);
|
|
22997
|
+
const sitePath = site.pathname.replace(/\/$/u, "");
|
|
22998
|
+
const supportedPath = url2.pathname === `${sitePath}/mod/resource/view.php` || url2.pathname.startsWith(`${sitePath}/pluginfile.php/`);
|
|
22999
|
+
if (url2.origin !== site.origin || !supportedPath) throw invalidFileSource();
|
|
23000
|
+
return target;
|
|
23001
|
+
}
|
|
23002
|
+
async function fileFromActivity(client, activityId) {
|
|
23003
|
+
const activity = await client.getActivity(activityId);
|
|
23004
|
+
if (activity.type !== "resource" || !("file_entries" in activity) || !Array.isArray(activity.file_entries)) {
|
|
23005
|
+
throw new MoodleGatewayError(
|
|
23006
|
+
"MOODLE_FILE_SOURCE_INVALID",
|
|
23007
|
+
`Activity ${activityId} is not a downloadable resource.`
|
|
23008
|
+
);
|
|
22865
23009
|
}
|
|
22866
|
-
|
|
22867
|
-
|
|
22868
|
-
|
|
23010
|
+
if (activity.file_entries.length !== 1) {
|
|
23011
|
+
if (activity.file_entries.length === 0) {
|
|
23012
|
+
const resource = activity;
|
|
23013
|
+
const url2 = resource.target_url || resource.url;
|
|
23014
|
+
if (url2) return { url: url2, name: resource.target_name || void 0 };
|
|
23015
|
+
}
|
|
23016
|
+
throw new MoodleGatewayError(
|
|
23017
|
+
"MOODLE_FILE_SOURCE_AMBIGUOUS",
|
|
23018
|
+
`Activity ${activityId} did not resolve to exactly one file. Inspect file_entries and request one URL.`
|
|
23019
|
+
);
|
|
22869
23020
|
}
|
|
23021
|
+
const [entry] = activity.file_entries;
|
|
23022
|
+
return { url: entry.url, name: entry.name };
|
|
23023
|
+
}
|
|
23024
|
+
function resourceLinks(html, baseUrl) {
|
|
23025
|
+
const root = (0, import_node_html_parser3.parse)(html);
|
|
23026
|
+
const entries = root.querySelectorAll(".resourceworkaround a[href], .resourcecontent a[href], a.resourceworkaround[href]").map((link) => ({
|
|
23027
|
+
name: link.textContent.trim(),
|
|
23028
|
+
url: new URL(link.getAttribute("href") ?? "", baseUrl).toString()
|
|
23029
|
+
})).filter((entry) => entry.url !== baseUrl);
|
|
23030
|
+
return entries.filter((entry, index) => entries.findIndex((candidate) => candidate.url === entry.url) === index);
|
|
23031
|
+
}
|
|
23032
|
+
async function readBoundedBody(response, maxBytes) {
|
|
23033
|
+
if (!response.body) return new Uint8Array();
|
|
23034
|
+
const reader = response.body.getReader();
|
|
23035
|
+
const chunks2 = [];
|
|
23036
|
+
let bytes = 0;
|
|
23037
|
+
while (true) {
|
|
23038
|
+
const { done, value } = await reader.read();
|
|
23039
|
+
if (done) break;
|
|
23040
|
+
bytes += value.byteLength;
|
|
23041
|
+
if (bytes > maxBytes) {
|
|
23042
|
+
await reader.cancel().catch(() => void 0);
|
|
23043
|
+
throw fileTooLarge();
|
|
23044
|
+
}
|
|
23045
|
+
chunks2.push(value);
|
|
23046
|
+
}
|
|
23047
|
+
const content = new Uint8Array(bytes);
|
|
23048
|
+
let offset = 0;
|
|
23049
|
+
for (const chunk of chunks2) {
|
|
23050
|
+
content.set(chunk, offset);
|
|
23051
|
+
offset += chunk.byteLength;
|
|
23052
|
+
}
|
|
23053
|
+
return content;
|
|
23054
|
+
}
|
|
23055
|
+
function invalidFileSource() {
|
|
23056
|
+
return new MoodleGatewayError(
|
|
23057
|
+
"MOODLE_FILE_SOURCE_INVALID",
|
|
23058
|
+
"Use a positive resource activity ID, a same-site resource URL, or a same-site pluginfile URL."
|
|
23059
|
+
);
|
|
22870
23060
|
}
|
|
22871
|
-
function
|
|
22872
|
-
return
|
|
23061
|
+
function fileTooLarge() {
|
|
23062
|
+
return new MoodleGatewayError(
|
|
23063
|
+
"MOODLE_FILE_TOO_LARGE",
|
|
23064
|
+
`Moodle files returned through MCP cannot exceed ${MAX_MCP_FILE_BYTES / 1024 / 1024} MiB.`
|
|
23065
|
+
);
|
|
22873
23066
|
}
|
|
22874
|
-
function
|
|
22875
|
-
return
|
|
23067
|
+
function fileAuthenticationRequired() {
|
|
23068
|
+
return new MoodleGatewayError(
|
|
23069
|
+
"MOODLE_AUTH_REQUIRED",
|
|
23070
|
+
"Moodle returned a login page instead of the requested file."
|
|
23071
|
+
);
|
|
22876
23072
|
}
|
|
22877
|
-
function
|
|
22878
|
-
|
|
23073
|
+
function isHtml(response) {
|
|
23074
|
+
if (/\battachment\b/iu.test(response.headers.get("content-disposition") ?? "")) return false;
|
|
23075
|
+
const type = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
23076
|
+
return type.includes("text/html") || type.includes("application/xhtml+xml");
|
|
22879
23077
|
}
|
|
22880
|
-
function
|
|
22881
|
-
|
|
23078
|
+
function looksLikeLoginPage(html) {
|
|
23079
|
+
const root = (0, import_node_html_parser3.parse)(html);
|
|
23080
|
+
return root.querySelector('form[action*="/login/"], input[name="password"], #page-login-index') !== null || /<title>\s*(?:log in|login)/iu.test(html);
|
|
22882
23081
|
}
|
|
22883
|
-
function
|
|
22884
|
-
return
|
|
23082
|
+
function contentType(response) {
|
|
23083
|
+
return response.headers.get("content-type")?.split(";", 1)[0]?.trim() || "application/octet-stream";
|
|
23084
|
+
}
|
|
23085
|
+
function contentDispositionFilename(value) {
|
|
23086
|
+
if (!value) return void 0;
|
|
23087
|
+
const extended = value.match(/filename\*\s*=\s*([^;]+)/iu)?.[1]?.trim().replace(/^"|"$/gu, "");
|
|
23088
|
+
if (extended) {
|
|
23089
|
+
const encoded = extended.replace(/^[^']*'[^']*'/u, "");
|
|
23090
|
+
try {
|
|
23091
|
+
return decodeURIComponent(encoded);
|
|
23092
|
+
} catch {
|
|
23093
|
+
return encoded;
|
|
23094
|
+
}
|
|
23095
|
+
}
|
|
23096
|
+
const quoted = value.match(/filename\s*=\s*"((?:\\.|[^"])*)"/iu)?.[1];
|
|
23097
|
+
if (quoted !== void 0) return quoted.replace(/\\([\\"])/gu, "$1");
|
|
23098
|
+
return value.match(/filename\s*=\s*([^;]+)/iu)?.[1]?.trim();
|
|
23099
|
+
}
|
|
23100
|
+
function urlFilename(value) {
|
|
23101
|
+
try {
|
|
23102
|
+
const encoded = new URL(value).pathname.split("/").at(-1) ?? "";
|
|
23103
|
+
try {
|
|
23104
|
+
return decodeURIComponent(encoded);
|
|
23105
|
+
} catch {
|
|
23106
|
+
return encoded;
|
|
23107
|
+
}
|
|
23108
|
+
} catch {
|
|
23109
|
+
return void 0;
|
|
23110
|
+
}
|
|
23111
|
+
}
|
|
23112
|
+
function safeFilename(value) {
|
|
23113
|
+
const name = value?.split(/[\\/]/u).at(-1)?.replace(/[\u0000-\u001f\u007f]/gu, "").trim();
|
|
23114
|
+
return name && name !== "." && name !== ".." ? name : void 0;
|
|
23115
|
+
}
|
|
23116
|
+
function publicFileUrl(value) {
|
|
23117
|
+
const url2 = new URL(value);
|
|
23118
|
+
url2.username = "";
|
|
23119
|
+
url2.password = "";
|
|
23120
|
+
for (const key of [...url2.searchParams.keys()]) {
|
|
23121
|
+
if (key !== "forcedownload" && key !== "download") url2.searchParams.delete(key);
|
|
23122
|
+
}
|
|
23123
|
+
return url2.toString();
|
|
23124
|
+
}
|
|
23125
|
+
function encodeBase64(content) {
|
|
23126
|
+
let binary = "";
|
|
23127
|
+
for (let offset = 0; offset < content.byteLength; offset += 32768) {
|
|
23128
|
+
binary += String.fromCharCode(...content.subarray(offset, offset + 32768));
|
|
23129
|
+
}
|
|
23130
|
+
return btoa(binary);
|
|
22885
23131
|
}
|
|
22886
23132
|
|
|
22887
23133
|
// src/mcp/server.ts
|
|
@@ -22894,14 +23140,122 @@ var READ_ONLY_ANNOTATIONS = {
|
|
|
22894
23140
|
};
|
|
22895
23141
|
var emptyInput = external_exports.object({}).strict();
|
|
22896
23142
|
var positiveId = external_exports.number().int().positive();
|
|
22897
|
-
var
|
|
22898
|
-
var
|
|
23143
|
+
var integer2 = external_exports.number().int();
|
|
23144
|
+
var userValue = external_exports.looseObject({
|
|
23145
|
+
userid: integer2,
|
|
23146
|
+
username: external_exports.string(),
|
|
23147
|
+
fullname: external_exports.string(),
|
|
23148
|
+
sitename: external_exports.string(),
|
|
23149
|
+
siteurl: external_exports.string(),
|
|
23150
|
+
lang: external_exports.string().optional()
|
|
23151
|
+
});
|
|
23152
|
+
var courseValue = external_exports.looseObject({
|
|
23153
|
+
id: integer2,
|
|
23154
|
+
shortname: external_exports.string(),
|
|
23155
|
+
fullname: external_exports.string(),
|
|
23156
|
+
category: external_exports.number().int(),
|
|
23157
|
+
visible: external_exports.boolean(),
|
|
23158
|
+
startdate: external_exports.number(),
|
|
23159
|
+
enddate: external_exports.number().optional()
|
|
23160
|
+
});
|
|
23161
|
+
var activityValue = external_exports.looseObject({
|
|
23162
|
+
id: integer2,
|
|
23163
|
+
name: external_exports.string(),
|
|
23164
|
+
modname: external_exports.string(),
|
|
23165
|
+
url: external_exports.string(),
|
|
23166
|
+
visible: external_exports.boolean(),
|
|
23167
|
+
description: external_exports.string()
|
|
23168
|
+
});
|
|
23169
|
+
var fileEntryValue = external_exports.looseObject({
|
|
23170
|
+
name: external_exports.string(),
|
|
23171
|
+
url: external_exports.string(),
|
|
23172
|
+
requires_authentication: external_exports.boolean()
|
|
23173
|
+
});
|
|
23174
|
+
var activityDetailValue = external_exports.looseObject({
|
|
23175
|
+
id: positiveId,
|
|
23176
|
+
name: external_exports.string(),
|
|
23177
|
+
type: external_exports.string(),
|
|
23178
|
+
url: external_exports.string().optional(),
|
|
23179
|
+
target_name: external_exports.string().optional(),
|
|
23180
|
+
target_url: external_exports.string().optional(),
|
|
23181
|
+
file_entries: external_exports.array(fileEntryValue).optional()
|
|
23182
|
+
});
|
|
23183
|
+
var sectionValue = external_exports.looseObject({
|
|
23184
|
+
id: external_exports.number().int(),
|
|
23185
|
+
name: external_exports.string(),
|
|
23186
|
+
section: external_exports.number().int(),
|
|
23187
|
+
visible: external_exports.boolean(),
|
|
23188
|
+
summary: external_exports.string(),
|
|
23189
|
+
activities: external_exports.array(activityValue)
|
|
23190
|
+
});
|
|
23191
|
+
var todoValue = external_exports.looseObject({
|
|
23192
|
+
id: external_exports.number().int(),
|
|
23193
|
+
name: external_exports.string(),
|
|
23194
|
+
course_id: external_exports.number().int(),
|
|
23195
|
+
course_name: external_exports.string(),
|
|
23196
|
+
due_at: external_exports.number(),
|
|
23197
|
+
url: external_exports.string()
|
|
23198
|
+
});
|
|
23199
|
+
var gradeItemValue = external_exports.looseObject({
|
|
23200
|
+
name: external_exports.string(),
|
|
23201
|
+
item_type: external_exports.string(),
|
|
23202
|
+
grade: external_exports.string(),
|
|
23203
|
+
range: external_exports.string(),
|
|
23204
|
+
percentage: external_exports.string(),
|
|
23205
|
+
feedback: external_exports.string(),
|
|
23206
|
+
url: external_exports.string()
|
|
23207
|
+
});
|
|
23208
|
+
var gradesValue = external_exports.looseObject({
|
|
23209
|
+
course_id: integer2,
|
|
23210
|
+
course_name: external_exports.string(),
|
|
23211
|
+
learner_name: external_exports.string(),
|
|
23212
|
+
total_grade: external_exports.string(),
|
|
23213
|
+
total_range: external_exports.string(),
|
|
23214
|
+
total_percentage: external_exports.string(),
|
|
23215
|
+
items: external_exports.array(gradeItemValue)
|
|
23216
|
+
});
|
|
23217
|
+
var forumValue = external_exports.looseObject({
|
|
23218
|
+
id: integer2,
|
|
23219
|
+
name: external_exports.string(),
|
|
23220
|
+
course_id: integer2,
|
|
23221
|
+
course_name: external_exports.string(),
|
|
23222
|
+
url: external_exports.string()
|
|
23223
|
+
});
|
|
23224
|
+
var forumSearchValue = external_exports.looseObject({
|
|
23225
|
+
course_id: integer2,
|
|
23226
|
+
course_name: external_exports.string(),
|
|
23227
|
+
forum_id: integer2,
|
|
23228
|
+
forum_name: external_exports.string(),
|
|
23229
|
+
discussion_id: integer2,
|
|
23230
|
+
discussion_subject: external_exports.string(),
|
|
23231
|
+
post_id: integer2,
|
|
23232
|
+
snippet: external_exports.string(),
|
|
23233
|
+
url: external_exports.string()
|
|
23234
|
+
});
|
|
23235
|
+
var forumPostValue = external_exports.looseObject({
|
|
23236
|
+
id: integer2,
|
|
23237
|
+
discussion_id: integer2,
|
|
23238
|
+
subject: external_exports.string(),
|
|
23239
|
+
message_text: external_exports.string(),
|
|
23240
|
+
author: external_exports.looseObject({ id: integer2, fullname: external_exports.string() }),
|
|
23241
|
+
url: external_exports.string()
|
|
23242
|
+
});
|
|
23243
|
+
var threadValue = external_exports.looseObject({
|
|
23244
|
+
id: integer2,
|
|
23245
|
+
subject: external_exports.string(),
|
|
23246
|
+
course_id: integer2,
|
|
23247
|
+
forum_id: integer2,
|
|
23248
|
+
group_id: external_exports.number().int(),
|
|
23249
|
+
group_name: external_exports.string(),
|
|
23250
|
+
url: external_exports.string(),
|
|
23251
|
+
posts: external_exports.array(forumPostValue)
|
|
23252
|
+
});
|
|
22899
23253
|
var TOOL_REGISTRATIONS = [
|
|
22900
23254
|
{
|
|
22901
23255
|
name: "get_user",
|
|
22902
23256
|
description: "Get the authenticated Moodle user and site.",
|
|
22903
23257
|
input: emptyInput,
|
|
22904
|
-
output: external_exports.object({ user:
|
|
23258
|
+
output: external_exports.object({ user: userValue })
|
|
22905
23259
|
},
|
|
22906
23260
|
{
|
|
22907
23261
|
name: "get_overview",
|
|
@@ -22911,19 +23265,31 @@ var TOOL_REGISTRATIONS = [
|
|
|
22911
23265
|
todoDays: external_exports.number().int().min(1).max(365).optional(),
|
|
22912
23266
|
alertsLimit: external_exports.number().int().min(1).max(100).optional().default(5)
|
|
22913
23267
|
}).strict(),
|
|
22914
|
-
output: external_exports.object({
|
|
23268
|
+
output: external_exports.object({
|
|
23269
|
+
overview: external_exports.looseObject({
|
|
23270
|
+
user: userValue,
|
|
23271
|
+
courses: external_exports.array(courseValue),
|
|
23272
|
+
todo: external_exports.array(todoValue),
|
|
23273
|
+
errors: external_exports.array(external_exports.string())
|
|
23274
|
+
})
|
|
23275
|
+
})
|
|
22915
23276
|
},
|
|
22916
23277
|
{
|
|
22917
23278
|
name: "list_courses",
|
|
22918
23279
|
description: "List the authenticated user's Moodle courses.",
|
|
22919
23280
|
input: external_exports.object({ limit: external_exports.number().int().min(1).max(200).optional().default(100) }).strict(),
|
|
22920
|
-
output: external_exports.object({ courses:
|
|
23281
|
+
output: external_exports.object({ courses: external_exports.array(courseValue) })
|
|
22921
23282
|
},
|
|
22922
23283
|
{
|
|
22923
23284
|
name: "get_course",
|
|
22924
23285
|
description: "Get one Moodle course and its sections.",
|
|
22925
23286
|
input: external_exports.object({ courseId: positiveId }).strict(),
|
|
22926
|
-
output: external_exports.object({
|
|
23287
|
+
output: external_exports.object({
|
|
23288
|
+
course: external_exports.looseObject({
|
|
23289
|
+
course: courseValue,
|
|
23290
|
+
sections: external_exports.array(sectionValue)
|
|
23291
|
+
})
|
|
23292
|
+
})
|
|
22927
23293
|
},
|
|
22928
23294
|
{
|
|
22929
23295
|
name: "list_activities",
|
|
@@ -22932,19 +23298,19 @@ var TOOL_REGISTRATIONS = [
|
|
|
22932
23298
|
courseId: positiveId,
|
|
22933
23299
|
limit: external_exports.number().int().min(1).max(200).optional().default(100)
|
|
22934
23300
|
}).strict(),
|
|
22935
|
-
output: external_exports.object({ activities:
|
|
23301
|
+
output: external_exports.object({ activities: external_exports.array(activityValue) })
|
|
22936
23302
|
},
|
|
22937
23303
|
{
|
|
22938
23304
|
name: "get_activity",
|
|
22939
23305
|
description: "Get the supported details for one Moodle activity.",
|
|
22940
23306
|
input: external_exports.object({ activityId: positiveId }).strict(),
|
|
22941
|
-
output: external_exports.object({ activity:
|
|
23307
|
+
output: external_exports.object({ activity: activityDetailValue })
|
|
22942
23308
|
},
|
|
22943
23309
|
{
|
|
22944
23310
|
name: "get_grades",
|
|
22945
23311
|
description: "Get the authenticated user's grades for one Moodle course.",
|
|
22946
23312
|
input: external_exports.object({ courseId: positiveId }).strict(),
|
|
22947
|
-
output: external_exports.object({ grades:
|
|
23313
|
+
output: external_exports.object({ grades: gradesValue })
|
|
22948
23314
|
},
|
|
22949
23315
|
{
|
|
22950
23316
|
name: "list_forums",
|
|
@@ -22953,7 +23319,7 @@ var TOOL_REGISTRATIONS = [
|
|
|
22953
23319
|
courseId: positiveId.optional(),
|
|
22954
23320
|
limit: external_exports.number().int().min(1).max(100).optional().default(50)
|
|
22955
23321
|
}).strict(),
|
|
22956
|
-
output: external_exports.object({ forums:
|
|
23322
|
+
output: external_exports.object({ forums: external_exports.array(forumValue) })
|
|
22957
23323
|
},
|
|
22958
23324
|
{
|
|
22959
23325
|
name: "search_forums",
|
|
@@ -22969,13 +23335,28 @@ var TOOL_REGISTRATIONS = [
|
|
|
22969
23335
|
maxForums: external_exports.number().int().min(1).max(50).optional(),
|
|
22970
23336
|
maxDiscussionsPerForum: external_exports.number().int().min(1).max(100).optional()
|
|
22971
23337
|
}).strict(),
|
|
22972
|
-
output: external_exports.object({ results:
|
|
23338
|
+
output: external_exports.object({ results: external_exports.array(forumSearchValue) })
|
|
22973
23339
|
},
|
|
22974
23340
|
{
|
|
22975
23341
|
name: "get_thread",
|
|
22976
23342
|
description: "Get one Moodle forum discussion and its posts.",
|
|
22977
23343
|
input: external_exports.object({ discussionId: positiveId }).strict(),
|
|
22978
|
-
output: external_exports.object({ thread:
|
|
23344
|
+
output: external_exports.object({ thread: threadValue })
|
|
23345
|
+
},
|
|
23346
|
+
{
|
|
23347
|
+
name: "get_file",
|
|
23348
|
+
description: "Fetch one authenticated Moodle file and return its content directly (maximum 16 MiB).",
|
|
23349
|
+
input: external_exports.object({
|
|
23350
|
+
source: external_exports.union([positiveId, external_exports.string().trim().min(1).max(2048)])
|
|
23351
|
+
}).strict(),
|
|
23352
|
+
output: external_exports.object({
|
|
23353
|
+
file: external_exports.object({
|
|
23354
|
+
name: external_exports.string(),
|
|
23355
|
+
mime_type: external_exports.string(),
|
|
23356
|
+
bytes: external_exports.number().int().nonnegative(),
|
|
23357
|
+
uri: external_exports.string()
|
|
23358
|
+
})
|
|
23359
|
+
})
|
|
22979
23360
|
}
|
|
22980
23361
|
];
|
|
22981
23362
|
var TOOL_CATALOG = TOOL_REGISTRATIONS.map(({ name, description, input, output }) => ({
|
|
@@ -23043,12 +23424,11 @@ function createMoodleMcpServer(gateway, options = {}) {
|
|
|
23043
23424
|
}
|
|
23044
23425
|
if (error51 instanceof UnsupportedProtocolVersionError) {
|
|
23045
23426
|
return jsonRpcFailure(id, {
|
|
23046
|
-
code: -
|
|
23047
|
-
message:
|
|
23427
|
+
code: -32022,
|
|
23428
|
+
message: "Unsupported protocol version",
|
|
23048
23429
|
data: {
|
|
23049
|
-
|
|
23050
|
-
|
|
23051
|
-
supportedVersions: [...error51.supportedVersions]
|
|
23430
|
+
supported: [...error51.supportedVersions],
|
|
23431
|
+
requested: error51.protocolVersion
|
|
23052
23432
|
}
|
|
23053
23433
|
});
|
|
23054
23434
|
}
|
|
@@ -23096,7 +23476,7 @@ async function callTool(gateway, params) {
|
|
|
23096
23476
|
const payload = await runGatewayTool(gateway, name, input);
|
|
23097
23477
|
const structuredContent = registration.output.parse(wrapToolOutput(name, payload));
|
|
23098
23478
|
return {
|
|
23099
|
-
content:
|
|
23479
|
+
content: toolContent(name, payload),
|
|
23100
23480
|
structuredContent,
|
|
23101
23481
|
resultType: "complete",
|
|
23102
23482
|
_meta: RESULT_META
|
|
@@ -23157,6 +23537,8 @@ async function runGatewayTool(gateway, name, input) {
|
|
|
23157
23537
|
});
|
|
23158
23538
|
case "get_thread":
|
|
23159
23539
|
return gateway.getThread({ discussionId: numberValue3(input.discussionId) });
|
|
23540
|
+
case "get_file":
|
|
23541
|
+
return gateway.getFile({ source: fileSource(input.source) });
|
|
23160
23542
|
default:
|
|
23161
23543
|
throw new McpCallError("TOOL_NOT_FOUND", `Unknown Moodle tool: ${name}`);
|
|
23162
23544
|
}
|
|
@@ -23174,8 +23556,33 @@ function wrapToolOutput(name, payload) {
|
|
|
23174
23556
|
search_forums: "results",
|
|
23175
23557
|
get_thread: "thread"
|
|
23176
23558
|
};
|
|
23559
|
+
if (name === "get_file" && isMoodleFile(payload)) {
|
|
23560
|
+
return {
|
|
23561
|
+
file: {
|
|
23562
|
+
name: payload.name,
|
|
23563
|
+
mime_type: payload.mimeType,
|
|
23564
|
+
bytes: payload.bytes,
|
|
23565
|
+
uri: payload.uri
|
|
23566
|
+
}
|
|
23567
|
+
};
|
|
23568
|
+
}
|
|
23177
23569
|
return { [keys[name] ?? "result"]: payload };
|
|
23178
23570
|
}
|
|
23571
|
+
function toolContent(name, payload) {
|
|
23572
|
+
const text = { type: "text", text: summarizeToolOutput(name, payload) };
|
|
23573
|
+
if (name !== "get_file" || !isMoodleFile(payload)) return [text];
|
|
23574
|
+
return [
|
|
23575
|
+
text,
|
|
23576
|
+
{
|
|
23577
|
+
type: "resource",
|
|
23578
|
+
resource: {
|
|
23579
|
+
uri: payload.uri,
|
|
23580
|
+
mimeType: payload.mimeType,
|
|
23581
|
+
blob: payload.blob
|
|
23582
|
+
}
|
|
23583
|
+
}
|
|
23584
|
+
];
|
|
23585
|
+
}
|
|
23179
23586
|
function summarizeToolOutput(name, payload) {
|
|
23180
23587
|
if (Array.isArray(payload)) {
|
|
23181
23588
|
const labels = {
|
|
@@ -23207,6 +23614,9 @@ function summarizeToolOutput(name, payload) {
|
|
|
23207
23614
|
if (name === "get_thread" && isRecord6(payload)) {
|
|
23208
23615
|
return `Loaded forum thread ${stringValue4(payload.subject) || numberValue3(payload.id)}.`;
|
|
23209
23616
|
}
|
|
23617
|
+
if (name === "get_file" && isMoodleFile(payload)) {
|
|
23618
|
+
return `Loaded Moodle file ${payload.name} (${payload.bytes} bytes).`;
|
|
23619
|
+
}
|
|
23210
23620
|
return "Moodle request completed.";
|
|
23211
23621
|
}
|
|
23212
23622
|
function mapMoodleError(error51) {
|
|
@@ -23242,6 +23652,9 @@ function optionalNumber(value) {
|
|
|
23242
23652
|
function stringValue4(value) {
|
|
23243
23653
|
return typeof value === "string" ? value : "";
|
|
23244
23654
|
}
|
|
23655
|
+
function fileSource(value) {
|
|
23656
|
+
return typeof value === "number" || typeof value === "string" ? value : "";
|
|
23657
|
+
}
|
|
23245
23658
|
function booleanValue2(value) {
|
|
23246
23659
|
return value === true;
|
|
23247
23660
|
}
|
|
@@ -23251,6 +23664,9 @@ function enumValue(value, values) {
|
|
|
23251
23664
|
function isRecord6(value) {
|
|
23252
23665
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23253
23666
|
}
|
|
23667
|
+
function isMoodleFile(value) {
|
|
23668
|
+
return isRecord6(value) && typeof value.name === "string" && typeof value.mimeType === "string" && typeof value.bytes === "number" && typeof value.uri === "string" && typeof value.blob === "string";
|
|
23669
|
+
}
|
|
23254
23670
|
|
|
23255
23671
|
// src/worker/moodle-upstream.ts
|
|
23256
23672
|
var DASHBOARD_PATH2 = "/my/";
|