Skip to content
Agentmart
Submit a tool

AGENTS.md · sirkirby/unifi-mcp

AGENTS.md from sirkirby/unifi-mcp: 354 lines covering Documentation scope, Non-Goals, Architecture Rules, Permission System and more.

Template by sirkirby

IndexedMIT★ 945
Write a review

Chalk Labs

Need help deploying AGENTS.md · sirkirby/unifi-mcp?

Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.

Book a call

Install

Works with Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Claude Code. Read the repository before you give any tool access to your data.

curl -fsSL https://raw.githubusercontent.com/sirkirby/unifi-mcp/main/AGENTS.md -o AGENTS.md

Read it and adapt it to your project before use.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

The AGENTS.md file that sirkirby/unifi-mcp (945 stars) keeps for coding agents. It has 354 lines in 8 sections: Documentation scope; Non-Goals; Architecture Rules; Permission System; Golden Paths; Quality Gates; Patterns; Myco Managed Guidance. Use it as a model for your own project's instructions.

Use cases: documentation

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

What's in it

An AGENTS.md file: instructions coding agents (Codex, Cursor, Copilot, Gemini CLI and others) read before working in a repo.

For
Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Claude Code
Length
354 lines
File
AGENTS.md

Sections

  1. Documentation scope
  2. Non-Goals
  3. Architecture Rules
  4. Permission System
  5. Golden Paths
  6. Quality Gates
  7. Patterns
  8. Myco Managed Guidance

