{"openapi":"3.1.0","info":{"title":"Database MCP Server","description":"Read-only SQL query endpoints for Postgres (Supabase ODI Cricket) and DuckDB (T20 Cricket). ~1M ball-by-ball records each. Exposed as MCP tools for AI client consumption.","version":"1.0.0"},"paths":{"/api/query/postgres":{"post":{"summary":"Run read-only SQL on Postgres (Supabase) — ODI Cricket ball-by-ball data","description":"Execute a read-only SQL query against the Supabase Postgres database. Contains ~1M rows of ODI (One Day International) cricket ball-by-ball data from 2013 onwards. Table: odi_cricket_ball_by_ball. Columns: match_id, season, start_date, venue, innings, ball, batting_team, bowling_team, striker, non_striker, bowler, runs_off_bat, extras, wides, noballs, byes, legbyes, penalty, wicket_type, player_dismissed, other_wicket_type, other_player_dismissed, match_type. Supports JSON (default) and TSV response formats. TSV uses shortened headers and is ~70% smaller (better for AI context windows).\n\nDATA SEMANTICS — Each row is a single delivery (ball) in a match. ODI = 50 overs/innings, usually 2 innings per match.\n\nBALL COUNTING — The ball field (e.g. 0.1, 7.5) is an over.ball identifier, NOT a sequential count. Overs may have >6 deliveries due to wides/no-balls (e.g. 0.7). Use COUNT(*) for total balls bowled.\n\nRUNS — runs_off_bat = runs scored by batsman. extras = additional runs (wides, no-balls, byes, legbyes, penalty). Total runs for a delivery = runs_off_bat + extras. NULL extras components should be treated as 0.\n\nWICKETS — Check both wicket_type AND other_wicket_type for dismissals. If either is non-null, that delivery has a dismissal. Common wicket_type values: bowled, caught, lbw, run out, stumped, caught and bowled, hit wicket, retired hurt.\n\nPLAYER NAMES — Use exact full name if known. If uncertain, use surname with LIKE wildcards (e.g. WHERE striker LIKE '%Kohli%').\n\nSEASON FORMAT — Can be a year (2023) or split-year (2023/24) for southern hemisphere seasons.\n\nMATCH_TYPE — Always 'ODI' in this table.\n\nExample query: SELECT striker, SUM(runs_off_bat) as runs, COUNT(*) as balls FROM odi_cricket_ball_by_ball WHERE season = '2023' GROUP BY striker ORDER BY runs DESC LIMIT 10","operationId":"query_postgres","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/query/duckdb":{"post":{"summary":"Run read-only SQL on DuckDB — T20 Cricket ball-by-ball data","description":"Execute a read-only SQL query against the DuckDB database. Contains ~1M rows of T20 (Twenty20) cricket ball-by-ball data from 2013 onwards. Table: ball_by_ball. Columns: match_id, season, start_date, venue, innings, ball, batting_team, bowling_team, striker, non_striker, bowler, runs_off_bat, extras, wides, noballs, byes, legbyes, penalty, wicket_type, player_dismissed, other_wicket_type, other_player_dismissed, match_type. Supports JSON (default) and TSV response formats. TSV uses shortened headers and is ~70% smaller (better for AI context windows).\n\nDATA SEMANTICS — Each row is a single delivery (ball) in a match. T20 = 20 overs/innings, usually 2 innings per match.\n\nBALL COUNTING — The ball field (e.g. 0.1, 7.5) is an over.ball identifier, NOT a sequential count. Overs may have >6 deliveries due to wides/no-balls (e.g. 0.7). Use COUNT(*) for total balls bowled.\n\nRUNS — runs_off_bat = runs scored by batsman. extras = additional runs (wides, no-balls, byes, legbyes, penalty). Total runs for a delivery = runs_off_bat + extras. NULL extras components should be treated as 0.\n\nWICKETS — Check both wicket_type AND other_wicket_type for dismissals. If either is non-null, that delivery has a dismissal. Common wicket_type values: bowled, caught, lbw, run out, stumped, caught and bowled, hit wicket, retired hurt.\n\nPLAYER NAMES — Use exact full name if known. If uncertain, use surname with LIKE wildcards (e.g. WHERE striker LIKE '%Kohli%').\n\nSEASON FORMAT — Can be a year (2023) or split-year (2023/24) for southern hemisphere seasons.\n\nMATCH_TYPE — Always 'T20' in this table.\n\nExample query: SELECT striker, SUM(runs_off_bat) as runs, COUNT(*) as balls FROM ball_by_ball WHERE season = '2023' GROUP BY striker ORDER BY runs DESC LIMIT 10","operationId":"query_duckdb","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health check","description":"Returns service status, version, and connectivity to both databases.","operationId":"health_check","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"QueryRequest":{"properties":{"sql":{"type":"string","title":"Sql"},"format":{"type":"string","title":"Format","default":"json"}},"type":"object","required":["sql"],"title":"QueryRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}