Files
mw-pfeddersheim-workstation/docs/learnings/2026-07-06-caddy-onlyoffice-mcp-sse-investigation.md
T
mw 441e28bda3 docs: fix CHANGELOG duplicate Added section + missing period in learning doc
Address review feedback on ce99f58:
- Merge the two '### Added' blocks in [Unreleased] into one (Keep a
  Changelog: each category appears once). New 2026-07-06 entries now
  sit at the top of the existing Added block.
- Add missing period after 'required SSE headers' in the liveness
  verification prose (was a run-on sentence).
2026-07-06 09:14:13 +02:00

7.7 KiB

description, tags, last_updated
description tags last_updated
Session record (2026-07-06): triaged Caddy journald warnings on onlyoffice.localhost/mcp showing "context canceled" SSE aborts (~1.5ms) from opencode 1.17.13 MCP client against the OnlyOffice MCP server on :3847. Verified upstream liveness, opened GitLab issue #92 in satware/mcp/onlyoffice, and documented the Caddy reverse-proxy as a manual override.
learning
caddy
onlyoffice
mcp
sse
reverse-proxy
investigation
2026-07-06

Caddy "context canceled" on onlyoffice.localhost/mcp - investigation - 2026-07-06

Summary

Caddy (systemd caddy.service) on mw-manjaro-pf is emitting a high volume of warn-level journald entries: aborting with incomplete response ... error: "reading: context canceled" for GET /mcp requests on https://onlyoffice.localhost. Each abort fires ~1.5ms after the request is forwarded to the upstream localhost:3847 (OnlyOffice MCP server). The upstream is alive and correctly rejects non-SSE GETs with HTTP 406, so this is a stream-lifecycle issue, not a server-down issue.

Investigation handed off to the OnlyOffice MCP repo as GitLab issue #92. This workstation's Caddy reverse-proxy config is now recorded as a manual override (not Ansible-managed).

Incident

Field Value
Symptom Repeated context canceled warnings in journalctl -u caddy
Caddy site https://onlyoffice.localhost -> reverse_proxy localhost:3847
Upstream localhost:3847 (OnlyOffice MCP server)
Request GET /mcp with Accept: text/event-stream
Client opencode/1.17.13 (MCP streamable-HTTP transport)
MCP protocol 2025-11-25
Abort duration ~1.5ms (request forwarded, then read context canceled)
Frequency Multiple per minute, across distinct Mcp-Session-Id values

Evidence

Caddy log sample (recurring)

{
  "level": "warn",
  "logger": "http.handlers.reverse_proxy",
  "msg": "aborting with incomplete response",
  "upstream": "localhost:3847",
  "duration": 0.00173631,
  "request": {
    "method": "GET",
    "host": "onlyoffice.localhost",
    "uri": "/mcp",
    "headers": {
      "Accept": ["text/event-stream"],
      "Mcp-Protocol-Version": ["2025-11-25"],
      "Mcp-Session-Id": ["a5e859b4-01f1-4c24-81bb-78ee02d74357"],
      "User-Agent": ["opencode/1.17.13"],
      "Via": ["1.1 Caddy"]
    }
  },
  "error": "reading: context canceled"
}

Observed Mcp-Session-Id values (each retried over several minutes): a5e859b4..., 4bfbb912..., 3c4a0454..., 08397c3d..., ed762f08..., b0fae341....

Upstream liveness verification

$ ss -tlnp | grep :3847
LISTEN 0  4096  0.0.0.0:3847  0.0.0.0:*

$ curl -sS -o /dev/null -w "HTTP %{http_code} in %{time_total}s\n" \
    --max-time 3 http://localhost:3847/mcp
HTTP 406 in 0.004665s

HTTP 406 is expected for a plain GET without the required SSE headers. The MCP server is alive and rejecting non-SSE requests correctly.

Diagnosis

Root cause (not yet confirmed - under investigation)

