gotify-mcp
by si-essence
README.md
# gotify-mcp (Gotify MCP connector)
MCP connector for a self-hosted [Gotify](https://github.com/gotify/server) server (v3.1.1 API). It speaks the Gotify REST API over stdio and over stateless Streamable HTTP on localhost.
Gotify server is MIT. This connector is a local client. It is not affiliated with the Gotify project. A call does not transfer trademarks or a right to say that Gotify endorses this project.
The registry covers 38 of the 42 operations in `gotify.openapi.json`. Four operations are excluded because they are not request/response calls. `scripts/gen-operations.mjs` holds that allow-list, and `test/operations.test.ts` repeats it.
There is no hosted Gotify to refuse. `GOTIFY_URL` is required. Set it to the origin of your server, for example `http://127.0.0.1:8090`. The value must be `http` or `https`, with no username, password, query, or hash.
## Setup
```bash
npm install
npm run ci
npm start
npm run start:http
```
`npm start` speaks stdio. `npm run start:http` serves Streamable HTTP at `http://127.0.0.1:3000/mcp` and `GET /health`. HTTP mode has no authentication. Keep it on localhost.
Create a client in the Gotify UI and copy its token into `GOTIFY_TOKEN`. Create an application and copy its token into `GOTIFY_APP_TOKEN` when you want to send messages. The connector sends either token as `X-Gotify-Key` and never prints the value.
```json
{
"mcpServers": {
"gotify": {
"command": "node",
"args": ["/workspace/connectors/gotify/dist/index.js"],
"env": {
"GOTIFY_URL": "http://127.0.0.1:8090",
"GOTIFY_TOKEN": "...",
"GOTIFY_APP_TOKEN": "...",
"GOTIFY_ALLOW_WRITES": "false",
"GOTIFY_TOOLSETS": "info,messages,applications",
"GOTIFY_CONTACT": "you@example.com"
}
}
}
}
```
Docker (HTTP, no authentication, localhost only):
```bash
docker build -t gotify .
docker run -p 3000:3000 \
-e GOTIFY_URL=http://host.docker.internal:8090 \
-e GOTIFY_TOKEN=... \
gotify
```
Do not put tokens in the image.
Regenerate the operations registry after a spec bump:
```bash
npm run gen:operations
```
## Config
| Variable | Meaning |
| --- | --- |
| `GOTIFY_URL` | Required. Self-hosted origin. No default. No userinfo, query, or hash. |
| `GOTIFY_TOKEN` | Client token for admin and read calls. Sent as `X-Gotify-Key`. At least 8 characters. |
| `GOTIFY_APP_TOKEN` | Application token. Used only by `create_message`, also as `X-Gotify-Key`. |
| `GOTIFY_USER` | Optional basic-auth user. Set together with `GOTIFY_PASSWORD`. Alternative to `GOTIFY_TOKEN`. |
| `GOTIFY_PASSWORD` | Optional basic-auth password. At least 8 characters. Never printed. |
| `GOTIFY_ALLOW_WRITES` | Must be the literal `true` before a confirmed write is sent. |
| `GOTIFY_TOOLSETS` | Comma list of toolsets, or `all`. Default `info,messages,applications`. |
| `GOTIFY_PER_MINUTE` | Local request budget. Default 30. |
| `GOTIFY_TIMEOUT_MS` | Upstream timeout. Default 20000. |
| `GOTIFY_CACHE_TTL_SEC` | GET cache TTL. Default 60. Writes clear the cache. |
| `GOTIFY_CONTACT` | Contact token in the User-Agent. |
| `MCP_TRANSPORT` | `stdio` or `http`. `--http` overrides. |
| `PORT` | HTTP port. Default 3000. |
Invalid values are reported by field name and issue code. The rejected value is not included.
When both `GOTIFY_TOKEN` and basic auth are set, client calls send the token.
## Safety
Every non-GET is a write. A write needs `confirm` set to the boolean `true` and `GOTIFY_ALLOW_WRITES=true`. Otherwise the tool returns a dry-run preview of the method, path, query, redacted body, content type, and which credential would be sent. The preview does not send the request. The string `"true"` is rejected.
These operations add an extra warning in the preview and in the tool description. Deletes of a message, of every message, of one application's messages, of an application, of a client, and of a user. Creates that mint a credential (application, client, user, local login, OIDC token exchange). Client elevation. Application security changes. Password changes. Plugin enable, disable, and config replacement. Logout, which invalidates the token in use.
`create_message` is a write because it pushes a notification. It does not add the extra warning. It refuses to run unless `GOTIFY_APP_TOKEN` is set. The preview names that variable and never the token value. `priority` must be an integer from -128 to 128.
`POST /application/{id}/image` takes `{filename, content_base64}` and sends multipart field `file`. Decoded size is capped at 2 MB. `POST /plugin/{id}/config` takes a YAML string and sends `application/x-yaml`. `local_login` takes `{name, username, password}`. Gotify receives form field `name` plus HTTP basic auth. The password is redacted.
Application, client, and plugin `token` fields are redacted on list, get, update, and preview. A confirmed `create_application`, `create_client`, `local_login`, or `oidc_external_token` returns the new token once, with the note `token shown only once`. The caller's own tokens and password are always redacted. `message` and `title` are user data and stay in the output, truncated at 4096 characters.
Responses are capped at 1 MB. Errors use a fixed set of codes. 401 unauthorized, 403 forbidden, 404 not-found, 400, 409, and 422 invalid, 429 rate-limit with Retry-After, 5xx server, network, and timeout. Stack traces and filesystem paths are removed.
## Tools
Default `GOTIFY_TOOLSETS` publishes 3 tools (17 operations). `GOTIFY_TOOLSETS=all` publishes 7 tools (38 operations). Each tool takes `action` plus `params`, `query`, `body`, and `confirm` on writes.
| Toolset | Tool | Operations | Default |
| --- | --- | --- | --- |
| info | `gotify_info` | 4 | yes |
| messages | `gotify_messages` | 6 | yes |
| applications | `gotify_applications` | 7 | yes |
| clients | `gotify_clients` | 5 | no |
| users | `gotify_users` | 6 | no |
| plugins | `gotify_plugins` | 6 | no |
| auth | `gotify_auth` | 4 | no |
Excluded operations:
| Operation | Why |
| --- | --- |
| `GET /stream` (`streamMessages`) | WebSocket of new messages. |
| `GET /auth/oidc/login` (`oidcLogin`) | Browser redirect. |
| `GET /auth/oidc/callback` (`oidcCallback`) | Browser redirect. |
| `GET /auth/oidc/elevate` (`oidcElevate`) | Browser redirect. |
Example. Read health, preview a message, then send it:
```json
{ "action": "get_health" }
```
```json
{ "action": "create_message", "body": { "message": "backup finished", "title": "Backup", "priority": 2 } }
```
```json
{ "action": "create_message", "body": { "message": "backup finished", "title": "Backup", "priority": 2 }, "confirm": true }
```
The first call uses `gotify_info`. The other two use `gotify_messages`.
## What this is not
This is not a Gotify WebSocket client, a browser OIDC login, or a hosted multi-tenant service. There is no default URL. It does not store tokens on disk. `scripts/live-check.mjs` is the parent live check against a local Gotify. This session did not run that script. See `LIVE-CHECK.md`.
## Limits
- In-memory TTL cache (`GOTIFY_CACHE_TTL_SEC`, default 60 seconds). A cache hit does not spend the local rate limit. The cache is dropped when the process exits. Writes clear it.
- Local sliding window (`GOTIFY_PER_MINUTE`, default 30 requests per 60 seconds) around upstream calls.
- When the local window is full, the tool returns `isError` with a `[rate-limit]` message and a retry hint.
## Elevated sessions (Gotify 3)
Gotify 3 requires an elevated session for user management, deleting or changing applications and clients, and token security changes. A plain client token is not elevated and gets a 403 "session not elevated". Use `GOTIFY_USER` and `GOTIFY_PASSWORD` (basic auth is always elevated), or elevate the client once with basic auth through the `clients` toolset `elevate_client` action. The connector adds a hint to that 403 error.
## License
MIT, see [LICENSE](LICENSE). This is an independent, unofficial connector and is not endorsed by the Gotify project or its owners.
gotify.openapi.json is from gotify/server (MIT, Copyright (c) 2018 jmattheis).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues