Skip to main content
Glama

bitacora-mcp — Fase 3 (código local)

MCP server en NestJS para crear, versionar, recuperar y publicar presentaciones HTML. El store git es la fuente de verdad; Apps Script (Google Workspace) es el target de publicación, descartable y reconstruible desde cualquier commit. Corriendo por HTTP, la identidad es un login real de Google Workspace, no un argumento de texto libre.

Qué hace (y qué no, todavía)

  • create / update / get / list / list_versions / rollback

  • ✅ Cada operación es un commit → historial real en git, rollback no destructivo

  • ✅ Normaliza el HTML: envuelve fragments en documento completo con <title> escapado (mismo patrón escHtml del review de XSS)

  • deploy / get_deployment: publica un commit como web app de Apps Script (idempotente por versión) y consulta el estado de publicación

  • ✅ Transform sandbox-safe antes de publicar: fuerza <!DOCTYPE html>, <base target="_top"> y bloquea assets http:// (mixed content)

  • ✅ Bootstrap HTTP alternativo (npm run start:http) con login de Google Workspace: presentation_create usa el email autenticado como owner real, ignorando lo que mande el cliente. Restringido a un dominio (bidcom.com.ar por defecto).

  • create_from_file / update_from_file: leen el HTML directo de un archivo local, byte a byte, para decks grandes o con contenido binario embebido (imágenes en base64) que un modelo no puede reproducir de forma confiable como argumento de texto.

  • ⛔ Sigue corriendo solo local — el service account de dominio (en vez de cuenta personal) queda pendiente, fuera del alcance de "solo código" de este momento. Este MCP nunca se hostea en un cluster propio: lo único que se publica en infraestructura externa son las presentaciones, como web apps de Google Workspace vía Apps Script.

Related MCP server: marp-agent-mcp

Arquitectura interna

Los módulos mapean 1:1 a los futuros packages/ del monorepo:

  • core/ → normalización y validación de HTML (DeckService, escHtml)

  • store/GitSpecStore, el store versionado en git (decks + índice de deployments)

  • presentations/ → orquestación + @McpController con las 6 tools de versionado

  • deployer/ → toda la fricción de Google (Apps Script) en un lugar: OAuth, cliente de la Apps Script API, transform sandbox-safe, y las 2 tools de publicación

  • auth/ → guard de dominio + config del servidor de autorización OAuth para el bootstrap HTTP

  • shared-tools.module.ts → controllers/providers de las 8 tools, importado tanto por el bootstrap stdio (app.module.ts) como por el HTTP (http-app.module.ts) para no duplicar la lista

Correr

npm install
npm run build
npm start          # levanta el server por stdio

El store se crea en ~/.bitacora-store (configurable con DECK_STORE_DIR).

Publicar en Apps Script (setup de Google, una sola vez)

El deploy corre bajo cuenta personal en esta fase (Fase 3 migra a service account de dominio). Pasos previos, una sola vez por máquina/cuenta:

  1. En Google Cloud Console, un proyecto (o uno nuevo).

  2. Habilitar la Google Apps Script API en ese proyecto (APIs & Services → Library).

  3. Credentials → Create credentials → OAuth client ID, tipo Desktop app.

  4. Descargar el JSON y guardarlo en ~/.bitacora-google/oauth-client.json (override con GOOGLE_OAUTH_CLIENT_PATH).

  5. Correr el consent flow local:

    npm run build
    npm run google:authorize

    Abre una URL de consentimiento, levanta un server local (loopback) para recibir el redirect, y cachea el refresh token en ~/.bitacora-google/token.json (override con GOOGLE_TOKEN_PATH). Se refresca solo de ahí en más.

Las credenciales de Google se guardan fuera del store git a propósito (~/.bitacora-google/, no DECK_STORE_DIR): el store se versiona y podría compartirse; nada con secretos debe vivir ahí.

Variables opcionales para el manifest del web app:

Env var

Default

Qué controla

APPS_SCRIPT_WEBAPP_ACCESS

DOMAIN

Quién puede abrir la URL publicada (DOMAIN, ANYONE, ANYONE_ANONYMOUS, MYSELF)

APPS_SCRIPT_WEBAPP_EXECUTE_AS

