# Deploying remote MCP servers
Source: https://speakeasy.com/mcp/deploying-mcp-servers

import { Callout, Table } from "@/mdx/components";



It is still early days for the Model Context Protocol (MCP), and even though it is already widely implemented, the MCP Specification is [changing rapidly](https://modelcontextprotocol.io/development/roadmap). For example, in the [March 2025 update](https://modelcontextprotocol.io/specification/2025-03-26/changelog), a new transport, ["streamable HTTP"](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) was added to the specification, along with an optional [authorization framework](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization), based on OAuth 2.1.

Keeping up with these changes feels a bit like treading water, but the water is alphabet soup, and someone keeps dumping more letters in.

In this guide, we'll focus on one specific topic: hosting remote MCP servers safely. As part of this, we'll cover the new streamable HTTP transport and the authorization framework.

## Remote MCP transports

Most of the early MCP servers began as small programs running on a developer's laptop, connected to MCP clients through local `stdio`. Indeed, as of writing this, the very first example on the MCP website still shows all servers running on "your computer". The specification, however, always included server-sent events (SSE) as a transport mechanism, allowing MCP clients to connect to remote servers via HTTP, and allowing servers to stream responses back to clients.

Besides the risk of prompt injection, the HTTP+SSE transport part of the specification seems the most contentious in the recent online discourse around MCP. This intensified after the release of the March 2025 update, with many commenters concluding that "streamable HTTP" is essentially HTTP+SSE with more steps and speculating that it will one day converge on something akin to WebSocket.

We're not throwing a hat in the ring with this debate today, but we'll say this: The specification in its current state is useful without immediate changes to the transport mechanism.

Relying on SSE and the normal HTTP pipeline avoids the complexities of upgrading connections from HTTP to WebSocket. With the addition of session IDs passed via headers in HTTP+SSE, maintaining stickiness in serverless or proxied servers is trivial.

Waiting for all the details to be ironed out feels like debating tabs vs spaces at this point.

With that out of the way, let's use the protocol we have, even if not everyone agrees about the current direction.

## Reasons to host MCP servers remotely

Remote MCP servers allow teams to connect to a common server that is always available, which avoids server-version disparity among team members and prevents secrets (such as organization-level API keys) from being shared with users.

If your users are less technical, you can also provide a simple web interface to the server, allowing them to run servers and use tools without needing to know how to use the command line or Docker.

Let's look at the options available for hosting remote MCP servers.

## Choosing your hosting model

<Table
  data={[
    {
      hostingModel: "Cloudflare Workers",
      bestFor: "Need zero-ops edge scale, instant HTTPS, built-in OAuth",
      painPoints:
        "Limited CPU / 50 ms wall-clock per event; can't spawn native binaries",
    },
    {
      hostingModel: "Cloud VM",
      bestFor:
        "Want full OS control, can hand-roll networking, cheap burst workloads",
      painPoints:
        "You patch and monitor; long-lived SSE needs extra Nginx tuning",
    },
    {
      hostingModel: "Docker / Kubernetes",
      bestFor:
        "Require strict isolation, multi-instance scaling, CI/CD pipelines",
      painPoints: "Learning curve; cluster maintenance",
    },
    {
      hostingModel: "On-prem (public-facing)",
      bestFor: "Must access internal data or comply with corporate firewalls",
      painPoints:
        "Outbound-only tunnels, complex identity-provider integration",
    },
  ]}
  columns={[
    { key: "hostingModel", header: "Hosting model" },
    { key: "bestFor", header: "Best for" },
    { key: "painPoints", header: "Pain points" },
  ]}
/>

The protocol is identical across models: An HTTPS endpoint accepts JSON-RPC 2.0 over an HTTP `POST` (optionally upgrading to SSE) and streams the response. Only the infrastructure changes.

## Deploying Cloudflare Workers

The Cloudflare blog post, [Build and deploy Remote Model Context Protocol (MCP) servers to Cloudflare](https://blog.cloudflare.com/remote-model-context-protocol-servers-mcp/), walks readers through all the tools and steps needed to deploy a remote MCP server to Cloudflare Workers.

Let's summarize the steps here by modifying the [Sentry MCP](https://github.com/getsentry/sentry-mcp) server and deploying it to Cloudflare Workers.

### Requirements

You'll need a [Cloudflare](https://www.cloudflare.com/) account (free is fine), and [Node.js](https://nodejs.org/en/download) with [pnpm](https://pnpm.io/installation) installed locally. Our example uses the Sentry MCP Server, so you'll also need a Sentry account.

### Starting with a template

If you're building your own server, you can start with one of the many [Cloudflare demos](https://github.com/cloudflare/ai/tree/main/demos). But to get something useful up and running in this guide, we decided to use the [Sentry MCP Server](https://github.com/getsentry/sentry-mcp) (itself based on the [mcp-github-oauth](https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-github-oauth) demo) as an example.

Clone the Sentry MCP Server:

```bash
git clone https://github.com/getsentry/sentry-mcp
cd sentry-mcp
```

### Installing dependencies

Install the dependencies using pnpm:

```bash
pnpm install
```

### Authenticating Wrangler with Cloudflare

Wrangler is Cloudflare's CLI for deploying and managing Workers. We'll use `npx` to run it without installing globally.

Check that you can run `npx wrangler`:

```bash
npx wrangler --version
```

This should print the version of Wrangler. If you get an error, you may need to install Node.js or add it to your PATH.

Next, you'll need to authenticate Wrangler with your Cloudflare account. This is a one-time step:

```bash
npx wrangler login
```

This opens a browser window and asks you to log in to your Cloudflare account. Once you've logged in, you'll be redirected to a page that says, **You have granted authorization to Wrangler!** You can close this page.

### Creating a Cloudflare KV namespace

The Sentry MCP Server uses a Cloudflare KV namespace to store OAuth tokens. You can create a new KV namespace using the Wrangler CLI:

```bash
npx wrangler kv namespace create "OAUTH_KV"
```

This creates a new KV namespace and prints the ID. Copy this ID, as you'll need it in the next step.

### Configuring the project

Now that you have your KV namespace set up, you can configure the project. The `packages/mcp-cloudflare/wrangler.jsonc` file contains all the configuration for your Cloudflare Worker.

Update the `kv_namespaces` section with the ID of the KV namespace you created earlier:

```jsonc filename="packages/mcp-cloudflare/wrangler.jsonc" focus=39
/**
 * For more details on how to configure Wrangler, refer to:
 * https://developers.cloudflare.com/workers/wrangler/configuration/
 */
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "sentry-mcp",
  "main": "./src/server/index.ts",
  "compatibility_date": "2025-03-21",
  "compatibility_flags": [
    "nodejs_compat",
    "nodejs_compat_populate_process_env",
  ],
  // we ask people to configure environment variables in prod
  "keep_vars": true,
  "migrations": [
    {
      "new_sqlite_classes": ["SentryMCP"],
      "tag": "v1",
    },
  ],
  "assets": {
    "directory": "public",
    "binding": "ASSETS",
    "not_found_handling": "single-page-application",
  },
  "vars": {},
  "durable_objects": {
    "bindings": [
      {
        "class_name": "SentryMCP",
        "name": "MCP_OBJECT",
      },
    ],
  },
  "kv_namespaces": [
    {
      "binding": "OAUTH_KV",
      "id": "8dd5e9bafe1945298e2d5ca3b408a553",
    },
  ],
  "ai": {
    "binding": "AI",
  },
  "observability": {
    "enabled": true,
    "head_sampling_rate": 1,
  },
  "tail_consumers": [
    // super noisy - disable until it can be improved
    // { "service": "sentry-mcp-tail" }
  ],
  "dev": {
    "port": 8788,
  },
}
```

### Creating a Sentry API application

Next, you'll need to create a Sentry API application. This is a one-time step that allows the MCP server to authenticate with Sentry.

1. Log in to your Sentry account.
2. Click on your profile picture in the top-left corner to open the menu.
3. Click on **User settings**.
4. Under **API**, click on **Applications**.
5. Click on **Create New Application**.

Take note of the **Client ID** and **Client Secret**. You'll need these in the next step.

### Configuring the environment variables

The Sentry MCP Server uses environment variables to configure the OAuth flow. You can set these using Cloudflare Wrangler:

Change the working directory to `packages/mcp-cloudflare`, where the `wrangler.jsonc` file is located:

```bash
cd packages/mcp-cloudflare
```

Now, set the environment variables in Cloudflare using the following commands:

```bash