@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/http-cli.js CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  OAuthError,
20
20
  OAuthErrorCode
21
21
  } from "@modelcontextprotocol/server";
22
+ import { pkhDid } from "@tinycloud/node-sdk";
22
23
  import { createRemoteJWKSet, errors, jwtVerify } from "jose";
23
24
  var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
24
25
  function createOpenKeyTokenVerifier(config) {
@@ -67,11 +68,11 @@ function authInfo(token, payload, config) {
67
68
  );
68
69
  }
69
70
  const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
70
- const ownerDids = Array.isArray(payload[OWNER_DIDS_CLAIM]) ? payload[OWNER_DIDS_CLAIM].filter((value) => typeof value === "string") : [];
71
- if (ownerDids.length === 0) {
71
+ const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
72
+ if (ownerDids === void 0) {
72
73
  throw new OAuthError(
73
74
  OAuthErrorCode.InvalidToken,
74
- "The OpenKey access token has no TinyCloud owner identity."
75
+ "The OpenKey access token has no valid TinyCloud owner identity."
75
76
  );
76
77
  }
77
78
  return {
@@ -86,6 +87,21 @@ function authInfo(token, payload, config) {
86
87
  }
87
88
  };
88
89
  }
90
+ var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
91
+ function canonicalOwnerDids(claim) {
92
+ if (!Array.isArray(claim) || claim.length === 0) return void 0;
93
+ const ownerDids = [];
94
+ for (const value of claim) {
95
+ const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
96
+ if (match === null) return void 0;
97
+ try {
98
+ ownerDids.push(pkhDid(match[2], Number(match[1])));
99
+ } catch {
100
+ return void 0;
101
+ }
102
+ }
103
+ return [...new Set(ownerDids)];
104
+ }
89
105
  function firstString(...values) {
90
106
  return values.find((value) => typeof value === "string" && value.length > 0);
91
107
  }
@@ -96,7 +112,9 @@ import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promi
96
112
  import { dirname, join } from "path";
97
113
  import {
98
114
  TinyCloudNode,
99
- principalDidEquals
115
+ isCapabilitySubset,
116
+ principalDidEquals,
117
+ siweRuntimeDelegation
100
118
  } from "@tinycloud/node-sdk";
101
119
  import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
102
120
  import { invokeOperation } from "@tinycloud/operations";
@@ -211,8 +229,7 @@ var RemoteTenantStore = class {
211
229
  if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
212
230
  throw new ApprovalOwnerMismatchError();
213
231
  }
214
- const granted = [...node.getVerifiedSessionCapabilities()];
215
- if (!permissionsCover(pending.permissions, granted)) {
232
+ if (!isCapabilitySubset(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
216
233
  throw new Error("The approved delegation does not cover the requested account access.");
217
234
  }
218
235
  const profile = await readJson(this.#profilePath(tenant.stateRoot));
@@ -228,14 +245,25 @@ var RemoteTenantStore = class {
228
245
  }
229
246
  async #storeDelegation(tenant, pending, data) {
230
247
  if (pending.requestId === void 0) throw new Error("Authority request is missing.");
231
- const { node } = await this.#restoreApproval(tenant, data);
248
+ const { node, session } = await this.#restoreApproval(tenant, data);
232
249
  if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
233
- const delegation = portableFromCallback(data, pending.permissions, this.#config.nodeHost);
250
+ const delegation = siweRuntimeDelegation(node, {
251
+ authorization: session.delegationHeader.Authorization,
252
+ cid: session.delegationCid,
253
+ spaceId: session.spaceId,
254
+ ownerAddress: session.address,
255
+ chainId: session.chainId,
256
+ siwe: session.siwe,
257
+ signature: session.signature,
258
+ host: this.#config.nodeHost
259
+ });
234
260
  const result = await invokeOperation(
235
261
  "tinycloud.auth.import",
236
262
  1,
237
263
  { profile: PROFILE, stateRoot: tenant.stateRoot },
238
264
  {
265
+ kind: "tinycloud.auth.delegation",
266
+ version: 1,
239
267
  requestId: pending.requestId,
240
268
  delegationCid: delegation.cid,
241
269
  delegation
@@ -245,8 +273,10 @@ var RemoteTenantStore = class {
245
273
  }
246
274
  /**
247
275
  * Verify the OpenKey callback as a SIWE session for this tenant's key. The
248
- * WASM verifier binds the authorization bytes and CID to the signed SIWE, so
249
- * `node.did` is the wallet that signed exactly the delegation being stored.
276
+ * callback must carry the full signed proof (SIWE, signature, address and
277
+ * chain), so the WASM verifier always runs: it binds the authorization bytes
278
+ * and CID to the signed SIWE, and `node.did` is the wallet that signed
279
+ * exactly the delegation being stored.
250
280
  */
251
281
  async #restoreApproval(tenant, data) {
252
282
  const session = sessionFromCallback(data, tenant.jwk);
@@ -426,7 +456,7 @@ function ensureWasm() {
426
456
  }
427
457
  function sessionFromCallback(data, privateJwk) {
428
458
  const delegationHeader = data.delegationHeader;
429
- if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
459
+ 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") {
430
460
  throw new Error("OpenKey returned an incomplete delegation.");
431
461
  }
432
462
  return {
@@ -435,63 +465,12 @@ function sessionFromCallback(data, privateJwk) {
435
465
  spaceId: data.spaceId,
436
466
  jwk: privateJwk,
437
467
  verificationMethod: data.verificationMethod,
438
- ...typeof data.address === "string" ? { address: data.address } : {},
439
- ...typeof data.chainId === "number" ? { chainId: data.chainId } : {},
440
- ...typeof data.siwe === "string" ? { siwe: data.siwe } : {},
441
- ...typeof data.signature === "string" ? { signature: data.signature } : {}
442
- };
443
- }
444
- function portableFromCallback(data, permissions, host) {
445
- const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
446
- if (primary === void 0) throw new Error("The authority request has no permissions.");
447
- const session = sessionFromCallback(data, {});
448
- const expiry = delegationExpiry(data);
449
- return {
450
- cid: session.delegationCid,
451
- delegationHeader: session.delegationHeader,
452
- spaceId: session.spaceId,
453
- path: primary.path,
454
- actions: [...primary.actions],
455
- resources: permissions.map((permission) => ({
456
- service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
457
- space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
458
- path: permission.path,
459
- actions: [...permission.actions]
460
- })),
461
- expiry,
462
- delegateDID: session.verificationMethod,
463
- ownerAddress: typeof data.address === "string" ? data.address : "",
464
- chainId: typeof data.chainId === "number" ? data.chainId : 1,
465
- host
468
+ address: data.address,
469
+ chainId: data.chainId,
470
+ siwe: data.siwe,
471
+ signature: data.signature
466
472
  };
467
473
  }
468
- function delegationExpiry(data) {
469
- for (const key of ["expiry", "expiresAt", "expirationTime"]) {
470
- const parsed = parseDate(data[key]);
471
- if (parsed !== void 0) return parsed;
472
- }
473
- if (typeof data.siwe === "string") {
474
- const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
475
- const parsed = parseDate(match?.[1]?.trim());
476
- if (parsed !== void 0) return parsed;
477
- }
478
- throw new Error("OpenKey did not return a delegation expiry.");
479
- }
480
- function permissionsCover(requested, granted) {
481
- return requested.every((request) => granted.some(
482
- (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))
483
- ));
484
- }
485
- function sameSpace(granted, requested) {
486
- return granted === requested || granted.endsWith(`:${requested}`);
487
- }
488
- function normalizeService(service) {
489
- return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
490
- }
491
- function requiredPermissionSpace(permission) {
492
- if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
493
- return permission.space;
494
- }
495
474
  function publicJwk(jwk) {
496
475
  const copy = { ...jwk };
497
476
  for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
@@ -514,11 +493,6 @@ function isPermissionEntry(value) {
514
493
  function isRecord(value) {
515
494
  return typeof value === "object" && value !== null && !Array.isArray(value);
516
495
  }
517
- function parseDate(value) {
518
- if (typeof value !== "string" && typeof value !== "number") return void 0;
519
- const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
520
- return Number.isNaN(date.getTime()) ? void 0 : date;
521
- }
522
496
  async function secureDirectory(path) {
523
497
  await mkdir(path, { recursive: true, mode: 448 });
524
498
  await chmod(path, 448);
@@ -692,7 +666,10 @@ var TOOL_BINDINGS = [
692
666
  readOnlyHint: false,
693
667
  idempotentHint: true,
694
668
  destructiveHint: false,
695
- openWorldHint: false
669
+ openWorldHint: false,
670
+ // Hosted delegations must arrive through the owner/request-bound OpenKey callback,
671
+ // not a caller-supplied import artifact.
672
+ surfaces: ["local"]
696
673
  },
697
674
  {
698
675
  name: "tinycloud_account_spaces_list",
@@ -864,7 +841,7 @@ function registerTinyCloudTools(server, selection, validator) {
864
841
  // package.json
865
842
  var package_default = {
866
843
  name: "@tinycloud/mcp",
867
- version: "0.5.0-beta.6",
844
+ version: "0.5.0-beta.7",
868
845
  description: "TinyCloud delegated operations MCP server",
869
846
  keywords: [
870
847
  "tinycloud",
@@ -935,9 +912,9 @@ var package_default = {
935
912
  dependencies: {
936
913
  "@modelcontextprotocol/node": "2.0.0-beta.4",
937
914
  "@modelcontextprotocol/server": "2.0.0-beta.4",
938
- "@tinycloud/node-sdk": "3.2.0-beta.5",
915
+ "@tinycloud/node-sdk": "3.2.0-beta.6",
939
916
  "@tinycloud/node-sdk-wasm": "1.7.7",
940
- "@tinycloud/operations": "0.5.0-beta.6",
917
+ "@tinycloud/operations": "0.5.0-beta.7",
941
918
  jose: "6.1.3"
942
919
  },
943
920
  devDependencies: {