Commit Graph
2202 Commits
Author SHA1 Message Date
Chris CoutinhoandClaude Opus 4.7 9cf4e16672 test: poll Astrolabe search until the target note is indexed
The previous run on nc32 failed at the search-result assertion because
`wait_for_vector_sync` returned on the first indexed-count bump (deck
seed cards) before this specific note hit Qdrant. Replace the single
search call with a poll that retries every 2s until the unique term
returns our note, or times out after 60s with a loud diagnostic. The
previously-observed flake would now wait past the deck-card indexing
window rather than racing it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:55:16 +02:00
Chris CoutinhoandClaude Opus 4.7 b3bd14f183 test: tighten regression guard and hoist httpx import
Per review:
- Hoist `import httpx` out of the two test function bodies and into
  the module imports at the top of
  test_astrolabe_chunk_context.py.
- Simplify the regression guard in
  test_management_chunk_context_endpoint.py to use
  `mock.assert_awaited_once_with(...)` instead of manually unpacking
  call_args. This is stricter — it fails loudly on signature change —
  and matches the canonical pattern for asserting mock calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:26:41 +02:00
Chris CoutinhoandClaude Opus 4.7 06d871ce22 test: address chunk-context review comments
- Rename test_chunk_context_endpoint_handles_missing_app_password to
  test_chunk_context_endpoint_rejects_invalid_bearer so it reflects
  what is actually exercised: an invalid bearer is rejected upfront at
  validate_token_and_get_user, not at the NotProvisionedError branch.
  The NotProvisionedError path is covered by the corresponding unit
  test in test_management_chunk_context_endpoint.py.
- Hoist `import base64` to module level per PEP 8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:17:34 +02:00
Chris CoutinhoandClaude Opus 4.7 bea5c3f5ee test: include Nextcloud CSRF token in chunk-context integration test
Astrolabe's ApiController endpoints (search, chunk-context) require a
CSRF `requesttoken` header — axios picks it up from OC.requestToken
automatically in the SPA, but page.request.get() does not.

The first CI run failed on the search step with 412 CSRF check failed
before reaching the chunk-context assertion that was supposed to
surface the handler bug. Load the Astrolabe page, read OC.requestToken,
and pass it on both calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:15:05 +02:00
Chris CoutinhoandClaude Opus 4.7 8a0d06107e fix(api): use stored app password for chunk-context and pdf-preview
The /api/v1/chunk-context and /api/v1/pdf-preview handlers in
api/visualization.py forwarded the incoming OAuth bearer directly to
Nextcloud via NextcloudClient.from_token. In multi-user BasicAuth mode
Nextcloud has no validator for those bearers on Notes/WebDAV, so it
treats the request as anonymous and returns 401 — surfaced to the user
as a 500 from /apps/astrolabe/api/chunk-context. Search worked because
it only hits Qdrant.

Architecturally, OAuth is only for Astrolabe→MCP server; MCP server→
Nextcloud always uses the per-user app password stored during provision
(background sync already does this via vector.oauth_sync).

- Resolve the Nextcloud client through get_user_client_basic_auth in
  both get_chunk_context and get_pdf_preview, surfacing
  NotProvisionedError as a clean 401 instead of opaque 500.
- Apply the same fix to the session-cookie variant in
  auth/viz_routes.chunk_context_endpoint for the internal viz UI.

Tests:
- New unit file test_management_chunk_context_endpoint.py, including a
  regression guard that asserts get_user_client_basic_auth is awaited
  (so reverting to from_token fails without needing a live Nextcloud).
- Updated test_management_pdf_preview_endpoint.py to mock the new auth
  path (drops extract_bearer_token / NextcloudClient.from_token patches).
- New integration test test_astrolabe_chunk_context.py drives the full
  chain (browser → Astrolabe → MCP → Nextcloud) in multi-user BasicAuth
  mode, plus bare-bones 401 checks on the MCP endpoint.

Full unit suite: 546 passed.

Companion PR on astrolabe (cbcoutinho/astrolabe#66) sends the Nextcloud
UID as loginName in the app-password POST body so the stored record is
complete. Submodule bump to that branch will follow once CI reproduces
the failure on the old submodule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:50:13 +02:00
github-actions[bot] d766f3c014 bump: version 0.72.3 → 0.72.4 2026-04-16 22:38:24 +00:00
Chris CoutinhoandGitHub 5b6942282c Merge pull request #711 from cbcoutinho/fix/mcp-client-session-cancel-scope
fix(tests): convert create_mcp_client_session to asynccontextmanager
2026-04-17 00:38:04 +02:00
Chris CoutinhoandClaude Opus 4.6 ea7e86f7f4 chore: update astrolabe submodule to v0.13.9
Picks up astrolabe/astrolabe#61 which fixes app password provisioning
failure caused by loginName mismatch in ITokenProvider::generateToken().
This was the root cause of vector sync never indexing in multi-user
BasicAuth mode, which caused the plotly visualization test to fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 00:25:47 +02:00
Chris CoutinhoandClaude Opus 4.6 3935f45be8 fix(tests): convert create_mcp_client_session to asynccontextmanager
The multi-user-basic integration job was consistently failing with
`CancelledError: Cancelled via cancel scope ... by <async_generator_athrow>`
followed by a cascade of `anyio.ClosedResourceError` in every subsequent
test. Root cause: `create_mcp_client_session` was declared as an async
generator driven by `async for session in ...:`, so Python's generator
finalizer (`aclose`) ran under pytest-asyncio's cleanup task instead of
the task that owned the nested `streamablehttp_client` cancel scope.
anyio then raised when the inner task group saw its scope being exited
from a foreign task, leaving the memory object streams half-closed and
poisoning the rest of the session.

Switching to `@asynccontextmanager` + `async with ... as session:` makes
`__aenter__`/`__aexit__` run in the frame that owns the context manager,
satisfying anyio's structured concurrency requirements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:58:47 +02:00
github-actions[bot] 9b6b554155 bump: version 0.72.2 → 0.72.3 2026-04-15 10:22:31 +00:00
Chris CoutinhoandGitHub e0ed533a54 Merge pull request #709 from dylanlangston/master
Fix: Nutrition.calories field rejects integer values from Nextcloud Cookbook API
2026-04-15 12:22:06 +02:00
DylanandGitHub 06a8262e0e Merge pull request #1 from dylanlangston/copilot/fix-issue-708
Fix Nutrition model to accept numeric values from Cookbook API
2026-04-15 01:05:16 -04:00
480504f8b5 raise ValueError for bool inputs instead of returning as-is
Agent-Logs-Url: https://github.com/dylanlangston/nextcloud-mcp-server/sessions/0360ab28-8913-450e-8c61-697e71ba9742

Co-authored-by: dylanlangston <16236219+dylanlangston@users.noreply.github.com>
2026-04-14 23:04:18 +00:00
6ae30acc6f address review: exclude bool from coercion, parameterize tests over all fields
Agent-Logs-Url: https://github.com/dylanlangston/nextcloud-mcp-server/sessions/0360ab28-8913-450e-8c61-697e71ba9742

Co-authored-by: dylanlangston <16236219+dylanlangston@users.noreply.github.com>
2026-04-14 23:02:56 +00:00
cb88d2b062 fix: coerce numeric nutrition values to strings in Cookbook model (fixes #708)
Agent-Logs-Url: https://github.com/dylanlangston/nextcloud-mcp-server/sessions/d7163bb6-ad5d-4406-8d11-145c5317ec19

Co-authored-by: dylanlangston <16236219+dylanlangston@users.noreply.github.com>
2026-04-14 22:49:34 +00:00
github-actions[bot] fea84dd646 bump: version 0.72.1 → 0.72.2 2026-04-14 19:52:21 +00:00
Chris CoutinhoandGitHub 80822e00e7 Merge pull request #707 from cbcoutinho/fix/uvx-pypi-deployment
fix: enable uvx/PyPI deployments without Docker assumptions
2026-04-14 21:51:58 +02:00
Chris CoutinhoandClaude Opus 4.6 512de1f6b0 test: address PR #707 reviewer feedback on config path helpers
- _resolve_settings_files() now raises FileNotFoundError when
  NEXTCLOUD_MCP_SETTINGS_FILE points to a missing file, instead of
  silently falling back to defaults (footgun on typos).
- .secrets.toml is now looked for alongside the explicit settings file
  when NEXTCLOUD_MCP_SETTINGS_FILE is set, matching user expectation for
  /etc-style deployments. Unset behaviour (cwd lookup) is unchanged.
- get_token_db_path() drops the redundant os.environ.get() short-circuit;
  TOKEN_STORAGE_DB is already bound through dynaconf because the key is
  declared in _DEFAULTS.
- is_ephemeral_token_db() docstring documents the "must call
  get_token_db_path() first" precondition.
- alembic.ini comment clarifies the ./tokens.db placeholder is cwd-relative
  by design and points readers at the -x database_url escape hatch.
- New tests/unit/test_config_paths.py (12 tests) covering the ephemeral
  tempfile lifecycle, the TOKEN_STORAGE_DB override path, and all six
  _resolve_settings_files() cases including the two new behaviours.

Full unit suite now at 476 passed (464 + 12 new). Ruff + ty clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 21:14:42 +02:00
Chris CoutinhoandClaude Opus 4.6 146b622ebf fix: enable uvx/PyPI deployments without Docker assumptions
Two bugs made `uvx --from . nextcloud-mcp-server run` (and any pip install)
unusable outside Docker:

1. Dynaconf was configured with ignore_unknown_envvars=True and relied on
   settings.toml to declare the key schema. With no settings.toml in a wheel
   install, every env var (NEXTCLOUD_HOST, MCP_DEPLOYMENT_MODE, ...) was
   silently dropped. Moved the schema into a Python _DEFAULTS dict passed
   directly to Dynaconf, kept settings.toml as an optional external override
   (renamed to settings.toml.example, gitignored), and pointed docker-compose
   at the example file.

2. Token SQLite DB defaulted to /app/data/tokens.db in multiple places
   (auth/storage.py, migrations.py, alembic/env.py, cli.py db subcommands),
   which blew up at uvicorn startup with FileNotFoundError on non-Docker
   hosts. Replaced with a new config.get_token_db_path() helper that
   resolves TOKEN_STORAGE_DB if explicitly set, otherwise allocates a
   per-process tempfile cleaned up at interpreter exit via atexit — mirroring
   the "ephemeral by default" pattern used for QDRANT_LOCATION=:memory:.

Containers are unaffected: docker-compose services now explicitly set
TOKEN_STORAGE_DB=/app/data/tokens.db (the fourth service that was missing
this pin has been brought in line with the other three).

Verified end-to-end in an isolated /tmp venv: env-var-only startup, Alembic
migrations run against the tempfile, Application startup complete, /health/live
returns 200, tempfile deleted on SIGTERM. Unit tests (464) + ruff + ty pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 20:11:23 +02:00
Chris Coutinho fd1846de03 ci: Bump astrolabe 2026-04-11 20:16:32 +02:00
Chris CoutinhoandGitHub edb6c8d820 Merge pull request #643 from cbcoutinho/renovate/uv_build-0.x
chore(deps): update dependency uv_build to >=0.11.5,<0.12.0
2026-04-09 18:12:03 +02:00
renovate-bot-cbcoutinho[bot]andGitHub e72a751fdb chore(deps): update dependency uv_build to >=0.11.5,<0.12.0 2026-04-08 22:17:06 +00:00
Chris CoutinhoandGitHub 26e632c1e2 Merge pull request #644 from cbcoutinho/renovate/ghcr.io-astral-sh-uv-0.x
chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.11.4
2026-04-08 15:12:59 +02:00
Chris CoutinhoandGitHub 6b6a5eb639 Merge pull request #698 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.90
2026-04-08 14:06:51 +02:00
renovate-bot-cbcoutinho[bot]andGitHub 4fbdd471ad chore(deps): update anthropics/claude-code-action action to v1.0.90 2026-04-08 10:19:11 +00:00
renovate-bot-cbcoutinho[bot]andGitHub 9c37f4d61d chore(deps): update ghcr.io/astral-sh/uv docker tag to v0.11.4 2026-04-08 04:15:10 +00:00
github-actions[bot] 214fead311 bump: version 0.72.0 → 0.72.1 2026-04-07 23:28:00 +00:00
Chris CoutinhoandGitHub 94226bdcfa Merge pull request #697 from cbcoutinho/fix/strip-resource-scope-prefix
fix: strip resource server prefix from JWT scopes
2026-04-08 01:27:39 +02:00
Chris CoutinhoandClaude Opus 4.6 2c0b764aae fix: strip resource server prefix from JWT scopes for tool filtering
External IdPs like AWS Cognito return scopes prefixed with the resource
server identifier (e.g. https://mcp.example.com/notes.read). MCP tools
use bare scope names (notes.read) in @require_scopes decorators. Without
stripping the prefix, scope matching fails and only identity-only tools
(openid/profile/email) are visible — resulting in 4/125 tools shown.

Strip the OIDC_RESOURCE_SERVER_ID prefix in both get_access_token_scopes()
(used by list_tools filtering) and the require_scopes decorator (used at
tool execution time).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 01:27:06 +02:00
github-actions[bot] 107fe7911f bump: version 0.71.0 → 0.72.0 2026-04-07 22:26:11 +00:00
Chris CoutinhoandClaude Opus 4.6 33d679e174 feat: add --version option to CLI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 00:24:50 +02:00
github-actions[bot] 4922fb3ac5 bump: version 0.70.4 → 0.71.0 2026-04-07 22:10:12 +00:00
Chris CoutinhoandGitHub 21e4174df3 Merge pull request #689 from cbcoutinho/feat/stdio-transport
feat: add stdio transport support for local MCP usage
2026-04-08 00:09:49 +02:00
github-actions[bot] 98a0ba3745 bump: version 0.70.3 → 0.70.4 2026-04-07 22:03:06 +00:00
Chris CoutinhoandGitHub e76e3e1306 Merge pull request #690 from cbcoutinho/fix/cognito-offline-access-scope
fix: conditionally include offline_access based on IdP discovery
2026-04-08 00:02:35 +02:00
Chris CoutinhoandClaude Opus 4.6 f340380898 fix: address third round of review feedback
Add BasicAuthLifespanContext Protocol to make the contract between
StdioContext and get_client() explicit and type-safe. Document why
mcp.get_context() is required for non-template resources. Add News
and Collectives to README Supported Apps table, fix transport default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:59:39 +02:00
Chris CoutinhoandClaude Opus 4.6 7730f926cb fix: conditionally include offline_access based on IdP discovery
AWS Cognito provides refresh tokens automatically with the authorization
code flow but does not list offline_access as a supported scope. Check
the IdP's scopes_supported discovery field before including it in
requests, and always accept refresh tokens from responses regardless.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:48:49 +02:00
Chris CoutinhoandClaude Opus 4.6 1af85bc05e fix: address second round of review feedback
- Remove dead monkeypatch in test_stdio_calls_get_stdio_mcp
- Add _reload_config() teardown to single_user_env fixture
- Tighten AVAILABLE_APPS type to Callable[[FastMCP], None]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:26:46 +02:00
Chris CoutinhoandClaude Opus 4.6 e9c46a04a0 fix: address PR review feedback and fix CI test failures
- Revert default transport to streamable-http (not a breaking change)
- Extract AVAILABLE_APPS constant to server/__init__.py (DRY)
- Wrap get_stdio_mcp ValueError in click.ClickException for clean errors
- Fix test_stdio.py: call _reload_config() so dynaconf sees env changes
- Use lazy %-style logging in stdio.py
- Add private API comments in test assertions
- Derive --enable-app CLI choices from AVAILABLE_APPS
- README: show explicit --transport stdio in uvx examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:03:12 +02:00
Chris CoutinhoandClaude Opus 4.6 09006fcea9 feat: add stdio transport support for local MCP usage
Add a lightweight stdio transport path so users can run the server
locally with MCP clients like Claude Code using `uvx nextcloud-mcp-server run`.

- New `nextcloud_mcp_server/stdio.py` with minimal FastMCP setup for
  single-user BasicAuth (no OAuth, semantic search, or background sync)
- Default transport changed from streamable-http to stdio
- Dockerfile updated to explicitly use streamable-http for containers
- CLI `--enable-app` now includes news, collectives, and sharing
- README Quick Start section with uvx and MCP client config examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:48:14 +02:00
github-actions[bot] 776e2ce693 bump: version 0.70.2 → 0.70.3 2026-04-07 20:23:42 +00:00
Chris CoutinhoandGitHub 23995ce64d Merge pull request #675 from cbcoutinho/renovate/mcp-1.x
fix(deps): update dependency mcp to >=1.27,<1.28
2026-04-07 22:23:22 +02:00
Chris CoutinhoandGitHub 9934a78970 Merge pull request #688 from cbcoutinho/ci/remove-pat-from-release-workflows
ci: remove PAT from release workflows
2026-04-07 22:11:37 +02:00
Chris CoutinhoandClaude Opus 4.6 1e380caade ci: remove PAT from release workflows, use workflow_call instead
Tags pushed with GITHUB_TOKEN don't trigger other workflows (GitHub's
anti-recursion protection), which is why a PAT was needed. Instead,
chain release and docker workflows directly via workflow_call from
bump-version, eliminating the need for a personal access token.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:10:25 +02:00
github-actions[bot] 3af670ace5 bump: version 0.70.1 → 0.70.2 2026-04-07 18:32:27 +00:00
Chris CoutinhoandClaude Opus 4.6 f8fb34d113 fix: conditionally include offline_access in Flow 2 scope request
Flow 2 hardcoded offline_access in the scope string, but providers
like AWS Cognito don't support this scope (they handle refresh tokens
via client config). This caused invalid_scope errors on the Astrolabe
semantic search enablement flow.

Only include offline_access when enable_offline_access is explicitly
set, matching the behavior of DCR scope registration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:31:55 +02:00
renovate-bot-cbcoutinho[bot]andGitHub c84df249a0 fix(deps): update dependency mcp to >=1.27,<1.28 2026-04-07 16:19:01 +00:00
github-actions[bot] c2b19a2983 bump: version 0.70.0 → 0.70.1 2026-04-07 16:08:16 +00:00
Chris CoutinhoandGitHub 0dbd031fe7 Merge pull request #687 from cbcoutinho/fix/cognito-audience-fallback
fix: fall back to client_id when aud claim is absent (Cognito compat)
2026-04-07 18:07:50 +02:00
Chris CoutinhoandClaude Opus 4.6 c3da7acc87 fix: fall back to client_id when aud claim is absent (Cognito compat)
AWS Cognito access tokens do not include an `aud` claim per RFC 7519 —
they use `client_id` instead. This causes `_has_mcp_audience` to reject
all Cognito-issued tokens with "Missing MCP audience. Got []".

When `aud` is empty, fall back to the `client_id` JWT claim for audience
validation. The MCP server's own client_id will be present there since
the AS proxy exchanges the authorization code using its credentials.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 18:06:15 +02:00