SOFRA · Plateforme IA
Architecture

Connecteurs MCP

Le pattern technique d'un connecteur — Rust pour les nouveaux, réutilisation du paperasse-mcp Go, transports stdio/HTTP, packaging flake.

Un connecteur est un serveur MCP qui expose un système du SI sous forme d'outils typés. C'est l'unité de base de la plateforme. Le catalogue système par système est dans Connecteurs ; cette page décrit le pattern commun.

Langage : Rust pour les nouveaux connecteurs

Les nouveaux connecteurs sont écrits en Rust (SDK officiel rmcp) : binaire statique unique, démarrage instantané, faible empreinte mémoire, packaging trivial en flake. Le connecteur navigateur fait exception et embarque Playwright (voir Automatisation navigateur).

On ne réécrit pas l'existant. paperasse-mcp (Go, mark3labs/mcp-go) expose déjà les capacités Qonto/Stripe, FEC, Factur-X et la validation des mentions obligatoires. MCP étant un contrat, ce serveur Go cohabite sans friction avec les connecteurs Rust : le hub et le chat ne voient que des outils MCP. La règle est : nouveau connecteur → Rust ; capacités déjà couvertes par paperasse-mcp → on garde le Go.

Anatomie d'un connecteur

mcp-sage/
├── flake.nix            # build + image OCI + module NixOS
├── Cargo.toml
└── src/
    ├── main.rs          # transport: stdio (dev) ou HTTP/SSE (prod, derrière le hub)
    ├── client.rs        # client du système (API REST / SQL Sage / SOAP…)
    ├── tools/
    │   ├── read.rs      # outils lecture (par défaut)
    │   └── write.rs     # outils écriture (feature-gated, désactivés par défaut)
    └── config.rs        # secrets via variables d'environnement (jamais en dur)

Cette structure est le miroir Rust de paperasse-mcp (internal/mcp/{server,tools,handlers}.go, internal/runner). On garde la même séparation : déclaration des outils, handlers, exécution.

Squelette d'un outil (lecture)

use rmcp::{tool, ServerHandler};

#[derive(Clone)]
pub struct SageConnector { client: SageClient }

#[tool(tool_box)]
impl SageConnector {
    /// Retourne la fiche fournisseur (SIRET, n° TVA intracom, RIB, complétude RFE).
    #[tool(description = "Lire une fiche fournisseur Sage 100 par code ou SIRET")]
    async fn get_supplier(
        &self,
        #[tool(param)] id: String,
    ) -> Result<Supplier, McpError> {
        self.client.fetch_supplier(&id).await
    }
}

Conventions :

  • Nom d'outil système.action (sage.get_supplier, yooz.list_invoices).
  • Description orientée métier et explicite sur les effets de bord (un outil d'écriture le dit).
  • Schéma de sortie typé — le chat reçoit des données structurées, pas du texte libre.
  • Lecture par défaut : tools/write.rs est derrière une feature Cargo désactivée tant que le hub n'autorise pas l'écriture.

Transports

TransportUsage
stdioDéveloppement local, tests, exécution depuis Claude Code.
HTTP / SSEProduction : le connecteur tourne en service NixOS, le hub s'y connecte.

paperasse-mcp illustre déjà les deux (cmd/paperasse-mcp/cmd/mcp.go pour stdio, mcp_http.go pour HTTP) — même découpage côté Rust.

Packaging Nix

Chaque connecteur est un flake autonome, exactement comme paperasse-mcp :

{
  description = "mcp-sage — connecteur MCP Sage 100 V12 / Sage Network";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  outputs = { self, nixpkgs }:
    let
      systems = [ "x86_64-linux" ];
      forAll = nixpkgs.lib.genAttrs systems;
      pkgsFor = s: nixpkgs.legacyPackages.${s};
    in {
      packages = forAll (s: {
        default = (pkgsFor s).rustPlatform.buildRustPackage {
          pname = "mcp-sage";
          version = "0.1.0";
          src = ./.;
          cargoLock.lockFile = ./Cargo.lock;
        };
      });
    };
}

Le service NixOS qui l'expose est décrit dans Déploiement NixOS.

Cycle de vie

  1. Spécifier les outils (lecture d'abord) à partir du besoin métier.
  2. Implémenter le client système + les handlers.
  3. Tester en stdio depuis Claude Code (boucle rapide).
  4. Packager en flake, déployer en service NixOS derrière le hub.
  5. Évaluer comme dans paperasse (evals avec/sans) pour mesurer la valeur ajoutée.