@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.
- package/README.md +9 -7
- package/dist/index.js +346 -218
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
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
|
|
639
|
-
for (const key of Object.keys(
|
|
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
|
|
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:
|
|
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:
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
1599
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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 }).
|
|
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
|
|
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
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
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
|
-
|
|
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(
|
|
3179
|
+
const preparedTool = readOnly ? void 0 : registry.list().find((tool2) => tool2.operation === "write" && prepareToolName(tool2) === request.params.name);
|
|
3055
3180
|
if (preparedTool) {
|
|
3056
|
-
|
|
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
|
-
|
|
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
|
|
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() {
|