Use configured contacts address book
All checks were successful
Build And Test / publish (push) Successful in 47s

This commit is contained in:
2026-01-01 15:55:00 -08:00
parent 5a9ef0e48f
commit bd8e1412e4
6 changed files with 39 additions and 33 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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:

View File

@@ -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")

View File

@@ -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,

View File

@@ -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()