@okx_ai/okx-trade-mcp 1.3.2-beta.2 → 1.3.2-beta.4

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
@@ -22,18 +22,21 @@ import {
22
22
  import { homedir as homedir2 } from "os";
23
23
  import { join as join2, dirname } from "path";
24
24
  import { createHmac } from "crypto";
25
+ import { spawn, execFile as execFile2 } from "child_process";
26
+ import { homedir as homedir3 } from "os";
27
+ import { join as join3 } from "path";
25
28
  import fs from "fs";
26
29
  import path from "path";
27
30
  import os from "os";
28
31
  import { writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
29
- import { join as join3, resolve, basename, sep } from "path";
32
+ import { join as join4, resolve, basename, sep } from "path";
30
33
  import { randomUUID } from "crypto";
31
34
  import yauzl from "yauzl";
32
- import { join as join5, dirname as dirname3 } from "path";
33
- import { homedir as homedir3 } from "os";
34
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
35
- import { join as join6, dirname as dirname4 } from "path";
35
+ import { join as join6, dirname as dirname3 } from "path";
36
36
  import { homedir as homedir4 } from "os";
37
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
38
+ import { join as join7, dirname as dirname4 } from "path";
39
+ import { homedir as homedir5 } from "os";
37
40
 
38
41
  // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
39
42
  function getLineColFromPtr(string, ptr) {
@@ -722,8 +725,8 @@ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
722
725
 
723
726
  // ../core/dist/index.js
724
727
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
725
- import { join as join7 } from "path";
726
- import { homedir as homedir5 } from "os";
728
+ import { join as join8 } from "path";
729
+ import { homedir as homedir6 } from "os";
727
730
  import fs2 from "fs";
728
731
  import path2 from "path";
729
732
  import os2 from "os";
@@ -731,6 +734,8 @@ import * as fs3 from "fs";
731
734
  import * as path3 from "path";
732
735
  import * as os3 from "os";
733
736
  import { execFileSync } from "child_process";
737
+ import { join as join12 } from "path";
738
+ import { homedir as homedir10 } from "os";
734
739
  var EXEC_TIMEOUT_MS = 3e4;
735
740
  var ALLOWED_DOMAIN_RE = /^[\w.-]+\.okx\.com$/;
736
741
  var PILOT_BIN_DIR = join(homedir(), ".okx", "bin");
@@ -1042,6 +1047,11 @@ var ConfigError = class extends OkxMcpError {
1042
1047
  super("ConfigError", message, { suggestion });
1043
1048
  }
1044
1049
  };
1050
+ var NotLoggedInError = class extends ConfigError {
1051
+ constructor(suggestion = "Run `okx auth login` to authenticate.") {
1052
+ super("Not logged in.", suggestion);
1053
+ }
1054
+ };
1045
1055
  var ValidationError = class extends OkxMcpError {
1046
1056
  constructor(message, suggestion) {
1047
1057
  super("ValidationError", message, { suggestion });
@@ -1094,6 +1104,97 @@ function toToolErrorPayload(error, fallbackEndpoint) {
1094
1104
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1095
1105
  };
1096
1106
  }
1107
+ var EXIT_CODES = {
1108
+ SUCCESS: 0,
1109
+ UNAUTHORIZED_CALLER: 1,
1110
+ NOT_LOGGED_IN: 2,
1111
+ REFRESH_FAILED: 3
1112
+ };
1113
+ var EXEC_TIMEOUT_MS2 = 5e3;
1114
+ var AUTH_BIN_DIR = join3(homedir3(), ".okx", "bin");
1115
+ function getAuthBinaryPath() {
1116
+ if (process.env.OKX_AUTH_BIN) {
1117
+ return process.env.OKX_AUTH_BIN;
1118
+ }
1119
+ const ext = process.platform === "win32" ? ".exe" : "";
1120
+ return join3(AUTH_BIN_DIR, `okx-auth${ext}`);
1121
+ }
1122
+ function execAuthToken() {
1123
+ const binPath = getAuthBinaryPath();
1124
+ return new Promise((resolve3, reject) => {
1125
+ const child = spawn(binPath, ["token"], {
1126
+ stdio: ["ignore", "ignore", "inherit", "pipe"]
1127
+ // stdin stdout stderr fd3 (pipe)
1128
+ });
1129
+ const chunks = [];
1130
+ const fd3 = child.stdio[3];
1131
+ fd3.on("data", (chunk) => chunks.push(chunk));
1132
+ child.on("error", (err) => {
1133
+ reject(new ConfigError(
1134
+ `Failed to spawn okx-auth: ${err.message}`,
1135
+ "Ensure the okx-auth binary exists and is executable."
1136
+ ));
1137
+ });
1138
+ child.on("close", (code) => {
1139
+ if (code === EXIT_CODES.SUCCESS) {
1140
+ const token = Buffer.concat(chunks).toString("utf-8").trim();
1141
+ if (!token) {
1142
+ reject(new AuthenticationError(
1143
+ "okx-auth returned empty token.",
1144
+ "Run `okx auth login` to re-authenticate."
1145
+ ));
1146
+ return;
1147
+ }
1148
+ resolve3(token);
1149
+ return;
1150
+ }
1151
+ if (code === EXIT_CODES.NOT_LOGGED_IN) {
1152
+ reject(new NotLoggedInError());
1153
+ return;
1154
+ }
1155
+ if (code === EXIT_CODES.UNAUTHORIZED_CALLER) {
1156
+ reject(new AuthenticationError(
1157
+ "okx-auth rejected the caller (unauthorized).",
1158
+ "Ensure you are running from a trusted OKX tool."
1159
+ ));
1160
+ return;
1161
+ }
1162
+ if (code === EXIT_CODES.REFRESH_FAILED) {
1163
+ reject(new AuthenticationError(
1164
+ "Token refresh failed.",
1165
+ "Run `okx auth login` to re-authenticate."
1166
+ ));
1167
+ return;
1168
+ }
1169
+ reject(new AuthenticationError(
1170
+ `okx-auth token exited with code ${code}.`,
1171
+ "Run `okx auth login` to re-authenticate."
1172
+ ));
1173
+ });
1174
+ });
1175
+ }
1176
+ function execAuthStatus() {
1177
+ const binPath = getAuthBinaryPath();
1178
+ return new Promise((resolve3) => {
1179
+ execFile2(
1180
+ binPath,
1181
+ ["status", "--json"],
1182
+ { timeout: EXEC_TIMEOUT_MS2, encoding: "utf-8" },
1183
+ (error, stdout) => {
1184
+ if (error) {
1185
+ resolve3(null);
1186
+ return;
1187
+ }
1188
+ try {
1189
+ const result = JSON.parse(stdout);
1190
+ resolve3(result);
1191
+ } catch {
1192
+ resolve3(null);
1193
+ }
1194
+ }
1195
+ );
1196
+ });
1197
+ }
1097
1198
  function sleep(ms) {
1098
1199
  return new Promise((resolve3) => {
1099
1200
  setTimeout(resolve3, ms);
@@ -1225,6 +1326,7 @@ function maskKey(key) {
1225
1326
  if (key.length <= 8) return "***";
1226
1327
  return `${key.slice(0, 3)}***${key.slice(-3)}`;
1227
1328
  }
1329
+ var TOKEN_CACHE_TTL_MS = 6e4;
1228
1330
  function vlog2(message) {
1229
1331
  process.stderr.write(`[verbose] ${message}
1230
1332
  `);
@@ -1233,6 +1335,8 @@ var OkxRestClient = class _OkxRestClient {
1233
1335
  config;
1234
1336
  rateLimiter;
1235
1337
  dispatcher;
1338
+ cachedAccessToken;
1339
+ cachedAccessTokenAt = 0;
1236
1340
  pilot;
1237
1341
  constructor(config) {
1238
1342
  this.config = config;
@@ -1247,6 +1351,51 @@ var OkxRestClient = class _OkxRestClient {
1247
1351
  hasCustomProxy: !!config.proxyUrl
1248
1352
  });
1249
1353
  }
1354
+ /**
1355
+ * Resolve OAuth access token via the okx-auth binary (fd3 pipe).
1356
+ * Caches the token for 60 s to avoid spawning the binary on every
1357
+ * request. The binary handles refresh internally (300s TTL lead),
1358
+ * so periodic re-calls let it serve a fresh token when needed.
1359
+ * Returns null when not logged in.
1360
+ */
1361
+ async resolveAccessToken() {
1362
+ if (this.cachedAccessToken && Date.now() - this.cachedAccessTokenAt < TOKEN_CACHE_TTL_MS) {
1363
+ return this.cachedAccessToken;
1364
+ }
1365
+ try {
1366
+ const token = await execAuthToken();
1367
+ this.cachedAccessToken = token;
1368
+ this.cachedAccessTokenAt = Date.now();
1369
+ return token;
1370
+ } catch (e) {
1371
+ if (e instanceof NotLoggedInError) {
1372
+ return null;
1373
+ }
1374
+ throw e;
1375
+ }
1376
+ }
1377
+ /**
1378
+ * Dynamic auth — determines auth method per request.
1379
+ *
1380
+ * 1. API key in config → HMAC signing (no OAuth fallback)
1381
+ * 2. OAuth token via okx-auth binary → Bearer token
1382
+ * 3. Neither → throw ConfigError
1383
+ */
1384
+ async applyAuth(headers, method, requestPath, bodyJson, timestamp) {
1385
+ if (this.config.apiKey && this.config.secretKey && this.config.passphrase) {
1386
+ this.setAuthHeaders(headers, method, requestPath, bodyJson, timestamp);
1387
+ return;
1388
+ }
1389
+ const accessToken = await this.resolveAccessToken();
1390
+ if (accessToken) {
1391
+ headers.set("Authorization", `Bearer ${accessToken}`);
1392
+ return;
1393
+ }
1394
+ throw new ConfigError(
1395
+ "No credentials found.",
1396
+ "Run `okx auth login` to authenticate, or configure API key credentials."
1397
+ );
1398
+ }
1250
1399
  /** The canonical base URL for this client (e.g. https://www.okx.com). */
1251
1400
  get baseUrl() {
1252
1401
  return this.config.baseUrl;
@@ -1305,18 +1454,6 @@ var OkxRestClient = class _OkxRestClient {
1305
1454
  });
1306
1455
  }
1307
1456
  setAuthHeaders(headers, method, requestPath, bodyJson, timestamp) {
1308
- if (!this.config.hasAuth) {
1309
- throw new ConfigError(
1310
- "Private endpoint requires API credentials.",
1311
- "Configure OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE."
1312
- );
1313
- }
1314
- if (!this.config.apiKey || !this.config.secretKey || !this.config.passphrase) {
1315
- throw new ConfigError(
1316
- "Invalid private API credentials state.",
1317
- "Ensure all OKX credentials are set."
1318
- );
1319
- }
1320
1457
  const payload = `${timestamp}${method.toUpperCase()}${requestPath}${bodyJson}`;
1321
1458
  const signature = signOkxPayload(payload, this.config.secretKey);
1322
1459
  headers.set("OK-ACCESS-KEY", this.config.apiKey);
@@ -1431,7 +1568,7 @@ var OkxRestClient = class _OkxRestClient {
1431
1568
  const conn = this.pilot.getConnectionParams();
1432
1569
  this.logRequest("POST", `${conn.baseUrl}${path4}`, "private");
1433
1570
  const reqConfig = { method: "POST", path: path4, auth: "private" };
1434
- const headers = this.buildHeaders(reqConfig, path4, bodyJson, getNow());
1571
+ const headers = await this.buildHeaders(reqConfig, path4, bodyJson, getNow());
1435
1572
  if (conn.userAgent) {
1436
1573
  headers.set("User-Agent", conn.userAgent);
1437
1574
  }
@@ -1552,7 +1689,7 @@ var OkxRestClient = class _OkxRestClient {
1552
1689
  // Header building
1553
1690
  // ---------------------------------------------------------------------------
1554
1691
  /** Build HTTP headers. reqConfig.extraHeaders must NOT contain auth keys (OK-ACCESS-*). */
1555
- buildHeaders(reqConfig, requestPath, bodyJson, timestamp) {
1692
+ async buildHeaders(reqConfig, requestPath, bodyJson, timestamp) {
1556
1693
  const headers = new Headers({
1557
1694
  "Content-Type": "application/json",
1558
1695
  Accept: "application/json"
@@ -1561,7 +1698,7 @@ var OkxRestClient = class _OkxRestClient {
1561
1698
  headers.set("User-Agent", this.config.userAgent);
1562
1699
  }
1563
1700
  if (reqConfig.auth === "private") {
1564
- this.setAuthHeaders(headers, reqConfig.method, requestPath, bodyJson, timestamp);
1701
+ await this.applyAuth(headers, reqConfig.method, requestPath, bodyJson, timestamp);
1565
1702
  }
1566
1703
  const useSimulated = reqConfig.simulatedTrading !== void 0 ? reqConfig.simulatedTrading : this.config.demo;
1567
1704
  if (useSimulated) {
@@ -1615,7 +1752,7 @@ var OkxRestClient = class _OkxRestClient {
1615
1752
  if (reqConfig.rateLimit) {
1616
1753
  await this.rateLimiter.consume(reqConfig.rateLimit);
1617
1754
  }
1618
- const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1755
+ const headers = await this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1619
1756
  if (conn.userAgent) {
1620
1757
  headers.set("User-Agent", conn.userAgent);
1621
1758
  }
@@ -1767,6 +1904,23 @@ function privateRateLimit(key, rps = 10) {
1767
1904
  refillPerSecond: rps
1768
1905
  };
1769
1906
  }
1907
+ var CURSOR_PROPS = {
1908
+ after: { type: "string", description: "Cursor: return older records" },
1909
+ before: { type: "string", description: "Cursor: return newer records" }
1910
+ };
1911
+ var TIME_RANGE_PROPS = {
1912
+ begin: { type: "string", description: "Start time (ms)" },
1913
+ end: { type: "string", description: "End time (ms)" }
1914
+ };
1915
+ function readPaginationParams(args, readStr, readNum) {
1916
+ return {
1917
+ after: readStr(args, "after"),
1918
+ before: readStr(args, "before"),
1919
+ begin: readStr(args, "begin"),
1920
+ end: readStr(args, "end"),
1921
+ limit: readNum(args, "limit")
1922
+ };
1923
+ }
1770
1924
  function assertNotDemo(config, endpoint) {
1771
1925
  if (config.demo) {
1772
1926
  throw new ConfigError(
@@ -2038,7 +2192,7 @@ var OKX_SITES = {
2038
2192
  },
2039
2193
  us: {
2040
2194
  label: "US",
2041
- apiBaseUrl: "https://app.okx.com",
2195
+ apiBaseUrl: "https://us.okx.com",
2042
2196
  webUrl: "https://app.okx.com"
2043
2197
  }
2044
2198
  };
@@ -3557,7 +3711,7 @@ function safeWriteFile(targetDir, fileName, data) {
3557
3711
  throw new Error(`Invalid file name: "${fileName}"`);
3558
3712
  }
3559
3713
  const resolvedDir = resolve(targetDir);
3560
- const filePath = join3(resolvedDir, safeName);
3714
+ const filePath = join4(resolvedDir, safeName);
3561
3715
  const resolvedPath = resolve(filePath);
3562
3716
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
3563
3717
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
@@ -3600,7 +3754,7 @@ async function downloadSkillZip(client, name, targetDir, format = "zip") {
3600
3754
  return filePath;
3601
3755
  }
3602
3756
  var DEFAULT_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
3603
- var DEFAULT_REGISTRY_PATH = join5(homedir3(), ".okx", "skills", "registry.json");
3757
+ var DEFAULT_REGISTRY_PATH = join6(homedir4(), ".okx", "skills", "registry.json");
3604
3758
  function registerSkillsTools() {
3605
3759
  return [
3606
3760
  {
@@ -5998,37 +6152,45 @@ function registerEventContractTools() {
5998
6152
  {
5999
6153
  name: "event_get_orders",
6000
6154
  module: "event",
6001
- description: "Query event contract orders. state=live for open orders; omit for history. outcome pre-translated (YES/NO/UP/DOWN).",
6155
+ description: "Query event contract orders (open, 7d history, or 3-month archive). outcome pre-translated (YES/NO/UP/DOWN). Do NOT use for trade executions \u2014 use event_get_fills for fill records and settlement outcomes.",
6002
6156
  isWrite: false,
6003
6157
  inputSchema: {
6004
6158
  type: "object",
6005
6159
  properties: {
6006
- instId: {
6160
+ status: {
6007
6161
  type: "string",
6008
- description: "Event contract instrument ID"
6162
+ enum: ["open", "history", "archive"],
6163
+ description: "open=active, history=7d (default), archive=3mo"
6009
6164
  },
6010
- state: {
6165
+ instId: {
6011
6166
  type: "string",
6012
- description: "live=pending orders; omit for history"
6167
+ description: "Event contract instrument ID"
6013
6168
  },
6014
- limit: {
6015
- type: "number",
6016
- description: "Max results (default 20)"
6017
- }
6169
+ ordType: { type: "string", description: "Order type filter" },
6170
+ state: { type: "string", description: "canceled|filled (only for history/archive)" },
6171
+ ...CURSOR_PROPS,
6172
+ ...TIME_RANGE_PROPS,
6173
+ limit: { type: "number", description: "Max results (default 100)" }
6018
6174
  }
6019
6175
  },
6020
6176
  handler: async (rawArgs, context) => {
6021
6177
  const args = asRecord(rawArgs);
6022
- const state = readString(args, "state");
6023
- const isPending = state === "live";
6024
- const endpoint = isPending ? "/api/v5/trade/orders-pending" : "/api/v5/trade/orders-history";
6178
+ const status = readString(args, "status") ?? "history";
6179
+ const endpointMap = {
6180
+ open: "/api/v5/trade/orders-pending",
6181
+ archive: "/api/v5/trade/orders-history-archive"
6182
+ };
6183
+ const endpoint = endpointMap[status] ?? "/api/v5/trade/orders-history";
6184
+ const params = compactObject({
6185
+ instType: "EVENTS",
6186
+ instId: readString(args, "instId"),
6187
+ ordType: readString(args, "ordType"),
6188
+ state: readString(args, "state"),
6189
+ ...readPaginationParams(args, readString, readNumber)
6190
+ });
6025
6191
  const response = await context.client.privateGet(
6026
6192
  endpoint,
6027
- compactObject({
6028
- instType: "EVENTS",
6029
- instId: readString(args, "instId"),
6030
- limit: readNumber(args, "limit")
6031
- }),
6193
+ params,
6032
6194
  privateRateLimit("event_get_orders", 20)
6033
6195
  );
6034
6196
  const base = normalizeResponse(response);
@@ -6042,41 +6204,51 @@ function registerEventContractTools() {
6042
6204
  stateLabel: mapOrderState(String(item["state"] ?? ""))
6043
6205
  };
6044
6206
  }) : base["data"];
6045
- return { ...base, data };
6207
+ return { ...base, data, requestParams: params };
6046
6208
  }
6047
6209
  },
6048
6210
  {
6049
6211
  name: "event_get_fills",
6050
6212
  module: "event",
6051
- description: "Get event contract fill history. outcome pre-translated (YES/NO/UP/DOWN). Each record includes a 'type' field: 'fill' (subType 410, opening trade) or 'settlement' (subType 414 win / subType 415 loss, contract expiry payout). Settlement records include 'settlementResult' (win/loss) and 'pnl' fields \u2014 no separate market lookup needed to determine outcome.",
6213
+ description: "Get event contract fill history (trade executions and settlement payouts). archive=true for up to 3mo, false (default) for last 3d. outcome pre-translated (YES/NO/UP/DOWN). Each record includes a 'type' field: 'fill' (opening trade) or 'settlement' (expiry payout with settlementResult win/loss and pnl). Do NOT use for order status \u2014 use event_get_orders instead.",
6052
6214
  isWrite: false,
6053
6215
  inputSchema: {
6054
6216
  type: "object",
6055
6217
  properties: {
6218
+ archive: {
6219
+ type: "boolean",
6220
+ description: "true=up to 3mo, false=3d (default)"
6221
+ },
6056
6222
  instId: {
6057
6223
  type: "string",
6058
6224
  description: "Event contract instrument ID"
6059
6225
  },
6060
- limit: {
6061
- type: "number",
6062
- description: "Max results (default 20)"
6063
- }
6226
+ ordId: { type: "string", description: "Order ID filter" },
6227
+ ...CURSOR_PROPS,
6228
+ ...TIME_RANGE_PROPS,
6229
+ limit: { type: "number", description: "Max results (default 100 or 20 for archive)" }
6064
6230
  }
6065
6231
  },
6066
6232
  handler: async (rawArgs, context) => {
6067
6233
  const args = asRecord(rawArgs);
6234
+ const archive = readBoolean(args, "archive") ?? false;
6235
+ const path4 = archive ? "/api/v5/trade/fills-history" : "/api/v5/trade/fills";
6236
+ const paging = readPaginationParams(args, readString, readNumber);
6237
+ const params = compactObject({
6238
+ instType: "EVENTS",
6239
+ instId: readString(args, "instId"),
6240
+ ordId: readString(args, "ordId"),
6241
+ ...paging,
6242
+ limit: paging.limit ?? (archive ? 20 : void 0)
6243
+ });
6068
6244
  const response = await context.client.privateGet(
6069
- "/api/v5/trade/fills",
6070
- compactObject({
6071
- instType: "EVENTS",
6072
- instId: readString(args, "instId"),
6073
- limit: readNumber(args, "limit")
6074
- }),
6245
+ path4,
6246
+ params,
6075
6247
  privateRateLimit("event_get_fills", 20)
6076
6248
  );
6077
6249
  const base = normalizeResponse(response);
6078
6250
  const data = Array.isArray(base["data"]) ? base["data"].map(enrichFill) : base["data"];
6079
- return { ...base, data };
6251
+ return { ...base, data, requestParams: params };
6080
6252
  }
6081
6253
  },
6082
6254
  // -----------------------------------------------------------------------
@@ -6160,7 +6332,7 @@ function registerEventContractTools() {
6160
6332
  {
6161
6333
  name: "event_amend_order",
6162
6334
  module: "event",
6163
- description: "Amend a pending event contract order (change price or size). [CAUTION] Modifies a real order. Before amending, call event_get_orders(state=live) to obtain the ordId and confirm the order is still pending. Only limit/post_only orders can be amended.",
6335
+ description: "Amend a pending event contract order (change price or size). [CAUTION] Modifies a real order. Before amending, call event_get_orders(status=open) to obtain the ordId and confirm the order is still pending. Only limit/post_only orders can be amended.",
6164
6336
  isWrite: true,
6165
6337
  inputSchema: {
6166
6338
  type: "object",
@@ -6191,7 +6363,7 @@ function registerEventContractTools() {
6191
6363
  {
6192
6364
  name: "event_cancel_order",
6193
6365
  module: "event",
6194
- description: "Cancel a pending event contract order. [CAUTION] Cancels a real order. Before cancelling, call event_get_orders(state=live) to obtain the ordId and confirm the order is still pending. instId must be the full event contract instrument ID (e.g. BTC-ABOVE-DAILY-260224-1600-69700), NOT a spot trading pair.",
6366
+ description: "Cancel a pending event contract order. [CAUTION] Cancels a real order. Before cancelling, call event_get_orders(status=open) to obtain the ordId and confirm the order is still pending. instId must be the full event contract instrument ID (e.g. BTC-ABOVE-DAILY-260224-1600-69700), NOT a spot trading pair.",
6195
6367
  isWrite: true,
6196
6368
  inputSchema: {
6197
6369
  type: "object",
@@ -6239,27 +6411,27 @@ var PATH_SIGNAL_HISTORY = "/api/v5/journal/smartmoney/signal-history";
6239
6411
  var SIGNAL_POOL_FILTER_PROPS = {
6240
6412
  sortType: {
6241
6413
  type: "string",
6242
- description: "pnl or pnlRatio"
6414
+ description: "Pool ranking: pnl|pnlRatio (default pnl)"
6243
6415
  },
6244
6416
  period: {
6245
6417
  type: "string",
6246
- description: "3|7|30|90 days"
6418
+ description: "Win-rate window days: 3|7|30|90 (default 90). Not snapshot range."
6247
6419
  },
6248
6420
  pnl: {
6249
6421
  type: "string",
6250
- description: "PNL_ANY|PNL_TOP50|PNL_TOP20|PNL_TOP5"
6422
+ description: "Top N% by PnL: PNL_ANY|PNL_TOP50|PNL_TOP20|PNL_TOP5 (default PNL_ANY)"
6251
6423
  },
6252
6424
  winRatio: {
6253
6425
  type: "string",
6254
- description: "WR_ANY|WR_GE_50|WR_GE_80"
6426
+ description: "Min win-rate: WR_ANY|WR_GE_50|WR_GE_80 (default WR_ANY)"
6255
6427
  },
6256
6428
  maxRetreat: {
6257
6429
  type: "string",
6258
- description: "MR_ANY|MR_LE_20|MR_LE_50"
6430
+ description: "Max drawdown: MR_ANY|MR_LE_20|MR_LE_50 (default MR_ANY)"
6259
6431
  },
6260
6432
  asset: {
6261
6433
  type: "string",
6262
- description: "AUM_ANY|AUM_TOP50|AUM_TOP20|AUM_TOP5"
6434
+ description: "Top N% by AUM: AUM_ANY|AUM_TOP50|AUM_TOP20|AUM_TOP5 (default AUM_ANY)"
6263
6435
  }
6264
6436
  };
6265
6437
  var LEADERBOARD_POOL_FILTER_PROPS = {
@@ -6311,39 +6483,39 @@ function registerSmartmoneyTools() {
6311
6483
  {
6312
6484
  name: "smartmoney_get_overview",
6313
6485
  module: "smartmoney",
6314
- description: "Multi-currency smart money overview ranked by most-watched currencies. Pass ts=Date.now() for latest data, or dataVersion (yyyyMMddHHmm) from a prior call. For single-currency signal with entry prices and trend, use smartmoney_get_signal.",
6486
+ description: "Multi-currency smart money overview, ranked by tradersWithPosition DESC (most-watched first). Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For single-currency signal with entry prices and trend, use smartmoney_get_signal.",
6315
6487
  isWrite: false,
6316
6488
  inputSchema: {
6317
6489
  type: "object",
6318
6490
  properties: {
6319
- dataVersion: {
6491
+ ts: {
6320
6492
  type: "string",
6321
- description: "yyyyMMddHHmm UTC (or use ts)"
6493
+ description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
6322
6494
  },
6323
- ts: {
6495
+ dataVersion: {
6324
6496
  type: "string",
6325
- description: "Timestamp ms (or use dataVersion)"
6497
+ description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
6326
6498
  },
6327
6499
  instType: {
6328
6500
  type: "string",
6329
- description: "SPOT|MARGIN|FUTURES|SWAP|OPTION"
6501
+ description: "SPOT|MARGIN|FUTURES|SWAP|OPTION (default SWAP)"
6330
6502
  },
6331
6503
  ...SIGNAL_POOL_FILTER_PROPS,
6332
6504
  lmtNum: {
6333
6505
  type: "string",
6334
- description: "Trader pool size 1-500"
6506
+ description: "Trader pool size 1-500 (default 100)"
6335
6507
  },
6336
6508
  instCcyList: {
6337
6509
  type: "string",
6338
- description: "Comma-separated e.g. BTC,ETH,SOL"
6510
+ description: "Comma-separated currency codes e.g. BTC,ETH,SOL (prefix-matched against instId)"
6339
6511
  },
6340
6512
  instCcy: {
6341
6513
  type: "string",
6342
- description: "Single currency e.g. BTC"
6514
+ description: "Single currency e.g. BTC; alias for instCcyList (instCcyList wins if both set)"
6343
6515
  },
6344
6516
  topInstruments: {
6345
6517
  type: "string",
6346
- description: "Top N instruments 1-100"
6518
+ description: "Top N instruments 1-100 (default 20)"
6347
6519
  }
6348
6520
  }
6349
6521
  },
@@ -6375,7 +6547,7 @@ function registerSmartmoneyTools() {
6375
6547
  {
6376
6548
  name: "smartmoney_get_signal",
6377
6549
  module: "smartmoney",
6378
- description: "Single-currency consensus signal: long/short ratio, entry prices, trend, capital flow. Prefer instId (e.g. BTC-USDT-SWAP); instCcy is accepted but may return empty \u2014 use instId for reliable results. Pass ts=Date.now() for latest data, or dataVersion from a prior call. For multi-currency overview, use smartmoney_get_overview. For timeline, use smartmoney_get_signal_history.",
6550
+ description: "Single-currency consensus signal: long/short ratio, entry prices, trend, capital flow. Requires either instId (e.g. BTC-USDT-SWAP, recommended) or instCcy (SPOT/SWAP only) \u2014 instId wins when both are sent. Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For multi-currency overview, use smartmoney_get_overview. For timeline, use smartmoney_get_signal_history.",
6379
6551
  isWrite: false,
6380
6552
  inputSchema: {
6381
6553
  type: "object",
@@ -6386,24 +6558,24 @@ function registerSmartmoneyTools() {
6386
6558
  },
6387
6559
  instCcy: {
6388
6560
  type: "string",
6389
- description: "e.g. BTC, SPOT/SWAP only. May return empty \u2014 prefer instId."
6561
+ description: "e.g. BTC (SPOT/SWAP only); instId takes precedence if both set"
6390
6562
  },
6391
- dataVersion: {
6563
+ ts: {
6392
6564
  type: "string",
6393
- description: "yyyyMMddHHmm UTC (or use ts)"
6565
+ description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
6394
6566
  },
6395
- ts: {
6567
+ dataVersion: {
6396
6568
  type: "string",
6397
- description: "Timestamp ms (or use dataVersion)"
6569
+ description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
6398
6570
  },
6399
6571
  ...SIGNAL_POOL_FILTER_PROPS,
6400
6572
  lmtNum: {
6401
6573
  type: "string",
6402
- description: "Trader pool size 1-500"
6574
+ description: "Trader pool size 1-500 (default 100)"
6403
6575
  },
6404
6576
  authorIds: {
6405
6577
  type: "string",
6406
- description: "Comma-separated user IDs e.g. 1001,1002"
6578
+ description: "Comma-separated user IDs e.g. 1001,1002 \u2014 restricts the trader pool to these IDs only (precise filter)"
6407
6579
  }
6408
6580
  }
6409
6581
  },
@@ -6439,7 +6611,7 @@ function registerSmartmoneyTools() {
6439
6611
  {
6440
6612
  name: "smartmoney_get_signal_history",
6441
6613
  module: "smartmoney",
6442
- description: "Signal history timeline sorted by ts DESC for trend analysis. Requires instId. Pass ts=Date.now() for latest data, or dataVersion from a prior call. For current snapshot, use smartmoney_get_signal.",
6614
+ description: "Signal history timeline sorted by ts DESC for trend analysis. Requires instId. Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For current snapshot, use smartmoney_get_signal.",
6443
6615
  isWrite: false,
6444
6616
  inputSchema: {
6445
6617
  type: "object",
@@ -6448,13 +6620,13 @@ function registerSmartmoneyTools() {
6448
6620
  type: "string",
6449
6621
  description: "e.g. BTC-USDT-SWAP"
6450
6622
  },
6451
- dataVersion: {
6623
+ ts: {
6452
6624
  type: "string",
6453
- description: "yyyyMMddHHmm UTC (or use ts)"
6625
+ description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
6454
6626
  },
6455
- ts: {
6627
+ dataVersion: {
6456
6628
  type: "string",
6457
- description: "Timestamp ms (or use dataVersion)"
6629
+ description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
6458
6630
  },
6459
6631
  granularity: {
6460
6632
  type: "string",
@@ -9967,7 +10139,7 @@ function toMcpTool(tool) {
9967
10139
  };
9968
10140
  }
9969
10141
  function configFilePath() {
9970
- return join6(homedir4(), ".okx", "config.toml");
10142
+ return join7(homedir5(), ".okx", "config.toml");
9971
10143
  }
9972
10144
  function readFullConfig() {
9973
10145
  const path4 = configFilePath();
@@ -9986,11 +10158,6 @@ Or re-run: okx config init`
9986
10158
  );
9987
10159
  }
9988
10160
  }
9989
- function readTomlProfile(profileName) {
9990
- const config = readFullConfig();
9991
- const name = profileName ?? config.default_profile ?? "default";
9992
- return config.profiles?.[name] ?? {};
9993
- }
9994
10161
  function expandShorthand(moduleId) {
9995
10162
  if (moduleId === "all") return [...MODULES];
9996
10163
  if (moduleId === "earn" || moduleId === "earn.all") return [...EARN_SUB_MODULE_IDS];
@@ -10024,18 +10191,24 @@ function parseModuleList(rawModules) {
10024
10191
  }
10025
10192
  return Array.from(deduped);
10026
10193
  }
10027
- function loadCredentials(toml) {
10194
+ async function loadCredentials(toml) {
10028
10195
  const apiKey = process.env.OKX_API_KEY?.trim() ?? toml.api_key;
10029
10196
  const secretKey = process.env.OKX_SECRET_KEY?.trim() ?? toml.secret_key;
10030
10197
  const passphrase = process.env.OKX_PASSPHRASE?.trim() ?? toml.passphrase;
10031
- const hasAuth = Boolean(apiKey && secretKey && passphrase);
10198
+ const hasApiKey = Boolean(apiKey && secretKey && passphrase);
10032
10199
  const partialAuth = Boolean(apiKey) || Boolean(secretKey) || Boolean(passphrase);
10033
- if (partialAuth && !hasAuth) {
10200
+ if (partialAuth && !hasApiKey) {
10034
10201
  throw new ConfigError(
10035
10202
  "Partial API credentials detected.",
10036
10203
  "Set OKX_API_KEY, OKX_SECRET_KEY and OKX_PASSPHRASE together (env vars or config.toml profile)."
10037
10204
  );
10038
10205
  }
10206
+ let hasOAuth = false;
10207
+ if (!hasApiKey) {
10208
+ const status = await execAuthStatus();
10209
+ hasOAuth = status?.status === "logged_in";
10210
+ }
10211
+ const hasAuth = hasOAuth || hasApiKey;
10039
10212
  return { apiKey, secretKey, passphrase, hasAuth };
10040
10213
  }
10041
10214
  function resolveSite(cliSite, tomlSite) {
@@ -10069,9 +10242,11 @@ function resolveDemo(cli, toml) {
10069
10242
  if (cli.demo === true) return true;
10070
10243
  return process.env.OKX_DEMO === "1" || process.env.OKX_DEMO === "true" || (toml.demo ?? false);
10071
10244
  }
10072
- function loadConfig(cli) {
10073
- const toml = readTomlProfile(cli.profile);
10074
- const creds = loadCredentials(toml);
10245
+ async function loadConfig(cli) {
10246
+ const config = readFullConfig();
10247
+ const profileName = cli.profile ?? config.default_profile ?? "default";
10248
+ const toml = config.profiles?.[profileName] ?? {};
10249
+ const creds = await loadCredentials(toml);
10075
10250
  const demo = resolveDemo(cli, toml);
10076
10251
  const site = resolveSite(cli.site, toml.site);
10077
10252
  const baseUrl = resolveBaseUrl(site, toml.base_url);
@@ -10091,6 +10266,7 @@ function loadConfig(cli) {
10091
10266
  }
10092
10267
  return {
10093
10268
  ...creds,
10269
+ profile: profileName,
10094
10270
  baseUrl,
10095
10271
  timeoutMs: Math.floor(rawTimeout),
10096
10272
  modules: parseModuleList(cli.modules),
@@ -10103,7 +10279,7 @@ function loadConfig(cli) {
10103
10279
  verbose: cli.verbose ?? false
10104
10280
  };
10105
10281
  }
10106
- var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
10282
+ var CACHE_FILE = join8(homedir6(), ".okx", "update-check.json");
10107
10283
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
10108
10284
  function readCache2() {
10109
10285
  try {
@@ -10116,7 +10292,7 @@ function readCache2() {
10116
10292
  }
10117
10293
  function writeCache2(cache) {
10118
10294
  try {
10119
- mkdirSync6(join7(homedir5(), ".okx"), { recursive: true });
10295
+ mkdirSync6(join8(homedir6(), ".okx"), { recursive: true });
10120
10296
  writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
10121
10297
  } catch {
10122
10298
  }
@@ -10270,13 +10446,13 @@ function findMsStoreClaudePath() {
10270
10446
  }
10271
10447
  function getConfigPath(client) {
10272
10448
  const home = os3.homedir();
10273
- const platform2 = process.platform;
10449
+ const platform3 = process.platform;
10274
10450
  switch (client) {
10275
10451
  case "claude-desktop":
10276
- if (platform2 === "win32") {
10452
+ if (platform3 === "win32") {
10277
10453
  return findMsStoreClaudePath() ?? path3.join(appData(), "Claude", CLAUDE_CONFIG_FILE);
10278
10454
  }
10279
- if (platform2 === "darwin") {
10455
+ if (platform3 === "darwin") {
10280
10456
  return path3.join(home, "Library", "Application Support", "Claude", CLAUDE_CONFIG_FILE);
10281
10457
  }
10282
10458
  return path3.join(process.env.XDG_CONFIG_HOME ?? path3.join(home, ".config"), "Claude", CLAUDE_CONFIG_FILE);
@@ -10378,6 +10554,8 @@ function runSetup(options) {
10378
10554
  `);
10379
10555
  }
10380
10556
  }
10557
+ var CACHE_PATH = join12(homedir10(), ".okx", "auth-binary-check.json");
10558
+ var CHECK_INTERVAL_MS2 = 2 * 60 * 60 * 1e3;
10381
10559
 
10382
10560
  // src/constants.ts
10383
10561
  import { createRequire } from "module";
@@ -10385,7 +10563,7 @@ var _require = createRequire(import.meta.url);
10385
10563
  var pkg = _require("../package.json");
10386
10564
  var SERVER_NAME = "okx-trade-mcp";
10387
10565
  var SERVER_VERSION = pkg.version;
10388
- var GIT_HASH = true ? "e0ee5a96" : "dev";
10566
+ var GIT_HASH = true ? "a4277b3" : "dev";
10389
10567
 
10390
10568
  // src/server.ts
10391
10569
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -10662,7 +10840,7 @@ async function main() {
10662
10840
  `);
10663
10841
  return;
10664
10842
  }
10665
- const config = loadConfig({
10843
+ const config = await loadConfig({
10666
10844
  modules: cli.modules,
10667
10845
  profile: cli.profile,
10668
10846
  site: cli.site,