Register a FastMCP Server with Claude Desktop and Claude Code wired a server into the two Claude clients. The AI-assisted code editors speak MCP too — VS Code (Copilot agent mode), Cursor, and Zed — and each keeps its config in a different file, under a different key, in a slightly different shape. This article takes one stdio FastMCP server and connects it to all three.

VS CodeCursorZed
File.vscode/mcp.json (or user config).cursor/mcp.json (or ~/.cursor/mcp.json)~/.config/zed/settings.json
Top-level keyserversmcpServerscontext_servers
Entry shapetype + command/args (or url)command/args (or url)command/args/env (or url/headers)
Scopeworkspace and userproject and globalglobal only

Two things are common to all three, and to the Claude clients before them:

  • stdio is launched as a subprocess, so the same “smoke-test the launch command first” rule applies.
  • A GUI editor launched from the Dock does not inherit your shell PATH. A committed project config can use a bare uv (the editor runs it with the workspace as the working directory), but a global/user config should use the absolute path to uv and name the project directory explicitly.

What you will build

  • A small stdio FastMCP server (greet, word_count).
  • A stdio smoke test that proves the launch command before any editor.
  • Portable committed configs for VS Code (.vscode/mcp.json) and Cursor (.cursor/mcp.json), plus a make target that renders each editor’s block with absolute paths for the global case.

Prerequisites

  • macOS 13+ with Homebrew (brew.sh) and uv 0.5+ (brew install uv).
  • At least one of: VS Code with GitHub Copilot (MCP tools are used in agent mode), Cursor, or Zed.
  • Familiarity with MCP servers (see Build an MCP Server with FastMCP).

Step 1: Scaffold and add hygiene

Create the files

mkdir -p mcp-server-vscode-cursor-zed-macos
cd mcp-server-vscode-cursor-zed-macos
touch .gitignore

Add the code: .gitignore

# Python
__pycache__/
*.py[cod]
.venv/
.uv/
.pytest_cache/
.ruff_cache/
.mypy_cache/
logs/
*.log
# Local editor config renders with absolute paths
*.local.json
# OS / editor noise
.DS_Store

Detailed breakdown

  • *.local.json is for any absolute-path config block you render and save locally; keep machine-specific paths out of version control. The portable .vscode/mcp.json and .cursor/mcp.json (Steps 5–6) are committed on purpose.

Step 2: Initialize the project

Create the files

uv init --name macmcp --no-workspace
rm -f main.py hello.py
uv add fastmcp

Add the code: pyproject.toml

[project]
name = "macmcp"
version = "0.1.0"
description = "A FastMCP server wired into VS Code, Cursor, and Zed"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastmcp>=3.4.4",
]

Detailed breakdown

  • One dependency: fastmcp. The editors launch the server through uv, so no global install is needed.

Step 3: Write the server

Create the file

touch server.py

Add the code: server.py

"""A small FastMCP server used to demonstrate editor integration.

The tools are incidental — the point is wiring this one stdio server into three
editors (VS Code, Cursor, Zed), each with its own config format. stdio is the
transport all three launch as a subprocess; HTTP is available for a remote
deployment.
"""

import os

from fastmcp import FastMCP

mcp = FastMCP("mac-greeter")


@mcp.tool
def greet(name: str) -> str:
    """Return a greeting for name."""
    return f"Hello, {name}! Served over MCP."


@mcp.tool
def word_count(text: str) -> int:
    """Count the words in text."""
    return len(text.split())


def main() -> None:
    if os.environ.get("MCP_TRANSPORT", "stdio").lower() == "http":
        mcp.run(
            transport="http",
            host=os.environ.get("MCP_HOST", "127.0.0.1"),
            port=int(os.environ.get("MCP_PORT", "8000")),
        )
    else:
        mcp.run()  # stdio: launched as a subprocess by the editor


if __name__ == "__main__":
    main()

Detailed breakdown

  • Two ordinary tools and the stdio/HTTP transport switch. Editors launch the default stdio transport as a subprocess.

Step 4: Smoke-test the launch command

Confirm the exact command the editors will run actually starts the server.

Create the file

mkdir -p scripts
touch scripts/smoke.py

Add the code: scripts/smoke.py

