Files
nike/.claude/rules/mcp-tools.md
Robert Helewka 9f1d85f04b
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 33s
CVE Scan & Docker Build / build-and-push (push) Successful in 1m36s
docs: add Claude rules and workspace configuration
2026-07-14 13:34:05 -04:00

2.2 KiB

description, paths
description paths
FastMCP tool rules — sync def, string-table returns, premium gating, _log, dual dict shapes
nike/server.py

MCP tools

  • Tools return plain formatted str, not JSON and not Pydantic models. They build human-readable ASCII blocks/tables for the LLM ("=== {name} ===", aligned standings columns, grouped rosters). This is Nike's convention — don't convert a tool to return JSON or a model to match other estate servers. Match the existing formatting.

  • Tools are synchronous def. sportsdb (requests) and db (psycopg2) are blocking; FastMCP runs sync tools in a threadpool. Don't make them async. Keep them plain module-level callables — /api/run invokes tool.fn(**args) directly.

  • Standard tool shape: @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True)) (every tool here is read-only), a docstring written for Claude, resolve the team via _resolve_team if needed, call the sportsdb client, format text, and _log(tool, args, duration_ms) before returning. Not-found returns a friendly string ("Team '…' not found."), never an exception.

  • Premium gating is two things kept in sync: the runtime check config.SPORTSDB_KEY in ('3', '') (→ return a clear "requires a premium key" message, or degrade to cached data like get_roster does) and the tags={"premium"} on the decorator (so /api/tools and the dashboard can flag it). Add both when a new tool needs premium data.

  • Rows come in two shapes — handle both. A team/player dict is either a raw TheSportsDB dict (strTeam, idTeam, strLeague) or a cached DB row (name, id, league_name). Read with x.get("strTeam") or x.get("name"). A cache hit and a cache miss must render identically — don't assume one shape.

  • Per-external-call try/except … pass is intentional so a partial API failure yields partial results. Keep each API call individually guarded; don't merge them into one try or turn them into hard failures.

  • When you add/rename a tool, update the hand-written strings too: the mcp instructions= and the football_analyst() prompt enumerate tools manually and do not auto-update (unlike /api/tools, which derives from mcp.list_tools()).