Skip to main content
Glama
alex-rimerman

Statcast MCP Server

expected_stats_batch

Retrieve expected batting statistics (xBA, xSLG, xwOBA) for multiple players in a single request to analyze lineup or rotation performance.

Instructions

Expected stats (xBA, xSLG, xwOBA vs actual) for multiple batters and/or pitchers in one call.

Use this when the user asks for a lineup, rotation, "team starters", or any named group (e.g. Yankees 1–9 and five starters). Pass comma-separated names. The MCP does not fetch MLB rosters automatically — use current player names from context or a quick web lookup, then list them here.

Args: year: Season year (e.g. 2026). If Savant has not published that season’s leaderboard yet (common before Opening Day or in early April), the table may be empty — use the prior year for full-season expected stats, or lower min_plate_appearances once enough games are played. batters: Comma-separated hitter names, e.g. "Aaron Judge, Juan Soto, Giancarlo Stanton, Anthony Volpe, ..." pitchers: Comma-separated pitcher names, e.g. "Gerrit Cole, Carlos Rodon, Marcus Stroman, Clarke Schmidt, Luis Gil" min_plate_appearances: Minimum PA to qualify (default 50).

Provide at least one of batters or pitchers. Semicolons and newlines also separate names.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
yearYes
battersNo
pitchersNo
min_plate_appearancesNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The implementation of the 'expected_stats_batch' tool, which fetches and filters expected statistics for multiple batters and pitchers.
    def expected_stats_batch(
        year: int,
        batters: str | None = None,
        pitchers: str | None = None,
        min_plate_appearances: int = 50,
    ) -> str:
        """Expected stats (xBA, xSLG, xwOBA vs actual) for *multiple* batters and/or pitchers in one call.
    
        Use this when the user asks for a lineup, rotation, "team starters",
        or any named group (e.g. Yankees 1–9 and five starters). Pass comma-separated
        names. The MCP does not fetch MLB rosters automatically — use current
        player names from context or a quick web lookup, then list them here.
    
        Args:
            year: Season year (e.g. 2026). If Savant has not published that season’s leaderboard
                yet (common before Opening Day or in early April), the table may be empty — use
                the prior year for full-season expected stats, or lower ``min_plate_appearances``
                once enough games are played.
            batters: Comma-separated hitter names, e.g.
                "Aaron Judge, Juan Soto, Giancarlo Stanton, Anthony Volpe, ..."
            pitchers: Comma-separated pitcher names, e.g.
                "Gerrit Cole, Carlos Rodon, Marcus Stroman, Clarke Schmidt, Luis Gil"
            min_plate_appearances: Minimum PA to qualify (default 50).
    
        Provide at least one of batters or pitchers. Semicolons and newlines also separate names.
        """
        from pybaseball import (
            statcast_batter_expected_stats as _bat_exp,
            statcast_pitcher_expected_stats as _pit_exp,
        )
    
        batter_names = _parse_player_name_list(batters or "")
        pitcher_names = _parse_player_name_list(pitchers or "")
        if not batter_names and not pitcher_names:
            return (
                "Provide at least one of batters or pitchers as a comma-separated list "
                '(e.g. batters="Aaron Judge, Juan Soto").'
            )
    
        sections: list[str] = []
    
        def _empty_leaderboard_note(role: str, min_pa: int) -> str:
            return (
                f"**No Statcast data for {year} yet** — the `{role}` expected-stats leaderboard "
                f"returned zero rows at `min_plate_appearances={min_pa}`. "
                "That usually means the regular season has not produced enough qualified players "
                "in Savant’s feed yet, or the year’s table is not published. "
                f"Use **`year={year - 1}`** for a complete prior-season leaderboard, or retry "
                "after more games with a **lower** `min_plate_appearances`.\n\n"
            )
    
        if batter_names:
            try:
                df = _bat_exp(year, minPA=min_plate_appearances)
            except Exception as e:
                return f"Error fetching batter expected stats: {e}"
            if df is None or getattr(df, "empty", True):
                sections.append(f"### Batters (expected stats, {year})\n\n")
                sections.append(_empty_leaderboard_note("batter", min_plate_appearances))
            else:
                merged, errs = _batch_filter_players(df, batter_names)
                sections.append(f"### Batters (expected stats, {year})\n\n")
                if merged.empty:
                    sections.append("No batter rows matched.\n\n")
                else:
                    sections.append(_fmt(merged, max_rows=len(merged)))
                if errs:
                    sections.append("\n**Notes:** " + " | ".join(errs))
    
        if pitcher_names:
            try:
                df = _pit_exp(year, minPA=min_plate_appearances)
            except Exception as e:
                return f"Error fetching pitcher expected stats: {e}"
            if df is None or getattr(df, "empty", True):
                sections.append(f"\n\n### Pitchers (expected stats allowed, {year})\n\n")
                sections.append(_empty_leaderboard_note("pitcher", min_plate_appearances))
            else:
                merged, errs = _batch_filter_players(df, pitcher_names)
                sections.append(f"\n\n### Pitchers (expected stats allowed, {year})\n\n")
                if merged.empty:
                    sections.append("No pitcher rows matched.\n\n")
                else:
                    sections.append(_fmt(merged, max_rows=len(merged)))
                if errs:
                    sections.append("\n**Notes:** " + " | ".join(errs))
    
        return "".join(sections).strip()
  • The registration of the 'expected_stats_batch' tool using the @mcp.tool() decorator.
    @mcp.tool()
    def expected_stats_batch(
Behavior4/5

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

With no annotations provided, the description carries the full burden and succeeds in disclosing data availability constraints ('If Savant has not published that season's leaderboard yet... the table may be empty'), parsing behavior ('Semicolons and newlines also separate names'), and validation rules ('Provide at least one of batters or pitchers'). Lacks explicit mention of read-only nature or rate limits.

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?

Well-structured and front-loaded with the core purpose first, followed by usage scenarios, then detailed parameter documentation. Despite length necessitated by zero schema coverage, every sentence adds value including the roster lookup limitation and early-season data caveats.

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

Completeness5/5

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

Complete for a batch statistics tool: explains the return value conceptually (expected vs actual stats), covers all input parameters with examples, addresses edge cases (pre-Opening Day data), and acknowledges the existence of an output schema makes detailed return value documentation unnecessary.

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?

Given 0% schema description coverage, the description comprehensively compensates by documenting all 4 parameters: year (with season example and Savant publishing warning), batters/pitchers (with comma-separated format and extensive real-world examples), and min_plate_appearances (with meaning and default value).

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 opens with a specific purpose statement including the exact metrics returned (xBA, xSLG, xwOBA vs actual) and clearly distinguishes this as a batch operation for 'multiple' players, differentiating it from likely single-player sibling tools like statcast_batter_expected_stats.

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

Usage Guidelines5/5

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

Provides explicit when-to-use guidance ('when the user asks for a lineup, rotation, team starters') with concrete examples ('Yankees 1–9'). Also documents a critical limitation ('The MCP does not fetch MLB rosters automatically') with a workaround strategy, and specifies input format requirements ('comma-separated names').

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/alex-rimerman/statcast-mcp'

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