Carved out of #95, whose item 3 is otherwise internal quality. This is the one part of it a remote
client can observe.
What reaches the client
McpServerHandler.ThrowSanitizedIfAClientAlreadyHasASchema replaces the failure with a generic
McpException only when a snapshot has already been served on that connection. On the first
request of a connection there is no previous snapshot, so the original HiddenRequiredOptionException
is rethrown and its message travels verbatim:
Option target '<parameterName>' (rendered as '<token>') for command '<route>' cannot be hidden
because it is required. Either drop the Required/Arity constraint on the option, or hide a
different one.
That is the option's target, its rendered token and its route — precisely the identity that hiding the
option was meant to withhold.
Why it is worth revisiting rather than a plain oversight
The asymmetry is deliberate and the comment above the method says so: a cold-start failure is a
configuration error whose reader is the operator at the console, so letting the detail through tells
them which option and route are misconfigured. That premise held while a Repl app had one local
operator.
It stopped holding when the caller became a concurrent, potentially remote MCP client. On the
mcp serve path the reader of the first request's failure is the client, not the operator — and the
same connection's second request would be sanitised, so the disclosure is not even consistent within
one session.
Shape
Sanitise unconditionally, and log the detail server-side so the operator keeps what the current
behaviour was protecting. That ordering is the reason this is blocked on #99: src/Repl.Mcp has
nowhere to log today, so sanitising first would trade a disclosure for a silent failure.
Guard belongs beside the existing snapshot-failure tests: a cold connection whose app hides a required
option must receive the generic message, and the operator sink must carry the option target and route.
Milestoned 0.12.0 — it is a disclosure on a surface that shipped in #71.
Carved out of #95, whose item 3 is otherwise internal quality. This is the one part of it a remote
client can observe.
What reaches the client
McpServerHandler.ThrowSanitizedIfAClientAlreadyHasASchemareplaces the failure with a genericMcpExceptiononly when a snapshot has already been served on that connection. On the firstrequest of a connection there is no previous snapshot, so the original
HiddenRequiredOptionExceptionis rethrown and its message travels verbatim:
That is the option's target, its rendered token and its route — precisely the identity that hiding the
option was meant to withhold.
Why it is worth revisiting rather than a plain oversight
The asymmetry is deliberate and the comment above the method says so: a cold-start failure is a
configuration error whose reader is the operator at the console, so letting the detail through tells
them which option and route are misconfigured. That premise held while a Repl app had one local
operator.
It stopped holding when the caller became a concurrent, potentially remote MCP client. On the
mcp servepath the reader of the first request's failure is the client, not the operator — and thesame connection's second request would be sanitised, so the disclosure is not even consistent within
one session.
Shape
Sanitise unconditionally, and log the detail server-side so the operator keeps what the current
behaviour was protecting. That ordering is the reason this is blocked on #99:
src/Repl.Mcphasnowhere to log today, so sanitising first would trade a disclosure for a silent failure.
Guard belongs beside the existing snapshot-failure tests: a cold connection whose app hides a required
option must receive the generic message, and the operator sink must carry the option target and route.
Milestoned 0.12.0 — it is a disclosure on a surface that shipped in #71.