Skip to main content
Glama
zachegner

EPA Envirofacts MCP Server

by zachegner

get_chemical_release_data

Query Toxics Release Inventory (TRI) data to find chemical release information by chemical name, CAS number, location, or year for environmental analysis.

Instructions

Query TRI chemical releases with flexible search parameters.

Provides comprehensive chemical release data from the Toxics Release Inventory (TRI), allowing searches by chemical name, CAS number, state, county, and year.

Args: chemical_name: Chemical name (partial match) cas_number: CAS Registry Number (exact match) state: Two-letter state code county: County name (filtered client-side) year: Reporting year (None for most recent available) limit: Maximum results to return (default: 100)

Returns: Comprehensive chemical release data with aggregations

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
chemical_nameNo
cas_numberNo
stateNo
countyNo
yearNo
limitNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
chemicalsNoChemicals grouped by chemical
facilitiesNoFacilities grouped by facility
air_releasesNoTotal air releases (pounds)
data_sourcesNoData sources used
land_releasesNoTotal land releases (pounds)
search_paramsYesSearch parameters used
top_chemicalsNoTop chemicals by total releases
reporting_yearNoYear of data (if single year)
top_facilitiesNoTop facilities by total releases
total_releasesYesTotal releases across all media (pounds)
water_releasesNoTotal water releases (pounds)
query_timestampYesTimestamp of query
total_chemicalsYesTotal unique chemicals released
total_facilitiesYesTotal facilities with releases
underground_injectionsNoTotal underground injections (pounds)

