fastmcp-patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited fastmcp-patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The decorator API maps Python signatures directly to JSON Schema. Docstrings + types = the contract.
from sportiq.server import mcp
from sportiq.cricket.chains import live_matches_chain
from sportiq.core.tool_response import tool_response, error_envelope
from sportiq.core.errors import AllSourcesFailedError
@mcp.tool()
async def cricket_get_live_matches() -> dict:
"""Return currently live cricket matches.
Returns:
Envelope with `data.matches` (list) and `meta` (source, is_stale, ...).
"""
try:
result = await live_matches_chain.fetch()
except AllSourcesFailedError as e:
return error_envelope(
code="ALL_SOURCES_FAILED",
message="No cricket data source reachable.",
sources_tried=e.attempts,
)
return tool_response(result)from pydantic import Field
@mcp.tool()
async def cricket_get_scorecard(
match_id: str = Field(..., description="CricAPI match_id, e.g. 'a1b2c3'."),
) -> dict:
"""Return the full scorecard for a single match."""
result = await scorecard_chain.fetch(match_id=match_id)
return tool_response(result)If the return shape is stable, use a pydantic model — the MCP schema becomes typed JSON Schema instead of a free dict.
class HealthReport(BaseModel):
cache_backend: Literal["redis", "diskcache"]
adapters: dict[str, bool]
quotas: dict[str, int]
@mcp.tool()
async def sportiq_health() -> HealthReport:
"""Report cache backend, per-adapter healthcheck, remaining quotas."""
...Field(..., description=...) is required — the description flows into the MCP schema.Optional[X]; use X | None (Python 3.10+ syntax) — FastMCP handles both, but the typing is cleaner.ctx parameter and call await ctx.report_progress(...).tools.py are only registered if server.py imports that module. Don't forget.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.