@tinycloud/mcp 0.5.3 → 0.5.4-beta.1

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.cjs CHANGED
@@ -24,6 +24,7 @@ __export(http_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(http_exports);
26
26
  var import_server4 = require("@modelcontextprotocol/server");
27
+ var import_node_sdk3 = require("@tinycloud/node-sdk");
27
28
 
28
29
  // src/oauth.ts
29
30
  var import_server = require("@modelcontextprotocol/server");
@@ -131,6 +132,26 @@ var ApprovalOwnerMismatchError = class extends Error {
131
132
  this.name = "ApprovalOwnerMismatchError";
132
133
  }
133
134
  };
135
+ var ApprovalStageError = class extends Error {
136
+ stage;
137
+ code;
138
+ reason;
139
+ constructor(stage, code, reason, cause) {
140
+ super(`The approval failed at ${stage} (${code}).`, { cause });
141
+ this.name = "ApprovalStageError";
142
+ this.stage = stage;
143
+ this.code = code;
144
+ this.reason = reason;
145
+ }
146
+ };
147
+ var ApprovalRequestError = class extends Error {
148
+ detail;
149
+ constructor(detail, message, cause) {
150
+ super(message, { cause });
151
+ this.name = "ApprovalRequestError";
152
+ this.detail = detail;
153
+ }
154
+ };
134
155
  var RemoteTenantStore = class {
135
156
  #config;
136
157
  #initializing = /* @__PURE__ */ new Map();
@@ -196,11 +217,20 @@ var RemoteTenantStore = class {
196
217
  }
197
218
  async completeApproval(state, request) {
198
219
  const length = Number(request.headers.get("content-length") ?? "0");
199
- if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) throw new Error("Callback body is too large.");
220
+ if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) {
221
+ throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
222
+ }
200
223
  const text = await request.text();
201
- if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) throw new Error("Callback body is too large.");
202
- const data = JSON.parse(text);
203
- if (!isRecord(data)) throw new Error("Callback body is invalid.");
224
+ if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) {
225
+ throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
226
+ }
227
+ let data;
228
+ try {
229
+ data = JSON.parse(text);
230
+ } catch (error) {
231
+ throw new ApprovalRequestError("body_invalid", "Callback body is invalid.", error);
232
+ }
233
+ if (!isRecord(data)) throw new ApprovalRequestError("body_invalid", "Callback body is invalid.");
204
234
  const pending = await this.#readPending(state, true);
205
235
  try {
206
236
  const tenant = await this.#readTenant(pending.tenantId);
@@ -238,16 +268,26 @@ var RemoteTenantStore = class {
238
268
  if (pending.requestId === void 0) throw new Error("Authority request is missing.");
239
269
  const { node, session } = await this.#restoreApproval(tenant, data);
240
270
  if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
241
- const delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
242
- authorization: session.delegationHeader.Authorization,
243
- cid: session.delegationCid,
244
- spaceId: session.spaceId,
245
- ownerAddress: session.address,
246
- chainId: session.chainId,
247
- siwe: session.siwe,
248
- signature: session.signature,
249
- host: this.#config.nodeHost
250
- });
271
+ let delegation;
272
+ try {
273
+ delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
274
+ authorization: session.delegationHeader.Authorization,
275
+ cid: session.delegationCid,
276
+ spaceId: session.spaceId,
277
+ ownerAddress: session.address,
278
+ chainId: session.chainId,
279
+ siwe: session.siwe,
280
+ signature: session.signature,
281
+ host: this.#config.nodeHost
282
+ });
283
+ } catch (error) {
284
+ throw new ApprovalStageError(
285
+ "siwe_runtime_delegation",
286
+ "DELEGATION_REJECTED",
287
+ error instanceof import_node_sdk2.RuntimeDelegationRejectedError ? error.reason : void 0,
288
+ error
289
+ );
290
+ }
251
291
  const result = await (0, import_operations.invokeOperation)(
252
292
  "tinycloud.auth.import",
253
293
  1,
@@ -260,7 +300,15 @@ var RemoteTenantStore = class {
260
300
  delegation
261
301
  }
262
302
  );
