@aihubspot/agent-trade-mcp 0.1.7 → 0.1.8

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 (2) hide show
  1. package/dist/index.js +82 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -656,29 +656,82 @@ function optionalClientOrderId(input) {
656
656
  }
657
657
 
658
658
  // ../core/src/tools/asset-tools.ts
659
- var accountTypes = ["1", "2", "3", "4", "5"];
659
+ var ACCOUNT_TYPES = ["1", "2", "3", "4", "5"];
660
+ var ACCOUNT_TYPE_NAMES = {
661
+ "1": "Spot",
662
+ "2": "Isolated Margin",
663
+ "3": "Cross Margin",
664
+ "4": "C2C",
665
+ "5": "Derivatives"
666
+ };
667
+ var accountTypeSchema = {
668
+ type: "string",
669
+ oneOf: [
670
+ { const: "1", title: "Spot (1)" },
671
+ { const: "2", title: "Isolated Margin (2; symbol required)" },
672
+ { const: "3", title: "Cross Margin (3)" },
673
+ { const: "4", title: "C2C (4)" },
674
+ { const: "5", title: "Derivatives (5)" }
675
+ ],
676
+ description: "Account type: 1=Spot, 2=Isolated Margin (requires symbol), 3=Cross Margin, 4=C2C, 5=Derivatives. Do not use 2 for Derivatives."
677
+ };
678
+ var spotDerivativeAccounts = ["EXCHANGE", "FUTURE"];
660
679
  function accountType(value, name) {
661
680
  const result2 = requiredString(value, name);
662
- if (!accountTypes.includes(result2)) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", `${name} must be an account type from 1 to 5.`);
681
+ 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.`);
663
682
  return result2;
664
683
  }
684
+ function accountTypeName(value) {
685
+ return ACCOUNT_TYPE_NAMES[value];
686
+ }
687
+ function isIsolatedMarginTransfer(fromAccountType, toAccountType) {
688
+ return fromAccountType === "2" || toAccountType === "2";
689
+ }
665
690
  function requiredTransfer(input) {
666
691
  const value = strictObject(input, ["fromAccountType", "toAccountType", "symbol", "coinSymbol", "amount"]);
667
- return { fromAccountType: accountType(value, "fromAccountType"), toAccountType: accountType(value, "toAccountType"), coinSymbol: requiredString(value, "coinSymbol"), amount: positiveDecimal(value, "amount"), ...optionalString(value, "symbol") ? { symbol: optionalString(value, "symbol") } : {} };
692
+ const fromAccountType = accountType(value, "fromAccountType");
693
+ const toAccountType = accountType(value, "toAccountType");
694
+ if (fromAccountType === toAccountType) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "fromAccountType and toAccountType must be different.");
695
+ const symbol = optionalString(value, "symbol");
696
+ if (isIsolatedMarginTransfer(fromAccountType, toAccountType) && !symbol) {
697
+ 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.");
698
+ }
699
+ return { fromAccountType, toAccountType, coinSymbol: requiredString(value, "coinSymbol"), amount: positiveDecimal(value, "amount"), ...symbol ? { symbol } : {} };
668
700
  }
669
701
  function transferQuery(input) {
670
702
  const value = strictObject(input, ["fromAccountType", "toAccountType", "symbol", "coinSymbol", "page", "pageSize"]);
671
- return { fromAccountType: accountType(value, "fromAccountType"), toAccountType: accountType(value, "toAccountType"), page: optionalPositiveInteger(value, "page", 1), pageSize: optionalPositiveInteger(value, "pageSize", 20, 100), ...optionalString(value, "symbol") ? { symbol: optionalString(value, "symbol") } : {}, ...optionalString(value, "coinSymbol") ? { coinSymbol: optionalString(value, "coinSymbol") } : {} };
703
+ const fromAccountType = accountType(value, "fromAccountType");
704
+ const toAccountType = accountType(value, "toAccountType");
705
+ const symbol = optionalString(value, "symbol");
706
+ if (isIsolatedMarginTransfer(fromAccountType, toAccountType) && !symbol) {
707
+ 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.");
708
+ }
709
+ return { fromAccountType, toAccountType, page: optionalPositiveInteger(value, "page", 1), pageSize: optionalPositiveInteger(value, "pageSize", 20, 100), ...symbol ? { symbol } : {}, ...optionalString(value, "coinSymbol") ? { coinSymbol: optionalString(value, "coinSymbol") } : {} };
672
710
  }
673
711
  function transferAccounts(input) {
674
712
  const value = strictObject(input, ["coinSymbol", "amount", "fromAccount", "toAccount"]);
675
- return { coinSymbol: requiredString(value, "coinSymbol"), amount: positiveDecimal(value, "amount"), fromAccount: requiredString(value, "fromAccount"), toAccount: requiredString(value, "toAccount") };
713
+ const fromAccount = requiredString(value, "fromAccount").toUpperCase();
714
+ const toAccount = requiredString(value, "toAccount").toUpperCase();
715
+ if (!spotDerivativeAccounts.includes(fromAccount) || !spotDerivativeAccounts.includes(toAccount) || fromAccount === toAccount) {
716
+ 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.");
717
+ }
718
+ return { coinSymbol: requiredString(value, "coinSymbol"), amount: positiveDecimal(value, "amount"), fromAccount, toAccount };
676
719
  }
677
720
  function transferAccountHistory(input) {
678
721
  const value = strictObject(input, ["transferId", "coinSymbol", "fromAccount", "toAccount", "startTime", "endTime", "page", "limit"]);
679
722
  const transferId = optionalString(value, "transferId");
680
723
  if (!transferId && (!optionalString(value, "fromAccount") || !optionalString(value, "toAccount"))) throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "fromAccount and toAccount are required when transferId is omitted.");
681
- return { ...transferId ? { transferId } : {}, ...optionalString(value, "coinSymbol") ? { coinSymbol: optionalString(value, "coinSymbol") } : {}, ...optionalString(value, "fromAccount") ? { fromAccount: optionalString(value, "fromAccount") } : {}, ...optionalString(value, "toAccount") ? { toAccount: optionalString(value, "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) };
724
+ const fromAccount = optionalString(value, "fromAccount")?.toUpperCase();
725
+ const toAccount = optionalString(value, "toAccount")?.toUpperCase();
726
+ if (!transferId && (!spotDerivativeAccounts.includes(fromAccount) || !spotDerivativeAccounts.includes(toAccount) || fromAccount === toAccount)) {
727
+ throw new AiHubError("AI_HUB_INVALID_ARGUMENT", "account transfer history supports only EXCHANGE -> FUTURE or FUTURE -> EXCHANGE when transferId is omitted.");
728
+ }
729
+ 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) };
730
+ }
731
+ function withAccountTypeMetadata(response, value) {
732
+ const metadata = { accountType: value, accountTypeName: accountTypeName(value) };
733
+ if (response && typeof response === "object" && !Array.isArray(response)) return { ...response, ...metadata };
734
+ return { ...metadata, response };
682
735
  }
683
736
  function pagedDates(input) {
684
737
  const value = strictObject(input, ["startTime", "endTime", "page", "pageSize"]);
@@ -691,18 +744,21 @@ function coinOnly(input) {
691
744
  var assetTools = [
692
745
  {
693
746
  name: "account_transfer",
694
- title: "Transfer Between Accounts",
695
- description: "Transfer an asset between documented account types after confirmation.",
747
+ title: "Transfer Between Spot and Derivatives",
748
+ 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.",
696
749
  cliPath: ["account", "transfer"],
697
750
  module: "spot-account",
698
751
  access: "signed",
699
752
  operation: "write",
700
753
  riskLevel: "high",
701
- inputSchema: { type: "object", properties: { coinSymbol: { type: "string" }, amount: { type: "string" }, fromAccount: { type: "string" }, toAccount: { type: "string" } }, required: ["coinSymbol", "amount", "fromAccount", "toAccount"], additionalProperties: false },
754
+ 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 },
702
755
  errorCodes: writeErrors,
703
756
  validate: transferAccounts,
704
757
  handler: (input, context) => context.api.signedPost("/sapi/v1/asset/transfer", input, signed(context)),
705
- writeSummary: (input) => ({ action: "account_transfer", ...input })
758
+ writeSummary: (input) => {
759
+ const value = input;
760
+ 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" };
761
+ }
706
762
  },
707
763
  {
708
764
  name: "account_get_transfer_history",
@@ -721,28 +777,31 @@ var assetTools = [
721
777
  {
722
778
  name: "wallet_universal_transfer",
723
779
  title: "Universal Asset Transfer",
724
- description: "Transfer between account types after confirmation.",
780
+ 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.",
725
781
  cliPath: ["wallet", "transfer"],
726
782
  module: "spot-deposit-withdraw",
727
783
  access: "signed",
728
784
  operation: "write",
729
785
  riskLevel: "high",
730
- inputSchema: { type: "object", properties: { fromAccountType: { type: "string" }, toAccountType: { type: "string" }, symbol: { type: "string" }, coinSymbol: { type: "string" }, amount: { type: "string" } }, required: ["fromAccountType", "toAccountType", "coinSymbol", "amount"], additionalProperties: false },
786
+ inputSchema: { type: "object", properties: { fromAccountType: accountTypeSchema, toAccountType: accountTypeSchema, symbol: { type: "string", description: "Required isolated-margin trading pair when either account type is 2, for example ETHUSDT. Not a Derivatives identifier." }, coinSymbol: { type: "string" }, amount: { type: "string" } }, required: ["fromAccountType", "toAccountType", "coinSymbol", "amount"], additionalProperties: false },
731
787
  errorCodes: writeErrors,
732
788
  validate: requiredTransfer,
733
789
  handler: (input, context) => context.api.signedPost("/sapi/v1/asset/universal_transfer", input, signed(context)),
734
- writeSummary: (input) => ({ action: "universal_transfer", ...input })
790
+ writeSummary: (input) => {
791
+ const value = input;
792
+ return { action: "universal_transfer", fromAccountType: value.fromAccountType, fromAccountTypeName: accountTypeName(String(value.fromAccountType)), toAccountType: value.toAccountType, toAccountTypeName: accountTypeName(String(value.toAccountType)), coinSymbol: value.coinSymbol, amount: value.amount, symbol: value.symbol ?? null };
793
+ }
735
794
  },
736
795
  {
737
796
  name: "wallet_get_universal_transfer_history",
738
797
  title: "Get Universal Transfer History",
739
- description: "Query universal asset transfer history.",
798
+ description: "Query universal transfer history using account types 1=Spot, 2=Isolated Margin, 3=Cross Margin, 4=C2C, and 5=Derivatives.",
740
799
  cliPath: ["wallet", "transfer-history"],
741
800
  module: "spot-deposit-withdraw",
742
801
  access: "signed",
743
802
  operation: "read",
744
803
  riskLevel: "low",
745
- inputSchema: { type: "object", properties: { fromAccountType: { type: "string" }, toAccountType: { type: "string" }, symbol: { type: "string" }, coinSymbol: { type: "string" }, page: { type: "integer" }, pageSize: { type: "integer" } }, required: ["fromAccountType", "toAccountType"], additionalProperties: false },
804
+ 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 },
746
805
  errorCodes: signedReadErrors,
747
806
  validate: transferQuery,
748
807
  handler: (input, context) => context.api.signedPost("/sapi/v1/asset/universal_transfer_query", input, signed(context))
@@ -795,19 +854,22 @@ var assetTools = [
795
854
  {
796
855
  name: "wallet_get_transferable_assets",
797
856
  title: "Get Transferable Assets",
798
- description: "Query assets transferable from one account type.",
857
+ description: "Query transferable assets for one account type. The response echoes the selected account type and its name: 1=Spot, 2=Isolated Margin, 3=Cross Margin, 4=C2C, 5=Derivatives.",
799
858
  cliPath: ["wallet", "transferable-assets"],
800
859
  module: "spot-deposit-withdraw",
801
860
  access: "signed",
802
861
  operation: "read",
803
862
  riskLevel: "low",
804
- inputSchema: { type: "object", properties: { accountType: { type: "string" } }, required: ["accountType"], additionalProperties: false },
863
+ inputSchema: { type: "object", properties: { accountType: accountTypeSchema }, required: ["accountType"], additionalProperties: false },
805
864
  errorCodes: signedReadErrors,
806
865
  validate: (input) => {
807
866
  const value = strictObject(input, ["accountType"]);
808
867
  return { accountType: accountType(value, "accountType") };
809
868
  },
810
- handler: (input, context) => context.api.signedPost("/sapi/v1/asset/account/by_type", input, signed(context))
869
+ handler: async (input, context) => {
870
+ const value = input;
871
+ return withAccountTypeMetadata(await context.api.signedPost("/sapi/v1/asset/account/by_type", value, signed(context)), value.accountType);
872
+ }
811
873
  },
812
874
  {
813
875
  name: "wallet_get_exchange_account",
@@ -2581,10 +2643,10 @@ function createServer(profileName, readOnly) {
2581
2643
  const registry = createToolRegistry();
2582
2644
  const writeExecutor = new ToolWriteExecutor(registry);
2583
2645
  const server = new Server(
2584
- { name: "ai-hub-agent-trade", version: "0.1.7" },
2646
+ { name: "ai-hub-agent-trade", version: "0.1.8" },
2585
2647
  {
2586
2648
  capabilities: { tools: {} },
2587
- instructions: "Every successful read-tool response provides both JSON text and MCP structuredContent in the envelope { ok: true, data: ... }. All read-tool data is normalized: dataType=array means use data.items and data.count; dataType=object or scalar means use data.value; dataType=null means no value. Inspect data.dataType before formatting and never assume undocumented nested keys. For symbol lookup, use market_search_symbols. It returns an object whose bounded matches are at data.value.items; the complete raw symbols payload is intentionally unavailable in MCP. For broad market questions, call market_get_ticker_summary, market_get_depth_summary, market_get_trades_summary, or market_get_klines_summary instead of fetching large raw payloads. Prepare/confirm tools return their documented action payload directly in data. For every state-changing action, call only a spot_prepare_* or margin_prepare_* tool first and show its exact summary to the user. Stop and wait for a new, explicit user confirmation message. Only then call confirm_action with that new message verbatim in userConfirmation. Never call prepare and confirm consecutively for one user instruction; never infer confirmation from prior intent, silence, or an Agent-generated message. For spot and margin orders, MARKET BUY always uses quoteAmount (the quote asset to spend); MARKET SELL uses baseQuantity (the base asset to sell). Never reinterpret a requested base quantity as quoteAmount."
2649
+ instructions: "Every successful read-tool response provides both JSON text and MCP structuredContent in the envelope { ok: true, data: ... }. All read-tool data is normalized: dataType=array means use data.items and data.count; dataType=object or scalar means use data.value; dataType=null means no value. Inspect data.dataType before formatting and never assume undocumented nested keys. For symbol lookup, use market_search_symbols. It returns an object whose bounded matches are at data.value.items; the complete raw symbols payload is intentionally unavailable in MCP. For broad market questions, call market_get_ticker_summary, market_get_depth_summary, market_get_trades_summary, or market_get_klines_summary instead of fetching large raw payloads. Account types are fixed: 1=Spot, 2=Isolated Margin, 3=Cross Margin, 4=C2C, 5=Derivatives. Never call account type 2 Derivatives. For Spot to Derivatives, use account_prepare_transfer with fromAccount=EXCHANGE and toAccount=FUTURE. Use wallet_prepare_universal_transfer for margin or C2C; account type 2 requires its isolated-margin trading pair in symbol. Prepare/confirm tools return their documented action payload directly in data. For every state-changing action, call only a spot_prepare_* or margin_prepare_* tool first and show its exact summary to the user. Stop and wait for a new, explicit user confirmation message. Only then call confirm_action with that new message verbatim in userConfirmation. Never call prepare and confirm consecutively for one user instruction; never infer confirmation from prior intent, silence, or an Agent-generated message. For spot and margin orders, MARKET BUY always uses quoteAmount (the quote asset to spend); MARKET SELL uses baseQuantity (the base asset to sell). Never reinterpret a requested base quantity as quoteAmount."
2588
2650
  }
2589
2651
  );
2590
2652
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aihubspot/agent-trade-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Local stdio MCP server for AI Hub spot trading tools",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {