Skip to main content
Glama
zachegner

EPA Envirofacts MCP Server

by zachegner

get_facility_compliance_history_tool

Retrieve compliance status, violations, and enforcement history for EPA-regulated facilities using FRS or program-specific IDs. Supports RCRA and TRI programs with configurable historical data.

Instructions

Get compliance and enforcement history for an EPA-regulated facility.

Retrieves compliance status, violations, and enforcement history for EPA-regulated facilities across RCRA and TRI programs. Supports both FRS registry IDs and program-specific IDs with intelligent fallback logic.

Args: registry_id: FRS Registry ID or program-specific ID (RCRA Handler ID, TRI Facility ID) program: Optional program filter ('TRI' or 'RCRA') years: Historical years to include (default: 5)

Returns: Complete compliance history with facility information, compliance records, violations, and summary statistics

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
registry_idYes
programNo
yearsNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
last_updatedNoDate of last compliance update
facility_infoYesFacility information
overall_statusYesOverall facility compliance status
years_analyzedYesNumber of years analyzed
total_penaltiesNoTotal penalties across all programs
total_violationsNoTotal violations across all programs
compliance_recordsNoCompliance records by program

Implementation Reference

  • The FastMCP tool handler function 'get_facility_compliance_history_tool' decorated with @mcp.tool(), which delegates to the core helper function.
    async def get_facility_compliance_history_tool(
        registry_id: str,
        program: Optional[str] = None,
        years: int = 5
    ) -> FacilityComplianceHistory:
        """Get compliance and enforcement history for an EPA-regulated facility.
        
        Retrieves compliance status, violations, and enforcement history for EPA-regulated
        facilities across RCRA and TRI programs. Supports both FRS registry IDs and 
        program-specific IDs with intelligent fallback logic.
        
        Args:
            registry_id: FRS Registry ID or program-specific ID (RCRA Handler ID, TRI Facility ID)
            program: Optional program filter ('TRI' or 'RCRA')
            years: Historical years to include (default: 5)
            
        Returns:
            Complete compliance history with facility information, compliance records,
            violations, and summary statistics
        """
        return await get_facility_compliance_history(registry_id, program, years)
  • Pydantic BaseModel defining the output structure FacilityComplianceHistory, including compliance records, status, violations summary, used by the tool.
    class FacilityComplianceHistory(BaseModel):
        """Complete compliance history for a facility."""
        
        facility_info: FacilityInfo = Field(..., description="Facility information")
        compliance_records: List[ComplianceRecord] = Field(default_factory=list, description="Compliance records by program")
        overall_status: ComplianceStatus = Field(..., description="Overall facility compliance status")
        total_violations: int = Field(0, description="Total violations across all programs")
        total_penalties: Optional[float] = Field(None, description="Total penalties across all programs")
        years_analyzed: int = Field(..., description="Number of years analyzed")
        last_updated: Optional[date] = Field(None, description="Date of last compliance update")
        
        def __str__(self) -> str:
            return f"Compliance History for {self.facility_info.name} ({self.facility_info.registry_id})"
  • server.py:95-98 (registration)
    Registration calls for all tools, including register_compliance_tool(mcp) which registers the get_facility_compliance_history_tool.
    register_tool(mcp)
    register_search_tool(mcp)
    register_compliance_tool(mcp)
    register_chemical_tool(mcp)
  • The register_tool function that defines the @mcp.tool() handler and registers it with the FastMCP instance.
    def register_tool(mcp: FastMCP):
        """Register the compliance history tool with FastMCP.
        
        Args:
            mcp: FastMCP instance
        """
        @mcp.tool()
        async def get_facility_compliance_history_tool(
            registry_id: str,
            program: Optional[str] = None,
            years: int = 5
        ) -> FacilityComplianceHistory:
            """Get compliance and enforcement history for an EPA-regulated facility.
            
            Retrieves compliance status, violations, and enforcement history for EPA-regulated
            facilities across RCRA and TRI programs. Supports both FRS registry IDs and 
            program-specific IDs with intelligent fallback logic.
            
            Args:
                registry_id: FRS Registry ID or program-specific ID (RCRA Handler ID, TRI Facility ID)
                program: Optional program filter ('TRI' or 'RCRA')
                years: Historical years to include (default: 5)
                
            Returns:
                Complete compliance history with facility information, compliance records,
                violations, and summary statistics
            """
            return await get_facility_compliance_history(registry_id, program, years)
  • Core helper function implementing the business logic: validates inputs, fetches facility info and compliance records from clients, computes overall status and totals, constructs and returns FacilityComplianceHistory.
    async def get_facility_compliance_history(
        registry_id: str,
        program: Optional[str] = None,
        years: int = 5
    ) -> FacilityComplianceHistory:
        """Get compliance and enforcement history for an EPA-regulated facility.
        
        This tool retrieves compliance status, violations, and enforcement history
        for EPA-regulated facilities across RCRA and TRI programs. It supports both
        FRS registry IDs and program-specific IDs with intelligent fallback logic.
        
        Args:
            registry_id: FRS Registry ID or program-specific ID (RCRA Handler ID, TRI Facility ID)
            program: Optional program filter ('TRI' or 'RCRA')
            years: Historical years to include (default: 5, max: 20)
        
        Returns:
            FacilityComplianceHistory containing:
            - Facility information
            - Compliance records by program
            - Violations with dates and status
            - Overall compliance status
            - Summary statistics
        
        Raises:
            ValueError: If parameters are invalid
            Exception: If EPA API queries fail
        
        Example:
            >>> # By FRS registry ID
            >>> compliance = await get_facility_compliance_history("110000012345")
            >>> 
            >>> # By program-specific ID with filter
            >>> compliance = await get_facility_compliance_history("VAD000012345", program="RCRA")
            >>> 
            >>> # With custom timeframe
            >>> compliance = await get_facility_compliance_history("110000012345", years=10)
        """
        # Validate input parameters
        if not registry_id or not registry_id.strip():
            raise ValueError("Registry ID cannot be empty")
        
        if not (1 <= years <= 20):
            raise ValueError("Years must be between 1 and 20")
        
        if program and program.upper() not in ['TRI', 'RCRA']:
            raise ValueError("Program must be 'TRI' or 'RCRA'")
        
        registry_id = registry_id.strip()
        if program:
            program = program.strip().upper()
        
        try:
            logger.info(f"Getting compliance history for facility {registry_id} "
                       f"(program: {program or 'all'}, years: {years})")
            
            # Step 1: Get facility information from FRS
            facility_info = None
            try:
                async with FRSClient() as frs_client:
                    facility_info = await frs_client.get_facility_by_id(registry_id)
            except Exception as e:
                logger.warning(f"Failed to get facility info from FRS for {registry_id}: {e}")
            
            # If FRS lookup failed, create a basic facility info
            if not facility_info:
                facility_info = FacilityInfo(
                    registry_id=registry_id,
                    name=f"Facility {registry_id}",
                    programs=[FacilityType.FRS]
                )
            
            # Step 2: Get compliance records
            compliance_records = []
            try:
                async with ComplianceClient() as compliance_client:
                    compliance_records = await compliance_client.get_compliance_by_registry_id(
                        registry_id=registry_id,
                        program=program,
                        years=years
                    )
            except Exception as e:
                logger.error(f"Failed to get compliance records for {registry_id}: {e}")
                raise Exception(f"Failed to retrieve compliance data: {e}")
            
            # Step 3: Calculate overall status and statistics
            overall_status = ComplianceStatus.UNKNOWN
            total_violations = 0
            total_penalties = None
            
            if compliance_records:
                # Determine overall status based on individual program statuses
                statuses = [record.status for record in compliance_records]
                if ComplianceStatus.VIOLATION in statuses:
                    overall_status = ComplianceStatus.VIOLATION
                elif ComplianceStatus.COMPLIANT in statuses:
                    overall_status = ComplianceStatus.COMPLIANT
                
                # Calculate totals
                total_violations = sum(record.violation_count for record in compliance_records)
                
                # Sum penalties (if available)
                penalties = [record.total_penalties for record in compliance_records 
                            if record.total_penalties is not None]
                if penalties:
                    total_penalties = sum(penalties)
            
            # Step 4: Build compliance history
            compliance_history = FacilityComplianceHistory(
                facility_info=facility_info,
                compliance_records=compliance_records,
                overall_status=overall_status,
                total_violations=total_violations,
                total_penalties=total_penalties,
                years_analyzed=years,
                last_updated=date.today()
            )
            
            logger.info(f"Compliance history complete for {registry_id}: "
                       f"{overall_status}, {total_violations} violations, "
                       f"{len(compliance_records)} programs")
            
            return compliance_history
            
        except ValueError:
            # Re-raise validation errors
            raise
        except Exception as e:
            logger.error(f"Failed to get compliance history for {registry_id}: {e}")
            raise Exception(f"Failed to retrieve compliance history: {e}")
