flash-trade-mcp 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +83 -70
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21063,7 +21063,7 @@ var require_fallback = __commonJS((exports, module) => {
21063
21063
 
21064
21064
  // node_modules/bufferutil/index.js
21065
21065
  var require_bufferutil = __commonJS((exports, module) => {
21066
- var __dirname = "/Users/kalebrupe/Desktop/flash-trade-MCP/mcp/node_modules/bufferutil";
21066
+ var __dirname = "/home/runner/work/flash-trade-MCP/flash-trade-MCP/mcp/node_modules/bufferutil";
21067
21067
  try {
21068
21068
  module.exports = require_node_gyp_build2()(__dirname);
21069
21069
  } catch (e) {
@@ -21479,7 +21479,7 @@ var require_fallback2 = __commonJS((exports, module) => {
21479
21479
 
21480
21480
  // node_modules/utf-8-validate/index.js
21481
21481
  var require_utf_8_validate = __commonJS((exports, module) => {
21482
- var __dirname = "/Users/kalebrupe/Desktop/flash-trade-MCP/mcp/node_modules/utf-8-validate";
21482
+ var __dirname = "/home/runner/work/flash-trade-MCP/flash-trade-MCP/mcp/node_modules/utf-8-validate";
21483
21483
  try {
21484
21484
  module.exports = require_node_gyp_build2()(__dirname);
21485
21485
  } catch (e) {
@@ -49053,6 +49053,15 @@ function loadConfig() {
49053
49053
  if (!apiBaseUrl) {
49054
49054
  throw new Error("FLASH_API_URL environment variable is required");
49055
49055
  }
49056
+ let parsed;
49057
+ try {
49058
+ parsed = new URL(apiBaseUrl);
49059
+ } catch {
49060
+ throw new Error(`FLASH_API_URL is not a valid URL: ${apiBaseUrl}`);
49061
+ }
49062
+ if (parsed.protocol !== "https:") {
49063
+ console.error(`[flash-trade-mcp] WARNING: FLASH_API_URL uses ${parsed.protocol} — HTTPS is strongly recommended for production`);
49064
+ }
49056
49065
  return {
49057
49066
  apiBaseUrl: apiBaseUrl.replace(/\/$/, ""),
49058
49067
  timeoutMs: parseInt(process.env.FLASH_API_TIMEOUT ?? "30000", 10),
@@ -49069,8 +49078,8 @@ class FlashApiError extends Error {
49069
49078
  super(`Flash API error [${statusCode}] ${endpoint}: ${message}`);
49070
49079
  this.statusCode = statusCode;
49071
49080
  this.endpoint = endpoint;
49072
- this.responseBody = responseBody;
49073
49081
  this.name = "FlashApiError";
49082
+ this.responseBody = responseBody?.slice(0, 500);
49074
49083
  }
49075
49084
  }
49076
49085
 
@@ -49137,51 +49146,51 @@ class FlashApiClient {
49137
49146
  return this.get("/markets");
49138
49147
  }
49139
49148
  async getMarket(pubkey) {
49140
- return this.get(`/markets/${pubkey}`);
49149
+ return this.get(`/markets/${encodeURIComponent(pubkey)}`);
49141
49150
  }
49142
49151
  async getPools() {
49143
49152
  return this.get("/pools");
49144
49153
  }
49145
49154
  async getPool(pubkey) {
49146
- return this.get(`/pools/${pubkey}`);
49155
+ return this.get(`/pools/${encodeURIComponent(pubkey)}`);
49147
49156
  }
49148
49157
  async getCustodies() {
49149
49158
  return this.get("/custodies");
49150
49159
  }
49151
49160
  async getCustody(pubkey) {
49152
- return this.get(`/custodies/${pubkey}`);
49161
+ return this.get(`/custodies/${encodeURIComponent(pubkey)}`);
49153
49162
  }
49154
49163
  async getPrices() {
49155
49164
  return this.get("/prices");
49156
49165
  }
49157
49166
  async getPrice(symbol2) {
49158
- return this.get(`/prices/${symbol2}`);
49167
+ return this.get(`/prices/${encodeURIComponent(symbol2)}`);
49159
49168
  }
49160
49169
  async getPositions(owner) {
49161
- const query = owner ? `?owner=${owner}` : "";
49170
+ const query = owner ? `?owner=${encodeURIComponent(owner)}` : "";
49162
49171
  return this.get(`/positions${query}`);
49163
49172
  }
49164
49173
  async getPosition(pubkey) {
49165
- return this.get(`/positions/${pubkey}`);
49174
+ return this.get(`/positions/${encodeURIComponent(pubkey)}`);
49166
49175
  }
49167
49176
  async getOwnerPositions(owner, includePnlInLeverage = false) {
49168
- return this.get(`/positions/owner/${owner}?includePnlInLeverageDisplay=${includePnlInLeverage}`);
49177
+ return this.get(`/positions/owner/${encodeURIComponent(owner)}?includePnlInLeverageDisplay=${includePnlInLeverage}`);
49169
49178
  }
49170
49179
  async getOrders(owner) {
49171
- const query = owner ? `?owner=${owner}` : "";
49180
+ const query = owner ? `?owner=${encodeURIComponent(owner)}` : "";
49172
49181
  return this.get(`/orders${query}`);
49173
49182
  }
49174
49183
  async getOrder(pubkey) {
49175
- return this.get(`/orders/${pubkey}`);
49184
+ return this.get(`/orders/${encodeURIComponent(pubkey)}`);
49176
49185
  }
49177
49186
  async getOwnerOrders(owner) {
49178
- return this.get(`/orders/owner/${owner}`);
49187
+ return this.get(`/orders/owner/${encodeURIComponent(owner)}`);
49179
49188
  }
49180
49189
  async getPoolData() {
49181
49190
  return this.get("/pool-data");
49182
49191
  }
49183
49192
  async getPoolSnapshot(poolPubkey) {
49184
- return this.get(`/pool-data/${poolPubkey}`);
49193
+ return this.get(`/pool-data/${encodeURIComponent(poolPubkey)}`);
49185
49194
  }
49186
49195
  async openPosition(req) {
49187
49196
  return this.post("/transaction-builder/open-position", req);
@@ -49307,7 +49316,7 @@ function registerMarketTools(server, client) {
49307
49316
  });
49308
49317
  server.registerTool("get_market", {
49309
49318
  description: "Get detailed information about a specific market by its on-chain account pubkey. Returns full market configuration including permissions and collective position data.",
49310
- inputSchema: { pubkey: exports_external.string().describe("Solana pubkey of the market account") }
49319
+ inputSchema: { pubkey: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Solana pubkey of the market account") }
49311
49320
  }, async ({ pubkey }) => {
49312
49321
  const market = await client.getMarket(pubkey);
49313
49322
  return { content: [{ type: "text", text: JSON.stringify(market, null, 2) }] };
@@ -49324,7 +49333,7 @@ function registerPoolTools(server, client) {
49324
49333
  });
49325
49334
  server.registerTool("get_pool", {
49326
49335
  description: "Get detailed information about a specific pool by its on-chain account pubkey.",
49327
- inputSchema: { pubkey: exports_external.string().describe("Solana pubkey of the pool account") }
49336
+ inputSchema: { pubkey: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Solana pubkey of the pool account") }
49328
49337
  }, async ({ pubkey }) => {
49329
49338
  const pool = await client.getPool(pubkey);
49330
49339
  return { content: [{ type: "text", text: JSON.stringify(pool, null, 2) }] };
@@ -49341,7 +49350,7 @@ function registerCustodyTools(server, client) {
49341
49350
  });
49342
49351
  server.registerTool("get_custody", {
49343
49352
  description: "Get detailed custody information for a specific custody account. Includes utilization, fees, and limits.",
49344
- inputSchema: { pubkey: exports_external.string().describe("Solana pubkey of the custody account") }
49353
+ inputSchema: { pubkey: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Solana pubkey of the custody account") }
49345
49354
  }, async ({ pubkey }) => {
49346
49355
  const custody = await client.getCustody(pubkey);
49347
49356
  return { content: [{ type: "text", text: JSON.stringify(custody, null, 2) }] };
@@ -49366,7 +49375,7 @@ function registerPriceTools(server, client) {
49366
49375
  });
49367
49376
  server.registerTool("get_price", {
49368
49377
  description: 'Get the current oracle price for a specific asset symbol (e.g., "SOL", "BTC", "ETH"). Case-insensitive. Returns price in USD with timestamp.',
49369
- inputSchema: { symbol: exports_external.string().describe('Asset symbol, e.g. "SOL", "BTC", "ETH"') }
49378
+ inputSchema: { symbol: exports_external.string().max(16).describe('Asset symbol, e.g. "SOL", "BTC", "ETH"') }
49370
49379
  }, async ({ symbol: symbol2 }) => {
49371
49380
  const data = await client.getPrice(symbol2);
49372
49381
  const usd = formatPrice(data.price, data.exponent);
@@ -49400,7 +49409,7 @@ function registerPositionTools(server, client) {
49400
49409
  server.registerTool("get_positions", {
49401
49410
  description: "List perpetual positions, optionally filtered by wallet owner. Without an owner filter, returns ALL open positions (may be large). With owner, returns enriched positions with computed PnL, leverage, and liquidation price.",
49402
49411
  inputSchema: {
49403
- owner: exports_external.string().optional().describe("Wallet pubkey to filter by. When provided, returns enriched positions with PnL, leverage, and liquidation price.")
49412
+ owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).optional().describe("Wallet pubkey to filter by. When provided, returns enriched positions with PnL, leverage, and liquidation price.")
49404
49413
  }
49405
49414
  }, async ({ owner }) => {
49406
49415
  if (owner) {
@@ -49420,7 +49429,7 @@ ${text}` }] };
49420
49429
  });
49421
49430
  server.registerTool("get_position", {
49422
49431
  description: "Get a single position by its on-chain account pubkey. Returns raw position data. For enriched data (PnL, leverage, liq price), use get_positions with the owner filter instead.",
49423
- inputSchema: { pubkey: exports_external.string().describe("Position account pubkey") }
49432
+ inputSchema: { pubkey: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey") }
49424
49433
  }, async ({ pubkey }) => {
49425
49434
  const position = await client.getPosition(pubkey);
49426
49435
  return { content: [{ type: "text", text: JSON.stringify(position, null, 2) }] };
@@ -49446,7 +49455,7 @@ function registerOrderTools(server, client) {
49446
49455
  server.registerTool("get_orders", {
49447
49456
  description: "List open orders (limit orders, take-profit, stop-loss), optionally filtered by wallet owner. When owner is provided, returns enriched order data with computed trigger prices and sizes.",
49448
49457
  inputSchema: {
49449
- owner: exports_external.string().optional().describe("Wallet pubkey to filter by. When provided, returns enriched orders.")
49458
+ owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).optional().describe("Wallet pubkey to filter by. When provided, returns enriched orders.")
49450
49459
  }
49451
49460
  }, async ({ owner }) => {
49452
49461
  if (owner) {
@@ -49466,7 +49475,7 @@ ${text}` }] };
49466
49475
  });
49467
49476
  server.registerTool("get_order", {
49468
49477
  description: "Get a single order account by its on-chain pubkey.",
49469
- inputSchema: { pubkey: exports_external.string().describe("Order account pubkey") }
49478
+ inputSchema: { pubkey: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Order account pubkey") }
49470
49479
  }, async ({ pubkey }) => {
49471
49480
  const order = await client.getOrder(pubkey);
49472
49481
  return { content: [{ type: "text", text: JSON.stringify(order, null, 2) }] };
@@ -49478,7 +49487,7 @@ function registerPoolDataTools(server, client) {
49478
49487
  server.registerTool("get_pool_data", {
49479
49488
  description: "Get computed pool metrics including AUM (assets under management), LP token stats, custody ratios, and utilization. Data is cached and refreshed every 15 seconds. Provide a pool_pubkey for a specific pool, or omit for all pools.",
49480
49489
  inputSchema: {
49481
- pool_pubkey: exports_external.string().optional().describe("Specific pool pubkey. If omitted, returns all pool snapshots.")
49490
+ pool_pubkey: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).optional().describe("Specific pool pubkey. If omitted, returns all pool snapshots.")
49482
49491
  }
49483
49492
  }, async ({ pool_pubkey }) => {
49484
49493
  if (pool_pubkey) {
@@ -49536,17 +49545,17 @@ function registerOpenPositionTool(server, client) {
49536
49545
  server.registerTool("open_position", {
49537
49546
  description: "Build a transaction to open a new perpetual position on Flash Trade. Returns a preview (entry price, fees, leverage, liquidation price) AND an unsigned transaction. The transaction must be signed by the user's wallet and submitted to Solana separately. IMPORTANT: Always present the preview to the user before they sign. This tool does NOT execute the trade. Supports both MARKET and LIMIT orders, with optional take-profit and stop-loss. COLLATERAL WARNING: Limit orders, take-profit, and stop-loss require >$10 collateral AFTER entry fees. A $10 position will have fees deducted, dropping collateral below $10 and preventing TP/SL/limit orders. Use at least $11-12 input_amount when planning to set TP/SL.",
49538
49547
  inputSchema: {
49539
- input_token_symbol: exports_external.string().describe('Token to pay with: "USDC", "SOL", etc.'),
49540
- output_token_symbol: exports_external.string().describe('Market to trade: "SOL", "BTC", "ETH", etc.'),
49541
- input_amount: exports_external.string().describe('Amount of input token, e.g. "100.0"'),
49542
- leverage: exports_external.string().describe('Leverage multiplier, e.g. "5.0"'),
49548
+ input_token_symbol: exports_external.string().max(16).describe('Token to pay with: "USDC", "SOL", etc.'),
49549
+ output_token_symbol: exports_external.string().max(16).describe('Market to trade: "SOL", "BTC", "ETH", etc.'),
49550
+ input_amount: exports_external.string().max(32).describe('Amount of input token, e.g. "100.0"'),
49551
+ leverage: exports_external.string().max(8).describe('Leverage multiplier, e.g. "5.0"'),
49543
49552
  trade_type: exports_external.enum(["LONG", "SHORT"]).describe("Trade direction"),
49544
- owner: exports_external.string().describe("Wallet pubkey (required to build the transaction)"),
49553
+ owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Wallet pubkey (required to build the transaction)"),
49545
49554
  order_type: exports_external.enum(["MARKET", "LIMIT"]).optional().describe("Default: MARKET"),
49546
- limit_price: exports_external.string().optional().describe("Required for LIMIT orders, UI format price"),
49547
- slippage_percentage: exports_external.string().optional().describe('Default: "0.5" (0.5%)'),
49548
- take_profit: exports_external.string().optional().describe("TP trigger price in UI format"),
49549
- stop_loss: exports_external.string().optional().describe("SL trigger price in UI format"),
49555
+ limit_price: exports_external.string().max(32).optional().describe("Required for LIMIT orders, UI format price"),
49556
+ slippage_percentage: exports_external.string().max(8).optional().describe('Default: "0.5" (0.5%)'),
49557
+ take_profit: exports_external.string().max(32).optional().describe("TP trigger price in UI format"),
49558
+ stop_loss: exports_external.string().max(32).optional().describe("SL trigger price in UI format"),
49550
49559
  degen_mode: exports_external.boolean().optional().describe("Enable degen mode (higher leverage limits)")
49551
49560
  }
49552
49561
  }, async (params) => {
@@ -49609,11 +49618,11 @@ function registerClosePositionTool(server, client) {
49609
49618
  server.registerTool("close_position", {
49610
49619
  description: "Build a transaction to close (fully or partially) an existing perpetual position. Returns a preview with PnL, fees, and receive amount, plus an unsigned transaction. For a full close, set input_usd to the position's full size. For a partial close, use a smaller amount. The transaction must be signed and submitted separately.",
49611
49620
  inputSchema: {
49612
- position_key: exports_external.string().describe("Position account pubkey to close"),
49613
- input_usd: exports_external.string().describe('USD amount to close, e.g. "500.00" for full or "250.00" for partial'),
49614
- withdraw_token_symbol: exports_external.string().describe('Token to receive: "USDC", "SOL", etc.'),
49621
+ position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey to close"),
49622
+ input_usd: exports_external.string().max(32).describe('USD amount to close, e.g. "500.00" for full or "250.00" for partial'),
49623
+ withdraw_token_symbol: exports_external.string().max(16).describe('Token to receive: "USDC", "SOL", etc.'),
49615
49624
  keep_leverage_same: exports_external.boolean().optional().describe("Keep leverage constant during partial close"),
49616
- slippage_percentage: exports_external.string().optional().describe('Default: "0.5" (0.5%)')
49625
+ slippage_percentage: exports_external.string().max(8).optional().describe('Default: "0.5" (0.5%)')
49617
49626
  }
49618
49627
  }, async (params) => {
49619
49628
  const res = await client.closePosition({
@@ -49672,11 +49681,11 @@ function registerCollateralTools(server, client) {
49672
49681
  server.registerTool("add_collateral", {
49673
49682
  description: "Build a transaction to add collateral to an existing position. This reduces leverage and moves the liquidation price further from the current price (safer). Returns a preview and unsigned transaction.",
49674
49683
  inputSchema: {
49675
- position_key: exports_external.string().describe("Position account pubkey"),
49676
- deposit_amount: exports_external.string().describe("Amount to deposit in UI format"),
49677
- deposit_token_symbol: exports_external.string().describe('Token to deposit: "USDC", "SOL", etc.'),
49678
- owner: exports_external.string().describe("Wallet pubkey"),
49679
- slippage_percentage: exports_external.string().optional().describe('Default: "0.5" (0.5%)')
49684
+ position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey"),
49685
+ deposit_amount: exports_external.string().max(32).describe("Amount to deposit in UI format"),
49686
+ deposit_token_symbol: exports_external.string().max(16).describe('Token to deposit: "USDC", "SOL", etc.'),
49687
+ owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Wallet pubkey"),
49688
+ slippage_percentage: exports_external.string().max(8).optional().describe('Default: "0.5" (0.5%)')
49680
49689
  }
49681
49690
  }, async (params) => {
49682
49691
  const res = await client.addCollateral({
@@ -49691,11 +49700,11 @@ function registerCollateralTools(server, client) {
49691
49700
  server.registerTool("remove_collateral", {
49692
49701
  description: "Build a transaction to remove collateral from an existing position. This increases leverage and moves the liquidation price closer (riskier). WARNING: Removing too much collateral can lead to liquidation. Returns a preview and unsigned transaction.",
49693
49702
  inputSchema: {
49694
- position_key: exports_external.string().describe("Position account pubkey"),
49695
- withdraw_amount_usd: exports_external.string().describe("USD amount to withdraw"),
49696
- withdraw_token_symbol: exports_external.string().describe('Token to receive: "USDC", "SOL", etc.'),
49697
- owner: exports_external.string().describe("Wallet pubkey"),
49698
- slippage_percentage: exports_external.string().optional().describe('Default: "0.5" (0.5%)')
49703
+ position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey"),
49704
+ withdraw_amount_usd: exports_external.string().max(32).describe("USD amount to withdraw"),
49705
+ withdraw_token_symbol: exports_external.string().max(16).describe('Token to receive: "USDC", "SOL", etc.'),
49706
+ owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Wallet pubkey"),
49707
+ slippage_percentage: exports_external.string().max(8).optional().describe('Default: "0.5" (0.5%)')
49699
49708
  }
49700
49709
  }, async (params) => {
49701
49710
  const res = await client.removeCollateral({
@@ -49743,9 +49752,9 @@ function registerReversePositionTool(server, client) {
49743
49752
  server.registerTool("reverse_position", {
49744
49753
  description: "Build a transaction to reverse a position (close current + open opposite direction). For example, close a LONG and open a SHORT with the same collateral. Returns combined close+open preview and a single unsigned transaction.",
49745
49754
  inputSchema: {
49746
- position_key: exports_external.string().describe("Position account pubkey to reverse"),
49747
- owner: exports_external.string().describe("Wallet pubkey"),
49748
- slippage_percentage: exports_external.string().optional().describe('Default: "0.5" (0.5%)'),
49755
+ position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey to reverse"),
49756
+ owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Wallet pubkey"),
49757
+ slippage_percentage: exports_external.string().max(8).optional().describe('Default: "0.5" (0.5%)'),
49749
49758
  degen_mode: exports_external.boolean().optional().describe("Enable degen mode for the new position")
49750
49759
  }
49751
49760
  }, async (params) => {
@@ -49764,11 +49773,11 @@ function registerPreviewTools(server, client) {
49764
49773
  server.registerTool("preview_limit_order_fees", {
49765
49774
  description: "Preview the entry price, fees, liquidation price, and borrow rate for a limit order BEFORE placing it. Use this to evaluate a trade before committing. No transaction is built.",
49766
49775
  inputSchema: {
49767
- market_symbol: exports_external.string().describe('Market symbol, e.g. "SOL", "BTC", "ETH"'),
49768
- input_amount: exports_external.string().describe("Collateral amount in UI format"),
49769
- output_amount: exports_external.string().describe("Position size in target token"),
49776
+ market_symbol: exports_external.string().max(16).describe('Market symbol, e.g. "SOL", "BTC", "ETH"'),
49777
+ input_amount: exports_external.string().max(32).describe("Collateral amount in UI format"),
49778
+ output_amount: exports_external.string().max(32).describe("Position size in target token"),
49770
49779
  side: exports_external.enum(["LONG", "SHORT"]).describe("Trade direction"),
49771
- limit_price: exports_external.string().optional().describe("Limit price; uses live price if omitted"),
49780
+ limit_price: exports_external.string().max(32).optional().describe("Limit price; uses live price if omitted"),
49772
49781
  trading_fee_discount_percent: exports_external.number().optional().describe("Fee discount from FAF staking (0-100)")
49773
49782
  }
49774
49783
  }, async (params) => {
@@ -49796,8 +49805,8 @@ WARNING: ${res.err}`);
49796
49805
  server.registerTool("preview_exit_fee", {
49797
49806
  description: "Preview the exit fee and exit price for closing a specific amount of a position. Use this to estimate close costs before calling close_position. No transaction is built.",
49798
49807
  inputSchema: {
49799
- position_key: exports_external.string().describe("Position account pubkey"),
49800
- close_amount_usd: exports_external.string().describe("USD amount to close")
49808
+ position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey"),
49809
+ close_amount_usd: exports_external.string().max(32).describe("USD amount to close")
49801
49810
  }
49802
49811
  }, async (params) => {
49803
49812
  const res = await client.previewExitFee({
@@ -49819,14 +49828,14 @@ WARNING: ${res.err}`);
49819
49828
  description: 'Calculate take-profit or stop-loss prices and projected PnL. Three modes: "forward" (trigger price → PnL), "reverse_pnl" (target PnL → trigger price), "reverse_roi" (target ROI% → trigger price). Works for existing positions (by pubkey) or hypothetical orders (provide market_symbol, entry_price, size, collateral, side).',
49820
49829
  inputSchema: {
49821
49830
  mode: exports_external.enum(["forward", "reverse_pnl", "reverse_roi"]).describe("Calculation mode"),
49822
- position_key: exports_external.string().optional().describe("Position pubkey (for existing positions)"),
49823
- market_symbol: exports_external.string().optional().describe("Market symbol (for hypothetical orders)"),
49824
- entry_price: exports_external.string().optional().describe("Entry price (for hypothetical orders)"),
49825
- size_usd: exports_external.string().optional().describe("Position size USD (for hypothetical orders)"),
49826
- collateral_usd: exports_external.string().optional().describe("Collateral USD (for hypothetical orders)"),
49831
+ position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).optional().describe("Position pubkey (for existing positions)"),
49832
+ market_symbol: exports_external.string().max(16).optional().describe("Market symbol (for hypothetical orders)"),
49833
+ entry_price: exports_external.string().max(32).optional().describe("Entry price (for hypothetical orders)"),
49834
+ size_usd: exports_external.string().max(32).optional().describe("Position size USD (for hypothetical orders)"),
49835
+ collateral_usd: exports_external.string().max(32).optional().describe("Collateral USD (for hypothetical orders)"),
49827
49836
  side: exports_external.enum(["LONG", "SHORT"]).optional().describe("Side (for hypothetical orders)"),
49828
- trigger_price: exports_external.string().optional().describe('Trigger price (required for "forward" mode)'),
49829
- target_pnl_usd: exports_external.string().optional().describe('Target PnL USD (required for "reverse_pnl" mode)'),
49837
+ trigger_price: exports_external.string().max(32).optional().describe('Trigger price (required for "forward" mode)'),
49838
+ target_pnl_usd: exports_external.string().max(32).optional().describe('Target PnL USD (required for "reverse_pnl" mode)'),
49830
49839
  target_roi_percent: exports_external.number().optional().describe('Target ROI% (required for "reverse_roi" mode)')
49831
49840
  }
49832
49841
  }, async (params) => {
@@ -49858,8 +49867,8 @@ WARNING: ${res.err}`);
49858
49867
  server.registerTool("preview_margin", {
49859
49868
  description: "Preview the effect of adding or removing margin (collateral) on a position. Shows new leverage, new liquidation price, and max adjustable amount. Use this before calling add_collateral or remove_collateral. No transaction is built.",
49860
49869
  inputSchema: {
49861
- position_key: exports_external.string().describe("Position account pubkey"),
49862
- margin_delta_usd: exports_external.string().describe("Amount in USD to add or remove"),
49870
+ position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey"),
49871
+ margin_delta_usd: exports_external.string().max(32).describe("Amount in USD to add or remove"),
49863
49872
  action: exports_external.enum(["ADD", "REMOVE"]).describe("ADD to reduce leverage, REMOVE to increase leverage")
49864
49873
  }
49865
49874
  }, async (params) => {
@@ -56796,15 +56805,19 @@ var $VersionedTransaction = VersionedTransaction;
56796
56805
 
56797
56806
  // src/tools/sign-and-send.ts
56798
56807
  import fs from "node:fs";
56808
+
56809
+ // src/sanitize.ts
56799
56810
  function sanitizeError(e) {
56800
56811
  const msg = e instanceof Error ? e.message : String(e);
56801
- return msg.replace(/\[[\d,\s]{20,}\]/g, "[REDACTED]").replace(/[0-9a-fA-F]{40,}/g, "[REDACTED]").replace(/[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{40,}/g, "[REDACTED]");
56812
+ return msg.replace(/\[[\d,\s]{20,}\]/g, "[REDACTED]").replace(/[0-9a-fA-F]{40,}/g, "[REDACTED]").replace(/[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{40,}/g, "[REDACTED]").replace(/[A-Za-z0-9+/]{40,}={0,2}/g, "[REDACTED]");
56802
56813
  }
56814
+
56815
+ // src/tools/sign-and-send.ts
56803
56816
  function registerSignAndSendTool(server) {
56804
56817
  server.registerTool("sign_and_send", {
56805
56818
  description: "Sign and submit a base64-encoded unsigned Solana transaction using the locally configured keypair. " + "Call this AFTER a transaction tool (open_position, close_position, add_collateral, remove_collateral, reverse_position) " + "returns a transactionBase64 string AND the user has reviewed and approved the preview. " + "The keypair is read from KEYPAIR_PATH (default: ~/.config/solana/id.json). " + "Returns the confirmed transaction signature and a Solscan link. " + "IMPORTANT: Always show the transaction preview to the user and get their approval BEFORE calling this tool. " + "This tool signs with the local keypair and submits to Solana mainnet — the action is IRREVERSIBLE. " + "NOTE: This tool never exposes private key material in its output.",
56806
56819
  inputSchema: {
56807
- transaction_base64: exports_external.string().describe("The base64-encoded unsigned transaction returned by a transaction tool")
56820
+ transaction_base64: exports_external.string().max(1e4).describe("The base64-encoded unsigned transaction returned by a transaction tool")
56808
56821
  }
56809
56822
  }, async (params) => {
56810
56823
  const rpcUrl = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";
@@ -56960,11 +56973,11 @@ function registerResources(server, client) {
56960
56973
 
56961
56974
  // src/index.ts
56962
56975
  process.on("uncaughtException", (err) => {
56963
- console.error("[flash-trade-mcp] Uncaught exception:", err);
56976
+ console.error("[flash-trade-mcp] Uncaught exception:", sanitizeError(err));
56964
56977
  process.exit(1);
56965
56978
  });
56966
56979
  process.on("unhandledRejection", (reason) => {
56967
- console.error("[flash-trade-mcp] Unhandled rejection:", reason);
56980
+ console.error("[flash-trade-mcp] Unhandled rejection:", sanitizeError(reason));
56968
56981
  process.exit(1);
56969
56982
  });
56970
56983
  try {
@@ -56972,7 +56985,7 @@ try {
56972
56985
  const client = new FlashApiClient(config2);
56973
56986
  const server = new McpServer({
56974
56987
  name: "flash-trade",
56975
- version: "0.2.2"
56988
+ version: "0.3.1"
56976
56989
  }, {
56977
56990
  capabilities: {
56978
56991
  tools: {},
@@ -56986,6 +56999,6 @@ try {
56986
56999
  const transport = new StdioServerTransport;
56987
57000
  await server.connect(transport);
56988
57001
  } catch (err) {
56989
- console.error("[flash-trade-mcp] Fatal startup error:", err);
57002
+ console.error("[flash-trade-mcp] Fatal startup error:", sanitizeError(err));
56990
57003
  process.exit(1);
56991
57004
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flash-trade-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "MCP server for Flash Trade — perpetual futures DEX on Solana. Provides AI agents with typed tools for trading, position management, and market data.",
5
5
  "module": "src/index.ts",
6
6
  "main": "dist/index.js",