From workshop-skills
Use when registering a local FastMCP/stdio MCP server with Claude Desktop, when the user asks "how do I add this to Claude Desktop", "claude_desktop_config", "register the server", "why doesn't Claude see my tools", or after scaffolding/finishing an MCP server and the user wants to actually try it. Edits the OS-specific `claude_desktop_config.json` to add the server under `mcpServers` and reminds the user to restart Claude Desktop.
How this skill is triggered — by the user, by Claude, or both
Slash command
/workshop-skills:connecting-to-claude-desktopThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Claude Desktop discovers local MCP servers through a single JSON file: `claude_desktop_config.json`. To wire up a server you (1) figure out the absolute path to the project, (2) merge an entry into the config under `mcpServers`, and (3) tell the user to fully quit + relaunch Claude Desktop.
Claude Desktop discovers local MCP servers through a single JSON file: claude_desktop_config.json. To wire up a server you (1) figure out the absolute path to the project, (2) merge an entry into the config under mcpServers, and (3) tell the user to fully quit + relaunch Claude Desktop.
Claude Desktop only speaks stdio to local servers (or streamable HTTPS to remote ones — plain http://localhost is rejected). Always launch the server with TRANSPORT=stdio.
claude_desktop_config.json, "how do I install this in Claude", or report "Claude Desktop doesn't see my tools".scaffolding-python-repo, prefab, or genui — those skills produce a server but stop short of registering it.Don't use for: remote/HTTPS-hosted MCP servers (those use the in-app integrations UI, not this file); MCP servers running inside Claude Code (different mechanism — .mcp.json or claude mcp add).
| OS | Default path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
If the file doesn't exist yet, create it with {} as the starting content. The parent directory always exists once Claude Desktop has launched at least once.
These are default paths, not guaranteed paths. On Windows — especially on managed/enterprise machines, machines migrated from macOS, MSIX/AppX installs, or profiles redirected by OneDrive or Group Policy — Claude Desktop's real config file can sit somewhere completely different. The authoritative location is whatever Settings → Developer → Edit Config opens inside the app itself. If the procedure below "succeeds" but Claude Desktop still doesn't see the server, jump straight to "Recovery: when the user says it still doesn't work" further down.
Under the top-level mcpServers object, add one key per server. For a uv-managed Python project:
{
"mcpServers": {
"mcp-demo": {
"command": "/absolute/path/to/uv",
"args": [
"--directory",
"/absolute/path/to/project",
"run",
"<console-script-name>"
],
"env": {
"TRANSPORT": "stdio"
}
}
}
}
Field-by-field:
"mcp-demo") — display name in Claude Desktop. Lowercase, hyphenated, unique within the file.command — absolute path to the uv binary (e.g. /Users/<name>/.local/bin/uv). A bare "uv" only works if Claude Desktop's PATH happens to include uv's install dir, which it usually doesn't on macOS — GUI apps don't inherit the shell PATH. Resolve it with which uv (step 4 below) and use that exact string.args[0..1] — --directory <abs-path> makes uv run resolve the project regardless of where Claude Desktop's CWD is. Always absolute, never ~ or relative.args[2..] — run <console-script-name>. The console script is what's defined under [project.scripts] in pyproject.toml (e.g. mcp-backend = "package_name.server:main"). If no console script exists, use run python -m <package_name> instead.env.TRANSPORT — must be "stdio". scaffolding-python-repo defaults servers to HTTP; this env var flips them to stdio at startup, which is the only transport Claude Desktop supports for local servers.These are the actual commands to invoke via the Bash tool. Run them, capture each output, and feed it into the next step. Never hardcode paths from this skill's examples or from memory — always read them from the user's filesystem.
uname -s
# "Darwin" → macOS → "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
# "MINGW*"/"MSYS*" → Windows (use $APPDATA/Claude/claude_desktop_config.json instead)
# anything else → not officially supported; stop and tell the user
If the Bash session is already in the project root:
pwd
ls pyproject.toml # confirm — must succeed before continuing
If not, ask the user where the project is, then cd and pwd:
cd "<path the user gave>" && pwd && ls pyproject.toml
Never use ~ in the final JSON — Claude Desktop won't expand it. On Windows, write paths with forward slashes (C:/Users/...) or escaped backslashes (C:\\Users\\...).
# pull script names out of [project.scripts]
awk '/^\[project\.scripts\]/{flag=1; next} /^\[/{flag=0} flag && /=/{print $1}' pyproject.toml
The first line of output is the script name (e.g. mcp-backend). If the section is missing, fall back to python -m <package> and find the package directory:
ls src/ # → the package directory name
uv's absolute pathGUI apps on macOS don't inherit the shell's PATH, so a bare "command": "uv" often fails. Always use the absolute path:
which uv # macOS/Linux → e.g. /Users/<name>/.local/bin/uv
# Windows (Git Bash / MSYS)
which uv
# or PowerShell:
# (Get-Command uv).Source
# macOS — adjust path for Windows
CONFIG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
mkdir -p "$(dirname "$CONFIG")"
[ -f "$CONFIG" ] && cat "$CONFIG" || echo '{}'
If the file already has mcpServers entries, preserve them. The merge step below only adds or replaces your single key.
Substitute the values you captured in steps 2–4. Use Python so JSON parsing handles existing keys correctly:
export CONFIG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
export PROJECT_DIR="$(pwd)" # from step 2
export UV_BIN="$(which uv)" # from step 4
export SCRIPT_NAME="mcp-backend" # from step 3 — REPLACE with what you found
export SERVER_KEY="mcp-demo" # display name in Claude Desktop — REPLACE
python3 - <<'PY'
import json, os, pathlib
p = pathlib.Path(os.environ["CONFIG"])
cfg = json.loads(p.read_text()) if p.exists() else {}
cfg.setdefault("mcpServers", {})[os.environ["SERVER_KEY"]] = {
"command": os.environ["UV_BIN"],
"args": ["--directory", os.environ["PROJECT_DIR"], "run", os.environ["SCRIPT_NAME"]],
"env": {"TRANSPORT": "stdio"},
}
p.write_text(json.dumps(cfg, indent=2) + "\n")
print(f"wrote {p}")
PY
cat "$CONFIG" # show the user the result
Closing the window is not enough — the menu-bar/tray process keeps running and ignores the new config.
osascript -e 'quit app "Claude"', then relaunch.After relaunch, the server appears in Claude Desktop's tool picker / Connected apps. If it doesn't, do not just retry the same write — there are two distinct failure modes and they need different fixes:
cd "$PROJECT_DIR" && TRANSPORT=stdio "$UV_BIN" run "$SCRIPT_NAME". If it fails there, fix the server first.Claude Desktop has a built-in Settings → Developer → Edit Config button. Whatever file that button opens is the file Claude Desktop actually reads — that's ground truth and overrides anything in the path table above. Use it whenever the procedure "succeeded" but the user reports the server still isn't visible after a full quit + relaunch.
Prompt the user (use AskUserQuestion or a plain message):
Open Claude Desktop → Settings → Developer → Edit Config. That opens the config file Claude Desktop actually reads. Don't change anything yet — first I need to know the absolute path of the file that just opened.
- Windows: in your editor, right-click the file's tab → "Copy Path" (works in Notepad, VS Code, Notepad++). Or in File Explorer, Shift + right-click the file → "Copy as path".
- macOS: in your editor, right-click the tab → "Copy Path". Or in Finder, select the file → ⌥ (Option) + right-click → "Copy as Pathname".
Paste that path back to me.
Once the user pastes the path, verify it against what step 5 used:
REAL_CONFIG="<the path the user just pasted>"
ls -la "$REAL_CONFIG" # confirm it exists
cat "$REAL_CONFIG" # see what's actually there
If $REAL_CONFIG differs from the path you wrote earlier, the earlier write went to a stale/wrong file. Re-run step 6 with CONFIG="$REAL_CONFIG", then ask the user to fully quit + relaunch.
If the user struggles to share the path, just hand them the exact JSON to paste into the Edit Config window. Substitute the values you captured in steps 2–4 (no placeholders, no <...>):
{
"mcpServers": {
"<your SERVER_KEY>": {
"command": "<absolute path from `which uv`>",
"args": ["--directory", "<absolute project path>", "run", "<console-script name>"],
"env": { "TRANSPORT": "stdio" }
}
}
}
Tell the user: "Merge the mcpServers block into the file that's already open (keeping any existing top-level keys like preferences and any other servers under mcpServers), save, then fully quit Claude Desktop and relaunch."
Ask them again for the path of the file they just edited (same Copy-as-Path trick), then verify the file is well-formed and contains what you expect:
REAL_CONFIG="<path the user pasted>"
python3 -m json.tool < "$REAL_CONFIG" # validates JSON; errors → show the user the line/column to fix
cat "$REAL_CONFIG" # eyeball that the mcpServers entry is actually there
If the JSON is malformed, walk the user through the fix. If the JSON is correct and Claude Desktop still doesn't see the tools after a full quit + relaunch, the file is definitively right — the problem is the server. Fall back to "Common failures" below.
For reference only — do not copy this verbatim. The path and script name come from steps 2–3 on the user's machine.
{
"mcpServers": {
"<your-server-key>": {
"command": "<absolute path to uv>",
"args": [
"--directory",
"<absolute path to your project root>",
"run",
"<your console-script name>"
],
"env": {
"TRANSPORT": "stdio"
}
}
}
}
A "preferences": {} block sometimes appears alongside mcpServers — that's Claude Desktop's own state. Leave it untouched.
mcp-backend on macOS)What a real, working entry looks like for a project at /Users/jonas/Documents/code/vibber-customers/mcp-backend whose pyproject.toml declares mcp-backend = "mcp_backend.server:main" under [project.scripts], with uv installed at /Users/jonas/.local/bin/uv:
{
"preferences": {},
"mcpServers": {
"mcp-demo": {
"command": "/Users/jonas/.local/bin/uv",
"args": [
"--directory",
"/Users/jonas/Documents/code/vibber-customers/mcp-backend",
"run",
"mcp-backend"
],
"env": {
"TRANSPORT": "stdio"
}
}
}
}
Use this to sanity-check the shape — keys, indentation, where --directory goes — but always substitute the user's own absolute paths and console-script name (steps 2–4 above).
| Symptom | Cause | Fix |
|---|---|---|
| Claude Desktop shows the server but tools never load | command: "uv" not on Claude Desktop's PATH (GUI apps on macOS don't inherit shell PATH) | Use the absolute path to uv: run which uv in a terminal and put the full path (e.g. /Users/jonas/.local/bin/uv) as command |
| "Server disconnected" immediately | Server is still defaulting to HTTP transport | Confirm env.TRANSPORT = "stdio" is set, and that the server's main() actually branches on settings.TRANSPORT |
| "Server disconnected" after a few seconds | Server crashes on startup (import error, missing dep) | Run the same command manually in a terminal: cd <project> && TRANSPORT=stdio uv run <script> and read stderr |
| New tool added in code but not visible in Claude Desktop | Claude Desktop caches the tool list at connect time | Fully quit (Cmd-Q) and relaunch; reloading the window is not enough |
| Edit didn't take effect at all | Edited the wrong file (e.g. claude.json for Claude Code, not claude_desktop_config.json) | Re-check the path table above |
| Procedure "succeeded", user says nothing changed, still no server in Claude Desktop | Wrote to the default path, but Claude Desktop reads from a different location (managed/MSIX/OneDrive-redirected install on Windows; uncommon on macOS) | Follow the "Recovery" section above — have the user open Settings → Developer → Edit Config, share that path, and re-write or paste the JSON into the file Claude Desktop actually uses |
If the user has just finished scaffolding, building a Prefab app, or wiring up GenUI and hasn't yet registered the server in Claude Desktop, surface this skill proactively:
Your server is ready. To try it in Claude Desktop, you'll need to register it in
claude_desktop_config.json— want me to do that? (Seeconnecting-to-claude-desktop.)
Don't run the edit silently — show the diff first and confirm the project path with the user.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub vibber-ai/workshop-skills --plugin workshop-skills