@tinycloud/mcp 0.5.0-beta.5 → 0.5.0-beta.7
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 +12 -5
- package/dist/cli.cjs +7 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -4
- package/dist/cli.js.map +1 -1
- package/dist/http-cli.cjs +53 -78
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +52 -75
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +53 -78
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +52 -75
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +53 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +52 -75
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +7 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +7 -4
- package/dist/server.js.map +1 -1
- package/dist/tools.cjs +4 -1
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.js +4 -1
- package/dist/tools.js.map +1 -1
- package/generated/mcp-facts.json +1 -2
- package/package.json +3 -3
- package/skills/tinycloud-delegated-secrets/SKILL.md +1 -1
package/dist/index.cjs
CHANGED
|
@@ -177,7 +177,10 @@ var TOOL_BINDINGS = [
|
|
|
177
177
|
readOnlyHint: false,
|
|
178
178
|
idempotentHint: true,
|
|
179
179
|
destructiveHint: false,
|
|
180
|
-
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"]
|
|
181
184
|
},
|
|
182
185
|
{
|
|
183
186
|
name: "tinycloud_account_spaces_list",
|
|
@@ -349,7 +352,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
349
352
|
// package.json
|
|
350
353
|
var package_default = {
|
|
351
354
|
name: "@tinycloud/mcp",
|
|
352
|
-
version: "0.5.0-beta.
|
|
355
|
+
version: "0.5.0-beta.7",
|
|
353
356
|
description: "TinyCloud delegated operations MCP server",
|
|
354
357
|
keywords: [
|
|
355
358
|
"tinycloud",
|
|
@@ -420,9 +423,9 @@ var package_default = {
|
|
|
420
423
|
dependencies: {
|
|
421
424
|
"@modelcontextprotocol/node": "2.0.0-beta.4",
|
|
422
425
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
423
|
-
"@tinycloud/node-sdk": "3.2.0-beta.
|
|
426
|
+
"@tinycloud/node-sdk": "3.2.0-beta.6",
|
|
424
427
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
425
|
-
"@tinycloud/operations": "0.5.0-beta.
|
|
428
|
+
"@tinycloud/operations": "0.5.0-beta.7",
|
|
426
429
|
jose: "6.1.3"
|
|
427
430
|
},
|
|
428
431
|
devDependencies: {
|
|
@@ -565,6 +568,7 @@ var import_server5 = require("@modelcontextprotocol/server");
|
|
|
565
568
|
|
|
566
569
|
// src/oauth.ts
|
|
567
570
|
var import_server4 = require("@modelcontextprotocol/server");
|
|
571
|
+
var import_node_sdk = require("@tinycloud/node-sdk");
|
|
568
572
|
var import_jose = require("jose");
|
|
569
573
|
var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
|
|
570
574
|
function createOpenKeyTokenVerifier(config) {
|
|
@@ -601,11 +605,11 @@ function authInfo(token, payload, config) {
|
|
|
601
605
|
);
|
|
602
606
|
}
|
|
603
607
|
const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
|
|
604
|
-
const ownerDids =
|
|
605
|
-
if (ownerDids
|
|
608
|
+
const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
|
|
609
|
+
if (ownerDids === void 0) {
|
|
606
610
|
throw new import_server4.OAuthError(
|
|
607
611
|
import_server4.OAuthErrorCode.InvalidToken,
|
|
608
|
-
"The OpenKey access token has no TinyCloud owner identity."
|
|
612
|
+
"The OpenKey access token has no valid TinyCloud owner identity."
|
|
609
613
|
);
|
|
610
614
|
}
|
|
611
615
|
return {
|
|
@@ -620,6 +624,21 @@ function authInfo(token, payload, config) {
|
|
|
620
624
|
}
|
|
621
625
|
};
|
|
622
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
|
+
}
|
|
623
642
|
function firstString(...values) {
|
|
624
643
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
625
644
|
}
|
|
@@ -628,7 +647,7 @@ function firstString(...values) {
|
|
|
628
647
|
var import_node_crypto = require("crypto");
|
|
629
648
|
var import_promises = require("fs/promises");
|
|
630
649
|
var import_node_path3 = require("path");
|
|
631
|
-
var
|
|
650
|
+
var import_node_sdk2 = require("@tinycloud/node-sdk");
|
|
632
651
|
var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
633
652
|
var import_operations2 = require("@tinycloud/operations");
|
|
634
653
|
var PROFILE = "agent";
|
|
@@ -742,8 +761,7 @@ var RemoteTenantStore = class {
|
|
|
742
761
|
if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
|
|
743
762
|
throw new ApprovalOwnerMismatchError();
|
|
744
763
|
}
|
|
745
|
-
|
|
746
|
-
if (!permissionsCover(pending.permissions, granted)) {
|
|
764
|
+
if (!(0, import_node_sdk2.isCapabilitySubset)(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
|
|
747
765
|
throw new Error("The approved delegation does not cover the requested account access.");
|
|
748
766
|
}
|
|
749
767
|
const profile = await readJson(this.#profilePath(tenant.stateRoot));
|
|
@@ -759,14 +777,25 @@ var RemoteTenantStore = class {
|
|
|
759
777
|
}
|
|
760
778
|
async #storeDelegation(tenant, pending, data) {
|
|
761
779
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
762
|
-
const { node } = await this.#restoreApproval(tenant, data);
|
|
780
|
+
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
763
781
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
764
|
-
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
|
+
});
|
|
765
792
|
const result = await (0, import_operations2.invokeOperation)(
|
|
766
793
|
"tinycloud.auth.import",
|
|
767
794
|
1,
|
|
768
795
|
{ profile: PROFILE, stateRoot: tenant.stateRoot },
|
|
769
796
|
{
|
|
797
|
+
kind: "tinycloud.auth.delegation",
|
|
798
|
+
version: 1,
|
|
770
799
|
requestId: pending.requestId,
|
|
771
800
|
delegationCid: delegation.cid,
|
|
772
801
|
delegation
|
|
@@ -776,12 +805,14 @@ var RemoteTenantStore = class {
|
|
|
776
805
|
}
|
|
777
806
|
/**
|
|
778
807
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
779
|
-
*
|
|
780
|
-
*
|
|
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.
|
|
781
812
|
*/
|
|
782
813
|
async #restoreApproval(tenant, data) {
|
|
783
814
|
const session = sessionFromCallback(data, tenant.jwk);
|
|
784
|
-
const node = new
|
|
815
|
+
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
785
816
|
await node.restoreSession(session);
|
|
786
817
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
787
818
|
throw new Error("The approved delegation targets a different session.");
|
|
@@ -802,7 +833,7 @@ var RemoteTenantStore = class {
|
|
|
802
833
|
const raw = await readJson(this.#sessionPath(tenant.stateRoot));
|
|
803
834
|
if (raw === null) return false;
|
|
804
835
|
try {
|
|
805
|
-
const node = new
|
|
836
|
+
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
806
837
|
await node.restoreSession(raw);
|
|
807
838
|
return samePrincipal(node.sessionDid, tenant.sessionDid) && ownerDids.some((did) => samePrincipal(node.did, did));
|
|
808
839
|
} catch {
|
|
@@ -957,7 +988,7 @@ function ensureWasm() {
|
|
|
957
988
|
}
|
|
958
989
|
function sessionFromCallback(data, privateJwk) {
|
|
959
990
|
const delegationHeader = data.delegationHeader;
|
|
960
|
-
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") {
|
|
961
992
|
throw new Error("OpenKey returned an incomplete delegation.");
|
|
962
993
|
}
|
|
963
994
|
return {
|
|
@@ -966,63 +997,12 @@ function sessionFromCallback(data, privateJwk) {
|
|
|
966
997
|
spaceId: data.spaceId,
|
|
967
998
|
jwk: privateJwk,
|
|
968
999
|
verificationMethod: data.verificationMethod,
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1000
|
+
address: data.address,
|
|
1001
|
+
chainId: data.chainId,
|
|
1002
|
+
siwe: data.siwe,
|
|
1003
|
+
signature: data.signature
|
|
973
1004
|
};
|
|
974
1005
|
}
|
|
975
|
-
function portableFromCallback(data, permissions, host) {
|
|
976
|
-
const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
|
|
977
|
-
if (primary === void 0) throw new Error("The authority request has no permissions.");
|
|
978
|
-
const session = sessionFromCallback(data, {});
|
|
979
|
-
const expiry = delegationExpiry(data);
|
|
980
|
-
return {
|
|
981
|
-
cid: session.delegationCid,
|
|
982
|
-
delegationHeader: session.delegationHeader,
|
|
983
|
-
spaceId: session.spaceId,
|
|
984
|
-
path: primary.path,
|
|
985
|
-
actions: [...primary.actions],
|
|
986
|
-
resources: permissions.map((permission) => ({
|
|
987
|
-
service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
|
|
988
|
-
space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
|
|
989
|
-
path: permission.path,
|
|
990
|
-
actions: [...permission.actions]
|
|
991
|
-
})),
|
|
992
|
-
expiry,
|
|
993
|
-
delegateDID: session.verificationMethod,
|
|
994
|
-
ownerAddress: typeof data.address === "string" ? data.address : "",
|
|
995
|
-
chainId: typeof data.chainId === "number" ? data.chainId : 1,
|
|
996
|
-
host
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
function delegationExpiry(data) {
|
|
1000
|
-
for (const key of ["expiry", "expiresAt", "expirationTime"]) {
|
|
1001
|
-
const parsed = parseDate(data[key]);
|
|
1002
|
-
if (parsed !== void 0) return parsed;
|
|
1003
|
-
}
|
|
1004
|
-
if (typeof data.siwe === "string") {
|
|
1005
|
-
const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
1006
|
-
const parsed = parseDate(match?.[1]?.trim());
|
|
1007
|
-
if (parsed !== void 0) return parsed;
|
|
1008
|
-
}
|
|
1009
|
-
throw new Error("OpenKey did not return a delegation expiry.");
|
|
1010
|
-
}
|
|
1011
|
-
function permissionsCover(requested, granted) {
|
|
1012
|
-
return requested.every((request) => granted.some(
|
|
1013
|
-
(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))
|
|
1014
|
-
));
|
|
1015
|
-
}
|
|
1016
|
-
function sameSpace(granted, requested) {
|
|
1017
|
-
return granted === requested || granted.endsWith(`:${requested}`);
|
|
1018
|
-
}
|
|
1019
|
-
function normalizeService(service) {
|
|
1020
|
-
return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
|
|
1021
|
-
}
|
|
1022
|
-
function requiredPermissionSpace(permission) {
|
|
1023
|
-
if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
|
|
1024
|
-
return permission.space;
|
|
1025
|
-
}
|
|
1026
1006
|
function publicJwk(jwk) {
|
|
1027
1007
|
const copy = { ...jwk };
|
|
1028
1008
|
for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
|
|
@@ -1031,7 +1011,7 @@ function publicJwk(jwk) {
|
|
|
1031
1011
|
function samePrincipal(left, right) {
|
|
1032
1012
|
if (typeof left !== "string") return false;
|
|
1033
1013
|
try {
|
|
1034
|
-
return (0,
|
|
1014
|
+
return (0, import_node_sdk2.principalDidEquals)(left, right);
|
|
1035
1015
|
} catch {
|
|
1036
1016
|
return false;
|
|
1037
1017
|
}
|
|
@@ -1045,11 +1025,6 @@ function isPermissionEntry(value) {
|
|
|
1045
1025
|
function isRecord(value) {
|
|
1046
1026
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1047
1027
|
}
|
|
1048
|
-
function parseDate(value) {
|
|
1049
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
1050
|
-
const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
|
|
1051
|
-
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
1052
|
-
}
|
|
1053
1028
|
async function secureDirectory(path) {
|
|
1054
1029
|
await (0, import_promises.mkdir)(path, { recursive: true, mode: 448 });
|
|
1055
1030
|
await (0, import_promises.chmod)(path, 448);
|