upstream: the MCP connector appears to open a fresh session per tool call #21
Labels
No labels
blocked
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jlxq0/caldav-mcp#21
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Not ours to fix, and filed anyway. This is the bug; #20 is the symptom's blast radius.
The observation
Twenty-four calendar events cost twenty-four
initializerequests. That means the MCP connector opens a fresh session per tool call rather than reusing the one it handshook, so a single conversation burns session slots in proportion to how much work it does.Why raising the limit does not fix it
#20 raises the initialize burst from 8 to 32. That covers twenty-four events. It does not cover forty, and the next person meeting the ceiling has the same afternoon at a higher number.
This issue exists so that person finds the reason rather than raising it to 64. Every raise looks like it worked, because the raise is always larger than the workload that provoked it, right up until the workload grows.
What is on our side
Nothing to fix, and two things to keep honest:
sha256(bearer)[..16]and the Logtosub— is what makes this visible at all. A client minting a fresh token per session gets a fresh bearer bucket and the samesubbucket, so thesubbucket is what refuses. Without the second key this would have been invisible until the session pool filled.session::SESSION_KEEP_ALIVEis 30 minutes precisely so a connector with a slow tool-call cadence does not lose its session and re-handshake. If sessions are being discarded after one call, that keep-alive is buying nothing for this client.What would confirm it
The warn line from #20's first step, on beta, showing consecutive
initializerequests carrying different session ids from one conversation. That is a direct observation rather than the current inference from a request count, and it is cheap: beta has logged two lines since it started.Until then this is a well-supported reading of a count, not a measurement — the same distinction #20 is about.
Where it goes
Upstream, if it reproduces against a current connector build. Recorded here first because the reasoning is ours and it is the thing that will be forgotten once the limit is raised and the symptom stops.
First evidence since the instrument exists, and it points here rather than at the limiter
Production has run
v0.2.0since14:37:52Z. Everything it has logged:Four authenticated requests in four seconds, zero refusals, zero tool calls.
f5a076c6a6c82848isjulian@kampong.social.What that settles and what it does not
Settles: the limiter did not refuse him. A refusal now writes
refused MCP initializewithscope,user_hashandretry_after_seconds, and incrementscaldav_mcp_initialize_refusals_total{scope}. Neither appears. That is the silent direction of a check that can fail both ways, and it is an answer rather than an absence of one, which is the first time tonight this question has had one.Does not settle: that these four were
initializerequests.introspectfires on any authenticated request, and the limiter logs only when it refuses, so an acceptedinitializeis indistinguishable in the log from any other authenticated call. What the log does establish is that four requests authenticated and none became a tool call, which is the same auth-succeeds-nothing-follows shape as before with our limiter now excluded from the candidates.Four authentications in four seconds with nothing following is the signature this issue describes: one session opened per attempt rather than one reused.
The gap this exposes
We count refusals and not acceptances, so the server still cannot say how many sessions an identity opened, only how many it was denied. That makes the central claim here, twenty-four events costing twenty-four initializes, still unmeasurable from our side.
The fix is small and is not in
v0.2.0: log or count acceptedinitializerequests, at debug or as a counter, so sessions-opened becomes a number rather than an inference from a request count. Filed as a follow-up rather than done, because it is a new behaviour rather than a correction and the release is already out.Related: the connect-time mount pitfall,
matrix-mcp#118— a mount keeps the tool set it connected with and this server emits nolist_changed, so tools vanishing from a session is explained by the mount rather than by the server, and a fresh conversation restores them.The count exists now, and it is not the shape this issue predicted
v0.2.1has been serving since2026-08-26T16:22Z. Eleven hours of production traffic, counted from the server rather than inferred from a call graph:Zero tool calls. Not one in eleven hours, against 36 sessions opened. So the claim in this issue, that twenty-four events cost twenty-four initializes, is not what this server sees. Sessions are being opened and nothing is being invoked in them.
The initializes arrive in pairs
Every connection event produces exactly two, in the same second or one apart. Eighteen events, thirty-six sessions:
19 of 35 inter-arrival gaps are under 60 seconds; the median gap is 5 seconds and the maximum is three hours.
Doubling, not one-per-tool-call. I cannot say from this log which of the obvious causes it is — a client that initializes twice, an initialize that fails inside the MCP layer and is retried, or two mounts connecting together — and I would rather leave that open than pick the plausible one.
What this does to the issue
The premise needs restating. The session cost here is two per connection, not one per tool call, and it is decoupled from tool use entirely because there is no tool use. Whatever is consuming sessions is doing it at connect time.
And it is consistent with the mount explanation rather than with a session-reuse bug.
matrix-mcp#118: a mount keeps the tool set it connected with and this server emits nolist_changed. A mount that connects, receives its tools and is never used produces exactly this: initializes, session traffic, no calls. The 73 authenticated non-introspect requests that were not initializes are session-bearing traffic, stream or teardown, with no tool among them.The raise was still right and for a reason unaffected by this. 36 initializes in eleven hours against the old burst of 8 refilling one per thirty minutes would have refused; against 24 refilling one per minute it did not, and
refused=0across the whole window is the measurement rather than the prediction.Method
Counted with
regexpagainst the nestedtracingfields, not| json, for the reason inAGENTS.md.admitted MCP initializeand the refusal line both landed inv0.2.1; before it, none of these numbers existed on any of the three servers this claim spans.