Examples

The last green end-to-end run (2026-09-08T01:26:41+00:00) — 52 cases, verdict GREEN.

{
  "ran_at": "2026-09-08T01:26:41+00:00",
  "started": "2026-09-08T01:26:02+00:00",
  "verdict": "GREEN",
  "surface": "claude+chatgpt+full",
  "surfaces": [
    "claude",
    "chatgpt",
    "full"
  ],
  "hosts": {
    "mcp": "mcp.sportscall.ai [public]",
    "auth": "auth.sportscall.ai [local \u00b7 no DNS (gaierror)]",
    "developers": "developers.sportscall.ai [local \u00b7 no DNS (gaierror)]"
  },
  "fixture": {
    "line": "a0000000-0000-4000-8000-0000000000a1",
    "did": "+15550001001",
    "contact": "+15550001002",
    "players": [
      "Bijan Robinson",
      "James Cook"
    ],
    "demo_code": "<from /opt/aria/.env SPORTSCALL_OAUTH_DEMO_CODE>"
  },
  "counts": {
    "cases": 52,
    "pos": 34,
    "neg": 17,
    "pass": 52,
    "fail": 0
  },
  "known_gaps": [
    "E_DEMO_NOOP has NO producer in the server (only the error-table entry exists); the plan expects the fixture to answer E_DEMO_NOOP on call tools"
  ],
  "cleanup": {
    "full_bearers_deleted": 1,
    "oauth_tokens_revoked": 0,
    "watches_cancelled": 0,
    "idempotency_rows_deleted": 2,
    "callback_rows_left": "1 (acgw has no DELETE grant on sportscall_callback_requests; refused-call audit rows on the synthetic line)",
    "queue_rows_deleted": 0,
    "fixture_restored": true
  },
  "render_chars": 60111
}
✅ fixture dressed: the five-player QA roster resolves on the fixture (saved config restored at close) [pos · -]
{
  "name": "fixture dressed: the five-player QA roster resolves on the fixture (saved config restored at close)",
  "kind": "pos",
  "surface": "-",
  "request": {
    "players": [
      "Josh Allen",
      "Bijan Robinson",
      "\u2026 +3 more"
    ]
  },
  "response": {
    "resolved": [
      "Josh Allen",
      "Bijan Robinson",
      "\u2026 +3 more"
    ]
  },
  "pass": true
}
✅ no bearer \u2192 401 + WWW-Authenticate resource_metadata (public: mcp.sportscall.ai [public]) [neg · -]
{
  "name": "no bearer \u2192 401 + WWW-Authenticate resource_metadata (public: mcp.sportscall.ai [public])",
  "kind": "neg",
  "surface": "-",
  "request": {
    "rpc": "tools/list",
    "auth": "none",
    "url": "https://mcp.sportscall.ai"
  },
  "response": {
    "status": 401,
    "ms": 22,
    "www-authenticate": "Bearer <redacted>\"https://mcp.sportscall.ai/.well-known/oauth-protected-resource\"",
    "error": {
      "code": -32001,
      "message": "bearer token missing, revoked, or unknown"
    }
  },
  "pass": true
}
✅ no bearer \u2192 401 + WWW-Authenticate resource_metadata (loopback: mcp.sportscall.ai [local \u00b7 forced loopback]) [neg · -]
{
  "name": "no bearer \u2192 401 + WWW-Authenticate resource_metadata (loopback: mcp.sportscall.ai [local \u00b7 forced loopback])",
  "kind": "neg",
  "surface": "-",
  "request": {
    "rpc": "tools/list",
    "auth": "none",
    "url": "http://127.0.0.1:9001/v1/mcp/sportscall"
  },
  "response": {
    "status": 401,
    "ms": 3,
    "www-authenticate": "Bearer <redacted>\"https://mcp.sportscall.ai/.well-known/oauth-protected-resource\"",
    "error": {
      "code": -32001,
      "message": "bearer token missing, revoked, or unknown"
    }
  },
  "pass": true
}
✅ initialize without a bearer (loopback) \u2192 200, serverInfo.version 1.0 (initialize is unauthenticated by design) [pos · -]
{
  "name": "initialize without a bearer (loopback) \u2192 200, serverInfo.version 1.0 (initialize is unauthenticated by design)",
  "kind": "pos",
  "surface": "-",
  "request": {
    "rpc": "initialize",
    "auth": "none",
    "params": {
      "protocolVersion": "2025-06-18",
      "clientInfo": {
        "name": "sportscall-e2e",
        "version": "1.0"
      }
    }
  },
  "response": {
    "status": 200,
    "ms": 2,
    "result": {
      "protocolVersion": "2025-06-18",
      "capabilities": {
        "tools": {
          "listChanged": false
        },
        "resources": {
          "listChanged": false
        }
      },
      "serverInfo": {
        "name": "sportscall",
        "version": "1.0",
        "title": "SportsCall \u2014 Fantasy Co-Manager"
      },
      "instructions": "One bearer = one SportsCall phone line. Start with get_my_fantasy_context when a question depends on the user's team. Decision tools return \u2026 (+14 chars)"
    }
  },
  "pass": true
}
✅ AS metadata \u2192 issuer auth.sportscall.ai \u00b7 S256 only \u00b7 registration + revocation endpoints [pos · -]
{
  "name": "AS metadata \u2192 issuer auth.sportscall.ai \u00b7 S256 only \u00b7 registration + revocation endpoints",
  "kind": "pos",
  "surface": "-",
  "request": {
    "method": "GET",
    "url": "http://127.0.0.1:9001/v1/oauth/sportscall/.well-known/oauth-authorization-server"
  },
  "response": {
    "status": 200,
    "ms": 2,
    "body": {
      "issuer": "https://auth.sportscall.ai",
      "authorization_endpoint": "https://auth.sportscall.ai/authorize",
      "token_endpoint": "https://auth.sportscall.ai/token",
      "registration_endpoint": "https://auth.sportscall.ai/register",
      "revocation_endpoint": "https://auth.sportscall.ai/revoke",
      "code_challenge_methods_supported": [
        "S256"
      ],
      "scopes_supported": [
        "league.read",
        "roster.read",
        "\u2026 +5 more"
      ]
    }
  },
  "pass": true
}
✅ connect \u00b7 DCR client reused (claude) [pos · claude]
{
  "name": "connect \u00b7 DCR client reused (claude)",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "client_id": "scoa_xVyCiQaSrRQ1Q8lxy-",
    "redirect_uri": "https://claude.ai/api/mcp/auth_callback"
  },
  "response": {
    "status": 200,
    "note": "authorize page rendered for the persisted client"
  },
  "pass": true
}
✅ connect \u00b7 DCR client reused (chatgpt) [pos · chatgpt]
{
  "name": "connect \u00b7 DCR client reused (chatgpt)",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "client_id": "scoa_g9KJbpWYF11O77ur4m",
    "redirect_uri": "https://chatgpt.com/connector_platform_oauth_redirect"
  },
  "response": {
    "status": 200,
    "note": "authorize page rendered for the persisted client"
  },
  "pass": true
}
✅ chatgpt client asks roster.write \u2192 302 error=invalid_scope (roster moves never on a directory client \u2014 Ian's ruling 3) [neg · chatgpt]
{
  "name": "chatgpt client asks roster.write \u2192 302 error=invalid_scope (roster moves never on a directory client \u2014 Ian's ruling 3)",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "method": "GET",
    "url": "http://127.0.0.1:9001/v1/oauth/sportscall/authorize",
    "headers": {
      "Host": "auth.sportscall.ai"
    },
    "params": {
      "client_id": "scoa_g9KJbpWYF11O77ur4m",
      "redirect_uri": "https://chatgpt.com/connector_platform_oauth_redirect",
      "response_type": "code",
      "state": "e2e-rw",
      "code_challenge": "IFNqKzmjo62HPwwKp7bZ5ipDrRElOHsmeoz6j7DfYXY",
      "code_challenge_method": "S256",
      "scope": "league.read roster.read analysis.read alerts.write messages.write calls.write roster.write"
    }
  },
  "response": {
    "status": 302,
    "ms": 3,
    "headers": {
      "cache-control": "no-store",
      "location": "https://chatgpt.com/connector_platform_oauth_redirect?error=invalid_scope&error_description=a+requested+scope+is+unknown+or+not+available+to\u2026 (+28 chars)"
    },
    "body": ""
  },
  "pass": true
}
✅ connect \u00b7 PKCE S256 walk on the demo fixture (claude \u00b7 all five directory scopes) [pos · claude]
{
  "name": "connect \u00b7 PKCE S256 walk on the demo fixture (claude \u00b7 all five directory scopes)",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "client_id": "scoa_xVyCiQaSrRQ1Q8lxy-",
    "redirect_uri": "https://claude.ai/api/mcp/auth_callback",
    "phone": "+15550001001",
    "code": "<SPORTSCALL_OAUTH_DEMO_CODE>",
    "pkce": "S256"
  },
  "response": {
    "steps": [
      {
        "step": "GET /authorize \u2192 200 page + rid + sc_authz cookie",
        "ok": true,
        "status": 200,
        "detail": {
          "rid": true,
          "cookie": "<redacted>"
        }
      },
      {
        "step": "phone step (fixture DID; the text is 555-faked)",
        "ok": true,
        "status": 200,
        "detail": "We texted a code to +1\u2022\u2022\u2022\u2022\u2022\u20221002 from your SportsCall line."
      },
      {
        "step": "code step (SPORTSCALL_OAUTH_DEMO_CODE, fixture only) \u2192 consent lists the requested scopes",
        "ok": true,
        "status": 200,
        "detail": {
          "consent_scopes": [
            "league.read",
            "roster.read",
            "analysis.read",
            "alerts.write",
            "messages.write"
          ]
        }
      },
      {
        "step": "consent allow (ticked ['league.read', 'roster.read', 'analysis.read', 'alerts.write', 'messages.write']) \u2192 302 redirect_uri?code&state",
        "ok": true,
        "status": 302,
        "detail": {
          "location_host": "claude.ai",
          "state_matches": true
        }
      },
      {
        "step": "POST /token (authorization_code + code_verifier) \u2192 Bearer, 24 h, no-store",
        "ok": true,
        "status": 200,
        "detail": {
          "token_type": "Bearer",
          "expires_in": 86400,
          "scope": "league.read roster.read analysis.read alerts.write messages.write",
          "refresh_token_present": true
        }
      }
    ],
    "granted_scope": "league.read roster.read analysis.read alerts.write messages.write"
  },
  "pass": true
}
✅ connect \u00b7 PKCE S256 walk on the demo fixture (chatgpt \u00b7 five + calls.write) [pos · chatgpt]
{
  "name": "connect \u00b7 PKCE S256 walk on the demo fixture (chatgpt \u00b7 five + calls.write)",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "client_id": "scoa_g9KJbpWYF11O77ur4m",
    "redirect_uri": "https://chatgpt.com/connector_platform_oauth_redirect",
    "phone": "+15550001001",
    "code": "<SPORTSCALL_OAUTH_DEMO_CODE>",
    "pkce": "S256"
  },
  "response": {
    "steps": [
      {
        "step": "GET /authorize \u2192 200 page + rid + sc_authz cookie",
        "ok": true,
        "status": 200,
        "detail": {
          "rid": true,
          "cookie": "<redacted>"
        }
      },
      {
        "step": "phone step (fixture DID; the text is 555-faked)",
        "ok": true,
        "status": 200,
        "detail": "We texted a code to +1\u2022\u2022\u2022\u2022\u2022\u20221002 from your SportsCall line."
      },
      {
        "step": "code step (SPORTSCALL_OAUTH_DEMO_CODE, fixture only) \u2192 consent lists the requested scopes",
        "ok": true,
        "status": 200,
        "detail": {
          "consent_scopes": [
            "league.read",
            "roster.read",
            "analysis.read",
            "alerts.write",
            "messages.write",
            "calls.write"
          ]
        }
      },
      {
        "step": "consent allow (ticked ['league.read', 'roster.read', 'analysis.read', 'alerts.write', 'messages.write', 'calls.write']) \u2192 302 redirect_uri?code&state",
        "ok": true,
        "status": 302,
        "detail": {
          "location_host": "chatgpt.com",
          "state_matches": true
        }
      },
      {
        "step": "POST /token (authorization_code + code_verifier) \u2192 Bearer, 24 h, no-store",
        "ok": true,
        "status": 200,
        "detail": {
          "token_type": "Bearer",
          "expires_in": 86400,
          "scope": "league.read roster.read analysis.read alerts.write messages.write calls.write",
          "refresh_token_present": true
        }
      }
    ],
    "granted_scope": "league.read roster.read analysis.read alerts.write messages.write calls.write"
  },
  "pass": true
}
✅ connect \u00b7 PKCE S256 walk on the demo fixture (chatgpt \u00b7 consent WITHOUT messages.write / calls.write) [pos · chatgpt]
{
  "name": "connect \u00b7 PKCE S256 walk on the demo fixture (chatgpt \u00b7 consent WITHOUT messages.write / calls.write)",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "client_id": "scoa_g9KJbpWYF11O77ur4m",
    "redirect_uri": "https://chatgpt.com/connector_platform_oauth_redirect",
    "phone": "+15550001001",
    "code": "<SPORTSCALL_OAUTH_DEMO_CODE>",
    "pkce": "S256"
  },
  "response": {
    "steps": [
      {
        "step": "GET /authorize \u2192 200 page + rid + sc_authz cookie",
        "ok": true,
        "status": 200,
        "detail": {
          "rid": true,
          "cookie": "<redacted>"
        }
      },
      {
        "step": "phone step (fixture DID; the text is 555-faked)",
        "ok": true,
        "status": 200,
        "detail": "We texted a code to +1\u2022\u2022\u2022\u2022\u2022\u20221002 from your SportsCall line."
      },
      {
        "step": "code step (SPORTSCALL_OAUTH_DEMO_CODE, fixture only) \u2192 consent lists the requested scopes",
        "ok": true,
        "status": 200,
        "detail": {
          "consent_scopes": [
            "league.read",
            "roster.read",
            "analysis.read",
            "alerts.write",
            "messages.write",
            "calls.write"
          ]
        }
      },
      {
        "step": "consent allow (ticked ['league.read', 'roster.read', 'analysis.read', 'alerts.write']) \u2192 302 redirect_uri?code&state",
        "ok": true,
        "status": 302,
        "detail": {
          "location_host": "chatgpt.com",
          "state_matches": true
        }
      },
      {
        "step": "POST /token (authorization_code + code_verifier) \u2192 Bearer, 24 h, no-store",
        "ok": true,
        "status": 200,
        "detail": {
          "token_type": "Bearer",
          "expires_in": 86400,
          "scope": "league.read roster.read analysis.read alerts.write",
          "refresh_token_present": true
        }
      }
    ],
    "granted_scope": "league.read roster.read analysis.read alerts.write"
  },
  "pass": true
}
✅ initialize \u2192 protocolVersion 2025-06-18 \u00b7 serverInfo.version 1.0 [pos · claude]
{
  "name": "initialize \u2192 protocolVersion 2025-06-18 \u00b7 serverInfo.version 1.0",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "initialize",
    "auth": "bearer",
    "params": {
      "protocolVersion": "2025-06-18",
      "clientInfo": {
        "name": "sportscall-e2e-claude",
        "version": "1.0"
      }
    }
  },
  "response": {
    "status": 200,
    "ms": 22,
    "result": {
      "protocolVersion": "2025-06-18",
      "capabilities": {
        "tools": {
          "listChanged": false
        },
        "resources": {
          "listChanged": false
        }
      },
      "serverInfo": {
        "name": "sportscall",
        "version": "1.0",
        "title": "SportsCall \u2014 Fantasy Co-Manager"
      },
      "instructions": "One bearer = one SportsCall phone line. Start with get_my_fantasy_context when a question depends on the user's team. Decision tools return \u2026 (+14 chars)"
    }
  },
  "pass": true
}
✅ tools/list (claude) \u2192 no voice tool, no roster move, channel enum = [sms], every description starts 'Use this' [pos · claude]
{
  "name": "tools/list (claude) \u2192 no voice tool, no roster move, channel enum = [sms], every description starts 'Use this'",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/list",
    "auth": "bearer"
  },
  "response": {
    "status": 200,
    "tools": [
      "get_my_leagues",
      "get_my_fantasy_context",
      "\u2026 +10 more"
    ],
    "watch_roster.channel.enum": [
      "sms"
    ]
  },
  "pass": true
}
✅ claude surface listing has NO schedule_sportscall / call_me_now / send_me_weekly_brief [neg · claude]
{
  "name": "claude surface listing has NO schedule_sportscall / call_me_now / send_me_weekly_brief",
  "kind": "neg",
  "surface": "claude",
  "request": {
    "method": "tools/list"
  },
  "response": {
    "tools": [
      "get_my_leagues",
      "get_my_fantasy_context",
      "\u2026 +10 more"
    ],
    "voice_tools_present": []
  },
  "pass": true
}
✅ get_my_leagues \u2192 ok:true, the fixture's league listed, isError:false [pos · claude]
{
  "name": "get_my_leagues \u2192 ok:true, the fixture's league listed, isError:false",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_my_leagues",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 22,
    "isError": false,
    "text": "1 league linked: QA League on MyFantasyLeague (QA Team) \u2014 primary.",
    "structuredContent": {
      "ok": true,
      "leagues": [
        {
          "platform": "mfl",
          "platform_label": "MyFantasyLeague",
          "league_id": "qa",
          "name": "QA League",
          "team": "QA Team",
          "record": "",
          "scoring": null,
          "team_count": null,
          "week": null,
          "matchup": {},
          "primary": true,
          "waiver_position": null
        }
      ],
      "primary_platform": "mfl"
    }
  },
  "pass": true
}
✅ get_my_fantasy_context \u2192 ok:true with league rules, week, roster [pos · claude]
{
  "name": "get_my_fantasy_context \u2192 ok:true with league rules, week, roster",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_my_fantasy_context",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 81,
    "isError": false,
    "text": "QA League on MyFantasyLeague \u2014 10-team \u00b7 Week 1. No matchup posted yet. Starters: QB Josh Allen (QB, BUF), RB Bijan Robinson (RB, ATL), RB2 \u2026 (+83 chars)",
    "structuredContent": {
      "ok": true,
      "league": {
        "platform": "MyFantasyLeague",
        "name": "QA League",
        "teams": 10,
        "scoring": "",
        "week": 1,
        "format_line": "10-team \u00b7 Week 1"
      },
      "leagues": [
        {
          "platform": "mfl",
          "platform_label": "MyFantasyLeague",
          "league_id": "qa",
          "name": "QA League",
          "team": "QA Team",
          "record": "",
          "scoring": null,
          "team_count": null,
          "week": null,
          "matchup": {},
          "primary": true,
          "waiver_position": null
        }
      ],
      "rules": {
        "sport": "football",
        "team_count": 10,
        "starting_slots": [
          "QB",
          "RB",
          "\u2026 +1 more"
        ],
        "bench_size": 1
      },
      "record": "",
      "week": 1,
      "matchup": {},
      "roster": {
        "starters": [
          {
            "slot": "QB",
            "name": "Josh Allen",
            "pos": "QB",
            "team": "BUF"
          },
          {
            "slot": "RB",
            "name": "Bijan Robinson",
            "pos": "RB",
            "team": "ATL"
          },
          "\u2026 +1 more"
        ],
        "bench": [
          {
            "slot": "BN",
            "name": "Tyler Allgeier",
            "pos": "RB",
            "team": "ATL"
          }
        ]
      },
      "roster_source": "mfl:qa"
    }
  },
  "pass": true
}
✅ get_roster_status \u2192 ok:true, every rostered player with a status line [pos · claude]
{
  "name": "get_roster_status \u2192 ok:true, every rostered player with a status line",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_roster_status",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 129,
    "isError": false,
    "text": "5 on file (4 starters). No starter is out or on bye. Next kickoff Sun 1 PM.",
    "structuredContent": {
      "ok": true,
      "platform": "mfl",
      "week": 1,
      "players": [
        {
          "name": "Josh Allen",
          "slot": "QB",
          "pos": "QB",
          "team": "BUF",
          "starter": true,
          "status": "",
          "detail": "",
          "return": "",
          "bye": false,
          "next_kickoff": "2026-09-13T17:00:00+00:00",
          "event_id": "401872660",
          "status_as_of": "2026-09-08T01:25:38.500901+00:00"
        },
        {
          "name": "Bijan Robinson",
          "slot": "RB",
          "pos": "RB",
          "team": "ATL",
          "starter": true,
          "status": "",
          "detail": "",
          "return": "",
          "bye": false,
          "next_kickoff": "2026-09-13T17:00:00+00:00",
          "event_id": "401872658",
          "status_as_of": "2026-09-08T01:25:38.500901+00:00"
        },
        "\u2026 +1 more"
      ],
      "flagged": [],
      "injury_feed_ok": true,
      "status_as_of": "2026-09-08T01:25:38.500901+00:00",
      "sources": [
        {
          "label": "RotoWire",
          "as_of": "2026-09-08T01:25:38.500901+00:00"
        },
        {
          "label": "our own ledger",
          "as_of": ""
        }
      ]
    }
  },
  "pass": true
}
✅ analyze_start_sit [Bijan Robinson vs James Cook] \u2192 THE CALL: every required field \u00b7 confidence 50\u201395 \u00b7 3 factors \u00b7 decision_id \u00b7 no odds words [pos · claude]
{
  "name": "analyze_start_sit [Bijan Robinson vs James Cook] \u2192 THE CALL: every required field \u00b7 confidence 50\u201395 \u00b7 3 factors \u00b7 decision_id \u00b7 no odds words",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "analyze_start_sit",
    "arguments": {
      "players": [
        "Bijan Robinson",
        "James Cook"
      ]
    }
  },
  "response": {
    "status": 200,
    "ms": 102,
    "isError": false,
    "text": "THE CALL \u00b7 START Bijan Robinson over James Cook \u00b7 70% \u00b7 10-team \u00b7 Week 1 \u00b7 Updated 9:26 PM ET \u00b7 Volume \u2192 \u00b7 Matchup \u2193 \u00b7 Injury risk \u2193 \u00b7 Why: Bijan Robinson projects 21.5 to James Cook's 17.1 and team expected to chase \u2014 fewer carries late. \u00b7 Risk: Game flow could flip it \u2014 team expected to chase \u2014 fewer carries late is the read on file today. \u00b7 Watch this call: watch_decision(decision_id=d_079b7a38dc8d)",
    "structuredContent": {
      "ok": true,
      "decision": {
        "schema_version": "1.0",
        "decision_id": "d_079b7a38dc8d",
        "kind": "start_sit",
        "verdict": "START",
        "headline": "START Bijan Robinson over James Cook",
        "subject": [
          {
            "name": "Bijan Robinson",
            "pos": "RB",
            "team": "ATL",
            "role": "pick",
            "status": "",
            "projection": 21.53
          },
          {
            "name": "James Cook",
            "pos": "RB",
            "team": "BUF",
            "role": "other",
            "status": "",
            "projection": 17.11
          }
        ],
        "confidence": 70,
        "confidence_method": "det-v1",
        "confidence_breakdown": {
          "method": "det-v1",
          "base": 50,
          "edge": 19.9,
          "freshness": 0,
          "injury": 0,
          "confidence": 70
        },
        "league": {
          "platform": "MyFantasyLeague",
          "name": "QA League",
          "teams": 10,
          "scoring": "",
          "week": 1,
          "format_line": "10-team \u00b7 Week 1"
        },
        "updated_at": "2026-09-08T01:26:39+00:00",
        "updated_display": "Updated 9:26 PM ET",
        "factors": [
          {
            "name": "Volume",
            "direction": "\u2192",
            "note": "No usage on file yet this season"
          },
          {
            "name": "Matchup",
            "direction": "\u2193",
            "note": "Team expected to chase \u2014 fewer carries late"
          },
          "\u2026 +1 more"
        ],
        "reasoning": "Bijan Robinson projects 21.5 to James Cook's 17.1 and team expected to chase \u2014 fewer carries late.",
        "risk": "Game flow could flip it \u2014 team expected to chase \u2014 fewer carries late is the read on file today.",
        "sources": [
          {
            "label": "RotoWire",
            "as_of": "fetched Sep 8, 12:40 AM UTC"
          },
          {
            "label": "RotoWire",
            "as_of": "2026-09-08T01:25:38.500901+00:00"
          }
        ],
        "watchable": true,
        "lock_at": "2026-09-13T17:00:00+00:00"
      },
      "players": [
        {
          "player": "Bijan Robinson",
          "pos": "RB",
          "team": "ATL",
          "on_roster": true,
          "slot": "RB",
          "status": "",
          "detail": "",
          "status_as_of": "2026-09-08T01:25:38.500901+00:00",
          "bye": false,
          "bye_week": null,
          "projection": {
            "points": 21.53,
            "source": "RotoWire NFL weekly projections feed (week 1)",
            "as_of": "fetched Sep 8, 12:40 AM UTC",
            "label": "week 1"
          },
          "game_env": {
            "script": "behind",
            "scoring": "neutral",
            "known": true
          },
          "why": [
            "projected 21.5",
            "team expected to chase \u2014 fewer carries late"
          ]
        },
        {
          "player": "James Cook",
          "pos": "RB",
          "team": "BUF",
          "on_roster": true,
          "slot": "RB2",
          "status": "",
          "detail": "",
          "status_as_of": "2026-09-08T01:25:38.500901+00:00",
          "bye": false,
          "bye_week": null,
          "projection": {
            "points": 17.11,
            "source": "RotoWire NFL weekly projections feed (week 1)",
            "as_of": "fetched Sep 8, 12:40 AM UTC",
            "label": "week 1"
          },
          "game_env": {
            "script": "even",
            "scoring": "neutral",
            "known": true
          },
          "why": [
            "projected 17.1",
            "game expected to stay close"
          ]
        }
      ]
    },
    "_meta": {
      "openai/outputTemplate": "ui://sportscall/decision-card.html",
      "sportscall/version": "1.0"
    }
  },
  "pass": true
}
✅ watch_decision(d_079b7a38dc8d) \u2192 ok:true, watch_id, ends at lock [pos · claude]
{
  "name": "watch_decision(d_079b7a38dc8d) \u2192 ok:true, watch_id, ends at lock",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "watch_decision",
    "arguments": {
      "decision_id": "d_079b7a38dc8d"
    }
  },
  "response": {
    "status": 200,
    "ms": 119,
    "isError": false,
    "text": "Got it \u2014 I'm watching the call on Robinson over Cook until Sun 1 PM. If it flips or the confidence moves a band, SportsCall texts you.",
    "structuredContent": {
      "ok": true,
      "watch_id": 104,
      "decision_id": "d_079b7a38dc8d",
      "kind": "start_sit",
      "verdict": "START",
      "channel": "sms",
      "until_at": "2026-09-13T17:00:00+00:00",
      "existing": false,
      "baseline": {
        "verdict": "START",
        "winner": "Bijan Robinson",
        "band": "solid",
        "statuses": {
          "Bijan Robinson": "",
          "James Cook": ""
        },
        "risk_realized": false
      }
    }
  },
  "pass": true
}
✅ list_watches \u2192 shows the decision watch [pos · claude]
{
  "name": "list_watches \u2192 shows the decision watch",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "list_watches",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 24,
    "isError": false,
    "text": "1 active watch: #104 decision START Robinson over Cook by sms until Sun 1 PM.",
    "structuredContent": {
      "ok": true,
      "watches": [
        {
          "id": 104,
          "kind": "decision",
          "subject": {
            "a": "Bijan Robinson",
            "b": "James Cook",
            "kind": "start_sit",
            "label": "START Robinson over Cook",
            "players": [
              "Bijan Robinson",
              "James Cook"
            ],
            "verdict": "START",
            "platform": "mfl",
            "decision_id": "d_079b7a38dc8d"
          },
          "channel": "sms",
          "until_at": "2026-09-13T17:00:00+00:00",
          "alerts_sent": 0,
          "last_alert_at": null,
          "created_at": "2026-09-08T01:26:40.067275+00:00",
          "last_skip_reason": null
        }
      ]
    }
  },
  "pass": true
}
✅ cancel_watch(#104) \u2192 ok:true [pos · claude]
{
  "name": "cancel_watch(#104) \u2192 ok:true",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "cancel_watch",
    "arguments": {
      "id": 104
    }
  },
  "response": {
    "status": 200,
    "ms": 22,
    "isError": false,
    "text": "Watch #104 cancelled.",
    "structuredContent": {
      "ok": true,
      "watch_id": 104
    }
  },
  "pass": true
}
✅ watch_roster \u00d72 with one Idempotency-Key \u2192 second answer replayed:true, same watch_id [pos · claude]
{
  "name": "watch_roster \u00d72 with one Idempotency-Key \u2192 second answer replayed:true, same watch_id",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "first": {
      "rpc": "tools/call",
      "auth": "bearer",
      "tool": "watch_roster",
      "arguments": {
        "channel": "sms"
      },
      "Idempotency-Key": "e2e-claude-eee5b8b4a9"
    },
    "second": {
      "rpc": "tools/call",
      "auth": "bearer",
      "tool": "watch_roster",
      "arguments": {
        "channel": "sms"
      },
      "Idempotency-Key": "e2e-claude-eee5b8b4a9"
    }
  },
  "response": {
    "first": {
      "status": 200,
      "ms": 122,
      "isError": false,
      "text": "Got it. I'm watching your whole roster on MyFantasyLeague. If something material changes before Sun 1 PM lock, SportsCall will text you.",
      "structuredContent": {
        "ok": true,
        "watch_id": 105,
        "channel": "sms",
        "until_at": "2026-09-13T17:00:00+00:00"
      }
    },
    "second": {
      "status": 200,
      "ms": 22,
      "isError": false,
      "text": "Got it. I'm watching your whole roster on MyFantasyLeague. If something material changes before Sun 1 PM lock, SportsCall will text you.",
      "structuredContent": {
        "ok": true,
        "watch_id": 105,
        "replayed": true,
        "keys": [
          "channel",
          "ok",
          "\u2026 +3 more"
        ]
      }
    }
  },
  "pass": true
}
✅ get_weekly_brief \u2192 ok:true, one read, sends nothing (no sid, no sends) [pos · claude]
{
  "name": "get_weekly_brief \u2192 ok:true, one read, sends nothing (no sid, no sends)",
  "kind": "pos",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_weekly_brief",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 192,
    "isError": false,
    "text": "SportsCall brief \u2014 10-team \u00b7 Week 1. Last week: W 149.4-109.1 vs Zero Eifert. No starter is out or on bye. In action this week: Josh Allen, \u2026 (+139 chars)",
    "structuredContent": {
      "ok": true,
      "sends": false,
      "week": 1,
      "keys": [
        "brief_text",
        "candidates",
        "\u2026 +15 more"
      ]
    }
  },
  "pass": true
}
✅ get_weekly_brief with `deliver` \u2192 E_BAD_ARGS (the brief never sends \u2014 Ian's ruling 2) [neg · claude]
{
  "name": "get_weekly_brief with `deliver` \u2192 E_BAD_ARGS (the brief never sends \u2014 Ian's ruling 2)",
  "kind": "neg",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_weekly_brief",
    "arguments": {
      "deliver": "sms"
    }
  },
  "response": {
    "status": 200,
    "ms": 22,
    "isError": true,
    "text": "The brief is read-only here \u2014 ask me to text you a specific note if you want one.",
    "structuredContent": {
      "ok": false,
      "code": "E_BAD_ARGS",
      "sends": false,
      "retryable": false
    }
  },
  "pass": true
}
✅ analyze_trade with empty `get` \u2192 E_BAD_ARGS [neg · claude]
{
  "name": "analyze_trade with empty `get` \u2192 E_BAD_ARGS",
  "kind": "neg",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "analyze_trade",
    "arguments": {
      "give": [
        "Josh Allen"
      ],
      "get": []
    }
  },
  "response": {
    "status": 200,
    "ms": 21,
    "isError": true,
    "text": "Tell me what you'd give and what you'd get.",
    "structuredContent": {
      "ok": false,
      "decision": null,
      "players": [],
      "code": "E_BAD_ARGS",
      "retryable": false
    },
    "_meta": {
      "openai/outputTemplate": "ui://sportscall/decision-card.html",
      "sportscall/version": "1.0"
    }
  },
  "pass": true
}
✅ unknown tool get_odds \u2192 JSON-RPC \u221232601 [neg · claude]
{
  "name": "unknown tool get_odds \u2192 JSON-RPC \u221232601",
  "kind": "neg",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_odds",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 20,
    "error": {
      "code": -32601,
      "message": "unknown tool: get_odds"
    }
  },
  "pass": true
}
✅ schedule_sportscall on the claude surface \u2192 \u221232601 (off this surface) [neg · claude]
{
  "name": "schedule_sportscall on the claude surface \u2192 \u221232601 (off this surface)",
  "kind": "neg",
  "surface": "claude",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "schedule_sportscall",
    "arguments": {
      "at_iso": "2026-09-07T20:26",
      "topic": "e2e past"
    }
  },
  "response": {
    "status": 200,
    "ms": 20,
    "error": {
      "code": -32601,
      "message": "unknown tool: schedule_sportscall"
    }
  },
  "pass": true
}
✅ initialize \u2192 protocolVersion 2025-06-18 \u00b7 serverInfo.version 1.0 [pos · chatgpt]
{
  "name": "initialize \u2192 protocolVersion 2025-06-18 \u00b7 serverInfo.version 1.0",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "initialize",
    "auth": "bearer",
    "params": {
      "protocolVersion": "2025-06-18",
      "clientInfo": {
        "name": "sportscall-e2e-chatgpt",
        "version": "1.0"
      }
    }
  },
  "response": {
    "status": 200,
    "ms": 19,
    "result": {
      "protocolVersion": "2025-06-18",
      "capabilities": {
        "tools": {
          "listChanged": false
        },
        "resources": {
          "listChanged": false
        }
      },
      "serverInfo": {
        "name": "sportscall",
        "version": "1.0",
        "title": "SportsCall \u2014 Fantasy Co-Manager"
      },
      "instructions": "One bearer = one SportsCall phone line. Start with get_my_fantasy_context when a question depends on the user's team. Decision tools return \u2026 (+14 chars)"
    }
  },
  "pass": true
}
✅ tools/list (chatgpt) \u2192 schedule_sportscall listed \u00b7 no roster move \u00b7 channel enum [sms, call] \u00b7 every description starts 'Use this' [pos · chatgpt]
{
  "name": "tools/list (chatgpt) \u2192 schedule_sportscall listed \u00b7 no roster move \u00b7 channel enum [sms, call] \u00b7 every description starts 'Use this'",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/list",
    "auth": "bearer"
  },
  "response": {
    "status": 200,
    "tools": [
      "get_my_leagues",
      "get_my_fantasy_context",
      "\u2026 +11 more"
    ],
    "watch_roster.channel.enum": [
      "sms",
      "call"
    ]
  },
  "pass": true
}
✅ get_my_leagues \u2192 ok:true, the fixture's league listed, isError:false [pos · chatgpt]
{
  "name": "get_my_leagues \u2192 ok:true, the fixture's league listed, isError:false",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_my_leagues",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 21,
    "isError": false,
    "text": "1 league linked: QA League on MyFantasyLeague (QA Team) \u2014 primary.",
    "structuredContent": {
      "ok": true,
      "leagues_n": 1,
      "keys": [
        "leagues",
        "ok",
        "\u2026 +1 more"
      ]
    }
  },
  "pass": true
}
✅ get_my_fantasy_context \u2192 ok:true with league rules, week, roster [pos · chatgpt]
{
  "name": "get_my_fantasy_context \u2192 ok:true with league rules, week, roster",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_my_fantasy_context",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 68,
    "isError": false,
    "text": "QA League on MyFantasyLeague \u2014 10-team \u00b7 Week 1. No matchup posted yet. Starters: QB Josh Allen (QB, BUF), RB Bijan Robinson (RB, ATL), RB2 \u2026 (+83 chars)",
    "structuredContent": {
      "ok": true,
      "week": 1,
      "roster_source": "mfl:qa",
      "leagues_n": 1,
      "keys": [
        "league",
        "leagues",
        "\u2026 +7 more"
      ]
    }
  },
  "pass": true
}
✅ get_roster_status \u2192 ok:true, every rostered player with a status line [pos · chatgpt]
{
  "name": "get_roster_status \u2192 ok:true, every rostered player with a status line",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_roster_status",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 107,
    "isError": false,
    "text": "5 on file (4 starters). No starter is out or on bye. Next kickoff Sun 1 PM.",
    "structuredContent": {
      "ok": true,
      "week": 1,
      "players_n": 5,
      "flagged_n": 0,
      "keys": [
        "flagged",
        "injury_feed_ok",
        "\u2026 +6 more"
      ]
    }
  },
  "pass": true
}
✅ analyze_start_sit [Bijan Robinson vs James Cook] \u2192 THE CALL: every required field \u00b7 confidence 50\u201395 \u00b7 3 factors \u00b7 decision_id \u00b7 no odds words [pos · chatgpt]
{
  "name": "analyze_start_sit [Bijan Robinson vs James Cook] \u2192 THE CALL: every required field \u00b7 confidence 50\u201395 \u00b7 3 factors \u00b7 decision_id \u00b7 no odds words",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "analyze_start_sit",
    "arguments": {
      "players": [
        "Bijan Robinson",
        "James Cook"
      ]
    }
  },
  "response": {
    "status": 200,
    "ms": 116,
    "isError": false,
    "text": "THE CALL \u00b7 START Bijan Robinson over James Cook \u00b7 70% \u00b7 10-team \u00b7 Week 1 \u00b7 Updated 9:26 PM ET \u00b7 Volume \u2192 \u00b7 Matchup \u2193 \u00b7 Injury risk \u2193 \u00b7 Why: \u2026 (+265 chars)",
    "structuredContent": {
      "ok": true,
      "decision": {
        "schema_version": "1.0",
        "decision_id": "d_079b7a38dc8d",
        "kind": "start_sit",
        "verdict": "START",
        "headline": "START Bijan Robinson over James Cook",
        "subject": [
          {
            "name": "Bijan Robinson",
            "pos": "RB",
            "team": "ATL",
            "role": "pick",
            "status": "",
            "projection": 21.53
          },
          {
            "name": "James Cook",
            "pos": "RB",
            "team": "BUF",
            "role": "other",
            "status": "",
            "projection": 17.11
          }
        ],
        "confidence": 70,
        "confidence_method": "det-v1",
        "confidence_breakdown": {
          "method": "det-v1",
          "base": 50,
          "edge": 19.9,
          "freshness": 0,
          "injury": 0,
          "confidence": 70
        },
        "league": {
          "platform": "MyFantasyLeague",
          "name": "QA League",
          "teams": 10,
          "scoring": "",
          "week": 1,
          "format_line": "10-team \u00b7 Week 1"
        },
        "updated_at": "2026-09-08T01:26:40+00:00",
        "updated_display": "Updated 9:26 PM ET",
        "factors": [
          {
            "name": "Volume",
            "direction": "\u2192",
            "note": "No usage on file yet this season"
          },
          {
            "name": "Matchup",
            "direction": "\u2193",
            "note": "Team expected to chase \u2014 fewer carries late"
          },
          "\u2026 +1 more"
        ],
        "reasoning": "Bijan Robinson projects 21.5 to James Cook's 17.1 and team expected to chase \u2014 fewer carries late.",
        "risk": "Game flow could flip it \u2014 team expected to chase \u2014 fewer carries late is the read on file today.",
        "sources": [
          {
            "label": "RotoWire",
            "as_of": "fetched Sep 8, 12:40 AM UTC"
          },
          {
            "label": "RotoWire",
            "as_of": "2026-09-08T01:25:38.500901+00:00"
          }
        ],
        "watchable": true,
        "lock_at": "2026-09-13T17:00:00+00:00"
      },
      "players": [
        {
          "player": "Bijan Robinson",
          "pos": "RB",
          "team": "ATL",
          "on_roster": true,
          "slot": "RB",
          "status": "",
          "detail": "",
          "status_as_of": "2026-09-08T01:25:38.500901+00:00",
          "bye": false,
          "bye_week": null,
          "projection": {
            "points": 21.53,
            "source": "RotoWire NFL weekly projections feed (week 1)",
            "as_of": "fetched Sep 8, 12:40 AM UTC",
            "label": "week 1"
          },
          "game_env": {
            "script": "behind",
            "scoring": "neutral",
            "known": true
          },
          "why": [
            "projected 21.5",
            "team expected to chase \u2014 fewer carries late"
          ]
        },
        {
          "player": "James Cook",
          "pos": "RB",
          "team": "BUF",
          "on_roster": true,
          "slot": "RB2",
          "status": "",
          "detail": "",
          "status_as_of": "2026-09-08T01:25:38.500901+00:00",
          "bye": false,
          "bye_week": null,
          "projection": {
            "points": 17.11,
            "source": "RotoWire NFL weekly projections feed (week 1)",
            "as_of": "fetched Sep 8, 12:40 AM UTC",
            "label": "week 1"
          },
          "game_env": {
            "script": "even",
            "scoring": "neutral",
            "known": true
          },
          "why": [
            "projected 17.1",
            "game expected to stay close"
          ]
        }
      ]
    },
    "_meta": {
      "openai/outputTemplate": "ui://sportscall/decision-card.html",
      "sportscall/version": "1.0"
    }
  },
  "pass": true
}
✅ watch_decision(d_079b7a38dc8d) \u2192 ok:true, watch_id, ends at lock [pos · chatgpt]
{
  "name": "watch_decision(d_079b7a38dc8d) \u2192 ok:true, watch_id, ends at lock",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "watch_decision",
    "arguments": {
      "decision_id": "d_079b7a38dc8d"
    }
  },
  "response": {
    "status": 200,
    "ms": 121,
    "isError": false,
    "text": "Got it \u2014 I'm watching the call on Robinson over Cook until Sun 1 PM. If it flips or the confidence moves a band, SportsCall texts you.",
    "structuredContent": {
      "ok": true,
      "watch_id": 106,
      "decision_id": "d_079b7a38dc8d",
      "existing": false,
      "keys": [
        "baseline",
        "channel",
        "\u2026 +7 more"
      ]
    }
  },
  "pass": true
}
✅ list_watches \u2192 shows the decision watch [pos · chatgpt]
{
  "name": "list_watches \u2192 shows the decision watch",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "list_watches",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 23,
    "isError": false,
    "text": "1 active watch: #106 decision START Robinson over Cook by sms until Sun 1 PM.",
    "structuredContent": {
      "ok": true,
      "watches_n": 1,
      "keys": [
        "ok",
        "watches"
      ]
    }
  },
  "pass": true
}
✅ cancel_watch(#106) \u2192 ok:true [pos · chatgpt]
{
  "name": "cancel_watch(#106) \u2192 ok:true",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "cancel_watch",
    "arguments": {
      "id": 106
    }
  },
  "response": {
    "status": 200,
    "ms": 26,
    "isError": false,
    "text": "Watch #106 cancelled.",
    "structuredContent": {
      "ok": true,
      "watch_id": 106
    }
  },
  "pass": true
}
✅ watch_roster \u00d72 with one Idempotency-Key \u2192 second answer replayed:true, same watch_id [pos · chatgpt]
{
  "name": "watch_roster \u00d72 with one Idempotency-Key \u2192 second answer replayed:true, same watch_id",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "first": {
      "rpc": "tools/call",
      "auth": "bearer",
      "tool": "watch_roster",
      "arguments": {
        "channel": "sms"
      },
      "Idempotency-Key": "e2e-chatgpt-9eeca74886"
    },
    "second": {
      "rpc": "tools/call",
      "auth": "bearer",
      "tool": "watch_roster",
      "arguments": {
        "channel": "sms"
      },
      "Idempotency-Key": "e2e-chatgpt-9eeca74886"
    }
  },
  "response": {
    "first": {
      "status": 200,
      "ms": 108,
      "isError": false,
      "text": "Got it. I'm watching your whole roster on MyFantasyLeague. If something material changes before Sun 1 PM lock, SportsCall will text you.",
      "structuredContent": {
        "ok": true,
        "watch_id": 107,
        "keys": [
          "channel",
          "ok",
          "\u2026 +2 more"
        ]
      }
    },
    "second": {
      "status": 200,
      "ms": 23,
      "isError": false,
      "text": "Got it. I'm watching your whole roster on MyFantasyLeague. If something material changes before Sun 1 PM lock, SportsCall will text you.",
      "structuredContent": {
        "ok": true,
        "watch_id": 107,
        "replayed": true,
        "keys": [
          "channel",
          "ok",
          "\u2026 +3 more"
        ]
      }
    }
  },
  "pass": true
}
✅ get_weekly_brief \u2192 ok:true, one read, sends nothing (no sid, no sends) [pos · chatgpt]
{
  "name": "get_weekly_brief \u2192 ok:true, one read, sends nothing (no sid, no sends)",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_weekly_brief",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 185,
    "isError": false,
    "text": "SportsCall brief \u2014 10-team \u00b7 Week 1. Last week: W 149.4-109.1 vs Zero Eifert. No starter is out or on bye. In action this week: Josh Allen, \u2026 (+139 chars)",
    "structuredContent": {
      "ok": true,
      "sends": false,
      "week": 1,
      "keys": [
        "brief_text",
        "candidates",
        "\u2026 +15 more"
      ]
    }
  },
  "pass": true
}
✅ get_weekly_brief with `deliver` \u2192 E_BAD_ARGS (the brief never sends \u2014 Ian's ruling 2) [neg · chatgpt]
{
  "name": "get_weekly_brief with `deliver` \u2192 E_BAD_ARGS (the brief never sends \u2014 Ian's ruling 2)",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_weekly_brief",
    "arguments": {
      "deliver": "sms"
    }
  },
  "response": {
    "status": 200,
    "ms": 23,
    "isError": true,
    "text": "The brief is read-only here \u2014 ask me to text you a specific note if you want one.",
    "structuredContent": {
      "ok": false,
      "code": "E_BAD_ARGS",
      "sends": false,
      "retryable": false
    }
  },
  "pass": true
}
✅ analyze_trade with empty `get` \u2192 E_BAD_ARGS [neg · chatgpt]
{
  "name": "analyze_trade with empty `get` \u2192 E_BAD_ARGS",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "analyze_trade",
    "arguments": {
      "give": [
        "Josh Allen"
      ],
      "get": []
    }
  },
  "response": {
    "status": 200,
    "ms": 24,
    "isError": true,
    "text": "Tell me what you'd give and what you'd get.",
    "structuredContent": {
      "ok": false,
      "decision": null,
      "players": [],
      "code": "E_BAD_ARGS",
      "retryable": false
    },
    "_meta": {
      "openai/outputTemplate": "ui://sportscall/decision-card.html",
      "sportscall/version": "1.0"
    }
  },
  "pass": true
}
✅ unknown tool get_odds \u2192 JSON-RPC \u221232601 [neg · chatgpt]
{
  "name": "unknown tool get_odds \u2192 JSON-RPC \u221232601",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "get_odds",
    "arguments": {}
  },
  "response": {
    "status": 200,
    "ms": 20,
    "error": {
      "code": -32601,
      "message": "unknown tool: get_odds"
    }
  },
  "pass": true
}
✅ schedule_sportscall in the past \u2192 E_PAST [neg · chatgpt]
{
  "name": "schedule_sportscall in the past \u2192 E_PAST",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "schedule_sportscall",
    "arguments": {
      "at_iso": "2026-09-07T20:26",
      "topic": "e2e past"
    }
  },
  "response": {
    "status": 200,
    "ms": 20,
    "isError": true,
    "text": "That time has passed \u2014 pick a later one, or say call me now.",
    "structuredContent": {
      "ok": false,
      "reason": "in_the_past",
      "code": "E_PAST",
      "retryable": false
    }
  },
  "pass": true
}
✅ schedule_sportscall 2 days out (fixture) \u2192 no call may leave: E_DEMO_NOOP \u00b7 or a pending queue row (deleted) \u00b7 or a closed gate [pos · chatgpt]
{
  "name": "schedule_sportscall 2 days out (fixture) \u2192 no call may leave: E_DEMO_NOOP \u00b7 or a pending queue row (deleted) \u00b7 or a closed gate",
  "kind": "pos",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "schedule_sportscall",
    "arguments": {
      "at_iso": "2026-09-09T12:00",
      "topic": "e2e demo"
    }
  },
  "response": {
    "status": 200,
    "ms": 23,
    "isError": true,
    "text": "I can't schedule that: this line is out of minutes \u2014 top up and ask again.",
    "structuredContent": {
      "ok": false,
      "reason": "refused_zero_balance",
      "code": "E_ZERO_BALANCE",
      "retryable": false
    }
  },
  "pass": true,
  "note": "gate refused (E_ZERO_BALANCE) \u2014 nothing queued; the fixture has no minutes, so the zero-balance gate answers before any call could queue",
  "known_defect": "E_DEMO_NOOP has NO producer in the server (only the error-table entry exists); the plan expects the fixture to answer E_DEMO_NOOP on call tools"
}
✅ scope-stripped token (no messages.write / calls.write) + watch_roster channel=call \u2192 HTTP 403 \u00b7 \u221232002 \u00b7 data.code E_SCOPE \u00b7 WWW-Authenticate insufficient_scope [neg · chatgpt]
{
  "name": "scope-stripped token (no messages.write / calls.write) + watch_roster channel=call \u2192 HTTP 403 \u00b7 \u221232002 \u00b7 data.code E_SCOPE \u00b7 WWW-Authenticate insufficient_scope",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "watch_roster",
    "arguments": {
      "channel": "call"
    }
  },
  "response": {
    "status": 403,
    "ms": 22,
    "www-authenticate": "Bearer <redacted>\"insufficient_scope\", scope=\"alerts.write calls.write\"",
    "error": {
      "code": -32002,
      "message": "This connection wasn't allowed to do that (calls.write). Reconnect SportsCall and tick \u201cCall your phone from your SportsCall line when you a\u2026 (+16 chars)",
      "data": {
        "code": "E_SCOPE",
        "missing": [
          "calls.write"
        ]
      }
    }
  },
  "pass": true
}
✅ scope-stripped token + send_fantasy_alert \u2192 \u221232002 E_SCOPE (missing messages.write) \u2014 no text leaves [neg · chatgpt]
{
  "name": "scope-stripped token + send_fantasy_alert \u2192 \u221232002 E_SCOPE (missing messages.write) \u2014 no text leaves",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "send_fantasy_alert",
    "arguments": {
      "message": "e2e must be refused"
    }
  },
  "response": {
    "status": 403,
    "ms": 20,
    "www-authenticate": "Bearer <redacted>\"insufficient_scope\", scope=\"messages.write\"",
    "error": {
      "code": -32002,
      "message": "This connection wasn't allowed to do that (messages.write). Reconnect SportsCall and tick \u201cText your phone from your SportsCall line when yo\u2026 (+19 chars)",
      "data": {
        "code": "E_SCOPE",
        "missing": [
          "messages.write"
        ]
      }
    }
  },
  "pass": true
}
✅ replayed authorization code \u2192 400 invalid_grant, and the token it minted is burned (tools/list \u2192 401) [neg · chatgpt]
{
  "name": "replayed authorization code \u2192 400 invalid_grant, and the token it minted is burned (tools/list \u2192 401)",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "grant_type": "authorization_code",
    "code": "<the code from the stripped walk, replayed>"
  },
  "response": {
    "token": {
      "status": 400,
      "ms": 7,
      "headers": {
        "cache-control": "no-store",
        "content-type": "application/json"
      },
      "body": {
        "error": "invalid_grant",
        "error_description": "code already used"
      }
    },
    "then_tools_list": {
      "status": 401,
      "ms": 20,
      "www-authenticate": "Bearer <redacted>\"https://mcp.sportscall.ai/.well-known/oauth-protected-resource\"",
      "error": {
        "code": -32001,
        "message": "bearer token missing, revoked, or unknown"
      }
    }
  },
  "pass": true
}
✅ tools/list (full \u00b7 the line's own bearer) \u2192 send_lineup_alert, call_me_now, fantasy_* present [pos · full]
{
  "name": "tools/list (full \u00b7 the line's own bearer) \u2192 send_lineup_alert, call_me_now, fantasy_* present",
  "kind": "pos",
  "surface": "full",
  "request": {
    "rpc": "tools/list",
    "auth": "bearer"
  },
  "response": {
    "status": 200,
    "tools": [
      "get_my_leagues",
      "get_my_fantasy_context",
      "\u2026 +24 more"
    ]
  },
  "pass": true
}
✅ send_lineup_alert on:true \u2192 ok:true, lineup_alert:true, a standing watch_id [pos · full]
{
  "name": "send_lineup_alert on:true \u2192 ok:true, lineup_alert:true, a standing watch_id",
  "kind": "pos",
  "surface": "full",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "send_lineup_alert",
    "arguments": {
      "on": true
    }
  },
  "response": {
    "status": 200,
    "ms": 121,
    "isError": false,
    "text": "Lineup alerts are on. Before every kickoff I check your starters \u2014 if one is out, doubtful, on bye or inactive, SportsCall texts you a swap.",
    "structuredContent": {
      "ok": true,
      "lineup_alert": true,
      "watch_id": 108
    }
  },
  "pass": true
}
✅ send_lineup_alert on:false \u2192 ok:true, lineup_alert:false (cleanup) [pos · full]
{
  "name": "send_lineup_alert on:false \u2192 ok:true, lineup_alert:false (cleanup)",
  "kind": "pos",
  "surface": "full",
  "request": {
    "rpc": "tools/call",
    "auth": "bearer",
    "tool": "send_lineup_alert",
    "arguments": {
      "on": false
    }
  },
  "response": {
    "status": 200,
    "ms": 23,
    "isError": false,
    "text": "Lineup alerts are off for this line. Ask again any time to turn them back on.",
    "structuredContent": {
      "ok": true,
      "lineup_alert": false
    }
  },
  "pass": true
}
✅ POST /revoke (claude) \u2192 200 \u00b7 then tools/list with the revoked token \u2192 401 [neg · claude]
{
  "name": "POST /revoke (claude) \u2192 200 \u00b7 then tools/list with the revoked token \u2192 401",
  "kind": "neg",
  "surface": "claude",
  "request": {
    "method": "POST",
    "url": "http://127.0.0.1:9001/v1/oauth/sportscall/revoke",
    "form": {
      "token": "<redacted>"
    }
  },
  "response": {
    "revoke": {
      "status": 200,
      "ms": 4,
      "headers": {
        "cache-control": "no-store",
        "content-type": "application/json"
      },
      "body": {}
    },
    "then_tools_list": {
      "status": 401,
      "ms": 19,
      "www-authenticate": "Bearer <redacted>\"https://mcp.sportscall.ai/.well-known/oauth-protected-resource\"",
      "error": {
        "code": -32001,
        "message": "bearer token missing, revoked, or unknown"
      }
    }
  },
  "pass": true
}
✅ POST /revoke (chatgpt) \u2192 200 \u00b7 then tools/list with the revoked token \u2192 401 [neg · chatgpt]
{
  "name": "POST /revoke (chatgpt) \u2192 200 \u00b7 then tools/list with the revoked token \u2192 401",
  "kind": "neg",
  "surface": "chatgpt",
  "request": {
    "method": "POST",
    "url": "http://127.0.0.1:9001/v1/oauth/sportscall/revoke",
    "form": {
      "token": "<redacted>"
    }
  },
  "response": {
    "revoke": {
      "status": 200,
      "ms": 4,
      "headers": {
        "cache-control": "no-store",
        "content-type": "application/json"
      },
      "body": {}
    },
    "then_tools_list": {
      "status": 401,
      "ms": 19,
      "www-authenticate": "Bearer <redacted>\"https://mcp.sportscall.ai/.well-known/oauth-protected-resource\"",
      "error": {
        "code": -32001,
        "message": "bearer token missing, revoked, or unknown"
      }
    }
  },
  "pass": true
}
✅ developers /examples renders this transcript (every case name on the page) [pos · -]
{
  "name": "developers /examples renders this transcript (every case name on the page)",
  "kind": "pos",
  "surface": "-",
  "request": {
    "method": "GET",
    "url": "http://127.0.0.1:9001/v1/developers/sportscall/examples"
  },
  "response": {
    "status": 200,
    "bytes": 83158,
    "missing_case_names": []
  },
  "pass": true
}

Fantasy-sports research and league-management assistance. Not for wagering or sportsbook transactions. · server 1.0 · status · generated 2026-09-08 06:16Z