mcp-server-productive
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
Descubrimiento — productive_search_capabilities, productive_describe_resource, productive_check_connection, productive_describe_custom_fields
Lectura — productive_list (filtros, ordenaciones, inclusiones, paginación y los 26 endpoints de informes con agrupación), productive_get
Escritura — productive_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 |
| Interruptor principal. Nada se modifica sin él. |
| 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. |
| 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. |
| 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 unsetEl flujo:
El llamante ejecuta
productive_connecty obtiene un enlace de un solo uso, válido durante 10 minutos, vinculado a su identidad.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.
Antes de almacenarlo, el servidor llama a
GET /userscon 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ó.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-Userse lee únicamente cuandoPRODUCTIVE_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 siPRODUCTIVE_API_TOKENestá definido. Un respaldo les daría derechos prestados, que es el fallo que este modo existe para eliminar./productive/enrolldebe 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/*enPRODUCTIVE_PUBLIC_BASE_URLdirectamente al contenedor. Su seguridad es el token de estado de un solo uso y vinculado a la identidad.Persiste
PRODUCTIVE_STORE_PATHen un volumen y mantén establePRODUCTIVE_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. EstablecePRODUCTIVE_REQUIRE_EMAIL_MATCH=truepara 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 nothingImágenes de Docker: ghcr.io/borgels/mcp-server-productive (publicadas al hacer push a main).
Licencia
Apache-2.0.
Maintenance
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
- AlicenseAqualityAmaintenanceA Model Context Protocol (MCP) server for accessing Productive.io API endpoints (projects, tasks, comments, todos), tailored for read-only operations, providing streamlined access to essential data while minimizing token consumption18MIT
- AlicenseAqualityDmaintenanceEnables interaction with Productive.io for task management, time tracking, budget monitoring, and project overview through natural language.8358ISC
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with a Productive.io workspace for managing projects, tasks, time entries, budgets, and invoices through natural language.
- AlicenseNot gradedqualityDmaintenanceEnables interaction with Productive.io task management platform, allowing users to retrieve tasks and filter by assignee, status, or project.32ISC
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
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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