@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-cli.cjs
CHANGED
|
@@ -29,6 +29,7 @@ var import_node = require("@modelcontextprotocol/node");
|
|
|
29
29
|
|
|
30
30
|
// src/http.ts
|
|
31
31
|
var import_server4 = require("@modelcontextprotocol/server");
|
|
32
|
+
var import_node_sdk3 = require("@tinycloud/node-sdk");
|
|
32
33
|
|
|
33
34
|
// src/oauth.ts
|
|
34
35
|
var import_server = require("@modelcontextprotocol/server");
|
|
@@ -128,6 +129,7 @@ var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
|
128
129
|
var import_operations = require("@tinycloud/operations");
|
|
129
130
|
var PROFILE = "agent";
|
|
130
131
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
132
|
+
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
131
133
|
var BOOTSTRAP_PERMISSIONS = [
|
|
132
134
|
{
|
|
133
135
|
service: "tinycloud.kv",
|
|
@@ -148,6 +150,26 @@ var ApprovalOwnerMismatchError = class extends Error {
|
|
|
148
150
|
this.name = "ApprovalOwnerMismatchError";
|
|
149
151
|
}
|
|
150
152
|
};
|
|
153
|
+
var ApprovalStageError = class extends Error {
|
|
154
|
+
stage;
|
|
155
|
+
code;
|
|
156
|
+
reason;
|
|
157
|
+
constructor(stage, code, reason, cause) {
|
|
158
|
+
super(`The approval failed at ${stage} (${code}).`, { cause });
|
|
159
|
+
this.name = "ApprovalStageError";
|
|
160
|
+
this.stage = stage;
|
|
161
|
+
this.code = code;
|
|
162
|
+
this.reason = reason;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var ApprovalRequestError = class extends Error {
|
|
166
|
+
detail;
|
|
167
|
+
constructor(detail, message, cause) {
|
|
168
|
+
super(message, { cause });
|
|
169
|
+
this.name = "ApprovalRequestError";
|
|
170
|
+
this.detail = detail;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
151
173
|
var RemoteTenantStore = class {
|
|
152
174
|
#config;
|
|
153
175
|
#initializing = /* @__PURE__ */ new Map();
|
|
@@ -213,11 +235,20 @@ var RemoteTenantStore = class {
|
|
|
213
235
|
}
|
|
214
236
|
async completeApproval(state, request) {
|
|
215
237
|
const length = Number(request.headers.get("content-length") ?? "0");
|
|
216
|
-
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES)
|
|
238
|
+
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) {
|
|
239
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
240
|
+
}
|
|
217
241
|
const text = await request.text();
|
|
218
|
-
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES)
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) {
|
|
243
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
244
|
+
}
|
|
245
|
+
let data;
|
|
246
|
+
try {
|
|
247
|
+
data = JSON.parse(text);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is invalid.", error);
|
|
250
|
+
}
|
|
251
|
+
if (!isRecord(data)) throw new ApprovalRequestError("body_invalid", "Callback body is invalid.");
|
|
221
252
|
const pending = await this.#readPending(state, true);
|
|
222
253
|
try {
|
|
223
254
|
const tenant = await this.#readTenant(pending.tenantId);
|
|
@@ -255,16 +286,26 @@ var RemoteTenantStore = class {
|
|
|
255
286
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
256
287
|
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
257
288
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
289
|
+
let delegation;
|
|
290
|
+
try {
|
|
291
|
+
delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
|
|
292
|
+
authorization: session.delegationHeader.Authorization,
|
|
293
|
+
cid: session.delegationCid,
|
|
294
|
+
spaceId: session.spaceId,
|
|
295
|
+
ownerAddress: session.address,
|
|
296
|
+
chainId: session.chainId,
|
|
297
|
+
siwe: session.siwe,
|
|
298
|
+
signature: session.signature,
|
|
299
|
+
host: this.#config.nodeHost
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
throw new ApprovalStageError(
|
|
303
|
+
"siwe_runtime_delegation",
|
|
304
|
+
"DELEGATION_REJECTED",
|
|
305
|
+
error instanceof import_node_sdk2.RuntimeDelegationRejectedError ? error.reason : void 0,
|
|
306
|
+
error
|
|
307
|
+
);
|
|
308
|
+
}
|
|
268
309
|
const result = await (0, import_operations.invokeOperation)(
|
|
269
310
|
"tinycloud.auth.import",
|
|
270
311
|
1,
|
|
@@ -277,7 +318,15 @@ var RemoteTenantStore = class {
|
|
|
277
318
|
delegation
|
|
278
319
|
}
|
|
279
320
|
);
|
|
280
|
-
if (result.status
|
|
321
|
+
if (result.status === "error") {
|
|
322
|
+
const reason = result.error.details?.reason;
|
|
323
|
+
throw new ApprovalStageError(
|
|
324
|
+
result.error.code === "DELEGATION_REJECTED" && typeof reason === "string" ? "activation" : "auth_import",
|
|
325
|
+
result.error.code,
|
|
326
|
+
typeof reason === "string" ? reason : void 0
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (result.status !== "ok") throw new ApprovalStageError("auth_import", "AUTHORITY_REQUIRED");
|
|
281
330
|
}
|
|
282
331
|
/**
|
|
283
332
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
@@ -287,11 +336,25 @@ var RemoteTenantStore = class {
|
|
|
287
336
|
* exactly the delegation being stored.
|
|
288
337
|
*/
|
|
289
338
|
async #restoreApproval(tenant, data) {
|
|
290
|
-
|
|
339
|
+
let session;
|
|
340
|
+
try {
|
|
341
|
+
session = sessionFromCallback(data, tenant.jwk);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
throw new ApprovalStageError("restore_approval", "CALLBACK_INCOMPLETE", void 0, error);
|
|
344
|
+
}
|
|
291
345
|
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
292
|
-
|
|
346
|
+
try {
|
|
347
|
+
await node.restoreSession(session);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
throw new ApprovalStageError(
|
|
350
|
+
"restore_approval",
|
|
351
|
+
error instanceof import_node_sdk2.InvalidRestoredSessionError || error instanceof import_node_sdk2.UnsupportedSessionRestoreError ? error.code : "SESSION_RESTORE_FAILED",
|
|
352
|
+
void 0,
|
|
353
|
+
error
|
|
354
|
+
);
|
|
355
|
+
}
|
|
293
356
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
294
|
-
throw new
|
|
357
|
+
throw new ApprovalStageError("restore_approval", "SESSION_MISMATCH");
|
|
295
358
|
}
|
|
296
359
|
return { node, session };
|
|
297
360
|
}
|
|
@@ -332,7 +395,8 @@ var RemoteTenantStore = class {
|
|
|
332
395
|
sessionDid = didFromJwk(jwk);
|
|
333
396
|
}
|
|
334
397
|
const profilePath = this.#profilePath(stateRoot);
|
|
335
|
-
|
|
398
|
+
const profile = await readJson(profilePath);
|
|
399
|
+
if (profile === null) {
|
|
336
400
|
await secureWrite(profilePath, {
|
|
337
401
|
name: PROFILE,
|
|
338
402
|
host: this.#config.nodeHost,
|
|
@@ -346,14 +410,20 @@ var RemoteTenantStore = class {
|
|
|
346
410
|
authMethod: "openkey",
|
|
347
411
|
openkeyHost: this.#config.openkeyHost
|
|
348
412
|
});
|
|
413
|
+
} else if (typeof profile.host === "string" && profile.host.replace(/\/+$/, "") === LEGACY_DEFAULT_NODE_HOST && this.#config.nodeHost.replace(/\/+$/, "") !== LEGACY_DEFAULT_NODE_HOST) {
|
|
414
|
+
await secureWrite(profilePath, { ...profile, host: this.#config.nodeHost });
|
|
415
|
+
process.stderr.write(
|
|
416
|
+
`[tinycloud-mcp-http] tenant node host migrated: from=${LEGACY_DEFAULT_NODE_HOST} to=${new URL(this.#config.nodeHost).origin}
|
|
417
|
+
`
|
|
418
|
+
);
|
|
349
419
|
}
|
|
350
420
|
return { stateRoot, sessionDid, jwk };
|
|
351
421
|
}
|
|
352
422
|
async #readTenant(tenantId) {
|
|
353
|
-
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new
|
|
423
|
+
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
354
424
|
const stateRoot = (0, import_node_path.join)(this.#config.stateDir, "tenants", tenantId);
|
|
355
425
|
const jwk = await readJson(this.#keyPath(stateRoot));
|
|
356
|
-
if (jwk === null) throw new
|
|
426
|
+
if (jwk === null) throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.");
|
|
357
427
|
return { stateRoot, jwk, sessionDid: didFromJwk(jwk) };
|
|
358
428
|
}
|
|
359
429
|
async #createPending(subject, ownerDids, kind, permissions, requestId) {
|
|
@@ -383,26 +453,40 @@ var RemoteTenantStore = class {
|
|
|
383
453
|
async #readPending(state, consume) {
|
|
384
454
|
const [tenantId, nonce, signature] = state.split(".");
|
|
385
455
|
if (tenantId === void 0 || nonce === void 0 || signature === void 0) {
|
|
386
|
-
throw new
|
|
456
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
387
457
|
}
|
|
388
458
|
const expected = this.#signState(tenantId, nonce);
|
|
389
459
|
const supplied = Buffer.from(signature, "base64url");
|
|
390
460
|
const expectedBytes = Buffer.from(expected, "base64url");
|
|
391
461
|
if (supplied.length !== expectedBytes.length || !(0, import_node_crypto.timingSafeEqual)(supplied, expectedBytes)) {
|
|
392
|
-
throw new
|
|
462
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
393
463
|
}
|
|
394
464
|
const stateRoot = (0, import_node_path.join)(this.#config.stateDir, "tenants", tenantId);
|
|
395
465
|
const path = this.#pendingPath(stateRoot, nonce);
|
|
396
466
|
const claimed = `${path}.processing`;
|
|
397
|
-
if (consume)
|
|
467
|
+
if (consume) {
|
|
468
|
+
try {
|
|
469
|
+
await (0, import_promises.rename)(path, claimed);
|
|
470
|
+
} catch (error) {
|
|
471
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
472
|
+
const tenantExists = await readJson(this.#keyPath(stateRoot)).then(
|
|
473
|
+
(jwk) => jwk !== null,
|
|
474
|
+
() => true
|
|
475
|
+
);
|
|
476
|
+
if (!tenantExists) {
|
|
477
|
+
throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.", error);
|
|
478
|
+
}
|
|
479
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.", error);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
398
482
|
try {
|
|
399
483
|
const pending = await readJson(consume ? claimed : path);
|
|
400
484
|
if (pending === null || pending.version !== 1 || pending.tenantId !== tenantId || pending.nonce !== nonce) {
|
|
401
|
-
throw new
|
|
485
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.");
|
|
402
486
|
}
|
|
403
487
|
if (new Date(pending.expiresAt).getTime() <= Date.now()) {
|
|
404
488
|
await (0, import_promises.rm)(consume ? claimed : path, { force: true });
|
|
405
|
-
throw new
|
|
489
|
+
throw new ApprovalRequestError("state_expired", "Approval state has expired.");
|
|
406
490
|
}
|
|
407
491
|
return pending;
|
|
408
492
|
} catch (error) {
|
|
@@ -525,9 +609,6 @@ async function readJson(path) {
|
|
|
525
609
|
throw error;
|
|
526
610
|
}
|
|
527
611
|
}
|
|
528
|
-
async function fileExists(path) {
|
|
529
|
-
return await readJson(path) !== null;
|
|
530
|
-
}
|
|
531
612
|
async function reusablePending(directory, expected) {
|
|
532
613
|
let names;
|
|
533
614
|
try {
|
|
@@ -850,7 +931,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
850
931
|
// package.json
|
|
851
932
|
var package_default = {
|
|
852
933
|
name: "@tinycloud/mcp",
|
|
853
|
-
version: "0.5.4-beta.
|
|
934
|
+
version: "0.5.4-beta.2",
|
|
854
935
|
description: "TinyCloud delegated operations MCP server",
|
|
855
936
|
keywords: [
|
|
856
937
|
"tinycloud",
|
|
@@ -923,7 +1004,7 @@ var package_default = {
|
|
|
923
1004
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
924
1005
|
"@tinycloud/node-sdk": "3.2.4-beta.0",
|
|
925
1006
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
926
|
-
"@tinycloud/operations": "0.5.4-beta.
|
|
1007
|
+
"@tinycloud/operations": "0.5.4-beta.1",
|
|
927
1008
|
jose: "6.1.3"
|
|
928
1009
|
},
|
|
929
1010
|
devDependencies: {
|
|
@@ -1026,8 +1107,23 @@ function createHostedMcpApp(config) {
|
|
|
1026
1107
|
await store.completeApproval(requiredState(url), request);
|
|
1027
1108
|
return callbackCors(json({ success: true }), config.openkeyHost);
|
|
1028
1109
|
} catch (error) {
|
|
1110
|
+
if (error instanceof ApprovalOwnerMismatchError) {
|
|
1111
|
+
return callbackCors(json({ error: error.message, code: "approval_owner_mismatch" }, 403), config.openkeyHost);
|
|
1112
|
+
}
|
|
1113
|
+
const stage = error instanceof ApprovalStageError ? error.stage : "unknown";
|
|
1114
|
+
const code = error instanceof ApprovalStageError && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code) ? error.code : "UNKNOWN";
|
|
1115
|
+
const reason = !(error instanceof ApprovalStageError) || error.reason === void 0 ? void 0 : import_node_sdk3.RUNTIME_DELEGATION_REJECTIONS.find((known) => known === error.reason) ?? "unknown";
|
|
1116
|
+
const detail = error instanceof ApprovalRequestError ? error.detail : "none";
|
|
1117
|
+
writeDiagnostic(
|
|
1118
|
+
`connect callback rejected: stage=${stage} code=${code}${reason === void 0 ? "" : ` reason=${reason}`} detail=${detail}`
|
|
1119
|
+
);
|
|
1029
1120
|
return callbackCors(
|
|
1030
|
-
|
|
1121
|
+
json({
|
|
1122
|
+
error: stage,
|
|
1123
|
+
code,
|
|
1124
|
+
...reason === void 0 ? {} : { reason },
|
|
1125
|
+
message: "TinyCloud could not accept this delegation."
|
|
1126
|
+
}, 400),
|
|
1031
1127
|
config.openkeyHost
|
|
1032
1128
|
);
|
|
1033
1129
|
}
|
|
@@ -1102,7 +1198,8 @@ function canonicalResourceUrl(url) {
|
|
|
1102
1198
|
}
|
|
1103
1199
|
function requiredState(url) {
|
|
1104
1200
|
const state = url.searchParams.get("state");
|
|
1105
|
-
if (state === null
|
|
1201
|
+
if (state === null) throw new ApprovalRequestError("state_missing", "Missing approval state.");
|
|
1202
|
+
if (state.length > 512) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
1106
1203
|
return state;
|
|
1107
1204
|
}
|
|
1108
1205
|
function validateOrigin(request, allowed) {
|