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:
| Address | What it is | Who uses it |
|---|---|---|
<base>/mcp | Immiscible’s own server: authorize_action, request_payment, request_personal_data, check_action_status, explain_decision, settle_action | an 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 credential | an 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\):
{
"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:
{
"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:
{
"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
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 tools | through the proxy | |
|---|---|---|
| allowed | the decision, with the receipt in the text and in structuredContent | the 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 |
| refused | the 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
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.