"""Smoke-test the server over stdio, the way an editor launches it.

A green run proves the exact launch command works before you paste it into any
editor's config. No editor, no GUI.
"""

import asyncio

from fastmcp import Client

client = Client("server.py")


async def main() -> None:
    async with client:
        tools = sorted(t.name for t in await client.list_tools())
        print("tools:", tools)
        greeting = (await client.call_tool("greet", {"name": "Ada"})).data
        print("greet ->", greeting)
        count = (await client.call_tool("word_count", {"text": "one two three"})).data
        print("word_count ->", count)


if __name__ == "__main__":
    asyncio.run(main())

Run it:

uv run python scripts/smoke.py
tools: ['greet', 'word_count']
greet -> Hello, Ada! Served over MCP.
word_count -> 3

Detailed breakdown

  • Client("server.py") launches the script over stdio, exactly as an editor does. A green run means any later “failed to start” is a config problem (path, cwd, PATH), not the server.

Step 5: VS Code

VS Code reads MCP servers from .vscode/mcp.json in the workspace (or a user-level config via the MCP: Open User Configuration command). Its top-level key is servers, and each entry names a type.

Create the file

mkdir -p .vscode
touch .vscode/mcp.json

Add the code: .vscode/mcp.json

{
  "servers": {
    "mac-greeter": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "server.py"]
    }
  }
}

Detailed breakdown

  • servers is the VS Code key (not mcpServers). Each entry sets type: "stdio" with command/args, or "http" with a "url" for a remote server.
  • This committed form uses a bare uv and relies on VS Code launching the server with the workspace folder as the working directory — portable across machines that have uv on PATH. For a user-level config, or if the bare command fails, render the absolute-path version with make vscode-config (Step 8), which pins uv’s full path and --directory.
  • Secrets use an inputs block, referenced as ${input:id}, so tokens are prompted and stored by VS Code instead of written into the file:
{
  "inputs": [
    { "type": "promptString", "id": "api-key", "description": "API key", "password": true }
  ],
  "servers": {
    "mac-greeter": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "server.py"],
      "env": { "API_KEY": "${input:api-key}" }
    }
  }
}

Open the folder in VS Code, then enable the server: MCP tools are used in Copilot agent mode, so open the Chat view, switch to Agent, and the mac-greeter tools appear in the tools picker. A Start action on the server entry in mcp.json launches it; hover for its status.

Step 6: Cursor

Cursor uses the same mcpServers shape as Claude Desktop, in .cursor/mcp.json at the project root (or ~/.cursor/mcp.json for all projects).

Create the file

mkdir -p .cursor
touch .cursor/mcp.json

Add the code: .cursor/mcp.json

{
  "mcpServers": {
    "mac-greeter": {
      "command": "uv",
      "args": ["run", "server.py"]
    }
  }
}

Detailed breakdown

  • mcpServers is the Cursor key, with flat command/args/env (or a "url" for a remote server) — identical to claude_desktop_config.json, so configs port straight over.
  • Project vs global: .cursor/mcp.json applies to this project; ~/.cursor/mcp.json applies everywhere. The global file is a GUI-launched context, so use the absolute uv path there (make cursor-config).
  • After saving, open Cursor Settings → MCP (or Tools & Integrations); the server appears with a toggle and a tool count. Enable it, then reference its tools from the agent.

Step 7: Zed

Zed keeps MCP servers (“context servers”) in its settings.json under context_servers. There is no per-project file, so this is a global edit.

Create/open the file

# Zed: press Cmd-, or run the "zed: open settings" command.
# The file lives at:
open -a Zed ~/.config/zed/settings.json

Add the code: ~/.config/zed/settings.json

{
  "context_servers": {
    "mac-greeter": {
      "command": "/opt/homebrew/bin/uv",
      "args": ["run", "--directory", "/Users/you/mcp-server-vscode-cursor-zed-macos", "server.py"],
      "env": {}
    }
  }
}

Detailed breakdown

  • context_servers is the Zed key (Zed’s older name for MCP). Each entry is flat: command, args, and env. A remote server uses "url" with optional "headers" instead.
  • This is a global file with no project context, so it must use the absolute path to uv and an absolute --directory — a bare uv or a relative path will not resolve when Zed launches the subprocess. Generate the exact block with make zed-config.
  • Merge context_servers into your existing settings.json rather than replacing the file. Zed picks up the change on save; the server appears in the Agent panel’s tool list.

