gotify-mcp
Gotify MCP
MCP 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.
Overview
Two MCP tools are exposed:
Tool | Purpose |
| Unified action router for all Gotify operations |
| Returns markdown documentation for all actions and parameters |
The server supports HTTP (default) and stdio transports. HTTP transport requires bearer authentication via GOTIFY_MCP_TOKEN.
What this repository ships
gotify_mcp/server.py: FastMCP server, action router, and BearerAuth middlewaregotify_mcp/services/gotify.py: Async HTTP client for the Gotify REST APIskills/gotify/SKILL.md: Client-facing skill documentationdocs/gotify-api.json: Bundled upstream Gotify API reference.claude-plugin/plugin.json,.codex-plugin/plugin.json,gemini-extension.json: Client manifestsdocker-compose.yml,Dockerfile,entrypoint.sh: Container deploymentscripts/: Smoke tests and contract checks
Tools
gotify
Single entry point for all Gotify operations. Select the operation with the action parameter.
gotify(action="send_message", app_token="AbCdEf", message="Build finished", priority=5)gotify_help
Returns the full action reference as Markdown. Call this to discover available actions.
gotify_help()Actions
send_message
Send a push notification. Requires an app_token — this is the per-application token, not the client token.
Parameter | Type | Required | Default | Description |
| string | yes | — | Application token from Gotify UI (Settings > Apps) |
| string | yes | — | Notification body. Supports Markdown when |
| string | no | — | Notification title |
| integer | no | app default | Priority 0–10. See Priority Levels below. |
| dict | no | — | Extended metadata. See Extras Structure below. |
Response fields:
Field | Type | Description |
| integer | Assigned message ID |
| integer | Application ID that sent the message |
| string | Message body |
| string | Message title |
| integer | Effective priority |
| string | ISO 8601 timestamp |
| dict | Extras as submitted |
Example:
gotify(action="send_message",
app_token="AbCdEf",
title="Deployment done",
message="## Summary\n- All steps complete\n- Ready for review",
priority=7,
extras={"client::display": {"contentType": "text/markdown"}})list_messages
List messages with pagination and optional filtering.
Parameter | Type | Required | Default | Description |
| integer | no | — | Filter to messages from one application |
| integer | no |
| Cursor offset (message ID) — items before this ID are skipped |
| integer | no |
| Maximum number of messages to return |
| string | no |
| Field to sort by. Valid values: |
| string | no |
|
|
| string | no |
| Case-insensitive substring filter applied to title and message body |
Response fields:
Field | Type | Description |
| array | Array of message objects (same shape as |
| integer | Total messages before pagination |
| integer | Limit used |
| integer | Offset used |
| boolean | Whether more pages exist |
Note: Gotify uses cursor-style pagination internally. The offset parameter maps to the since query parameter (a message ID), not a row count.
Example:
gotify(action="list_messages", limit=20, sort_order="desc")
gotify(action="list_messages", app_id=3, query="error", limit=10)delete_message
Delete a single message by ID. Destructive — requires confirm=True.
Parameter | Type | Required | Default | Description |
| integer | yes | — | ID of the message to delete |
| boolean | yes |
| Must be |
Example:
gotify(action="delete_message", message_id=42, confirm=True)delete_all_messages
Delete all messages across all applications. Destructive — requires confirm=True.
Parameter | Type | Required | Default | Description |
| boolean | yes |
| Must be |
Example:
gotify(action="delete_all_messages", confirm=True)list_applications
List all applications registered on the Gotify server.
Parameter | Type | Required | Default | Description |
| integer | no |
| Number of items to skip |
| integer | no |
| Maximum items to return |
| string | no |
| Case-insensitive substring filter on application name |
Response fields:
Field | Type | Description |
| array | Array of application objects |
| integer | Total applications before pagination |
| integer | Limit used |
| integer | Offset used |
| boolean | Whether more pages exist |
Each application object contains:
Field | Type | Description |
| integer | Application ID |
| string | Application token (use for |
| string | Application name |
| string | Application description |
| integer | Default message priority |
| string | Path to application image |
| boolean | Whether this is an internal application |
Example:
gotify(action="list_applications")
gotify(action="list_applications", query="homelab")create_application
Create a new Gotify application.
Parameter | Type | Required | Default | Description |
| string | yes | — | Application name |
| string | no | — | Application description |
| integer | no | — | Default priority for messages from this app (0–10) |
Returns the created application object.
Example:
gotify(action="create_application",
name="homelab-alerts",
description="Claude Code homelab notifications",
default_priority=5)update_application
Update an existing application. Provide at least one of name, description, or default_priority.
Parameter | Type | Required | Default | Description |
| integer | yes | — | ID of the application to update |
| string | no | — | New application name |
| string | no | — | New description |
| integer | no | — | New default priority (0–10) |
Returns the updated application object.
Example:
gotify(action="update_application", app_id=3, name="homelab-alerts-v2", default_priority=7)delete_application
Delete an application and all its messages. Destructive — requires confirm=True.
Parameter | Type | Required | Default | Description |
| integer | yes | — | ID of the application to delete |
| boolean | yes |
| Must be |
Example:
gotify(action="delete_application", app_id=3, confirm=True)list_clients
List all registered Gotify clients. Requires GOTIFY_CLIENT_TOKEN.
Parameter | Type | Required | Default | Description |
| integer | no |
| Number of items to skip |
| integer | no |
| Maximum items to return |
| string | no |
| Case-insensitive substring filter on client name |
Response has the same pagination shape as list_applications. Each client object contains id, token, and name.
Example:
gotify(action="list_clients")create_client
Create a new Gotify client. Returns the client object including its token.
Parameter | Type | Required | Default | Description |
| string | yes | — | Client name |
Example:
gotify(action="create_client", name="my-phone")delete_client
Delete a Gotify client. Destructive — requires confirm=True.
Parameter | Type | Required | Default | Description |
| integer | yes | — | ID of the client to delete |
| boolean | yes |
| Must be |
Example:
gotify(action="delete_client", client_id=5, confirm=True)health
Check the Gotify server health status. No additional parameters.
Returns 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.
Example:
gotify(action="health")version
Get the Gotify server version. No additional parameters. No authentication required on the upstream call.
Example:
gotify(action="version")current_user
Get the current authenticated user's account information. Requires GOTIFY_CLIENT_TOKEN.
No additional parameters. Returns the user object with id, name, and admin fields.
Example:
gotify(action="current_user")Token Types
Gotify uses two separate token types. Using the wrong type will produce a 401 error.
Token | Source | Used for |
App token | Gotify UI: Settings > Apps > Create Application |
|
Client token | Gotify UI: Settings > Clients > Create Client | All management actions: list/delete messages, list/create/delete apps and clients, current_user |
The MCP server reads GOTIFY_CLIENT_TOKEN from the environment and uses it automatically for management actions. You never pass it explicitly to the tool.
The app_token for send_message is always passed explicitly per call — it is not read from the server environment.
Priority Levels
The priority field is an integer from 0 to 10. Gotify clients interpret priority ranges as follows:
Range | Level | Recommended use |
0–3 | Low | Informational, FYI messages |
4–7 | Normal | Task updates, completions, standard alerts |
8–10 | High | Blocked states, errors, urgent alerts |
If priority is omitted from send_message, the application's defaultPriority is used. If the application has no default, Gotify falls back to 0.
Extras Structure
The extras field in send_message is a free-form dict passed to the Gotify API. The most common use is enabling Markdown rendering:
extras={"client::display": {"contentType": "text/markdown"}}Other known namespaces from the upstream Gotify extras specification:
Key | Value type | Description |
| dict | Display hints for Gotify clients |
| string |
|
| dict | Platform-specific notification overrides |
Any key/value pairs are accepted — the server passes them through as-is.
Destructive Operations
Four actions are gated behind a confirmation check:
delete_messagedelete_all_messagesdelete_applicationdelete_client
Without confirm=True, the server returns:
{"error": "Destructive operation. Pass confirm=True to proceed."}To bypass the gate server-wide, set either environment variable:
ALLOW_DESTRUCTIVE=true # skip confirm check
ALLOW_YOLO=true # identical effectThese env vars are intended for automated environments where interactive confirmation is not possible.
Pagination
List actions (list_messages, list_applications, list_clients) share a common pagination interface:
Parameter | Type | Default | Notes |
| integer |
| Items to skip. For |
| integer |
| Maximum items per page |
| string |
|
|
| string |
|
|
| string |
| Substring filter. Matches title and body for messages; name for apps and clients. Case-insensitive. |
All list responses include total, limit, offset, and has_more alongside the items array.
Error Handling
All errors return a JSON object with these fields:
Field | Type | Description |
| string | Short error identifier |
| integer | HTTP status code or 500 for network errors |
| string | Human-readable explanation |
Common errors:
error | errorCode | Cause |
| 401 | Wrong or missing token type for the operation |
| 403 | Token valid but operation not permitted for this user |
| 404 | Message, application, or client ID does not exist |
| 400 |
|
| 500 | Network failure reaching the Gotify server |
| 401 | Neither |
Responses are truncated at 512 KB. Truncated responses include ... [truncated] at the end.
Installation
Claude Code plugin (recommended)
Install as a Claude Code plugin. You will be prompted for:
Gotify Server URL — base URL of your Gotify instance
Gotify App Token — for sending messages (from Gotify UI: Settings > Apps)
Gotify Client Token — for management operations (from Gotify UI: Settings > Clients)
The plugin uses stdio transport with ${userConfig.*} interpolation — no .env file needed.
Docker Compose
cp .env.example .env
chmod 600 .env
# Edit .env with your credentials
docker compose up -dLocal development
uv sync --dev
uv run gotify-mcp-serverConfiguration
Two deployment paths are supported:
Path | Transport | Credentials | Auth |
Plugin (stdio) | stdio |
| None |
Docker (HTTP) | http |
| Bearer token |
See docs/CONFIG.md for the full environment variable reference.
Docker URL rewriting
When 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.
Usage examples
Send a plain text notification
gotify(action="send_message",
app_token="AbCdEf",
title="Build finished",
message="All tests passed.",
priority=5)Send a Markdown notification
gotify(action="send_message",
app_token="AbCdEf",
title="Deploy complete",
message="## Status\n- All steps done\n- Ready for review",
priority=7,
extras={"client::display": {"contentType": "text/markdown"}})Page through messages
# First page
gotify(action="list_messages", limit=25, offset=0)
# Next page (use the ID of the last message as offset)
gotify(action="list_messages", limit=25, offset=99)Filter messages by text
gotify(action="list_messages", query="error", limit=20)Filter messages from one application
gotify(action="list_messages", app_id=3, limit=50)Manage applications
# List all applications
gotify(action="list_applications")
# Create
gotify(action="create_application",
name="homelab-alerts",
description="Automated notifications",
default_priority=5)
# Update
gotify(action="update_application", app_id=3, default_priority=7)
# Delete (destructive)
gotify(action="delete_application", app_id=3, confirm=True)Manage clients
# List all clients
gotify(action="list_clients")
# Create
gotify(action="create_client", name="my-phone")
# Delete (destructive)
gotify(action="delete_client", client_id=5, confirm=True)Server info
gotify(action="health")
gotify(action="version")
gotify(action="current_user")HTTP fallback
When MCP tools are unavailable, use direct HTTP calls. App tokens go to /message, client tokens go to management endpoints.
# Send a notification
curl -s -X POST "$GOTIFY_URL/message" \
-H "X-Gotify-Key: $GOTIFY_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Done","message":"All steps complete","priority":7}'
# List messages
curl -s "$GOTIFY_URL/message" \
-H "X-Gotify-Key: $GOTIFY_CLIENT_TOKEN"
# List applications
curl -s "$GOTIFY_URL/application" \
-H "X-Gotify-Key: $GOTIFY_CLIENT_TOKEN"
# Health (no auth)
curl -s "$GOTIFY_URL/health"Development
Setup
just setupThis copies .env.example to .env (if not already present) and installs all dependencies.
Commands
just dev # Run the server locally (uv run python -m gotify_mcp.server)
just lint # Run ruff check
just fmt # Run ruff format
just typecheck # Run ty check
just test # Run pytest
just build # Build Docker image
just up # Start via docker compose
just down # Stop docker compose
just restart # Restart docker compose
just logs # Follow docker compose logs
just health # curl http://localhost:9158/health
just test-live # Run live integration tests (requires running server)
just gen-token # Generate a random bearer token
just clean # Remove build artifactsVerification
Run before committing:
just lint
just typecheck
just testLive verification (requires a running server and Gotify instance):
just test-liveServer health endpoint
The MCP server exposes an unauthenticated HTTP health endpoint:
GET http://localhost:9158/healthThis proxies through to the Gotify server's /health and returns:
{"status": "ok", "gotify": {...}}Or on failure:
{"status": "error", "reason": "..."}Logs
The server writes rotating logs to logs/gotify_mcp.log (max 5 MB, 3 backups). Log level is controlled by GOTIFY_LOG_LEVEL.
Related plugins
Plugin | Category | Description |
core | Core agents, commands, skills, and setup/health workflows for homelab management. | |
media | Search movies and TV shows, submit requests, and monitor failed requests via Overseerr. | |
infrastructure | Query, monitor, and manage Unraid servers: Docker, VMs, array, parity, and live telemetry. | |
infrastructure | Monitor and manage UniFi devices, clients, firewall rules, and network health. | |
infrastructure | Create, edit, and manage SWAG nginx reverse proxy configurations. | |
infrastructure | Docker management (Flux) and SSH remote operations (Scout) across homelab hosts. | |
infrastructure | Manage Docker environments, containers, images, volumes, networks, and GitOps via Arcane. | |
infrastructure | Receive, index, and search syslog streams from all homelab hosts via SQLite FTS5. | |
dev-tools | Scaffold, review, align, and deploy homelab MCP plugins with agents and canonical templates. |
License
MIT