fix: harden MCP extension-point registration against races and stale state#164
fix: harden MCP extension-point registration against races and stale state#164jdneo wants to merge 1 commit into
Conversation
…state - Synchronize all extMcpInfoMap accesses on the manager monitor so the HashMap is never iterated/mutated concurrently by the async doRegistration() worker, the contribution-point-policy event handler, and the UI-thread approval flow. - approveExtMcpRegistration() now snapshots the map under the lock and opens McpApprovalDialog outside the lock so the worker is not stalled while the user interacts; post-dialog reconciliation re-acquires the lock and the LSP-pushing event publish runs outside the lock. - Mark approvedExtMcpServers volatile and make hasExtMcpRegistration() synchronized for consistent visibility from the UI thread. - Avoid pre-populating in-memory approved servers from the persisted cache at startup; let doRegistration() refresh state and fire TOPIC_MCP_EXTENSION_POINT_REGISTRATION_COMPLETED so the LSP receives the verified set instead of stale data (uninstalled plugin, changed port, removed server). - Fix bundle-state guard in loadMcpRegistrationExtensionPoint() (|| -> &&) so already-active or starting bundles are not pointlessly started again. - LanguageServerSettingManager subscribes to the new completion topic and exposes an overload that accepts the approved JSON directly, avoiding the ChatServiceManager lookup from early-startup paths. - Add regression tests covering plugin uninstall, config change (re-approval required), and unchanged config (approval carry-over). Issue: #153 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Hardens MCP extension-point registration in the Copilot for Eclipse UI layer by improving cross-thread state consistency (async registration worker vs. UI approval flow) and ensuring the language server receives verified (non-stale) extension-contributed MCP server state.
Changes:
- Synchronizes extension-contribution state updates and publishes a new completion event so the LSP is updated even when preference-change notifications don’t fire.
- Avoids seeding in-memory approved servers from persisted cache at startup to prevent stale configs (e.g., uninstalled plugin / changed port) from being pushed to the LSP first.
- Adds regression tests for uninstall/config-change/no-change scenarios.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/McpPreferencePage.java | Removes direct LSP sync after approval; relies on manager event publication. |
| com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/LanguageServerSettingManager.java | Subscribes to new “registration completed” topic and adds overload to sync using provided approved JSON. |
| com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/McpExtensionPointManager.java | Adds locking/visibility hardening, publishes completion event, avoids stale startup push, fixes bundle state guard. |
| com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java | Removes direct LSP sync after approval; relies on manager event publication. |
| com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/McpExtensionPointManagerTest.java | Adds regression tests for stale-state scenarios (uninstall/config change/unchanged). |
| com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/events/CopilotEventConstants.java | Introduces new event topic for “MCP extension-point registration completed”. |
| synchronized (this) { | ||
| FeatureFlags flags = CopilotCore.getPlugin().getFeatureFlags(); | ||
| if (flags != null && !flags.isMcpEnabled()) { | ||
| return; | ||
| } | ||
| loadMcpRegistrationExtensionPoint(); | ||
| detectChangesInMcpContribs(persistedMcpContribs); |
| Map<String, McpRegistrationInfo> dialogInput; | ||
| synchronized (this) { | ||
| if (extMcpInfoMap.isEmpty()) { | ||
| MessageDialog.openInformation(shell, "", "No MCP server registration found"); | ||
| return null; | ||
| } | ||
| // Take a shallow snapshot so the dialog can iterate the map safely even if doRegistration() | ||
| // mutates extMcpInfoMap on the async worker. Approval flips happen on the McpRegistrationInfo | ||
| // value objects, which are shared between the snapshot and the live map by reference, so the | ||
| // user's choices are reflected in extMcpInfoMap once the dialog returns. | ||
| dialogInput = new HashMap<>(extMcpInfoMap); | ||
| } | ||
|
|
| updateApprovedMcpServerString(extMcpInfoMap); | ||
| persistExtMcpInfo(extMcpInfoMap); |
|
@martinlippert Would you mind helping to verify if this patch fixes the issue? Clone the code and switch to the branch Then run |
@jdneo Happy to it a try, but running the build doesn't work when you checkout the repo and go from there. Would be great if you could have a look or provide a snapshot repo location to be used instead. |
|
@martinlippert Have you tried to run See: https://github.com/microsoft/copilot-for-eclipse/blob/main/CONTRIBUTING.md#building-the-project |
Yes, I did that, but the build still failed for me locally. Switching Tycho to |
Since I can build the project locally now using Tycho When adding an MCP server config that works, everything seems to be fine. When I remove the entire server definition from the preferences, click "Apply", and re-enter the server config, the preferences dialog behaves fine (reports an error if the config is wrong). However, changing the existing config doesn't seem to trigger the re-evaluation of the server config (even after clicking "Apply"). I haven't tried the same scenario with the extension point yet (again), but I can take a look at the extension point variant again once the preference dialog works. My assumption is that the underlying issue affects both (preference dialog and extension point). |
Issue: #153