@tinycloud/mcp 0.5.4-beta.0 → 0.5.4-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/http-cli.cjs +130 -33
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +133 -33
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +130 -33
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +133 -33
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +130 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +133 -33
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +2 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
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");
|
|
@@ -111,6 +112,7 @@ var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
|
111
112
|
var import_operations = require("@tinycloud/operations");
|
|
112
113
|
var PROFILE = "agent";
|
|
113
114
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
115
|
+
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
114
116
|
var BOOTSTRAP_PERMISSIONS = [
|
|
115
117
|
{
|
|
116
118
|
service: "tinycloud.kv",
|
|
@@ -131,6 +133,26 @@ var ApprovalOwnerMismatchError = class extends Error {
|
|
|
131
133
|
this.name = "ApprovalOwnerMismatchError";
|
|
132
134
|
}
|
|
133
135
|
};
|
|
136
|
+
var ApprovalStageError = class extends Error {
|
|
137
|
+
stage;
|
|
138
|
+
code;
|
|
139
|
+
reason;
|
|
140
|
+
constructor(stage, code, reason, cause) {
|
|
141
|
+
super(`The approval failed at ${stage} (${code}).`, { cause });
|
|
142
|
+
this.name = "ApprovalStageError";
|
|
143
|
+
this.stage = stage;
|
|
144
|
+
this.code = code;
|
|
145
|
+
this.reason = reason;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var ApprovalRequestError = class extends Error {
|
|
149
|
+
detail;
|
|
150
|
+
constructor(detail, message, cause) {
|
|
151
|
+
super(message, { cause });
|
|
152
|
+
this.name = "ApprovalRequestError";
|
|
153
|
+
this.detail = detail;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
134
156
|
var RemoteTenantStore = class {
|
|
135
157
|
#config;
|
|
136
158
|
#initializing = /* @__PURE__ */ new Map();
|
|
@@ -196,11 +218,20 @@ var RemoteTenantStore = class {
|
|
|
196
218
|
}
|
|
197
219
|
async completeApproval(state, request) {
|
|
198
220
|
const length = Number(request.headers.get("content-length") ?? "0");
|
|
199
|
-
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES)
|
|
221
|
+
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) {
|
|
222
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
223
|
+
}
|
|
200
224
|
const text = await request.text();
|
|
201
|
-
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES)
|
|
202
|
-
|
|
203
|
-
|
|
225
|
+
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) {
|
|
226
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
227
|
+
}
|
|
228
|
+
let data;
|
|
229
|
+
try {
|
|
230
|
+
data = JSON.parse(text);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is invalid.", error);
|
|
233
|
+
}
|
|
234
|
+
if (!isRecord(data)) throw new ApprovalRequestError("body_invalid", "Callback body is invalid.");
|
|
204
235
|
const pending = await this.#readPending(state, true);
|
|
205
236
|
try {
|
|
206
237
|
const tenant = await this.#readTenant(pending.tenantId);
|
|
@@ -238,16 +269,26 @@ var RemoteTenantStore = class {
|
|
|
238
269
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
239
270
|
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
240
271
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
272
|
+
let delegation;
|
|
273
|
+
try {
|
|
274
|
+
delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
|
|
275
|
+
authorization: session.delegationHeader.Authorization,
|
|
276
|
+
cid: session.delegationCid,
|
|
277
|
+
spaceId: session.spaceId,
|
|
278
|
+
ownerAddress: session.address,
|
|
279
|
+
chainId: session.chainId,
|
|
280
|
+
siwe: session.siwe,
|
|
281
|
+
signature: session.signature,
|
|
282
|
+
host: this.#config.nodeHost
|
|
283
|
+
});
|
|
284
|
+
} catch (error) {
|
|
285
|
+
throw new ApprovalStageError(
|
|
286
|
+
"siwe_runtime_delegation",
|
|
287
|
+
"DELEGATION_REJECTED",
|
|
288
|
+
error instanceof import_node_sdk2.RuntimeDelegationRejectedError ? error.reason : void 0,
|
|
289
|
+
error
|
|
290
|
+
);
|
|
291
|
+
}
|
|
251
292
|
const result = await (0, import_operations.invokeOperation)(
|
|
252
293
|
"tinycloud.auth.import",
|
|
253
294
|
1,
|
|
@@ -260,7 +301,15 @@ var RemoteTenantStore = class {
|
|
|
260
301
|
delegation
|
|
261
302
|
}
|
|
262
303
|
);
|
|
263
|
-
if (result.status
|
|
304
|
+
if (result.status === "error") {
|
|
305
|
+
const reason = result.error.details?.reason;
|
|
306
|
+
throw new ApprovalStageError(
|
|
307
|
+
result.error.code === "DELEGATION_REJECTED" && typeof reason === "string" ? "activation" : "auth_import",
|
|
308
|
+
result.error.code,
|
|
309
|
+
typeof reason === "string" ? reason : void 0
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
if (result.status !== "ok") throw new ApprovalStageError("auth_import", "AUTHORITY_REQUIRED");
|
|
264
313
|
}
|
|
265
314
|
/**
|
|
266
315
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
@@ -270,11 +319,25 @@ var RemoteTenantStore = class {
|
|
|
270
319
|
* exactly the delegation being stored.
|
|
271
320
|
*/
|
|
272
321
|
async #restoreApproval(tenant, data) {
|
|
273
|
-
|
|
322
|
+
let session;
|
|
323
|
+
try {
|
|
324
|
+
session = sessionFromCallback(data, tenant.jwk);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
throw new ApprovalStageError("restore_approval", "CALLBACK_INCOMPLETE", void 0, error);
|
|
327
|
+
}
|
|
274
328
|
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
275
|
-
|
|
329
|
+
try {
|
|
330
|
+
await node.restoreSession(session);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
throw new ApprovalStageError(
|
|
333
|
+
"restore_approval",
|
|
334
|
+
error instanceof import_node_sdk2.InvalidRestoredSessionError || error instanceof import_node_sdk2.UnsupportedSessionRestoreError ? error.code : "SESSION_RESTORE_FAILED",
|
|
335
|
+
void 0,
|
|
336
|
+
error
|
|
337
|
+
);
|
|
338
|
+
}
|
|
276
339
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
277
|
-
throw new
|
|
340
|
+
throw new ApprovalStageError("restore_approval", "SESSION_MISMATCH");
|
|
278
341
|
}
|
|
279
342
|
return { node, session };
|
|
280
343
|
}
|
|
@@ -315,7 +378,8 @@ var RemoteTenantStore = class {
|
|
|
315
378
|
sessionDid = didFromJwk(jwk);
|
|
316
379
|
}
|
|
317
380
|
const profilePath = this.#profilePath(stateRoot);
|
|
318
|
-
|
|
381
|
+
const profile = await readJson(profilePath);
|
|
382
|
+
if (profile === null) {
|
|
319
383
|
await secureWrite(profilePath, {
|
|
320
384
|
name: PROFILE,
|
|
321
385
|
host: this.#config.nodeHost,
|
|
@@ -329,14 +393,20 @@ var RemoteTenantStore = class {
|
|
|
329
393
|
authMethod: "openkey",
|
|
330
394
|
openkeyHost: this.#config.openkeyHost
|
|
331
395
|
});
|
|
396
|
+
} else if (typeof profile.host === "string" && profile.host.replace(/\/+$/, "") === LEGACY_DEFAULT_NODE_HOST && this.#config.nodeHost.replace(/\/+$/, "") !== LEGACY_DEFAULT_NODE_HOST) {
|
|
397
|
+
await secureWrite(profilePath, { ...profile, host: this.#config.nodeHost });
|
|
398
|
+
process.stderr.write(
|
|
399
|
+
`[tinycloud-mcp-http] tenant node host migrated: from=${LEGACY_DEFAULT_NODE_HOST} to=${new URL(this.#config.nodeHost).origin}
|
|
400
|
+
`
|
|
401
|
+
);
|
|
332
402
|
}
|
|
333
403
|
return { stateRoot, sessionDid, jwk };
|
|
334
404
|
}
|
|
335
405
|
async #readTenant(tenantId) {
|
|
336
|
-
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new
|
|
406
|
+
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
337
407
|
const stateRoot = (0, import_node_path.join)(this.#config.stateDir, "tenants", tenantId);
|
|
338
408
|
const jwk = await readJson(this.#keyPath(stateRoot));
|
|
339
|
-
if (jwk === null) throw new
|
|
409
|
+
if (jwk === null) throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.");
|
|
340
410
|
return { stateRoot, jwk, sessionDid: didFromJwk(jwk) };
|
|
341
411
|
}
|
|
342
412
|
async #createPending(subject, ownerDids, kind, permissions, requestId) {
|
|
@@ -366,26 +436,40 @@ var RemoteTenantStore = class {
|
|
|
366
436
|
async #readPending(state, consume) {
|
|
367
437
|
const [tenantId, nonce, signature] = state.split(".");
|
|
368
438
|
if (tenantId === void 0 || nonce === void 0 || signature === void 0) {
|
|
369
|
-
throw new
|
|
439
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
370
440
|
}
|
|
371
441
|
const expected = this.#signState(tenantId, nonce);
|
|
372
442
|
const supplied = Buffer.from(signature, "base64url");
|
|
373
443
|
const expectedBytes = Buffer.from(expected, "base64url");
|
|
374
444
|
if (supplied.length !== expectedBytes.length || !(0, import_node_crypto.timingSafeEqual)(supplied, expectedBytes)) {
|
|
375
|
-
throw new
|
|
445
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
376
446
|
}
|
|
377
447
|
const stateRoot = (0, import_node_path.join)(this.#config.stateDir, "tenants", tenantId);
|
|
378
448
|
const path = this.#pendingPath(stateRoot, nonce);
|
|
379
449
|
const claimed = `${path}.processing`;
|
|
380
|
-
if (consume)
|
|
450
|
+
if (consume) {
|
|
451
|
+
try {
|
|
452
|
+
await (0, import_promises.rename)(path, claimed);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
455
|
+
const tenantExists = await readJson(this.#keyPath(stateRoot)).then(
|
|
456
|
+
(jwk) => jwk !== null,
|
|
457
|
+
() => true
|
|
458
|
+
);
|
|
459
|
+
if (!tenantExists) {
|
|
460
|
+
throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.", error);
|
|
461
|
+
}
|
|
462
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.", error);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
381
465
|
try {
|
|
382
466
|
const pending = await readJson(consume ? claimed : path);
|
|
383
467
|
if (pending === null || pending.version !== 1 || pending.tenantId !== tenantId || pending.nonce !== nonce) {
|
|
384
|
-
throw new
|
|
468
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.");
|
|
385
469
|
}
|
|
386
470
|
if (new Date(pending.expiresAt).getTime() <= Date.now()) {
|
|
387
471
|
await (0, import_promises.rm)(consume ? claimed : path, { force: true });
|
|
388
|
-
throw new
|
|
472
|
+
throw new ApprovalRequestError("state_expired", "Approval state has expired.");
|
|
389
473
|
}
|
|
390
474
|
return pending;
|
|
391
475
|
} catch (error) {
|
|
@@ -508,9 +592,6 @@ async function readJson(path) {
|
|
|
508
592
|
throw error;
|
|
509
593
|
}
|
|
510
594
|
}
|
|
511
|
-
async function fileExists(path) {
|
|
512
|
-
return await readJson(path) !== null;
|
|
513
|
-
}
|
|
514
595
|
async function reusablePending(directory, expected) {
|
|
515
596
|
let names;
|
|
516
597
|
try {
|
|
@@ -833,7 +914,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
833
914
|
// package.json
|
|
834
915
|
var package_default = {
|
|
835
916
|
name: "@tinycloud/mcp",
|
|
836
|
-
version: "0.5.4-beta.
|
|
917
|
+
version: "0.5.4-beta.2",
|
|
837
918
|
description: "TinyCloud delegated operations MCP server",
|
|
838
919
|
keywords: [
|
|
839
920
|
"tinycloud",
|
|
@@ -906,7 +987,7 @@ var package_default = {
|
|
|
906
987
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
907
988
|
"@tinycloud/node-sdk": "3.2.4-beta.0",
|
|
908
989
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
909
|
-
"@tinycloud/operations": "0.5.4-beta.
|
|
990
|
+
"@tinycloud/operations": "0.5.4-beta.1",
|
|
910
991
|
jose: "6.1.3"
|
|
911
992
|
},
|
|
912
993
|
devDependencies: {
|
|
@@ -1009,8 +1090,23 @@ function createHostedMcpApp(config) {
|
|
|
1009
1090
|
await store.completeApproval(requiredState(url), request);
|
|
1010
1091
|
return callbackCors(json({ success: true }), config.openkeyHost);
|
|
1011
1092
|
} catch (error) {
|
|
1093
|
+
if (error instanceof ApprovalOwnerMismatchError) {
|
|
1094
|
+
return callbackCors(json({ error: error.message, code: "approval_owner_mismatch" }, 403), config.openkeyHost);
|
|
1095
|
+
}
|
|
1096
|
+
const stage = error instanceof ApprovalStageError ? error.stage : "unknown";
|
|
1097
|
+
const code = error instanceof ApprovalStageError && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code) ? error.code : "UNKNOWN";
|
|
1098
|
+
const reason = !(error instanceof ApprovalStageError) || error.reason === void 0 ? void 0 : import_node_sdk3.RUNTIME_DELEGATION_REJECTIONS.find((known) => known === error.reason) ?? "unknown";
|
|
1099
|
+
const detail = error instanceof ApprovalRequestError ? error.detail : "none";
|
|
1100
|
+
writeDiagnostic(
|
|
1101
|
+
`connect callback rejected: stage=${stage} code=${code}${reason === void 0 ? "" : ` reason=${reason}`} detail=${detail}`
|
|
1102
|
+
);
|
|
1012
1103
|
return callbackCors(
|
|
1013
|
-
|
|
1104
|
+
json({
|
|
1105
|
+
error: stage,
|
|
1106
|
+
code,
|
|
1107
|
+
...reason === void 0 ? {} : { reason },
|
|
1108
|
+
message: "TinyCloud could not accept this delegation."
|
|
1109
|
+
}, 400),
|
|
1014
1110
|
config.openkeyHost
|
|
1015
1111
|
);
|
|
1016
1112
|
}
|
|
@@ -1085,7 +1181,8 @@ function canonicalResourceUrl(url) {
|
|
|
1085
1181
|
}
|
|
1086
1182
|
function requiredState(url) {
|
|
1087
1183
|
const state = url.searchParams.get("state");
|
|
1088
|
-
if (state === null
|
|
1184
|
+
if (state === null) throw new ApprovalRequestError("state_missing", "Missing approval state.");
|
|
1185
|
+
if (state.length > 512) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
1089
1186
|
return state;
|
|
1090
1187
|
}
|
|
1091
1188
|
function validateOrigin(request, allowed) {
|