USER_DEPLOYING

Con qué identidad corre el doGet (USER_DEPLOYING o USER_ACCESSING)

Prueba end-to-end

npm run smoke      # cliente MCP que ejercita todo el ciclo sobre stdio

El smoke test corre con DECK_DEPLOYER_MOCK=1: el ciclo deploy / get_deployment se ejercita contra un cliente de Apps Script en memoria, sin tocar Google ni requerir credenciales. Para probar contra la API real, corré el server con DECK_DEPLOYER_MOCK sin setear (o en 0) y las credenciales de la sección anterior ya cacheadas.

Correr con login de Google Workspace (HTTP, local)

Bootstrap alternativo al stdio de siempre: el server escucha por HTTP y exige un login real de Workspace antes de dejar usar cualquier tool. Pensado para probar el flujo de identidad localmente antes de decidir dónde corre este proceso para uso remoto (Fase 3 completa, fuera de alcance por ahora) — nunca en un cluster propio como EKS; lo único que este proyecto publica en infraestructura externa son las presentaciones, vía Apps Script.

Es un segundo OAuth client, distinto del "Desktop app" que ya usa el deployer de Apps Script — este es para loguear usuarios, no para llamar a una API:

  1. En el mismo proyecto de Google Cloud ConsoleCreate Credentials → OAuth client ID → tipo Web application (¡no Desktop app! ese tipo no tiene redirect URI configurable).

  2. Authorized JavaScript origins: http://localhost:3030 (sin path, sin / final).

  3. Authorized redirect URIs: http://localhost:3030/auth/callback (con path, exacto).

  4. Anotá el Client ID y el Client secret (o descargá el JSON).

Variables de entorno para levantar el bootstrap HTTP:

Env var

Requerida

Qué es

GOOGLE_WORKSPACE_CLIENT_ID

Client ID del OAuth client "Web application" de arriba

GOOGLE_WORKSPACE_CLIENT_SECRET

Su client secret

JWT_SECRET

Firma los tokens que emite el servidor de autorización propio. Generá uno con openssl rand -hex 32 (mínimo 32 caracteres)

WORKSPACE_DOMAIN

no (default bidcom.com.ar)

Dominio al que se restringen los logins — cualquier otra cuenta de Google recibe 403

MCP_SERVER_URL

