zoho-projects-mcp
by falegria86
README.md
# zoho-projects-mcp
Servidor [MCP (Model Context Protocol)](https://modelcontextprotocol.io) que conecta **Zoho Projects** con **Claude AI**. Permite gestionar proyectos y tareas, registrar tiempo, manejar comentarios y sincronizar horas directamente desde conversaciones con Claude.
## Requisitos
- Node.js 18+
- Cuenta de Zoho Projects con acceso a la API
- Claude Desktop (u otro cliente MCP compatible)
## Instalación
```bash
git clone https://github.com/tu-org/zoho-projects-mcp.git
cd zoho-projects-mcp
npm install
```
## Configuración
### 1. Variables de entorno
Crea un archivo `.env` en la raíz del proyecto:
```env
ZOHO_CLIENT_ID=tu_client_id
ZOHO_CLIENT_SECRET=tu_client_secret
ZOHO_PORTAL_NAME=nombre_de_tu_portal
ZOHO_MY_USER_ID=tu_id_de_usuario_numerico
ZOHO_MY_NAME=Tu Nombre Completo
ZOHO_TEAM_EMAILS=usuario1@empresa.com,usuario2@empresa.com
ZOHO_TEAM_NAMES=nombre1,apellido1,nombre2,apellido2
ZOHO_AUTO_TIMER_PROJECT_ID=id_numerico_del_proyecto
ZOHO_AUTO_TIMER_TASK_ID=id_numerico_de_la_tarea
ZOHO_REFRESH_TOKEN=tu_refresh_token
```
- `ZOHO_PORTAL_NAME` — nombre del portal en la URL de Zoho Projects (ej: `sigobproyectos`)
- `ZOHO_MY_USER_ID` — ID numérico del usuario que se asigna por defecto al crear tareas; obténlo con `list_users` en cualquier proyecto
- `ZOHO_MY_NAME` — nombre completo del usuario (opcional); amplía la detección de menciones por nombre en `my-mentions`
- `ZOHO_TEAM_EMAILS` — lista separada por comas de emails del equipo para `team-tasks`
- `ZOHO_TEAM_NAMES` — fragmentos de nombre separados por comas para detectar miembros del equipo por nombre (ej: `"jose ramon,tejeda,kevin"`)
- `ZOHO_AUTO_TIMER_PROJECT_ID` — ID numérico del proyecto para el timer automático (ver sección Railway)
- `ZOHO_AUTO_TIMER_TASK_ID` — ID numérico de la tarea en la que se inicia/detiene el timer automático
- `ZOHO_REFRESH_TOKEN` — refresh token OAuth; solo necesario en entornos sin `tokens.json` (Railway, CI)
> Para crear las credenciales OAuth, registra una aplicación en la [Consola de Desarrolladores de Zoho](https://api-console.zoho.com/) con URI de redirección `http://localhost:8080/callback`.
### 2. Autenticación OAuth2
Ejecuta el flujo de autenticación una sola vez. Abrirá el navegador para autorizar la app y guardará los tokens en `tokens.json`:
```bash
npm run setup
```
Los tokens se renuevan automáticamente; no es necesario repetir este paso.
### 3. Integrar con Claude Desktop
Agrega el servidor al archivo de configuración de Claude Desktop (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"zoho-projects": {
"command": "node",
"args": ["/ruta/absoluta/al/proyecto/src/server.js"]
}
}
}
```
## Uso
### Iniciar el servidor manualmente
```bash
npm start
```
### Scripts de utilidad
**Tareas abiertas del equipo**
Muestra las tareas abiertas asignadas a los miembros del equipo en todos los proyectos. Requiere `ZOHO_TEAM_EMAILS` y/o `ZOHO_TEAM_NAMES` en `.env`:
```bash
npm run team-tasks
```
**Menciones en comentarios**
Busca todos los comentarios donde te mencionan (`[~ZOHO_MY_USER_ID]`) en uno o todos los proyectos. Soporta filtro por rango de fechas:
```bash
npm run my-mentions # todos los proyectos
npm run my-mentions -- "nombre-proyecto" # un proyecto específico
npm run my-mentions -- --from=2026-05-01 --to=2026-06-09 # con rango de fechas
npm run my-mentions -- "nombre-proyecto" --from=2026-06-01
```
## Herramientas MCP disponibles
### Proyectos y tareas
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
| `list_projects` | Lista todos los proyectos del portal | — |
| `list_tasks` | Lista todas las tareas de un proyecto (paginación automática) | `project_id` |
| `get_task` | Detalle completo de una tarea | `project_id`, `task_id` |
| `create_task` | Crea una nueva tarea | `project_id`, `name` |
| `create_subtask` | Crea una subtarea bajo una tarea padre | `project_id`, `parent_task_id`, `name` |
| `update_task` | Actualiza estado, prioridad, responsable, fechas, etc. | `project_id`, `task_id` |
| `delete_task` | Elimina una tarea permanentemente (irreversible) | `project_id`, `task_id` |
| `list_users` | Lista los usuarios de un proyecto con su zpuid | `project_id` |
| `list_task_fields` | Lista los campos personalizados disponibles | `project_id` |
### Comentarios
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
| `list_comments` | Lista todos los comentarios de una tarea | `project_id`, `task_id` |
| `add_comment` | Agrega un comentario a una tarea | `project_id`, `task_id`, `content` |
| `update_comment` | Edita un comentario existente | `project_id`, `task_id`, `comment_id`, `content` |
| `delete_comment` | Elimina un comentario (irreversible) | `project_id`, `task_id`, `comment_id` |
### Archivos adjuntos
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
| `get_task_attachments` | Lista los archivos adjuntos de una tarea con URLs de descarga | `project_id`, `task_id` |
| `disassociate_attachment` | Desvincula un adjunto de una tarea (no lo elimina de WorkDrive) | `project_id`, `task_id`, `attachment_id` |
### Tiempo
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
| `start_timer` | Inicia el timer de tiempo en una tarea | `project_id`, `task_id` |
| `stop_timer` | Detiene el timer activo | `project_id`, `task_id` |
| `get_time_logs` | Lista los registros de tiempo de un proyecto por rango de fechas | `project_id` |
| `add_time_log` | Registra horas manualmente en una tarea | `project_id`, `task_id`, `hours`, `date` |
| `sync_task_hours` | Para cada tarea cerrada con horas planeadas, crea un time log igual a `total_work × factor` | `project_id` |
---
### Notas sobre `create_task`
- `project_id` acepta nombre o ID numérico: `"sigob-sir-lite"` o `"123456789"`
- Si no se especifica `person_responsible`, se asigna automáticamente `ZOHO_MY_USER_ID`
- `priority` debe ir en minúsculas: `"high"`, `"medium"`, `"low"`, `"none"`
- `start_date` y `due_date` usan formato `MM-DD-YYYY` (se convierten a ISO internamente)
- Para campos personalizados usa `list_task_fields` para obtener los `api_name` y pásalos en `custom_fields`:
```json
{ "cf_area_tecnica": "Backend" }
```
### Notas sobre `update_task`
- Solo se envían los campos que se pasen; los demás quedan intactos.
- Para cambiar el estado necesitas el ID numérico del estado (no el nombre). Patrón: busca con `list_tasks` una tarea que ya tenga ese estado → llama a `get_task` → lee el ID en el campo Estado.
- Para mover la tarea a otra lista usa el parámetro `tasklist_id`.
### Notas sobre `get_time_logs`
- Itera sobre las tareas del proyecto consultando los logs de cada una individualmente (limitación de la API de Zoho).
- Si se pasa `user_zpuid`, solo consulta las tareas asignadas a ese usuario.
- Si no se pasan fechas, devuelve el mes actual.
### Notas sobre `sync_task_hours`
- Opera sobre tareas **cerradas** con `owners_and_work.total_work > 0`.
- `factor` (0.0–2.0, por defecto `1.0`): multiplica las horas planeadas antes de crear el log. Ej: `0.95` registra el 95% de las horas planeadas.
- La fecha del log usa `end_date` de la tarea; si no tiene, usa la fecha de hoy.
- **No reemplaza logs existentes** — crea un entry adicional.
- Procesa en lotes de 3 con pausa entre ellos para respetar el rate limit de Zoho.
### Formato HTML de descripciones
El campo `description` en `create_task` y `update_task` se convierte automáticamente a HTML antes de enviarse a Zoho.
- Si el texto **ya contiene HTML**, se envía tal cual.
- Si es **texto plano**, se aplica la siguiente conversión:
| Entrada | HTML generado |
|---|---|
| Línea en MAYÚSCULAS (ej: `SITUACIÓN ACTUAL`) | `<h3>SITUACIÓN ACTUAL</h3><br><br>` |
| Línea con `-`, `*` o `•` al inicio | Agrupada en `<ul><li>...</li></ul>` |
| Cualquier otro texto | `<p>texto</p>` |
## Timer automático (cron)
El script `scripts/auto-timer.js` inicia o detiene el timer de una tarea de Zoho automáticamente. La tarea objetivo se configura vía variables de entorno — **cada usuario apunta a su propia tarea sin tocar el código**.
```bash
npm run timer:start # inicia el timer
npm run timer:stop # detiene el timer
```
### Paso 1 — obtener los IDs de tu tarea
Abre la tarea en Zoho Projects y copia los IDs del URL:
```
https://projects.zoho.com/portal/miportal#zp/projects/106599000032339072/tasks/.../106599000033129351
↑ project_id ↑ task_id
```
### Paso 2 — obtener el refresh token
Corre el flujo de autenticación local una vez (`npm run setup`) y luego copia el valor de `refresh_token` de tu `tokens.json`:
```bash
cat tokens.json | python3 -c "import sys,json; print(json.load(sys.stdin)['refresh_token'])"
```
### Paso 3 — elegir dónde desplegar
---
#### Opción A — Railway *(recomendado, sin servidor propio)*
Railway ejecuta cron services como contenedores que corren el comando y terminan. Se necesitan **dos servicios** dentro del mismo proyecto Railway.
**Variables de entorno** (configurar en cada servicio):
```env
ZOHO_CLIENT_ID=tu_client_id
ZOHO_CLIENT_SECRET=tu_client_secret
ZOHO_PORTAL_NAME=tu_portal
ZOHO_REFRESH_TOKEN=tu_refresh_token
ZOHO_AUTO_TIMER_PROJECT_ID=id_numerico_del_proyecto
ZOHO_AUTO_TIMER_TASK_ID=id_numerico_de_la_tarea
TZ=America/Mexico_City
```
**Servicio 1 — iniciar timer**
| Campo | Valor |
|---|---|
| Tipo | Cron Job |
| Comando | `node scripts/auto-timer.js start` |
| Schedule (UTC) | `0 13 * * 1-5` *(8am CDMX, verano UTC-5)* |
**Servicio 2 — detener timer**
| Campo | Valor |
|---|---|
| Tipo | Cron Job |
| Comando | `node scripts/auto-timer.js stop` |
| Schedule (UTC) | `0 22 * * 1-5` *(5pm CDMX, verano UTC-5)* |
> **Timezone:** Railway siempre interpreta el schedule en UTC. Ajusta la hora según tu zona:
>
> | Zona | UTC offset | 8am en UTC | 5pm en UTC |
> |---|---|---|---|
> | CDMX verano (CDT) | UTC-5 | `0 13 * * 1-5` | `0 22 * * 1-5` |
> | CDMX invierno (CST) | UTC-6 | `0 14 * * 1-5` | `0 23 * * 1-5` |
> | Colombia / Perú | UTC-5 | `0 13 * * 1-5` | `0 22 * * 1-5` |
> | Argentina | UTC-3 | `0 11 * * 1-5` | `0 20 * * 1-5` |
> | España (verano) | UTC+2 | `0 6 * * 1-5` | `0 15 * * 1-5` |
Para verificar que funciona, usa el botón **"Run Now"** en cada servicio y revisa los logs. Deberías ver `Timer iniciado` o `Timer detenido`.
---
#### Opción B — VPS / servidor Linux *(Ubuntu, Debian, etc.)*
No necesitas `tokens.json` si defines `ZOHO_REFRESH_TOKEN` en el entorno. Clona el repo, instala dependencias y registra los crons:
```bash
git clone https://github.com/tu-org/zoho-projects-mcp.git /opt/zoho-mcp
cd /opt/zoho-mcp
npm install
cp .env.example .env # edita con tus valores, incluyendo ZOHO_AUTO_TIMER_* y ZOHO_REFRESH_TOKEN
```
Edita el crontab:
```bash
crontab -e
```
Agrega las dos líneas (ajusta la hora a tu zona horaria del servidor):
```cron
0 8 * * 1-5 cd /opt/zoho-mcp && node scripts/auto-timer.js start >> /var/log/zoho-timer.log 2>&1
0 17 * * 1-5 cd /opt/zoho-mcp && node scripts/auto-timer.js stop >> /var/log/zoho-timer.log 2>&1
```
> Si el servidor corre en UTC, convierte las horas igual que en Railway. Verifica la zona del servidor con `timedatectl` y cámbiala si quieres usar hora local: `sudo timedatectl set-timezone America/Mexico_City`.
---
#### Opción C — Mac o Linux local *(la máquina debe estar encendida a esas horas)*
Requiere `tokens.json` generado por `npm run setup`. Registra los crons con `crontab -e`:
```cron
0 8 * * 1-5 cd /ruta/al/proyecto && node scripts/auto-timer.js start >> /tmp/zoho-timer.log 2>&1
0 17 * * 1-5 cd /ruta/al/proyecto && node scripts/auto-timer.js stop >> /tmp/zoho-timer.log 2>&1
```
En Mac, si la máquina duerme exactamente a esa hora el cron puede no dispararse. Una alternativa más robusta en Mac es usar `launchd` en lugar de `crontab`.
---
### Solución de problemas
| Síntoma | Posible causa |
|---|---|
| `ZOHO_AUTO_TIMER_PROJECT_ID y ZOHO_AUTO_TIMER_TASK_ID son requeridos` | Faltan esas variables de entorno |
| `No hay timer activo` al hacer stop | El timer no se inició antes (revisa logs del servicio start) |
| `Error 401` | El refresh token expiró — corre `npm run setup` localmente y actualiza `ZOHO_REFRESH_TOKEN` |
| Timer descartado (< 30 segundos) | Normal si se prueba con "Run Now" dos veces seguidas muy rápido |
## Arquitectura
```
src/
├── server.js # Punto de entrada MCP — registra las 20 herramientas con esquemas Zod
├── zoho-client.js # Cliente HTTP singleton — refresco automático de tokens en 401
└── setup-auth.js # Flujo OAuth2 de una sola vez
scripts/
├── auto-timer.js # Cron — inicia/detiene timer automáticamente (Railway, VPS o local)
├── my-open-tasks.js # Utilidad CLI — tareas abiertas del equipo en todos los proyectos
└── my-mentions.js # Utilidad CLI — comentarios que te mencionan, con filtro de fechas
```
El cliente HTTP (`zoho-client.js`) intercepta respuestas 401, renueva el access token usando el refresh token y reintenta la solicitud original de forma transparente. Para endpoints que requieren `application/x-www-form-urlencoded` (como `addbulktimelogs`) expone `postForm()` en lugar del `post()` estándar que envía JSON.
## Archivos sensibles
Los siguientes archivos contienen credenciales y están excluidos del repositorio (`.gitignore`):
- `.env` — variables de entorno con credenciales OAuth
- `tokens.json` — tokens de acceso activos generados por `npm run setup`
TDQS
A3.5/5.0
Scored across 11 tools
Disambiguation5/5
Cada herramienta tiene un propósito claramente distinto: comentarios, tareas, proyectos, usuarios, temporizadores. No hay superposición ni ambigüedad entre ellas.
Naming Consistency5/5
Todas las herramientas siguen el patrón verbo_substantivo en snake_case (add_comment, create_task, list_projects, etc.) sin excepciones ni mezcla de convenciones.
Tool Count5/5
Con 11 herramientas, la superficie está bien delimitada para la gestión de proyectos de Zoho. Cubre las operaciones esenciales sin ser excesiva ni insuficiente.
Completeness4/5
Faltan operaciones de eliminación (delete_task, delete_comment) y algunas de actualización en comentarios, pero el núcleo de creación, lectura y actualización de tareas está cubierto. Es un conjunto funcional con lagunas menores.
Maintenance
ActivityInactive
ResponsivenessUnresponsive