@tinycloud/mcp 0.5.0-beta.6 → 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.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.6",
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.5",
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.6",
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 = Array.isArray(payload[OWNER_DIDS_CLAIM]) ? payload[OWNER_DIDS_CLAIM].filter((value) => typeof value === "string") : [];
579
- if (ownerDids.length === 0) {
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
- principalDidEquals
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
- const granted = [...node.getVerifiedSessionCapabilities()];
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 = portableFromCallback(data, pending.permissions, this.#config.nodeHost);
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
- * WASM verifier binds the authorization bytes and CID to the signed SIWE, so
757
- * `node.did` is the wallet that signed exactly the delegation being stored.
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
- ...typeof data.address === "string" ? { address: data.address } : {},
947
- ...typeof data.chainId === "number" ? { chainId: data.chainId } : {},
948
- ...typeof data.siwe === "string" ? { siwe: data.siwe } : {},
949
- ...typeof data.signature === "string" ? { signature: data.signature } : {}
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);