A remote MCP server for Productive.io running on Cloudflare Workers. Connects Claude Desktop, Claude Code, and other MCP-compatible clients to your Productive.io workspace — no local installation, no API keys on client machines.
- Remote-first — runs on Cloudflare Workers, clients connect via URL
- Microsoft Entra ID — OAuth 2.1 with your organization's identity provider (SSO + MFA)
- Per-user authorization (BYOT) — every call uses the user's own Productive token, encrypted at rest; no shared admin token (details)
- Auto user resolution — maps Entra email to Productive user ID automatically
- Name resolution — people, companies, projects, tasks, and deals are shown by name, not raw ID
- 70+ tools — projects, tasks, time tracking, invoicing, comments, pages, and more
- Zero client setup — just add one URL to Claude Desktop or Claude Code
If someone in your organization has already deployed this server, you only need to configure your MCP client:
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"productive": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://<your-worker>.<your-subdomain>.workers.dev/mcp"]
}
}
}Restart Claude Desktop. A browser window opens for Entra ID login on first connect. The session persists — you only sign in once.
claude mcp add productive -- npx -y mcp-remote https://<your-worker>.<your-subdomain>.workers.dev/mcp- Cloudflare account with Workers (free tier works)
- Microsoft Entra ID tenant with App Registration rights
- Node.js 18+ and npm
git clone https://github.com/MonadsAG/monads-mcp-productive.git
cd monads-mcp-productive
npm installnpx wrangler login
npx wrangler kv namespace create "OAUTH_KV"
npx wrangler kv namespace create "USER_MAPPING_KV"
npx wrangler kv namespace create "USER_PAT_KV"Copy the namespace IDs into wrangler.jsonc. (USER_PAT_KV stores each user's
encrypted Productive token — see Per-user tokens.)
# Login to your Entra tenant
az login --tenant <your-tenant-id> --allow-no-subscriptions
# Create the app registration (single tenant).
# Register BOTH redirect URIs: /callback (MCP login) and /settings/callback (token settings page).
az ad app create \
--display-name "Productive MCP" \
--sign-in-audience "AzureADMyOrg" \
--web-redirect-uris \
"https://<your-worker>.<your-subdomain>.workers.dev/callback" \
"https://<your-worker>.<your-subdomain>.workers.dev/settings/callback"
# Note the appId from the output, then add API permissions
az ad app permission add \
--id <app-id> \
--api 00000003-0000-0000-c000-000000000000 \
--api-permissions \
37f7f235-527c-4136-accd-4a02d197296e=Scope \
14dad69e-099b-42c9-810b-d002981feec1=Scope \
64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0=Scope \
e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope
# Grant admin consent
az ad app permission admin-consent --id <app-id>
# Generate a client secret (2-year expiry)
az ad app credential reset \
--id <app-id> \
--append \
--display-name "Cloudflare Worker" \
--years 2The permission IDs correspond to: openid, profile, email, User.Read (delegated).
npx wrangler secret put PRODUCTIVE_ORG_ID # your org ID with slug (e.g. 12345-company-name)
npx wrangler secret put PRODUCTIVE_API_BASE_URL # https://api.productive.io/api/v2/ (or sandbox URL)
npx wrangler secret put PRODUCTIVE_TOOLSETS # optional, comma-separated toolset names (default: all tools)
npx wrangler secret put ENTRA_CLIENT_ID # app (client) ID from step 3
npx wrangler secret put ENTRA_CLIENT_SECRET # client secret from step 3
npx wrangler secret put ENTRA_TENANT_ID # your Entra directory (tenant) ID
openssl rand -hex 32 | npx wrangler secret put COOKIE_ENCRYPTION_KEY # signs session/CSRF cookies
openssl rand -hex 32 | npx wrangler secret put PAT_ENC_KEY # encrypts per-user PATs at rest (AES-256-GCM)No shared Productive token. Each user supplies their own Productive PAT via the
/settingspage (see Per-user tokens), soPRODUCTIVE_API_TOKENis not needed by the Worker. It remains only for the legacy local stdio entrypoint (set it in.env, not as a Worker secret).
npm run worker:deployConfigure Claude Desktop or Claude Code as shown in Quick Start, replacing the placeholder URL with your Worker URL.
Every Productive API call runs with the requesting user's own Personal Access Token (Bring Your Own Token), so Productive enforces each user's real permissions — there is no shared admin token. Identity still comes from Microsoft Entra ID; only the upstream credential is now per-user.
After connecting the MCP, each user opens the settings page once to store their token:
https://<your-worker>.<your-subdomain>.workers.dev/settings
The page signs the user in with Entra ID, then lets them set, rotate, or delete their
Productive PAT — no MCP reconnect required. The token is validated against Productive,
encrypted with AES-256-GCM (PAT_ENC_KEY) before being written to USER_PAT_KV (keyed by
the user's stable Entra object ID), and never displayed, logged, or sent through the model.
Until a user stores a token, their tool calls return a friendly message linking to
/settings instead of any data.
The settings page and the sign-in dialog render in German or English automatically, based on
the browser's Accept-Language (default English); append ?lang=de / ?lang=en to force one.
┌─────────────────┐ Streamable HTTP ┌──────────────────────────────┐
│ Claude Desktop │ ◄──────────────────► │ Cloudflare Worker │
│ / Claude Code │ │ │
│ (via mcp-remote)│ │ OAuthProvider (OAuth 2.1) │
└─────────────────┘ │ ↓ │
│ Entra ID (OIDC login) │
Browser ←── login redirect ───────→│ ↓ │
│ McpAgent (Durable Object) │
│ ↓ │
│ ProductiveAPIClient │
└──────────────┬───────────────┘
↓
Productive.io API
| Tool | Description |
|---|---|
whoami |
Get current user context |
list_companies |
List companies/customers |
list_projects |
List projects with status filtering |
Productive's UI calls this resource a "folder"; the API models it as a "board" (tasks and task lists reference their parent via a board/board_id relationship). One tool set covers it -- there is no separate "board" tool.
| Tool | Description |
|---|---|
list_folders / get_folder / create_folder / update_folder |
Folder CRUD |
archive_folder / restore_folder / copy_folder / move_folder / reposition_folder |
Folder lifecycle & positioning |
list_task_lists / create_task_list / get_task_list / update_task_list |
Task list CRUD |
archive_task_list / restore_task_list / copy_task_list / move_task_list / reposition_task_list |
Task list operations |
| Tool | Description |
|---|---|
list_tasks / get_task / get_project_tasks / my_tasks |
Query tasks |
create_task / update_task / delete_task |
Task CRUD |
update_task_status |
Task state changes |
list_custom_fields / list_custom_field_options |
Custom field discovery |
move_task_to_list / add_to_backlog / reposition_task |
Task positioning |
list_subtasks / create_subtask |
Subtask management |
list_task_dependencies / create_task_dependency / delete_task_dependency |
Dependencies |
| Tool | Description |
|---|---|
add_task_comment / list_comments / get_comment / update_comment / delete_comment |
Comment CRUD |
pin_comment / unpin_comment / add_comment_reaction |
Comment actions |
list_todos / get_todo / create_todo / update_todo / delete_todo |
Todo management |
| Tool | Description |
|---|---|
list_pages / get_page / create_page / update_page / delete_page |
Page CRUD |
move_page / copy_page |
Page operations |
| Tool | Description |
|---|---|
list_time_entries / create_time_entry / update_time_entry |
Time entry CRUD |
set_time_entry_approval |
Approval workflow (action param) |
start_timer / stop_timer / get_timer |
Real-time timers |
list_services / get_project_services / list_project_deals / list_deal_services |
Budget & service lookup |
create_budget_service / update_budget_service |
Budget service (line item) CRUD |
| Tool | Description |
|---|---|
list_invoices / get_invoice / create_invoice / update_invoice / delete_invoice |
Invoice CRUD |
list_company_budgets / create_budget / update_budget / create_budget_from_deal |
Budget CRUD & derivation |
generate_line_items / finalize_invoice / mark_invoice_paid |
Invoice workflow |
get_invoice_pdf_url / get_timesheet_report_url |
Document URLs |
| Tool | Description |
|---|---|
list_activities |
List activities with filters |
get_recent_updates |
Get recent updates |
list_workflow_statuses |
List workflow statuses |
Follow the hierarchy: Project → Deal/Budget → Service → Time Entry.
list_projects → list_project_deals → list_deal_services → create_time_entry
Update by name — no ID lookup needed:
{ "task_id": "123", "status_name": "In Progress" }list_companies → list_company_budgets → create_invoice → generate_line_items → finalize_invoice → mark_invoice_paid
Direct creation:
list_companies → create_budget → update_budget (optional)
Derived from an existing deal/contract (the deal must be in a "Won" status):
list_project_deals → create_budget_from_deal
Attach services (line items) to a budget:
create_budget → create_budget_service → update_budget_service (optional)
{ "budget_id": "123", "name": "Consulting Hours", "price": 150, "quantity": 20 }unit_id (1=Hour, 2=Piece, 3=Day) defaults to 1; billing_type_id (1=Fixed, 2=Actuals, 3=None, 4=Percentage) defaults to 2.
npm run worker:dev # local dev on port 8788 (requires .dev.vars with secrets)
npm run worker:deploy # deploy to Cloudflare
npm run build # compile TypeScript (for local stdio fallback)
npm run format # prettierCreate a .dev.vars file (gitignored) with the same variables as the Cloudflare secrets for local development — including PAT_ENC_KEY (a 64-char hex string, e.g. openssl rand -hex 32). wrangler dev provisions local KV namespaces automatically.
Originally based on productive-mcp by jayat3dn (ISC). See NOTICE for attribution details.
Maintained by Monads AG