Notion
Interactúa con la API de Notion para leer, crear y modificar contenido usando lenguaje natural.
Documentación
Servidor MCP de Notion — Conecta Claude, Cursor y VS Code a Notion
Dale a tu IA acceso de lectura/escritura a Notion con un token y un comando. Claude Code, Claude Desktop, Cursor, VS Code, Cline, Zed, cualquier cosa que hable MCP: puede crear páginas, consultar bases de datos, añadir bloques, aplicar plantillas, comentar y subir archivos, en lenguaje natural.
Notion incluye su propio servidor MCP. En esto se diferencia este:
- Se autentica con un token, así que funciona sin interfaz. El MCP alojado de Notion es solo OAuth y alguien tiene que hacer clic en "Autorizar". Este funciona en CI, cron jobs, agentes en segundo plano e implementaciones autoalojadas.
- No gasta tu contexto en esquemas de herramientas. El servidor oficial de código abierto carga 24 esquemas de endpoints en el contexto del modelo al conectar: 17,163 tokens, reenviados con cada solicitud durante el resto de la sesión. Este carga tres herramientas, 1,005 tokens — 94% menos, 17× más pequeño — y obtiene el esquema de una operación solo cuando una tarea realmente lo necesita.
- Tampoco gasta tu contexto en respuestas. Leyendo las mismas páginas a través de ambos servidores, obtener el contenido de una página cuesta 82% menos (26,071 → 4,568 tokens en una página de 88 bloques), una consulta de base de datos de 25 filas 81% menos, un objeto de página 68% menos. El JSON crudo de Notion es mayormente envoltorios
id/type,annotationsy bloquescreated_by/parent/icon, y nada de eso llega al modelo. Esa es la mitad que se acumula, porque la superficie de herramientas se paga una vez y las respuestas se pagan en cada llamada. Medido contra un fixture reproducible, con las advertencias indicadas →
Nada se pierde para lograrlo: una consulta de base de datos devuelve filas planas de nombre → valor en lugar de las bolsas crudas properties de Notion (5.3× más ligero en el benchmark), y verbose: true te da la forma intacta del SDK cuando quieras — dentro de 4 tokens de lo que devuelve el servidor oficial, que es como el benchmark demuestra que ambos leen lo mismo. Mutaciones por lotes con rollback atómico, claves de idempotencia, reintentos en límites de tasa y errores de validación autocorregibles están integrados, y la comparación a continuación tiene el resto.
Inicio rápido
1. Obtén un token de Notion. Abre app.notion.com/developers/tokens → + New token → nómbralo, elige tu espacio de trabajo → Create token → copia el valor de ntn_…. Un Personal Access Token ve todo lo que tú puedes ver, sin compartir por página. (¿Página faltante o vacía? Tu administrador deshabilitó los PAT — consulta alternativas de autenticación.)
2. Instálalo.
npx add-mcp notion-mcp-server --env NOTION_TOKEN=ntn_paste_your_token_here
add-mcp encuentra los clientes MCP en tu máquina y escribe la configuración para los que elijas: Claude Code, Claude Desktop, Cursor, VS Code, Codex, Gemini CLI, Cline, Windsurf, Zed y una docena más. Añade -g para instalar a nivel de usuario en lugar del proyecto actual, -a claude-code para omitir el selector, --all para escribir todos los clientes a la vez.
Mantén la bandera
--env. Sin ella, la entrada se escribe sin token, y el servidor arranca y luego falla en cada llamada con un error de autenticación.
O instálalo a mano: configuración JSON, Claude Code, Cursor, VS Code, Gemini CLI, Claude Desktop, Docker
Cualquier cliente que lea un bloque mcpServers (el ~/.cursor/mcp.json de Cursor, el claude_desktop_config.json de Claude Desktop, la configuración de Cline, Zed, Continue…):
{
"mcpServers": {
"notion": {
"command": "npx",
"args": ["-y", "notion-mcp-server"],
"env": { "NOTION_TOKEN": "ntn_paste_your_token_here" }
}
}
}
Claude Code:
claude mcp add notion -s user \
-e NOTION_TOKEN=ntn_paste_your_token_here \
-- npx -y notion-mcp-server
Claude Code habla el protocolo de la era 2025 sobre stdio a menos que se le indique lo contrario. Establece MCP_PROTOCOL_NEGOTIATION=auto en su entorno y sondea MCP 2026-07-28 (solicitudes sin estado, sugerencias de caché en cada lista). El servidor atiende ambos.
Cursor: — haz clic, luego reemplaza
YOUR_NOTION_TOKEN en la entrada generada.
VS Code (modo agente de Copilot): — VS Code te pide el token y lo almacena como entrada secreta.
Gemini CLI:
gemini extensions install https://github.com/awkoy/notion-mcp-server
El repositorio incluye un gemini-extension.json, así que esto se instala como extensión: pide el token una vez, lo guarda en tu llavero del sistema e inicia el servidor con npx.
Claude Desktop, sin Node.js: descarga notion-mcp-server.mcpb de la última versión y haz doble clic (o arrástralo a Settings → Extensions), luego pega tu token cuando se te pida. ¿Nunca has editado un archivo de configuración? El tutorial paso a paso no asume nada.
Docker / Podman / OrbStack:
claude mcp add notion -s user \
-e NOTION_TOKEN=ntn_paste_your_token_here \
-- docker run --rm -i -e NOTION_TOKEN ghcr.io/awkoy/notion-mcp-server:latest
La bandera -i es obligatoria para stdio. La imagen cumple con OCI, así que Podman, OrbStack, colima, Rancher Desktop, Finch y nerdctl aceptan las mismas banderas. Para un contenedor HTTP de larga duración, consulta Transporte remoto / HTTP.
3. Pruébalo. En un chat nuevo:
"Usa Notion para crear una página llamada 'Hola desde mi agente' y añade una lista de verificación de tres cosas para probar hoy."
Tu IA llama a notion_write y responde con un enlace a la página en vivo.
Lo que tu IA puede hacer con él
- "Encuentra cada fila en mi base de datos de Tareas donde Estado sea 'Haciendo' y dime cuáles están vencidas." — filtros tipados
where, filas aplanadas - "Renombra estas 50 páginas a la nueva convención." — una llamada por lotes, paralelismo de 10 vías, reintento idempotente
- "Crea una página desde mi plantilla 'Revisión semanal' y completa este resumen."
- "Reescribe esa página de especificaciones: corrige los encabezados y añade un ejemplo de código." — ida y vuelta de markdown vía
get_page_markdown→ editar →update_page_markdown - "Comenta en las notas de la reunión de ayer con un resumen de un párrafo."
- "Sube este diagrama a la página de diseño." — subidas de una y varias partes
- "Mira la captura de pantalla en ese informe de error y dime qué está mal." —
get_imageentrega la imagen al modelo
El catálogo completo está en el menú de operaciones: 47 operaciones detrás de tres herramientas.
¿Qué MCP de Notion deberías usar?
| Mejor para | Autenticación | Sin interfaz / CI | Notas | |
|---|---|---|---|---|
MCP alojado de Notion (mcp.notion.com) | Chat interactivo en claude.ai, ChatGPT, Cursor | OAuth (un humano debe hacer clic; Notion dice que la autenticación no interactiva está en desarrollo) | ❌ | De primera parte, ~34 herramientas de markdown (11 de ellas herramientas de sesión de Custom Agent que necesitan Notion AI), algunas limitadas por plan |
| Servidor oficial de código abierto | — | Token | ✅ | Notion lo llama obsoleto y "ya no mantenido activamente"; el repositorio dice que "puede retirarlo" y que los issues y PRs no se monitorean activamente |
| Este servidor | Agentes, automatización, CI, autoalojamiento, cargas sensibles a tokens | Token (PAT) | ✅ | Mantenido activamente, diseño centrado en agentes |
Para chatear con tu Notion en la interfaz web de claude.ai, usa el conector alojado de Notion: es un clic. Usa este servidor cuando el agente funcione sin supervisión, cuando el costo de contexto importe, o cuando quieras semántica por lotes e idempotente y tu propio host.
Comparación detallada vs. el servidor oficial de código abierto
| Capacidad | Notion MCP oficial (código abierto) | Este servidor |
|---|---|---|
| Superficie de herramientas | 24 herramientas (una por endpoint), 17,163 tokens cargados en contexto | 3 herramientas, 1,005 tokens — 94% menos esquema al conectar |
| Tamaño de respuesta | Envoltorio completo de Notion en cada lectura | 82% menos al leer los bloques de una página, 81% en una consulta de base de datos de 25 filas, 68% en un objeto de página, 71% en una búsqueda — mismos objetos, ambos servidores, pares emparejados |
| Operaciones cubiertas | ~24 endpoints | 47 operaciones (más un alias trash_page) en páginas, bloques, bases de datos, fuentes de datos, vistas, plantillas, comentarios, usuarios, archivos |
| Mutaciones por lotes | No documentado | ✅ Envoltorio universal { items: [...] }; hasta 10 en paralelo |
| Lotes atómicos + rollback | No documentado | ✅ atomic: true aborta en el primer fallo, archiva con el mejor esfuerzo las entidades creadas antes |
| Idempotencia | No documentado | ✅ idempotency_key — misma clave + operación devuelve el resultado en caché durante 5 minutos |
| Manejo de límites de tasa | Los 429 suben | ✅ Limitador de cubo de tokens (3 req/s por defecto) + retroceso exponencial, respeta Retry-After |
| Formas de respuesta | JSON crudo del SDK de Notion | Modeladores esbeltos eliminan ruido por defecto; verbose: true opta por no participar y devuelve la forma cruda |
| Consultas de base de datos | Bolsa cruda properties por fila | Mapa aplanado de nombre → primitivo (todos los 20+ tipos de propiedad) — 16,629 → 3,143 tokens en la consulta de 25 filas del benchmark |
| Escribir propiedades | JSON de propiedad completo de Notion | Valores simples: { Status: "Done", Due: "2026-10-01", Tags: ["a"] }, tipados desde el esquema de la fuente de datos (caché de 5 min); nombres y opciones incorrectos rechazados con los válidos |
| Filtros | JSON de filtro crudo de Notion | Abreviatura tipada where — { Status: "Done", Priority: { in: [...] }, OR: [...] } y sorts: ["-Due Date"]; los filtros crudos aún se aceptan |
| Campos desconocidos | Rechazados | Ignorados con una entrada warnings que nombra el campo y los aceptados, para que la llamada aún se ejecute |
| Paginación | Cursores manuales | paginate: true opcional recorre next_cursor (límite ≈ 1000 elementos) |
| Formato de cable | Serialización predeterminada del SDK | JSON compacto — cargas ~30% más pequeñas |
| Markdown | Herramientas de markdown a nivel de página | ✅ Aceptado por create_page / append_blocks / update_block / comentarios, más ida y vuelta completo (get_page_markdown / update_page_markdown), GFM completo |
| Plantillas | — | ✅ create_page desde una plantilla de Notion + descubrimiento list_data_source_templates |
| Vistas de base de datos | — | ✅ listar / obtener / consultar / crear / actualizar / eliminar; query_view ejecuta los filtros y ordenamientos almacenados de una vista y devuelve filas hidratadas |
| Subidas de archivos | No en la superficie de herramientas documentada | ✅ De una y varias partes (fragmentos de 5 MB), MIME inferido |
| Errores de validación | Cadena de error simple | Autocorregible: { code, message, path, issues, schema, example, fix } — corregido en un solo ida y vuelta |
| Versión de la API de Notion | — | 2026-03-11 fijada (fuentes de datos, vistas, plantillas) |
Lo que eso te compra en la práctica: renombrar 50 páginas es una llamada notion_write con { items: [...], concurrency: 10 } en lugar de 50 viajes por el bucle de razonamiento del agente, y el ahorro de tokens de prompt es la mitad más grande de la victoria. El benchmark tiene el método, el tokenizador, el control que lo valida, un peor caso honesto y los límites de su propia muestra.
Configuración
Token: PAT o integración interna
Ambos van en la misma variable de entorno NOTION_TOKEN; solo difiere de dónde los obtienes.
| Personal Access Token (recomendado) | Integración interna (con ámbito) | |
|---|---|---|
| Dónde | app.notion.com/developers/tokens → + New token | app.notion.com/developers/connections → + New connection |
| Alcance | Todo lo que tú puedes ver | Solo páginas donde hiciste clic en • • • → Connect → <integration> |
| Fricción | Ninguna | Un paso de Connect por página o base de datos |
| Úsalo cuando | Por defecto: espacios de trabajo personales y de equipo, prototipado | Un administrador requiere ámbito explícito por recurso, o para bots de producción compartidos |
💡 La mayoría de los errores
object_not_foundson la elección de autenticación incorrecta en lugar de un bug: un token de integración interna que nunca se conectó a la página. Cambia a un PAT.
Detalles del PAT: capacidades, caducidad, revocación, alternativa si el administrador lo deshabilita
**Puede:** leer todas las páginas a las que tengas acceso; crear y actualizar páginas y bases de datos donde tengas derechos de edición; comentar como tú; subir archivos. **No puede:** acceder a páginas que no puedas ver, eludir permisos del espacio de trabajo, actuar como otro usuario ni cambiar ajustes de administración. El alcance de un PAT es tu cuenta, así que si pierdes acceso a una página, el PAT también lo pierde. Emite tokens separados por cada compañero.Caducidad: Los PAT caducan 1 año después de su creación (documentación de Notion). Configura un recordatorio para el mes 11.
Revocación: app.notion.com/developers/tokens → Revocar junto al token, con efecto inmediato. Los administradores del espacio de trabajo pueden revocar el de cualquiera desde Ajustes y miembros → Conexiones → Todos los tokens de acceso personal.
¿PAT deshabilitados por el administrador? Pídeles que lo habiliten, o crea una Integración Interna en app.notion.com/developers/connections (+ Nueva conexión) y con • • • → Conectar vincúlala a cada página que el agente deba tocar. Misma variable de entorno NOTION_TOKEN.
Referencia oficial: Guía de PAT · Resumen de autorización.
Variables de entorno
| Variable de entorno | Obligatoria | Predeterminado | Significado |
|---|---|---|---|
NOTION_TOKEN | ✅ | — | PAT (ntn_…, recomendado) o secreto de Integración Interna (secret_… / ntn_…) |
NOTION_PAGE_ID | — | — | Padre predeterminado para create_page / create_database cuando no se pasa ningún parent (página → Compartir → Copiar enlace; sirve la URL completa o el id de 32 caracteres) |
NOTION_RATE_LIMIT | — | 3 | Solicitudes/segundo para el limitador compartido (límite documentado por integración de Notion) |
NOTION_READ_ONLY | — | — | true/1/yes desactiva toda operación de escritura con un solo interruptor |
NOTION_ALLOWED_OPERATIONS | — | all | Lista de permitidos separada por comas de operaciones o ajustes predefinidos de grupo — ver Restringir operaciones |
NOTION_BLOCKED_OPERATIONS | — | — | Lista de bloqueados separada por comas (mismo vocabulario); prevalece sobre la lista de permitidos |
NOTION_CONFIRM_DESTRUCTIVE | — | — | true/1 mantiene las operaciones destructivas habilitadas pero te pregunta primero — ver Restringir operaciones |
NOTION_UPLOAD_ROOT | — | — | Confina la fuente de path de upload_file a un solo directorio — ver Archivos |
NOTION_FILE_URLS | — | full | ref reemplaza las URL firmadas de archivos de Notion (~1.650 caracteres, válidas por una hora) en respuestas reducidas con referencias notion-file: cortas — ver Archivos |
HTTPS_PROXY / HTTP_PROXY | — | — | Enruta todo el tráfico saliente — llamadas a la API de Notion y las descargas en la fuente url de get_image y upload_file — a través de un proxy HTTP(S) (variables de entorno estándar, también se aceptan minúsculas) |
NOTION_DAILY_LOG_PAGE_ID | — | — | Solo lo usa el prompt MCP de registro diario |
Las variables de transporte HTTP (MCP_TRANSPORT, PORT, HOST, MCP_AUTH_TOKEN, …) están en Transporte remoto / HTTP.
¿Actualizando desde v1.x o v2.x? Cada variable de entorno sigue funcionando sin cambios. La diferencia está en la superficie de herramientas: las cinco herramientas de v1, luego las
notion_executede v2, se convirtieron ennotion_read+notion_write, ynotion_describequedó como estaba. Los clientes modernos redescubren las herramientas automáticamente. Detalles en MIGRATION.md.
Restringir operaciones
NOTION_ALLOWED_OPERATIONS (lista de permitidos) y NOTION_BLOCKED_OPERATIONS (lista de bloqueados) aceptan cada una una lista separada por comas de ajustes predefinidos de grupo o nombres exactos de operación.
| Ajuste predefinido | Se expande a |
|---|---|
read | toda operación no mutante |
write | toda operación mutante |
destructive | operaciones cuyo propósito es la eliminación (archive_page/trash_page, delete_block, batch_mixed_blocks, delete_comment, delete_view) |
pages blocks databases data_sources views comments users files | toda operación de esa familia, de lectura y escritura |
{ "env": { "NOTION_ALLOWED_OPERATIONS": "read" } } // read-only, the common case
{ "env": { "NOTION_BLOCKED_OPERATIONS": "destructive" } } // everything except removals
{ "env": { "NOTION_ALLOWED_OPERATIONS": "read,append_blocks,add_page_comment" } }
Los nombres no distinguen mayúsculas de minúsculas, los tokens desconocidos se ignoran con una advertencia, la lista de bloqueados prevalece, y una lista de permitidos que se resuelva a cero operaciones lo desactiva todo (cierre seguro). Las operaciones deshabilitadas desaparecen de los enums operation de las herramientas, de notion_describe y del menú notion://operations, así que nombrar una falla la validación antes de que se ejecute; cuando no hay ninguna operación de escritura habilitada, notion_write no se anuncia en absoluto. Una línea en stderr al inicio dice qué se resolvió. Revísala primero cuando la configuración no se comporte como esperas:
Operation access: 22/48 enabled (allow=read; block=(none))
Confirmar en lugar de bloquear. NOTION_CONFIRM_DESTRUCTIVE=true mantiene disponibles las operaciones destructivas y hace que notion_write te pregunte antes de ejecutar una, mediante elicitation de MCP: una solicitud elicitation/create en clientes de la era 2025, un viaje de ida y vuelta input_required en clientes MCP 2026-07-28, donde el reintento lleva un requestState sellado que solo coincide con la llamada para la que fue emitido. Recibes un diálogo de sí/no que nombra la operación y su objetivo (la página, base de datos, fuente de datos o título de bloque cuando una recuperación puede obtenerlo en 5 s; si no, el id; para un lote, cuántos elementos).
Las restauraciones (restore_page, delete_database / delete_data_source con in_trash: false) y una llamada batch_mixed_blocks sin entrada delete nunca preguntan, y una operación bloqueada se rechaza igualmente con operation_not_allowed antes de preguntar a nadie. Rechazar, cancelar o responder no hace que la llamada devuelva confirmation_declined; las instrucciones del servidor le dicen al modelo que no reintente y que te pregunte a ti en su lugar. Un cliente que no haya declarado la capacidad de elicitation recibe confirmation_unavailable en lugar de una ejecución silenciosa. Usa un cliente que admita elicitation, desactiva la variable o bloquea las operaciones destructivas por completo.
Referencia por operación y limitaciones
| Dominio | Lectura | Escritura |
|---|---|---|
pages | search_pages get_page get_page_markdown | create_page set_page_title set_page_property set_page_properties update_page_markdown move_page restore_page archive_page† trash_page† |
blocks | get_block get_block_children | append_blocks update_block delete_block† batch_mixed_blocks† |
databases | query_database | create_database update_database delete_database† |
data_sources | list_data_sources get_data_source list_data_source_templates | update_data_source delete_data_source† |
views | list_views get_view query_view | create_view update_view delete_view† |
comments | list_comments get_comment | add_page_comment add_discussion_comment update_comment delete_comment† |
users | list_users get_user get_bot_user get_self | — |
files | list_file_uploads get_file_upload get_file_url get_image | upload_file |
† = también en el grupo destructive.
Limitaciones. El control es por operación, no por parámetro: update_page_markdown es una operación de escritura que puede reemplazar el cuerpo de una página, y bloquear destructive no la desactiva. Para un despliegue garantizado sin mutaciones usa NOTION_ALLOWED_OPERATIONS=read o NOTION_READ_ONLY=true. Los prompts de MCP pueden seguir mencionando operaciones deshabilitadas, pero la ejecución se rechaza.
Archivos
Subidas. upload_file toma sus bytes como base64, un url público, o un path local que el servidor lee directamente. Una fuente path puede leer cualquier archivo que el proceso del servidor pueda, así que cuando un modelo componga la ruta, configura NOTION_UPLOAD_ROOT para confinarla: las rutas relativas se resuelven dentro de la raíz, y los enlaces simbólicos se resuelven antes de la verificación para que no puedan apuntar fuera de ella.
URL de archivos. Notion emite una URL S3 firmada nueva para cada archivo alojado en cada lectura: unos 1.650 caracteres (~500 tokens), válida por una hora, diferente cada vez, y fácil de estropear para un modelo pequeño. NOTION_FILE_URLS=ref las reemplaza en respuestas reducidas (get_page, search_pages, query_database, query_view, get_block, get_block_children, …) con referencias cortas y estables.
| Referencia | Nombres | Resuelta por |
|---|---|---|
notion-file:block/<block-id> | El archivo en un bloque de imagen | get_file_url → { ref, url }, una URL firmada nueva válida por aproximadamente una hora |
notion-file:page/<page-id>/<property>/<index> | Una entrada de la propiedad files de una página (nombre de propiedad codificado en URL) | get_image → la imagen como contenido de imagen MCP, para que el modelo pueda verla (solo image/*, hasta 5 MB) |
Ambos resolvedores releen el objeto a través de la API de Notion, así que una referencia sigue siendo válida mientras el archivo exista. get_image obtiene solo la URL que Notion devolvió para un archivo alojado en Notion, nunca una proporcionada por quien llama, así que no se puede dirigir a un host de LAN, un endpoint de metadatos en la nube o un objetivo de exfiltración. Las URL externas (imágenes enlazadas, archivos external) ya son cortas y estables: pasan sin cambios en cualquier modo, y get_image las devuelve como texto en lugar de obtenerlas. get_page_markdown es el markdown renderizado propio de Notion y no se reescribe. El valor predeterminado, full, deja cada respuesta como estaba.
Transporte remoto / HTTP
El servidor habla stdio por defecto. Configura MCP_TRANSPORT=http para ejecutarlo como endpoint remoto en su lugar, para clientes web, agentes en red y despliegues compartidos:
MCP_TRANSPORT=http PORT=3000 NOTION_TOKEN=ntn_xxx npx -y notion-mcp-server
# -> notion-mcp-server vX.Y.Z running on http://127.0.0.1:3000/mcp
Sirve MCP Streamable HTTP en /mcp para ambas generaciones de protocolo actuales, elegido por solicitud según lo que envíe el cliente. Los clientes MCP 2026-07-28 obtienen la ruta sin estado, donde cada POST es independiente: sin sesión, server/discover, sugerencias de caché en cada lista. Los clientes 2024-11-05 … 2025-11-25 obtienen sesiones mediante el encabezado mcp-session-id más el flujo GET y DELETE, y un GET/DELETE sin id de sesión recibe 405. También hay un GET /health sin autenticación. El proceso es de un solo inquilino: cada solicitud actúa como el único NOTION_TOKEN con el que comenzó.
| env | predeterminado | significado |
|---|---|---|
MCP_TRANSPORT | stdio | configúralo en http para habilitar HTTP |
PORT | 3000 | puerto de escucha (0 = asignado por el SO) |
HOST | 127.0.0.1 | dirección de enlace; configura 0.0.0.0 para exponer externamente (solo con MCP_AUTH_TOKEN) |
MCP_AUTH_TOKEN | — | cuando se configura, cada solicitud /mcp debe enviar Authorization: Bearer <token> |
MCP_ALLOWED_HOSTS | localhost + host enlazado | lista separada por comas para la lista de permitidos Host de reenlace de DNS |
MCP_ALLOWED_ORIGINS | orígenes de localhost | lista separada por comas para la lista de permitidos Origin del navegador |
⚠️ Quien llegue a
/mcpactúa como tuNOTION_TOKEN. En loopback, el valor predeterminado, eso significa solo procesos locales. Antes de enlazar unHOSTque no sea loopback, configuraMCP_AUTH_TOKEN(el servidor advierte si no lo haces) y pon un proxy inverso autenticador delante.
Conexión desde un cliente que admita encabezados (Claude Code, Cursor, VS Code) y verificación local:
claude mcp add --transport http notion https://your-host/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN>"
curl http://127.0.0.1:3000/health
# -> {"status":"healthy","transport":"http","port":3000}
npx @modelcontextprotocol/inspector --transport http --server-url http://127.0.0.1:3000/mcp
En Docker, HOST=0.0.0.0 es lo que hace alcanzable el puerto publicado, ya que dentro del contenedor 127.0.0.1 es el loopback propio del contenedor. Un enlace que no sea loopback es exactamente donde MCP_AUTH_TOKEN demuestra su valor:
docker run --rm -e NOTION_TOKEN=ntn_xxx -e MCP_TRANSPORT=http -e HOST=0.0.0.0 -e MCP_AUTH_TOKEN=change-me \
-p 3000:3000 ghcr.io/awkoy/notion-mcp-server
Las compilaciones de Claude Desktop afectadas por anthropics/claude-code#93290 envían un cuerpo 2026-07-28 bajo un encabezado
MCP-Protocol-Version: 2025-11-25. El servidor realinea esa única discrepancia conocida para que esas compilaciones funcionen; cualquier otro desajuste entre encabezado y cuerpo recibe el rechazo que prescribe la especificación (-32020).
Comprobaciones de salud para un contenedor HTTP
La imagen se distribuye sin un `HEALTHCHECK` porque arranca en modo stdio, donde nada escucha y una sonda integrada de `/health` marcaría cada contenedor stdio como no saludable. Añade uno tú mismo para un despliegue HTTP. El mismo comando está comentado en el `Dockerfile` y funciona como `--health-cmd` en `docker run` también:services:
notion-mcp-server:
image: ghcr.io/awkoy/notion-mcp-server:latest
environment:
NOTION_TOKEN: ${NOTION_TOKEN:?NOTION_TOKEN is required}
MCP_TRANSPORT: http
HOST: 0.0.0.0
MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN:?MCP_AUTH_TOKEN is required}
ports: ["3000:3000"]
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 30s
timeout: 3s
start_period: 5s
retries: 3
Herramientas MCP
Tres herramientas, cualquiera de las 47 operaciones que termines llamando. notion_read ejecuta las lecturas, notion_write las escrituras y notion_describe devuelve el JSON Schema de una operación más un ejemplo funcional, lo que vale un viaje de ida y vuelta antes de una llamada compleja: expresiones de filtro, lotes de bloques mixtos, definiciones de propiedades de base de datos. El campo operation de cada herramienta es un enum de exactamente lo que este servidor tiene habilitado, así que el menú se envía con la lista de herramientas, un cliente puede validar una llamada antes de enviarla, y un nombre enviado a la herramienta equivocada falla en un viaje de ida y vuelta con un mensaje que nombra la correcta.
Cada campo de id (page_id, block_id, database_id, view_id, …) también acepta una URL de Notion, así que pega lo que Compartir → Copiar enlace te da. El #fragment de un enlace de bloque se usa para campos block_id y el ?v= de un enlace de base de datos para campos view_id.
// notion_read
{ "operation": "search_pages", "payload": { "query": "Q3 plan" } }
{ "operation": "get_page_markdown", "payload": { "page_id": "https://www.notion.so/Q3-plan-1f3c…" } }
// notion_write, single call
{ "operation": "set_page_title", "payload": { "page_id": "<page-id>", "title": "Q3 plan" } }
// notion_write, batch: every mutating op takes { items: [...], atomic?, concurrency?, idempotency_key? }
{
"operation": "set_page_title",
"payload": {
"items": [{ "page_id": "<p1>", "title": "First" }, { "page_id": "<p2>", "title": "Second" }],
"concurrency": 3,
"idempotency_key": "rename-pass-2026-07-02"
}
}
// markdown shortcut (create_page, append_blocks, update_block, update_page_markdown)
{
"operation": "create_page",
"payload": {
"parent": { "type": "page_id", "page_id": "<parent>" },
"title": "Notes",
"markdown": "# Heading\n\n- [ ] todo\n- [x] done\n\n```ts\nconst x = 1;\n```"
}
}
// a database row: plain property values, typed from the data source's schema
{
"operation": "create_page",
"payload": {
"parent": { "type": "data_source_id", "data_source_id": "<data-source-id>" },
"title": "Write the report",
"properties": { "Status": "In Progress", "Due Date": "2026-10-01", "Tags": ["q3", "docs"] }
}
}
// upload a file and place it on a page in one call
{
"operation": "upload_file",
"payload": {
"source": { "type": "path", "path": "~/Desktop/chart.png" },
"attach_to": { "block_id": "<page-or-block-id>", "caption": "Q3 revenue" }
}
}
Una carga útil que no valida regresa con el JSON Schema completo de la operación, un ejemplo funcional y una pista fix, para que la próxima llamada pueda corregirse sin un viaje de ida y vuelta notion_describe.
Permisos por herramienta
Los clientes MCP otorgan permisos por nombre de herramienta, así que la división lectura/escritura te permite aprobar lecturas una vez y mantener las escrituras detrás de un aviso. En Claude Code (~/.claude/settings.json o el .claude/settings.json del proyecto, donde notion es como sea que hayas nombrado el servidor):
{
"permissions": {
"allow": ["mcp__notion__notion_read", "mcp__notion__notion_describe"]
}
}
La configuración MCP de Cursor ofrece la misma lista de permitidos por herramienta. notion_read está anotado como readOnlyHint: true y notion_write como destructiveHint: true, para clientes que leen anotaciones.
Menú de operaciones (47 operaciones, más un alias)
Las lecturas (get_*, list_*, search_pages, query_database, query_view) pasan por notion_read, todo lo demás por notion_write.
| Área | Operaciones |
|---|---|
| Páginas | create_page, get_page, set_page_title, set_page_property, set_page_properties, archive_page (alias: trash_page), restore_page, search_pages, move_page, get_page_markdown, update_page_markdown |
| Bloques | append_blocks, get_block, get_block_children, update_block, delete_block, batch_mixed_blocks |
| Bases de datos | create_database, query_database, update_database, delete_database |
| Fuentes de datos | list_data_sources, get_data_source, update_data_source, delete_data_source, list_data_source_templates |
| Vistas | list_views, get_view, query_view, create_view, update_view, delete_view |
| Comentarios | list_comments, add_page_comment, add_discussion_comment, get_comment, update_comment, delete_comment |
| Usuarios | list_users, get_user, get_bot_user, get_self |
| Archivos | upload_file, list_file_uploads, get_file_upload, get_file_url, get_image |
La lista autoritativa, con capacidad de lote y la herramienta que ejecuta cada operación, se sirve como recurso MCP en notion://operations.
Recursos MCP
Los clientes que admiten adjuntar recursos (mención @) pueden traer contenido de Notion al contexto sin una llamada de herramienta. Los recursos dinámicos pasan por la misma autenticación, límite de velocidad y control de acceso que las llamadas de herramienta.
| URI del recurso | Devuelve |
|---|---|
notion://operations | Hoja de referencia Markdown de cada operación habilitada |
notion://page/<page_id> | Cuerpo de la página como markdown |
notion://database/<data_source_id> | Esquema de la fuente de datos como JSON |
Solución de problemas
object_not_found/ "No se pudo encontrar …" — un token de Integración Interna solo ve páginas explícitamente Conectadas a él. Cambia a un PAT para omitir el uso compartido por página.- "Falló la autenticación de Notion" en cada llamada — token faltante, revocado o caducado (los PAT duran un año). Verifica
NOTION_TOKENen la configuración de tu cliente, luego que el token siga Activo en app.notion.com/developers/tokens. ¿Instalado conadd-mcpy omitido--env? La entrada no tiene token; vuelve a ejecutarlo con él. - "No se configuró una página principal" — pasa
parenten la llamada, o estableceNOTION_PAGE_ID. multi_source_databasedequery_databaseocreate_page— la base de datos tiene varias fuentes de datos. Llama alist_data_sources, luego pasadata_source_id(o un padredata_source_id) en lugar dedatabase_id.- Un resultado exitoso lleva
warnings— la llamada se ejecutó; cada entrada nombra un campo que se ignoró (mal escrito o mal colocado) o un nombre de propiedad que se corrigió. Arregla la carga útil la próxima vez, nada que reintentar. - Las herramientas no aparecen en Claude Desktop — error tipográfico en el token (debe permanecer dentro de las comillas) o la aplicación no se cerró por completo (
Cmd+Q, no cerrar la ventana) antes de reabrirla. - Los registros de inicio dicen "Falló la verificación de autenticación de Notion" pero las herramientas funcionan — la verificación de inicio es de mejor esfuerzo; ignórala si las llamadas tienen éxito.
- Docker sale inmediatamente / "Conexión cerrada" — el indicador
-ies obligatorio:docker run --rm -i …. - Docker: "NOTION_TOKEN no está establecido" a pesar de
-e— escribe-e NOTION_TOKEN(reenvía desde el entorno padre) o-e NOTION_TOKEN=ntn_xxx, no-e NOTION_TOKEN ntn_xxx.
¿Sigue atascado? Problemas de GitHub · Preguntas frecuentes · Referencia de la API de Notion · Especificación MCP
Privacidad
El servidor se ejecuta en tu máquina o en tu propio host y solo habla con api.notion.com, a través de HTTPS, con el token que configures. Sin telemetría, sin análisis, sin servidor nuestro en el camino: nada de lo que leas o escribas en Notion va a ningún otro lugar. El token permanece donde tu cliente MCP lo guarda, en su archivo de configuración o en un llavero para clientes que tengan uno. Con HTTPS_PROXY establecido, el tráfico pasa por tu proxy en su lugar. get_image obtiene solo las URL firmadas que Notion devuelve para los archivos que aloja, nunca una URL suministrada por el modelo, y upload_file lee un archivo local solo cuando se le pide, dentro de NOTION_UPLOAD_ROOT cuando eso está establecido. El manejo de tus datos por parte de Notion está cubierto por la política de privacidad de Notion.
Desarrollo
git clone https://github.com/awkoy/notion-mcp-server.git
cd notion-mcp-server
npm install
echo "NOTION_TOKEN=ntn_xxx" > .env
npm run build # tsc -> build/
npm test # vitest suite
npm run inspector # MCP inspector against the built binary
Apunta un cliente a la compilación local en lugar de npx:
claude mcp add notion -s user -e NOTION_TOKEN=ntn_xxx -- node "$(pwd)/build/index.js"
Los registros van a stderr y también se envían al cliente como entradas MCP notifications/message (registrador notion-mcp-server), para que aparezcan en la vista de registros del propio cliente — el canal de salida de VS Code, MCP Inspector, los registros de Claude Desktop — donde stderr suele estar oculto. Los clientes de la era 2025 eligen el nivel con logging/setLevel (por defecto info); los clientes MCP 2026-07-28 no tienen tal llamada y preguntan por solicitud con la clave de sobre io.modelcontextprotocol/logLevel, así que una solicitud sin ella no recibe notificaciones de registro. Stderr no se ve afectado de ninguna manera. En debug también obtienes una línea por llamada notion_read / notion_write: operación, tamaño del lote, duración, ok o error, nunca la carga útil o el contenido de la página.
Detalles técnicos: cómo está construido
- TypeScript + SDK de TypeScript MCP v2 (
@modelcontextprotocol/server+@modelcontextprotocol/node2.0.0); transportes stdio + Streamable HTTP; revisiones de protocolo 2024-11-05 a 2026-07-28 (serveStdio/createMcpHandlerpara la ruta sin estado 2026-07-28, el transporte con sesión para el resto) - SDK de Notion
@notionhq/client@^5.22.0, fijado enNotion-Version: 2026-03-11 - Validación de carga útil Zod 4; emite JSON Schema draft-7 con deduplicación
$defspara sobres de error - Markdown → bloques de Notion vía
remark/remark-gfm - Trabajador de lotes con concurrencia limitada (por defecto 3, máximo 10); limitador de velocidad de cubo de fichas compartido;
withRetrycon retroceso exponencial alrededor de cada llamada enviada - Caché de idempotencia en memoria (TTL de 5 minutos, 512 entradas)
- Modeladores delgados por tipo de entidad con exclusión voluntaria
verbose: true - Suite Vitest que cubre el analizador de markdown, modeladores, emisor de esquema, despachador, semántica de lotes (éxito parcial / reversión atómica / idempotencia), control de acceso y transporte HTTP
Prueba de humo de extremo a extremo contra un espacio de trabajo real
npm test se ejecuta contra un cliente de Notion simulado. scripts/e2e.mjs impulsa el servidor compilado a través de stdio contra un espacio de trabajo real: cada operación de lectura, los recursos y avisos, notion_describe para cada operación, y, con --write, cada operación de escritura dentro de una página desechable.
npm run build
printf 'NOTION_TOKEN=ntn_...\nNOTION_PAGE_ID=<page the token can write under>\n' > .env # gitignored
npm run e2e # read-only pass
npm run e2e -- --write # full pass; creates one page under NOTION_PAGE_ID and trashes it at the end
npm run e2e -- --write --keep # keep the test page for inspection
npm run e2e -- --modern # any of the above as an MCP 2026-07-28 client (stateless envelope, input_required confirmations)
Imprime una tabla de APROBADO/FALLIDO por verificación, lista cualquier operación que la ejecución no alcanzó y sale con código distinto de cero en caso de fallo. No es parte de CI.
Contribuciones
Se aceptan solicitudes de extracción. Bifurca → rama → confirma → empuja → PR. Ejecuta npm test antes de enviar.
Licencia
MIT — ver LICENCIA.
mcp-name: io.github.awkoy/notion-mcp-server