-
-
Notifications
You must be signed in to change notification settings - Fork 926
Add Google Keep Integration #499
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
Open
hGriff0n
wants to merge
10
commits into
taylorwilsdon:main
Choose a base branch
from
hGriff0n:keep
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
8b8034f
Initial Scoping of Files
hGriff0n 8813323
Add Scope and Auth Definitions
hGriff0n a93fac0
Implement Google Keep API as Tools
hGriff0n 306f205
Fix Code Review Comments
hGriff0n 35c796b
Add Validation of member_type to set_permissions
hGriff0n c8e14eb
Reorder Assertions in test_set_permissions
hGriff0n ea905a0
Improve Granularity of Keep Tool Tiers
hGriff0n 7f279bf
download_attachment Now Actually Downloads the Attachment
hGriff0n 3985e57
Respond to Rabbit Codereview
hGriff0n b6c18ff
Fix Invalid Parameter Name
hGriff0n 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
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
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
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 |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Google Keep MCP Integration""" |
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 |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| """ | ||
| Google Keep Helper Functions | ||
|
|
||
| Shared utilities for Google Keep operations. | ||
| """ | ||
|
|
||
| from dataclasses import dataclass, field | ||
| from typing import Any, Dict, List, Optional | ||
|
|
||
|
|
||
| @dataclass | ||
| class ListItem: | ||
| """A single checklist item in a Keep note.""" | ||
|
|
||
| text: str | ||
| checked: bool = False | ||
| children: List["ListItem"] = field(default_factory=list) | ||
|
|
||
| @classmethod | ||
| def from_api(cls, data: Dict[str, Any]) -> "ListItem": | ||
| children = [ | ||
| cls.from_api(child) for child in data.get("childListItems", []) | ||
| ] | ||
| return cls( | ||
| text=data.get("text", {}).get("text", ""), | ||
| checked=data.get("checked", False), | ||
| children=children, | ||
| ) | ||
|
|
||
|
|
||
| @dataclass | ||
| class Attachment: | ||
| """An attachment on a Keep note.""" | ||
|
|
||
| name: str | ||
| mime_types: List[str] = field(default_factory=list) | ||
|
|
||
| @classmethod | ||
| def from_api(cls, data: Dict[str, Any]) -> "Attachment": | ||
| return cls( | ||
| name=data.get("name", ""), | ||
| mime_types=data.get("mimeType", []), | ||
| ) | ||
|
|
||
|
|
||
| @dataclass | ||
| class Permission: | ||
| """A permission entry on a Keep note.""" | ||
|
|
||
| name: str | ||
| role: str | ||
| email: str | ||
|
|
||
| @classmethod | ||
| def from_api(cls, data: Dict[str, Any]) -> "Permission": | ||
| return cls( | ||
| name=data.get("name", ""), | ||
| role=data.get("role", ""), | ||
| email=data.get("email", ""), | ||
| ) | ||
|
|
||
|
|
||
| @dataclass | ||
| class Note: | ||
| """A Google Keep note.""" | ||
|
|
||
| name: str | ||
| title: str | ||
| text: Optional[str] = None | ||
| list_items: Optional[List[ListItem]] = None | ||
| trashed: bool = False | ||
| trash_time: Optional[str] = None | ||
| create_time: Optional[str] = None | ||
| update_time: Optional[str] = None | ||
| attachments: List[Attachment] = field(default_factory=list) | ||
| permissions: List[Permission] = field(default_factory=list) | ||
|
|
||
| @classmethod | ||
| def from_api(cls, data: Dict[str, Any]) -> "Note": | ||
| body = data.get("body", {}) | ||
| text_content = body.get("text", {}) | ||
| list_content = body.get("list", {}) | ||
|
|
||
| text = text_content.get("text") if text_content else None | ||
| list_items = ( | ||
| [ListItem.from_api(item) for item in list_content["listItems"]] | ||
| if list_content and list_content.get("listItems") | ||
| else None | ||
| ) | ||
|
|
||
| return cls( | ||
| name=data.get("name", ""), | ||
| title=data.get("title", ""), | ||
| text=text, | ||
| list_items=list_items, | ||
| trashed=data.get("trashed", False), | ||
| trash_time=data.get("trashTime"), | ||
| create_time=data.get("createTime"), | ||
| update_time=data.get("updateTime"), | ||
| attachments=[ | ||
| Attachment.from_api(att) for att in data.get("attachments", []) | ||
| ], | ||
| permissions=[ | ||
| Permission.from_api(perm) for perm in data.get("permissions", []) | ||
| ], | ||
| ) | ||
|
|
||
|
|
||
| def format_note(note: Note) -> str: | ||
| """Format a Note into a human-readable summary string.""" | ||
| lines = [] | ||
| lines.append(f"- Name: {note.name or 'N/A'}") | ||
| lines.append(f" Title: {note.title or 'Untitled'}") | ||
|
|
||
| if note.text: | ||
| preview = note.text[:200] + ("..." if len(note.text) > 200 else "") | ||
| lines.append(f" Body (text): {preview}") | ||
| elif note.list_items is not None: | ||
| lines.append(f" Body (list): {len(note.list_items)} item(s)") | ||
| for item in note.list_items[:10]: | ||
| marker = "[x]" if item.checked else "[ ]" | ||
| lines.append(f" {marker} {item.text}") | ||
| for child in item.children: | ||
| child_marker = "[x]" if child.checked else "[ ]" | ||
| lines.append(f" {child_marker} {child.text}") | ||
| if len(note.list_items) > 10: | ||
| lines.append(f" ... and {len(note.list_items) - 10} more item(s)") | ||
|
|
||
| if note.trashed: | ||
| lines.append(f" Trashed: {note.trash_time or 'Yes'}") | ||
| lines.append(f" Created: {note.create_time or 'N/A'}") | ||
| lines.append(f" Updated: {note.update_time or 'N/A'}") | ||
|
|
||
| if note.attachments: | ||
| lines.append(f" Attachments: {len(note.attachments)}") | ||
| for att in note.attachments: | ||
| lines.append(f" - {att.name or 'N/A'} ({', '.join(att.mime_types)})") | ||
|
|
||
| if note.permissions: | ||
| lines.append(f" Permissions: {len(note.permissions)}") | ||
| for perm in note.permissions: | ||
| lines.append(f" - {perm.email or 'N/A'} ({perm.role or 'N/A'})") | ||
|
|
||
| return "\n".join(lines) | ||
|
|
||
|
|
||
| def _format_list_item(item: ListItem, indent: str = "") -> List[str]: | ||
| marker = "x" if item.checked else " " | ||
| lines = [f"{indent}- [{marker}] {item.text}"] | ||
| for child in item.children: | ||
| lines.extend(_format_list_item(child, indent=f' {indent}')) | ||
| return lines | ||
|
|
||
|
|
||
| def format_note_content(note: Note) -> str: | ||
| """Format only the content of a note (full text or checklist) without truncation.""" | ||
| lines = [] | ||
| if note.title: | ||
| lines.append(f"Title: {note.title}") | ||
|
|
||
| if note.text: | ||
| lines.append(note.text) | ||
| elif note.list_items is not None: | ||
| for item in note.list_items: | ||
| lines.extend(_format_list_item(item)) | ||
| else: | ||
| lines.append("(empty note)") | ||
|
|
||
| return "\n".join(lines) | ||
|
|
||
|
|
||
| def build_note_body( | ||
| title: str, | ||
| text: Optional[str] = None, | ||
| list_items: Optional[List[Dict[str, Any]]] = None, | ||
| ) -> Dict[str, Any]: | ||
| """Build a note request body for creation.""" | ||
| if text is not None and list_items is not None: | ||
| raise ValueError("Provide either 'text' or 'list_items', not both.") | ||
|
|
||
| body: Dict[str, Any] = {"title": title} | ||
| if list_items is not None: | ||
| body["body"] = {"list": {"listItems": list_items}} | ||
| elif text is not None: | ||
| body["body"] = {"text": {"text": text}} | ||
| return body | ||
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.
Uh oh!
There was an error while loading. Please reload this page.