> ## 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.

# How Proxy MCP Works

> What BundleUp does to each MCP message on its way to the provider’s MCP server, and what it leaves alone.

Proxy MCP puts BundleUp in front of a provider’s own remote MCP server. The provider defines the tools. BundleUp handles what the provider doesn’t: storing and refreshing users’ tokens, keeping one user’s connection away from another’s, rate limiting, and logging.

Proxy MCP doesn’t define, rename, filter or normalize tools. It’s a pass-through.

## Endpoint

```text theme={null}
https://mcp.bundleup.io
```

There’s no per-provider path. The **connection** decides which provider’s MCP server a message goes to. Any path or query string you add is ignored.

## What happens to each message

Every MCP message (`initialize`, `tools/list`, `tools/call`, `resources/read`, notifications) takes the same path:

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as MCP client
    participant B as mcp.bundleup.io
    participant S as Provider MCP server

    C->>B: JSON-RPC message<br/>Authorization: Bearer API key<br/>BU-Connection-Id: conn_abc123
    B->>B: Validate API key → workspace
    B->>B: Resolve connection (same workspace)
    B->>B: Rate limit (100 / 60 s per connection)
    B->>B: Refresh token if it expires within 60 s
    B->>B: Check the provider has an MCP server
    B->>S: Same body, byte for byte<br/>Authorization: Bearer user's token
    S-->>B: Response (JSON or event stream)
    B-->>C: Response, unchanged
    B-)B: Log status and duration (asynchronously)
```

1. **Authenticate the workspace.** Missing or unknown API key → `401`.
2. **Resolve the connection.** It must belong to the same workspace as the API key. Otherwise → `400 connection_invalid`.
3. **Rate limit.** Over 100 messages in 60 seconds on this connection → `429` with `Retry-After: 60`. Nothing is sent upstream.
4. **Make sure the token is usable.** If it’s expired or expires within 60 seconds, it’s refreshed first. If it’s only due for an early refresh, that happens in the background.
5. **Check MCP support.** If the provider has no MCP server → `400 mcp_not_supported`.
6. **Forward.** The body is sent unchanged to the provider’s MCP server, with `Authorization` set to the user’s token.
7. **Return and log.** The provider’s response comes back unmodified. A log entry is written afterwards without adding latency.

## What happens to headers

| Header | Treatment |
| - | - |
| `Authorization` | **Replaced** with `Bearer <user's access token>`. Your API key never reaches the provider. |
| `BU-Connection-Id`, `BU-Max-Retries` | Removed. |
| `Mcp-Session-Id` | Passed through in both directions. |
| `MCP-Protocol-Version`, `Accept`, `Content-Type`, `Last-Event-ID` | Passed through unchanged. |
| Everything else | Passed through unchanged. |

The request body is never read, parsed or rewritten.

## Tools

BundleUp keeps no tool registry. There’s nothing to sync or configure.

* **Discovery.** `tools/list` goes to the provider, and its response comes back verbatim: names, descriptions, input and output schemas, annotations and pagination cursors.
* **Calls.** `tools/call` goes to the provider verbatim. Arguments aren’t validated or changed by BundleUp.
* **Scope.** A connection sees and can call whatever its OAuth grant allows. Narrow the [scopes](/docs/guides/integrations/scopes) on the integration and the provider returns fewer tools, or rejects calls.
* **Beyond tools.** `resources/*`, `prompts/*`, `completion/*`, `logging/*` and server notifications work if the provider supports them.
* **No namespacing.** Tool names aren’t prefixed. If you register two connections as two servers, their tool names can collide; your client has to tell them apart.

<Warning>
  The provider owns the tool definitions, so tool names and arguments can change without a BundleUp release. Treat the tool list as a live contract.
</Warning>

## Sessions and streaming

The proxy is stateless. Session state lives on the provider’s server.

* The provider’s `Mcp-Session-Id` from `initialize` is returned to your client. Send it back on later messages and they reach the same upstream session.
* Streamable HTTP responses (`text/event-stream`) are passed straight through, event by event.
* `DELETE` (session teardown) is forwarded like any other method.

<Note>
  A long-lived stream is authorized once, when it opens. If the access token expires while the stream is open, the provider closes it. Reconnect, and BundleUp attaches a fresh token.
</Note>

## No retries

Proxy MCP doesn’t retry, and ignores `BU-Max-Retries`. A failed upstream message is returned on the first attempt, and your MCP client decides what to do next.

## Errors

Errors from BundleUp, before anything reaches the provider:

| Status | Code | Meaning |
| - | - | - |
| `401` | — | `Authorization` missing, or the API key isn’t recognized. |
| `400` | `connection_invalid` | `BU-Connection-Id` missing, unknown, or from another workspace. |
| `400` | `connection_refresh_failed` | The token expired and couldn’t be refreshed. The user may need to reconnect. |
| `400` | `integration_not_found` | The connection’s integration no longer exists. |
| `400` | `mcp_not_supported` | This provider has no MCP server. Use the [Proxy](/docs/guides/proxy/overview) instead. |
| `429` | `rate_limit` | Over the per-connection limit. `Retry-After: 60`. |

<Warning>
  These are plain HTTP responses with a JSON body, not JSON-RPC errors. Most MCP clients report them as connection or transport errors rather than tool errors.
</Warning>

Anything the provider returns, including its own `401`, `403` or JSON-RPC error objects, is passed through with its original status and body. When a tool call fails upstream, your agent sees the provider’s error.

## Logging

Each message is logged with its status, duration, upstream URL and method, and the connection, integration and API key that made it. **Bodies are never logged**, so tool arguments and results stay out of BundleUp’s records.

## No OAuth discovery

Proxy MCP authenticates with static headers. It doesn’t implement the MCP authorization spec: there’s no `/.well-known/oauth-protected-resource`, no dynamic client registration and no consent step at connect time. Users connect their accounts through BundleUp’s [authorization flow](/docs/guides/auth/authorization-flow) beforehand.

Clients that can only authenticate through OAuth discovery can’t connect to `mcp.bundleup.io`. Use a client that supports static headers or a single bearer token, or bridge with `mcp-remote`.


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