Claude Desktop & the MCP Bridge

Overview

Sound Suite runs on your machine and speaks HTTP. Claude Desktop speaks stdio MCP — it launches a process and talks to it over standard input and output. Those don't meet on their own.

The bridge (bridge.mjs) is the piece in between: a small, stateless forwarder that Claude Desktop launches as a subprocess, which translates stdio MCP calls into HTTP requests against your local Sound Suite instance and translates the answers back.

┌─────────────────┐   stdio MCP    ┌──────────────┐   HTTP    ┌────────────────────┐
│  Claude Desktop │ ◄────────────► │  bridge.mjs  │ ◄───────► │  Sound Suite       │
│                 │  (subprocess)  │  (forwarder) │           │  127.0.0.1:3000    │
└─────────────────┘                └──────────────┘           └────────────────────┘
                                                                        │
                                                              ┌─────────▼─────────┐
                                                              │ SQLite + LanceDB  │
                                                              │ (your documents)  │
                                                              └───────────────────┘

Nothing in this path leaves your machine. The bridge talks to 127.0.0.1 by default, and your documents are never uploaded anywhere.


How the bridge works

The bridge is deliberately thin. It is a forwarder, not a layer of cleverness — it holds no state, caches almost nothing, and never rewrites your results.

It proxies exactly two things

MCP method Becomes
tools/list GET /api/mcp/tools?profile=<profile>
tools/call POST /api/mcp/execute with { tool, params, profile }

That's the whole surface. Tool definitions come from the server, so tools added to Sound Suite appear in Claude Desktop without touching the bridge.

It relays long-running jobs as progress

Research and report jobs can run for minutes. Rather than blocking, the bridge polls the job's event stream in the background and converts events into MCP notifications:

Sound Suite event MCP notification
progress notifications/progress — sequence number plus phase text
thoughts notifications/message — level info

So a long investigation reports what it's doing while it does it, instead of going silent and then returning.

It returns results two ways

A successful tools/call comes back as both:

  • content[0] — a text block containing the pretty-printed JSON. Every MCP client understands this.
  • structuredContent — the same object, verbatim, for clients that read structured results.

structuredContent is only populated when the response body is a JSON object. Arrays, bare scalars and non-JSON bodies are forwarded as text only — the MCP schema defines structuredContent as a record, and the bridge won't invent a wrapper shape it has no business inventing.

This roughly doubles the wire size of a large response. That's deliberate. The bridge never trims, caps or paginates; any size policy belongs in Sound Suite, not in the forwarder.

It pins no protocol version

The bridge echoes back whatever protocol revision the client negotiates, falling back to the latest it supports. structuredContent is an optional additive field, so older sessions simply carry it as an ignored extra property alongside the text block they already understood.


Installation

The repo copy at scripts/mcp-bridge/ is the source of truth. The copy clients actually run lives at ~/sound-suite-bridge/ with its own node_modules.

mkdir -p ~/sound-suite-bridge
cp scripts/mcp-bridge/bridge.mjs scripts/mcp-bridge/package.json ~/sound-suite-bridge/
cd ~/sound-suite-bridge && npm install

After editing the repo copy, sync it — this is the step people forget:

cp scripts/mcp-bridge/bridge.mjs ~/sound-suite-bridge/

Configuration

Variable Default Meaning
SOUND_SUITE_URL http://127.0.0.1:3000 Sound Suite base URL
SOUND_SUITE_PROFILE required local or routed
MCP_API_KEY none Sent as Authorization: Bearer … when the server runs in apikey auth mode

Profiles

SOUND_SUITE_PROFILE is required and must be local or routed. If it's missing or misspelled, the bridge logs the reason to stderr and exits with status 2 rather than starting in an ambiguous state.

  • local — tools that run entirely on your hardware.
  • routed — includes tools that may spend API credit against a configured provider.

Run one process per profile. The server name Claude Desktop sees is sound-suite-<profile>, so the two appear as separate servers and you can see at a glance which one a tool call went through.

Claude Desktop config

{
  "mcpServers": {
    "sound-suite-local": {
      "command": "node",
      "args": ["/Users/you/sound-suite-bridge/bridge.mjs"],
      "env": {
        "SOUND_SUITE_URL": "http://127.0.0.1:3000",
        "SOUND_SUITE_PROFILE": "local"
      }
    }
  }
}

Use an absolute path to bridge.mjs — Claude Desktop does not expand ~.


Verifying the bridge

# Syntax check
node --check scripts/mcp-bridge/bridge.mjs

# Starts, logs the profile to stderr, waits on stdin (Ctrl-C to exit)
SOUND_SUITE_PROFILE=routed node ~/sound-suite-bridge/bridge.mjs

# Missing profile → error on stderr, exit 2
node ~/sound-suite-bridge/bridge.mjs; echo "exit $?"

On a healthy start the bridge logs connected via stdio; backend http://127.0.0.1:3000; profile local.

Common failures

Symptom Cause
Exits immediately, status 2 SOUND_SUITE_PROFILE missing or not local/routed
HTTP 401 on tools/list Server in apikey mode; set MCP_API_KEY
ECONNREFUSED Sound Suite isn't running — start it first
Tools missing after an update The ~/sound-suite-bridge/ copy is stale; re-copy bridge.mjs
Server absent in Claude Desktop Relative path in args; use an absolute path

The skill

The bridge gets Claude connected. The skill teaches it how to use what it finds.

An MCP client can see tool names and schemas, but not the judgment around them — which call to make first, how to tell a real absence from a retrieval failure, which numbers are safe to quote. The skill at skills/soundsuite-mcp/SKILL.md carries that.

What it enforces

Preflight before trusting anything. The skill directs a session to run a health probe first, which returns a verdict — healthy, or degraded with the reason. Retrieval can flap: serving in seconds, then hanging minutes later, inside one session. If a call stalls, the instruction is to re-probe rather than assume the query was at fault.

Read the retrieval block, not just the passages. query_case_knowledge returns a retrieval block and warnings[] alongside its results, reporting what the search actually did — the mode requested versus the mode that ran. A hybrid search that silently degraded to keyword-only returns real passages that are nonetheless not the answer you asked for. The block is how you catch that.

Sub-query failures are signalled by presence, not by a count. A failure array is absent when nothing failed, so its existence is the signal — a pattern worth knowing before you write code against the response.

No number in the skill is a fact about your corpus. Document counts, coverage percentages, chunk totals and tool counts all move. The skill names the call that returns each live value instead of hardcoding it. A bare constant in that file is treated as a bug in the file.

That last rule is the most transferable idea here: documentation that quotes a number goes stale silently, and a stale number in an AI's context becomes a confident wrong answer.


Bridge, skill, and scope together

The three layers do different jobs:

Layer Job
Bridge Transport — gets stdio MCP to your local HTTP API
Skill Judgment — which calls, in what order, and how to read them
Scope Boundary — which documents a question may draw on

A scope drawn in the Haystack Block View applies to searches from Claude Desktop too. Narrow to one motion's constellation, and an AI question about "what the other side argued" is answered from that set rather than from everything you've ever indexed.


Related