feat(assistant): Claude reads studysets through a connector on the instance

A Claude plan can't drive a chat inside another app, but Claude's apps
(every plan, the phone apps too) can add a remote MCP server as a custom
connector. The instance now is one (ADR 0006): Serverpod's web server
serves the MCP endpoint (/mcp, Streamable HTTP, JSON answers, no
sessions) with five read-only tools over the person's own synced rows
(their studysets, a studyset's cards and notes a page at a time, a
search across cards, a note, their progress), and the OAuth the
specification asks for: protected resource and authorization server
metadata, dynamic client registration, authorization code with PKCE
(S256 only), tokens bound to the resource, iss in the response, refresh
tokens that rotate. Caddy forwards these paths alone to the web server,
which production now runs on 8082.

People connect with a code from the app (Settings › Assistant › Connect
Claude): eight Crockford characters, valid for ten minutes and once,
typed on the authorization page, which names where it returns to, loads
nothing from elsewhere and may not be framed. Anonymous people can
connect too. Codes and tokens are kept only as SHA-256 hashes; an access
token lives an hour, a refresh token 30 days. The app lists what is
connected and disconnects it, which ends its tokens at once; deleting
the account deletes it; a daily job forgets what expired and clients
nobody connected within a week. InstanceInfo says where the connector is
(additive); the migration only adds server-only tables, so devices and
older apps are untouched.

The handlers take plain requests, so the integration tests drive the
whole flow (registration, the code, tokens, refresh, the tools, another
person's content, disconnecting, expiry, pruning, account deletion)
without a web server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-10 12:13:00 +02:00
commit 56c5b36084
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1,12 +1,21 @@
# Caddy forwards to the API server only. Insights (8081) and a web server (8082) don't
# run in production; if Insights is ever re-enabled, reach it through an SSH tunnel,
# never through this file.
# Caddy forwards to the API server, and the assistant connector's paths (ADR 0006) to
# the web server (8082), which serves nothing else. Insights (8081) doesn't run in
# production; if it is ever re-enabled, reach it through an SSH tunnel, never through
# this file.
#
# {$DOMAIN} is `learn.halkyon.eu` on sahaquiel, where Caddy obtains and renews a
# Let's Encrypt certificate by itself, and `http://localhost` for the local rehearsal.
{$DOMAIN} {
encode zstd gzip
# The assistant connector: the MCP endpoint, its OAuth pages, and their metadata.
@connector path /mcp /oauth/* /.well-known/oauth-protected-resource /.well-known/oauth-protected-resource/* /.well-known/oauth-authorization-server
handle @connector {
reverse_proxy server:8082
}
# Serverpod's streaming methods use WebSockets; reverse_proxy upgrades them as-is.
reverse_proxy server:8080
handle {
reverse_proxy server:8080
}
}