nessie

MCP i eines connectades

Connecta Nessie a servidors MCP i empareja un agent extern perquè pugui treballar a Nessie en nom teu.

Què és MCP

El Model Context Protocol és una convenció petita i oberta per descriure eines a un model de llenguatge i per invocar-les. Un servidor declara què pot fer (una llista d’eines amb nom, cadascuna amb un esquema JSON per als seus arguments), i un client descobreix aquesta llista i les invoca en nom del model. És deliberadament sobri: el valor és que una integració escrita una sola vegada funciona amb tots els models i tots els productes que el parlen.

Nessie es troba a tots dos costats

Val la pena ser precisos, perquè les dues meitats es confonen fàcilment i viuen en camins deliberadament diferents.

  • Sortint. Nessie és un client de MCP. Connectes servidors que gestiona un altre, i les eines que exposen esdevenen eines que els teus agents poden fer servir. La superfície de gestió és /api/mcp/*.
  • Entrant. Nessie és també un servidor de MCP. Un agent que s’executa en un altre lloc (Claude Code o Codex a l’ordinador d’un desenvolupador) pot cridar els teus taulers i documents. Aquest endpoint és POST /mcp, fora de /api a propòsit, perquè els dos significats oposats no quedin a un sol segment de distància.

Connectar un servidor de MCP a Nessie

Entrades del catàleg i instàncies

Hi ha dos objectes, i la distinció importa:

  • Una entrada de catàleg és la definició d’un servidor: el seu nom, el seu transport i com s’autentica. S’escriu una vegada i es pot compartir.
  • Una instància és aquesta entrada instal·lada en un àmbit determinat, amb les credencials que hi donen accés. Instal·lar la mateixa entrada per a dos equips diferents produeix dues instàncies.

La majoria de persones no toquen cap de les dues directament. La porta d’entrada del panell és l’app store a /admin/apps: navega pel mateix catàleg amb els detalls de connexió amagats, i Connect en un anunci crea una instància. Els operadors que volen els objectes en brut usen /api/mcp/catalog i /api/mcp/instances.

Si un servidor encara no és al catàleg, POST /api/mcp/discover rep una URL i retorna el que el servidor diu de si mateix, i POST /api/mcp/library/import carrega una entrada de la biblioteca pública de servidors. Una primera connexió també es pot crear directament des d’una URL amb POST /api/apps/custom.

Transports

Nessie es connecta a servidors remots per HTTP. Funcionen dos transports: HTTP en flux (streamable HTTP, el transport actual de MCP) i el transport HTTP+SSE, més antic. Tria http, tret que el servidor només ofereixi sse.

El transport stdio es rebutja per als connectors que tu escrius. stdio vol dir que el servidor de MCP és un procés local que inicia l’amfitrió, i executar programes arbitraris dins del servidor de Nessie no és una cosa que un operador pugui revisar de manera significativa. Els servidors que només es publiquen com a paquet per executar-se en local s’eliminen de les importacions de la biblioteca pel mateix motiu. El valor ws existeix al camp de protocol però no està implementat: una connexió que l’usi falla quan Nessie intenta obrir-la.

Com s’autentica Nessie davant del servidor

Una entrada de catàleg declara un de cinc mètodes: none, api_key, bearer, basic o oauth2.

api_key és injecció de capçalera, i l’entrada indica quina capçalera i quin prefix: així, Authorization: Bearer …, X-API-Key: … i X-Auth-Token: Token … són el mateix mètode configurat de maneres diferents. La clau s’enganxa una sola vegada amb POST /api/mcp/instances/:instanceId/secret i no es torna a llegir mai més; cap resposta de les rutes d’instància no porta una referència a la credencial.

oauth2 té dues formes. Un servidor amb endpoints fixos els té a la seva entrada de catàleg. Un servidor que publica metadades estàndard no necessita res configurat: Nessie llegeix els documents de recurs protegit i d’autoritat d’autorització, i es crea un client a si mateix mitjançant registre dinàmic de clients o mitjançant el document de metadades de l’ID de client que publica a la seva pròpia ruta well-known. En tots dos casos, el handshake és POST /api/mcp/instances/:instanceId/oauth/start, seguit de la redirecció del proveïdor de tornada a GET /api/mcp/oauth/callback. El token d’estat és d’un sol ús, es guarda a la base de dades, és vàlid durant deu minuts, i PKCE s’usa sempre.

Hi ha una norma que sorprèn la gent: ni tan sols un propietari d’organització pot adjuntar un token personal o d’OAuth a una altra persona. Una credencial compartida només és possible per a una entrada d’API key que s’hagi validat com a tal; qualsevol altra cosa es rebutja. Una credencial que pertany a una persona continua sent una credencial que pertany a aquella persona.

On s’emmagatzemen les credencials

Els secrets dels connectors i els paquets de tokens d’OAuth s’emmagatzemen a la teva pròpia base de dades Postgres, en sobres AES-256-GCM amb un propòsit definit, sota l’anell de claus de xifratge del desplegament, que té la seva pròpia versió independent, i només s’hi accedeix mitjançant referències opaques secret_*. Deliberadament no són al magatzem extern de secrets que guarda la resta de secrets de Nessie. Un desplegament de producció que no ha configurat l’emmagatzematge xifrat es nega a arrencar, en lloc de completar un handshake d’OAuth que després perdria.

On arriba una connexió

Una instància s’instal·la en un de sis àmbits: system, organization, project, team, channel o user. Aquest àmbit és un sostre fix de les execucions que el poden fer servir, i les concessions només el poden estrènyer més, mai ampliar-lo. Així, un connector que has instal·lat amb el teu propi compte no es pot convertir en un de per a tota l’organització en silenci.

Qui pot instal·lar on segueix la mateixa forma: els propietaris i administradors de l’organització gestionen les instal·lacions a qualsevol àmbit; tots els altres instal·len connectors al seu propi àmbit user i poden veure les instal·lacions compartides a què tenen dret d’accedir.

Donar una eina a un agent

Connectar un servidor no passa les seves eines a ningú. Quan s’instal·la una instància, Nessie la sondeja i projecta cada eina que anuncia al registre d’eines, que és el que l’administració mostra a /admin/advanced/tools: eines integrades, eines de MCP, eines de paquets i eines de l’executor en una sola llista, cadascuna amb el seu origen i el seu estat.

Una eina projectada té un de tres estats: active, pending_review o disabled. Les eines d’una instal·lació d’àmbit compartit arriben com a pending_review i s’han d’aprovar en aquella pàgina abans que un agent les vegi; les eines de la teva pròpia instal·lació d’àmbit user són actives de seguida, perquè l’única persona exposada ets tu. La revisió és recurrent, no un tràmit d’un sol cop: si un sondeig posterior troba que la descripció o l’esquema d’una eina han canviat, torna a pending_review i deixa de ser ofertada fins que algú revisi el canvi.

Quins agents poden cridar després una eina activa es decideix de dues maneres:

  • Una connexió creada des de l’app store requereix una concessió explícita. Un agent només obté l’eina quan una concessió la nomena, i la concessió es comprova contra l’empremta actual de l’eina, de manera que una eina canviada necessita una decisió nova.
  • En cas contrari, l’eina està activada per defecte per als agents a l’abast del seu àmbit, i cada agent es pot desactivar al panell de detall de l’eina.

Una concessió nomena exactament una entitat: un rol o un agent, mai els dos, i una denegació guanya a una autorització. Per a una connexió d’àmbit user la qüestió no es planteja: qualsevol agent amb qui parla aquesta persona la pot fer servir, i l’única cosa que la retira és un interruptor de desactivació explícit.

Una cosa que es busca fàcilment al lloc equivocat: /settings/accounts del panell són els comptes connectats, és a dir, Slack, bústies de correu, subscripcions de models i els enllaços d’OAuth a Linear, Jira, GitHub i Trello que donen suport als taulers externs. Els connectors de MCP són a /admin/apps, i les eines que produeixen són a /admin/advanced/tools.

Portes d’aprovació a les crides d’eines

Algunes crides d’eines s’aturen i esperen una persona. Quan això passa, l’execució no falla: la invocació es congela (el nom de l’eina, un hash dels seus arguments i l’estat suficient per reprendre-la) i apareix una sol·licitud d’aprovació a /api/approvals perquè algú la respongui. Reprendre és idempotent, de manera que una aprovació duplicada no pot executar la crida dues vegades. La persona que decideix veu un resum acotat del que es demana; els arguments exactes i l’execució d’origen es queden al servidor.

Hi ha dues portes diferents, i es configuren de manera diferent.

  • Les portes estructurals són una propietat de certes eines integrades i no són configurables. Enviar correu des de la bústia d’un agent o des d’una de connectada, enviar un esborrany de Gmail, desconnectar un compte de correu, crear, canviar o cancel·lar un esdeveniment de calendari, i una acció del navegador que escriuria en un lloc on el navegador no té la sessió iniciada, estan totes gestionades així. Tenen una finestra de 24 hores per respondre-les, en lloc dels 30 minuts habituals.
  • Les portes de política provenen de regles de política sobre el parell tool/invoke. Una regla que permet una invocació pot portar requiresApproval, i sense una aprovació verificada l’avaluació retorna APPROVAL_REQUIRED en lloc d’executar-se. Les regles s’avaluen primer les denegacions i s’ordenen per àmbit.

No hem pogut confirmar quina pantalla de l’administració escriu una regla de política requiresApproval per a una eina: el model de dades i l’aplicació estan tots dos en marxa, però no sabem quina pantalla l’escriu.


Connectar un agent extern a Nessie

L’altra direcció. Nessie executa un servidor de MCP propi a POST /mcp, sobre HTTP en flux, i és sense estat: cada crida porta la seva pròpia credencial i resol el seu propi actor, de manera que no hi ha cap sessió a la qual fixar un client a una rèplica. ChatGPT, Claude, Claude Code, Codex o un agent a l’ordinador portàtil d’algú poden treballar amb els teus taulers, documents i clients des de dins de l’eina que aquella persona ja fa servir. Les instruccions pas a pas per a cada app són a Connectar ChatGPT, Claude i altres apps d’IA.

Què és realment una credencial emparellada

Aquesta és la part que cal entendre bé, perquè el model mental evident (una clau d’API amb permisos adjunts) és el model equivocat.

Una credencial emparellada no és mai una identitat. Designa una persona, i cada crida d’eina s’executa com aquesta persona, contra les mateixes funcions de servei que criden les rutes HTTP. Per tant, un agent no pot arribar a res al que la persona que l’ha cedit no podria arribar fent clic. Els àmbits només estrenyen aquest abast; no hi ha cap àmbit que l’ampliï.

La vigència es comprova a cada crida i mai no es dona per bona des del token: revocació, caducitat, la versió del token de la persona, la seva pertinença a l’organització i el seu rol actual. Una degradació, una desactivació o un «tanca la sessió a tot arreu» arriben a la pròxima crida de l’agent, no quan caduqui la credencial.

El token és opac, té el prefix nag1_, només s’emmagatzema com a hash SHA-256 i viu 90 dies. Es rebutja amb un 403 específic en qualsevol ruta que no sigui l’endpoint de MCP: presentar-lo en un altre lloc no és un èxit parcial que calgui tornar a provar, i això és el que impedeix que «he deixat a un agent els meus taulers» vulgui dir «he deixat el meu compte».

Dues maneres de connectar

Una app que pot obrir un navegador (ChatGPT, Claude, Claude Code, Codex) usa el perfil d’autorització de MCP: OAuth 2.1 amb PKCE (S256), registre dinàmic de clients a POST /mcp/oauth/register, tokens de refresc i indicadors de recurs. L’app envia la persona a /settings/security, qui tria què pot fer i a quins dels seus equips arriba, i el navegador torna a l’app amb un codi. El seu token d’accés dura una hora i es renova; la connexió dura 90 dies.

Un agent de línia d’ordres no controla cap navegador i no pot allotjar cap URL de retorn, així que s’empara amb la concessió de dispositiu (RFC 8628).

  1. L’agent crida POST /mcp/auth/device amb un clientName i els scopes que vol. Rep un device_code, un user_code curt, un verification_uri i un verification_uri_complete amb el codi ja a la consulta, a més de l’interval de sondeig i un expires_in. Imprimeix l’URI.
  2. Una persona obre aquest URI amb la sessió iniciada, a /settings/security. Veu quin agent ho demana, en nom de quin compte treballaria, fins quan, i exactament què podria fer; i tria Allow o Don’t allow.
  3. L’agent sondeja POST /mcp/auth/token amb el deviceCode, respectant authorization_pending, slow_down, expired_token i access_denied. Si tot va bé, rep { access_token, token_type, scope, expires_at }.

En qualsevol cas hi ha un pas humà, perquè la credencial hereta els drets d’una persona, de manera que una persona ha de triar cedir-los. Aquí no es crea accés de la nada. Es pot desactivar la connexió per a una organització sencera a /admin/security?tab=programs, i aquesta resposta es comprova quan algú permet una connexió, no quan un agent n’inicia una.

Descobriment

Un POST /mcp sense autenticar respon 401 amb una capçalera WWW-Authenticate que apunta a /.well-known/oauth-protected-resource (RFC 9728), que indica aquest origen com a servidor d’autorització; les seves metadades a /.well-known/oauth-authorization-server (RFC 8414) indiquen els endpoints de registre, autorització, token i revocació. Aquesta cadena és tot el que necessita un client quan ja té l’adreça. Per trobar l’adreça a partir d’un domini, un agent llegeix /.well-known/ai-catalog.json, que indica la Server Card a /mcp/server-card (MCP Server Cards, SEP-2127). Els endpoints de la concessió de dispositiu apareixen al document de recurs protegit sota x-nessie-device-authorization.

Un desplegament també pot respondre en una segona adreça, més curta: https://nessie.works/mcp, de Nessie Cloud, es reenvia a l’API, i un client que hi arriba per aquesta via rep aquesta adreça com a recurs; l’inici de sessió es queda a l’API. Una redirecció no serviria, perquè un client rebutja les metadades que indiquen una altra adreça que no sigui la que se li ha donat. L’operador enumera aquests orígens a NESSIE_MCP_ALIAS_ORIGINS.

Àmbits i eines

Els àmbits són deliberadament genèrics: un àmbit que ningú no pot explicar és un àmbit que ningú no configura bé. Una connexió que arriba a més d’un equip els enumera amb nessie_team_list, i cada altra eina després indica el seu teamId.

ÀmbitAbast
boards_readProjectes, taulers i tiquets, amb els seus comentaris, fitxers, etiquetes, tipus i llistes de verificació
boards_writeCrear, editar i moure tiquets, i tot el que hi ha en ells
documents_readEspais de coneixement, documents i fulls de càlcul
documents_writeRedactar-los i editar-los, i demanar publicar-ne un
clients_readLa safata d’entrada de clients, els contactes, les llistes de contactes, els butlletins i els formularis de contacte
clients_writeNotes, fer-se càrrec, assignar i tancar converses, llistes de contactes i preparar butlletins
clients_sendQualsevol cosa que rep un client: una resposta, un primer correu o un SMS. Mai no es marca tret que la persona el marqui
website_toolsLes eines de pàgina del web registrat del teu equip, operades en un navegador allotjat que pots controlar. Mai no està marcada tret que la persona la marqui

Una crida sense l’àmbit que necessita torna com a resultat d’eina que indica l’àmbit que cal demanar, i no com a error de transport, de manera que el model pot informar d’alguna cosa útil en lloc de tornar a intentar-ho contra un mur.

No hi ha una família d’eines separada per als seguidors externs. Un tauler és un tauler tant si les seves tasques s’originen a Nessie com si es reflecteixen des de Linear, Jira, GitHub o Trello, així que un sol conjunt d’eines les cobreix totes, i cada tasca indica d’on prové. Allò que canvia és l’escriptura: un tauler reflectit d’una font de només lectura rebutja una escriptura amb paraules, i diu a l’agent que l’altre sistema és propietari del tiquet i que tornar-ho a intentar no servirà de res.

Publicar un document és una aprovació, mai un àmbit

Els agents redacten; una persona publica. Aquesta norma s’aplica als agents propis de Nessie rebutjant directament un actor que sigui agent, però una credencial emparellada es resol com a persona, de manera que aquest rebuig no la pot detectar. La porta ha d’estar en un altre lloc.

Per això nessie_doc_publish no publica. Obre una aprovació knowledge.page.publish, la mateixa que obre un agent de Nessie, i retorna { status: "awaiting_approval", approvalId }. Consultar-la torna la mateixa sol·licitud en lloc d’obrir-ne una de segona. La decisió es pren com totes les altres decisions de publicació de Nessie: per una persona, sobre aquell document, en el moment que el document existeix.

Deliberadament no hi ha cap àmbit de publicació. N’hi havia un (una casella marcada en el moment d’emparellar que deia que aquest agent podia publicar), i es va retirar. Decidia, una vegada i durant noranta dies, una pregunta que el producte fa per document a tot arreu, i ho decidia abans que el document existís; així que la persona que la marcava no podia saber què estava acceptant que es publiqués. nessie_doc_publish necessita documents_write, perquè demanar no és gratuït: llegeix la pàgina i posa una decisió davant d’algú; però aquest àmbit no concedeix res sobre la publicació.

A la sol·licitud es registren dues identitats, i la distinció és determinant. El sol·licitant és la credencial, mai la persona en nom de la qual actua. L’aprovador requerit és aquesta persona, i només ella pot respondre. Si la sol·licitud nomenés la persona com a sol·licitant, la regla que impedeix que algú aprovi la seva pròpia sol·licitud la descartaria justament de l’única persona amb dret a decidir.

Veure i revocar el que està emparellat

Els agents emparellats d’una persona apareixen llistats a /settings/security, amb una etiqueta i un control de revocació per a cadascun. Un propietari o administrador de l’organització veu totes les credencials de l’organització a /admin/security?tab=programs (com es diu cadascuna, què pot fer, si és vigent i de quin compte es fa servir) i les pot revocar totes. La llista personal és només per a la mateixa persona: una llista de credencials vigents és una llista de punts d’entrada, no una lectura general.

Les escriptures fetes amb una credencial s’atribueixen a la persona que la fa servir i porten l’identificador de la credencial a les metadades d’auditoria, de manera que el registre pot distingir una edició pròpia d’una persona d’una que el seu agent ha fet en nom seu.

Què encara no està disponible

Ho diem explícitament, perquè la distància entre les capacitats d’un protocol i les d’un servidor concret és on la documentació sol fallar:

  • El xat, els canals, les execucions i la gestió d’agents no estan exposats per MCP.
  • Un programa prepara un butlletí i una persona l’envia: res no envia, programa ni esborra una campanya, i el correu mateix s’escriu al dissenyador.
  • L’enllaç i la fusió de contactes, els canvis d’esquema del CRM, les importacions i la configuració de canals es mantenen en mans d’una persona al navegador.
  • L’interruptor de connexió és una configuració a nivell d’organització. Els equips i les persones no poden definir-ne de propis avui.