Skip to main content
Glama
borgels

mcp-server-productive

by borgels

mcp-server-productive

Servidor MCP para la API v2 de Productive.io: proyectos, tareas, seguimiento de tiempo, planificación de recursos, finanzas, CRM e informes para una organización.

Productive expone unas 650 operaciones en 132 recursos. Convertirlas en 650 herramientas MCP saturaría la lista de herramientas de cualquier cliente, por lo que este servidor consta de doce herramientas impulsadas por un registro generado: las herramientas son genéricas y el registro sabe qué acepta realmente cada recurso.

Herramientas

Descubrimientoproductive_search_capabilities, productive_describe_resource, productive_check_connection, productive_describe_custom_fields

Lecturaproductive_list (filtros, ordenaciones, inclusiones, paginación y los 26 endpoints de informes con agrupación), productive_get

Escrituraproductive_create, productive_update, productive_delete, productive_run_action (150 verbos con nombre: archive, restore, approve, close, copy, finalize, send…), productive_track_time, productive_commit_operation

Autenticación por usuario (opt-in)productive_connect, productive_status, productive_disconnect — ver Autenticación

Empieza con productive_search_capabilities. Los nombres de recursos de Productive son propios: un presupuesto es un deal, una columna de tablero es un workflow_status, una aprobación de parte de horas vive en time_entries — y adivinar cuesta llamadas.

Related MCP server: productive-mcp-rb2

El registro

src/productive/registry.generated.ts se deriva del documento OpenAPI publicado por Productive mediante scripts/generate-registry.mjs y se confirma en el repositorio, de modo que la CI nunca necesita la red y un cambio en la especificación aparece como un diff revisable. Para cada recurso registra los campos de filtro, las claves de ordenación, las claves de agrupación de informes, las relaciones incluibles, los atributos escribibles para crear y actualizar con los obligatorios marcados, y cada acción con nombre.

Eso es lo que permite que doce herramientas sigan siendo honestas. productive_describe_resource devuelve el contrato exacto de un recurso, y cada argumento se comprueba contra él antes de enviar una solicitud.

Regenéralo con npm run registry:generate (añade un argumento de ruta para usar una copia local de la especificación). El generador hace fallar la compilación si una clasificación escrita a mano — un nivel de riesgo, un indicador orientado al exterior, una operación bloqueada — ya no coincide con ninguna ruta de la especificación, de modo que un cambio de nombre upstream no puede eliminar silenciosamente una protección.

Lo que hace la API, según lo medido

Todo lo que aparece aquí se verificó contra una organización real, porque la especificación y la API discrepan en lugares que importan.

El tiempo son minutos. El dinero son unidades menores. Una entrada de tiempo de 2 son dos minutos. Ambos vuelven también así.

Los filtros, ordenaciones e inclusiones desconocidos fallan de forma ruidosa. HTTP 400 con unsupported_filter, sort_param_unsupported, unsupported_include. Así que validarlos aquí es un error mejor, no una red de seguridad.

Los atributos de escritura desconocidos fallan en silencio. Un PATCH con un atributo mal escrito devuelve HTTP 200 y no cambia nada — indistinguible de un éxito. Por tanto, este servidor rechaza un atributo que el recurso no declara, en lugar de informar de una escritura que no ocurrió. Es lo más útil que hace el registro.

Hay exactamente seis operadores de filtro, en todos los campos: contains, eq, gt, lt, not_contain, not_eq. La especificación lista cuatro por campo y omite gt/lt, que sí funcionan; gte, lte, in, not_in, starts_with, ends_with, blank y present se rechazan todos con unsupported_filter_operation. No existe la comparación inclusiva, por lo que un rango inclusivo necesita los propios campos de filtro after/before o <field>_after/<field>_before del recurso.

page[size] tiene un tope de 200 y lo recorta en silencio. Pedir 500 devuelve 200 sin error. Los resultados incluyen total y nextPage para que una página no se confunda con la respuesta completa.

PATCH es genuinamente parcial. Los atributos omitidos conservan sus valores; no hace falta reenviar el registro completo.

data.type no se comprueba. Aplicar un PATCH a una tarea con type: "projects" tiene éxito y aplica el cambio. Este servidor envía el tipo correcto de todos modos.

Un 403 que dice que el id de organización "has to be provided" puede significar que era incorrecto, no que faltaba. El mismo código no_organization_id cubre una cabecera ausente y una organización a la que el token no puede acceder.

Una funcionalidad ausente responde 404, no 403. /boards devuelve 404 en una organización sin ella, lo que parece una ruta rota.

Los borrados pueden ser restaurables. Una tarea borrada aparece en deleted_items con item_type e item_id y puede restaurarse mediante la acción restore de ese recurso. Verificado solo para tareas: no asumas que se cumple para todos los tipos.

