{
  "markdown": "# Gotify MCP\n\n<!-- mcp-name: tv.tootie/gotify-mcp -->\n\n[![PyPI](https://img.shields.io/pypi/v/gotify-mcp)](https://pypi.org/project/gotify-mcp/) [![ghcr.io](https://img.shields.io/badge/ghcr.io-jmagar%2Fgotify--mcp-blue?logo=docker)](https://github.com/jmagar/gotify-mcp/pkgs/container/gotify-mcp)\n\nMCP server for self-hosted Gotify. Exposes a unified `gotify` action router and a `gotify_help` companion tool for sending notifications and managing Gotify messages, applications, clients, and account metadata.\n\n## Overview\n\nTwo MCP tools are exposed:\n\n| Tool | Purpose |\n| --- | --- |\n| `gotify` | Unified action router for all Gotify operations |\n| `gotify_help` | Returns markdown documentation for all actions and parameters |\n\nThe server supports HTTP (default) and stdio transports. HTTP transport requires bearer authentication via `GOTIFY_MCP_TOKEN`.\n\n## What this repository ships\n\n- `gotify_mcp/server.py`: FastMCP server, action router, and BearerAuth middleware\n- `gotify_mcp/services/gotify.py`: Async HTTP client for the Gotify REST API\n- `skills/gotify/SKILL.md`: Client-facing skill documentation\n- `docs/gotify-api.json`: Bundled upstream Gotify API reference\n- `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`: Client manifests\n- `docker-compose.yml`, `Dockerfile`, `entrypoint.sh`: Container deployment\n- `scripts/`: Smoke tests and contract checks\n\n## Tools\n\n### `gotify`\n\nSingle entry point for all Gotify operations. Select the operation with the `action` parameter.\n\n```python\ngotify(action=\"send_message\", app_token=\"AbCdEf\", message=\"Build finished\", priority=5)\n```\n\n### `gotify_help`\n\nReturns the full action reference as Markdown. Call this to discover available actions.\n\n```python\ngotify_help()\n```\n\n## Actions\n\n### `send_message`\n\nSend a push notification. Requires an `app_token` — this is the per-application token, not the client token.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `app_token` | string | yes | — | Application token from Gotify UI (Settings > Apps) |\n| `message` | string | yes | — | Notification body. Supports Markdown when `extras` sets `contentType`. |\n| `title` | string | no | — | Notification title |\n| `priority` | integer | no | app default | Priority 0–10. See Priority Levels below. |\n| `extras` | dict | no | — | Extended metadata. See Extras Structure below. |\n\nResponse fields:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `id` | integer | Assigned message ID |\n| `appid` | integer | Application ID that sent the message |\n| `message` | string | Message body |\n| `title` | string | Message title |\n| `priority` | integer | Effective priority |\n| `date` | string | ISO 8601 timestamp |\n| `extras` | dict | Extras as submitted |\n\nExample:\n\n```python\ngotify(action=\"send_message\",\n       app_token=\"AbCdEf\",\n       title=\"Deployment done\",\n       message=\"## Summary\\n- All steps complete\\n- Ready for review\",\n       priority=7,\n       extras={\"client::display\": {\"contentType\": \"text/markdown\"}})\n```\n\n### `list_messages`\n\nList messages with pagination and optional filtering.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `app_id` | integer | no | — | Filter to messages from one application |\n| `offset` | integer | no | `0` | Cursor offset (message ID) — items before this ID are skipped |\n| `limit` | integer | no | `50` | Maximum number of messages to return |\n| `sort_by` | string | no | `\"id\"` | Field to sort by. Valid values: `id`, `date`, `priority` |\n| `sort_order` | string | no | `\"desc\"` | `\"asc\"` or `\"desc\"` |\n| `query` | string | no | `\"\"` | Case-insensitive substring filter applied to title and message body |\n\nResponse fields:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `items` | array | Array of message objects (same shape as `send_message` response) |\n| `total` | integer | Total messages before pagination |\n| `limit` | integer | Limit used |\n| `offset` | integer | Offset used |\n| `has_more` | boolean | Whether more pages exist |\n\nNote: Gotify uses cursor-style pagination internally. The `offset` parameter maps to the `since` query parameter (a message ID), not a row count.\n\nExample:\n\n```python\ngotify(action=\"list_messages\", limit=20, sort_order=\"desc\")\ngotify(action=\"list_messages\", app_id=3, query=\"error\", limit=10)\n```\n\n### `delete_message`\n\nDelete a single message by ID. Destructive — requires `confirm=True`.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `message_id` | integer | yes | — | ID of the message to delete |\n| `confirm` | boolean | yes | `False` | Must be `True` to proceed |\n\nExample:\n\n```python\ngotify(action=\"delete_message\", message_id=42, confirm=True)\n```\n\n### `delete_all_messages`\n\nDelete all messages across all applications. Destructive — requires `confirm=True`.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `confirm` | boolean | yes | `False` | Must be `True` to proceed |\n\nExample:\n\n```python\ngotify(action=\"delete_all_messages\", confirm=True)\n```\n\n### `list_applications`\n\nList all applications registered on the Gotify server.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `offset` | integer | no | `0` | Number of items to skip |\n| `limit` | integer | no | `50` | Maximum items to return |\n| `query` | string | no | `\"\"` | Case-insensitive substring filter on application name |\n\nResponse fields:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `items` | array | Array of application objects |\n| `total` | integer | Total applications before pagination |\n| `limit` | integer | Limit used |\n| `offset` | integer | Offset used |\n| `has_more` | boolean | Whether more pages exist |\n\nEach application object contains:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `id` | integer | Application ID |\n| `token` | string | Application token (use for `send_message`) |\n| `name` | string | Application name |\n| `description` | string | Application description |\n| `defaultPriority` | integer | Default message priority |\n| `image` | string | Path to application image |\n| `internal` | boolean | Whether this is an internal application |\n\nExample:\n\n```python\ngotify(action=\"list_applications\")\ngotify(action=\"list_applications\", query=\"homelab\")\n```\n\n### `create_application`\n\nCreate a new Gotify application.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `name` | string | yes | — | Application name |\n| `description` | string | no | — | Application description |\n| `default_priority` | integer | no | — | Default priority for messages from this app (0–10) |\n\nReturns the created application object.\n\nExample:\n\n```python\ngotify(action=\"create_application\",\n       name=\"homelab-alerts\",\n       description=\"Claude Code homelab notifications\",\n       default_priority=5)\n```\n\n### `update_application`\n\nUpdate an existing application. Provide at least one of `name`, `description`, or `default_priority`.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `app_id` | integer | yes | — | ID of the application to update |\n| `name` | string | no | — | New application name |\n| `description` | string | no | — | New description |\n| `default_priority` | integer | no | — | New default priority (0–10) |\n\nReturns the updated application object.\n\nExample:\n\n```python\ngotify(action=\"update_application\", app_id=3, name=\"homelab-alerts-v2\", default_priority=7)\n```\n\n### `delete_application`\n\nDelete an application and all its messages. Destructive — requires `confirm=True`.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `app_id` | integer | yes | — | ID of the application to delete |\n| `confirm` | boolean | yes | `False` | Must be `True` to proceed |\n\nExample:\n\n```python\ngotify(action=\"delete_application\", app_id=3, confirm=True)\n```\n\n### `list_clients`\n\nList all registered Gotify clients. Requires `GOTIFY_CLIENT_TOKEN`.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `offset` | integer | no | `0` | Number of items to skip |\n| `limit` | integer | no | `50` | Maximum items to return |\n| `query` | string | no | `\"\"` | Case-insensitive substring filter on client name |\n\nResponse has the same pagination shape as `list_applications`. Each client object contains `id`, `token`, and `name`.\n\nExample:\n\n```python\ngotify(action=\"list_clients\")\n```\n\n### `create_client`\n\nCreate a new Gotify client. Returns the client object including its token.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `name` | string | yes | — | Client name |\n\nExample:\n\n```python\ngotify(action=\"create_client\", name=\"my-phone\")\n```\n\n### `delete_client`\n\nDelete a Gotify client. Destructive — requires `confirm=True`.\n\n| Parameter | Type | Required | Default | Description |\n| --- | --- | --- | --- | --- |\n| `client_id` | integer | yes | — | ID of the client to delete |\n| `confirm` | boolean | yes | `False` | Must be `True` to proceed |\n\nExample:\n\n```python\ngotify(action=\"delete_client\", client_id=5, confirm=True)\n```\n\n### `health`\n\nCheck the Gotify server health status. No additional parameters.\n\nReturns a JSON object with health fields from the upstream Gotify `/health` endpoint. Note: this MCP tool call requires bearer authentication. The raw HTTP `/health` endpoint on the MCP server is unauthenticated.\n\nExample:\n\n```python\ngotify(action=\"health\")\n```\n\n### `version`\n\nGet the Gotify server version. No additional parameters. No authentication required on the upstream call.\n\nExample:\n\n```python\ngotify(action=\"version\")\n```\n\n### `current_user`\n\nGet the current authenticated user's account information. Requires `GOTIFY_CLIENT_TOKEN`.\n\nNo additional parameters. Returns the user object with `id`, `name`, and `admin` fields.\n\nExample:\n\n```python\ngotify(action=\"current_user\")\n```\n\n## Token Types\n\nGotify uses two separate token types. Using the wrong type will produce a 401 error.\n\n| Token | Source | Used for |\n| --- | --- | --- |\n| **App token** | Gotify UI: Settings > Apps > Create Application | `send_message` only — passed per call as `app_token` |\n| **Client token** | Gotify UI: Settings > Clients > Create Client | All management actions: list/delete messages, list/create/delete apps and clients, current_user |\n\nThe MCP server reads `GOTIFY_CLIENT_TOKEN` from the environment and uses it automatically for management actions. You never pass it explicitly to the tool.\n\nThe `app_token` for `send_message` is always passed explicitly per call — it is not read from the server environment.\n\n## Priority Levels\n\nThe `priority` field is an integer from 0 to 10. Gotify clients interpret priority ranges as follows:\n\n| Range | Level | Recommended use |\n| --- | --- | --- |\n| 0–3 | Low | Informational, FYI messages |\n| 4–7 | Normal | Task updates, completions, standard alerts |\n| 8–10 | High | Blocked states, errors, urgent alerts |\n\nIf `priority` is omitted from `send_message`, the application's `defaultPriority` is used. If the application has no default, Gotify falls back to 0.\n\n## Extras Structure\n\nThe `extras` field in `send_message` is a free-form dict passed to the Gotify API. The most common use is enabling Markdown rendering:\n\n```python\nextras={\"client::display\": {\"contentType\": \"text/markdown\"}}\n```\n\nOther known namespaces from the upstream Gotify extras specification:\n\n| Key | Value type | Description |\n| --- | --- | --- |\n| `client::display` | dict | Display hints for Gotify clients |\n| `client::display.contentType` | string | `\"text/plain\"` (default) or `\"text/markdown\"` |\n| `client::notification` | dict | Platform-specific notification overrides |\n\nAny key/value pairs are accepted — the server passes them through as-is.\n\n## Destructive Operations\n\nFour actions are gated behind a confirmation check:\n\n- `delete_message`\n- `delete_all_messages`\n- `delete_application`\n- `delete_client`\n\nWithout `confirm=True`, the server returns:\n\n```json\n{\"error\": \"Destructive operation. Pass confirm=True to proceed.\"}\n```\n\nTo bypass the gate server-wide, set either environment variable:\n\n```\nALLOW_DESTRUCTIVE=true   # skip confirm check\nALLOW_YOLO=true          # identical effect\n```\n\nThese env vars are intended for automated environments where interactive confirmation is not possible.\n\n## Pagination\n\nList actions (`list_messages`, `list_applications`, `list_clients`) share a common pagination interface:\n\n| Parameter | Type | Default | Notes |\n| --- | --- | --- | --- |\n| `offset` | integer | `0` | Items to skip. For `list_messages`, maps to the `since` cursor (a message ID). For `list_applications` and `list_clients`, applied client-side as a row offset. |\n| `limit` | integer | `50` | Maximum items per page |\n| `sort_by` | string | `\"id\"` | `list_messages` only. Field to sort by: `id`, `date`, `priority`. Not applied for apps or clients. |\n| `sort_order` | string | `\"desc\"` | `list_messages` only. `\"asc\"` or `\"desc\"`. |\n| `query` | string | `\"\"` | Substring filter. Matches title and body for messages; name for apps and clients. Case-insensitive. |\n\nAll list responses include `total`, `limit`, `offset`, and `has_more` alongside the `items` array.\n\n## Error Handling\n\nAll errors return a JSON object with these fields:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Short error identifier |\n| `errorCode` | integer | HTTP status code or 500 for network errors |\n| `errorDescription` | string | Human-readable explanation |\n\nCommon errors:\n\n| error | errorCode | Cause |\n| --- | --- | --- |\n| `Unauthorized` | 401 | Wrong or missing token type for the operation |\n| `HTTP 403` | 403 | Token valid but operation not permitted for this user |\n| `HTTP 404` | 404 | Message, application, or client ID does not exist |\n| `NoUpdateFields` | 400 | `update_application` called with no fields to update |\n| `RequestError` | 500 | Network failure reaching the Gotify server |\n| `No token provided` | 401 | Neither `app_token` nor `GOTIFY_CLIENT_TOKEN` is set |\n\nResponses are truncated at 512 KB. Truncated responses include `... [truncated]` at the end.\n\n## Installation\n\n### Claude Code plugin (recommended)\n\nInstall as a Claude Code plugin. You will be prompted for:\n- **Gotify Server URL** — base URL of your Gotify instance\n- **Gotify App Token** — for sending messages (from Gotify UI: Settings > Apps)\n- **Gotify Client Token** — for management operations (from Gotify UI: Settings > Clients)\n\nThe plugin uses stdio transport with `${userConfig.*}` interpolation — no `.env` file needed.\n\n### Docker Compose\n\n```bash\ncp .env.example .env\nchmod 600 .env\n# Edit .env with your credentials\ndocker compose up -d\n```\n\n### Local development\n\n```bash\nuv sync --dev\nuv run gotify-mcp-server\n```\n\n## Configuration\n\nTwo deployment paths are supported:\n\n| Path | Transport | Credentials | Auth |\n|------|-----------|-------------|------|\n| **Plugin (stdio)** | stdio | `userConfig` in plugin settings | None |\n| **Docker (HTTP)** | http | `.env` file | Bearer token |\n\nSee [docs/CONFIG.md](docs/CONFIG.md) for the full environment variable reference.\n\n### Docker URL rewriting\n\nWhen running inside Docker, `localhost` and `127.0.0.1` in `GOTIFY_URL` are automatically rewritten to `host.docker.internal` so the container can reach a host-side Gotify server.\n\n## Usage examples\n\n### Send a plain text notification\n\n```python\ngotify(action=\"send_message\",\n       app_token=\"AbCdEf\",\n       title=\"Build finished\",\n       message=\"All tests passed.\",\n       priority=5)\n```\n\n### Send a Markdown notification\n\n```python\ngotify(action=\"send_message\",\n       app_token=\"AbCdEf\",\n       title=\"Deploy complete\",\n       message=\"## Status\\n- All steps done\\n- Ready for review\",\n       priority=7,\n       extras={\"client::display\": {\"contentType\": \"text/markdown\"}})\n```\n\n### Page through messages\n\n```python\n# First page\ngotify(action=\"list_messages\", limit=25, offset=0)\n\n# Next page (use the ID of the last message as offset)\ngotify(action=\"list_messages\", limit=25, offset=99)\n```\n\n### Filter messages by text\n\n```python\ngotify(action=\"list_messages\", query=\"error\", limit=20)\n```\n\n### Filter messages from one application\n\n```python\ngotify(action=\"list_messages\", app_id=3, limit=50)\n```\n\n### Manage applications\n\n```python\n# List all applications\ngotify(action=\"list_applications\")\n\n# Create\ngotify(action=\"create_application\",\n       name=\"homelab-alerts\",\n       description=\"Automated notifications\",\n       default_priority=5)\n\n# Update\ngotify(action=\"update_application\", app_id=3, default_priority=7)\n\n# Delete (destructive)\ngotify(action=\"delete_application\", app_id=3, confirm=True)\n```\n\n### Manage clients\n\n```python\n# List all clients\ngotify(action=\"list_clients\")\n\n# Create\ngotify(action=\"create_client\", name=\"my-phone\")\n\n# Delete (destructive)\ngotify(action=\"delete_client\", client_id=5, confirm=True)\n```\n\n### Server info\n\n```python\ngotify(action=\"health\")\ngotify(action=\"version\")\ngotify(action=\"current_user\")\n```\n\n## HTTP fallback\n\nWhen MCP tools are unavailable, use direct HTTP calls. App tokens go to `/message`, client tokens go to management endpoints.\n\n```bash\n# Send a notification\ncurl -s -X POST \"$GOTIFY_URL/message\" \\\n  -H \"X-Gotify-Key: $GOTIFY_APP_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\":\"Done\",\"message\":\"All steps complete\",\"priority\":7}'\n\n# List messages\ncurl -s \"$GOTIFY_URL/message\" \\\n  -H \"X-Gotify-Key: $GOTIFY_CLIENT_TOKEN\"\n\n# List applications\ncurl -s \"$GOTIFY_URL/application\" \\\n  -H \"X-Gotify-Key: $GOTIFY_CLIENT_TOKEN\"\n\n# Health (no auth)\ncurl -s \"$GOTIFY_URL/health\"\n```\n\n## Development\n\n### Setup\n\n```bash\njust setup\n```\n\nThis copies `.env.example` to `.env` (if not already present) and installs all dependencies.\n\n### Commands\n\n```bash\njust dev          # Run the server locally (uv run python -m gotify_mcp.server)\njust lint         # Run ruff check\njust fmt          # Run ruff format\njust typecheck    # Run ty check\njust test         # Run pytest\njust build        # Build Docker image\njust up           # Start via docker compose\njust down         # Stop docker compose\njust restart      # Restart docker compose\njust logs         # Follow docker compose logs\njust health       # curl http://localhost:9158/health\njust test-live    # Run live integration tests (requires running server)\njust gen-token    # Generate a random bearer token\njust clean        # Remove build artifacts\n```\n\n## Verification\n\nRun before committing:\n\n```bash\njust lint\njust typecheck\njust test\n```\n\nLive verification (requires a running server and Gotify instance):\n\n```bash\njust test-live\n```\n\n## Server health endpoint\n\nThe MCP server exposes an unauthenticated HTTP health endpoint:\n\n```\nGET http://localhost:9158/health\n```\n\nThis proxies through to the Gotify server's `/health` and returns:\n\n```json\n{\"status\": \"ok\", \"gotify\": {...}}\n```\n\nOr on failure:\n\n```json\n{\"status\": \"error\", \"reason\": \"...\"}\n```\n\n## Logs\n\nThe server writes rotating logs to `logs/gotify_mcp.log` (max 5 MB, 3 backups). Log level is controlled by `GOTIFY_LOG_LEVEL`.\n\n## Related plugins\n\n| Plugin | Category | Description |\n|--------|----------|-------------|\n| [homelab-core](https://github.com/jmagar/claude-homelab) | core | Core agents, commands, skills, and setup/health workflows for homelab management. |\n| [overseerr-mcp](https://github.com/jmagar/overseerr-mcp) | media | Search movies and TV shows, submit requests, and monitor failed requests via Overseerr. |\n| [unraid-mcp](https://github.com/jmagar/unraid-mcp) | infrastructure | Query, monitor, and manage Unraid servers: Docker, VMs, array, parity, and live telemetry. |\n| [unifi-mcp](https://github.com/jmagar/unifi-mcp) | infrastructure | Monitor and manage UniFi devices, clients, firewall rules, and network health. |\n| [swag-mcp](https://github.com/jmagar/swag-mcp) | infrastructure | Create, edit, and manage SWAG nginx reverse proxy configurations. |\n| [synapse-mcp](https://github.com/jmagar/synapse-mcp) | infrastructure | Docker management (Flux) and SSH remote operations (Scout) across homelab hosts. |\n| [arcane-mcp](https://github.com/jmagar/arcane-mcp) | infrastructure | Manage Docker environments, containers, images, volumes, networks, and GitOps via Arcane. |\n| [syslog-mcp](https://github.com/jmagar/syslog-mcp) | infrastructure | Receive, index, and search syslog streams from all homelab hosts via SQLite FTS5. |\n| [plugin-lab](https://github.com/jmagar/plugin-lab) | dev-tools | Scaffold, review, align, and deploy homelab MCP plugins with agents and canonical templates. |\n\n## License\n\nMIT\n",
  "bytes": 20677,
  "sha": "05c0e0ce30bb9b9f9e4256080786da3806551e68c039ca0efe8aa58c22bb0f20",
  "repo_slug": "a422015028/gotify-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_a422015028_gotify_mcp_86a1062b/readme"
}