Skip to main content
Glama
firetix

MCP Vulnerability Checker Server

by firetix

get_epss_score

Assess the probability of CVE exploitation within 30 days using AI-powered EPSS scores to prioritize security vulnerabilities based on real-world exploit likelihood.

Instructions

Get EPSS exploitability prediction score for a CVE

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cve_idYesGet Exploit Prediction Scoring System (EPSS) scores for a CVE to assess the probability of exploitation in the wild within 30 days. Provide a CVE ID in the format CVE-YYYY-NNNN (e.g., CVE-2021-44228). Returns AI-powered risk prioritization scores with percentile rankings to help security teams focus on vulnerabilities most likely to be exploited by attackers.

Implementation Reference

  • The core handler function for the 'get_epss_score' tool. It validates the CVE ID, queries the FIRST.org EPSS API, processes the score into risk levels and percentiles, formats a detailed Markdown response with prioritization guidance, and handles various errors.
    async def get_epss_score(
        cve_id: str,
    ) -> List[types.TextContent | types.ImageContent | types.EmbeddedResource]:
        """
        Get EPSS (Exploit Prediction Scoring System) score for a CVE.
        EPSS provides probability estimates of exploitation in the wild.
    
        Args:
            cve_id: CVE identifier in format CVE-YYYY-NNNN
    
        Returns:
            List of content containing EPSS score information or error messages
        """
        # Clean up CVE ID format
        cve_id = cve_id.upper().strip()
        if not cve_id.startswith("CVE-"):
            cve_id = f"CVE-{cve_id}"
    
        # Validate CVE ID format (CVE-YYYY-NNNN)
        if not re.match(r"^CVE-\d{4}-\d{4,}$", cve_id):
            return [
                types.TextContent(
                    type="text",
                    text=f"Error: Invalid CVE ID format. Expected format: CVE-YYYY-NNNN (e.g., CVE-2021-44228). Got: {cve_id}",
                )
            ]
    
        headers = {
            "User-Agent": "MCP EPSS Lookup Tool v1.0",
            "Accept": "application/json",
        }
    
        try:
            timeout = httpx.Timeout(15.0, connect=10.0)
            async with httpx.AsyncClient(
                follow_redirects=True, headers=headers, timeout=timeout
            ) as client:
                # FIRST.org EPSS API endpoint
                url = f"https://api.first.org/data/v1/epss?cve={cve_id}"
                response = await client.get(url)
                response.raise_for_status()
    
                data = response.json()
    
                if data.get("status") != "OK":
                    return [
                        types.TextContent(
                            type="text",
                            text=f"Error: EPSS API returned status: {data.get('status', 'Unknown')}",
                        )
                    ]
    
                epss_data = data.get("data", [])
                if not epss_data:
                    return [
                        types.TextContent(
                            type="text",
                            text=f"No EPSS score found for {cve_id}. This CVE might be too new or not in the EPSS database.",
                        )
                    ]
    
                cve_epss = epss_data[0]  # EPSS API returns array, we want first item
    
                # Extract EPSS information
                epss_score = float(cve_epss.get("epss", 0))
                percentile = float(cve_epss.get("percentile", 0))
                date = cve_epss.get("date", "Unknown")
    
                # Convert EPSS score to percentage and risk level
                epss_percentage = epss_score * 100
    
                # Determine risk level based on EPSS score
                if epss_score >= 0.7:
                    risk_level = "šŸ”“ CRITICAL"
                    risk_desc = "Extremely high likelihood of exploitation"
                elif epss_score >= 0.3:
                    risk_level = "🟠 HIGH"
                    risk_desc = "High likelihood of exploitation"
                elif epss_score >= 0.1:
                    risk_level = "🟔 MEDIUM"
                    risk_desc = "Moderate likelihood of exploitation"
                elif epss_score >= 0.01:
                    risk_level = "🟢 LOW"
                    risk_desc = "Low likelihood of exploitation"
                else:
                    risk_level = "⚪ VERY LOW"
                    risk_desc = "Very low likelihood of exploitation"
    
                # Determine percentile interpretation
                if percentile >= 95:
                    percentile_desc = "Top 5% most likely to be exploited"
                elif percentile >= 90:
                    percentile_desc = "Top 10% most likely to be exploited"
                elif percentile >= 75:
                    percentile_desc = "Top 25% most likely to be exploited"
                elif percentile >= 50:
                    percentile_desc = "Above average exploitation likelihood"
                else:
                    percentile_desc = "Below average exploitation likelihood"
    
                # Format the response
                result = f"šŸ“Š **EPSS Vulnerability Exploit Prediction: {cve_id}**\n\n"
    
                result += f"šŸŽÆ **EPSS Score:** {epss_score:.6f} ({epss_percentage:.4f}%)\n"
                result += f"šŸ“ˆ **Percentile:** {percentile:.2f}% - {percentile_desc}\n"
                result += f"āš ļø **Risk Level:** {risk_level}\n"
                result += f"šŸ“ **Risk Description:** {risk_desc}\n"
                result += f"šŸ“… **Data Date:** {date}\n\n"
    
                result += "šŸ” **Understanding EPSS Scores:**\n"
                result += "   • EPSS predicts the probability of exploitation in the wild within 30 days\n"
                result += "   • Scores range from 0 (0%) to 1 (100%)\n"
                result += (
                    "   • Higher scores indicate higher likelihood of active exploitation\n"
                )
                result += "   • Percentile shows how this CVE ranks against all CVEs\n\n"
    
                result += "šŸ“‹ **Prioritization Guidance:**\n"
                if epss_score >= 0.3:
                    result += "   🚨 **URGENT:** This CVE should be prioritized for immediate patching\n"
                    result += (
                        "   šŸ›”ļø Consider implementing additional monitoring and controls\n"
                    )
                elif epss_score >= 0.1:
                    result += "   ⚔ **HIGH PRIORITY:** Schedule patching within your next maintenance window\n"
                    result += "   šŸ‘€ Monitor for signs of exploitation attempts\n"
                elif epss_score >= 0.01:
                    result += (
                        "   šŸ“‹ **MEDIUM PRIORITY:** Include in regular patching cycle\n"
                    )
                    result += "   šŸ“Š Continue monitoring threat landscape\n"
                else:
                    result += "   šŸ• **LOW PRIORITY:** Can be addressed during routine maintenance\n"
                    result += "   šŸ“ˆ Monitor for changes in threat landscape\n"
    
                result += "\nšŸ“Š **Data Source:** FIRST.org Exploit Prediction Scoring System (EPSS)\n"
                result += "🌐 **EPSS Project:** https://www.first.org/epss/"
    
                return [types.TextContent(type="text", text=result)]
    
        except httpx.TimeoutException:
            return [
                types.TextContent(
                    type="text",
                    text="Error: Request timed out while fetching EPSS score from FIRST.org.",
                )
            ]
        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                return [
                    types.TextContent(
                        type="text",
                        text=f"EPSS score for {cve_id} not found. This CVE might be too new or not in the EPSS database.",
                    )
                ]
            return [
                types.TextContent(
                    type="text",
                    text=f"Error: HTTP {e.response.status_code} error while fetching EPSS data.",
                )
            ]
        except json.JSONDecodeError:
            return [
                types.TextContent(
                    type="text", text="Error: Invalid JSON response from EPSS API."
                )
            ]
        except (ValueError, TypeError) as e:
            return [
                types.TextContent(
                    type="text",
                    text=f"Error: Failed to parse EPSS score data: {str(e)}",
                )
            ]
        except Exception as e:
            return [
                types.TextContent(
                    type="text",
                    text=f"Error: Failed to fetch EPSS information: {str(e)}",
                )
            ]
  • Dispatch logic in the @app.call_tool() handler that routes calls to 'get_epss_score' by validating arguments and invoking the tool function.
    elif name == "get_epss_score":
        if "cve_id" not in arguments:
            return [
                types.TextContent(
                    type="text", text="Error: Missing required argument 'cve_id'"
                )
            ]
        return await get_epss_score(arguments["cve_id"])
  • Tool registration in @app.list_tools() including name, description, and input schema definition for 'get_epss_score'.
    types.Tool(
        name="get_epss_score",
        description="Get EPSS exploitability prediction score for a CVE",
        inputSchema={
            "type": "object",
            "required": ["cve_id"],
            "properties": {
                "cve_id": {
                    "type": "string",
                    "description": epss_description,
                }
            },
        },
    ),
  • Input schema definition specifying required 'cve_id' string parameter with description.
    inputSchema={
        "type": "object",
        "required": ["cve_id"],
        "properties": {
            "cve_id": {
                "type": "string",
                "description": epss_description,
            }
        },
    },
  • Re-export of the get_epss_score function in tools package __init__ for easy imports.
    import json
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It mentions the tool returns 'AI-powered risk prioritization scores with percentile rankings', which adds some behavioral context, but it does not disclose other traits like rate limits, authentication needs, or error handling. The description is minimal and lacks comprehensive behavioral details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that directly states the tool's purpose without unnecessary words. It is front-loaded and every part of the sentence contributes to understanding the tool's function.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has one parameter with full schema coverage and no output schema, the description is adequate but minimal. It explains what the tool does but lacks details on output format, error cases, or integration with sibling tools. For a simple lookup tool, it meets basic needs but could be more complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents the 'cve_id' parameter thoroughly. The description does not add any parameter-specific semantics beyond what the schema provides, such as format details or examples. Baseline score of 3 is appropriate as the schema handles parameter documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Get EPSS exploitability prediction score') and the resource ('for a CVE'), distinguishing it from sibling tools like 'calculate_cvss_score' or 'get_exploit_availability' by focusing on EPSS scores rather than CVSS or exploit availability.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for assessing exploitation probability, but it does not explicitly state when to use this tool versus alternatives like 'calculate_cvss_score' or 'get_exploit_availability'. It provides some context (e.g., 'to assess the probability of exploitation') but lacks clear exclusions or named alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/firetix/vulnerability-intelligence-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server