@tinycloud/mcp 0.5.4-beta.0 → 0.5.4-beta.2
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 +130 -33
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +133 -33
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +130 -33
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +133 -33
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +130 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +133 -33
- 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.2",
|
|
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");
|
|
@@ -652,6 +653,7 @@ var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
|
652
653
|
var import_operations2 = require("@tinycloud/operations");
|
|
653
654
|
var PROFILE = "agent";
|
|
654
655
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
656
|
+
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
655
657
|
var BOOTSTRAP_PERMISSIONS = [
|
|
656
658
|
{
|
|
657
659
|
service: "tinycloud.kv",
|
|
@@ -672,6 +674,26 @@ var ApprovalOwnerMismatchError = class extends Error {
|
|
|
672
674
|
this.name = "ApprovalOwnerMismatchError";
|
|
673
675
|
}
|
|
674
676
|
};
|
|
677
|
+
var ApprovalStageError = class extends Error {
|
|
678
|
+
stage;
|
|
679
|
+
code;
|
|
680
|
+
reason;
|
|
681
|
+
constructor(stage, code, reason, cause) {
|
|
682
|
+
super(`The approval failed at ${stage} (${code}).`, { cause });
|
|
683
|
+
this.name = "ApprovalStageError";
|
|
684
|
+
this.stage = stage;
|
|
685
|
+
this.code = code;
|
|
686
|
+
this.reason = reason;
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
var ApprovalRequestError = class extends Error {
|
|
690
|
+
detail;
|
|
691
|
+
constructor(detail, message, cause) {
|
|
692
|
+
super(message, { cause });
|
|
693
|
+
this.name = "ApprovalRequestError";
|
|
694
|
+
this.detail = detail;
|
|
695
|
+
}
|
|
696
|
+
};
|
|
675
697
|
var RemoteTenantStore = class {
|
|
676
698
|
#config;
|
|
677
699
|
#initializing = /* @__PURE__ */ new Map();
|
|
@@ -737,11 +759,20 @@ var RemoteTenantStore = class {
|
|
|
737
759
|
}
|
|
738
760
|
async completeApproval(state, request) {
|
|
739
761
|
const length = Number(request.headers.get("content-length") ?? "0");
|
|
740
|
-
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES)
|
|
762
|
+
if (Number.isFinite(length) && length > MAX_CALLBACK_BYTES) {
|
|
763
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
764
|
+
}
|
|
741
765
|
const text = await request.text();
|
|
742
|
-
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES)
|
|
743
|
-
|
|
744
|
-
|
|
766
|
+
if (Buffer.byteLength(text) > MAX_CALLBACK_BYTES) {
|
|
767
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is too large.");
|
|
768
|
+
}
|
|
769
|
+
let data;
|
|
770
|
+
try {
|
|
771
|
+
data = JSON.parse(text);
|
|
772
|
+
} catch (error) {
|
|
773
|
+
throw new ApprovalRequestError("body_invalid", "Callback body is invalid.", error);
|
|
774
|
+
}
|
|
775
|
+
if (!isRecord(data)) throw new ApprovalRequestError("body_invalid", "Callback body is invalid.");
|
|
745
776
|
const pending = await this.#readPending(state, true);
|
|
746
777
|
try {
|
|
747
778
|
const tenant = await this.#readTenant(pending.tenantId);
|
|
@@ -779,16 +810,26 @@ var RemoteTenantStore = class {
|
|
|
779
810
|
if (pending.requestId === void 0) throw new Error("Authority request is missing.");
|
|
780
811
|
const { node, session } = await this.#restoreApproval(tenant, data);
|
|
781
812
|
if (!pending.ownerDids.some((did) => samePrincipal(node.did, did))) throw new ApprovalOwnerMismatchError();
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
813
|
+
let delegation;
|
|
814
|
+
try {
|
|
815
|
+
delegation = (0, import_node_sdk2.siweRuntimeDelegation)(node, {
|
|
816
|
+
authorization: session.delegationHeader.Authorization,
|
|
817
|
+
cid: session.delegationCid,
|
|
818
|
+
spaceId: session.spaceId,
|
|
819
|
+
ownerAddress: session.address,
|
|
820
|
+
chainId: session.chainId,
|
|
821
|
+
siwe: session.siwe,
|
|
822
|
+
signature: session.signature,
|
|
823
|
+
host: this.#config.nodeHost
|
|
824
|
+
});
|
|
825
|
+
} catch (error) {
|
|
826
|
+
throw new ApprovalStageError(
|
|
827
|
+
"siwe_runtime_delegation",
|
|
828
|
+
"DELEGATION_REJECTED",
|
|
829
|
+
error instanceof import_node_sdk2.RuntimeDelegationRejectedError ? error.reason : void 0,
|
|
830
|
+
error
|
|
831
|
+
);
|
|
832
|
+
}
|
|
792
833
|
const result = await (0, import_operations2.invokeOperation)(
|
|
793
834
|
"tinycloud.auth.import",
|
|
794
835
|
1,
|
|
@@ -801,7 +842,15 @@ var RemoteTenantStore = class {
|
|
|
801
842
|
delegation
|
|
802
843
|
}
|
|
803
844
|
);
|
|
804
|
-
if (result.status
|
|
845
|
+
if (result.status === "error") {
|
|
846
|
+
const reason = result.error.details?.reason;
|
|
847
|
+
throw new ApprovalStageError(
|
|
848
|
+
result.error.code === "DELEGATION_REJECTED" && typeof reason === "string" ? "activation" : "auth_import",
|
|
849
|
+
result.error.code,
|
|
850
|
+
typeof reason === "string" ? reason : void 0
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
if (result.status !== "ok") throw new ApprovalStageError("auth_import", "AUTHORITY_REQUIRED");
|
|
805
854
|
}
|
|
806
855
|
/**
|
|
807
856
|
* Verify the OpenKey callback as a SIWE session for this tenant's key. The
|
|
@@ -811,11 +860,25 @@ var RemoteTenantStore = class {
|
|
|
811
860
|
* exactly the delegation being stored.
|
|
812
861
|
*/
|
|
813
862
|
async #restoreApproval(tenant, data) {
|
|
814
|
-
|
|
863
|
+
let session;
|
|
864
|
+
try {
|
|
865
|
+
session = sessionFromCallback(data, tenant.jwk);
|
|
866
|
+
} catch (error) {
|
|
867
|
+
throw new ApprovalStageError("restore_approval", "CALLBACK_INCOMPLETE", void 0, error);
|
|
868
|
+
}
|
|
815
869
|
const node = new import_node_sdk2.TinyCloudNode({ host: this.#config.nodeHost });
|
|
816
|
-
|
|
870
|
+
try {
|
|
871
|
+
await node.restoreSession(session);
|
|
872
|
+
} catch (error) {
|
|
873
|
+
throw new ApprovalStageError(
|
|
874
|
+
"restore_approval",
|
|
875
|
+
error instanceof import_node_sdk2.InvalidRestoredSessionError || error instanceof import_node_sdk2.UnsupportedSessionRestoreError ? error.code : "SESSION_RESTORE_FAILED",
|
|
876
|
+
void 0,
|
|
877
|
+
error
|
|
878
|
+
);
|
|
879
|
+
}
|
|
817
880
|
if (!samePrincipal(node.sessionDid, tenant.sessionDid)) {
|
|
818
|
-
throw new
|
|
881
|
+
throw new ApprovalStageError("restore_approval", "SESSION_MISMATCH");
|
|
819
882
|
}
|
|
820
883
|
return { node, session };
|
|
821
884
|
}
|
|
@@ -856,7 +919,8 @@ var RemoteTenantStore = class {
|
|
|
856
919
|
sessionDid = didFromJwk(jwk);
|
|
857
920
|
}
|
|
858
921
|
const profilePath = this.#profilePath(stateRoot);
|
|
859
|
-
|
|
922
|
+
const profile = await readJson(profilePath);
|
|
923
|
+
if (profile === null) {
|
|
860
924
|
await secureWrite(profilePath, {
|
|
861
925
|
name: PROFILE,
|
|
862
926
|
host: this.#config.nodeHost,
|
|
@@ -870,14 +934,20 @@ var RemoteTenantStore = class {
|
|
|
870
934
|
authMethod: "openkey",
|
|
871
935
|
openkeyHost: this.#config.openkeyHost
|
|
872
936
|
});
|
|
937
|
+
} else if (typeof profile.host === "string" && profile.host.replace(/\/+$/, "") === LEGACY_DEFAULT_NODE_HOST && this.#config.nodeHost.replace(/\/+$/, "") !== LEGACY_DEFAULT_NODE_HOST) {
|
|
938
|
+
await secureWrite(profilePath, { ...profile, host: this.#config.nodeHost });
|
|
939
|
+
process.stderr.write(
|
|
940
|
+
`[tinycloud-mcp-http] tenant node host migrated: from=${LEGACY_DEFAULT_NODE_HOST} to=${new URL(this.#config.nodeHost).origin}
|
|
941
|
+
`
|
|
942
|
+
);
|
|
873
943
|
}
|
|
874
944
|
return { stateRoot, sessionDid, jwk };
|
|
875
945
|
}
|
|
876
946
|
async #readTenant(tenantId) {
|
|
877
|
-
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new
|
|
947
|
+
if (!/^[a-f0-9]{64}$/.test(tenantId)) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
878
948
|
const stateRoot = (0, import_node_path3.join)(this.#config.stateDir, "tenants", tenantId);
|
|
879
949
|
const jwk = await readJson(this.#keyPath(stateRoot));
|
|
880
|
-
if (jwk === null) throw new
|
|
950
|
+
if (jwk === null) throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.");
|
|
881
951
|
return { stateRoot, jwk, sessionDid: didFromJwk(jwk) };
|
|
882
952
|
}
|
|
883
953
|
async #createPending(subject, ownerDids, kind, permissions, requestId) {
|
|
@@ -907,26 +977,40 @@ var RemoteTenantStore = class {
|
|
|
907
977
|
async #readPending(state, consume) {
|
|
908
978
|
const [tenantId, nonce, signature] = state.split(".");
|
|
909
979
|
if (tenantId === void 0 || nonce === void 0 || signature === void 0) {
|
|
910
|
-
throw new
|
|
980
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
911
981
|
}
|
|
912
982
|
const expected = this.#signState(tenantId, nonce);
|
|
913
983
|
const supplied = Buffer.from(signature, "base64url");
|
|
914
984
|
const expectedBytes = Buffer.from(expected, "base64url");
|
|
915
985
|
if (supplied.length !== expectedBytes.length || !(0, import_node_crypto.timingSafeEqual)(supplied, expectedBytes)) {
|
|
916
|
-
throw new
|
|
986
|
+
throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
917
987
|
}
|
|
918
988
|
const stateRoot = (0, import_node_path3.join)(this.#config.stateDir, "tenants", tenantId);
|
|
919
989
|
const path = this.#pendingPath(stateRoot, nonce);
|
|
920
990
|
const claimed = `${path}.processing`;
|
|
921
|
-
if (consume)
|
|
991
|
+
if (consume) {
|
|
992
|
+
try {
|
|
993
|
+
await (0, import_promises.rename)(path, claimed);
|
|
994
|
+
} catch (error) {
|
|
995
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
996
|
+
const tenantExists = await readJson(this.#keyPath(stateRoot)).then(
|
|
997
|
+
(jwk) => jwk !== null,
|
|
998
|
+
() => true
|
|
999
|
+
);
|
|
1000
|
+
if (!tenantExists) {
|
|
1001
|
+
throw new ApprovalRequestError("tenant_missing", "The TinyCloud delegate no longer exists.", error);
|
|
1002
|
+
}
|
|
1003
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.", error);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
922
1006
|
try {
|
|
923
1007
|
const pending = await readJson(consume ? claimed : path);
|
|
924
1008
|
if (pending === null || pending.version !== 1 || pending.tenantId !== tenantId || pending.nonce !== nonce) {
|
|
925
|
-
throw new
|
|
1009
|
+
throw new ApprovalRequestError("state_not_pending", "Approval state is invalid or already used.");
|
|
926
1010
|
}
|
|
927
1011
|
if (new Date(pending.expiresAt).getTime() <= Date.now()) {
|
|
928
1012
|
await (0, import_promises.rm)(consume ? claimed : path, { force: true });
|
|
929
|
-
throw new
|
|
1013
|
+
throw new ApprovalRequestError("state_expired", "Approval state has expired.");
|
|
930
1014
|
}
|
|
931
1015
|
return pending;
|
|
932
1016
|
} catch (error) {
|
|
@@ -1049,9 +1133,6 @@ async function readJson(path) {
|
|
|
1049
1133
|
throw error;
|
|
1050
1134
|
}
|
|
1051
1135
|
}
|
|
1052
|
-
async function fileExists(path) {
|
|
1053
|
-
return await readJson(path) !== null;
|
|
1054
|
-
}
|
|
1055
1136
|
async function reusablePending(directory, expected) {
|
|
1056
1137
|
let names;
|
|
1057
1138
|
try {
|
|
@@ -1163,8 +1244,23 @@ function createHostedMcpApp(config) {
|
|
|
1163
1244
|
await store.completeApproval(requiredState(url), request);
|
|
1164
1245
|
return callbackCors(json({ success: true }), config.openkeyHost);
|
|
1165
1246
|
} catch (error) {
|
|
1247
|
+
if (error instanceof ApprovalOwnerMismatchError) {
|
|
1248
|
+
return callbackCors(json({ error: error.message, code: "approval_owner_mismatch" }, 403), config.openkeyHost);
|
|
1249
|
+
}
|
|
1250
|
+
const stage = error instanceof ApprovalStageError ? error.stage : "unknown";
|
|
1251
|
+
const code = error instanceof ApprovalStageError && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code) ? error.code : "UNKNOWN";
|
|
1252
|
+
const reason = !(error instanceof ApprovalStageError) || error.reason === void 0 ? void 0 : import_node_sdk3.RUNTIME_DELEGATION_REJECTIONS.find((known) => known === error.reason) ?? "unknown";
|
|
1253
|
+
const detail = error instanceof ApprovalRequestError ? error.detail : "none";
|
|
1254
|
+
writeDiagnostic(
|
|
1255
|
+
`connect callback rejected: stage=${stage} code=${code}${reason === void 0 ? "" : ` reason=${reason}`} detail=${detail}`
|
|
1256
|
+
);
|
|
1166
1257
|
return callbackCors(
|
|
1167
|
-
|
|
1258
|
+
json({
|
|
1259
|
+
error: stage,
|
|
1260
|
+
code,
|
|
1261
|
+
...reason === void 0 ? {} : { reason },
|
|
1262
|
+
message: "TinyCloud could not accept this delegation."
|
|
1263
|
+
}, 400),
|
|
1168
1264
|
config.openkeyHost
|
|
1169
1265
|
);
|
|
1170
1266
|
}
|
|
@@ -1239,7 +1335,8 @@ function canonicalResourceUrl(url) {
|
|
|
1239
1335
|
}
|
|
1240
1336
|
function requiredState(url) {
|
|
1241
1337
|
const state = url.searchParams.get("state");
|
|
1242
|
-
if (state === null
|
|
1338
|
+
if (state === null) throw new ApprovalRequestError("state_missing", "Missing approval state.");
|
|
1339
|
+
if (state.length > 512) throw new ApprovalRequestError("state_invalid", "Approval state is invalid.");
|
|
1243
1340
|
return state;
|
|
1244
1341
|
}
|
|
1245
1342
|
function validateOrigin(request, allowed) {
|