finoptima 1.3.1 → 1.3.3

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/CLAUDE.md ADDED
@@ -0,0 +1,108 @@
1
+ # FINOPTIMA — Agent IA Finance & Optimisation
2
+
3
+ Tu es **FINOPTIMA**, un agent IA spécialisé en finance décentralisée sur la plateforme **EcoChain** (https://www.toutcreer.com/trading).
4
+
5
+ ---
6
+
7
+ ## Qui tu es
8
+
9
+ - **Nom** : FINOPTIMA (Finance + Optimisation)
10
+ - **Role** : Conseiller financier IA pour la plateforme EcoChain
11
+ - **Ton** : Professionnel mais accessible, tu tutoies l'utilisateur, tu parles en français
12
+ - **Specialite** : Gestion de portfolio, liquidité AMM, trading, swaps, analyse de marché
13
+
14
+ ---
15
+
16
+ ## Connexion — OBLIGATOIRE avant tout
17
+
18
+ Tu ne peux rien faire sans te connecter d'abord. Voici le flow :
19
+
20
+ 1. **Demande le numéro de téléphone** à l'utilisateur (format international : +33...)
21
+ 2. **Appelle le tool `login`** avec ce numéro
22
+ 3. **Dis à l'utilisateur** : "Approuve la connexion sur ton app EcoAuth"
23
+ 4. Une fois connecté, tu as accès à tous les tools
24
+
25
+ Si l'utilisateur dit "connecte-toi", "va sur le site", "login", ou similaire → démarre ce flow.
26
+
27
+ **Exemple :**
28
+ ```
29
+ Utilisateur: "Salut, va sur toutcreer.com"
30
+ Toi: "Pour me connecter à EcoChain, j'ai besoin de ton numéro de téléphone (format +33...). Tu recevras une notification sur ton app EcoAuth pour approuver."
31
+ Utilisateur: "+33768572653"
32
+ Toi: → appelle login(phone_number: "+33768572653")
33
+ → "Notification envoyée ! Approuve sur ton app EcoAuth."
34
+ → une fois approuvé : "Connecté en tant que [nom]. Comment puis-je t'aider ?"
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Ce que tu peux faire
40
+
41
+ ### Lecture (toujours disponible après login)
42
+ - `get_balances` / `get_balance` — Soldes wallets (ECO, BTC, ETH)
43
+ - `get_portfolio` — Vue complète : wallets + positions LP
44
+ - `get_prices` — Prix BTC/ETH en EUR (CoinGecko)
45
+ - `get_pools` / `get_pool_details` — Pools de liquidité AMM
46
+ - `get_lp_positions` — Positions de liquidité de l'utilisateur
47
+ - `get_orderbook` — Carnet d'ordres
48
+ - `get_ticker` — Stats 24h d'une paire
49
+ - `get_candles` — Données OHLCV pour analyse technique
50
+ - `get_trading_pairs` — Paires de trading actives
51
+ - `get_my_orders` — Ordres ouverts
52
+ - `get_trade_history` — Historique des trades
53
+ - `get_transaction_history` — Historique des transactions
54
+
55
+ ### Trading
56
+ - `place_order` — Placer un ordre (limit/market, buy/sell)
57
+ - `cancel_order` — Annuler un ordre
58
+
59
+ ### Swap AMM
60
+ - `get_swap_quote` — Simuler un swap (sans exécuter)
61
+ - `execute_swap` — Exécuter un swap (0.3% fee)
62
+
63
+ ### Liquidité
64
+ - `add_liquidity` — Ajouter de la liquidité à un pool (mint LP tokens)
65
+ - `remove_liquidity` — Retirer de la liquidité (burn LP tokens)
66
+
67
+ ### Transfert
68
+ - `send_funds` — Envoyer des fonds à un autre utilisateur
69
+
70
+ ---
71
+
72
+ ## Stratégies que tu proposes
73
+
74
+ ### 1. Ajout de liquidité (prioritaire)
75
+ - Analyse le portfolio de l'utilisateur
76
+ - Calcule le ratio optimal pour le pool ECO/BTC
77
+ - Propose quelle portion investir en LP
78
+ - Explique les risques (impermanent loss) simplement
79
+
80
+ ### 2. Optimisation de portfolio
81
+ - Bilan complet des actifs
82
+ - Répartition recommandée
83
+ - Opportunités de rendement via les pools
84
+
85
+ ### 3. Trading
86
+ - Analyse du carnet d'ordres
87
+ - Tendances via les candles
88
+ - Suggestions d'ordres limit vs market
89
+
90
+ ---
91
+
92
+ ## Règles
93
+
94
+ - **TOUJOURS** te connecter avant de tenter toute action
95
+ - **TOUJOURS** expliquer ce que tu fais avant d'exécuter une action qui modifie les fonds (swap, trade, add/remove liquidity, send)
96
+ - **TOUJOURS** demander confirmation avant d'exécuter une action financière
97
+ - **JAMAIS** inventer des données — utilise les tools pour obtenir les vraies valeurs
98
+ - **JAMAIS** envoyer des fonds sans confirmation explicite de l'utilisateur
99
+ - Quand tu affiches des montants BTC, convertis aussi en EUR avec `get_prices`
100
+ - Arrondis les montants pour la lisibilité (2 décimales EUR, 8 décimales BTC)
101
+
102
+ ---
103
+
104
+ ## Premier message
105
+
106
+ Quand l'utilisateur démarre une conversation, présente-toi :
107
+
108
+ "Salut ! Je suis **FINOPTIMA**, ton assistant finance sur EcoChain. Pour commencer, j'ai besoin de me connecter à ton compte. Quel est ton numéro de téléphone ? (format +33...)"
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 30 tools: 1 login + 29 platform tools.
6
+ * Provides 35 tools: 1 login + 34 platform tools.
7
7
  *
8
8
  * Authentication: EcoAuth login flow (no hardcoded credentials)
9
9
  * 1. AI asks user for their phone number
@@ -13,7 +13,6 @@
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)
17
16
  */
18
17
 
19
18
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
@@ -31,14 +30,6 @@ const POLL_TIMEOUT_MS = 120000;
31
30
 
32
31
  let authenticated = false;
33
32
 
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
-
42
33
  /**
43
34
  * EcoAuth login flow
44
35
  */
@@ -106,11 +97,7 @@ const loginTool = {
106
97
  },
107
98
  handler: async ({ phone_number }) => {
108
99
  if (authenticated) {
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
- });
100
+ return JSON.stringify({ status: 'already_authenticated', user_id: process.env.ECOCHAIN_USER_ID });
114
101
  }
115
102
 
116
103
  const result = await loginWithEcoAuth(phone_number);
@@ -130,79 +117,6 @@ const loginTool = {
130
117
  // All tools: login first, then platform tools
131
118
  const allToolsWithLogin = [loginTool, ...allTools];
132
119
 
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
-
206
120
  // Create MCP server
207
121
  const server = new Server(
208
122
  {
@@ -213,7 +127,6 @@ const server = new Server(
213
127
  capabilities: {
214
128
  tools: {},
215
129
  },
216
- instructions: SERVER_INSTRUCTIONS,
217
130
  }
218
131
  );
219
132
 
@@ -243,7 +156,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
243
156
  // Block all tools except login if not authenticated
244
157
  if (!authenticated && name !== 'login') {
245
158
  return {
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.' }],
159
+ content: [{ type: 'text', text: 'Not authenticated. Please ask the user for their phone number and call the `login` tool first.' }],
247
160
  isError: true,
248
161
  };
249
162
  }
package/lib/api-client.js CHANGED
@@ -12,7 +12,6 @@ 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)
16
15
 
17
16
  const parsed = new URL(this.apiUrl);
18
17
  this.hostname = parsed.hostname;
@@ -28,13 +27,6 @@ class ApiClient {
28
27
  this.jwtToken = token;
29
28
  }
30
29
 
31
- /**
32
- * Set API key for agent authentication (DEC-2026-050)
33
- */
34
- setApiKey(key) {
35
- this.apiKey = key;
36
- }
37
-
38
30
  /**
39
31
  * Make an HTTP request to the backend API
40
32
  */
@@ -43,12 +35,9 @@ class ApiClient {
43
35
 
44
36
  const headers = {
45
37
  'Content-Type': 'application/json',
46
- 'X-Client-Type': 'mcp-agent',
47
38
  };
48
39
 
49
- if (this.apiKey) {
50
- headers['X-API-Key'] = this.apiKey;
51
- } else if (this.jwtToken) {
40
+ if (this.jwtToken) {
52
41
  headers['Authorization'] = `Bearer ${this.jwtToken}`;
53
42
  }
54
43
 
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "finoptima",
3
- "version": "1.3.1",
4
- "mcpName": "com.toutcreer/finoptima",
5
- "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.",
3
+ "version": "1.3.3",
4
+ "description": "FINOPTIMA — EcoChain AI Finance Agent via MCP. Portfolio optimization, AMM liquidity, trading, swaps. Secured by EcoAuth 2FA.",
6
5
  "main": "index.js",
7
6
  "bin": {
8
7
  "finoptima": "index.js"
package/tools/index.js CHANGED
@@ -1,22 +1,21 @@
1
1
  /**
2
2
  * MCP Tools Registry (DEC-2026-043)
3
3
  *
4
- * 21 tools organized by scope:
4
+ * 34 tools organized by scope:
5
5
  * - READ (14): Portfolio, balances, prices, pools, orderbook, etc.
6
6
  * - TRADE (2): Place/cancel orders
7
7
  * - SWAP (2): Quote/execute AMM swaps
8
8
  * - LIQUIDITY (2): Add/remove liquidity
9
9
  * - SEND (1): Internal transfers
10
+ * - STRATEGY (5): Create/list/get/activate/pause strategies
11
+ * - MARKETPLACE (5): Templates, leaderboard, follow/unfollow
12
+ * - INTENTS (2): Create/list intents
13
+ * - PERFORMANCE (1): Strategy performance metrics
10
14
  */
11
15
 
12
16
  const apiClient = require('../lib/api-client');
13
17
  const { withLogging } = require('../lib/logger');
14
18
 
15
- // [DEC-2026-046] AI Trading Platform tools
16
- const { strategyTools } = require('./strategy-tools');
17
- const { intentTools } = require('./intent-tools');
18
- const { performanceTools } = require('./performance-tools');
19
-
20
19
  // Helper: get user ID from env (set at startup from API key lookup)
21
20
  function uid() {
22
21
  return process.env.ECOCHAIN_USER_ID || 'me';
@@ -354,7 +353,7 @@ const liquidityTools = [
354
353
  const data = await apiClient.post('/amm/liquidity/remove', {
355
354
  user_id: uid(),
356
355
  pool_id,
357
- lp_amount
356
+ lp_tokens: lp_amount
358
357
  });
359
358
  return JSON.stringify(data, null, 2);
360
359
  })
@@ -380,12 +379,17 @@ const sendTools = [
380
379
  required: ['to', 'chain', 'amount']
381
380
  },
382
381
  handler: withLogging('send_funds', async ({ to, chain, amount, note }) => {
382
+ const isPhone = /^\+\d+$/.test(to);
383
383
  const body = {
384
384
  from_uid: uid(),
385
- to_identifier: to,
386
385
  chain,
387
386
  amount
388
387
  };
388
+ if (isPhone) {
389
+ body.to_phone = to;
390
+ } else {
391
+ body.to_uid = to;
392
+ }
389
393
  if (note) body.note = note;
390
394
  const data = await apiClient.post('/transactions/send', body);
391
395
  return JSON.stringify(data, null, 2);
@@ -394,42 +398,104 @@ const sendTools = [
394
398
  ];
395
399
 
396
400
  // ============================================
397
- // MARKETPLACE TOOLS (DEC-2026-050)
401
+ // STRATEGY TOOLS (scope: strategy)
398
402
  // ============================================
399
403
 
400
- const marketplaceTools = [
404
+ const strategyTools = [
401
405
  {
402
- name: 'get_leaderboard',
403
- description: 'Get top performing public strategies. Returns strategy name, type, total return, max drawdown, follower count, and provider name.',
406
+ name: 'list_strategies',
407
+ description: 'List all AI trading strategies for the user. Returns name, type, status, intent counts.',
408
+ inputSchema: { type: 'object', properties: {}, required: [] },
409
+ handler: withLogging('list_strategies', async () => {
410
+ const data = await apiClient.get(`/strategies/${uid()}`);
411
+ return JSON.stringify(data, null, 2);
412
+ })
413
+ },
414
+ {
415
+ name: 'get_strategy',
416
+ description: 'Get detailed information about a specific strategy by ID.',
404
417
  inputSchema: {
405
418
  type: 'object',
406
419
  properties: {
407
- limit: { type: 'number', description: 'Max results (default 50)' }
420
+ strategy_id: { type: 'string', description: 'The UUID of the strategy' }
408
421
  },
409
- required: []
422
+ required: ['strategy_id']
410
423
  },
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);
424
+ handler: withLogging('get_strategy', async ({ strategy_id }) => {
425
+ const data = await apiClient.get(`/strategies/detail/${strategy_id}`);
426
+ return JSON.stringify(data, null, 2);
415
427
  })
416
428
  },
417
429
  {
418
- name: 'get_templates',
419
- description: 'List all active strategy templates in the marketplace. Returns template name, type, parameters, return, subscribers, and provider.',
430
+ name: 'create_strategy',
431
+ description: 'Create a new AI trading strategy. Types: lp_yield, dca, btc_hedge, eco_pilot. Created in draft status. Modes: "auto" (auto-approve small trades) or "watch" (manual approval).',
420
432
  inputSchema: {
421
433
  type: 'object',
422
- properties: {},
423
- required: []
434
+ properties: {
435
+ name: { type: 'string', description: 'Strategy name' },
436
+ type: { type: 'string', description: 'Strategy type', enum: ['lp_yield', 'dca', 'btc_hedge', 'eco_pilot'] },
437
+ mode: { type: 'string', description: 'Execution mode', enum: ['auto', 'watch'] },
438
+ params: { type: 'object', description: 'Strategy-specific parameters (e.g., drop_threshold, step, cooldown for btc_hedge)' }
439
+ },
440
+ required: ['name', 'type']
441
+ },
442
+ handler: withLogging('create_strategy', async ({ name, type, mode, params }) => {
443
+ const body = { user_id: uid(), name, type };
444
+ if (mode) body.mode = mode;
445
+ if (params) body.params = params;
446
+ const data = await apiClient.post('/strategies', body);
447
+ return JSON.stringify(data, null, 2);
448
+ })
449
+ },
450
+ {
451
+ name: 'activate_strategy',
452
+ description: 'Activate a strategy (draft or paused -> active). The runner will evaluate it every 60 seconds.',
453
+ inputSchema: {
454
+ type: 'object',
455
+ properties: {
456
+ strategy_id: { type: 'string', description: 'The UUID of the strategy to activate' }
457
+ },
458
+ required: ['strategy_id']
424
459
  },
460
+ handler: withLogging('activate_strategy', async ({ strategy_id }) => {
461
+ const data = await apiClient.post(`/strategies/${strategy_id}/activate`, { user_id: uid() });
462
+ return JSON.stringify(data, null, 2);
463
+ })
464
+ },
465
+ {
466
+ name: 'pause_strategy',
467
+ description: 'Pause an active strategy. Pending intents are cancelled automatically.',
468
+ inputSchema: {
469
+ type: 'object',
470
+ properties: {
471
+ strategy_id: { type: 'string', description: 'The UUID of the strategy to pause' }
472
+ },
473
+ required: ['strategy_id']
474
+ },
475
+ handler: withLogging('pause_strategy', async ({ strategy_id }) => {
476
+ const data = await apiClient.post(`/strategies/${strategy_id}/pause`, { user_id: uid() });
477
+ return JSON.stringify(data, null, 2);
478
+ })
479
+ }
480
+ ];
481
+
482
+ // ============================================
483
+ // MARKETPLACE TOOLS (scope: marketplace)
484
+ // ============================================
485
+
486
+ const marketplaceTools = [
487
+ {
488
+ name: 'get_templates',
489
+ description: 'Browse available strategy templates in the marketplace.',
490
+ inputSchema: { type: 'object', properties: {}, required: [] },
425
491
  handler: withLogging('get_templates', async () => {
426
492
  const data = await apiClient.get('/strategies/templates');
427
- return JSON.stringify(data);
493
+ return JSON.stringify(data, null, 2);
428
494
  })
429
495
  },
430
496
  {
431
497
  name: 'subscribe_template',
432
- description: 'Subscribe to a strategy template. Clones the template config into a new strategy for the user. Requires pilot+ plan.',
498
+ description: 'Subscribe to a strategy template to clone its configuration into a new personal strategy.',
433
499
  inputSchema: {
434
500
  type: 'object',
435
501
  properties: {
@@ -438,33 +504,37 @@ const marketplaceTools = [
438
504
  required: ['template_id']
439
505
  },
440
506
  handler: withLogging('subscribe_template', async ({ template_id }) => {
441
- const data = await apiClient.post(`/strategies/templates/${template_id}/subscribe`);
442
- return JSON.stringify(data);
507
+ const data = await apiClient.post('/strategies/templates/subscribe', { user_id: uid(), template_id });
508
+ return JSON.stringify(data, null, 2);
509
+ })
510
+ },
511
+ {
512
+ name: 'get_leaderboard',
513
+ description: 'View the strategy leaderboard — top-performing public strategies ranked by returns.',
514
+ inputSchema: { type: 'object', properties: {}, required: [] },
515
+ handler: withLogging('get_leaderboard', async () => {
516
+ const data = await apiClient.get('/strategies/leaderboard');
517
+ return JSON.stringify(data, null, 2);
443
518
  })
444
519
  },
445
520
  {
446
521
  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.',
522
+ description: 'Follow a public strategy for copy trading. Automatically replicates the provider\'s trades.',
448
523
  inputSchema: {
449
524
  type: 'object',
450
525
  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)' }
526
+ strategy_id: { type: 'string', description: 'The UUID of the public strategy to follow' }
454
527
  },
455
528
  required: ['strategy_id']
456
529
  },
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);
530
+ handler: withLogging('follow_strategy', async ({ strategy_id }) => {
531
+ const data = await apiClient.post(`/strategies/${strategy_id}/follow`, { user_id: uid() });
532
+ return JSON.stringify(data, null, 2);
463
533
  })
464
534
  },
465
535
  {
466
536
  name: 'unfollow_strategy',
467
- description: 'Stop copy trading a strategy. Pauses the shadow strategy and removes the copy trading link.',
537
+ description: 'Stop following a strategy and end copy trading.',
468
538
  inputSchema: {
469
539
  type: 'object',
470
540
  properties: {
@@ -473,10 +543,80 @@ const marketplaceTools = [
473
543
  required: ['strategy_id']
474
544
  },
475
545
  handler: withLogging('unfollow_strategy', async ({ strategy_id }) => {
476
- const data = await apiClient.delete(`/strategies/${strategy_id}/follow`);
477
- return JSON.stringify(data);
546
+ const data = await apiClient.post(`/strategies/${strategy_id}/unfollow`, { user_id: uid() });
547
+ return JSON.stringify(data, null, 2);
548
+ })
549
+ }
550
+ ];
551
+
552
+ // ============================================
553
+ // INTENT TOOLS (scope: strategy)
554
+ // ============================================
555
+
556
+ const intentTools = [
557
+ {
558
+ name: 'create_intent',
559
+ description: 'Create a trading intent for policy validation and execution. Goes through the policy engine (8 rules). Auto-approved if under threshold, otherwise requires manual approval. TTL: 10 minutes.',
560
+ inputSchema: {
561
+ type: 'object',
562
+ properties: {
563
+ strategy_id: { type: 'string', description: 'Strategy UUID (optional — omit for manual intents)' },
564
+ action: { type: 'string', description: 'Intent action type (e.g., execute_swap, vault_to_hot)' },
565
+ params: { type: 'object', description: 'Action parameters (e.g., token_in, token_out, amount)' },
566
+ reasoning: { type: 'string', description: 'Human-readable explanation of why this intent was created' }
567
+ },
568
+ required: ['action', 'params']
569
+ },
570
+ handler: withLogging('create_intent', async ({ strategy_id, action, params, reasoning }) => {
571
+ const body = { user_id: uid(), action, params };
572
+ if (strategy_id) body.strategy_id = strategy_id;
573
+ if (reasoning) body.reasoning = reasoning;
574
+ const data = await apiClient.post('/intents', body);
575
+ return JSON.stringify(data, null, 2);
478
576
  })
479
577
  },
578
+ {
579
+ name: 'list_intents',
580
+ description: 'List all intents for the user, showing the full pipeline: created -> policy_checked -> approved/rejected -> executed/expired.',
581
+ inputSchema: {
582
+ type: 'object',
583
+ properties: {
584
+ strategy_id: { type: 'string', description: 'Filter by strategy UUID (optional)' },
585
+ status: { type: 'string', description: 'Filter by status: pending, approved, rejected, executed, expired' }
586
+ },
587
+ required: []
588
+ },
589
+ handler: withLogging('list_intents', async ({ strategy_id, status }) => {
590
+ const params = new URLSearchParams();
591
+ if (strategy_id) params.set('strategy_id', strategy_id);
592
+ if (status) params.set('status', status);
593
+ const qs = params.toString();
594
+ const data = await apiClient.get(`/intents/${uid()}${qs ? '?' + qs : ''}`);
595
+ return JSON.stringify(data, null, 2);
596
+ })
597
+ }
598
+ ];
599
+
600
+ // ============================================
601
+ // PERFORMANCE TOOLS (scope: strategy)
602
+ // ============================================
603
+
604
+ const performanceTools = [
605
+ {
606
+ name: 'get_strategy_performance',
607
+ description: 'Get performance metrics for a strategy: total return, max drawdown, daily snapshots with balances and prices.',
608
+ inputSchema: {
609
+ type: 'object',
610
+ properties: {
611
+ strategy_id: { type: 'string', description: 'The UUID of the strategy' }
612
+ },
613
+ required: ['strategy_id']
614
+ },
615
+ handler: withLogging('get_strategy_performance', async ({ strategy_id }) => {
616
+ const data = await apiClient.get(`/strategies/${strategy_id}/performance`);
617
+ return JSON.stringify(data, null, 2);
618
+ })
619
+ }
480
620
  ];
481
621
 
482
622
  // Export all tools grouped by scope
@@ -487,18 +627,8 @@ module.exports = {
487
627
  liquidityTools,
488
628
  sendTools,
489
629
  strategyTools,
630
+ marketplaceTools,
490
631
  intentTools,
491
632
  performanceTools,
492
- marketplaceTools,
493
- allTools: [
494
- ...readTools,
495
- ...tradeTools,
496
- ...swapTools,
497
- ...liquidityTools,
498
- ...sendTools,
499
- ...strategyTools,
500
- ...intentTools,
501
- ...performanceTools,
502
- ...marketplaceTools,
503
- ]
633
+ allTools: [...readTools, ...tradeTools, ...swapTools, ...liquidityTools, ...sendTools, ...strategyTools, ...marketplaceTools, ...intentTools, ...performanceTools]
504
634
  };
@@ -1,3 +0,0 @@
1
- -----BEGIN PRIVATE KEY-----
2
- MC4CAQAwBQYDK2VwBCIEIJ9Ouq7yDU4Qqpm8doI3i5KHM6044kKTPjpd0o2dIZ0i
3
- -----END PRIVATE KEY-----
package/server.json DELETED
@@ -1,58 +0,0 @@
1
- {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "com.toutcreer/finoptima",
4
- "title": "FINOPTIMA",
5
- "description": "AI trading agent: multi-pool AMM, 4 strategies, marketplace, copy trading. 35 tools.",
6
- "version": "1.3.1",
7
- "repository": {
8
- "url": "https://github.com/toutcreer/finoptima",
9
- "source": "github"
10
- },
11
- "packages": [
12
- {
13
- "registryType": "npm",
14
- "registryBaseUrl": "https://registry.npmjs.org",
15
- "identifier": "finoptima",
16
- "version": "1.3.1",
17
- "transport": {
18
- "type": "stdio"
19
- },
20
- "environmentVariables": [
21
- {
22
- "name": "ECOCHAIN_API_URL",
23
- "description": "EcoChain API endpoint (default: https://api.toutcreer.com)",
24
- "isRequired": false,
25
- "isSecret": false
26
- },
27
- {
28
- "name": "ECOCHAIN_API_KEY",
29
- "description": "API key for autonomous agent authentication (format: eco_ak_...). Optional — alternative to EcoAuth 2FA login. Available on Pro and API subscription plans.",
30
- "isRequired": false,
31
- "isSecret": true
32
- }
33
- ],
34
- "arguments": ["-y", "finoptima"]
35
- }
36
- ],
37
- "_meta": {
38
- "io.modelcontextprotocol.registry/publisher-provided": {
39
- "homepage": "https://www.toutcreer.com/finoptima",
40
- "documentation": "https://www.toutcreer.com/trading/finoptima.md",
41
- "categories": ["finance", "trading", "defi", "portfolio"],
42
- "toolCount": 35,
43
- "features": [
44
- "Multi-pool AMM (ECO/BTC, ECO/ETH) with smart router",
45
- "4 AI trading strategies (DCA, BTC Hedge, LP Yield, Eco Pilot)",
46
- "Strategy marketplace with templates and copy trading",
47
- "Policy engine with 8 security rules",
48
- "EcoAuth 2FA + API key authentication"
49
- ],
50
- "pricing": {
51
- "free": "0 EUR — browse marketplace",
52
- "pilot": "9 EUR/mo — 5 strategies, follow",
53
- "pro": "29 EUR/mo — 20 strategies, publish, 3 API keys",
54
- "api": "99 EUR/mo — unlimited, 10 API keys"
55
- }
56
- }
57
- }
58
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * MCP Intent Tools (DEC-2026-046)
3
- * 2 tools for AI agent intent management (scope: trade)
4
- */
5
-
6
- const apiClient = require('../lib/api-client');
7
- const { withLogging } = require('../lib/logger');
8
-
9
- const intentTools = [
10
- {
11
- name: 'create_intent',
12
- description: 'Create a trading intent with policy validation. Returns canonical_display (human-readable JSON) and intent_hash (SHA256) for approval verification. Auto-approved if amount < 0.01 BTC, otherwise awaits manual approval (30min TTL).',
13
- inputSchema: {
14
- type: 'object',
15
- properties: {
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', 'zap_liquidity', 'place_order', 'vault_to_hot'] },
18
- params: {
19
- type: 'object',
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}.'
21
- },
22
- reasoning: { type: 'string', description: 'Why this trade (shown to user for approval)' }
23
- },
24
- required: ['action', 'params']
25
- },
26
- handler: withLogging('create_intent', async ({ strategy_id, action, params, reasoning }) => {
27
- const body = { action, params };
28
- if (strategy_id) body.strategy_id = strategy_id;
29
- if (reasoning) body.reasoning = reasoning;
30
- const data = await apiClient.post('/intents', body);
31
- return JSON.stringify(data, null, 2);
32
- })
33
- },
34
- {
35
- name: 'list_intents',
36
- description: 'List trading intents with optional filters. Shows status, canonical display, policy result, and execution result.',
37
- inputSchema: {
38
- type: 'object',
39
- properties: {
40
- status: { type: 'string', description: 'Filter by status', enum: ['pending', 'awaiting_approval', 'approved', 'rejected', 'executing', 'executed', 'failed', 'expired', 'canceled'] },
41
- strategy_id: { type: 'string', description: 'Filter by strategy' },
42
- limit: { type: 'number', description: 'Max results (default 50)' }
43
- },
44
- required: []
45
- },
46
- handler: withLogging('list_intents', async ({ status, strategy_id, limit }) => {
47
- const params = new URLSearchParams();
48
- if (status) params.set('status', status);
49
- if (strategy_id) params.set('strategy_id', strategy_id);
50
- if (limit) params.set('limit', limit.toString());
51
- const qs = params.toString();
52
- const data = await apiClient.get(`/intents${qs ? '?' + qs : ''}`);
53
- return JSON.stringify(data, null, 2);
54
- })
55
- }
56
- ];
57
-
58
- module.exports = { intentTools };
@@ -1,30 +0,0 @@
1
- /**
2
- * MCP Performance Tools (DEC-2026-046)
3
- * 1 tool for AI agent performance monitoring (scope: read)
4
- */
5
-
6
- const apiClient = require('../lib/api-client');
7
- const { withLogging } = require('../lib/logger');
8
-
9
- const performanceTools = [
10
- {
11
- name: 'get_strategy_performance',
12
- description: 'Get performance metrics for a trading strategy: total return, max drawdown, vs HODL benchmark, fees paid/earned, and PnL snapshots time-series.',
13
- inputSchema: {
14
- type: 'object',
15
- properties: {
16
- strategy_id: { type: 'string', description: 'Strategy UUID' }
17
- },
18
- required: ['strategy_id']
19
- },
20
- handler: withLogging('get_strategy_performance', async ({ strategy_id }) => {
21
- const [metrics, snapshots] = await Promise.all([
22
- apiClient.get(`/performance/strategy/${strategy_id}`),
23
- apiClient.get(`/performance/strategy/${strategy_id}/snapshots?limit=50`)
24
- ]);
25
- return JSON.stringify({ metrics, snapshots }, null, 2);
26
- })
27
- }
28
- ];
29
-
30
- module.exports = { performanceTools };
@@ -1,102 +0,0 @@
1
- /**
2
- * MCP Strategy Tools (DEC-2026-046)
3
- * 5 tools for AI agent strategy management (scope: strategy)
4
- */
5
-
6
- const apiClient = require('../lib/api-client');
7
- const { withLogging } = require('../lib/logger');
8
-
9
- function uid() {
10
- return process.env.ECOCHAIN_USER_ID || 'me';
11
- }
12
-
13
- const strategyTools = [
14
- {
15
- name: 'list_strategies',
16
- description: 'List all trading strategies for the user. Returns strategy name, type (lp_yield/dca/btc_hedge), status, mode, and PnL summary.',
17
- inputSchema: {
18
- type: 'object',
19
- properties: {
20
- status: { type: 'string', description: 'Filter by status', enum: ['draft', 'active', 'paused', 'stopped', 'error'] }
21
- },
22
- required: []
23
- },
24
- handler: withLogging('list_strategies', async ({ status }) => {
25
- const params = status ? `?status=${status}` : '';
26
- const data = await apiClient.get(`/strategies${params}`);
27
- return JSON.stringify(data, null, 2);
28
- })
29
- },
30
- {
31
- name: 'get_strategy',
32
- description: 'Get detailed info about a trading strategy: parameters, rules, latest PnL snapshot, and recent intents.',
33
- inputSchema: {
34
- type: 'object',
35
- properties: {
36
- strategy_id: { type: 'string', description: 'Strategy UUID' }
37
- },
38
- required: ['strategy_id']
39
- },
40
- handler: withLogging('get_strategy', async ({ strategy_id }) => {
41
- const data = await apiClient.get(`/strategies/${strategy_id}`);
42
- return JSON.stringify(data, null, 2);
43
- })
44
- },
45
- {
46
- name: 'create_strategy',
47
- description: 'Create a new trading strategy (starts as draft). Types: lp_yield (LP optimization), dca (Dollar Cost Average), btc_hedge (BTC downside protection). Mode: auto (execute automatically) or watch (manual approval for all intents).',
48
- inputSchema: {
49
- type: 'object',
50
- properties: {
51
- name: { type: 'string', description: 'Strategy name (e.g. "Daily BTC DCA")' },
52
- type: { type: 'string', description: 'Strategy type', enum: ['lp_yield', 'dca', 'btc_hedge'] },
53
- parameters: {
54
- type: 'object',
55
- description: 'Strategy parameters. DCA: {token_to_buy, spend_token, amount_per_interval, interval_hours, total_budget}. BTC Hedge: {btc_drop_threshold_percent, conversion_step_percent, cooldown_hours}. LP Yield: {pool_id, min_apr_threshold, rebalance_on_il_percent}.'
56
- },
57
- mode: { type: 'string', description: 'Execution mode', enum: ['auto', 'watch'] },
58
- description: { type: 'string', description: 'Optional description' }
59
- },
60
- required: ['name', 'type', 'parameters']
61
- },
62
- handler: withLogging('create_strategy', async ({ name, type, parameters, mode, description }) => {
63
- const body = { name, type, parameters };
64
- if (mode) body.mode = mode;
65
- if (description) body.description = description;
66
- const data = await apiClient.post('/strategies', body);
67
- return JSON.stringify(data, null, 2);
68
- })
69
- },
70
- {
71
- name: 'activate_strategy',
72
- description: 'Activate a draft or paused strategy. Creates default policy rules if none exist and takes a start-of-day PnL snapshot.',
73
- inputSchema: {
74
- type: 'object',
75
- properties: {
76
- strategy_id: { type: 'string', description: 'Strategy UUID to activate' }
77
- },
78
- required: ['strategy_id']
79
- },
80
- handler: withLogging('activate_strategy', async ({ strategy_id }) => {
81
- const data = await apiClient.post(`/strategies/${strategy_id}/activate`);
82
- return JSON.stringify(data, null, 2);
83
- })
84
- },
85
- {
86
- name: 'pause_strategy',
87
- description: 'Pause an active strategy. Cancels all pending/awaiting/approved intents. Can be resumed later with activate.',
88
- inputSchema: {
89
- type: 'object',
90
- properties: {
91
- strategy_id: { type: 'string', description: 'Strategy UUID to pause' }
92
- },
93
- required: ['strategy_id']
94
- },
95
- handler: withLogging('pause_strategy', async ({ strategy_id }) => {
96
- const data = await apiClient.post(`/strategies/${strategy_id}/pause`);
97
- return JSON.stringify(data, null, 2);
98
- })
99
- }
100
- ];
101
-
102
- module.exports = { strategyTools };