Show when the MCP server fails to start - #598
Conversation
StartMcpServer fired McpHostService.StartAsync and set the menu to "Running" in the same breath, before Kestrel had bound anything. A failure to bind -- most often another process already on the configured port -- only reached Debug.WriteLine, so the menu kept saying Running while the server was actually down. - McpHostService.ExecuteAsync splits the old combined RunAsync into StartAsync followed by WaitForShutdownAsync, so a bind failure is observable on its own. A new Started task resolves to null on success, a short reason on failure (naming the port when it is taken), or cancelled if the host stopped before either happened. - DescribeStartFailure walks the exception chain for Kestrel's AddressInUseException or a bare SocketException(AddressAlreadyInUse), and falls back to the exception's own message for anything else. - MainWindow sets "MCP Server: Starting (port N)" immediately, then resolves it to Running or Failed once Started completes, posted to the UI thread. A closed window is left alone. BuildMcpStatusHeader holds the three header strings so they can be tested without a real port or window, the same way DecideClose already covers CloseAction. - McpHostServiceTests: an occupied port resolves Started to its reason; a free port resolves it to null. - McpStatusHeaderTests: pins the three header strings directly. - Updated the now-stale comment on RunningServer.WaitUntilListeningAsync, which used to say a start failure reached only the debugger. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
|
Reviewed the diff. I didn't run the build or tests. It looks correct and I have no blocking findings.
Two minor, non-blocking notes:
|
A start failure that is not a taken port showed the exception's raw message, which can be long or run to several lines. The menu item now shows "port N: " and the message's first line, cut to 100 characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
| /// <summary> | ||
| /// The success half of the same contract: Started resolves to null, not just "eventually | ||
| /// stops throwing". <see cref="AClientOnThisMachineCanListAndCallTools"/> already covers the | ||
| /// server actually working once up; this one is only about the signal that it got there. | ||
| /// </summary> |
There was a problem hiding this comment.
This "success half" summary is stacked directly on top of the next doc comment, so it's attached to AnotherFailureShowsThePortAndTheFirstLineOfItsMessage and reads as a second <summary> on it. It was meant for AFreePortResolvesStartedToNull further down. Move it there and delete it here.
|
Logic looks right: |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
|
Reviewed the diff. I found nothing that needs changing.
I only read the diff. I did not build or run the tests. The occupied-port test binds |
A menu header reads "_" as an access key marker, so a reason taken from an exception message now has its underscores doubled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
|
Reviewed the diff and found nothing blocking. Optional nit: |
What does this PR do?
The MCP status item in the Help menu said "MCP Server: Running (port N)" as soon as the app asked the server to start. It did not wait to see if the server came up. When the port was already in use, the start failed, but the menu still said Running. The error went only to the debug output.
Now the item says "MCP Server: Starting (port N)" first. When the start is done, it changes to "MCP Server: Running (port N)" or to "MCP Server: Failed (port N is in use)". A start that fails for another reason shows the port and the first line of the error's message, for example "MCP Server: Failed (port N: Unable to start.)". A message longer than 100 characters is cut.
McpHostService:StartAsyncand then waits withWaitForShutdownAsync. Before, oneRunAsynccall did both, so a start failure never got back to the app.Startedtask gives the result: null when the server is listening, or a short reason when the start failed. If the app closes before the start is done, the task is cancelled.AddressInUseExceptionand for aSocketExceptionwithAddressAlreadyInUse.MainWindowwaits forStarted, then sets the menu text on the UI thread. If the window is closed by then, it does nothing. A small internal function makes the menu text, so tests can check it. A menu header reads_as an access key marker, so that function doubles each_in a failure reason to show it as written.Which component(s) does this affect?
How was this tested?
McpHostServiceTests:TcpListener, then starts the server on that port.Startedgives "port N is in use".Startedgives null.McpStatusHeaderTests: the menu text for Starting, Running and Failed, and a_in a failure reason.Checklist
--no-incremental)dotnet test)🤖 Generated with Claude Code
https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza