nextcloud-organizer-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. nextcloud_organizer_mcp-0.1.0/.env.example +96 -0
  2. nextcloud_organizer_mcp-0.1.0/.github/dependabot.yml +18 -0
  3. nextcloud_organizer_mcp-0.1.0/.github/workflows/ci.yml +37 -0
  4. nextcloud_organizer_mcp-0.1.0/.github/workflows/integration.yml +104 -0
  5. nextcloud_organizer_mcp-0.1.0/.github/workflows/release.yml +48 -0
  6. nextcloud_organizer_mcp-0.1.0/.gitignore +27 -0
  7. nextcloud_organizer_mcp-0.1.0/.pre-commit-config.yaml +25 -0
  8. nextcloud_organizer_mcp-0.1.0/CHANGELOG.md +664 -0
  9. nextcloud_organizer_mcp-0.1.0/CLAUDE.md +14 -0
  10. nextcloud_organizer_mcp-0.1.0/CONTRIBUTING.md +74 -0
  11. nextcloud_organizer_mcp-0.1.0/LICENSE +21 -0
  12. nextcloud_organizer_mcp-0.1.0/PKG-INFO +467 -0
  13. nextcloud_organizer_mcp-0.1.0/README.md +441 -0
  14. nextcloud_organizer_mcp-0.1.0/SECURITY.md +89 -0
  15. nextcloud_organizer_mcp-0.1.0/assets/logo-mark.png +0 -0
  16. nextcloud_organizer_mcp-0.1.0/assets/logo-mark.svg +7 -0
  17. nextcloud_organizer_mcp-0.1.0/assets/logo.png +0 -0
  18. nextcloud_organizer_mcp-0.1.0/assets/logo.svg +14 -0
  19. nextcloud_organizer_mcp-0.1.0/assets/social-preview.png +0 -0
  20. nextcloud_organizer_mcp-0.1.0/docs/architecture.md +155 -0
  21. nextcloud_organizer_mcp-0.1.0/docs/deployment.md +290 -0
  22. nextcloud_organizer_mcp-0.1.0/docs/tools.md +1901 -0
  23. nextcloud_organizer_mcp-0.1.0/pyproject.toml +122 -0
  24. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/__init__.py +3 -0
  25. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/admin.py +234 -0
  26. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/caldav_client.py +4272 -0
  27. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/config.py +251 -0
  28. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/errors.py +98 -0
  29. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/event_mapping.py +1365 -0
  30. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/mapping.py +2130 -0
  31. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/notes_client.py +218 -0
  32. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/notes_mapping.py +80 -0
  33. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/notes_sections.py +140 -0
  34. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/personal_auth.py +637 -0
  35. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/py.typed +0 -0
  36. nextcloud_organizer_mcp-0.1.0/src/nextcloud_organizer_mcp/server.py +2202 -0
  37. nextcloud_organizer_mcp-0.1.0/tests/conftest.py +168 -0
  38. nextcloud_organizer_mcp-0.1.0/tests/test_admin.py +266 -0
  39. nextcloud_organizer_mcp-0.1.0/tests/test_auth.py +437 -0
  40. nextcloud_organizer_mcp-0.1.0/tests/test_auth_lifecycle.py +367 -0
  41. nextcloud_organizer_mcp-0.1.0/tests/test_caldav_client.py +7486 -0
  42. nextcloud_organizer_mcp-0.1.0/tests/test_config.py +591 -0
  43. nextcloud_organizer_mcp-0.1.0/tests/test_event_mapping.py +2099 -0
  44. nextcloud_organizer_mcp-0.1.0/tests/test_integration.py +772 -0
  45. nextcloud_organizer_mcp-0.1.0/tests/test_mapping.py +2406 -0
  46. nextcloud_organizer_mcp-0.1.0/tests/test_notes_client.py +558 -0
  47. nextcloud_organizer_mcp-0.1.0/tests/test_notes_mapping.py +104 -0
  48. nextcloud_organizer_mcp-0.1.0/tests/test_notes_sections.py +195 -0
  49. nextcloud_organizer_mcp-0.1.0/tests/test_server.py +2064 -0
  50. nextcloud_organizer_mcp-0.1.0/uv.lock +2723 -0
