MCP server for Gotify push notifications
Project description
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 |
|---|---|
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.
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 |
|---|---|---|---|---|
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
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_message
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
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_applications
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_application
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_application
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_application
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_clients
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_client
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_client
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)
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 | 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.
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 |
|---|---|---|
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.
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 effect
These 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 |
|---|---|---|---|
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.
Error Handling
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.
Installation
Marketplace
/plugin marketplace add jmagar/claude-homelab
/plugin install gotify-mcp @jmagar-claude-homelab
Local development
uv sync --dev
uv run gotify-mcp-server
Direct module invocation:
uv run python -m gotify_mcp.server
Docker
just up
Or manually:
docker compose up -d
Configuration
Copy .env.example to .env and fill in the required values:
cp .env.example .env
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
GOTIFY_URL |
yes | — | Base URL of your Gotify server (no trailing slash). Server exits at startup if unset. |
GOTIFY_CLIENT_TOKEN |
yes* | — | Client token for management operations. Without this, all management actions fail. |
GOTIFY_APP_TOKEN |
no | — | App token used in HTTP fallback examples. The MCP tool requires app_token per call. |
GOTIFY_MCP_HOST |
no | 0.0.0.0 |
Interface for the MCP HTTP server to bind to |
GOTIFY_MCP_PORT |
no | 9158 |
Port for the MCP HTTP server |
GOTIFY_MCP_TRANSPORT |
no | http |
Transport mode: http or stdio |
GOTIFY_MCP_TOKEN |
yes** | — | Bearer token for MCP server authentication. Generate with openssl rand -hex 32. Required when transport is http and GOTIFY_MCP_NO_AUTH is not set. |
GOTIFY_MCP_NO_AUTH |
no | false |
Set true to disable bearer auth. Appropriate only behind a trusted reverse proxy. |
GOTIFY_LOG_LEVEL |
no | INFO |
Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL |
ALLOW_DESTRUCTIVE |
no | false |
Set true to skip confirm=True requirement for destructive actions |
ALLOW_YOLO |
no | false |
Identical to ALLOW_DESTRUCTIVE |
PUID |
no | 1000 |
User ID for container process |
PGID |
no | 1000 |
Group ID for container process |
*GOTIFY_CLIENT_TOKEN is required for management actions. Without it, a warning is logged at startup and management actions return 401.
**GOTIFY_MCP_TOKEN is required when GOTIFY_MCP_TRANSPORT=http and GOTIFY_MCP_NO_AUTH=false. The server exits at startup if neither is set.
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 setup
This 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 artifacts
Verification
Run before committing:
just lint
just typecheck
just test
Live verification (requires a running server and Gotify instance):
just test-live
Server health endpoint
The 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": "..."}
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 |
|---|---|---|
| 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. |
| axon | research | Self-hosted web crawl, ingest, embed, and RAG pipeline with MCP tooling. |
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gotify_mcp-0.3.2.tar.gz.
File metadata
- Download URL: gotify_mcp-0.3.2.tar.gz
- Upload date:
- Size: 157.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57a124dcb6bdb19f4d6ddb7ca0c886e274e45ee6e3b57b3249f122624419302d
|
|
| MD5 |
9e9ad989a987b3cd702ea8785401b115
|
|
| BLAKE2b-256 |
402fe4addf87957da3b6c1bec282ef91cba5b9b250ca846cf0bf3baaaa4246a5
|
Provenance
The following attestation bundles were made for gotify_mcp-0.3.2.tar.gz:
Publisher:
publish-pypi.yml on jmagar/gotify-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gotify_mcp-0.3.2.tar.gz -
Subject digest:
57a124dcb6bdb19f4d6ddb7ca0c886e274e45ee6e3b57b3249f122624419302d - Sigstore transparency entry: 1238667133
- Sigstore integration time:
-
Permalink:
jmagar/gotify-mcp@f002c714d51dc0cd09574ed65805026eef98b56e -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/jmagar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@f002c714d51dc0cd09574ed65805026eef98b56e -
Trigger Event:
push
-
Statement type:
File details
Details for the file gotify_mcp-0.3.2-py3-none-any.whl.
File metadata
- Download URL: gotify_mcp-0.3.2-py3-none-any.whl
- Upload date:
- Size: 16.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ec721d72825bc5f94f2b194160b81ff8dc9bd29837e5c3f8321ac84b55702f8
|
|
| MD5 |
7ce0f172146bcb1a822b6cfb845c9e59
|
|
| BLAKE2b-256 |
7e39112896bb04869e53c8ed782a64a2c214c53171b1ae54cc4b3ec430ca5661
|
Provenance
The following attestation bundles were made for gotify_mcp-0.3.2-py3-none-any.whl:
Publisher:
publish-pypi.yml on jmagar/gotify-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gotify_mcp-0.3.2-py3-none-any.whl -
Subject digest:
5ec721d72825bc5f94f2b194160b81ff8dc9bd29837e5c3f8321ac84b55702f8 - Sigstore transparency entry: 1238667136
- Sigstore integration time:
-
Permalink:
jmagar/gotify-mcp@f002c714d51dc0cd09574ed65805026eef98b56e -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/jmagar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@f002c714d51dc0cd09574ed65805026eef98b56e -
Trigger Event:
push
-
Statement type: