@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.js
CHANGED
|
@@ -315,7 +315,7 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
315
315
|
// package.json
|
|
316
316
|
var package_default = {
|
|
317
317
|
name: "@tinycloud/mcp",
|
|
318
|
-
version: "0.5.4-beta.
|
|
318
|
+
version: "0.5.4-beta.1",
|
|
319
319
|
description: "TinyCloud delegated operations MCP server",
|
|
320
320
|
keywords: [
|
|
321
321
|
"tinycloud",
|
|
@@ -388,7 +388,7 @@ var package_default = {
|
|
|
388
388
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
389
389
|
"@tinycloud/node-sdk": "3.2.4-beta.0",
|
|
390
390
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
391
|
-
"@tinycloud/operations": "0.5.4-beta.
|
|
391
|
+
"@tinycloud/operations": "0.5.4-beta.1",
|
|
392
392
|
jose: "6.1.3"
|
|
393
393
|
},
|
|
394
394
|
devDependencies: {
|
|
@@ -536,6 +536,7 @@ import {
|
|
|
536
536
|
oauthMetadataResponse,
|
|
537
537
|
requireBearerAuth
|
|
538
538
|
} from "@modelcontextprotocol/server";
|
|
539
|
+
import { RUNTIME_DELEGATION_REJECTIONS } from "@tinycloud/node-sdk";
|
|
539
540
|
|
|
540
541
|
// src/oauth.ts
|
|
541
542
|
import {
|
|
@@ -622,7 +623,10 @@ import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
|
622
623
|
import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promises";
|
|
623
624
|
import { dirname, join as join2 } from "path";
|
|
624
625
|
import {
|
|
626
|
+
InvalidRestoredSessionError,
|
|
627
|
+
RuntimeDelegationRejectedError,
|
|
625
628
|
TinyCloudNode,
|
|
629
|
+
UnsupportedSessionRestoreError,
|
|
626
630
|
isCapabilitySubset,
|
|
627
631
|
principalDidEquals,
|
|
628
632
|
siweRuntimeDelegation
|
|
@@ -651,6 +655,26 @@ var ApprovalOwnerMismatchError = class extends Error {
|
|
|
651
655
|
this.name = "ApprovalOwnerMismatchError";
|
|
652
656
|
}
|
|
653
657
|
};
|
|
658
|
+
var ApprovalStageError = class extends Error {
|
|
659
|
+
stage;
|
|
660
|
+
code;
|
|
661
|
+
reason;
|
|
662
|
+
constructor(stage, code, reason, cause) {
|
|
663
|
+
super(`The approval failed at ${stage} (${code}).`, { cause });
|
|
664
|
+
this.name = "ApprovalStageError";
|
|
665
|
+
this.stage = stage;
|
|
666
|
+
this.code = code;
|
|
667
|
+
this.reason = reason;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
var ApprovalRequestError = class extends Error {
|
|
671
|
+
detail;
|
|
672
|
+
constructor(detail, message, cause) {
|
|
673
|
+
super(message, { cause });
|
|
674
|
+
this.name = "ApprovalRequestError";
|
|
675
|
+
this.detail = detail;
|
|
676
|
+
}
|
|
677
|
+
};
|
|
654
678
|
var RemoteTenantStore = class {
|
|
655
679
|
#config;
|
|
656
680
|
#initializing = /* @__PURE__ */ new Map();
|
|
@@ -716,11 +740,20 @@ var RemoteTenantStore = class {
|
|
|
716
740
|
}
|
|
717
741
|
async completeApproval(state, request) {
|
|
718
742
|
const length = Number(request.headers.get("content-length") ?? "0");
|
|
719
|
-
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES)
|
|
743
|
+
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) {
|
|
744
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
745
|
+
}
|
|
720
746
|
const text = await request.text();
|
|
721
|
-
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES)
|
|
722
|
-
|
|
723
|
-
|
|
747
|
+
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) {
|
|
748
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
749
|
+
}
|
|
750
|
+
let data;
|
|
751
|
+
try {
|
|
752
|
+
data = JSON.parse(text);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is invalid.", error);
|
|
755
|
+
}
|
|
756
|
+
if (!isRecord(data)) throw new ApprovalRequestError("body_invalid", "Callback body is invalid.");
|
|
724
757
|
const pending = await this.#readPending(state, true);
|
|
725
758
|
try {
|
|
726
759
|
const tenant = await this.#readTenant(pending.tenantId);
|
|
@@ -758,16 +791,26 @@ var RemoteTenantStore = class {
|
|
|
758
791
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
759
792
|
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
760
793
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
794
|
+
let delegation;
|
|
795
|
+
try {
|
|
796
|
+
delegation = siweRuntimeDelegation(node, {
|
|
797
|
+
authorization: session.delegationHeader.Authorization,
|
|
798
|
+
cid: session.delegationCid,
|
|
799
|
+
spaceId: session.spaceId,
|
|
800
|
+
ownerAddress: session.address,
|
|
801
|
+
chainId: session.chainId,
|
|
802
|
+
siwe: session.siwe,
|
|
803
|
+
signature: session.signature,
|
|
804
|
+
host: this.#config.nodeHost
|
|
805
|
+
});
|
|
806
|
+
} catch (error) {
|
|
807
|
+
throw new ApprovalStageError(
|
|
808
|
+
"siwe_runtime_delegation",
|
|
809
|
+
"DELEGATION_REJECTED",
|
|
810
|
+
error instanceof RuntimeDelegationRejectedError ? error.reason : void 0,
|
|
811
|
+
error
|
|
812
|
+
);
|
|
813
|
+
}
|
|
771
814
|
const result = await invokeOperation2(
|
|
772
815
|
"tinycloud.auth.import",
|
|
773
816
|
1,
|
|
@@ -780,7 +823,15 @@ var RemoteTenantStore = class {
|
|
|
780
823
|
delegation
|
|
781
824
|
}
|
|
782
825
|
);
|
|
783
|
-
if (result.status
|
|
826
|
+
if (result.status === "error") {
|
|
827
|
+
const reason = result.error.details?.reason;
|
|
828
|
+
throw new ApprovalStageError(
|
|
829
|
+
result.error.code === "DELEGATION_REJECTED" && typeof reason === "string" ? "activation" : "auth_import",
|
|
830
|
+
result.error.code,
|
|
831
|
+
typeof reason === "string" ? reason : void 0
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
if (result.status !== "ok") throw new ApprovalStageError("auth_import", "AUTHORITY_REQUIRED");
|
|
784
835
|
}
|
|
785
836
|
/**
|
|
786
837
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
@@ -790,11 +841,25 @@ var RemoteTenantStore = class {
|
|
|
790
841
|
* exactly the delegation being stored.
|
|
791
842
|
*/
|
|
792
843
|
async #restoreApproval(tenant, data) {
|
|
793
|
-
|
|
844
|
+
let session;
|
|
845
|
+
try {
|
|
846
|
+
session = sessionFromCallback(data, tenant.jwk);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
throw new ApprovalStageError("restore_approval", "CALLBACK_INCOMPLETE", void 0, error);
|
|
849
|
+
}
|
|
794
850
|
const node = new TinyCloudNode({ host: this.#config.nodeHost });
|
|
795
|
-
|
|
851
|
+
try {
|
|
852
|
+
await node.restoreSession(session);
|
|
853
|
+
} catch (error) {
|
|
854
|
+
throw new ApprovalStageError(
|
|
855
|
+
"restore_approval",
|
|
856
|
+
error instanceof InvalidRestoredSessionError || error instanceof UnsupportedSessionRestoreError ? error.code : "SESSION_RESTORE_FAILED",
|
|
857
|
+
void 0,
|
|
858
|
+
error
|
|
859
|
+
);
|
|
860
|
+
}
|
|
796
861
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
797
|
-
throw new
|
|
862
|
+
throw new ApprovalStageError("restore_approval", "SESSION_MISMATCH");
|
|
798
863
|
}
|
|
799
864
|
return { node, session };
|
|
800
865
|
}
|
|
@@ -853,10 +918,10 @@ var RemoteTenantStore = class {
|
|
|
853
918
|
return { stateRoot, sessionDid, jwk };
|
|
854
919
|
}
|
|
855
920
|
async #readTenant(tenantId) {
|
|
856
|
-
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new
|
|
921
|
+
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
857
922
|
const stateRoot = join2(this.#config.stateDir, "tenants", tenantId);
|
|
858
923
|
const jwk = await readJson(this.#keyPath(stateRoot));
|
|
859
|
-
if (jwk === null) throw new
|
|
924
|
+
if (jwk === null) throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.");
|
|
860
925
|
return { stateRoot, jwk, sessionDid: didFromJwk(jwk) };
|
|
861
926
|
}
|
|
862
927
|
async #createPending(subject, ownerDids, kind, permissions, requestId) {
|
|
@@ -886,26 +951,40 @@ var RemoteTenantStore = class {
|
|
|
886
951
|
async #readPending(state, consume) {
|
|
887
952
|
const [tenantId, nonce, signature] = state.split(".");
|
|
888
953
|
if (tenantId === void 0 || nonce === void 0 || signature === void 0) {
|
|
889
|
-
throw new
|
|
954
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
890
955
|
}
|
|
891
956
|
const expected = this.#signState(tenantId, nonce);
|
|
892
957
|
const supplied = Buffer.from(signature, "base64url");
|
|
893
958
|
const expectedBytes = Buffer.from(expected, "base64url");
|
|
894
959
|
if (supplied.length !== expectedBytes.length || !timingSafeEqual(supplied, expectedBytes)) {
|
|
895
|
-
throw new
|
|
960
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
896
961
|
}
|
|
897
962
|
const stateRoot = join2(this.#config.stateDir, "tenants", tenantId);
|
|
898
963
|
const path = this.#pendingPath(stateRoot, nonce);
|
|
899
964
|
const claimed = `${path}.processing`;
|
|
900
|
-
if (consume)
|
|
965
|
+
if (consume) {
|
|
966
|
+
try {
|
|
967
|
+
await rename(path, claimed);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
970
|
+
const tenantExists = await readJson(this.#keyPath(stateRoot)).then(
|
|
971
|
+
(jwk) => jwk !== null,
|
|
972
|
+
() => true
|
|
973
|
+
);
|
|
974
|
+
if (!tenantExists) {
|
|
975
|
+
throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.", error);
|
|
976
|
+
}
|
|
977
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.", error);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
901
980
|
try {
|
|
902
981
|
const pending = await readJson(consume ? claimed : path);
|
|
903
982
|
if (pending === null || pending.version !== 1 || pending.tenantId !== tenantId || pending.nonce !== nonce) {
|
|
904
|
-
throw new
|
|
983
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.");
|
|
905
984
|
}
|
|
906
985
|
if (new Date(pending.expiresAt).getTime() <= Date.now()) {
|
|
907
986
|
await rm(consume ? claimed : path, { force: true });
|
|
908
|
-
throw new
|
|
987
|
+
throw new ApprovalRequestError("state_expired", "Approval state has expired.");
|
|
909
988
|
}
|
|
910
989
|
return pending;
|
|
911
990
|
} catch (error) {
|
|
@@ -1142,8 +1221,23 @@ function createHostedMcpApp(config) {
|
|
|
1142
1221
|
await store.completeApproval(requiredState(url), request);
|
|
1143
1222
|
return callbackCors(json({ success: true }), config.openkeyHost);
|
|
1144
1223
|
} catch (error) {
|
|
1224
|
+
if (error instanceof ApprovalOwnerMismatchError) {
|
|
1225
|
+
return callbackCors(json({ error: error.message, code: "approval_owner_mismatch" }, 403), config.openkeyHost);
|
|
1226
|
+
}
|
|
1227
|
+
const stage = error instanceof ApprovalStageError ? error.stage : "unknown";
|
|
1228
|
+
const code = error instanceof ApprovalStageError && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code) ? error.code : "UNKNOWN";
|
|
1229
|
+
const reason = !(error instanceof ApprovalStageError) || error.reason === void 0 ? void 0 : RUNTIME_DELEGATION_REJECTIONS.find((known) => known === error.reason) ?? "unknown";
|
|
1230
|
+
const detail = error instanceof ApprovalRequestError ? error.detail : "none";
|
|
1231
|
+
writeDiagnostic(
|
|
1232
|
+
`connect callback rejected: stage=${stage} code=${code}${reason === void 0 ? "" : ` reason=${reason}`} detail=${detail}`
|
|
1233
|
+
);
|
|
1145
1234
|
return callbackCors(
|
|
1146
|
-
|
|
1235
|
+
json({
|
|
1236
|
+
error: stage,
|
|
1237
|
+
code,
|
|
1238
|
+
...reason === void 0 ? {} : { reason },
|
|
1239
|
+
message: "TinyCloud could not accept this delegation."
|
|
1240
|
+
}, 400),
|
|
1147
1241
|
config.openkeyHost
|
|
1148
1242
|
);
|
|
1149
1243
|
}
|
|
@@ -1218,7 +1312,8 @@ function canonicalResourceUrl(url) {
|
|
|
1218
1312
|
}
|
|
1219
1313
|
function requiredState(url) {
|
|
1220
1314
|
const state = url.searchParams.get("state");
|
|
1221
|
-
if (state === null
|
|
1315
|
+
if (state === null) throw new ApprovalRequestError("state_missing", "Missing approval state.");
|
|
1316
|
+
if (state.length > 512) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
1222
1317
|
return state;
|
|
1223
1318
|
}
|
|
1224
1319
|
function validateOrigin(request, allowed) {
|