On this page
A local MCP server is an MCP server that runs on your own computer. Most
use stdio: your AI client starts the server and talks to it through a pipe,
so nothing listens on the network. Some open a local HTTP port instead. Keep
that port on 127.0.0.1, give it a token, and never open it to the internet.
That last part is where people get caught. On r/mcp, one poster scanned the 99 most-starred MCP servers, another listed four MCP security traps, and a third ran a decoy MCP server and watched AI tools connect to it. There's also a scan of 25 public MCP servers for auth gaps, an r/ClaudeAI user who audited the MCP servers connected to their Claude, and an r/ClaudeCode thread on running your own MCP server on a NAS. We link these by title; we haven't quoted them.
This guide is for people running servers, not writing them. If you're building one, the rules for your code are in how to create an MCP server securely.
What is a local MCP server?
It's a program on your machine that gives your AI client tools: read files, query a database, drive a browser. The MCP spec defines two standard ways for the client to reach it, and a local server can use either.
| stdio | Local HTTP | |
|---|---|---|
| How it starts | Your client launches it | You (or a script) start it, and it keeps running |
| How they talk | The process's stdin and stdout | HTTP requests to a URL like http://127.0.0.1:8080/mcp |
| Opens a network port? | No | Yes |
| Who can connect | Only the client that started it | Anything that can reach the port |
| In your config | "command" and "args" |
"url" (often with "type": "http") |
The spec's
stdio page
says "the client launches the MCP server as a subprocess" and the server
"reads JSON-RPC messages from stdin and writes JSON-RPC messages to
stdout." With
Streamable HTTP,
"the server operates as an independent process that can handle multiple
client connections." That's the whole difference that matters here: a stdio
server has one caller, an HTTP server has a door.
Either way, a local server runs as you, with your files and keys. Limiting that is a separate job, covered in how to sandbox MCP servers.
Local vs remote MCP server
| Local MCP server | Remote MCP server | |
|---|---|---|
| Runs on | Your machine | The vendor's (or your company's) servers |
| You add it with | A command, or a localhost URL |
An https:// URL |
| Sign-in | Usually an API key in an environment variable | Usually OAuth in the browser, sometimes a token |
| What it can reach | Whatever your user account can | Only what its token allows on that service |
| Main risk | It runs as you; an open port lets others in | A token with too much access |
Use a local server when the job is on your machine (files, a local database, a browser you watch) or when no hosted version exists. Use the remote one when the vendor offers it and the job is their service: there's nothing to install, and the access is limited by the token's scopes. The spec agrees on the split: its authorization rules are for HTTP, and stdio servers "SHOULD NOT follow this specification, and instead retrieve credentials from the environment." Sign-in for remote servers is covered in MCP authentication.
Is my MCP server stdio or HTTP?
Look at its entry in your client's config file.
- It has
"command"(npx,uvx,docker,node, a path to a program): it's stdio. Your client starts it when it needs it and stops it after. No port. - It has
"url"withlocalhost,127.0.0.1or your machine's name: it's a local HTTP server. Something else started it, and it's listening on that port for anyone who can reach it. - It has
"url"with anhttps://address on someone else's domain: it's remote. Nothing runs on your machine.
Watch for a mix: some READMEs tell you to start the server with a flag like
--port or --transport http in a terminal, then add the URL. That's a
local HTTP server even though you started it with a command. In Claude Code,
claude mcp list lists every server you've added; see
Claude Code MCP setup.
If you have the choice, pick stdio. The MCP
security best practices
tell servers meant to run locally to "Use the stdio transport to limit
access to just the MCP client."
How to see what's listening on your machine
Run one of these while the server is up. Each lists the ports something is listening on and which program owns them.
macOS or Linux, with lsof:
lsof -nP -iTCP -sTCP:LISTEN
-iTCP -sTCP:LISTEN is the man page's own example for "network files with
TCP state LISTEN". -n and -P keep addresses and ports as numbers instead
of names.
Linux, with ss:
ss -ltnp
-l shows "only listening sockets", -t TCP, -n numbers instead of
service names, and -p the process using each socket.
Windows, with netstat:
netstat -ano -p tcp
-a includes "the TCP and UDP ports on which the computer is listening",
-n keeps addresses as numbers, -o adds the process ID, and -p tcp
limits it to TCP. Look at the rows whose state is listening, then find the
PID in Task Manager.
How to read the address column
| You see | It means |
|---|---|
127.0.0.1:8080 or [::1]:8080 |
Only programs on this machine can connect. Good. |
0.0.0.0:8080, [::]:8080 or *:8080 |
Every network interface: your Wi-Fi, your LAN, a VPN. Anyone who can reach your machine can connect. |
192.168.1.20:8080 (your LAN address) |
Anyone on that network can connect. |
If a port belongs to node, python, uv or docker and you don't know
why, match the port to your MCP config before you assume it's harmless. A
stdio server shouldn't show up in this list at all.
MCP server on localhost: bind to 127.0.0.1, not 0.0.0.0
127.0.0.1 (and localhost) is the loopback address: traffic never leaves
your machine. 0.0.0.0 tells the server to accept connections on every
address the machine has. The spec's Streamable HTTP rules say that "When
running locally, servers SHOULD bind only to localhost (127.0.0.1) rather
than all network interfaces (0.0.0.0)."
Not every server does that by default. From servers we've graded:
- DBHub:
--hostdefaults to0.0.0.0. Set--host 127.0.0.1. - SerpApi's MCP server, self-hosted:
listens on
0.0.0.0:8000by default. SetMCP_HOST=127.0.0.1. - Playwright MCP:
--hostdefaults to localhost, but the README's long-running Docker example binds0.0.0.0.
So check the README for a host flag or variable before you start a server in HTTP mode, then confirm with the commands above.
Docker is the usual surprise. Inside a container, a server often has to
listen on 0.0.0.0 so Docker can reach it. The limit then goes on the
publish flag. Docker's
port publishing docs
say "Publishing container ports is insecure by default": -p 8080:8080
publishes to "all host addresses (0.0.0.0 and [::])". Publish to
loopback instead:
docker run -p 127.0.0.1:8080:8080 <image>
With that, "only the Docker host can access the published container port."
MCP DNS rebinding: why localhost alone isn't enough
127.0.0.1 keeps other machines out, but your browser runs on your machine.
In a DNS rebinding attack, you open a web page on a domain the attacker
controls; the attacker then switches that domain's DNS to 127.0.0.1. The
browser still thinks the page is talking to its own site, so the page's
script can send requests to your local MCP server's port and call its tools.
The spec's
Streamable HTTP page
is blunt about it: "attackers could use DNS rebinding to interact with local
MCP servers from remote websites." Its defense is on the server: servers
"MUST validate the Origin header on all incoming connections" and answer
a bad one with "HTTP 403 Forbidden". Real servers have shipped fixes for this
(Playwright MCP's CVE-2025-9611, DBHub's CVE-2026-61742), so your part is to
keep HTTP-mode servers updated, leave their host or origin checks
(--allowed-hosts and the like) at the defaults, and add a token, which a
web page can't guess.
How to add a token to a local MCP server
The spec's security page tells servers meant to run locally that use HTTP to "Require an authorization token". Many servers have an option for it, but check whether it's on: DBHub's, for one, is off by default.
1. Check the README for a token option. DBHub, for example, has
--auth-token; with it set, clients must send Authorization: Bearer <token>.
If the server has no option, see the last step.
2. Make a long random token, and keep it in an environment variable, not in a file you commit:
openssl rand -hex 32
3. Start the server with it, using the flag or variable its README names.
4. Send it from your client. In Claude Code:
claude mcp add --transport http my-local-server http://127.0.0.1:8080/mcp \
--header "Authorization: Bearer $MY_MCP_TOKEN"
Or in a .mcp.json, where Claude Code expands ${VAR} in headers:
{
"mcpServers": {
"my-local-server": {
"type": "http",
"url": "http://127.0.0.1:8080/mcp",
"headers": {
"Authorization": "Bearer ${MY_MCP_TOKEN}"
}
}
}
}
The spec's rule for real OAuth tokens is the same idea: they go in the
Authorization header and "MUST NOT be included in the URI query string".
Don't use a server that only accepts a token in the URL; URLs end up in logs
and browser history. More on keeping the value out of config in
MCP API keys and config.
5. No token option? Then don't open it beyond 127.0.0.1. Run it over
stdio if it supports that, or put something in front that checks who's
calling: an MCP gateway or the Cloudflare Access setup
below.
A shared token is a door key, not a sign-in. Everyone who has it gets the same access, and the server can't tell callers apart. Fine for you and your own tools; not for a team.
Use a tunnel instead of an open port
Sometimes you do need a local server from somewhere else: a laptop away from home, or a server on a NAS. Don't forward a port on your router to it. Use a tunnel, where the connection goes out from your machine and no inbound port is opened.
Cloudflare Tunnel
works that way: a small program, cloudflared, "initiates an outbound
connection through your firewall" to Cloudflare, so your machine doesn't need
"a publicly routable IP address" and can keep blocking inbound traffic. Two
ways to use it:
A quick tunnel, for a five-minute test only. Cloudflare's Quick Tunnels page gives:
cloudflared tunnel --url http://localhost:8080
That prints a random trycloudflare.com address, with no account needed.
Read the warnings before you use it for MCP: "Anyone with the URL can access
your local service", Quick Tunnels "are for testing and development", and
they "do not support Server-Sent Events (SSE)". MCP's HTTP transport can
answer with SSE streams, so some calls may fail. Keep the server's token on
and stop cloudflared when you're done.
A named tunnel with Cloudflare Access, for anything you keep. Create the
tunnel on your own domain
(dashboard steps),
then put a Cloudflare Access policy in front of it. A browser sign-in won't
help an MCP client, so use a
service token:
set the policy action to Service Auth, and have the client send the
CF-Access-Client-Id and CF-Access-Client-Secret headers (the same
--header flag or headers block as above). Keep the server's own token
too, so a mistake in one layer doesn't open the other.
Whatever is in front, the server still runs on your machine with your rights. Trim what it can reach first (sandbox it), then decide who can reach it.
Lock down a local MCP server in 5 steps
0 of 5 done
Tick these off for each local server you run.
1. Use stdio when the server offers it
No port, one caller. Only switch to HTTP when you need a server that stays running or several clients sharing it.
2. Check what's listening
Run lsof -nP -iTCP -sTCP:LISTEN, ss -ltnp or netstat -ano -p tcp and
match every port to a server you meant to run.
3. Bind to 127.0.0.1
Set the server's host flag to 127.0.0.1, and publish Docker ports as
127.0.0.1:PORT:PORT. Re-check the address column after.
4. Turn on a token and keep the server updated
A long random token in an environment variable, sent as
Authorization: Bearer. Update HTTP-mode servers so their DNS rebinding
checks are current.
5. Reach it from elsewhere through a tunnel, not an open port
A Cloudflare Tunnel with an Access service token, never a router port forward, and a quick tunnel only for a short test.
Before you add a server at all, run it through our MCP security checklist. If your team needs MCP servers that several people can reach safely, with real sign-in and a log of every call, that's the kind of setup we build. Tell us what you need to connect.
Frequently asked questions
What is a local MCP server?
An MCP server that runs on your own computer instead of a vendor's. Your AI
client either starts it and talks over stdio, or connects to it on a
localhost HTTP port.
Is a local MCP server safe?
It's as safe as the code and how you run it. It runs with your user's
rights, so only install servers you've checked. Over stdio, nothing else can
connect to it. Over HTTP, keep it on 127.0.0.1 with a token.
What port does a local MCP server use?
There's no standard port. A stdio server uses none. An HTTP server uses
whatever its README or your flag sets: 8000 for a self-hosted SerpApi
server, 8080 in DBHub's Docker example, 8931 in Playwright MCP's. Check with
lsof, ss or netstat.
Should a local MCP server listen on 0.0.0.0?
Not on your own machine. 0.0.0.0 accepts connections from every network
you're on. The spec says local servers should bind to 127.0.0.1. Inside a
Docker container, 0.0.0.0 is often needed, so limit it on the host side
with -p 127.0.0.1:PORT:PORT.
What is DNS rebinding in MCP?
A trick that lets a web page you visit send requests to a server on your
localhost, by switching its domain's DNS to 127.0.0.1. MCP servers must
check the Origin header to stop it; you add a token and keep the server
updated.
What's the difference between a local and a remote MCP server?
Where it runs and how it signs in. A local server runs on your machine with your rights and usually reads an API key from the environment. A remote server runs on the vendor's side, you connect by URL, and it usually signs you in with OAuth.
How do I connect to an MCP server on localhost?
Add its URL to your client, for example http://127.0.0.1:8080/mcp. In
Claude Code that's
claude mcp add --transport http my-local-server http://127.0.0.1:8080/mcp,
plus --header "Authorization: Bearer $MY_MCP_TOKEN" if the server has a
token. A stdio server needs no URL: you add its command and the client
starts it.
How do I use my local MCP server from another computer?
Through a tunnel, not an open port. A Cloudflare Tunnel connects outward, so nothing on your machine is opened to the internet. Put a Cloudflare Access service token in front, and keep the server's own token on.