Je gère 5 applications SaaS et impossible de savoir laquelle plante. Alors j'ai donné à Claude un outil pour toutes les surveiller.

11 min read

43 notifications Discord non lues. Cinq confirmations de sauvegarde, toutes au vert. Un bot de contenu qui a publié 3 articles sur Shopify pendant la nuit. Un audit de crédits signalant un utilisateur à 10 crédits au lieu de 600. Et un flux RSS qui a planté il y a 3 heures mais personne ne l'a remarqué parce que l'alerte s'est noyée sous 12 messages de succès.

TL;DR : Quand vous gérez plusieurs produits sur des stacks différentes, votre monitoring se fragmente entre canaux Discord, tableaux de bord et rapports cron jusqu'à ce que vous deveniez la couche d'agrégation humaine. J'ai construit un serveur MCP personnalisé, un outil privé qui permet à Claude d'interroger toutes mes apps et de me dire ce qui déconne. 16 commits, 4 heures de debug, et une spec OAuth qui a failli me tuer. Ci-dessous : ce que ça a pris, ce qui a changé, et un framework pour décider quand vous en avez besoin vs quand vous sur-ingénieurez.

Tout va bien ET quelque chose brûle. Je n'arrive juste pas à savoir quoi sans lire les 43, les trier mentalement par gravité, puis faire des recoupements entre les canaux.

Cinq produits SaaS. Convex ici, Supabase là, des crons n8n partout. Chaque produit rapporte dans son propre canal Discord dans son propre format. Chaque canal mélange confirmations de routine et vraies urgences. Le ratio bruit/signal est d'environ 15 pour 1.

Le monitoring fonctionne. Chaque app rapporte. Les sauvegardes confirment. Les audits de crédits signalent. Les pipelines de contenu loggent. Les données existent.

Mais je suis devenu la couche d'agrégation.

Le mec qui ouvre 4 canaux, 2 tableaux de bord et un log d'exécution n8n pour reconstituer mentalement une image de "comment ça se passe." App 1 va bien. App 2 a un problème mineur. App 3 a un souci qui nécessitait de l'attention il y a 3 heures. Je ne le savais juste pas parce que l'alerte était la notification 37 sur 43.

Si vous êtes comme moi, vous avez probablement essayé de corriger ça en construisant plus d'outils 🤓. Un tableau de bord personnalisé ici. Un bot Slack là. Un script de résumé hebdomadaire. Félicitations, vous avez maintenant 5 produits SaaS ET 3 outils de monitoring à monitorer. Vous n'avez pas résolu la fragmentation. Vous y avez ajouté une couche.

Un tableau de bord unifié m'a traversé l'esprit. Puis j'ai réalisé que c'est un sixième produit à maintenir. Et ça nécessite toujours que je le regarde, l'interprète, décide ce qui compte. Ce dont j'avais vraiment besoin était quelque chose qui pouvait interroger les 5 sources et répondre à une question, pas un écran de plus.

Alors j'ai donné à Claude un outil personnalisé pour accéder à toutes mes apps d'un coup. Construit comme un serveur MCP sur Next.js, déployé sur Vercel, connecté à Convex, Supabase, mon API de contenu, et un adaptateur Discord. L'idée était simple. L'exécution a failli tuer le projet.

Données redacted monitoring applications SaaS surveillance système agrégation
Quand votre monitoring ressemble à un document de la CIA déclassifié

Avant la construction : quand vous en avez vraiment besoin

Je mets cette section en premier parce que j'ai failli construire quelque chose dont je n'avais pas besoin il y a 6 mois, et je ne veux pas que vous fassiez la même erreur. Un serveur MCP personnel est le bon outil à un moment spécifique. Avant ce moment, c'est du développement guidé par le CV.

Si vous gérez un projet sur une stack, les notifications Discord et un endpoint de health check suffisent. Peut-être Grafana. Vous voyez tout depuis un endroit. Si vous êtes là et pensez à MCP, arrêtez. Allez construire des fonctionnalités à la place.

Deux ou trois projets sur des stacks similaires ? Un tableau de bord partagé couvre le besoin. Des scripts CLI gèrent les vérifications ad-hoc. Je maintiens toujours que les CLI battent MCP pour les tâches d'agent à usage unique et rien dans ce projet n'a changé cette opinion. Si votre agent doit exécuter une action contre un système, un CLI est plus simple et plus prévisible. À chaque fois.

Trois à cinq projets sur des stacks mixtes, c'est là que ça commence à faire mal. Convex ici, Supabase là, des API REST personnalisées, différents systèmes d'auth, différentes formes de données. Aucun outil unique ne les interroge tous. Votre routine matinale implique 3 onglets et un scroll Discord. Vous commencez à rater des trucs. Pas parce que vous vous en foutez, mais parce qu'agréger 4 formats différents avant le café dépasse ce qu'un cerveau humain devrait faire à 7h du matin.

Cinq plus, vous réalisez que vous êtes le middleware. Chaque source fonctionne bien individuellement. Le problème c'est la synthèse. "Laquelle de mes apps a besoin d'attention" nécessite d'interroger 4 bases de données, comparer les résultats, classer par gravité. Un tableau de bord ne peut pas faire ça. Un LLM avec accès à vos données peut.

J'étais solidement dans cette dernière étape avant de l'admettre.

Le seuil que je suggérerais : si vous passez plus de 15 minutes par jour à faire du context-switching entre sources de monitoring, et que les données sont accessibles via API ou base de données, ça se rentabilise dès la première semaine.

Le désastre des 16 commits

Je pensais que ça prendrait 10 minutes 😬. Un serveur MCP c'est quelques route handlers, deux définitions d'outils, déployer sur Vercel, fini. Je pensais déjà à ce que j'allais cuisiner pour le dîner quand j'ai commencé.

Quatre heures plus tard je debuggais encore OAuth. Ça a pris 4 heures, environ 40 échanges avec Claude Code, environ 8-12$ de tokens API, et 16 commits pour avoir un prototype fonctionnel. Regarder le git log le lendemain matin était humiliant.

Le serveur MCP lui-même est une seule app Next.js. Six fichiers de routes, un module utilitaire. Vous déclarez des outils que Claude peut appeler, chaque outil interroge une source de données, Claude décide quels outils utiliser selon votre question.

app/
├── .well-known/
   ├── oauth-authorization-server/route.ts
   └── oauth-protected-resource/route.ts
├── authorize/route.ts
├── token/route.ts
└── mcp/route.ts
lib/
└── oauth.ts

Déclarer les outils a pris peut-être 30 minutes. Chacun est une fonction avec un nom, une description, un schéma de paramètres, et une logique qui interroge Convex ou Supabase ou une API. C'est la partie facile. Le git log vous dit exactement où le temps est parti.

Premier mur : les sessions MCP. J'ai commencé avec des sessions avec état parce que c'est ce que montrent les exemples du SDK. Déployé sur Vercel. Rien ne marchait. Les fonctions serverless ne gardent pas l'état entre invocations. Deux commits pour arracher ça et passer en stateless. J'aurais dû savoir. Je ne savais pas.

Deuxième mur : le routing Next.js. Trois commits pour comprendre que la route dynamique App Router [transport] causait des problèmes de résolution de chemin et j'avais besoin d'une route fixe /mcp à la place. Puis le basePath était faux. Puis un favicon interférait d'une façon ou d'une autre. Tâtonnement est le mot poli.

Puis OAuth.

Sept commits. C'est là que j'ai failli abandonner le projet.

Claude.ai nécessite OAuth 2.1 pour les connexions MCP. La spec définit des endpoints de découverte, des flux d'autorisation, l'échange de tokens, PKCE. Sur le papier c'est propre. En pratique, les contraintes ne sont pas documentées assez clairement pour implémenter sans essai-erreur. Et chaque essai nécessite un déploiement Vercel complet parce que vous ne pouvez pas tester le flux OAuth Claude.ai localement. Déployer, attendre 30 secondes, cliquer connecter dans Claude, regarder ça échouer, lire l'erreur cryptique, corriger, redéployer. Répéter.

Ce que j'ai appris en brûlant ces 7 commits : Claude ignore les URLs d'endpoint que vous déclarez dans vos métadonnées OAuth. Vous pouvez définir authorization_endpoint à /oauth/authorize toute la journée. Claude lit le champ issuer et construit les chemins lui-même. Vos endpoints doivent être à /authorize et /token à la racine, point. J'ai découvert ça après avoir déployé 3 fois avec différentes configurations de chemins.

Un autre type d'échec : mes route handlers retournaient Response.json(). API Web standard. Marche localement. Marche dans les tests. Sur Vercel en production, ça retourne une réponse vide sans erreur, sans log, sans indication que quelque chose a mal tourné. Juste un 200 vide et un flux OAuth cassé. La solution c'est NextResponse de next/server. J'ai trouvé ça dans une issue GitHub avec 3 upvotes enterrée dans un thread sur autre chose.

