A Model Context Protocol (MCP) server for integrating Productive.io into AI workflows. This server allows AI assistants and tools to access projects, folders, workflow statuses, time entries, tasks, comments, pages, attachments, todos, and people. Built with FastMCP.
This implementation is optimized for read-focused operations, with optional guarded write capabilities (for example, task creation) and LLM-friendly output options (Hybrid, JSON, and TOON). It is optimized for efficiency and simplicity, exposing only the necessary information. For a more comprehensive solution, consider BerwickGeek's implementation: Productive MCP by BerwickGeek.
- List Projects: Retrieve all projects with basic information
- List Folders: Retrieve folders within a project
- Get Folder: Retrieve a specific folder by ID
- List Workflow Statuses: Retrieve workflow statuses with optional filters
- List Time Entries: Retrieve time entries with optional date and relationship filters
- List Tasks: Retrieve tasks with filtering and pagination
- Get Task: Retrieve a specific task by internal ID
- Get Task History: Retrieve task status changes, assignments, milestones, and activity summaries
- List Comments: Retrieve comments with filtering
- List Pages: Retrieve pages/documents with filtering
- Get Page: Retrieve a specific page/document by ID
- List Attachments: Retrieve attachments/files with filtering
- List Todos: Retrieve todo checklist items with filtering
- Get Todo: Retrieve a specific todo by ID
- List People: Retrieve people/team members with pagination
- Get Person: Retrieve a specific person by ID
- List Recent Activity: Summarized activity feed for status updates
- Quick Search: Fast, comprehensive search across projects, tasks, pages, and actions
- Create Task: Create a new task in a project
- Update Task: Update task fields — title, description, assignee, due date, status, board, task list
- Delete Task: Permanently delete a task by ID (irreversible)
- Create Comment: Create a new comment on a task or project
- Update Comment: Update a comment body
- Delete Comment: Permanently delete a comment by ID (irreversible)
- Create Time Entry: Log time spent on tasks or services
- Update Time Entry: Modify existing time entries
- Delete Time Entry: Remove time entries (irreversible)
- Create Page: Create new documents/pages in projects
- Update Page: Edit page content and titles
- Delete Page: Remove pages/documents (irreversible)
- Create Todo: Add checklist items to tasks
- Update Todo: Modify todo items and completion status
- Delete Todo: Remove todo items (irreversible)
- LLM-Optimized Responses: Filtered output removes noise, strips HTML, and reduces token consumption
- Python 3.10+
- Productive API token
- FastMCP 3.x
- Clone or download this repository
- Install dependencies:
pip install -r requirements.txtor
uv venv && uv syncThe server uses environment variables for configuration:
PRODUCTIVE_API_KEY: Your Productive API token (required)PRODUCTIVE_ORGANIZATION: Your Productive organization ID (required)PRODUCTIVE_BASE_URL: Base URL for Productive API (default: https://api.productive.io/api/v2)PRODUCTIVE_TIMEOUT: Request timeout in seconds (default: 30)OUTPUT_FORMAT: Output format for tool responses ("hybrid","toon", or"json", default:"hybrid")READ_ONLY: Global write-protection toggle for write tools — create_task, update_task, delete_task, create_comment, update_comment, delete_comment, create_time_entry, update_time_entry, delete_time_entry, create_page, update_page, append_page_content, delete_page, create_todo, update_todo, delete_todo ("true" or "false", default: "true")
"productive": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/druellan/Productive-Simple-MCP",
"productive-mcp"
],
"env": {
"PRODUCTIVE_API_KEY": "<api-key>",
"PRODUCTIVE_ORGANIZATION": "<organization-id>"
}
} "productive": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/druellan/Productive-Simple-MCP",
"productive-mcp"
],
"env": {
"PRODUCTIVE_API_KEY": "<api-key>",
"PRODUCTIVE_ORGANIZATION": "<organization-id>",
"OUTPUT_FORMAT": "toon"
}
} "productive": {
"command": "python",
"args": [
"server.py"
],
"env": {
"PRODUCTIVE_API_KEY": "<api-key>",
"PRODUCTIVE_ORGANIZATION": "<organization-id>"
}
} "productive": {
"command": "uv",
"args": [
"--directory", "<path-to-productive-mcp>",
"run", "server.py"
],
"env": {
"PRODUCTIVE_API_KEY": "<api-key>",
"PRODUCTIVE_ORGANIZATION": "<organization-id>"
}
}Retrieve all projects with basic information.
Properties:
- No parameters (returns all projects)
Retrieve folders within a specific project.
Productive's API exposes these resources through the /folders endpoint.
Properties:
project_id(int, required): Filter folders by Productive project IDstatus(int, optional): Folder status filter (1active,2archived). Defaults to1limit(int, optional): Maximum number of folders to return (default: 50, max: 200)
Retrieve a specific folder by its ID.
Productive's API exposes this resource through the /folders endpoint.
Properties:
folder_id(int): The unique Productive folder identifier
Retrieve workflow statuses with optional filters.
Properties:
workflow_id(int, optional): Filter statuses by workflow IDcategory_id(int, optional): Filter by category (1Not Started,2Started,3Closed)limit(int, optional): Maximum number of statuses to return (default: 50, max: 200)
Retrieve time entries with optional date and relationship filters.
Properties:
date(str, optional): Exact date filter (YYYY-MM-DD)after(str, optional): Lower bound date filter (YYYY-MM-DD)before(str, optional): Upper bound date filter (YYYY-MM-DD)person_id(int, optional): Filter by person IDproject_id(int, optional): Filter by project IDtask_id(int, optional): Filter by task IDservice_id(int, optional): Filter by service IDpage_number(int, optional): Page number for paginationlimit(int, optional): Maximum number of entries to return (default: 50, max: 200)
Retrieve people/team members with optional pagination.
Properties:
page_number(int, optional): Page number for paginationpage_size(int, optional): Number of people to return per page (max: 200)
Retrieve a specific person by ID.
Properties:
person_id(int): The unique Productive person identifier
Retrieve tasks with optional filtering and pagination.
Properties:
project_id(int, optional): Filter tasks by Productive project IDuser_id(int, optional): Filter tasks by assignee/user IDpage_number(int, optional): Page number for paginationpage_size(int, optional): Page size for pagination (default: 50)sort(str, optional): Sort parameter (e.g., 'last_activity_at', '-last_activity_at', 'created_at', 'due_date')extra_filters(dict, optional): Additional Productive API filters (e.g.,{'filter[status][eq]': 1}for open tasks,{'filter[status][eq]': 2}for closed tasks)
Retrieve a specific task by its internal ID. Returns task details including title, description, status, dates, time tracking metrics (initial_estimate, worked_time, billable_time, remaining_time), and todo counts.
Properties:
task_id(int): The unique Productive task identifier (internal ID, e.g., 14677418)
Retrieve the full history for a specific task, including status changes, assignment history, milestones, and activity summary.
Properties:
task_id(int): The unique Productive task identifier (internal ID, e.g., 14677418)hours(int, optional): Number of hours to look back for activity history (default: 720 = 30 days, max: 8760)
Returns:
status_history: Timeline of status changes with timestamps (from/to status and changed_at)assignment_history: Assignment changes showing who was assigned and when (assigned_to and changed_at)milestones: Key deliverables and completion markers from comments and activitiesactivity_summary: Counts of comments, changes, status updates, assignments, and milestones
Example:
get_task_history(14677921) # Default 30-day history
get_task_history(14677921, hours=168) # Last week only
get_task_history(14677921, hours=24) # Last 24 hoursRetrieve comments with optional filtering and pagination.
Properties:
project_id(int, optional): Filter comments by Productive project IDtask_id(int, optional): Filter comments by Productive task IDpage_number(int, optional): Page number for paginationpage_size(int, optional): Page size for paginationextra_filters(dict, optional): Additional Productive API filters (e.g.,{'filter[discussion_id]': '123'})
Retrieve pages/documents with optional filtering and pagination.
Properties:
project_id(int, optional): Filter pages by Productive project IDcreator_id(int, optional): Filter pages by creator IDpage_number(int, optional): Page number for paginationpage_size(int, optional): Page size for pagination
Retrieve a specific page/document by ID.
Properties:
page_id(int): The unique Productive page identifier
Retrieve attachments/files with optional filtering and pagination.
Properties:
page_number(int, optional): Page number for paginationpage_size(int, optional): Page size for paginationextra_filters(dict, optional): Additional Productive API filters
Get a summarized feed of recent activities and updates. Perfect for status updates.
Properties:
hours(int, optional): Number of hours to look back (default: 24, use 168 for a week)user_id(int, optional): Filter by specific user/person IDproject_id(int, optional): Filter by specific project IDactivity_type(int, optional): Filter by activity type (1: Comment, 2: Changeset, 3: Email)item_type(str, optional): Filter by item type (e.g., 'Task', 'Page', 'Deal', 'Workspace')event_type(str, optional): Filter by event type (e.g., 'create', 'copy', 'update', 'delete')task_id(int, optional): Filter by specific task IDmax_results(int, optional): Maximum number of activities to return (default: 100, max: 200)
Retrieve todo checklist items with optional filtering and pagination.
Properties:
task_id(int, optional): Filter todos by Productive task IDpage_number(int, optional): Page number for paginationpage_size(int, optional): Page size for paginationextra_filters(dict, optional): Additional Productive API filters
Quick search across projects, tasks, pages, and actions.
Properties:
query(str): Search query stringsearch_types(list[str], optional): List of types to search (action, project, task, page). Defaults to all.deep_search(bool, optional): Whether to perform deep search (default: True)page(int, optional): Page number for pagination (default: 1)per_page(int, optional): Results per page (default: 50)
Description: Provides fast, comprehensive search across all Productive content types including projects, tasks, pages, and actions. It's optimized for quick lookups and general search queries.
Response Format: Returns filtered results optimized for LLM consumption with only essential fields:
record_id: Unique identifier for the resourcerecord_type: Type of resource (project, task, page, etc.)title: Display title (with search highlights removed)subtitle: Additional context or descriptionicon_url: URL to the resource's icon/avatar (if available)status: Current status (active, closed, etc.)project_name: Name of the associated projectupdated_at: Last update timestampwebapp_url: Direct link to view the resource in Productive web interface
Examples:
quick_search("deployment") # Search for "deployment" across all content types
quick_search("meeting notes", search_types=["project"]) # Search only in projects
quick_search("this week summary", deep_search=False) # Quick search without deep scanRetrieve a specific todo checklist item by ID.
Properties:
todo_id(int): The unique Productive todo checklist item identifier
Create a new task in Productive.
When READ_ONLY=true, this tool is blocked globally and returns a write-protection error.
Properties:
title(str, required): Task titleproject_id(int, required): Productive project ID where the task will be createddescription(str, optional): Task descriptionboard_id(int, optional): Board IDtask_list_id(int, optional): Task list IDassignee_id(int, optional): Assignee/person IDdue_date(str, optional): Due date (YYYY-MM-DD)status(str, optional):openorclosed(default:open)
Update an existing task in Productive. Only provided fields are modified (partial PATCH).
At least one field must be given. When READ_ONLY=true, this tool is blocked globally.
Properties:
task_id(int, required): Productive task ID to updatetitle(str, optional): New task titledescription(str, optional): New task descriptionassignee_id(int, optional): New assignee person ID. Use 0 or negative to unassign.due_date(str, optional): New due date (YYYY-MM-DD)status(str, optional): New status —openorclosedboard_id(int, optional): Move task to this boardtask_list_id(int, optional): Move task to this task list
Permanently delete a task from Productive by its ID. This action is irreversible —
the task and all associated data will be removed. When READ_ONLY=true, this tool is
blocked globally.
Properties:
task_id(int, required): Productive task ID to delete
Create a new comment on a task or project in Productive. A comment must be attached to
at least one of task or project. When READ_ONLY=true, this tool is blocked globally.
Properties:
body(str, required): Comment body text (HTML supported)task_id(int, optional): Productive task ID to attach the comment toproject_id(int, optional): Productive project ID to attach the comment to
Update an existing comment in Productive. Only the body attribute can be modified.
When READ_ONLY=true, this tool is blocked globally.
Properties:
comment_id(int, required): Productive comment ID to updatebody(str, required): New comment body text (HTML supported)
Permanently delete a comment from Productive by its ID. This action is irreversible —
the comment will be removed from the task or project. When READ_ONLY=true, this tool
is blocked globally.
Properties:
comment_id(int, required): Productive comment ID to delete
Create a new time entry in Productive for time tracking.
Logs time spent on tasks or services. Either task_id or service_id must be provided.
When READ_ONLY=true, this tool is blocked globally.
Properties:
date(str, required): Date for the time entry (YYYY-MM-DD)time(float, required): Time spent in hours (e.g., 2.5)person_id(int, required): Person ID who logged the timetask_id(int, optional): Task ID to associate the time entry withservice_id(int, optional): Service ID to associate the time entry withnote(str, optional): Optional note or description
Update an existing time entry in Productive. Only provided fields are modified (partial PATCH).
At least one field must be given. When READ_ONLY=true, this tool is blocked globally.
Properties:
time_entry_id(int, required): Productive time entry ID to updatedate(str, optional): New date (YYYY-MM-DD)time(float, optional): New time in hoursperson_id(int, optional): New person IDtask_id(int, optional): New task IDservice_id(int, optional): New service IDnote(str, optional): New note
Permanently delete a time entry from Productive by its ID. This action is irreversible —
the time entry will be removed from time tracking records. When READ_ONLY=true, this tool
is blocked globally.
Properties:
time_entry_id(int, required): Productive time entry ID to delete
Create a new page/document in a Productive project.
Pages are documents that can contain rich text content and are organized within projects.
When READ_ONLY=true, this tool is blocked globally.
Properties:
title(str, required): Page titleproject_id(int, required): Productive project ID where the page will be createdcontent(str, optional): Page content as HTML markup (e.g.<h1>Title</h1><p>Body text</p>)
Update an existing page/document in Productive. Only provided fields are modified (partial PATCH).
At least one field must be given. When READ_ONLY=true, this tool is blocked globally.
Properties:
page_id(int, required): Productive page ID to updatetitle(str, optional): New page titlecontent(str, optional): New page content as HTML markup (e.g.<h1>Title</h1><p>Body text</p>)
Permanently delete a page/document from Productive by its ID. This action is irreversible —
the page and all its content will be removed. When READ_ONLY=true, this tool is blocked globally.
Properties:
page_id(int, required): Productive page ID to delete
Append content to the end of an existing page/document in Productive without fetching its current body. Exactly one of markdown or html must be provided.
When READ_ONLY=true, this tool is blocked globally.
Properties:
page_id(int, required): Productive page ID to append content tomarkdown(str, optional): Markdown content to append (e.g.# New section\n\n- item one)html(str, optional): HTML content to append (e.g.<h2>New section</h2><p>Body text</p>)
Create a new todo checklist item for a task in Productive.
Todos are checkbox items within tasks for granular tracking of work items.
When READ_ONLY=true, this tool is blocked globally.
Properties:
content(str, required): Todo item content/descriptiontask_id(int, required): Productive task ID to add the todo to
Update an existing todo checklist item in Productive. Only provided fields are modified (partial PATCH).
At least one field must be given. When READ_ONLY=true, this tool is blocked globally.
Properties:
todo_id(int, required): Productive todo ID to updatecontent(str, optional): New todo contentcompleted(bool, optional): Mark todo as completed (true) or incomplete (false)
Permanently delete a todo checklist item from Productive by its ID. This action is irreversible —
the todo item will be removed from the task. When READ_ONLY=true, this tool is blocked globally.
Properties:
todo_id(int, required): Productive todo ID to delete
The server supports three output formats configured via the OUTPUT_FORMAT environment variable:
| Mode | content (text) |
structured_content |
Description |
|---|---|---|---|
hybrid (default) |
TOON | JSON dict | Both TOON for token efficiency and structured data for programmatic access |
toon |
TOON | { "toon": toonText } |
Pure TOON (Token-Optimized Object Notation) with no dual-format overhead |
json |
JSON string | JSON dict | Standard FastMCP JSON output |
TOON (Token-Optimized Object Notation) reduces token consumption by 30-60% compared to JSON. The hybrid mode is the recommended default: it exposes the TOON rendering as the message text while preserving the full JSON dict as structured output, so agents that rely on structured data can still parse it programmatically.
All tools return filtered data optimized for LLM processing:
LLM Optimizations:
- Unwanted fields removed (e.g.,
creation_method_id,email_key,placementfrom tasks) - HTML stripped from descriptions and comments
- Empty/null values removed
- Pagination links removed
- List views use lightweight output (e.g.,
get_project_tasksexcludes descriptions and relationships) - Web app URLs included: Each resource includes a
webapp_urlfield linking directly to the Productive web interface
Response Structure:
data: Main resource data (array for collections, object for single items)meta: Pagination and metadataincluded: Related resource data (when applicable)webapp_url: Direct link to view the resource in Productive (e.g.,https://app.productive.io/12345/tasks/67890)
The server provides comprehensive error handling:
- 401 Unauthorized: Invalid API token
- 404 Not Found: Resource not found
- 429 Rate Limited: Too many requests
- 500 Server Error: Productive API issues
All errors are logged via MCP context with appropriate severity levels.
- API tokens are loaded from environment variables
- No sensitive data is logged
- HTTPS is used for all API requests
- Error messages don't expose internal details
MIT License.