263
- if (result.status !== "ok") throw new Error("TinyCloud rejected the approved delegation.");
303
+ if (result.status === "error") {
304
+ const reason = result.error.details?.reason;
305
+ throw new ApprovalStageError(
306
+ result.error.code === "DELEGATION_REJECTED" && typeof reason === "string" ? "activation" : "auth_import",
307
+ result.error.code,
308
+ typeof reason === "string" ? reason : void 0
309
+ );
310
+ }
311
+ if (result.status !== "ok") throw new ApprovalStageError("auth_import", "AUTHORITY_REQUIRED");
264
312
  }
265
313
  /**
266
314
  * Verify the OpenKey callback as a SIWE session for this tenant's key. The
@@ -270,11 +318,25 @@ var RemoteTenantStore = class {
270
318
  * exactly the delegation being stored.
271
319
  */
272
320
  async #restoreApproval(tenant, data) {
273
- const session = sessionFromCallback(data, tenant.jwk);
321
+ let session;
322
+ try {
323
+ session = sessionFromCallback(data, tenant.jwk);
324
+ } catch (error) {
325
+ throw new ApprovalStageError("restore_approval", "CALLBACK_INCOMPLETE", void 0, error);
326
+ }
274
327
  const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
275
- await node.restoreSession(session);
328
+ try {
329
+ await node.restoreSession(session);
330
+ } catch (error) {
331
+ throw new ApprovalStageError(
332
+ "restore_approval",
333
+ error instanceof import_node_sdk2.InvalidRestoredSessionError || error instanceof import_node_sdk2.UnsupportedSessionRestoreError ? error.code : "SESSION_RESTORE_FAILED",
334
+ void 0,
335
+ error
336
+ );
337
+ }
276
338
  if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
277
- throw new Error("The approved delegation targets a different session.");
339
+ throw new ApprovalStageError("restore_approval", "SESSION_MISMATCH");
278
340
  }
279
341
  return { node, session };
280
342
  }
@@ -333,10 +395,10 @@ var RemoteTenantStore = class {
333
395
  return { stateRoot, sessionDid, jwk };
334
396
  }
335
397
  async #readTenant(tenantId) {
