Skip to main content

interactsh-mcp

An MCP server that gives LLMs first-class access to ProjectDiscovery's Interactsh — the out-of-band (OOB) interaction collector behind tools like Nuclei.

Use it to let an agent validate OOB-style vulnerabilities end-to-end: blind SSRF, log4shell-style JNDI callbacks, blind XXE, blind SQLi exfil, DNS exfiltration, command-injection callbacks, blind XSS, etc. The agent gets a fresh payload URL, embeds it in a request to the target, and polls for the callback to confirm the bug.

Works against the public Interactsh service (the public oast.* rotation) out of the box, and against any self-hosted server — with or without a token.

The package ships two things in one install:

  1. An MCP server binary (interactsh-mcp) for Claude Code, Codex CLI, etc.
  2. An async Python library (interactsh_mcp) you can import directly if you'd rather drive Interactsh from your own code — see Use as a Python library below.

Install

From PyPI (recommended)

pip install interactsh-mcp
# or, with uv:
uv tool install interactsh-mcp

After install the interactsh-mcp command starts the MCP server on stdio.

From GitHub (latest, no PyPI release needed)

pip install git+https://github.com/rek7/interactsh-mcp.git
# or, run without installing:
uvx --from git+https://github.com/rek7/interactsh-mcp.git interactsh-mcp

From source

git clone https://github.com/rek7/interactsh-mcp.git
cd interactsh-mcp
pip install .

Docker

# Build locally
docker build -t interactsh-mcp .
docker run --rm -i interactsh-mcp     # smoke test; Ctrl-D to exit

The image runs as a non-root user and only needs outbound HTTPS to the Interactsh server.


Configure your client

Claude Code

Claude Code reads MCP servers from ~/.claude.json (or a project-local .mcp.json). Add an entry under "mcpServers":

Local Python install — public servers

{
  "mcpServers": {
    "interactsh": {
      "command": "interactsh-mcp"
    }
  }
}

Local Python install — self-hosted server

{
  "mcpServers": {
    "interactsh": {
      "command": "interactsh-mcp",
      "env": {
        "INTERACTSH_SERVER": "interact.example.com",
        "INTERACTSH_TOKEN": "your-server-token"
      }
    }
  }
}

Omit INTERACTSH_TOKEN if your self-hosted server was started without -auth.

Docker

{
  "mcpServers": {
    "interactsh": {
      "command": "docker",
      "args": [
        "run", "--rm", "-i",
        "-e", "INTERACTSH_SERVER",
        "-e", "INTERACTSH_TOKEN",
        "interactsh-mcp"
      ],
      "env": {
        "INTERACTSH_SERVER": "interact.example.com",
        "INTERACTSH_TOKEN": "your-server-token"
      }
    }
  }
}

You can also register the server with the CLI:

claude mcp add interactsh -- interactsh-mcp
# with env vars:
claude mcp add interactsh \
  -e INTERACTSH_SERVER=interact.example.com \
  -e INTERACTSH_TOKEN=your-server-token \
  -- interactsh-mcp

Codex CLI

