mcp-server-productive
mcp-server-productive
面向 Productive.io API v2 的 MCP 服务器——为单个组织提供项目、任务、时间跟踪、资源规划、财务、CRM 和报表。
Productive 在 132 个资源上暴露了约 650 个操作。如果把这些变成 650 个 MCP 工具,会淹没任何客户端的工具列表,因此这个服务器是由生成的注册表驱动的十二个工具:工具是通用的,注册表知道每个资源真正接受什么。
工具
发现 — productive_search_capabilities、productive_describe_resource、productive_check_connection、productive_describe_custom_fields
读取 — productive_list(筛选、排序、包含关系、分页,以及带分组的 26 个报表端点)、productive_get
写入 — productive_create、productive_update、productive_delete、productive_run_action(150 个命名动词:archive、restore、approve、close、copy、finalize、send……)、productive_track_time、productive_commit_operation
按用户认证(可选) — productive_connect、productive_status、productive_disconnect — 参见认证
从 productive_search_capabilities 开始。Productive 的资源名是它自己的——预算是 deal,看板列是 workflow_status,工时表审批位于 time_entries 上——而猜测会付出调用代价。
Related MCP server: productive-mcp-rb2
注册表
src/productive/registry.generated.ts 由 scripts/generate-registry.mjs 从 Productive 发布的 OpenAPI 文档生成,并已提交,因此 CI 永远不需要网络,规范变更会以可审查的 diff 形式呈现。对于每个资源,它记录筛选字段、排序键、报表分组键、可包含的关系、创建和更新的可写属性(并标记必填项),以及每个命名操作。
这正是让十二个工具保持诚实的原因。productive_describe_resource 返回某个资源的精确契约,每个参数在请求发出前都会对照它进行检查。
使用 npm run registry:generate 重新生成(添加路径参数以使用本地副本的规范)。如果手写的分类——风险等级、面向外部的标志、被阻止的操作——不再匹配规范中的任何路径,生成器会使构建失败,因此上游的重命名无法悄悄移除防护。
实测中 API 的行为
这里的一切都针对一个真实组织验证过,因为规范与 API 在关键之处存在分歧。
时间以分钟计,金额以最小货币单位计。 一条 2 的工时记录就是两分钟。返回时两者也都是这样。
未知的筛选、排序和包含关系会明确失败。 HTTP 400,附带 unsupported_filter、sort_param_unsupported、unsupported_include。因此在这里校验它们能提供更好的错误信息,而不是一道安全网。
未知的写入属性会静默失败。 使用拼写错误的属性进行 PATCH 会返回 HTTP 200 且不改变任何内容——与成功无法区分。因此,本服务器会拒绝资源未声明的属性,而不是报告一次并未发生的写入。这是注册表所做的唯一最有价值的事情。
每个字段上恰好有六个筛选运算符: contains、eq、gt、lt、not_contain、not_eq。规范为每个字段列出了四个,并省略了实际可用的 gt/lt;gte、lte、in、not_in、starts_with、ends_with、blank 和 present 都会被以 unsupported_filter_operation 拒绝。没有包含边界的比较,因此包含边界的范围需要使用资源自身的 after/before 或 <field>_after/<field>_before 筛选字段。
page[size] 上限为 200,并静默截断。 请求 500 会返回 200 且不报错。结果带有 total 和 nextPage,因此不会把一页误认为全部答案。
PATCH 是真正的部分更新。 省略的属性保持原值;无需重新发送整条记录。
data.type 不会被检查。 用 type: "projects" 修补任务会成功并应用更改。本服务器无论如何都会发送正确的类型。
返回 403 并提示组织 id“必须提供”,可能意味着它错了,而不是缺失。 同一个 no_organization_id 代码既涵盖缺失的请求头,也涵盖令牌无法访问的组织。
缺少的功能返回 404,而不是 403。 在没有该功能的组织上,/boards 会返回 404,看起来像路径错误。
删除可能是可恢复的。 已删除的任务会出现在 deleted_items 中,带有 item_type 和 item_id,并可通过该资源的 restore 操作恢复。仅对任务验证过——不要假设它对所有类型都成立。
GET /users 是唯一按调用者限定的端点。 它恰好返回一条记录——你——本服务器正是以此识别令牌的所有者。没有 /users/me;该路径会 404。注意 /organization_memberships:它不限定于固定的组织,而是列出调用者所属每个组织的成员关系,因此它的行数不是人数统计。
没有速率限制头。 只有 x-request-id,本服务器的错误信息会引用它。遇到 429 应退避,而不是探测限制。
权限
四个开关,默认全部关闭。只读服务器是有用且安全的默认配置。
开关 | 覆盖范围 |
| 总开关。没有它,任何内容都不会被修改。 |
| 资金、定价、工资、客户收到的文档:发票、行项目、付款、账单、费用、采购订单、提案、合同、价格、费率卡、薪资、间接费用、税率、银行账户、子公司。 |
| 访问权限和全组织配置:人员、成员关系、权限集、团队、邀请、自定义字段、Webhook、集成、审批和时间跟踪策略。 |
| 在层级门禁之上的删除操作。 |
对于 Productive 来说,一个总开关是不够的:同一个 API 可以移动任务、开具发票和授予权限集,而这是三个不同的决策。一个被信任用于运行项目工作的服务器,不应因此就能发送发票。
PRODUCTIVE_ALLOWED_RESOURCES / PRODUCTIVE_DENIED_RESOURCES 可进一步缩小实例范围,并且同样适用于读取——一个限定于时间跟踪的实例也不应读取薪资。
无论开关如何,以下内容永远不会暴露: passwords、sessions、organization_subscriptions、未认证的 public/* 分享链接,以及 PATCH /users/{id}/update_password。这些内容不在注册表中,而不是被门禁控制,因此任何策略错误都无法重新打开它们。
两步写入
普通的项目工作——任务、工时记录、预订、评论——通过一次调用写入。如果要求每次工时记录都进行握手,服务器将无法用于人们最常做的事情。
所有影响范围更大的操作都是分阶段的:工具返回确切的请求加一个哈希,但不发送任何内容,productive_commit_operation 仅在操作原样返回时才会执行它。这涵盖了财务和管理层级、所有删除、任何离开组织的操作,以及所有 bulk_* 操作——这些操作作用于筛选匹配的所有记录,因此它们也拒绝在没有显式筛选的情况下运行。
有六个操作被标记为 outward,因为它们在运行的那一刻就会触达组织之外的某个人:invoices.send、invoices.send_einvoice、people.invite、people.resend、organizations.resend_code,以及创建 invitation。
认证
两种模式。两种模式都需要 PRODUCTIVE_ORGANIZATION_ID,它绝不是工具参数。
按用户令牌(推荐)
每个人关联自己的 Productive 令牌,这样 Productive 会应用他们的权限,并在他们的操作上记录他们的名字。
这一点在 Productive 上比在大多数系统上更重要。Productive 将工作归因于人:一条工时记录属于某个 person_id,每次更改都会在活动日志中盖上令牌所有者的印记——而这份日志正是客户发票据以辩护的记录。如果使用一个共享令牌,该日志会显示服务账户做了所有事情。
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流程:
调用者运行
productive_connect,获得一个一次性链接,有效期 10 分钟,与其身份绑定。他们打开链接,粘贴他们在 Productive 的设置 → API 集成下创建的令牌。令牌从他们的浏览器直接发送到服务器,因此永远不会进入对话记录——Productive 令牌等同于其整个账户的持有者凭证,而且如下文实测所示,通常可以访问多个组织。
在存储之前,服务器使用该令牌和本组织的 id 调用
GET /users。一次调用证明三件事:令牌有效、可以访问这个组织,以及它属于谁。然后页面会确认关联了哪个账户。令牌使用 AES-256-GCM 静态加密,每个已验证身份一行。
需要注意的边界情况:
身份只来自网关。 仅当
PRODUCTIVE_TRUST_FORWARDED_USER=true时才读取X-MCP-User,绝不读取 MCP 客户端控制的任何内容。只有在网关根据已验证的令牌设置该请求头并剥离客户端提供的副本时,才启用它——否则调用者可以指定任意身份并冒充他们。没有回退。 未注册的调用者会得到
NOT_CONNECTED,绝不会得到共享令牌,即使碰巧设置了PRODUCTIVE_API_TOKEN。回退会把借来的权限交给他们,这正是该模式要消除的失败。/productive/enroll必须能被用户的浏览器访问,绕过 MCP 网关——浏览器无法携带网关的 bearer 令牌。将PRODUCTIVE_PUBLIC_BASE_URL上的/productive/*直接路由到容器。其安全性在于一次性、与身份绑定的状态令牌。将
PRODUCTIVE_STORE_PATH持久化到卷上,并保持PRODUCTIVE_ENCRYPTION_KEY稳定——更改它会导致所有已存储的令牌无法解密。如果令牌自身的 Productive 邮箱与调用者的目录地址不同,页面和
productive_status会明确报告,并且仍然连接。设置PRODUCTIVE_REQUIRE_EMAIL_MATCH=true可以改为拒绝。默认关闭,因为粘贴他人令牌的人已经持有该令牌,拒绝并不能带来多少安全性,而使用不同地址的 Productive 账户是完全可能的。按用户认证区分的是权限和归属,而不是组织。组织固定仍然适用于每个人。
共享令牌
将 PRODUCTIVE_API_TOKEN 设置为一个令牌。简单,适合 stdio 或单一操作者——但每个调用者都会以该令牌所有者的身份行事,拥有他们的权限,并且 Productive 的活动日志会把每次更改都记在他们名下。
PRODUCTIVE_TRUST_FORWARDED_USER 在这里仍然有帮助:以人为对象的写入(一个时间条目、一次预订)默认使用已解析的调用者,而不是令牌的所有者;当地址匹配不到任何人或匹配到多个人时,服务器拒绝猜测。productive_check_connection 无论如何都会指明令牌的所有者,因此归属永远不会出人意料。
多组织
一个实例恰好服务一个组织。X-Organization-Id 来自环境变量,绝不是工具参数,因此任何代码路径——包括通用工具——都无法访问另一个租户。为第二个组织运行第二个实例;镜像是相同的。
这并非理论上的说法。单个令牌经常能访问多个组织:在开发此功能所用的账户上,GET /organizations 返回了三个组织,而仅切换请求头就能在它们之间切换(另外两个返回 403 subscription_expired,而不是“not found”)。请求头就是整个边界,这就是为什么它被固定下来而不是作为参数传入——也是为什么已注册的按用户令牌在存储之前会针对这个组织进行验证。
配置
参见 .env.example。两个必需的变量是 PRODUCTIVE_API_TOKEN(Productive 中的 Settings → API integrations;它继承创建用户的权限)和 PRODUCTIVE_ORGANIZATION_ID(你的 Productive URL 中的数字 id)。
设置 PRODUCTIVE_AUDIT_LOG 以在每次变更尝试时追加一行 JSON,包括被策略拒绝的尝试。请求体有意不被记录:它们携带薪资、费率和个人数据;而审计跟踪必须像源系统一样严密保护,且往往不会被阅读。
运行
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 nothingDocker 镜像:ghcr.io/borgels/mcp-server-productive(在推送到 main 时发布)。
许可证
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