{
  "markdown": "# UniFi MCP Server — Queryable API Documentation\n\n<!-- Ownership token for the MCP registry: it fetches this README as the PyPI\n     long_description and looks for this exact token. Do not remove. -->\n<!-- mcp-name: io.github.KallistoX/mcp-unifi-applications -->\n\n![CI](https://github.com/KallistoX/mcp-unifi-applications/actions/workflows/ci.yml/badge.svg)\n[![PyPI](https://img.shields.io/pypi/v/mcp-unifi-applications)](https://pypi.org/project/mcp-unifi-applications/)\n[![Python](https://img.shields.io/pypi/pyversions/mcp-unifi-applications)](https://pypi.org/project/mcp-unifi-applications/)\n[![Glama](https://glama.ai/mcp/servers/KallistoX/mcp-unifi-applications/badges/score.svg)](https://glama.ai/mcp/servers/KallistoX/mcp-unifi-applications)\n\nA Model Context Protocol (MCP) server that makes the official [UniFi API documentation](https://developer.ui.com) queryable by AI agents — endpoint search, schema drill-down, and code examples in five languages, for Claude Desktop, Claude Code (VS Code / JetBrains), or any MCP-compatible client.\n\nCovers every application Ubiquiti publishes API docs for: **Network, Protect, Site Manager, InnerSpace, Mobility and Carrier Fabric**.\n\nIt is **read-only and credential-free**: it serves documentation, it does not talk to your controller. Includes a Playwright-based scraper that turns the JS-rendered docs SPA into structured JSON, and a Python MCP server that serves it.\n\n## Example\n\n> *\"Without any context, just by using the unifi-applications MCP Server: can you\n> tell me how to create a network with Go with the network API from UniFi, and\n> what options do I have regarding the managed IPv4 DHCP gateway configuration?\"*\n\n<p align=\"center\">\n  <img src=\"screenshots/mcp_init.png\" width=\"300\" alt=\"unifi-applications MCP server connected in Claude Code\">\n  <br><br>\n  <img src=\"screenshots/prompt.png\" width=\"640\" alt=\"The prompt typed into Claude Code\">\n</p>\n\nClaude works that out through the server, with none of the documentation in its\ncontext to begin with:\n\n| Call | What comes back |\n|---|---|\n| `search_endpoints(\"create network\")` | `POST /v1/sites/{siteId}/networks` |\n| `get_endpoint(\"network/createnetwork\")` | the request body — `management` is a discriminated union |\n| `get_field_schema(…, \"management[GATEWAY].ipv4Configuration.dhcpConfiguration\")` | **only** the DHCP subtree |\n| `get_example(\"network/createnetwork\", \"go\")` | a working request in Go |\n\nThe third call is the one that earns the server its place. It returns this, and\nnothing else — not the 70 KB endpoint schema it is buried in:\n\n```text\n# management[GATEWAY].ipv4Configuration.dhcpConfiguration (in requestBody)\n- dhcpConfiguration: object (Gateway Managed IPv4 DHCP Configuration) — IPv4 DHCP\n  configuration for this network. If omitted or null, DHCP is not working and hosts\n  must get an address statically or from another server in this broadcast domain.\n  - mode: string (required)\n    [RELAY]:\n      - dhcpServerIpAddresses: Array of string — DHCP Server IP addresses\n    [SERVER]:\n      - ipAddressRange: object\n        - start: string (required)\n        - stop: string (required)\n      - leaseTimeSeconds: integer — The lease time in seconds for addresses in this range.\n      - dnsServerIpAddressesOverride: Array of string — List of DNS servers assigned to\n        client devices by the DHCP server. If none are specified, they will be selected\n        automatically.\n      - gatewayIpAddressOverride: string — Gateway IP address provided to DHCP clients.\n      - domainName: string — Domain name that can be used to access network in the browser.\n      - option43Value: string — Custom DHCP option (43) — the value MUST be the UniFi\n        Network application's host IP address.\n      - pxeConfiguration: object — Pre execution environment configuration for network boot\n      … ntpServerIpAddresses, tftpServerAddress, timeOffsetSeconds, wpadUrl,\n        winsServerIpAddresses, pingConflictDetectionEnabled\n```\n\nBoth discriminator variants, every field typed and described. That is the answer\nto *\"what are my options\"* — and the model writes the Go from it without ever\nhaving seen the UniFi docs.\n\n\n## Quick Start\n\n### 1. Install\n\n```bash\npip install mcp-unifi-applications\n```\n\nThe scraped docs ship inside the package — there is nothing to scrape and no\nAPI key to configure. What you get:\n\n<!-- docs-versions:start -->\n| Application | API version | Scraped | Pages |\n|---|---|---|---|\n| Network | v10.4.57 | 2026-09-10 | 82 |\n| Protect | v7.3.47 | 2026-09-10 | 81 |\n| Site Manager | v1.0.0 | 2026-09-10 | 12 |\n| InnerSpace | v1.3.23 | 2026-09-10 | 12 |\n| Mobility | v1.0.0 | 2026-09-10 | 9 |\n| Carrier Fabric | v1.0.0 | 2026-09-10 | 14 |\n<!-- docs-versions:end -->\n\n<details>\n<summary>From source instead</summary>\n\n```bash\ngit clone https://github.com/KallistoX/mcp-unifi-applications.git\ncd mcp-unifi-applications\npython -m venv .venv\nsource .venv/bin/activate  # or: source .venv/bin/activate.fish\npip install .\n```\n\n</details>\n\n### 2. Register with your client\n\n**Claude Code (VS Code / JetBrains)** — add `.mcp.json` to your project root (Reload Window after):\n\n```json\n{\n  \"mcpServers\": {\n    \"unifi-docs\": {\n      \"type\": \"stdio\",\n      \"command\": \"mcp-unifi-applications\"\n    }\n  }\n}\n```\n\n**Claude Desktop** — add to `~/.config/Claude/claude_desktop_config.json` (Linux) or\n`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):\n\n```json\n{\n  \"mcpServers\": {\n    \"unifi-docs\": {\n      \"command\": \"mcp-unifi-applications\"\n    }\n  }\n}\n```\n\nIf the command is not on your client's `PATH` — Claude Desktop often does not\ninherit a shell `PATH` — give the absolute path instead, e.g.\n`/path/to/.venv/bin/mcp-unifi-applications`.\n\n\n## How this differs from other UniFi MCP servers\n\nMost UniFi MCP servers are **control planes**: they authenticate against your controller and expose tools that read and change live state — devices, clients, firewall rules. This one is a **knowledge plane**. It never sees your network.\n\n|  | Control-plane servers | This server |\n|---|---|---|\n| Needs controller credentials | Yes | No |\n| Touches live network state | Yes | No |\n| Answers \"what does this endpoint accept?\" | Rarely | That is the whole job |\n| Useful before you have hardware | No | Yes |\n\nThey are complements, not competitors. Pair this one with a control-plane server when you are *building* against the UniFi API: this one tells the model what the API looks like, the other one calls it.\n\n### Why not just feed the model the OpenAPI spec?\n\nUbiquiti does publish one — `developer.ui.com/<app>/v<version>/openapi.json`. It is a good spec, and it is missing exactly the parts an agent needs most. For Network v10.4.57 (44 paths, 73 operations, 379 schemas):\n\n- **0 code examples.** No `x-codeSamples` anywhere. This server carries ten per endpoint — curl, Go, Node.js, Python and Ansible, each in a local and a remote variant.\n- **0 response examples.** This server ships the rendered response sample for every endpoint.\n- **No guide pages.** Filtering syntax, error handling, getting started — those live only in the rendered docs.\n\nAnd a 409 KB spec does not fit usefully into a context window. `get_field_schema` returns one field subtree (`management[GATEWAY].dhcpV4`) instead of a 70 KB endpoint schema, so the model pulls in what it needs and nothing else.\n\n## Supported Applications\n\n| Application | URL | Local/Remote | Notes |\n|---|---|---|---|\n| Network | `developer.ui.com/network` | Both | Default app |\n| Protect | `developer.ui.com/protect` | Both | |\n| Site Manager | `developer.ui.com/site-manager` | Remote only | No local/remote switch |\n| InnerSpace | `developer.ui.com/innerspace` | Both | Early Access; version label reads `v1.3.23 (EA)` |\n| Mobility | `developer.ui.com/mobility` | Remote only | Sidebar links out to `mobility.ui.com` |\n| Carrier Fabric | `developer.ui.com/carrier-fabric` | Remote only | Subscriber API |\n\nAll applications share the same docs SPA structure with version dropdowns, endpoint pages, and guide pages, so adding one is a single entry in the `APPS` object in `scrape.mjs` — the server discovers new app directories on its own.\n\n## Available Tools\n\n| Tool | Description |\n|---|---|\n| `list_endpoints` | List all API endpoints, optionally filtered by HTTP method or app |\n| `search_endpoints` | Fuzzy search by name, path, method, or description (filterable by app) |\n| `get_endpoint` | Full schema for an endpoint (summary or raw JSON) |\n| `get_endpoint_group` | All CRUD operations for a resource (e.g. \"networks\") |\n| `get_example` | Code examples in curl, Go, Node.js, Python, or Ansible (local/remote) |\n| `get_response_sample` | Example JSON response for an endpoint |\n| `find_field` | Search for a field name across all endpoint schemas |\n| `get_field_schema` | Drill into a specific field's subtree (e.g. `management[GATEWAY].dhcpV4`) |\n| `get_guide` | API guide pages (filtering syntax, error handling, getting started) |\n| `get_docs_info` | Which docs are loaded: API version, scrape date, endpoint/guide counts per app |\n\nTools that return multiple results accept an optional `app` parameter (`network`, `protect`, `site-manager`, `innerspace`, `mobility`, `carrier-fabric`) to filter by application.\n\n## Environment Variables\n\n| Variable | Default | Description |\n|---|---|---|\n| `DOCS_DIR` | the `docs/` directory inside the installed package | Directory containing scraped JSON docs. Expects one subdirectory per application (`network/`, `protect/`, …). Set it to point at a checkout's freshly scraped output. |\n\n## Re-scraping the docs\n\nOnly needed to pull a newer API version before the weekly workflow does, or to\nadd an application.\n\n<details>\n<summary>Docker commands (needs Playwright/Chromium)</summary>\n\n```bash\n# Build the scraper image\ndocker build -t unifi-scraper .\n\n# Scrape Network API docs (default, latest version)\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs\n\n# Scrape Protect API docs\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs --app protect\n\n# Scrape Site Manager API docs\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs --app site-manager\n\n# Scrape InnerSpace API docs\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs --app innerspace\n\n# Scrape Mobility or Carrier Fabric API docs\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs --app mobility\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs --app carrier-fabric\n\n# Scrape a specific API version\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs --app network --version v9.5.21\n\n# List available API versions for an app\ndocker run --rm unifi-scraper node scrape.mjs --app protect --list-versions\n\n# Scrape specific pages only\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs createnetwork filtering\n\n# Force re-scrape (overwrite existing files)\ndocker run --rm -v \"$(pwd)/src/mcp_unifi_applications/docs:/output\" unifi-scraper node scrape.mjs --force\n```\n\n</details>\n\n## Scraper CLI\n\n<details>\n<summary>Options and arguments</summary>\n\n```\nnode scrape.mjs [options] [slug...]\n\nOptions:\n  --app <name>      Application: network (default), protect, site-manager, innerspace, mobility, carrier-fabric.\n  --version <ver>   API version to scrape (e.g. v10.1.84). Default: latest.\n  --list-versions   Print available versions and exit.\n  --force           Re-scrape even if output file exists.\n\nArguments:\n  [slug...]         Scrape only these pages. Omit to scrape all pages.\n```\n\nThe slug is the last path segment of the docs URL:\n`https://developer.ui.com/network/v10.1.84/createnetwork` -> `createnetwork`\n\nOutput is written to `<output>/<app>/` — mount the package's docs directory (`src/mcp_unifi_applications/docs/`) so a scrape lands where the server reads it.\n\nThe full scan is resumable - already-scraped pages are skipped. Use `--force` to re-scrape.\n\n</details>\n\n## Project Structure\n\n<details>\n<summary>Repository layout</summary>\n\n```\nmcp-unifi-applications/\n├── scrape.mjs          # Playwright scraper (runs in Docker)\n├── lib/\n│   └── parse.mjs       # Scraper parsing logic, kept out of the browser so it is testable\n├── Dockerfile          # Scraper container image\n├── pyproject.toml      # Python project config\n├── server.json         # MCP registry manifest\n├── CHANGELOG.md        # Keep a Changelog\n├── ROADMAP.md          # What is planned, what is not, and why\n├── glama.json          # Glama maintainer declaration\n├── src/\n│   └── mcp_unifi_applications/\n│       ├── server.py   # MCP server (Python, stdio transport)\n│       └── docs/       # Scraped JSON, shipped with the package\n│           ├── network/\n│           ├── protect/\n│           ├── site-manager/\n│           ├── innerspace/\n│           ├── mobility/\n│           └── carrier-fabric/\n├── scripts/\n│   └── update_readme_versions.py   # Regenerates the README version table (run on main by CI)\n└── tests/\n    ├── test_mcp_server.py    # pytest\n    └── scrape-parse.test.mjs # node --test\n```\n\n</details>\n\n## Output Format\n\nWhat the scraper writes, and what the server reads.\n\n<details>\n<summary>Endpoint pages, guide pages, and the recursive field object</summary>\n\n### Endpoint pages\n\n```json\n{\n  \"h1\": \"Create Network\",\n  \"method\": \"POST\",\n  \"path\": \"/v1/sites/{siteId}/networks\",\n  \"description\": \"Create a new network on a site.\",\n  \"pathParameters\": [ \"...fields\" ],\n  \"requestBody\": [ \"...fields\" ],\n  \"responses\": [{ \"statuses\": [\"201\"], \"fields\": [ \"...fields\" ] }],\n  \"examples\": {\n    \"local\": { \"curl\": \"...\", \"go\": \"...\", \"nodejs\": \"...\", \"python\": \"...\", \"ansible\": \"...\" },\n    \"remote\": { \"curl\": \"...\", \"go\": \"...\", \"nodejs\": \"...\", \"python\": \"...\", \"ansible\": \"...\" }\n  },\n  \"responseSample\": \"{ ... }\",\n  \"sourceUrl\": \"https://developer.ui.com/network/v10.1.84/createnetwork\"\n}\n```\n\n### Guide pages\n\n```json\n{\n  \"h1\": \"Filtering\",\n  \"type\": \"guide\",\n  \"content\": \"Markdown content...\",\n  \"sourceUrl\": \"https://developer.ui.com/network/v10.1.84/filtering\"\n}\n```\n\n### Field objects (recursive)\n\n```json\n{\n  \"name\": \"management\",\n  \"required\": true,\n  \"type\": \"string\",\n  \"description\": null,\n  \"discriminator\": [\n    { \"value\": \"UNMANAGED\", \"selected\": true, \"schema\": [ \"...sibling fields\" ] },\n    { \"value\": \"GATEWAY\", \"selected\": false, \"schema\": [ \"...sibling fields\" ] }\n  ],\n  \"children\": [ \"...child fields for object types\" ]\n}\n```\n\n- `discriminator.schema` contains sibling fields visible when that option is active (not the discriminator field itself)\n- Nesting is recursive - discriminators within variants are fully expanded\n- `children` captures statically expanded object fields\n\n</details>\n\n## Disclaimer\n\nThis project is not affiliated with, endorsed by, or sponsored by Ubiquiti Inc. The API documentation content scraped and served by this tool is the property of [Ubiquiti Inc.](https://ui.com) and is sourced from their public [developer portal](https://developer.ui.com). \"UniFi\" is a trademark of Ubiquiti Inc.\n\n## License\n\nMIT\n",
  "bytes": 15272,
  "sha": "0c71dfa0642f9accef58bf0fec6c3151ffaec1ebba85d266694f76f848610931",
  "repo_slug": "kallistox/mcp-unifi-applications",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_kallistox_mcp_unifi_applicatio_f470c804/readme"
}