Skip to main content
Glama
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).