feat: add Zed editor MCP context server support#46
Conversation
Adds enable, disable, and list subcommands for Zed, following the same pattern as the existing Claude Desktop, VSCode, and Cursor integrations. Zed's settings.json is JSONC - it contains line comments, block comments, and trailing commas that encoding/json rejects outright. A Preprocessor interface is introduced so loadConfig can normalise raw bytes before unmarshaling, without touching the other managers. zed.Config implements it using github.com/tailscale/hujson. Because settings.json is Zed's general editor config file (themes, fonts, keybindings etc.), zed.Config uses custom UnmarshalJSON and MarshalJSON to read and write only the context_servers key, leaving everything else untouched. After the first write the file is strict JSON, which Zed reads without issue. Default config paths: macOS/Linux: ~/.config/zed/settings.json (XDG_CONFIG_HOME respected) Windows: %APPDATA%\Zed\settings.json Workspace: .zed/settings.json Closes njayp#45
There was a problem hiding this comment.
Overall this is well-structured and follows the existing patterns cleanly. The JSONC preprocessing approach via hujson is a reasonable solution. Two minor notes:
hujson marked as // indirect in go.mod
github.com/tailscale/hujson is directly imported by internal/cfgmgr/manager/zed/config.go, so it should not carry the // indirect marker. Running go mod tidy should fix this.
macOS does not check XDG_CONFIG_HOME
zed_darwin.go hardcodes ~/.config/zed/settings.json without checking XDG_CONFIG_HOME, while zed_linux.go correctly checks it first. macOS doesn't use XDG by convention, but some developers configure it there. The inconsistency is at least worth a comment explaining the deliberate omission.
Note on JSONC loss (acknowledged in PR description)
After the first write, all comments and trailing commas in the user's settings.json are gone permanently. This is described in the PR body and Zed handles standard JSON fine, but it's a meaningful side-effect for users who rely on comments to organize their config. Consider printing a one-time notice like "Note: settings.json will be converted from JSONC to JSON" during enable/disable.
Address review feedback on the Zed editor MCP support: 1. hujson indirect marker — go mod tidy already resolved this; hujson is now correctly listed as a direct dependency. 2. macOS XDG_CONFIG_HOME — deliberate omission; macOS does not use the XDG spec and Zed hardcodes ~/.config/zed on that platform regardless. A comment was added to zed_darwin.go to document the decision. 3. JSONC loss — rather than warning the user, the loss is prevented entirely. A new Serializer interface allows a Config to control how it is written back to disk. Zed's Config implements Serialize() using the hujson AST: Parse() reads the original JSONC into a syntax tree (comments and trailing commas fully represented), the context_servers node is surgically updated or appended, and Pack() emits the tree back byte-for-byte with all other content unchanged. A subtle bug was also fixed: hujson.Standardize() rewrites comment characters to spaces in-place via sub-slices that alias the original input buffer. Without a defensive bytes.Clone() before preprocessing, the originalData field was silently corrupted before Serialize() could use it. The clone ensures the raw JSONC is always available for the round-trip write.
There was a problem hiding this comment.
The implementation is solid. The design choices are well-thought-out:
- The
Preprocessor/Serializerinterfaces are clean abstractions that leave all existing managers untouched. - The
bytes.Clone()beforehujson.Standardizeis the right fix — the in-place mutation would silently corruptoriginalDatawithout it. - The surgical AST update in
Serializecorrectly preserves comments, trailing commas, and formatting in the rest of the file. - The fallback path in
Serialize(whenhujson.Parsefails on a non-emptyoriginal) still preserves all non-context_serverssettings viaMarshalJSON/c.extra, so it can't silently drop user settings — just loses JSONC formatting, which Zed handles fine.
One minor note: the appended BeforeExtra: hujson.Extra("\n ") when inserting a new context_servers key hardcodes 2-space indentation. That's the right default for Zed's shipped settings format, but it will look slightly off if someone happens to use 4-space indent in their file. Not worth blocking on.
Adds `mcp zed {enable, disable, list}` symmetric with the Claude / Cursor /
VSCode editor managers, plus the structural differences Zed requires:
- `context_servers` top-level key (not `mcpServers` or `servers`).
- ZedConfig preserves every non-`context_servers` top-level key via a
flattened pass-through map — themes, fonts, keymaps, and any other
user-authored settings.json content round-trip verbatim through
enable / disable / save.
- `EditorConfig::preprocess` trait hook strips JSONC syntax (line and
block comments + trailing commas) on load so a hand-edited Zed
settings.json parses cleanly. Mirrors the Preprocessor interface from
njayp/ophis#46.
- Per-OS path resolvers (zed_config_path / zed_workspace_path) honor
XDG_CONFIG_HOME on Linux and APPDATA on Windows; macOS uses
~/.config/zed/settings.json (Zed's chosen layout, not Library/AS).
- --workspace flag on all three leaves routes through .zed/settings.json
under \$CWD, parity with Cursor/VSCode workspace mode.
12 integration tests in tests/manager_zed.rs cover the full surface:
first-write, disable, unrelated-key preservation, JSONC input, backup
semantics, no-op missing-server disable, workspace path resolution,
env merging, log-level threading, and malformed-env rejection.
README compare-table flips "Zed pending" → ✓ on the brontes column.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
What does this PR do?
Adds enable, disable, and list subcommands for Zed, following the same pattern as the existing Claude Desktop, VSCode, and Cursor integrations.
Zed's settings.json is JSONC - it contains line comments, block comments, and trailing commas that encoding/json rejects outright. A Preprocessor interface is introduced so loadConfig can normalise raw bytes before unmarshaling, without touching the other managers. zed.Config implements it using github.com/tailscale/hujson.
Because settings.json is Zed's general editor config file (themes, fonts, keybindings etc.), zed.Config uses custom UnmarshalJSON and MarshalJSON to read and write only the context_servers key, leaving everything else untouched. After the first write the file is strict JSON, which Zed reads without issue.
Default config paths:
macOS/Linux: ~/.config/zed/settings.json (XDG_CONFIG_HOME respected)
Windows: %APPDATA%\Zed\settings.json
Workspace: .zed/settings.json
Related Issues
Closes #45
Checklist