Commit Graph
2274 Commits
Author SHA1 Message Date
github-actions[bot] 0c2d3e1086 bump: version 0.75.1 → 0.75.2 2026-04-30 12:49:42 +00:00
Chris CoutinhoandGitHub bec105d8d2 Merge pull request #747 from cbcoutinho/fix/webhook-receiver-uri-and-handler
fix(webhooks): wire receiver to vector sync queue and fix registered URI
2026-04-30 14:49:16 +02:00
Chris CoutinhoandClaude Opus 4.7 affe29f72e fix(webhooks): escape webhook_uri, lazy logging, document 401 header omission
Address round-5 reviewer feedback on PR #747:

- Escape `webhook_uri` in the admin pane HTML template so an operator-
  controlled env value (`WEBHOOK_INTERNAL_URL`, `NEXTCLOUD_MCP_SERVER_URL`)
  can't inject markup. The sibling `preset_id` and exception messages were
  already escaped — this one was the odd one out.
- Convert the eight remaining f-string `logger.warning`/`logger.error`
  calls in `api/webhooks.py` to lazy `%s` formatting, matching the style
  already adopted by `webhook_receiver.py` and `webhook_routes.py`.
- Document why the 401 from `handle_nextcloud_webhook` deliberately omits
  `WWW-Authenticate`: NC's webhook delivery worker has no auth-flow state
  machine to negotiate against, the bearer is a static shared secret
  configured out-of-band via `WEBHOOK_SECRET`, and a challenge response
  wouldn't change client behaviour. The existing warning log already
  records the rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 14:37:34 +02:00
Chris CoutinhoandGitHub 6e74112122 Merge pull request #700 from cbcoutinho/renovate/anthropics-claude-code-action-1.x
chore(deps): update anthropics/claude-code-action action to v1.0.110
2026-04-30 14:18:57 +02:00
Chris CoutinhoandClaude Opus 4.7 4a3857aabb fix(webhooks): escape HTML in error responses, compare bearer as bytes
Address the two Security findings from PR review:

- webhook_receiver: encode Authorization header and expected bearer to
  utf-8 bytes before hmac.compare_digest. Conventional form; doesn't
  rely on Python's implicit ASCII encoding.
- webhook_routes: html.escape user-influenced and exception-derived
  strings before interpolating into HTMLResponse content. Covers the
  preset_id path param echoed in the "Unknown preset" branch and the
  str(e) text rendered on handler exceptions.

Adds regression tests verifying compare_digest is invoked on bytes and
that <script> payloads (in preset_id and exception messages) are
emitted as escaped entities, not active markup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 14:17:45 +02:00
renovate-bot-cbcoutinho[bot]andGitHub 88b186d245 chore(deps): update anthropics/claude-code-action action to v1.0.110 2026-04-30 04:23:37 +00:00
Chris CoutinhoandClaude Opus 4.7 c1368b9a7f refactor(webhooks): bound queue waits, route URLs through dynaconf
Addresses round-3 review feedback on PR #747:

- webhook_receiver: wrap send_stream.send() in anyio.fail_after(1.0)
  and return 503 with reason="queue full" if the queue is saturated.
  Avoids pinning the handler until NC's outbound timeout fires; the
  503 retry contract is the same as the existing "sync not running"
  branch.
- webhook_receiver: revise the compare_digest comment to match what
  the function actually guarantees — it avoids the per-character
  short-circuit of `==` but is not fully constant-time across length
  differences.
- _get_webhook_uri: read WEBHOOK_INTERNAL_URL and
  NEXTCLOUD_MCP_SERVER_URL via dynaconf so operators using
  settings.toml (rather than env vars) aren't silently routed into
  the docker/localhost fallback. Adds webhook_internal_url to
  Settings/_DEFAULTS/_field_map; nextcloud_mcp_server_url already
  existed. Docker-detection markers stay on os.getenv since they're
  container-runtime signals, not user-facing config.
- webhook_routes: sweep remaining f-string logger calls to lazy %s
  formatting per CLAUDE.md.
- client/webhooks: modernise full file's type hints to
  dict / list / | None per CLAUDE.md.

Tests:
- New test_returns_503_when_queue_is_full exercises the timeout
  branch with a saturated buffer and a shortened deadline.