GET /users es el único endpoint con ámbito del llamante. Devuelve exactamente un registro — tú — y así es como este servidor identifica al propietario de un token. No existe /users/me; esa ruta devuelve 404. Cuidado con /organization_memberships: no está limitado a la organización fijada, sino que lista las membresías del llamante en todas las organizaciones a las que pertenece, así que su número de filas no es un recuento de personas.

No hay cabeceras de límite de tasa. Solo x-request-id, que los errores de este servidor citan. Reduce la velocidad ante un 429 en lugar de sondear el límite.

Permisos

Cuatro interruptores, todos desactivados por defecto. Un servidor de solo lectura es el valor por defecto útil y seguro.

Interruptor

Cubre

PRODUCTIVE_ENABLE_WRITES

Interruptor principal. Nada se modifica sin él.

PRODUCTIVE_ENABLE_FINANCIALS

Dinero, precios, nóminas, documentos que recibe un cliente: facturas, líneas de factura, pagos, recibos, gastos, órdenes de compra, propuestas, contratos, precios, tarifas, salarios, costes generales, tipos impositivos, cuentas bancarias, filiales.

PRODUCTIVE_ENABLE_ADMIN

Acceso y configuración de toda la organización: personas, membresías, conjuntos de permisos, equipos, invitaciones, campos personalizados, webhooks, integraciones, políticas de aprobación y de seguimiento de tiempo.

PRODUCTIVE_ENABLE_DELETES

Borrados, además del control por niveles.

Un único interruptor principal no basta para Productive: la misma API mueve una tarea, emite una factura y concede un conjunto de permisos, y esas son tres decisiones distintas. Un servidor en el que se confía para gestionar el trabajo de un proyecto no debería por ello poder enviar una factura.

PRODUCTIVE_ALLOWED_RESOURCES / PRODUCTIVE_DENIED_RESOURCES restringen aún más una instancia, y se aplican también a las lecturas: una instancia limitada al seguimiento de tiempo tampoco debería leer salarios.

Nunca expuestos en absoluto, independientemente de los interruptores: passwords, sessions, organization_subscriptions, los enlaces de compartir public/* sin autenticación, y PATCH /users/{id}/update_password. Estos están ausentes del registro en lugar de estar restringidos, de modo que ningún error de política puede reabrirlos.

Escrituras en dos pasos

El trabajo ordinario de un proyecto — una tarea, una entrada de tiempo, una reserva, un comentario — se escribe en una sola llamada. Exigir un apretón de manos en cada entrada de tiempo haría que el servidor fuera inutilizable para lo que la gente hace con más frecuencia.

Todo lo que tiene un radio de explosión mayor está preparado: la herramienta devuelve la solicitud exacta más un hash y no envía nada, y productive_commit_operation la ejecuta solo si la operación vuelve sin alteraciones. Eso cubre los niveles financiero y de administración, todos los borrados, cualquier cosa que salga de la organización y todas las acciones bulk_* — estas actúan sobre todos los registros que coinciden con un filtro, así que también se niegan a ejecutarse sin un filtro explícito.

Seis operaciones están marcadas como outward porque alcanzan a alguien fuera de la organización en el momento en que se ejecutan: invoices.send, invoices.send_einvoice, people.invite, people.resend, organizations.resend_code, y la creación de una invitation.

Autenticación

Dos modos. PRODUCTIVE_ORGANIZATION_ID es obligatorio en ambos, y nunca es un argumento de herramienta.

Tokens por usuario (recomendado)

Cada persona vincula su propio token de Productive, de modo que Productive aplica sus permisos y registra su nombre en lo que hacen.

Esto importa más en Productive que en la mayoría de los sistemas. Productive atribuye el trabajo a personas: una entrada de tiempo pertenece a un person_id, y cada cambio queda sellado con el propietario del token en el registro de actividad — que es el registro con el que se defiende una factura de cliente. Con un token compartido, ese registro dice que la cuenta de servicio lo hizo todo.

PRODUCTIVE_PER_USER_AUTH=true
PRODUCTIVE_TRUST_FORWARDED_USER=true
PRODUCTIVE_ENCRYPTION_KEY=<min 16 chars>
PRODUCTIVE_STORE_PATH=/data/store.json
PRODUCTIVE_PUBLIC_BASE_URL=https://productive.example.com
# PRODUCTIVE_API_TOKEN deliberately unset

El flujo:

  1. El llamante ejecuta productive_connect y obtiene un enlace de un solo uso, válido durante 10 minutos, vinculado a su identidad.

  2. Lo abre y pega un token que creó en Productive en Ajustes → Integraciones de API. El token va del navegador directamente al servidor, por lo que nunca entra en la transcripción de la conversación: un token de Productive equivale a un token de portador de toda su cuenta y, como se ha medido más abajo, suele llegar a más de una organización.

  3. Antes de almacenarlo, el servidor llama a GET /users con ese token y el id de esta organización. Una llamada demuestra tres cosas: que el token es válido, que puede llegar a esta organización y de quién es. La página confirma entonces qué cuenta se vinculó.

  4. Los tokens se cifran en reposo con AES-256-GCM, una fila por identidad verificada.

Bordes afilados:

  • La identidad solo proviene de la puerta de enlace. X-MCP-User se lee únicamente cuando PRODUCTIVE_TRUST_FORWARDED_USER=true, nunca de nada que controle el cliente MCP. Actívalo solo detrás de una puerta de enlace que establezca la cabecera a partir de un token validado y elimine una copia suministrada por el cliente — de lo contrario, un llamante puede nombrar cualquier identidad y actuar como ella.

  • Sin respaldo. Un llamante no registrado recibe NOT_CONNECTED, nunca el token compartido, incluso si PRODUCTIVE_API_TOKEN está definido. Un respaldo les daría derechos prestados, que es el fallo que este modo existe para eliminar.

  • /productive/enroll debe ser accesible desde el navegador del usuario, sin pasar por la puerta de enlace MCP: un navegador no puede llevar el token de portador de la puerta de enlace. Enruta /productive/* en PRODUCTIVE_PUBLIC_BASE_URL directamente al contenedor. Su seguridad es el token de estado de un solo uso y vinculado a la identidad.

  • Persiste PRODUCTIVE_STORE_PATH en un volumen y mantén estable PRODUCTIVE_ENCRYPTION_KEY: si la cambias, todos los tokens almacenados se vuelven indescifrables.

  • Si el correo electrónico del propio token de Productive difiere de la dirección de directorio del llamante, eso se notifica de forma visible en la página y en productive_status, y se conecta de todos modos. Establece PRODUCTIVE_REQUIRE_EMAIL_MATCH=true para rechazarlo en su lugar. Está desactivado por defecto porque quien pega el token de otra persona ya lo tiene, así que rechazarlo aporta poca seguridad, mientras que una cuenta de Productive con una dirección diferente es totalmente plausible.

  • La autenticación por usuario separa permisos y atribución, no organizaciones. La fijación de organización sigue aplicándose a todos.

Token compartido

Establece PRODUCTIVE_API_TOKEN a un token. Simple y adecuado para stdio o un único operador — pero entonces cada llamante actúa como el propietario de ese token, con sus permisos, y el registro de actividad de Productive acredita cada cambio a esa persona.

PRODUCTIVE_TRUST_FORWARDED_USER sigue ayudando aquí: las escrituras relativas a una persona (una entrada de tiempo, una reserva) se asignan por defecto al llamante resuelto en lugar del propietario del token, y el servidor se niega a adivinar cuando la dirección no coincide con nadie o con más de una persona. productive_check_connection nombra al propietario del token en cualquier caso, por lo que la atribución nunca es una sorpresa.

Multiorganización

Una instancia sirve exactamente a una organización. X-Organization-Id proviene del entorno y nunca es un argumento de herramienta, por lo que ninguna ruta de código — incluidas las herramientas genéricas — puede alcanzar a otro inquilino. Ejecuta una segunda instancia para una segunda organización; la imagen es la misma.

Esto no es teórico. Un solo token llega habitualmente a varias organizaciones: en la cuenta contra la que se desarrolló, GET /organizations devolvió tres, y cambiando solo la cabecera se movía entre ellas (las otras dos respondieron 403 subscription_expired, no "no encontrado"). La cabecera es todo el límite, por eso está fijada en lugar de pasarse — y por eso un token por usuario registrado se verifica contra esta organización antes de almacenarse.

Configuración

Consulta .env.example. Las dos variables obligatorias son PRODUCTIVE_API_TOKEN (Ajustes → Integraciones de API en Productive; hereda los permisos del usuario que la crea) y PRODUCTIVE_ORGANIZATION_ID (el id numérico en tu URL de Productive).

Establece PRODUCTIVE_AUDIT_LOG para añadir una línea JSON por cada intento de mutación, incluidos los que la política rechazó. Los cuerpos de las solicitudes no se registran deliberadamente: contienen salarios, tarifas y datos personales, y una pista de auditoría que debe protegerse tan estrechamente como el sistema de origen tiende a no leerse.

Ejecución

npm install
npm run dev          # stdio
npm run dev:http     # streamable HTTP on :3000/mcp (stateless), /healthz open
npm test
npm run smoke:live   # reads a real organization; stages one write, commits nothing

Imágenes de Docker: ghcr.io/borgels/mcp-server-productive (publicadas al hacer push a main).

Licencia

Apache-2.0.

Install Server
A
license - permissive license
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

  • ClickUp MCP — wraps the ClickUp REST API v2 (BYO API key)

  • Product Hunt MCP — wraps the Product Hunt GraphQL API v2 (api.producthunt.com)

  • Direct access to your Sanity projects (content, datasets, releases, schemas) and agent rules

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/borgels/mcp-server-productive'

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