@@ -0,0 +1,96 @@
1
+ # Required. Base URL of your Nextcloud instance (no path), e.g. https://cloud.example.com.
2
+ # Used for the Notes app's REST API and as the base from which NEXTCLOUD_CALDAV_URL is
3
+ # derived when unset. Reuses NEXTCLOUD_USERNAME/NEXTCLOUD_APP_PASSWORD.
4
+ # Must be https:// - the server refuses to start with a http:// URL unless it
5
+ # points at a local address (localhost/127.0.0.1/::1) or
6
+ # NEXTCLOUD_ALLOW_INSECURE_HTTP=1 is set (see below), since http:// sends this
7
+ # app password in cleartext Basic Auth on every request.
8
+ NEXTCLOUD_BASE_URL=https://cloud.example.com
9
+ NEXTCLOUD_USERNAME=your-nextcloud-username
10
+ NEXTCLOUD_APP_PASSWORD=your-nextcloud-app-password
11
+
12
+ # Optional. DAV endpoint root. Defaults to <NEXTCLOUD_BASE_URL>/remote.php/dav/.
13
+ # Only needed when the DAV endpoint is not <base>/remote.php/dav/ (e.g. custom reverse
14
+ # proxy or different host). Both NEXTCLOUD_BASE_URL and NEXTCLOUD_CALDAV_URL must point
15
+ # at the same Nextcloud instance.
16
+ # NEXTCLOUD_CALDAV_URL=https://cloud.example.com/remote.php/dav/
17
+
18
+ # Optional. How long (in seconds) to wait for a CalDAV or Notes API HTTP
19
+ # request to Nextcloud before giving up. Without a timeout, a hung Nextcloud
20
+ # server would hang this server forever. Default: 30.
21
+ # NEXTCLOUD_HTTP_TIMEOUT_SECONDS=30
22
+
23
+ # Only needed if NEXTCLOUD_CALDAV_URL or NEXTCLOUD_BASE_URL above is genuinely
24
+ # http:// and does NOT point at localhost/127.0.0.1/::1 (e.g. an
25
+ # internal-network Nextcloud without its own TLS). WARNING: this sends your
26
+ # Nextcloud app password in cleartext on every request - do not set this for
27
+ # a real deployment.
28
+ # NEXTCLOUD_ALLOW_INSECURE_HTTP=1
29
+
30
+ # OAuth 2.1 authentication (PersonalAuthProvider). The server is a Claude
31
+ # custom connector, authenticated via OAuth + PKCE instead of a static token.
32
+
33
+ # Required. The exact public URL clients (Claude) use to reach this server -
34
+ # must match precisely, including scheme, or the OAuth discovery/redirect
35
+ # flow will fail. Locally this is http://127.0.0.1:8000; in production it's
36
+ # your public Tailscale Funnel HTTPS URL, e.g.
37
+ # https://your-host.your-tailnet.ts.net
38
+ PUBLIC_BASE_URL=http://127.0.0.1:8000
39
+
40
+ # Password gate on the OAuth /authorize step. Required (the server refuses
41
+ # to start without it) whenever PUBLIC_BASE_URL above is not localhost, or
42
+ # MCP_HOST below is bound to a non-local address (e.g. 0.0.0.0) - the
43
+ # redirect-domain allow-list alone does NOT stop a scripted client from
44
+ # registering itself via the (intentionally open) DCR endpoint and
45
+ # self-issuing a valid access token, since it never has to actually control
46
+ # the redirect domain, only claim one that's on the allow-list. This password
47
+ # is the only real gate once the server is reachable from the public
48
+ # internet (Tailscale Funnel). Not needed for local-only use
49
+ # (PUBLIC_BASE_URL=http://127.0.0.1:... or http://localhost:... AND
50
+ # MCP_HOST=127.0.0.1/localhost).
51
+ #
52
+ # Uncomment and set this to a long random secret for any non-localhost
53
+ # deployment - the server will refuse to start with the placeholder value
54
+ # below, and will also refuse to start with no password at all once it's
55
+ # reachable from anywhere but localhost. Generate a value with:
56
+ # python3 -c "import secrets; print(secrets.token_urlsafe(24))"
57
+ # MCP_OAUTH_PASSWORD=change-me-to-a-long-random-password
58
+
59
+ # Optional, comma-separated. Only these domains (and subdomains) may
60
+ # complete the OAuth redirect. Default when unset: claude.ai,claude.com for a
61
+ # non-localhost PUBLIC_BASE_URL (a "localhost" entry in a public deployment's
62
+ # allow-list can never be reached by a real OAuth redirect anyway); for a
63
+ # localhost PUBLIC_BASE_URL, claude.ai,claude.com,localhost.
64
+ # MCP_OAUTH_ALLOWED_REDIRECT_DOMAINS=claude.ai,claude.com,localhost
65
+
66
+ # Optional. How long issued access tokens are valid, in seconds.
67
+ # Default: 2592000 (30 days).
68
+ # MCP_OAUTH_ACCESS_TOKEN_EXPIRY_SECONDS=2592000
69
+
70
+ # Optional. How long issued refresh tokens are valid, in seconds, before a
71
+ # client must fully re-authorize (D5) - bounds how long a leaked
72
+ # oauth_tokens.json grants access for. Default: 15552000 (180 days).
73
+ # MCP_OAUTH_REFRESH_TOKEN_EXPIRY_SECONDS=15552000
74
+
75
+ # Optional. Directory where OAuth client registrations and tokens are
76
+ # persisted (oauth_tokens.json) so they survive server restarts.
77
+ # Default: .oauth-state (relative to the working directory).
78
+ # MCP_OAUTH_STATE_DIR=.oauth-state
79
+
80
+ # Networking (optional, defaults shown). The server only needs to bind to
81
+ # a local port - tailscale funnel handles TLS termination in front of it.
82
+ MCP_HOST=127.0.0.1
83
+ MCP_PORT=8000
84
+
85
+ # Optional. Default timezone for naive datetime inputs, day windows (agenda/filter bounds),
86
+ # and output timestamps returned to callers. Default: Europe/Berlin.
87
+ # Setting MCP_DEFAULT_TIMEZONE=UTC restores the previous UTC behavior.
88
+ # MCP_DEFAULT_TIMEZONE=Europe/Berlin
89
+
90
+ # Set to 1 to enable integration tests that hit the real Nextcloud instance
91
+ # configured above (see README for details). Leave unset for unit tests only.
92
+ # RUN_INTEGRATION_TESTS=1
93
+
94
+ # Name of an existing (ideally disposable) task list to use for integration
95
+ # tests. Tasks are created and deleted in this list during the test run.
96
+ # INTEGRATION_TEST_LIST=Test
@@ -0,0 +1,18 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "uv"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ groups:
8
+ python-dependencies:
9
+ patterns: ["*"]
10
+ update-types: ["minor", "patch"]
11
+
12
+ - package-ecosystem: "github-actions"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "weekly"
16
+ groups:
17
+ github-actions:
18
+ patterns: ["*"]
@@ -0,0 +1,37 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ lint-and-test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ matrix:
12
+ python-version: ["3.10", "3.12"]
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v6
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ enable-cache: true
21
+ cache-dependency-glob: "uv.lock"
22
+
23
+ - name: Install dependencies
24
+ run: uv sync --locked
25
+
26
+ - name: Lint with ruff
27
+ run: |
28
+ uv run ruff check .
29
+ uv run ruff format --check .
30
+
31
+ - name: Type check with mypy
32
+ run: uv run mypy src tests
33
+
34
+ - name: Run unit tests
35
+ run: |
36
+ uv run pytest -q --cov=src/nextcloud_organizer_mcp --cov-report=term-missing \
37
+ --cov-fail-under=90
@@ -0,0 +1,104 @@
1
+ # Scheduled integration test run against a real (containerized) Nextcloud
2
+ # instance (E11). This complements ci.yml's unit tests, which mock the
3
+ # `caldav` library entirely and never touch a real CalDAV server.
4
+ #
5
+ # NOTE: this workflow could not be executed/verified end-to-end while writing
6
+ # it (no way to run GitHub Actions service containers in this environment).
7
+ # The env vars it sets are wired to exactly what tests/test_integration.py
8
+ # reads (NEXTCLOUD_CALDAV_URL / NEXTCLOUD_USERNAME / NEXTCLOUD_APP_PASSWORD /
9
+ # INTEGRATION_TEST_LIST / RUN_INTEGRATION_TESTS), and the Nextcloud
10
+ # auto-setup env vars below follow the documented `nextcloud` Docker image
11
+ # contract - but the full flow (image boot, auto-install, calendar creation,
12
+ # first real test run) has only been reviewed, not observed. If it fails on
13
+ # its first scheduled/dispatched run, start by checking the "Wait for
14
+ # Nextcloud to be ready" and "Provision the test task list" steps' output.
15
+ name: Integration tests
16
+
17
+ on:
18
+ schedule:
19
+ # Mondays at 04:00 UTC.
20
+ - cron: "0 4 * * 1"
21
+ workflow_dispatch: {}
22
+
23
+ # Deliberately no `push`/`pull_request` trigger: this hits a real (if
24
+ # ephemeral, containerized) Nextcloud instance and is too slow/flaky to gate
25
+ # every PR on.
26
+
27
+ jobs:
28
+ integration:
29
+ runs-on: ubuntu-latest
30
+ # Fail the workflow (and surface it) on any test failure - no silent
31
+ # green runs.
32
+ continue-on-error: false
33
+
34
+ services:
35
+ nextcloud:
36
+ image: nextcloud:latest
37
+ ports:
38
+ - 8080:80
39
+ env:
40
+ # Auto-setup env vars documented by the official `nextcloud` image's
41
+ # entrypoint: presence of NEXTCLOUD_ADMIN_USER/PASSWORD plus a
42
+ # database config (SQLITE_DATABASE here, to avoid needing a
43
+ # separate DB service container for a throwaway test instance)
44
+ # triggers `occ maintenance:install` automatically on first boot.
45
+ SQLITE_DATABASE: nextcloud
46
+ NEXTCLOUD_ADMIN_USER: admin
47
+ NEXTCLOUD_ADMIN_PASSWORD: ${{ secrets.INTEGRATION_NEXTCLOUD_ADMIN_PASSWORD || 'nextcloud-ci-admin-pw' }}
48
+
49
+ env:
50
+ NEXTCLOUD_USERNAME: admin
51
+ # Real deployments use a per-app password generated under Settings ->
52
+ # Security; for this disposable, network-isolated CI instance the admin
53
+ # account password is used directly to keep provisioning simple - it
54
+ # only exists for the lifetime of the job's service container.
55
+ NEXTCLOUD_APP_PASSWORD: ${{ secrets.INTEGRATION_NEXTCLOUD_ADMIN_PASSWORD || 'nextcloud-ci-admin-pw' }}
56
+ NEXTCLOUD_CALDAV_URL: "http://localhost:8080/remote.php/dav/"
57
+ INTEGRATION_TEST_LIST: "Test"
58
+ RUN_INTEGRATION_TESTS: "1"
59
+ # The container is plain http:// (no TLS) - Settings.from_env() would
60
+ # normally refuse that (D8), but tests/test_integration.py builds
61
+ # CalDavService directly and never goes through Settings, so this
62
+ # isn't actually read today. Set anyway in case that changes.
63
+ NEXTCLOUD_ALLOW_INSECURE_HTTP: "1"
64
+
65
+ steps:
66
+ - uses: actions/checkout@v4
67
+
68
+ - name: Install uv
69
+ uses: astral-sh/setup-uv@v6
70
+ with:
71
+ python-version: "3.12"
72
+ enable-cache: true
73
+ cache-dependency-glob: "uv.lock"
74
+
75
+ - name: Install dependencies
76
+ run: uv sync --locked
77
+
78
+ - name: Wait for Nextcloud to be ready
79
+ run: |
80
+ for i in $(seq 1 60); do
81
+ if curl -sf http://localhost:8080/status.php | grep -q '"installed":true'; then
82
+ echo "Nextcloud is up and installed."
83
+ exit 0
84
+ fi
85
+ echo "Waiting for Nextcloud... ($i/60)"
86
+ sleep 5
87
+ done
88
+ echo "Nextcloud did not become ready in time." >&2
89
+ exit 1
90
+
91
+ - name: Provision the test task list
92
+ run: |
93
+ container_id="$(docker ps -q --filter ancestor=nextcloud:latest)"
94
+ # The calendar app (bundled with Nextcloud, provides the CalDAV
95
+ # VTODO backend Tasks/this server both use) may ship disabled by
96
+ # default - make sure it's on before creating a calendar in it.
97
+ docker exec "$container_id" php occ app:enable calendar
98
+ # `dav:create-calendar` (part of the bundled DAV app) creates a
99
+ # calendar (= task list, from this server's point of view) for a
100
+ # user by uid.
101
+ docker exec "$container_id" php occ dav:create-calendar "$NEXTCLOUD_USERNAME" "$INTEGRATION_TEST_LIST"
102
+
103
+ - name: Run integration tests
104
+ run: uv run pytest tests/test_integration.py -q
@@ -0,0 +1,48 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - name: Install uv
14
+ uses: astral-sh/setup-uv@v6
15
+
16
+ - name: Check tag matches project version
17
+ run: |
18
+ version="$(uv version --short)"
19
+ if [ "v${version}" != "${GITHUB_REF_NAME}" ]; then
20
+ echo "Tag ${GITHUB_REF_NAME} does not match project version ${version}" >&2
21
+ exit 1
22
+ fi
23
+
24
+ - name: Build sdist and wheel
25
+ run: uv build
26
+
27
+ - uses: actions/upload-artifact@v4
28
+ with:
29
+ name: dist
30
+ path: dist/
31
+
32
+ publish:
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ environment:
36
+ name: pypi
37
+ url: https://pypi.org/p/nextcloud-organizer-mcp
38
+ permissions:
39
+ # Required for PyPI trusted publishing (OIDC); no API token is stored.
40
+ id-token: write
41
+ steps:
42
+ - uses: actions/download-artifact@v4
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+
47
+ - name: Publish to PyPI
48
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,27 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+
6
+ .venv/
7
+ venv/
8
+ env/
9
+
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+ .coverage
14
+ htmlcov/
15
+ dist/
16
+ build/
17
+
18
+ .env
19
+ .env.local
20
+
21
+ # Persisted OAuth clients/tokens (PersonalAuthProvider), contains secrets.
22
+ .oauth-state/
23
+
24
+ .DS_Store
25
+
26
+ # graphify skill output (generated knowledge-graph artifacts, not project source).
27
+ graphify-out/
@@ -0,0 +1,25 @@
1
+ # Local hooks that shell out to `uv run`, so the tool versions used here are
2
+ # always the ones pinned in uv.lock - no separate, potentially-drifting
3
+ # pre-commit-managed toolchain to keep in sync (E10). Run `pre-commit install`
4
+ # once (see CONTRIBUTING.md) to wire these into `git commit`.
5
+ repos:
6
+ - repo: local
7
+ hooks:
8
+ - id: ruff-check
9
+ name: ruff check --fix
10
+ entry: uv run ruff check --fix
11
+ language: system
12
+ types: [python]
13
+
14
+ - id: ruff-format
15
+ name: ruff format
16
+ entry: uv run ruff format
17
+ language: system
18
+ types: [python]
19
+
20
+ - id: mypy
21
+ name: mypy src tests
22
+ entry: uv run mypy src tests
23
+ language: system
24
+ pass_filenames: false
25
+ types: [python]