docs: add Claude rules and workspace configuration
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

This commit is contained in:
2026-07-14 13:34:05 -04:00
parent 6271c99173
commit 9f1d85f04b
7 changed files with 418 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
---
description: FastMCP tool rules — sync def, string-table returns, premium gating, _log, dual dict shapes
paths:
- "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()`).