vuln·telToolsField notesGet a key

The vulntel MCP tools

vulntel is a hosted MCP server that gives an AI security agent source-grounded vulnerability intelligence: a fused corpus (NVD + CISA KEV + FIRST EPSS + OSV/GHSA + CISA SSVC) plus a mechanic layer distilled from ~14,000 disclosed, paid bug-bounty reports. This page shows what the twelve tools actually return, with real output from the live server, trimmed for length. (Two that are async or verbose, observe and assist_submission, show the response shape rather than one capture.)

Full per-argument reference: TOOLS.md on GitHub. To drive it on a real hunt: the MIT-licensed bug-bounty skill + playbook. A key is free: mcp.rozetyp.com/signup.

Recon a target

observe - live recon of a host

Fingerprints a live host: recovers the real backend from the JS bundle (even for SPAs that hide it from header-fingerprinting), crawls the endpoints, and joins the stack to the corpus as a ranked dig-order. Async: a cache miss returns status="scanning" and you poll. The ready payload carries these fields:

observe("acme.example.com")  ->  { "status": "scanning", "retry_after_seconds": 8 }   # poll again
observe("acme.example.com")  ->  { "status": "ready",
                                    "backend": "...recovered from the JS bundle...",
                                    "endpoints": [ ...the real API surface... ],
                                    "recurring_loci": [ ...the bug-class shapes to hunt... ],
                                    "affected": [ ...CVE join for the resolved stack... ] }

hunt_plan - a recon'd stack to a ranked dig-order

Ranks a stack by its most-exploitable bug and names where each component historically bleeds.

hunt_plan(["craftcms 4.4", "nginx", "keycloak"])

#1  craftcms 4.4   97 CVEs, 12 high-risk   recurring_loci CWE-94 code injection x7 (2 exploited in the wild)
    dig here  P1 KEV EPSS 99.8  CVE-2025-32432  unauthenticated RCE (CVSS 10)   your 4.4 is AFFECTED, fixed in 4.14.15
#2  nginx          HTTP/2 Rapid Reset  CVE-2023-44487 (KEV)
#3  keycloak       recurring_loci CWE-287 auth x12

check_technology - one product to version-resolved, ranked CVEs

De-dupes across NVD CPE and OSV, ranks exploitation-first, flags name ambiguity instead of a silent zero.

check_technology("Apache Struts")
  P1  KEV  CVE-2017-5638   Jakarta Multipart parser RCE via a crafted Content-Type
  P1  KEV  CVE-2013-2251   OGNL expression injection -> remote code execution
  P1  KEV  CVE-2020-17530  forced OGNL evaluation on tag attributes -> RCE

program_outcome_prior - what actually pays on a bug-bounty program

A minute-zero, disclosure-conditioned prior: which bug classes have historically landed, with lift over the global base rate. Descriptive, not predictive; use it to reorder attention, never to eliminate a class.

program_outcome_prior("gitlab")     # n_reports: 257
  broken access control   16.7%   (lift 2.0x)
  SSRF                      7.0%   (lift 3.4x)
  code/command injection    7.0%   (lift 1.4x)

Transfer a mechanic to your target

The corpus distills ~14,000 disclosed, paid reports into transferable mechanics. These tools find the move that worked on another product and hand it to you for yours.

find_attack_approaches - borrow a proven attack, and grow tried= to reach the non-obvious

Retrieves the disclosed mechanics closest to your situation, each with the transferable trigger to adapt. The tried= parameter is what makes it a loop and not a list: name the bug classes you have already worked and it pushes them down, surfacing the approaches you have not tried yet. Grow tried= each round.

Same query, before and after you tell it what you have already worked (real output, trimmed):

find_attack_approaches("a multi-tenant SaaS with a REST API and file uploads")   # tried=[]
  broken access control (public S3 bucket)     zomato      over-permissive bucket ACL to write objects
  arbitrary file read                          Nextcloud   trick the victim into an attacker-controlled app
  broken access control (exported component)   owncloud    abuse an exported file-receiving activity

find_attack_approaches(..., tried=["IDOR", "access control", "broken object level auth"])
  cloud storage takeover                       IBM         take over the S3 bucket tied to the endpoint
  authorization bypass (signed upload)         Rails       a mismatched content_length so the presigned URL does not bind it
  arbitrary file write (S3 upload policy)      Shopify     an upload policy that constrains the key prefix, not the content

