myco:api-endpoint-serializer-authoring — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited myco:api-endpoint-serializer-authoring (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.
GET /v1/sites/{id}/cameras): implies the resourceexists; return HTTP 409 when the required product is absent.
capability_not_available envelope when the capability is missing — never 404 or 409 for action endpoints.
Use Cursor and paginate() from apps/api/src/unifi_api/services/pagination.py:
from unifi_api.services.pagination import Cursor, InvalidCursor, paginate
cursor = Cursor.decode(cursor_str) if cursor_str else None
page, next_cursor = paginate(items, cursor=cursor, limit=limit, key_fn=key_fn){last_id, last_ts} as Base64 — survives new inserts;offset/page-number pagination does not.
cursor as an opaque query-string param.apps/api/ MUST only import from unifi-core. Never import unifi-mcp-shared inside apps/api/ — it couples the REST server to MCP protocol concerns.
ManagerFactory lives in apps/api/src/unifi_api/services/managers.py. It caches one manager per (controller_id, product) pair behind asyncio locks:
manager = await factory.get_connection_manager(session, controller_id, product)product_kinds in the DB row is a comma-separated string. The factorysplits it and raises UnknownProduct if the product isn't listed — so set product_kinds accurately when registering a controller.
per-request teardown.
When two products expose identically-named endpoint families (e.g., both Network and Protect expose /events), dispatch at request time by consulting controller.product_kinds. Route to the correct manager based on which products are active — avoids collisions without duplicate routes.
Add per-product prefixes on ambiguous routes:
/access/events — not /events/protect/health — not /healthWithout prefixes, three product packages collide in the route namespace.
Accept a kind=timeseries|detail query parameter on the same endpoint; select the appropriate RenderKind at the route layer. Avoids duplicating tools for two render modes of the same data.
When a UniFi API returns a wrapper dict (e.g., switch-ports, port-stats, lldp-neighbors):
RenderKind.DETAIL.render_hint toemit LIST.
Do NOT push list logic into the DETAIL serializer.
When a route function name doesn't follow the default convention (tool name minus product prefix), add an entry to TOOL_ROUTE_OVERRIDES in:
apps/api/tests/test_resource_route_coverage.pyThis is a test-time mapping only — it is NOT applied at server startup. Adding an override here does NOT register a route; you still need a real FastAPI route function. The dict currently has 17 entries (Task 22 audit).
Phase 6 shift: Read tools (unifi_list_*,unifi_get_*,protect_*,access_list_*) now project via Strawberry types — see thegraphql-api-extensionskill. The serializer layer covers mutation acks only (create/update/delete/toggle).validate_manifestaccepts either a serializer or a Strawberry type registration as valid coverage.
apps/api/src/unifi_api/serializers/<domain>/<resource>.pyExample: apps/api/src/unifi_api/serializers/network/dns.py
tools= dict formThe tool_name= / kind= keyword form does NOT exist. Use:
from unifi_api.serializers._base import RenderKind, Serializer, register_serializer
@register_serializer(
tools={
"unifi_create_dns_record": {"kind": RenderKind.DETAIL},
"unifi_update_dns_record": {"kind": RenderKind.DETAIL},
"unifi_delete_dns_record": {"kind": RenderKind.DETAIL},
},
)
class DnsMutationAckSerializer(Serializer):
@staticmethod
def serialize(obj) -> dict:
if isinstance(obj, bool):
return {"success": obj}
if isinstance(obj, dict):
return obj
return {"result": str(obj)}For a single tool using the class-level kind:
@register_serializer(tools=["unifi_create_foo"])Seven values in apps/api/src/unifi_api/serializers/_base.py:
| Value | Use |
|---|---|
LIST | Collection of items |
DETAIL | Single resource or mutation ack |
DIFF | Before/after comparison |
TIMESERIES | Time-ordered data points |
EVENT_LOG | Event stream |
EMPTY | Action with no meaningful return |
STREAM | Streaming response |
Declare exactly which fields appear for each kind. Serializers are curated, not pass-through. Do not add a "return everything" serializer — the cumulative curation effort across 235+ tools is intentional.
Serializers live in apps/api/ only. Managers and MCP tool functions MUST NOT reference render hints or catalog metadata. This keeps MCP servers free of serialization coupling (decision-a63cb266).
Runs at server startup via discover_serializers() in apps/api/src/unifi_api/serializers/_registry.py. Raises SerializerRegistryError if any manifest tool lacks both a serializer registration and a Strawberry type registration. After Phase 6, you need at least one — not necessarily both.
File: apps/api/tests/test_resource_route_coverage.py
Every read tool must have a registered GET resource route. When adding a read tool:
TOOL_ROUTE_OVERRIDES in that test file.
pytest apps/api/tests/test_resource_route_coverage.py locally.Import _reset_registry_for_tests from unifi_api.serializers._registry. It clears module-level registries AND evicts serializer submodules from sys.modules, forcing discover_serializers to re-import and re-run all decorators. Call in both setup and teardown.
from unifi_api.serializers._registry import _reset_registry_for_tests
def setup_function():
_reset_registry_for_tests()
def teardown_function():
_reset_registry_for_tests()apps/api/src/unifi_api/services/managers.py. Wrong name → ImportError.
.split(",") and filter empty strings. Inaccurate product_kinds causes UnknownProduct at request time.
need a real FastAPI route function registered in the app.
Six confirmed violation classes only surfaced via live hardware testing. See live-smoke-testing skill before cutting a PR.
unifi_mcp_sharedanywhere in apps/api/ is a build-time error.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.