Skip to content

feat(community-containers): add Astrolabe - #8489

Draft
cbcoutinho wants to merge 2 commits into
nextcloud:mainfrom
cbcoutinho:feat/community-container-astrolabe
Draft

feat(community-containers): add Astrolabe#8489
cbcoutinho wants to merge 2 commits into
nextcloud:mainfrom
cbcoutinho:feat/community-container-astrolabe

Conversation

@cbcoutinho

@cbcoutinho cbcoutinho commented Jul 27, 2026

Copy link
Copy Markdown

What

Adds a community container that runs nextcloud-mcp-server, so the Nextcloud Assistant's "Chat with AI" agent can answer from the user's own files, notes, calendar, contacts and Deck cards, with links back to what it used.

Opening as a draft — see "Not ready to merge" below. Proposed in #8490.

Why not the Context Agent ExApp

Upstream, that agent needs the Context Agent app, which is an ExApp: it only runs via AppAPI plus a deploy daemon (HaRP, or previously the Docker Socket Proxy), which means giving Nextcloud reach into a Docker socket. That is a recurring source of trouble here — #6613, #8469, #8159, #7861, #7848, #7221, #8225.

Since v0.39.0 the Astrolabe app registers an ordinary in-process TaskProcessing provider for the core task type core:contextagent:interaction, and the Assistant treats its agent as available as soon as any provider claims that type. No AppAPI, no ExApp, no Context Chat, no Docker socket.

Scope

Deliberately small for a first version:

  • No LLM is bundled and none is called by the container. Astrolabe does retrieval only; generation stays with whatever provider already serves core:text2text:chatwithtools (typically integration_openai, which can point at the existing local-ai community container).
  • Semantic search and background indexing are off. Nothing is embedded, indexed or copied into a vector database — the agent reads through Nextcloud's own APIs, live, as the user asking the question. Adding semantic search would mean pulling in a vector database and an embedding model, so I would rather propose that separately.
  • Read-only scopes (files.read notes.read webdav.read calendar.read contacts.read deck.read). The MCP server hides every tool those scopes do not grant.
  • Each user opts in once from the Astrolabe app before the agent can act for them.

How it is wired

Everything on the Nextcloud side goes through nextcloud_exec_commands: install and enable oidc, astrolabe and assistant, register two OIDC clients, point Astrolabe at the MCP server, and switch the agent on. Nothing for the admin to configure by hand.

Two OIDC clients, both created with AIO-generated secrets and guarded by an oidc:list | grep -q … || check so restarts do not accumulate duplicates:

  • a public client for the Astrolabe app — it never performs a code exchange, it mints per-user tokens through the oidc app's TokenGenerationRequestEvent;
  • a confidential client for the MCP server itself, for OIDC discovery, key retrieval and token validation. Registered statically on purpose: the oidc app deletes dynamically registered clients after about an hour.

Internal and public addresses are kept separate:

Internal (Docker network) NEXTCLOUD_HOST=http://nextcloud-aio-apache.nextcloud-aio:23973, and Nextcloud's mcp_server_url → the MCP container
Public OIDC_ISSUER / NEXTCLOUD_PUBLIC_ISSUER_URLhttps://$NC_DOMAIN, and NEXTCLOUD_MCP_SERVER_URL / mcp_server_public_urlhttps://mcp.$NC_DOMAIN

NEXTCLOUD_HOST reuses the plain-HTTP internal listener already in Containers/apache/Caddyfile for Collabora's WOPI and callback URLs, so the Nextcloud hop never leaves the host. Port 8000 is published so an admin who also wants to connect an external MCP client can reverse-proxy mcp.$NC_DOMAIN at it — the same shape vaultwarden uses with bw.$NC_DOMAIN. That step is optional; skipping it costs only the external-client case, since the Assistant agent reaches the server over the Docker network.

Not ready to merge

Two things outstanding, which is why this is a draft:

  1. TOKEN_ENCRYPTION_KEY format. The MCP server currently requires a Fernet key (base64url-encoded 32 bytes), but AIO generates bin2hex(random_bytes(24)) — 48 hex characters — and a community container has no settings UI to paste a real key into. I intend to fix this on the MCP server side by deriving a key from any sufficiently random string while keeping existing Fernet keys working unchanged, and to pin image_tag to the release that carries it. Happy to hear if you would rather it were solved differently.
  2. Manual testing on a live instance. Not done yet.

Testing

CI-equivalent checks were run locally: the JSON validates against php/containers-schema.json, the folder/readme structure check passes, and codespell is clean. The generated NEXTCLOUD_EXEC_COMMANDS script was also checked to be valid bash, and simulated across repeated restarts to confirm it creates each OIDC client exactly once without writing a client secret to the container log. Manual testing on a live AIO instance still to come.


This PR was generated with the help of AI, and reviewed by a Human

@cbcoutinho
cbcoutinho force-pushed the feat/community-container-astrolabe branch from 5930601 to 8336ddb Compare July 27, 2026 20:38
Adds a community container running nextcloud-mcp-server, which lets the
Nextcloud Assistant's "Chat with AI" agent answer from the user's own
files, notes, calendar, contacts and Deck cards.

Upstream this needs the Context Agent ExApp, which requires AppAPI and a
deploy daemon (HaRP or the Docker Socket Proxy). Since v0.39.0 the
Astrolabe app registers an in-process provider for the core task type
core:contextagent:interaction instead, so none of that is needed.

Scope is deliberately small for a first version: semantic search and
background indexing are off, so nothing is embedded or indexed and the
agent reads through Nextcloud's own APIs as the asking user. No LLM is
bundled -- generation stays with whatever provider already serves
core:text2text:chatwithtools. Agent scopes are read-only.

Nextcloud is reached over the Docker network via the internal plain-HTTP
listener already used for Collabora's WOPI callbacks, while the token
audience and OAuth callback use the public mcp.$NC_DOMAIN, which an admin
can reverse-proxy at the published port 8000.

Assisted-by: ClaudeCode:claude-opus-5
Signed-off-by: Chris Coutinho <chris@coutinho.io>
@cbcoutinho
cbcoutinho force-pushed the feat/community-container-astrolabe branch from 8336ddb to f4b0bef Compare July 27, 2026 20:39
@szaimen szaimen added 2. developing Work in progress enhancement New feature or request labels Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2. developing Work in progress enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants