@tinycloud/mcp 0.4.1-beta.1 → 0.5.0-beta.10
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 +27 -5
- package/dist/cli.cjs +40 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +40 -8
- package/dist/cli.js.map +1 -1
- package/dist/http-cli.cjs +86 -81
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +85 -78
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +86 -81
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +85 -78
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +89 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +88 -80
- package/dist/index.js.map +1 -1
- package/dist/results.cjs +14 -2
- package/dist/results.cjs.map +1 -1
- package/dist/results.d.cts +7 -2
- package/dist/results.d.ts +7 -2
- package/dist/results.js +14 -2
- package/dist/results.js.map +1 -1
- package/dist/server.cjs +40 -8
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +5 -3
- package/dist/server.d.ts +5 -3
- package/dist/server.js +40 -8
- package/dist/server.js.map +1 -1
- package/dist/tools.cjs +42 -5
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +12 -3
- package/dist/tools.d.ts +12 -3
- package/dist/tools.js +40 -5
- package/dist/tools.js.map +1 -1
- package/generated/mcp-facts.json +80 -0
- package/package.json +3 -3
- package/skills/tinycloud-delegated-secrets/SKILL.md +26 -18
package/dist/http.cjs
CHANGED
|
@@ -27,6 +27,7 @@ var import_server4 = require("@modelcontextprotocol/server");
|
|
|
27
27
|
|
|
28
28
|
// src/oauth.ts
|
|
29
29
|
var import_server = require("@modelcontextprotocol/server");
|
|
30
|
+
var import_node_sdk = require("@tinycloud/node-sdk");
|
|
30
31
|
var import_jose = require("jose");
|
|
31
32
|
var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
|
|
32
33
|
function createOpenKeyTokenVerifier(config) {
|
|
@@ -63,11 +64,11 @@ function authInfo(token, payload, config) {
|
|
|
63
64
|
);
|
|
64
65
|
}
|
|
65
66
|
const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
|
|
66
|
-
const ownerDids =
|
|
67
|
-
if (ownerDids
|
|
67
|
+
const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
|
|
68
|
+
if (ownerDids === void 0) {
|
|
68
69
|
throw new import_server.OAuthError(
|
|
69
70
|
import_server.OAuthErrorCode.InvalidToken,
|
|
70
|
-
"The OpenKey access token has no TinyCloud owner identity."
|
|
71
|
+
"The OpenKey access token has no valid TinyCloud owner identity."
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
return {
|
|
@@ -82,6 +83,21 @@ function authInfo(token, payload, config) {
|
|
|
82
83
|
}
|
|
83
84
|
};
|
|
84
85
|
}
|
|
86
|
+
var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
|
|
87
|
+
function canonicalOwnerDids(claim) {
|
|
88
|
+
if (!Array.isArray(claim) || claim.length === 0) return void 0;
|
|
89
|
+
const ownerDids = [];
|
|
90
|
+
for (const value of claim) {
|
|
91
|
+
const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
|
|
92
|
+
if (match === null) return void 0;
|
|
93
|
+
try {
|
|
94
|
+
ownerDids.push((0, import_node_sdk.pkhDid)(match[2], Number(match[1])));
|
|
95
|
+
} catch {
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return [...new Set(ownerDids)];
|
|
100
|
+
}
|
|
85
101
|
function firstString(...values) {
|
|
86
102
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
87
103
|
}
|
|
@@ -90,7 +106,7 @@ function firstString(...values) {
|
|
|
90
106
|
var import_node_crypto = require("crypto");
|
|
91
107
|
var import_promises = require("fs/promises");
|
|
92
108
|
var import_node_path = require("path");
|
|
93
|
-
var
|
|
109
|
+
var import_node_sdk2 = require("@tinycloud/node-sdk");
|
|
94
110
|
var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
95
111
|
var import_operations = require("@tinycloud/operations");
|
|
96
112
|
var PROFILE = "agent";
|
|
@@ -204,8 +220,7 @@ var RemoteTenantStore = class {
|
|
|
204
220
|
if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
|
|
205
221
|
throw new ApprovalOwnerMismatchError();
|
|
206
222
|
}
|
|
207
|
-
|
|
208
|
-
if (!permissionsCover(pending.permissions, granted)) {
|
|
223
|
+
if (!(0, import_node_sdk2.isCapabilitySubset)(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
|
|
209
224
|
throw new Error("The approved delegation does not cover the requested account access.");
|
|
210
225
|
}
|
|
211
226
|
const profile = await readJson(this.#profilePath(tenant.stateRoot));
|
|
@@ -221,14 +236,25 @@ var RemoteTenantStore = class {
|
|
|
221
236
|
}
|
|
222
237
|
async #storeDelegation(tenant, pending, data) {
|
|
223
238
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
224
|
-
const { node } = await this.#restoreApproval(tenant, data);
|
|
239
|
+
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
225
240
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
226
|
-
const delegation =
|
|
241
|
+
const delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
|
|
242
|
+
authorization: session.delegationHeader.Authorization,
|
|
243
|
+
cid: session.delegationCid,
|
|
244
|
+
spaceId: session.spaceId,
|
|
245
|
+
ownerAddress: session.address,
|
|
246
|
+
chainId: session.chainId,
|
|
247
|
+
siwe: session.siwe,
|
|
248
|
+
signature: session.signature,
|
|
249
|
+
host: this.#config.nodeHost
|
|
250
|
+
});
|
|
227
251
|
const result = await (0, import_operations.invokeOperation)(
|
|
228
252
|
"tinycloud.auth.import",
|
|
229
253
|
1,
|
|
230
254
|
{ profile: PROFILE, stateRoot: tenant.stateRoot },
|
|
231
255
|
{
|
|
256
|
+
kind: "tinycloud.auth.delegation",
|
|
257
|
+
version: 1,
|
|
232
258
|
requestId: pending.requestId,
|
|
233
259
|
delegationCid: delegation.cid,
|
|
234
260
|
delegation
|
|
@@ -238,12 +264,14 @@ var RemoteTenantStore = class {
|
|
|
238
264
|
}
|
|
239
265
|
/**
|
|
240
266
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
241
|
-
*
|
|
242
|
-
*
|
|
267
|
+
* callback must carry the full signed proof (SIWE, signature, address and
|
|
268
|
+
* chain), so the WASM verifier always runs: it binds the authorization bytes
|
|
269
|
+
* and CID to the signed SIWE, and `node.did` is the wallet that signed
|
|
270
|
+
* exactly the delegation being stored.
|
|
243
271
|
*/
|
|
244
272
|
async #restoreApproval(tenant, data) {
|
|
245
273
|
const session = sessionFromCallback(data, tenant.jwk);
|
|
246
|
-
const node = new
|
|
274
|
+
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
247
275
|
await node.restoreSession(session);
|
|
248
276
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
249
277
|
throw new Error("The approved delegation targets a different session.");
|
|
@@ -264,7 +292,7 @@ var RemoteTenantStore = class {
|
|
|
264
292
|
const raw = await readJson(this.#sessionPath(tenant.stateRoot));
|
|
265
293
|
if (raw === null) return false;
|
|
266
294
|
try {
|
|
267
|
-
const node = new
|
|
295
|
+
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
268
296
|
await node.restoreSession(raw);
|
|
269
297
|
return samePrincipal(node.sessionDid, tenant.sessionDid) && ownerDids.some((did) => samePrincipal(node.did, did));
|
|
270
298
|
} catch {
|
|
@@ -419,7 +447,7 @@ function ensureWasm() {
|
|
|
419
447
|
}
|
|
420
448
|
function sessionFromCallback(data, privateJwk) {
|
|
421
449
|
const delegationHeader = data.delegationHeader;
|
|
422
|
-
if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
|
|
450
|
+
if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string" || typeof data.address !== "string" || typeof data.chainId !== "number" || typeof data.siwe !== "string" || typeof data.signature !== "string") {
|
|
423
451
|
throw new Error("OpenKey returned an incomplete delegation.");
|
|
424
452
|
}
|
|
425
453
|
return {
|
|
@@ -428,63 +456,12 @@ function sessionFromCallback(data, privateJwk) {
|
|
|
428
456
|
spaceId: data.spaceId,
|
|
429
457
|
jwk: privateJwk,
|
|
430
458
|
verificationMethod: data.verificationMethod,
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
function portableFromCallback(data, permissions, host) {
|
|
438
|
-
const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
|
|
439
|
-
if (primary === void 0) throw new Error("The authority request has no permissions.");
|
|
440
|
-
const session = sessionFromCallback(data, {});
|
|
441
|
-
const expiry = delegationExpiry(data);
|
|
442
|
-
return {
|
|
443
|
-
cid: session.delegationCid,
|
|
444
|
-
delegationHeader: session.delegationHeader,
|
|
445
|
-
spaceId: session.spaceId,
|
|
446
|
-
path: primary.path,
|
|
447
|
-
actions: [...primary.actions],
|
|
448
|
-
resources: permissions.map((permission) => ({
|
|
449
|
-
service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
|
|
450
|
-
space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
|
|
451
|
-
path: permission.path,
|
|
452
|
-
actions: [...permission.actions]
|
|
453
|
-
})),
|
|
454
|
-
expiry,
|
|
455
|
-
delegateDID: session.verificationMethod,
|
|
456
|
-
ownerAddress: typeof data.address === "string" ? data.address : "",
|
|
457
|
-
chainId: typeof data.chainId === "number" ? data.chainId : 1,
|
|
458
|
-
host
|
|
459
|
+
address: data.address,
|
|
460
|
+
chainId: data.chainId,
|
|
461
|
+
siwe: data.siwe,
|
|
462
|
+
signature: data.signature
|
|
459
463
|
};
|
|
460
464
|
}
|
|
461
|
-
function delegationExpiry(data) {
|
|
462
|
-
for (const key of ["expiry", "expiresAt", "expirationTime"]) {
|
|
463
|
-
const parsed = parseDate(data[key]);
|
|
464
|
-
if (parsed !== void 0) return parsed;
|
|
465
|
-
}
|
|
466
|
-
if (typeof data.siwe === "string") {
|
|
467
|
-
const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
468
|
-
const parsed = parseDate(match?.[1]?.trim());
|
|
469
|
-
if (parsed !== void 0) return parsed;
|
|
470
|
-
}
|
|
471
|
-
throw new Error("OpenKey did not return a delegation expiry.");
|
|
472
|
-
}
|
|
473
|
-
function permissionsCover(requested, granted) {
|
|
474
|
-
return requested.every((request) => granted.some(
|
|
475
|
-
(grant) => normalizeService(grant.service) === normalizeService(request.service) && typeof grant.space === "string" && typeof request.space === "string" && sameSpace(grant.space, request.space) && grant.path === request.path && request.actions.every((action) => grant.actions.includes(action))
|
|
476
|
-
));
|
|
477
|
-
}
|
|
478
|
-
function sameSpace(granted, requested) {
|
|
479
|
-
return granted === requested || granted.endsWith(`:${requested}`);
|
|
480
|
-
}
|
|
481
|
-
function normalizeService(service) {
|
|
482
|
-
return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
|
|
483
|
-
}
|
|
484
|
-
function requiredPermissionSpace(permission) {
|
|
485
|
-
if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
|
|
486
|
-
return permission.space;
|
|
487
|
-
}
|
|
488
465
|
function publicJwk(jwk) {
|
|
489
466
|
const copy = { ...jwk };
|
|
490
467
|
for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
|
|
@@ -493,7 +470,7 @@ function publicJwk(jwk) {
|
|
|
493
470
|
function samePrincipal(left, right) {
|
|
494
471
|
if (typeof left !== "string") return false;
|
|
495
472
|
try {
|
|
496
|
-
return (0,
|
|
473
|
+
return (0, import_node_sdk2.principalDidEquals)(left, right);
|
|
497
474
|
} catch {
|
|
498
475
|
return false;
|
|
499
476
|
}
|
|
@@ -507,11 +484,6 @@ function isPermissionEntry(value) {
|
|
|
507
484
|
function isRecord(value) {
|
|
508
485
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
509
486
|
}
|
|
510
|
-
function parseDate(value) {
|
|
511
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
512
|
-
const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
|
|
513
|
-
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
514
|
-
}
|
|
515
487
|
async function secureDirectory(path) {
|
|
516
488
|
await (0, import_promises.mkdir)(path, { recursive: true, mode: 448 });
|
|
517
489
|
await (0, import_promises.chmod)(path, 448);
|
|
@@ -581,8 +553,8 @@ var import_ajv = require("@modelcontextprotocol/server/validators/ajv");
|
|
|
581
553
|
var import_operations2 = require("@tinycloud/operations");
|
|
582
554
|
|
|
583
555
|
// src/results.ts
|
|
584
|
-
function toMcpToolResult(result) {
|
|
585
|
-
const canonical = isCanonicalOperationResult(result) ? result : {
|
|
556
|
+
function toMcpToolResult(result, bindings = []) {
|
|
557
|
+
const canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
586
558
|
status: "error",
|
|
587
559
|
operation: {},
|
|
588
560
|
context: {},
|
|
@@ -593,6 +565,18 @@ function toMcpToolResult(result) {
|
|
|
593
565
|
structuredContent: canonical
|
|
594
566
|
};
|
|
595
567
|
}
|
|
568
|
+
function withRecoveryTool(result, bindings) {
|
|
569
|
+
if (result.status !== "error") return result;
|
|
570
|
+
const error = result.error;
|
|
571
|
+
const recovery = error?.details?.recovery;
|
|
572
|
+
if (recovery?.action !== "complete_migration") return result;
|
|
573
|
+
const binding = bindings.find((candidate) => candidate.operationId === recovery.operationId);
|
|
574
|
+
if (binding === void 0) return result;
|
|
575
|
+
return {
|
|
576
|
+
...result,
|
|
577
|
+
error: { ...error, details: { ...error?.details, recovery: { ...recovery, tool: binding.name } } }
|
|
578
|
+
};
|
|
579
|
+
}
|
|
596
580
|
function isCanonicalOperationResult(value) {
|
|
597
581
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
598
582
|
const status = value.status;
|
|
@@ -626,6 +610,10 @@ var packageRequire = (0, import_node_module.createRequire)(
|
|
|
626
610
|
var generatedCatalog = packageRequire(
|
|
627
611
|
"@tinycloud/operations/operations.json"
|
|
628
612
|
);
|
|
613
|
+
var ALL_SURFACES = ["local", "hosted"];
|
|
614
|
+
function bindingSurfaces(binding) {
|
|
615
|
+
return binding.surfaces ?? ALL_SURFACES;
|
|
616
|
+
}
|
|
629
617
|
var TOOL_BINDINGS = [
|
|
630
618
|
{
|
|
631
619
|
name: "tinycloud_status",
|
|
@@ -670,7 +658,10 @@ var TOOL_BINDINGS = [
|
|
|
670
658
|
readOnlyHint: false,
|
|
671
659
|
idempotentHint: true,
|
|
672
660
|
destructiveHint: false,
|
|
673
|
-
openWorldHint: false
|
|
661
|
+
openWorldHint: false,
|
|
662
|
+
// Hosted delegations must arrive through the owner/request-bound OpenKey callback,
|
|
663
|
+
// not a caller-supplied import artifact.
|
|
664
|
+
surfaces: ["local"]
|
|
674
665
|
},
|
|
675
666
|
{
|
|
676
667
|
name: "tinycloud_account_spaces_list",
|
|
@@ -770,6 +761,18 @@ var TOOL_BINDINGS = [
|
|
|
770
761
|
idempotentHint: true,
|
|
771
762
|
destructiveHint: false,
|
|
772
763
|
openWorldHint: true
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: "tinycloud_secrets_status",
|
|
767
|
+
operationId: "tinycloud.secrets.status",
|
|
768
|
+
operationVersion: 1,
|
|
769
|
+
readOnlyHint: true,
|
|
770
|
+
idempotentHint: true,
|
|
771
|
+
destructiveHint: false,
|
|
772
|
+
openWorldHint: true,
|
|
773
|
+
// Hosted has no persistent reauthorization path for a revoked session
|
|
774
|
+
// yet (TC-896), so status is local/stdio only.
|
|
775
|
+
surfaces: ["local"]
|
|
773
776
|
}
|
|
774
777
|
];
|
|
775
778
|
function createJsonSchemaValidator() {
|
|
@@ -785,6 +788,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
785
788
|
])
|
|
786
789
|
);
|
|
787
790
|
for (const binding of TOOL_BINDINGS) {
|
|
791
|
+
if (!bindingSurfaces(binding).includes(selection.surface)) continue;
|
|
788
792
|
const operation = definitions.get(`${binding.operationId}@${binding.operationVersion}`);
|
|
789
793
|
if (operation === void 0) {
|
|
790
794
|
throw new Error(`Generated operations catalog is missing ${binding.operationId}@${binding.operationVersion}.`);
|
|
@@ -820,7 +824,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
820
824
|
input
|
|
821
825
|
);
|
|
822
826
|
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
823
|
-
return toMcpToolResult(projected);
|
|
827
|
+
return toMcpToolResult(projected, TOOL_BINDINGS);
|
|
824
828
|
}
|
|
825
829
|
);
|
|
826
830
|
}
|
|
@@ -829,7 +833,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
829
833
|
// package.json
|
|
830
834
|
var package_default = {
|
|
831
835
|
name: "@tinycloud/mcp",
|
|
832
|
-
version: "0.
|
|
836
|
+
version: "0.5.0-beta.10",
|
|
833
837
|
description: "TinyCloud delegated operations MCP server",
|
|
834
838
|
keywords: [
|
|
835
839
|
"tinycloud",
|
|
@@ -900,9 +904,9 @@ var package_default = {
|
|
|
900
904
|
dependencies: {
|
|
901
905
|
"@modelcontextprotocol/node": "2.0.0-beta.4",
|
|
902
906
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
903
|
-
"@tinycloud/node-sdk": "3.2.0-beta.
|
|
907
|
+
"@tinycloud/node-sdk": "3.2.0-beta.9",
|
|
904
908
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
905
|
-
"@tinycloud/operations": "0.5.0-beta.
|
|
909
|
+
"@tinycloud/operations": "0.5.0-beta.10",
|
|
906
910
|
jose: "6.1.3"
|
|
907
911
|
},
|
|
908
912
|
devDependencies: {
|
|
@@ -956,6 +960,7 @@ function createHostedMcpApp(config) {
|
|
|
956
960
|
}
|
|
957
961
|
);
|
|
958
962
|
registerTinyCloudTools(server, {
|
|
963
|
+
surface: "hosted",
|
|
959
964
|
profile: "agent",
|
|
960
965
|
explicitProfile: true,
|
|
961
966
|
allowOwnerProfile: false,
|