Codex CLI (OpenAI's terminal coding agent) reads MCP servers from ~/.codex/config.toml:

Public servers

[mcp_servers.interactsh]
command = "interactsh-mcp"

Self-hosted with token

[mcp_servers.interactsh]
command = "interactsh-mcp"
env = { INTERACTSH_SERVER = "interact.example.com", INTERACTSH_TOKEN = "your-server-token" }

Docker

[mcp_servers.interactsh]
command = "docker"
args = ["run", "--rm", "-i", "-e", "INTERACTSH_SERVER", "-e", "INTERACTSH_TOKEN", "interactsh-mcp"]
env = { INTERACTSH_SERVER = "interact.example.com", INTERACTSH_TOKEN = "your-server-token" }

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows. Same shape as the Claude Code example above.


Environment variables

Variable Purpose
INTERACTSH_SERVER Default hostname (e.g. interact.example.com). If unset, a random host from the public oast.* rotation is used. Per-call server argument overrides.
INTERACTSH_TOKEN Default Authorization token for -auth-protected servers. Per-call token argument overrides.
INTERACTSH_LOG_LEVEL Python log level (default WARNING).

Tools the LLM sees

Tool What it does
create_session Registers a new Interactsh session, starts background polling, and returns session_id + payload_url. Optional args: server, token, poll_interval, scheme, correlation_id_length.
list_sessions Lists active sessions with their buffer counts.
poll_interactions Drains buffered interactions for a session. wait (seconds) blocks until the first event arrives; clear=false peeks without consuming.
new_payload Vends an additional payload URL on an existing session (different nonce, same correlation-id — all routed back to the same session).
destroy_session Deregisters and frees the session.
get_default_servers Returns the public oast.* host list.

Typical agent workflow

  1. Agent calls create_session → gets payload_url e.g. c7lci09s8mts0o3og0g0abc12.oast.pro.
  2. Agent crafts an exploit that uses payload_url — a JNDI string in a User-Agent header, a ?url=http://payload_url/ SSRF, a DNS subdomain in a SQL payload, etc.
  3. Agent sends the request to the target.
  4. Agent calls poll_interactions(session_id, wait=10) and, if interactions come back, reports the vuln as confirmed (with the source IP / raw request from the callback as evidence).
  5. Agent calls destroy_session when done.

For multiple test variants in one conversation the agent can either call create_session per variant or call new_payload to get distinct URLs that share a session.


Use as a Python library

The package exports an async client you can import directly — no MCP, no LLM, just interactsh_mcp:

import asyncio
from interactsh_mcp import InteractshClient

async def main():
    # No args → random public oast.* server. Pass server=/token= to target a
    # self-hosted instance (token only needed if it was started with -auth).
    async with InteractshClient() as client:
        payload_url = client.generate_payload()
        print(f"send something to: {payload_url}")

        # ... fire your exploit / trigger the target with that URL ...

        for event in await client.poll():
            print(f"{event.protocol} from {event.remote_address}")

asyncio.run(main())

For longer-lived setups (multiple payloads, background polling, buffered events), use the session manager:

from interactsh_mcp import SessionManager

mgr = SessionManager()
session = await mgr.create(poll_interval=2.0)

url_a = mgr.new_payload(session)   # different nonce, same correlation-id
url_b = mgr.new_payload(session)
# ... trigger both ...

events = await session.drain(wait=15)   # blocks until first event or timeout
await mgr.aclose()

Exports at the package root:

Name What it is
InteractshClient Async client. Context-manage it; register() / poll() / deregister() / generate_payload().
Interaction Dataclass for a single decoded event: protocol, unique_id, full_id, q_type, raw_request, raw_response, remote_address, timestamp, raw (full JSON).
InteractshError Raised on registration/poll failures.
Session / SessionManager Background-polling multi-session helpers.
DEFAULT_SERVERS Tuple of public oast.* hostnames.

The package ships type stubs (py.typed), so mypy / pyright pick the types up automatically. If you need to call from sync code, wrap with asyncio.run(...) — see examples/04_sync_wrapper.py.

More runnable examples: examples/.


Self-hosting Interactsh

Quick reminder of how to stand up a server you can point this MCP at:

# Public, no auth:
interactsh-server -domain interact.example.com

# Token-protected (server prints a random token; pass it as INTERACTSH_TOKEN):
interactsh-server -domain interact.example.com -auth

See the interactsh server docs for DNS, TLS, and infra setup.


Development

git clone https://github.com/rek7/interactsh-mcp.git
cd interactsh-mcp
python -m venv .venv && source .venv/bin/activate
pip install -e '.[test]'
pytest

Live integration tests (DNS-callback against a real server) are opt-in:

INTERACTSH_LIVE=1 pytest tests/test_live.py
# or against a self-hosted server:
INTERACTSH_LIVE=1 INTERACTSH_SERVER=interact.example.com INTERACTSH_TOKEN=... pytest tests/test_live.py

The mocked test suite exercises the full register → poll → decrypt → drain path against an in-process fake server, so the crypto and HTTP wiring are covered without network access.


License

MIT. Interactsh itself is MIT-licensed and developed by ProjectDiscovery.

Metadata

Release files for interactsh-mcp 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for interactsh-mcp 0.1.1
File Size Uploaded Provenance Actions
interactsh_mcp-0.1.1.tar.gz 19.5 kB With provenance Details for interactsh_mcp-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for interactsh-mcp 0.1.1
File Interpreter ABI Platform Actions
interactsh_mcp-0.1.1-py3-none-any.whl Python 3 none any Details for interactsh_mcp-0.1.1-py3-none-any.whl

Total release size: 34.2 kB

Release files / interactsh_mcp-0.1.1.tar.gz

Download URL interactsh_mcp-0.1.1.tar.gz
Size 19.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ea00e6635a7fb790451ff0e5596aefb1fb784fd0450781f592ac0fbb951c68ef
BLAKE2b-256 checksum
How to use checksums
75ac0b456e2e1e3c44ccf3a422709ca70a3338380a20e82473c27c03b77cb406
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 17, 2026.

Transparency log

Release files / interactsh_mcp-0.1.1-py3-none-any.whl

Download URL interactsh_mcp-0.1.1-py3-none-any.whl
Size 14.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
73ebee1fd12f4e899bd87e103246eb0c3f123b283c138253cdbcfbbd94e9a9c2
BLAKE2b-256 checksum
How to use checksums
5ba3d93b3119b46f7ac65ae8d9980f00f87476c432487c6dc9a4dc43d58299f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 17, 2026.

Transparency log

Security for interactsh-mcp 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for interactsh_mcp-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page