@tinycloud/mcp 0.5.4-beta.0 → 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/index.cjs CHANGED
@@ -352,7 +352,7 @@ function registerTinyCloudTools(server, selection, validator) {
352
352
  // package.json
353
353
  var package_default = {
354
354
  name: "@tinycloud/mcp",
355
- version: "0.5.4-beta.0",
355
+ version: "0.5.4-beta.1",
356
356
  description: "TinyCloud delegated operations MCP server",
357
357
  keywords: [
358
358
  "tinycloud",
@@ -425,7 +425,7 @@ var package_default = {
425
425
  "@modelcontextprotocol/server": "2.0.0-beta.4",
426
426
  "@tinycloud/node-sdk": "3.2.4-beta.0",
427
427
  "@tinycloud/node-sdk-wasm": "1.7.7",
428
- "@tinycloud/operations": "0.5.4-beta.0",
428
+ "@tinycloud/operations": "0.5.4-beta.1",
429
429
  jose: "6.1.3"
430
430
  },
431
431
  devDependencies: {
@@ -565,6 +565,7 @@ if (isDirectInvocation()) {
565
565
 
566
566
  // src/http.ts
567
567
  var import_server5 = require("@modelcontextprotocol/server");
568
+ var import_node_sdk3 = require("@tinycloud/node-sdk");
568
569
 
569
570
  // src/oauth.ts
570
571
  var import_server4 = require("@modelcontextprotocol/server");
@@ -672,6 +673,26 @@ var ApprovalOwnerMismatchError = class extends Error {
672
673
  this.name = "ApprovalOwnerMismatchError";
673
674
  }
674
675
  };
676
+ var ApprovalStageError = class extends Error {
677
+ stage;
678
+ code;
679
+ reason;
680
+ constructor(stage, code, reason, cause) {
681
+ super(`The approval failed at ${stage} (${code}).`, { cause });
682
+ this.name = "ApprovalStageError";
683
+ this.stage = stage;
684
+ this.code = code;
685
+ this.reason = reason;
686
+ }
687
+ };
688
+ var ApprovalRequestError = class extends Error {
689
+ detail;
690
+ constructor(detail, message, cause) {
691
+ super(message, { cause });
692
+ this.name = "ApprovalRequestError";
693
+ this.detail = detail;
694
+ }
695
+ };
675
696
  var RemoteTenantStore = class {
676
697
  #config;
677
698
  #initializing = /* @__PURE__ */ new Map();
@@ -737,11 +758,20 @@ var RemoteTenantStore = class {
737
758
  }
738
759
  async completeApproval(state, request) {
739
760
  const length = Number(request.headers.get("content-length") ?? "0");
740
- if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) throw new Error("Callback body is too large.");
761
+ if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) {
762
+ throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
763
+ }
741
764
  const text = await request.text();
742
- if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) throw new Error("Callback body is too large.");
743
- const data = JSON.parse(text);
744
- if (!isRecord(data)) throw new Error("Callback body is invalid.");
765
+ if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) {
766
+ throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
767
+ }
768
+ let data;
769
+ try {
770
+ data = JSON.parse(text);
771
+ } catch (error) {
772
+ throw new ApprovalRequestError("body_invalid", "Callback body is invalid.", error);
773
+ }
774
+ if (!isRecord(data)) throw new ApprovalRequestError("body_invalid", "Callback body is invalid.");
745
775
  const pending = await this.#readPending(state, true);
746
776
  try {
747
777
  const tenant = await this.#readTenant(pending.tenantId);
@@ -779,16 +809,26 @@ var RemoteTenantStore = class {
779
809
  if (pending.requestId === void 0) throw new Error("Authority request is missing.");
780
810
  const { node, session } = await this.#restoreApproval(tenant, data);
781
811
  if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
782
- const delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
783
- authorization: session.delegationHeader.Authorization,
784
- cid: session.delegationCid,
785
- spaceId: session.spaceId,
786
- ownerAddress: session.address,
787
- chainId: session.chainId,
788
- siwe: session.siwe,
789
- signature: session.signature,
790
- host: this.#config.nodeHost
791
- });
812
+ let delegation;
813
+ try {
814
+ delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
815
+ authorization: session.delegationHeader.Authorization,
816
+ cid: session.delegationCid,
817
+ spaceId: session.spaceId,
818
+ ownerAddress: session.address,
819
+ chainId: session.chainId,
820
+ siwe: session.siwe,
821
+ signature: session.signature,
822
+ host: this.#config.nodeHost
823
+ });
824
+ } catch (error) {
825
+ throw new ApprovalStageError(
826
+ "siwe_runtime_delegation",
827
+ "DELEGATION_REJECTED",
828
+ error instanceof import_node_sdk2.RuntimeDelegationRejectedError ? error.reason : void 0,
829
+ error
830
+ );
831
+ }
792
832
  const result = await (0, import_operations2.invokeOperation)(
793
833
  "tinycloud.auth.import",
794
834
  1,
@@ -801,7 +841,15 @@ var RemoteTenantStore = class {
801
841
  delegation
802
842
  }
803
843
  );
804
- if (result.status !== "ok") throw new Error("TinyCloud rejected the approved delegation.");
844
+ if (result.status === "error") {
845
+ const reason = result.error.details?.reason;
846
+ throw new ApprovalStageError(
847
+ result.error.code === "DELEGATION_REJECTED" && typeof reason === "string" ? "activation" : "auth_import",
848
+ result.error.code,
849
+ typeof reason === "string" ? reason : void 0
850
+ );
851
+ }
852
+ if (result.status !== "ok") throw new ApprovalStageError("auth_import", "AUTHORITY_REQUIRED");
805
853
  }
806
854
  /**
807
855
  * Verify the OpenKey callback as a SIWE session for this tenant's key. The
@@ -811,11 +859,25 @@ var RemoteTenantStore = class {
811
859
  * exactly the delegation being stored.
812
860
  */
813
861
  async #restoreApproval(tenant, data) {
814
- const session = sessionFromCallback(data, tenant.jwk);
862
+ let session;
863
+ try {
864
+ session = sessionFromCallback(data, tenant.jwk);
865
+ } catch (error) {
866
+ throw new ApprovalStageError("restore_approval", "CALLBACK_INCOMPLETE", void 0, error);
867
+ }
815
868
  const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
816
- await node.restoreSession(session);
869
+ try {
870
+ await node.restoreSession(session);
871
+ } catch (error) {
872
+ throw new ApprovalStageError(
873
+ "restore_approval",
874
+ error instanceof import_node_sdk2.InvalidRestoredSessionError || error instanceof import_node_sdk2.UnsupportedSessionRestoreError ? error.code : "SESSION_RESTORE_FAILED",
875
+ void 0,
876
+ error
877
+ );
878
+ }
817
879
  if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
818
- throw new Error("The approved delegation targets a different session.");
880
+ throw new ApprovalStageError("restore_approval", "SESSION_MISMATCH");
819
881
  }
820
882
  return { node, session };
821
883
  }
@@ -874,10 +936,10 @@ var RemoteTenantStore = class {
874
936
  return { stateRoot, sessionDid, jwk };
875
937
  }
876
938
  async #readTenant(tenantId) {
