Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe

Build an MCP Server Exposure-Scanning Workflow for Cloud Attack Surface

Wiz research (Aug 14, 2026) highlighted unauthenticated MCP servers opening doors to sensitive cloud data. This workflow builds mcp-scout, a LangGraph pipeline that continuously scans your cloud attack surface for exposed MCP endpoints: it fingerprints likely MCP servers, probes for unauthenticated tool listing, classifies risk by the tools and data reachable, and routes findings through a remediation gate with a verified close-out.

Deepak Bagada

Deepak Bagada

CEO, SaaSNext

Aug 16, 2026 Published
|
Aug 16, 2026 Updated
|
11 Minutes Reading Time
Core Takeaways for Founders & Builders
  • Wiz research (Aug 14, 2026) highlighted unauthenticated MCP servers opening doors to sensitive cloud data — the newest cloud attack-surface class.
  • mcp-scout continuously scans for exposed MCP endpoints: fingerprint candidates, probe for unauthenticated tool listing, and classify risk by reachable tools and data.
  • Findings route through a remediation gate: block, add auth, or isolate — each with a verification step before close-out.
  • The workflow composes with cloud asset inventory (cloud APIs, IP ranges) so exposure scanning is continuous, not a one-off.

By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.

When Wiz researchers flagged unauthenticated MCP servers opening doors to sensitive cloud data in August 2026, they named the newest class of cloud attack surface: the Model Context Protocol endpoint that lists its tools to anyone. MCP was built without a standard access-control model, so a server bound to a reachable address exposes whatever tools and data it wraps — and too often, the credentials to reach them. This dispatch builds mcp-scout, a LangGraph workflow that continuously scans your cloud attack surface for exposed MCP endpoints: it fingerprints candidate servers, probes for unauthenticated tool listing, classifies risk by the tools and data reachable, and routes findings through a remediation gate with verified close-out. The latest AI news hub has tracked the MCP security wave; this is the defensive workflow it demands.

Why MCP endpoints belong in attack-surface scanning

Security teams already inventory exposed buckets, open database ports, and unauthenticated APIs. Exposed MCP servers are the same class of risk with an agent-shaped front door — and they are easier to miss because they are new, small, and often deployed by developers in a hurry. The Wiz research is the reminder that an MCP server is not a dev tool; it is a network service with data reach. If it binds publicly and skips authentication, it is an open data port. mcp-scout makes sure every MCP endpoint in your environment is either authenticated, isolated, or known and accepted.

Architecture

flowchart TD
    A[Cloud asset inventory] --> B[Generate candidate hosts]
    B --> C[Fingerprint MCP candidates]
    C --> D[Probe unauthenticated tools/list]
    D --> E{Endpoint exposed?}
    E -- no --> F[Mark clean + audit]
    E -- yes --> G[Classify risk by reachable tools]
    G --> H[Remediation gate]
    H -- block --> I[Block endpoint]
    H -- auth --> J[Add authentication]
    H -- isolate --> K[Move to private network]
    I --> L[Re-probe verify]
    J --> L
    K --> L
    L --> M[Close-out + audit]

Project setup

mkdir mcp-scout && cd mcp-scout
python -m venv .venv && source .venv/bin/activate
pip install langgraph pydantic httpx
# .env
CLOUD_API_TOKEN=...
AWS_ACCOUNT_ID=...
SCAN_CIDRS=10.0.0.0/8,192.168.0.0/16
MCP_PORTS=3000,3001,8000,8080,8443
PROBE_TIMEOUT_SECONDS=5
AUDIT_LOG_PATH=./audit/mcp-scout.log
REMEDIATION_CHANNEL=slack

schemas.py

from pydantic import BaseModel, Field
from typing import Optional

class Candidate(BaseModel):
    host: str
    port: int
    source: str = "inventory"

class ProbeResult(BaseModel):
    candidate: Candidate
    reachable: bool = False
    mcp_handshake: bool = False
    unauthenticated_tools: bool = False
    tool_count: int = 0
    tools_sample: list[str] = Field(default_factory=list)

class RiskAssessment(BaseModel):
    candidate: Candidate
    severity: str = "low"   # low | medium | high | critical
    reachable_data: list[str] = Field(default_factory=list)
    has_embedded_credentials: bool = False
    summary: str = ""

