@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.js
CHANGED
|
@@ -140,7 +140,10 @@ var TOOL_BINDINGS = [
|
|
|
140
140
|
readOnlyHint: false,
|
|
141
141
|
idempotentHint: true,
|
|
142
142
|
destructiveHint: false,
|
|
143
|
-
openWorldHint: false
|
|
143
|
+
openWorldHint: false,
|
|
144
|
+
// Hosted delegations must arrive through the owner/request-bound OpenKey callback,
|
|
145
|
+
// not a caller-supplied import artifact.
|
|
146
|
+
surfaces: ["local"]
|
|
144
147
|
},
|
|
145
148
|
{
|
|
146
149
|
name: "tinycloud_account_spaces_list",
|
|
@@ -312,7 +315,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
312
315
|
// package.json
|
|
313
316
|
var package_default = {
|
|
314
317
|
name: "@tinycloud/mcp",
|
|
315
|
-
version: "0.5.0-beta.
|
|
318
|
+
version: "0.5.0-beta.7",
|
|
316
319
|
description: "TinyCloud delegated operations MCP server",
|
|
317
320
|
keywords: [
|
|
318
321
|
"tinycloud",
|
|
@@ -383,9 +386,9 @@ var package_default = {
|
|
|
383
386
|
dependencies: {
|
|
384
387
|
"@modelcontextprotocol/node": "2.0.0-beta.4",
|
|
385
388
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
386
|
-
"@tinycloud/node-sdk": "3.2.0-beta.
|
|
389
|
+
"@tinycloud/node-sdk": "3.2.0-beta.6",
|
|
387
390
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
388
|
-
"@tinycloud/operations": "0.5.0-beta.
|
|
391
|
+
"@tinycloud/operations": "0.5.0-beta.7",
|
|
389
392
|
jose: "6.1.3"
|
|
390
393
|
},
|
|
391
394
|
devDependencies: {
|
|
@@ -539,6 +542,7 @@ import {
|
|
|
539
542
|
OAuthError,
|
|
540
543
|
OAuthErrorCode
|
|
541
544
|
} from "@modelcontextprotocol/server";
|
|
545
|
+
import { pkhDid } from "@tinycloud/node-sdk";
|
|
542
546
|
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
543
547
|
var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
|
|
544
548
|
function createOpenKeyTokenVerifier(config) {
|
|
@@ -575,11 +579,11 @@ function authInfo(token, payload, config) {
|
|
|
575
579
|
);
|
|
576
580
|
}
|
|
577
581
|
const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
|
|
578
|
-
const ownerDids =
|
|
579
|
-
if (ownerDids
|
|
582
|
+
const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
|
|
583
|
+
if (ownerDids === void 0) {
|
|
580
584
|
throw new OAuthError(
|
|
581
585
|
OAuthErrorCode.InvalidToken,
|
|
582
|
-
"The OpenKey access token has no TinyCloud owner identity."
|
|
586
|
+
"The OpenKey access token has no valid TinyCloud owner identity."
|
|
583
587
|
);
|
|
584
588
|
}
|
|
585
589
|
return {
|
|
@@ -594,6 +598,21 @@ function authInfo(token, payload, config) {
|
|
|
594
598
|
}
|
|
595
599
|
};
|
|
596
600
|
}
|
|
601
|
+
var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
|
|
602
|
+
function canonicalOwnerDids(claim) {
|
|
603
|
+
if (!Array.isArray(claim) || claim.length === 0) return void 0;
|
|
604
|
+
const ownerDids = [];
|
|
605
|
+
for (const value of claim) {
|
|
606
|
+
const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
|
|
607
|
+
if (match === null) return void 0;
|
|
608
|
+
try {
|
|
609
|
+
ownerDids.push(pkhDid(match[2], Number(match[1])));
|
|
610
|
+
} catch {
|
|
611
|
+
return void 0;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return [...new Set(ownerDids)];
|
|
615
|
+
}
|
|
597
616
|
function firstString(...values) {
|
|
598
617
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
599
618
|
}
|
|
@@ -604,7 +623,9 @@ import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promi
|
|
|
604
623
|
import { dirname, join as join2 } from "path";
|
|
605
624
|
import {
|
|
606
625
|
TinyCloudNode,
|
|
607
|
-
|
|
626
|
+
isCapabilitySubset,
|
|
627
|
+
principalDidEquals,
|
|
628
|
+
siweRuntimeDelegation
|
|
608
629
|
} from "@tinycloud/node-sdk";
|
|
609
630
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
610
631
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
@@ -719,8 +740,7 @@ var RemoteTenantStore = class {
|
|
|
719
740
|
if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
|
|
720
741
|
throw new ApprovalOwnerMismatchError();
|
|
721
742
|
}
|
|
722
|
-
|
|
723
|
-
if (!permissionsCover(pending.permissions, granted)) {
|
|
743
|
+
if (!isCapabilitySubset(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
|
|
724
744
|
throw new Error("The approved delegation does not cover the requested account access.");
|
|
725
745
|
}
|
|
726
746
|
const profile = await readJson(this.#profilePath(tenant.stateRoot));
|
|
@@ -736,14 +756,25 @@ var RemoteTenantStore = class {
|
|
|
736
756
|
}
|
|
737
757
|
async #storeDelegation(tenant, pending, data) {
|
|
738
758
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
739
|
-
const { node } = await this.#restoreApproval(tenant, data);
|
|
759
|
+
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
740
760
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
741
|
-
const delegation =
|
|
761
|
+
const delegation = siweRuntimeDelegation(node, {
|
|
762
|
+
authorization: session.delegationHeader.Authorization,
|
|
763
|
+
cid: session.delegationCid,
|
|
764
|
+
spaceId: session.spaceId,
|
|
765
|
+
ownerAddress: session.address,
|
|
766
|
+
chainId: session.chainId,
|
|
767
|
+
siwe: session.siwe,
|
|
768
|
+
signature: session.signature,
|
|
769
|
+
host: this.#config.nodeHost
|
|
770
|
+
});
|
|
742
771
|
const result = await invokeOperation2(
|
|
743
772
|
"tinycloud.auth.import",
|
|
744
773
|
1,
|
|
745
774
|
{ profile: PROFILE, stateRoot: tenant.stateRoot },
|
|
746
775
|
{
|
|
776
|
+
kind: "tinycloud.auth.delegation",
|
|
777
|
+
version: 1,
|
|
747
778
|
requestId: pending.requestId,
|
|
748
779
|
delegationCid: delegation.cid,
|
|
749
780
|
delegation
|
|
@@ -753,8 +784,10 @@ var RemoteTenantStore = class {
|
|
|
753
784
|
}
|
|
754
785
|
/**
|
|
755
786
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
756
|
-
*
|
|
757
|
-
*
|
|
787
|
+
* callback must carry the full signed proof (SIWE, signature, address and
|
|
788
|
+
* chain), so the WASM verifier always runs: it binds the authorization bytes
|
|
789
|
+
* and CID to the signed SIWE, and `node.did` is the wallet that signed
|
|
790
|
+
* exactly the delegation being stored.
|
|
758
791
|
*/
|
|
759
792
|
async #restoreApproval(tenant, data) {
|
|
760
793
|
const session = sessionFromCallback(data, tenant.jwk);
|
|
@@ -934,7 +967,7 @@ function ensureWasm() {
|
|
|
934
967
|
}
|
|
935
968
|
function sessionFromCallback(data, privateJwk) {
|
|
936
969
|
const delegationHeader = data.delegationHeader;
|
|
937
|
-
if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
|
|
970
|
+
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") {
|
|
938
971
|
throw new Error("OpenKey returned an incomplete delegation.");
|
|
939
972
|
}
|
|
940
973
|
return {
|
|
@@ -943,63 +976,12 @@ function sessionFromCallback(data, privateJwk) {
|
|
|
943
976
|
spaceId: data.spaceId,
|
|
944
977
|
jwk: privateJwk,
|
|
945
978
|
verificationMethod: data.verificationMethod,
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
function portableFromCallback(data, permissions, host) {
|
|
953
|
-
const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
|
|
954
|
-
if (primary === void 0) throw new Error("The authority request has no permissions.");
|
|
955
|
-
const session = sessionFromCallback(data, {});
|
|
956
|
-
const expiry = delegationExpiry(data);
|
|
957
|
-
return {
|
|
958
|
-
cid: session.delegationCid,
|
|
959
|
-
delegationHeader: session.delegationHeader,
|
|
960
|
-
spaceId: session.spaceId,
|
|
961
|
-
path: primary.path,
|
|
962
|
-
actions: [...primary.actions],
|
|
963
|
-
resources: permissions.map((permission) => ({
|
|
964
|
-
service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
|
|
965
|
-
space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
|
|
966
|
-
path: permission.path,
|
|
967
|
-
actions: [...permission.actions]
|
|
968
|
-
})),
|
|
969
|
-
expiry,
|
|
970
|
-
delegateDID: session.verificationMethod,
|
|
971
|
-
ownerAddress: typeof data.address === "string" ? data.address : "",
|
|
972
|
-
chainId: typeof data.chainId === "number" ? data.chainId : 1,
|
|
973
|
-
host
|
|
979
|
+
address: data.address,
|
|
980
|
+
chainId: data.chainId,
|
|
981
|
+
siwe: data.siwe,
|
|
982
|
+
signature: data.signature
|
|
974
983
|
};
|
|
975
984
|
}
|
|
976
|
-
function delegationExpiry(data) {
|
|
977
|
-
for (const key of ["expiry", "expiresAt", "expirationTime"]) {
|
|
978
|
-
const parsed = parseDate(data[key]);
|
|
979
|
-
if (parsed !== void 0) return parsed;
|
|
980
|
-
}
|
|
981
|
-
if (typeof data.siwe === "string") {
|
|
982
|
-
const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
|
|
983
|
-
const parsed = parseDate(match?.[1]?.trim());
|
|
984
|
-
if (parsed !== void 0) return parsed;
|
|
985
|
-
}
|
|
986
|
-
throw new Error("OpenKey did not return a delegation expiry.");
|
|
987
|
-
}
|
|
988
|
-
function permissionsCover(requested, granted) {
|
|
989
|
-
return requested.every((request) => granted.some(
|
|
990
|
-
(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))
|
|
991
|
-
));
|
|
992
|
-
}
|
|
993
|
-
function sameSpace(granted, requested) {
|
|
994
|
-
return granted === requested || granted.endsWith(`:${requested}`);
|
|
995
|
-
}
|
|
996
|
-
function normalizeService(service) {
|
|
997
|
-
return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
|
|
998
|
-
}
|
|
999
|
-
function requiredPermissionSpace(permission) {
|
|
1000
|
-
if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
|
|
1001
|
-
return permission.space;
|
|
1002
|
-
}
|
|
1003
985
|
function publicJwk(jwk) {
|
|
1004
986
|
const copy = { ...jwk };
|
|
1005
987
|
for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
|
|
@@ -1022,11 +1004,6 @@ function isPermissionEntry(value) {
|
|
|
1022
1004
|
function isRecord(value) {
|
|
1023
1005
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1024
1006
|
}
|
|
1025
|
-
function parseDate(value) {
|
|
1026
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
1027
|
-
const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
|
|
1028
|
-
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
1029
|
-
}
|
|
1030
1007
|
async function secureDirectory(path) {
|
|
1031
1008
|
await mkdir(path, { recursive: true, mode: 448 });
|
|
1032
1009
|
await chmod(path, 448);
|