@okx_ai/okx-trade-mcp 1.3.0-beta.4 → 1.3.0

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 CHANGED
@@ -5,32 +5,20 @@ import { parseArgs } from "util";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
 
7
7
  // ../core/dist/index.js
8
- import { Agent, ProxyAgent } from "undici";
9
- import { execFile } from "child_process";
10
- import { homedir } from "os";
11
- import { join } from "path";
12
- import {
13
- readFileSync,
14
- writeFileSync,
15
- mkdirSync,
16
- unlinkSync,
17
- renameSync
18
- } from "fs";
19
- import { homedir as homedir2 } from "os";
20
- import { join as join2, dirname } from "path";
8
+ import { ProxyAgent } from "undici";
21
9
  import { createHmac } from "crypto";
22
10
  import fs from "fs";
23
11
  import path from "path";
24
12
  import os from "os";
25
- import { writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
26
- import { join as join3, resolve, basename, sep } from "path";
13
+ import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
14
+ import { join, resolve, basename, sep } from "path";
27
15
  import { randomUUID } from "crypto";
28
16
  import yauzl from "yauzl";
29
- import { join as join5, dirname as dirname3 } from "path";
30
- import { homedir as homedir3 } from "os";
31
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
32
- import { join as join6, dirname as dirname4 } from "path";
33
- import { homedir as homedir4 } from "os";
17
+ import { join as join3, dirname as dirname2 } from "path";
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";
21
+ import { homedir as homedir2 } from "os";
34
22
 
35
23
  // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
36
24
  function getLineColFromPtr(string, ptr) {
@@ -718,9 +706,9 @@ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
718
706
  }
719
707
 
720
708
  // ../core/dist/index.js
721
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
722
- import { join as join7 } from "path";
723
- import { homedir as homedir5 } from "os";
709
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync4 } from "fs";
710
+ import { join as join5 } from "path";
711
+ import { homedir as homedir3 } from "os";
724
712
  import fs2 from "fs";
725
713
  import path2 from "path";
726
714
  import os2 from "os";
@@ -728,123 +716,6 @@ import * as fs3 from "fs";
728
716
  import * as path3 from "path";
729
717
  import * as os3 from "os";
730
718
  import { execFileSync } from "child_process";
731
- var EXEC_TIMEOUT_MS = 3e4;
732
- var DOH_BIN_DIR = join(homedir(), ".okx", "bin");
733
- function getDohBinaryPath() {
734
- if (process.env.OKX_DOH_BINARY_PATH) {
735
- return process.env.OKX_DOH_BINARY_PATH;
736
- }
737
- const ext = process.platform === "win32" ? ".exe" : "";
738
- return join(DOH_BIN_DIR, `okx-doh-resolver${ext}`);
739
- }
740
- function execDohBinary(domain, exclude = [], userAgent) {
741
- const binPath = getDohBinaryPath();
742
- const args = ["--domain", domain];
743
- if (exclude.length > 0) {
744
- args.push("--exclude", exclude.join(","));
745
- }
746
- if (userAgent) {
747
- args.push("--user-agent", userAgent);
748
- }
749
- return new Promise((resolve3) => {
750
- execFile(
751
- binPath,
752
- args,
753
- { timeout: EXEC_TIMEOUT_MS, encoding: "utf-8" },
754
- (error, stdout) => {
755
- if (error) {
756
- resolve3(null);
757
- return;
758
- }
759
- try {
760
- const result = JSON.parse(stdout);
761
- if (result.code === 0 && result.data) {
762
- resolve3(result.data);
763
- } else {
764
- resolve3(null);
765
- }
766
- } catch {
767
- resolve3(null);
768
- }
769
- }
770
- );
771
- });
772
- }
773
- var DOH_CACHE_PATH = join2(homedir2(), ".okx", "doh-node-cache.json");
774
- function readCache(hostname, cachePath = DOH_CACHE_PATH) {
775
- try {
776
- const raw = readFileSync(cachePath, "utf-8");
777
- const file = JSON.parse(raw);
778
- return file[hostname] ?? null;
779
- } catch {
780
- return null;
781
- }
782
- }
783
- function writeCache(hostname, entry, cachePath = DOH_CACHE_PATH) {
784
- try {
785
- const dir = dirname(cachePath);
786
- mkdirSync(dir, { recursive: true });
787
- let file = {};
788
- try {
789
- file = JSON.parse(readFileSync(cachePath, "utf-8"));
790
- } catch {
791
- }
792
- file[hostname] = entry;
793
- const tmpPath = `${cachePath}.tmp`;
794
- writeFileSync(tmpPath, JSON.stringify(file));
795
- renameSync(tmpPath, cachePath);
796
- } catch {
797
- }
798
- }
799
- var FAILED_NODE_TTL_MS = 60 * 60 * 1e3;
800
- function classifyAndCache(node, hostname, failedNodes, cachePath) {
801
- if (!node) {
802
- return { mode: null, node: null };
803
- }
804
- if (node.ip === hostname || node.host === hostname) {
805
- writeCache(hostname, {
806
- mode: "direct",
807
- node: null,
808
- failedNodes,
809
- updatedAt: Date.now()
810
- }, cachePath);
811
- return { mode: "direct", node: null };
812
- }
813
- writeCache(hostname, {
814
- mode: "proxy",
815
- node,
816
- failedNodes,
817
- updatedAt: Date.now()
818
- }, cachePath);
819
- return { mode: "proxy", node };
820
- }
821
- function getActiveFailedNodes(nodes) {
822
- if (!nodes || nodes.length === 0) return [];
823
- const now = Date.now();
824
- return nodes.filter((n) => now - n.failedAt < FAILED_NODE_TTL_MS);
825
- }
826
- function resolveDoh(hostname, cachePath) {
827
- const entry = readCache(hostname, cachePath);
828
- if (entry) {
829
- if (entry.mode === "direct") {
830
- return { mode: "direct", node: null };
831
- }
832
- if (entry.mode === "proxy" && entry.node) {
833
- return { mode: "proxy", node: entry.node };
834
- }
835
- }
836
- return { mode: null, node: null };
837
- }
838
- async function reResolveDoh(hostname, failedIp, userAgent, cachePath) {
839
- const entry = readCache(hostname, cachePath);
840
- const active = getActiveFailedNodes(entry?.failedNodes);
841
- const now = Date.now();
842
- const alreadyFailed = failedIp && active.some((n) => n.ip === failedIp);
843
- const failedNodes = failedIp && !alreadyFailed ? [...active, { ip: failedIp, failedAt: now }] : active;
844
- const excludeIps = failedNodes.map((n) => n.ip);
845
- const node = await execDohBinary(hostname, excludeIps, userAgent);
846
- return classifyAndCache(node, hostname, failedNodes, cachePath);
847
- }
848
719
  function getNow() {
849
720
  return (/* @__PURE__ */ new Date()).toISOString();
850
721
  }
@@ -1063,14 +934,6 @@ var OkxRestClient = class _OkxRestClient {
1063
934
  config;
1064
935
  rateLimiter;
1065
936
  dispatcher;
1066
- // DoH proxy state (lazy-resolved on first request)
1067
- dohResolved = false;
1068
- dohRetried = false;
1069
- directUnverified = false;
1070
- // The first direct connection has not yet been verified
1071
- dohNode = null;
1072
- dohAgent = null;
1073
- dohBaseUrl = null;
1074
937
  constructor(config) {
1075
938
  this.config = config;
1076
939
  this.rateLimiter = new RateLimiter(3e4, config.verbose);
@@ -1078,98 +941,6 @@ var OkxRestClient = class _OkxRestClient {
1078
941
  this.dispatcher = new ProxyAgent(config.proxyUrl);
1079
942
  }
1080
943
  }
1081
- /**
1082
- * Lazily resolve the DoH proxy node on the first request.
1083
- * Uses cache-first strategy via the resolver.
1084
- */
1085
- ensureDoh() {
1086
- if (this.dohResolved || this.dispatcher) return;
1087
- this.dohResolved = true;
1088
- try {
1089
- const { hostname, protocol } = new URL(this.config.baseUrl);
1090
- const result = resolveDoh(hostname);
1091
- if (!result.mode) {
1092
- this.directUnverified = true;
1093
- if (this.config.verbose) {
1094
- vlog("DoH: no cache, trying direct connection first");
1095
- }
1096
- return;
1097
- }
1098
- if (result.mode === "direct") {
1099
- if (this.config.verbose) {
1100
- vlog("DoH: mode=direct (overseas or cached), using direct connection");
1101
- }
1102
- return;
1103
- }
1104
- if (result.node) {
1105
- this.applyDohNode(result.node, protocol);
1106
- }
1107
- } catch (err) {
1108
- if (this.config.verbose) {
1109
- const cause = err instanceof Error ? err.message : String(err);
1110
- vlog(`DoH resolution failed, falling back to direct: ${cause}`);
1111
- }
1112
- }
1113
- }
1114
- /** Apply a DoH node: set up the custom Agent + base URL. */
1115
- applyDohNode(node, protocol) {
1116
- this.dohNode = node;
1117
- this.dohBaseUrl = `${protocol}//${node.host}`;
1118
- this.dohAgent = new Agent({
1119
- connect: {
1120
- lookup: (_hostname, options, callback) => {
1121
- if (options?.all) {
1122
- callback(null, [{ address: node.ip, family: 4 }]);
1123
- } else {
1124
- callback(null, node.ip, 4);
1125
- }
1126
- }
1127
- }
1128
- });
1129
- if (this.config.verbose) {
1130
- vlog(`DoH proxy active: \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
1131
- }
1132
- }
1133
- /**
1134
- * Handle network failure: re-resolve with --exclude and retry once.
1135
- * Returns true if retry should proceed, false if already retried.
1136
- */
1137
- async handleDohNetworkFailure() {
1138
- if (this.dohRetried) return false;
1139
- this.dohRetried = true;
1140
- const failedIp = this.dohNode?.ip ?? "";
1141
- const { hostname, protocol } = new URL(this.config.baseUrl);
1142
- this.dohNode = null;
1143
- this.dohAgent = null;
1144
- this.dohBaseUrl = null;
1145
- if (!failedIp) this.directUnverified = false;
1146
- if (this.config.verbose) {
1147
- vlog(failedIp ? `DoH: proxy node ${failedIp} failed, re-resolving with --exclude` : "DoH: direct connection failed, calling binary for DoH resolution");
1148
- }
1149
- try {
1150
- const result = await reResolveDoh(hostname, failedIp, this.dohUserAgent);
1151
- if (result.mode === "proxy" && result.node) {
1152
- this.applyDohNode(result.node, protocol);
1153
- this.dohRetried = false;
1154
- return true;
1155
- }
1156
- } catch {
1157
- }
1158
- if (this.config.verbose) {
1159
- vlog("DoH: re-resolution failed or switched to direct, retrying with direct connection");
1160
- }
1161
- return true;
1162
- }
1163
- get activeBaseUrl() {
1164
- return this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
1165
- }
1166
- get activeDispatcher() {
1167
- return this.dispatcher ?? this.dohAgent ?? void 0;
1168
- }
1169
- /** User-Agent for DoH proxy requests: OKX/@okx_ai/{packageName}/{version} */
1170
- get dohUserAgent() {
1171
- return `OKX/@okx_ai/${this.config.userAgent ?? "unknown"}`;
1172
- }
1173
944
  logRequest(method, url, auth) {
1174
945
  if (!this.config.verbose) return;
1175
946
  vlog(`\u2192 ${method} ${url}`);
@@ -1184,13 +955,14 @@ var OkxRestClient = class _OkxRestClient {
1184
955
  vlog(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1185
956
  }
1186
957
  }
1187
- async publicGet(path4, query, rateLimit) {
958
+ async publicGet(path4, query, rateLimit, simulatedTrading) {
1188
959
  return this.request({
1189
960
  method: "GET",
1190
961
  path: path4,
1191
962
  auth: "public",
1192
963
  query,
1193
- rateLimit
964
+ rateLimit,
965
+ simulatedTrading
1194
966
  });
1195
967
  }
1196
968
  async privateGet(path4, query, rateLimit) {
@@ -1339,17 +1111,13 @@ var OkxRestClient = class _OkxRestClient {
1339
1111
  * Security: validates Content-Type and enforces maxBytes limit.
1340
1112
  */
1341
1113
  async privatePostBinary(path4, body, opts) {
1342
- this.ensureDoh();
1343
1114
  const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
1344
1115
  const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
1345
1116
  const bodyJson = body ? JSON.stringify(body) : "";
1346
1117
  const endpoint = `POST ${path4}`;
1347
- this.logRequest("POST", `${this.activeBaseUrl}${path4}`, "private");
1118
+ this.logRequest("POST", `${this.config.baseUrl}${path4}`, "private");
1348
1119
  const reqConfig = { method: "POST", path: path4, auth: "private" };
1349
1120
  const headers = this.buildHeaders(reqConfig, path4, bodyJson, getNow());
1350
- if (this.dohNode) {
1351
- headers.set("User-Agent", this.dohUserAgent);
1352
- }
1353
1121
  const t0 = Date.now();
1354
1122
  const response = await this.fetchBinary(path4, endpoint, headers, bodyJson, t0);
1355
1123
  const elapsed = Date.now() - t0;
@@ -1383,10 +1151,10 @@ var OkxRestClient = class _OkxRestClient {
1383
1151
  method: "POST",
1384
1152
  headers,
1385
1153
  body: bodyJson || void 0,
1386
- signal: AbortSignal.timeout(this.config.timeoutMs),
1387
- dispatcher: this.activeDispatcher
1154
+ signal: AbortSignal.timeout(this.config.timeoutMs)
1388
1155
  };
1389
- return await fetch(`${this.activeBaseUrl}${path4}`, fetchOptions);
1156
+ if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
1157
+ return await fetch(`${this.config.baseUrl}${path4}`, fetchOptions);
1390
1158
  } catch (error) {
1391
1159
  if (this.config.verbose) {
1392
1160
  vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
@@ -1408,7 +1176,8 @@ var OkxRestClient = class _OkxRestClient {
1408
1176
  if (reqConfig.auth === "private") {
1409
1177
  this.setAuthHeaders(headers, reqConfig.method, requestPath, bodyJson, timestamp);
1410
1178
  }
1411
- if (this.config.demo) {
1179
+ const useSimulated = reqConfig.simulatedTrading !== void 0 ? reqConfig.simulatedTrading : this.config.demo;
1180
+ if (useSimulated) {
1412
1181
  headers.set("x-simulated-trading", "1");
1413
1182
  }
1414
1183
  return headers;
@@ -1416,55 +1185,10 @@ var OkxRestClient = class _OkxRestClient {
1416
1185
  // ---------------------------------------------------------------------------
1417
1186
  // JSON request
1418
1187
  // ---------------------------------------------------------------------------
1419
- /**
1420
- * Handle network error during a JSON request: refresh DoH and maybe retry.
1421
- * Always either returns a retry result or throws NetworkError.
1422
- */
1423
- async handleRequestNetworkError(error, reqConfig, requestPath, t0) {
1424
- if (!this.dohRetried) {
1425
- if (this.config.verbose) {
1426
- const cause = error instanceof Error ? error.message : String(error);
1427
- vlog(`Network failure, refreshing DoH: ${cause}`);
1428
- }
1429
- const shouldRetry = await this.handleDohNetworkFailure();
1430
- if (shouldRetry && reqConfig.method === "GET") {
1431
- return this.request(reqConfig);
1432
- }
1433
- }
1434
- if (this.config.verbose) {
1435
- const elapsed = Date.now() - t0;
1436
- const cause = error instanceof Error ? error.message : String(error);
1437
- vlog(`\u2717 NetworkError after ${elapsed}ms: ${cause}`);
1438
- }
1439
- throw new NetworkError(
1440
- `Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
1441
- `${reqConfig.method} ${requestPath}`,
1442
- error
1443
- );
1444
- }
1445
- /**
1446
- * After a successful HTTP response on direct connection, cache mode=direct.
1447
- * (Even if the business response is an error, the network path is valid.)
1448
- */
1449
- cacheDirectConnectionIfNeeded() {
1450
- if (!this.directUnverified || this.dohNode) return;
1451
- this.directUnverified = false;
1452
- const { hostname } = new URL(this.config.baseUrl);
1453
- writeCache(hostname, {
1454
- mode: "direct",
1455
- node: null,
1456
- failedNodes: [],
1457
- updatedAt: Date.now()
1458
- });
1459
- if (this.config.verbose) {
1460
- vlog("DoH: direct connection succeeded, cached mode=direct");
1461
- }
1462
- }
1463
1188
  async request(reqConfig) {
1464
- this.ensureDoh();
1465
1189
  const queryString = buildQueryString(reqConfig.query);
1466
1190
  const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1467
- const url = `${this.activeBaseUrl}${requestPath}`;
1191
+ const url = `${this.config.baseUrl}${requestPath}`;
1468
1192
  const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1469
1193
  const timestamp = getNow();
1470
1194
  this.logRequest(reqConfig.method, url, reqConfig.auth);
@@ -1472,9 +1196,6 @@ var OkxRestClient = class _OkxRestClient {
1472
1196
  await this.rateLimiter.consume(reqConfig.rateLimit);
1473
1197
  }
1474
1198
  const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1475
- if (this.dohNode) {
1476
- headers.set("User-Agent", this.dohUserAgent);
1477
- }
1478
1199
  const t0 = Date.now();
1479
1200
  let response;
1480
1201
  try {
@@ -1482,17 +1203,27 @@ var OkxRestClient = class _OkxRestClient {
1482
1203
  method: reqConfig.method,
1483
1204
  headers,
1484
1205
  body: reqConfig.method === "POST" ? bodyJson : void 0,
1485
- signal: AbortSignal.timeout(this.config.timeoutMs),
1486
- dispatcher: this.activeDispatcher
1206
+ signal: AbortSignal.timeout(this.config.timeoutMs)
1487
1207
  };
1208
+ if (this.dispatcher) {
1209
+ fetchOptions.dispatcher = this.dispatcher;
1210
+ }
1488
1211
  response = await fetch(url, fetchOptions);
1489
1212
  } catch (error) {
1490
- return await this.handleRequestNetworkError(error, reqConfig, requestPath, t0);
1213
+ if (this.config.verbose) {
1214
+ const elapsed2 = Date.now() - t0;
1215
+ const cause = error instanceof Error ? error.message : String(error);
1216
+ vlog(`\u2717 NetworkError after ${elapsed2}ms: ${cause}`);
1217
+ }
1218
+ throw new NetworkError(
1219
+ `Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
1220
+ `${reqConfig.method} ${requestPath}`,
1221
+ error
1222
+ );
1491
1223
  }
1492
1224
  const rawText = await response.text();
1493
1225
  const elapsed = Date.now() - t0;
1494
1226
  const traceId = extractTraceId(response.headers);
1495
- this.cacheDirectConnectionIfNeeded();
1496
1227
  return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
1497
1228
  }
1498
1229
  };
@@ -3371,19 +3102,19 @@ function safeWriteFile(targetDir, fileName, data) {
3371
3102
  throw new Error(`Invalid file name: "${fileName}"`);
3372
3103
  }
3373
3104
  const resolvedDir = resolve(targetDir);
3374
- const filePath = join3(resolvedDir, safeName);
3105
+ const filePath = join(resolvedDir, safeName);
3375
3106
  const resolvedPath = resolve(filePath);
3376
3107
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
3377
3108
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
3378
3109
  }
3379
- mkdirSync2(resolvedDir, { recursive: true });
3110
+ mkdirSync(resolvedDir, { recursive: true });
3380
3111
  const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
3381
3112
  try {
3382
- writeFileSync2(tmpPath, data);
3383
- renameSync2(tmpPath, resolvedPath);
3113
+ writeFileSync(tmpPath, data);
3114
+ renameSync(tmpPath, resolvedPath);
3384
3115
  } catch (err) {
3385
3116
  try {
3386
- unlinkSync2(tmpPath);
3117
+ unlinkSync(tmpPath);
3387
3118
  } catch {
3388
3119
  }
3389
3120
  throw err;
@@ -3391,18 +3122,19 @@ function safeWriteFile(targetDir, fileName, data) {
3391
3122
  return resolvedPath;
3392
3123
  }
3393
3124
  var MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024;
3394
- async function downloadSkillZip(client, name, targetDir) {
3125
+ async function downloadSkillZip(client, name, targetDir, format = "zip") {
3395
3126
  const result = await client.privatePostBinary(
3396
3127
  "/api/v5/skill/download",
3397
3128
  { name },
3398
3129
  { maxBytes: MAX_DOWNLOAD_BYTES }
3399
3130
  );
3400
- const fileName = `${name}.zip`;
3131
+ const ext = format === "skill" ? "skill" : "zip";
3132
+ const fileName = `${name}.${ext}`;
3401
3133
  const filePath = safeWriteFile(targetDir, fileName, result.data);
3402
3134
  return filePath;
3403
3135
  }
3404
3136
  var DEFAULT_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
3405
- var DEFAULT_REGISTRY_PATH = join5(homedir3(), ".okx", "skills", "registry.json");
3137
+ var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
3406
3138
  function registerSkillsTools() {
3407
3139
  return [
3408
3140
  {
@@ -3449,7 +3181,7 @@ function registerSkillsTools() {
3449
3181
  {
3450
3182
  name: "skills_download",
3451
3183
  module: "skills",
3452
- description: "Download a skill zip file from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: Downloads third-party developer content as a zip \u2014 does NOT install to agents. For full installation use CLI: okx skill add <name>. Use when the user wants to inspect or manually install a skill package.",
3184
+ description: "Download a skill package from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: Downloads third-party developer content \u2014 does NOT install to agents. For full installation use CLI: okx skill add <name>. Use when the user wants to inspect or manually install a skill package.",
3453
3185
  inputSchema: {
3454
3186
  type: "object",
3455
3187
  properties: {
@@ -3459,7 +3191,12 @@ function registerSkillsTools() {
3459
3191
  },
3460
3192
  targetDir: {
3461
3193
  type: "string",
3462
- description: "Directory path where the zip file will be saved"
3194
+ description: "Directory path where the file will be saved"
3195
+ },
3196
+ format: {
3197
+ type: "string",
3198
+ description: "Output file format: 'zip' or 'skill' (default: 'skill')",
3199
+ enum: ["zip", "skill"]
3463
3200
  }
3464
3201
  },
3465
3202
  required: ["name", "targetDir"],
@@ -3492,7 +3229,8 @@ async function handleSearch(args, ctx) {
3492
3229
  async function handleDownload(args, ctx) {
3493
3230
  const name = String(args.name);
3494
3231
  const targetDir = String(args.targetDir);
3495
- const filePath = await downloadSkillZip(ctx.client, name, targetDir);
3232
+ const format = args.format === "zip" ? "zip" : "skill";
3233
+ const filePath = await downloadSkillZip(ctx.client, name, targetDir, format);
3496
3234
  return {
3497
3235
  endpoint: "POST /api/v5/skill/download",
3498
3236
  requestTime: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5639,6 +5377,12 @@ function registerFuturesTools() {
5639
5377
  ];
5640
5378
  }
5641
5379
  var TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1e3;
5380
+ var DEMO_PROPERTY = {
5381
+ demo: {
5382
+ type: "boolean",
5383
+ description: "Query simulated trading (demo) market data. Default: false (live market data)."
5384
+ }
5385
+ };
5642
5386
  function registerMarketTools() {
5643
5387
  return [
5644
5388
  {
@@ -5652,7 +5396,8 @@ function registerMarketTools() {
5652
5396
  instId: {
5653
5397
  type: "string",
5654
5398
  description: "e.g. BTC-USDT, BTC-USDT-SWAP"
5655
- }
5399
+ },
5400
+ ...DEMO_PROPERTY
5656
5401
  },
5657
5402
  required: ["instId"]
5658
5403
  },
@@ -5661,7 +5406,8 @@ function registerMarketTools() {
5661
5406
  const response = await context.client.publicGet(
5662
5407
  "/api/v5/market/ticker",
5663
5408
  { instId: requireString(args, "instId") },
5664
- publicRateLimit("market_get_ticker", 20)
5409
+ publicRateLimit("market_get_ticker", 20),
5410
+ readBoolean(args, "demo") ?? false
5665
5411
  );
5666
5412
  return normalizeResponse(response);
5667
5413
  }
@@ -5685,7 +5431,8 @@ function registerMarketTools() {
5685
5431
  instFamily: {
5686
5432
  type: "string",
5687
5433
  description: "e.g. BTC-USD"
5688
- }
5434
+ },
5435
+ ...DEMO_PROPERTY
5689
5436
  },
5690
5437
  required: ["instType"]
5691
5438
  },
@@ -5698,7 +5445,8 @@ function registerMarketTools() {
5698
5445
  uly: readString(args, "uly"),
5699
5446
  instFamily: readString(args, "instFamily")
5700
5447
  }),
5701
- publicRateLimit("market_get_tickers", 20)
5448
+ publicRateLimit("market_get_tickers", 20),
5449
+ readBoolean(args, "demo") ?? false
5702
5450
  );
5703
5451
  return normalizeResponse(response);
5704
5452
  }
@@ -5718,7 +5466,8 @@ function registerMarketTools() {
5718
5466
  sz: {
5719
5467
  type: "number",
5720
5468
  description: "Depth per side, default 1, max 400"
5721
- }
5469
+ },
5470
+ ...DEMO_PROPERTY
5722
5471
  },
5723
5472
  required: ["instId"]
5724
5473
  },
@@ -5730,7 +5479,8 @@ function registerMarketTools() {
5730
5479
  instId: requireString(args, "instId"),
5731
5480
  sz: readNumber(args, "sz")
5732
5481
  }),
5733
- publicRateLimit("market_get_orderbook", 20)
5482
+ publicRateLimit("market_get_orderbook", 20),
5483
+ readBoolean(args, "demo") ?? false
5734
5484
  );
5735
5485
  return normalizeResponse(response);
5736
5486
  }
@@ -5763,7 +5513,8 @@ function registerMarketTools() {
5763
5513
  limit: {
5764
5514
  type: "number",
5765
5515
  description: "Max results (default 100)"
5766
- }
5516
+ },
5517
+ ...DEMO_PROPERTY
5767
5518
  },
5768
5519
  required: ["instId"]
5769
5520
  },
@@ -5771,6 +5522,7 @@ function registerMarketTools() {
5771
5522
  const args = asRecord(rawArgs);
5772
5523
  const afterTs = readString(args, "after");
5773
5524
  const beforeTs = readString(args, "before");
5525
+ const demo = readBoolean(args, "demo") ?? false;
5774
5526
  const query = compactObject({
5775
5527
  instId: requireString(args, "instId"),
5776
5528
  bar: readString(args, "bar"),
@@ -5782,9 +5534,9 @@ function registerMarketTools() {
5782
5534
  const hasTimestamp = afterTs !== void 0 || beforeTs !== void 0;
5783
5535
  const useHistory = afterTs !== void 0 && Number(afterTs) < Date.now() - TWO_DAYS_MS;
5784
5536
  const path4 = useHistory ? "/api/v5/market/history-candles" : "/api/v5/market/candles";
5785
- const response = await context.client.publicGet(path4, query, rateLimit);
5537
+ const response = await context.client.publicGet(path4, query, rateLimit, demo);
5786
5538
  if (!useHistory && hasTimestamp && Array.isArray(response.data) && response.data.length === 0) {
5787
- return normalizeResponse(await context.client.publicGet("/api/v5/market/history-candles", query, rateLimit));
5539
+ return normalizeResponse(await context.client.publicGet("/api/v5/market/history-candles", query, rateLimit, demo));
5788
5540
  }
5789
5541
  return normalizeResponse(response);
5790
5542
  }
@@ -5812,7 +5564,8 @@ function registerMarketTools() {
5812
5564
  instFamily: {
5813
5565
  type: "string",
5814
5566
  description: "e.g. BTC-USD"
5815
- }
5567
+ },
5568
+ ...DEMO_PROPERTY
5816
5569
  },
5817
5570
  required: ["instType"]
5818
5571
  },
@@ -5826,7 +5579,8 @@ function registerMarketTools() {
5826
5579
  uly: readString(args, "uly"),
5827
5580
  instFamily: readString(args, "instFamily")
5828
5581
  }),
5829
- publicRateLimit("market_get_instruments", 20)
5582
+ publicRateLimit("market_get_instruments", 20),
5583
+ readBoolean(args, "demo") ?? false
5830
5584
  );
5831
5585
  return normalizeResponse(response);
5832
5586
  }
@@ -5858,13 +5612,15 @@ function registerMarketTools() {
5858
5612
  limit: {
5859
5613
  type: "number",
5860
5614
  description: "History records (default 20, max 100)"
5861
- }
5615
+ },
5616
+ ...DEMO_PROPERTY
5862
5617
  },
5863
5618
  required: ["instId"]
5864
5619
  },
5865
5620
  handler: async (rawArgs, context) => {
5866
5621
  const args = asRecord(rawArgs);
5867
5622
  const isHistory = readBoolean(args, "history") ?? false;
5623
+ const demo = readBoolean(args, "demo") ?? false;
5868
5624
  if (isHistory) {
5869
5625
  const response2 = await context.client.publicGet(
5870
5626
  "/api/v5/public/funding-rate-history",
@@ -5874,14 +5630,16 @@ function registerMarketTools() {
5874
5630
  before: readString(args, "before"),
5875
5631
  limit: readNumber(args, "limit") ?? 20
5876
5632
  }),
5877
- publicRateLimit("market_get_funding_rate", 20)
5633
+ publicRateLimit("market_get_funding_rate", 20),
5634
+ demo
5878
5635
  );
5879
5636
  return normalizeResponse(response2);
5880
5637
  }
5881
5638
  const response = await context.client.publicGet(
5882
5639
  "/api/v5/public/funding-rate",
5883
5640
  { instId: requireString(args, "instId") },
5884
- publicRateLimit("market_get_funding_rate", 20)
5641
+ publicRateLimit("market_get_funding_rate", 20),
5642
+ demo
5885
5643
  );
5886
5644
  return normalizeResponse(response);
5887
5645
  }
@@ -5908,7 +5666,8 @@ function registerMarketTools() {
5908
5666
  },
5909
5667
  instFamily: {
5910
5668
  type: "string"
5911
- }
5669
+ },
5670
+ ...DEMO_PROPERTY
5912
5671
  },
5913
5672
  required: ["instType"]
5914
5673
  },
@@ -5922,7 +5681,8 @@ function registerMarketTools() {
5922
5681
  uly: readString(args, "uly"),
5923
5682
  instFamily: readString(args, "instFamily")
5924
5683
  }),
5925
- publicRateLimit("market_get_mark_price", 10)
5684
+ publicRateLimit("market_get_mark_price", 10),
5685
+ readBoolean(args, "demo") ?? false
5926
5686
  );
5927
5687
  return normalizeResponse(response);
5928
5688
  }
@@ -5942,7 +5702,8 @@ function registerMarketTools() {
5942
5702
  limit: {
5943
5703
  type: "number",
5944
5704
  description: "Default 20, max 500"
5945
- }
5705
+ },
5706
+ ...DEMO_PROPERTY
5946
5707
  },
5947
5708
  required: ["instId"]
5948
5709
  },
@@ -5954,7 +5715,8 @@ function registerMarketTools() {
5954
5715
  instId: requireString(args, "instId"),
5955
5716
  limit: readNumber(args, "limit") ?? 20
5956
5717
  }),
5957
- publicRateLimit("market_get_trades", 20)
5718
+ publicRateLimit("market_get_trades", 20),
5719
+ readBoolean(args, "demo") ?? false
5958
5720
  );
5959
5721
  return normalizeResponse(response);
5960
5722
  }
@@ -5974,7 +5736,8 @@ function registerMarketTools() {
5974
5736
  quoteCcy: {
5975
5737
  type: "string",
5976
5738
  description: "e.g. USD or USDT"
5977
- }
5739
+ },
5740
+ ...DEMO_PROPERTY
5978
5741
  }
5979
5742
  },
5980
5743
  handler: async (rawArgs, context) => {
@@ -5985,7 +5748,8 @@ function registerMarketTools() {
5985
5748
  instId: readString(args, "instId"),
5986
5749
  quoteCcy: readString(args, "quoteCcy")
5987
5750
  }),
5988
- publicRateLimit("market_get_index_ticker", 20)
5751
+ publicRateLimit("market_get_index_ticker", 20),
5752
+ readBoolean(args, "demo") ?? false
5989
5753
  );
5990
5754
  return normalizeResponse(response);
5991
5755
  }
@@ -6022,7 +5786,8 @@ function registerMarketTools() {
6022
5786
  history: {
6023
5787
  type: "boolean",
6024
5788
  description: "true=older historical data"
6025
- }
5789
+ },
5790
+ ...DEMO_PROPERTY
6026
5791
  },
6027
5792
  required: ["instId"]
6028
5793
  },
@@ -6039,7 +5804,8 @@ function registerMarketTools() {
6039
5804
  before: readString(args, "before"),
6040
5805
  limit: readNumber(args, "limit")
6041
5806
  }),
6042
- publicRateLimit("market_get_index_candles", 20)
5807
+ publicRateLimit("market_get_index_candles", 20),
5808
+ readBoolean(args, "demo") ?? false
6043
5809
  );
6044
5810
  return normalizeResponse(response);
6045
5811
  }
@@ -6055,7 +5821,8 @@ function registerMarketTools() {
6055
5821
  instId: {
6056
5822
  type: "string",
6057
5823
  description: "SWAP or FUTURES ID, e.g. BTC-USDT-SWAP"
6058
- }
5824
+ },
5825
+ ...DEMO_PROPERTY
6059
5826
  },
6060
5827
  required: ["instId"]
6061
5828
  },
@@ -6064,7 +5831,8 @@ function registerMarketTools() {
6064
5831
  const response = await context.client.publicGet(
6065
5832
  "/api/v5/public/price-limit",
6066
5833
  { instId: requireString(args, "instId") },
6067
- publicRateLimit("market_get_price_limit", 20)
5834
+ publicRateLimit("market_get_price_limit", 20),
5835
+ readBoolean(args, "demo") ?? false
6068
5836
  );
6069
5837
  return normalizeResponse(response);
6070
5838
  }
@@ -6091,7 +5859,8 @@ function registerMarketTools() {
6091
5859
  },
6092
5860
  instFamily: {
6093
5861
  type: "string"
6094
- }
5862
+ },
5863
+ ...DEMO_PROPERTY
6095
5864
  },
6096
5865
  required: ["instType"]
6097
5866
  },
@@ -6105,7 +5874,8 @@ function registerMarketTools() {
6105
5874
  uly: readString(args, "uly"),
6106
5875
  instFamily: readString(args, "instFamily")
6107
5876
  }),
6108
- publicRateLimit("market_get_open_interest", 20)
5877
+ publicRateLimit("market_get_open_interest", 20),
5878
+ readBoolean(args, "demo") ?? false
6109
5879
  );
6110
5880
  return normalizeResponse(response);
6111
5881
  }
@@ -6126,7 +5896,8 @@ function registerMarketTools() {
6126
5896
  instId: {
6127
5897
  type: "string",
6128
5898
  description: "Optional: filter by specific instrument ID, e.g. AAPL-USDT-SWAP"
6129
- }
5899
+ },
5900
+ ...DEMO_PROPERTY
6130
5901
  },
6131
5902
  required: []
6132
5903
  },
@@ -6137,7 +5908,8 @@ function registerMarketTools() {
6137
5908
  const response = await context.client.publicGet(
6138
5909
  "/api/v5/public/instruments",
6139
5910
  compactObject({ instType, instId }),
6140
- publicRateLimit("market_get_stock_tokens", 20)
5911
+ publicRateLimit("market_get_stock_tokens", 20),
5912
+ readBoolean(args, "demo") ?? false
6141
5913
  );
6142
5914
  const data = response.data;
6143
5915
  const filtered = Array.isArray(data) ? data.filter((item) => item.instCategory === "3") : data;
@@ -6147,7 +5919,7 @@ function registerMarketTools() {
6147
5919
  {
6148
5920
  name: "market_get_instruments_by_category",
6149
5921
  module: "market",
6150
- description: "Discover tradeable instruments by asset category. Stock tokens (instCategory=3, e.g. AAPL-USDT-SWAP, TSLA-USDT-SWAP), Metals (4, e.g. XAUUSDT-USDT-SWAP for gold), Commodities (5, e.g. OIL-USDT-SWAP for crude oil), Forex (6, e.g. EURUSDT-USDT-SWAP for EUR/USD), Bonds (7, e.g. US30Y-USDT-SWAP). Use this to find instIds before querying prices or placing orders. Filters client-side by instCategory.",
5922
+ description: "Discover tradeable instruments by asset category. Stock tokens (instCategory=3, e.g. AAPL-USDT-SWAP, TSLA-USDT-SWAP), Metals (4, e.g. XAUUSDT-USDT-SWAP for gold), Commodities (5, e.g. OIL-USDT-SWAP for crude oil), Forex (6, e.g. EURUSDT-USDT-SWAP for EUR/USD), Bonds (7, e.g. US30Y-USDT-SWAP for crude oil). Use this to find instIds before querying prices or placing orders. Filters client-side by instCategory.",
6151
5923
  isWrite: false,
6152
5924
  inputSchema: {
6153
5925
  type: "object",
@@ -6165,7 +5937,8 @@ function registerMarketTools() {
6165
5937
  instId: {
6166
5938
  type: "string",
6167
5939
  description: "Optional: filter by specific instrument ID"
6168
- }
5940
+ },
5941
+ ...DEMO_PROPERTY
6169
5942
  },
6170
5943
  required: ["instCategory"]
6171
5944
  },
@@ -6177,7 +5950,8 @@ function registerMarketTools() {
6177
5950
  const response = await context.client.publicGet(
6178
5951
  "/api/v5/public/instruments",
6179
5952
  compactObject({ instType, instId }),
6180
- publicRateLimit("market_get_instruments_by_category", 20)
5953
+ publicRateLimit("market_get_instruments_by_category", 20),
5954
+ readBoolean(args, "demo") ?? false
6181
5955
  );
6182
5956
  const data = response.data;
6183
5957
  const filtered = Array.isArray(data) ? data.filter((item) => item.instCategory === instCategory) : data;
@@ -7709,12 +7483,12 @@ function toMcpTool(tool) {
7709
7483
  };
7710
7484
  }
7711
7485
  function configFilePath() {
7712
- return join6(homedir4(), ".okx", "config.toml");
7486
+ return join4(homedir2(), ".okx", "config.toml");
7713
7487
  }
7714
7488
  function readFullConfig() {
7715
7489
  const path4 = configFilePath();
7716
7490
  if (!existsSync3(path4)) return { profiles: {} };
7717
- const raw = readFileSync4(path4, "utf-8");
7491
+ const raw = readFileSync3(path4, "utf-8");
7718
7492
  try {
7719
7493
  return parse(raw);
7720
7494
  } catch (err) {
@@ -7845,21 +7619,21 @@ function loadConfig(cli) {
7845
7619
  verbose: cli.verbose ?? false
7846
7620
  };
7847
7621
  }
7848
- var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
7622
+ var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
7849
7623
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7850
- function readCache2() {
7624
+ function readCache() {
7851
7625
  try {
7852
7626
  if (existsSync4(CACHE_FILE)) {
7853
- return JSON.parse(readFileSync5(CACHE_FILE, "utf-8"));
7627
+ return JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
7854
7628
  }
7855
7629
  } catch {
7856
7630
  }
7857
7631
  return {};
7858
7632
  }
7859
- function writeCache2(cache) {
7633
+ function writeCache(cache) {
7860
7634
  try {
7861
- mkdirSync6(join7(homedir5(), ".okx"), { recursive: true });
7862
- writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7635
+ mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
7636
+ writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7863
7637
  } catch {
7864
7638
  }
7865
7639
  }
@@ -7890,14 +7664,14 @@ async function fetchLatestVersion(packageName) {
7890
7664
  function refreshCacheInBackground(packageName) {
7891
7665
  fetchLatestVersion(packageName).then((latest) => {
7892
7666
  if (!latest) return;
7893
- const cache = readCache2();
7667
+ const cache = readCache();
7894
7668
  cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
7895
- writeCache2(cache);
7669
+ writeCache(cache);
7896
7670
  }).catch(() => {
7897
7671
  });
7898
7672
  }
7899
7673
  function checkForUpdates(packageName, currentVersion) {
7900
- const cache = readCache2();
7674
+ const cache = readCache();
7901
7675
  const entry = cache[packageName];
7902
7676
  if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
7903
7677
  process.stderr.write(
@@ -8127,7 +7901,7 @@ var _require = createRequire(import.meta.url);
8127
7901
  var pkg = _require("../package.json");
8128
7902
  var SERVER_NAME = "okx-trade-mcp";
8129
7903
  var SERVER_VERSION = pkg.version;
8130
- var GIT_HASH = true ? "caa6dae" : "dev";
7904
+ var GIT_HASH = true ? "6d4d559" : "dev";
8131
7905
 
8132
7906
  // src/server.ts
8133
7907
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";