01Threat intelligence for Python & MCP

Know what you’re talking to.

sec-mcp checks domains, URLs and IP addresses against ten live blacklist feeds — in process, in microseconds, with no API key. Try the MCP server right here: the console answers from a demo dataset until you connect your own.

Python 3.11+Library, CLI and MCP serverstdio + streamable HTTP

sec-mcpplayground

Simulation

Start one with sec-mcp-server --http
Add
check_batch

Results appear here. Try the sample values above, or pick a playbook below.

Simulated answers from a 10-feed demo dataset (13 known values). Connect your own server for real verdicts.

Test your own server

  1. 01

    Install and index the feeds

    $pip install sec-mcp && sec-mcp update
  2. 02

    Serve MCP over HTTP

    $sec-mcp-server --http
  3. 03

    Press Connect in the console. It defaults to port 8000 on the host serving this page. On the public site, that host is 127.0.0.1.

First-run checklist

  • MCP server reachable — not connected yet
  • Blacklists indexed — unknown until connected
Threat sources
10feeds
Domain lookup
0.006ms
450K entries in memory
~45MB
Open source
Apache-2.0

02Playbooks

Start from the job, not the API.

Six things people use sec-mcp for. Run any of them in the console above, or say the prompt to an assistant with the MCP server attached.

  • 01

    Triage a phishing email

    Pull every link out of a suspicious message and check them in one call.

    “Are any of the links in this email on a blacklist?”
    check_batch(values=[3])
  • 02

    Vet outbound connections

    Screen the destination IPs from a firewall or proxy log before you escalate.

    “Which of these destination IPs are known-bad?”
    check_batch(values=[3])
  • 03

    Guard an agent’s clicks

    Have an agent verify a URL before it fetches, downloads or recommends it.

    “Before you download that file, check the URL with sec-mcp.”
    check_batch(values=[3])
  • 04

    Check feed freshness

    Entry counts per source and when the blacklists were last refreshed.

    “How fresh are your blacklists, and how many entries does each feed hold?”
    get_status()
  • 05

    Health-check the server

    Database reachable, scheduler alive, last update recorded — in one answer.

    “Run a health check on the sec-mcp server.”
    get_diagnostics(mode="health")
  • 06

    Sample the blacklist

    Pull a handful of real entries to see what the feeds actually contain.

    “Show me five random entries from the blacklist.”
    get_diagnostics(mode="sample", sample_count=5)

03Capabilities

A blacklist engine that behaves like a local function call.

No hosted service sits between you and an answer. The feeds are downloaded, normalised and indexed on your machine, and every check after that is a memory read.

01

Ten feeds, one index

OpenPhish, PhishTank, PhishStats, URLhaus, Spamhaus DROP, DShield, CINS, Emerging Threats, Feodo Tracker and Blocklist.de — merged into a single lookup.

02

Microsecond lookups

One O(1) in-memory index per entry type. A domain resolves in 0.006 ms and a URL in 0.0007 ms once in-memory storage is enabled.

03

Three ways in

Import it as a Python library, drive it from the terminal, or run it as an MCP server so an assistant can check a link before it recommends one.

04

CIDR-aware IP matching

IPv4 addresses are stored as integers and matched against whole networks, not just literal addresses — so a single range covers what it should.

05

Refreshed on a schedule

Feeds update daily at 00:00 from a background scheduler, and sec-mcp update forces a refresh whenever you need one.

06

Safe under concurrency

SQLite in WAL mode behind an in-memory cache, so parallel checks from threads or a busy MCP client stay consistent.

04Sources

Ten public feeds, normalised into one register.

Each feed has its own format and its own idea of what an entry looks like. sec-mcp parses them per source, normalises URLs, stores IPv4 as integers, and hands you one answer.

  1. 01OpenPhishLive phishing URLsURL
  2. 02PhishStatsScored phishing URLsURL
  3. 03URLhausMalware distribution URLsURL
  4. 04PhishTankCommunity-verified phishingURL
  5. 05Spamhaus DROPHijacked and rogue netblocksCIDR
  6. 06DShieldMost-attacking networksCIDR
  7. 07CINS ScorePoorly-reputed addressesIP
  8. 08Emerging ThreatsCompromised hostsIP
  9. 09Feodo TrackerBotnet command & controlIP
  10. 10Blocklist.deReported attacking hostsIP

Need a source of your own? Add entries directly with add_entry or point the config at another feed.

05Model Context Protocol

Give your assistant a way to check before it recommends.

Run sec-mcp as an MCP server and six tools appear in your client. The model can verify a link mid-conversation instead of guessing at it.

Client configuration

Point your MCP client at the interpreter inside the virtual environment where sec-mcp is installed.

mcp config
{
  "mcpServers": {
    "sec-mcp": {
      "command": "/absolute/path/to/.venv/bin/python",
      "args": ["-m", "sec_mcp.start_server"]
    }
  }
}

Or serve it over streamable HTTP for remote clients and the playground above. It binds to 127.0.0.1 unless you pass --host.

http transport
sec-mcp-server --http --port 8000
# → http://127.0.0.1:8000/mcp

