@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.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
OAuthError,
|
|
15
15
|
OAuthErrorCode
|
|
16
16
|
} from "@modelcontextprotocol/server";
|
|
17
|
+
import { pkhDid } from "@tinycloud/node-sdk";
|
|
17
18
|
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
18
19
|
var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
|
|
19
20
|
function createOpenKeyTokenVerifier(config) {
|
|
@@ -50,11 +51,11 @@ function authInfo(token, payload, config) {
|
|
|
50
51
|
);
|
|
51
52
|
}
|
|
52
53
|
const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
|
|
53
|
-
const ownerDids =
|
|
54
|
-
if (ownerDids
|
|
54
|
+
const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
|
|
55
|
+
if (ownerDids === void 0) {
|
|
55
56
|
throw new OAuthError(
|
|
56
57
|
OAuthErrorCode.InvalidToken,
|
|
57
|
-
"The OpenKey access token has no TinyCloud owner identity."
|
|
58
|
+
"The OpenKey access token has no valid TinyCloud owner identity."
|
|
58
59
|
);
|
|
59
60
|
}
|
|
60
61
|
return {
|
|
@@ -69,6 +70,21 @@ function authInfo(token, payload, config) {
|
|
|
69
70
|
}
|
|
70
71
|
};
|
|
71
72
|
}
|
|
73
|
+
var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
|
|
74
|
+
function canonicalOwnerDids(claim) {
|
|
75
|
+
if (!Array.isArray(claim) || claim.length === 0) return void 0;
|
|
76
|
+
const ownerDids = [];
|
|
77
|
+
for (const value of claim) {
|
|
78
|
+
const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
|
|
79
|
+
if (match === null) return void 0;
|
|
80
|
+
try {
|
|
81
|
+
ownerDids.push(pkhDid(match[2], Number(match[1])));
|
|
82
|
+
} catch {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return [...new Set(ownerDids)];
|
|
87
|
+
}
|
|
72
88
|
function firstString(...values) {
|
|
73
89
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
74
90
|
}
|
|
@@ -79,7 +95,9 @@ import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promi
|
|
|
79
95
|
import { dirname, join } from "path";
|
|
80
96
|
import {
|
|
81
97
|
TinyCloudNode,
|
|
82
|
-
|
|
98
|
+
isCapabilitySubset,
|
|
99
|
+
principalDidEquals,
|
|
100
|
+
siweRuntimeDelegation
|
|
83
101
|
} from "@tinycloud/node-sdk";
|
|
84
102
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
85
103
|
import { invokeOperation } from "@tinycloud/operations";
|
|
@@ -194,8 +212,7 @@ var RemoteTenantStore = class {
|
|
|
194
212
|
if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
|
|
195
213
|
throw new ApprovalOwnerMismatchError();
|
|
196
214
|
}
|
|
197
|
-
|
|
198
|
-
if (!permissionsCover(pending.permissions, granted)) {
|
|
215
|
+
if (!isCapabilitySubset(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
|
|
199
216
|
throw new Error("The approved delegation does not cover the requested account access.");
|
|
200
217
|
}
|
|
201
218
|
const profile = await readJson(this.#profilePath(tenant.stateRoot));
|
|
@@ -211,14 +228,25 @@ var RemoteTenantStore = class {
|
|
|
211
228
|
}
|
|
212
229
|
async #storeDelegation(tenant, pending, data) {
|
|
213
230
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
214
|
-
const { node } = await this.#restoreApproval(tenant, data);
|
|
231
|
+
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
215
232
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
216
|
-
const delegation =
|
|
233
|
+
const delegation = siweRuntimeDelegation(node, {
|
|
234
|
+
authorization: session.delegationHeader.Authorization,
|
|
235
|
+
cid: session.delegationCid,
|
|
236
|
+
spaceId: session.spaceId,
|
|
237
|
+
ownerAddress: session.address,
|
|
238
|
+
chainId: session.chainId,
|
|
239
|
+
siwe: session.siwe,
|
|
240
|
+
signature: session.signature,
|
|
241
|
+
host: this.#config.nodeHost
|
|
242
|
+
});
|
|
217
243
|
const result = await invokeOperation(
|
|
218
244
|
"tinycloud.auth.import",
|
|
219
245
|
1,
|
|
220
246
|
{ profile: PROFILE, stateRoot: tenant.stateRoot },
|
|
221
247
|
{
|
|
248
|
+
kind: "tinycloud.auth.delegation",
|
|
249
|
+
version: 1,
|
|
222
250
|
requestId: pending.requestId,
|
|
223
251
|
delegationCid: delegation.cid,
|
|
224
252
|
delegation
|
|
@@ -228,8 +256,10 @@ var RemoteTenantStore = class {
|
|
|
228
256
|
}
|
|
229
257
|
/**
|
|
230
258
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
231
|
-
*
|
|
232
|
-
*
|
|
259
|
+
* callback must carry the full signed proof (SIWE, signature, address and
|
|
260
|
+
* chain), so the WASM verifier always runs: it binds the authorization bytes
|
|
261
|
+
* and CID to the signed SIWE, and `node.did` is the wallet that signed
|
|
262
|
+
* exactly the delegation being stored.
|
|
233
263
|
*/
|
|
234
264
|
async #restoreApproval(tenant, data) {
|
|
235
265
|
const session = sessionFromCallback(data, tenant.jwk);
|
|
@@ -409,7 +439,7 @@ function ensureWasm() {
|
|
|
409
439
|
}
|
|
410
440
|
function sessionFromCallback(data, privateJwk) {
|
|
411
441
|
const delegationHeader = data.delegationHeader;
|
|
412
|
-
if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
|
|
442
|
+
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") {
|
|
413
443
|
throw new Error("OpenKey returned an incomplete delegation.");
|
|
414
444
|
}
|
|
415
445
|
return {
|
|
@@ -418,63 +448,12 @@ function sessionFromCallback(data, privateJwk) {
|
|
|
418
448
|
spaceId: data.spaceId,
|
|
419
449
|
jwk: privateJwk,
|
|
420
450
|
verificationMethod: data.verificationMethod,
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
451
|
+
address: data.address,
|
|
452
|
+
chainId: data.chainId,
|
|
453
|
+
siwe: data.siwe,
|
|
454
|
+
signature: data.signature
|
|
425
455
|
};
|
|
426
456
|
}
|
|
427
|
-
function portableFromCallback(data, permissions, host) {
|
|
428
|
-
const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
|
|
429
|
-
if (primary === void 0) throw new Error("The authority request has no permissions.");
|
|
430
|
-
const session = sessionFromCallback(data, {});
|
|
431
|
-
const expiry = delegationExpiry(data);
|
|
432
|
-
return {
|
|
433
|
-
cid: session.delegationCid,
|
|
434
|
-
delegationHeader: session.delegationHeader,
|
|
435
|
-
spaceId: session.spaceId,
|
|
436
|
-
path: primary.path,
|
|
437
|
-
actions: [...primary.actions],
|
|
438
|
-
resources: permissions.map((permission) => ({
|
|
439
|
-
service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
|
|
440
|
-
space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
|
|
441
|
-
path: permission.path,
|
|
442
|
-
actions: [...permission.actions]
|
|
443
|
-
})),
|
|
444
|
-
expiry,
|
|
445
|
-
delegateDID: session.verificationMethod,
|
|
446
|
-
ownerAddress: typeof data.address === "string" ? data.address : "",
|
|
447
|
-
chainId: typeof data.chainId === "number" ? data.chainId : 1,
|
|
448
|
-
host
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
function delegationExpiry(data) {
|
|
452
|
-
for (const key of ["expiry", "expiresAt", "expirationTime"]) {
|
|
453
|
-
const parsed = parseDate(data[key]);
|
|
454
|
-
if (parsed !== void 0) return parsed;
|
|
455
|
-
}
|
|
456
|
-
if (typeof data.siwe === "string") {
|
|
457
|
-
const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
458
|
-
const parsed = parseDate(match?.[1]?.trim());
|
|
459
|
-
if (parsed !== void 0) return parsed;
|
|
460
|
-
}
|
|
461
|
-
throw new Error("OpenKey did not return a delegation expiry.");
|
|
462
|
-
}
|
|
463
|
-
function permissionsCover(requested, granted) {
|
|
464
|
-
return requested.every((request) => granted.some(
|
|
465
|
-
(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))
|
|
466
|
-
));
|
|
467
|
-
}
|
|
468
|
-
function sameSpace(granted, requested) {
|
|
469
|
-
return granted === requested || granted.endsWith(`:${requested}`);
|
|
470
|
-
}
|
|
471
|
-
function normalizeService(service) {
|
|
472
|
-
return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
|
|
473
|
-
}
|
|
474
|
-
function requiredPermissionSpace(permission) {
|
|
475
|
-
if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
|
|
476
|
-
return permission.space;
|
|
477
|
-
}
|
|
478
457
|
function publicJwk(jwk) {
|
|
479
458
|
const copy = { ...jwk };
|
|
480
459
|
for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
|
|
@@ -497,11 +476,6 @@ function isPermissionEntry(value) {
|
|
|
497
476
|
function isRecord(value) {
|
|
498
477
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
499
478
|
}
|
|
500
|
-
function parseDate(value) {
|
|
501
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
502
|
-
const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
|
|
503
|
-
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
504
|
-
}
|
|
505
479
|
async function secureDirectory(path) {
|
|
506
480
|
await mkdir(path, { recursive: true, mode: 448 });
|
|
507
481
|
await chmod(path, 448);
|
|
@@ -571,8 +545,8 @@ import { addFormats, Ajv, AjvJsonSchemaValidator } from "@modelcontextprotocol/s
|
|
|
571
545
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
572
546
|
|
|
573
547
|
// src/results.ts
|
|
574
|
-
function toMcpToolResult(result) {
|
|
575
|
-
const canonical = isCanonicalOperationResult(result) ? result : {
|
|
548
|
+
function toMcpToolResult(result, bindings = []) {
|
|
549
|
+
const canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
576
550
|
status: "error",
|
|
577
551
|
operation: {},
|
|
578
552
|
context: {},
|
|
@@ -583,6 +557,18 @@ function toMcpToolResult(result) {
|
|
|
583
557
|
structuredContent: canonical
|
|
584
558
|
};
|
|
585
559
|
}
|
|
560
|
+
function withRecoveryTool(result, bindings) {
|
|
561
|
+
if (result.status !== "error") return result;
|
|
562
|
+
const error = result.error;
|
|
563
|
+
const recovery = error?.details?.recovery;
|
|
564
|
+
if (recovery?.action !== "complete_migration") return result;
|
|
565
|
+
const binding = bindings.find((candidate) => candidate.operationId === recovery.operationId);
|
|
566
|
+
if (binding === void 0) return result;
|
|
567
|
+
return {
|
|
568
|
+
...result,
|
|
569
|
+
error: { ...error, details: { ...error?.details, recovery: { ...recovery, tool: binding.name } } }
|
|
570
|
+
};
|
|
571
|
+
}
|
|
586
572
|
function isCanonicalOperationResult(value) {
|
|
587
573
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
588
574
|
const status = value.status;
|
|
@@ -615,6 +601,10 @@ var packageRequire = createRequire(
|
|
|
615
601
|
var generatedCatalog = packageRequire(
|
|
616
602
|
"@tinycloud/operations/operations.json"
|
|
617
603
|
);
|
|
604
|
+
var ALL_SURFACES = ["local", "hosted"];
|
|
605
|
+
function bindingSurfaces(binding) {
|
|
606
|
+
return binding.surfaces ?? ALL_SURFACES;
|
|
607
|
+
}
|
|
618
608
|
var TOOL_BINDINGS = [
|
|
619
609
|
{
|
|
620
610
|
name: "tinycloud_status",
|
|
@@ -659,7 +649,10 @@ var TOOL_BINDINGS = [
|
|
|
659
649
|
readOnlyHint: false,
|
|
660
650
|
idempotentHint: true,
|
|
661
651
|
destructiveHint: false,
|
|
662
|
-
openWorldHint: false
|
|
652
|
+
openWorldHint: false,
|
|
653
|
+
// Hosted delegations must arrive through the owner/request-bound OpenKey callback,
|
|
654
|
+
// not a caller-supplied import artifact.
|
|
655
|
+
surfaces: ["local"]
|
|
663
656
|
},
|
|
664
657
|
{
|
|
665
658
|
name: "tinycloud_account_spaces_list",
|
|
@@ -759,6 +752,18 @@ var TOOL_BINDINGS = [
|
|
|
759
752
|
idempotentHint: true,
|
|
760
753
|
destructiveHint: false,
|
|
761
754
|
openWorldHint: true
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
name: "tinycloud_secrets_status",
|
|
758
|
+
operationId: "tinycloud.secrets.status",
|
|
759
|
+
operationVersion: 1,
|
|
760
|
+
readOnlyHint: true,
|
|
761
|
+
idempotentHint: true,
|
|
762
|
+
destructiveHint: false,
|
|
763
|
+
openWorldHint: true,
|
|
764
|
+
// Hosted has no persistent reauthorization path for a revoked session
|
|
765
|
+
// yet (TC-896), so status is local/stdio only.
|
|
766
|
+
surfaces: ["local"]
|
|
762
767
|
}
|
|
763
768
|
];
|
|
764
769
|
function createJsonSchemaValidator() {
|
|
@@ -774,6 +779,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
774
779
|
])
|
|
775
780
|
);
|
|
776
781
|
for (const binding of TOOL_BINDINGS) {
|
|
782
|
+
if (!bindingSurfaces(binding).includes(selection.surface)) continue;
|
|
777
783
|
const operation = definitions.get(`${binding.operationId}@${binding.operationVersion}`);
|
|
778
784
|
if (operation === void 0) {
|
|
779
785
|
throw new Error(`Generated operations catalog is missing ${binding.operationId}@${binding.operationVersion}.`);
|
|
@@ -809,7 +815,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
809
815
|
input
|
|
810
816
|
);
|
|
811
817
|
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
812
|
-
return toMcpToolResult(projected);
|
|
818
|
+
return toMcpToolResult(projected, TOOL_BINDINGS);
|
|
813
819
|
}
|
|
814
820
|
);
|
|
815
821
|
}
|
|
@@ -818,7 +824,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
818
824
|
// package.json
|
|
819
825
|
var package_default = {
|
|
820
826
|
name: "@tinycloud/mcp",
|
|
821
|
-
version: "0.
|
|
827
|
+
version: "0.5.0-beta.10",
|
|
822
828
|
description: "TinyCloud delegated operations MCP server",
|
|
823
829
|
keywords: [
|
|
824
830
|
"tinycloud",
|
|
@@ -889,9 +895,9 @@ var package_default = {
|
|
|
889
895
|
dependencies: {
|
|
890
896
|
"@modelcontextprotocol/node": "2.0.0-beta.4",
|
|
891
897
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
892
|
-
"@tinycloud/node-sdk": "3.2.0-beta.
|
|
898
|
+
"@tinycloud/node-sdk": "3.2.0-beta.9",
|
|
893
899
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
894
|
-
"@tinycloud/operations": "0.5.0-beta.
|
|
900
|
+
"@tinycloud/operations": "0.5.0-beta.10",
|
|
895
901
|
jose: "6.1.3"
|
|
896
902
|
},
|
|
897
903
|
devDependencies: {
|
|
@@ -945,6 +951,7 @@ function createHostedMcpApp(config) {
|
|
|
945
951
|
}
|
|
946
952
|
);
|
|
947
953
|
registerTinyCloudTools(server, {
|
|
954
|
+
surface: "hosted",
|
|
948
955
|
profile: "agent",
|
|
949
956
|
explicitProfile: true,
|
|
950
957
|
allowOwnerProfile: false,
|