/mcp Web access log Triage: low

GET /mcp and /sse Requests in Your Logs: Scanners Hunting MCP Servers

Published

An automated scan for Model Context Protocol endpoints — the HTTP paths an MCP server exposes for tool calls and its Server-Sent Events stream — looking for AI integrations that answer without authentication.

What it looks like

203.0.113.7 - - [18/Oct/2026:03:51:11 +0000] "GET /mcp HTTP/1.1" 404 162 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
198.51.100.42 - - [18/Oct/2026:03:51:12 +0000] "GET /sse HTTP/1.1" 404 162 "-" "python-requests/2.31.0"
192.0.2.88 - - [18/Oct/2026:04:12:03 +0000] "POST /api/mcp HTTP/1.1" 404 162 "-" "Go-http-client/2.0"

On our own server, the 30 days to 18 October 2026

181

requests · about 6.0 a day

168

source addresses

Most requested variantsRequests
/mcp54
/sse45
/mcp/41
/api/mcp41

LogTriage's production API server (Hetzner, Germany): port 22 open to the internet, key-only since 6 October 2026 (password and keyboard-interactive authentication off); nginx in front of the API on ports 80 and 443. Paths as requested, query strings dropped and identifiers redacted; counted weekly from the nginx access log. More in the monthly attack report.

What the request is

The Model Context Protocol (MCP) is an open protocol for connecting LLM applications to outside tools and data. An MCP server advertises what it can do — tools it will run, resources it will read — and a client calls them. Servers that run locally talk over stdio and never touch a web log; servers published over HTTP are what these scans are looking for.

Two HTTP transports are in circulation. The older HTTP+SSE transport uses two endpoints: the client opens a long-lived GET that stays open as a Server-Sent Events stream, and the server tells it which URL to POST messages back to. The newer Streamable HTTP transport uses a single endpoint that takes POST for requests and GET for the event stream. Neither transport fixes a URL path, but SDK examples, framework integrations and deployment guides have converged on /mcp for the streamable endpoint and /sse for the older stream, with /api/mcp appearing where the server sits behind an existing API prefix. That convention is the entire reason these paths are now in scanner wordlists — the same way /actuator and /.env got there. The protocol documentation lives at modelcontextprotocol.io.

The payoff for a scanner is an MCP endpoint that answers without authentication. Such an endpoint can be asked what tools it offers and then asked to run them, and those tools execute with whatever access the server process was given: database connections, filesystem reads, cloud API tokens, ticketing and mail integrations. Nothing is leaked by the probe itself; the exposure, if there is one, is the server that answers.

Who sends it

This is undirected, internet-wide scanning, and it is recent — these paths only became worth sweeping once MCP deployments became common. The senders are mass scanners sweeping address ranges for newly fashionable endpoints, research crawlers that catalogue what is exposed, and botnets that fold any live hit into a follow-up pass.

The user agent proves little: some clients announce an HTTP library such as python-requests or Go-http-client, many copy a current Chrome string. The shape is more telling — a handful of paths from one address within seconds, no Referer, no requests for CSS, JavaScript or images, and a move straight on to other management and secret paths when everything returns 404. The same crawler run usually shows up on the /actuator and /.env entries of your log.

Is your server exposed?

Read what your logs already answered, then ask your own host:

# Every MCP/SSE probe in the log, with the status it received
awk '$7 ~ /^\/(api\/)?(mcp|sse)\/?$/ {print $9, $7}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -20

# Anything that was NOT 404/403 — this is the list that matters
awk '$7 ~ /^\/(api\/)?(mcp|sse)\/?$/ && $9 !~ /^(404|403)$/' /var/log/nginx/access.log

# Ask your own server, per vhost, and read only the status code
for p in /mcp /mcp/ /sse /api/mcp; do
  printf '%s ' "$p"
  curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: api.example.com' "http://127.0.0.1$p"
done

# Does any vhost proxy one of these paths to an application?
grep -rnE '/(api/)?(mcp|sse)' /etc/nginx/ 2>/dev/null

Run the curl loop with the Host header of each vhost you serve, including staging and internal names — an endpoint that was added for a demo and never removed is the usual way one stays reachable. Give rotated logs the same awk pass: an exposure may have been closed after it was already found.

How to block it

If you run no MCP server on a hostname, answer the probe cheaply and stop paying for it in log volume:

# No MCP endpoint on this vhost
location ~ ^/(api/)?(mcp|sse)/?$ {
    access_log off;
    return 404;
}

If you do run one, the fix is not a path rule — it is making sure nothing unauthenticated reaches the application. Bind the server to loopback or a private interface, put authentication in front of it, and restrict the sources that may connect:

location = /mcp {
    allow 10.0.0.0/8;
    deny  all;

    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Connection '';
    proxy_buffering off;          # SSE streams must not be buffered
    proxy_read_timeout 1h;        # default is 60s, which cuts long streams
}

Buffering has to be off or the event stream arrives in chunks, and proxy_read_timeout — 60 seconds by default — has to be raised or an idle stream is closed under you. Then alert on any non-404 status for these paths from an address outside the allow list, which is the only signal on this page worth a page-out.

Probes that arrive with it

Detect it

MITRE ATT&CK: T1595.003T1190

Frequently Asked Questions

Every one of these got a 404. Is there anything to investigate?
No, not as an incident: a 404 means no handler exists on that hostname and the traffic is undirected scanning that will keep arriving. The useful work is preventive — confirm no MCP server is proxied out on any vhost without authentication, and alert on non-404 responses to these paths rather than on the requests themselves.
Which response codes to /mcp or /sse should wake someone up?
Anything that is not 404 or 403. A 200, 400, 405 or 406 all mean something behind the proxy parsed the request and answered, which tells the scanner a real endpoint lives there and is worth a second, better-formed request. Treat a 200 on these paths from an unknown address as an exposed endpoint until you have proved otherwise.
Does a request to /sse always mean the scanner is looking for MCP?
No. Server-Sent Events is a generic browser streaming mechanism and plenty of applications serve notifications or live dashboards from a path called /sse that has nothing to do with MCP. Check your own routing table before you decide what the scanner found; the path is a convention, not a protocol marker.
We do run an MCP server behind this hostname. What should we check first?
Whether an unauthenticated request reaches it. Call the endpoint from an address outside your network and read the status, confirm the proxy enforces authentication or a source restriction in front of the application, and review the access log for earlier non-404 responses on those paths and the addresses that got them.

See what else is probing your server

Drop an nginx or Apache access log into the free browser analyzer: secret-file sweeps, exploit probes, scanners and credential stuffing are grouped by source with the evidence lines, and every known probe links back here. Nothing is uploaded.