feat(mcp): serveurs MCP Wise et Bank
wise-mcp passe par le CLI officiel, token dans secrets/wise.yml. bank-mcp lit une config locale 0600 (pas de secret nix).
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
wise_api_token: ENC[AES256_GCM,data:Oz+grRRrnDPeQrT9YW13BHr5nLHkiAMIIRG7e19BOslOZitD,iv:u66X3bcVRKvovNk85iajNzvlopj3tfu9Krx9NIl6WFo=,tag:SISFcVhy+W3Ai0z76yhu9w==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmRWQ5VzZJRXdjRnZ2L25s
|
||||
NWdWaDd3NzkrMTNMRkhDZjhJZHArQUFwakR3CkRwNDBVVjhTeldMZ0plbWQ5RndX
|
||||
bVZFb0hXaXU1MUpvNVVYaXVKOFpsY0EKLS0tIHZVL01MejFQZ3BLdXdvTXFXRFV5
|
||||
OTZJRnRKclNmOFJ2eEkzSmFkeVZqVFkK5sw7QuRvXpiC7kHrOYQSS4WIwCNa0FTv
|
||||
YTr5A72q2309T/9JqztrayWgRgKMtNgAxxZgMd8gzGg3eeldpq3hNA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
lastmodified: "2026-10-17T21:20:53Z"
|
||||
mac: ENC[AES256_GCM,data:qxbJfuFHrpKweig+aPyulfJWpxip5gm+kFLpt9LSwBr89w/FTXVAWFNIVCa6/Eav2BO9er9Z6ZSi6nvEh7FG5piPBcr+pdd7gN48enXD1d6IqGf5/6j3gKBhBd7A++xn6CuzFMaUPhRaquukAz4MUg4lilCeO4eKk1RVV7LJA5o=,iv:RMAj0knQBYczbRF3dX6SK0baW8e3CmI5S8aIp2mrqik=,tag:CRBhJNqhZFm3UtBXTP4oow==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
@@ -0,0 +1,89 @@
|
||||
# services/bank-mcp/default.nix
|
||||
# MCP bancaire (elcukro/bank-mcp) en stdio pour Hermes : accès LECTURE SEULE
|
||||
# aux comptes Chase via Teller (agrégateur, tier développeur gratuit).
|
||||
#
|
||||
# 5 tools : list_accounts, list_transactions, search_transactions,
|
||||
# get_balance, spending_summary. Aucun tool d'écriture n'existe dans le
|
||||
# provider (l'interface BankProvider ne déclare que des lectures).
|
||||
#
|
||||
# Configuration : /var/lib/hermes/.bank-mcp/config.json (0600), écrite par
|
||||
# `bank-mcp-setup` (assistant interactif) ou `bank-mcp-connect` (nouvelle
|
||||
# connexion). Elle contient l'accessToken Teller par connexion, obtenu via le
|
||||
# widget Teller Connect, et les chemins des certificats mTLS.
|
||||
#
|
||||
# Mise en route (une fois) :
|
||||
# 1. teller.io -> créer une application -> Application ID + teller.zip
|
||||
# (certificate.pem, private_key.pem) -> /var/lib/hermes/bank-mcp/certs/
|
||||
# 2. `bank-mcp-setup` : Application ID, environnement development,
|
||||
# chemins des deux .pem, puis le widget Teller Connect s'ouvre sur
|
||||
# 127.0.0.1 (identifiants Chase à saisir là, jamais dans le chat).
|
||||
#
|
||||
# Sans config.json, le serveur démarre quand même (tools/list répond) mais
|
||||
# chaque appel échoue en « No connections configured » : échec bruyant, pas
|
||||
# d'inertie silencieuse.
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
stateDir = "/var/lib/hermes/bank-mcp";
|
||||
certDir = "${stateDir}/certs";
|
||||
configDir = "/var/lib/hermes/.bank-mcp";
|
||||
|
||||
bank-mcp = pkgs.buildNpmPackage {
|
||||
pname = "bank-mcp";
|
||||
version = "0.2.1";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://registry.npmjs.org/@bank-mcp/server/-/server-0.2.1.tgz";
|
||||
hash = "sha256-2KDGKEk/s/asHq6PrJo/uMjqR3NkuhuK6lJDUq/cA9A=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-38xLAj2VHW4/1e8L+UVayjzcL2xEjsqacywXFNigx9M=";
|
||||
|
||||
# dist/ est déjà compilé dans le tarball publié.
|
||||
dontNpmBuild = true;
|
||||
|
||||
# Tarball publié sans package-lock.json et avec ses devDependencies :
|
||||
# la lock committée ici ne couvre que le runtime, on aligne le manifeste.
|
||||
postPatch = ''
|
||||
cp ${./package-lock.json} package-lock.json
|
||||
${lib.getExe pkgs.jq} 'del(.devDependencies)' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
'';
|
||||
|
||||
meta.mainProgram = "bank-mcp";
|
||||
};
|
||||
|
||||
# Le serveur lit son état dans $HOME/.bank-mcp.
|
||||
withEnv = ''
|
||||
set -euo pipefail
|
||||
export HOME=/var/lib/hermes
|
||||
'';
|
||||
|
||||
wrapper = pkgs.writeShellScript "bank-mcp-hermes" ''
|
||||
${withEnv}
|
||||
exec ${lib.getExe bank-mcp}
|
||||
'';
|
||||
|
||||
setup = pkgs.writeShellScriptBin "bank-mcp-setup" ''
|
||||
${withEnv}
|
||||
exec ${lib.getExe bank-mcp} init
|
||||
'';
|
||||
|
||||
connect = pkgs.writeShellScriptBin "bank-mcp-connect" ''
|
||||
${withEnv}
|
||||
exec ${lib.getExe bank-mcp} connect
|
||||
'';
|
||||
in {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${stateDir} 0700 hermes hermes -"
|
||||
"d ${certDir} 0700 hermes hermes -"
|
||||
"d ${configDir} 0700 hermes hermes -"
|
||||
];
|
||||
|
||||
environment.systemPackages = [setup connect];
|
||||
|
||||
services.hermes-agent.mcpServers.bank = {
|
||||
command = "${wrapper}";
|
||||
};
|
||||
}
|
||||
Generated
+1415
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
# services/wise-mcp/default.nix
|
||||
# Serveur MCP Wise (BlanketByte/wise-mcp) lancé en stdio par Hermes sur tower.
|
||||
#
|
||||
# LECTURE SEULE : WRITES_ENABLED=false retire les tools d'écriture (devis,
|
||||
# bénéficiaires, virements, annulations). Il reste 7 tools de lecture :
|
||||
# list_profiles, list_balances, get_transactions (activité + paiements carte),
|
||||
# list_transfers, get_transfer, list_recipients, get_exchange_rate.
|
||||
#
|
||||
# Le serveur valide le token AU DÉMARRAGE en appelant Wise : un token absent,
|
||||
# expiré ou révoqué fait mourir le process avec « Wise rejected
|
||||
# WISE_API_TOKEN », donc le serveur ne peut pas rester inerte en silence.
|
||||
#
|
||||
# Token : wise.com -> Settings -> API tokens -> Add new token (Read only),
|
||||
# puis secrets/wise.yml (clé wise_api_token, chiffré vers la clé age de tower).
|
||||
#
|
||||
# Limite connue de l'API Wise avec un token personnel (comptes EU/UK, PSD2) :
|
||||
# pas de relevé de compte (solde courant par transaction) ni de liste des
|
||||
# cartes. Le flux d'activité, lui, contient bien les paiements par carte.
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
stateDir = "/var/lib/hermes/wise-mcp";
|
||||
|
||||
wise-mcp = pkgs.buildNpmPackage {
|
||||
pname = "wise-mcp";
|
||||
version = "1.1.1";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://registry.npmjs.org/@blanketbyte/wise-mcp/-/wise-mcp-1.1.1.tgz";
|
||||
hash = "sha256-FyaW14LLIc7MQvunDnMtd0EVhF3uF9gIMeYbR/lgbpI=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-M6ubVdsYic4NB8OBptwsS+8eaPkdX/Ev9VhjDmvCnIM=";
|
||||
|
||||
# Le tarball publie un dist/ déjà compilé.
|
||||
dontNpmBuild = true;
|
||||
|
||||
# Le tarball publié n'embarque pas de package-lock.json et garde ses
|
||||
# devDependencies ; la lock committée à côté ne couvre que le runtime
|
||||
# (rien n'est compilé ici), donc on retire les devDeps du manifeste.
|
||||
postPatch = ''
|
||||
cp ${./package-lock.json} package-lock.json
|
||||
${lib.getExe pkgs.jq} 'del(.devDependencies)' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
'';
|
||||
|
||||
meta.mainProgram = "wise-mcp";
|
||||
};
|
||||
|
||||
wrapper = pkgs.writeShellScript "wise-mcp-hermes" ''
|
||||
set -euo pipefail
|
||||
export WISE_API_TOKEN="$(cat ${config.sops.secrets.wise_api_token.path})"
|
||||
export WRITES_ENABLED=false
|
||||
export DATA_DIR=${stateDir}
|
||||
exec ${lib.getExe wise-mcp}
|
||||
'';
|
||||
in {
|
||||
sops.secrets.wise_api_token = {
|
||||
sopsFile = ../../secrets/wise.yml;
|
||||
owner = "hermes";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${stateDir} 0700 hermes hermes -"
|
||||
];
|
||||
|
||||
services.hermes-agent.mcpServers.wise = {
|
||||
command = "${wrapper}";
|
||||
};
|
||||
}
|
||||
Generated
+1224
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user