Merge remote-tracking branch 'origin/master' into fix/qdrant-doc-id-keyword-index
This commit is contained in:
@@ -50,10 +50,10 @@ jobs:
|
||||
- nextcloud_version: "31"
|
||||
nextcloud_image: "docker.io/library/nextcloud:31.0.14@sha256:07ec73cc816e58d6f45a162cd53ef886462c29271a23fc68d0124cec276e3767"
|
||||
- nextcloud_version: "32"
|
||||
nextcloud_image: "docker.io/library/nextcloud:32.0.9@sha256:fdca48a6025e63d7b52913cad8ddf341c37b9dd8d03c4962ea0a1ec0c42c7442"
|
||||
nextcloud_image: "docker.io/library/nextcloud:32.0.9@sha256:6052173f5abdd4e37868262234b9fa651f70223d3bcd7c178c51e59101fdb733"
|
||||
# Disabled until all upstream apps support NC 33
|
||||
# - nextcloud_version: "33"
|
||||
# nextcloud_image: "docker.io/library/nextcloud:33.0.3@sha256:76f04e434a82572dbfee7ad6dca313229eade89ee538fccd2bf6396f4440d48b"
|
||||
# nextcloud_image: "docker.io/library/nextcloud:33.0.3@sha256:90a730e9a3dd290d9626ca64740c4d2fa901b4f231fe4ab6eb0dcf300dbc7271"
|
||||
|
||||
# Mode-specific properties
|
||||
- mode: single-user
|
||||
|
||||
@@ -5,6 +5,12 @@ All notable changes to the Nextcloud MCP Server will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [PEP 440](https://peps.python.org/pep-0440/).
|
||||
|
||||
## v0.84.0 (2026-05-10)
|
||||
|
||||
### Feat
|
||||
|
||||
- **contacts**: add nc_contacts_search_contacts free-text search tool
|
||||
|
||||
## v0.83.4 (2026-05-10)
|
||||
|
||||
### Fix
|
||||
|
||||
@@ -142,6 +142,84 @@ def configure_contacts_tools(mcp: FastMCP):
|
||||
contacts=contacts, addressbook=addressbook, total_count=len(contacts)
|
||||
)
|
||||
|
||||
@mcp.tool(
|
||||
title="Search Contacts",
|
||||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||||
)
|
||||
@require_scopes("contacts.read")
|
||||
@instrument_tool
|
||||
async def nc_contacts_search_contacts(
|
||||
ctx: Context, *, query: str, addressbook: str | None = None
|
||||
) -> ListContactsResponse:
|
||||
"""Search contacts by free-text query across name, nickname, email, and phone.
|
||||
|
||||
The query is matched case-insensitively as a substring against:
|
||||
- the contact's full name (FN)
|
||||
- any nickname
|
||||
- every email address
|
||||
- every phone number (digits only — formatting is stripped before
|
||||
comparison so '+1 234 567 890' matches '2345678' and '234.567.890')
|
||||
|
||||
Args:
|
||||
query: Free-text search string (case-insensitive substring match).
|
||||
An empty query returns no results — use list_contacts for that.
|
||||
addressbook: Optional URI slug of a specific addressbook to search.
|
||||
When omitted, every addressbook for the user is searched.
|
||||
|
||||
Returns:
|
||||
ListContactsResponse with matching contacts. The ``addressbook``
|
||||
field is set to the searched addressbook, or ``"*"`` when all
|
||||
addressbooks were searched.
|
||||
"""
|
||||
client = await get_client(ctx)
|
||||
needle = (query or "").strip().lower()
|
||||
if not needle:
|
||||
return ListContactsResponse(
|
||||
contacts=[], addressbook=addressbook or "*", total_count=0
|
||||
)
|
||||
|
||||
# Phone numbers are normalised to digits-only for comparison so that
|
||||
# users can search for "2345678" and find "+1 234-567-8" etc.
|
||||
digits_needle = "".join(ch for ch in needle if ch.isdigit())
|
||||
|
||||
if addressbook:
|
||||
address_books = [addressbook]
|
||||
else:
|
||||
address_books = [
|
||||
ab["name"] for ab in await client.contacts.list_addressbooks()
|
||||
]
|
||||
|
||||
matches: list[Contact] = []
|
||||
for ab_slug in address_books:
|
||||
raw_contacts = await client.contacts.list_contacts(addressbook=ab_slug)
|
||||
for raw in raw_contacts:
|
||||
contact = _raw_contact_to_model(raw)
|
||||
hay_parts: list[str] = []
|
||||
if contact.fn:
|
||||
hay_parts.append(contact.fn.lower())
|
||||
nickname = contact.custom_fields.get("nickname") if contact.custom_fields else None
|
||||
if nickname:
|
||||
hay_parts.append(str(nickname).lower())
|
||||
for e in contact.emails:
|
||||
hay_parts.append(e.value.lower())
|
||||
hay = " ".join(hay_parts)
|
||||
|
||||
phone_digits = "".join(
|
||||
"".join(ch for ch in p.value if ch.isdigit())
|
||||
for p in contact.phones
|
||||
)
|
||||
|
||||
if needle in hay:
|
||||
matches.append(contact)
|
||||
elif digits_needle and digits_needle in phone_digits:
|
||||
matches.append(contact)
|
||||
|
||||
return ListContactsResponse(
|
||||
contacts=matches,
|
||||
addressbook=addressbook or "*",
|
||||
total_count=len(matches),
|
||||
)
|
||||
|
||||
@mcp.tool(
|
||||
title="Create Address Book",
|
||||
annotations=ToolAnnotations(idempotentHint=False, openWorldHint=True),
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "nextcloud-mcp-server"
|
||||
version = "0.83.4"
|
||||
version = "0.84.0"
|
||||
description = "Model Context Protocol (MCP) server for Nextcloud integration - enables AI assistants to interact with Nextcloud data"
|
||||
authors = [
|
||||
{name = "Chris Coutinho", email = "chris@coutinho.io"}
|
||||
|
||||
Reference in New Issue
Block a user