Behavior3/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It adds useful context such as 'intelligent fallback logic' for IDs and default behavior for years, but it does not cover critical aspects like rate limits, authentication needs, or error handling, leaving gaps for a tool with no annotation coverage.

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

Conciseness4/5

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

The description is appropriately sized and front-loaded, starting with the core purpose followed by details on retrieval scope, parameters, and returns. Every sentence adds value, but the Args and Returns sections could be integrated more seamlessly into the flow for slightly better structure.

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

Completeness4/5

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

Given the tool's complexity (3 parameters, no annotations, but with output schema), the description is fairly complete. It covers purpose, parameter semantics, and return values, and the output schema reduces the need to detail return formats. However, it lacks behavioral details like rate limits or error cases, which would enhance completeness.

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

Parameters4/5

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

Schema description coverage is 0%, so the description must compensate. It adds meaningful semantics beyond the schema by explaining registry_id accepts FRS or program-specific IDs, program filters as 'TRI' or 'RCRA', and years defaults to 5 with historical context. This compensates well for the lack of schema descriptions, though not exhaustively.

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 tool's purpose with specific verbs ('Get', 'Retrieves') and resources ('compliance and enforcement history for an EPA-regulated facility'), distinguishing it from siblings like environmental_summary_by_location or search_facilities_tool by focusing on historical compliance data rather than summaries or searches.

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 by specifying it retrieves history for EPA-regulated facilities across RCRA and TRI programs, but it does not explicitly state when to use this tool versus alternatives like get_chemical_release_data or provide exclusions. The context is clear but lacks explicit guidance on tool selection.

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/zachegner/envirofacts-mcp'

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