# optional: allow another web origin, require a token
export SEC_MCP_CORS_ORIGINS=https://your.site
export SEC_MCP_HTTP_AUTH_TOKEN=change-me

Before you start

  • Use the absolute path to the venv’s Python. A bare python will start the wrong interpreter.
  • Run sec-mcp update once first, then sec-mcp status to confirm the database is populated.
  • Over stdio the server starts and stops with your MCP client; over HTTP it runs until you stop it.

Full steps in the install guide.

Exposed tools(6)

check_batchcheck_batch(values: List[str])
Check multiple domains/URLs/IPs in one call. Returns list of {value, is_safe, verdict, explanation}.
get_statusget_status()
Get blacklist status including entry counts and sources. Returns {entry_count, last_update, sources, server_status, source_counts}.
update_blacklistsupdate_blacklists()
Force immediate update of all blacklists. Returns {updated: bool}.
get_diagnosticsget_diagnostics(mode: str = "summary", sample_count: int = 10)
Get diagnostic information. Mode options: 'summary' (default), 'full', 'health', 'performance', 'sample'.
add_entryadd_entry(url, ip, date, score, source)
Add a manual blacklist entry.
remove_entryremove_entry(value: str)
Remove a blacklist entry by URL or IP.

In conversation

transcript
User: "Is example.com safe to visit?"

AI: Let me check that URL for you.
[Uses sec-mcp check_batch tool]

I've checked example.com against our security database.
The domain is not found in any blacklists and
appears to be safe to visit.

06Install

From pip install to a first verdict.

Four commands to a working install. The only part people trip on is step three — the database is empty until you populate it.

  1. Create a virtual environment

    Python 3.11 or newer. The MCP config later needs this path, so keep it somewhere stable.

    step 1
    python3 -m venv .venv
    source .venv/bin/activate  # Windows: .venv\Scripts\activate
  2. Install the package

    step 2
    pip install sec-mcp
    
    # Verify installation
    sec-mcp --version
  3. Populate the database

    The first download pulls all ten feeds and takes a few minutes. After that a scheduler refreshes them daily at 00:00.

    step 3
    # Download and index every configured feed
    sec-mcp update
    
    # Entry counts, last update, per-source breakdown
    sec-mcp status
  4. Run a check

    Prints "Status: Safe" or "Status: Blacklisted" with an explanation. Add --json for machine-readable output.

    step 4
    sec-mcp check example.com
    sec-mcp check 8.8.8.8
    sec-mcp check https://example.com/path --json

Or import it

Constructing SecMCP() is what creates the database and starts the scheduler — importing the package on its own does nothing.

python
from sec_mcp import SecMCP

# Default database location
client = SecMCP()

# Or point it at your own file
client = SecMCP(db_path="/srv/sec-mcp/blacklist.db")

result = client.check("https://example.com/login")

if result.blacklisted:
    # e.g. "Blacklisted URL by OpenPhish"
    print(f"Blocked: {result.explanation}")
else:
    print("Clean")
batch
values = [
    "example.com",
    "192.168.1.1",
    "https://example.org/login",
]

# Results come back in input order; CheckResult carries
# the verdict, so zip it against the values you sent.
for value, result in zip(values, client.check_batch(values)):
    verdict = "BLACKLISTED" if result.blacklisted else "clean"
    print(f"{value:<28} {verdict:<12} {result.explanation}")

Every method is listed in the API reference.

07Python API

Nine methods and two dataclasses. That is the whole surface.

Every check returns a CheckResult: a boolean and a sentence explaining it. There is nothing else to learn.

Methods(9)

check(value: str) -> CheckResult
Check a domain, URL or IP. Infers which of the three it is.
check_domain(domain: str) -> CheckResult
Check a domain and each of its parent domains.
check_url(url: str) -> CheckResult
Check a URL exactly, then fall back to its domain.
check_ip(ip: str) -> CheckResult
Check an address, including the networks that contain it.
check_batch(values: List[str]) -> List[CheckResult]
Check many values at once. Results come back in input order.
get_status() -> StatusInfo
Entry count, last update time, active sources and server state.
update() -> dict
Force a refresh of every feed. Rate limited; returns {"updated": bool}.
sample(count: int = 10) -> List[str]
Return a random sample of indexed entries.
scheduler_alive() -> bool
Report whether the background update thread is still running.

Return types

CheckResult

blacklistedbool
True when the value matched a feed.
explanationstr
"Blacklisted URL by OpenPhish", or "Not blacklisted".

StatusInfo

entry_countint
Total indexed entries.
last_updatedatetime
When the feeds last refreshed.
sourcesList[str]
Active feed names.
server_statusstr
Current server state.

Putting it together

python
from sec_mcp import SecMCP

client = SecMCP()

status = client.get_status()
print(f"{status.entry_count} entries "
      f"from {len(status.sources)} sources")
print(f"last update: {status.last_update}")

result = client.check_ip("185.0.0.1")
print(result.blacklisted, result.explanation)

# Force a refresh (rate limited)
print(client.update())   # {'updated': True}