Et celui qui m'a coûté une soirée : j'ai défini mon secret JWT avec echo "value" | vercel env add. La commande echo ajoute un retour à la ligne. Le retour à la ligne devient partie du secret. Chaque signature JWT échoue silencieusement. Chaque échange de token retourne "invalid_grant." Les logs ne disent rien d'utile. J'ai utilisé printf au lieu d'echo, redéployé, et tout a marché. J'ai fixé le plafond un moment après ça 💀

C'était le commit 14 sur 16. Les deux derniers étaient du nettoyage.

Le tout aurait été 3 ou 4 commits propres si les contraintes avaient été documentées. Mais c'est toujours le jeu quand vous intégrez avec des systèmes dont vous découvrez le comportement exact en déployant et échouant. La spec MCP est encore jeune. L'implémentation de Claude.ai a des bugs connus, surtout autour du refresh de token. Traitez toute intégration MCP comme un prototype de production pour l'instant. Assez stable pour un usage quotidien. Pas quelque chose que j'expédierais à des clients payants sans plus de durcissement.

Pour l'image plus large de comment ces services se connectent, j'ai documenté l'architecture complète avec cron jobs, mémoire, et tableau de bord dans un article précédent. Le serveur MCP se branche sur ce même pattern.

À quoi ça ressemble maintenant

Après le baptême des 16 commits, voici où on en est.

J'ouvre Claude et je tape :

Quelque chose a besoin de mon attention ce matin ?

Claude frappe le serveur MCP. Le serveur interroge Convex pour les données utilisateur et de crédits, Supabase pour la santé des sauvegardes et crons, l'API de contenu pour le statut de publication, et un adaptateur Discord pour les alertes récentes filtrées par gravité. La première fois que j'ai lancé ça, la réponse a pris 11 secondes et les données de crédit sont revenues comme un blob JSON brut que Claude a joyeusement décrit comme "des informations financières." Pas exactement l'expérience cockpit polie.

Après avoir ajusté les descriptions d'outils et le format de réponse, ça retourne quelque chose d'effectivement utile :

2 éléments nécessitent attention :

1. Anomalie de crédit sur [ClientApp] : utilisateur sa57*** montre 10 crédits,
   attendu 600. Dérive : -590. Signalé il y a 14h, non résolu.
2. Erreur flux RSS sur ContentForge : "Stories by [author]" down 
   depuis 3h. Pas de nouveau contenu depuis 1h00.
Tout le reste au vert :
- 3/3 sauvegardes complétées
- Pipeline de contenu : 6 scrapés, 3 publiés, social programmé 
  jusqu'au 27 fév
- Pas d'autres anomalies sur 16 utilisateurs actifs

Voici ce à quoi je ne m'attendais pas : les descriptions d'outils comptent plus que le code. Claude décide quels outils appeler selon les descriptions que vous écrivez. Et il interprète les résultats selon ce que vous lui avez dit que l'outil fait.

Attendez, laissez-moi reformuler ça. Ce n'est pas que les descriptions "comptent plus" dans un sens abstrait. C'est que j'ai passé 2 heures à écrire du TypeScript et 45 minutes à réécrire des descriptions, et les descriptions ont bougé l'aiguille dix fois plus.

Ma première tentative pour l'outil de monitoring de crédits :

Name: "get_credits"
Description: "Query credit data from Convex"

Claude l'a appelé. A récupéré du JSON brut. Me l'a décrit comme "des informations financières qui semblent contenir des soldes utilisateur." Techniquement correct. Complètement inutile à 6h du matin.

Deuxième version :

Name: "check_credit_anomalies"  
Description: "Trouve les utilisateurs dont le solde de crédit actuel 
diffère de leur solde attendu de plus de 10%. Retourne l'ID utilisateur 
(anonymisé), solde actuel, solde attendu, et dérive. 
Signaler toute dérive de plus de 50 crédits comme urgente."

Mêmes données sous-jacentes. Même requête Convex. Mais maintenant Claude retourne une liste priorisée avec des flags de gravité et me dit lesquels nécessitent une attention immédiate. La description est un prompt. Traitez-la comme tel. Écrivez-la comme si vous briefiez un dev junior qui a besoin de savoir ce qui compte, pas juste ce que la fonction retourne.

Ça s'applique à chaque outil. Un vérificateur de sauvegarde décrit comme "get backup status" vous donne des timestamps et tailles de fichiers. Un décrit comme "vérifier si des sauvegardes programmées ont échoué ou sont en retard dans les dernières 24h, signaler les sauvegardes manquantes par nom d'app" vous donne des réponses actionnables.

