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:
- An MCP server binary (
interactsh-mcp) for Claude Code, Codex CLI, etc. - An async Python library (
interactsh_mcp) you canimportdirectly 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
- Agent calls
create_session→ getspayload_urle.g.c7lci09s8mts0o3og0g0abc12.oast.pro. - 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. - Agent sends the request to the target.
- 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). - Agent calls
destroy_sessionwhen 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.