@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.cjs CHANGED
@@ -32,6 +32,7 @@ var import_server4 = require("@modelcontextprotocol/server");
32
32
 
33
33
  // src/oauth.ts
34
34
  var import_server = require("@modelcontextprotocol/server");
35
+ var import_node_sdk = require("@tinycloud/node-sdk");
35
36
  var import_jose = require("jose");
36
37
  var OWNER_DIDS_CLAIM = "https://tinycloud.xyz/owner_dids";
37
38
  function createOpenKeyTokenVerifier(config) {
@@ -80,11 +81,11 @@ function authInfo(token, payload, config) {
80
81
  );
81
82
  }
82
83
  const clientId = firstString(payload.azp, payload.client_id) ?? "unknown";
83
- const ownerDids = Array.isArray(payload[OWNER_DIDS_CLAIM]) ? payload[OWNER_DIDS_CLAIM].filter((value) => typeof value === "string") : [];
84
- if (ownerDids.length === 0) {
84
+ const ownerDids = canonicalOwnerDids(payload[OWNER_DIDS_CLAIM]);
85
+ if (ownerDids === void 0) {
85
86
  throw new import_server.OAuthError(
86
87
  import_server.OAuthErrorCode.InvalidToken,
87
- "The OpenKey access token has no TinyCloud owner identity."
88
+ "The OpenKey access token has no valid TinyCloud owner identity."
88
89
  );
89
90
  }
90
91
  return {
@@ -99,6 +100,21 @@ function authInfo(token, payload, config) {
99
100
  }
100
101
  };
101
102
  }
103
+ var OWNER_DID = /^did:pkh:eip155:([1-9][0-9]{0,15}):(0x[0-9a-fA-F]{40})$/;
104
+ function canonicalOwnerDids(claim) {
105
+ if (!Array.isArray(claim) || claim.length === 0) return void 0;
106
+ const ownerDids = [];
107
+ for (const value of claim) {
108
+ const match = typeof value === "string" ? OWNER_DID.exec(value) : null;
109
+ if (match === null) return void 0;
110
+ try {
111
+ ownerDids.push((0, import_node_sdk.pkhDid)(match[2], Number(match[1])));
112
+ } catch {
113
+ return void 0;
114
+ }
115
+ }
116
+ return [...new Set(ownerDids)];
117
+ }
102
118
  function firstString(...values) {
103
119
  return values.find((value) => typeof value === "string" && value.length > 0);
104
120
  }
