@aihubspot/agent-trade-mcp 0.1.11 → 0.1.13

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.
Files changed (3) hide show
  1. package/README.md +18 -8
  2. package/dist/index.js +381 -227
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AI Hub Agent Trade MCP
2
2
 
3
- Install with `npm install -g @aihubspot/agent-trade-mcp`, then start `ai-hub-trade-mcp --profile default`.
3
+ Install with `npm install -g @aihubspot/agent-trade-mcp`, then start `ai-hub-trade-mcp --profile default`. The default Toolset exposes the focused market, spot-order, universal-transfer, and wallet-query workflows. Use `--toolset full` to expose every supported Core capability. Read responses use compact text by default; use `--response-mode compat` only for clients that require the legacy duplicated JSON text.
4
4
 
5
5
  This package provides a local stdio MCP server. State-changing operations require a preview and a new explicit user confirmation.
6
6
 
@@ -15,9 +15,19 @@ ai-hub-trade-mcp setup --client cursor --profile default
15
15
  ai-hub-trade-mcp setup --client claude-desktop --profile default
16
16
  ai-hub-trade-mcp setup --client claude-code --profile default
17
17
  ai-hub-trade-mcp setup --client codex --profile default
18
+ ai-hub-trade-mcp setup --client openclaw --profile default
19
+ ai-hub-trade-mcp setup --client codex --profile default --toolset full
20
+ ai-hub-trade-mcp setup --client codex --profile default --response-mode compat
18
21
  ```
19
22
 
20
- Setup registers the currently installed MCP binary through its absolute Node runtime and entrypoint path, so desktop clients do not depend on a global PATH or an unpublished `npx` package. Cursor and Claude Desktop configurations are merged with existing MCP servers through their JSON configurations. Claude Code and Codex are registered through their official CLIs; Claude Code uses user scope. Existing JSON configurations are validated, atomically updated, and backed up before their first modification. The setup command stores no API credentials; the MCP server continues to read its profile from `~/.ai-hub/config.toml`.
23
+ Setup registers the currently installed MCP binary through its absolute Node runtime and entrypoint path, so desktop clients do not depend on a global PATH or an unpublished `npx` package. Cursor and Claude Desktop configurations are merged with existing MCP servers through their JSON configurations. Claude Code, Codex, and OpenClaw are registered through their official CLIs; Claude Code uses user scope. Existing JSON configurations are validated, atomically updated, and backed up before their first modification. The setup command stores no API credentials; the MCP server continues to read its profile from `~/.ai-hub/config.toml`.
24
+
25
+ For OpenClaw, run setup on the machine that runs the OpenClaw Gateway and as the same operating-system user as that Gateway. The selected AI Hub profile must exist in that user's `~/.ai-hub/config.toml`. Verify the registration after setup:
26
+
27
+ ```bash
28
+ openclaw mcp doctor ai-hub-trade-mcp-default --probe
29
+ openclaw mcp reload
30
+ ```
21
31
 
22
32
  ## Spot market-order units
23
33
 
@@ -33,7 +43,7 @@ All read tools return `{ "ok": true, "data": ... }` in one of these stable forms
33
43
  - Objects: `{ "dataType": "object", "value": { ... } }`
34
44
  - Scalars: `{ "dataType": "scalar", "value": "..." }`
35
45
 
36
- MCP clients that support the current protocol also receive the same envelope in `structuredContent`, validated by each read tool's output schema. Check `data.dataType` before formatting; do not infer raw OpenAPI nesting.
46
+ MCP clients receive the full envelope in `structuredContent`, validated by each read tool's output schema. In the default `compact` mode, text contains only the response type plus safe list metadata (`count`, `returnedCount`, `totalCount`, `nextOffset`, or `truncated` when applicable), so the same JSON is not duplicated in the Agent context. Use `--response-mode compat` to also place the full envelope in text. Check `data.dataType` before formatting; do not infer raw OpenAPI nesting.
37
47
 
38
48
  ## Bounded market analysis
39
49
 
@@ -41,11 +51,11 @@ Use these tools for requests that would otherwise return a broad market payload:
41
51
 
42
52
  - `market_get_symbol_overview`: counts by quote asset and a small sample. Use this first for a generic request to list trading pairs.
43
53
  - `market_list_symbols`: a paged, optionally quote-asset-filtered list without order-rule metadata.
44
- - `market_search_symbols`: required-keyword lookup with at most 20 basic matching rows. Do not use it for a generic pair list.
54
+ - `market_search_symbols`: required-keyword lookup with at most 50 basic matching rows. Do not use it for a generic pair list.
45
55
  - `market_get_symbol_info`: exact precision and minimum order rules for one symbol only.
46
- - `market_get_ticker_summary`: watchlist, gainers, losers, and quote-volume leaders.
47
- - `market_get_depth_summary`: best bid/ask, spread, and up to 20 levels per side.
56
+ - `market_get_ticker_summary`: watchlist, gainers, losers, and quote-volume leaders under one total result budget (20 by default, 50 maximum).
57
+ - `market_get_depth_summary`: best bid/ask, spread, and up to 50 levels per side.
48
58
  - `market_get_trades_summary`: price range, buy/sell statistics, and up to 50 recent trades.
49
- - `market_get_klines_summary`: period change, high/low, latest candle, and up to 100 candles. It defaults to `60min`; formal intervals are `1min`, `5min`, `15min`, `30min`, `60min`, `1day`, `1week`, and `1month`.
59
+ - `market_get_klines_summary`: period change, high/low, latest candle, and up to 50 candles. It defaults to `60min`; formal intervals are `1min`, `5min`, `15min`, `30min`, `60min`, `1day`, `1week`, and `1month`.
50
60
 
51
- The complete raw symbols endpoint is intentionally CLI-only because its payload is too large for Agent contexts. Raw ticker, depth, trades, and kline tools remain available only when an exact payload is explicitly needed. Raw trades are capped at 100 rows; raw klines support the upstream maximum of 300 rows.
61
+ The default Toolset exposes only these bounded forms. The `full` Toolset additionally exposes raw symbols, depth, trades, and kline responses for explicit advanced use. Every list is limited to 50 rows; raw symbols are paged with `offset` and `nextOffset`.
package/dist/index.js CHANGED
@@ -451,7 +451,7 @@ async function normalizeOpenApiBaseUrl(value) {
451
451
  throw new AiHubError("AI_HUB_UNSAFE_OPENAPI_URL", "Localhost is not allowed as an OpenAPI base URL.");
452
452
  }
453
453
  const records2 = isIP(url.hostname) ? [{ address: url.hostname }] : await lookup(url.hostname, { all: true, verbatim: true });
454
- if (records2.length === 0 || records2.some((record3) => blockedIp(record3.address))) {
454
+ if (records2.length === 0 || records2.some((record4) => blockedIp(record4.address))) {
455
455
  throw new AiHubError("AI_HUB_UNSAFE_OPENAPI_URL", "OpenAPI base URL must resolve only to public addresses.");
456
456
  }
457
457
  url.pathname = url.pathname.replace(/\/+$/, "");
@@ -635,11 +635,11 @@ function strictObject(input, allowedKeys) {
635
635
  if (!input || typeof input !== "object" || Array.isArray(input)) {
636
636
  throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "Tool input must be an object.");
637
637
  }
638
- const record3 = input;
639
- for (const key of Object.keys(record3)) {
638
+ const record4 = input;
639
+ for (const key of Object.keys(record4)) {
640
640
  if (!allowedKeys.includes(key)) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", `Unknown argument "${key}".`);
641
641
  }
642
- return record3;
642
+ return record4;
643
643
  }
644
644
  function requiredString(input, name) {
645
645
  const value = input[name];
@@ -663,23 +663,6 @@ function optionalInteger(input, name, fallback, minimum, maximum) {
663
663
 
664
664
  // ../core/src/tools/account-tools.ts
665
665
  var accountTools = [
666
- {
667
- name: "spot_get_account",
668
- title: "Get Spot Account",
669
- description: "Get the signed account overview for the configured profile.",
670
- cliPath: ["account", "get"],
671
- module: "spot-account",
672
- access: "signed",
673
- operation: "read",
674
- riskLevel: "low",
675
- inputSchema: { type: "object", additionalProperties: false },
676
- errorCodes: ["AI_HUB_CREDENTIAL_NOT_CONFIGURED", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE", "AI_HUB_OPENAPI_BUSINESS_ERROR"],
677
- validate: (input) => strictObject(input, []),
678
- handler: async (_input, context) => {
679
- if (!context.credentials) throw new AiHubError("AI_HUB_CREDENTIAL_NOT_CONFIGURED", `Credentials are not configured for profile "${context.profile.name}".`);
680
- return context.api.account(context.credentials);
681
- }
682
- },
683
666
  {
684
667
  name: "account_get_asset_balance",
685
668
  title: "Get Asset Balance",
@@ -740,6 +723,95 @@ function optionalClientOrderId(input) {
740
723
  return optionalString(input, "newClientOrderId") ?? `agent_${randomUUID2().replaceAll("-", "").slice(0, 24)}`;
741
724
  }
742
725
 
726
+ // ../core/src/tools/list-limit.ts
727
+ var STANDARD_LIST_LIMIT = {
728
+ field: "limit",
729
+ defaultValue: 20,
730
+ maximum: 50
731
+ };
732
+ var STANDARD_PAGE_SIZE = {
733
+ field: "pageSize",
734
+ defaultValue: 20,
735
+ maximum: 50
736
+ };
737
+ function listLimitSchema(limit, description) {
738
+ return {
739
+ type: "integer",
740
+ minimum: 1,
741
+ maximum: limit.maximum,
742
+ description: description ?? `Defaults to ${limit.defaultValue}; maximum ${limit.maximum}.`
743
+ };
744
+ }
745
+ function optionalListLimit(input, limit) {
746
+ return optionalInteger(input, limit.field, limit.defaultValue, 1, limit.maximum);
747
+ }
748
+ function normalizeListLimitInput(input, limit) {
749
+ const value = record2(input);
750
+ return value ? { ...value, [limit.field]: optionalListLimit(value, limit) } : input;
751
+ }
752
+ function normalizedListLimit(input, limit) {
753
+ const value = input[limit.field];
754
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > limit.maximum) {
755
+ throw new AiHubError("AI_HUB_INVALID_ARGUMENT", `${limit.field} must be an integer between 1 and ${limit.maximum}.`);
756
+ }
757
+ return value;
758
+ }
759
+ function withListLimit(tool) {
760
+ if (!tool.listLimit) return tool;
761
+ const limit = tool.listLimit;
762
+ return {
763
+ ...tool,
764
+ inputSchema: {
765
+ ...tool.inputSchema,
766
+ properties: {
767
+ ...tool.inputSchema.properties,
768
+ [limit.field]: listLimitSchema(limit, tool.inputSchema.properties?.[limit.field]?.description)
769
+ }
770
+ },
771
+ validate: (input) => tool.validate(normalizeListLimitInput(input, limit))
772
+ };
773
+ }
774
+ function record2(value) {
775
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
776
+ }
777
+ function truncateUnpagedListResponse(response, definition, input, maximum = STANDARD_LIST_LIMIT.maximum) {
778
+ const root = record2(response);
779
+ if (!root || definition.path.length === 0) return response;
780
+ const ancestors = [root];
781
+ let current = root;
782
+ for (const key of definition.path) {
783
+ const next = record2(current)?.[key];
784
+ if (key === definition.path[definition.path.length - 1]) {
785
+ if (!Array.isArray(next) || next.length <= maximum) return response;
786
+ const parent = record2(current);
787
+ if (!parent) return response;
788
+ const currentPage = definition.pageField ? input[definition.pageField] : void 0;
789
+ const nextPage = typeof currentPage === "number" ? currentPage + 1 : null;
790
+ const limitedParent = {
791
+ ...parent,
792
+ [key]: next.slice(0, maximum),
793
+ returnedCount: maximum,
794
+ totalCount: next.length,
795
+ truncated: true,
796
+ continuation: definition.pageField ? { available: true, [definition.pageField]: nextPage } : { available: false, reason: "The upstream endpoint does not expose a continuation parameter." }
797
+ };
798
+ let replacement = limitedParent;
799
+ for (let index = ancestors.length - 2; index >= 0; index -= 1) {
800
+ const ancestor = ancestors[index];
801
+ const childKey = definition.path[index];
802
+ if (!ancestor || !childKey) return response;
803
+ replacement = { ...ancestor, [childKey]: replacement };
804
+ }
805
+ return replacement;
806
+ }
807
+ const nextRecord = record2(next);
808
+ if (!nextRecord) return response;
809
+ ancestors.push(nextRecord);
810
+ current = nextRecord;
811
+ }
812
+ return response;
813
+ }
814
+
743
815
  // ../core/src/tools/asset-tools.ts
744
816
  var ACCOUNT_TYPES = ["1", "2", "3", "4", "5"];
745
817
  var ACCOUNT_TYPE_NAMES = {
@@ -760,7 +832,6 @@ var accountTypeSchema = {
760
832
  ],
761
833
  description: "Account type: 1=Spot, 2=Isolated Margin (requires symbol), 3=Cross Margin, 4=C2C, 5=Derivatives. Do not use 2 for Derivatives."
762
834
  };
763
- var spotDerivativeAccounts = ["EXCHANGE", "FUTURE"];
764
835
  function accountType(value, name) {
765
836
  const result2 = requiredString(value, name);
766
837
  if (!ACCOUNT_TYPES.includes(result2)) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", `${name} must be an account type: 1=Spot, 2=Isolated Margin, 3=Cross Margin, 4=C2C, or 5=Derivatives.`);
@@ -791,27 +862,7 @@ function transferQuery(input) {
791
862
  if (isIsolatedMarginTransfer(fromAccountType, toAccountType) && !symbol) {
792
863
  throw new AiHubError("AI_HUB_ISOLATED_MARGIN_SYMBOL_REQUIRED", "symbol is required when fromAccountType or toAccountType is 2 (Isolated Margin). Provide the isolated-margin trading pair, for example ETHUSDT.");
793
864
  }
794
- return { fromAccountType, toAccountType, page: optionalPositiveInteger(value, "page", 1), pageSize: optionalPositiveInteger(value, "pageSize", 20, 100), ...symbol ? { symbol } : {}, ...optionalString(value, "coinSymbol") ? { coinSymbol: optionalString(value, "coinSymbol") } : {} };
795
- }
796
- function transferAccounts(input) {
797
- const value = strictObject(input, ["coinSymbol", "amount", "fromAccount", "toAccount"]);
798
- const fromAccount = requiredString(value, "fromAccount").toUpperCase();
799
- const toAccount = requiredString(value, "toAccount").toUpperCase();
800
- if (!spotDerivativeAccounts.includes(fromAccount) || !spotDerivativeAccounts.includes(toAccount) || fromAccount === toAccount) {
801
- throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "account transfer supports only EXCHANGE -> FUTURE or FUTURE -> EXCHANGE. Use wallet transfer for isolated margin, cross margin, or C2C.");
802
- }
803
- return { coinSymbol: requiredString(value, "coinSymbol"), amount: positiveDecimal(value, "amount"), fromAccount, toAccount };
804
- }
805
- function transferAccountHistory(input) {
806
- const value = strictObject(input, ["transferId", "coinSymbol", "fromAccount", "toAccount", "startTime", "endTime", "page", "limit"]);
807
- const transferId = optionalString(value, "transferId");
808
- if (!transferId && (!optionalString(value, "fromAccount") || !optionalString(value, "toAccount"))) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "fromAccount and toAccount are required when transferId is omitted.");
809
- const fromAccount = optionalString(value, "fromAccount")?.toUpperCase();
810
- const toAccount = optionalString(value, "toAccount")?.toUpperCase();
811
- if (!transferId && (!spotDerivativeAccounts.includes(fromAccount) || !spotDerivativeAccounts.includes(toAccount) || fromAccount === toAccount)) {
812
- throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "account transfer history supports only EXCHANGE -> FUTURE or FUTURE -> EXCHANGE when transferId is omitted.");
813
- }
814
- return { ...transferId ? { transferId } : {}, ...optionalString(value, "coinSymbol") ? { coinSymbol: optionalString(value, "coinSymbol") } : {}, ...fromAccount ? { fromAccount } : {}, ...toAccount ? { toAccount } : {}, ...optionalString(value, "startTime") ? { startTime: optionalString(value, "startTime") } : {}, ...optionalString(value, "endTime") ? { endTime: optionalString(value, "endTime") } : {}, page: optionalPositiveInteger(value, "page", 1), limit: optionalPositiveInteger(value, "limit", 20, 100) };
865
+ return { fromAccountType, toAccountType, page: optionalPositiveInteger(value, "page", 1), pageSize: normalizedListLimit(value, STANDARD_PAGE_SIZE), ...symbol ? { symbol } : {}, ...optionalString(value, "coinSymbol") ? { coinSymbol: optionalString(value, "coinSymbol") } : {} };
815
866
  }
816
867
  function withAccountTypeMetadata(response, value) {
817
868
  const metadata = { accountType: value, accountTypeName: accountTypeName(value) };
@@ -820,49 +871,17 @@ function withAccountTypeMetadata(response, value) {
820
871
  }
821
872
  function pagedDates(input) {
822
873
  const value = strictObject(input, ["startTime", "endTime", "page", "pageSize"]);
823
- return { ...optionalString(value, "startTime") ? { startTime: optionalString(value, "startTime") } : {}, ...optionalString(value, "endTime") ? { endTime: optionalString(value, "endTime") } : {}, page: optionalPositiveInteger(value, "page", 1), pageSize: optionalPositiveInteger(value, "pageSize", 20, 100) };
874
+ return { ...optionalString(value, "startTime") ? { startTime: optionalString(value, "startTime") } : {}, ...optionalString(value, "endTime") ? { endTime: optionalString(value, "endTime") } : {}, page: optionalPositiveInteger(value, "page", 1), pageSize: normalizedListLimit(value, STANDARD_PAGE_SIZE) };
824
875
  }
825
876
  function coinOnly(input) {
826
877
  const value = strictObject(input, ["mainCoinSymbol"]);
827
878
  return { mainCoinSymbol: requiredString(value, "mainCoinSymbol") };
828
879
  }
829
880
  var assetTools = [
830
- {
831
- name: "account_transfer",
832
- title: "Transfer Between Spot and Derivatives",
833
- description: "Transfer only between Spot and Derivatives after confirmation. Use fromAccount=EXCHANGE and toAccount=FUTURE for Spot to Derivatives; use the reverse for Derivatives to Spot. Do not pass numeric account types to this Tool.",
834
- cliPath: ["account", "transfer"],
835
- module: "spot-account",
836
- access: "signed",
837
- operation: "write",
838
- riskLevel: "high",
839
- inputSchema: { type: "object", properties: { coinSymbol: { type: "string" }, amount: { type: "string" }, fromAccount: { type: "string", enum: spotDerivativeAccounts, description: "EXCHANGE means Spot; FUTURE means Derivatives." }, toAccount: { type: "string", enum: spotDerivativeAccounts, description: "EXCHANGE means Spot; FUTURE means Derivatives." } }, required: ["coinSymbol", "amount", "fromAccount", "toAccount"], additionalProperties: false },
840
- errorCodes: writeErrors,
841
- validate: transferAccounts,
842
- handler: (input, context) => context.api.signedPost("/sapi/v1/asset/transfer", input, signed(context)),
843
- writeSummary: (input) => {
844
- const value = input;
845
- return { action: "spot_derivatives_transfer", coinSymbol: value.coinSymbol, amount: value.amount, fromAccount: value.fromAccount, fromAccountName: value.fromAccount === "EXCHANGE" ? "Spot" : "Derivatives", toAccount: value.toAccount, toAccountName: value.toAccount === "EXCHANGE" ? "Spot" : "Derivatives" };
846
- }
847
- },
848
- {
849
- name: "account_get_transfer_history",
850
- title: "Get Account Transfer History",
851
- description: "Query documented account transfer history.",
852
- cliPath: ["account", "transfer-history"],
853
- module: "spot-account",
854
- access: "signed",
855
- operation: "read",
856
- riskLevel: "low",
857
- inputSchema: { type: "object", properties: { transferId: { type: "string" }, coinSymbol: { type: "string" }, fromAccount: { type: "string" }, toAccount: { type: "string" }, startTime: { type: "string" }, endTime: { type: "string" }, page: { type: "integer" }, limit: { type: "integer" } }, additionalProperties: false },
858
- errorCodes: signedReadErrors,
859
- validate: transferAccountHistory,
860
- handler: (input, context) => context.api.signedPost("/sapi/v1/asset/transferQuery", input, signed(context))
861
- },
862
881
  {
863
882
  name: "wallet_universal_transfer",
864
883
  title: "Universal Asset Transfer",
865
- description: "Transfer between numeric account types after confirmation: 1=Spot, 2=Isolated Margin, 3=Cross Margin, 4=C2C, 5=Derivatives. Account type 2 is not Derivatives and requires symbol. For a straightforward Spot to Derivatives transfer, prefer account_transfer with EXCHANGE -> FUTURE.",
884
+ description: "Transfer between numeric account types after confirmation: 1=Spot, 2=Isolated Margin, 3=Cross Margin, 4=C2C, 5=Derivatives. Account type 2 is not Derivatives and requires symbol.",
866
885
  cliPath: ["wallet", "transfer"],
867
886
  module: "spot-deposit-withdraw",
868
887
  access: "signed",
@@ -886,8 +905,9 @@ var assetTools = [
886
905
  access: "signed",
887
906
  operation: "read",
888
907
  riskLevel: "low",
889
- inputSchema: { type: "object", properties: { fromAccountType: accountTypeSchema, toAccountType: accountTypeSchema, symbol: { type: "string", description: "Isolated-margin trading pair when account type 2 is involved." }, coinSymbol: { type: "string" }, page: { type: "integer" }, pageSize: { type: "integer" } }, required: ["fromAccountType", "toAccountType"], additionalProperties: false },
908
+ inputSchema: { type: "object", properties: { fromAccountType: accountTypeSchema, toAccountType: accountTypeSchema, symbol: { type: "string", description: "Isolated-margin trading pair when account type 2 is involved." }, coinSymbol: { type: "string" }, page: { type: "integer", minimum: 1 }, pageSize: listLimitSchema(STANDARD_PAGE_SIZE) }, required: ["fromAccountType", "toAccountType"], additionalProperties: false },
890
909
  errorCodes: signedReadErrors,
910
+ listLimit: STANDARD_PAGE_SIZE,
891
911
  validate: transferQuery,
892
912
  handler: (input, context) => context.api.signedPost("/sapi/v1/asset/universal_transfer_query", input, signed(context))
893
913
  },
@@ -900,8 +920,9 @@ var assetTools = [
900
920
  access: "signed",
901
921
  operation: "read",
902
922
  riskLevel: "low",
903
- inputSchema: { type: "object", properties: { startTime: { type: "string" }, endTime: { type: "string" }, page: { type: "integer" }, pageSize: { type: "integer" } }, additionalProperties: false },
923
+ inputSchema: { type: "object", properties: { startTime: { type: "string" }, endTime: { type: "string" }, page: { type: "integer", minimum: 1 }, pageSize: listLimitSchema(STANDARD_PAGE_SIZE) }, additionalProperties: false },
904
924
  errorCodes: signedReadErrors,
925
+ listLimit: STANDARD_PAGE_SIZE,
905
926
  validate: pagedDates,
906
927
  handler: (input, context) => context.api.signedPost("/sapi/v1/deposit/his_list", input, signed(context))
907
928
  },
@@ -916,6 +937,7 @@ var assetTools = [
916
937
  riskLevel: "low",
917
938
  inputSchema: { type: "object", properties: { mainCoinSymbol: { type: "string" } }, required: ["mainCoinSymbol"], additionalProperties: false },
918
939
  errorCodes: signedReadErrors,
940
+ unpagedListLimit: { path: ["data", "depositAddrList"] },
919
941
  validate: coinOnly,
920
942
  handler: (input, context) => context.api.signedPost("/sapi/v1/deposit/query_address", input, signed(context))
921
943
  },
@@ -930,6 +952,7 @@ var assetTools = [
930
952
  riskLevel: "low",
931
953
  inputSchema: { type: "object", properties: { mainCoinSymbol: { type: "string" }, trustType: { type: "string" }, addrType: { type: "string" } }, required: ["mainCoinSymbol"], additionalProperties: false },
932
954
  errorCodes: signedReadErrors,
955
+ unpagedListLimit: { path: ["data", "addressList"] },
933
956
  validate: (input) => {
934
957
  const value = strictObject(input, ["mainCoinSymbol", "trustType", "addrType"]);
935
958
  return { mainCoinSymbol: requiredString(value, "mainCoinSymbol"), ...optionalString(value, "trustType") ? { trustType: optionalString(value, "trustType") } : {}, ...optionalString(value, "addrType") ? { addrType: optionalString(value, "addrType") } : {} };
@@ -947,6 +970,7 @@ var assetTools = [
947
970
  riskLevel: "low",
948
971
  inputSchema: { type: "object", properties: { accountType: accountTypeSchema }, required: ["accountType"], additionalProperties: false },
949
972
  errorCodes: signedReadErrors,
973
+ unpagedListLimit: { path: ["data", "accountList"] },
950
974
  validate: (input) => {
951
975
  const value = strictObject(input, ["accountType"]);
952
976
  return { accountType: accountType(value, "accountType") };
@@ -956,23 +980,6 @@ var assetTools = [
956
980
  return withAccountTypeMetadata(await context.api.signedPost("/sapi/v1/asset/account/by_type", value, signed(context)), value.accountType);
957
981
  }
958
982
  },
959
- {
960
- name: "wallet_get_exchange_account",
961
- title: "Get Exchange Account Assets",
962
- description: "Query exchange account assets.",
963
- cliPath: ["wallet", "exchange-account"],
964
- module: "spot-deposit-withdraw",
965
- access: "signed",
966
- operation: "read",
967
- riskLevel: "low",
968
- inputSchema: { type: "object", additionalProperties: false },
969
- errorCodes: signedReadErrors,
970
- validate: (input) => {
971
- strictObject(input, []);
972
- return {};
973
- },
974
- handler: (_input, context) => context.api.signedPost("/sapi/v1/asset/exchange/account", {}, signed(context))
975
- },
976
983
  {
977
984
  name: "wallet_create_withdraw",
978
985
  title: "Create Withdrawal",
@@ -1005,6 +1012,7 @@ var assetTools = [
1005
1012
  riskLevel: "low",
1006
1013
  inputSchema: { type: "object", properties: { withdrawId: { type: "string" }, withdrawOrderId: { type: "string" }, symbol: { type: "string" }, startTime: { type: "string" }, endTime: { type: "string" }, page: { type: "integer" } }, additionalProperties: false },
1007
1014
  errorCodes: signedReadErrors,
1015
+ unpagedListLimit: { path: ["data", "withdrawList"], pageField: "page" },
1008
1016
  validate: (input) => {
1009
1017
  const value = strictObject(input, ["withdrawId", "withdrawOrderId", "symbol", "startTime", "endTime", "page"]);
1010
1018
  return { ...optionalString(value, "withdrawId") ? { withdrawId: optionalString(value, "withdrawId") } : {}, ...optionalString(value, "withdrawOrderId") ? { withdrawOrderId: optionalString(value, "withdrawOrderId") } : {}, ...optionalString(value, "symbol") ? { symbol: optionalString(value, "symbol") } : {}, ...optionalString(value, "startTime") ? { startTime: optionalString(value, "startTime") } : {}, ...optionalString(value, "endTime") ? { endTime: optionalString(value, "endTime") } : {}, page: optionalPositiveInteger(value, "page", 1) };
@@ -1014,7 +1022,7 @@ var assetTools = [
1014
1022
  ];
1015
1023
 
1016
1024
  // ../core/src/tools/market-summaries.ts
1017
- function record2(value, message) {
1025
+ function record3(value, message) {
1018
1026
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1019
1027
  throw new AiHubError("AI_HUB_OPENAPI_INVALID_RESPONSE", message);
1020
1028
  }
@@ -1092,7 +1100,7 @@ function tickerItem(value) {
1092
1100
  }
1093
1101
  function tickerRows(value) {
1094
1102
  if (Array.isArray(value)) return records(value, "Ticker response must be an array.");
1095
- const payload = record2(value, "Ticker response must be an array.");
1103
+ const payload = record3(value, "Ticker response must be an array.");
1096
1104
  if (Array.isArray(payload.items)) return records(payload.items, "Ticker response items must be an array.");
1097
1105
  throw new AiHubError("AI_HUB_OPENAPI_INVALID_RESPONSE", "Ticker response must be an array.");
1098
1106
  }
@@ -1107,21 +1115,32 @@ function summarizeTickers(value, options) {
1107
1115
  const bySymbol = new Map(rows2.map((item) => [text(item.symbol)?.toUpperCase(), item]));
1108
1116
  const watchlistSymbols = ["BTC", "ETH", "SOL", "XRP", "DOGE", "BNB", "ADA"];
1109
1117
  const watchlist = watchlistSymbols.map((asset) => bySymbol.get(`${asset}/${quoteAsset}`)).filter((item) => Boolean(item)).map(tickerItem);
1110
- const limit = options.limit;
1118
+ const watchlistItems = watchlist.slice(0, options.limit);
1119
+ const leaderboardBudget = Math.max(0, options.limit - watchlistItems.length);
1120
+ const leaderboards = [
1121
+ [...rows2].sort(compareByNumericField("rose", "desc")),
1122
+ [...rows2].sort(compareByNumericField("rose", "asc")),
1123
+ [...rows2].sort(compareByNumericField("amount", "desc"))
1124
+ ];
1125
+ const leaderboardLimits = leaderboards.map((_items, index) => Math.floor((leaderboardBudget + 2 - index) / 3));
1126
+ const topGainers = leaderboards[0]?.slice(0, leaderboardLimits[0]).map(tickerItem) ?? [];
1127
+ const topLosers = leaderboards[1]?.slice(0, leaderboardLimits[1]).map(tickerItem) ?? [];
1128
+ const topByQuoteVolume = leaderboards[2]?.slice(0, leaderboardLimits[2]).map(tickerItem) ?? [];
1111
1129
  return {
1112
1130
  quoteAsset,
1113
1131
  totalSymbols: rows2.length,
1114
- watchlist,
1115
- topGainers: [...rows2].sort(compareByNumericField("rose", "desc")).slice(0, limit).map(tickerItem),
1116
- topLosers: [...rows2].sort(compareByNumericField("rose", "asc")).slice(0, limit).map(tickerItem),
1117
- topByQuoteVolume: [...rows2].sort(compareByNumericField("amount", "desc")).slice(0, limit).map(tickerItem)
1132
+ returnedSymbols: watchlistItems.length + topGainers.length + topLosers.length + topByQuoteVolume.length,
1133
+ watchlist: watchlistItems,
1134
+ topGainers,
1135
+ topLosers,
1136
+ topByQuoteVolume
1118
1137
  };
1119
1138
  }
1120
1139
  function normalizedSymbol(value) {
1121
1140
  return value.replaceAll("/", "").trim().toUpperCase();
1122
1141
  }
1123
1142
  function parsedSymbolRows(value) {
1124
- const payload = record2(value, "Symbols response must be an object.");
1143
+ const payload = record3(value, "Symbols response must be an object.");
1125
1144
  const rows2 = records(payload.symbols, "Symbols response must contain a symbols array.");
1126
1145
  return rows2.map((item) => {
1127
1146
  const apiSymbol = text(item.symbol);
@@ -1200,6 +1219,18 @@ function listSymbols(value, options) {
1200
1219
  items
1201
1220
  };
1202
1221
  }
1222
+ function listFullSymbols(value, options) {
1223
+ const rows2 = sortSymbolMatches(parsedSymbolRows(value));
1224
+ const items = rows2.slice(options.offset, options.offset + options.limit).map(fullSymbolItem);
1225
+ const nextOffset = options.offset + items.length;
1226
+ return {
1227
+ totalSymbols: rows2.length,
1228
+ offset: options.offset,
1229
+ limit: options.limit,
1230
+ nextOffset: nextOffset < rows2.length ? nextOffset : null,
1231
+ items
1232
+ };
1233
+ }
1203
1234
  function searchSymbols(value, options) {
1204
1235
  const allRows = parsedSymbolRows(value);
1205
1236
  const query = options.query.trim().toUpperCase();
@@ -1228,7 +1259,7 @@ function level(value) {
1228
1259
  return { price: text(value[0]), quantity: text(value[1]) };
1229
1260
  }
1230
1261
  function summarizeDepth(value, symbol) {
1231
- const payload = record2(value, "Depth response must be an object.");
1262
+ const payload = record3(value, "Depth response must be an object.");
1232
1263
  const bids = Array.isArray(payload.bids) ? payload.bids : [];
1233
1264
  const asks = Array.isArray(payload.asks) ? payload.asks : [];
1234
1265
  const bestBid = level(bids[0]);
@@ -1249,7 +1280,7 @@ function summarizeDepth(value, symbol) {
1249
1280
  };
1250
1281
  }
1251
1282
  function summarizeTrades(value, symbol) {
1252
- const payload = record2(value, "Trades response must be an object.");
1283
+ const payload = record3(value, "Trades response must be an object.");
1253
1284
  const rows2 = records(payload.list, "Trades response must contain a list array.");
1254
1285
  let buyCount = 0;
1255
1286
  let sellCount = 0;
@@ -1511,6 +1542,28 @@ async function preflightSymbolOrder(context, order) {
1511
1542
  return rule;
1512
1543
  }
1513
1544
 
1545
+ // ../core/src/tools/ticker-summary-cache.ts
1546
+ var TICKER_SUMMARY_CACHE_TTL_MS = 3e3;
1547
+ var cache2 = /* @__PURE__ */ new Map();
1548
+ var pendingLoads2 = /* @__PURE__ */ new Map();
1549
+ function cacheKey2(context) {
1550
+ return `${context.profile.name}\0${context.profile.configVersion}\0${context.profile.openApiBaseUrl}`;
1551
+ }
1552
+ async function getCachedTickerSummarySource(context) {
1553
+ const key = cacheKey2(context);
1554
+ const existing = cache2.get(key);
1555
+ if (existing && existing.expiresAt > Date.now()) return existing.response;
1556
+ const pending = pendingLoads2.get(key);
1557
+ if (pending) return (await pending).response;
1558
+ const load = (async () => {
1559
+ const entry = { response: await context.api.ticker(), expiresAt: Date.now() + TICKER_SUMMARY_CACHE_TTL_MS };
1560
+ cache2.set(key, entry);
1561
+ return entry;
1562
+ })().finally(() => pendingLoads2.delete(key));
1563
+ pendingLoads2.set(key, load);
1564
+ return (await load).response;
1565
+ }
1566
+
1514
1567
  // ../core/src/tools/market-tools.ts
1515
1568
  var readErrors = ["AI_HUB_INVALID_ARGUMENT", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE", "AI_HUB_OPENAPI_BUSINESS_ERROR"];
1516
1569
  var symbolReadErrors = [...readErrors, "AI_HUB_SYMBOL_NOT_FOUND"];
@@ -1548,7 +1601,7 @@ function validateKlineInput(input, options) {
1548
1601
  startTime,
1549
1602
  endTime,
1550
1603
  timezone: optionalString(value, "timezone"),
1551
- limit: optionalInteger(value, "limit", options.summary ? 20 : 50, 1, options.summary ? 100 : 300)
1604
+ limit: normalizedListLimit(value, STANDARD_LIST_LIMIT)
1552
1605
  };
1553
1606
  }
1554
1607
  var marketTools = [
@@ -1586,17 +1639,20 @@ var marketTools = [
1586
1639
  {
1587
1640
  name: "market_get_symbols",
1588
1641
  title: "Get Spot Symbols",
1589
- description: "Get complete spot symbol metadata from the configured tenant OpenAPI. Use market_get_symbol_overview, market_list_symbols, market_search_symbols, or market_get_symbol_info for Agent-facing symbol requests, because this response may be large.",
1642
+ description: "Get one bounded page of complete spot symbol metadata from the configured tenant OpenAPI.",
1590
1643
  cliPath: ["market", "symbols"],
1591
1644
  module: "spot-common",
1592
1645
  access: "public",
1593
1646
  operation: "read",
1594
1647
  riskLevel: "low",
1595
- mcpVisible: false,
1596
- inputSchema: { type: "object", additionalProperties: false },
1648
+ inputSchema: { type: "object", properties: { offset: { type: "integer", minimum: 0, description: "Zero-based page offset. Defaults to 0." }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, additionalProperties: false },
1597
1649
  errorCodes: readErrors,
1598
- validate: (input) => strictObject(input, []),
1599
- handler: (_input, context) => getCachedSymbols(context)
1650
+ listLimit: STANDARD_LIST_LIMIT,
1651
+ validate: (input) => {
1652
+ const value = strictObject(input, ["offset", "limit"]);
1653
+ return { offset: optionalInteger(value, "offset", 0, 0, Number.MAX_SAFE_INTEGER), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
1654
+ },
1655
+ handler: async (input, context) => listFullSymbols(await getCachedSymbols(context), input)
1600
1656
  },
1601
1657
  {
1602
1658
  name: "market_get_symbol_overview",
@@ -1607,11 +1663,12 @@ var marketTools = [
1607
1663
  access: "public",
1608
1664
  operation: "read",
1609
1665
  riskLevel: "low",
1610
- inputSchema: { type: "object", properties: { limit: { type: "integer", minimum: 1, maximum: 20, description: "Number of sample display symbols. Defaults to 12." } }, additionalProperties: false },
1666
+ inputSchema: { type: "object", properties: { limit: listLimitSchema(STANDARD_LIST_LIMIT, "Number of sample display symbols. Defaults to 20; maximum 50.") }, additionalProperties: false },
1611
1667
  errorCodes: readErrors,
1668
+ listLimit: STANDARD_LIST_LIMIT,
1612
1669
  validate: (input) => {
1613
1670
  const value = strictObject(input, ["limit"]);
1614
- return { limit: optionalInteger(value, "limit", 12, 1, 20) };
1671
+ return { limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
1615
1672
  },
1616
1673
  handler: async (input, context) => summarizeSymbolOverview(await getCachedSymbols(context), input)
1617
1674
  },
@@ -1624,14 +1681,15 @@ var marketTools = [
1624
1681
  access: "public",
1625
1682
  operation: "read",
1626
1683
  riskLevel: "low",
1627
- inputSchema: { type: "object", properties: { quoteAsset: { type: "string" }, offset: { type: "integer", minimum: 0, description: "Zero-based page offset. Defaults to 0." }, limit: { type: "integer", minimum: 1, maximum: 50, description: "Page size. Defaults to 20." } }, additionalProperties: false },
1684
+ inputSchema: { type: "object", properties: { quoteAsset: { type: "string" }, offset: { type: "integer", minimum: 0, description: "Zero-based page offset. Defaults to 0." }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, additionalProperties: false },
1628
1685
  errorCodes: readErrors,
1686
+ listLimit: STANDARD_LIST_LIMIT,
1629
1687
  validate: (input) => {
1630
1688
  const value = strictObject(input, ["quoteAsset", "offset", "limit"]);
1631
1689
  return {
1632
1690
  quoteAsset: optionalString(value, "quoteAsset"),
1633
1691
  offset: optionalInteger(value, "offset", 0, 0, Number.MAX_SAFE_INTEGER),
1634
- limit: optionalInteger(value, "limit", 20, 1, 50)
1692
+ limit: normalizedListLimit(value, STANDARD_LIST_LIMIT)
1635
1693
  };
1636
1694
  },
1637
1695
  handler: async (input, context) => listSymbols(await getCachedSymbols(context), input)
@@ -1645,11 +1703,12 @@ var marketTools = [
1645
1703
  access: "public",
1646
1704
  operation: "read",
1647
1705
  riskLevel: "low",
1648
- inputSchema: { type: "object", properties: { query: { type: "string", minLength: 1, description: "Required asset or symbol keyword, for example BTC." }, quoteAsset: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 20 } }, required: ["query"], additionalProperties: false },
1706
+ inputSchema: { type: "object", properties: { query: { type: "string", minLength: 1, description: "Required asset or symbol keyword, for example BTC." }, quoteAsset: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, required: ["query"], additionalProperties: false },
1649
1707
  errorCodes: readErrors,
1708
+ listLimit: STANDARD_LIST_LIMIT,
1650
1709
  validate: (input) => {
1651
1710
  const value = strictObject(input, ["query", "quoteAsset", "limit"]);
1652
- return { query: requiredString(value, "query"), quoteAsset: optionalString(value, "quoteAsset"), limit: optionalInteger(value, "limit", 10, 1, 20) };
1711
+ return { query: requiredString(value, "query"), quoteAsset: optionalString(value, "quoteAsset"), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
1653
1712
  },
1654
1713
  handler: async (input, context) => {
1655
1714
  const value = input;
@@ -1684,7 +1743,7 @@ var marketTools = [
1684
1743
  riskLevel: "low",
1685
1744
  inputSchema: {
1686
1745
  type: "object",
1687
- properties: { symbol: { type: "string", minLength: 1, description: "One exact symbol, for example ETHUSDT." }, symbols: { type: "string", minLength: 1, description: "An explicitly requested upstream symbol list." }, timeZone: { type: "string" } },
1746
+ properties: { symbol: { type: "string", minLength: 1, description: "One exact symbol, for example ETHUSDT." }, symbols: { type: "string", minLength: 1, description: "An explicitly requested comma-separated upstream symbol list of at most 50 symbols." }, timeZone: { type: "string" } },
1688
1747
  oneOf: [{ required: ["symbol"] }, { required: ["symbols"] }],
1689
1748
  additionalProperties: false
1690
1749
  },
@@ -1695,6 +1754,7 @@ var marketTools = [
1695
1754
  const symbols = optionalString(value, "symbols");
1696
1755
  if (!symbol && !symbols) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "market_get_ticker requires symbol or symbols. Use market_get_ticker_summary for all-market data.");
1697
1756
  if (symbol && symbols) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "market_get_ticker accepts either symbol or symbols, not both.");
1757
+ if (symbols && symbols.split(",").map((item) => item.trim()).filter(Boolean).length > STANDARD_LIST_LIMIT.maximum) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", `symbols supports at most ${STANDARD_LIST_LIMIT.maximum} explicit symbols.`);
1698
1758
  return { symbol, symbols, timeZone: optionalString(value, "timeZone") };
1699
1759
  },
1700
1760
  handler: (input, context) => context.api.ticker(input)
@@ -1708,15 +1768,16 @@ var marketTools = [
1708
1768
  access: "public",
1709
1769
  operation: "read",
1710
1770
  riskLevel: "low",
1711
- inputSchema: { type: "object", properties: { quoteAsset: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 20 } }, additionalProperties: false },
1771
+ inputSchema: { type: "object", properties: { quoteAsset: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, additionalProperties: false },
1712
1772
  errorCodes: readErrors,
1773
+ listLimit: STANDARD_LIST_LIMIT,
1713
1774
  validate: (input) => {
1714
1775
  const value = strictObject(input, ["quoteAsset", "limit"]);
1715
- return { quoteAsset: optionalString(value, "quoteAsset") ?? "USDT", limit: optionalInteger(value, "limit", 5, 1, 20) };
1776
+ return { quoteAsset: optionalString(value, "quoteAsset") ?? "USDT", limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
1716
1777
  },
1717
1778
  handler: async (input, context) => {
1718
1779
  const value = input;
1719
- return summarizeTickers(await context.api.ticker(), value);
1780
+ return summarizeTickers(await getCachedTickerSummarySource(context), value);
1720
1781
  }
1721
1782
  },
1722
1783
  {
@@ -1728,12 +1789,12 @@ var marketTools = [
1728
1789
  access: "public",
1729
1790
  operation: "read",
1730
1791
  riskLevel: "low",
1731
- mcpVisible: false,
1732
- inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 100 } }, required: ["symbol"], additionalProperties: false },
1792
+ inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, required: ["symbol"], additionalProperties: false },
1733
1793
  errorCodes: readErrors,
1794
+ listLimit: STANDARD_LIST_LIMIT,
1734
1795
  validate: (input) => {
1735
1796
  const value = strictObject(input, ["symbol", "limit"]);
1736
- return { symbol: requiredString(value, "symbol"), limit: optionalInteger(value, "limit", 20, 1, 100) };
1797
+ return { symbol: requiredString(value, "symbol"), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
1737
1798
  },
1738
1799
  handler: (input, context) => {
1739
1800
  const value = input;
@@ -1749,11 +1810,12 @@ var marketTools = [
1749
1810
  access: "public",
1750
1811
  operation: "read",
1751
1812
  riskLevel: "low",
1752
- inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 20 } }, required: ["symbol"], additionalProperties: false },
1813
+ inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, required: ["symbol"], additionalProperties: false },
1753
1814
  errorCodes: readErrors,
1815
+ listLimit: STANDARD_LIST_LIMIT,
1754
1816
  validate: (input) => {
1755
1817
  const value = strictObject(input, ["symbol", "limit"]);
1756
- return { symbol: requiredString(value, "symbol"), limit: optionalInteger(value, "limit", 10, 1, 20) };
1818
+ return { symbol: requiredString(value, "symbol"), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
1757
1819
  },
1758
1820
  handler: async (input, context) => {
1759
1821
  const value = input;
@@ -1769,12 +1831,12 @@ var marketTools = [
1769
1831
  access: "public",
1770
1832
  operation: "read",
1771
1833
  riskLevel: "low",
1772
- mcpVisible: false,
1773
- inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 100 } }, required: ["symbol"], additionalProperties: false },
1834
+ inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, required: ["symbol"], additionalProperties: false },
1774
1835
  errorCodes: readErrors,
1836
+ listLimit: STANDARD_LIST_LIMIT,
1775
1837
  validate: (input) => {
1776
1838
  const value = strictObject(input, ["symbol", "limit"]);
1777
- return { symbol: requiredString(value, "symbol"), limit: optionalInteger(value, "limit", 20, 1, 100) };
1839
+ return { symbol: requiredString(value, "symbol"), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
1778
1840
  },
1779
1841
  handler: (input, context) => {
1780
1842
  const value = input;
@@ -1790,11 +1852,12 @@ var marketTools = [
1790
1852
  access: "public",
1791
1853
  operation: "read",
1792
1854
  riskLevel: "low",
1793
- inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 50 } }, required: ["symbol"], additionalProperties: false },
1855
+ inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, required: ["symbol"], additionalProperties: false },
1794
1856
  errorCodes: readErrors,
1857
+ listLimit: STANDARD_LIST_LIMIT,
1795
1858
  validate: (input) => {
1796
1859
  const value = strictObject(input, ["symbol", "limit"]);
1797
- return { symbol: requiredString(value, "symbol"), limit: optionalInteger(value, "limit", 20, 1, 50) };
1860
+ return { symbol: requiredString(value, "symbol"), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
1798
1861
  },
1799
1862
  handler: async (input, context) => {
1800
1863
  const value = input;
@@ -1810,9 +1873,9 @@ var marketTools = [
1810
1873
  access: "public",
1811
1874
  operation: "read",
1812
1875
  riskLevel: "low",
1813
- mcpVisible: false,
1814
- inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Spot symbol, for example ETHUSDT or ETH/USDT." }, interval: { type: "string", enum: KLINE_INTERVALS, description: klineIntervalDescription }, startTime: { type: "integer", description: "Optional inclusive Unix timestamp in milliseconds." }, endTime: { type: "integer", description: "Optional inclusive Unix timestamp in milliseconds." }, timezone: { type: "string", description: klineTimezoneDescription }, limit: { type: "integer", minimum: 1, maximum: 300, description: "Number of newest-first candles. The upstream maximum is 300." } }, required: ["symbol", "interval"], additionalProperties: false },
1876
+ inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Spot symbol, for example ETHUSDT or ETH/USDT." }, interval: { type: "string", enum: KLINE_INTERVALS, description: klineIntervalDescription }, startTime: { type: "integer", description: "Optional inclusive Unix timestamp in milliseconds." }, endTime: { type: "integer", description: "Optional inclusive Unix timestamp in milliseconds." }, timezone: { type: "string", description: klineTimezoneDescription }, limit: listLimitSchema(STANDARD_LIST_LIMIT, "Number of newest-first candles. Defaults to 20; maximum 50.") }, required: ["symbol", "interval"], additionalProperties: false },
1815
1877
  errorCodes: readErrors,
1878
+ listLimit: STANDARD_LIST_LIMIT,
1816
1879
  validate: (input) => {
1817
1880
  return validateKlineInput(input, { summary: false });
1818
1881
  },
@@ -1827,8 +1890,9 @@ var marketTools = [
1827
1890
  access: "public",
1828
1891
  operation: "read",
1829
1892
  riskLevel: "low",
1830
- inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Spot symbol, for example ETHUSDT or ETH/USDT." }, interval: { type: "string", enum: KLINE_INTERVALS, description: `${klineIntervalDescription} Defaults to 60min when omitted.` }, startTime: { type: "integer", description: "Optional inclusive Unix timestamp in milliseconds." }, endTime: { type: "integer", description: "Optional inclusive Unix timestamp in milliseconds." }, timezone: { type: "string", description: klineTimezoneDescription }, limit: { type: "integer", minimum: 1, maximum: 100, description: "Number of candles included in the bounded analysis." } }, required: ["symbol"], additionalProperties: false },
1893
+ inputSchema: { type: "object", properties: { symbol: { type: "string", description: "Spot symbol, for example ETHUSDT or ETH/USDT." }, interval: { type: "string", enum: KLINE_INTERVALS, description: `${klineIntervalDescription} Defaults to 60min when omitted.` }, startTime: { type: "integer", description: "Optional inclusive Unix timestamp in milliseconds." }, endTime: { type: "integer", description: "Optional inclusive Unix timestamp in milliseconds." }, timezone: { type: "string", description: klineTimezoneDescription }, limit: listLimitSchema(STANDARD_LIST_LIMIT, "Number of candles included in the bounded analysis. Defaults to 20; maximum 50.") }, required: ["symbol"], additionalProperties: false },
1831
1894
  errorCodes: readErrors,
1895
+ listLimit: STANDARD_LIST_LIMIT,
1832
1896
  validate: (input) => {
1833
1897
  return validateKlineInput(input, { summary: true });
1834
1898
  },
@@ -1910,11 +1974,11 @@ function validateMarginOrderLookup(input) {
1910
1974
  }
1911
1975
  function validateMarginOpenOrders(input) {
1912
1976
  const value = strictObject(input, ["symbol", "limit", "isolated"]);
1913
- return { ...optionalString(value, "symbol") ? { symbol: optionalString(value, "symbol") } : {}, limit: optionalPositiveInteger(value, "limit", 100, 1e3), ...optionalBoolean(value, "isolated") !== void 0 ? { isolated: optionalBoolean(value, "isolated") } : {} };
1977
+ return { ...optionalString(value, "symbol") ? { symbol: optionalString(value, "symbol") } : {}, limit: normalizedListLimit(value, STANDARD_LIST_LIMIT), ...optionalBoolean(value, "isolated") !== void 0 ? { isolated: optionalBoolean(value, "isolated") } : {} };
1914
1978
  }
1915
1979
  function validateMarginTrades(input) {
1916
1980
  const value = strictObject(input, ["symbol", "limit", "fromId"]);
1917
- return { symbol: requiredString(value, "symbol"), limit: optionalPositiveInteger(value, "limit", 100, 1e3), ...optionalString(value, "fromId") ? { fromId: optionalString(value, "fromId") } : {} };
1981
+ return { symbol: requiredString(value, "symbol"), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT), ...optionalString(value, "fromId") ? { fromId: optionalString(value, "fromId") } : {} };
1918
1982
  }
1919
1983
  var marginTools = [
1920
1984
  {
@@ -1940,8 +2004,9 @@ var marginTools = [
1940
2004
  access: "signed",
1941
2005
  operation: "read",
1942
2006
  riskLevel: "low",
1943
- inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 1e3 }, isolated: { type: "boolean" } }, additionalProperties: false },
2007
+ inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT), isolated: { type: "boolean" } }, additionalProperties: false },
1944
2008
  errorCodes: signedReadErrors,
2009
+ listLimit: STANDARD_LIST_LIMIT,
1945
2010
  validate: validateMarginOpenOrders,
1946
2011
  handler: (input, context) => context.api.signedGet("/sapi/v2/margin/openOrders", input, signed(context))
1947
2012
  },
@@ -1954,8 +2019,9 @@ var marginTools = [
1954
2019
  access: "signed",
1955
2020
  operation: "read",
1956
2021
  riskLevel: "low",
1957
- inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 1e3 }, fromId: { type: "string" } }, required: ["symbol"], additionalProperties: false },
2022
+ inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT), fromId: { type: "string" } }, required: ["symbol"], additionalProperties: false },
1958
2023
  errorCodes: signedReadErrors,
2024
+ listLimit: STANDARD_LIST_LIMIT,
1959
2025
  validate: validateMarginTrades,
1960
2026
  handler: (input, context) => context.api.signedGet("/sapi/v2/margin/myTrades", input, signed(context))
1961
2027
  },
@@ -2306,11 +2372,12 @@ var orderTools = [
2306
2372
  access: "signed",
2307
2373
  operation: "read",
2308
2374
  riskLevel: "low",
2309
- inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 1e3 } }, additionalProperties: false },
2375
+ inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT) }, additionalProperties: false },
2310
2376
  errorCodes: signedReadErrors2,
2377
+ listLimit: STANDARD_LIST_LIMIT,
2311
2378
  validate: (input) => {
2312
2379
  const value = strictObject(input, ["symbol", "limit"]);
2313
- return { symbol: optionalString(value, "symbol"), limit: optionalInteger(value, "limit", 100, 1, 1e3) };
2380
+ return { symbol: optionalString(value, "symbol"), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT) };
2314
2381
  },
2315
2382
  handler: (input, context) => context.api.getOpenOrders(input, signed2(context))
2316
2383
  },
@@ -2323,11 +2390,12 @@ var orderTools = [
2323
2390
  access: "signed",
2324
2391
  operation: "read",
2325
2392
  riskLevel: "low",
2326
- inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 1e3 }, fromId: { type: "string" } }, required: ["symbol"], additionalProperties: false },
2393
+ inputSchema: { type: "object", properties: { symbol: { type: "string" }, limit: listLimitSchema(STANDARD_LIST_LIMIT), fromId: { type: "string" } }, required: ["symbol"], additionalProperties: false },
2327
2394
  errorCodes: signedReadErrors2,
2395
+ listLimit: STANDARD_LIST_LIMIT,
2328
2396
  validate: (input) => {
2329
2397
  const value = strictObject(input, ["symbol", "limit", "fromId"]);
2330
- return { symbol: requiredString(value, "symbol"), limit: optionalInteger(value, "limit", 100, 1, 1e3), fromId: optionalString(value, "fromId") };
2398
+ return { symbol: requiredString(value, "symbol"), limit: normalizedListLimit(value, STANDARD_LIST_LIMIT), fromId: optionalString(value, "fromId") };
2331
2399
  },
2332
2400
  handler: (input, context) => context.api.getMyTrades(input, signed2(context))
2333
2401
  },
@@ -2424,7 +2492,7 @@ function fields(input, allowed, required = []) {
2424
2492
  return result2;
2425
2493
  }
2426
2494
  function pages(value) {
2427
- return { page: optionalPositiveInteger(value, "page", 1), pageSize: optionalPositiveInteger(value, "pageSize", 20, 100) };
2495
+ return { page: optionalPositiveInteger(value, "page", 1), pageSize: normalizedListLimit(value, STANDARD_PAGE_SIZE) };
2428
2496
  }
2429
2497
  function transfer(value, requireSubUid) {
2430
2498
  const result2 = { coinSymbol: requiredString(value, "coinSymbol"), amount: positiveDecimal(value, "amount") };
@@ -2443,6 +2511,7 @@ var subAccountTools = [
2443
2511
  riskLevel: "low",
2444
2512
  inputSchema: { type: "object", additionalProperties: false },
2445
2513
  errorCodes: signedReadErrors,
2514
+ unpagedListLimit: { path: ["data", "subUserList"] },
2446
2515
  validate: (input) => {
2447
2516
  strictObject(input, []);
2448
2517
  return {};
@@ -2498,6 +2567,7 @@ var subAccountTools = [
2498
2567
  riskLevel: "low",
2499
2568
  inputSchema: { type: "object", properties: { subUid: { type: "string" } }, required: ["subUid"], additionalProperties: false },
2500
2569
  errorCodes: signedReadErrors,
2570
+ unpagedListLimit: { path: ["data", "apiList"] },
2501
2571
  validate: (input) => fields(input, ["subUid"], ["subUid"]),
2502
2572
  handler: (input, context) => context.api.signedPost("/sapi/v1/sub_user/sub_account_api/list", input, signed(context))
2503
2573
  },
@@ -2547,6 +2617,7 @@ var subAccountTools = [
2547
2617
  riskLevel: "low",
2548
2618
  inputSchema: { type: "object", properties: { subUid: { type: "string" }, accountType: { type: "string" }, type: { type: "string" } }, required: ["subUid", "accountType"], additionalProperties: false },
2549
2619
  errorCodes: signedReadErrors,
2620
+ unpagedListLimit: { path: ["data", "accountList"] },
2550
2621
  validate: (input) => fields(input, ["subUid", "accountType", "type"], ["subUid", "accountType"]),
2551
2622
  handler: (input, context) => context.api.signedPost("/sapi/v1/sub_user/asset/account", input, signed(context))
2552
2623
  },
@@ -2577,8 +2648,9 @@ var subAccountTools = [
2577
2648
  access: "signed",
2578
2649
  operation: "read",
2579
2650
  riskLevel: "low",
2580
- inputSchema: { type: "object", properties: { subUid: { type: "string" }, coinSymbol: { type: "string" }, page: { type: "integer" }, pageSize: { type: "integer" } }, required: ["subUid", "coinSymbol"], additionalProperties: false },
2651
+ inputSchema: { type: "object", properties: { subUid: { type: "string" }, coinSymbol: { type: "string" }, page: { type: "integer", minimum: 1 }, pageSize: listLimitSchema(STANDARD_PAGE_SIZE) }, required: ["subUid", "coinSymbol"], additionalProperties: false },
2581
2652
  errorCodes: signedReadErrors,
2653
+ listLimit: STANDARD_PAGE_SIZE,
2582
2654
  validate: (input) => {
2583
2655
  const value = strictObject(input, ["subUid", "coinSymbol", "page", "pageSize"]);
2584
2656
  return { subUid: requiredString(value, "subUid"), coinSymbol: requiredString(value, "coinSymbol"), ...pages(value) };
@@ -2612,62 +2684,37 @@ var subAccountTools = [
2612
2684
  access: "signed",
2613
2685
  operation: "read",
2614
2686
  riskLevel: "low",
2615
- inputSchema: { type: "object", properties: { subUid: { type: "string" }, type: { type: "string" }, accountType: { type: "string" }, coinSymbol: { type: "string" }, page: { type: "integer" }, pageSize: { type: "integer" } }, required: ["subUid", "type", "accountType", "coinSymbol"], additionalProperties: false },
2687
+ inputSchema: { type: "object", properties: { subUid: { type: "string" }, type: { type: "string" }, accountType: { type: "string" }, coinSymbol: { type: "string" }, page: { type: "integer", minimum: 1 }, pageSize: listLimitSchema(STANDARD_PAGE_SIZE) }, required: ["subUid", "type", "accountType", "coinSymbol"], additionalProperties: false },
2616
2688
  errorCodes: signedReadErrors,
2689
+ listLimit: STANDARD_PAGE_SIZE,
2617
2690
  validate: (input) => {
2618
2691
  const value = strictObject(input, ["subUid", "type", "accountType", "coinSymbol", "page", "pageSize"]);
2619
2692
  return { subUid: requiredString(value, "subUid"), type: requiredString(value, "type"), accountType: requiredString(value, "accountType"), coinSymbol: requiredString(value, "coinSymbol"), ...pages(value) };
2620
2693
  },
2621
2694
  handler: (input, context) => context.api.signedPost("/sapi/v1/sub_user/asset/transfer_query", input, signed(context))
2622
- },
2623
- {
2624
- name: "sub_account_transfer_to_parent",
2625
- title: "Transfer From Sub-account to Parent",
2626
- description: "Transfer assets to the parent account after confirmation.",
2627
- cliPath: ["sub-account", "transfer-to-parent"],
2628
- module: "spot-sub-account",
2629
- access: "signed",
2630
- operation: "write",
2631
- riskLevel: "high",
2632
- inputSchema: { type: "object", properties: { coinSymbol: { type: "string" }, amount: { type: "string" } }, required: ["coinSymbol", "amount"], additionalProperties: false },
2633
- errorCodes: writeErrors,
2634
- validate: (input) => {
2635
- const value = strictObject(input, ["coinSymbol", "amount"]);
2636
- return transfer(value, false);
2637
- },
2638
- handler: (input, context) => context.api.signedPost("/sapi/v1/asset/subaccount/transfer", input, signed(context)),
2639
- writeSummary: (input) => ({ action: "sub_account_transfer_to_parent", ...input })
2640
- },
2641
- {
2642
- name: "sub_account_get_parent_transfer_history",
2643
- title: "Get Parent Transfer History",
2644
- description: "Query transfers between this sub-account and its parent.",
2645
- cliPath: ["sub-account", "parent-transfer-history"],
2646
- module: "spot-sub-account",
2647
- access: "signed",
2648
- operation: "read",
2649
- riskLevel: "low",
2650
- inputSchema: { type: "object", properties: { coinSymbol: { type: "string" }, page: { type: "integer" }, pageSize: { type: "integer" } }, required: ["coinSymbol"], additionalProperties: false },
2651
- errorCodes: signedReadErrors,
2652
- validate: (input) => {
2653
- const value = strictObject(input, ["coinSymbol", "page", "pageSize"]);
2654
- return { coinSymbol: requiredString(value, "coinSymbol"), ...pages(value) };
2655
- },
2656
- handler: (input, context) => context.api.signedPost("/sapi/v1/asset/subaccount/transfer_query", input, signed(context))
2657
2695
  }
2658
2696
  ];
2659
2697
 
2660
2698
  // ../core/src/tools/tool-registry.ts
2661
2699
  var allTools = [...marketTools, ...accountTools, ...orderTools, ...marginTools, ...assetTools, ...subAccountTools];
2700
+ function assertListLimitSchema(tool) {
2701
+ const limit = tool.listLimit;
2702
+ if (!limit) return;
2703
+ const property = tool.inputSchema.properties?.[limit.field];
2704
+ if (!property || property.type !== "integer" || property.minimum !== 1 || property.maximum !== limit.maximum) {
2705
+ throw new AiHubError("AI_HUB_TOOL_LIST_LIMIT_INVALID", `Tool "${tool.name}" must expose its declared ${limit.field} range in inputSchema.`);
2706
+ }
2707
+ }
2662
2708
  var ToolRegistry = class {
2663
2709
  toolsByName = /* @__PURE__ */ new Map();
2664
2710
  toolsByCliPath = /* @__PURE__ */ new Map();
2665
2711
  constructor(tools = allTools) {
2666
- for (const tool of tools) {
2712
+ for (const tool of tools.map(withListLimit)) {
2667
2713
  const path2 = tool.cliPath.join(" ");
2668
2714
  if (this.toolsByName.has(tool.name) || this.toolsByCliPath.has(path2)) {
2669
2715
  throw new AiHubError("AI_HUB_TOOL_DUPLICATE", `Duplicate tool registration: ${tool.name}.`);
2670
2716
  }
2717
+ assertListLimitSchema(tool);
2671
2718
  this.toolsByName.set(tool.name, tool);
2672
2719
  this.toolsByCliPath.set(path2, tool);
2673
2720
  }
@@ -2693,7 +2740,8 @@ var ToolRegistry = class {
2693
2740
  const tool = this.byName(name, options);
2694
2741
  if (tool.operation === "write") throw new AiHubError("AI_HUB_WRITE_CONFIRMATION_REQUIRED", `Tool "${name}" must be executed through confirmation.`);
2695
2742
  const validInput = tool.validate(input);
2696
- return tool.handler(validInput, context);
2743
+ const response = await tool.handler(validInput, context);
2744
+ return tool.unpagedListLimit ? truncateUnpagedListResponse(response, tool.unpagedListLimit, validInput) : response;
2697
2745
  }
2698
2746
  async prepareWrite(name, input, context, confirmations) {
2699
2747
  const tool = this.byName(name);
@@ -2722,8 +2770,8 @@ var ToolRegistry = class {
2722
2770
  }));
2723
2771
  }
2724
2772
  };
2725
- function createToolRegistry() {
2726
- return new ToolRegistry();
2773
+ function createToolRegistry(tools) {
2774
+ return new ToolRegistry(tools);
2727
2775
  }
2728
2776
 
2729
2777
  // ../core/src/tools/write-executor.ts
@@ -2750,6 +2798,53 @@ var ToolWriteExecutor = class {
2750
2798
  }
2751
2799
  };
2752
2800
 
2801
+ // ../core/src/tools/mcp-toolset.ts
2802
+ var MCP_TOOLSETS = ["default", "full"];
2803
+ var DEFAULT_MCP_TOOLSET = "default";
2804
+ var DEFAULT_MCP_TOOL_NAMES = /* @__PURE__ */ new Set([
2805
+ "market_get_symbol_overview",
2806
+ "market_list_symbols",
2807
+ "market_search_symbols",
2808
+ "market_get_symbol_info",
2809
+ "market_get_depth_summary",
2810
+ "market_get_ticker",
2811
+ "market_get_ticker_summary",
2812
+ "market_get_trades_summary",
2813
+ "market_get_klines_summary",
2814
+ "spot_get_order",
2815
+ "spot_batch_place_orders",
2816
+ "spot_batch_cancel_orders",
2817
+ "spot_get_open_orders",
2818
+ "spot_get_fills",
2819
+ "spot_market_buy",
2820
+ "spot_market_sell",
2821
+ "spot_limit_order",
2822
+ "spot_cancel_order",
2823
+ "wallet_universal_transfer",
2824
+ "wallet_get_universal_transfer_history",
2825
+ "wallet_get_deposit_history",
2826
+ "wallet_get_deposit_address",
2827
+ "wallet_get_withdraw_address",
2828
+ "wallet_get_transferable_assets"
2829
+ ]);
2830
+ function parseMcpToolset(value) {
2831
+ if (value === void 0) return DEFAULT_MCP_TOOLSET;
2832
+ if (MCP_TOOLSETS.includes(value)) return value;
2833
+ throw new AiHubError("AI_HUB_INVALID_ARGUMENT", `--toolset must be one of: ${MCP_TOOLSETS.join(", ")}.`);
2834
+ }
2835
+ function selectMcpToolset(tools, toolset) {
2836
+ return toolset === "full" ? [...tools] : tools.filter((tool) => DEFAULT_MCP_TOOL_NAMES.has(tool.name));
2837
+ }
2838
+
2839
+ // ../core/src/mcp-response-mode.ts
2840
+ var MCP_RESPONSE_MODES = ["compact", "compat"];
2841
+ var DEFAULT_MCP_RESPONSE_MODE = "compact";
2842
+ function parseMcpResponseMode(value) {
2843
+ if (value === void 0) return DEFAULT_MCP_RESPONSE_MODE;
2844
+ if (MCP_RESPONSE_MODES.includes(value)) return value;
2845
+ throw new AiHubError("AI_HUB_INVALID_ARGUMENT", `--response-mode must be one of: ${MCP_RESPONSE_MODES.join(", ")}.`);
2846
+ }
2847
+
2753
2848
  // ../core/src/setup.ts
2754
2849
  import { execFileSync } from "child_process";
2755
2850
  import * as fs from "fs";
@@ -2760,7 +2855,8 @@ var MCP_CLIENT_NAMES = {
2760
2855
  cursor: "Cursor",
2761
2856
  "claude-desktop": "Claude Desktop",
2762
2857
  "claude-code": "Claude Code",
2763
- codex: "Codex"
2858
+ codex: "Codex",
2859
+ openclaw: "OpenClaw"
2764
2860
  };
2765
2861
  var SUPPORTED_MCP_CLIENTS = Object.keys(MCP_CLIENT_NAMES);
2766
2862
  function runtime() {
@@ -2795,21 +2891,23 @@ function findMicrosoftStoreClaudePath(value, home) {
2795
2891
  return void 0;
2796
2892
  }
2797
2893
  }
2798
- function getMcpClientConfigPath(client, value = runtime()) {
2799
- if (client === "cursor") return path.join(value.home, ".cursor", "mcp.json");
2894
+ function getMcpClientConfigPaths(client, value = runtime()) {
2895
+ if (client === "cursor") return [path.join(value.home, ".cursor", "mcp.json")];
2800
2896
  if (value.platform === "win32") {
2801
- return findMicrosoftStoreClaudePath(value.localAppData, value.home) ?? path.join(windowsAppData(value.appData, value.home), "Claude", "claude_desktop_config.json");
2897
+ return [findMicrosoftStoreClaudePath(value.localAppData, value.home) ?? path.join(windowsAppData(value.appData, value.home), "Claude", "claude_desktop_config.json")];
2802
2898
  }
2803
2899
  if (value.platform === "darwin") {
2804
- return path.join(value.home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
2900
+ const legacyPath = path.join(value.home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
2901
+ const modernPath = path.join(value.home, "Library", "Application Support", "Claude-3p", "claude_desktop_config.json");
2902
+ return fs.existsSync(modernPath) ? [legacyPath, modernPath] : [legacyPath];
2805
2903
  }
2806
- return path.join(value.xdgConfigHome ?? path.join(value.home, ".config"), "Claude", "claude_desktop_config.json");
2904
+ return [path.join(value.xdgConfigHome ?? path.join(value.home, ".config"), "Claude", "claude_desktop_config.json")];
2807
2905
  }
2808
- function buildServerSpec(profile, launch) {
2906
+ function buildServerSpec(profile, toolset, responseMode, launch) {
2809
2907
  return {
2810
2908
  name: serverName(profile),
2811
2909
  command: launch.command,
2812
- args: [...launch.args, ...profile ? ["--profile", profile] : []]
2910
+ args: [...launch.args, ...profile ? ["--profile", profile] : [], "--toolset", toolset, "--response-mode", responseMode]
2813
2911
  };
2814
2912
  }
2815
2913
  function serverName(profile) {
@@ -2869,8 +2967,9 @@ function jsonFileAdapter(id) {
2869
2967
  id,
2870
2968
  name: MCP_CLIENT_NAMES[id],
2871
2969
  install(server, value) {
2872
- mergeJsonMcpConfig(getMcpClientConfigPath(id, value), server);
2873
- process.stdout.write(`Configured ${this.name}: ${getMcpClientConfigPath(id, value)}
2970
+ const configPaths = getMcpClientConfigPaths(id, value);
2971
+ for (const configPath of configPaths) mergeJsonMcpConfig(configPath, server);
2972
+ process.stdout.write(`Configured ${this.name}: ${configPaths.join(", ")}
2874
2973
  Restart ${this.name} to apply changes.
2875
2974
  `);
2876
2975
  }
@@ -2897,11 +2996,33 @@ function cliAdapter(id) {
2897
2996
  }
2898
2997
  };
2899
2998
  }
2999
+ function openClawAdapter() {
3000
+ return {
3001
+ id: "openclaw",
3002
+ name: MCP_CLIENT_NAMES.openclaw,
3003
+ install(server, value) {
3004
+ const args = [
3005
+ "mcp",
3006
+ "add",
3007
+ server.name,
3008
+ "--command",
3009
+ server.command,
3010
+ ...server.args.flatMap((argument) => ["--arg", argument])
3011
+ ];
3012
+ executeClientRegistration("openclaw", args, value);
3013
+ process.stdout.write(
3014
+ `Configured ${this.name} with local stdio MCP server "${server.name}". Run \`openclaw mcp doctor ${server.name} --probe\` to verify it.
3015
+ `
3016
+ );
3017
+ }
3018
+ };
3019
+ }
2900
3020
  var MCP_CLIENT_ADAPTERS = {
2901
3021
  cursor: jsonFileAdapter("cursor"),
2902
3022
  "claude-desktop": jsonFileAdapter("claude-desktop"),
2903
3023
  "claude-code": cliAdapter("claude-code"),
2904
- codex: cliAdapter("codex")
3024
+ codex: cliAdapter("codex"),
3025
+ openclaw: openClawAdapter()
2905
3026
  };
