Compare commits

...

82 Commits

Author SHA1 Message Date
github-actions[bot] 1786e204ec bump: version 0.63.2 → 0.63.3 2026-02-08 12:57:41 +00:00
Chris Coutinho 0a599c5c03 Merge pull request #543 from cbcoutinho/fix/recurring-event-expansion
fix: expand recurring events in date-range queries
2026-02-08 13:57:22 +01:00
Chris Coutinho 66e32d4705 fix: expand recurring events in date-range queries
PR #539 fixed date-range filtering so events outside the queried range
are excluded. However, recurring events still returned the master event
with its original DTSTART instead of expanded occurrences.

Add <C:expand> element to CalDAV REPORT requests (RFC 4791 §9.6.5) when
both date bounds are provided, so the server returns one VEVENT per
occurrence with the correct DTSTART. Refactor VEVENT parsing into a
shared helper and add _parse_all_ical_events() to handle multi-VEVENT
responses from expanded results.

Closes #538

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 12:43:40 +01:00
github-actions[bot] 8603ed114e bump: version 0.57.39 → 0.57.40 2026-02-07 16:38:25 +00:00
github-actions[bot] 7e6ef90423 bump: version 0.63.1 → 0.63.2 2026-02-07 16:38:24 +00:00
Chris Coutinho c5f2c8369f Merge pull request #539 from cbcoutinho/fix/calendar-date-range-filtering
fix: use CalDAV time-range filter for calendar date range queries
2026-02-07 17:38:05 +01:00
Chris Coutinho b79ac29a9d fix: use CalDAV time-range filter for calendar date range queries
get_calendar_events() accepted start/end datetime parameters but called
calendar.events() which fetches all events, silently discarding the
date filters. This caused nc_calendar_list_events and
nc_calendar_get_upcoming_events to return the entire calendar history.

Add _search_events_by_date() helper that builds a CalDAV REPORT query
with a <time-range> filter (RFC 4791 §9.9) for server-side filtering.
Falls back to calendar.events() when no dates are given.

Closes #538

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:35:33 +01:00
github-actions[bot] 334d62825c bump: version 0.57.38 → 0.57.39 2026-02-07 14:49:39 +00:00
Chris Coutinho 2233cb423c Merge pull request #537 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 9e01bf1
2026-02-07 15:49:23 +01:00
github-actions[bot] 196a6cdfb2 bump: version 0.57.37 → 0.57.38 2026-02-07 14:47:36 +00:00
Chris Coutinho 93f5e70128 Merge pull request #519 from cbcoutinho/renovate/astral-sh-setup-uv-7.x
chore(deps): update astral-sh/setup-uv action to v7.3.0
2026-02-07 15:47:20 +01:00
renovate-bot-cbcoutinho[bot] e5248e70ee chore(deps): update astral-sh/setup-uv action to v7.3.0 2026-02-07 11:10:43 +00:00
renovate-bot-cbcoutinho[bot] 018b946b5b chore(deps): update docker.io/library/python:3.12-slim-trixie docker digest to 9e01bf1 2026-02-07 11:10:27 +00:00
github-actions[bot] 863ba0d52a bump: version 0.57.36 → 0.57.37 2026-02-06 20:08:06 +00:00
Chris Coutinho d3903c5e2e Merge pull request #529 from cbcoutinho/renovate/ollama-1.x
chore(deps): update helm release ollama to v1.41.0
2026-02-06 21:07:48 +01:00
github-actions[bot] 6ea97c5b88 bump: version 0.57.35 → 0.57.36 2026-02-06 17:44:50 +00:00
Chris Coutinho c12c825b11 Merge pull request #530 from cbcoutinho/renovate/hoverkraft-tech-compose-action-2.x
chore(deps): update hoverkraft-tech/compose-action action to v2.5.0
2026-02-06 18:44:32 +01:00
github-actions[bot] 3d8f7692a8 bump: version 0.57.34 → 0.57.35 2026-02-06 15:18:18 +00:00
Chris Coutinho b21c874c14 Merge pull request #531 from cbcoutinho/renovate/docker.io-library-nginx-alpine
chore(deps): update docker.io/library/nginx:alpine docker digest to 5878d06
2026-02-06 16:18:00 +01:00
github-actions[bot] a4661099e5 bump: version 0.57.33 → 0.57.34 2026-02-06 14:49:36 +00:00
Chris Coutinho a46d74d999 Merge pull request #522 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.45
2026-02-06 15:49:19 +01:00
github-actions[bot] 92f69c8dba bump: version 0.57.32 → 0.57.33 2026-02-06 14:23:11 +00:00
Chris Coutinho 6692a85007 Merge pull request #534 from cbcoutinho/renovate/uv_build-0.x
chore(deps): update dependency uv_build to >=0.10.0,<0.11.0
2026-02-06 15:22:55 +01:00
github-actions[bot] 1f09079b5a bump: version 0.57.31 → 0.57.32 2026-02-06 14:04:59 +00:00
Chris Coutinho 2535c95f4e Merge pull request #535 from cbcoutinho/renovate/ghcr.io-astral-sh-uv-0.x
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.10.0
2026-02-06 15:04:30 +01:00
renovate-bot-cbcoutinho[bot] 4fac0ca40d chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.10.0 2026-02-06 11:09:24 +00:00
renovate-bot-cbcoutinho[bot] 719a432a95 chore(deps): update dependency uv_build to >=0.10.0,<0.11.0 2026-02-06 11:09:13 +00:00
renovate-bot-cbcoutinho[bot] 14c4512ef8 chore(deps): update anthropics/claude-code-action action to v1.0.45 2026-02-06 11:08:46 +00:00
github-actions[bot] 6f482c9245 bump: version 0.57.30 → 0.57.31 2026-02-06 07:23:12 +00:00
Chris Coutinho a6ad3707c6 Merge pull request #513 from cbcoutinho/renovate/ghcr.io-astral-sh-uv-0.x
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.9.30
2026-02-06 08:22:55 +01:00
github-actions[bot] b34f8d96e3 bump: version 0.57.29 → 0.57.30 2026-02-06 07:09:22 +00:00
Chris Coutinho d948f51b10 Merge pull request #532 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 43e4d70
2026-02-06 08:09:07 +01:00
renovate-bot-cbcoutinho[bot] 5eb5b5023c chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.9.30 2026-02-05 11:12:00 +00:00
renovate-bot-cbcoutinho[bot] 504213ae79 chore(deps): update docker.io/library/python:3.12-slim-trixie docker digest to 43e4d70 2026-02-05 11:11:49 +00:00
renovate-bot-cbcoutinho[bot] 5eeaafbe95 chore(deps): update docker.io/library/nginx:alpine docker digest to 5878d06 2026-02-05 11:11:43 +00:00
renovate-bot-cbcoutinho[bot] 0ddc62c371 chore(deps): update hoverkraft-tech/compose-action action to v2.5.0 2026-02-04 11:09:33 +00:00
renovate-bot-cbcoutinho[bot] 36d901d5ae chore(deps): update helm release ollama to v1.41.0 2026-02-04 11:09:28 +00:00
github-actions[bot] 0a3052d0d9 bump: version 0.57.28 → 0.57.29 2026-02-04 06:25:10 +00:00
Chris Coutinho 2b691f1792 Merge pull request #525 from cbcoutinho/renovate/docker.io-library-nextcloud-32.0.5
chore(deps): update docker.io/library/nextcloud:32.0.5 docker digest to 4b66e9b
2026-02-04 07:24:55 +01:00
github-actions[bot] e3da2e006c bump: version 0.57.27 → 0.57.28 2026-02-03 19:57:46 +00:00
Chris Coutinho 4539f2f486 Merge pull request #526 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 87b49ee
2026-02-03 20:57:27 +01:00
renovate-bot-cbcoutinho[bot] c85ad95faf chore(deps): update docker.io/library/python:3.12-slim-trixie docker digest to 87b49ee 2026-02-03 11:12:24 +00:00
renovate-bot-cbcoutinho[bot] 60f7234908 chore(deps): update docker.io/library/nextcloud:32.0.5 docker digest to 4b66e9b 2026-02-03 11:12:18 +00:00
github-actions[bot] 1dd5698389 bump: version 0.10.0 → 0.10.1 2026-02-03 06:50:26 +00:00
github-actions[bot] 3a0096f8df bump: version 0.57.26 → 0.57.27 2026-02-03 06:50:26 +00:00
github-actions[bot] 7bcffd1e96 bump: version 0.63.0 → 0.63.1 2026-02-03 06:50:25 +00:00
Chris Coutinho 9674366312 Merge pull request #524 from rule88/master
create persistant volume in basis auth as well
2026-02-03 07:50:06 +01:00
Chris Coutinho a7581a1d1b fix(helm): add backward compatibility for legacy persistence configs
- Add helper functions to detect and use legacy persistence configs
- Legacy auth.multiUserBasic.persistence.* and qdrant.localPersistence.*
  configs continue to work but show deprecation warnings in NOTES.txt
- New dataStorage.enabled takes precedence when explicitly set
- PVC size/accessMode/storageClass values from legacy configs are honored

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 07:45:30 +01:00
Rick 0ff442d61c create persistant volume in basis auth as well 2026-02-02 12:10:53 +01:00
github-actions[bot] 96598510ee bump: version 0.57.25 → 0.57.26 2026-01-31 16:56:55 +00:00
Chris Coutinho 02cb1f5491 Merge pull request #512 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.40
2026-01-31 17:56:40 +01:00
github-actions[bot] 3856698d0a bump: version 0.57.24 → 0.57.25 2026-01-31 16:48:42 +00:00
Chris Coutinho 3a05f0cfb3 Merge pull request #500 from cbcoutinho/renovate/phpunit-phpunit-10.x-lockfile
chore(deps): update dependency phpunit/phpunit to v10.5.63
2026-01-31 17:48:27 +01:00
github-actions[bot] fe5e7f7a60 bump: version 0.57.23 → 0.57.24 2026-01-31 16:10:39 +00:00
Chris Coutinho b7257f4e59 Merge pull request #481 from cbcoutinho/renovate/docker.io-library-nginx-alpine
chore(deps): update docker.io/library/nginx:alpine docker digest to 4870c12
2026-01-31 17:10:21 +01:00
renovate-bot-cbcoutinho[bot] 7cc852f0da chore(deps): update dependency phpunit/phpunit to v10.5.63 2026-01-31 11:08:40 +00:00
renovate-bot-cbcoutinho[bot] 525258be67 chore(deps): update anthropics/claude-code-action action to v1.0.40 2026-01-31 11:08:20 +00:00
renovate-bot-cbcoutinho[bot] 49bd3100ad chore(deps): update docker.io/library/nginx:alpine docker digest to 4870c12 2026-01-31 11:08:13 +00:00
github-actions[bot] 6693bab9f9 bump: version 0.57.22 → 0.57.23 2026-01-30 19:26:59 +00:00
Chris Coutinho 8e0d64f7d3 Merge branch 'master' of github.com:cbcoutinho/nextcloud-mcp-server 2026-01-30 19:26:34 +00:00
Chris Coutinho c97ffe8e47 docs(astrolabe): Add initial blog post 2026-01-30 19:17:23 +00:00
github-actions[bot] d0115170c2 bump: version 0.57.21 → 0.57.22 2026-01-30 19:14:47 +00:00
Chris Coutinho 9ec00d4de5 chore: Update screenshot names 2026-01-30 19:14:10 +00:00
github-actions[bot] 9527427782 bump: version 0.57.20 → 0.57.21 2026-01-30 14:38:25 +00:00
Chris Coutinho fbfc8b8a05 Merge pull request #514 from cbcoutinho/renovate/ollama-1.x
chore(deps): update helm release ollama to v1.40.0
2026-01-30 15:38:09 +01:00
renovate-bot-cbcoutinho[bot] e85000424d chore(deps): update helm release ollama to v1.40.0 2026-01-30 11:10:16 +00:00
github-actions[bot] 58ac60be12 bump: version 0.57.19 → 0.57.20 2026-01-29 21:55:19 +00:00
Chris Coutinho 77ef928060 Merge pull request #494 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 9945a84
2026-01-29 22:54:56 +01:00
renovate-bot-cbcoutinho[bot] 00afac8e46 chore(deps): update downloads.unstructured.io/unstructured-io/unstructured-api:latest docker digest to 9945a84 2026-01-29 11:12:00 +00:00
github-actions[bot] d22cebc69a bump: version 0.57.18 → 0.57.19 2026-01-28 20:15:42 +00:00
Chris Coutinho 151d595360 Merge pull request #515 from cbcoutinho/renovate/docker.io-library-redis-alpine
chore(deps): update docker.io/library/redis:alpine docker digest to 0804c39
2026-01-28 21:15:26 +01:00
github-actions[bot] 7e02a58546 bump: version 0.57.17 → 0.57.18 2026-01-28 12:46:37 +00:00
Chris Coutinho 25dee9bfaf Merge pull request #496 from cbcoutinho/renovate/vue-monorepo
chore(deps): update dependency vue to v3.5.27
2026-01-28 13:46:20 +01:00
github-actions[bot] f898d61077 bump: version 0.57.16 → 0.57.17 2026-01-28 12:45:31 +00:00
Chris Coutinho 0aaa3fc912 Merge pull request #468 from cbcoutinho/renovate/nextcloud-vue-9.x-lockfile
chore(deps): update dependency @nextcloud/vue to v9.4.0
2026-01-28 13:45:15 +01:00
renovate-bot-cbcoutinho[bot] 77fabccdb7 chore(deps): update dependency @nextcloud/vue to v9.4.0 2026-01-28 11:11:49 +00:00
renovate-bot-cbcoutinho[bot] 2648ef2567 chore(deps): update dependency vue to v3.5.27 2026-01-28 11:11:24 +00:00
renovate-bot-cbcoutinho[bot] 405a57649a chore(deps): update docker.io/library/redis:alpine docker digest to 0804c39 2026-01-28 11:10:42 +00:00
github-actions[bot] 252df1d398 bump: version 0.9.0 → 0.10.0 2026-01-28 07:39:10 +00:00
github-actions[bot] 0ad81a1fd8 bump: version 0.57.15 → 0.57.16 2026-01-28 07:39:10 +00:00
github-actions[bot] dce864e947 bump: version 0.62.0 → 0.63.0 2026-01-28 07:39:09 +00:00
Chris Coutinho b9f1040dd5 Merge pull request #511 from cbcoutinho/feat/background-token-refresh
feat(astrolabe): add background token refresh job
2026-01-28 08:38:50 +01:00
33 changed files with 930 additions and 415 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@f64219702d7454cf29fe32a74104be6ed43dc637 # v1.0.34
uses: anthropics/claude-code-action@b113f49a56229d8276e2bf05743ad6900121239c # v1.0.45
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@f64219702d7454cf29fe32a74104be6ed43dc637 # v1.0.34
uses: anthropics/claude-code-action@b113f49a56229d8276e2bf05743ad6900121239c # v1.0.45
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Run docker compose with vector sync
uses: hoverkraft-tech/compose-action@05da55b2bb8a5a759d1c4732095044bd9018c050 # v2.4.3
uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0
with:
compose-file: |
./docker-compose.yml
@@ -42,7 +42,7 @@ jobs:
VECTOR_SYNC_SCAN_INTERVAL: "5"
- name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
- name: Wait for Nextcloud to be ready
run: |
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Install uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
- name: Install Python 3.11
run: uv python install 3.11
- name: Build
+3 -3
View File
@@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
- name: Check format
run: |
uv run --frozen ruff format --diff
@@ -66,14 +66,14 @@ jobs:
- name: Run docker compose
uses: hoverkraft-tech/compose-action@05da55b2bb8a5a759d1c4732095044bd9018c050 # v2.4.3
uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0
with:
compose-file: "./docker-compose.yml"
#compose-flags: "--profile qdrant"
up-flags: "--build"
- name: Install the latest version of uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
- name: Install Playwright dependencies
run: |
+30
View File
@@ -5,6 +5,36 @@ 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.63.3 (2026-02-08)
### Fix
- expand recurring events in date-range queries
## v0.63.2 (2026-02-07)
### Fix
- use CalDAV time-range filter for calendar date range queries
## v0.63.1 (2026-02-03)
### Fix
- **helm**: add backward compatibility for legacy persistence configs
## v0.63.0 (2026-01-28)
### Feat
- **astrolabe**: add background token refresh job
### Fix
- **astrolabe**: add pagination and psalm fixes for token refresh
- **astrolabe**: add locking to prevent token refresh race condition
- **astrolabe**: add issued_at to on-demand token refresh
## v0.62.0 (2026-01-26)
### Feat
+2 -2
View File
@@ -1,6 +1,6 @@
FROM docker.io/library/python:3.12-slim-trixie@sha256:5e2dbd4bbdd9c0e67412aea9463906f74a22c60f89eb7b5bbb7d45b66a2b68a6
FROM docker.io/library/python:3.12-slim-trixie@sha256:9e01bf1ae5db7649a236da7be1e94ffbbbdd7a93f867dd0d8d5720d9e1f89fab
COPY --from=ghcr.io/astral-sh/uv:0.9.26@sha256:9a23023be68b2ed09750ae636228e903a54a05ea56ed03a934d00fe9fbeded4b /uv /uvx /bin/
COPY --from=ghcr.io/astral-sh/uv:0.10.0@sha256:78a7ff97cd27b7124a5f3c2aefe146170793c56a1e03321dd31a289f6d82a04f /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:5e2dbd4bbdd9c0e67412aea9463906f74a22c60f89eb7b5bbb7d45b66a2b68a6
FROM docker.io/library/python:3.12-slim-trixie@sha256:9e01bf1ae5db7649a236da7be1e94ffbbbdd7a93f867dd0d8d5720d9e1f89fab
WORKDIR /app
# Install uv for fast dependency management
COPY --from=ghcr.io/astral-sh/uv:0.9.26@sha256:9a23023be68b2ed09750ae636228e903a54a05ea56ed03a934d00fe9fbeded4b /uv /uvx /bin/
COPY --from=ghcr.io/astral-sh/uv:0.10.0@sha256:78a7ff97cd27b7124a5f3c2aefe146170793c56a1e03321dd31a289f6d82a04f /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.15"
version = "0.57.40"
tag_format = "nextcloud-mcp-server-$version"
version_scheme = "semver"
update_changelog_on_bump = true
+68
View File
@@ -14,6 +14,74 @@ 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.40 (2026-02-07)
### Fix
- use CalDAV time-range filter for calendar date range queries
## nextcloud-mcp-server-0.57.39 (2026-02-07)
## nextcloud-mcp-server-0.57.38 (2026-02-07)
## nextcloud-mcp-server-0.57.37 (2026-02-06)
## nextcloud-mcp-server-0.57.36 (2026-02-06)
## nextcloud-mcp-server-0.57.35 (2026-02-06)
## nextcloud-mcp-server-0.57.34 (2026-02-06)
## nextcloud-mcp-server-0.57.33 (2026-02-06)
## nextcloud-mcp-server-0.57.32 (2026-02-06)
## nextcloud-mcp-server-0.57.31 (2026-02-06)
## nextcloud-mcp-server-0.57.30 (2026-02-06)
## nextcloud-mcp-server-0.57.29 (2026-02-04)
## nextcloud-mcp-server-0.57.28 (2026-02-03)
## nextcloud-mcp-server-0.57.27 (2026-02-03)
### Fix
- **helm**: add backward compatibility for legacy persistence configs
## nextcloud-mcp-server-0.57.26 (2026-01-31)
## nextcloud-mcp-server-0.57.25 (2026-01-31)
## nextcloud-mcp-server-0.57.24 (2026-01-31)
## nextcloud-mcp-server-0.57.23 (2026-01-30)
## nextcloud-mcp-server-0.57.22 (2026-01-30)
## nextcloud-mcp-server-0.57.21 (2026-01-30)
## nextcloud-mcp-server-0.57.20 (2026-01-29)
## nextcloud-mcp-server-0.57.19 (2026-01-28)
## nextcloud-mcp-server-0.57.18 (2026-01-28)
## nextcloud-mcp-server-0.57.17 (2026-01-28)
## nextcloud-mcp-server-0.57.16 (2026-01-28)
### Feat
- **astrolabe**: add background token refresh job
### Fix
- **astrolabe**: add pagination and psalm fixes for token refresh
- **astrolabe**: add locking to prevent token refresh race condition
- **astrolabe**: add issued_at to on-demand token refresh
## nextcloud-mcp-server-0.57.15 (2026-01-26)
### Feat
+3 -3
View File
@@ -4,6 +4,6 @@ dependencies:
version: 1.16.3
- name: ollama
repository: https://otwld.github.io/ollama-helm
version: 1.38.0
digest: sha256:60b09d52759c84f8add5782c867f5a373aa6eb2477dc9380bef0134183c4b1ae
generated: "2026-01-20T11:11:57.230612063Z"
version: 1.41.0
digest: sha256:1d5b958a64eb2102cf347ec199638bfac5b289bafdecff2529099ee6bce03b86
generated: "2026-02-04T11:09:21.837825534Z"
+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.15
appVersion: "0.62.0"
version: 0.57.40
appVersion: "0.63.3"
keywords:
- nextcloud
- mcp
@@ -31,6 +31,6 @@ dependencies:
repository: https://qdrant.github.io/qdrant-helm
condition: qdrant.networkMode.deploySubchart
- name: ollama
version: "1.38.0"
version: "1.41.0"
repository: https://otwld.github.io/ollama-helm
condition: ollama.enabled
+19
View File
@@ -118,6 +118,25 @@ ingress:
| `auth.oauth.persistence.enabled` | Enable persistent storage for OAuth | `true` |
| `auth.oauth.persistence.size` | Size of OAuth storage PVC | `100Mi` |
#### Data Storage
The `/app/data` directory is used for application data (token databases, Qdrant persistent storage, etc.). It is always mounted as writable to support the read-only root filesystem security context.
| Parameter | Description | Default |
|-----------|-------------|---------|
| `dataStorage.enabled` | Enable persistent storage for `/app/data` | `false` |
| `dataStorage.size` | Size of data storage PVC | `1Gi` |
| `dataStorage.storageClass` | Storage class (leave empty for default) | `""` |
| `dataStorage.accessMode` | Access mode | `ReadWriteOnce` |
| `dataStorage.existingClaim` | Use existing PVC | `""` |
**When to enable persistence:**
- Multi-user basic auth with offline access (stores `tokens.db`)
- Qdrant persistent mode (stores vector database)
- Any feature requiring persistent app data
**When persistence is disabled:** Uses `emptyDir` (non-persistent, data lost on pod restart, but directory remains writable).
#### MCP Server Configuration
| Parameter | Description | Default |
@@ -120,6 +120,55 @@ Your Nextcloud MCP Server has been deployed in {{ .Values.auth.mode }} authentic
The dashboard JSON is available in the chart at charts/nextcloud-mcp-server/dashboards/nextcloud-mcp-server.json
{{- end }}
{{- $legacyMultiUserBasic := eq (include "nextcloud-mcp-server.legacyMultiUserBasicPersistence" .) "true" }}
{{- $legacyQdrant := eq (include "nextcloud-mcp-server.legacyQdrantPersistence" .) "true" }}
{{- if or $legacyMultiUserBasic $legacyQdrant }}
================================================================================
DEPRECATION WARNING
================================================================================
You are using deprecated persistence configuration that will be removed in a
future release. Your deployment will continue to work, but please migrate to
the new unified dataStorage configuration.
Deprecated settings detected:
{{- if $legacyMultiUserBasic }}
- auth.multiUserBasic.persistence.* (currently enabled)
{{- end }}
{{- if $legacyQdrant }}
- qdrant.localPersistence.* (currently enabled)
{{- end }}
To migrate, update your values.yaml:
dataStorage:
enabled: true
{{- if $legacyMultiUserBasic }}
size: {{ .Values.auth.multiUserBasic.persistence.size }}
{{- else if $legacyQdrant }}
size: {{ .Values.qdrant.localPersistence.size }}
{{- end }}
# storageClass: "" # Optional: specify storage class
# existingClaim: "" # Optional: use existing PVC to preserve data
After migrating, remove the deprecated settings:
{{- if $legacyMultiUserBasic }}
- auth.multiUserBasic.persistence.enabled
- auth.multiUserBasic.persistence.size
- auth.multiUserBasic.persistence.storageClass
- auth.multiUserBasic.persistence.accessMode
{{- end }}
{{- if $legacyQdrant }}
- qdrant.localPersistence.enabled
- qdrant.localPersistence.size
- qdrant.localPersistence.storageClass
- qdrant.localPersistence.accessMode
{{- end }}
================================================================================
{{- end }}
For more information and documentation:
- GitHub: https://github.com/cbcoutinho/nextcloud-mcp-server
- Documentation: https://github.com/cbcoutinho/nextcloud-mcp-server#readme
@@ -127,6 +127,55 @@ Create the name of the PVC to use for Qdrant local persistent storage
{{- end }}
{{- end }}
{{/*
Create the name of the PVC to use for /app/data storage
*/}}
{{- define "nextcloud-mcp-server.dataStoragePvcName" -}}
{{- if .Values.dataStorage.existingClaim }}
{{- .Values.dataStorage.existingClaim }}
{{- else }}
{{- include "nextcloud-mcp-server.fullname" . }}-data-storage
{{- end }}
{{- end }}
{{/*
Determine if data storage PVC should be enabled (backward compatible)
Checks new dataStorage.enabled OR legacy persistence configs
*/}}
{{- define "nextcloud-mcp-server.dataStorageEnabled" -}}
{{- if .Values.dataStorage.enabled -}}
true
{{- else if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled -}}
true
{{- else if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled -}}
true
{{- else -}}
false
{{- end -}}
{{- end }}
{{/*
Check if legacy multi-user-basic persistence config is being used
*/}}
{{- define "nextcloud-mcp-server.legacyMultiUserBasicPersistence" -}}
{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled (not .Values.dataStorage.enabled) -}}
true
{{- else -}}
false
{{- end -}}
{{- end }}
{{/*
Check if legacy qdrant persistence config is being used
*/}}
{{- define "nextcloud-mcp-server.legacyQdrantPersistence" -}}
{{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled (not .Values.dataStorage.enabled) -}}
true
{{- else -}}
false
{{- end -}}
{{- end }}
{{/*
Return the MCP server port
*/}}
@@ -286,14 +286,8 @@ spec:
- name: oauth-storage
mountPath: /app/.oauth
{{- end }}
{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled }}
- name: token-storage
- name: data-storage
mountPath: /app/data
{{- end }}
{{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled }}
- name: qdrant-data
mountPath: /app/data
{{- end }}
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
@@ -305,15 +299,12 @@ spec:
persistentVolumeClaim:
claimName: {{ include "nextcloud-mcp-server.oauthPvcName" . }}
{{- end }}
{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled }}
- name: token-storage
- name: data-storage
{{- if eq (include "nextcloud-mcp-server.dataStorageEnabled" .) "true" }}
persistentVolumeClaim:
claimName: {{ include "nextcloud-mcp-server.multiUserBasicPvcName" . }}
{{- end }}
{{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled }}
- name: qdrant-data
persistentVolumeClaim:
claimName: {{ include "nextcloud-mcp-server.qdrantPvcName" . }}
claimName: {{ include "nextcloud-mcp-server.dataStoragePvcName" . }}
{{- else }}
emptyDir: {}
{{- end }}
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
+20 -24
View File
@@ -16,38 +16,34 @@ spec:
storage: {{ .Values.auth.oauth.persistence.size }}
{{- end }}
---
{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled (not .Values.auth.multiUserBasic.persistence.existingClaim) }}
{{- if and (eq (include "nextcloud-mcp-server.dataStorageEnabled" .) "true") (not .Values.dataStorage.existingClaim) }}
{{- $legacyMultiUserBasic := eq (include "nextcloud-mcp-server.legacyMultiUserBasicPersistence" .) "true" }}
{{- $legacyQdrant := eq (include "nextcloud-mcp-server.legacyQdrantPersistence" .) "true" }}
{{- $accessMode := .Values.dataStorage.accessMode }}
{{- $storageClass := .Values.dataStorage.storageClass }}
{{- $size := .Values.dataStorage.size }}
{{- if $legacyMultiUserBasic }}
{{- $accessMode = .Values.auth.multiUserBasic.persistence.accessMode }}
{{- $storageClass = .Values.auth.multiUserBasic.persistence.storageClass }}
{{- $size = .Values.auth.multiUserBasic.persistence.size }}
{{- else if $legacyQdrant }}
{{- $accessMode = .Values.qdrant.localPersistence.accessMode }}
{{- $storageClass = .Values.qdrant.localPersistence.storageClass }}
{{- $size = .Values.qdrant.localPersistence.size }}
{{- end }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-token-storage
name: {{ include "nextcloud-mcp-server.fullname" . }}-data-storage
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.auth.multiUserBasic.persistence.accessMode }}
{{- if .Values.auth.multiUserBasic.persistence.storageClass }}
storageClassName: {{ .Values.auth.multiUserBasic.persistence.storageClass }}
- {{ $accessMode }}
{{- if $storageClass }}
storageClassName: {{ $storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.auth.multiUserBasic.persistence.size }}
{{- end }}
---
{{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled (not .Values.qdrant.localPersistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "nextcloud-mcp-server.fullname" . }}-qdrant-data
labels:
{{- include "nextcloud-mcp-server.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.qdrant.localPersistence.accessMode }}
{{- if .Values.qdrant.localPersistence.storageClass }}
storageClassName: {{ .Values.qdrant.localPersistence.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.qdrant.localPersistence.size }}
storage: {{ $size }}
{{- end }}
+21
View File
@@ -139,6 +139,27 @@ auth:
# Use existing PVC
existingClaim: ""
# Data Storage Configuration
# Persistent volume for /app/data directory
# Used for: token databases, qdrant persistent storage, and any app data
# When disabled, uses emptyDir (non-persistent, but still writable)
dataStorage:
# Enable persistent storage for /app/data
# Set to true when using:
# - Multi-user basic auth with offline access (stores tokens.db)
# - Qdrant persistent mode (stores vector database)
# - Any feature requiring persistent app data
# Set to false for basic auth without persistence (uses emptyDir)
enabled: false
# Storage class (leave empty for default)
storageClass: ""
accessMode: ReadWriteOnce
# Size for data storage (should accommodate tokens.db and/or qdrant data)
# Recommended: 1Gi minimum, 5Gi for production with qdrant
size: 1Gi
# Use existing PVC
existingClaim: ""
# MCP server configuration
mcp:
# Transport mode (default: streamable-http for SSE)
+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:6cbef353e480a8a6e7f10ec545f13d7d3fa85a212cdcc5ffaf5a1c818b9d3798
image: docker.io/library/redis:alpine@sha256:0804c395e634e624243387d3c3a9c45fcaca876d313c2c8b52c3fdf9a912dded
restart: always
app:
image: docker.io/library/nextcloud:32.0.5@sha256:11a3a4f63bad8813c7455b4a3c473ccd1c41e2c48f55decb51718f15691e7568
image: docker.io/library/nextcloud:32.0.5@sha256:4b66e9bd8cb2c8af5457c1e2606c9937af2fcccbe4f6338956bc5990caec8968
restart: always
ports:
- 127.0.0.1:8080:80
@@ -54,14 +54,14 @@ services:
retries: 30
recipes:
image: docker.io/library/nginx:alpine@sha256:66d420cc54ef85bcc1d72220e83d7aaa6c4850bd2904794e3a56f09fd4ccb66e
image: docker.io/library/nginx:alpine@sha256:5878d06ae4c83d73285438255f705bb3f9a736f41cd24876ed25bb33faf76c7d
restart: always
volumes:
- ./tests/fixtures/test_recipe.html:/usr/share/nginx/html/test_recipe.html:ro
- ./tests/fixtures/nginx.conf:/etc/nginx/nginx.conf:ro
unstructured:
image: downloads.unstructured.io/unstructured-io/unstructured-api:latest@sha256:db5fcc831eb673ec835c41e8d47f993fdde276562285d6837cebb03f958536a2
image: downloads.unstructured.io/unstructured-io/unstructured-api:latest@sha256:9945a842ba983afcf110053cbcc0df7e4bd09ba9f02aa213824ce3f986713635
restart: always
ports:
- 127.0.0.1:8002:8000
+206
View File
@@ -0,0 +1,206 @@
# Introducing Astrolabe: Navigate Your Data Universe in Nextcloud
Your Nextcloud instance holds years of notes, projects, recipes, contacts, and documents. But when you need to find something, you're stuck typing exact keywords and hoping for the best. Search "car repair" and miss that note titled "Vehicle maintenance tips." Search "meeting agenda" and overlook the calendar event called "Team sync." Traditional keyword search demands that you remember exactly how you wrote things down.
What if your search could understand what you *mean*, not just what you type?
Meet **Astrolabe**—a Nextcloud app that brings AI-powered semantic search to your self-hosted cloud. Named after the ancient navigational instrument that helped travelers chart courses by the stars, Astrolabe helps you navigate your personal knowledge by mapping the semantic connections between your documents.
## The Astrolabe Metaphor
The astrolabe was one of humanity's most elegant scientific instruments—an analog computer for solving problems related to time and the position of celestial bodies. Its theoretical foundation traces back to **Hipparchus of Nicaea** (c. 190120 BCE), who discovered the stereographic projection that allows a three-dimensional celestial sphere to be represented on a flat surface. Later Greek scholars like **Theon of Alexandria** and his daughter **Hypatia** refined it into a practical instrument, and during the Islamic Golden Age, astronomers in Baghdad, Damascus, and Cordoba perfected its design and applications.
For nearly two millennia, astrolabes served astronomers, navigators, scholars, and religious officials across the Greek, Byzantine, Islamic, and medieval European worlds. These instruments allowed users to determine time, find celestial positions, calculate daylight hours, identify constellations, and even determine the direction of Mecca for prayer—all without complex calculations. The astrolabe made the vast complexity of the heavens understandable and navigable.
**Astrolabe** (the app) does the same for your data. Every document, note, and calendar event becomes a point of light in your personal data universe. The app maps their semantic relationships—their meaning, not just their words—and suddenly the connections become visible. Documents cluster by topic, related ideas sit nearby, and you can navigate this landscape as naturally as medieval scholars once read the stars. Where the original astrolabe projected the celestial sphere onto brass, this one projects your knowledge into explorable semantic space.
## Semantic Search: Find Meaning, Not Just Keywords
The core feature of Astrolabe is semantic search. Instead of matching exact keywords, it understands the concepts in your query and finds related content.
**What this looks like in practice:**
| You Search For | Traditional Search Finds | Astrolabe Also Finds |
|----------------|--------------------------|----------------------|
| "car repair" | Documents containing "car repair" | Notes about "vehicle maintenance," "fixing the truck" |
| "team planning" | Documents with "team planning" | Calendar events titled "Q2 kickoff," Deck cards about "project roadmap" |
| "pasta recipes" | Documents with "pasta recipes" | Notes about "Italian cooking," "homemade noodles," "carbonara tips" |
This works across multiple Nextcloud apps: Notes, Files (including PDFs with OCR), Deck cards, Calendar events, Contacts, and News/RSS items. One search bar, all your content, understood by meaning.
### Hybrid Search: Best of Both Worlds
Sometimes you want exact matches ("PROJ-2024-001"), sometimes you want semantic understanding ("that project from last year about authentication"). Astrolabe's hybrid search combines both approaches:
- **Semantic search** uses embeddings to find conceptually related content
- **BM25 keyword search** finds exact matches and important terms
- **Reciprocal Rank Fusion (RRF)** intelligently merges the results
You can adjust the balance or switch modes entirely depending on your needs.
![Unified Search Integration](https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/third_party/astrolabe/screenshots/01-unified-search-astrolabe.png?raw=1)
*Astrolabe results appear alongside traditional search in Nextcloud's unified search bar*
## Visualize Your Data Universe
Beyond search, Astrolabe includes an interactive 3D visualization that shows your documents positioned in semantic space. Similar documents cluster together. Topics form constellations. You can rotate, zoom, and explore.
This isn't just eye candy—it's a practical tool for knowledge discovery:
- **Find forgotten connections**: Search for your current project and watch as related documents from months ago light up nearby
- **Spot topic clusters**: See how your notes naturally group by subject
- **Explore the unknown**: Click on points near your search results to discover content you didn't know was related
The visualization uses Principal Component Analysis (PCA) to project high-dimensional embeddings (768 dimensions) down to 3D space while preserving the relationships between documents. We implemented a lightweight, custom PCA specifically for this—no heavyweight ML libraries required.
![3D Vector Visualization](https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/third_party/astrolabe/screenshots/02-semantic-search-with-plot.png?raw=1)
*Documents cluster by semantic similarity. The query point (red) shows your search, and related documents cluster nearby*
## Power Your AI Agents
Astrolabe isn't just for humans—it's for your AI assistants too.
The backend runs a **Model Context Protocol (MCP)** server, which means AI tools like Claude Desktop, Cursor, or custom agents can connect directly to your Nextcloud data. Your AI assistant can:
- Search your notes semantically ("Find everything related to the Kubernetes migration")
- Retrieve document content for context
- Get AI-generated answers with citations from your documents (RAG)
The critical point: **your data never leaves your infrastructure**. The MCP server runs on your hardware. Your AI assistant sends queries, the server returns results, and you maintain full control. No documents uploaded to third-party services.
### Retrieval-Augmented Generation (RAG)
Ask a question, and Astrolabe can retrieve relevant documents and have your AI synthesize an answer—complete with citations:
```
You: "What were the main issues we had deploying to production last month?"
Astrolabe finds: 3 relevant notes, 2 Deck cards, 1 calendar event
AI generates: "Based on your documents, there were three main issues:
1. Database migration timeout (see Note: 'Prod deploy 2024-01-15')
2. SSL certificate renewal (see Deck card: 'Ops Tasks')
3. Resource limits on the new pods (see Note: 'K8s troubleshooting')
```
This uses MCP's sampling capability—the server doesn't run its own LLM. Instead, it asks your client's AI to generate the response. You choose the model, you control the costs.
## Under the Hood
For the technically curious, here's how Astrolabe works:
### Embedding Providers
Astrolabe supports multiple backends for generating semantic embeddings:
- **Amazon Bedrock**: Enterprise-grade, Titan embeddings
- **OpenAI**: Direct OpenAI API or compatible endpoints (including GitHub Models)
- **Ollama**: Self-hosted, privacy-focused, runs entirely on your hardware
The system auto-detects available providers based on environment variables and falls back gracefully. Deploy Ollama on your server for full privacy, or use Bedrock for enterprise scale—same codebase, zero code changes.
### Background Indexing
Documents are indexed automatically via webhooks. When you create or edit a note, Nextcloud fires an event, and the MCP server processes it in the background. No manual sync required.
The indexing pipeline:
1. **Scanner** detects changes via ETags and modification timestamps
2. **Queue** manages backpressure (up to 10k pending documents)
3. **Worker pool** processes embeddings concurrently (configurable, default 3 workers)
4. **Qdrant** stores vectors for fast similarity search
### Lightweight by Design
We deliberately avoided heavyweight dependencies:
- **Custom PCA**: No scikit-learn, just efficient eigendecomposition
- **In-process async**: No separate message queues or worker processes—just anyio TaskGroups
- **Plugin architecture**: New apps (Notes, Calendar, etc.) are simple scanner/processor implementations
This means Astrolabe runs comfortably alongside your Nextcloud on modest hardware.
```
┌──────────────┐ ┌─────────────┐ ┌─────────┐
│ Nextcloud │────▶│ MCP Server │────▶│ Qdrant │
│ (Astrolabe) │◀────│ (Python) │◀────│ (Vectors)│
└──────────────┘ └─────────────┘ └─────────┘
│ │
│ OAuth/Token │ Embeddings
▼ ▼
┌────────┐ ┌──────────┐
│ User │ │ Ollama/ │
│Browser │ │ Bedrock │
└────────┘ └──────────┘
```
## Getting Started
### Requirements
- Nextcloud 31 or 32
- MCP server instance (Docker recommended)
- Vector database (Qdrant, included in Docker setup)
- Embedding provider (Ollama for self-hosted, or cloud options)
### Quick Setup
1. **Install the Astrolabe app** from the Nextcloud App Store (or manually)
2. **Start the MCP server** (Docker Compose makes this easy):
```bash
docker compose up -d mcp qdrant ollama
```
3. **Configure the connection** in your Nextcloud `config.php`:
```php
'astrolabe' => [
'mcp_server_url' => 'http://localhost:8000',
],
```
4. **Authorize access** in Settings → Personal → Astrolabe
5. **Start searching** using Nextcloud's unified search bar
For detailed setup instructions, including OAuth configuration and embedding provider options, see the [documentation](https://github.com/cbcoutinho/nextcloud-mcp-server).
## What Can You Index?
Astrolabe currently supports:
| App | What Gets Indexed |
|-----|-------------------|
| **Notes** | Full text and metadata |
| **Files** | PDFs (with OCR), DOCX, text files |
| **Deck** | Card titles and descriptions |
| **Calendar** | Event titles, descriptions, and details |
| **Contacts** | Names, notes, and contact information |
| **News** | RSS/Atom feed articles |
Each result shows the document type, relevance score, and a direct link to the source. For large documents, it shows which chunk (section) matched.
![Chunk Viewer](https://github.com/cbcoutinho/nextcloud-mcp-server/blob/master/third_party/astrolabe/screenshots/03-chunk-viewer-open.png?raw=1)
*Click a result to see the matching chunk in context*
## Who Is This For?
**Researchers and students**: Find all notes related to your thesis topic, even when you used different terminology across semesters. Discover connections between papers you read months apart.
**Teams and organizations**: Surface institutional knowledge that would otherwise stay buried. New team members can search for concepts instead of knowing exactly what to look for.
**Developers**: Connect your AI coding assistant to your Nextcloud. Give it access to project notes, meeting records, and documentation without copy-pasting context.
**Personal knowledge managers**: Discover forgotten documents related to your current work. Watch your knowledge base evolve over time through the visualization.
## Try It Out
Astrolabe is open source (AGPL) and ready to use. Your data universe has been waiting in the dark—it's time to turn on the lights.
- **Install**: [Nextcloud App Store](https://apps.nextcloud.com/apps/astrolabe)
- **Source**: [GitHub](https://github.com/cbcoutinho/nextcloud-mcp-server)
- **Documentation**: [Setup Guide](https://github.com/cbcoutinho/nextcloud-mcp-server/tree/master/docs)
- **Issues**: [Report bugs or request features](https://github.com/cbcoutinho/nextcloud-mcp-server/issues)
---
*Astrolabe is maintained by [Chris Coutinho](https://github.com/cbcoutinho). Contributions welcome.*
+151 -66
View File
@@ -255,18 +255,35 @@ class CalendarClient:
"""List events in a calendar within date range."""
calendar = self._get_calendar(calendar_name)
# Get all events using caldav library (now with proper filter)
events = await calendar.events()
if start_datetime or end_datetime:
# Build CalDAV REPORT with time-range filter for server-side filtering
events = await self._search_events_by_date(
calendar, start_datetime, end_datetime
)
# Expand is only used when both bounds are provided
expanded = bool(start_datetime and end_datetime)
else:
# No date filter — fetch all events
events = await calendar.events()
expanded = False
result = []
for event in events:
await event.load(only_if_unloaded=True)
if event.data:
event_dict = self._parse_ical_event(event.data)
if event_dict:
event_dict["href"] = str(event.url)
event_dict["etag"] = ""
result.append(event_dict)
if expanded:
# Server-side expansion: each response resource may contain
# multiple VEVENTs (one per recurrence occurrence)
for event_dict in self._parse_all_ical_events(event.data):
event_dict["href"] = str(event.url)
event_dict["etag"] = ""
result.append(event_dict)
else:
event_dict = self._parse_ical_event(event.data)
if event_dict:
event_dict["href"] = str(event.url)
event_dict["etag"] = ""
result.append(event_dict)
if len(result) >= limit:
break
@@ -274,6 +291,57 @@ class CalendarClient:
logger.debug(f"Found {len(result)} events")
return result
async def _search_events_by_date(
self,
calendar: AsyncCalendar,
start_datetime: Optional[dt.datetime] = None,
end_datetime: Optional[dt.datetime] = None,
) -> list:
"""Execute a CalDAV REPORT with time-range filter."""
from caldav.async_collection import AsyncEvent
from caldav.elements import cdav, dav
from lxml import etree # type: ignore[import-untyped]
# Ensure naive datetimes are treated as UTC
if start_datetime and start_datetime.tzinfo is None:
start_datetime = start_datetime.replace(tzinfo=dt.UTC)
if end_datetime and end_datetime.tzinfo is None:
end_datetime = end_datetime.replace(tzinfo=dt.UTC)
# Build comp-filter with time-range (mirrors sync Calendar.build_search_xml_query)
inner_comp_filter = cdav.CompFilter(name="VEVENT")
inner_comp_filter += cdav.TimeRange(start_datetime, end_datetime)
outer_comp_filter = cdav.CompFilter(name="VCALENDAR") + inner_comp_filter
filter_element = cdav.Filter() + outer_comp_filter
# When both bounds are provided, request server-side expansion of
# recurring events (RFC 4791 §9.6.5). Each occurrence is returned as
# a separate VEVENT with its own DTSTART, with RRULE stripped.
data = cdav.CalendarData()
if start_datetime and end_datetime:
data += cdav.Expand(start_datetime, end_datetime)
query = cdav.CalendarQuery() + [dav.Prop() + data] + filter_element
body = etree.tostring(
query.xmlelement(), encoding="utf-8", xml_declaration=True
)
assert calendar.client is not None
response = await calendar.client.report(str(calendar.url), body, depth=1)
# Parse response (same pattern as AsyncCalendar.search)
objects = []
response_data = response.expand_simple_props([cdav.CalendarData()])
for href, props in response_data.items():
if href == str(calendar.url):
continue
cal_data = props.get(cdav.CalendarData.tag)
if cal_data:
obj = AsyncEvent(client=calendar.client, data=cal_data, parent=calendar)
objects.append(obj)
return objects
async def create_event(
self, calendar_name: str, event_data: Dict[str, Any]
) -> Dict[str, Any]:
@@ -633,75 +701,92 @@ class CalendarClient:
cal.add_component(event)
return cal.to_ical().decode("utf-8")
def _extract_vevent_data(self, component) -> Dict[str, Any]:
"""Extract event data from a single VEVENT component.
Shared helper used by both _parse_ical_event() and _parse_all_ical_events().
"""
event_data: Dict[str, Any] = {
"uid": str(component.get("uid", "")),
"title": str(component.get("summary", "")),
"description": str(component.get("description", "")),
"location": str(component.get("location", "")),
"status": str(component.get("status", "CONFIRMED")),
"priority": int(component.get("priority", 5)),
"privacy": str(component.get("class", "PUBLIC")),
"url": str(component.get("url", "")),
}
# Handle dates
dtstart = component.get("dtstart")
if dtstart:
if isinstance(dtstart.dt, dt.date) and not isinstance(
dtstart.dt, dt.datetime
):
event_data["start_datetime"] = dtstart.dt.isoformat()
event_data["all_day"] = True
else:
event_data["start_datetime"] = dtstart.dt.isoformat()
event_data["all_day"] = False
dtend = component.get("dtend")
if dtend:
if isinstance(dtend.dt, dt.date) and not isinstance(dtend.dt, dt.datetime):
event_data["end_datetime"] = dtend.dt.isoformat()
else:
event_data["end_datetime"] = dtend.dt.isoformat()
# Handle categories
categories = component.get("categories")
if categories:
event_data["categories"] = self._extract_categories(categories)
# Handle recurrence
rrule = component.get("rrule")
if rrule:
event_data["recurring"] = True
event_data["recurrence_rule"] = str(rrule)
# Handle attendees
attendees = []
for attendee in component.get("attendee", []):
if isinstance(attendee, list):
attendees.extend(str(a).replace("mailto:", "") for a in attendee)
else:
attendees.append(str(attendee).replace("mailto:", ""))
if attendees:
event_data["attendees"] = ",".join(attendees)
return event_data
def _parse_ical_event(self, ical_text: str) -> Optional[Dict[str, Any]]:
"""Parse iCalendar text and extract event data."""
"""Parse iCalendar text and extract the first event."""
try:
cal = Calendar.from_ical(ical_text)
for component in cal.walk():
if component.name == "VEVENT":
event_data = {
"uid": str(component.get("uid", "")),
"title": str(component.get("summary", "")),
"description": str(component.get("description", "")),
"location": str(component.get("location", "")),
"status": str(component.get("status", "CONFIRMED")),
"priority": int(component.get("priority", 5)),
"privacy": str(component.get("class", "PUBLIC")),
"url": str(component.get("url", "")),
}
# Handle dates
dtstart = component.get("dtstart")
if dtstart:
if isinstance(dtstart.dt, dt.date) and not isinstance(
dtstart.dt, dt.datetime
):
event_data["start_datetime"] = dtstart.dt.isoformat()
event_data["all_day"] = True
else:
event_data["start_datetime"] = dtstart.dt.isoformat()
event_data["all_day"] = False
dtend = component.get("dtend")
if dtend:
if isinstance(dtend.dt, dt.date) and not isinstance(
dtend.dt, dt.datetime
):
event_data["end_datetime"] = dtend.dt.isoformat()
else:
event_data["end_datetime"] = dtend.dt.isoformat()
# Handle categories
categories = component.get("categories")
if categories:
event_data["categories"] = self._extract_categories(categories)
# Handle recurrence
rrule = component.get("rrule")
if rrule:
event_data["recurring"] = True
event_data["recurrence_rule"] = str(rrule)
# Handle attendees
attendees = []
for attendee in component.get("attendee", []):
if isinstance(attendee, list):
attendees.extend(
str(a).replace("mailto:", "") for a in attendee
)
else:
attendees.append(str(attendee).replace("mailto:", ""))
if attendees:
event_data["attendees"] = ",".join(attendees)
return event_data
return self._extract_vevent_data(component)
return None
except Exception as e:
logger.error(f"Error parsing iCalendar event: {e}")
return None
def _parse_all_ical_events(self, ical_text: str) -> list[Dict[str, Any]]:
"""Parse iCalendar text and extract ALL event occurrences.
Used with server-side expansion where a single VCALENDAR contains
multiple VEVENT components (one per recurrence occurrence).
"""
results: list[Dict[str, Any]] = []
try:
cal = Calendar.from_ical(ical_text)
for component in cal.walk():
if component.name == "VEVENT":
results.append(self._extract_vevent_data(component))
except Exception as e:
logger.error(f"Error parsing iCalendar events: {e}")
return results
def _merge_ical_properties(
self, raw_ical: str, event_data: Dict[str, Any], event_uid: str
) -> str:
+2 -2
View File
@@ -1,6 +1,6 @@
[project]
name = "nextcloud-mcp-server"
version = "0.62.0"
version = "0.63.3"
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"}
@@ -114,7 +114,7 @@ caldav = { git = "https://github.com/cbcoutinho/caldav", branch = "feature/httpx
qdrant-client = { git = "https://github.com/cbcoutinho/qdrant-client", branch = "fix/fusion-score-threshold" }
[build-system]
requires = ["uv_build>=0.9.4,<0.10.0"]
requires = ["uv_build>=0.10.0,<0.11.0"]
build-backend = "uv_build"
[tool.uv.build-backend]
@@ -380,6 +380,177 @@ async def test_event_with_url_and_categories(
raise
async def test_list_events_date_range_filtering(
nc_client: NextcloudClient, temporary_calendar: str
):
"""Test that date range filtering actually excludes events outside the range.
Reproduces GH-538: get_calendar_events() accepted date range parameters
but returned events from the entire calendar history, ignoring date filters.
"""
calendar_name = temporary_calendar
past_uid = None
future_uid = None
try:
# Create Event A: 30 days in the past
past_date = datetime.now() - timedelta(days=30)
past_event_data = {
"title": f"Past Event {uuid.uuid4().hex[:8]}",
"start_datetime": past_date.strftime("%Y-%m-%dT10:00:00"),
"end_datetime": past_date.strftime("%Y-%m-%dT11:00:00"),
"description": "Event in the past for date range test",
}
result_past = await nc_client.calendar.create_event(
calendar_name, past_event_data
)
past_uid = result_past["uid"]
logger.info(f"Created past event: {past_uid}")
# Create Event B: 1 day in the future
future_date = datetime.now() + timedelta(days=1)
future_event_data = {
"title": f"Future Event {uuid.uuid4().hex[:8]}",
"start_datetime": future_date.strftime("%Y-%m-%dT14:00:00"),
"end_datetime": future_date.strftime("%Y-%m-%dT15:00:00"),
"description": "Event in the future for date range test",
}
result_future = await nc_client.calendar.create_event(
calendar_name, future_event_data
)
future_uid = result_future["uid"]
logger.info(f"Created future event: {future_uid}")
# Query with date range: today → 7 days ahead
now = datetime.now()
week_ahead = now + timedelta(days=7)
events = await nc_client.calendar.get_calendar_events(
calendar_name=calendar_name,
start_datetime=now,
end_datetime=week_ahead,
limit=50,
)
event_uids = [e["uid"] for e in events]
# Future event (tomorrow) SHOULD be in results
assert future_uid in event_uids, (
f"Future event {future_uid} should be in date-filtered results"
)
# Past event (30 days ago) should NOT be in results
assert past_uid not in event_uids, (
f"Past event {past_uid} should be excluded by date range filter "
f"(GH-538: date range was being ignored)"
)
logger.info(
f"Date range filtering works: {len(events)} events returned, "
f"past event correctly excluded"
)
finally:
# Cleanup both events
for uid in [past_uid, future_uid]:
if uid:
try:
await nc_client.calendar.delete_event(calendar_name, uid)
except Exception as e:
logger.warning(f"Cleanup failed for event {uid}: {e}")
async def test_recurring_event_date_range_expansion(
nc_client: NextcloudClient, temporary_calendar: str
):
"""Test that recurring events are expanded into individual occurrences.
When querying with a date range, a recurring event should return one
event dict per occurrence within the range, each with the correct
start_datetime for that occurrence (not the original master event date).
This is a follow-up to GH-538: the time-range filter correctly selected
recurring events, but returned the master event with its original DTSTART
instead of expanding occurrences.
"""
calendar_name = temporary_calendar
event_uid = None
try:
# Create a daily recurring event starting 7 days ago
start = datetime.now() - timedelta(days=7)
event_data = {
"title": f"Daily Recurrence {uuid.uuid4().hex[:8]}",
"start_datetime": start.strftime("%Y-%m-%dT09:00:00"),
"end_datetime": start.strftime("%Y-%m-%dT10:00:00"),
"description": "Daily recurring event for expansion test",
"recurring": True,
"recurrence_rule": "FREQ=DAILY",
}
result = await nc_client.calendar.create_event(calendar_name, event_data)
event_uid = result["uid"]
logger.info(f"Created daily recurring event: {event_uid}")
# Query with date range: today → 3 days ahead
query_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
query_end = query_start + timedelta(days=3)
events = await nc_client.calendar.get_calendar_events(
calendar_name=calendar_name,
start_datetime=query_start,
end_datetime=query_end,
limit=50,
)
# Filter to only our recurring event (calendar may have others)
our_events = [e for e in events if e["uid"] == event_uid]
# Should have multiple occurrences (one per day in the range)
assert len(our_events) >= 2, (
f"Expected multiple expanded occurrences, got {len(our_events)}. "
f"Expansion may not be working."
)
# Each occurrence should have a different start_datetime
start_dates = [e["start_datetime"] for e in our_events]
assert len(set(start_dates)) == len(our_events), (
f"Each occurrence should have a unique start_datetime, got: {start_dates}"
)
# No start_datetime should fall outside the queried range
for e in our_events:
event_start = datetime.fromisoformat(e["start_datetime"])
# Remove timezone info for comparison if present
if event_start.tzinfo is not None:
event_start = event_start.replace(tzinfo=None)
assert event_start >= query_start - timedelta(hours=1), (
f"Occurrence {e['start_datetime']} is before query start {query_start}"
)
assert event_start < query_end + timedelta(hours=1), (
f"Occurrence {e['start_datetime']} is after query end {query_end}"
)
# Expanded occurrences should NOT have recurrence rules
# (server strips RRULE when expanding)
for e in our_events:
assert not e.get("recurring"), (
"Expanded occurrence should not have recurring=True, "
"RRULE should be stripped by server-side expansion"
)
logger.info(
f"Recurring event expansion works: {len(our_events)} occurrences "
f"returned with unique start dates"
)
finally:
if event_uid:
try:
await nc_client.calendar.delete_event(calendar_name, event_uid)
except Exception as e:
logger.warning(f"Cleanup failed for recurring event {event_uid}: {e}")
async def test_calendar_operations_error_handling(
nc_client: NextcloudClient,
):
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.commitizen]
name = "cz_conventional_commits"
version = "0.9.0"
version = "0.10.1"
tag_format = "astrolabe-v$version"
version_scheme = "semver"
update_changelog_on_bump = true
+18
View File
@@ -25,6 +25,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Requires external MCP server deployment
- See documentation for setup: https://github.com/cbcoutinho/nextcloud-mcp-server
## astrolabe-v0.10.1 (2026-02-03)
### Fix
- **helm**: add backward compatibility for legacy persistence configs
## astrolabe-v0.10.0 (2026-01-28)
### Feat
- **astrolabe**: add background token refresh job
### Fix
- **astrolabe**: add pagination and psalm fixes for token refresh
- **astrolabe**: add locking to prevent token refresh race condition
- **astrolabe**: add issued_at to on-demand token refresh
## astrolabe-v0.9.0 (2026-01-26)
### Feat
+1 -1
View File
@@ -29,7 +29,7 @@ Astrolabe connects to a semantic search service that understands the meaning of
See [documentation](https://github.com/cbcoutinho/nextcloud-mcp-server) for configuration details.
]]></description>
<version>0.9.0</version>
<version>0.10.1</version>
<licence>agpl</licence>
<author homepage="https://github.com/cbcoutinho">Chris Coutinho</author>
<namespace>Astrolabe</namespace>
+13 -13
View File
@@ -921,16 +921,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.60",
"version": "10.5.63",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "f2e26f52f80ef77832e359205f216eeac00e320c"
"reference": "33198268dad71e926626b618f3ec3966661e4d90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c",
"reference": "f2e26f52f80ef77832e359205f216eeac00e320c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90",
"reference": "33198268dad71e926626b618f3ec3966661e4d90",
"shasum": ""
},
"require": {
@@ -951,7 +951,7 @@
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
"sebastian/comparator": "^5.0.4",
"sebastian/comparator": "^5.0.5",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.4",
@@ -1002,7 +1002,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.60"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63"
},
"funding": [
{
@@ -1026,7 +1026,7 @@
"type": "tidelift"
}
],
"time": "2025-12-06T07:50:42+00:00"
"time": "2026-01-27T05:48:37+00:00"
},
{
"name": "psr/cache",
@@ -2452,16 +2452,16 @@
},
{
"name": "sebastian/comparator",
"version": "5.0.4",
"version": "5.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
"reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"shasum": ""
},
"require": {
@@ -2517,7 +2517,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5"
},
"funding": [
{
@@ -2537,7 +2537,7 @@
"type": "tidelift"
}
],
"time": "2025-09-07T05:25:07+00:00"
"time": "2026-01-24T09:25:16+00:00"
},
{
"name": "sebastian/complexity",
+66 -254
View File
@@ -1,12 +1,12 @@
{
"name": "astrolabe",
"version": "0.8.3",
"version": "0.10.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "astrolabe",
"version": "0.8.3",
"version": "0.10.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@nextcloud/axios": "^2.5.1",
@@ -16,7 +16,6 @@
"@nextcloud/router": "^3.0.1",
"@nextcloud/vue": "^9.3.3",
"markdown-it": "^14.1.0",
"pdfjs-dist": "^4.0.379",
"plotly.js-dist-min": "^3.0.0",
"vue": "^3.0.0",
"vue-material-design-icons": "^5.3.1"
@@ -1191,185 +1190,6 @@
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
"license": "Apache-2.0"
},
"node_modules/@napi-rs/canvas": {
"version": "0.1.84",
"license": "MIT",
"optional": true,
"workspaces": [
"e2e/*"
],
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@napi-rs/canvas-android-arm64": "0.1.84",
"@napi-rs/canvas-darwin-arm64": "0.1.84",
"@napi-rs/canvas-darwin-x64": "0.1.84",
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.84",
"@napi-rs/canvas-linux-arm64-gnu": "0.1.84",
"@napi-rs/canvas-linux-arm64-musl": "0.1.84",
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.84",
"@napi-rs/canvas-linux-x64-gnu": "0.1.84",
"@napi-rs/canvas-linux-x64-musl": "0.1.84",
"@napi-rs/canvas-win32-x64-msvc": "0.1.84"
}
},
"node_modules/@napi-rs/canvas-android-arm64": {
"version": "0.1.84",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.84.tgz",
"integrity": "sha512-pdvuqvj3qtwVryqgpAGornJLV6Ezpk39V6wT4JCnRVGy8I3Tk1au8qOalFGrx/r0Ig87hWslysPpHBxVpBMIww==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-darwin-arm64": {
"version": "0.1.84",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.84.tgz",
"integrity": "sha512-A8IND3Hnv0R6abc6qCcCaOCujTLMmGxtucMTZ5vbQUrEN/scxi378MyTLtyWg+MRr6bwQJ6v/orqMS9datIcww==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-darwin-x64": {
"version": "0.1.84",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.84.tgz",
"integrity": "sha512-AUW45lJhYWwnA74LaNeqhvqYKK/2hNnBBBl03KRdqeCD4tKneUSrxUqIv8d22CBweOvrAASyKN3W87WO2zEr/A==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
"version": "0.1.84",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.84.tgz",
"integrity": "sha512-8zs5ZqOrdgs4FioTxSBrkl/wHZB56bJNBqaIsfPL4ZkEQCinOkrFF7xIcXiHiKp93J3wUtbIzeVrhTIaWwqk+A==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
"version": "0.1.84",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.84.tgz",
"integrity": "sha512-i204vtowOglJUpbAFWU5mqsJgH0lVpNk/Ml4mQtB4Lndd86oF+Otr6Mr5KQnZHqYGhlSIKiU2SYnUbhO28zGQA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
"version": "0.1.84",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.84.tgz",
"integrity": "sha512-VyZq0EEw+OILnWk7G3ZgLLPaz1ERaPP++jLjeyLMbFOF+Tr4zHzWKiKDsEV/cT7btLPZbVoR3VX+T9/QubnURQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
"version": "0.1.84",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.84.tgz",
"integrity": "sha512-PSMTh8DiThvLRsbtc/a065I/ceZk17EXAATv9uNvHgkgo7wdEfTh2C3aveNkBMGByVO3tvnvD5v/YFtZL07cIg==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
"version": "0.1.84",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-x64-musl": {
"version": "0.1.84",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
"version": "0.1.84",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.84.tgz",
"integrity": "sha512-YSs8ncurc1xzegUMNnQUTYrdrAuaXdPMOa+iYYyAxydOtg0ppV386hyYMsy00Yip1NlTgLCseRG4sHSnjQx6og==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@@ -1657,9 +1477,9 @@
}
},
"node_modules/@nextcloud/vue": {
"version": "9.3.3",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-9.3.3.tgz",
"integrity": "sha512-M/M4L9vp1AJQ8RRk75mbMwUo7sOwWDaTDmAwgpTa9LARDe5e6UBJoMhOmiz5EPkYRHLn2SLE+baOIXVmtVMdqw==",
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-9.4.0.tgz",
"integrity": "sha512-MoEbaFqFeZfTB+8d/BtgObAfzJMQ+vdidzMP/zKzx9J4cW+vgY5bciDUueY+t3f0uwSJXO3xsqXXWj9x2KihzQ==",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@ckpack/vue-color": "^1.6.0",
@@ -1684,7 +1504,7 @@
"emoji-mart-vue-fast": "^15.0.5",
"escape-html": "^1.0.3",
"floating-vue": "^5.2.2",
"focus-trap": "7.6.6",
"focus-trap": "^7.8.0",
"linkifyjs": "^4.3.2",
"p-queue": "^9.1.0",
"rehype-external-links": "^3.0.0",
@@ -3003,22 +2823,22 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz",
"integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz",
"integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.26",
"@vue/shared": "3.5.27",
"entities": "^7.0.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-core/node_modules/entities": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz",
"integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -3028,26 +2848,26 @@
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz",
"integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz",
"integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.26",
"@vue/shared": "3.5.26"
"@vue/compiler-core": "3.5.27",
"@vue/shared": "3.5.27"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz",
"integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz",
"integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.26",
"@vue/compiler-dom": "3.5.26",
"@vue/compiler-ssr": "3.5.26",
"@vue/shared": "3.5.26",
"@vue/compiler-core": "3.5.27",
"@vue/compiler-dom": "3.5.27",
"@vue/compiler-ssr": "3.5.27",
"@vue/shared": "3.5.27",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.6",
@@ -3055,13 +2875,13 @@
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz",
"integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz",
"integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.26",
"@vue/shared": "3.5.26"
"@vue/compiler-dom": "3.5.27",
"@vue/shared": "3.5.27"
}
},
"node_modules/@vue/devtools-api": {
@@ -3095,53 +2915,53 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz",
"integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz",
"integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.26"
"@vue/shared": "3.5.27"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz",
"integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz",
"integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.26",
"@vue/shared": "3.5.26"
"@vue/reactivity": "3.5.27",
"@vue/shared": "3.5.27"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz",
"integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz",
"integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.26",
"@vue/runtime-core": "3.5.26",
"@vue/shared": "3.5.26",
"@vue/reactivity": "3.5.27",
"@vue/runtime-core": "3.5.27",
"@vue/shared": "3.5.27",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz",
"integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz",
"integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.26",
"@vue/shared": "3.5.26"
"@vue/compiler-ssr": "3.5.27",
"@vue/shared": "3.5.27"
},
"peerDependencies": {
"vue": "3.5.26"
"vue": "3.5.27"
}
},
"node_modules/@vue/shared": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz",
"integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz",
"integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==",
"license": "MIT"
},
"node_modules/@vuepic/vue-datepicker": {
@@ -5352,10 +5172,12 @@
}
},
"node_modules/focus-trap": {
"version": "7.6.6",
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz",
"integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
"license": "MIT",
"dependencies": {
"tabbable": "^6.3.0"
"tabbable": "^6.4.0"
}
},
"node_modules/follow-redirects": {
@@ -7879,16 +7701,6 @@
"node": ">=8"
}
},
"node_modules/pdfjs-dist": {
"version": "4.10.38",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
},
"optionalDependencies": {
"@napi-rs/canvas": "^0.1.65"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"license": "ISC"
@@ -10299,16 +10111,16 @@
}
},
"node_modules/vue": {
"version": "3.5.26",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz",
"integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==",
"version": "3.5.27",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz",
"integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.26",
"@vue/compiler-sfc": "3.5.26",
"@vue/runtime-dom": "3.5.26",
"@vue/server-renderer": "3.5.26",
"@vue/shared": "3.5.26"
"@vue/compiler-dom": "3.5.27",
"@vue/compiler-sfc": "3.5.27",
"@vue/runtime-dom": "3.5.27",
"@vue/server-renderer": "3.5.27",
"@vue/shared": "3.5.27"
},
"peerDependencies": {
"typescript": "*"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "astrolabe",
"version": "0.9.0",
"version": "0.10.1",
"license": "AGPL-3.0-or-later",
"engines": {
"node": "^22.0.0",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 736 KiB

After

Width:  |  Height:  |  Size: 218 KiB

+14 -14
View File
@@ -566,16 +566,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.60",
"version": "10.5.63",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "f2e26f52f80ef77832e359205f216eeac00e320c"
"reference": "33198268dad71e926626b618f3ec3966661e4d90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c",
"reference": "f2e26f52f80ef77832e359205f216eeac00e320c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90",
"reference": "33198268dad71e926626b618f3ec3966661e4d90",
"shasum": ""
},
"require": {
@@ -596,7 +596,7 @@
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
"sebastian/comparator": "^5.0.4",
"sebastian/comparator": "^5.0.5",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.4",
@@ -647,7 +647,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.60"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63"
},
"funding": [
{
@@ -671,7 +671,7 @@
"type": "tidelift"
}
],
"time": "2025-12-06T07:50:42+00:00"
"time": "2026-01-27T05:48:37+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -843,16 +843,16 @@
},
{
"name": "sebastian/comparator",
"version": "5.0.4",
"version": "5.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
"reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"shasum": ""
},
"require": {
@@ -908,7 +908,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5"
},
"funding": [
{
@@ -928,7 +928,7 @@
"type": "tidelift"
}
],
"time": "2025-09-07T05:25:07+00:00"
"time": "2026-01-24T09:25:16+00:00"
},
{
"name": "sebastian/complexity",
@@ -1687,5 +1687,5 @@
"platform-overrides": {
"php": "8.1"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.9.0"
}
Generated
+1 -1
View File
@@ -1988,7 +1988,7 @@ wheels = [
[[package]]
name = "nextcloud-mcp-server"
version = "0.62.0"
version = "0.63.3"
source = { editable = "." }
dependencies = [
{ name = "aiosqlite" },