How to Deploy a Remote MCP Server
Deploy a secure remote MCP server with Streamable HTTP, OAuth, testing, hosting choices, and production operations.
A remote MCP server is an Internet-reachable service that lets MCP clients such as Claude, Cursor, or another agent discover and call your tools. For a new deployment, use stateless Streamable HTTP at a stable endpoint such as /mcp. Use local stdio only when the client and server run on the same machine.
This guide walks through an implementation on Cloudflare Workers, then covers authentication, testing, AWS and private-network deployments, migration from SSE, and production operations.
1. Choose the remote MCP architecture
| Decision | Recommended starting point | When to choose differently |
|---|---|---|
| Transport | Stateless Streamable HTTP | Keep SSE only while migrating an existing deployment |
| Endpoint | https://your-domain.example/mcp |
Use a gateway URL when routing several servers |
| State | Stateless requests | Add durable session state only for a documented requirement |
| Authentication | OAuth 2.1 with scopes and consent | Use Cloudflare Access or an enterprise identity provider for private users |
| Hosting | Managed edge or serverless runtime | Use a VPC service for private data or network-only systems |
Cloudflare describes Streamable HTTP as the standard transport for remote MCP connections, while its documentation marks SSE as deprecated for new servers. Amazon Quick also supports remote servers and prefers HTTP streaming over SSE. See the Cloudflare transport documentation and AWS MCP hosting guidance.
2. Build a stateless MCP server
Design tools around user goals. Do not expose your entire internal API schema as hundreds of loosely described operations. Give each tool a narrow purpose, precise input validation, and the minimum permissions it needs. Run evaluation tests whenever a tool description or parameter changes.
The following Worker uses Cloudflare’s createMcpHandler path for a new stateless server. Create a project, install the MCP package used by the current Cloudflare Agents documentation, and place this in your Worker entry file:
import { createMcpHandler } from "agents/mcp";
const handler = createMcpHandler({
name: "inventory-server",
version: "1.0.0",
tools: {
get_inventory: {
description: "Return the available quantity for one SKU.",
inputSchema: {
type: "object",
properties: {
sku: { type: "string", minLength: 1 }
},
required: ["sku"],
additionalProperties: false
},
execute: async ({ sku }, { env }) => {
// Replace this with a query to your own service.
const response = await fetch(`${env.INVENTORY_API}/items/${encodeURIComponent(sku)}`);
if (!response.ok) {
throw new Error(`Inventory API returned ${response.status}`);
}
const item = await response.json();
return {
content: [{
type: "text",
text: JSON.stringify({ sku, available: item.available })
}]
};
}
}
}
});
export default {
fetch(request, env, ctx) {
return handler(request, env, ctx);
}
};
Keep the /mcp route stable. A client may discover tools, call them, and send protocol messages to that same URL. Validate every input again on the server even if the model supplied a JSON schema-compliant request.
Local project setup
npm create cloudflare@latest remote-mcp-server
cd remote-mcp-server
npm install
npm run dev
Cloudflare’s documented local endpoint is http://localhost:8788/mcp. Opening that URL in a normal browser does not test MCP: a browser tab does not perform the protocol negotiation. Use MCP Inspector instead.
3. Deploy the server
- Set the API URL and other secrets as Worker environment variables. Keep credentials out of source control.
- Run the local server and connect MCP Inspector to
http://localhost:8788/mcp. - List tools, inspect their schemas, and invoke each tool with valid and invalid inputs.
- Deploy with Wrangler:
npx wrangler@latest deploy
The deployment produces an HTTPS endpoint similar to https://your-worker.workers.dev/mcp. A custom domain or an API gateway can sit in front of it. Keep deployment configuration in version control so code, environment bindings, and route changes are reviewed together.
4. Add authentication and authorization
Do not expose account data or write actions on an unauthenticated endpoint. A remote MCP server needs both authentication (who is calling) and authorization (which tools that caller may use).
OAuth discovery flow
- Require authentication at the MCP endpoint.
- Return
401 Unauthorizedwith aWWW-Authenticateheader containing the resource metadata URL. - Publish authorization-server metadata and supported scopes.
- Let the client obtain an access token, using PKCE for public clients that cannot keep a client secret.
- Validate the token audience, issuer, expiry, and scopes on every MCP request.
- Map scopes to individual tools and enforce the mapping in the tool handler.
- Show user consent before granting access to data or destructive operations.
Amazon Quick can discover OAuth metadata from the initial 401 response or a well-known URI. If Dynamic Client Registration is available, it can register automatically; otherwise provide client credentials manually. Cloudflare documents OAuth 2.1, Cloudflare Access, and integrations such as Stytch, Auth0, WorkOS, and Descope in its authorization guidance.
Practical scope design
| Scope | Example tools | Risk controls |
|---|---|---|
catalog:read |
Search products, read inventory | Tenant filter on every query |
orders:read |
Read order status | Allow only the caller’s account |
orders:write |
Cancel or create an order | Confirmation and idempotency key |
admin |
Configuration changes | Separate role, audit log, and short token lifetime |
Reject unknown tools, unknown parameters, missing tenant identifiers, and requests that exceed the caller’s scope. Log authorization decisions without logging access tokens or sensitive payloads.
5. Test locally and remotely
MCP Inspector
- Start the Worker locally.
- Open MCP Inspector and enter
http://localhost:8788/mcp. - Connect, list tools, inspect each input schema, and invoke representative cases.
- Repeat against the deployed HTTPS endpoint with authentication enabled.
Test an invalid token, an expired token, a missing scope, malformed JSON, an unknown tool, an upstream timeout, and a request from the wrong tenant. These cases should return structured protocol errors without leaking stack traces.
Using a local proxy for clients without remote transport
Some clients can connect to stdio processes but do not have native remote transport. The documented mcp-remote proxy bridges a local stdio command to your HTTPS endpoint. A Claude Desktop configuration has this shape:
{
"mcpServers": {
"inventory": {
"command": "npx",
"args": [
"mcp-remote",
"https://your-worker.workers.dev/mcp"
]
}
}
}
Raw HTTP smoke checks
Use a protocol-aware client for full testing. A basic HTTPS check still catches DNS, TLS, routing, and authentication mistakes:
curl -i https://your-domain.example/mcp
Expect an authentication response when protection is enabled. Do not assume a successful response from a browser GET means that MCP negotiation works.
6. Choose a hosting model
Cloudflare Workers
The official path uses a stateless createMcpHandler, Wrangler deployment, an HTTPS /mcp endpoint, and MCP Inspector. Cloudflare also documents Access and OAuth provider integrations. This is a practical fit when you want an edge deployment with Git-based automation.
AWS remote hosting
AWS describes HTTPS hosting as a way to centralize authentication, authorization, versioning, and updates. An API gateway can provide one endpoint for routing and access control, while individual MCP servers remain independently deployable.
Private enterprise connections
Amazon Quick requires an active VPC connection with network access for a private MCP server. OAuth discovery can use the configured authentication-server VPC connection instead of the public Internet. Choose this model when the tools must reach databases or services that have no public route.
Gateway architecture
A gateway can centralize authentication, authorization, routing, protocol translation, and dynamic server or tool availability. It also prevents every agent from needing to register every backend server. Define clear ownership, per-tenant routing, timeouts, and audit fields at the gateway boundary.
7. Migrate an existing SSE or stateful server
Do not switch a stateful production server by changing one URL and hoping clients recover. Inventory whether you use sessions, replay, server-to-client streams, pushed requests, or RPC state. Cloudflare advises serving stateless and legacy lanes during a staged transition when those features are involved.
- Deploy a new
/mcpStreamable HTTP endpoint alongside the old SSE endpoint. - Update clients that support the new transport and monitor errors by client version.
- Move long-lived state into an explicit store if it is still required.
- Drain old connections and remove the SSE lane only after dependent clients have migrated.
8. Production reliability and performance
- Timeouts: Set an MCP request deadline and shorter upstream deadlines. Return a retryable error when an upstream service times out.
- Idempotency: Require an idempotency key for writes so a client retry cannot create duplicate records.
- Retries: Retry only transient upstream failures, with exponential backoff and a bounded attempt count. Never blindly retry a non-idempotent write.
- Concurrency: Apply per-tenant and global limits. Protect slow tools from exhausting the runtime.
- Payload size: Limit input and output sizes. Prefer pagination or summaries over returning an entire dataset to the model.
- Caching: Cache immutable or short-lived reads with an explicit freshness policy. Never share cached private data across tenants.
- Observability: Record request ID, tool name, tenant, latency, status, upstream status, and authorization result. Redact tokens and sensitive fields.
- Health checks: Check DNS, TLS, authentication metadata, and a harmless read tool separately. A process that is alive may still be unable to reach its dependencies.
- Versioning: Treat tool names, descriptions, and input schemas as an API contract. Add a new tool or version when a breaking change is necessary.
Remote transport adds network latency and an authentication round trip. Keep tools focused so each call does useful work, and move large computation into the backend rather than making the model orchestrate dozens of tiny calls.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Client reports unsupported transport | Server exposes SSE or stdio only | Add stateless Streamable HTTP at /mcp; use a proxy for clients without remote support |
Browser shows an error at /mcp |
A browser is not an MCP client | Use MCP Inspector or a compatible client |
| Every request returns 401 | Missing token, wrong audience, or undiscovered metadata | Inspect WWW-Authenticate, verify issuer and audience, and complete OAuth discovery |
| Tool list is empty | Tool registration failed or the client cached an old schema | Check startup logs, validate schemas, reconnect, and bump the server version when needed |
| Tool works locally but not after deploy | Missing environment binding, route, or secret | Compare local and production configuration and verify the deployed route |
| Requests hang | Upstream call has no deadline or a stream is left open | Add bounded timeouts, cancel work on disconnect, and inspect upstream latency |
| Duplicate writes appear | Client retried a non-idempotent call | Require and persist an idempotency key |
| Private backend is unreachable | No VPC or network route | Use a private connection, gateway, or an egress path permitted by the backend |
| OAuth registration fails | Dynamic Client Registration is unavailable | Provision client credentials manually and configure the redirect URI |
10. Or skip the browser setup
If your agent needs website screenshots as an MCP tool, ScreenshotNeo provides a hosted MCP server and HTTP API. It removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP tools include take_screenshot, get_page_info, and capture_pdf.
One request returns an image or PDF:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for the full option set. It supports full-page or CSS-selector captures, device presets, dark mode, retina scale, PDF output, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, geolocation, transparent backgrounds, resizing, caching, signed links, async webhooks, bulk capture, and a usage API. Every feature is on every plan. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
FAQ
Can I deploy a remote MCP server with stdio?
Stdio is for a process on the same machine as its client. For an Internet-reachable service, expose Streamable HTTP and keep a stable HTTPS endpoint.
Is SSE completely unusable?
No. Existing clients may still depend on it, but new deployments should use Streamable HTTP. Run both transports during a planned migration when session or streaming behavior requires it.
Does every remote MCP server need OAuth?
Any server handling user data or write operations needs authentication and authorization. A deliberately public, read-only server may use another access policy, but it should still validate inputs and limit abuse.
Should I put several MCP servers behind one URL?
A gateway is useful when you need centralized access control, routing, protocol translation, or dynamic tool availability. Keep tenant isolation and audit context explicit at the gateway.
How do I know whether a failure is the MCP server or its backend?
Correlate the MCP request ID with upstream status and latency. Test protocol negotiation, authentication, a harmless tool, and the backend dependency as separate checks.


