@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-cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
OAuthError,
|
|
20
20
|
OAuthErrorCode
|
|
21
21
|
} from "@modelcontextprotocol/server";
|
|
22
|
+
import { pkhDid } from "@tinycloud/node-sdk";
|
|
22
23
|
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
23
24
|
var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
|
|
24
25
|
function createOpenKeyTokenVerifier(config) {
|
|
@@ -67,11 +68,11 @@ function authInfo(token, payload, config) {
|
|
|
67
68
|
);
|
|
68
69
|
}
|
|
69
70
|
const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
|
|
70
|
-
const ownerDids =
|
|
71
|
-
if (ownerDids
|
|
71
|
+
const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
|
|
72
|
+
if (ownerDids === void 0) {
|
|
72
73
|
throw new OAuthError(
|
|
73
74
|
OAuthErrorCode.InvalidToken,
|
|
74
|
-
"The OpenKey access token has no TinyCloud owner identity."
|
|
75
|
+
"The OpenKey access token has no valid TinyCloud owner identity."
|
|
75
76
|
);
|
|
76
77
|
}
|
|
77
78
|
return {
|
|
@@ -86,6 +87,21 @@ function authInfo(token, payload, config) {
|
|
|
86
87
|
}
|
|
87
88
|
};
|
|
88
89
|
}
|
|
90
|
+
var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
|
|
91
|
+
function canonicalOwnerDids(claim) {
|
|
92
|
+
if (!Array.isArray(claim) || claim.length === 0) return void 0;
|
|
93
|
+
const ownerDids = [];
|
|
94
|
+
for (const value of claim) {
|
|
95
|
+
const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
|
|
96
|
+
if (match === null) return void 0;
|
|
97
|
+
try {
|
|
98
|
+
ownerDids.push(pkhDid(match[2], Number(match[1])));
|
|
99
|
+
} catch {
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return [...new Set(ownerDids)];
|
|
104
|
+
}
|
|
89
105
|
function firstString(...values) {
|
|
90
106
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
91
107
|
}
|
|
@@ -96,7 +112,9 @@ import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promi
|
|
|
96
112
|
import { dirname, join } from "path";
|
|
97
113
|
import {
|
|
98
114
|
TinyCloudNode,
|
|
99
|
-
|
|
115
|
+
isCapabilitySubset,
|
|
116
|
+
principalDidEquals,
|
|
117
|
+
siweRuntimeDelegation
|
|
100
118
|
} from "@tinycloud/node-sdk";
|
|
101
119
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
102
120
|
import { invokeOperation } from "@tinycloud/operations";
|
|
@@ -211,8 +229,7 @@ var RemoteTenantStore = class {
|
|
|
211
229
|
if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
|
|
212
230
|
throw new ApprovalOwnerMismatchError();
|
|
213
231
|
}
|
|
214
|
-
|
|
215
|
-
if (!permissionsCover(pending.permissions, granted)) {
|
|
232
|
+
if (!isCapabilitySubset(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
|
|
216
233
|
throw new Error("The approved delegation does not cover the requested account access.");
|
|
217
234
|
}
|
|
218
235
|
const profile = await readJson(this.#profilePath(tenant.stateRoot));
|
|
@@ -228,14 +245,25 @@ var RemoteTenantStore = class {
|
|
|
228
245
|
}
|
|
229
246
|
async #storeDelegation(tenant, pending, data) {
|
|
230
247
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
231
|
-
const { node } = await this.#restoreApproval(tenant, data);
|
|
248
|
+
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
232
249
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
233
|
-
const delegation =
|
|
250
|
+
const delegation = siweRuntimeDelegation(node, {
|
|
251
|
+
authorization: session.delegationHeader.Authorization,
|
|
252
|
+
cid: session.delegationCid,
|
|
253
|
+
spaceId: session.spaceId,
|
|
254
|
+
ownerAddress: session.address,
|
|
255
|
+
chainId: session.chainId,
|
|
256
|
+
siwe: session.siwe,
|
|
257
|
+
signature: session.signature,
|
|
258
|
+
host: this.#config.nodeHost
|
|
259
|
+
});
|
|
234
260
|
const result = await invokeOperation(
|
|
235
261
|
"tinycloud.auth.import",
|
|
236
262
|
1,
|
|
237
263
|
{ profile: PROFILE, stateRoot: tenant.stateRoot },
|
|
238
264
|
{
|
|
265
|
+
kind: "tinycloud.auth.delegation",
|
|
266
|
+
version: 1,
|
|
239
267
|
requestId: pending.requestId,
|
|
240
268
|
delegationCid: delegation.cid,
|
|
241
269
|
delegation
|
|
@@ -245,8 +273,10 @@ var RemoteTenantStore = class {
|
|
|
245
273
|
}
|
|
246
274
|
/**
|
|
247
275
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
248
|
-
*
|
|
249
|
-
*
|
|
276
|
+
* callback must carry the full signed proof (SIWE, signature, address and
|
|
277
|
+
* chain), so the WASM verifier always runs: it binds the authorization bytes
|
|
278
|
+
* and CID to the signed SIWE, and `node.did` is the wallet that signed
|
|
279
|
+
* exactly the delegation being stored.
|
|
250
280
|
*/
|
|
251
281
|
async #restoreApproval(tenant, data) {
|
|
252
282
|
const session = sessionFromCallback(data, tenant.jwk);
|
|
@@ -426,7 +456,7 @@ function ensureWasm() {
|
|
|
426
456
|
}
|
|
427
457
|
function sessionFromCallback(data, privateJwk) {
|
|
428
458
|
const delegationHeader = data.delegationHeader;
|
|
429
|
-
if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
|
|
459
|
+
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") {
|
|
430
460
|
throw new Error("OpenKey returned an incomplete delegation.");
|
|
431
461
|
}
|
|
432
462
|
return {
|
|
@@ -435,63 +465,12 @@ function sessionFromCallback(data, privateJwk) {
|
|
|
435
465
|
spaceId: data.spaceId,
|
|
436
466
|
jwk: privateJwk,
|
|
437
467
|
verificationMethod: data.verificationMethod,
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
468
|
+
address: data.address,
|
|
469
|
+
chainId: data.chainId,
|
|
470
|
+
siwe: data.siwe,
|
|
471
|
+
signature: data.signature
|
|
442
472
|
};
|
|
443
473
|
}
|
|
444
|
-
function portableFromCallback(data, permissions, host) {
|
|
445
|
-
const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
|
|
446
|
-
if (primary === void 0) throw new Error("The authority request has no permissions.");
|
|
447
|
-
const session = sessionFromCallback(data, {});
|
|
448
|
-
const expiry = delegationExpiry(data);
|
|
449
|
-
return {
|
|
450
|
-
cid: session.delegationCid,
|
|
451
|
-
delegationHeader: session.delegationHeader,
|
|
452
|
-
spaceId: session.spaceId,
|
|
453
|
-
path: primary.path,
|
|
454
|
-
actions: [...primary.actions],
|
|
455
|
-
resources: permissions.map((permission) => ({
|
|
456
|
-
service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
|
|
457
|
-
space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
|
|
458
|
-
path: permission.path,
|
|
459
|
-
actions: [...permission.actions]
|
|
460
|
-
})),
|
|
461
|
-
expiry,
|
|
462
|
-
delegateDID: session.verificationMethod,
|
|
463
|
-
ownerAddress: typeof data.address === "string" ? data.address : "",
|
|
464
|
-
chainId: typeof data.chainId === "number" ? data.chainId : 1,
|
|
465
|
-
host
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
function delegationExpiry(data) {
|
|
469
|
-
for (const key of ["expiry", "expiresAt", "expirationTime"]) {
|
|
470
|
-
const parsed = parseDate(data[key]);
|
|
471
|
-
if (parsed !== void 0) return parsed;
|
|
472
|
-
}
|
|
473
|
-
if (typeof data.siwe === "string") {
|
|
474
|
-
const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
475
|
-
const parsed = parseDate(match?.[1]?.trim());
|
|
476
|
-
if (parsed !== void 0) return parsed;
|
|
477
|
-
}
|
|
478
|
-
throw new Error("OpenKey did not return a delegation expiry.");
|
|
479
|
-
}
|
|
480
|
-
function permissionsCover(requested, granted) {
|
|
481
|
-
return requested.every((request) => granted.some(
|
|
482
|
-
(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))
|
|
483
|
-
));
|
|
484
|
-
}
|
|
485
|
-
function sameSpace(granted, requested) {
|
|
486
|
-
return granted === requested || granted.endsWith(`:${requested}`);
|
|
487
|
-
}
|
|
488
|
-
function normalizeService(service) {
|
|
489
|
-
return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
|
|
490
|
-
}
|
|
491
|
-
function requiredPermissionSpace(permission) {
|
|
492
|
-
if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
|
|
493
|
-
return permission.space;
|
|
494
|
-
}
|
|
495
474
|
function publicJwk(jwk) {
|
|
496
475
|
const copy = { ...jwk };
|
|
497
476
|
for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
|
|
@@ -514,11 +493,6 @@ function isPermissionEntry(value) {
|
|
|
514
493
|
function isRecord(value) {
|
|
515
494
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
516
495
|
}
|
|
517
|
-
function parseDate(value) {
|
|
518
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
519
|
-
const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
|
|
520
|
-
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
521
|
-
}
|
|
522
496
|
async function secureDirectory(path) {
|
|
523
497
|
await mkdir(path, { recursive: true, mode: 448 });
|
|
524
498
|
await chmod(path, 448);
|
|
@@ -588,8 +562,8 @@ import { addFormats, Ajv, AjvJsonSchemaValidator } from "@modelcontextprotocol/s
|
|
|
588
562
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
589
563
|
|
|
590
564
|
// src/results.ts
|
|
591
|
-
function toMcpToolResult(result) {
|
|
592
|
-
const canonical = isCanonicalOperationResult(result) ? result : {
|
|
565
|
+
function toMcpToolResult(result, bindings = []) {
|
|
566
|
+
const canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
593
567
|
status: "error",
|
|
594
568
|
operation: {},
|
|
595
569
|
context: {},
|
|
@@ -600,6 +574,18 @@ function toMcpToolResult(result) {
|
|
|
600
574
|
structuredContent: canonical
|
|
601
575
|
};
|
|
602
576
|
}
|
|
577
|
+
function withRecoveryTool(result, bindings) {
|
|
578
|
+
if (result.status !== "error") return result;
|
|
579
|
+
const error = result.error;
|
|
580
|
+
const recovery = error?.details?.recovery;
|
|
581
|
+
if (recovery?.action !== "complete_migration") return result;
|
|
582
|
+
const binding = bindings.find((candidate) => candidate.operationId === recovery.operationId);
|
|
583
|
+
if (binding === void 0) return result;
|
|
584
|
+
return {
|
|
585
|
+
...result,
|
|
586
|
+
error: { ...error, details: { ...error?.details, recovery: { ...recovery, tool: binding.name } } }
|
|
587
|
+
};
|
|
588
|
+
}
|
|
603
589
|
function isCanonicalOperationResult(value) {
|
|
604
590
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
605
591
|
const status = value.status;
|
|
@@ -632,6 +618,10 @@ var packageRequire = createRequire(
|
|
|
632
618
|
var generatedCatalog = packageRequire(
|
|
633
619
|
"@tinycloud/operations/operations.json"
|
|
634
620
|
);
|
|
621
|
+
var ALL_SURFACES = ["local", "hosted"];
|
|
622
|
+
function bindingSurfaces(binding) {
|
|
623
|
+
return binding.surfaces ?? ALL_SURFACES;
|
|
624
|
+
}
|
|
635
625
|
var TOOL_BINDINGS = [
|
|
636
626
|
{
|
|
637
627
|
name: "tinycloud_status",
|
|
@@ -676,7 +666,10 @@ var TOOL_BINDINGS = [
|
|
|
676
666
|
readOnlyHint: false,
|
|
677
667
|
idempotentHint: true,
|
|
678
668
|
destructiveHint: false,
|
|
679
|
-
openWorldHint: false
|
|
669
|
+
openWorldHint: false,
|
|
670
|
+
// Hosted delegations must arrive through the owner/request-bound OpenKey callback,
|
|
671
|
+
// not a caller-supplied import artifact.
|
|
672
|
+
surfaces: ["local"]
|
|
680
673
|
},
|
|
681
674
|
{
|
|
682
675
|
name: "tinycloud_account_spaces_list",
|
|
@@ -776,6 +769,18 @@ var TOOL_BINDINGS = [
|
|
|
776
769
|
idempotentHint: true,
|
|
777
770
|
destructiveHint: false,
|
|
778
771
|
openWorldHint: true
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
name: "tinycloud_secrets_status",
|
|
775
|
+
operationId: "tinycloud.secrets.status",
|
|
776
|
+
operationVersion: 1,
|
|
777
|
+
readOnlyHint: true,
|
|
778
|
+
idempotentHint: true,
|
|
779
|
+
destructiveHint: false,
|
|
780
|
+
openWorldHint: true,
|
|
781
|
+
// Hosted has no persistent reauthorization path for a revoked session
|
|
782
|
+
// yet (TC-896), so status is local/stdio only.
|
|
783
|
+
surfaces: ["local"]
|
|
779
784
|
}
|
|
780
785
|
];
|
|
781
786
|
function createJsonSchemaValidator() {
|
|
@@ -791,6 +796,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
791
796
|
])
|
|
792
797
|
);
|
|
793
798
|
for (const binding of TOOL_BINDINGS) {
|
|
799
|
+
if (!bindingSurfaces(binding).includes(selection.surface)) continue;
|
|
794
800
|
const operation = definitions.get(`${binding.operationId}@${binding.operationVersion}`);
|
|
795
801
|
if (operation === void 0) {
|
|
796
802
|
throw new Error(`Generated operations catalog is missing ${binding.operationId}@${binding.operationVersion}.`);
|
|
@@ -826,7 +832,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
826
832
|
input
|
|
827
833
|
);
|
|
828
834
|
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
829
|
-
return toMcpToolResult(projected);
|
|
835
|
+
return toMcpToolResult(projected, TOOL_BINDINGS);
|
|
830
836
|
}
|
|
831
837
|
);
|
|
832
838
|
}
|
|
@@ -835,7 +841,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
835
841
|
// package.json
|
|
836
842
|
var package_default = {
|
|
837
843
|
name: "@tinycloud/mcp",
|
|
838
|
-
version: "0.
|
|
844
|
+
version: "0.5.0-beta.10",
|
|
839
845
|
description: "TinyCloud delegated operations MCP server",
|
|
840
846
|
keywords: [
|
|
841
847
|
"tinycloud",
|
|
@@ -906,9 +912,9 @@ var package_default = {
|
|
|
906
912
|
dependencies: {
|
|
907
913
|
"@modelcontextprotocol/node": "2.0.0-beta.4",
|
|
908
914
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
909
|
-
"@tinycloud/node-sdk": "3.2.0-beta.
|
|
915
|
+
"@tinycloud/node-sdk": "3.2.0-beta.9",
|
|
910
916
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
911
|
-
"@tinycloud/operations": "0.5.0-beta.
|
|
917
|
+
"@tinycloud/operations": "0.5.0-beta.10",
|
|
912
918
|
jose: "6.1.3"
|
|
913
919
|
},
|
|
914
920
|
devDependencies: {
|
|
@@ -962,6 +968,7 @@ function createHostedMcpApp(config) {
|
|
|
962
968
|
}
|
|
963
969
|
);
|
|
964
970
|
registerTinyCloudTools(server, {
|
|
971
|
+
surface: "hosted",
|
|
965
972
|
profile: "agent",
|
|
966
973
|
explicitProfile: true,
|
|
967
974
|
allowOwnerProfile: false,
|