This guide is written for non-technical users and is fully step-by-step from start to finish.
Claude Desktop (stdio config) -> mcp-remote -> PiecesOS MCP
Claude Desktop's local JSON config launches command-line tools. PiecesOS exposes MCP over HTTP/SSE.
mcp-remote is the bridge between the two.
Per Pieces docs, Claude Desktop can be configured in multiple ways. This guide standardizes on direct mcp-remote integration because it is easier to verify and troubleshoot in one place (claude_desktop_config.json) and avoids stale pieces.exe ... mcp start path issues.
- Internet is required on first run of
npx -y mcp-remote ...so it can downloadmcp-remote. - Keep Pieces Desktop running during setup.
- If you used a previous Pieces CLI MCP config, this guide removes it safely in Step 6.
Install these first:
- Pieces Desktop (runs PiecesOS) - https://pieces.app
- Claude Desktop - https://claude.ai/download
- Node.js LTS (
npxdepends on this)
If you're unsure whether Node.js is installed, run the check first.
If not installed, run the install one-liner right below it.
command -v node >/dev/null 2>&1 && node --version || echo "Node.js is not installed"command -v brew >/dev/null 2>&1 || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Follow the Homebrew prompts, then reload your shell environment so brew is available:
eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null || /usr/local/bin/brew shellenv)"Now install Node.js:
brew install nodeThen rerun the macOS check command from Step 1:
command -v node >/dev/null 2>&1 && node --version || echo "Node.js is not installed"command -v node >/dev/null 2>&1 && node --version || echo "Node.js is not installed"command -v node >/dev/null 2>&1 || (curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - && sudo apt-get install -y nodejs)if (Get-Command node -ErrorAction SilentlyContinue) { node --version } else { Write-Host "Node.js is not installed" }if (-not (Get-Command node -ErrorAction SilentlyContinue)) { winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements }Then verify Node/npm/npx:
node --version && npm --version && npx --versionnode --version; npm --version; npx --versionFinally, verify mcp-remote can be resolved:
npx -y mcp-remote@latest --versionNote: If
mcp-remotefails to resolve or you encounter errors, you may need to update Node.js and npm to the latest LTS version:macOS (Homebrew):
brew upgrade nodeWindows (winget):
winget upgrade OpenJS.NodeJS.LTSLinux (Ubuntu/Debian):
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - && sudo apt-get install -y nodejsAfter updating, restart your terminal and rerun the verification command above.
Run the one-liner for your OS:
open "pieces://launch"xdg-open "pieces://launch"Start-Process "pieces://launch"If nothing opens, launch Pieces Desktop manually once from your app launcher, then re-run the command.
Expected result: PiecesOS is active.
PiecesOS uses a local port in 39300-39333.
PORT=$(for p in $(seq 39300 39333); do curl -fsS "http://localhost:$p/.well-known/version" >/dev/null 2>&1 && echo "$p" && break; done); echo "Detected PORT=$PORT"$PORT = 39300..39333 | ForEach-Object { try { $r = Invoke-WebRequest -Uri "http://localhost:$($_)/.well-known/version" -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop; if ($r.StatusCode -eq 200) { $_ } } catch {} } | Select-Object -First 1; Write-Host "Detected PORT=$PORT"If no port is detected, close/reopen Pieces Desktop and run again.
Config file path by OS:
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
Optional directory check first:
[ -d "$HOME/Library/Application Support/Claude" ] && echo "Found: $HOME/Library/Application Support/Claude" || echo "Not found yet (will be created)"[ -d "$HOME/.config/Claude" ] && echo "Found: $HOME/.config/Claude" || echo "Not found yet (will be created)"if (Test-Path "$env:APPDATA\Claude") { Write-Host "Found: $env:APPDATA\Claude" } else { Write-Host "Not found yet (will be created)" }Now run one open/create command:
CFG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"; mkdir -p "$(dirname "$CFG")"; [ -f "$CFG" ] || printf '{\n "mcpServers": {}\n}\n' > "$CFG"; open "$CFG"CFG="$HOME/.config/Claude/claude_desktop_config.json"; mkdir -p "$(dirname "$CFG")"; [ -f "$CFG" ] || printf '{\n "mcpServers": {}\n}\n' > "$CFG"; xdg-open "$CFG"New-Item -ItemType Directory -Force "$env:APPDATA\Claude" | Out-Null; if (!(Test-Path "$env:APPDATA\Claude\claude_desktop_config.json")) { '{ "mcpServers": {} }' | Set-Content "$env:APPDATA\Claude\claude_desktop_config.json" }; notepad "$env:APPDATA\Claude\claude_desktop_config.json"Alternative UI path: Claude Desktop -> Settings -> Developer -> Edit Config.
CFG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"; [ -f "$CFG" ] && cp "$CFG" "${CFG}.backup.$(date +%Y%m%d-%H%M%S)" && echo "Backup created" || echo "No existing config to back up"CFG="$HOME/.config/Claude/claude_desktop_config.json"; [ -f "$CFG" ] && cp "$CFG" "${CFG}.backup.$(date +%Y%m%d-%H%M%S)" && echo "Backup created" || echo "No existing config to back up"$cfg="$env:APPDATA\Claude\claude_desktop_config.json"; if (Test-Path $cfg) { Copy-Item $cfg "$cfg.backup.$(Get-Date -Format 'yyyyMMdd-HHmmss')"; Write-Host "Backup created" } else { Write-Host "No existing config to back up" }If your config has an old block like this, remove it:
"Pieces": {
"command": "C:\\...\\pieces.exe",
"args": ["--ignore-onboarding", "mcp", "start"]
}Run one cleanup command:
CFG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"; [ -f "$CFG" ] && node -e 'const fs=require("fs");const p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,"utf8"));const servers=j.mcpServers||{};for(const [k,s] of Object.entries({...servers})){const cmd=String((s&&s.command)||"").toLowerCase();const args=(Array.isArray(s&&s.args)?s.args:[]).map(v=>String(v).toLowerCase());if(cmd.includes("pieces")&&args.includes("mcp")&&args.includes("start"))delete servers[k];}j.mcpServers=servers;fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");console.log("Legacy Pieces CLI MCP entries removed if present.");' "$CFG" || echo "Config file not found yet; continuing."CFG="$HOME/.config/Claude/claude_desktop_config.json"; [ -f "$CFG" ] && node -e 'const fs=require("fs");const p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,"utf8"));const servers=j.mcpServers||{};for(const [k,s] of Object.entries({...servers})){const cmd=String((s&&s.command)||"").toLowerCase();const args=(Array.isArray(s&&s.args)?s.args:[]).map(v=>String(v).toLowerCase());if(cmd.includes("pieces")&&args.includes("mcp")&&args.includes("start"))delete servers[k];}j.mcpServers=servers;fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");console.log("Legacy Pieces CLI MCP entries removed if present.");' "$CFG" || echo "Config file not found yet; continuing."$p="$env:APPDATA\Claude\claude_desktop_config.json"; if (Test-Path $p) { $j=Get-Content $p -Raw | ConvertFrom-Json; if (-not $j.mcpServers) { $j | Add-Member -NotePropertyName mcpServers -NotePropertyValue ([ordered]@{}) -Force }; $new=[ordered]@{}; foreach ($prop in $j.mcpServers.PSObject.Properties) { $s=$prop.Value; $cmd=("$($s.command)").ToLower(); $args=@(); if ($s.args) { $args=@($s.args | ForEach-Object { "$_".ToLower() }) }; if (-not ($cmd -like "*pieces*" -and $args -contains "mcp" -and $args -contains "start")) { $new[$prop.Name]=$s } }; $j.mcpServers=$new; $j | ConvertTo-Json -Depth 50 | Set-Content $p; Write-Host "Legacy Pieces CLI MCP entries removed if present." } else { Write-Host "Config file not found yet; continuing." }If you also added old Pieces entries in Claude Desktop -> Settings -> Connectors, remove those old entries too so you do not have duplicate/conflicting configurations.
This step keeps your other MCP servers and only updates mcpServers.pieces.
It auto-detects the current PiecesOS port, so this step still works even if you opened a new terminal window.
CFG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"; PORT="$(for p in $(seq 39300 39333); do curl -fsS "http://localhost:$p/.well-known/version" >/dev/null 2>&1 && echo "$p" && break; done)"; [ -n "$PORT" ] || { echo "Could not detect PiecesOS port. Start Pieces Desktop and rerun Step 3."; exit 1; }; node -e 'const fs=require("fs");const p=process.argv[1];const port=process.argv[2];let j={};try{j=JSON.parse(fs.readFileSync(p,"utf8"));}catch{};if(!j||typeof j!=="object")j={};if(!j.mcpServers||typeof j.mcpServers!=="object")j.mcpServers={};j.mcpServers.pieces={command:"npx",args:["-y","mcp-remote",`http://localhost:${port}/model_context_protocol/2024-11-05/sse`,`--allow-http`,`--transport`,`sse-only`]};fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");console.log("Updated mcpServers.pieces using PORT="+port);' "$CFG" "$PORT"CFG="$HOME/.config/Claude/claude_desktop_config.json"; PORT="$(for p in $(seq 39300 39333); do curl -fsS "http://localhost:$p/.well-known/version" >/dev/null 2>&1 && echo "$p" && break; done)"; [ -n "$PORT" ] || { echo "Could not detect PiecesOS port. Start Pieces Desktop and rerun Step 3."; exit 1; }; node -e 'const fs=require("fs");const p=process.argv[1];const port=process.argv[2];let j={};try{j=JSON.parse(fs.readFileSync(p,"utf8"));}catch{};if(!j||typeof j!=="object")j={};if(!j.mcpServers||typeof j.mcpServers!=="object")j.mcpServers={};j.mcpServers.pieces={command:"npx",args:["-y","mcp-remote",`http://localhost:${port}/model_context_protocol/2024-11-05/sse`,`--allow-http`,`--transport`,`sse-only`]};fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");console.log("Updated mcpServers.pieces using PORT="+port);' "$CFG" "$PORT"$PORT = 39300..39333 | ForEach-Object { try { $r = Invoke-WebRequest -Uri "http://localhost:$($_)/.well-known/version" -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop; if ($r.StatusCode -eq 200) { $_ } } catch {} } | Select-Object -First 1; if (-not $PORT) { throw "Could not detect PiecesOS port. Start Pieces Desktop and rerun Step 3." }; $cfg="$env:APPDATA\Claude\claude_desktop_config.json"; if (Test-Path $cfg) { $j=Get-Content $cfg -Raw | ConvertFrom-Json } else { $j=[pscustomobject]@{} }; if (-not $j.mcpServers) { $j | Add-Member -NotePropertyName mcpServers -NotePropertyValue ([pscustomobject]@{}) -Force }; $j.mcpServers | Add-Member -NotePropertyName pieces -NotePropertyValue ([pscustomobject]@{ command="npx"; args=@("-y","mcp-remote","http://localhost:$PORT/model_context_protocol/2024-11-05/sse","--allow-http","--transport","sse-only") }) -Force; $j | ConvertTo-Json -Depth 50 | Set-Content $cfg; Write-Host "Updated mcpServers.pieces using PORT=$PORT"CFG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"; node -e 'const fs=require("fs");const j=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));if(!j?.mcpServers?.pieces)throw new Error("mcpServers.pieces missing");console.log("Config valid. pieces entry found.");' "$CFG"CFG="$HOME/.config/Claude/claude_desktop_config.json"; node -e 'const fs=require("fs");const j=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));if(!j?.mcpServers?.pieces)throw new Error("mcpServers.pieces missing");console.log("Config valid. pieces entry found.");' "$CFG"$cfg="$env:APPDATA\Claude\claude_desktop_config.json"; try { $j=Get-Content $cfg -Raw | ConvertFrom-Json; if ($null -eq $j.mcpServers.pieces) { throw "mcpServers.pieces missing" }; Write-Host "Config valid. pieces entry found." } catch { Write-Error $_ }Recommended (safest for non-technical users):
- Fully quit Claude Desktop.
- Reopen Claude Desktop.
Optional command one-liners:
osascript -e 'quit app "Claude"' >/dev/null 2>&1; sleep 2; open -a "Claude"pkill -x Claude >/dev/null 2>&1; sleep 2; (command -v claude-desktop >/dev/null 2>&1 && nohup claude-desktop >/dev/null 2>&1 &) || echo "Reopen Claude Desktop from your app launcher"Get-Process Claude -ErrorAction SilentlyContinue | Stop-Process -Force; Start-Sleep -Seconds 2; $paths=@("$env:LOCALAPPDATA\Programs\Claude\Claude.exe","$env:LOCALAPPDATA\AnthropicClaude\Claude.exe"); $exe=$paths | Where-Object { Test-Path $_ } | Select-Object -First 1; if ($exe) { Start-Process $exe } else { Write-Host "Reopen Claude Desktop from Start menu" }- Open a new Claude Desktop chat.
- Look for the tools/hammer icon near the chat box.
- Click it and confirm Pieces tools are listed.
- Ask:
"What Pieces MCP tools are available?"
If you see Pieces tools, setup is complete.
| Problem | What to do |
|---|---|
npx not found |
Install Node.js LTS, then rerun node --version && npm --version && npx --version |
First run fails at npx with network error |
Confirm internet access, then rerun npx -y mcp-remote@latest --version |
| JSON parsing/config errors | Rerun Step 8 validate command and fix JSON syntax |
| No tools icon or no Pieces tools | Fully quit and reopen Claude Desktop |
| Could not connect to localhost | Make sure Pieces Desktop is running, then rerun Step 3 |
404 or transport error |
Keep --transport set to sse-only for this endpoint |
| Other MCP servers disappeared | Restore from backup in Step 5 and rerun Step 7 (safe merge command) |
If PiecesOS is on another machine, first expose it using ngrok, then use the ngrok HTTPS URL:
{
"mcpServers": {
"pieces-remote": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://YOUR_NGROK_URL.ngrok.app/model_context_protocol/2024-11-05/sse",
"--transport",
"sse-only"
]
}
}
}- Bridging Local MCP Clients to Remote Servers with mcp-remote - Full
mcp-remotereference - Claude Desktop Setup Guide - Full Claude Desktop options
- Connecting to PiecesOS from the Outside World via Ngrok - Remote tunnel setup
| ← Back to MCP Guides |