Configurar un MCP en AdonisJS (o cómo darle llaves de tu casa a una IA)

Post de prueba: cómo montar un servidor MCP dentro de AdonisJS para que tu IA favorita pueda hablar con tu app sin que tengas que copiar y pegar como un monje medieval.

Gustavo Barragán
Configurar un MCP en AdonisJS (o cómo darle llaves de tu casa a una IA)

¿Qué es eso de MCP y por qué debería importarme?

MCP (Model Context Protocol) es básicamente el USB-C de las IAs: un estándar para que un modelo como Claude se conecte a tus herramientas, datos y servicios sin que tengas que inventar una integración distinta para cada cosa. Tú expones "tools" (funciones), la IA las descubre y las usa. Fin. Sin magia negra, solo JSON-RPC con buena actitud.

Y si ya tienes una app en AdonisJS, ¿por qué no meter el servidor MCP ahí mismo? Ya tienes modelos, auth, rutas... solo falta enseñarle a la IA a tocar el timbre.

Paso 1: Instalar lo necesario

npm i @modelcontextprotocol/sdk zod

Zod viene de regalo porque el SDK lo usa para describir los parámetros de las tools. Sí, otra dependencia. No, no vamos a hablar del tamaño de node_modules, eso es un tema de terapia.

Paso 2: Crear el servidor MCP

Yo lo dejo en app/mcp/server.ts, pero ponlo donde tu corazón (y tu linter) te lo permitan:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'

export function buildMcpServer() {
  const server = new McpServer({ name: 'mi-blog', version: '1.0.0' })

  server.registerTool(
    'saludar',
    {
      description: 'Saluda a alguien con mucho entusiasmo',
      inputSchema: { nombre: z.string() },
    },
    async ({ nombre }) => ({
      content: [{ type: 'text', text: `¡Hola ${nombre}! Te saluda tu servidor Adonis favorito 🎉` }],
    })
  )

  return server
}

Aquí podrías registrar tools que consulten tus modelos de Lucid, creen posts, borren comentarios de spam... lo que quieras. Con gran poder viene gran responsabilidad, Peter.

Paso 3: El controlador

Usamos el transporte HTTP "streamable" del SDK y le pasamos los objetos crudos de Node que Adonis tiene guardados por ahí:

import type { HttpContext } from '@adonisjs/core/http'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import { buildMcpServer } from '#mcp/server'

export default class McpController {
  async handle({ request, response }: HttpContext) {
    const server = buildMcpServer()
    const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })

    response.response.on('close', () => {
      transport.close()
      server.close()
    })

    await server.connect(transport)
    await transport.handleRequest(request.request, response.response, request.body())
  }
}

sessionIdGenerator: undefined lo deja en modo stateless: cada request es un servidor nuevecito, sin recuerdos, como yo los lunes. Es lo más simple para empezar.

Ojo: el alias #mcp/server lo tienes que agregar en los imports de tu package.json, o usar una ruta relativa como un ser humano normal.

Paso 4: La ruta

En start/routes.ts:

const McpController = () => import('#controllers/mcp_controller')

router.post('/mcp', [McpController, 'handle'])

Y si tienes Shield con CSRF activado (deberías), excluye la ruta en config/shield.ts, porque la IA no sabe de tokens CSRF y se va a poner triste:

csrf: {
  enabled: true,
  exceptRoutes: ['/mcp'],
}

Ya que estás, ponle auth a esa ruta. Un endpoint MCP abierto al mundo es como dejar la puerta de tu casa abierta con un cartel que dice "hay cerveza adentro".

Paso 5: Conectarlo y probar

Levanta tu app con node ace serve --hmr y conéctalo desde Claude Code:

claude mcp add --transport http mi-blog http://localhost:3333/mcp

Ahora pídele a Claude que salude a alguien y mira cómo usa tu tool. Si funciona, haz un bailecito. Si no funciona, revisa la consola, toma café y vuelve a intentar. Ese es el ciclo de la vida del desarrollador.

Conclusión

Montar un MCP en AdonisJS es sorprendentemente sencillo: un servidor, un controlador, una ruta y listo. Lo difícil no es configurarlo, es decidir qué tanto poder le das a la IA sobre tu app sin que termine reescribiendo tu base de datos "para optimizarla".

Como siempre, estamos aprendiendo juntos, así que cualquier comentario es bienvenido. xD