no (default http://localhost:3030)

Base URL del server

PORT

no (default 3030)

Puerto HTTP

npm run build
export GOOGLE_WORKSPACE_CLIENT_ID=...
export GOOGLE_WORKSPACE_CLIENT_SECRET=...
export JWT_SECRET=$(openssl rand -hex 32)
npm run start:http

El server queda escuchando en http://localhost:3030/mcp, con los endpoints OAuth estándar del servidor de autorización embebido bajo /auth/* y /.well-known/* (discovery). Un cliente MCP real (Claude, MCP Inspector) hace todo el baile de login solo; para un chequeo manual sin un cliente a mano, un cliente OAuth de prueba (registro DCR + PKCE + tools/call) sirve para verificar que owner termina siendo el email autenticado, no lo que mande quien llama.

Por qué dos capas de OAuth: Google no soporta Dynamic Client Registration, que es justo lo que un cliente MCP remoto (Claude) necesita para autoregistrarse contra el servidor de autorización sin configuración previa. Apuntar un cliente MCP directo a Google como authorization server rompe con mcp_registration_failed — ya nos pasó antes. La solución (la que usa este server) es que nuestro propio servidor de autorización (el McpAuthModule de @rekog/mcp-nest-auth) hable el protocolo OAuth 2.1/MCP completo con el cliente MCP (DCR, PKCE, discovery), y delegue solo el login a Google por dentro. El cliente MCP nunca sabe que Google existe.

Conectar a Claude Desktop

En claude_desktop_config.json:

{
  "mcpServers": {
    "bitacora": {
      "command": "node",
      "args": ["/RUTA/ABSOLUTA/bitacora-mcp/dist/main.js"],
      "env": { "DECK_STORE_DIR": "/RUTA/ABSOLUTA/deck-store" }
    }
  }
}

Decks grandes

Hay dos problemas distintos detrás de "el HTML es grande", con soluciones distintas:

1. El HTML ya existe como archivo en disco. Usá create_from_file / update_from_file — el server lo lee directo del filesystem, byte a byte. El modelo nunca ve ni reproduce el contenido, así que no importa cuán grande sea ni si tiene imágenes en base64 embebidas: no hay riesgo de truncamiento ni de corrupción por transcripción.

presentation_create_from_file({ path: "/ruta/absoluta/deck.html", title, owner })
presentation_deploy({ id })

2. El HTML lo está generando el modelo mismo (no existe como archivo) y es demasiado grande para una sola tool call — el límite real lo pone el cliente MCP emitiendo el argumento (en la práctica, unos ~40 KB por llamada), no este server. Para ese caso existe la carga por chunks:

presentation_create({ title, html: chunk0, owner, partial: true })  -> { id }
presentation_append({ id, html: chunk1, done: false })              // repetir N veces
presentation_append({ id, html: chunkN, done: true })                // cierra y comitea
presentation_deploy({ id })                                          // igual que siempre

Con partial: true no se toca git todavía — solo se reserva el id y se guarda el HTML recibido en memoria. Recién en el done: true corre la normalización de siempre (Fase 1) y se comitea, exactamente como si hubiera llegado en una sola llamada. Una carga sin cerrar (sin done: true) no deja rastro en el store; se pierde si el proceso reinicia, que es aceptable porque solo afecta a esa carga en curso, no a decks ya guardados.

Chunking no resuelve el problema (1): un modelo tiene que regenerar cada byte del argumento de cada chunk, y para contenido grande con base64 embebido eso es un problema de fidelidad de transcripción, no de tamaño — por eso existe create_from_file como camino aparte.

Tools

Tool

Qué hace

presentation_create

Crea un deck y lo guarda versionado. Devuelve id + version (SHA). Con partial: true, reserva el id y guarda el HTML recibido como primer chunk sin comitear nada — hay que cerrar con presentation_append.

presentation_create_from_file

Igual que presentation_create, pero lee el HTML de un archivo local (path absoluto) en vez de tomarlo como argumento. Para decks grandes o con base64 embebido.

presentation_append

Agrega un chunk de HTML a una carga iniciada con presentation_create({partial: true}). done: true en el último chunk cierra, valida y comitea el deck completo. Para decks grandes que no entran en una sola tool call.

presentation_update

Nueva versión con HTML y/o metadata nuevos.

presentation_update_from_file

Igual que presentation_update, pero lee el HTML nuevo de un archivo local.

presentation_get

HTML + metadata en HEAD o en un version (SHA) histórico. La descripción de la tool le pide al asistente que muestre el html como Artifact en vez de texto/código.

presentation_list

Lista los decks, filtrable por owner.

presentation_list_versions

Historial de commits de un deck.

presentation_rollback

Vuelve a un version anterior creando un commit nuevo.

presentation_deploy

Publica un version (default HEAD) como web app de Apps Script. access opcional controla quién puede verla (MYSELF/DOMAIN/ANYONE/ANYONE_ANONYMOUS, default DOMAIN). Idempotente por commit + access.

presentation_get_deployment

Devuelve el estado de publicación actual (commit, scriptId, deploymentId, url).

Notas de stack

  • @rekog/mcp-nest v2 — API McpStrategy + @McpController (no McpModule.forRoot).

  • Transporte stdio: logger: false porque stdout está reservado para el protocolo.

  • @rekog/mcp-nest-auth — servidor de autorización OAuth 2.1/MCP embebido (McpAuthModule), con GoogleOAuthProvider delegando el login. Solo se usa en el bootstrap HTTP (http-app.module.ts / main-http.ts); el stdio (app.module.ts / main.ts) no lo toca.

  • Bajo stdio no hay request HTTP, así que ninguna tool ve un usuario autenticado (@McpUser() da undefined) — es el comportamiento esperado para uso local en Claude Desktop, documentado por la propia librería.

Install Server
F
license - not found
A
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • List, share, upload, and manage Slideless HTML presentations from any MCP host.

  • Streamable HTTP MCP server for Google Calendar and Sheets with OAuth login.

  • A MCP server built for developers enabling Git based project management with project and personal…

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/diohernandez/bitacora-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server