docs: add Claude rules and workspace configuration
This commit is contained in:
43
.claude/rules/mcp-tools.md
Normal file
43
.claude/rules/mcp-tools.md
Normal 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()`).
|
||||
Reference in New Issue
Block a user