Implementation Reference

  • The registered MCP tool handler function `get_chemical_release_data` that defines the tool interface and delegates to the core implementation.
    async def get_chemical_release_data(
        chemical_name: Optional[str] = None,
        cas_number: Optional[str] = None,
        state: Optional[str] = None,
        county: Optional[str] = None,
        year: Optional[int] = None,
        limit: int = 100
    ) -> ChemicalReleaseData:
            """Query TRI chemical releases with flexible search parameters.
            
            Provides comprehensive chemical release data from the Toxics Release Inventory (TRI),
            allowing searches by chemical name, CAS number, state, county, and year.
            
            Args:
                chemical_name: Chemical name (partial match)
                cas_number: CAS Registry Number (exact match)
                state: Two-letter state code
                county: County name (filtered client-side)
                year: Reporting year (None for most recent available)
                limit: Maximum results to return (default: 100)
                
            Returns:
                Comprehensive chemical release data with aggregations
            """
            # Call the actual implementation function
            return await _get_chemical_release_data_impl(
                chemical_name=chemical_name,
                cas_number=cas_number,
                state=state,
                county=county,
                year=year,
                limit=limit
            )
  • Core helper function implementing the tool logic: input validation, querying TRIClient, aggregating data by facility and chemical, calculating summaries and top lists.
    async def _get_chemical_release_data_impl(
        chemical_name: Optional[str] = None,
        cas_number: Optional[str] = None,
        state: Optional[str] = None,
        county: Optional[str] = None,
        year: Optional[int] = None,
        limit: int = 100,
    ) -> ChemicalReleaseData:
        """Query TRI chemical releases with flexible search parameters.
        
        This tool provides comprehensive chemical release data from the Toxics Release Inventory (TRI),
        allowing searches by chemical name, CAS number, state, county, and year. Results include
        both facility-centric and chemical-centric aggregations with optional year-over-year trends.
        
        Args:
            chemical_name: Chemical name (partial match, e.g., 'benzene')
            cas_number: CAS Registry Number (exact match, e.g., '71-43-2')
            state: Two-letter state code (e.g., 'NY', 'CA')
            county: County name (filtered client-side)
            year: Reporting year (None for most recent available)
            limit: Maximum results to return (default: 100, max: 1000)
        
        Returns:
            ChemicalReleaseData containing:
            - Search parameters used
            - Summary statistics (total facilities, chemicals, releases)
            - Releases by medium (air, water, land, underground injection)
            - Facilities grouped by facility with all their chemical releases
            - Chemicals grouped by chemical with all facilities releasing them
            - Top facilities and chemicals by total releases
        
        Raises:
            ValueError: If no search parameters provided or parameters invalid
            Exception: If EPA API queries fail
        
        Example:
            >>> # Search by chemical name
            >>> data = await get_chemical_release_data(chemical_name="benzene", state="CA")
            >>> print(f"Found {data.total_facilities} facilities releasing benzene")
            
            >>> # Search by CAS number
            >>> data = await get_chemical_release_data(cas_number="71-43-2", year=2022)
            >>> print(f"Total releases: {data.total_releases} pounds")
            
        """
        # Validate input parameters
        if not any([chemical_name, cas_number, state, county]):
            raise ValueError("At least one search parameter must be provided (chemical_name, cas_number, state, or county)")
        
        if limit <= 0 or limit > 1000:
            raise ValueError("Limit must be between 1 and 1000")
        
        if state and len(state) != 2:
            raise ValueError("State must be a two-letter code (e.g., 'NY', 'CA')")
        
        # Clean and validate parameters
        search_params = {}
        if chemical_name:
            chemical_name = chemical_name.strip()
            search_params['chemical_name'] = chemical_name
        if cas_number:
            cas_number = cas_number.strip()
            search_params['cas_number'] = cas_number
        if state:
            state = state.strip().upper()
            search_params['state'] = state
        if county:
            county = county.strip()
            search_params['county'] = county
        if year:
            search_params['year'] = year
        
        try:
            logger.info(f"Getting chemical release data with params: {search_params}")
            
            # Initialize TRI client
            async with TRIClient() as tri_client:
                # Get chemical releases
                releases = await tri_client.get_chemical_releases(
                    chemical_name=chemical_name,
                    cas_number=cas_number,
                    state=state,
                    county=county,
                    year=year,
                    limit=limit
                )
            
            if not releases:
                logger.info("No chemical releases found")
                return ChemicalReleaseData(
                    search_params=search_params,
                    total_facilities=0,
                    total_chemicals=0,
                    total_releases=0.0,
                    query_timestamp=datetime.utcnow().isoformat() + "Z"
                )
            
            # Apply county filter client-side if specified
            if county:
                # Note: This would require additional facility data for county matching
                # For now, we'll include all releases
                logger.info(f"County filtering not yet implemented - returning all releases")
            
            # Aggregate data by facility
            facilities_dict = defaultdict(list)
            for release in releases:
                facilities_dict[release.facility_id].append(release)
            
            facilities = []
            for facility_id, facility_releases in facilities_dict.items():
                facility_info = FacilityReleaseInfo(
                    facility_id=facility_id,
                    facility_name=facility_releases[0].facility_name,
                    chemical_releases=facility_releases
                )
                facilities.append(facility_info)
            
            # Aggregate data by chemical
            chemicals_dict = defaultdict(list)
            for release in releases:
                chemical_key = f"{release.chemical_name}_{release.cas_number or 'unknown'}"
                chemicals_dict[chemical_key].append(release)
            
            chemicals = []
            for chemical_key, chemical_releases in chemicals_dict.items():
                chemical_agg = ChemicalAggregation(
                    chemical_name=chemical_releases[0].chemical_name,
                    cas_number=chemical_releases[0].cas_number,
                    facilities_releasing=chemical_releases
                )
                chemicals.append(chemical_agg)
            
            # Calculate totals by medium
            air_releases = sum(r.air_release or 0.0 for r in releases)
            water_releases = sum(r.water_release or 0.0 for r in releases)
            land_releases = sum(r.land_release or 0.0 for r in releases)
            underground_injections = sum(r.underground_injection or 0.0 for r in releases)
            total_releases = sum(r.total_release for r in releases)
            
            # Sort facilities and chemicals by total releases
            facilities.sort(key=lambda f: f.total_releases, reverse=True)
            chemicals.sort(key=lambda c: c.total_releases, reverse=True)
            
            # Get top facilities and chemicals (top 20 each)
            top_facilities = facilities[:20]
            top_chemicals = chemicals[:20]
            
            
            # Determine reporting year
            reporting_year = None
            if year:
                reporting_year = year
            elif releases:
                # Use the most common year in the results
                years = [r.reporting_year for r in releases]
                reporting_year = max(set(years), key=years.count)
            
            # Build response
            result = ChemicalReleaseData(
                search_params=search_params,
                total_facilities=len(facilities),
                total_chemicals=len(chemicals),
                total_releases=total_releases,
                air_releases=air_releases,
                water_releases=water_releases,
                land_releases=land_releases,
                underground_injections=underground_injections,
                facilities=facilities,
                chemicals=chemicals,
                top_facilities=top_facilities,
                top_chemicals=top_chemicals,
                reporting_year=reporting_year,
                query_timestamp=datetime.utcnow().isoformat() + "Z"
            )
            
            logger.info(f"Chemical release data complete: {result.total_facilities} facilities, "
                       f"{result.total_chemicals} chemicals, {result.total_releases:.1f} pounds total")
            
            return result
            
        except ValueError:
            # Re-raise validation errors
            raise
        except Exception as e:
            logger.error(f"Failed to get chemical release data: {e}")
            raise Exception(f"Failed to retrieve chemical release data: {e}")
  • Pydantic BaseModel defining the output schema `ChemicalReleaseData` with fields for search params, statistics, medium breakdowns, facility/chemical aggregations, top lists, and metadata.
    class ChemicalReleaseData(BaseModel):
        """Comprehensive chemical release data response."""
        
        # Search parameters
        search_params: Dict[str, Any] = Field(..., description="Search parameters used")
        
        # Summary statistics
        total_facilities: int = Field(..., ge=0, description="Total facilities with releases")
        total_chemicals: int = Field(..., ge=0, description="Total unique chemicals released")
        total_releases: float = Field(..., ge=0, description="Total releases across all media (pounds)")
        
        # Releases by medium
        air_releases: float = Field(default=0.0, ge=0, description="Total air releases (pounds)")
        water_releases: float = Field(default=0.0, ge=0, description="Total water releases (pounds)")
        land_releases: float = Field(default=0.0, ge=0, description="Total land releases (pounds)")
        underground_injections: float = Field(default=0.0, ge=0, description="Total underground injections (pounds)")
        
        # Aggregated data
        facilities: List[FacilityReleaseInfo] = Field(default_factory=list, description="Facilities grouped by facility")
        chemicals: List[ChemicalAggregation] = Field(default_factory=list, description="Chemicals grouped by chemical")
        
        # Top facilities and chemicals
        top_facilities: List[FacilityReleaseInfo] = Field(default_factory=list, description="Top facilities by total releases")
        top_chemicals: List[ChemicalAggregation] = Field(default_factory=list, description="Top chemicals by total releases")
        
        # Metadata
        reporting_year: Optional[int] = Field(None, description="Year of data (if single year)")
        query_timestamp: str = Field(..., description="Timestamp of query")
        data_sources: List[str] = Field(default=["TRI"], description="Data sources used")
  • Local registration function that applies the `@mcp.tool()` decorator to the handler function.
    def register_tool(mcp: FastMCP):
        """Register the chemical release data tool with FastMCP.
        
        Args:
            mcp: FastMCP instance
        """
        mcp.tool()(get_chemical_release_data)
  • server.py:95-100 (registration)
    Server main() function calls the module's register_tool to register the tool with the FastMCP instance and logs the registered tools list including 'get_chemical_release_data'.
    register_tool(mcp)
    register_search_tool(mcp)
    register_compliance_tool(mcp)
    register_chemical_tool(mcp)
    
    logger.info("Registered tools", tools=["environmental_summary_by_location", "search_facilities", "get_facility_compliance_history", "get_chemical_release_data", "health_check"])
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions the tool 'allows searches' and provides parameter details, but doesn't disclose important behavioral traits like whether this is a read-only operation, potential rate limits, authentication requirements, data freshness, or what happens when no parameters are provided. The mention of 'county: County name (filtered client-side)' is useful context but insufficient for comprehensive transparency.

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 well-structured with a clear purpose statement followed by detailed parameter explanations. Every sentence adds value, though the 'Returns' section is somewhat redundant given the existence of an output schema. The text is appropriately sized for a 6-parameter tool with no schema descriptions.

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 complexity (6 parameters, 0% schema coverage) and the existence of an output schema, the description is reasonably complete. It thoroughly documents all parameters and their semantics. The main gaps are the lack of behavioral context (no annotations) and no guidance on when to use versus siblings, but the parameter documentation is comprehensive.

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

Parameters5/5

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

With 0% schema description coverage and 6 parameters, the description provides excellent parameter semantics. It clearly explains each parameter's purpose, match behavior (partial vs exact), filtering approach, and default values. The description adds substantial meaning beyond the bare schema, fully compensating for the lack of schema descriptions.

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 'Query TRI chemical releases with flexible search parameters' and specifies it provides 'comprehensive chemical release data from the Toxics Release Inventory (TRI)'. It uses specific verbs ('Query', 'search') and identifies the exact resource (TRI chemical releases), distinguishing it from sibling tools that focus on facilities, compliance, or summaries.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus its siblings. While it mentions 'flexible search parameters', it doesn't explain when this tool is preferred over alternatives like 'search_facilities_tool' or 'environmental_summary_by_location'. There's no mention of use cases, prerequisites, or exclusions.

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