@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/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.5",
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.5",
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.5",
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 = Array.isArray(payload[OWNER_DIDS_CLAIM]) ? payload[OWNER_DIDS_CLAIM].filter((value) => typeof value === "string") : [];
605
- if (ownerDids.length === 0) {
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 import_node_sdk = require("@tinycloud/node-sdk");
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
- const granted = [...node.getVerifiedSessionCapabilities()];
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 = portableFromCallback(data, pending.permissions, this.#config.nodeHost);
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
- * WASM verifier binds the authorization bytes and CID to the signed SIWE, so
780
- * `node.did` is the wallet that signed exactly the delegation being stored.
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 import_node_sdk.TinyCloudNode({ host: this.#config.nodeHost });
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 import_node_sdk.TinyCloudNode({ host: this.#config.nodeHost });
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
- ...typeof data.address === "string" ? { address: data.address } : {},
970
- ...typeof data.chainId === "number" ? { chainId: data.chainId } : {},
971
- ...typeof data.siwe === "string" ? { siwe: data.siwe } : {},
972
- ...typeof data.signature === "string" ? { signature: data.signature } : {}
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, import_node_sdk.principalDidEquals)(left, right);
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);