Compare commits

...

71 Commits

Author SHA1 Message Date
github-actions[bot] 1176479ec1 bump: version 0.64.4 → 0.64.5 2026-03-03 08:33:47 +00:00
Chris Coutinho 0f8b1c6325 Merge pull request #602 from cbcoutinho/fix/contacts-vcard-dict-format-601
fix: handle pythonvCard4 dict-format fields and missing phones (#601)
2026-03-03 09:33:27 +01:00
Chris Coutinho fdb7b87baf fix: handle pythonvCard4 dict-format fields and missing phone numbers (#601)
Fix three related contacts bugs:
- Parse dict-format vCard fields ({value, type}) that pythonvCard4 returns,
  which previously crashed Pydantic validation expecting plain strings
- Include tel field in client output so phone numbers reach MCP tools
- Clarify addressbook parameter expects URI slug, not displayname

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:32:53 +01:00
github-actions[bot] 184415eca1 bump: version 0.57.92 → 0.57.93 2026-03-03 06:13:03 +00:00
Chris Coutinho 658fd7e138 Merge pull request #600 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.66
2026-03-03 07:12:48 +01:00
renovate-bot-cbcoutinho[bot] a5d2025797 chore(deps): update anthropics/claude-code-action action to v1.0.66 2026-03-02 17:17:24 +00:00
github-actions[bot] 08aaa85ab3 bump: version 0.57.91 → 0.57.92 2026-03-02 11:35:44 +00:00
Chris Coutinho ecab777efa Merge pull request #598 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.65
2026-03-02 12:35:28 +01:00
github-actions[bot] c960560716 bump: version 0.57.90 → 0.57.91 2026-03-02 11:33:46 +00:00
Chris Coutinho 023927afff Merge pull request #599 from cbcoutinho/renovate/ollama-1.x
chore(deps): update helm release ollama to v1.47.0
2026-03-02 12:33:28 +01:00
renovate-bot-cbcoutinho[bot] 3a87b33288 chore(deps): update helm release ollama to v1.47.0 2026-03-02 11:15:34 +00:00
renovate-bot-cbcoutinho[bot] c8ebd9c089 chore(deps): update anthropics/claude-code-action action to v1.0.65 2026-03-02 11:15:16 +00:00
github-actions[bot] 5d81d60262 bump: version 0.57.89 → 0.57.90 2026-03-01 16:26:00 +00:00
Chris Coutinho b86e798ba8 Merge pull request #592 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.64
2026-03-01 17:25:47 +01:00
Chris Coutinho a7d623733b Merge pull request #591 from cbcoutinho/renovate/docker.io-library-python-3.12-slim-trixie
chore(deps): update docker.io/library/python:3.12-slim-trixie docker digest to f3fa41d
2026-03-01 17:25:35 +01:00
github-actions[bot] 3311b20ef6 bump: version 0.57.88 → 0.57.89 2026-03-01 16:24:44 +00:00
Chris Coutinho 28c7f1cdbd Merge pull request #594 from cbcoutinho/renovate/ghcr.io-astral-sh-uv-0.x
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.10.7
2026-03-01 17:24:30 +01:00
github-actions[bot] 2713f74be6 bump: version 0.57.87 → 0.57.88 2026-03-01 16:23:38 +00:00
Chris Coutinho e3c5a87b22 Merge pull request #590 from cbcoutinho/renovate/docker.io-library-nextcloud-32.0.6
chore(deps): update docker.io/library/nextcloud:32.0.6 docker digest to 5c4e09f
2026-03-01 17:23:22 +01:00
Chris Coutinho 53cf223a56 Merge pull request #595 from cbcoutinho/renovate/ollama-1.x
chore(deps): update helm release ollama to v1.46.0
2026-03-01 17:23:11 +01:00
Chris Coutinho 6bfde0de1f Merge pull request #596 from cbcoutinho/renovate/major-github-artifact-actions
chore(deps): update actions/upload-artifact action to v7
2026-03-01 17:22:58 +01:00
github-actions[bot] 8cf3264914 bump: version 0.57.86 → 0.57.87 2026-03-01 16:05:13 +00:00
Chris Coutinho ed2f400ed8 Merge pull request #593 from cbcoutinho/renovate/astral-sh-setup-uv-7.x
chore(deps): update astral-sh/setup-uv action to v7.3.1
2026-03-01 17:04:56 +01:00
renovate-bot-cbcoutinho[bot] 6ba598afd1 chore(deps): update actions/upload-artifact action to v7 2026-03-01 16:03:54 +00:00
renovate-bot-cbcoutinho[bot] d0bfecea97 chore(deps): update helm release ollama to v1.46.0 2026-03-01 16:03:49 +00:00
renovate-bot-cbcoutinho[bot] bf0a4ac5d3 chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.10.7 2026-03-01 16:03:28 +00:00
renovate-bot-cbcoutinho[bot] 3da6feba41 chore(deps): update astral-sh/setup-uv action to v7.3.1 2026-03-01 16:03:20 +00:00
renovate-bot-cbcoutinho[bot] 1224090469 chore(deps): update anthropics/claude-code-action action to v1.0.64 2026-03-01 16:03:13 +00:00
renovate-bot-cbcoutinho[bot] aa624401c3 chore(deps): update docker.io/library/python:3.12-slim-trixie docker digest to f3fa41d 2026-03-01 16:03:08 +00:00
renovate-bot-cbcoutinho[bot] 61e867397c chore(deps): update docker.io/library/nextcloud:32.0.6 docker digest to 5c4e09f 2026-03-01 16:03:02 +00:00
github-actions[bot] 5796e2ba54 bump: version 0.57.85 → 0.57.86 2026-02-26 15:42:35 +00:00
github-actions[bot] 37141ea79f bump: version 0.64.3 → 0.64.4 2026-02-26 15:42:35 +00:00
Chris Coutinho 68126f6fe3 Merge pull request #555 from cbcoutinho/renovate/icalendar-7.x
fix(deps): update dependency icalendar to v7
2026-02-26 16:42:12 +01:00
github-actions[bot] 78b934ffa6 bump: version 0.57.84 → 0.57.85 2026-02-25 12:43:46 +00:00
Chris Coutinho 01a9ad5278 Merge pull request #588 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.60
2026-02-25 13:43:30 +01:00
github-actions[bot] b67a566902 bump: version 0.57.83 → 0.57.84 2026-02-25 11:35:38 +00:00
Chris Coutinho c9e8a56355 Merge pull request #584 from cbcoutinho/renovate/docker.io-library-nextcloud-32.0.6
chore(deps): update docker.io/library/nextcloud:32.0.6 docker digest to dcf9c60
2026-02-25 12:35:22 +01:00
renovate-bot-cbcoutinho[bot] 785ba5bf09 chore(deps): update anthropics/claude-code-action action to v1.0.60 2026-02-25 11:18:33 +00:00
renovate-bot-cbcoutinho[bot] 159ffb6110 chore(deps): update docker.io/library/nextcloud:32.0.6 docker digest to dcf9c60 2026-02-25 11:18:25 +00:00
github-actions[bot] 70139c4782 bump: version 0.57.82 → 0.57.83 2026-02-25 09:14:58 +00:00
Chris Coutinho a922187489 Merge pull request #586 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.59
2026-02-25 10:14:43 +01:00
github-actions[bot] 1ba6a142f5 bump: version 0.57.81 → 0.57.82 2026-02-25 08:42:21 +00:00
Chris Coutinho 79478f2483 Merge pull request #585 from cbcoutinho/renovate/docker.io-library-python-3.12-slim-trixie
chore(deps): update docker.io/library/python:3.12-slim-trixie docker digest to 39e4e1c
2026-02-25 09:42:03 +01:00
github-actions[bot] 4721a5da52 bump: version 0.57.80 → 0.57.81 2026-02-25 07:26:59 +00:00
Chris Coutinho be2b683604 Merge pull request #587 from cbcoutinho/renovate/ghcr.io-astral-sh-uv-0.x
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.10.6
2026-02-25 08:26:45 +01:00
renovate-bot-cbcoutinho[bot] 9fd3d92a0f chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.10.6 2026-02-25 05:15:47 +00:00
renovate-bot-cbcoutinho[bot] ceebda071f chore(deps): update anthropics/claude-code-action action to v1.0.59 2026-02-25 05:15:42 +00:00
renovate-bot-cbcoutinho[bot] 26fc48dc46 chore(deps): update docker.io/library/python:3.12-slim-trixie docker digest to 39e4e1c 2026-02-25 05:15:37 +00:00
renovate-bot-cbcoutinho[bot] 3edc226d17 fix(deps): update dependency icalendar to v7 2026-02-24 17:17:49 +00:00
github-actions[bot] 7384b47795 bump: version 0.57.79 → 0.57.80 2026-02-24 12:44:15 +00:00
Chris Coutinho b62d275dc9 Merge pull request #583 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.57
2026-02-24 13:43:57 +01:00
renovate-bot-cbcoutinho[bot] a0fa0230ab chore(deps): update anthropics/claude-code-action action to v1.0.57 2026-02-24 11:15:33 +00:00
github-actions[bot] 7314097483 bump: version 0.57.78 → 0.57.79 2026-02-24 09:34:47 +00:00
Chris Coutinho 3d070f74c5 Merge pull request #581 from cbcoutinho/renovate/ghcr.io-astral-sh-uv-0.x
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.10.5
2026-02-24 10:34:30 +01:00
github-actions[bot] 80366a4e1e bump: version 0.57.77 → 0.57.78 2026-02-24 08:21:58 +00:00
Chris Coutinho 91941a9ece Merge pull request #582 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.56
2026-02-24 09:21:39 +01:00
github-actions[bot] 8fd6f4158f bump: version 0.57.76 → 0.57.77 2026-02-24 08:19:59 +00:00
Chris Coutinho b8e6539b6f Merge pull request #580 from cbcoutinho/renovate/downloads.unstructured.io-unstructured-io-unstructured-api-latest
chore(deps): update downloads.unstructured.io/unstructured-io/unstructured-api:latest docker digest to ba6cb07
2026-02-24 09:19:15 +01:00
github-actions[bot] fe53e93fe9 bump: version 0.57.75 → 0.57.76 2026-02-24 07:51:52 +00:00
Chris Coutinho 71d4c44b05 Merge pull request #579 from cbcoutinho/renovate/docker.io-library-redis-alpine
chore(deps): update docker.io/library/redis:alpine docker digest to 2afba59
2026-02-24 08:51:37 +01:00
renovate-bot-cbcoutinho[bot] 8261048741 chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.10.5 2026-02-24 05:14:52 +00:00
renovate-bot-cbcoutinho[bot] 6443aca743 chore(deps): update anthropics/claude-code-action action to v1.0.56 2026-02-24 05:14:46 +00:00
renovate-bot-cbcoutinho[bot] a1b5e676e9 chore(deps): update downloads.unstructured.io/unstructured-io/unstructured-api:latest docker digest to ba6cb07 2026-02-23 23:14:33 +00:00
renovate-bot-cbcoutinho[bot] 1d9168f614 chore(deps): update docker.io/library/redis:alpine docker digest to 2afba59 2026-02-23 23:14:28 +00:00
github-actions[bot] 9229440a58 bump: version 0.57.74 → 0.57.75 2026-02-23 06:08:54 +00:00
Chris Coutinho e507f29e83 Merge pull request #578 from cbcoutinho/renovate/ollama-1.x
chore(deps): update helm release ollama to v1.45.0
2026-02-23 07:08:40 +01:00
renovate-bot-cbcoutinho[bot] 5ac6d8d396 chore(deps): update helm release ollama to v1.45.0 2026-02-23 05:14:16 +00:00
github-actions[bot] ab71003c5d bump: version 0.57.73 → 0.57.74 2026-02-21 08:53:06 +00:00
Chris Coutinho 726b71eea1 Merge pull request #575 from cbcoutinho/renovate/quay.io-keycloak-keycloak-26.x
chore(deps): update quay.io/keycloak/keycloak docker tag to v26.5.4
2026-02-21 09:52:51 +01:00
github-actions[bot] 3e50924169 bump: version 0.57.72 → 0.57.73 2026-02-21 08:38:08 +00:00
renovate-bot-cbcoutinho[bot] 960d060d27 chore(deps): update quay.io/keycloak/keycloak docker tag to v26.5.4 2026-02-20 11:15:05 +00:00
18 changed files with 320 additions and 50 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@edd85d61533cbba7b57ed0ca4af1750b1fdfd3c4 # v1.0.55
uses: anthropics/claude-code-action@64c7a0ef71df67b14cb4471f4d9c8565c61042bf # v1.0.66
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
allowed_bots: "renovate-bot-cbcoutinho"
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@edd85d61533cbba7b57ed0ca4af1750b1fdfd3c4 # v1.0.55
uses: anthropics/claude-code-action@64c7a0ef71df67b14cb4471f4d9c8565c61042bf # v1.0.66
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+2 -2
View File
@@ -42,7 +42,7 @@ jobs:
VECTOR_SYNC_SCAN_INTERVAL: "5"
- name: Install the latest version of uv
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
- name: Wait for Nextcloud to be ready
run: |
@@ -97,7 +97,7 @@ jobs:
- name: Upload test results
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: rag-evaluation-results
path: |
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install uv
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
- name: Install Python 3.11
run: uv python install 3.11
- name: Build
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install the latest version of uv
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
- name: Check format
run: |
uv run --frozen ruff format --diff
@@ -56,7 +56,7 @@ jobs:
up-flags: "--build"
- name: Install the latest version of uv
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
- name: Install Playwright dependencies
run: |
+12
View File
@@ -5,6 +5,18 @@ 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.64.5 (2026-03-03)
### Fix
- handle pythonvCard4 dict-format fields and missing phone numbers (#601)
## v0.64.4 (2026-02-26)
### Fix
- **deps**: update dependency icalendar to v7
## v0.64.3 (2026-02-21)
### Fix
+2 -2
View File
@@ -1,6 +1,6 @@
FROM docker.io/library/python:3.12-slim-trixie@sha256:9e01bf1ae5db7649a236da7be1e94ffbbbdd7a93f867dd0d8d5720d9e1f89fab
FROM docker.io/library/python:3.12-slim-trixie@sha256:f3fa41d74a768c2fce8016b98c191ae8c1bacd8f1152870a3f9f87d350920b7c
COPY --from=ghcr.io/astral-sh/uv:0.10.4@sha256:4cac394b6b72846f8a85a7a0e577c6d61d4e17fe2ccee65d9451a8b3c9efb4ac /uv /uvx /bin/
COPY --from=ghcr.io/astral-sh/uv:0.10.7@sha256:edd1fd89f3e5b005814cc8f777610445d7b7e3ed05361f9ddfae67bebfe8456a /uv /uvx /bin/
# Install dependencies
# 1. git (required for caldav dependency from git)
+2 -2
View File
@@ -12,12 +12,12 @@
# - Per-session app password authentication
# - Multi-user support via Smithery session config
FROM docker.io/library/python:3.12-slim-trixie@sha256:9e01bf1ae5db7649a236da7be1e94ffbbbdd7a93f867dd0d8d5720d9e1f89fab
FROM docker.io/library/python:3.12-slim-trixie@sha256:f3fa41d74a768c2fce8016b98c191ae8c1bacd8f1152870a3f9f87d350920b7c
WORKDIR /app
# Install uv for fast dependency management
COPY --from=ghcr.io/astral-sh/uv:0.10.4@sha256:4cac394b6b72846f8a85a7a0e577c6d61d4e17fe2ccee65d9451a8b3c9efb4ac /uv /uvx /bin/
COPY --from=ghcr.io/astral-sh/uv:0.10.7@sha256:edd1fd89f3e5b005814cc8f777610445d7b7e3ed05361f9ddfae67bebfe8456a /uv /uvx /bin/
# Install dependencies
# 1. git (required for caldav dependency from git)
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.commitizen]
name = "cz_conventional_commits"
version = "0.57.72"
version = "0.57.93"
tag_format = "nextcloud-mcp-server-$version"
version_scheme = "semver"
update_changelog_on_bump = true
+54
View File
@@ -14,6 +14,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Configurable resource limits
- Grafana dashboard annotations
## nextcloud-mcp-server-0.57.93 (2026-03-03)
## nextcloud-mcp-server-0.57.92 (2026-03-02)
## nextcloud-mcp-server-0.57.91 (2026-03-02)
## nextcloud-mcp-server-0.57.90 (2026-03-01)
## nextcloud-mcp-server-0.57.89 (2026-03-01)
## nextcloud-mcp-server-0.57.88 (2026-03-01)
## nextcloud-mcp-server-0.57.87 (2026-03-01)
## nextcloud-mcp-server-0.57.86 (2026-02-26)
### Fix
- **deps**: update dependency icalendar to v7
## nextcloud-mcp-server-0.57.85 (2026-02-25)
## nextcloud-mcp-server-0.57.84 (2026-02-25)
## nextcloud-mcp-server-0.57.83 (2026-02-25)
## nextcloud-mcp-server-0.57.82 (2026-02-25)
## nextcloud-mcp-server-0.57.81 (2026-02-25)
## nextcloud-mcp-server-0.57.80 (2026-02-24)
## nextcloud-mcp-server-0.57.79 (2026-02-24)
## nextcloud-mcp-server-0.57.78 (2026-02-24)
## nextcloud-mcp-server-0.57.77 (2026-02-24)
## nextcloud-mcp-server-0.57.76 (2026-02-24)
## nextcloud-mcp-server-0.57.75 (2026-02-23)
## nextcloud-mcp-server-0.57.74 (2026-02-21)
## nextcloud-mcp-server-0.57.73 (2026-02-21)
### Fix
- address PR #574 fourth review round
- address PR #574 third review round
- address PR #574 second review round
- address PR #574 review comments
- wrap raw list returns in response models to produce single TextContent block
## nextcloud-mcp-server-0.57.72 (2026-02-20)
## nextcloud-mcp-server-0.57.71 (2026-02-20)
+3 -3
View File
@@ -4,6 +4,6 @@ dependencies:
version: 1.17.0
- name: ollama
repository: https://otwld.github.io/ollama-helm
version: 1.44.0
digest: sha256:3a9d071ea6da98ab0508549ce35e7a2db3fc1fd496c13ec7f95f1102378a1ac8
generated: "2026-02-20T17:15:49.985311295Z"
version: 1.47.0
digest: sha256:08d589dd1b3386e8e8a2ac2c03a2194218ab12ed9e02016e7b981e554385dd11
generated: "2026-03-02T11:15:27.688786078Z"
+3 -3
View File
@@ -2,8 +2,8 @@ apiVersion: v2
name: nextcloud-mcp-server
description: A Helm chart for Nextcloud MCP Server - enables AI assistants to interact with Nextcloud
type: application
version: 0.57.72
appVersion: "0.64.3"
version: 0.57.93
appVersion: "0.64.5"
keywords:
- nextcloud
- mcp
@@ -31,6 +31,6 @@ dependencies:
repository: https://qdrant.github.io/qdrant-helm
condition: qdrant.networkMode.deploySubchart
- name: ollama
version: "1.44.0"
version: "1.47.0"
repository: https://otwld.github.io/ollama-helm
condition: ollama.enabled
+4 -4
View File
@@ -19,11 +19,11 @@ services:
# Note: Redis is an external service. You can find more information about the configuration here:
# https://hub.docker.com/_/redis
redis:
image: docker.io/library/redis:alpine@sha256:fd83658b0e40e2164617d262f13c02ca9ee9e1e6b276fd2fa06617e09bd5c780
image: docker.io/library/redis:alpine@sha256:2afba59292f25f5d1af200496db41bea2c6c816b059f57ae74703a50a03a27d0
restart: always
app:
image: docker.io/library/nextcloud:32.0.6@sha256:0e1084cc59df77bec7d6bb29d9ac6939da8372512237a9c51f74ff0a970524f2
image: docker.io/library/nextcloud:32.0.6@sha256:5c4e09f72f096cd68379a8ae69f71e61d13da5a07430fc4a17c702a14e6a4267
restart: always
ports:
- 127.0.0.1:8080:80
@@ -60,7 +60,7 @@ services:
- ./tests/fixtures/nginx.conf:/etc/nginx/nginx.conf:ro
unstructured:
image: downloads.unstructured.io/unstructured-io/unstructured-api:latest@sha256:3b9280eb9aa53d76a8f4a2465400ae747774d4bfd71dd73d603353b0b55c435d
image: downloads.unstructured.io/unstructured-io/unstructured-api:latest@sha256:ba6cb073af079c498e9466a5a9152ba4b6c9cad12efeeaf053ba383023d5db08
restart: always
ports:
- 127.0.0.1:8002:8000
@@ -207,7 +207,7 @@ services:
- oauth-tokens:/app/data
keycloak:
image: quay.io/keycloak/keycloak:26.5.3@sha256:5a236ae4dd8ece77490115bace15a11a4d15e9cbcf58a490b95a7da2cd71d32a
image: quay.io/keycloak/keycloak:26.5.4@sha256:ae8efb0d218d8921334b03a2dbee7069a0b868240691c50a3ffc9f42fabba8b4
command:
- "start-dev"
- "--import-realm"
+1
View File
@@ -274,6 +274,7 @@ class ContactsClient(BaseNextcloudClient):
"nickname": contact.nickname,
"birthday": contact.bday,
"email": contact.email,
"tel": contact.tel,
},
"addressdata": addressdata,
}
+70 -15
View File
@@ -18,23 +18,60 @@ from nextcloud_mcp_server.observability.metrics import instrument_tool
logger = logging.getLogger(__name__)
def _parse_vcard_fields(
raw_values: str | dict | list | None, field_type: str
) -> list[ContactField]:
"""Parse polymorphic vCard field data into a list of ContactField.
pythonvCard4 returns field values in several shapes:
- ``str`` plain value, e.g. ``"alice@example.com"``
- ``dict`` ``{'value': '...', 'type': ['HOME', 'PREF']}``
- ``list`` a list whose items are any of the above
The ``PREF`` type parameter is treated as a *preferred* flag rather than a
label. All other type values are lowercased and joined with ``", "``.
"""
if raw_values is None:
return []
items: list[str | dict] = (
raw_values if isinstance(raw_values, list) else [raw_values]
)
fields: list[ContactField] = []
for item in items:
if isinstance(item, dict):
value = str(item.get("value", ""))
if not value:
continue
raw_types: list[str] = item.get("type") or []
preferred = any(t.upper() == "PREF" for t in raw_types)
labels = [t.lower() for t in raw_types if t.upper() != "PREF"]
fields.append(
ContactField(
type=field_type,
value=value,
label=", ".join(labels) if labels else None,
preferred=preferred,
)
)
elif isinstance(item, str) and item:
fields.append(ContactField(type=field_type, value=item))
return fields
def _raw_contact_to_model(raw: dict) -> Contact:
"""Convert a raw contact dict from the contacts client to a Contact model.
Only maps fields the client's list_contacts() currently returns:
fullname, nickname, birthday, and email. Additional Contact model fields
(phones, addresses, organization, etc.) require expanding the client's
vCard parsing in ContactsClient.list_contacts().
Maps fullname, nickname, birthday, email, and tel fields.
Email/tel values may be plain strings, dicts with ``value``/``type`` keys,
or lists of either see :func:`_parse_vcard_fields`.
"""
contact_info = raw.get("contact", {})
# Convert email field (str, list, or None) to list[ContactField]
raw_email = contact_info.get("email")
emails: list[ContactField] = []
if isinstance(raw_email, list):
emails = [ContactField(type="email", value=e) for e in raw_email if e]
elif isinstance(raw_email, str) and raw_email:
emails = [ContactField(type="email", value=raw_email)]
emails = _parse_vcard_fields(contact_info.get("email"), "email")
phones = _parse_vcard_fields(contact_info.get("tel"), "phone")
# Nickname goes into custom_fields (no dedicated model field)
custom_fields: dict[str, Any] = {}
@@ -48,6 +85,7 @@ def _raw_contact_to_model(raw: dict) -> Contact:
etag=raw.get("getetag"),
birthday=contact_info.get("birthday"),
emails=emails,
phones=phones,
custom_fields=custom_fields,
)
@@ -87,7 +125,13 @@ def configure_contacts_tools(mcp: FastMCP):
async def nc_contacts_list_contacts(
ctx: Context, *, addressbook: str
) -> ListContactsResponse:
"""List all contacts in the specified addressbook."""
"""List all contacts in the specified addressbook.
Args:
addressbook: The URI slug of the addressbook (e.g. "contacts"),
not the display name. Use nc_contacts_list_addressbooks to
find available URI slugs.
"""
client = await get_client(ctx)
contacts_data = await client.contacts.list_contacts(addressbook=addressbook)
contacts = [_raw_contact_to_model(c) for c in contacts_data]
@@ -140,7 +184,9 @@ def configure_contacts_tools(mcp: FastMCP):
"""Create a new contact.
Args:
addressbook: The name of the addressbook to create the contact in.
addressbook: The URI slug of the addressbook (e.g. "contacts"),
not the display name. Use nc_contacts_list_addressbooks to
find available URI slugs.
uid: The unique ID for the contact.
contact_data: A dictionary with the contact's details, e.g. {"fn": "John Doe", "email": "john.doe@example.com"}.
"""
@@ -158,7 +204,14 @@ def configure_contacts_tools(mcp: FastMCP):
@require_scopes("contacts:write")
@instrument_tool
async def nc_contacts_delete_contact(ctx: Context, *, addressbook: str, uid: str):
"""Delete a contact."""
"""Delete a contact.
Args:
addressbook: The URI slug of the addressbook (e.g. "contacts"),
not the display name. Use nc_contacts_list_addressbooks to
find available URI slugs.
uid: The unique ID of the contact to delete.
"""
client = await get_client(ctx)
return await client.contacts.delete_contact(addressbook=addressbook, uid=uid)
@@ -174,7 +227,9 @@ def configure_contacts_tools(mcp: FastMCP):
"""Update an existing contact while preserving all existing properties.
Args:
addressbook: The name of the addressbook containing the contact.
addressbook: The URI slug of the addressbook (e.g. "contacts"),
not the display name. Use nc_contacts_list_addressbooks to
find available URI slugs.
uid: The unique ID of the contact to update.
contact_data: A dictionary with the contact's updated details, e.g. {"fn": "Jane Doe", "email": "jane.doe@example.com"}.
etag: Optional ETag for optimistic concurrency control.
+2 -2
View File
@@ -1,6 +1,6 @@
[project]
name = "nextcloud-mcp-server"
version = "0.64.3"
version = "0.64.5"
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"}
@@ -13,7 +13,7 @@ dependencies = [
"mcp[cli] (>=1.26,<1.27)",
"httpx (>=0.28.1,<0.29.0)",
"pillow (>=10.3.0,<12.0.0)", # Compatible with fastembed
"icalendar (>=6.0.0,<7.0.0)",
"icalendar (>=7.0.2,<7.1.0)",
"pythonvcard4>=0.2.0",
"pydantic>=2.11.4",
"click>=8.1.8",
+147
View File
@@ -376,6 +376,153 @@ def test_list_contacts_response_wraps_contacts():
assert c["custom_fields"]["nickname"] == "Ali"
@pytest.mark.unit
def test_contact_mapping_dict_format_emails():
"""Regression for #601: pythonvCard4 returns dicts, not plain strings."""
raw_contact = {
"vcard_id": "dict-email-1",
"contact": {
"fullname": "Evrim Yilmaz",
"email": [
{"value": "evrim@example.com", "type": ["HOME"]},
{"value": "evrim@work.com", "type": ["WORK"]},
],
},
}
contact = _map_contact(raw_contact)
assert len(contact.emails) == 2
assert contact.emails[0].value == "evrim@example.com"
assert contact.emails[0].label == "home"
assert contact.emails[1].value == "evrim@work.com"
assert contact.emails[1].label == "work"
@pytest.mark.unit
def test_contact_mapping_dict_format_phones():
"""Phones from dict-format tel field are parsed into Contact.phones."""
raw_contact = {
"vcard_id": "dict-tel-1",
"contact": {
"fullname": "Phone User",
"tel": [
{"value": "+1-555-0100", "type": ["CELL"]},
{"value": "+1-555-0200", "type": ["WORK", "VOICE"]},
],
},
}
contact = _map_contact(raw_contact)
assert len(contact.phones) == 2
assert contact.phones[0].value == "+1-555-0100"
assert contact.phones[0].type == "phone"
assert contact.phones[0].label == "cell"
assert contact.phones[1].value == "+1-555-0200"
assert contact.phones[1].label == "work, voice"
@pytest.mark.unit
def test_contact_mapping_pref_flag_extraction():
"""PREF type is extracted as preferred=True, not included in labels."""
raw_contact = {
"vcard_id": "pref-1",
"contact": {
"fullname": "Pref User",
"email": [
{"value": "pref@example.com", "type": ["HOME", "PREF"]},
{"value": "other@example.com", "type": ["WORK"]},
],
"tel": [
{"value": "+1-555-0001", "type": ["pref", "CELL"]},
],
},
}
contact = _map_contact(raw_contact)
assert contact.emails[0].preferred is True
assert contact.emails[0].label == "home" # PREF stripped from label
assert contact.emails[1].preferred is False
assert contact.primary_email == "pref@example.com"
assert contact.phones[0].preferred is True
assert contact.phones[0].label == "cell"
assert contact.primary_phone == "+1-555-0001"
@pytest.mark.unit
def test_contact_mapping_backward_compat_plain_strings():
"""Plain string emails/phones still work (backward compatibility)."""
raw_contact = {
"vcard_id": "compat-1",
"contact": {
"fullname": "Plain String",
"email": "plain@example.com",
"tel": "+1-555-9999",
},
}
contact = _map_contact(raw_contact)
assert len(contact.emails) == 1
assert contact.emails[0].value == "plain@example.com"
assert contact.emails[0].label is None
assert contact.emails[0].preferred is False
assert len(contact.phones) == 1
assert contact.phones[0].value == "+1-555-9999"
@pytest.mark.unit
def test_contact_mapping_empty_type_list():
"""Dict with empty or missing type list produces no label."""
raw_contact = {
"vcard_id": "empty-type-1",
"contact": {
"fullname": "No Type",
"email": {"value": "notype@example.com", "type": []},
},
}
contact = _map_contact(raw_contact)
assert len(contact.emails) == 1
assert contact.emails[0].value == "notype@example.com"
assert contact.emails[0].label is None
assert contact.emails[0].preferred is False
@pytest.mark.unit
def test_contact_mapping_multiple_dict_emails_with_labels():
"""Multiple dict-format emails preserve individual labels."""
raw_contact = {
"vcard_id": "multi-label-1",
"contact": {
"fullname": "Multi Label",
"email": [
{"value": "home@example.com", "type": ["HOME", "PREF"]},
{"value": "work@example.com", "type": ["WORK"]},
{"value": "other@example.com"},
],
},
}
contact = _map_contact(raw_contact)
assert len(contact.emails) == 3
assert contact.emails[0].value == "home@example.com"
assert contact.emails[0].label == "home"
assert contact.emails[0].preferred is True
assert contact.emails[1].value == "work@example.com"
assert contact.emails[1].label == "work"
assert contact.emails[1].preferred is False
assert contact.emails[2].value == "other@example.com"
assert contact.emails[2].label is None
assert contact.primary_email == "home@example.com"
# ============= _event_dict_to_summary tests =============
Generated
+12 -11
View File
@@ -1166,15 +1166,16 @@ wheels = [
[[package]]
name = "icalendar"
version = "6.3.2"
version = "7.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "python-dateutil" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
{ name = "tzdata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5d/70/458092b3e7c15783423fe64d07e63ea3311a597e723be6a1060513e3db93/icalendar-6.3.2.tar.gz", hash = "sha256:e0c10ecbfcebe958d33af7d491f6e6b7580d11d475f2eeb29532d0424f9110a1", size = 178422, upload-time = "2025-11-05T12:49:32.286Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/51/43458f01e229763b05dd937b5e9d41ef506b6eb8b4bf939f8ea34350b853/icalendar-7.0.2.tar.gz", hash = "sha256:de844ff5cde32f539bea7644e36d8494032a926b933bedb92621f2f239760806", size = 440039, upload-time = "2026-02-24T16:13:42.887Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/06/ee/2ff96bb5bd88fe03ab90aedf5180f96dc0f3ae4648ca264b473055bcaaff/icalendar-6.3.2-py3-none-any.whl", hash = "sha256:d400e9c9bb8c025e5a3c77c236941bb690494be52528a0b43cc7e8b7c9505064", size = 242403, upload-time = "2025-11-05T12:49:30.691Z" },
{ url = "https://files.pythonhosted.org/packages/62/ab/e0d44b1de0beb703bbc507ca064300b34046f9f9628f052d1a97ffa61b95/icalendar-7.0.2-py3-none-any.whl", hash = "sha256:ad31a5825b39522a30b073c6ced3ffcdf6c02cbb7dab69ba2e4de32ddbf77cc9", size = 437913, upload-time = "2026-02-24T16:13:40.631Z" },
]
[[package]]
@@ -1988,7 +1989,7 @@ wheels = [
[[package]]
name = "nextcloud-mcp-server"
version = "0.64.3"
version = "0.64.5"
source = { editable = "." }
dependencies = [
{ name = "aiosqlite" },
@@ -2051,7 +2052,7 @@ requires-dist = [
{ name = "click", specifier = ">=8.1.8" },
{ name = "fastembed", specifier = ">=0.7.3" },
{ name = "httpx", specifier = ">=0.28.1,<0.29.0" },
{ name = "icalendar", specifier = ">=6.0.0,<7.0.0" },
{ name = "icalendar", specifier = ">=7.0.2,<7.1.0" },
{ name = "jinja2", specifier = ">=3.1.6" },
{ name = "langchain-text-splitters", specifier = ">=1.0.0" },
{ name = "markdownify", specifier = ">=0.14.1" },
@@ -3383,7 +3384,7 @@ wheels = [
[[package]]
name = "recurring-ical-events"
version = "3.8.0"
version = "3.8.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "icalendar" },
@@ -3391,9 +3392,9 @@ dependencies = [
{ name = "tzdata" },
{ name = "x-wr-timezone" },
]
sdist = { url = "https://files.pythonhosted.org/packages/83/90/05dfcc02ecf58bd170305c88db9e3e3933aa73a1f8abac2b326c7fdc1a98/recurring_ical_events-3.8.0.tar.gz", hash = "sha256:3e8c7c35d9bd8956a7ab91afad51477c60d972e1236d3fd1b55087a66bce7d04", size = 602665, upload-time = "2025-06-10T13:23:50.662Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/d4/51c9361bb0efb2290dfd850c036b49acb502794e0fe9cc3520dbf60fd7db/recurring_ical_events-3.8.1.tar.gz", hash = "sha256:c3eb2490a00559fb963d2bdee39acf2f287c91c07dcea4ce80ade1c60a8c3acf", size = 603730, upload-time = "2026-02-18T11:45:53.272Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/25/88a4218cccae06ce6b15e41d2f263dd4a73e8e8cbe41537cd7784a17479b/recurring_ical_events-3.8.0-py3-none-any.whl", hash = "sha256:cf958eb17c92d4dca5c621e44c2b3fffd4ba700dca0db66287c5dc11438f63ba", size = 238228, upload-time = "2025-06-10T13:23:49.048Z" },
{ url = "https://files.pythonhosted.org/packages/14/67/4d4aead359164de68d30ee67efcdbe3784063cb21535c85b9a9a03dd2ebb/recurring_ical_events-3.8.1-py3-none-any.whl", hash = "sha256:3bb3aaa0c87a4d3ab5951360480686bd69f1512945f478be6a2c0f141da0bf78", size = 238286, upload-time = "2026-02-18T11:45:51.631Z" },
]
[[package]]
@@ -3954,11 +3955,11 @@ wheels = [
[[package]]
name = "tzdata"
version = "2025.2"
version = "2025.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
]
[[package]]