bitacora-mcp

bitacora-mcp

MCP server for creating, versioning, retrieving, and publishing HTML presentations, using git as the source of truth and Google Apps Script for deployment.

Category
访问服务器

README

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.

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.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选