@okx_ai/okx-trade-mcp 1.2.8 → 1.2.9-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.js +170 -19
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/postinstall-download.js +152 -0
- package/scripts/postinstall.js +8 -1
package/dist/index.js
CHANGED
|
@@ -5,20 +5,25 @@ import { parseArgs } from "util";
|
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
|
|
7
7
|
// ../core/dist/index.js
|
|
8
|
-
import { ProxyAgent } from "undici";
|
|
8
|
+
import { Agent, ProxyAgent } from "undici";
|
|
9
|
+
import { unlink } from "fs/promises";
|
|
10
|
+
import { execFile } from "child_process";
|
|
11
|
+
import { access, constants } from "fs/promises";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { join } from "path";
|
|
9
14
|
import { createHmac } from "crypto";
|
|
10
15
|
import fs from "fs";
|
|
11
16
|
import path from "path";
|
|
12
17
|
import os from "os";
|
|
13
18
|
import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
|
|
14
|
-
import { join, resolve, basename, sep } from "path";
|
|
19
|
+
import { join as join2, resolve, basename, sep } from "path";
|
|
15
20
|
import { randomUUID } from "crypto";
|
|
16
21
|
import yauzl from "yauzl";
|
|
17
|
-
import { join as
|
|
18
|
-
import { homedir } from "os";
|
|
19
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync3 } from "fs";
|
|
20
|
-
import { join as join4, dirname as dirname3 } from "path";
|
|
22
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
21
23
|
import { homedir as homedir2 } from "os";
|
|
24
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync3 } from "fs";
|
|
25
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
26
|
+
import { homedir as homedir3 } from "os";
|
|
22
27
|
|
|
23
28
|
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
|
|
24
29
|
function getLineColFromPtr(string, ptr) {
|
|
@@ -707,8 +712,8 @@ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
|
707
712
|
|
|
708
713
|
// ../core/dist/index.js
|
|
709
714
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync4 } from "fs";
|
|
710
|
-
import { join as
|
|
711
|
-
import { homedir as
|
|
715
|
+
import { join as join6 } from "path";
|
|
716
|
+
import { homedir as homedir4 } from "os";
|
|
712
717
|
import fs2 from "fs";
|
|
713
718
|
import path2 from "path";
|
|
714
719
|
import os2 from "os";
|
|
@@ -716,6 +721,60 @@ import * as fs3 from "fs";
|
|
|
716
721
|
import * as path3 from "path";
|
|
717
722
|
import * as os3 from "os";
|
|
718
723
|
import { execFileSync } from "child_process";
|
|
724
|
+
var EXEC_TIMEOUT_MS = 15e3;
|
|
725
|
+
var DOH_BIN_DIR = join(homedir(), ".okx", "bin");
|
|
726
|
+
var DOH_CACHE_FILE = join(homedir(), ".okx", ".doh-cache.json");
|
|
727
|
+
function getDohBinaryPath() {
|
|
728
|
+
if (process.env.OKX_DOH_BINARY_PATH) {
|
|
729
|
+
return process.env.OKX_DOH_BINARY_PATH;
|
|
730
|
+
}
|
|
731
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
732
|
+
return join(DOH_BIN_DIR, `okx-doh-resolver${ext}`);
|
|
733
|
+
}
|
|
734
|
+
function getDohCachePath() {
|
|
735
|
+
return DOH_CACHE_FILE;
|
|
736
|
+
}
|
|
737
|
+
async function dohBinaryExists() {
|
|
738
|
+
try {
|
|
739
|
+
const flag = process.platform === "win32" ? constants.F_OK : constants.X_OK;
|
|
740
|
+
await access(getDohBinaryPath(), flag);
|
|
741
|
+
return true;
|
|
742
|
+
} catch {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function execDohBinary(domain) {
|
|
747
|
+
const binPath = getDohBinaryPath();
|
|
748
|
+
return new Promise((resolve3) => {
|
|
749
|
+
execFile(
|
|
750
|
+
binPath,
|
|
751
|
+
["--domain", domain],
|
|
752
|
+
{ timeout: EXEC_TIMEOUT_MS, encoding: "utf-8" },
|
|
753
|
+
(error, stdout) => {
|
|
754
|
+
if (error) {
|
|
755
|
+
resolve3(null);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
const result = JSON.parse(stdout);
|
|
760
|
+
if (result.code === 0 && result.data) {
|
|
761
|
+
resolve3(result.data);
|
|
762
|
+
} else {
|
|
763
|
+
resolve3(null);
|
|
764
|
+
}
|
|
765
|
+
} catch {
|
|
766
|
+
resolve3(null);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
async function resolveDoh(domain) {
|
|
773
|
+
if (!await dohBinaryExists()) {
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
return execDohBinary(domain);
|
|
777
|
+
}
|
|
719
778
|
function getNow() {
|
|
720
779
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
721
780
|
}
|
|
@@ -934,12 +993,80 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
934
993
|
config;
|
|
935
994
|
rateLimiter;
|
|
936
995
|
dispatcher;
|
|
937
|
-
|
|
996
|
+
// DoH proxy state (lazy-resolved on first request)
|
|
997
|
+
dohResolverFn;
|
|
998
|
+
dohResolved = false;
|
|
999
|
+
dohNode = null;
|
|
1000
|
+
dohAgent = null;
|
|
1001
|
+
dohBaseUrl = null;
|
|
1002
|
+
/**
|
|
1003
|
+
* @param config - OKX API client configuration
|
|
1004
|
+
* @param options - Optional overrides (e.g. custom DoH resolver for testing).
|
|
1005
|
+
* Pass `{ resolveDoh: null }` to disable DoH entirely.
|
|
1006
|
+
*/
|
|
1007
|
+
constructor(config, options) {
|
|
938
1008
|
this.config = config;
|
|
939
1009
|
this.rateLimiter = new RateLimiter(3e4, config.verbose);
|
|
940
1010
|
if (config.proxyUrl) {
|
|
941
1011
|
this.dispatcher = new ProxyAgent(config.proxyUrl);
|
|
942
1012
|
}
|
|
1013
|
+
this.dohResolverFn = options?.resolveDoh !== void 0 ? options.resolveDoh : resolveDoh;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Lazily resolve the DoH proxy node on the first request.
|
|
1017
|
+
* Skipped entirely when the user has configured proxy_url or DoH is disabled.
|
|
1018
|
+
* On failure, silently falls back to direct connection.
|
|
1019
|
+
*/
|
|
1020
|
+
async ensureDoh() {
|
|
1021
|
+
if (this.dohResolved || this.dispatcher || !this.dohResolverFn) return;
|
|
1022
|
+
this.dohResolved = true;
|
|
1023
|
+
try {
|
|
1024
|
+
const { hostname, protocol } = new URL(this.config.baseUrl);
|
|
1025
|
+
const node = await this.dohResolverFn(hostname);
|
|
1026
|
+
if (!node) return;
|
|
1027
|
+
if (node.ip === hostname) {
|
|
1028
|
+
if (this.config.verbose) {
|
|
1029
|
+
vlog(`DoH: resolved ip matches hostname (${hostname}), using direct connection`);
|
|
1030
|
+
}
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
this.dohNode = node;
|
|
1034
|
+
this.dohBaseUrl = `${protocol}//${node.host}`;
|
|
1035
|
+
this.dohAgent = new Agent({
|
|
1036
|
+
connect: {
|
|
1037
|
+
lookup: (_hostname, options, callback) => {
|
|
1038
|
+
if (options?.all) {
|
|
1039
|
+
callback(null, [{ address: node.ip, family: 4 }]);
|
|
1040
|
+
} else {
|
|
1041
|
+
callback(null, node.ip, 4);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
if (this.config.verbose) {
|
|
1047
|
+
vlog(`DoH proxy active: ${hostname} \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
|
|
1048
|
+
}
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
if (this.config.verbose) {
|
|
1051
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
1052
|
+
vlog(`DoH resolution failed, falling back to direct: ${cause}`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Invalidate DoH state and delete the binary's cache file so the next
|
|
1058
|
+
* resolution performs a fresh lookup. Called on network-level failures
|
|
1059
|
+
* when a DoH proxy node was in use.
|
|
1060
|
+
*/
|
|
1061
|
+
async invalidateDoh() {
|
|
1062
|
+
this.dohNode = null;
|
|
1063
|
+
this.dohAgent = null;
|
|
1064
|
+
this.dohBaseUrl = null;
|
|
1065
|
+
this.dohResolved = false;
|
|
1066
|
+
try {
|
|
1067
|
+
await unlink(getDohCachePath());
|
|
1068
|
+
} catch {
|
|
1069
|
+
}
|
|
943
1070
|
}
|
|
944
1071
|
logRequest(method, url, auth) {
|
|
945
1072
|
if (!this.config.verbose) return;
|
|
@@ -1110,13 +1237,18 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1110
1237
|
* Security: validates Content-Type and enforces maxBytes limit.
|
|
1111
1238
|
*/
|
|
1112
1239
|
async privatePostBinary(path4, body, opts) {
|
|
1240
|
+
await this.ensureDoh();
|
|
1113
1241
|
const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
|
|
1114
1242
|
const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
|
|
1115
1243
|
const bodyJson = body ? JSON.stringify(body) : "";
|
|
1116
1244
|
const endpoint = `POST ${path4}`;
|
|
1117
|
-
this.
|
|
1245
|
+
const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
|
|
1246
|
+
this.logRequest("POST", `${baseUrl}${path4}`, "private");
|
|
1118
1247
|
const reqConfig = { method: "POST", path: path4, auth: "private" };
|
|
1119
1248
|
const headers = this.buildHeaders(reqConfig, path4, bodyJson, getNow());
|
|
1249
|
+
if (this.dohNode) {
|
|
1250
|
+
headers.set("User-Agent", "OKX/2.7.2");
|
|
1251
|
+
}
|
|
1120
1252
|
const t0 = Date.now();
|
|
1121
1253
|
const response = await this.fetchBinary(path4, endpoint, headers, bodyJson, t0);
|
|
1122
1254
|
const elapsed = Date.now() - t0;
|
|
@@ -1146,14 +1278,19 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1146
1278
|
/** Execute fetch for binary endpoint, wrapping network errors. */
|
|
1147
1279
|
async fetchBinary(path4, endpoint, headers, bodyJson, t0) {
|
|
1148
1280
|
try {
|
|
1281
|
+
const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
|
|
1149
1282
|
const fetchOptions = {
|
|
1150
1283
|
method: "POST",
|
|
1151
1284
|
headers,
|
|
1152
1285
|
body: bodyJson || void 0,
|
|
1153
1286
|
signal: AbortSignal.timeout(this.config.timeoutMs)
|
|
1154
1287
|
};
|
|
1155
|
-
if (this.dispatcher)
|
|
1156
|
-
|
|
1288
|
+
if (this.dispatcher) {
|
|
1289
|
+
fetchOptions.dispatcher = this.dispatcher;
|
|
1290
|
+
} else if (this.dohAgent) {
|
|
1291
|
+
fetchOptions.dispatcher = this.dohAgent;
|
|
1292
|
+
}
|
|
1293
|
+
return await fetch(`${baseUrl}${path4}`, fetchOptions);
|
|
1157
1294
|
} catch (error) {
|
|
1158
1295
|
if (this.config.verbose) {
|
|
1159
1296
|
vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1184,9 +1321,11 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1184
1321
|
// JSON request
|
|
1185
1322
|
// ---------------------------------------------------------------------------
|
|
1186
1323
|
async request(reqConfig) {
|
|
1324
|
+
await this.ensureDoh();
|
|
1187
1325
|
const queryString = buildQueryString(reqConfig.query);
|
|
1188
1326
|
const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
|
|
1189
|
-
const
|
|
1327
|
+
const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
|
|
1328
|
+
const url = `${baseUrl}${requestPath}`;
|
|
1190
1329
|
const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
|
|
1191
1330
|
const timestamp = getNow();
|
|
1192
1331
|
this.logRequest(reqConfig.method, url, reqConfig.auth);
|
|
@@ -1194,6 +1333,9 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1194
1333
|
await this.rateLimiter.consume(reqConfig.rateLimit);
|
|
1195
1334
|
}
|
|
1196
1335
|
const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
|
|
1336
|
+
if (this.dohNode) {
|
|
1337
|
+
headers.set("User-Agent", "OKX/2.7.2");
|
|
1338
|
+
}
|
|
1197
1339
|
const t0 = Date.now();
|
|
1198
1340
|
let response;
|
|
1199
1341
|
try {
|
|
@@ -1205,9 +1347,18 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1205
1347
|
};
|
|
1206
1348
|
if (this.dispatcher) {
|
|
1207
1349
|
fetchOptions.dispatcher = this.dispatcher;
|
|
1350
|
+
} else if (this.dohAgent) {
|
|
1351
|
+
fetchOptions.dispatcher = this.dohAgent;
|
|
1208
1352
|
}
|
|
1209
1353
|
response = await fetch(url, fetchOptions);
|
|
1210
1354
|
} catch (error) {
|
|
1355
|
+
if (this.dohNode) {
|
|
1356
|
+
if (this.config.verbose) {
|
|
1357
|
+
vlog(`DoH request failed, invalidating cache and retrying: ${error instanceof Error ? error.message : String(error)}`);
|
|
1358
|
+
}
|
|
1359
|
+
await this.invalidateDoh();
|
|
1360
|
+
return this.request(reqConfig);
|
|
1361
|
+
}
|
|
1211
1362
|
if (this.config.verbose) {
|
|
1212
1363
|
const elapsed2 = Date.now() - t0;
|
|
1213
1364
|
const cause = error instanceof Error ? error.message : String(error);
|
|
@@ -2833,7 +2984,7 @@ function safeWriteFile(targetDir, fileName, data) {
|
|
|
2833
2984
|
throw new Error(`Invalid file name: "${fileName}"`);
|
|
2834
2985
|
}
|
|
2835
2986
|
const resolvedDir = resolve(targetDir);
|
|
2836
|
-
const filePath =
|
|
2987
|
+
const filePath = join2(resolvedDir, safeName);
|
|
2837
2988
|
const resolvedPath = resolve(filePath);
|
|
2838
2989
|
if (!resolvedPath.startsWith(resolvedDir + sep)) {
|
|
2839
2990
|
throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
|
|
@@ -2864,7 +3015,7 @@ async function downloadSkillZip(client, name, targetDir) {
|
|
|
2864
3015
|
return filePath;
|
|
2865
3016
|
}
|
|
2866
3017
|
var DEFAULT_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
|
|
2867
|
-
var DEFAULT_REGISTRY_PATH =
|
|
3018
|
+
var DEFAULT_REGISTRY_PATH = join4(homedir2(), ".okx", "skills", "registry.json");
|
|
2868
3019
|
function registerSkillsTools() {
|
|
2869
3020
|
return [
|
|
2870
3021
|
{
|
|
@@ -6966,7 +7117,7 @@ function toMcpTool(tool) {
|
|
|
6966
7117
|
};
|
|
6967
7118
|
}
|
|
6968
7119
|
function configFilePath() {
|
|
6969
|
-
return
|
|
7120
|
+
return join5(homedir3(), ".okx", "config.toml");
|
|
6970
7121
|
}
|
|
6971
7122
|
function readFullConfig() {
|
|
6972
7123
|
const path4 = configFilePath();
|
|
@@ -7102,7 +7253,7 @@ function loadConfig(cli) {
|
|
|
7102
7253
|
verbose: cli.verbose ?? false
|
|
7103
7254
|
};
|
|
7104
7255
|
}
|
|
7105
|
-
var CACHE_FILE =
|
|
7256
|
+
var CACHE_FILE = join6(homedir4(), ".okx", "update-check.json");
|
|
7106
7257
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
7107
7258
|
function readCache() {
|
|
7108
7259
|
try {
|
|
@@ -7115,7 +7266,7 @@ function readCache() {
|
|
|
7115
7266
|
}
|
|
7116
7267
|
function writeCache(cache) {
|
|
7117
7268
|
try {
|
|
7118
|
-
mkdirSync5(
|
|
7269
|
+
mkdirSync5(join6(homedir4(), ".okx"), { recursive: true });
|
|
7119
7270
|
writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
7120
7271
|
} catch {
|
|
7121
7272
|
}
|
|
@@ -7384,7 +7535,7 @@ var _require = createRequire(import.meta.url);
|
|
|
7384
7535
|
var pkg = _require("../package.json");
|
|
7385
7536
|
var SERVER_NAME = "okx-trade-mcp";
|
|
7386
7537
|
var SERVER_VERSION = pkg.version;
|
|
7387
|
-
var GIT_HASH = true ? "
|
|
7538
|
+
var GIT_HASH = true ? "53b8eb6" : "dev";
|
|
7388
7539
|
|
|
7389
7540
|
// src/server.ts
|
|
7390
7541
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|