Quan una aplicació basada en intel·ligència artificial necessita interactuar amb sistemes empresarials reals, el protocol MCP (Model Context Protocol) s'ha convertit en l'estàndard de facto. No obstant això, portar un servidor MCP a un entorn de producció amb NestJS implica molt més que seguir la documentació bàsica del SDK. Requereix una arquitectura que respecti els principis d' injecció de dependències, aïllament per tenant i seguretat perimetral. En Q2BSTUDIO, com a empresa de desenvolupament de programari i tecnologia, hem implementat aquest patró en múltiples clients, combinant ia per a empreses amb capacitats d'agents IA sense sacrificar la solidesa del backend.
El principal repte sorgeix quan l' equip decideix connectar un model de llenguatge a serveis ja existents. Una API REST típica de NestJS sap gestionar autenticació, lògica de negoci i multiinquilí. Però un LLM no pot invocar aquests endpoints directament llevat que hi hagi una capa d'eines (tools) que el model entengui. El MCP defineix precisament aquesta interfície: un conjunt d'eines amb esquemes i descripcions que el model interpreta per decidir què anomenar i amb quins arguments. La temptació inicial és muntar un servidor MCP ràpid amb Express en poques línies, però això significa duplicar la lògica d'accés a base de dades, sessions i logging que ja té NestJS. L' enfocament correcte és integrar el servidor MCP dins del mateix contenidor d' injecció de dependències, de manera que cada eina pugui usar els mateixos serveis que fa servir la resta de l' aplicació.
Això és precisament el que hem fet en projectes recents. En lloc d'escriure un únic arxiu de 900 línies amb closures, es defineix una classe per domini d'eines, decorada amb @Injectable() i que rep en el seu constructor els serveis necessaris (per exemple, un servei de comandes). Cada classe exposa un mètode register() que rep una instància de McpServer i un context d'execució. Aquest context conté la informació del tenant i l'entorn (sandbox o producció), que s'ha extret prèviament de la clau API. Registrar l'eina dins d'aquest mètode permet que el SDK infereixi els tipus automàticament a partir de l'esquema Zod, evitant genèrics complexos. Després, una fàbrica central recull totes les eines mitjançant un token multi-provider i construeix un nou servidor per cada petició. D'aquesta manera, cada sol·licitud al servidor MCP té el seu propi context de tenant, i el controlador només s'encarrega de connectar el transport Streamable HTTP de forma stateless.
L'elecció del mode stateless no és casual. Per a un servidor que respon a agents IA en producció, el més senzill és no mantenir sessions entre peticions. Cada trucada a l'endpoint /mcp crea un servidor nou, processa l'eina i retorna la resposta. Això simplifica l' escalat horitzontal i facilita la integració amb balancejadors de càrrega. El controlador de NestJS injecta @Res() per evitar que els interceptors de resposta interfereixin amb l'streaming, i s'assegura de netejar el transport en tancar la connexió. Aquest controlador ha de romandre el més simple possible, delegant tota la lògica de negoci a les eines registrades.
Una de les lliçons més importants que hem après en posar això en producció és que la qualitat de les descripcions de les eines determina el comportament del model. No n'hi ha prou amb posar tipus i noms. Cal descriure el comportament esperat i les restriccions. Per exemple, en lloc d'un paràmetre 'limit: number', és millor escriure 'Nombre màxim de comandes a retornar, ordenats del més recent al més antic'. També convé acotar els valors possibles amb enumeracions i valors per defecte, perquè un model pot inventar estats o IDs que no existeixen. Hem vist com dividir una eina genèrica de recerca en diverses petites (llistar comandes, obtenir comanda per ID, buscar per client) redueix dràsticament els errors d'invocació. I quan alguna cosa falla, el millor és retornar un error estructurat amb isError: true i un missatge clar que indiqui quina alternativa usar, en lloc de llançar una excepció que el model no sap interpretar.
L'àmbit multiinquilí és un altre punt crític. És molt temptador afegir un paràmetre 'tenantId' a cada eina perquè el model el proporcioni, però això és un risc de seguretat enorme: un model pot al·lucinar un tenant que no li correspon. La solució correcta és que la identitat del llogater es resolgui a partir de la credencial (la clau API) abans que el servidor MCP processi la petició, i que aquest valor s'injecti en el context de totes les eines mitjançant el tancament de la fàbrica. D'aquesta manera, el model mai veu el tenantId; simplement opera dins dels límits del llogater autenticat. El mateix passa amb l'entorn: una clau de sandbox permet accedir a eines de prova, com sembrar dades de prova, mentre que una clau de producció les oculta. Això s'implementa amb un simple if en el mètode register() de l'eina.
El testing d'aquesta arquitectura també té les seves particularitats. Per a les proves unitàries, es pot instànciar l' eina directament amb el mòdul de testing de NestJS i els serveis mockejats, sense necessitat d' infraestructura MCP. Però per a proves d'integració, el SDK proporciona un parell de transports en memòria que permeten simular el flux complet sense HTTP. Es crea un servidor amb la fàbrica, es connecta a un transport en memòria, es crea un client MCP que crida a l'eina i es verifiquen tant els arguments rebuts pel servei mock com la resposta. Això detecta errors d'esquema que les proves unitàries no capturen, com que l'esquema Zod no coincideixi amb el que l'eina espera.
En Q2BSTUDIO, combinem aquestes tècniques amb altres capacitats que oferim com a part dels nostres serveis de programari a mida. Per exemple, quan un client necessita integrar agents IA amb els seus sistemes heretats, no només implementem el servidor MCP, sinó que també dissenyem la lògica de negoci amb aplicacions a mesura que es comuniquen de forma segura. A més, assegurem tot el flux amb mesures de ciberseguretat com a autenticació per API key i restricció d'eines per entorn. En molts casos, aquests agents es despleguen sobre serveis cloud aws i azure, aprofitant els balancejadors i autoescalat. Fins i tot hem integrat aquests servidors MCP amb dashboards de power bi perquè els usuaris finals vegin quines eines s'han executat, formant part de serveis intel·ligència de negoci més amplis.
Des d'una perspectiva empresarial, la decisió d'adoptar MCP amb NestJS redueix la fricció entre els equips de backend i els d'IA. Els desenvolupadors de NestJS poden continuar escrivint serveis amb el seu flux habitual, i els científics de dades o enginyers de prompts només necessiten conèixer la interfície d'eines. Això accelera la creació d'agents IA que realment resolen problemes del negoci, com l'atenció al client automatitzada o la gestió de comandes. I quan s'afegeixen eines d'anàlisi, com consultes a bases de dades o invocacions a APIs externes, la mateixa arquitectura escala sense reescriure.
El resultat, després de diverses implementacions en producció, és un conjunt d' aproximadament 300 línies de codi addicional sobre l' aplicació NestJS existent. La major part correspon a les definicions de les eines, que de tota manera caldria escriure. El protocol en si és la part fàcil; el que marca la diferència és com s'integra amb la resta del sistema. Mantenir el transport en un únic controlador, resoldre el llogater abans que el SDK entri en joc i tractar les descripcions com la veritable superfície d'API són les claus perquè un servidor MCP en producció no es converteixi en un problema, sinó en un habilitador de la intel·ligència artificial.




