@aihubspot/agent-trade-mcp 0.1.11 → 0.1.12

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 +9 -7
  2. package/dist/index.js +346 -218
  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,6 +15,8 @@ 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 codex --profile default --toolset full
19
+ ai-hub-trade-mcp setup --client codex --profile default --response-mode compat
18
20
  ```
19
21
 
20
22
  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`.
@@ -33,7 +35,7 @@ All read tools return `{ "ok": true, "data": ... }` in one of these stable forms
33
35
  - Objects: `{ "dataType": "object", "value": { ... } }`
34
36
  - Scalars: `{ "dataType": "scalar", "value": "..." }`
35
37
 
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.
38
+ 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
39
 
38
40
  ## Bounded market analysis
39
41
 
@@ -41,11 +43,11 @@ Use these tools for requests that would otherwise return a broad market payload:
41
43
 
42
44
  - `market_get_symbol_overview`: counts by quote asset and a small sample. Use this first for a generic request to list trading pairs.
43
45
  - `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.
46
+ - `market_search_symbols`: required-keyword lookup with at most 50 basic matching rows. Do not use it for a generic pair list.
45
47
  - `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.
48
+ - `market_get_ticker_summary`: watchlist, gainers, losers, and quote-volume leaders under one total result budget (20 by default, 50 maximum).
49
+ - `market_get_depth_summary`: best bid/ask, spread, and up to 50 levels per side.
48
50
  - `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`.
51
+ - `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
52
 
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.
53
+ 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";
@@ -2805,11 +2900,11 @@ function getMcpClientConfigPath(client, value = runtime()) {
2805
2900
  }
2806
2901
  return path.join(value.xdgConfigHome ?? path.join(value.home, ".config"), "Claude", "claude_desktop_config.json");
2807
2902
  }
2808
- function buildServerSpec(profile, launch) {
2903
+ function buildServerSpec(profile, toolset, responseMode, launch) {
2809
2904
  return {
2810
2905
  name: serverName(profile),
2811
2906
  command: launch.command,
2812
- args: [...launch.args, ...profile ? ["--profile", profile] : []]
2907
+ args: [...launch.args, ...profile ? ["--profile", profile] : [], "--toolset", toolset, "--response-mode", responseMode]
2813
2908
  };
2814
2909
  }
2815
2910
  function serverName(profile) {
@@ -2909,11 +3004,13 @@ function runMcpSetup(options, value = runtime()) {
2909
3004
  throw new Error(`Unknown MCP client "${options.client}". Supported clients: ${SUPPORTED_MCP_CLIENTS.join(", ")}.`);
2910
3005
  }
2911
3006
  const profile = options.profile ? validateProfileName(options.profile) : void 0;
2912
- adapter.install(buildServerSpec(profile, options.launch), value);
3007
+ const toolset = parseMcpToolset(options.toolset ?? DEFAULT_MCP_TOOLSET);
3008
+ const responseMode = parseMcpResponseMode(options.responseMode ?? DEFAULT_MCP_RESPONSE_MODE);
3009
+ adapter.install(buildServerSpec(profile, toolset, responseMode, options.launch), value);
2913
3010
  }
2914
3011
  function printMcpSetupUsage() {
2915
3012
  process.stdout.write(
2916
- `Usage: ${MCP_BINARY} setup --client <client> [--profile <name>]
3013
+ `Usage: ${MCP_BINARY} setup --client <client> [--profile <name>] [--toolset <default|full>] [--response-mode <compact|compat>]
2917
3014
 
2918
3015
  Supported clients:
2919
3016
  ` + SUPPORTED_MCP_CLIENTS.map((client) => ` ${client.padEnd(16)} ${MCP_CLIENT_NAMES[client]}`).join("\n") + "\n"