J'ai réalisé pendant cette phase que le TypeScript est presque irrelevant. N'importe quel dev peut écrire une fonction qui interroge Convex. La partie difficile c'est écrire des descriptions qui font que Claude fait la bonne chose du premier coup. Si vous avez travaillé avec des contrats de prompt, même principe. Vous ne partagez plus de code. Vous partagez l'intention, les contraintes, et le comportement attendu. La description d'outil EST le contrat entre vous et Claude.

Je peux suivre naturellement. "Montre-moi le log de transaction de cet utilisateur" renvoie Claude vers le serveur MCP avec une requête Convex ciblée. "Quand le flux RSS a-t-il marché pour la dernière fois" frappe un outil différent. La conversation coule mais les données sont structurées en dessous.

La vérification matinale est passée de 15 minutes de scroll et context-switching à environ 30 secondes et peut-être une question de suivi.

À deux occasions cette semaine passée, ça a attrapé des problèmes que j'aurais ratés jusqu'à ce qu'un utilisateur se plaigne. Pas parce que les alertes n'étaient pas là. Parce qu'elles étaient enterrées.

Ce qui change quand vous donnez à Claude accès à vos vraies données ce n'est pas la portée. Les données étaient toujours là. Vous arrêtez d'être le routeur. Mon téléphone buzz encore avec 43 notifications Discord chaque matin. Je ne les lis juste plus en premier.

La stack, pour ceux qui veulent en construire une

Un serveur MCP sécurisé avec OAuth 2.1, déployé et qui tourne, coûte zéro dollars. Le tier gratuit de Vercel le gère. Pas de base de données nécessaire. Pas de facture mensuelle. Ce que j'ai décrit au-dessus est la base, mais c'est extensible à tout ce dont vous avez besoin. Chaque nouvelle source de données est une fonction d'outil de plus. Chaque nouvelle question que vous voulez poser à Claude est une description de plus à écrire. L'architecture ne change pas.

Le setup complet c'est 6 fichiers de routes dans une app Next.js. Les tokens sont des JWT stateless signés avec jose. L'endpoint authorize auto-approuve parce que c'est un serveur personnel.

Ce que vous devez comprendre avant de commencer :

Pouvez-vous interroger vos sources de données depuis une fonction serverless ? Si oui, chacune devient un outil. Si vos données vivent derrière un VPN ou nécessitent une connexion persistante, Vercel serverless ne marchera pas et vous aurez besoin d'un modèle de déploiement différent.

Êtes-vous à l'aise avec OAuth 2.1 ? Si vous n'avez jamais implémenté un serveur OAuth avant, budgetez une journée complète pour la couche auth seule. Les outils MCP eux-mêmes sont triviaux. La danse OAuth c'est là que les projets meurent.

Le setup prend environ une heure si vous connaissez les contraintes exactes à l'avance. Puisque j'ai déjà payé la taxe de 4 heures et documenté les pièges ci-dessus, vous pourriez avoir de la chance.

Le lendemain matin

Logo rentierdigital Phil développeur SaaS outils IA surveillance applications
Le logo qui surveille vos apps mieux que vous

Petite île au nord du Panama. Un mec sur la plage vendait du café artisanal plus cher que Starbucks. J'en ai acheté un parce que j'étais à moitié endormi et qu'il était persuasif. Je préfère le café instantané. Ne me jugez pas.

Je me suis assis, j'ai ouvert Claude sur mon téléphone, tapé "quelque chose de cassé," eu la réponse en 4 secondes, et suis retourné fixer l'océan. Discord avait 38 notifications non lues. Je ne l'ai pas ouvert.

Le serveur MCP ne m'a pas donné de nouvelles données. Tout ce qu'il sait était déjà dans mes canaux Discord, mes bases de données, mes logs cron. Il m'a juste remplacé comme algorithme de tri. Et honnêtement, j'étais un algorithme de tri terrible 🤷‍♂️

Si vos side projects se multiplient plus vite que votre attention, la réponse n'est probablement pas un meilleur tableau de bord.

Si vous construisez des trucs et voulez occasionnellement lire qu'ils cassent en production, suivez le mouvement.


* Oui, l'image de couverture est générée par IA. Mes compétences artistiques plafonnent aux diagrammes en boîtes dans Excalidraw.


Quand vos applications SaaS deviennent un labyrinthe de notifications et de monitoring fragmenté, il est temps de repenser votre stratégie. Découvrez comment un développeur a résolu ce casse-tête avec Claude et un serveur MCP personnalisé.

Rejoindre la newsletter de production