First lines (shown under the repo's MIT license)

# Project Rules

## Documentation scope

Commit user-facing documentation and maintained contributor/agent instructions only.
Keep internal plans, research notes, review/audit reports, execution records, and
release checklists in Myco or private storage outside the checkout. These working
documents MUST NOT be committed to Git. Before committing documentation, check its
audience and remove references to any working documents moved out of the repository.

## Non-Goals

This project is **not**:

- a general-purpose network management framework
- a UniFi controller replacement or admin dashboard
- a real-time monitoring or alerting service
- a CI/CD pipeline runner or deployment tool
- a web application with a frontend UI (dev consoles are debugging REPLs only)
- a database or persistent storage layer (all state lives on UniFi controllers)

## Architecture Rules

### Layering

- Tool functions MUST NOT contain business logic beyond argument validation and response formatting
- Tool functions MUST delegate to manager methods for all controller interactions
- Manager methods MUST NOT import from `<app>.tools` (no circular dependencies)
- All controller communication MUST flow through `ConnectionManager`
- Tool modules MUST import singletons from `<app>.runtime`, never instantiate directly
- Shared packages MUST NOT import from app packages (dependency flows downward only)
- App packages MUST NOT import from each other (no cross-app dependencies)

### Singletons

- All shared objects (server, config, managers) MUST be created via `@lru_cache` factories in `<app>/runtime.py`
- Tests MUST monkey-patch the factory or alias before importing tool modules
- There MUST be exactly one `ConnectionManager` instance per server process

### Tool Response Contract

All tools MUST return `Dict[str, Any]`:

```python
{"success": True, "data": <result>}                                    # Success
{"success": False, "error": "<specific, actionable message>"}          # Error
{"success": True, "requires_confirmation": True, "preview": <payload>} # Mutation preview
```

- Exceptions MUST NOT escape tool functions. Catch, log with `exc_info=True`, return error dict.
- Error messages MUST include the operation that failed (e.g., `"Failed to list devices: ..."` not just `str(e)`).
- Raw tracebacks MUST NOT be exposed to MCP clients.

### Confirmation System

All state-changing tools MUST implement preview-then-confirm:
- `confirm=False` (default): validate input, return preview payload
- `confirm=True`: execute the mutation on the controller
- Bypass mode injects `confirm=True` automatically
- **Anchor:** `packages/unifi-mcp-shared/src/unifi_mcp_shared/confirmation.py`

### MCP Tool Annotations

All tools MUST include `annotations=ToolAnnotations(...)` in `@server.tool()`:
- Read-only: `readOnlyHint=True, openWorldHint=False`
- Mutating: `readOnlyHint=False, destructiveHint=<bool>, idempotentHint=<bool>, openWorldHint=False`
- `destructiveHint=True` for delete, block, reboot, revoke operations
- `idempotentHint=True` for update/rename (same args = same result)
- All tools: `openWorldHint=False` (closed UniFi controller domain)

### Async

- All I/O-bound operations MUST use `async`/`await`
- No synchronous blocking calls in tool implementations or managers
- `asyncio.run()` MUST NOT be called from within an async context

### Logging

- Network client/device managers and tools log operation context and exception class only; never log MAC/IP addresses, names, update payloads, exception messages, or tracebacks. This is a narrow privacy exception to the ordinary tool `exc_info=True` rule because controller exceptions can embed those values.
- Network connection authentication/refresh failure logs and system settings update failure logs follow the same operation-and-exception-class pattern. Do not log sanitized exception text or controller response bodies at these boundaries: configured/submitted-secret scrubbing cannot cover controller-only secrets or opaque exception renderers.
- Carry this privacy rule through the auto-backup settings read/preview/update and DPI refresh caller chains, including manager logs and MCP error responses. Catching a safely logged but re-raised exception must not reintroduce its message or traceback.
- Cached Network authentication failures contain exception class and fixed guidance only, since later tool calls may log them. ConnectionManager settings request failure logs omit controller text and tracebacks before the exception reaches a domain manager.
- ConnectionManager translates failed `/get/setting/` and `/set/setting/` requests into a new `RequestError` with fixed operation context and the original exception class, suppressing original traceback context. This protects all settings callers, including those that log the received exception.
- StatsManager DPI refresh failures use the same safe-error translation before leaving the core manager; REST and GraphQL bypass the MCP tool wrapper.
- ConnectionManager handler refresh failures are translated to safe RequestError instances after reauthentication/circuit handling. All collection callers, including clients and devices, must receive safe text and suppressed original traceback context.

- All log output MUST go to stderr (stdout is reserved for JSON-RPC in stdio mode)
- Use `%s` format strings in logger calls, not f-strings, for lazy evaluation
- Configuration errors SHOULD fail fast at startup with clear guidance
- Privacy-boundary support-bundle code is the narrow exception to the ordinary tool `exc_info=True` rule: it MUST NOT log the original exception, traceback, arguments, raw collected payload, or returned bundle. It may log only fixed allowlisted fields such as probe enum, normalized error category, duration bucket, and an independently generated correlation ID.

### Hard Bans

- Hardcoding host, port, credentials, or feature flags in Python source is **banned** — use `config.yaml` with `${oc.env:VAR,default}` or `UNIFI_`-prefixed env vars
- Permission category strings MUST be defined in `<app>/categories.py` (`NETWORK_CATEGORY_MAP`, etc.)
- Tool-to-module mappings MUST be in `TOOL_MODULE_MAP` in `<app>/categories.py`
- Product-specific validation models MUST be pydantic BaseModel classes in `packages/unifi-core/src/unifi_core/<server>/models/<domain>.py` — never inline JSON schema dicts in tool functions or app-local schemas modules
- Cross-product privacy contracts that use one discriminated schema for Network, Protect, and Access MUST remain a single closed model in a dedicated root `unifi-core` domain module (currently `unifi_core/support_bundle.py`); do not duplicate that contract under each server
- No monkey-patches in production code
- Interpolating a caller-supplied id into a Network controller path without `unifi_core.request_paths.object_id` is **banned**. `ConnectionManager.request` also refuses dot-segment, percent-encoded and fragment paths, but that is the backstop, not the check: an id must be exactly one path segment.
- One-shot support probes MUST use the request-scoped middleware in `unifi_core/support_transport.py` to prevent aiohttp's internal GET retry. Never toggle shared-session retry settings; keep transport behavior out of the pure support-bundle privacy kernel.
- **Anchor:** `apps/network/src/unifi_network_mcp/config/config.yaml`

### API Surface Boundaries

UniFi exposes three API surfaces with disjoint ID/auth models: the V2 controller API (session cookie auth, Mongo ObjectIDs, `snake_case` fields), the public Integration API (`X-API-Key` auth, UUIDs, `camelCase` fields), and the UniFi-OS **Alarm Manager v2** service (`/api/v2/alarms/`, session + **SuperAdmin** auth, UUIDs, `snake_case` fields). They are **not interchangeable**.

Authentication credentials and API surfaces are separate concepts. Network 10.6.106
also accepts API keys on verified legacy inventory GET endpoints, preserving legacy
IDs and fields. Detect that capability at runtime; do not assume it on every
controller or infer write/websocket support from successful reads.

### Authentication routing golden path

- ConnectionManagers own credential availability and transport selection. Reuse
  the shared authentication status contract in `unifi_core.auth`; retain each
  product's SDK/lifecycle implementation.
- Network preserves working session behavior when both credentials are configured;
  Access retains its existing per-operation preference. A failed session must not
  block an independently usable API-key path. Protect public setters still require
  session bootstrap, so they are not an independent key-only startup path.
- Choose a route before an operation. Never replay an uncertain mutation using
  another credential or API surface. Require a session for unvalidated key-backed
  legacy writes, including direct SDK callers.
- Keep tool auth metadata accurate (`local_only`, `api_key_only`, `either`, or
  `both`); describe field-dependent requirements. Metadata is discovery, not
  enforcement. Core manager checks apply equally to MCP and REST/GraphQL/actions.
- Public-only inventory must label its source and incomplete field/collection
  coverage. Do not fabricate legacy IDs or default unknown values to false/zero.
  Preserve MAC-based identity where verified; public UUIDs are separate fields.
- Anchors: Access `ConnectionManager.initialize` and `DoorManager.list_doors`;
  Protect `require_public_api_key` and `validate_public_id_portability`.

### API tool families

- **Tool families share an ID space.** Tools whose returned IDs and accepted IDs are mutually portable form a *family*. Cross-family ID use is prohibited.
- **Tool descriptions MUST scope IDs** when they could be confused with another family's IDs. Standard formula: *"These IDs are scoped to the <family> tool family — do not pass them to other <resource> tools."* This applies to MCP tool descriptions, GraphQL type/query descriptions, and REST route descriptions alike.
- **Integration API tools MUST require an API key** and fail with a clear remediation message when it is missing. The auth check belongs in the manager so every tool in the family inherits it.
- **Alarm Manager v2 (`/api/v2/alarms/`) requires a SuperAdmin credential.** Reach it via `uiprotect`'s `api_request(..., api_path="/api/v2/alarms/")` (no bespoke client). A Protect-scoped account returns 403 — managers MUST map this to an actionable "requires SuperAdmin" error so callers/agents self-diagnose. Blast radius is topology-dependent: broad on a combined UDM console, contained to Protect on a standalone UNVR.
- **Silent cross-API ID translation is banned** unless the mapping is 1:1, stable, and verified against live data. Zones are grandfathered (1:1 by name, stable, unique). Policies are not (membership diverges, no bridge field, V2 has duplicate names).
- **Bridging tools must be explicit.** If you need to cross ID namespaces, write a named bridging tool (`unifi_resolve_<resource>_id_for_<family>`) with documented failure modes. Never bury the bridge inside another tool.

**Enforcement:** family scoping is currently *guidance* — descriptions + this rule. There is no runtime or type-level check. Cross-family ID misuse fails at the controller (e.g., 400 from the integration API) and surfaces as a tool error. Stronger enforcement (typed IDs, runtime regex checks, manifest family field) is on the table if and when the integration-API surface grows beyond the current single family.

**Reference family (Integration API):** `unifi_get_firewall_policy_ordering` + `unifi_reorder_firewall_policies` in `apps/network/src/unifi_network_mcp/tools/firewall.py`.

**Skill:** `add-integration-api-tool` (full procedure for contributors).

### Plugin Bundles

- Plugin MCP configs launch the server over stdio only. Never set `UNIFI_MCP_HTTP_ENABLED` or `UNIFI_MCP_HTTP_FORCE` in a plugin, and keep `http.force` defaulting to `false` in every app `config.yaml`: forcing HTTP binds a listener from client-launched (non-PID-1) processes.
- Shell scripts under `plugins/` MUST run on macOS `/bin/bash` 3.2 (no `declare -A`, `mapfile` or `readarray`). Setup scripts ship a PowerShell equivalent for Windows.
- Declare Claude Code's MCP server inline in `.claude-plugin/plugin.json`; Codex and OpenClaw run `.mcp.codex.json`. Never ship a root `.mcp.json`: OpenClaw merges it into the server it runs, and its `${user_config.*}` templates arrive as literal values.
- Plugin skill directory names MUST be unique across all bundles and distinct from `.agents/skills/` names: standalone skill installers place skills in one flat directory.

## Permission System

Two concepts:

**Permission Mode** — controls mutation handling:

Signals

Stars
945
Forks
132
Open issues
12
Contributors
47
Last commit (this folder)
Oct 13, 2026 (18 days ago)
Last release
Oct 22, 2026 (8 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from sirkirby

Done for you

Rather not set it up yourself?

Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.

See how we build AI agents →Read the case studies

Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.

launches shipped
50+
launches shipped
media placements
500+
media placements
founders on every account
2
founders on every account

Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block

Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.

Get AGENTS.md · sirkirby/unifi-mcp running in your business

Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.

Goes to the Chalk Labs founders, not the newsletter. Privacy

Need help deploying AGENTS.md · sirkirby/unifi-mcp?

CallGet help
Cite this page

AGENTS.md · sirkirby/unifi-mcp. Agent Mart. https://agentmart.dev/templates/sirkirby-unifi-mcp-agents-md (last updated 2026-10-24)