@tinycloud/mcp 0.5.3 → 0.5.4-beta.1
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/dist/cli.cjs +3 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/http-cli.cjs +122 -30
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +125 -30
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +122 -30
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +125 -30
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +122 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +125 -30
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +3 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/http.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(http_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(http_exports);
|
|
26
26
|
var import_server4 = require("@modelcontextprotocol/server");
|
|
27
|
+
var import_node_sdk3 = require("@tinycloud/node-sdk");
|
|
27
28
|
|
|
28
29
|
// src/oauth.ts
|
|
29
30
|
var import_server = require("@modelcontextprotocol/server");
|
|
@@ -131,6 +132,26 @@ var ApprovalOwnerMismatchError = class extends Error {
|
|
|
131
132
|
this.name = "ApprovalOwnerMismatchError";
|
|
132
133
|
}
|
|
133
134
|
};
|
|
135
|
+
var ApprovalStageError = class extends Error {
|
|
136
|
+
stage;
|
|
137
|
+
code;
|
|
138
|
+
reason;
|
|
139
|
+
constructor(stage, code, reason, cause) {
|
|
140
|
+
super(`The approval failed at ${stage} (${code}).`, { cause });
|
|
141
|
+
this.name = "ApprovalStageError";
|
|
142
|
+
this.stage = stage;
|
|
143
|
+
this.code = code;
|
|
144
|
+
this.reason = reason;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var ApprovalRequestError = class extends Error {
|
|
148
|
+
detail;
|
|
149
|
+
constructor(detail, message, cause) {
|
|
150
|
+
super(message, { cause });
|
|
151
|
+
this.name = "ApprovalRequestError";
|
|
152
|
+
this.detail = detail;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
134
155
|
var RemoteTenantStore = class {
|
|
135
156
|
#config;
|
|
136
157
|
#initializing = /* @__PURE__ */ new Map();
|
|
@@ -196,11 +217,20 @@ var RemoteTenantStore = class {
|
|
|
196
217
|
}
|
|
197
218
|
async completeApproval(state, request) {
|
|
198
219
|
const length = Number(request.headers.get("content-length") ?? "0");
|
|
199
|
-
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES)
|
|
220
|
+
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) {
|
|
221
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
222
|
+
}
|
|
200
223
|
const text = await request.text();
|
|
201
|
-
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES)
|
|
202
|
-
|
|
203
|
-
|
|
224
|
+
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) {
|
|
225
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
226
|
+
}
|
|
227
|
+
let data;
|
|
228
|
+
try {
|
|
229
|
+
data = JSON.parse(text);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is invalid.", error);
|
|
232
|
+
}
|
|
233
|
+
if (!isRecord(data)) throw new ApprovalRequestError("body_invalid", "Callback body is invalid.");
|
|
204
234
|
const pending = await this.#readPending(state, true);
|
|
205
235
|
try {
|
|
206
236
|
const tenant = await this.#readTenant(pending.tenantId);
|
|
@@ -238,16 +268,26 @@ var RemoteTenantStore = class {
|
|
|
238
268
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
239
269
|
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
240
270
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
271
|
+
let delegation;
|
|
272
|
+
try {
|
|
273
|
+
delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
|
|
274
|
+
authorization: session.delegationHeader.Authorization,
|
|
275
|
+
cid: session.delegationCid,
|
|
276
|
+
spaceId: session.spaceId,
|
|
277
|
+
ownerAddress: session.address,
|
|
278
|
+
chainId: session.chainId,
|
|
279
|
+
siwe: session.siwe,
|
|
280
|
+
signature: session.signature,
|
|
281
|
+
host: this.#config.nodeHost
|
|
282
|
+
});
|
|
283
|
+
} catch (error) {
|
|
284
|
+
throw new ApprovalStageError(
|
|
285
|
+
"siwe_runtime_delegation",
|
|
286
|
+
"DELEGATION_REJECTED",
|
|
287
|
+
error instanceof import_node_sdk2.RuntimeDelegationRejectedError ? error.reason : void 0,
|
|
288
|
+
error
|
|
289
|
+
);
|
|
290
|
+
}
|
|
251
291
|
const result = await (0, import_operations.invokeOperation)(
|
|
252
292
|
"tinycloud.auth.import",
|
|
253
293
|
1,
|
|
@@ -260,7 +300,15 @@ var RemoteTenantStore = class {
|
|
|
260
300
|
delegation
|
|
261
301
|
}
|
|
262
302
|
);
|
|
263
|
-
if (result.status
|
|
303
|
+
if (result.status === "error") {
|
|
304
|
+
const reason = result.error.details?.reason;
|
|
305
|
+
throw new ApprovalStageError(
|
|
306
|
+
result.error.code === "DELEGATION_REJECTED" && typeof reason === "string" ? "activation" : "auth_import",
|
|
307
|
+
result.error.code,
|
|
308
|
+
typeof reason === "string" ? reason : void 0
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
if (result.status !== "ok") throw new ApprovalStageError("auth_import", "AUTHORITY_REQUIRED");
|
|
264
312
|
}
|
|
265
313
|
/**
|
|
266
314
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
@@ -270,11 +318,25 @@ var RemoteTenantStore = class {
|
|
|
270
318
|
* exactly the delegation being stored.
|
|
271
319
|
*/
|
|
272
320
|
async #restoreApproval(tenant, data) {
|
|
273
|
-
|
|
321
|
+
let session;
|
|
322
|
+
try {
|
|
323
|
+
session = sessionFromCallback(data, tenant.jwk);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
throw new ApprovalStageError("restore_approval", "CALLBACK_INCOMPLETE", void 0, error);
|
|
326
|
+
}
|
|
274
327
|
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
275
|
-
|
|
328
|
+
try {
|
|
329
|
+
await node.restoreSession(session);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
throw new ApprovalStageError(
|
|
332
|
+
"restore_approval",
|
|
333
|
+
error instanceof import_node_sdk2.InvalidRestoredSessionError || error instanceof import_node_sdk2.UnsupportedSessionRestoreError ? error.code : "SESSION_RESTORE_FAILED",
|
|
334
|
+
void 0,
|
|
335
|
+
error
|
|
336
|
+
);
|
|
337
|
+
}
|
|
276
338
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
277
|
-
throw new
|
|
339
|
+
throw new ApprovalStageError("restore_approval", "SESSION_MISMATCH");
|
|
278
340
|
}
|
|
279
341
|
return { node, session };
|
|
280
342
|
}
|
|
@@ -333,10 +395,10 @@ var RemoteTenantStore = class {
|
|
|
333
395
|
return { stateRoot, sessionDid, jwk };
|
|
334
396
|
}
|
|
335
397
|
async #readTenant(tenantId) {
|
|
336
|
-
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new
|
|
398
|
+
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
337
399
|
const stateRoot = (0, import_node_path.join)(this.#config.stateDir, "tenants", tenantId);
|
|
338
400
|
const jwk = await readJson(this.#keyPath(stateRoot));
|
|
339
|
-
if (jwk === null) throw new
|
|
401
|
+
if (jwk === null) throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.");
|
|
340
402
|
return { stateRoot, jwk, sessionDid: didFromJwk(jwk) };
|
|
341
403
|
}
|
|
342
404
|
async #createPending(subject, ownerDids, kind, permissions, requestId) {
|
|
@@ -366,26 +428,40 @@ var RemoteTenantStore = class {
|
|
|
366
428
|
async #readPending(state, consume) {
|
|
367
429
|
const [tenantId, nonce, signature] = state.split(".");
|
|
368
430
|
if (tenantId === void 0 || nonce === void 0 || signature === void 0) {
|
|
369
|
-
throw new
|
|
431
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
370
432
|
}
|
|
371
433
|
const expected = this.#signState(tenantId, nonce);
|
|
372
434
|
const supplied = Buffer.from(signature, "base64url");
|
|
373
435
|
const expectedBytes = Buffer.from(expected, "base64url");
|
|
374
436
|
if (supplied.length !== expectedBytes.length || !(0, import_node_crypto.timingSafeEqual)(supplied, expectedBytes)) {
|
|
375
|
-
throw new
|
|
437
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
376
438
|
}
|
|
377
439
|
const stateRoot = (0, import_node_path.join)(this.#config.stateDir, "tenants", tenantId);
|
|
378
440
|
const path = this.#pendingPath(stateRoot, nonce);
|
|
379
441
|
const claimed = `${path}.processing`;
|
|
380
|
-
if (consume)
|
|
442
|
+
if (consume) {
|
|
443
|
+
try {
|
|
444
|
+
await (0, import_promises.rename)(path, claimed);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
447
|
+
const tenantExists = await readJson(this.#keyPath(stateRoot)).then(
|
|
448
|
+
(jwk) => jwk !== null,
|
|
449
|
+
() => true
|
|
450
|
+
);
|
|
451
|
+
if (!tenantExists) {
|
|
452
|
+
throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.", error);
|
|
453
|
+
}
|
|
454
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.", error);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
381
457
|
try {
|
|
382
458
|
const pending = await readJson(consume ? claimed : path);
|
|
383
459
|
if (pending === null || pending.version !== 1 || pending.tenantId !== tenantId || pending.nonce !== nonce) {
|
|
384
|
-
throw new
|
|
460
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.");
|
|
385
461
|
}
|
|
386
462
|
if (new Date(pending.expiresAt).getTime() <= Date.now()) {
|
|
387
463
|
await (0, import_promises.rm)(consume ? claimed : path, { force: true });
|
|
388
|
-
throw new
|
|
464
|
+
throw new ApprovalRequestError("state_expired", "Approval state has expired.");
|
|
389
465
|
}
|
|
390
466
|
return pending;
|
|
391
467
|
} catch (error) {
|
|
@@ -833,7 +909,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
833
909
|
// package.json
|
|
834
910
|
var package_default = {
|
|
835
911
|
name: "@tinycloud/mcp",
|
|
836
|
-
version: "0.5.
|
|
912
|
+
version: "0.5.4-beta.1",
|
|
837
913
|
description: "TinyCloud delegated operations MCP server",
|
|
838
914
|
keywords: [
|
|
839
915
|
"tinycloud",
|
|
@@ -904,9 +980,9 @@ var package_default = {
|
|
|
904
980
|
dependencies: {
|
|
905
981
|
"@modelcontextprotocol/node": "2.0.0-beta.4",
|
|
906
982
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
907
|
-
"@tinycloud/node-sdk": "3.2.
|
|
983
|
+
"@tinycloud/node-sdk": "3.2.4-beta.0",
|
|
908
984
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
909
|
-
"@tinycloud/operations": "0.5.
|
|
985
|
+
"@tinycloud/operations": "0.5.4-beta.1",
|
|
910
986
|
jose: "6.1.3"
|
|
911
987
|
},
|
|
912
988
|
devDependencies: {
|
|
@@ -1009,8 +1085,23 @@ function createHostedMcpApp(config) {
|
|
|
1009
1085
|
await store.completeApproval(requiredState(url), request);
|
|
1010
1086
|
return callbackCors(json({ success: true }), config.openkeyHost);
|
|
1011
1087
|
} catch (error) {
|
|
1088
|
+
if (error instanceof ApprovalOwnerMismatchError) {
|
|
1089
|
+
return callbackCors(json({ error: error.message, code: "approval_owner_mismatch" }, 403), config.openkeyHost);
|
|
1090
|
+
}
|
|
1091
|
+
const stage = error instanceof ApprovalStageError ? error.stage : "unknown";
|
|
1092
|
+
const code = error instanceof ApprovalStageError && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code) ? error.code : "UNKNOWN";
|
|
1093
|
+
const reason = !(error instanceof ApprovalStageError) || error.reason === void 0 ? void 0 : import_node_sdk3.RUNTIME_DELEGATION_REJECTIONS.find((known) => known === error.reason) ?? "unknown";
|
|
1094
|
+
const detail = error instanceof ApprovalRequestError ? error.detail : "none";
|
|
1095
|
+
writeDiagnostic(
|
|
1096
|
+
`connect callback rejected: stage=${stage} code=${code}${reason === void 0 ? "" : ` reason=${reason}`} detail=${detail}`
|
|
1097
|
+
);
|
|
1012
1098
|
return callbackCors(
|
|
1013
|
-
|
|
1099
|
+
json({
|
|
1100
|
+
error: stage,
|
|
1101
|
+
code,
|
|
1102
|
+
...reason === void 0 ? {} : { reason },
|
|
1103
|
+
message: "TinyCloud could not accept this delegation."
|
|
1104
|
+
}, 400),
|
|
1014
1105
|
config.openkeyHost
|
|
1015
1106
|
);
|
|
1016
1107
|
}
|
|
@@ -1085,7 +1176,8 @@ function canonicalResourceUrl(url) {
|
|
|
1085
1176
|
}
|
|
1086
1177
|
function requiredState(url) {
|
|
1087
1178
|
const state = url.searchParams.get("state");
|
|
1088
|
-
if (state === null
|
|
1179
|
+
if (state === null) throw new ApprovalRequestError("state_missing", "Missing approval state.");
|
|
1180
|
+
if (state.length > 512) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
1089
1181
|
return state;
|
|
1090
1182
|
}
|
|
1091
1183
|
function validateOrigin(request, allowed) {
|