@@ -2945,9 +3042,37 @@ var READ_RESULT_OUTPUT_SCHEMA = {
2945
3042
  function result(data) {
2946
3043
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
2947
3044
  }
2948
- function toMcpReadResult(data) {
3045
+ function numberField(value, ...names) {
3046
+ for (const name of names) {
3047
+ const candidate = value[name];
3048
+ if (typeof candidate === "number" && Number.isFinite(candidate)) return candidate;
3049
+ }
3050
+ return void 0;
3051
+ }
3052
+ function compactReadText(data) {
3053
+ const envelope = data;
3054
+ const dataType = typeof envelope?.dataType === "string" ? envelope.dataType : "unknown";
3055
+ const compact = { dataType };
3056
+ if (typeof envelope?.count === "number") compact.count = envelope.count;
3057
+ if (dataType === "object" && envelope.value && typeof envelope.value === "object" && !Array.isArray(envelope.value)) {
3058
+ const value = envelope.value;
3059
+ const items = Array.isArray(value.items) ? value.items : void 0;
3060
+ const returnedCount = numberField(value, "returnedCount", "returnedSymbols") ?? items?.length;
3061
+ const totalCount = numberField(value, "totalCount", "totalSymbols", "matchedSymbols");
3062
+ if (returnedCount !== void 0) compact.returnedCount = returnedCount;
3063
+ if (totalCount !== void 0) compact.totalCount = totalCount;
3064
+ if (typeof value.nextOffset === "number" || value.nextOffset === null) compact.nextOffset = value.nextOffset;
3065
+ if (value.truncated === true) compact.truncated = true;
3066
+ }
3067
+ compact.summary = "Full result is available in structuredContent.";
3068
+ return JSON.stringify({ ok: true, data: compact });
3069
+ }
3070
+ function toMcpReadResult(data, responseMode = DEFAULT_MCP_RESPONSE_MODE) {
2949
3071
  const payload = { ok: true, data };
2950
- return { content: [{ type: "text", text: JSON.stringify(payload) }], structuredContent: payload };
3072
+ return {
3073
+ content: [{ type: "text", text: responseMode === "compat" ? JSON.stringify(payload) : compactReadText(data) }],
3074
+ structuredContent: payload
3075
+ };
2951
3076
  }
2952
3077
  function formatMcpData(data) {
2953
3078
  if (Array.isArray(data)) {
@@ -2980,9 +3105,6 @@ function toMcpTool(tool) {
2980
3105
  }
2981
3106
  };
2982
3107
  }
2983
- function isMcpVisible(tool) {
2984
- return tool.mcpVisible !== false;
2985
- }
2986
3108
  function prepareToolName(tool) {
2987
3109
  const [prefix, ...rest] = tool.name.split("_");
2988
3110
  return `${prefix ?? "spot"}_prepare_${rest.join("_")}`;
@@ -2996,8 +3118,9 @@ function toPrepareMcpTool(tool) {
2996
3118
  annotations: { readOnlyHint: false, destructiveHint: tool.riskLevel === "high", idempotentHint: true, openWorldHint: false }
2997
3119
  };
2998
3120
  }
2999
- function createServer(profileName, readOnly) {
3000
- const registry = createToolRegistry();
3121
+ function createServer(profileName, readOnly, toolset = DEFAULT_MCP_TOOLSET, responseMode = DEFAULT_MCP_RESPONSE_MODE) {
3122
+ const allTools2 = createToolRegistry();
3123
+ const registry = createToolRegistry(selectMcpToolset(allTools2.list(), toolset));
3001
3124
  const writeExecutor = new ToolWriteExecutor(registry);
3002
3125
  const server = new Server(
3003
3126
  { name: "ai-hub-agent-trade", version: "0.1.11" },
@@ -3015,7 +3138,7 @@ function createServer(profileName, readOnly) {
3015
3138
  inputSchema: { type: "object", additionalProperties: false },
3016
3139
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3017
3140
  },
3018
- ...registry.list({ readOnly }).filter(isMcpVisible).flatMap((tool) => tool.operation === "write" ? [toPrepareMcpTool(tool)] : [toMcpTool(tool)]),
3141
+ ...registry.list({ readOnly }).flatMap((tool) => tool.operation === "write" ? [toPrepareMcpTool(tool)] : [toMcpTool(tool)]),
3019
3142
  ...readOnly ? [] : [{
3020
3143
  name: CONFIRM_ACTION_TOOL,
3021
3144
  title: "Confirm Prepared Action",
@@ -3027,40 +3150,41 @@ function createServer(profileName, readOnly) {
3027
3150
  }));
3028
3151
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
3029
3152
  try {
3030
- const context = await createToolExecutionContext(profileName);
3031
3153
  if (request.params.name === CAPABILITIES_TOOL) {
3032
- const visibleTools = registry.list({ readOnly }).filter(isMcpVisible);
3154
+ const context2 = await createToolExecutionContext(profileName);
3155
+ const visibleTools = registry.list({ readOnly });
3033
3156
  const toolCounts = visibleTools.reduce((counts, tool2) => {
3034
3157
  counts[tool2.operation] = (counts[tool2.operation] ?? 0) + 1;
3035
3158
  return counts;
3036
3159
  }, {});
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
- });
3160
+ return toMcpReadResult(formatMcpData({
3161
+ profile: { name: context2.profile.name, host: new URL(context2.profile.openApiBaseUrl).host, configVersion: context2.profile.configVersion },
3162
+ toolset,
3163
+ responseMode,
3164
+ modules: [...new Set(visibleTools.map((tool2) => tool2.module))].sort(),
3165
+ readOnly,
3166
+ serviceVersion: "0.1.11",
3167
+ toolCounts
3168
+ }), responseMode);
3046
3169
  }
3047
3170
  if (request.params.name === CONFIRM_ACTION_TOOL) {
3171
+ if (readOnly) throw new AiHubError("AI_HUB_TOOL_NOT_AVAILABLE", `Tool "${CONFIRM_ACTION_TOOL}" is not available in this server session.`);
3048
3172
  const input = request.params.arguments ?? {};
3049
3173
  if (typeof input.confirmationId !== "string" || typeof input.userConfirmation !== "string" || !input.userConfirmation.trim()) {
3050
3174
  throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "confirmationId and a non-empty userConfirmation are required.");
3051
3175
  }
3052
- return result({ ok: true, data: await writeExecutor.confirm(input.confirmationId, input.userConfirmation, context) });
3176
+ const context2 = await createToolExecutionContext(profileName);
3177
+ return result({ ok: true, data: await writeExecutor.confirm(input.confirmationId, input.userConfirmation, context2) });
3053
3178
  }
3054
- const preparedTool = registry.list({ readOnly: false }).find((tool2) => tool2.operation === "write" && prepareToolName(tool2) === request.params.name);
3179
+ const preparedTool = readOnly ? void 0 : registry.list().find((tool2) => tool2.operation === "write" && prepareToolName(tool2) === request.params.name);
3055
3180
  if (preparedTool) {
3056
- return result({ ok: true, data: await writeExecutor.prepare(preparedTool.name, request.params.arguments ?? {}, context) });
3181
+ const context2 = await createToolExecutionContext(profileName);
3182
+ return result({ ok: true, data: await writeExecutor.prepare(preparedTool.name, request.params.arguments ?? {}, context2) });
3057
3183
  }
3058
3184
  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
- }
3185
+ const context = await createToolExecutionContext(profileName);
3062
3186
  const data = await registry.execute(request.params.name, request.params.arguments ?? {}, context, { readOnly });
3063
- return toMcpReadResult(formatMcpData(data));
3187
+ return toMcpReadResult(formatMcpData(data), responseMode);
3064
3188
  } catch (error) {
3065
3189
  return toMcpErrorResult(error);
3066
3190
  }
@@ -3078,7 +3202,7 @@ function readOption(args, option) {
3078
3202
  }
3079
3203
  function printUsage() {
3080
3204
  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"
3205
+ "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, or Codex.\n"
3082
3206
  );
3083
3207
  }
