@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/cli.cjs +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/http-cli.cjs +121 -29
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +124 -29
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +121 -29
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +124 -29
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +121 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +124 -29
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +2 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
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.
|
|
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.
|
|
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)
|
|
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)
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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) {
|