Conversation
…mbr#433) `update_mcp_servers` defaulted with `mcp_config.get(key, {})`, which returns the default only when the key is absent. An explicit `"mcpServers": null` in mcp.json therefore yielded None, and the loader raised `AttributeError: 'NoneType' object has no attribute 'keys'` at every session start. The user lost every MCP server for the session and got no error at the point they caused it; a later reader saw an unrelated traceback. `participants` had the identical defect. It is read on the next line and iterated directly, so `"participants": null` failed the same way. That half was not reported and is fixed here too. The shape validator already rejects these values, but it is wired only into the save path, so a file already on disk never met it: hand-edited, written by an older build, or produced by any tool other than the settings dialog. Rather than validate on read and refuse to start, the two section reads now coerce a non-mapping value to empty and log a warning naming the key. No servers is a state the loader already handles, and every downstream use of both sections is inert against an empty mapping. The strict validator stays as-is on the save path, where rejecting bad input at the point of entry is still right. Tests drive the real `update_mcp_servers` rather than only the helper: with no servers configured the loader connects nothing, so the manager is constructible in a unit test. Verified by mutation that they pin the regression, not the implementation: reverting just the two call sites fails 8 of the 18, while the pure-helper and ordinary-path cases keep passing, which is the expected split.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
"mcpServers": nullinmcp.jsontook out MCP setup for the whole session.update_mcp_serversdefaulted withmcp_config.get(key, {}), which returns the default only when the key is absent, so an explicit JSON null yieldedNoneand the loader then raisedAttributeError: 'NoneType' object has no attribute 'keys'at every session start. The user lost every MCP server and got no error at the point they caused it; a later reader saw an unrelated traceback.participantshad the identical defect. It is read on the next line and iterated directly, so"participants": nullfailed the same way. That half was not in the original report and is fixed here too.The shape validator (
validate_mcp_config) already rejects both values, but it is wired only into the save path (extension.py:1059). A file already on disk never meets it: hand-edited, written by an older build, or produced by any tool other than the settings dialog. The read path runs at session start viaai_service_manager.py:257-258and again fromextension.py:1084and:1103.Solution
The two section reads go through a small helper that coerces a non-mapping value to empty and logs a warning naming the key.
Choosing to degrade rather than validate-and-refuse on read: no servers is a state the loader already handles, and every downstream use of both sections is inert against an empty mapping (
servers_configat:892,:898,:907;participants_configat:885,:886,:908). Refusing to start would turn a bad optional section into a hard failure of the whole extension. The strict validator stays untouched on the save path, where rejecting bad input at the point of entry is still the right behavior.The helper distinguishes an absent key from a present-but-unusable one: absent is silent, since configuring only one of the two sections is the ordinary shape, while a present bad value warns and names which key, because the loader reads two sections of identical shape and "invalid mcp.json" alone would not say which.
Testing
pytest1771 passed, including 18 new cases intests/test_mcp_null_config.py.Unlike a helper-only suite, these drive the real
MCPManager.update_mcp_servers: with no servers configured the loader connects nothing, so the manager is constructible in a unit test without stubbing the worker machinery. Covered: nullmcpServers, nullparticipants, both null, wrong-typed sections (string, list, int, bool), the ordinary empty config, a valid section still reachingcreate_serverswithdisabledhonored, and the reload path (update_mcp_serversis called again on every config POST) leaving the manager empty rather than half-populated.Verified by mutation that these pin the regression rather than the implementation. Reverting just the two call sites to
mcp_config.get(key, {})fails 8 of the 18; the 10 that still pass are the pure-helper cases plus the ordinary empty and valid paths, which is the expected split since those do not depend on the loader wiring. The originalAttributeErroralso reproduces directly from the old expression.tsc --noEmitclean, jest 423 passed,eslint,stylelint,prettierclean. No TypeScript changed.Risks and follow-ups
create_serverserror path.Closes #433