877
- if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new Error("Approval state is invalid.");
939
+ if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
878
940
  const stateRoot = (0, import_node_path3.join)(this.#config.stateDir, "tenants", tenantId);
879
941
  const jwk = await readJson(this.#keyPath(stateRoot));
880
- if (jwk === null) throw new Error("The TinyCloud delegate no longer exists.");
942
+ if (jwk === null) throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.");
881
943
  return { stateRoot, jwk, sessionDid: didFromJwk(jwk) };
882
944
  }
883
945
  async #createPending(subject, ownerDids, kind, permissions, requestId) {
@@ -907,26 +969,40 @@ var RemoteTenantStore = class {
907
969
  async #readPending(state, consume) {
908
970
  const [tenantId, nonce, signature] = state.split(".");
909
971
  if (tenantId === void 0 || nonce === void 0 || signature === void 0) {
910
- throw new Error("Approval state is invalid.");
972
+ throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
911
973
  }
912
974
  const expected = this.#signState(tenantId, nonce);
913
975
  const supplied = Buffer.from(signature, "base64url");
914
976
  const expectedBytes = Buffer.from(expected, "base64url");
915
977
  if (supplied.length !== expectedBytes.length || !(0, import_node_crypto.timingSafeEqual)(supplied, expectedBytes)) {
916
- throw new Error("Approval state is invalid.");
978
+ throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
917
979
  }
918
980
  const stateRoot = (0, import_node_path3.join)(this.#config.stateDir, "tenants", tenantId);
919
981
  const path = this.#pendingPath(stateRoot, nonce);
920
982
  const claimed = `${path}.processing`;
921
- if (consume) await (0, import_promises.rename)(path, claimed);
983
+ if (consume) {
984
+ try {
985
+ await (0, import_promises.rename)(path, claimed);
986
+ } catch (error) {
987
+ if (!isRecord(error) || error.code !== "ENOENT") throw error;
988
+ const tenantExists = await readJson(this.#keyPath(stateRoot)).then(
989
+ (jwk) => jwk !== null,
990
+ () => true
991
+ );
992
+ if (!tenantExists) {
993
+ throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.", error);
994
+ }
995
+ throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.", error);
996
+ }
997
+ }
922
998
  try {
923
999
  const pending = await readJson(consume ? claimed : path);
924
1000
  if (pending === null || pending.version !== 1 || pending.tenantId !== tenantId || pending.nonce !== nonce) {
925
- throw new Error("Approval state is invalid or already used.");
1001
+ throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.");
926
1002
  }
927
1003
  if (new Date(pending.expiresAt).getTime() <= Date.now()) {
928
1004
  await (0, import_promises.rm)(consume ? claimed : path, { force: true });
929
- throw new Error("Approval state has expired.");
1005
+ throw new ApprovalRequestError("state_expired", "Approval state has expired.");
930
1006
  }
931
1007
  return pending;
932
1008
  } catch (error) {
@@ -1163,8 +1239,23 @@ function createHostedMcpApp(config) {
1163
1239
  await store.completeApproval(requiredState(url), request);
1164
1240
  return callbackCors(json({ success: true }), config.openkeyHost);
1165
1241
  } catch (error) {
1242
+ if (error instanceof ApprovalOwnerMismatchError) {
1243
+ return callbackCors(json({ error: error.message, code: "approval_owner_mismatch" }, 403), config.openkeyHost);
1244
+ }
1245
+ const stage = error instanceof ApprovalStageError ? error.stage : "unknown";
1246
+ const code = error instanceof ApprovalStageError && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code) ? error.code : "UNKNOWN";
1247
+ const reason = !(error instanceof ApprovalStageError) || error.reason === void 0 ? void 0 : import_node_sdk3.RUNTIME_DELEGATION_REJECTIONS.find((known) => known === error.reason) ?? "unknown";
1248
+ const detail = error instanceof ApprovalRequestError ? error.detail : "none";
1249
+ writeDiagnostic(
1250
+ `connect callback rejected: stage=${stage} code=${code}${reason === void 0 ? "" : ` reason=${reason}`} detail=${detail}`
1251
+ );
1166
1252
  return callbackCors(
1167
- error instanceof ApprovalOwnerMismatchError ? json({ error: error.message, code: "approval_owner_mismatch" }, 403) : json({ error: "TinyCloud could not accept this delegation." }, 400),
1253
+ json({
1254
+ error: stage,
1255
+ code,
1256
+ ...reason === void 0 ? {} : { reason },
1257
+ message: "TinyCloud could not accept this delegation."
1258
+ }, 400),
1168
1259
  config.openkeyHost
1169
1260
  );
1170
1261
  }
@@ -1239,7 +1330,8 @@ function canonicalResourceUrl(url) {
1239
1330
  }
1240
1331
  function requiredState(url) {
1241
1332
  const state = url.searchParams.get("state");
1242
- if (state === null || state.length > 512) throw new Error("Missing approval state.");
1333
+ if (state === null) throw new ApprovalRequestError("state_missing", "Missing approval state.");
1334
+ if (state.length > 512) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
1243
1335
  return state;
1244
1336
  }
1245
1337
  function validateOrigin(request, allowed) {