> ## Documentation Index
> Fetch the complete documentation index at: https://bundleup.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Using MCP Clients

> Connect OpenAI, Anthropic, the MCP SDKs, or Claude Code to a user’s account through mcp.bundleup.io.

BundleUp doesn’t ship an MCP client. It works with the one you already use. There are two ways to run one, and the difference is where your API key goes.

```mermaid theme={null}
flowchart TB
    subgraph A["Model-hosted MCP"]
        direction LR
        A1[Your backend] -->|"prompt + API key.connection ID"| A2["Model provider<br/>(OpenAI / Anthropic)"]
        A2 -->|"API key.connection ID"| A3[mcp.bundleup.io]
    end
    subgraph B["MCP client in your backend"]
        direction LR
        B1["Your backend<br/>+ MCP client"] -->|"API key + BU-Connection-Id"| B3[mcp.bundleup.io]
        B1 <-->|"prompt, tool calls"| B2[Model provider]
    end
```

With **model-hosted MCP**, the model provider calls `mcp.bundleup.io` itself, so your API key is sent to the model provider. With an **MCP client in your backend**, your backend calls `mcp.bundleup.io` and passes tool definitions and results to the model, so your API key stays with you.

| | Model-hosted MCP | MCP client in your backend |
| - | - | - |
| **Who calls BundleUp** | The model provider’s servers | Your backend |
| **Your API key goes to** | The model provider | Nowhere else |
| **Code you write** | One tool entry per connection | A tool loop: list tools, pass them to the model, run calls |
| **Best for** | Getting started fast; trusted model providers | Production systems with strict key handling; any model |

## Model-hosted MCP

OpenAI’s Responses API and Anthropic’s Messages API can connect to an MCP server themselves. Both accept a single bearer credential, so pass the API key and connection ID joined by a dot. The SDK’s `hosted()` returns the URL and that token.

<CodeGroup>
  ```typescript OpenAI theme={null}
  const { url, token } = bundleup.mcp(connectionId).hosted();

  const response = await openai.responses.create({
    model: 'gpt-4o',
    input: 'What issues are assigned to me?',
    tools: [
      {
        type: 'mcp',
        server_label: 'linear',
        server_url: url, // https://mcp.bundleup.io
        authorization: token, // YOUR_API_KEY.conn_abc123
        require_approval: 'never',
      },
    ],
  });
  ```

  ```typescript Anthropic theme={null}
  const { url, token } = bundleup.mcp(connectionId).hosted();

  const message = await anthropic.beta.messages.create({
    model: 'claude-opus-5-5',
    max_tokens: 16000,
    betas: ['mcp-client-2025-11-20'],
    mcp_servers: [
      { type: 'url', url, name: 'linear', authorization_token: token },
    ],
    tools: [{ type: 'mcp_toolset', mcp_server_name: 'linear' }],
    messages: [{ role: 'user', content: 'What issues are assigned to me?' }],
  });
  ```

  ```python OpenAI (Python) theme={null}
  hosted = bundleup.mcp(connection_id).hosted()

  response = openai.responses.create(
      model="gpt-4o",
      input="What issues are assigned to me?",
      tools=[{
          "type": "mcp",
          "server_label": "linear",
          "server_url": hosted["url"],
          "authorization": hosted["token"],
          "require_approval": "never",
      }],
  )
  ```

  ```ruby OpenAI (Ruby) theme={null}
  hosted = bundleup.mcp(connection_id).hosted

  response = openai.responses.create(
    model: 'gpt-4o',
    input: 'What issues are assigned to me?',
    tools: [{
      type: 'mcp',
      server_label: 'linear',
      server_url: hosted[:url],
      authorization: hosted[:token],
      require_approval: 'never'
    }]
  )
  ```
</CodeGroup>

Without the SDK, the values are:

* URL: `https://mcp.bundleup.io` (exactly; any path you add is ignored)
* Token: `YOUR_API_KEY.CONNECTION_ID`

<Warning>
  On this path your BundleUp API key is sent to the model provider, and their servers make the MCP calls. If that isn’t acceptable, run the MCP client in your own backend.
</Warning>

## MCP client in your backend

Use the official MCP SDK (or any client that speaks streamable HTTP) with the two headers. The BundleUp SDK’s `transport()` returns the URL and headers.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

  const { url, headers } = bundleup.mcp(connectionId).transport();

  const mcp = new Client({ name: 'my-agent', version: '1.0.0' });
  await mcp.connect(
    new StreamableHTTPClientTransport(new URL(url), { requestInit: { headers } }),
  );

  const { tools } = await mcp.listTools();
  const result = await mcp.callTool({
    name: 'create_issue',
    arguments: { title: 'Login broken' },
  });
  ```

  ```python Python theme={null}
  from mcp import ClientSession
  from mcp.client.streamable_http import streamablehttp_client

  transport = bundleup.mcp(connection_id).transport()

  async with streamablehttp_client(transport["url"], headers=transport["headers"]) as (read, write, _):
      async with ClientSession(read, write) as session:
          await session.initialize()
          tools = await session.list_tools()
          result = await session.call_tool("create_issue", {"title": "Login broken"})
  ```
</CodeGroup>

Pass the tool list to your model in its tool format, run the calls the model makes through the MCP client, and return the results. Your API key never leaves your backend.

### Sending JSON-RPC yourself

The SDKs’ `mcp.post()` and `mcp.delete()` send raw messages and return the response untouched. You’re then responsible for the MCP handshake: send `initialize`, keep the `Mcp-Session-Id` it returns, send `notifications/initialized`, and include the session ID on every later message. Responses can be JSON or `text/event-stream`. See the [JavaScript SDK](/docs/sdks/javascript#mcp-api) for a full example.

## Developer tools

Handy for checking a connection works before wiring it into an agent.

<CodeGroup>
  ```bash Claude Code theme={null}
  claude mcp add linear \
    --transport http \
    --header "Authorization: Bearer YOUR_API_KEY" \
    --header "BU-Connection-Id: conn_abc123" \
    https://mcp.bundleup.io
  ```

  ```bash curl theme={null}
  curl -s https://mcp.bundleup.io \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "BU-Connection-Id: conn_abc123" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
  ```

  ```bash mcp-remote theme={null}
  npx mcp-remote https://mcp.bundleup.io \
    --header "Authorization: Bearer YOUR_API_KEY" \
    --header "BU-Connection-Id: conn_abc123"
  ```
</CodeGroup>

Desktop MCP clients that only support OAuth discovery can’t connect directly. Use `mcp-remote` as a local bridge with static headers.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.