@tinycloud/mcp 0.4.1-beta.0 → 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/index.cjs
CHANGED
|
@@ -50,8 +50,8 @@ var import_ajv = require("@modelcontextprotocol/server/validators/ajv");
|
|
|
50
50
|
var import_operations = require("@tinycloud/operations");
|
|
51
51
|
|
|
52
52
|
// src/results.ts
|
|
53
|
-
function toMcpToolResult(result) {
|
|
54
|
-
const canonical = isCanonicalOperationResult(result) ? result : {
|
|
53
|
+
function toMcpToolResult(result, bindings = []) {
|
|
54
|
+
const canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
55
55
|
status: "error",
|
|
56
56
|
operation: {},
|
|
57
57
|
context: {},
|
|
@@ -62,6 +62,18 @@ function toMcpToolResult(result) {
|
|
|
62
62
|
structuredContent: canonical
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
+
function withRecoveryTool(result, bindings) {
|
|
66
|
+
if (result.status !== "error") return result;
|
|
67
|
+
const error = result.error;
|
|
68
|
+
const recovery = error?.details?.recovery;
|
|
69
|
+
if (recovery?.action !== "complete_migration") return result;
|
|
70
|
+
const binding = bindings.find((candidate) => candidate.operationId === recovery.operationId);
|
|
71
|
+
if (binding === void 0) return result;
|
|
72
|
+
return {
|
|
73
|
+
...result,
|
|
74
|
+
error: { ...error, details: { ...error?.details, recovery: { ...recovery, tool: binding.name } } }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
65
77
|
function canonicalMcpAdapterResult(result) {
|
|
66
78
|
return toMcpToolResult(result).structuredContent;
|
|
67
79
|
}
|
|
@@ -101,7 +113,8 @@ var TOOL_NAMES = [
|
|
|
101
113
|
"tinycloud_sql_schema_inspect",
|
|
102
114
|
"tinycloud_sql_query",
|
|
103
115
|
"tinycloud_sql_execute",
|
|
104
|
-
"tinycloud_secrets_get"
|
|
116
|
+
"tinycloud_secrets_get",
|
|
117
|
+
"tinycloud_secrets_status"
|
|
105
118
|
];
|
|
106
119
|
function invocationTargetForMcp(selection) {
|
|
107
120
|
return {
|
|
@@ -116,6 +129,10 @@ var packageRequire = (0, import_node_module.createRequire)(
|
|
|
116
129
|
var generatedCatalog = packageRequire(
|
|
117
130
|
"@tinycloud/operations/operations.json"
|
|
118
131
|
);
|
|
132
|
+
var ALL_SURFACES = ["local", "hosted"];
|
|
133
|
+
function bindingSurfaces(binding) {
|
|
134
|
+
return binding.surfaces ?? ALL_SURFACES;
|
|
135
|
+
}
|
|
119
136
|
var TOOL_BINDINGS = [
|
|
120
137
|
{
|
|
121
138
|
name: "tinycloud_status",
|
|
@@ -160,7 +177,10 @@ var TOOL_BINDINGS = [
|
|
|
160
177
|
readOnlyHint: false,
|
|
161
178
|
idempotentHint: true,
|
|
162
179
|
destructiveHint: false,
|
|
163
|
-
openWorldHint: false
|
|
180
|
+
openWorldHint: false,
|
|
181
|
+
// Hosted delegations must arrive through the owner/request-bound OpenKey callback,
|
|
182
|
+
// not a caller-supplied import artifact.
|
|
183
|
+
surfaces: ["local"]
|
|
164
184
|
},
|
|
165
185
|
{
|
|
166
186
|
name: "tinycloud_account_spaces_list",
|
|
@@ -260,6 +280,18 @@ var TOOL_BINDINGS = [
|
|
|
260
280
|
idempotentHint: true,
|
|
261
281
|
destructiveHint: false,
|
|
262
282
|
openWorldHint: true
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "tinycloud_secrets_status",
|
|
286
|
+
operationId: "tinycloud.secrets.status",
|
|
287
|
+
operationVersion: 1,
|
|
288
|
+
readOnlyHint: true,
|
|
289
|
+
idempotentHint: true,
|
|
290
|
+
destructiveHint: false,
|
|
291
|
+
openWorldHint: true,
|
|
292
|
+
// Hosted has no persistent reauthorization path for a revoked session
|
|
293
|
+
// yet (TC-896), so status is local/stdio only.
|
|
294
|
+
surfaces: ["local"]
|
|
263
295
|
}
|
|
264
296
|
];
|
|
265
297
|
function createJsonSchemaValidator() {
|
|
@@ -275,6 +307,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
275
307
|
])
|
|
276
308
|
);
|
|
277
309
|
for (const binding of TOOL_BINDINGS) {
|
|
310
|
+
if (!bindingSurfaces(binding).includes(selection.surface)) continue;
|
|
278
311
|
const operation = definitions.get(`${binding.operationId}@${binding.operationVersion}`);
|
|
279
312
|
if (operation === void 0) {
|
|
280
313
|
throw new Error(`Generated operations catalog is missing ${binding.operationId}@${binding.operationVersion}.`);
|
|
@@ -310,7 +343,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
310
343
|
input
|
|
311
344
|
);
|
|
312
345
|
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
313
|
-
return toMcpToolResult(projected);
|
|
346
|
+
return toMcpToolResult(projected, TOOL_BINDINGS);
|
|
314
347
|
}
|
|
315
348
|
);
|
|
316
349
|
}
|
|
@@ -319,7 +352,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
319
352
|
// package.json
|
|
320
353
|
var package_default = {
|
|
321
354
|
name: "@tinycloud/mcp",
|
|
322
|
-
version: "0.
|
|
355
|
+
version: "0.5.0-beta.10",
|
|
323
356
|
description: "TinyCloud delegated operations MCP server",
|
|
324
357
|
keywords: [
|
|
325
358
|
"tinycloud",
|
|
@@ -390,9 +423,9 @@ var package_default = {
|
|
|
390
423
|
dependencies: {
|
|
391
424
|
"@modelcontextprotocol/node": "2.0.0-beta.4",
|
|
392
425
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
393
|
-
"@tinycloud/node-sdk": "3.2.0-beta.
|
|
426
|
+
"@tinycloud/node-sdk": "3.2.0-beta.9",
|
|
394
427
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
395
|
-
"@tinycloud/operations": "0.5.0-beta.
|
|
428
|
+
"@tinycloud/operations": "0.5.0-beta.10",
|
|
396
429
|
jose: "6.1.3"
|
|
397
430
|
},
|
|
398
431
|
devDependencies: {
|
|
@@ -419,7 +452,7 @@ function createTinyCloudMcpServer(selection) {
|
|
|
419
452
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
420
453
|
{ jsonSchemaValidator: validator }
|
|
421
454
|
);
|
|
422
|
-
registerTinyCloudTools(server, selection, validator);
|
|
455
|
+
registerTinyCloudTools(server, { ...selection, surface: "local" }, validator);
|
|
423
456
|
return server;
|
|
424
457
|
}
|
|
425
458
|
function serveTinyCloudMcp(selection) {
|
|
@@ -535,6 +568,7 @@ var import_server5 = require("@modelcontextprotocol/server");
|
|
|
535
568
|
|
|
536
569
|
// src/oauth.ts
|
|
537
570
|
var import_server4 = require("@modelcontextprotocol/server");
|
|
571
|
+
var import_node_sdk = require("@tinycloud/node-sdk");
|
|
538
572
|
var import_jose = require("jose");
|
|
539
573
|
var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
|
|
540
574
|
function createOpenKeyTokenVerifier(config) {
|
|
@@ -571,11 +605,11 @@ function authInfo(token, payload, config) {
|
|
|
571
605
|
);
|
|
572
606
|
}
|
|
573
607
|
const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
|
|
574
|
-
const ownerDids =
|
|
575
|
-
if (ownerDids
|
|
608
|
+
const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
|
|
609
|
+
if (ownerDids === void 0) {
|
|
576
610
|
throw new import_server4.OAuthError(
|
|
577
611
|
import_server4.OAuthErrorCode.InvalidToken,
|
|
578
|
-
"The OpenKey access token has no TinyCloud owner identity."
|
|
612
|
+
"The OpenKey access token has no valid TinyCloud owner identity."
|
|
579
613
|
);
|
|
580
614
|
}
|
|
581
615
|
return {
|
|
@@ -590,6 +624,21 @@ function authInfo(token, payload, config) {
|
|
|
590
624
|
}
|
|
591
625
|
};
|
|
592
626
|
}
|
|
627
|
+
var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
|
|
628
|
+
function canonicalOwnerDids(claim) {
|
|
629
|
+
if (!Array.isArray(claim) || claim.length === 0) return void 0;
|
|
630
|
+
const ownerDids = [];
|
|
631
|
+
for (const value of claim) {
|
|
632
|
+
const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
|
|
633
|
+
if (match === null) return void 0;
|
|
634
|
+
try {
|
|
635
|
+
ownerDids.push((0, import_node_sdk.pkhDid)(match[2], Number(match[1])));
|
|
636
|
+
} catch {
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return [...new Set(ownerDids)];
|
|
641
|
+
}
|
|
593
642
|
function firstString(...values) {
|
|
594
643
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
595
644
|
}
|
|
@@ -598,7 +647,7 @@ function firstString(...values) {
|
|
|
598
647
|
var import_node_crypto = require("crypto");
|
|
599
648
|
var import_promises = require("fs/promises");
|
|
600
649
|
var import_node_path3 = require("path");
|
|
601
|
-
var
|
|
650
|
+
var import_node_sdk2 = require("@tinycloud/node-sdk");
|
|
602
651
|
var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
603
652
|
var import_operations2 = require("@tinycloud/operations");
|
|
604
653
|
var PROFILE = "agent";
|
|
@@ -712,8 +761,7 @@ var RemoteTenantStore = class {
|
|
|
712
761
|
if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
|
|
713
762
|
throw new ApprovalOwnerMismatchError();
|
|
714
763
|
}
|
|
715
|
-
|
|
716
|
-
if (!permissionsCover(pending.permissions, granted)) {
|
|
764
|
+
if (!(0, import_node_sdk2.isCapabilitySubset)(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
|
|
717
765
|
throw new Error("The approved delegation does not cover the requested account access.");
|
|
718
766
|
}
|
|
719
767
|
const profile = await readJson(this.#profilePath(tenant.stateRoot));
|
|
@@ -729,14 +777,25 @@ var RemoteTenantStore = class {
|
|
|
729
777
|
}
|
|
730
778
|
async #storeDelegation(tenant, pending, data) {
|
|
731
779
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
732
|
-
const { node } = await this.#restoreApproval(tenant, data);
|
|
780
|
+
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
733
781
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
734
|
-
const delegation =
|
|
782
|
+
const delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
|
|
783
|
+
authorization: session.delegationHeader.Authorization,
|
|
784
|
+
cid: session.delegationCid,
|
|
785
|
+
spaceId: session.spaceId,
|
|
786
|
+
ownerAddress: session.address,
|
|
787
|
+
chainId: session.chainId,
|
|
788
|
+
siwe: session.siwe,
|
|
789
|
+
signature: session.signature,
|
|
790
|
+
host: this.#config.nodeHost
|
|
791
|
+
});
|
|
735
792
|
const result = await (0, import_operations2.invokeOperation)(
|
|
736
793
|
"tinycloud.auth.import",
|
|
737
794
|
1,
|
|
738
795
|
{ profile: PROFILE, stateRoot: tenant.stateRoot },
|
|
739
796
|
{
|
|
797
|
+
kind: "tinycloud.auth.delegation",
|
|
798
|
+
version: 1,
|
|
740
799
|
requestId: pending.requestId,
|
|
741
800
|
delegationCid: delegation.cid,
|
|
742
801
|
delegation
|
|
@@ -746,12 +805,14 @@ var RemoteTenantStore = class {
|
|
|
746
805
|
}
|
|
747
806
|
/**
|
|
748
807
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
749
|
-
*
|
|
750
|
-
*
|
|
808
|
+
* callback must carry the full signed proof (SIWE, signature, address and
|
|
809
|
+
* chain), so the WASM verifier always runs: it binds the authorization bytes
|
|
810
|
+
* and CID to the signed SIWE, and `node.did` is the wallet that signed
|
|
811
|
+
* exactly the delegation being stored.
|
|
751
812
|
*/
|
|
752
813
|
async #restoreApproval(tenant, data) {
|
|
753
814
|
const session = sessionFromCallback(data, tenant.jwk);
|
|
754
|
-
const node = new
|
|
815
|
+
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
755
816
|
await node.restoreSession(session);
|
|
756
817
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
757
818
|
throw new Error("The approved delegation targets a different session.");
|
|
@@ -772,7 +833,7 @@ var RemoteTenantStore = class {
|
|
|
772
833
|
const raw = await readJson(this.#sessionPath(tenant.stateRoot));
|
|
773
834
|
if (raw === null) return false;
|
|
774
835
|
try {
|
|
775
|
-
const node = new
|
|
836
|
+
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
776
837
|
await node.restoreSession(raw);
|
|
777
838
|
return samePrincipal(node.sessionDid, tenant.sessionDid) && ownerDids.some((did) => samePrincipal(node.did, did));
|
|
778
839
|
} catch {
|
|
@@ -927,7 +988,7 @@ function ensureWasm() {
|
|
|
927
988
|
}
|
|
928
989
|
function sessionFromCallback(data, privateJwk) {
|
|
929
990
|
const delegationHeader = data.delegationHeader;
|
|
930
|
-
if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
|
|
991
|
+
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") {
|
|
931
992
|
throw new Error("OpenKey returned an incomplete delegation.");
|
|
932
993
|
}
|
|
933
994
|
return {
|
|
@@ -936,63 +997,12 @@ function sessionFromCallback(data, privateJwk) {
|
|
|
936
997
|
spaceId: data.spaceId,
|
|
937
998
|
jwk: privateJwk,
|
|
938
999
|
verificationMethod: data.verificationMethod,
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1000
|
+
address: data.address,
|
|
1001
|
+
chainId: data.chainId,
|
|
1002
|
+
siwe: data.siwe,
|
|
1003
|
+
signature: data.signature
|
|
943
1004
|
};
|
|
944
1005
|
}
|
|
945
|
-
function portableFromCallback(data, permissions, host) {
|
|
946
|
-
const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
|
|
947
|
-
if (primary === void 0) throw new Error("The authority request has no permissions.");
|
|
948
|
-
const session = sessionFromCallback(data, {});
|
|
949
|
-
const expiry = delegationExpiry(data);
|
|
950
|
-
return {
|
|
951
|
-
cid: session.delegationCid,
|
|
952
|
-
delegationHeader: session.delegationHeader,
|
|
953
|
-
spaceId: session.spaceId,
|
|
954
|
-
path: primary.path,
|
|
955
|
-
actions: [...primary.actions],
|
|
956
|
-
resources: permissions.map((permission) => ({
|
|
957
|
-
service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
|
|
958
|
-
space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
|
|
959
|
-
path: permission.path,
|
|
960
|
-
actions: [...permission.actions]
|
|
961
|
-
})),
|
|
962
|
-
expiry,
|
|
963
|
-
delegateDID: session.verificationMethod,
|
|
964
|
-
ownerAddress: typeof data.address === "string" ? data.address : "",
|
|
965
|
-
chainId: typeof data.chainId === "number" ? data.chainId : 1,
|
|
966
|
-
host
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
function delegationExpiry(data) {
|
|
970
|
-
for (const key of ["expiry", "expiresAt", "expirationTime"]) {
|
|
971
|
-
const parsed = parseDate(data[key]);
|
|
972
|
-
if (parsed !== void 0) return parsed;
|
|
973
|
-
}
|
|
974
|
-
if (typeof data.siwe === "string") {
|
|
975
|
-
const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
976
|
-
const parsed = parseDate(match?.[1]?.trim());
|
|
977
|
-
if (parsed !== void 0) return parsed;
|
|
978
|
-
}
|
|
979
|
-
throw new Error("OpenKey did not return a delegation expiry.");
|
|
980
|
-
}
|
|
981
|
-
function permissionsCover(requested, granted) {
|
|
982
|
-
return requested.every((request) => granted.some(
|
|
983
|
-
(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))
|
|
984
|
-
));
|
|
985
|
-
}
|
|
986
|
-
function sameSpace(granted, requested) {
|
|
987
|
-
return granted === requested || granted.endsWith(`:${requested}`);
|
|
988
|
-
}
|
|
989
|
-
function normalizeService(service) {
|
|
990
|
-
return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
|
|
991
|
-
}
|
|
992
|
-
function requiredPermissionSpace(permission) {
|
|
993
|
-
if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
|
|
994
|
-
return permission.space;
|
|
995
|
-
}
|
|
996
1006
|
function publicJwk(jwk) {
|
|
997
1007
|
const copy = { ...jwk };
|
|
998
1008
|
for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
|
|
@@ -1001,7 +1011,7 @@ function publicJwk(jwk) {
|
|
|
1001
1011
|
function samePrincipal(left, right) {
|
|
1002
1012
|
if (typeof left !== "string") return false;
|
|
1003
1013
|
try {
|
|
1004
|
-
return (0,
|
|
1014
|
+
return (0, import_node_sdk2.principalDidEquals)(left, right);
|
|
1005
1015
|
} catch {
|
|
1006
1016
|
return false;
|
|
1007
1017
|
}
|
|
@@ -1015,11 +1025,6 @@ function isPermissionEntry(value) {
|
|
|
1015
1025
|
function isRecord(value) {
|
|
1016
1026
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1017
1027
|
}
|
|
1018
|
-
function parseDate(value) {
|
|
1019
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
1020
|
-
const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
|
|
1021
|
-
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
1022
|
-
}
|
|
1023
1028
|
async function secureDirectory(path) {
|
|
1024
1029
|
await (0, import_promises.mkdir)(path, { recursive: true, mode: 448 });
|
|
1025
1030
|
await (0, import_promises.chmod)(path, 448);
|
|
@@ -1109,6 +1114,7 @@ function createHostedMcpApp(config) {
|
|
|
1109
1114
|
}
|
|
1110
1115
|
);
|
|
1111
1116
|
registerTinyCloudTools(server, {
|
|
1117
|
+
surface: "hosted",
|
|
1112
1118
|
profile: "agent",
|
|
1113
1119
|
explicitProfile: true,
|
|
1114
1120
|
allowOwnerProfile: false,
|