Large Language ModelsGenerate imagesGenerate videos

ChatGPT MCP Tunnel: Connect a Local MCP Server to ChatGPT

ChatGPT cannot reach localhost, so a local MCP server needs a tunnel. See the exact ngrok and Cloudflare commands, the connector form in developer mode, the errors that block a first tool call, and the habits that keep a public URL safe, plus when a hosted server beats a tunnel.

ChatGPT MCP Tunnel: Connect a Local MCP Server to ChatGPT
Cristian Da Conceicao
Founder of Picasso IA

Your MCP server runs perfectly on localhost:3000. Then you paste that address into ChatGPT and get an error. Nothing is wrong with your code. ChatGPT lives in OpenAI's cloud, and your laptop sits behind a router that never invited it in. A ChatGPT MCP tunnel closes that gap: a small program on your machine opens an outbound connection to a relay, the relay hands you a public HTTPS address, and every request ChatGPT sends to that address travels back down the connection to your local server.

This article follows the order you will actually work in: what ChatGPT requires from a remote MCP server, a small server worth testing with, two tunnel options with exact commands, the connector form inside ChatGPT, the errors that eat an afternoon, and the habits that keep a public URL from becoming a liability. You need a free tunnel account at most, and nothing beyond a normal laptop.

💡 The short version: serve your MCP endpoint over Streamable HTTP, point a tunnel at that port, paste https://<your-tunnel-host>/mcp into the connector form in ChatGPT developer mode, and keep both processes running while you chat.

Why ChatGPT Can't Reach Localhost

Remote Servers Only

ChatGPT connectors are built for servers that live on the public internet. A server that speaks stdio, the transport where a desktop client launches your program as a child process, cannot work here, because ChatGPT has no way to start a process on your computer. What it can do is call an HTTPS endpoint, and OpenAI's documentation lists both Server-Sent Events and Streamable HTTP as supported protocols. Pick Streamable HTTP unless you have a reason not to: it replaced the older HTTP plus SSE transport in the Model Context Protocol specification, and it is what current SDKs recommend.

There is a second, simpler reason localhost fails. The word means "this machine" for whoever reads it. When ChatGPT tries http://localhost:3000, it looks at its own servers, finds nothing on port 3000, and gives up.

Brick railway tunnel with rails leading to a small circle of daylight

What a Tunnel Actually Does

A tunnel flips the direction of the connection. Your machine dials out to the tunnel provider, which every home router and most office firewalls allow, and keeps that connection open. The provider owns a public hostname with a valid TLS certificate and pushes incoming requests down the open connection. In practice one request takes five steps:

  1. ChatGPT sends a request to https://abc123.ngrok-free.app/mcp.
  2. The provider's edge receives it and finds your open session.
  3. The request travels down to the tunnel client on your laptop.
  4. The client forwards it to http://localhost:3000/mcp.
  5. Your server's reply returns along the same path.

Compared with classic port forwarding, you skip router settings, dynamic DNS and certificate renewal. You also get an off switch: close the tunnel and the public address stops working immediately.

What to Prepare First

Plan and Developer Mode

Custom connectors for remote MCP servers sit behind developer mode. OpenAI's documentation lists it for Plus, Pro, Business, Enterprise and Education accounts on the web. On workspace plans an administrator may have to allow it first, so check that before blaming your server.

Menu names move around between releases. Today the switch lives in Settings, in the Apps section, as a Developer mode toggle near the bottom. Older builds placed it under Connectors. If you cannot find it, search the settings panel for the word "developer".

RequirementWhat it means in practice
ChatGPT planPlus, Pro, Business, Enterprise or Education, used on the web
TransportStreamable HTTP (Server-Sent Events also works)
AddressPublic HTTPS URL that ends at the MCP route, usually /mcp
AuthenticationOAuth, or no authentication for a throwaway test
Tunnelngrok, Cloudflare Tunnel or Tailscale Funnel
Running processesYour server and the tunnel, both alive during the chat

A Minimal Streamable HTTP Server

You need something small to test the tunnel with. This TypeScript server exposes one tool, in stateless mode, so there are no sessions to lose when you restart it. Install the dependencies first:

npm install @modelcontextprotocol/sdk express zod
npm install -D tsx typescript @types/express

Then save this as server.ts:

import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

const app = express();
app.use(express.json());

function buildServer() {
  const server = new McpServer({ name: "local-notes", version: "1.0.0" });
  server.tool(
    "add_numbers",
    "Use this when the user asks to add two numbers together.",
    { a: z.number(), b: z.number() },
    async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] })
  );
  return server;
}

