# Add this to the Pi's existing Caddyfile. # # Caddy obtains and renews the certificate automatically, provided the VPS # forwards ports 80 and 443 through to this Caddy and the DNS name resolves to # the VPS's public address. # # The bearer-token check lives in the application, not here: Caddy would have # to be reloaded to rotate the token, whereas the app reads it from the # environment. Caddy's job is TLS, timeouts and keeping the container off the # public interface. mcp.example.org { encode zstd gzip # `schulcloud-mcp` is the Compose service name; Docker's embedded DNS # resolves it on the shared network. No host port is published. One proxy # serves both surfaces: /mcp for Claude and /api for the CLI. reverse_proxy schulcloud-mcp:8080 { # MCP's Streamable HTTP transport keeps a server-sent-events channel # open for server-initiated messages. Without flush_interval -1 Caddy # buffers those, and the connector appears to hang. flush_interval -1 # Long enough for a full re-crawl (~270 upstream requests) and for the # CLI streaming large files out of the mirror. transport http { read_timeout 600s write_timeout 600s } } header { Strict-Transport-Security "max-age=31536000; includeSubDomains" X-Content-Type-Options "nosniff" Referrer-Policy "no-referrer" -Server } log { output file /var/log/caddy/schulcloud-mcp.log # With MCP_PATH_SECRET set, a request path *is* a credential: claude.ai # reaches the server at //mcp. The filter rewrites that segment # before the entry is written. The Authorization header must never reach # disk either; Caddy does not log headers by default — do not add them. format filter { request>uri regexp ^/[A-Za-z0-9_-]{32,}/mcp //mcp wrap json } } }