@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.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var import_server4 = require("@modelcontextprotocol/server");
|
|
|
32
32
|
|
|
33
33
|
// src/oauth.ts
|
|
34
34
|
var import_server = require("@modelcontextprotocol/server");
|
|
35
|
+
var import_node_sdk = require("@tinycloud/node-sdk");
|
|
35
36
|
var import_jose = require("jose");
|
|
36
37
|
var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
|
|
37
38
|
function createOpenKeyTokenVerifier(config) {
|
|
@@ -80,11 +81,11 @@ function authInfo(token, payload, config) {
|
|
|
80
81
|
);
|
|
81
82
|
}
|
|
82
83
|
const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
|
|
83
|
-
const ownerDids =
|
|
84
|
-
if (ownerDids
|
|
84
|
+
const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
|
|
85
|
+
if (ownerDids === void 0) {
|
|
85
86
|
throw new import_server.OAuthError(
|
|
86
87
|
import_server.OAuthErrorCode.InvalidToken,
|
|
87
|
-
"The OpenKey access token has no TinyCloud owner identity."
|
|
88
|
+
"The OpenKey access token has no valid TinyCloud owner identity."
|
|
88
89
|
);
|
|
89
90
|
}
|
|
90
91
|
return {
|
|
@@ -99,6 +100,21 @@ function authInfo(token, payload, config) {
|
|
|
99
100
|
}
|
|
100
101
|
};
|
|
101
102
|
}
|
|
103
|
+
var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
|
|
104
|
+
function canonicalOwnerDids(claim) {
|
|
105
|
+
if (!Array.isArray(claim) || claim.length === 0) return void 0;
|
|
106
|
+
const ownerDids = [];
|
|
107
|
+
for (const value of claim) {
|
|
108
|
+
const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
|
|
109
|
+
if (match === null) return void 0;
|
|
110
|
+
try {
|
|
111
|
+
ownerDids.push((0, import_node_sdk.pkhDid)(match[2], Number(match[1])));
|
|
112
|
+
} catch {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return [...new Set(ownerDids)];
|
|
117
|
+
}
|
|
102
118
|
function firstString(...values) {
|
|
103
119
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
104
120
|
}
|
|
@@ -107,7 +123,7 @@ function firstString(...values) {
|
|
|
107
123
|
var import_node_crypto = require("crypto");
|
|
108
124
|
var import_promises = require("fs/promises");
|
|
109
125
|
var import_node_path = require("path");
|
|
110
|
-
var
|
|
126
|
+
var import_node_sdk2 = require("@tinycloud/node-sdk");
|
|
111
127
|
var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
112
128
|
var import_operations = require("@tinycloud/operations");
|
|
113
129
|
var PROFILE = "agent";
|
|
@@ -221,8 +237,7 @@ var RemoteTenantStore = class {
|
|
|
221
237
|
if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
|
|
222
238
|
throw new ApprovalOwnerMismatchError();
|
|
223
239
|
}
|
|
224
|
-
|
|
225
|
-
if (!permissionsCover(pending.permissions, granted)) {
|
|
240
|
+
if (!(0, import_node_sdk2.isCapabilitySubset)(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
|
|
226
241
|
throw new Error("The approved delegation does not cover the requested account access.");
|
|
227
242
|
}
|
|
228
243
|
const profile = await readJson(this.#profilePath(tenant.stateRoot));
|
|
@@ -238,14 +253,25 @@ var RemoteTenantStore = class {
|
|
|
238
253
|
}
|
|
239
254
|
async #storeDelegation(tenant, pending, data) {
|
|
240
255
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
241
|
-
const { node } = await this.#restoreApproval(tenant, data);
|
|
256
|
+
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
242
257
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
243
|
-
const delegation =
|
|
258
|
+
const delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
|
|
259
|
+
authorization: session.delegationHeader.Authorization,
|
|
260
|
+
cid: session.delegationCid,
|
|
261
|
+
spaceId: session.spaceId,
|
|
262
|
+
ownerAddress: session.address,
|
|
263
|
+
chainId: session.chainId,
|
|
264
|
+
siwe: session.siwe,
|
|
265
|
+
signature: session.signature,
|
|
266
|
+
host: this.#config.nodeHost
|
|
267
|
+
});
|
|
244
268
|
const result = await (0, import_operations.invokeOperation)(
|
|
245
269
|
"tinycloud.auth.import",
|
|
246
270
|
1,
|
|
247
271
|
{ profile: PROFILE, stateRoot: tenant.stateRoot },
|
|
248
272
|
{
|
|
273
|
+
kind: "tinycloud.auth.delegation",
|
|
274
|
+
version: 1,
|
|
249
275
|
requestId: pending.requestId,
|
|
250
276
|
delegationCid: delegation.cid,
|
|
251
277
|
delegation
|
|
@@ -255,12 +281,14 @@ var RemoteTenantStore = class {
|
|
|
255
281
|
}
|
|
256
282
|
/**
|
|
257
283
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
258
|
-
*
|
|
259
|
-
*
|
|
284
|
+
* callback must carry the full signed proof (SIWE, signature, address and
|
|
285
|
+
* chain), so the WASM verifier always runs: it binds the authorization bytes
|
|
286
|
+
* and CID to the signed SIWE, and `node.did` is the wallet that signed
|
|
287
|
+
* exactly the delegation being stored.
|
|
260
288
|
*/
|
|
261
289
|
async #restoreApproval(tenant, data) {
|
|
262
290
|
const session = sessionFromCallback(data, tenant.jwk);
|
|
263
|
-
const node = new
|
|
291
|
+
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
264
292
|
await node.restoreSession(session);
|
|
265
293
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
266
294
|
throw new Error("The approved delegation targets a different session.");
|
|
@@ -281,7 +309,7 @@ var RemoteTenantStore = class {
|
|
|
281
309
|
const raw = await readJson(this.#sessionPath(tenant.stateRoot));
|
|
282
310
|
if (raw === null) return false;
|
|
283
311
|
try {
|
|
284
|
-
const node = new
|
|
312
|
+
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
285
313
|
await node.restoreSession(raw);
|
|
286
314
|
return samePrincipal(node.sessionDid, tenant.sessionDid) && ownerDids.some((did) => samePrincipal(node.did, did));
|
|
287
315
|
} catch {
|
|
@@ -436,7 +464,7 @@ function ensureWasm() {
|
|
|
436
464
|
}
|
|
437
465
|
function sessionFromCallback(data, privateJwk) {
|
|
438
466
|
const delegationHeader = data.delegationHeader;
|
|
439
|
-
if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
|
|
467
|
+
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") {
|
|
440
468
|
throw new Error("OpenKey returned an incomplete delegation.");
|
|
441
469
|
}
|
|
442
470
|
return {
|
|
@@ -445,63 +473,12 @@ function sessionFromCallback(data, privateJwk) {
|
|
|
445
473
|
spaceId: data.spaceId,
|
|
446
474
|
jwk: privateJwk,
|
|
447
475
|
verificationMethod: data.verificationMethod,
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
function portableFromCallback(data, permissions, host) {
|
|
455
|
-
const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
|
|
456
|
-
if (primary === void 0) throw new Error("The authority request has no permissions.");
|
|
457
|
-
const session = sessionFromCallback(data, {});
|
|
458
|
-
const expiry = delegationExpiry(data);
|
|
459
|
-
return {
|
|
460
|
-
cid: session.delegationCid,
|
|
461
|
-
delegationHeader: session.delegationHeader,
|
|
462
|
-
spaceId: session.spaceId,
|
|
463
|
-
path: primary.path,
|
|
464
|
-
actions: [...primary.actions],
|
|
465
|
-
resources: permissions.map((permission) => ({
|
|
466
|
-
service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
|
|
467
|
-
space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
|
|
468
|
-
path: permission.path,
|
|
469
|
-
actions: [...permission.actions]
|
|
470
|
-
})),
|
|
471
|
-
expiry,
|
|
472
|
-
delegateDID: session.verificationMethod,
|
|
473
|
-
ownerAddress: typeof data.address === "string" ? data.address : "",
|
|
474
|
-
chainId: typeof data.chainId === "number" ? data.chainId : 1,
|
|
475
|
-
host
|
|
476
|
+
address: data.address,
|
|
477
|
+
chainId: data.chainId,
|
|
478
|
+
siwe: data.siwe,
|
|
479
|
+
signature: data.signature
|
|
476
480
|
};
|
|
477
481
|
}
|
|
478
|
-
function delegationExpiry(data) {
|
|
479
|
-
for (const key of ["expiry", "expiresAt", "expirationTime"]) {
|
|
480
|
-
const parsed = parseDate(data[key]);
|
|
481
|
-
if (parsed !== void 0) return parsed;
|
|
482
|
-
}
|
|
483
|
-
if (typeof data.siwe === "string") {
|
|
484
|
-
const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
485
|
-
const parsed = parseDate(match?.[1]?.trim());
|
|
486
|
-
if (parsed !== void 0) return parsed;
|
|
487
|
-
}
|
|
488
|
-
throw new Error("OpenKey did not return a delegation expiry.");
|
|
489
|
-
}
|
|
490
|
-
function permissionsCover(requested, granted) {
|
|
491
|
-
return requested.every((request) => granted.some(
|
|
492
|
-
(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))
|
|
493
|
-
));
|
|
494
|
-
}
|
|
495
|
-
function sameSpace(granted, requested) {
|
|
496
|
-
return granted === requested || granted.endsWith(`:${requested}`);
|
|
497
|
-
}
|
|
498
|
-
function normalizeService(service) {
|
|
499
|
-
return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
|
|
500
|
-
}
|
|
501
|
-
function requiredPermissionSpace(permission) {
|
|
502
|
-
if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
|
|
503
|
-
return permission.space;
|
|
504
|
-
}
|
|
505
482
|
function publicJwk(jwk) {
|
|
506
483
|
const copy = { ...jwk };
|
|
507
484
|
for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
|
|
@@ -510,7 +487,7 @@ function publicJwk(jwk) {
|
|
|
510
487
|
function samePrincipal(left, right) {
|
|
511
488
|
if (typeof left !== "string") return false;
|
|
512
489
|
try {
|
|
513
|
-
return (0,
|
|
490
|
+
return (0, import_node_sdk2.principalDidEquals)(left, right);
|
|
514
491
|
} catch {
|
|
515
492
|
return false;
|
|
516
493
|
}
|
|
@@ -524,11 +501,6 @@ function isPermissionEntry(value) {
|
|
|
524
501
|
function isRecord(value) {
|
|
525
502
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
526
503
|
}
|
|
527
|
-
function parseDate(value) {
|
|
528
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
529
|
-
const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
|
|
530
|
-
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
531
|
-
}
|
|
532
504
|
async function secureDirectory(path) {
|
|
533
505
|
await (0, import_promises.mkdir)(path, { recursive: true, mode: 448 });
|
|
534
506
|
await (0, import_promises.chmod)(path, 448);
|
|
@@ -598,8 +570,8 @@ var import_ajv = require("@modelcontextprotocol/server/validators/ajv");
|
|
|
598
570
|
var import_operations2 = require("@tinycloud/operations");
|
|
599
571
|
|
|
600
572
|
// src/results.ts
|
|
601
|
-
function toMcpToolResult(result) {
|
|
602
|
-
const canonical = isCanonicalOperationResult(result) ? result : {
|
|
573
|
+
function toMcpToolResult(result, bindings = []) {
|
|
574
|
+
const canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
603
575
|
status: "error",
|
|
604
576
|
operation: {},
|
|
605
577
|
context: {},
|
|
@@ -610,6 +582,18 @@ function toMcpToolResult(result) {
|
|
|
610
582
|
structuredContent: canonical
|
|
611
583
|
};
|
|
612
584
|
}
|
|
585
|
+
function withRecoveryTool(result, bindings) {
|
|
586
|
+
if (result.status !== "error") return result;
|
|
587
|
+
const error = result.error;
|
|
588
|
+
const recovery = error?.details?.recovery;
|
|
589
|
+
if (recovery?.action !== "complete_migration") return result;
|
|
590
|
+
const binding = bindings.find((candidate) => candidate.operationId === recovery.operationId);
|
|
591
|
+
if (binding === void 0) return result;
|
|
592
|
+
return {
|
|
593
|
+
...result,
|
|
594
|
+
error: { ...error, details: { ...error?.details, recovery: { ...recovery, tool: binding.name } } }
|
|
595
|
+
};
|
|
596
|
+
}
|
|
613
597
|
function isCanonicalOperationResult(value) {
|
|
614
598
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
615
599
|
const status = value.status;
|
|
@@ -643,6 +627,10 @@ var packageRequire = (0, import_node_module.createRequire)(
|
|
|
643
627
|
var generatedCatalog = packageRequire(
|
|
644
628
|
"@tinycloud/operations/operations.json"
|
|
645
629
|
);
|
|
630
|
+
var ALL_SURFACES = ["local", "hosted"];
|
|
631
|
+
function bindingSurfaces(binding) {
|
|
632
|
+
return binding.surfaces ?? ALL_SURFACES;
|
|
633
|
+
}
|
|
646
634
|
var TOOL_BINDINGS = [
|
|
647
635
|
{
|
|
648
636
|
name: "tinycloud_status",
|
|
@@ -687,7 +675,10 @@ var TOOL_BINDINGS = [
|
|
|
687
675
|
readOnlyHint: false,
|
|
688
676
|
idempotentHint: true,
|
|
689
677
|
destructiveHint: false,
|
|
690
|
-
openWorldHint: false
|
|
678
|
+
openWorldHint: false,
|
|
679
|
+
// Hosted delegations must arrive through the owner/request-bound OpenKey callback,
|
|
680
|
+
// not a caller-supplied import artifact.
|
|
681
|
+
surfaces: ["local"]
|
|
691
682
|
},
|
|
692
683
|
{
|
|
693
684
|
name: "tinycloud_account_spaces_list",
|
|
@@ -787,6 +778,18 @@ var TOOL_BINDINGS = [
|
|
|
787
778
|
idempotentHint: true,
|
|
788
779
|
destructiveHint: false,
|
|
789
780
|
openWorldHint: true
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
name: "tinycloud_secrets_status",
|
|
784
|
+
operationId: "tinycloud.secrets.status",
|
|
785
|
+
operationVersion: 1,
|
|
786
|
+
readOnlyHint: true,
|
|
787
|
+
idempotentHint: true,
|
|
788
|
+
destructiveHint: false,
|
|
789
|
+
openWorldHint: true,
|
|
790
|
+
// Hosted has no persistent reauthorization path for a revoked session
|
|
791
|
+
// yet (TC-896), so status is local/stdio only.
|
|
792
|
+
surfaces: ["local"]
|
|
790
793
|
}
|
|
791
794
|
];
|
|
792
795
|
function createJsonSchemaValidator() {
|
|
@@ -802,6 +805,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
802
805
|
])
|
|
803
806
|
);
|
|
804
807
|
for (const binding of TOOL_BINDINGS) {
|
|
808
|
+
if (!bindingSurfaces(binding).includes(selection.surface)) continue;
|
|
805
809
|
const operation = definitions.get(`${binding.operationId}@${binding.operationVersion}`);
|
|
806
810
|
if (operation === void 0) {
|
|
807
811
|
throw new Error(`Generated operations catalog is missing ${binding.operationId}@${binding.operationVersion}.`);
|
|
@@ -837,7 +841,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
837
841
|
input
|
|
838
842
|
);
|
|
839
843
|
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
840
|
-
return toMcpToolResult(projected);
|
|
844
|
+
return toMcpToolResult(projected, TOOL_BINDINGS);
|
|
841
845
|
}
|
|
842
846
|
);
|
|
843
847
|
}
|
|
@@ -846,7 +850,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
846
850
|
// package.json
|
|
847
851
|
var package_default = {
|
|
848
852
|
name: "@tinycloud/mcp",
|
|
849
|
-
version: "0.
|
|
853
|
+
version: "0.5.0-beta.10",
|
|
850
854
|
description: "TinyCloud delegated operations MCP server",
|
|
851
855
|
keywords: [
|
|
852
856
|
"tinycloud",
|
|
@@ -917,9 +921,9 @@ var package_default = {
|
|
|
917
921
|
dependencies: {
|
|
918
922
|
"@modelcontextprotocol/node": "2.0.0-beta.4",
|
|
919
923
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
920
|
-
"@tinycloud/node-sdk": "3.2.0-beta.
|
|
924
|
+
"@tinycloud/node-sdk": "3.2.0-beta.9",
|
|
921
925
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
922
|
-
"@tinycloud/operations": "0.5.0-beta.
|
|
926
|
+
"@tinycloud/operations": "0.5.0-beta.10",
|
|
923
927
|
jose: "6.1.3"
|
|
924
928
|
},
|
|
925
929
|
devDependencies: {
|
|
@@ -973,6 +977,7 @@ function createHostedMcpApp(config) {
|
|
|
973
977
|
}
|
|
974
978
|
);
|
|
975
979
|
registerTinyCloudTools(server, {
|
|
980
|
+
surface: "hosted",
|
|
976
981
|
profile: "agent",
|
|
977
982
|
explicitProfile: true,
|
|
978
983
|
allowOwnerProfile: false,
|