Step 8: The Makefile

Render each editor’s config block with your real uv path and project directory filled in — the safe way to produce the global/user-level configs.

Create the file

touch Makefile

Add the code: Makefile

.DEFAULT_GOAL := help

# Absolute paths for the GUI-app configs: editors launched from the Dock/Finder
# do not inherit your shell PATH, so global configs must name uv by full path
# and pass the project directory explicitly.
UV  := $(shell command -v uv)
DIR := $(shell pwd)

.PHONY: help install smoke vscode-config cursor-config zed-config clean

help:  ## Show this help screen
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
		| awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[36m%-14s\033[0m %s\n", $$1, $$2}'

install:  ## Sync dependencies
	uv sync

smoke:  ## Launch the server over stdio and call its tools (no editor)
	uv run python scripts/smoke.py

vscode-config:  ## Print a VS Code mcp.json block (absolute paths, "servers" key)
	@printf '%s\n' \
	'{' \
	'  "servers": {' \
	'    "mac-greeter": {' \
	'      "type": "stdio",' \
	'      "command": "$(UV)",' \
	'      "args": ["run", "--directory", "$(DIR)", "server.py"]' \
	'    }' \
	'  }' \
	'}'

cursor-config:  ## Print a Cursor mcp.json block (absolute paths, "mcpServers" key)
	@printf '%s\n' \
	'{' \
	'  "mcpServers": {' \
	'    "mac-greeter": {' \
	'      "command": "$(UV)",' \
	'      "args": ["run", "--directory", "$(DIR)", "server.py"]' \
	'    }' \
	'  }' \
	'}'

zed-config:  ## Print a Zed settings.json block ("context_servers" key)
	@printf '%s\n' \
	'{' \
	'  "context_servers": {' \
	'    "mac-greeter": {' \
	'      "command": "$(UV)",' \
	'      "args": ["run", "--directory", "$(DIR)", "server.py"],' \
	'      "env": {}' \
	'    }' \
	'  }' \
	'}'

clean:  ## Remove caches
	rm -rf .pytest_cache __pycache__ scripts/__pycache__

Detailed breakdown

  • Plain make prints the help screen listing every target.
  • vscode-config / cursor-config / zed-config each emit the correct key and shape for that editor with uv’s absolute path and the project directory substituted. Pipe any of them through python3 -m json.tool to confirm it parses before pasting.

Troubleshooting

  • The server does not appear at all. Check the key name — servers (VS Code), mcpServers (Cursor), context_servers (Zed) are not interchangeable, and the wrong key is silently ignored.
  • “Failed to start” / it never connects. Almost always PATH: a GUI editor has no shell PATH, so use the absolute path to uv (the make *-config output) rather than a bare uv, especially in global/user configs.
  • VS Code shows the config but no tools. MCP tools surface in Copilot agent mode; switch the Chat view to Agent and start the server from mcp.json.
  • Zed ignores the entry. Confirm it is under context_servers in the global settings.json (there is no project-level Zed file) and that the JSON is valid (a trailing comma breaks the whole file).
  • First launch is slow. uv run may resolve the environment on first start; give it a moment, then reload the editor’s MCP servers.
  • Changes not picked up. Reload the window (VS Code/Cursor) or re-save settings.json (Zed); some versions cache servers until reload.

Recap

  • The three editors read MCP servers from different files and keys — servers (VS Code), mcpServers (Cursor), context_servers (Zed) — but all launch a stdio server as a subprocess and all support a remote url form.
  • Committed project configs (.vscode/mcp.json, .cursor/mcp.json) can use a bare uv; global configs (Zed’s settings.json, user-level files) need uv’s absolute path and an explicit --directory.
  • make vscode-config / cursor-config / zed-config render each block with the right shape and real paths; make smoke proves the launch command first.

Next improvements

  • Point the editors at a remote HTTPS server (VS Code type: "http", Cursor url, Zed url/headers) — for example the Cloudflare Workers deploy — instead of a local subprocess.
  • Register the published uvx package (from Publish a FastMCP Server to PyPI) so the config is just uvx <name> with no local path.
  • Add secret inputs (VS Code inputs, or env from a secret store) for a server that needs an API key, instead of hardcoding it.