2906
3027
  function runMcpSetup(options, value = runtime()) {
2907
3028
  const adapter = MCP_CLIENT_ADAPTERS[options.client];
@@ -2909,11 +3030,13 @@ function runMcpSetup(options, value = runtime()) {
2909
3030
  throw new Error(`Unknown MCP client "${options.client}". Supported clients: ${SUPPORTED_MCP_CLIENTS.join(", ")}.`);
2910
3031
  }
2911
3032
  const profile = options.profile ? validateProfileName(options.profile) : void 0;
2912
- adapter.install(buildServerSpec(profile, options.launch), value);
3033
+ const toolset = parseMcpToolset(options.toolset ?? DEFAULT_MCP_TOOLSET);
3034
+ const responseMode = parseMcpResponseMode(options.responseMode ?? DEFAULT_MCP_RESPONSE_MODE);
3035
+ adapter.install(buildServerSpec(profile, toolset, responseMode, options.launch), value);
2913
3036
  }
2914
3037
  function printMcpSetupUsage() {
2915
3038
  process.stdout.write(
2916
- `Usage: ${MCP_BINARY} setup --client <client> [--profile <name>]
3039
+ `Usage: ${MCP_BINARY} setup --client <client> [--profile <name>] [--toolset <default|full>] [--response-mode <compact|compat>]
2917
3040
 
2918
3041
  Supported clients:
2919
3042
  ` + SUPPORTED_MCP_CLIENTS.map((client) => ` ${client.padEnd(16)} ${MCP_CLIENT_NAMES[client]}`).join("\n") + "\n"
@@ -2945,9 +3068,37 @@ var READ_RESULT_OUTPUT_SCHEMA = {
2945
3068
  function result(data) {
2946
3069
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
2947
3070
  }
2948
- function toMcpReadResult(data) {
3071
+ function numberField(value, ...names) {
3072
+ for (const name of names) {
3073
+ const candidate = value[name];
3074
+ if (typeof candidate === "number" && Number.isFinite(candidate)) return candidate;
3075
+ }
3076
+ return void 0;
3077
+ }
3078
+ function compactReadText(data) {
3079
+ const envelope = data;
3080
+ const dataType = typeof envelope?.dataType === "string" ? envelope.dataType : "unknown";
3081
+ const compact = { dataType };
3082
+ if (typeof envelope?.count === "number") compact.count = envelope.count;
3083
+ if (dataType === "object" && envelope.value && typeof envelope.value === "object" && !Array.isArray(envelope.value)) {
3084
+ const value = envelope.value;
3085
+ const items = Array.isArray(value.items) ? value.items : void 0;
3086
+ const returnedCount = numberField(value, "returnedCount", "returnedSymbols") ?? items?.length;
3087
+ const totalCount = numberField(value, "totalCount", "totalSymbols", "matchedSymbols");
3088
+ if (returnedCount !== void 0) compact.returnedCount = returnedCount;
3089
+ if (totalCount !== void 0) compact.totalCount = totalCount;
3090
+ if (typeof value.nextOffset === "number" || value.nextOffset === null) compact.nextOffset = value.nextOffset;
3091
+ if (value.truncated === true) compact.truncated = true;
3092
+ }
3093
+ compact.summary = "Full result is available in structuredContent.";
3094
+ return JSON.stringify({ ok: true, data: compact });
3095
+ }
3096
+ function toMcpReadResult(data, responseMode = DEFAULT_MCP_RESPONSE_MODE) {
2949
3097
  const payload = { ok: true, data };
2950
- return { content: [{ type: "text", text: JSON.stringify(payload) }], structuredContent: payload };
3098
+ return {
3099
+ content: [{ type: "text", text: responseMode === "compat" ? JSON.stringify(payload) : compactReadText(data) }],
3100
+ structuredContent: payload
3101
+ };
2951
3102
  }
2952
3103
  function formatMcpData(data) {
2953
3104
  if (Array.isArray(data)) {
@@ -2980,9 +3131,6 @@ function toMcpTool(tool) {
2980
3131
  }
2981
3132
  };
2982
3133
  }
2983
- function isMcpVisible(tool) {
2984
- return tool.mcpVisible !== false;
2985
- }
2986
3134
  function prepareToolName(tool) {
2987
3135
  const [prefix, ...rest] = tool.name.split("_");
2988
3136
  return `${prefix ?? "spot"}_prepare_${rest.join("_")}`;
@@ -2996,8 +3144,9 @@ function toPrepareMcpTool(tool) {
2996
3144
  annotations: { readOnlyHint: false, destructiveHint: tool.riskLevel === "high", idempotentHint: true, openWorldHint: false }
2997
3145
  };
2998
3146
  }
2999
- function createServer(profileName, readOnly) {
3000
- const registry = createToolRegistry();
3147
+ function createServer(profileName, readOnly, toolset = DEFAULT_MCP_TOOLSET, responseMode = DEFAULT_MCP_RESPONSE_MODE) {
3148
+ const allTools2 = createToolRegistry();
3149
+ const registry = createToolRegistry(selectMcpToolset(allTools2.list(), toolset));
3001
3150
  const writeExecutor = new ToolWriteExecutor(registry);
3002
3151
  const server = new Server(
3003
3152
  { name: "ai-hub-agent-trade", version: "0.1.11" },
@@ -3015,7 +3164,7 @@ function createServer(profileName, readOnly) {
3015
3164
  inputSchema: { type: "object", additionalProperties: false },
3016
3165
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3017
3166
  },
3018
- ...registry.list({ readOnly }).filter(isMcpVisible).flatMap((tool) => tool.operation === "write" ? [toPrepareMcpTool(tool)] : [toMcpTool(tool)]),
3167
+ ...registry.list({ readOnly }).flatMap((tool) => tool.operation === "write" ? [toPrepareMcpTool(tool)] : [toMcpTool(tool)]),
3019
3168
  ...readOnly ? [] : [{
3020
3169
  name: CONFIRM_ACTION_TOOL,
3021
3170
  title: "Confirm Prepared Action",
@@ -3027,40 +3176,41 @@ function createServer(profileName, readOnly) {
3027
3176
  }));
3028
3177
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
3029
3178
  try {
3030
- const context = await createToolExecutionContext(profileName);
3031
3179
  if (request.params.name === CAPABILITIES_TOOL) {
3032
- const visibleTools = registry.list({ readOnly }).filter(isMcpVisible);
3180
+ const context2 = await createToolExecutionContext(profileName);
3181
+ const visibleTools = registry.list({ readOnly });
3033
3182
  const toolCounts = visibleTools.reduce((counts, tool2) => {
3034
3183
  counts[tool2.operation] = (counts[tool2.operation] ?? 0) + 1;
3035
3184
  return counts;
3036
3185
  }, {});
3037
- return result({
3038
- ok: true,
3039
- data: {
3040
- profile: { name: context.profile.name, host: new URL(context.profile.openApiBaseUrl).host, configVersion: context.profile.configVersion },
3041
- readOnly,
3042
- serviceVersion: "0.1.11",
3043
- toolCounts
3044
- }
3045
- });
3186
+ return toMcpReadResult(formatMcpData({
3187
+ profile: { name: context2.profile.name, host: new URL(context2.profile.openApiBaseUrl).host, configVersion: context2.profile.configVersion },
3188
+ toolset,
3189
+ responseMode,
3190
+ modules: [...new Set(visibleTools.map((tool2) => tool2.module))].sort(),
3191
+ readOnly,
3192
+ serviceVersion: "0.1.11",
3193
+ toolCounts
3194
+ }), responseMode);
3046
3195
  }
3047
3196
  if (request.params.name === CONFIRM_ACTION_TOOL) {
3197
+ if (readOnly) throw new AiHubError("AI_HUB_TOOL_NOT_AVAILABLE", `Tool "${CONFIRM_ACTION_TOOL}" is not available in this server session.`);
3048
3198
  const input = request.params.arguments ?? {};
3049
3199
  if (typeof input.confirmationId !== "string" || typeof input.userConfirmation !== "string" || !input.userConfirmation.trim()) {
3050
3200
  throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "confirmationId and a non-empty userConfirmation are required.");
3051
3201
  }
3052
- return result({ ok: true, data: await writeExecutor.confirm(input.confirmationId, input.userConfirmation, context) });
3202
+ const context2 = await createToolExecutionContext(profileName);
3203
+ return result({ ok: true, data: await writeExecutor.confirm(input.confirmationId, input.userConfirmation, context2) });
3053
3204
  }
3054
- const preparedTool = registry.list({ readOnly: false }).find((tool2) => tool2.operation === "write" && prepareToolName(tool2) === request.params.name);
3205
+ const preparedTool = readOnly ? void 0 : registry.list().find((tool2) => tool2.operation === "write" && prepareToolName(tool2) === request.params.name);
3055
3206
  if (preparedTool) {
3056
- return result({ ok: true, data: await writeExecutor.prepare(preparedTool.name, request.params.arguments ?? {}, context) });
3207
+ const context2 = await createToolExecutionContext(profileName);
3208
+ return result({ ok: true, data: await writeExecutor.prepare(preparedTool.name, request.params.arguments ?? {}, context2) });
3057
3209
  }
3058
3210
  const tool = registry.byName(request.params.name, { readOnly });
3059
- if (!isMcpVisible(tool)) {
3060
- throw new AiHubError("AI_HUB_TOOL_NOT_AVAILABLE", `Tool "${request.params.name}" is not available through MCP. Use the corresponding bounded summary or symbol-browsing tool instead.`);
3061
- }
3211
+ const context = await createToolExecutionContext(profileName);
3062
3212
  const data = await registry.execute(request.params.name, request.params.arguments ?? {}, context, { readOnly });
3063
- return toMcpReadResult(formatMcpData(data));
3213
+ return toMcpReadResult(formatMcpData(data), responseMode);
3064
3214
  } catch (error) {
3065
3215
  return toMcpErrorResult(error);
3066
3216
  }
@@ -3078,7 +3228,7 @@ function readOption(args, option) {
3078
3228
  }
3079
3229
  function printUsage() {
3080
3230
  process.stdout.write(
3081
- "AI Hub Agent Trade MCP\n\nUsage:\n ai-hub-trade-mcp [--profile <name>] [--read-only]\n ai-hub-trade-mcp setup --client <client> [--profile <name>]\n\nThe default command starts the local stdio MCP server.\nUse setup to register it with Cursor, Claude Desktop, Claude Code, or Codex.\n"
3231
+ "AI Hub Agent Trade MCP\n\nUsage:\n ai-hub-trade-mcp [--profile <name>] [--toolset <default|full>] [--response-mode <compact|compat>] [--read-only]\n ai-hub-trade-mcp setup --client <client> [--profile <name>] [--toolset <default|full>] [--response-mode <compact|compat>]\n\nThe default command starts the local stdio MCP server.\nUse setup to register it with Cursor, Claude Desktop, Claude Code, Codex, or OpenClaw.\n"
3082
3232
  );
3083
3233
  }
3084
3234
  async function main(argv) {
@@ -3089,6 +3239,8 @@ async function main(argv) {
3089
3239
  if (argv[0] === "setup") {
3090
3240
  const client = readOption(argv.slice(1), "--client");
3091
3241
  const profile = readOption(argv.slice(1), "--profile");
3242
+ const toolset2 = parseMcpToolset(readOption(argv.slice(1), "--toolset"));
3243
+ const responseMode2 = parseMcpResponseMode(readOption(argv.slice(1), "--response-mode"));
3092
3244
  if (!client) {
3093
3245
  printMcpSetupUsage();
3094
3246
  return;
@@ -3098,12 +3250,14 @@ async function main(argv) {
3098
3250
  }
3099
3251
  const entrypoint = process.argv[1];
3100
3252
  if (!entrypoint) throw new AiHubError("AI_HUB_UNEXPECTED_ERROR", "Cannot locate the local MCP server entrypoint for client setup.");
3101
- runMcpSetup({ client, profile, launch: { command: process.execPath, args: [entrypoint] } });
3253
+ runMcpSetup({ client, profile, toolset: toolset2, responseMode: responseMode2, launch: { command: process.execPath, args: [entrypoint] } });
3102
3254
  return;
3103
3255
  }
3104
3256
  const profileName = readOption(argv, "--profile");
3105
3257
  const readOnly = argv.includes("--read-only");
3106
- const server = createServer(profileName, readOnly);
3258
+ const toolset = parseMcpToolset(readOption(argv, "--toolset"));
3259
+ const responseMode = parseMcpResponseMode(readOption(argv, "--response-mode"));
3260
+ const server = createServer(profileName, readOnly, toolset, responseMode);
3107
3261
  await server.connect(new StdioServerTransport());
3108
3262
  }
3109
3263
  function isDirectExecution() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aihubspot/agent-trade-mcp",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Local stdio MCP server for AI Hub spot trading tools",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {