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.
Two MCP tools are exposed:
| Tool | Purpose |
|---|---|
gotify |
Unified action router for all Gotify operations |
gotify_help |
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.
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
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)Returns the full action reference as Markdown. Call this to discover available actions.
gotify_help()Send a push notification. Requires an app_token — this is the per-application token, not the client token.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
app_token |
string | yes | — | Application token from Gotify UI (Settings > Apps) |
message |
string | yes | — | Notification body. Supports Markdown when extras sets contentType. |
title |
string | no | — | Notification title |
priority |
integer | no | app default | Priority 0–10. See Priority Levels below. |
extras |
dict | no | — | Extended metadata. See Extras Structure below. |
Response fields:
| Field | Type | Description |
|---|---|---|
id |
integer | Assigned message ID |
appid |
integer | Application ID that sent the message |
message |
string | Message body |
title |
string | Message title |
priority |
integer | Effective priority |
date |
string | ISO 8601 timestamp |
extras |
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 with pagination and optional filtering.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
app_id |
integer | no | — | Filter to messages from one application |
offset |
integer | no | 0 |
Cursor offset (message ID) — items before this ID are skipped |
limit |
integer | no | 50 |
Maximum number of messages to return |
sort_by |
string | no | "id" |
Field to sort by. Valid values: id, date, priority |
sort_order |
string | no | "desc" |
"asc" or "desc" |
query |
string | no | "" |
Case-insensitive substring filter applied to title and message body |
Response fields:
| Field | Type | Description |
|---|---|---|
items |
array | Array of message objects (same shape as send_message response) |
total |
integer | Total messages before pagination |
limit |
integer | Limit used |
offset |
integer | Offset used |
has_more |
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 a single message by ID. Destructive — requires confirm=True.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
message_id |
integer | yes | — | ID of the message to delete |
confirm |
boolean | yes | False |
Must be True to proceed |
Example:
gotify(action="delete_message", message_id=42, confirm=True)Delete all messages across all applications. Destructive — requires confirm=True.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
confirm |
boolean | yes | False |
Must be True to proceed |
Example:
gotify(action="delete_all_messages", confirm=True)List all applications registered on the Gotify server.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
offset |
integer | no | 0 |
Number of items to skip |
limit |
integer | no | 50 |
Maximum items to return |
query |
string | no | "" |
Case-insensitive substring filter on application name |
Response fields:
| Field | Type | Description |
|---|---|---|
items |
array | Array of application objects |
total |
integer | Total applications before pagination |
limit |
integer | Limit used |
offset |
integer | Offset used |
has_more |
boolean | Whether more pages exist |
Each application object contains:
| Field | Type | Description |
|---|---|---|
id |
integer | Application ID |
token |
string | Application token (use for send_message) |
name |
string | Application name |
description |
string | Application description |
defaultPriority |
integer | Default message priority |
image |
string | Path to application image |
internal |
boolean | Whether this is an internal application |
Example:
gotify(action="list_applications")
gotify(action="list_applications", query="homelab")Create a new Gotify application.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | yes | — | Application name |
description |
string | no | — | Application description |
default_priority |
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 an existing application. Provide at least one of name, description, or default_priority.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
app_id |
integer | yes | — | ID of the application to update |
name |
string | no | — | New application name |
description |
string | no | — | New description |
default_priority |
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 an application and all its messages. Destructive — requires confirm=True.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
app_id |
integer | yes | — | ID of the application to delete |
confirm |
boolean | yes | False |
Must be True to proceed |
Example:
gotify(action="delete_application", app_id=3, confirm=True)List all registered Gotify clients. Requires GOTIFY_CLIENT_TOKEN.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
offset |
integer | no | 0 |
Number of items to skip |
limit |
integer | no | 50 |
Maximum items to return |
query |
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 a new Gotify client. Returns the client object including its token.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | yes | — | Client name |
Example:
gotify(action="create_client", name="my-phone")Delete a Gotify client. Destructive — requires confirm=True.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
client_id |
integer | yes | — | ID of the client to delete |
confirm |
boolean | yes | False |
Must be True to proceed |
Example:
gotify(action="delete_client", client_id=5, confirm=True)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")Get the Gotify server version. No additional parameters. No authentication required on the upstream call.
Example:
gotify(action="version")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")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 | send_message only — passed per call as app_token |
| 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.
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.
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 |
|---|---|---|
client::display |
dict | Display hints for Gotify clients |
client::display.contentType |
string | "text/plain" (default) or "text/markdown" |
client::notification |
dict | Platform-specific notification overrides |
Any key/value pairs are accepted — the server passes them through as-is.
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 effect
These env vars are intended for automated environments where interactive confirmation is not possible.
List actions (list_messages, list_applications, list_clients) share a common pagination interface:
| Parameter | Type | Default | Notes |
|---|---|---|---|
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. |
limit |
integer | 50 |
Maximum items per page |
sort_by |
string | "id" |
list_messages only. Field to sort by: id, date, priority. Not applied for apps or clients. |
sort_order |
string | "desc" |
list_messages only. "asc" or "desc". |
query |
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.
All errors return a JSON object with these fields:
| Field | Type | Description |
|---|---|---|
error |
string | Short error identifier |
errorCode |
integer | HTTP status code or 500 for network errors |
errorDescription |
string | Human-readable explanation |
Common errors:
| error | errorCode | Cause |
|---|---|---|
Unauthorized |
401 | Wrong or missing token type for the operation |
HTTP 403 |
403 | Token valid but operation not permitted for this user |
HTTP 404 |
404 | Message, application, or client ID does not exist |
NoUpdateFields |
400 | update_application called with no fields to update |
RequestError |
500 | Network failure reaching the Gotify server |
No token provided |
401 | Neither app_token nor GOTIFY_CLIENT_TOKEN is set |
Responses are truncated at 512 KB. Truncated responses include ... [truncated] at the end.
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.
cp .env.example .env
chmod 600 .env
# Edit .env with your credentials
docker compose up -duv sync --dev
uv run gotify-mcp-serverTwo deployment paths are supported:
| Path | Transport | Credentials | Auth |
|---|---|---|---|
| Plugin (stdio) | stdio | userConfig in plugin settings |
None |
| Docker (HTTP) | http | .env file |
Bearer token |
See docs/CONFIG.md for the full environment variable reference.
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.
gotify(action="send_message",
app_token="AbCdEf",
title="Build finished",
message="All tests passed.",
priority=5)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"}})# 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)gotify(action="list_messages", query="error", limit=20)gotify(action="list_messages", app_id=3, limit=50)# 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)# 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)gotify(action="health")
gotify(action="version")
gotify(action="current_user")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"just setupThis copies .env.example to .env (if not already present) and installs all dependencies.
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 artifactsRun before committing:
just lint
just typecheck
just testLive verification (requires a running server and Gotify instance):
just test-liveThe MCP server exposes an unauthenticated HTTP health endpoint:
GET http://localhost:9158/health
This proxies through to the Gotify server's /health and returns:
{"status": "ok", "gotify": {...}}Or on failure:
{"status": "error", "reason": "..."}The server writes rotating logs to logs/gotify_mcp.log (max 5 MB, 3 backups). Log level is controlled by GOTIFY_LOG_LEVEL.
| Plugin | Category | Description |
|---|---|---|
| homelab-core | core | Core agents, commands, skills, and setup/health workflows for homelab management. |
| overseerr-mcp | media | Search movies and TV shows, submit requests, and monitor failed requests via Overseerr. |
| unraid-mcp | infrastructure | Query, monitor, and manage Unraid servers: Docker, VMs, array, parity, and live telemetry. |
| unifi-mcp | infrastructure | Monitor and manage UniFi devices, clients, firewall rules, and network health. |
| swag-mcp | infrastructure | Create, edit, and manage SWAG nginx reverse proxy configurations. |
| synapse-mcp | infrastructure | Docker management (Flux) and SSH remote operations (Scout) across homelab hosts. |
| arcane-mcp | infrastructure | Manage Docker environments, containers, images, volumes, networks, and GitOps via Arcane. |
| syslog-mcp | infrastructure | Receive, index, and search syslog streams from all homelab hosts via SQLite FTS5. |
| plugin-lab | dev-tools | Scaffold, review, align, and deploy homelab MCP plugins with agents and canonical templates. |
MIT