Skip to main content
Glama
innovasbuild

innovas-workspace-mcp

Official
by innovasbuild
README.md
# innovas-workspace-mcp

Servidor MCP remoto multi-tenant para Google Workspace y el company brain en Drive.

Un tenant es una empresa. Agregar un cliente es agregar una entrada en `tenants.json`, no tocar el codigo.

## Que expone

**Tools semanticas del brain**, que son el diferencial. El servidor habla el idioma del brain, no el de Google:

| Tool | Efecto |
|---|---|
| `brain_index` | Indice parseado, con la lista de paginas declaradas |
| `brain_read` | Frontmatter parseado y cuerpo, por separado |
| `brain_search` | Busqueda de texto completo sobre el drive del brain |
| `brain_upsert` | Unica via de escritura. Valida frontmatter, escribe, agrega al indice y registra en el log |
| `brain_link_check` | Wikilinks del indice que no tienen pagina |

**Primitivas de Google**: `drive_search`, `drive_read`, `gmail_search`, `gmail_draft`, `calendar_events`.

Regla de diseno: si un paso puede fallar por olvido del modelo, va en el servidor. Que `index.md` y `log.md` queden al dia es un efecto de `brain_upsert`, no una instruccion.

## Modelo de auth

Dos ejes independientes.

**Como se autentica quien llama** (`AUTH_MODE`):

- `apikey`: header `x-api-key`. La key se guarda hasheada en sha256 y mapea a un email. Suficiente para un piloto.
- `google`: bearer token de Google. El dominio del email resuelve el tenant. Un usuario de otro dominio no resuelve a este tenant, y eso no depende de ninguna configuracion.
- `both`: acepta los dos.

**Como el servidor llama a Google** (`auth.mode` por tenant):

- `user_token`: usa el token del propio usuario. Privilegio minimo, sin admin del Workspace del cliente. No sirve desatendido.
- `service_account`: credenciales propias. Con `impersonate: false` alcanza para Drive si la service account es miembro del shared drive. Con `impersonate: true` usa domain-wide delegation, que hace falta para Gmail y Calendar y es la unica via de correr sin usuario presente.

La allow list `allowedSubjects` es dura: vacia es igual a nadie.

## Setup local

```bash
cp .env.example .env
cp tenants.example.json tenants.json   # completar ids de drives y hashes de api key
npm install
npm run dev
```

Generar el hash de una api key:

```bash
node -e "console.log(require('crypto').createHash('sha256').update('LA_API_KEY').digest('hex'))"
```

Obtener el id de un shared drive: esta en la URL, `drive.google.com/drive/folders/<ID>`.

## Deploy

Railway con NIXPACKS, ver `railway.json`. Variables minimas: `MCP_SERVER_URL`, `TENANTS_JSON` o `TENANTS_FILE`, la variable de credenciales de cada tenant, `AUTH_MODE` y `MCP_ALLOW_WRITES`.

El endpoint MCP es `POST /mcp`. Health en `/.well-known/health`.

## Decisiones que no se re-discuten

- Transporte sin estado, una instancia de servidor por request. Cuesta milisegundos y elimina la fuga de contexto entre tenants.
- `MCP_ALLOW_WRITES` arranca apagado. Un despliegue nuevo no escribe hasta que alguien lo decida.
- `gmail_draft` crea borradores y no envia. El envio queda en manos de la persona.
- El JSON de las service accounts nunca vive en `tenants.json`: ahi va el nombre de la variable de entorno.