You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
RoselineMCP:ConfirmDestructiveWritesTimeout (default 300000 ms, 5 minutes) — bounds how
long the write-confirmation elicitation waits for the client's answer. Set it via appsettings.json or ROSELINE_RoselineMCP__ConfirmDestructiveWritesTimeout=<ms>; 0 or less
removes the bound and restores the previous, indefinite wait. This changes default behavior:
a client that advertises elicitation support, accepts the confirmation request and then never
answers — a CI job, a headless agent, a human who walked away — used to block apply_fixes / edit_member / rename_symbolforever, because RoselineMCP:DefaultTimeout is an analysis
budget and by construction does not apply to the human round-trip. The server now stops
waiting after the timeout — note that a client whose elicitation handler never returns may still
not read the response, since the SDK's client dispatches server-initiated requests on its read
loop; the bound is on RoselineMCP's side of the wire. It returns a preview, not a write:
silence is not consent, so previewOnly
comes back true and notes[] explains that the confirmation timed out. Writing without a human
remains an explicit operator decision (ConfirmDestructiveWrites=false), so the security posture
is no weaker than before — only the hang is gone. The clock is deliberately separate from DefaultTimeout: a human reading a real diff may legitimately exceed an analysis budget.
RoselineMCP:ConfirmDestructiveWrites (default true) — an operator switch that turns off
the write-confirmation elicitation. The write tools (ApplyFixes, EditMember, RenameSymbol)
ask the connected client to confirm before writing when the caller passed previewOnly: false;
on an unattended host that prompt is not a second guard but a stop, because MCP elicitation is a
separate channel from tool permissions and no client-side setting can pre-answer it — so claude -p runs, CI jobs and overnight agent loops blocked on a human keypress. Set it via appsettings.json or ROSELINE_RoselineMCP__ConfirmDestructiveWrites=false and no elicitation
is sent at all (rather than one being auto-accepted); the explicit previewOnly: false opt-in
then stands as the only guard before a write — see SECURITY.md. Interactive installs are
unaffected: leave it at its default and behavior is unchanged. The server logs a warning at
startup when the switch is off, so a gate-off deployment is identifiable from its stderr rather
than being indistinguishable from a confirmed one.
Fixed
A write confirmation that times out can no longer be read as consent when the SDK reports the
abandoned prompt as something other than a cancellation. The confirmation gate downgraded to a
preview only on OperationCanceledException; every other exception fell through to a catch-all
meaning "this client cannot elicit — honor the explicit opt-in" and returned proceed. Cancelling
an in-flight JSON-RPC request is not guaranteed to surface as an OCE, so a transport or protocol
exception raised by our own deadline would have written to disk on a confirmation nobody
answered — the exact inversion of the gate. The timeout branch now filters on the deadline
rather than on the exception type, so any failure caused by it downgrades to a preview; genuine
caller cancellations and broken sessions still propagate unchanged.
The RoselineMCP:DefaultTimeout clock no longer runs while a human is being asked to confirm a
write. It was armed before the confirmation elicitation, so its 120 s budget was spent on
think-time — the very thing the confirmation's separate clock exists to prevent. Two consequences,
both gone: a human who approved a previewOnly: false call more than 120 s after it started got {"ok": false, "error": {"type": "TimeoutError"}} instead of the write they had just authorized;
and with the new ConfirmDestructiveWritesTimeout default (300 s) exceedingDefaultTimeout,
the timeout path could never have delivered the documented preview-and-note either. The analysis
budget now starts once the confirmation resolves, so DefaultTimeout measures analysis — as
documented — rather than analysis plus however long the human took.
The EditMember write-confirmation prompt no longer asks the human to approve a write in ''
when project was omitted (the documented auto-discovery default) — it now names "the
auto-discovered project", matching ApplyFixes. RenameSymbol's prompt likewise names the
project it resolved, instead of describing a solution-wide rename without saying which solution.
Changed
Upgraded the MCP SDK from ModelContextProtocol 1.4.1 to 2.2.0, which negotiates protocol
revision 2026-07-28 by default. No tool's wire shape, parameters, or response envelope
changes.
Client-side log forwarding is now inert for clients on protocol 2026-07-28 or later. SEP-2577
deprecated the MCP Logging feature in that revision: logging/setLevel is rejected, and a server
must not emit notifications/message for a request that did not carry an io.modelcontextprotocol/logLevel_meta field — which the SDK's own McpClient provides no way
to set (on such a session it injects per-request _meta and strips that key). So the tool-failure
log notifications RoselineMCP sends via AsClientLoggerProvider() are delivered only to clients
that negotiate 2025-11-25 or earlier; the code is kept for them and stays a no-op otherwise. Nothing is lost for anyone: the correlation ID that those notifications carried is still in
every error envelope (error.correlationId) and in the server's own stderr log, which is exactly
what SEP-2577 names as the replacement. Deprecated features remain in the spec for at least twelve
months.
Releases now publish to NuGet.org via Trusted Publishing instead of a long-lived NUGET_API_KEY secret: publish-nuget.yml exchanges the GitHub OIDC token for a key valid
~1 hour, the same way the registry publish already proved this repo's identity. The only
remaining secret is NUGET_USER, the nuget.org profile name. Both prerequisites are in place as
of 2026-07-27: the Trusted Publishing policy is registered on nuget.org (package owner phmatray, naming this repository and publish-nuget.yml) and NUGET_USER is set. The
long-lived NUGET_API_KEY repository secret is deliberately kept until one real release has been
published and verified — see PUBLISH.md.
The write-confirmation gate now lives in one place.apply_fixes, edit_member and rename_symbol each carried their own copy of the block deciding whether to ask, what a declined
or unanswered prompt means for the call, what to log, and which note to attach — one policy with
three edit sites. All three now call a single ToolExecutionHelper.ResolveWriteModeAsync, and the
one part of the prompt that had drifted — how the target project is named — is single-sourced
through ToolExecutionHelper.DescribeWriteTarget. No behavior change: no tool's parameters,
response shape, prompt wording or notes text differ. This removes the cause of the three
divergent confirmation messages already fixed under Fixed above, rather than fixing them
again; ElicitationTests now pins all three prompts (and edit_member's decline path, which had
no end-to-end cover) so the same divergence cannot re-form.
Documentation
RoselineMCP:RunAnalyzers = false no longer claims to stop all analyzer-assembly execution —
source generators run regardless, and the docs now say so.SECURITY.md promised the switch "disables all analyzer execution (bundled and project-referenced alike)", and CLAUDE.md and README.md repeated it. Source generators ship through the same AnalyzerReferences and are
equally arbitrary in-process code from the analyzed repository, but they run as part of building any compilation rather than as part of the diagnostics pass — which RunAnalyzers is the only
thing gating. Every semantic path therefore executes them: all seven navigation tools (via SymbolResolver), ApplyFixes (via CodeFixService) and AnalyzeSolution (via SolutionAnalyzerService). Verified on Roslyn 5.6.0 / .NET SDK 10.0.302 — a project whose only
content is a [GeneratedRegex] partial method compiles through MSBuildWorkspace with the
generated implementation bound and zero errors, which is only possible if the generator ran.
The consequence for an operator is the point: someone who set RunAnalyzers=false before
pointing RoselineMCP at an untrusted repository believed they had closed a code-execution
surface that was still fully open. Suppressing generators is not offered because it would not be
honest — stripping AnalyzerReferences removes the generated types too, so every symbol
resolving through generated code would be reported as a compile error. The switch narrows the
surface; isolation, not configuration, is the mitigation, and the operator recommendations now
lead with that. No behavior changed — only the guarantee the documentation advertised.
RoselineMCP:WorkspaceCache = false is documented as what it is: an isolation/debugging
switch, never a way to save memory. The docs described it only as "loads a fresh workspace on
every call", which reads as the memory-frugal option; measured, it is the opposite — disposing
the workspace after every call costs +26 % resident memory (~374 MB vs ~296 MB after two
calls) and ~45× second-call latency (0.92 s vs 0.02 s), because a disposed workspace's memory
is never returned to the OS and the reload allocates on top of it. An operator who set it to
reduce a server's footprint was getting a regression.
docs/ARCHITECTURE.md gains the measured memory profile behind that correction (~78 MB before
any workspace exists — runtime plus the Roslyn/MSBuild/Roslynator assembly set, not the cache;
~300 MB once real work has been served; disposal plus a forced compacting GC moves the working
set 276 MB → 276 MB), so the cache's 4-entry LRU bound is visible as the only lever that affects
it. Releasing cached workspaces on idle was evaluated against these numbers and rejected.
docs/ARCHITECTURE.md now states that the stdio transport, not UseConsoleLifetime, is what
stops the host when a client closes stdin, with the four measured EOF paths (after handshake,
before handshake, mid-tool-call, and stdin held open). UseConsoleLifetime handles only
SIGINT/SIGTERM, and reading it as the whole lifetime story suggests a stranded-server bug that
does not exist.
Dependency versions asserted in prose corrected against RoselineMCP.csproj, which the README
already names as the source of truth: ModelContextProtocol 1.4.0 → 1.4.1 (README tech stack
and tool-annotations section, CLAUDE.md), MSBuild 18.7.1 → 18.8.2, and Microsoft.Extensions.Hosting 10.0.9 → 10.0.10.