@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/http.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  OAuthError,
15
15
  OAuthErrorCode
16
16
  } from "@modelcontextprotocol/server";
17
+ import { pkhDid } from "@tinycloud/node-sdk";
17
18
  import { createRemoteJWKSet, errors, jwtVerify } from "jose";
18
19
  var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
19
20
  function createOpenKeyTokenVerifier(config) {
@@ -50,11 +51,11 @@ function authInfo(token, payload, config) {
50
51
  );
51
52
  }
52
53
  const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
53
- const ownerDids = Array.isArray(payload[OWNER_DIDS_CLAIM]) ? payload[OWNER_DIDS_CLAIM].filter((value) => typeof value === "string") : [];
54
- if (ownerDids.length === 0) {
54
+ const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
55
+ if (ownerDids === void 0) {
55
56
  throw new OAuthError(
56
57
  OAuthErrorCode.InvalidToken,
57
- "The OpenKey access token has no TinyCloud owner identity."
58
+ "The OpenKey access token has no valid TinyCloud owner identity."
58
59
  );
59
60
  }
60
61
  return {
@@ -69,6 +70,21 @@ function authInfo(token, payload, config) {
69
70
  }
70
71
  };
71
72
  }
73
+ var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
74
+ function canonicalOwnerDids(claim) {
75
+ if (!Array.isArray(claim) || claim.length === 0) return void 0;
76
+ const ownerDids = [];
77
+ for (const value of claim) {
78
+ const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
79
+ if (match === null) return void 0;
80
+ try {
81
+ ownerDids.push(pkhDid(match[2], Number(match[1])));
82
+ } catch {
83
+ return void 0;
84
+ }
85
+ }
86
+ return [...new Set(ownerDids)];
87
+ }
72
88
  function firstString(...values) {
73
89
  return values.find((value) => typeof value === "string" && value.length > 0);
74
90
  }
@@ -79,7 +95,9 @@ import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promi
79
95
  import { dirname, join } from "path";
80
96
  import {
81
97
  TinyCloudNode,
82
- principalDidEquals
98
+ isCapabilitySubset,
99
+ principalDidEquals,
100
+ siweRuntimeDelegation
83
101
  } from "@tinycloud/node-sdk";
84
102
  import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
85
103
  import { invokeOperation } from "@tinycloud/operations";
@@ -194,8 +212,7 @@ var RemoteTenantStore = class {
194
212
  if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
195
213
  throw new ApprovalOwnerMismatchError();
196
214
  }
197
- const granted = [...node.getVerifiedSessionCapabilities()];
198
- if (!permissionsCover(pending.permissions, granted)) {
215
+ if (!isCapabilitySubset(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
199
216
  throw new Error("The approved delegation does not cover the requested account access.");
200
217
  }
201
218
  const profile = await readJson(this.#profilePath(tenant.stateRoot));
@@ -211,14 +228,25 @@ var RemoteTenantStore = class {
211
228
  }
212
229
  async #storeDelegation(tenant, pending, data) {
213
230
  if (pending.requestId === void 0) throw new Error("Authority request is missing.");
214
- const { node } = await this.#restoreApproval(tenant, data);
231
+ const { node, session } = await this.#restoreApproval(tenant, data);
215
232
  if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
216
- const delegation = portableFromCallback(data, pending.permissions, this.#config.nodeHost);
233
+ const delegation = siweRuntimeDelegation(node, {
234
+ authorization: session.delegationHeader.Authorization,
235
+ cid: session.delegationCid,
236
+ spaceId: session.spaceId,
237
+ ownerAddress: session.address,
238
+ chainId: session.chainId,
239
+ siwe: session.siwe,
240
+ signature: session.signature,
241
+ host: this.#config.nodeHost
242
+ });
217
243
  const result = await invokeOperation(
218
244
  "tinycloud.auth.import",
219
245
  1,
220
246
  { profile: PROFILE, stateRoot: tenant.stateRoot },
221
247
  {
248
+ kind: "tinycloud.auth.delegation",
249
+ version: 1,
222
250
  requestId: pending.requestId,
223
251
  delegationCid: delegation.cid,
224
252
  delegation
@@ -228,8 +256,10 @@ var RemoteTenantStore = class {
228
256
  }
229
257
  /**
230
258
  * Verify the OpenKey callback as a SIWE session for this tenant's key. The
231
- * WASM verifier binds the authorization bytes and CID to the signed SIWE, so
232
- * `node.did` is the wallet that signed exactly the delegation being stored.
259
+ * callback must carry the full signed proof (SIWE, signature, address and
260
+ * chain), so the WASM verifier always runs: it binds the authorization bytes
261
+ * and CID to the signed SIWE, and `node.did` is the wallet that signed
262
+ * exactly the delegation being stored.
233
263
  */
234
264
  async #restoreApproval(tenant, data) {
235
265
  const session = sessionFromCallback(data, tenant.jwk);
@@ -409,7 +439,7 @@ function ensureWasm() {
409
439
  }
410
440
  function sessionFromCallback(data, privateJwk) {
411
441
  const delegationHeader = data.delegationHeader;
412
- if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
442
+ 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") {
413
443
  throw new Error("OpenKey returned an incomplete delegation.");
414
444
  }
415
445
  return {
@@ -418,63 +448,12 @@ function sessionFromCallback(data, privateJwk) {
418
448
  spaceId: data.spaceId,
419
449
  jwk: privateJwk,
420
450
  verificationMethod: data.verificationMethod,
421
- ...typeof data.address === "string" ? { address: data.address } : {},
422
- ...typeof data.chainId === "number" ? { chainId: data.chainId } : {},
423
- ...typeof data.siwe === "string" ? { siwe: data.siwe } : {},
424
- ...typeof data.signature === "string" ? { signature: data.signature } : {}
425
- };
426
- }
427
- function portableFromCallback(data, permissions, host) {
428
- const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
429
- if (primary === void 0) throw new Error("The authority request has no permissions.");
430
- const session = sessionFromCallback(data, {});
431
- const expiry = delegationExpiry(data);
432
- return {
433
- cid: session.delegationCid,
434
- delegationHeader: session.delegationHeader,
435
- spaceId: session.spaceId,
436
- path: primary.path,
437
- actions: [...primary.actions],
438
- resources: permissions.map((permission) => ({
439
- service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
440
- space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
441
- path: permission.path,
442
- actions: [...permission.actions]
443
- })),
444
- expiry,
445
- delegateDID: session.verificationMethod,
446
- ownerAddress: typeof data.address === "string" ? data.address : "",
447
- chainId: typeof data.chainId === "number" ? data.chainId : 1,
448
- host
451
+ address: data.address,
452
+ chainId: data.chainId,
453
+ siwe: data.siwe,
454
+ signature: data.signature
449
455
  };
450
456
  }
451
- function delegationExpiry(data) {
452
- for (const key of ["expiry", "expiresAt", "expirationTime"]) {
453
- const parsed = parseDate(data[key]);
454
- if (parsed !== void 0) return parsed;
455
- }
456
- if (typeof data.siwe === "string") {
457
- const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
458
- const parsed = parseDate(match?.[1]?.trim());
459
- if (parsed !== void 0) return parsed;
460
- }
461
- throw new Error("OpenKey did not return a delegation expiry.");
462
- }
463
- function permissionsCover(requested, granted) {
464
- return requested.every((request) => granted.some(
465
- (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))
466
- ));
467
- }
468
- function sameSpace(granted, requested) {
469
- return granted === requested || granted.endsWith(`:${requested}`);
470
- }
471
- function normalizeService(service) {
472
- return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
473
- }
474
- function requiredPermissionSpace(permission) {
475
- if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
476
- return permission.space;
477
- }
478
457
  function publicJwk(jwk) {
479
458
  const copy = { ...jwk };
480
459
  for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
@@ -497,11 +476,6 @@ function isPermissionEntry(value) {
497
476
  function isRecord(value) {
498
477
  return typeof value === "object" && value !== null && !Array.isArray(value);
499
478
  }
500
- function parseDate(value) {
501
- if (typeof value !== "string" && typeof value !== "number") return void 0;
502
- const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
503
- return Number.isNaN(date.getTime()) ? void 0 : date;
504
- }
505
479
  async function secureDirectory(path) {
506
480
  await mkdir(path, { recursive: true, mode: 448 });
507
481
  await chmod(path, 448);
@@ -675,7 +649,10 @@ var TOOL_BINDINGS = [
675
649
  readOnlyHint: false,
676
650
  idempotentHint: true,
677
651
  destructiveHint: false,
678
- openWorldHint: false
652
+ openWorldHint: false,
653
+ // Hosted delegations must arrive through the owner/request-bound OpenKey callback,
654
+ // not a caller-supplied import artifact.
655
+ surfaces: ["local"]
679
656
  },
680
657
  {
681
658
  name: "tinycloud_account_spaces_list",
@@ -847,7 +824,7 @@ function registerTinyCloudTools(server, selection, validator) {
847
824
  // package.json
848
825
  var package_default = {
849
826
  name: "@tinycloud/mcp",
850
- version: "0.5.0-beta.5",
827
+ version: "0.5.0-beta.7",
851
828
  description: "TinyCloud delegated operations MCP server",
852
829
  keywords: [
853
830
  "tinycloud",
@@ -918,9 +895,9 @@ var package_default = {
918
895
  dependencies: {
919
896
  "@modelcontextprotocol/node": "2.0.0-beta.4",
920
897
  "@modelcontextprotocol/server": "2.0.0-beta.4",
921
- "@tinycloud/node-sdk": "3.2.0-beta.5",
898
+ "@tinycloud/node-sdk": "3.2.0-beta.6",
922
899
  "@tinycloud/node-sdk-wasm": "1.7.7",
923
- "@tinycloud/operations": "0.5.0-beta.5",
900
+ "@tinycloud/operations": "0.5.0-beta.7",
924
901
  jose: "6.1.3"
925
902
  },
926
903
  devDependencies: {