44 lines
2.2 KiB
Markdown
44 lines
2.2 KiB
Markdown
---
|
|
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()`).
|