diff --git a/.env.example b/.env.example index d80b8c7..5833b23 100644 --- a/.env.example +++ b/.env.example @@ -54,7 +54,7 @@ CALDAV_PASSWORD=your-caldav-password CARDDAV_URL=https://carddav.example.com/dav CARDDAV_USERNAME=user@example.com CARDDAV_PASSWORD=your-carddav-password -CONTACTS_ADDRESSBOOK_ID=/dav/addressbooks/users/user@example.com/contacts/ +CONTACTS_ADDRESSBOOK_URL=https://carddav.example.com/dav/addressbooks/users/user@example.com/contacts/ # ============================================================================= # Cache Configuration diff --git a/README.md b/README.md index c446060..13baba5 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,10 @@ ICS_CALENDAR_TIMEOUT=20 CARDDAV_URL=https://carddav.example.com/dav CARDDAV_USERNAME=you@example.com CARDDAV_PASSWORD=your-password -CONTACTS_ADDRESSBOOK_ID=/dav/addressbooks/users/you@example.com/contacts/ +CONTACTS_ADDRESSBOOK_URL=https://carddav.example.com/dav/addressbooks/users/you@example.com/contacts/ ``` -Contacts tools always use `CONTACTS_ADDRESSBOOK_ID`. Listing address books is not exposed via MCP. +Contacts tools always use `CONTACTS_ADDRESSBOOK_URL` (full CardDAV address book URL). Listing address books is not exposed via MCP. ICS calendars are optional and read-only. Set `ICS_CALENDARS` to a comma-separated list of entries, each as `name|url` or just `url` if you want the name inferred. diff --git a/src/config.py b/src/config.py index e7eafbc..f282acb 100644 --- a/src/config.py +++ b/src/config.py @@ -40,9 +40,9 @@ class Settings(BaseSettings): carddav_url: Optional[str] = Field(default=None, alias="CARDDAV_URL") carddav_username: Optional[str] = Field(default=None, alias="CARDDAV_USERNAME") carddav_password: Optional[SecretStr] = Field(default=None, alias="CARDDAV_PASSWORD") - contacts_addressbook_id: Optional[str] = Field( + contacts_addressbook_url: Optional[str] = Field( default=None, - alias="CONTACTS_ADDRESSBOOK_ID", + alias="CONTACTS_ADDRESSBOOK_URL", ) # SQLite Cache @@ -154,7 +154,7 @@ class Settings(BaseSettings): self.carddav_url, self.carddav_username, self.carddav_password, - self.contacts_addressbook_id, + self.contacts_addressbook_url, ]) def is_notification_configured(self) -> bool: diff --git a/src/services/contacts_service.py b/src/services/contacts_service.py index 4d9de6b..4581354 100644 --- a/src/services/contacts_service.py +++ b/src/services/contacts_service.py @@ -351,7 +351,6 @@ class ContactsService: def delete_contact(self, contact_id: str, addressbook_id: Optional[str] = None) -> OperationResult: try: client = self._get_client() - addressbook_id = self._resolve_addressbook_id(addressbook_id) # Build URL contact_url = self._build_url(contact_id) @@ -375,7 +374,7 @@ class ContactsService: return OperationResult(success=False, message=str(e)) def _parse_vcard( - self, vcard_data: str, addressbook_id: str, href: str + self, vcard_data: str, addressbook_id: Optional[str], href: str ) -> Optional[Contact]: try: vcard = vobject.readOne(vcard_data) @@ -471,9 +470,10 @@ class ContactsService: except Exception: pass + resolved_addressbook_id = addressbook_id or self._derive_addressbook_id(href) return Contact( id=href, - addressbook_id=addressbook_id, + addressbook_id=resolved_addressbook_id, first_name=first_name, last_name=last_name, display_name=display_name, @@ -486,9 +486,15 @@ class ContactsService: birthday=birthday, ) + def _derive_addressbook_id(self, contact_href: str) -> str: + if "/" not in contact_href: + return contact_href + base = contact_href.rsplit("/", 1)[0] + return f"{base}/" + def _resolve_addressbook_id(self, addressbook_id: Optional[str]) -> str: if addressbook_id: return addressbook_id - if self.settings.contacts_addressbook_id: - return self.settings.contacts_addressbook_id - raise ValueError("CONTACTS_ADDRESSBOOK_ID must be set to use contacts tools") + if self.settings.contacts_addressbook_url: + return self.settings.contacts_addressbook_url + raise ValueError("CONTACTS_ADDRESSBOOK_URL must be set to use contacts tools") diff --git a/src/tools/contacts_tools.py b/src/tools/contacts_tools.py index a18e34c..997081b 100644 --- a/src/tools/contacts_tools.py +++ b/src/tools/contacts_tools.py @@ -70,7 +70,6 @@ def register_contacts_tools(mcp: FastMCP, service: ContactsService): birthday: Birthday in ISO format (YYYY-MM-DD) """ result = service.create_contact( - addressbook_id=None, first_name=first_name, last_name=last_name, display_name=display_name, @@ -115,7 +114,6 @@ def register_contacts_tools(mcp: FastMCP, service: ContactsService): """ result = service.update_contact( contact_id, - addressbook_id=None, first_name=first_name, last_name=last_name, display_name=display_name, diff --git a/src/tools/email_tools.py b/src/tools/email_tools.py index f77e2c7..fdebfe9 100644 --- a/src/tools/email_tools.py +++ b/src/tools/email_tools.py @@ -181,15 +181,16 @@ def register_email_tools(mcp: FastMCP, service: EmailService): reply_all: Whether to include original recipients when replying (default: False) """ result = service.save_draft( - to, - subject, - body, - cc, - bcc, - mailbox, - in_reply_to_email_id, - in_reply_to_mailbox, - reply_all, + to=to, + subject=subject, + body=body, + cc=cc, + bcc=bcc, + html_body=None, + mailbox=mailbox, + in_reply_to_email_id=in_reply_to_email_id, + in_reply_to_mailbox=in_reply_to_mailbox, + reply_all=reply_all, ) return result.model_dump() @@ -223,16 +224,17 @@ def register_email_tools(mcp: FastMCP, service: EmailService): reply_all: Whether to include original recipients when replying (default: False) """ result = service.update_draft( - email_id, - mailbox, - to, - subject, - body, - cc, - bcc, - in_reply_to_email_id, - in_reply_to_mailbox, - reply_all, + email_id=email_id, + mailbox=mailbox, + to=to, + subject=subject, + body=body, + cc=cc, + bcc=bcc, + html_body=None, + in_reply_to_email_id=in_reply_to_email_id, + in_reply_to_mailbox=in_reply_to_mailbox, + reply_all=reply_all, ) return result.model_dump()