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 Code | Cursor | Zed | |
|---|---|---|---|
| File | .vscode/mcp.json (or user config) | .cursor/mcp.json (or ~/.cursor/mcp.json) | ~/.config/zed/settings.json |
| Top-level key | servers | mcpServers | context_servers |
| Entry shape | type + command/args (or url) | command/args (or url) | command/args/env (or url/headers) |
| Scope | workspace and user | project and global | global 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 bareuv(the editor runs it with the workspace as the working directory), but a global/user config should use the absolute path touvand 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 amaketarget 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.jsonis for any absolute-path config block you render and save locally; keep machine-specific paths out of version control. The portable.vscode/mcp.jsonand.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 throughuv, 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
serversis the VS Code key (notmcpServers). Each entry setstype:"stdio"withcommand/args, or"http"with a"url"for a remote server.- This committed form uses a bare
uvand relies on VS Code launching the server with the workspace folder as the working directory — portable across machines that haveuvonPATH. For a user-level config, or if the bare command fails, render the absolute-path version withmake vscode-config(Step 8), which pinsuv’s full path and--directory. - Secrets use an
inputsblock, 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
mcpServersis the Cursor key, with flatcommand/args/env(or a"url"for a remote server) — identical toclaude_desktop_config.json, so configs port straight over.- Project vs global:
.cursor/mcp.jsonapplies to this project;~/.cursor/mcp.jsonapplies everywhere. The global file is a GUI-launched context, so use the absoluteuvpath 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_serversis the Zed key (Zed’s older name for MCP). Each entry is flat:command,args, andenv. 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
uvand an absolute--directory— a bareuvor a relative path will not resolve when Zed launches the subprocess. Generate the exact block withmake zed-config. - Merge
context_serversinto your existingsettings.jsonrather 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
makeprints the help screen listing every target. vscode-config/cursor-config/zed-configeach emit the correct key and shape for that editor withuv’s absolute path and the project directory substituted. Pipe any of them throughpython3 -m json.toolto 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 shellPATH, so use the absolute path touv(themake *-configoutput) rather than a bareuv, 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_serversin the globalsettings.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 runmay 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 remoteurlform. - Committed project configs (
.vscode/mcp.json,.cursor/mcp.json) can use a bareuv; global configs (Zed’ssettings.json, user-level files) needuv’s absolute path and an explicit--directory. make vscode-config/cursor-config/zed-configrender each block with the right shape and real paths;make smokeproves the launch command first.
Next improvements
- Point the editors at a remote HTTPS server (VS Code
type: "http", Cursorurl, Zedurl/headers) — for example the Cloudflare Workers deploy — instead of a local subprocess. - Register the published
uvxpackage (from Publish a FastMCP Server to PyPI) so the config is justuvx <name>with no local path. - Add secret inputs (VS Code
inputs, orenvfrom a secret store) for a server that needs an API key, instead of hardcoding it.