finoptima 1.2.1 → 1.3.0

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/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * EcoChain MCP Server (DEC-2026-043)
4
4
  *
5
5
  * Model Context Protocol server for AI agent access to EcoChain.
6
- * Provides 22 tools: 1 login + 21 platform tools.
6
+ * Provides 30 tools: 1 login + 29 platform tools.
7
7
  *
8
8
  * Authentication: EcoAuth login flow (no hardcoded credentials)
9
9
  * 1. AI asks user for their phone number
@@ -13,6 +13,7 @@
13
13
  *
14
14
  * Environment variables:
15
15
  * ECOCHAIN_API_URL — Optional. API base URL (default: https://api.toutcreer.com)
16
+ * ECOCHAIN_API_KEY — Optional. API key for agent auth (skips phone login)
16
17
  */
17
18
 
18
19
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
@@ -30,6 +31,14 @@ const POLL_TIMEOUT_MS = 120000;
30
31
 
31
32
  let authenticated = false;
32
33
 
34
+ // API key auth — skip phone login if ECOCHAIN_API_KEY is set (DEC-2026-050)
35
+ const API_KEY = process.env.ECOCHAIN_API_KEY;
36
+ if (API_KEY) {
37
+ apiClient.setApiKey(API_KEY);
38
+ authenticated = true;
39
+ console.error('[EcoChain MCP] Authenticated via API key (no phone login required)');
40
+ }
41
+
33
42
  /**
34
43
  * EcoAuth login flow
35
44
  */
@@ -97,7 +106,11 @@ const loginTool = {
97
106
  },
98
107
  handler: async ({ phone_number }) => {
99
108
  if (authenticated) {
100
- return JSON.stringify({ status: 'already_authenticated', user_id: process.env.ECOCHAIN_USER_ID });
109
+ return JSON.stringify({
110
+ status: 'already_authenticated',
111
+ auth_method: API_KEY ? 'api_key' : 'ecoauth',
112
+ user_id: process.env.ECOCHAIN_USER_ID
113
+ });
101
114
  }
102
115
 
103
116
  const result = await loginWithEcoAuth(phone_number);
@@ -117,16 +130,90 @@ const loginTool = {
117
130
  // All tools: login first, then platform tools
118
131
  const allToolsWithLogin = [loginTool, ...allTools];
119
132
 
133
+ // Server instructions — shown to the AI to explain how to use FINOPTIMA
134
+ const SERVER_INSTRUCTIONS = `FINOPTIMA — EcoChain AI Finance Agent (v1.3.0, ${allTools.length + 1} tools)
135
+
136
+ ## Setup
137
+ If the user doesn't have FINOPTIMA installed, they can add it to their MCP config:
138
+ \`\`\`json
139
+ {
140
+ "mcpServers": {
141
+ "ecochain": {
142
+ "command": "npx",
143
+ "args": ["-y", "finoptima"],
144
+ "env": { "ECOCHAIN_API_URL": "https://api.toutcreer.com" }
145
+ }
146
+ }
147
+ }
148
+ \`\`\`
149
+
150
+ ## Authentication
151
+ Two authentication methods are supported:
152
+
153
+ ### Method 1: EcoAuth (interactive — for users)
154
+ 1. Ask the user for their phone number (format: +33...)
155
+ 2. Call the \`login\` tool — a push notification is sent to their EcoAuth mobile app
156
+ 3. The user approves on their phone — JWT session is established
157
+ 4. All other tools are now active
158
+
159
+ ### Method 2: API Key (non-interactive — for agents)
160
+ Set \`ECOCHAIN_API_KEY\` in the MCP config env:
161
+ \`\`\`json
162
+ {
163
+ "env": {
164
+ "ECOCHAIN_API_URL": "https://api.toutcreer.com",
165
+ "ECOCHAIN_API_KEY": "eco_ak_..."
166
+ }
167
+ }
168
+ \`\`\`
169
+ No login call needed — all tools are immediately available.
170
+ API keys are scoped (read/trade/swap/liquidity/send/strategy) and rate-limited.
171
+ Requires a Pro or API subscription plan.
172
+
173
+ ## Available Capabilities
174
+ - **Portfolio**: get_balances, get_portfolio, get_prices, get_lp_positions, get_transaction_history
175
+ - **Market**: get_pools, get_pool_details, get_orderbook, get_ticker, get_candles, get_trading_pairs
176
+ - **Trading**: place_order, cancel_order, get_my_orders, get_trade_history
177
+ - **Swap (AMM)**: get_swap_quote, execute_swap (0.3% fee, multi-hop routing supported)
178
+ - **Liquidity**: add_liquidity, remove_liquidity
179
+ - **Transfer**: send_funds
180
+ - **AI Strategies**: create_strategy, list_strategies, get_strategy, activate_strategy, pause_strategy
181
+ - **Marketplace**: get_templates, subscribe_template, get_leaderboard, follow_strategy, unfollow_strategy
182
+ - **Intents**: create_intent, list_intents
183
+ - **Performance**: get_strategy_performance
184
+
185
+ ## Multi-Pool AMM
186
+ Multiple liquidity pools supported (ECO/BTC, ECO/ETH). Swaps between non-direct pairs (e.g., BTC↔ETH) are automatically routed via ECO as a hub token (2-hop swap).
187
+
188
+ ## AI Trading Strategies
189
+ 4 strategy types: LP Yield, DCA, BTC Hedge, Eco Pilot.
190
+ Pipeline: Create (draft) → Activate → Runner evaluates every 60s → Creates intents → Policy validation (8 rules) → Auto-approve if < 0.01 BTC, else manual approval → Execute.
191
+ Modes: "auto" (auto-approve small trades) or "watch" (all manual approval).
192
+
193
+ ## Strategy Marketplace
194
+ - Browse public strategy templates (\`get_templates\`)
195
+ - Subscribe to a template to clone its config (\`subscribe_template\`)
196
+ - Follow a provider's live trades with copy trading (\`follow_strategy\`)
197
+ - View top-performing strategies (\`get_leaderboard\`)
198
+
199
+ ## Rules
200
+ - Always call \`login\` first before any other tool (unless using API key auth)
201
+ - Always confirm financial actions (trade, swap, send) with the user before executing
202
+ - Use \`get_swap_quote\` before \`execute_swap\` to show expected outcome
203
+ - Platform: https://www.toutcreer.com/trading
204
+ `;
205
+
120
206
  // Create MCP server
121
207
  const server = new Server(
122
208
  {
123
209
  name: 'ecochain',
124
- version: '1.0.0',
210
+ version: '1.3.0',
125
211
  },
126
212
  {
127
213
  capabilities: {
128
214
  tools: {},
129
215
  },
216
+ instructions: SERVER_INSTRUCTIONS,
130
217
  }
131
218
  );
132
219
 
@@ -156,7 +243,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
156
243
  // Block all tools except login if not authenticated
157
244
  if (!authenticated && name !== 'login') {
158
245
  return {
159
- content: [{ type: 'text', text: 'Not authenticated. Please ask the user for their phone number and call the `login` tool first.' }],
246
+ content: [{ type: 'text', text: 'Not authenticated. Either set ECOCHAIN_API_KEY env var or ask the user for their phone number and call the `login` tool first.' }],
160
247
  isError: true,
161
248
  };
162
249
  }
package/lib/api-client.js CHANGED
@@ -12,6 +12,7 @@ class ApiClient {
12
12
  constructor() {
13
13
  this.apiUrl = process.env.ECOCHAIN_API_URL || 'https://api.toutcreer.com';
14
14
  this.jwtToken = null; // Set after EcoAuth login
15
+ this.apiKey = null; // Set via ECOCHAIN_API_KEY env var (DEC-2026-050)
15
16
 
16
17
  const parsed = new URL(this.apiUrl);
17
18
  this.hostname = parsed.hostname;
@@ -27,6 +28,13 @@ class ApiClient {
27
28
  this.jwtToken = token;
28
29
  }
29
30
 
31
+ /**
32
+ * Set API key for agent authentication (DEC-2026-050)
33
+ */
34
+ setApiKey(key) {
35
+ this.apiKey = key;
36
+ }
37
+
30
38
  /**
31
39
  * Make an HTTP request to the backend API
32
40
  */
@@ -38,7 +46,9 @@ class ApiClient {
38
46
  'X-Client-Type': 'mcp-agent',
39
47
  };
40
48
 
41
- if (this.jwtToken) {
49
+ if (this.apiKey) {
50
+ headers['X-API-Key'] = this.apiKey;
51
+ } else if (this.jwtToken) {
42
52
  headers['Authorization'] = `Bearer ${this.jwtToken}`;
43
53
  }
44
54
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "finoptima",
3
- "version": "1.2.1",
4
- "description": "FINOPTIMA — EcoChain AI Finance Agent via MCP. Portfolio optimization, AMM liquidity, AI trading strategies, swaps. Secured by EcoAuth 2FA.",
3
+ "version": "1.3.0",
4
+ "description": "FINOPTIMA — EcoChain AI Finance Agent via MCP. Multi-pool AMM (ECO/BTC, ECO/ETH), 4 AI strategies, strategy marketplace, copy trading, leaderboard. 35 tools. API key auth. Secured by EcoAuth 2FA.",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "finoptima": "index.js"
package/tools/index.js CHANGED
@@ -393,6 +393,92 @@ const sendTools = [
393
393
  }
394
394
  ];
395
395
 
396
+ // ============================================
397
+ // MARKETPLACE TOOLS (DEC-2026-050)
398
+ // ============================================
399
+
400
+ const marketplaceTools = [
401
+ {
402
+ name: 'get_leaderboard',
403
+ description: 'Get top performing public strategies. Returns strategy name, type, total return, max drawdown, follower count, and provider name.',
404
+ inputSchema: {
405
+ type: 'object',
406
+ properties: {
407
+ limit: { type: 'number', description: 'Max results (default 50)' }
408
+ },
409
+ required: []
410
+ },
411
+ handler: withLogging('get_leaderboard', async ({ limit }) => {
412
+ const params = limit ? `?limit=${limit}` : '';
413
+ const data = await apiClient.get(`/performance/leaderboard${params}`);
414
+ return JSON.stringify(data);
415
+ })
416
+ },
417
+ {
418
+ name: 'get_templates',
419
+ description: 'List all active strategy templates in the marketplace. Returns template name, type, parameters, return, subscribers, and provider.',
420
+ inputSchema: {
421
+ type: 'object',
422
+ properties: {},
423
+ required: []
424
+ },
425
+ handler: withLogging('get_templates', async () => {
426
+ const data = await apiClient.get('/strategies/templates');
427
+ return JSON.stringify(data);
428
+ })
429
+ },
430
+ {
431
+ name: 'subscribe_template',
432
+ description: 'Subscribe to a strategy template. Clones the template config into a new strategy for the user. Requires pilot+ plan.',
433
+ inputSchema: {
434
+ type: 'object',
435
+ properties: {
436
+ template_id: { type: 'string', description: 'The UUID of the template to subscribe to' }
437
+ },
438
+ required: ['template_id']
439
+ },
440
+ handler: withLogging('subscribe_template', async ({ template_id }) => {
441
+ const data = await apiClient.post(`/strategies/templates/${template_id}/subscribe`);
442
+ return JSON.stringify(data);
443
+ })
444
+ },
445
+ {
446
+ name: 'follow_strategy',
447
+ description: 'Start copy trading a public strategy. Creates a shadow strategy that mirrors the provider\'s trades with optional scaling. Requires pilot+ plan.',
448
+ inputSchema: {
449
+ type: 'object',
450
+ properties: {
451
+ strategy_id: { type: 'string', description: 'The UUID of the public strategy to follow' },
452
+ scale_factor: { type: 'number', description: 'Trade size multiplier (0.1-2.0, default 1.0)' },
453
+ max_trade_btc: { type: 'number', description: 'Maximum trade size in BTC (default 0.01)' }
454
+ },
455
+ required: ['strategy_id']
456
+ },
457
+ handler: withLogging('follow_strategy', async ({ strategy_id, scale_factor, max_trade_btc }) => {
458
+ const body = {};
459
+ if (scale_factor !== undefined) body.scale_factor = scale_factor;
460
+ if (max_trade_btc !== undefined) body.max_trade_btc = max_trade_btc;
461
+ const data = await apiClient.post(`/strategies/${strategy_id}/follow`, body);
462
+ return JSON.stringify(data);
463
+ })
464
+ },
465
+ {
466
+ name: 'unfollow_strategy',
467
+ description: 'Stop copy trading a strategy. Pauses the shadow strategy and removes the copy trading link.',
468
+ inputSchema: {
469
+ type: 'object',
470
+ properties: {
471
+ strategy_id: { type: 'string', description: 'The UUID of the strategy to unfollow' }
472
+ },
473
+ required: ['strategy_id']
474
+ },
475
+ handler: withLogging('unfollow_strategy', async ({ strategy_id }) => {
476
+ const data = await apiClient.delete(`/strategies/${strategy_id}/follow`);
477
+ return JSON.stringify(data);
478
+ })
479
+ },
480
+ ];
481
+
396
482
  // Export all tools grouped by scope
397
483
  module.exports = {
398
484
  readTools,
@@ -403,6 +489,7 @@ module.exports = {
403
489
  strategyTools,
404
490
  intentTools,
405
491
  performanceTools,
492
+ marketplaceTools,
406
493
  allTools: [
407
494
  ...readTools,
408
495
  ...tradeTools,
@@ -411,6 +498,7 @@ module.exports = {
411
498
  ...sendTools,
412
499
  ...strategyTools,
413
500
  ...intentTools,
414
- ...performanceTools
501
+ ...performanceTools,
502
+ ...marketplaceTools,
415
503
  ]
416
504
  };
@@ -14,7 +14,7 @@ const intentTools = [
14
14
  type: 'object',
15
15
  properties: {
16
16
  strategy_id: { type: 'string', description: 'Optional: link to a strategy' },
17
- action: { type: 'string', description: 'Trading action', enum: ['execute_swap', 'add_liquidity', 'remove_liquidity', 'place_order'] },
17
+ action: { type: 'string', description: 'Trading action', enum: ['execute_swap', 'add_liquidity', 'remove_liquidity', 'zap_liquidity', 'place_order', 'vault_to_hot'] },
18
18
  params: {
19
19
  type: 'object',
20
20
  description: 'Action parameters. Swap: {token_in, token_out, amount_in, max_slippage_bps}. Liquidity: {pool_id, amount_a, amount_b}. Order: {pair_id, side, type, amount, price}.'