nessie

MCP et outils connectés

Connectez Nessie à des serveurs MCP, et appairez un agent externe pour qu’il puisse travailler dans Nessie en votre nom.

Qu’est-ce que MCP

Le Model Context Protocol est une convention ouverte, petite et volontairement sobre, qui décrit des outils à un modèle de langage et permet de les appeler. Un serveur déclare ce qu’il sait faire — une liste d’outils nommés, chacun avec un schéma JSON pour ses arguments — et un client découvre cette liste et invoque les outils pour le compte du modèle. Sa force tient à sa simplicité : une intégration écrite une seule fois fonctionne avec chaque modèle et chaque produit qui le parle.

Nessie se trouve des deux côtés

Il faut être précis sur ce point, car les deux moitiés se confondent facilement, et elles vivent sur des chemins volontairement différents.

  • Sortant. Nessie est un client MCP : vous connectez des serveurs gérés par d’autres, et les outils qu’ils exposent deviennent des outils que vos agents peuvent utiliser. La surface de gestion se trouve sous /api/mcp/*.
  • Entrant. Nessie est aussi un serveur MCP. Un agent qui tourne ailleurs, comme Claude Code ou Codex sur la machine d’un développeur, peut accéder à vos tableaux et à vos documents. Ce point de terminaison est POST /mcp, volontairement en dehors de /api, pour que deux sens opposés ne soient pas à un seul segment de chemin l’un de l’autre.

Connecter un serveur MCP à Nessie

Entrées du catalogue et instances

Il existe deux objets, et la distinction compte :

  • Une entrée du catalogue est la définition d’un serveur : son nom, son transport, sa méthode d’authentification. Elle est écrite une seule fois et peut être partagée.
  • Une instance est cette entrée installée à un périmètre donné, avec les identifiants qui permettent de l’atteindre. Installer la même entrée pour deux équipes différentes produit deux instances.

La plupart des utilisateurs n’ont jamais affaire directement à l’un ou l’autre. Le point d’entrée dans l’admin est la boutique d’applications, à /admin/apps : elle parcourt le même catalogue sans les détails de connexion, et Connecter sur une fiche crée une instance. Les opérateurs qui veulent les objets bruts utilisent /api/mcp/catalog et /api/mcp/instances.

Si un serveur ne figure pas encore dans le catalogue, POST /api/mcp/discover prend une URL et renvoie ce que le serveur dit de lui-même, et POST /api/mcp/library/import importe une entrée depuis la bibliothèque publique de serveurs. Une première connexion peut aussi être créée directement depuis une URL, avec POST /api/apps/custom.

Transports

Nessie se connecte aux serveurs distants en HTTP. Deux transports fonctionnent : le HTTP en flux continu (le transport MCP actuel) et l’ancien transport HTTP+SSE. Choisissez http, sauf si le serveur ne propose que sse.

Le transport stdio est refusé pour les connecteurs que vous écrivez. stdio signifie que le serveur MCP est un processus local lancé par l’hôte, et faire exécuter des programmes arbitraires à l’intérieur du serveur Nessie n’est pas quelque chose qu’un opérateur peut examiner de façon utile. Les serveurs publiés uniquement sous forme de paquet à exécuter en local sont écartés des importations de bibliothèque pour la même raison. Une valeur ws existe dans le champ de protocole mais n’est pas implémentée : une connexion qui l’utilise échoue dès que Nessie tente de l’ouvrir.

Comment Nessie s’authentifie auprès du serveur

Une entrée du catalogue déclare l’une de cinq méthodes : none, api_key, bearer, basic ou oauth2.

api_key est une injection d’en-tête : l’entrée indique lequel et quel préfixe. Ainsi, Authorization: Bearer …, X-API-Key: … et X-Auth-Token: Token … relèvent de la même méthode, simplement configurée différemment. La clé elle-même se saisit une seule fois avec POST /api/mcp/instances/:instanceId/secret et n’est plus jamais relisible ensuite. Aucune réponse des routes d’instance ne renvoie de référence d’identifiant.

oauth2 existe sous deux formes. Un serveur aux points de terminaison fixes les indique dans son entrée du catalogue. Un serveur qui publie les métadonnées standard n’a rien à configurer : Nessie lit les documents de ressource protégée et de serveur d’autorisation, et se crée lui-même un client, par enregistrement dynamique de client ou par le document de métadonnées d’identifiant client qu’il publie à son propre chemin well-known. Dans les deux cas, l’échange commence par POST /api/mcp/instances/:instanceId/oauth/start, puis le fournisseur redirige vers GET /api/mcp/oauth/callback. Le jeton d’état est à usage unique, conservé dans la base et valable dix minutes, et PKCE est toujours utilisé.

Une règle surprend souvent : même un propriétaire d’organisation ne peut pas rattacher à quelqu’un d’autre un jeton personnel ou OAuth. Un identifiant partagé n’est possible que pour une entrée à clé d’API validée comme telle ; tout autre cas est refusé. Un identifiant qui appartient à une personne reste un identifiant qui appartient à cette personne.

Où sont conservés les identifiants

Les secrets des connecteurs et les ensembles de jetons OAuth sont stockés dans votre propre base Postgres, dans des enveloppes AES-256-GCM propres à chaque usage, sous le trousseau de chiffrement du déploiement, versionné indépendamment. Ils ne sont adressés que par des références opaques secret_*. Ils ne se trouvent volontairement pas dans le coffre de secrets externe qui conserve les autres secrets de Nessie. Un déploiement de production qui n’a pas configuré le stockage chiffré refuse de démarrer, plutôt que de terminer une poignée de main OAuth qu’il laisserait ensuite tomber.

Où une connexion porte

Une instance est installée à l’un de six périmètres : system, organization, project, team, channel ou user. Ce périmètre constitue un plafond strict pour les exécutions qui peuvent l’utiliser, et les autorisations ne peuvent que le restreindre davantage, jamais l’élargir. Un connecteur que vous avez installé avec votre propre compte ne peut donc pas discrètement devenir un connecteur pour toute l’organisation.

Qui peut installer où suit la même logique : les propriétaires et administrateurs de l’organisation gèrent les installations à n’importe quel périmètre ; tout le monde installe les connecteurs à son propre périmètre user, et peut voir les installations partagées auxquelles il a accès.

Donner un outil à un agent

Connecter un serveur ne transmet ses outils à personne. Lorsqu’une instance est installée, Nessie l’interroge et projette chaque outil qu’elle annonce dans le registre des outils, que l’admin affiche à /admin/advanced/tools : outils intégrés, outils MCP, outils de bundles et outils de l’exécuteur réunis dans une seule liste, chacun avec sa source et son statut.

Un outil projeté a l’un de trois statuts : active, pending_review ou disabled. Les outils issus d’une installation de périmètre partagé arrivent en pending_review et doivent être approuvés sur cette page avant qu’un agent ne les voie. Ceux de votre propre installation de périmètre user sont actifs immédiatement, puisque la seule personne concernée est vous. La revue est récurrente, et non une formalité ponctuelle : si une interrogation ultérieure constate que la description ou le schéma d’un outil a dérivé, l’outil repasse en pending_review et n’est plus proposé tant que quelqu’un n’a pas examiné le changement.

Quels agents peuvent alors appeler un outil actif se décide de deux façons :

  • Une connexion créée depuis la boutique d’applications exige une autorisation explicite. Un agent n’obtient l’outil que si une autorisation le désigne, et l’autorisation est vérifiée contre l’empreinte actuelle de l’outil : un outil modifié exige donc une nouvelle décision.
  • Sinon, l’outil est activé par défaut pour les agents joignables dans le périmètre de son installation, et chaque agent peut être désactivé individuellement depuis le panneau de détail de l’outil.

Une autorisation désigne exactement un bénéficiaire, un rôle ou un agent, jamais les deux, et un refus l’emporte sur une autorisation. Pour une connexion de périmètre user, la question ne se pose pas : tout agent avec lequel cette personne parle peut l’utiliser, et seule une désactivation explicite l’en empêche.

Un point facile à chercher au mauvais endroit : /settings/accounts dans l’admin correspond aux comptes connectés, c’est-à-dire Slack, les messageries, les abonnements aux modèles et les liaisons OAuth vers Linear, Jira, GitHub et Trello qui alimentent les tableaux externes. Les connecteurs MCP se trouvent à /admin/apps, et les outils qu’ils produisent à /admin/advanced/tools.

Contrôles d’approbation sur les appels d’outils

Certains appels d’outils s’arrêtent et attendent une personne. Dans ce cas, l’exécution n’échoue pas : l’appel est figé (le nom de l’outil, un hachage de ses arguments et assez d’état pour reprendre), et une demande d’approbation apparaît dans /api/approvals, pour que quelqu’un y réponde. La reprise est idempotente : une approbation dupliquée ne peut pas exécuter l’appel deux fois. La personne qui décide voit un résumé borné de ce qui est demandé ; les arguments exacts et l’exécution d’origine restent côté serveur.

Il existe deux contrôles distincts, configurés différemment :

  • Les contrôles structurels dépendent de certains outils intégrés et ne sont pas configurables. L’envoi d’e-mails depuis la boîte de messagerie d’un agent ou depuis une boîte connectée, l’envoi d’un brouillon Gmail, la déconnexion d’un compte de messagerie, la création, la modification ou l’annulation d’un événement de calendrier, et une action de navigateur qui écrirait sur un site où le navigateur n’est pas connecté, sont tous soumis à ce contrôle. Ils disposent d’une fenêtre de 24 heures pour recevoir une réponse, au lieu des 30 minutes habituelles.
  • Les contrôles de politique proviennent des règles de politique sur le couple tool/invoke. Une règle qui autorise un appel peut porter requiresApproval ; sans approbation vérifiée, l’évaluation renvoie APPROVAL_REQUIRED au lieu d’exécuter l’appel. Les règles sont évaluées en commençant par les refus, et ordonnées par périmètre.

Nous n’avons pas pu confirmer quel écran de l’admin rédige une règle de politique requiresApproval pour un outil : le modèle de données et l’application sont tous deux en place, mais l’écran qui écrit une telle règle reste à identifier.


Connecter un agent extérieur à Nessie

Dans l’autre sens, Nessie exécute son propre serveur MCP à POST /mcp, en HTTP en flux continu, et il est sans état : chaque appel porte son propre identifiant et résout son propre acteur, si bien qu’aucune session n’oblige à épingler un client sur un réplica. ChatGPT, Claude, Claude Code, Codex ou un agent sur l’ordinateur portable de quelqu’un peuvent travailler sur vos tableaux, vos documents et vos clients depuis l’outil que cette personne utilise déjà. La marche à suivre pour chaque application figure dans Connecter ChatGPT, Claude et d’autres applications d’IA.

Ce qu’est réellement un identifiant appairé

C’est la partie à bien comprendre, car le modèle mental le plus évident, celui d’une clé d’API à laquelle on attache des permissions, est le mauvais.

Un identifiant appairé n’est jamais une identité. Il désigne une personne, et chaque appel d’outil s’exécute au nom de cette personne, sur les mêmes fonctions de service que celles qu’appellent les routes HTTP. Un agent ne peut donc rien atteindre que la personne qui le lui a prêté ne pourrait atteindre en cliquant. Les périmètres ne font jamais que restreindre cette portée ; aucun périmètre ne l’élargit.

La validité est relue à chaque appel, jamais déduite du jeton : révocation, expiration, version du jeton de l’utilisateur, appartenance à l’organisation et rôle actuel. Une rétrogradation, une désactivation ou une déconnexion de tous les appareils prend effet au prochain appel de l’agent, et non à l’expiration de l’identifiant.

Le jeton est opaque, préfixé nag1_, stocké uniquement sous forme de hachage SHA-256, et valable 90 jours. Il est refusé avec une erreur 403 spécifique sur toute route autre que le point de terminaison MCP : le présenter ailleurs n’est pas un succès partiel à réessayer, ce qui empêche « j’ai prêté mes tableaux à un agent » de devenir « je lui ai prêté mon compte ».

Deux façons de se connecter

Une application capable d’ouvrir un navigateur (ChatGPT, Claude, Claude Code, Codex) utilise le profil d’autorisation MCP : OAuth 2.1 avec PKCE (S256), enregistrement dynamique de client sur POST /mcp/oauth/register, jetons d’actualisation et indicateurs de ressource. L’application envoie la personne vers /settings/security ; elle choisit ce que l’application peut faire et lesquelles de ses équipes elle atteint, puis le navigateur revient vers l’application avec un code. Le jeton d’accès dure une heure et est actualisé ; la connexion dure 90 jours.

Un agent en ligne de commande ne contrôle aucun navigateur et ne peut héberger aucune URL de rappel : il s’appaire donc avec le flux d’autorisation par code d’appareil (RFC 8628).

  1. L’agent appelle POST /mcp/auth/device avec un clientName et les scopes souhaités. Il reçoit un device_code, un user_code court, un verification_uri et un verification_uri_complete dont le code figure déjà dans la requête, ainsi que l’interval d’interrogation et un expires_in. Il affiche l’URI.
  2. Une personne ouvre cette URI en étant connectée, à /settings/security. Elle voit quel agent demande l’accès, au nom de quel compte il agirait, jusqu’à quand, et exactement ce qu’il pourrait faire. Elle choisit alors Autoriser ou Ne pas autoriser.
  3. L’agent interroge POST /mcp/auth/token avec le deviceCode, en tenant compte de authorization_pending, slow_down, expired_token et access_denied. En cas de succès, il reçoit { access_token, token_type, scope, expires_at }.

Dans les deux cas, une étape humaine est requise, car l’identifiant hérite des droits d’une personne : cette personne doit donc choisir de le prêter. Rien ici ne crée un accès à partir de rien. La connexion peut aussi être désactivée pour toute une organisation à /admin/security?tab=programs, et cette réponse est vérifiée au moment où quelqu’un autorise une connexion, et non au moment où un agent en démarre une.

Découverte

Un POST /mcp non authentifié répond 401 avec un en-tête WWW-Authenticate pointant vers /.well-known/oauth-protected-resource (RFC 9728), qui désigne cette origine comme serveur d’autorisation. Ses métadonnées à /.well-known/oauth-authorization-server (RFC 8414) donnent les points de terminaison d’enregistrement, d’autorisation, de jeton et de révocation. Cette chaîne suffit à un client une fois qu’il connaît l’adresse. Pour trouver l’adresse à partir d’un domaine, un agent lit /.well-known/ai-catalog.json, qui indique la Server Card à /mcp/server-card (MCP Server Cards, SEP-2127). Les points de terminaison du flux d’appareil sont listés dans le document de ressource protégée, sous x-nessie-device-authorization.

Un déploiement peut aussi répondre à une seconde adresse, plus courte : https://nessie.works/mcp de Nessie Cloud est relayé vers l’API, et un client arrivé par ce chemin reçoit cette adresse en retour comme ressource, la connexion restant sur l’API. Une redirection ne suffirait pas, car un client refuse des métadonnées qui nomment une autre adresse que celle qu’il a utilisée. L’opérateur liste ces origines dans NESSIE_MCP_ALIAS_ORIGINS.

Périmètres et outils

Les périmètres sont volontairement grossiers : un périmètre que personne ne sait expliquer est un périmètre que personne ne règle correctement. Une connexion qui atteint plusieurs équipes les liste avec nessie_team_list, et chaque autre outil désigne alors son teamId.

PérimètreAtteint
boards_readProjets, tableaux et tickets, avec leurs commentaires, fichiers, libellés, types et listes de contrôle
boards_writeCréer, modifier et déplacer des tickets, et tout ce qui s’y rattache
documents_readEspaces de connaissances, documents et tableurs
documents_writeRédiger et modifier ces documents, et demander leur publication
clients_readLa boîte de réception clients, les contacts, les listes de contacts, les newsletters et les formulaires de contact
clients_writeNotes, prise en charge, attribution et clôture de conversations, listes de contacts et préparation des newsletters
clients_sendTout ce que reçoit un client : une réponse, un premier e-mail ou un SMS. Jamais coché sans que la personne le coche elle-même
website_toolsLes outils de page du site web enregistré par votre équipe, pilotés dans un navigateur hébergé que vous pouvez contrôler. Jamais coché sans que la personne le coche elle-même

Un appel sans le périmètre dont il a besoin revient sous forme de résultat d’outil qui nomme le périmètre à demander, et non sous forme d’erreur de transport. Le modèle peut ainsi signaler quelque chose d’utile au lieu de réessayer contre un mur.

Il n’existe pas de famille d’outils distincte pour les outils de suivi externes. Un tableau reste un tableau, que ses tâches proviennent de Nessie ou soient répliquées depuis Linear, Jira, GitHub ou Trello : un seul jeu d’outils couvre les deux, et chaque tâche indique son origine. La différence porte sur l’écriture : un tableau répliqué depuis une source en lecture seule refuse une écriture par un message explicite, qui indique à l’agent que l’autre système possède le ticket et qu’il est inutile de réessayer.

Publier un document relève d’une approbation, jamais d’un périmètre

Les agents rédigent ; une personne publie. Cette règle est appliquée aux agents de Nessie en refusant purement et simplement un acteur de type agent. Mais un identifiant appairé se résout comme une personne, donc ce refus ne peut pas le bloquer. Le contrôle doit se trouver ailleurs.

Ainsi, nessie_doc_publish ne publie pas. Il ouvre une approbation knowledge.page.publish, la même que celle qu’ouvre un agent de Nessie, et renvoie { status: "awaiting_approval", approvalId }. Une interrogation renvoie la même demande au lieu d’en ouvrir une seconde. La décision se prend comme toutes les décisions de publication dans Nessie : par une personne, sur ce document, au moment où il existe.

Il n’existe volontairement aucun périmètre de publication. Il y en avait un, une case cochée au moment de l’appairage indiquant que cet agent pouvait publier, et il a été retiré. Elle décidait, une fois pour toutes et pour quatre-vingt-dix jours, une question que le produit pose document par document partout ailleurs, et elle la décidait avant que le document n’existe : la personne qui cochait ne pouvait donc pas savoir ce qu’elle acceptait de publier. nessie_doc_publish exige documents_write, car demander n’est pas gratuit : cela lit la page et place une décision devant quelqu’un. Mais ce périmètre n’accorde rien quant à la publication.

Deux identités sont enregistrées sur la demande, et la distinction est déterminante. Le demandeur est l’identifiant, jamais la personne au nom de laquelle il agit. L’approbateur requis est cette personne, et elle seule peut répondre. Si la demande désignait la personne comme demandeur, la règle qui empêche quiconque d’approuver sa propre demande disqualifierait précisément l’unique personne habilitée à décider.

Voir et révoquer ce qui est appairé

Les agents appairés d’une personne sont listés à /settings/security, avec un libellé et un contrôle de révocation pour chacun. Un propriétaire ou un administrateur d’organisation voit chaque identifiant de l’organisation à /admin/security?tab=programs (son nom, ce qu’il peut faire, s’il est actif et le compte qu’il emprunte) et peut en révoquer n’importe lequel. La liste personnelle reste réservée à chacun : une liste d’identifiants actifs est une liste de points d’appui, et non une lecture ordinaire.

Les écritures faites par un identifiant sont attribuées à la personne qui l’emprunte et portent l’identifiant du jeton dans les métadonnées d’audit. Le journal peut ainsi distinguer une modification personnelle d’une modification faite par son agent pour elle.

Ce qui n’est pas encore disponible

Dit explicitement, car l’écart entre les capacités d’un protocole et celles d’un serveur donné est précisément l’endroit où la documentation pèche d’ordinaire :

  • Le chat, les canaux, les exécutions et la gestion des agents ne sont pas exposés via MCP.
  • Un agent prépare une newsletter et une personne l’envoie : rien n’envoie, ne planifie ni ne supprime une campagne, et l’e-mail lui-même est rédigé dans l’éditeur.
  • La liaison et la fusion de contacts, les modifications du schéma CRM, les imports et la configuration des canaux restent du ressort d’une personne, dans le navigateur.
  • L’interrupteur de connexion est un réglage au niveau de l’organisation. Les réglages par équipe et les dérogations personnelles ne peuvent pas être définis aujourd’hui par une équipe ou une personne.