unifi-cloud-fleet-tools
Skill294 starsChanged 18 days agoApache-2.0View on GitHub
Use when adding, changing or testing a cloud or multi-controller MCP tool (Site Manager, the connector proxy, the integration API tools, sites, Mobility, InnerSpace, Carrier Fabric) or the fleet stack (router, registry, pool, read cache, workers, fleet_* and admin tools) - covers API modes, gating, per-call routing, tokens and roles, test fixtures and known gaps.
- Reads credentials
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 0 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 50
Every routed tool gets a keyword-only controller argument.
- Very specific: line 90
Audit: Every mutating call writes a hash-chained attempt record and outcome record (src/utils/audit.py).
- Very specific: line 107
Client tests: use a real httpx client with httpx.MockTransport (tests/unit/api/test_site_manager_client.py).
- Very specific: line 108
Redis tests: use fakeredis (tests/unit/fleet/test_read_cache.py).
- Very specific: line 109
Postgres tests: start a real server with pgserver, with no Docker.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
UniFi Cloud and Fleet Tools
Cloud tools call https://api.ui.com (Site Manager and the connector proxy). Fleet code lets one server serve many controllers. Read this before you change either. For the general add-a-tool workflow, see unifi-mcp-tool-builder.
Module map
| Module | Tools | Examples |
src/tools/site_manager.py | 16 | list_all_sites_aggregated, get_isp_metrics, list_hosts |
src/tools/connector.py | 10 | connector_network_get, connector_protect_post |
src/tools/integration_api.py | 14 | list_integration_sites, list_integration_wans |
src/tools/sites.py | 3 | list_sites, get_site_details, get_site_statistics |
src/tools/mobility.py | 8 | list_mobility_workspaces, update_mobility_device |
src/tools/innerspace.py | 6 | list_innerspace_floor_plans (reads only) |
src/tools/carrier.py | 11 | create_carrier_subscriber, suspend_carrier_subscriber |
Fleet tools are built inside functions, not defined at module level, so the "every public async function is a tool" rule does not apply to them:
| Source | Tools | Registered when |
register_fleet_tools() in src/fleet/tools.py | list_controllers, select_controller, get_active_controller | always |
register_admin_tools() in src/fleet/admin_tools.py | 11, such as register_controller, create_api_token, verify_audit_chain | DATABASE_URL is set, and not read-only |
register_fleet_read_tools() in src/fleet/fleet_tools.py | fleet_health, fleet_sites, fleet_inventory, fleet_fanout | DATABASE_URL is set |
API modes and gating
APIType(src/config/config.py) islocal,cloud-v1orcloud-ea. The code default iscloud-ea.- Cloud modes register only
_CLOUD_TOOL_MODULES(src/main.py):sites,site_manager,connector,integration_api,carrier,innerspaceandmobility. Local mode registers these too. In cloud modeget_site_statisticsis left out, because its endpoints return 404 on the cloud API. That gives 71 tools, including the 4 fleet tools. - Local mode registers both lists, so the cloud tools are available there too.
- Site Manager tools need
UNIFI_SITE_MANAGER_ENABLED=true:site_managertools use therequire_site_managerdecorator;mobility,innerspaceandcarriercall their own_require_cloud_api(), which checkssite_manager_enabled;- the connector tools repeat the check inline.
- Paths:
SiteManagerClient(src/api/site_manager_client.py) uses the base URLhttps://api.ui.com/v1/and theX-API-KEYheader.- Connector calls go to
connector/consoles/{console_id}/proxy/{network|protect}/{path}. - InnerSpace goes through the connector proxy.
settings.get_integration_path()gives/v1/...for cloud-v1 and/integration/v1/...for cloud-ea.
- Profiles:
UNIFI_PROFILEhasmobility,innerspaceandcarriersets.
Per-call routing
Routing happens in the registry wrapper (_make_tool_wrapper() in src/tool_registry.py), not in tool bodies.
- Every routed tool gets a keyword-only
controllerargument. FleetRouter.resolve()(src/fleet/router.py) picks the controller in this order:- the explicit
controllerargument; - the session choice from
select_controller(state keySESSION_KEY); - the registry default.
- the explicit
- A write must name its target. With two or more controllers registered, a write that would fall back to the default raises
ValidationError. Never weaken this. - The tool receives per-controller
SettingsfromControllerProfile.apply_to()(src/fleet/registry.py). Only connection fields can differ; safety switches such as read-only stay process-wide. select_controlleris refused in stateless HTTP mode (MCP_STATELESS_HTTP).
Fleet stack
Registry. Without
DATABASE_URL,EnvRegistryholds one controller nameddefault. With it,PostgresRegistry(src/fleet/postgres_registry.py) serves lookups from memory and refreshes:- every
UNIFI_FLEET_REGISTRY_REFRESH_SECONDS; - on a Redis change event.
Stored credentials are encrypted with
UNIFI_FLEET_CREDENTIAL_KEY(src/fleet/credentials.py).- every
- Pool.
ClientPool(src/api/pool.py) keeps one HTTP client, one rate limiter and one circuit breaker per controller, up toUNIFI_FLEET_MAX_OPEN_CLIENTS. - Rate limit.
UNIFI_RATE_LIMIT_REQUESTSperUNIFI_RATE_LIMIT_PERIOD(src/api/rate_limit.py).- With Redis, the window is shared across replicas, and cloud calls share one window per API key.
- Without Redis, each process gets the limit divided by
UNIFI_FLEET_EXPECTED_REPLICAS.
- Read cache.
ReadCache(src/cache.py) needsUNIFI_CACHE_ENABLEDand an explicit Redis setting.- It caches only the modules in
CACHED_MODULES, which hold no cloud module. - Cached tools gain a
freshargument. - Any write that is not a dry run clears the controller's cached reads.
- It caches only the modules in
- Workers.
python -m src.workerruns health and sync jobs (src/worker/jobs.py). Controllers are split between workers by rendezvous hashing and Redis leases (src/worker/shards.py). - Fan-out.
fleet_fanoutruns only read-tier tools that take acontrollerargument, and returns one{ok, data|error}entry per controller. Writes are never fanned out. The limits areUNIFI_FLEET_FANOUT_CONCURRENCY,UNIFI_FLEET_FANOUT_TIMEOUT_SECONDSandUNIFI_FLEET_FANOUT_DEADLINE_SECONDS. - Install. The
fleetextra inpyproject.tomladds redis, SQLAlchemy, asyncpg and alembic.
Auth, roles and audit
- Network transports refuse to start without auth (
ensure_network_transport_authenticated()insrc/main.py).MCP_AUTH_TOKENholds static bearer tokens.- With a database,
FleetTokenVerifierchecks server-issued tokens (src/fleet/tokens.py). They start withumcp_, are stored only as SHA-256 hashes, and can be limited by role, module and controller label.
Roles (
ROLE_TIERSinsrc/access.py):- viewer: read
- operator: read and write
- admin: adds destructive
- fleet-admin: everything
stdio callers and
MCP_AUTH_TOKENcallers are fleet-admin, unlessUNIFI_STDIO_ROLElowers the stdio role.- Audit. Every mutating call writes a hash-chained attempt record and outcome record (
src/utils/audit.py). The call is refused if the write fails whileUNIFI_AUDIT_FAIL_CLOSEDis on. Verify a log withpython -m src.utils.audit_verify.
Write tools
- Signature:
confirm: bool | str = False, dry_run: bool | str = False. Callvalidate_confirmation(confirm, "<operation>", dry_run)first, and return{"dry_run": True, "operation": ..., "payload": ...}before you open a client (seesrc/tools/mobility.py). DESTRUCTIVE_TOOLS(src/tool_registry.py) already holds all 8 mutating connector tools andsuspend_carrier_subscriber. Carrier writes have the largest blast radius; keep them confirm-gated.- A risky word in a tool name needs an entry in
DESTRUCTIVE_TOOLSor inRISKY_NAME_EXEMPTIONS(tests/unit/fleet/test_tiers.py). Regenerate the snapshot withUPDATE_TOOL_TIERS=1 pytest tests/unit/fleet/test_tiers.py.
Models and responses
- Models:
src/models/site_manager.py,src/models/mobility.py,src/models/innerspace.py,src/models/carrier.py,src/models/integration_api.py. They usepopulate_by_name=True,extra="allow"and camelCase aliases. - List tools return
{"count", "data"}. Integration API lists also returnoffset,limitandtotalCount. - Many
site_managertools return raw dicts or lists.
Tests
- Tool tests patch the client:
patch("src.tools.carrier.SiteManagerClient", return_value=cm). Herecmis anAsyncMockcontext manager, and the settingsMagicMockhassite_manager_enabled=True. Representative:tests/unit/tools/test_carrier.py. Assert the endpoint string and the gate error. - Client tests use a real httpx client with
httpx.MockTransport(tests/unit/api/test_site_manager_client.py). - Redis tests use
fakeredis(tests/unit/fleet/test_read_cache.py). - Postgres tests start a real server with
pgserver, with no Docker. The fixtures are intests/unit/fleet/conftest.py. make verify-fleet-scaleruns 3 replicas and 2 workers locally.
Gotchas
- A docs test requires every setting to be listed in
API.mdand.env.example. Add both when you add a setting. - Spec snapshots for Mobility, InnerSpace and Carrier Fabric are in
scripts/. Upstream hosts only the current version, so keep old snapshots when you need a diff. - MCP resources still read the default controller, not the routed one.
- Stateful HTTP behind a load balancer needs sticky routing on
mcp-session-id. - Tool modules are chosen once, from the base
UNIFI_API_TYPE. A registered controller with a different API type still gets that tool set.
Known defects (not fixed)
SiteManagerClienthardcodes its base URL. It ignoresUNIFI_CLOUD_API_URL, and it does not use the pool or the rate limiter.- The connector
pathargument is checked only for empty values and a leading slash.
In this repository52 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .agents/skills/fastmcp/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- .claude/skills/fastmcp/CLAUDE.mdCLAUDE.md
- .cursor/rules/common-mistakes.mdcCursor rule
- .cursor/rules/core-principles.mdcCursor rule
- .cursor/rules/environment-setup.mdcCursor rule
- .cursor/rules/mcp-tools.mdcCursor rule
- .cursor/rules/project-context.mdcCursor rule
- .cursor/rules/README.mdCursor rule
- .cursor/rules/unifi-api.mdcCursor rule
- .cursor/rules/workflow.mdcCursor rule
- FastMCP DevelopmentSkill
- mcp-builderSkill
- source-command-build-dockerSkill
- source-command-check-depsSkill
- source-command-cleanSkill
- source-command-docsSkill
- source-command-formatSkill
- source-command-lintSkill
- source-command-prSkill
- source-command-securitySkill
- source-command-setupSkill
- source-command-testSkill
- source-command-unifi-api-docs-update-2Skill
- source-command-unifi-api-docs-updateSkill
- source-command-unifi-mcp-add-toolSkill
- source-command-unifi-mcp-release-prepSkill
- source-command-unifi-mcp-test-coverageSkill
- source-command-unifi-mcp-update-docsSkill
- unifi-cloud-fleet-toolsSkill
- unifi-mcp-tool-builderSkill
- unifi-network-config-toolsSkill
- unifi-protect-toolsSkill
- unifi-security-toolsSkill
- FastMCP DevelopmentSkill
- mcp-builderSkill
- unifi-cloud-fleet-toolsSkill
- unifi-mcp-tool-builderSkill
- unifi-network-config-toolsSkill
- unifi-protect-toolsSkill
- unifi-security-toolsSkill
- unifiSkill
- unifi-channel-planningSkill
- unifi-devicesSkill
- unifi-mcp-tool-builderSkill
- unifi-network-config-toolsSkill
- unifi-networkSkill
- unifi-protect-toolsSkill
- unifi-securitySkill
- unifi-security-toolsSkill
- unifi-systemSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

