fix(mcp): wire requireAuthentication into auth path and gate restart race (#1093)#1453
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5563281c4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return nil | ||
| } | ||
|
|
||
| let tokenStore = MCPTokenStore() |
There was a problem hiding this comment.
Persist bootstrap token through the scheduled restart
When Require authentication is enabled while the MCP server is already running and there are no user tokens, this creates the default token in a separate MCPTokenStore. The subsequent scheduled restart first stops the currently running server, whose older in-memory token store only contains the bridge token; cleanupBridgeToken() then saves that stale store back to mcp-tokens.json, overwriting the just-created default token. In this first-enable path the UI reveals a token that is no longer on disk, and the restarted server loads no user token, so authenticated requests with the revealed token fail.
Useful? React with 👍 / 👎.
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
Summary
Fixes #1093. The reported symptom (first authenticated request hangs after turning on Require Authentication; quitting and relaunching does not recover) is the surface of three converging bugs:
requireAuthenticationwas a dead toggle. It was persisted, displayed, and triggered a server restart, but no code on the server side ever read it.MCPHttpRequestRouteralways calledauthenticate(), andMCPBearerTokenAuthenticatoralways demanded a bearer token. The toggle did nothing semantically, it only fired a restart.AppSettingsManager.mcp.didSetenqueued unstructuredTask { restart }calls that could overlap, andMCPHttpServerTransport.start()returned before theNWListeneractually reached.ready. The user's curl could land in the gap.MCPTokenCreateSheethad no@FocusState. Mouse-click into the Token Name field on first sheet open was silently dropped until Tab established first responder.The fix is structural so the wrong state becomes unreachable:
MCPCompositeAuthenticatorwraps the bearer authenticator. WhenrequireAuthentication = falseand the request is loopback, it returns an anonymous principal (matches Jupyter--no-token). Remote connections always require a bearer (existingallowRemoteConnections → requireAuthentication = truecoercion inAppSettingsManageris unchanged).MCPServerManagernow exposesscheduleStart/scheduleStop/scheduleRestartthat chain through a seriallifecycleTask.AppSettingsManager.mcp.didSetuses the scheduler instead of fire-and-forget Tasks.MCPHttpServerTransport.start()awaitsNWListener.ready(or.failed,.cancelled) via a single-resumeCheckedContinuationwith a 5s timeout. Callers no longer return until the listener is actually accepting.AppSettingsManager.setRequireAuthentication(_:)auto-provisions a default token on first enable, persists to disk before the restart task fires (mirrors the existingConnectionStorage"persist first, then notify" invariant), and surfaces the new token to the UI via the existing reveal sheet.MCPTokenCreateSheetuses.defaultFocus($focused, .name)(macOS 14+, WWDC23 session 10162).Test plan
PBXFileSystemSynchronizedRootGroup; no pbxproj edits./mcpinitialize. First request should return200 OKwithMcp-Session-Id, no hang.allowRemoteConnectionscoerces the flag anyway).swiftlint lint --stricton the changed files (already clean locally).MCPCompositeAuthenticatorTests(six matrix cases for loopback/remote × auth on/off × token-present).CHANGELOG and
docs/features/mcp.mdxupdated.