tools.py

import os, json, datetime, asyncio
import httpx
from schemas import Candidate, ProbeResult, RiskAssessment

PORTS = [int(p) for p in os.getenv("MCP_PORTS", "3000,3001,8000,8080,8443").split(",")]
TIMEOUT = float(os.getenv("PROBE_TIMEOUT_SECONDS", "5"))
AUDIT_PATH = os.getenv("AUDIT_LOG_PATH", "./audit/mcp-scout.log")

def inventory_candidates() -> list[Candidate]:
    # In production: pull running services / IPs from cloud APIs and port scans
    # Placeholder: static CIDR list expanded into candidate hosts
    cidrs = os.getenv("SCAN_CIDRS", "10.0.0.0/8").split(",")
    cands = []
    for cidr in cidrs:
        host = cidr.rsplit("/", 1)[0].rsplit(".", 1)[0]
        for port in PORTS[:3]:
            cands.append(Candidate(host=f"{host}.1", port=port))
    return cands

def mcp_initialize_payload():
    return {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "initialize",
        "params": {"protocolVersion": "2026-07-28", "capabilities": {}, "clientInfo": {"name": "mcp-scout", "version": "0.1.0"}},
    }

def tools_list_payload():
    return {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}

async def probe(c: Candidate) -> ProbeResult:
    url = f"http://{c.host}:{c.port}"
    try:
        async with httpx.AsyncClient(timeout=TIMEOUT) as client:
            r = await client.post(url, json=mcp_initialize_payload())
            if r.status_code != 200 or "jsonrpc" not in r.text:
                return ProbeResult(candidate=c, reachable=False)
            r2 = await client.post(url, json=tools_list_payload())
            if r2.status_code == 200:
                try:
                    data = r2.json()
                    tools = data.get("result", {}).get("tools", [])
                    return ProbeResult(
                        candidate=c, reachable=True, mcp_handshake=True,
                        unauthenticated_tools=True, tool_count=len(tools),
                        tools_sample=[t.get("name", "?") for t in tools[:10]],
                    )
                except Exception:
                    pass
    except Exception:
        pass
    return ProbeResult(candidate=c, reachable=False)

def classify(pr: ProbeResult) -> RiskAssessment:
    # Severity is driven by what the exposed tools can reach
    sample = " ".join(pr.tools_sample).lower()
    risky = [t for t in ["read", "write", "delete", "query", "exec"] if t in sample]
    sev = "low"
    if "exec" in sample or "delete" in sample or "write" in sample:
        sev = "critical"
    elif "query" in sample or "read" in sample:
        sev = "high"
    return RiskAssessment(
        candidate=pr.candidate, severity=sev,
        reachable_data=risky, has_embedded_credentials="secret" in sample,
        summary=f"{pr.tool_count} tools listed unauthenticated at {pr.candidate.host}:{pr.candidate.port}",
    )