@@ -107,7 +123,7 @@ function firstString(...values) {
107
123
  var import_node_crypto = require("crypto");
108
124
  var import_promises = require("fs/promises");
109
125
  var import_node_path = require("path");
110
- var import_node_sdk = require("@tinycloud/node-sdk");
126
+ var import_node_sdk2 = require("@tinycloud/node-sdk");
111
127
  var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
112
128
  var import_operations = require("@tinycloud/operations");
113
129
  var PROFILE = "agent";
@@ -221,8 +237,7 @@ var RemoteTenantStore = class {
221
237
  if (pending.ownerDids.length > 0 && !pending.ownerDids.some((did) => samePrincipal(node.did, did))) {
222
238
  throw new ApprovalOwnerMismatchError();
223
239
  }
224
- const granted = [...node.getVerifiedSessionCapabilities()];
225
- if (!permissionsCover(pending.permissions, granted)) {
240
+ if (!(0, import_node_sdk2.isCapabilitySubset)(pending.permissions, node.getVerifiedSessionCapabilities()).subset) {
226
241
  throw new Error("The approved delegation does not cover the requested account access.");
227
242
  }
228
243
  const profile = await readJson(this.#profilePath(tenant.stateRoot));
@@ -238,14 +253,25 @@ var RemoteTenantStore = class {
238
253
  }
239
254
  async #storeDelegation(tenant, pending, data) {
240
255
  if (pending.requestId === void 0) throw new Error("Authority request is missing.");
241
- const { node } = await this.#restoreApproval(tenant, data);
256
+ const { node, session } = await this.#restoreApproval(tenant, data);
242
257
  if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
243
- const delegation = portableFromCallback(data, pending.permissions, this.#config.nodeHost);
258
+ const delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
259
+ authorization: session.delegationHeader.Authorization,
260
+ cid: session.delegationCid,
261
+ spaceId: session.spaceId,
262
+ ownerAddress: session.address,
263
+ chainId: session.chainId,
264
+ siwe: session.siwe,
265
+ signature: session.signature,
266
+ host: this.#config.nodeHost
267
+ });
244
268
  const result = await (0, import_operations.invokeOperation)(
245
269
  "tinycloud.auth.import",
246
270
  1,
247
271
  { profile: PROFILE, stateRoot: tenant.stateRoot },
248
272
  {
273
+ kind: "tinycloud.auth.delegation",
274
+ version: 1,
249
275
  requestId: pending.requestId,
250
276
  delegationCid: delegation.cid,
251
277
  delegation
@@ -255,12 +281,14 @@ var RemoteTenantStore = class {
255
281
  }
256
282
  /**
257
283
  * Verify the OpenKey callback as a SIWE session for this tenant's key. The
258
- * WASM verifier binds the authorization bytes and CID to the signed SIWE, so
259
- * `node.did` is the wallet that signed exactly the delegation being stored.
284
+ * callback must carry the full signed proof (SIWE, signature, address and
285
+ * chain), so the WASM verifier always runs: it binds the authorization bytes
286
+ * and CID to the signed SIWE, and `node.did` is the wallet that signed
287
+ * exactly the delegation being stored.
260
288
  */
261
289
  async #restoreApproval(tenant, data) {
262
290
  const session = sessionFromCallback(data, tenant.jwk);
263
- const node = new import_node_sdk.TinyCloudNode({ host: this.#config.nodeHost });
291
+ const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
264
292
  await node.restoreSession(session);
265
293
  if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
266
294
  throw new Error("The approved delegation targets a different session.");
@@ -281,7 +309,7 @@ var RemoteTenantStore = class {
281
309
  const raw = await readJson(this.#sessionPath(tenant.stateRoot));
282
310
  if (raw === null) return false;
283
311
  try {
284
- const node = new import_node_sdk.TinyCloudNode({ host: this.#config.nodeHost });
312
+ const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
285
313
  await node.restoreSession(raw);
286
314
  return samePrincipal(node.sessionDid, tenant.sessionDid) && ownerDids.some((did) => samePrincipal(node.did, did));
287
315
  } catch {
@@ -436,7 +464,7 @@ function ensureWasm() {
436
464
  }
437
465
  function sessionFromCallback(data, privateJwk) {
438
466
  const delegationHeader = data.delegationHeader;
439
- if (!isRecord(delegationHeader) || typeof delegationHeader.Authorization !== "string" || typeof data.delegationCid !== "string" || typeof data.spaceId !== "string" || typeof data.verificationMethod !== "string") {
467
+ 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") {
440
468
  throw new Error("OpenKey returned an incomplete delegation.");
441
469
  }
442
470
  return {
@@ -445,63 +473,12 @@ function sessionFromCallback(data, privateJwk) {
445
473
  spaceId: data.spaceId,
446
474
  jwk: privateJwk,
447
475
  verificationMethod: data.verificationMethod,
448
- ...typeof data.address === "string" ? { address: data.address } : {},
449
- ...typeof data.chainId === "number" ? { chainId: data.chainId } : {},
450
- ...typeof data.siwe === "string" ? { siwe: data.siwe } : {},
451
- ...typeof data.signature === "string" ? { signature: data.signature } : {}
452
- };
453
- }
454
- function portableFromCallback(data, permissions, host) {
455
- const primary = permissions.find((permission) => permission.service !== "tinycloud.encryption") ?? permissions[0];
456
- if (primary === void 0) throw new Error("The authority request has no permissions.");
457
- const session = sessionFromCallback(data, {});
458
- const expiry = delegationExpiry(data);
459
- return {
460
- cid: session.delegationCid,
461
- delegationHeader: session.delegationHeader,
462
- spaceId: session.spaceId,
463
- path: primary.path,
464
- actions: [...primary.actions],
465
- resources: permissions.map((permission) => ({
466
- service: permission.service.startsWith("tinycloud.") ? permission.service.slice("tinycloud.".length) : permission.service,
467
- space: permission.service === "tinycloud.encryption" ? requiredPermissionSpace(permission) : session.spaceId,
468
- path: permission.path,
469
- actions: [...permission.actions]
470
- })),
471
- expiry,
472
- delegateDID: session.verificationMethod,
473
- ownerAddress: typeof data.address === "string" ? data.address : "",
474
- chainId: typeof data.chainId === "number" ? data.chainId : 1,
475
- host
476
+ address: data.address,
477
+ chainId: data.chainId,
478
+ siwe: data.siwe,
479
+ signature: data.signature
476
480
  };
477
481
  }
478
- function delegationExpiry(data) {
479
- for (const key of ["expiry", "expiresAt", "expirationTime"]) {
480
- const parsed = parseDate(data[key]);
481
- if (parsed !== void 0) return parsed;
482
- }
483
- if (typeof data.siwe === "string") {
484
- const match = data.siwe.match(/^Expiration Time:\s*(.+)$/im);
485
- const parsed = parseDate(match?.[1]?.trim());
486
- if (parsed !== void 0) return parsed;
487
- }
488
- throw new Error("OpenKey did not return a delegation expiry.");
489
- }
490
- function permissionsCover(requested, granted) {
491
- return requested.every((request) => granted.some(
492
- (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))
493
- ));
494
- }
495
- function sameSpace(granted, requested) {
496
- return granted === requested || granted.endsWith(`:${requested}`);
497
- }
498
- function normalizeService(service) {
499
- return service.startsWith("tinycloud.") ? service : `tinycloud.${service}`;
500
- }
501
- function requiredPermissionSpace(permission) {
502
- if (typeof permission.space !== "string") throw new Error("The authority request has no space.");
503
- return permission.space;
504
- }
505
482
  function publicJwk(jwk) {
506
483
  const copy = { ...jwk };
507
484
  for (const field of ["d", "p", "q", "dp", "dq", "qi", "oth", "k"]) delete copy[field];
@@ -510,7 +487,7 @@ function publicJwk(jwk) {
510
487
  function samePrincipal(left, right) {
511
488
  if (typeof left !== "string") return false;
512
489
  try {
513
- return (0, import_node_sdk.principalDidEquals)(left, right);
490
+ return (0, import_node_sdk2.principalDidEquals)(left, right);
514
491
  } catch {
515
492
  return false;
516
493
  }
@@ -524,11 +501,6 @@ function isPermissionEntry(value) {
524
501
  function isRecord(value) {
525
502
  return typeof value === "object" && value !== null && !Array.isArray(value);
526
503
  }
527
- function parseDate(value) {
528
- if (typeof value !== "string" && typeof value !== "number") return void 0;
529
- const date = new Date(typeof value === "number" && value < 1e10 ? value * 1e3 : value);
530
- return Number.isNaN(date.getTime()) ? void 0 : date;
531
- }
532
504
  async function secureDirectory(path) {
533
505
  await (0, import_promises.mkdir)(path, { recursive: true, mode: 448 });
534
506
  await (0, import_promises.chmod)(path, 448);
@@ -703,7 +675,10 @@ var TOOL_BINDINGS = [
703
675
  readOnlyHint: false,
704
676
  idempotentHint: true,
705
677
  destructiveHint: false,
706
- openWorldHint: false
678
+ openWorldHint: false,
679
+ // Hosted delegations must arrive through the owner/request-bound OpenKey callback,
680
+ // not a caller-supplied import artifact.
681
+ surfaces: ["local"]
707
682
  },
708
683
  {
709
684
  name: "tinycloud_account_spaces_list",
@@ -875,7 +850,7 @@ function registerTinyCloudTools(server, selection, validator) {
875
850
  // package.json
876
851
  var package_default = {
877
852
  name: "@tinycloud/mcp",
878
- version: "0.5.0-beta.6",
853
+ version: "0.5.0-beta.7",
879
854
  description: "TinyCloud delegated operations MCP server",
880
855
  keywords: [
881
856
  "tinycloud",
@@ -946,9 +921,9 @@ var package_default = {
946
921
  dependencies: {
947
922
  "@modelcontextprotocol/node": "2.0.0-beta.4",
948
923
  "@modelcontextprotocol/server": "2.0.0-beta.4",
949
- "@tinycloud/node-sdk": "3.2.0-beta.5",
924
+ "@tinycloud/node-sdk": "3.2.0-beta.6",
950
925
  "@tinycloud/node-sdk-wasm": "1.7.7",
951
- "@tinycloud/operations": "0.5.0-beta.6",
926
+ "@tinycloud/operations": "0.5.0-beta.7",
952
927
  jose: "6.1.3"
953
928
  },
954
929
  devDependencies: {