3084
3208
  async function main(argv) {
@@ -3089,6 +3213,8 @@ async function main(argv) {
3089
3213
  if (argv[0] === "setup") {
3090
3214
  const client = readOption(argv.slice(1), "--client");
3091
3215
  const profile = readOption(argv.slice(1), "--profile");
3216
+ const toolset2 = parseMcpToolset(readOption(argv.slice(1), "--toolset"));
3217
+ const responseMode2 = parseMcpResponseMode(readOption(argv.slice(1), "--response-mode"));
3092
3218
  if (!client) {
3093
3219
  printMcpSetupUsage();
3094
3220
  return;
@@ -3098,12 +3224,14 @@ async function main(argv) {
3098
3224
  }
3099
3225
  const entrypoint = process.argv[1];
3100
3226
  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] } });
3227
+ runMcpSetup({ client, profile, toolset: toolset2, responseMode: responseMode2, launch: { command: process.execPath, args: [entrypoint] } });
3102
3228
  return;
3103
3229
  }
3104
3230
  const profileName = readOption(argv, "--profile");
3105
3231
  const readOnly = argv.includes("--read-only");
3106
- const server = createServer(profileName, readOnly);
3232
+ const toolset = parseMcpToolset(readOption(argv, "--toolset"));
3233
+ const responseMode = parseMcpResponseMode(readOption(argv, "--response-mode"));
3234
+ const server = createServer(profileName, readOnly, toolset, responseMode);
3107
3235
  await server.connect(new StdioServerTransport());
3108
3236
  }
3109
3237
  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.12",
4
4
  "description": "Local stdio MCP server for AI Hub spot trading tools",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {