MCP Distant
This commit is contained in:
+5
-141
@@ -21,6 +21,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
||||
import { z } from 'zod';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { Readability } from '@mozilla/readability';
|
||||
import { registerDataTools, toolError } from './tools.js';
|
||||
|
||||
const API_BASE = (process.env.CROWDLENDING_API_URL || 'http://localhost:4000/api/v1').replace(/\/$/, '');
|
||||
const API_KEY = process.env.CROWDLENDING_API_KEY;
|
||||
@@ -72,28 +73,6 @@ async function apiGet(path, params) {
|
||||
return body;
|
||||
}
|
||||
|
||||
/** Formate le résultat d'un outil : texte JSON lisible + structuredContent.
|
||||
* Le protocole MCP exige que `structuredContent` soit un objet JSON (pas un
|
||||
* tableau brut) — les endpoints qui renvoient une liste (investissements,
|
||||
* remboursements, dépôts/retraits) sont donc enveloppés dans { items: [...] }.
|
||||
* Le texte lisible (`content`), lui, reste le JSON brut tel que renvoyé par
|
||||
* l'API, tableau ou objet. */
|
||||
function toolResult(data) {
|
||||
const structuredContent = Array.isArray(data) ? { items: data } : data;
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
||||
structuredContent,
|
||||
};
|
||||
}
|
||||
|
||||
/** Formate une erreur d'outil de façon à ce que l'agent comprenne quoi faire. */
|
||||
function toolError(e) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Erreur : ${e.message}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
/* ── Serveur MCP ──────────────────────────────────────────────────────── */
|
||||
const server = new McpServer({
|
||||
name: 'crowdlending-mcp-server' + (LABEL ? `-${LABEL}` : ''),
|
||||
@@ -115,125 +94,10 @@ const withLabel = (title) => LABEL ? `${title} [${LABEL.toUpperCase()}]` : title
|
||||
const withSource = (description) =>
|
||||
`${description}\n\nSource de données : ${API_BASE}${LABEL ? ` (environnement : ${LABEL})` : ''}`;
|
||||
|
||||
server.registerTool(
|
||||
'crowdlending_get_investisseur',
|
||||
{
|
||||
title: withLabel('Profil investisseur'),
|
||||
description: withSource(
|
||||
"Retourne le profil de l'investisseur associé à la clé API utilisée " +
|
||||
"(nom, type famille/entreprise, régime fiscal). Utile pour savoir sur " +
|
||||
"quel portefeuille portent les autres outils."),
|
||||
inputSchema: {},
|
||||
annotations: READ_ONLY_ANNOTATIONS,
|
||||
},
|
||||
async () => {
|
||||
try { return toolResult(await apiGet('/investisseur')); }
|
||||
catch (e) { return toolError(e); }
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'crowdlending_get_dashboard',
|
||||
{
|
||||
title: withLabel('Synthèse du portefeuille'),
|
||||
description: withSource(
|
||||
"Retourne les KPIs du portefeuille : nombre d'investissements, total " +
|
||||
"investi, capital investi actuel (= capital restant dû sur les prêts en " +
|
||||
"cours et en défaut, net des remboursements déjà perçus — équivalent au " +
|
||||
"KPI \"Capital investi\" de l'app), capital en risque (sous-ensemble en " +
|
||||
"retard/procédure), montant remboursé, intérêts bruts/nets perçus, " +
|
||||
"capital reçu, total dépôts/retraits. Les montants d'investissements sont " +
|
||||
"des soldes actuels (photo à aujourd'hui), pas des cumuls par période. " +
|
||||
"Point d'entrée idéal pour une vue d'ensemble avant d'aller chercher le détail."),
|
||||
inputSchema: {
|
||||
annee: z.number().int().optional()
|
||||
.describe("Filtre les intérêts/capital reçu sur une année (ex. 2026). Omettre pour le cumul total. N'affecte pas le capital investi/en risque, qui sont toujours des soldes actuels."),
|
||||
},
|
||||
annotations: READ_ONLY_ANNOTATIONS,
|
||||
},
|
||||
async ({ annee }) => {
|
||||
try { return toolResult(await apiGet('/dashboard', { annee })); }
|
||||
catch (e) { return toolError(e); }
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'crowdlending_list_investissements',
|
||||
{
|
||||
title: withLabel('Liste des investissements'),
|
||||
description: withSource(
|
||||
"Liste les investissements (prêts participatifs) du portefeuille : " +
|
||||
"projet, émetteur, plateforme, montant investi, taux, durée, statut. " +
|
||||
"Filtrable par statut. Ne renvoie pas les remboursements détaillés — " +
|
||||
"utiliser crowdlending_get_investissement pour le détail d'un projet."),
|
||||
inputSchema: {
|
||||
statut: z.enum(['en_cours', 'rembourse', 'en_retard', 'procedure', 'cloture'])
|
||||
.optional()
|
||||
.describe("Filtrer par statut. Omettre pour lister tous les investissements."),
|
||||
},
|
||||
annotations: READ_ONLY_ANNOTATIONS,
|
||||
},
|
||||
async ({ statut }) => {
|
||||
try { return toolResult(await apiGet('/investissements', { statut })); }
|
||||
catch (e) { return toolError(e); }
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'crowdlending_get_investissement',
|
||||
{
|
||||
title: withLabel("Détail d'un investissement"),
|
||||
description: withSource(
|
||||
"Retourne le détail complet d'un investissement (identifié par son id, " +
|
||||
"obtenu via crowdlending_list_investissements), y compris la liste de " +
|
||||
"ses remboursements réels perçus (capital, intérêts bruts/nets, cashback)."),
|
||||
inputSchema: {
|
||||
id: z.number().int().positive().describe("Identifiant de l'investissement"),
|
||||
},
|
||||
annotations: READ_ONLY_ANNOTATIONS,
|
||||
},
|
||||
async ({ id }) => {
|
||||
try { return toolResult(await apiGet(`/investissements/${id}`)); }
|
||||
catch (e) { return toolError(e); }
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'crowdlending_list_remboursements',
|
||||
{
|
||||
title: withLabel('Liste des remboursements'),
|
||||
description: withSource(
|
||||
"Liste les remboursements réels perçus (toutes plateformes confondues), " +
|
||||
"avec le nom du projet et de la plateforme. Filtrable par période."),
|
||||
inputSchema: {
|
||||
date_debut: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
|
||||
.describe('Date de début au format YYYY-MM-DD (incluse)'),
|
||||
date_fin: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
|
||||
.describe('Date de fin au format YYYY-MM-DD (incluse)'),
|
||||
},
|
||||
annotations: READ_ONLY_ANNOTATIONS,
|
||||
},
|
||||
async ({ date_debut, date_fin }) => {
|
||||
try { return toolResult(await apiGet('/remboursements', { date_debut, date_fin })); }
|
||||
catch (e) { return toolError(e); }
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'crowdlending_list_depots_retraits',
|
||||
{
|
||||
title: withLabel('Liste des dépôts / retraits'),
|
||||
description: withSource(
|
||||
"Liste les mouvements de cash (dépôts et retraits) sur les plateformes " +
|
||||
"de crowdlending, du plus récent au plus ancien."),
|
||||
inputSchema: {},
|
||||
annotations: READ_ONLY_ANNOTATIONS,
|
||||
},
|
||||
async () => {
|
||||
try { return toolResult(await apiGet('/depots-retraits')); }
|
||||
catch (e) { return toolError(e); }
|
||||
},
|
||||
);
|
||||
// Les 6 outils de lecture de données (investisseur, dashboard, investissements,
|
||||
// remboursements, dépôts/retraits) sont définis dans tools.js, partagés avec
|
||||
// le serveur HTTP distant — voir ce fichier pour leur documentation.
|
||||
registerDataTools(server, { apiGet, withLabel, withSource });
|
||||
|
||||
/* ── Outil fetch_url (Phase 3) ────────────────────────────────────────────
|
||||
Récupère une page web (annonce de projet sur une plateforme, par ex.) et en
|
||||
|
||||
Reference in New Issue
Block a user