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.
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.
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:
ChatGPT sends a request to https://abc123.ngrok-free.app/mcp.
The provider's edge receives it and finds your open session.
The request travels down to the tunnel client on your laptop.
The client forwards it to http://localhost:3000/mcp.
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".
Requirement
What it means in practice
ChatGPT plan
Plus, Pro, Business, Enterprise or Education, used on the web
Transport
Streamable HTTP (Server-Sent Events also works)
Address
Public HTTPS URL that ends at the MCP route, usually /mcp
Authentication
OAuth, or no authentication for a throwaway test
Tunnel
ngrok, Cloudflare Tunnel or Tailscale Funnel
Running processes
Your 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:
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:
You should see an HTTP 200 and a response that mentions local-notes. If this fails locally, no tunnel will fix it.
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:
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.
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
Option
Setup effort
Address stability
Best for
ngrok free account
Account plus token
Random unless you claim a static domain
A fast first test
Cloudflare quick tunnel
One command, no login
New on every run
Throwaway demos
Cloudflare named tunnel
Domain plus login
Stable
Daily work
Tailscale Funnel
Tailscale installed
Stable hostname under your tailnet
Setups 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.
Add the Connector in ChatGPT
Turn On Developer Mode
Open ChatGPT in a browser and go to Settings.
Find the Developer mode toggle and switch it on.
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
Next to the toggle, click Create app. Older builds label this button Create under Connectors.
Enter a name such as "Local Notes".
Write a short description of when the model should use it.
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.
Set Authentication to No authentication for a throwaway test, or OAuth if your server implements it.
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.
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.
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:
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.
Symptom
Likely cause
Fix
Error while saving the app
Wrong path or server down
Run the curl handshake against the public URL
Worked yesterday, dead today
Tunnel hostname changed
Update the connector or claim a fixed domain
403 or 421 from your server
Host header validation
Allow the tunnel hostname
New tool missing in chat
Cached tool list
Refresh or recreate the connector
Tool call times out
Slow tool behind the tunnel
Return 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.
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:
Paste one tool's name, its input schema and a one-line goal. Ask for three description variants that start with "Use this when".
Ask the model to list two situations where ChatGPT should not call that tool, then add those lines to the description.
Paste the exact error from your terminal or the ngrok inspector and ask for the three most likely causes, ranked.
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.