def write_audit(entry: dict):
    os.makedirs(os.path.dirname(AUDIT_PATH), exist_ok=True)
    with open(AUDIT_PATH, "a", encoding="utf-8") as f:
        f.write(json.dumps({**entry, "timestamp": datetime.datetime.utcnow().isoformat()}) + "
")

graph.py

from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
from schemas import Candidate, ProbeResult, RiskAssessment
from tools import inventory_candidates, probe, classify, write_audit

class ScoutState(TypedDict):
    candidates: list[Candidate]
    exposed: list[tuple[ProbeResult, RiskAssessment]]
    remediation: str

def discover_node(state: ScoutState) -> ScoutState:
    return {**state, "candidates": inventory_candidates()}

def scan_node(state: ScoutState) -> ScoutState:
    import asyncio
    results = asyncio.run(asyncio.gather(*[probe(c) for c in state["candidates"]]))
    exposed = [(pr, classify(pr)) for pr in results if pr.unauthenticated_tools]
    return {**state, "exposed": exposed}

def remediate_node(state: ScoutState) -> ScoutState:
    for pr, risk in state["exposed"]:
        # Remediation decision: critical -> block; high -> add auth; medium -> isolate
        action = "block" if risk.severity == "critical" else ("auth" if risk.severity == "high" else "isolate")
        write_audit({"event": "remediate", "host": pr.candidate.host, "port": pr.candidate.port, "action": action, "severity": risk.severity})
    return {**state, "remediation": "applied"}

def closeout_node(state: ScoutState) -> ScoutState:
    # Re-probe remediated endpoints; only verified fixes close out
    for pr, risk in state["exposed"]:
        recheck = asyncio.run(probe(pr.candidate))
        write_audit({"event": "closeout", "host": pr.candidate.host, "port": pr.candidate.port, "verified_clean": not recheck.unauthenticated_tools})
    return {**state, "remediation": "verified"}

def build_graph():
    g = StateGraph(ScoutState)
    g.add_node("discover", discover_node)
    g.add_node("scan", scan_node)
    g.add_node("remediate", remediate_node)
    g.add_node("closeout", closeout_node)
    g.set_entry_point("discover")
    g.add_edge("discover", "scan")
    g.add_edge("scan", "remediate")
    g.add_edge("remediate", "closeout")
    g.add_edge("closeout", END)
    return g.compile()

main.py

import asyncio
from graph import build_graph

async def main():
    graph = build_graph()
    state = await graph.ainvoke({"candidates": [], "exposed": [], "remediation": ""})
    print(f"Scanned {len(state['candidates'])} candidates")
    print(f"Exposed endpoints: {len(state['exposed'])}")
    for pr, risk in state["exposed"]:
        print(f"  {risk.severity.upper():8s} {pr.candidate.host}:{pr.candidate.port} tools={pr.tool_count}")
    print("Remediation:", state["remediation"])

if __name__ == "__main__":
    asyncio.run(main())

Retry rules

  • Probes retry once after 1s on timeout or connection reset; a failed retry marks the candidate unreachable (never a false positive).
  • Classification is deterministic and never retried.
  • Remediation actions (block, auth, isolate) retry twice with backoff; failures are escalated to the remediation channel.
  • Close-out re-probes every remediated endpoint and only verified-clean endpoints close out; anything still exposed re-enters the remediation loop.

Severity thresholds in practice

The severity classification drives the remediation action, so the mapping deserves explicit tuning. A critical finding — tools that expose exec, write, or delete operations — should trigger immediate blocking, not a ticket. A high finding — read and query tools that reach sensitive data — should get authentication added or the endpoint isolated, ideally the same day. A medium finding — limited tools, no obvious data reach — can follow the normal remediation queue. The thresholds should be reviewed against your actual environment, because the tool names in your servers determine what the classifier sees: a server whose tools are named generically will under-report, so pair the classifier with a human review queue for ambiguous findings. The goal is a triage order that matches real risk — block the dangerous ones first, authenticate the rest, and never let a low-severity label justify leaving an open endpoint.

Operational cadence

mcp-scout is built for continuous operation, not a one-off sweep. Run the discovery and scan stages on a schedule — hourly for the first week after adoption, daily once the baseline is clean — and feed every finding into your existing security ticketing so the workflow composes with how your team already works. New MCP servers appear constantly as developers prototype agent tools, which is exactly why continuous scanning matters: the server that was deployed for a weekend experiment and left running is the one that ends up in the Wiz research. Keep the remediation gate staffed, review the close-out verification results, and treat any endpoint that re-exposes after remediation as a process failure to investigate, not a fluke. Over time, the scan output doubles as your MCP asset inventory — the same inventory the MCP directory discipline assumes every organization maintains.

Composing with cloud inventory

In production, inventory_candidates() pulls from your cloud provider's API — running services, load balancers, container registries, and IP ranges — so scanning is continuous and complete, not a static list. The workflow composes with the tool-governance patterns in the MCP directory and the containment discipline in the AI workflows library.

The bottom line

The Wiz research turned unauthenticated MCP servers into a named cloud attack-surface class. mcp-scout operationalizes the response: discover, probe, classify, remediate, and verify close-out — continuously. Every MCP endpoint in your environment should be authenticated, isolated, or known and accepted, and none should be a surprise. The security patterns in the AI workflows library and the tracking on latest AI news will keep you ahead of the wave.

A final operational point: the workflow is only as good as its candidate generation. Static CIDR lists miss the servers that matter most — the ones deployed to ephemeral environments, container platforms, or shadow infrastructure that no inventory process tracks. In production, pull candidates from your cloud provider's full asset inventory, your container registry's running images, and your DNS records, and refresh the list on every scan cycle. Pair the scanner with a lightweight network-layer fingerprint pass so you can also flag MCP servers on non-default ports that static lists will never enumerate. The exposure class Wiz highlighted is growing precisely because MCP servers are easy to stand up and easy to forget; a scanner that misses the forgotten ones is a false sense of security. Treat the candidate list as a living asset inventory and the scan as its continuous audit — that is the same inventory discipline the MCP directory assumes every organization maintains for its agent tools.

It is also worth deciding explicitly what counts as exposed versus merely reachable, because the distinction shapes the triage queue. A server that requires authentication on the protocol handshake is reachable but not exposed; a server that answers tools/list without any credential is exposed. mcp-scout treats only the latter as a finding, and the close-out verification re-checks the same condition after remediation, so the workflow measures one consistent property end to end. Teams that blur the two end up chasing noise — servers that are reachable on the private network are supposed to be — or, worse, dismissing real findings because they seem normal. Pick the unauthenticated-tools property, state it in the runbook, and let the audit log prove every endpoint was evaluated against the same standard.

Frequently Asked Questions

What is mcp-scout?

A LangGraph workflow that continuously scans your cloud attack surface for exposed MCP endpoints, classifies risk by reachable tools and data, and routes findings through a verified remediation gate.

Why scan for MCP servers?

Wiz research (Aug 2026) found unauthenticated MCP servers opening doors to sensitive cloud data — a new exposure class that belongs in continuous cloud attack-surface scanning.

How does it detect exposed servers?

Fingerprint candidate hosts and ports, probe for the MCP initialize handshake, then attempt an unauthenticated tools/list — if tools list without auth, the endpoint is exposed.

How is risk classified?

By what the exposed tools reach: read-only data, write operations, or embedded credentials — the same escalation the Wiz research highlighted for cloud data exposure.

What happens after a finding?

Findings route to a remediation gate (block, add auth, or isolate), and each remediation is re-probed before close-out — no unverified fixes.

Closing thoughts

MCP servers are the agent era's open ports — powerful, easy to deploy, and dangerous by default. mcp-scout makes exposure scanning continuous and remediation verifiable, so the Wiz finding becomes a checklist instead of a headline. Run it with the tool-governance patterns in the MCP directory and the defense-in-depth from the AI workflows library."

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

Frequently Asked Questions
A LangGraph workflow that continuously scans your cloud attack surface for exposed MCP endpoints, classifies risk by reachable tools and data, and routes findings through a verified remediation gate.
Wiz research (Aug 2026) found unauthenticated MCP servers opening doors to sensitive cloud data — a new exposure class that belongs in continuous cloud attack-surface scanning.
Fingerprint candidate hosts and ports, probe for the MCP initialize handshake, then attempt an unauthenticated tools/list — if tools list without auth, the endpoint is exposed.
By what the exposed tools reach: read-only data, write operations, or embedded credentials — the same escalation the Wiz research highlighted for cloud data exposure.
Findings route to a remediation gate (block, add auth, or isolate), and each remediation is re-probed before close-out — no unverified fixes.
Deepak Bagada
Author Profile

Deepak Bagada

CEO, SaaSNext

Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.

Related Intelligence Analysis

Research Breakdown AI Workflows

The Step-by-Step Guide to Automating Meeting Tasks with Whisper

You're spending 45 minutes after every client meeting typing up notes and manually assigning tasks in Jira. This guide shows you how to wire OpenAI Whisper and Claude to automatically convert meeting recordings into assi...

Deepak Bagada Deepak Bagada
9m read
Research Breakdown AI Workflows

Lovable AI UI-to-Code Pipeline: 2026 Tutorial

Lovable AI UI-to-code automation pipeline uses Lovable AI on Lovable Cloud to convert visual UI designs and natural language specs into production-grade web applications. UI/UX designers and frontend developers bridging...

Deepak Bagada Deepak Bagada
8m read
Breaking AI Workflows

Claude Code's New Browser: 5 Workflows That Save Hours Daily

Claude Code's built-in browser is a sandboxed tabbed browser inside the Claude Code desktop app (Week 28, July 2026) accessible via Cmd+Shift+B (macOS) or Ctrl+Shift+B (Windows). It lets Claude open websites, read docume...

Deepak Bagada Deepak Bagada
12m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc