Skip to content

SDKs

MCP clients: Claude Desktop, Cursor, VS Code, Claude Code

Immiscible speaks MCP (Streamable HTTP, JSON-RPC 2.0, protocol 2025-06-18) at two kinds of address:

AddressWhat it isWho uses it
<base>/mcpImmiscible’s own server: authorize_action, request_payment, request_personal_data, check_action_status, explain_decision, settle_actionan agent that asks before it acts, and wants receipts
<base>/mcp/proxy/<upstream id>the MCP proxy: a tool server you registered under Settings, Tools (MCP) in the console, with Immiscible holding its credentialan agent that should reach a tool server only through the gate

Through the proxy there is no path to the tool that skips the gate: every tools/call is authorised first, an approval pauses the call (nothing reaches the upstream), and a refusal comes back as an error with the reasons. The agent only sees the tools its mandates could allow.

Authentication is Authorization: Bearer <agent key>. For <base>/mcp, apps that add connectors by URL can sign in with OAuth instead (no key to copy; see Claude and ChatGPT as connectors).

Every file below is in docs/sdk/integrations/mcp/. They point at the hosted service; if you run your own server, replace https://immiscible.fly.dev with its address and ups_REPLACE_ME with the upstream id from the console.

#Claude Desktop

Immiscible’s own server, by OAuth: Settings, Connectors, Add custom connector, paste <base>/mcp, Connect. You sign in and choose which agent Claude acts as.

A proxied tool server: Claude Desktop’s config file runs local (stdio) servers, so bridge to the remote proxy with mcp-remote, which forwards the header. claude_desktop_config.json (macOS ~/Library/Application Support/Claude/, Windows %APPDATA%\Claude\):

JSON
{
  "mcpServers": {
    "immiscible-shop": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://immiscible.fly.dev/mcp/proxy/ups_REPLACE_ME", "--header", "Authorization:${IMMISCIBLE_AUTH}"],
      "env": { "IMMISCIBLE_AUTH": "Bearer ask_REPLACE_WITH_YOUR_AGENT_KEY" }
    }
  }
}

The header is passed through an environment variable with no space after the colon because some platforms split arguments on spaces. File: claude_desktop_config.json. Restart Claude Desktop after editing.

#Cursor

~/.cursor/mcp.json for every project, or .cursor/mcp.json in one:

JSON
{
  "mcpServers": {
    "immiscible": {
      "url": "https://immiscible.fly.dev/mcp",
      "headers": { "Authorization": "Bearer ${env:IMMISCIBLE_AGENT_KEY}" }
    },
    "immiscible-shop": {
      "url": "https://immiscible.fly.dev/mcp/proxy/ups_REPLACE_ME",
      "headers": { "Authorization": "Bearer ${env:IMMISCIBLE_AGENT_KEY}" }
    }
  }
}

${env:...} reads the key from Cursor’s environment; on versions without interpolation, write the key in place and keep the file out of version control. File: cursor-mcp.json.

#VS Code

.vscode/mcp.json in the workspace (or MCP: Open User Configuration). VS Code prompts for the key once and stores it securely:

JSON
{
  "inputs": [
    { "type": "promptString", "id": "immiscible-agent-key", "description": "Immiscible agent key", "password": true }
  ],
  "servers": {
    "immiscible": {
      "type": "http",
      "url": "https://immiscible.fly.dev/mcp",
      "headers": { "Authorization": "Bearer ${input:immiscible-agent-key}" }
    },
    "immiscible-shop": {
      "type": "http",
      "url": "https://immiscible.fly.dev/mcp/proxy/ups_REPLACE_ME",
      "headers": { "Authorization": "Bearer ${input:immiscible-agent-key}" }
    }
  }
}

File: vscode-mcp.json.

#Claude Code

Shell
claude mcp add --transport http immiscible https://immiscible.fly.dev/mcp --header "Authorization: Bearer $IMMISCIBLE_AGENT_KEY"
claude mcp add --transport http immiscible-shop https://immiscible.fly.dev/mcp/proxy/ups_REPLACE_ME --header "Authorization: Bearer $IMMISCIBLE_AGENT_KEY"

Or check a .mcp.json into the project (Claude Code expands ${IMMISCIBLE_AGENT_KEY} from the environment, so the key is not in the file): claude-code-mcp.json. MCP tools run when the model chooses to call them; for a check on every tool call whatever the model chooses, add the PreToolUse hook too.

#What the model sees

<base>/mcp toolsthrough the proxy
allowedthe decision, with the receipt in the text and in structuredContentthe upstream tool’s own result
a person must approve“A person has been asked. Poll check_action_status ...”a tool error with the approval link and structuredContent.decision: "approval_required"; nothing was sent upstream
refusedthe decision with reasons, and “Do not proceed. Tell the person why.”a JSON-RPC error (-32003) with the reasons

After a person approves a proxied call, retry the same tool with the same arguments and _meta: { "immiscible/approvalId": "apr_..." }; the proxy forwards it exactly once. The SDKs do this for you: mcpProxy(id).callWithApproval(tool, args) and mcp_proxy(id).call_with_approval(tool, args).

#Check a connection

Shell
curl -s "$IMMISCIBLE_URL/mcp" -H "authorization: Bearer $IMMISCIBLE_AGENT_KEY" -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

The SDK integration test (packages/immiscible-js/test/integration.test.mjs) does the same against the real server: initialize, then tools/list with an agent key.