336
- if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new Error("Approval state is invalid.");
398
+ if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
337
399
  const stateRoot = (0, import_node_path.join)(this.#config.stateDir, "tenants", tenantId);
338
400
  const jwk = await readJson(this.#keyPath(stateRoot));
339
- if (jwk === null) throw new Error("The TinyCloud delegate no longer exists.");
401
+ if (jwk === null) throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.");
340
402
  return { stateRoot, jwk, sessionDid: didFromJwk(jwk) };
341
403
  }
342
404
  async #createPending(subject, ownerDids, kind, permissions, requestId) {
@@ -366,26 +428,40 @@ var RemoteTenantStore = class {
366
428
  async #readPending(state, consume) {
367
429
  const [tenantId, nonce, signature] = state.split(".");
368
430
  if (tenantId === void 0 || nonce === void 0 || signature === void 0) {
369
- throw new Error("Approval state is invalid.");
431
+ throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
370
432
  }
371
433
  const expected = this.#signState(tenantId, nonce);
372
434
  const supplied = Buffer.from(signature, "base64url");
373
435
  const expectedBytes = Buffer.from(expected, "base64url");
374
436
  if (supplied.length !== expectedBytes.length || !(0, import_node_crypto.timingSafeEqual)(supplied, expectedBytes)) {
375
- throw new Error("Approval state is invalid.");
437
+ throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
376
438
  }
377
439
  const stateRoot = (0, import_node_path.join)(this.#config.stateDir, "tenants", tenantId);
378
440
  const path = this.#pendingPath(stateRoot, nonce);
379
441
  const claimed = `${path}.processing`;
380
- if (consume) await (0, import_promises.rename)(path, claimed);
442
+ if (consume) {
443
+ try {
444
+ await (0, import_promises.rename)(path, claimed);
445
+ } catch (error) {
446
+ if (!isRecord(error) || error.code !== "ENOENT") throw error;
447
+ const tenantExists = await readJson(this.#keyPath(stateRoot)).then(
448
+ (jwk) => jwk !== null,
449
+ () => true
450
+ );
451
+ if (!tenantExists) {
452
+ throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.", error);
453
+ }
454
+ throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.", error);
455
+ }
456
+ }
381
457
  try {
382
458
  const pending = await readJson(consume ? claimed : path);
383
459
  if (pending === null || pending.version !== 1 || pending.tenantId !== tenantId || pending.nonce !== nonce) {
384
- throw new Error("Approval state is invalid or already used.");
460
+ throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.");
385
461
  }
386
462
  if (new Date(pending.expiresAt).getTime() <= Date.now()) {
387
463
  await (0, import_promises.rm)(consume ? claimed : path, { force: true });
388
- throw new Error("Approval state has expired.");
464
+ throw new ApprovalRequestError("state_expired", "Approval state has expired.");
389
465
  }
390
466
  return pending;
391
467
  } catch (error) {
@@ -833,7 +909,7 @@ function registerTinyCloudTools(server, selection, validator) {
833
909
  // package.json
834
910
  var package_default = {
835
911
  name: "@tinycloud/mcp",
836
- version: "0.5.3",
912
+ version: "0.5.4-beta.1",
837
913
  description: "TinyCloud delegated operations MCP server",
838
914
  keywords: [
839
915
  "tinycloud",
@@ -904,9 +980,9 @@ var package_default = {
904
980
  dependencies: {
905
981
  "@modelcontextprotocol/node": "2.0.0-beta.4",
906
982
  "@modelcontextprotocol/server": "2.0.0-beta.4",
907
- "@tinycloud/node-sdk": "3.2.3",
983
+ "@tinycloud/node-sdk": "3.2.4-beta.0",
908
984
  "@tinycloud/node-sdk-wasm": "1.7.7",
909
- "@tinycloud/operations": "0.5.3",
985
+ "@tinycloud/operations": "0.5.4-beta.1",
910
986
  jose: "6.1.3"
911
987
  },
912
988
  devDependencies: {
@@ -1009,8 +1085,23 @@ function createHostedMcpApp(config) {
1009
1085
  await store.completeApproval(requiredState(url), request);
1010
1086
  return callbackCors(json({ success: true }), config.openkeyHost);
1011
1087
  } catch (error) {
1088
+ if (error instanceof ApprovalOwnerMismatchError) {
1089
+ return callbackCors(json({ error: error.message, code: "approval_owner_mismatch" }, 403), config.openkeyHost);
1090
+ }
1091
+ const stage = error instanceof ApprovalStageError ? error.stage : "unknown";
1092
+ const code = error instanceof ApprovalStageError && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code) ? error.code : "UNKNOWN";
1093
+ const reason = !(error instanceof ApprovalStageError) || error.reason === void 0 ? void 0 : import_node_sdk3.RUNTIME_DELEGATION_REJECTIONS.find((known) => known === error.reason) ?? "unknown";
1094
+ const detail = error instanceof ApprovalRequestError ? error.detail : "none";
1095
+ writeDiagnostic(
1096
+ `connect callback rejected: stage=${stage} code=${code}${reason === void 0 ? "" : ` reason=${reason}`} detail=${detail}`
1097
+ );
1012
1098
  return callbackCors(
1013
- error instanceof ApprovalOwnerMismatchError ? json({ error: error.message, code: "approval_owner_mismatch" }, 403) : json({ error: "TinyCloud could not accept this delegation." }, 400),
1099
+ json({
1100
+ error: stage,
1101
+ code,
1102
+ ...reason === void 0 ? {} : { reason },
1103
+ message: "TinyCloud could not accept this delegation."
1104
+ }, 400),
1014
1105
  config.openkeyHost
1015
1106
  );
1016
1107
  }
@@ -1085,7 +1176,8 @@ function canonicalResourceUrl(url) {
1085
1176
  }
1086
1177
  function requiredState(url) {
1087
1178
  const state = url.searchParams.get("state");
1088
- if (state === null || state.length > 512) throw new Error("Missing approval state.");
1179
+ if (state === null) throw new ApprovalRequestError("state_missing", "Missing approval state.");
1180
+ if (state.length > 512) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
1089
1181
  return state;
1090
1182
  }
1091
1183
  function validateOrigin(request, allowed) {