Tell it you have worked IDOR and access-control, and it surfaces the S3-bucket-takeover and the presigned-URL content-length bypass instead, adapted from real reports on other products. Each card carries the trigger to adapt, the program it paid on, and whether it is still in scope.

find_continuations - your position to the concrete next step

Give it your product-free attacker position (what you control, reach, observe, and are blocked by) and it returns the concrete payload-bearing step from real report bodies, each UNVERIFIED with the decisive check that settles it and a link to the source report.

find_continuations("I control a webhook URL the server fetches server-side; the response is blind; I want to reach cloud metadata")

  move_step: point the webhook at a server that issues a 303 redirect, then read the responses of
             arbitrary HTTP/HTTPS requests the app makes        [UNVERIFIED]  hackerone.com/reports/508459
  move_step: register a DNS-rebinding host (alternating to 127.0.0.1) as the webhook so the SSRF
             filter passes but the fetch lands internally        [UNVERIFIED]  hackerone.com/reports/1379656
  move_step: a failure email arrives for http://localhost but NOT for the internal URL that worked,
             so the absence of the email is your blind-SSRF oracle [UNVERIFIED] hackerone.com/reports/243277

Real disclosed moves adapted to your exact position, not a list of CVEs. The decisive_check on each says what to read on the target to confirm the precondition holds.

search_vulns - CVE search by keyword or by mechanism

One entry point, three modes: free-text (keyword + semantic fused), a seed cve_id (mechanism-siblings across products), or a time window (recent high-risk).

search_vulns(cve_id="CVE-2021-44228")    "what else works like Log4Shell"
  sim 0.88  CVE-2021-44832  Log4j2 JDBC Appender, JNDI LDAP RCE
  sim 0.82  CVE-2022-40145  Apache Karaf, code injection via a JNDI URL   # different product, same mechanism

Dig, fact-check, finish

enrich_cve - one CVE, the full dossier

enrich_cve("CVE-2024-3400")   Palo Alto PAN-OS GlobalProtect
  P1  KEV  CVSS 10.0    unauthenticated command injection -> root RCE
  SSVC        exploitation=active   automatable=yes   technical_impact=total
  Metasploit  exploit/linux/http/panos_telemetry_cmd_exec   (rank: excellent)
  Public PoC  44 repos   (h4x0r-dz 161 stars, ...)

verify_cve_claim - fact-check a claim, catch the hallucination

Per-assertion supported / refuted / unverifiable with cited evidence.

verify_cve_claim("CVE-2025-99999")
  ->  exists: false   "No record in NVD / OSV / GHSA. Likely hallucinated or not-yet-published."

verify_cve_claim("CVE-2021-44228", product="Apache Struts", severity="medium", exploited=false)
  refuted  "not exploited"          ->  in CISA KEV (added 2021-12-10)
  refuted  "severity medium"        ->  actual CVSS 10.0 -> P1
  refuted  "affects Apache Struts"  ->  this CVE is Apache Log4j2, not Struts

search_public_code - who copied a vulnerable line

Finds public repositories containing an exact code string. Raw hits you read yourself, best for spotting an unpatched vendored copy or how a bug was fixed in the wild.

search_public_code("<an exact line from the vulnerable function>")
  -> [ { "repo": "someorg/someproj", "path": "src/parser.py", "url": "https://github.com/..." }, ... ]

assist_submission - draft a submission, guarded

Pulls the closest disclosed precedent for a draft finding and returns a grounded brief with a citation guard. It surfaces prior art and what is novel; it does not decide whether to submit. Shape:

assist_submission("IDOR: GET /api/v2/invoices/{id} returns another tenant's invoice PDF")
  ->  { "count": <n>, "citations_grounded": <bool>,
        "prior_art": [ ...closest disclosed IDOR reports, program, payout... ],
        "brief": "precedent, what is novel here, how to frame the impact" }

corpus_stats - is the data fresh?

corpus_stats()
  -> { "cves": 368765, "kev_entries": 1638, "epss_scores": 344957,
       "latest_cve_modified": "2026-07-14", "data_age_days": 0.4, "stale": false }

Figures drift daily; call corpus_stats for the live count rather than trusting a number here.


Get a free key at mcp.rozetyp.com/signup, point your agent at mcp.rozetyp.com/mcp, and the bug-bounty skill drives all twelve on a real engagement. For the field notes behind the corpus, start here.