Five candidate hypotheses, listed in the GitLab issue. The leading suspect is client-side disconnect: opencode 1.17.13 opens the SSE stream then immediately closes it (the ~1.5ms duration is too short for a genuine read). Secondary suspects are server-side stream teardown, MCP protocol-version mismatch (2025-11-25), Caddy HTTP/1.1 framing of SSE, or a client session-pool bug creating and abandoning sessions.

Why this workstation's concern

The Caddy reverse-proxy is the boundary that surfaces the symptom. Even if the root cause is client- or server-side, the local config matters because:

  1. Caddy logs are being polluted with warn-level noise (masks real errors).
  2. The reverse-proxy block has no SSE-specific tuning (flush_interval, explicit HTTP/1.1 transport) that could mask or mitigate upstream misbehavior.
  3. The Caddy config at /etc/caddy/conf.d/localhost-dev.conf is not Ansible-managed by this repo - it is a manual override that must be recorded (see docs/tech/manual-overrides.md).

Environment

Component Version / Value
Host mw-manjaro-pf (mw-pfeddersheim-workstation)
OS Manjaro Linux, Wayland session
Caddy systemd caddy.service, active since 2026-07-02
Caddy config /etc/caddy/Caddyfile + /etc/caddy/conf.d/localhost-dev.conf
TLS cert mkcert pair at /etc/caddy/localhost{,-key}.pem
Upstream localhost:3847 (OnlyOffice MCP server, process TBD)
MCP client opencode/1.17.13
MCP protocol 2025-11-25 (streamable HTTP transport)

*.localhost sites configured

All defined in /etc/caddy/conf.d/localhost-dev.conf (14 sites total):

Site Backend / Handler
mcp.localhost reverse_proxy localhost:50880
artifacts.localhost file_server browse root /var/www/artifacts
onlyoffice.localhost reverse_proxy localhost:3847 (subject of this issue)
project-1.localhost ... project-10.localhost respond "project-N - placeholder" (10 reserved)
demo.satware.com.localhost reverse_proxy localhost:38081 (webdevops/php-apache-dev container)

Handoff

GitLab issue

Item Value
Project satware/mcp/onlyoffice (gitlab.satware.com)
Issue #92
Title Investigation: Caddy reports 'context canceled' on onlyoffice.localhost/mcp SSE stream (~1.5ms aborts)
Labels bug, investigation, mcp
State open

The issue body contains the full evidence, 5 hypotheses, 7 investigation tasks, and a proposed Caddy mitigation (flush_interval -1 + transport http { versions 1.1 }).

Next actions (assigned to OnlyOffice MCP repo)

  1. Identify the process listening on :3847 (sudo ss -tlnp | grep 3847).
  2. Collect server-side logs for the Mcp-Session-Id values cited above.
  3. Reproduce with curl -N -H "Accept: text/event-stream" -H "Mcp-Protocol-Version: 2025-11-25" https://onlyoffice.localhost/mcp.
  4. Check opencode 1.17.13 changelog for MCP SSE session bugs.
  5. Verify Caddy reverse_proxy SSE tuning (proposed block in issue #92).
  6. Confirm whether notifications/initialized round-trip completes.
  7. Log the negotiated MCP protocol version server-side.

Local documentation updates

File Change
docs/tech/manual-overrides.md Added row for /etc/caddy/ config (not Ansible-managed)
docs/tech/stack.md Added "Local Dev Services" section listing Caddy + *.localhost sites
CHANGELOG.md Added [Unreleased] -> Added entry
GitLab issue #92 Created with full evidence + hypotheses + tasks

Proposed Caddy mitigation (to test on this workstation)

https://onlyoffice.localhost {
    tls /etc/caddy/localhost.pem /etc/caddy/localhost-key.pem
    reverse_proxy localhost:3847 {
        flush_interval -1
        transport http {
            versions 1.1
        }
    }
}

flush_interval -1 disables Caddy's buffering so SSE events flush to the client immediately; transport http { versions 1.1 } pins the upstream leg to HTTP/1.1 (Caddy logs already show HTTP/1.1 for the upstream, this makes it explicit and avoids HTTP/2 SSE framing surprises).

Not applied yet - pending GitLab issue #92 resolution to avoid masking the root cause.

References