Skip to content

feat: add Zed editor MCP context server support#46

Open
damianoneill wants to merge 3 commits into
njayp:mainfrom
damianoneill:feat/zed-editor-support
Open

feat: add Zed editor MCP context server support#46
damianoneill wants to merge 3 commits into
njayp:mainfrom
damianoneill:feat/zed-editor-support

Conversation

@damianoneill
Copy link
Copy Markdown
Contributor

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

  • Tests added or updated
  • Docs added or updated

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
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is solid. The design choices are well-thought-out:

  • The Preprocessor/Serializer interfaces are clean abstractions that leave all existing managers untouched.
  • The bytes.Clone() before hujson.Standardize is the right fix — the in-place mutation would silently corrupt originalData without it.
  • The surgical AST update in Serialize correctly preserves comments, trailing commas, and formatting in the rest of the file.
  • The fallback path in Serialize (when hujson.Parse fails on a non-empty original) still preserves all non-context_servers settings via MarshalJSON/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.

tj-smith47 added a commit to tj-smith47/brontes that referenced this pull request May 14, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Zed editor support

2 participants