-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat(mcp): create, complete, update, and search action items via MCP #8439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Git-on-my-level
merged 8 commits into
BasedHardware:main
from
ZachL111:zach/mcp-action-item-writes
Jun 28, 2026
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
fe52a04
Add shared MCP action-item write and search orchestration
ZachL111 b742bc9
Expose action-item create, complete, update, delete, and search as MC…
ZachL111 797338e
Add REST MCP endpoints for action-item writes and search
ZachL111 4a9b16f
Add action_items:write rate-limit policy
ZachL111 81e8189
Test MCP action-item writes across the shared util and both transports
ZachL111 cd53249
Run the MCP action-item write tests in CI
ZachL111 3f07d87
Address review: honor the delete result, guard the post-write reload,…
ZachL111 ad96742
Test delete no-op, blank due date, post-write reload race, and REST u…
ZachL111 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |
| from models.conversation_enums import CategoryEnum | ||
| from utils.llm.memories import identify_category_for_memory | ||
| from utils.mcp_data import clean_action_item, clean_chat_message, clean_person, clean_screen_activity_row | ||
| import utils.mcp_action_items as mcp_action_items | ||
| from utils.mcp_memories import ( | ||
| collect_filtered_memories, | ||
| parse_mcp_bool, | ||
|
|
@@ -60,6 +61,7 @@ | |
| "memories.write", | ||
| "conversations.read", | ||
| "action_items.read", | ||
| "action_items.write", | ||
| "goals.read", | ||
| "chat.read", | ||
| "screen_activity.read", | ||
|
|
@@ -88,6 +90,7 @@ | |
| MEMORIES_WRITE_SECURITY = [{"type": "oauth2", "scopes": ["memories.write"]}] | ||
| CONVERSATIONS_READ_SECURITY = [{"type": "oauth2", "scopes": ["conversations.read"]}] | ||
| ACTION_ITEMS_READ_SECURITY = [{"type": "oauth2", "scopes": ["action_items.read"]}] | ||
| ACTION_ITEMS_WRITE_SECURITY = [{"type": "oauth2", "scopes": ["action_items.write"]}] | ||
| GOALS_READ_SECURITY = [{"type": "oauth2", "scopes": ["goals.read"]}] | ||
| CHAT_READ_SECURITY = [{"type": "oauth2", "scopes": ["chat.read"]}] | ||
| SCREEN_ACTIVITY_READ_SECURITY = [{"type": "oauth2", "scopes": ["screen_activity.read"]}] | ||
|
|
@@ -360,6 +363,100 @@ def invalid_mcp_auth_exception( | |
| }, | ||
| }, | ||
| }, | ||
| { | ||
| "name": "search_action_items", | ||
| "description": ( | ||
| "Semantic search across the user's action items (tasks/to-dos). Returns tasks ranked by relevance to " | ||
| "the query — use this to find a specific task by what it is about before completing or updating it." | ||
| ), | ||
| "annotations": READ_ONLY_ANNOTATIONS, | ||
| "securitySchemes": ACTION_ITEMS_READ_SECURITY, | ||
| "inputSchema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "query": {"type": "string", "description": "What to search the user's tasks for"}, | ||
| "limit": {"type": "integer", "description": "Max number of tasks to return (1-50)", "default": 10}, | ||
| }, | ||
| "required": ["query"], | ||
| }, | ||
| }, | ||
| { | ||
| "name": "create_action_item", | ||
| "description": ( | ||
| "Create a new action item (task/to-do) for the user — for example a follow-up you identified while " | ||
| "helping them. Retries with the same description return the existing task instead of duplicating it." | ||
| ), | ||
| "annotations": WRITE_ANNOTATIONS, | ||
| "securitySchemes": ACTION_ITEMS_WRITE_SECURITY, | ||
| "inputSchema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "description": {"type": "string", "description": "What the user needs to do"}, | ||
| "due_at": { | ||
| "type": "string", | ||
| "description": "Optional due date/time, ISO 8601 (2026-07-01T17:00:00Z) or YYYY-MM-DD", | ||
| }, | ||
| "completed": { | ||
| "type": "boolean", | ||
| "description": "Create it already completed (default false)", | ||
| "default": False, | ||
| }, | ||
| }, | ||
| "required": ["description"], | ||
| }, | ||
| }, | ||
| { | ||
| "name": "complete_action_item", | ||
| "description": "Mark an action item complete, or reopen it by passing completed=false.", | ||
| "annotations": WRITE_ANNOTATIONS, | ||
| "securitySchemes": ACTION_ITEMS_WRITE_SECURITY, | ||
| "inputSchema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "action_item_id": {"type": "string", "description": "The ID of the action item"}, | ||
| "completed": { | ||
| "type": "boolean", | ||
| "description": "True to complete (default), false to reopen", | ||
| "default": True, | ||
| }, | ||
| }, | ||
| "required": ["action_item_id"], | ||
| }, | ||
| }, | ||
| { | ||
| "name": "update_action_item", | ||
| "description": ( | ||
| "Update an action item's description and/or due date. Only the fields you pass are changed; an omitted " | ||
| "due date is left unchanged." | ||
| ), | ||
| "annotations": WRITE_ANNOTATIONS, | ||
| "securitySchemes": ACTION_ITEMS_WRITE_SECURITY, | ||
| "inputSchema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "action_item_id": {"type": "string", "description": "The ID of the action item"}, | ||
| "description": {"type": "string", "description": "New description for the task"}, | ||
| "due_at": { | ||
| "type": "string", | ||
| "description": "New due date/time, ISO 8601 (2026-07-01T17:00:00Z) or YYYY-MM-DD", | ||
| }, | ||
| }, | ||
| "required": ["action_item_id"], | ||
| }, | ||
| }, | ||
| { | ||
| "name": "delete_action_item", | ||
| "description": "Delete an action item by ID. Use this to clean up a task that is no longer relevant.", | ||
| "annotations": DESTRUCTIVE_WRITE_ANNOTATIONS, | ||
| "securitySchemes": ACTION_ITEMS_WRITE_SECURITY, | ||
| "inputSchema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "action_item_id": {"type": "string", "description": "The ID of the action item to delete"}, | ||
| }, | ||
| "required": ["action_item_id"], | ||
| }, | ||
| }, | ||
| { | ||
| "name": "get_goals", | ||
| "description": ( | ||
|
|
@@ -820,6 +917,74 @@ def execute_tool(user_id: str, tool_name: str, arguments: dict) -> dict: | |
| ) | ||
| return {"action_items": [clean_action_item(i) for i in items if not i.get("deleted", False)]} | ||
|
|
||
| elif tool_name == "search_action_items": | ||
| try: | ||
| items = mcp_action_items.search_action_items( | ||
| user_id, arguments.get("query"), limit=arguments.get("limit", 10) | ||
| ) | ||
| except ValueError as e: | ||
| raise ToolExecutionError(str(e), code=-32602) | ||
| return {"action_items": items} | ||
|
|
||
| elif tool_name == "create_action_item": | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Prompt for AI agents |
||
| try: | ||
| completed = parse_mcp_bool(arguments.get("completed"), "completed", default=False) | ||
| item = mcp_action_items.create_action_item( | ||
| user_id, | ||
| arguments.get("description"), | ||
| due_at=arguments.get("due_at"), | ||
| completed=completed, | ||
| ) | ||
| except ValueError as e: | ||
| raise ToolExecutionError(str(e), code=-32602) | ||
| return {"success": True, "action_item": item} | ||
|
|
||
| elif tool_name == "complete_action_item": | ||
| action_item_id = arguments.get("action_item_id") | ||
| if not action_item_id: | ||
| raise ToolExecutionError("action_item_id is required", code=-32602) | ||
| try: | ||
| completed = parse_mcp_bool(arguments.get("completed"), "completed", default=True) | ||
| item = mcp_action_items.set_completed(user_id, action_item_id, completed=completed) | ||
| except ValueError as e: | ||
| raise ToolExecutionError(str(e), code=-32602) | ||
| except mcp_action_items.ActionItemNotFound: | ||
| raise ToolExecutionError("Action item not found", code=-32001) | ||
| except mcp_action_items.ActionItemLocked: | ||
| raise ToolExecutionError("A paid plan is required to modify this action item.", code=-32002) | ||
| return {"success": True, "action_item": item} | ||
|
|
||
| elif tool_name == "update_action_item": | ||
| action_item_id = arguments.get("action_item_id") | ||
| if not action_item_id: | ||
| raise ToolExecutionError("action_item_id is required", code=-32602) | ||
| try: | ||
| item = mcp_action_items.update_action_item( | ||
| user_id, | ||
| action_item_id, | ||
| description=arguments.get("description"), | ||
| due_at=arguments.get("due_at"), | ||
| ) | ||
| except ValueError as e: | ||
| raise ToolExecutionError(str(e), code=-32602) | ||
| except mcp_action_items.ActionItemNotFound: | ||
| raise ToolExecutionError("Action item not found", code=-32001) | ||
| except mcp_action_items.ActionItemLocked: | ||
| raise ToolExecutionError("A paid plan is required to modify this action item.", code=-32002) | ||
| return {"success": True, "action_item": item} | ||
|
|
||
| elif tool_name == "delete_action_item": | ||
| action_item_id = arguments.get("action_item_id") | ||
| if not action_item_id: | ||
| raise ToolExecutionError("action_item_id is required", code=-32602) | ||
| try: | ||
| mcp_action_items.delete_action_item(user_id, action_item_id) | ||
| except mcp_action_items.ActionItemNotFound: | ||
| raise ToolExecutionError("Action item not found", code=-32001) | ||
| except mcp_action_items.ActionItemLocked: | ||
| raise ToolExecutionError("A paid plan is required to modify this action item.", code=-32002) | ||
| return {"success": True} | ||
|
|
||
| elif tool_name == "get_goals": | ||
| include_inactive = parse_mcp_bool(arguments.get("include_inactive"), "include_inactive", default=False) | ||
| return {"goals": goals_db.get_all_goals(user_id, include_inactive=include_inactive)} | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: New write tools rely on metadata-only
securitySchemesdeclarations (ACTION_ITEMS_WRITE_SECURITY) without enforcingaction_items.writescope during SSE tool execution.authenticate_api_keyreturns only auser_id; thehandle_mcp_message→execute_toolpath never validates scopes before allowing creates, updates, completions, or deletes.Prompt for AI agents