app.post("/mcp", async (req, res) => {
  const server = buildServer();
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  res.on("close", () => {
    transport.close();
    server.close();
  });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(3000, "127.0.0.1", () => console.log("MCP on http://127.0.0.1:3000/mcp"));

Run it with npx tsx server.ts. Three details matter. First, the route is /mcp, and that exact path ends up in ChatGPT's form. Second, sessionIdGenerator: undefined makes every request independent, which suits a tunnel that may restart. Third, the tool description starts with "Use this when", a habit that helps ChatGPT choose the right tool, since it picks tools by reading those sentences.

Before any tunnel exists, prove the server answers a handshake:

curl -i http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

You should see an HTTP 200 and a response that mentions local-notes. If this fails locally, no tunnel will fix it.

Open notebook with a pencil sketch of two boxes joined by a line, beside a closed laptop

Open the Tunnel

ngrok in Two Commands

Install ngrok with brew install ngrok on macOS, or use the installer from ngrok's site on Windows and Linux. Then add the token from your dashboard and start the tunnel:

ngrok config add-authtoken YOUR_TOKEN
ngrok http 3000

The terminal prints a forwarding line such as https://abc123.ngrok-free.app -> http://localhost:3000. Add /mcp and you have your connector URL. ngrok also serves a local inspector at http://127.0.0.1:4040 that lists every request and response, which is the fastest way to see what ChatGPT actually sent.

Out of the box the address changes whenever the tunnel restarts. Claim a free static domain in the ngrok dashboard, then run ngrok http --url=your-name.ngrok-free.app 3000 (older client versions use --domain) so the connector URL survives restarts and you stop editing it every morning.

Developer's hands typing on a laptop in a dim home office, terminal window out of focus

Cloudflare Quick Tunnel

If you prefer Cloudflare, install cloudflared and run one command:

cloudflared tunnel --url http://localhost:3000

It prints an address like https://random-words.trycloudflare.com. Quick tunnels need no account, and that is their charm and their limit: the hostname is new on every run, so you re-paste it into ChatGPT each time. Developers also report that quick tunnels can struggle with Server-Sent Events, which makes Streamable HTTP the safer pairing. For a permanent address, create a named tunnel attached to a domain you own.

Picking the Right Tunnel

OptionSetup effortAddress stabilityBest for
ngrok free accountAccount plus tokenRandom unless you claim a static domainA fast first test
Cloudflare quick tunnelOne command, no loginNew on every runThrowaway demos
Cloudflare named tunnelDomain plus loginStableDaily work
Tailscale FunnelTailscale installedStable hostname under your tailnetSetups already on Tailscale

For a first test, take ngrok or a quick tunnel. For daily use, a stable address matters more than any feature on that table.

Blue ethernet patch cables plugged into a black patch panel in a small closet

Add the Connector in ChatGPT

Turn On Developer Mode

  1. Open ChatGPT in a browser and go to Settings.
  2. Find the Developer mode toggle and switch it on.
  3. Read the warning. A connector can read your data and, if you allow it, change things, so connect only servers you trust.

Create the App

  1. Next to the toggle, click Create app. Older builds label this button Create under Connectors.
  2. Enter a name such as "Local Notes".
  3. Write a short description of when the model should use it.
  4. Paste your public address into MCP server URL, with the route included: https://abc123.ngrok-free.app/mcp. The bare hostname without /mcp is the most common mistake.
  5. Set Authentication to No authentication for a throwaway test, or OAuth if your server implements it.
  6. Tick the box confirming you trust the app, then click Create.

ChatGPT now contacts your URL, performs the MCP handshake and lists the tools it finds. Seeing add_numbers on that screen means the whole chain works: ChatGPT, tunnel, your server.

Person working at a café window table with a laptop, flat white and croissant

Run Your First Tool Call

Start a new chat, open the + menu, choose More, pick Developer mode, and switch on your app. Then ask something that needs it: "Use Local Notes to add 19 and 23."

ChatGPT shows the tool call it wants to make. Read-only tools can run freely, while tools that write data ask for explicit confirmation, so approve the call and watch three places at once:

  • The chat: the answer 42, with the tool call expandable above it.
  • Your server terminal: the incoming request.
  • The ngrok inspector: the raw JSON ChatGPT sent and your server returned.

When all three agree, you have a working ChatGPT MCP tunnel and a template for every tool you add later.

Two colleagues smiling at a laptop in a loft office

Fix the Errors You Will Hit

Connection Errors and the /mcp Path

When ChatGPT refuses to save the app or reports it could not reach the server, work down this list before changing any code:

  • Is the server running? Open the terminal where it started and confirm it is still alive.
  • Is the tunnel pointing at the same port? ngrok http 3000 only works if your server listens on 3000.
  • Does the path match? The URL in ChatGPT must end in the same route your code registers.
  • Did the tunnel restart? A new random hostname means the old connector URL is dead.
  • Is the address HTTPS? ChatGPT will not take plain HTTP.

Then repeat your local handshake test against the public address. If it fails there but passes on localhost, the fault sits between the tunnel and your server, never inside ChatGPT:

curl -i https://abc123.ngrok-free.app/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

Tired developer rubbing the bridge of the nose at a desk lit by a single lamp at night

Stale Tools and Host Headers

Two problems look like bugs and are not.

Stale tools. ChatGPT stores the tool list when you create the app. If you add, rename or reword a tool, the chat keeps showing the old version until you refresh the connector from its settings page or recreate it. Whenever a new tool refuses to appear, refresh first.

Host header checks. Some frameworks, and some MCP SDK helpers, validate the Host header to block DNS rebinding attacks. A request arriving through a tunnel carries the tunnel hostname instead of localhost, so your server may answer 403 or 421. Add the tunnel hostname to your allowed hosts. Quick tunnels change hostname on every run, so allow a suffix such as .trycloudflare.com instead of switching the check off.

SymptomLikely causeFix
Error while saving the appWrong path or server downRun the curl handshake against the public URL
Worked yesterday, dead todayTunnel hostname changedUpdate the connector or claim a fixed domain
403 or 421 from your serverHost header validationAllow the tunnel hostname
New tool missing in chatCached tool listRefresh or recreate the connector
Tool call times outSlow tool behind the tunnelReturn early and keep calls to a few seconds

Lock It Down

Treat the URL as Public

Anyone who gets hold of your tunnel address can call your server unless something stops them. A random hostname is obscurity, not protection, and it shows up in logs, screenshots and browser history. Use OAuth on the server, or put an access layer in front of the tunnel: Cloudflare Access and ngrok's traffic policies both exist for exactly this. Bind your server to 127.0.0.1, as the sample code does, so that only the tunnel client on your own machine can reach it directly.

Heavy brass padlock on a weathered wooden gate latch

Limit What Tools Can Write

A tool is a promise about what the model may do on your machine. Keep it small:

  • Start with read-only tools and add write tools one at a time.
  • Never expose a general shell command or unrestricted file deletion.
  • Restrict file tools to one project folder.
  • Log every call with its arguments, so you can see what happened afterwards.
  • Treat tool output as untrusted text. A web page or document your tool returns may contain instructions aimed at the model, a risk known as prompt injection.
  • Stop the tunnel with Ctrl+C when you finish. An idle public address is all downside.

When a Hosted Server Wins

A tunnel is the right tool for building and debugging. For daily use, a hosted server removes an entire class of problems, because it already lives at a public address: no laptop to keep awake, no hostname to re-paste, no port to forget.

PicassoIA works this way on the API side. Its developer API runs at https://api.picassoia.com/v1 with Replicate-style endpoints: you create a prediction, poll it, then fetch the result. MCP connections are managed from your account at picassoia.com/en/mcp/accounts after you log in, and an account can run up to 5 predictions at once, shared across tokens and MCP connections. Check the PicassoIA API page for the current access rules before you build on it.

How to Use GPT 5.4 on PicassoIA

Debugging an MCP server involves a lot of writing: tool descriptions, JSON schemas, error explanations. GPT 5.4 is a good drafting partner for that work. Here is a workflow that fits this article:

  1. Open the GPT 5.4 model page on PicassoIA.
  2. Paste one tool's name, its input schema and a one-line goal. Ask for three description variants that start with "Use this when".
  3. Ask the model to list two situations where ChatGPT should not call that tool, then add those lines to the description.
  4. Paste the exact error from your terminal or the ngrok inspector and ask for the three most likely causes, ranked.
  5. Copy the best description back into your server, restart it, and refresh the connector in ChatGPT.

Parameter tips: paste real schemas instead of describing them, change one thing per prompt, and keep each request under a single tool. For a second opinion on tricky code, run the same prompt through Claude Sonnet 5 or GPT 5.6 Sol and compare the answers.

💡 Tip: when two models disagree about why a request fails, trust the one that points at a line you can verify in the ngrok inspector.

Make Your Own Images Next

Once your server works, you will want to write it up, demo it, or put it in a README, and a post needs visuals. Every photograph in this article was generated, not shot: each prompt names a subject, a setting, the direction of the light, a lens and a film stock. That recipe works for any topic you throw at it.

  • Name the light: "low golden sunlight from the left" beats "nice lighting".
  • Pick a lens: "85mm at f/1.8" gives a portrait feel, "24mm at f/8" gives a wide, sharp scene.
  • Describe textures: wool, brushed aluminium and wet brick make an image feel real.

Open PicassoIA, choose an image model, and try a prompt for your own project. If a still is not enough, a text-to-video model can turn the same idea into motion. Start with one scene from your own setup and see how close the first result lands.

Creative working at a bright studio desk with printed landscape photographs and a camera

Share this article