@alfe.ai/ctrader-mcp 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/server.js +31 -31
  2. package/package.json +2 -2
package/dist/server.js CHANGED
@@ -1187,7 +1187,7 @@ async function withLiveSubscription(pool, numericAccountId, symbolId, kind, hand
1187
1187
  * required-when-multiple rule at runtime so the model gets a helpful listing
1188
1188
  * instead of a bare validation error.
1189
1189
  */
1190
- const accountIdField = z.string().optional().describe("ctidTraderAccountId of the account to act on (from get_accounts). Optional when exactly one account is connected; REQUIRED when several are — this tool never defaults to an arbitrary account.");
1190
+ const accountIdField = z.coerce.string().optional().describe("ctidTraderAccountId of the account to act on (from get_accounts). Optional when exactly one account is connected; REQUIRED when several are — this tool never defaults to an arbitrary account.");
1191
1191
  function registerTools(server, pool) {
1192
1192
  const register = server.registerTool.bind(server);
1193
1193
  register("get_accounts", {
@@ -1323,9 +1323,9 @@ function registerTools(server, pool) {
1323
1323
  description: "Get recent OHLC candles (trendbars) for a symbol on a connected cTrader account. Specify symbolId (from get_symbols), a period, and how many bars. Prices are returned as real decimal prices. Pass `accountId` when several accounts are connected.",
1324
1324
  inputSchema: {
1325
1325
  accountId: accountIdField,
1326
- symbolId: z.number().int().positive().describe("The symbol id from get_symbols"),
1326
+ symbolId: z.coerce.number().int().positive().describe("The symbol id from get_symbols"),
1327
1327
  period: z.enum(Object.keys(TRENDBAR_PERIOD)).default("H1").describe("Candle period: M1, M5, M15, M30, H1, H4, D1, W1, MN1, etc."),
1328
- count: z.number().int().min(1).max(1e3).default(50).describe("Number of most-recent bars to return (1-1000)")
1328
+ count: z.coerce.number().int().min(1).max(1e3).default(50).describe("Number of most-recent bars to return (1-1000)")
1329
1329
  }
1330
1330
  }, async (args) => {
1331
1331
  const resolved = resolveAccount(pool, args.accountId);
@@ -1352,7 +1352,7 @@ function registerTools(server, pool) {
1352
1352
  description: "Get full trading details for one symbol on a connected cTrader account: price digits, pip position, lot size, min/max/step volume (in lots), swap rates, and short-selling availability. Pass `accountId` when several accounts are connected.",
1353
1353
  inputSchema: {
1354
1354
  accountId: accountIdField,
1355
- symbolId: z.number().int().positive().describe("Symbol id from get_symbols")
1355
+ symbolId: z.coerce.number().int().positive().describe("Symbol id from get_symbols")
1356
1356
  }
1357
1357
  }, async (args) => {
1358
1358
  const resolved = resolveAccount(pool, args.accountId);
@@ -1385,8 +1385,8 @@ function registerTools(server, pool) {
1385
1385
  description: "Get the LIVE bid/ask for a symbol on a connected cTrader account via a brief spot subscription (ticks merge until both sides are seen, up to waitMs). Returns bid, ask, and spread as real decimal prices. Pass `accountId` when several accounts are connected.",
1386
1386
  inputSchema: {
1387
1387
  accountId: accountIdField,
1388
- symbolId: z.number().int().positive().describe("Symbol id from get_symbols"),
1389
- waitMs: z.number().int().min(200).max(1e4).default(3e3).describe("Max milliseconds to wait for both bid and ask ticks (returns early once both are seen)")
1388
+ symbolId: z.coerce.number().int().positive().describe("Symbol id from get_symbols"),
1389
+ waitMs: z.coerce.number().int().min(200).max(1e4).default(3e3).describe("Max milliseconds to wait for both bid and ask ticks (returns early once both are seen)")
1390
1390
  }
1391
1391
  }, async (args) => {
1392
1392
  const resolved = resolveAccount(pool, args.accountId);
@@ -1428,9 +1428,9 @@ function registerTools(server, pool) {
1428
1428
  description: "Get the LIVE Level 2 order book (depth of market) for a symbol on a connected cTrader account. Subscribes briefly, assembles the bid/ask ladder (sizes aggregated per price level), then unsubscribes. Returns bids (descending) and asks (ascending) with sizes in base-asset units and lots, plus best bid/ask and spread. Not every broker/symbol publishes depth — a NO_DEPTH_DATA error means none arrived. Pass `accountId` when several accounts are connected.",
1429
1429
  inputSchema: {
1430
1430
  accountId: accountIdField,
1431
- symbolId: z.number().int().positive().describe("Symbol id from get_symbols"),
1432
- levels: z.number().int().min(1).max(50).default(10).describe("Max price levels per side to return"),
1433
- collectMs: z.number().int().min(200).max(5e3).default(1e3).describe("How long to collect depth events before snapshotting (the full book arrives on subscribe; longer windows fold in more updates)")
1431
+ symbolId: z.coerce.number().int().positive().describe("Symbol id from get_symbols"),
1432
+ levels: z.coerce.number().int().min(1).max(50).default(10).describe("Max price levels per side to return"),
1433
+ collectMs: z.coerce.number().int().min(200).max(5e3).default(1e3).describe("How long to collect depth events before snapshotting (the full book arrives on subscribe; longer windows fold in more updates)")
1434
1434
  }
1435
1435
  }, async (args) => {
1436
1436
  const resolved = resolveAccount(pool, args.accountId);
@@ -1503,7 +1503,7 @@ function registerTools(server, pool) {
1503
1503
  description: "Get historical tick-by-tick prices (BID or ASK side) for a symbol on a connected cTrader account. Defaults to the last 5 minutes — keep ranges short, tick volumes are large; `hasMore` signals truncation. Ticks are returned newest-first with real decimal prices.",
1504
1504
  inputSchema: {
1505
1505
  accountId: accountIdField,
1506
- symbolId: z.number().int().positive().describe("Symbol id from get_symbols"),
1506
+ symbolId: z.coerce.number().int().positive().describe("Symbol id from get_symbols"),
1507
1507
  type: z.enum(["BID", "ASK"]).default("BID").describe("Which side's ticks to fetch"),
1508
1508
  from: z.string().optional().describe("ISO 8601 range start (default: 5 minutes before `to`)"),
1509
1509
  to: z.string().optional().describe("ISO 8601 range end (default: now)")
@@ -1542,7 +1542,7 @@ function registerTools(server, pool) {
1542
1542
  accountId: accountIdField,
1543
1543
  from: z.string().optional().describe("ISO 8601 range start (default: 7 days before `to`)"),
1544
1544
  to: z.string().optional().describe("ISO 8601 range end (default: now)"),
1545
- maxRows: z.number().int().min(1).max(1e3).default(100).describe("Max deals to return")
1545
+ maxRows: z.coerce.number().int().min(1).max(1e3).default(100).describe("Max deals to return")
1546
1546
  }
1547
1547
  }, async (args) => {
1548
1548
  const resolved = resolveAccount(pool, args.accountId);
@@ -1620,8 +1620,8 @@ function registerTools(server, pool) {
1620
1620
  description: "Get the margin that would be required to open BUY/SELL positions of given sizes (in lots) on a symbol, in the account's deposit currency. Useful before place_order to check affordability. Pass `accountId` when several accounts are connected.",
1621
1621
  inputSchema: {
1622
1622
  accountId: accountIdField,
1623
- symbolId: z.number().int().positive().describe("Symbol id from get_symbols"),
1624
- volumesLots: z.array(z.number().positive()).min(1).max(10).describe("Position sizes in lots to quote margin for, e.g. [0.1, 0.5, 1]")
1623
+ symbolId: z.coerce.number().int().positive().describe("Symbol id from get_symbols"),
1624
+ volumesLots: z.array(z.coerce.number().positive()).min(1).max(10).describe("Position sizes in lots to quote margin for, e.g. [0.1, 0.5, 1]")
1625
1625
  }
1626
1626
  }, async (args) => {
1627
1627
  const resolved = resolveAccount(pool, args.accountId);
@@ -1692,7 +1692,7 @@ function registerTools(server, pool) {
1692
1692
  description: "Place a MARKET, LIMIT, STOP, or STOP_LIMIT order on a connected cTrader account. Volume is in LOTS and is converted to the symbol's protocol volume (validated against min/max/step). LIMIT requires limitPrice; STOP requires stopPrice; STOP_LIMIT requires stopPrice + slippageInPoints. SL/TP: pass ABSOLUTE prices via stopLoss/takeProfit, or price DISTANCES via stopLossDistance/takeProfitDistance (required for a trailing stop). timeInForce defaults to GOOD_TILL_CANCEL; GOOD_TILL_DATE requires expiresAt. Pass `accountId` when several accounts are connected. WARNING: on a live account this moves real money.",
1693
1693
  inputSchema: {
1694
1694
  accountId: accountIdField,
1695
- symbolId: z.number().int().positive().describe("Symbol id from get_symbols"),
1695
+ symbolId: z.coerce.number().int().positive().describe("Symbol id from get_symbols"),
1696
1696
  side: z.enum(["BUY", "SELL"]).describe("Trade side"),
1697
1697
  orderType: z.enum([
1698
1698
  "MARKET",
@@ -1700,14 +1700,14 @@ function registerTools(server, pool) {
1700
1700
  "STOP",
1701
1701
  "STOP_LIMIT"
1702
1702
  ]).default("MARKET").describe("MARKET fills now; LIMIT rests at limitPrice; STOP triggers at stopPrice; STOP_LIMIT triggers at stopPrice with a max slippage of slippageInPoints"),
1703
- volumeLots: z.number().positive().describe("Order size in lots (e.g. 0.1). Converted to protocol volume via the symbol lotSize."),
1704
- limitPrice: z.number().positive().optional().describe("Required for LIMIT orders — the price to rest the order at"),
1705
- stopPrice: z.number().positive().optional().describe("Required for STOP and STOP_LIMIT orders — the trigger price"),
1706
- slippageInPoints: z.number().int().min(0).optional().describe("Required for STOP_LIMIT — max slippage from stopPrice, in points"),
1707
- stopLoss: z.number().positive().optional().describe("Absolute stop-loss price (mutually exclusive with stopLossDistance)"),
1708
- takeProfit: z.number().positive().optional().describe("Absolute take-profit price (mutually exclusive with takeProfitDistance)"),
1709
- stopLossDistance: z.number().positive().optional().describe("Stop-loss as a price DISTANCE from entry (e.g. 0.0010 = 10 pips on a 5-digit FX pair). Required when trailingStopLoss is true."),
1710
- takeProfitDistance: z.number().positive().optional().describe("Take-profit as a price DISTANCE from entry"),
1703
+ volumeLots: z.coerce.number().positive().describe("Order size in lots (e.g. 0.1). Converted to protocol volume via the symbol lotSize."),
1704
+ limitPrice: z.coerce.number().positive().optional().describe("Required for LIMIT orders — the price to rest the order at"),
1705
+ stopPrice: z.coerce.number().positive().optional().describe("Required for STOP and STOP_LIMIT orders — the trigger price"),
1706
+ slippageInPoints: z.coerce.number().int().min(0).optional().describe("Required for STOP_LIMIT — max slippage from stopPrice, in points"),
1707
+ stopLoss: z.coerce.number().positive().optional().describe("Absolute stop-loss price (mutually exclusive with stopLossDistance)"),
1708
+ takeProfit: z.coerce.number().positive().optional().describe("Absolute take-profit price (mutually exclusive with takeProfitDistance)"),
1709
+ stopLossDistance: z.coerce.number().positive().optional().describe("Stop-loss as a price DISTANCE from entry (e.g. 0.0010 = 10 pips on a 5-digit FX pair). Required when trailingStopLoss is true."),
1710
+ takeProfitDistance: z.coerce.number().positive().optional().describe("Take-profit as a price DISTANCE from entry"),
1711
1711
  trailingStopLoss: z.boolean().optional().describe("Trail the stop-loss as price moves in your favour. Requires stopLossDistance (not an absolute stopLoss)."),
1712
1712
  timeInForce: z.enum([
1713
1713
  "GOOD_TILL_CANCEL",
@@ -1777,12 +1777,12 @@ function registerTools(server, pool) {
1777
1777
  description: "Modify an existing order on a connected cTrader account. For a PENDING order (by orderId) you can change limitPrice/stopPrice/SL/TP. For an OPEN position's protection, pass positionId to set stopLoss/takeProfit. Provide exactly one of orderId or positionId. Pass `accountId` when several accounts are connected.",
1778
1778
  inputSchema: {
1779
1779
  accountId: accountIdField,
1780
- orderId: z.number().int().positive().optional().describe("Pending order id (from get_orders) to amend"),
1781
- positionId: z.number().int().positive().optional().describe("Open position id (from get_positions) to set SL/TP on"),
1782
- limitPrice: z.number().positive().optional().describe("New limit price (pending order only)"),
1783
- stopPrice: z.number().positive().optional().describe("New stop price (pending order only)"),
1784
- stopLoss: z.number().positive().optional().describe("New absolute stop-loss price"),
1785
- takeProfit: z.number().positive().optional().describe("New absolute take-profit price")
1780
+ orderId: z.coerce.number().int().positive().optional().describe("Pending order id (from get_orders) to amend"),
1781
+ positionId: z.coerce.number().int().positive().optional().describe("Open position id (from get_positions) to set SL/TP on"),
1782
+ limitPrice: z.coerce.number().positive().optional().describe("New limit price (pending order only)"),
1783
+ stopPrice: z.coerce.number().positive().optional().describe("New stop price (pending order only)"),
1784
+ stopLoss: z.coerce.number().positive().optional().describe("New absolute stop-loss price"),
1785
+ takeProfit: z.coerce.number().positive().optional().describe("New absolute take-profit price")
1786
1786
  }
1787
1787
  }, async (args) => {
1788
1788
  const resolved = resolveAccount(pool, args.accountId);
@@ -1828,8 +1828,8 @@ function registerTools(server, pool) {
1828
1828
  description: "Close an open position (fully or partially) on a connected cTrader account. Pass the positionId from get_positions. volumeLots defaults to the full position size when omitted. Pass `accountId` when several accounts are connected.",
1829
1829
  inputSchema: {
1830
1830
  accountId: accountIdField,
1831
- positionId: z.number().int().positive().describe("Position id from get_positions"),
1832
- volumeLots: z.number().positive().optional().describe("Lots to close; omit to close the whole position")
1831
+ positionId: z.coerce.number().int().positive().describe("Position id from get_positions"),
1832
+ volumeLots: z.coerce.number().positive().optional().describe("Lots to close; omit to close the whole position")
1833
1833
  }
1834
1834
  }, async (args) => {
1835
1835
  const resolved = resolveAccount(pool, args.accountId);
@@ -1869,7 +1869,7 @@ function registerTools(server, pool) {
1869
1869
  description: "Cancel a pending (not-yet-filled) order on a connected cTrader account. Pass the orderId from get_orders. Pass `accountId` when several accounts are connected.",
1870
1870
  inputSchema: {
1871
1871
  accountId: accountIdField,
1872
- orderId: z.number().int().positive().describe("Pending order id from get_orders")
1872
+ orderId: z.coerce.number().int().positive().describe("Pending order id from get_orders")
1873
1873
  }
1874
1874
  }, async (args) => {
1875
1875
  const resolved = resolveAccount(pool, args.accountId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/ctrader-mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "cTrader MCP server — full trading (market/limit/stop/stop-limit orders, trailing stops), Level 2 depth, live quotes, trade history, PnL & margin over the cTrader Open API (protobuf/TLS)",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",
@@ -21,7 +21,7 @@
21
21
  "@modelcontextprotocol/sdk": "^1.29.0",
22
22
  "protobufjs": "^8.7.0",
23
23
  "zod": "^4.0.5",
24
- "@alfe.ai/agent-api-client": "0.11.2",
24
+ "@alfe.ai/agent-api-client": "0.13.0",
25
25
  "@alfe.ai/config": "0.3.0"
26
26
  },
27
27
  "license": "UNLICENSED",