- test_webhook_uri tests now patch get_settings (matching the
  auth-pair tests in the same file) instead of monkeypatching env
  vars directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 04:17:00 +02:00
Chris Coutinho 42675e7c20 Merge remote-tracking branch 'origin/master' into fix/webhook-receiver-uri-and-handler 2026-04-30 04:14:45 +02:00
Chris CoutinhoandGitHub 616b5680b4 Merge pull request #743 from cbcoutinho/docs/login-flow-v2-and-astrolabe-cloud
docs: pivot to Login Flow v2; add Astrolabe Cloud hosted offering
2026-04-30 04:14:16 +02:00
Chris CoutinhoandClaude Opus 4.7 367816e4e4 docs: round-4 reviewer nits
Address four small items from the latest PR #743 review:

- login-flow-v2.md Compose example: add an inline comment +
  follow-up note pointing readers at Docker secrets for
  TOKEN_ENCRYPTION_KEY (the snippet is likely to be copy-pasted
  into production).
- auth-flows.md: rename the third column in the Astrolabe → MCP
  Server diagram from "Nextcloud OIDC" to "OIDC Provider" so the
  diagram matches the multi-IdP framing in the surrounding prose.
- login-flow-v2.md OAuth Endpoints section: rewrite the
  ambiguous "token issuance still comes from the IdP" line to
  make the cryptographic separation explicit — the MCP server
  exposes /token, but tokens are signed by the IdP's key and
  validated against its JWKS; the MCP server has no signing keys
  of its own.
- README.md auth bullet: replace the jargony "OAuth-to-MCP
  supported, with app-password conversion to Nextcloud" with the
  reviewer's clearer wording: "MCP clients authenticate via
  OAuth, the server handles Nextcloud app passwords
  transparently".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 04:07:43 +02:00
Chris CoutinhoandClaude Opus 4.7 f5f05b7c84 refactor(webhooks): address PR review on auth-pass
- webhook_receiver: always run hmac.compare_digest (drop the
  `not provided or` short-circuit) so the constant-time path is
  taken regardless of whether the Authorization header is present.
- client/webhooks: modernise the new `auth_data` type hint to
  `dict[str, str] | None` per CLAUDE.md.
- tests/client: rename `test_create_webhook_with_auth_headers` →
  `test_create_webhook_with_static_headers` and use
  `auth_method="header"` (NC's webhook_listeners only supports
  "none" and "header"; the previous "bearer" value was invalid).
- auth/webhook_routes: extract `_register_preset_webhooks` from
  `enable_webhook_preset` so the auth-threading behaviour is
  testable without standing up a Starlette app + auth middleware.
- tests/unit: new test_webhook_routes_register covering the helper
  with secret set / unset, and verifying ids round-trip in order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 04:02:26 +02:00
Chris CoutinhoandClaude Opus 4.7 224428fca5 fix(webhooks): authenticate deliveries via WEBHOOK_SECRET; review nits
Adds optional shared-secret authentication for /webhooks/nextcloud,
addressing the security follow-up flagged in #747.

Behavior:
- WEBHOOK_SECRET set: registrations pass authMethod="header" with
  authData={"Authorization": "Bearer <secret>"} (encrypted at-rest in
  Nextcloud's DB and forwarded on every delivery). The receiver
  validates the same header with hmac.compare_digest before parsing
  any payload; missing/invalid → 401.
- WEBHOOK_SECRET unset: registrations stay on authMethod="none" and
  the receiver accepts unauthenticated POSTs (logging a one-time
  startup warning). Backward compatible — operators can roll out at
  their own pace.

Implementation notes:
- WebhooksClient.create_webhook gains an `auth_data` parameter mapped
  to NC's `authData` body field; this is distinct from the existing
  `headers` parameter (`headers` is plaintext static request headers,
  `authData` is encrypted at-rest in NC and only emitted when
  authMethod="header"). The previous `auth_method="bearer"` mention in
  the docstring was incorrect — NC supports only "none" and "header".
- A small `webhook_auth_pair()` helper in auth/webhook_routes.py
  centralises the secret→(auth_method, auth_data) resolution so the
  preset flow and the Astrolabe-facing /api/v1/webhooks endpoint stay
  in sync.

Also addresses the smaller review points from #747:
- f-string → lazy %s formatting in webhook_receiver.py and
  webhook_routes.py.
- Move `int(time)` inside webhook_parser's try/except so a malformed
  `time` field returns None instead of raising ValueError.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 03:50:28 +02:00
Chris CoutinhoandClaude Opus 4.7 2e2a098bee fix(webhooks): wire receiver to vector sync queue and fix registered URI
The /webhooks/nextcloud endpoint was a no-op stub that logged the
payload and returned 200 OK; webhook deletions never reached Qdrant.
Compounding that, _get_webhook_uri() registered the docker-compose
internal hostname (http://mcp:8000) with Nextcloud whenever
/.dockerenv existed — including ECS Fargate — so cloud deployments
were registering a URL NC could not resolve.

- New vector/webhook_parser.py extracts a DocumentTask from
  NodeCreatedEvent / NodeWrittenEvent / BeforeNodeDeletedEvent
  payloads scoped to */files/Notes/*.md (matching the registered
  preset filters).
- New vector/webhook_receiver.py pushes that task onto the same
  send-stream the scanner uses (app.state.document_send_stream),
  with 503 when sync is not running so NC retries delivery.
- _get_webhook_uri() now prefers NEXTCLOUD_MCP_SERVER_URL over the
  /.dockerenv branch, so the explicit public URL set on cloud tasks
  wins; docker-compose dev still falls back to the internal name when
  no public URL is configured.

Calendar / Tables event parsing is intentionally out of scope here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 03:13:50 +02:00
Chris CoutinhoandClaude Opus 4.7 0c6b766e7e docs: fix scope naming and round-3 reviewer feedback
The docs claimed scopes are mcp:-prefixed (mcp:notes.read,
mcp:notes.write) and that the notes.* pair "covers all Nextcloud
apps". Both are false. Per @require_scopes decorators across
nextcloud_mcp_server/server/, scopes are unprefixed and per-app:
notes.read/write, talk.read/write, files.read/write,
calendar.read/write, contacts.read/write, deck.read/write,
news.read, tables.read/write, cookbook.read/write,
todo.read/write, collectives.read/write, sharing.write,
semantic.read, plus standard OIDC scopes.

Changes:

- login-flow-v2.md: replace the false 2-row "covers all apps"
  scope table with the real per-app reference (links to
  scope_authorization.discover_all_scopes() as authoritative
  source); strip mcp: prefix from intro paragraph, sequence
  diagrams, @require_scopes example, WWW-Authenticate header
  example. Also fix sticky-session keying advice per reviewer:
  route on user identity (sub claim) rather than the raw bearer
  token, since tokens rotate on refresh.
- auth-flows.md: clarify "Astrolabe (hosted UI) → MCP" matrix
  column header; strip mcp: from sequence diagram and key
  characteristics bullet; correct "issued by MCP server" to
  "issued by configured IdP" on the Login Flow v2 token.
- authentication.md: strip mcp: from the high-level diagram and
  scope-enforcement prose; cross-link to the scope reference.
- configuration.md: add NEXTCLOUD_OIDC_CLIENT_ID,
  NEXTCLOUD_OIDC_CLIENT_SECRET, and OIDC_DISCOVERY_URL to the
  Login Flow v2 vars table — these were undocumented in the
  table after the round-2 multi-IdP fix.
- running.md: drop deprecated `version: '3.8'` from compose
  snippets (Compose v2 ignores it and emits warnings).
- testing-oidc-consent.md: fix sample authorize URL and consent
  description to use real scope names instead of mcp:-prefixed
  ones (the manual test as written would have failed with
  invalid_scope).
- CLAUDE.md: replace dead links to deleted oauth-architecture.md,
  oauth-setup.md, and audience-validation-setup.md with
  login-flow-v2.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 03:05:33 +02:00
Chris CoutinhoandClaude Opus 4.7 d21be6d5e1 docs: generalize OIDC framing to support multiple IdPs
Previous round narrowed the framing too far in the other direction —
made it sound like Nextcloud OIDC is *the* IdP. The MCP server
actually supports any OIDC-compliant provider (Nextcloud's built-in
OIDC, Keycloak, AWS Cognito, Auth0, etc.) selected via
`OIDC_DISCOVERY_URL`. `NEXTCLOUD_OIDC_CLIENT_ID/SECRET` are generic
OIDC client credentials despite the Nextcloud-flavored naming.

Code references:
- IdP discovery: app.py:607-668 (auto-detects integrated vs external
  by comparing discovered issuer to NEXTCLOUD_HOST)
- JWKS: unified_verifier.py:71-73 (dynamically discovered, not
  hard-coded to Nextcloud)
- IdP selection knob: OIDC_DISCOVERY_URL (config.py)

Changes:
- login-flow-v2.md: redraw "How It Works" diagram to show the IdP as
  a separate component; replace "Nextcloud OIDC" with "configurable
  IdP" framing throughout; add OIDC_DISCOVERY_URL to the env-var
  reference; clarify NEXTCLOUD_OIDC_CLIENT_ID/SECRET are generic OIDC
  creds; rename "OAuth Endpoints" subtitle to point at "the configured
  IdP".
- running.md: rewrite the OAuth Mode intro and Quick Start note to
  mention IdP configurability and OIDC_DISCOVERY_URL.
- configuration.md: update Best Practices "For Production" multi-user
  bullet to reference the IdP selector and generic-creds caveat.
- auth-flows.md: generalize Astrolabe-flow and Login Flow v2
  characteristics bullets — IdP and JWKS source are configurable.
- keycloak-multi-client-validation.md: REMOVE the "deprecated"
  banner I added in 35c115e. The doc covers active behavior in
  external-IdP mode (realm-level token validation by user_oidc),
  not retired direct-OAuth-to-Nextcloud architecture. Replaced with
  a scope note pointing at when this applies.

oauth-impersonation-findings.md keeps its deprecation banner — that
doc *is* about the rejected service-account / impersonation path
(ADR-002 Tier 2, "Will Not Implement"), so the deprecation framing
remains correct there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:34:38 +02:00
Chris CoutinhoandClaude Opus 4.7 319e82774e docs: correct OIDC architecture framing for Login Flow v2
The previous round of review feedback rested on a misunderstanding —
that the MCP server is "the OAuth issuer" under Login Flow v2 and that
NEXTCLOUD_OIDC_CLIENT_ID/SECRET are external-IdP-only. Code says
otherwise (app.py:619/625/703-717, unified_verifier.py:72):

- The MCP server is an OIDC relying party of Nextcloud OIDC. Tokens are
  signed by Nextcloud and validated against Nextcloud's JWKS in all
  modes — the server has no private signing keys.
- Static NEXTCLOUD_OIDC_CLIENT_ID/SECRET are the preferred way to
  register the MCP server as that relying party; RFC 7591 DCR is a
  fallback when both are unset.
- Login Flow v2 layers per-user app-password acquisition on top — it
  governs the MCP→Nextcloud data leg, not the relying-party setup.

This commit reverts the inaccuracies introduced by 35c115e and reframes
the original `login-flow-v2.md` to match what the code does:

- login-flow-v2.md: revise "How It Works" to describe the MCP server
  as an OIDC RP + OAuth facade (not a standalone issuer); rename
  "OAuth Issuer Endpoints" → "OAuth Endpoints" with a note that those
  endpoints front Nextcloud OIDC; add NEXTCLOUD_OIDC_CLIENT_ID/SECRET
  to the required env vars with DCR documented as fallback.
- running.md: restore the static-creds Docker example (deleted in
  35c115e on the wrong reasoning that it was tied to the retired
  direct-OAuth-to-Nextcloud flow); rewrite the OAuth Mode section
  intro to describe the actual relying-party + facade architecture.
- configuration.md: fix Best Practices "For Production" to mention
  static creds as preferred / DCR as fallback; restore the .oauth
  Docker volume alongside data so DCR-registered MCP-client state and
  the encrypted app-password DB both persist.
- auth-flows.md: drop the note added in 35c115e that wrongly claimed
  the MCP server validates Bearer tokens against its own JWKS under
  Login Flow v2 — it validates against Nextcloud's JWKS in all modes;
  reword the Login Flow v2 "Key characteristics" bullet that called
  the MCP server "the OAuth authorization server".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:29:01 +02:00
github-actions[bot] 111ae988f9 bump: version 0.75.0 → 0.75.1 2026-04-30 00:07:27 +00:00
Chris CoutinhoandGitHub b9a85dda0b Merge pull request #746 from cbcoutinho/fix/vector-sync-status-oauth
fix(vector-sync): wire document streams into OAuthAppContext
2026-04-30 02:07:09 +02:00
Chris CoutinhoandClaude Opus 4.7 783adeca6e fix(vector-sync): wire document streams into OAuthAppContext
The nc_get_vector_sync_status MCP tool was returning hardcoded
status="unknown", indexed=0, pending=0 for all OAuth deployments
because OAuthAppContext lacked the document_receive_stream field.
The tool's getattr() lookup against the lifespan context returned
None and triggered an early-return before the Qdrant count query.

Add the four vector-sync fields to OAuthAppContext (matching
AppContext) and populate them from the _vector_sync_state singleton
at the lifespan yield site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:06:40 +02:00
Chris CoutinhoandClaude Opus 4.7 35c115ead6 docs: address remaining Login Flow v2 review feedback
Round-2 cleanup of PR #743 review comments not covered by d153e96:

- configuration.md: fix broken `#multi-user-oauth-modes` anchor; replace
  with the two real anchors (Multi-User BasicAuth, Login Flow v2). Rewrite
  the stale "always use OAuth2/OIDC with pre-configured clients" Best
  Practices section to reflect the post-pivot mode matrix, and update the
  Docker volume example to mount the encrypted app-password store
  (`TOKEN_STORAGE_DB`) rather than obsolete `.oauth` client storage.
- semantic-search-architecture.md: rename remaining body references from
  the deprecated `VECTOR_SYNC_ENABLED` to `ENABLE_SEMANTIC_SEARCH` so the
  doc matches configuration.md / troubleshooting.md.
- running.md: relabel "OAuth Mode (Recommended)" as
  "Login Flow v2 / OAuth issuer mode (--oauth)", drop the misleading
  "(Legacy)" suffix from BasicAuth, drop the
  `NEXTCLOUD_OIDC_CLIENT_ID/SECRET` example (tied to the retired
  direct-OAuth-to-Nextcloud flow), and add a note explaining what
  `--oauth` actually enables post-pivot.
- keycloak-multi-client-validation.md, oauth-impersonation-findings.md:
  add a deprecation banner pointing at ADR-022 / Login Flow v2. Files
  retained because ADR-002 and CLAUDE.md still cite them.
- auth-flows.md: clarify under the Astrolabe → MCP diagram that the
  Nextcloud-OIDC JWKS path applies to Multi-User BasicAuth; under
  Login Flow v2 the MCP server validates tokens against its own JWKS.
- login-flow-v2.md: clarify the sticky-session note — affinity must key
  on the OAuth bearer token (or user-bound cookie), not source IP, since
  MCP clients may not maintain stable IPs across the provisioning flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:57:16 +02:00
Chris Coutinho 074d20998c Merge remote-tracking branch 'origin/master' into docs/login-flow-v2-and-astrolabe-cloud 2026-04-30 01:36:03 +02:00
Chris CoutinhoandClaude Opus 4.7 d153e96520 docs: address Login Flow v2 review feedback
Fix issues raised by reviewer on PR #743:

- troubleshooting.md: renumber "Getting Help" steps (4→3, 5→4) after
  earlier consolidation left a gap
- installation.md: drop stale "OIDC app" prerequisite; admin access is
  now optional under Login Flow v2 (works on stock Nextcloud 16+)
- semantic-search-architecture.md: rename VECTOR_SYNC_ENABLED to
  ENABLE_SEMANTIC_SEARCH in the Status callout (renamed in v0.58.0)
- configuration.md: remove Quick Start references to deprecated
  oauth-multi-user / oauth-advanced templates and point to
  login-flow-v2.md; update "OAuth, Multi-User BasicAuth" label to
  "Login Flow v2, Multi-User BasicAuth"
- auth-flows.md: fix background-sync diagram so Encrypt+persist step
  no longer crosses into the Nextcloud column

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:27:53 +02:00
github-actions[bot] 404b551ec5 bump: version 0.74.0 → 0.75.0 2026-04-29 23:27:51 +00:00
Chris CoutinhoandGitHub a2711b4bda Merge pull request #741 from cbcoutinho/feat/talk-spreed-integration
feat(talk): add MCP integration for Nextcloud Talk (spreed)
2026-04-30 01:26:47 +02:00
github-actions[bot] 9f74dcb83d bump: version 0.73.2 → 0.74.0 2026-04-29 23:24:08 +00:00
Chris CoutinhoandGitHub 4bf6937827 Merge pull request #745 from cbcoutinho/feat/strict-auth-allowlists
feat(auth): drop test-client defaults, add ALLOWED_MGMT_CLIENT allowlist
2026-04-30 01:23:07 +02:00
Chris CoutinhoandClaude Opus 4.7 bd7702ad12 feat(auth): drop test-client defaults, add ALLOWED_MGMT_CLIENT allowlist
Both auth surfaces now fail-closed by default:

- ALLOWED_MCP_CLIENTS: removed the silent `claude-desktop` and
  `test-mcp-client` fallbacks. Empty/unset env var leaves the registry
  empty so /oauth/authorize rejects every client_id.
- ALLOWED_MGMT_CLIENT (new): comma-separated list of OIDC client_ids
  whose tokens are accepted by /api/management/*. Enforced in
  verify_token_for_management_api on both the cache-hit and cache-miss
  paths against the token's client_id claim. Unset/empty rejects all.

Compose: set ALLOWED_MGMT_CLIENT=nextcloudMcpServerUIPublicClient on
mcp-multi-user-basic so the existing Astrolabe integration test
(test_astrolabe_chunk_context.py) still passes.

env.sample documents both vars and notes they may be consolidated later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:20:21 +02:00
github-actions[bot] 9eba5f1ed4 bump: version 0.73.1 → 0.73.2 2026-04-29 23:20:11 +00:00
Chris CoutinhoandGitHub 9824a24775 Merge pull request #744 from cbcoutinho/fix/oidc-discovery-follow-redirects
fix(oauth): follow redirects when fetching OIDC discovery
2026-04-30 01:19:49 +02:00
Chris CoutinhoandClaude Opus 4.7 d4760f64dc fix(oauth): follow redirects when fetching OIDC discovery
Nextcloud installs without pretty URLs return a 301 from
`/.well-known/openid-configuration` to
`/index.php/.well-known/openid-configuration` (e.g. Hetzner StorageShare).
`_get_cached_discovery` did not enable follow_redirects, so httpx raised
HTTPStatusError on the 301 and the AS-proxy authorize handler returned
500, breaking client connections (e.g. claude.ai).

Pass `follow_redirects=True` to the httpx client used for the discovery
fetch only — downstream OIDC endpoints (token, userinfo, etc.) are
absolute URLs read from the discovery doc and are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:18:46 +02:00
Chris CoutinhoandClaude Opus 4.7 f075540232 fix(talk): address remaining PR #741 reviewer feedback
Closes the seven outstanding items from the @claude review on PR #741:

1. Add empty `tests/client/talk/__init__.py` for pytest discovery parity
   with `tests/client/{collectives,news}/`.
2. Standardise boolean query params to integers — `includeStatus` was the
   string `"true"` in `list_conversations`/`list_participants` while every
   other flag (`noStatusUpdate`, `lookIntoFuture`, `setReadMarker`,
   `includeLastKnown`) used `1`/`0`.
3. Replace the `app:install || app:enable` chain in the spreed install hook
   with `app:install --keep-disabled --force || true; app:enable spreed`,
   so unrelated install failures surface as a clear "app not found" from
   `app:enable` rather than being silently masked.
4. Add `_validate_token()` (alphanumeric whitelist) and call it from all
   six TalkClient methods that interpolate the token into a URL path —
   defence-in-depth against pathological tokens reaching httpx.
5. Rename `TalkConversation.type` to `room_type` with `Field(alias="type")`
   and `populate_by_name=True`, so the field no longer shadows Python's
   builtin while preserving spreed's wire format on input. MCP responses
   now serialize `room_type` (field name) instead of `type`.
6. `mark_as_read` now passes `json=body or None` so the bodyless
   "mark everything as read" call doesn't send a spurious `{}` body and
   `Content-Type: application/json` header.
7. `_validate_message_text` rejects whitespace-only messages, not just
   empty strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:14:32 +02:00
Chris Coutinho fd47d49886 Merge remote-tracking branch 'origin/master' into docs/login-flow-v2-and-astrolabe-cloud
# Conflicts:
#	README.md
2026-04-30 01:12:44 +02:00
Chris Coutinho 2bd1c18b43 Merge remote-tracking branch 'origin/master' into worktree-staged-squishing-rainbow 2026-04-30 01:11:02 +02:00
Chris CoutinhoandClaude Opus 4.7 9614c0b361 test(talk): cover include_status + malformed-header paths; drop Content-Type from default headers
Addresses the missing-test and Content-Type points from the latest
PR #741 review:

- client/talk.py _talk_headers(): drop the manual `Content-Type:
  application/json`. httpx sets it automatically on requests that pass
  `json=`, and we no longer leak it onto bodyless GETs and DELETEs.
- tests/client/talk/test_talk_api.py:
  - new `test_talk_list_participants_with_include_status` asserting
    `includeStatus=true` is forwarded.
  - new `test_talk_get_messages_invalid_last_given_header` covering
    the defensive try/except around the `X-Chat-Last-Given` parse —
    asserts the fallback `last_given=None` and that a warning is
    logged.
  - existing `test_talk_list_participants` extended to assert that
    `includeStatus` is *absent* by default.

Unit tests: 13 → 15. Integration tests still 7/7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:47:20 +02:00
Chris CoutinhoandClaude Opus 4.7 1306849353 docs: pivot to Login Flow v2; add Astrolabe Cloud hosted offering
Replace the seven OAuth-to-Nextcloud docs (oauth-setup, quickstart-oauth,
oauth-architecture, oauth-upstream-status, oauth-troubleshooting,
jwt-oauth-reference, audience-validation-setup) with a single new
docs/login-flow-v2.md. The deprecated flow required upstream user_oidc
patches that were never merged; Login Flow v2 is the forward-looking
multi-user mode (see ADR-022), and works with stock Nextcloud 16+.

Rewrite docs/authentication.md and docs/auth-flows.md around three modes:
Single-User BasicAuth, Multi-User BasicAuth pass-through, and Login Flow v2.

Update README to add an Astrolabe Cloud (https://astrolabecloud.com)
callout for users who prefer not to self-host, drop the OAuth deployment
mode from the auth table, simplify the Docker block, and trim the
Examples and Security sections.

Sweep configuration.md, installation.md, troubleshooting.md, running.md,
and semantic-search-architecture.md to replace links to the deleted docs
and update deprecated mode names.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:37:13 +02:00
Chris CoutinhoandClaude Opus 4.7 8f92a7ea29 docs(talk): address PR #741 reviewer nits
Comment-only follow-up to surface non-obvious behavior at the call
sites flagged in review:

- server/talk.py: note the `uuid.uuid4().hex` 32-char no-dashes format
  (spreed accepts either form).
- models/talk.py: warn that spreed returns `lastReadMessage: 0` rather
  than `null` for unread rooms, so consumers should compare to ``None``
  rather than rely on truthiness.
- 10-install-spreed-app.sh: document that the `app:install || app:enable`
  fallback also masks unrelated install failures, and limit its use to
  dev fixtures.

No runtime behavior changes; tests unchanged (still 13 unit + 7 integ).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:29:04 +02:00
Chris CoutinhoandGitHub 02ea8f5fca Merge pull request #740 from cbcoutinho/docs/security-policy-and-issue-templates
docs: add SECURITY.md and GitHub issue templates
2026-04-30 00:15:40 +02:00
Chris CoutinhoandClaude Opus 4.7 b6eb7a6bb8 fix(talk): address PR #741 reviewer feedback
Four targeted fixes from the AI code review:

1. TalkConversation.description: drop the misleading `str | None`
   union (spreed always sends `""`, never null) — type is now `str`
   with default `""`.

2. get_messages: guard the X-Chat-Last-Given int parse with
   try/except so a misbehaving proxy can't crash the read flow;
   logs a warning and falls back to None.

3. get_messages: clamp `limit` to [1, 200] in the client (spreed
   caps server-side at 200 and silently truncates) so the returned
   `count` always matches what was actually requested. Both client
   and server-tool docstrings updated to state the valid range.

4. Add an integration test covering the 32000-char message ceiling
   in talk_send_message — the empty-message case was already tested,
   the over-length case was not.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:03:24 +02:00
Chris CoutinhoandGitHub 19c4911954 Merge pull request #742 from cbcoutinho/chore/remove-pymupdf-layout
chore(deps): remove unused proprietary dep pymupdf-layout
2026-04-29 23:49:51 +02:00
Chris CoutinhoandClaude Opus 4.7 bd4921091e docs(security): address second round of review feedback
- SECURITY.md: add Supported Versions table; reword SLA paragraph as
  a bullet list per reviewer suggestion
- bug_report.yml: render reproduction textarea as shell so commands and
  JSON get syntax highlighting, matching the logs field
- question.yml: add transport and install_method dropdowns mirroring
  bug_report.yml so setup questions capture the same context

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:45:25 +02:00
Chris CoutinhoandClaude Opus 4.7 ad132dd6f1 chore(deps): remove unused proprietary dep pymupdf-layout
pymupdf-layout is Artifex commercial-only proprietary (its wheel ships
only a one-line COPYING noting "Commercial license. See artifex.com"),
incompatible with the project's AGPL build. It was declared as a runtime
dependency but unused: not imported anywhere in nextcloud_mcp_server/ or
tests/.

Also drop tools/parse-doc.py, an unused dev scratch script that was the
only caller of pymupdf.layout.activate(). Per the explicit warning in
document_processors/pymupdf.py, activating layout breaks
pymupdf4llm.to_markdown(page_chunks=True) per pymupdf4llm#323.

Closes #725

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:25:41 +02:00
Chris CoutinhoandClaude Opus 4.7 dc820aaa4c docs(security): address review feedback on PR #740
- SECURITY.md: add response SLA (5 business days / 30 days)
- bug_report.yml: scope the Docker log command to Docker installs
- question.yml: align deployment_mode catch-all wording with bug_report.yml

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:22:07 +02:00
Chris CoutinhoandClaude Opus 4.7 69814f30e3 feat(talk): add MCP integration for Nextcloud Talk (spreed)
Adds 6 MCP tools so an LLM can read a user's Talk conversations and
post messages on their behalf, addressing the "read my chats and reply"
use case from issue #720:

  - talk_list_conversations
  - talk_get_conversation
  - talk_get_messages
  - talk_list_participants
  - talk_send_message    (auto-attaches a referenceId for retry dedup)
  - talk_mark_as_read

Edit/delete messages, reactions, threads, and call/session ops are
intentionally out of scope for this first PR.

The TalkClient also exposes create_conversation/delete_conversation
for the integration test fixture; these are not registered as MCP
tools. A post-installation hook enables spreed in the docker dev env
so the integration suite has a real Talk backend to talk to.

Closes #720

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:19:57 +02:00
Chris CoutinhoandClaude Opus 4.7 d6362dc773 docs(security): prefer GitHub private vulnerability reporting
Surface GitHub's native private reporting workflow as the primary
disclosure channel, with security@astrolabecloud.com kept as a fallback
for reporters without a GitHub account. Updates SECURITY.md, the README
Security section, the issue-template config link, and the bug-template
warning banner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:09:39 +02:00
Chris CoutinhoandClaude Opus 4.7 8cc84ac08b docs: add SECURITY.md and GitHub issue templates
Add a security policy directing private vulnerability reports to
security@astrolabecloud.com instead of public issues, and update the
README's Security section to point at it.

Add structured issue forms under .github/ISSUE_TEMPLATE/ covering bugs,
feature requests, questions, and documentation, plus a config.yml that
disables blank issues and routes security reports and open-ended
questions to the appropriate channels. The bug template captures
fields most commonly missing from past reports (server/Nextcloud/app
versions, deployment mode, transport, MCP client).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:05:29 +02:00
github-actions[bot] c7162cc695 bump: version 0.73.0 → 0.73.1 2026-04-29 21:05:18 +00:00
Chris CoutinhoandGitHub 43598783e4 Merge pull request #734 from cbcoutinho/fix/calendar-niquests-auth-731
fix(calendar): thread raw credentials to caldav AsyncDAVClient (fixes #731)
2026-04-29 23:04:50 +02:00
github-actions[bot] a9c5759869 bump: version 0.72.7 → 0.73.0 2026-04-29 20:27:36 +00:00
Chris CoutinhoandGitHub b4a9639002 Merge pull request #739 from cbcoutinho/ci/sticky-claude-code-review
ci(claude-review): use sticky tracking comment instead of stacking
2026-04-29 22:27:13 +02:00
Chris CoutinhoandGitHub 517ee154ee Merge pull request #737 from cbcoutinho/feat/deck-card-comments
feat(deck): add card comment tools
2026-04-29 22:26:55 +02:00