# Reported issues for ntfy-mcp-server

Pod holds 18 of 23 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to [ntfy-mcp-server](/mcp/ntfy-mcp-server).

## Most discussed

### bug(ntfy-service): upstream ntfy error text never reaches content[], and is discarded on classified failures

### Server version

2.1.1

### mcp-ts-core version

0.10.6

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

http

### Description

[ntfy](https://ntfy.sh) returns a descriptive JSON error body on every 4xx — an error string plus a docs link pointing at the exact parameter that was rejected. None of it reaches the agent through `content[]`, and classified failures drop it from `structuredContent` too.

Unclassified errors keep the body in `structuredContent.error.data.body`, but

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/16) · 2026-07-28 · closed · 3 comments

### feat(consent): elicit before high-impact notification actions

`ntfy_manage_message` is marked `destructiveHint: true`, but the handler calls `NtfyService.manage()` directly with no `ctx.elicit` confirmation. `ntfy_publish_message` can also trigger high-impact side effects (`email`, `call`, `broadcast` actions, and `http` action buttons) from model-supplied input.

## Proposal

Add an elicitation boundary before side effects where a mistaken or prompt-injected tool call is meaningfully disruptive.

### Scope

- `ntfy_manage_message`: ask for confirmation be

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/13) · 2026-06-28 · closed · 3 comments

### feat(ntfy-service): SSRF protection for base_url override

### Use case

The `base_url` override on `ntfy_publish_message`, `ntfy_manage_message`, and `ntfy_fetch_messages` accepts any string passing `z.url()` — no scheme allowlist, no host filtering. When the server runs publicly (Streamable HTTP transport, `MCP_AUTH_MODE=none`), unauthenticated callers can supply private/loopback/link-local URLs that the server then dereferences via `fetch`. This is a server-side request forgery surface.

Reachable from the host running the server:

- IPv4 private (10

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/6) · 2026-05-10 · closed · 3 comments

### feat(ntfy_fetch_messages): limit drops the newest messages instead of the oldest

`ntfy_fetch_messages` truncates with `notifications.slice(0, input.limit)`, keeping the **oldest** N of the matching window. The default lookback is `since: 10m` with `limit: 20`, so on a busy topic the most common question the tool exists to answer — "did my recent notification land?" — comes back with the start of the window and the recent messages dropped.

Related: #20

## Proposal

Keep the newest `limit` messages (`slice(-limit)`), matching what the `ntfy://{topic}` resource does, and stat

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/22) · 2026-07-30 · closed · 2 comments

### feat(schema): replace the priority literal union with an enum so validation errors stay readable

### Use case

`PrioritySchema` is built as `z.union([z.literal(1), … z.literal(5)])`. It serializes to a five-branch `anyOf` of `const` values instead of a plain enum, and a wrong value produces an `invalid_union` dump listing all five branches — roughly 40 lines of JSON for a one-character mistake.

Calling `ntfy_publish_message` with `priority: 9` returns:

```
MCP error -32602: Input validation error: Invalid arguments for tool ntfy_publish_message: [
  { "code": "invalid_union", "errors": [ 

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/21) · 2026-07-28 · closed · 2 comments

### bug(ntfy-topic.resource): description promises the latest 20 messages but returns the oldest 20

### Server version

2.1.1

### mcp-ts-core version

0.10.6

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

http

### Description

The `ntfy://{topic}` resource advertises "latest 20 from the last 1 hour". ntfy returns cached messages oldest-first, and the handler takes `notifications.slice(0, SNAPSHOT_LIMIT)` — the **oldest** 20 in the window. On any topic with more than 20 messages in the hour, the snapshot shows the start of the window and drops everything recent, which is t

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/20) · 2026-07-28 · closed · 2 comments

### bug(ntfy_publish_message): time is documented as accept time but returns delivery time for delayed messages

### Server version

2.1.1

### mcp-ts-core version

0.10.6

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

http

### Description

The `time` field in `ntfy_publish_message`'s output schema is described as "ISO 8601 timestamp when ntfy accepted the message (not delivery time)". For a publish carrying `delay`, ntfy sets `time` to the scheduled **delivery** time, so the description states the opposite of what the field holds.

`format()` compounds it: under a `**Scheduled**` bann

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/19) · 2026-07-28 · closed · 2 comments

### feat(ntfy_search_emoji_tags): add an offset so results past the 200-row cap are reachable

### Use case

The bundled reference holds 1,812 emoji tags. `limit` caps at 200 and there is no `offset` or cursor, so 1,612 rows are unreachable — `truncated: true` is reported with no way to act on it.

Narrowing the query does not close the gap: `query: "a"` matches 1,179 tags and still returns 200. An agent hunting for a tag that sorts late in doc order has to guess ever-shorter substrings with no signal about what it has not seen.

### Proposed behavior

Add `offset: number` (default 0) to 

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/18) · 2026-07-28 · closed · 2 comments

## Most recent

### fix(config): pin MCP_SESSION_MODE to stateful — the image ships stateless and breaks the confirmation gate for v1 HTTP clients

`Dockerfile` sets `ENV MCP_SESSION_MODE="stateless"`, but `.env.example` carries a commented `# MCP_SESSION_MODE=stateful`, whose comment documents the value set as `stateful | stateless (default: stateful)`. The container and the same code run via `bunx`, `npm start`, or from source therefore resolve to different session modes. `README.md` disagrees with both, listing the default as `auto` — three surfaces, three answers.

For this server the container is the one that is wrong.

Related: cyanhe

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/31) · 2026-08-22 · open · 0 comments

### feat(config): default MCP_SESSION_MODE to stateful across env, Docker, and src

`Dockerfile` sets `ENV MCP_SESSION_MODE="stateless"`, `.env.example` carries a commented `# MCP_SESSION_MODE=stateful`, and `README.md` documents the default as `auto`. The same code therefore runs stateless in the container and stateful everywhere else — `bunx`, `npm start`, or from source — since the framework schema defaults to `auto` and `auto` resolves to `stateful`.

The `.env.example` comment is also factually wrong. It reads `stateful | stateless (default: stateful)`, omitting `auto`, wh

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/30) · 2026-08-22 · open · 0 comments

### feat(ntfy_publish_message): add structured recovery for invalid delay

### Use case

`ntfy_publish_message` preserves ntfy's invalid-delay explanation on both response surfaces, but the failure has no stable `data.reason` and no recovery hint naming the accepted delay forms. One of ntfy's three delay rejections is also actively misrouted today.

ntfy answers a bad `delay` with one of three errors, all HTTP 400:

| Code | Upstream error | Current classification |
|:---|:---|:---|
| 40004 | `invalid delay parameter: unable to parse delay` | unclassified — bubbles to 

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/29) · 2026-08-17 · open · 0 comments

### bug(ntfy_manage_message): idempotentHint conflicts with emitted events

### Server version

2.3.0

### mcp-ts-core version

0.11.0

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP (Streamable HTTP)

### Description

`ntfy_manage_message` advertises `idempotentHint: true`. `@modelcontextprotocol/sdk` 1.29.0 defines that hint as "calling the tool repeatedly with the same arguments will have no additional effect on its environment", and it is meaningful on this tool because `readOnlyHint` is unset (defaults to `false`).

Clearing and deleting are

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/25) · 2026-08-17 · open · 0 comments

### bug(ntfy_fetch_messages): empty filters silently widen the query

### Server version

2.3.0

### mcp-ts-core version

0.11.0

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP (Streamable HTTP)

### Description

`since` carries a schema default of `10m`, but a Zod `.default()` only fires on `undefined`. An explicit `""` passes validation, is then dropped from the upstream query by a truthiness check in `NtfyService.fetch()`, and ntfy applies its own poll default of `since=all`. The documented `10m` guardrail is silently replaced by the ent

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/26) · 2026-08-17 · open · 0 comments

### bug(ntfy_fetch_messages): hard cap drops messages without a retrieval path

### Server version

2.3.0

### mcp-ts-core version

0.11.0

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP (Streamable HTTP)

### Description

`ntfy_fetch_messages` keeps the newest `limit` matches and drops the older head, with `limit` capped at 100. Once more than 100 messages match, the dropped head is unreachable: the tool exposes no offset or cursor, and every path the truncation notice suggests is a dead end at the cap.

ntfy has no upstream lever for this either. I

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/27) · 2026-08-17 · open · 0 comments

### bug(formatting): plain ntfy text is interpreted as Markdown in content[]

### Server version

2.3.0

### mcp-ts-core version

0.11.0

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP (Streamable HTTP)

### Description

`format()` in `ntfy_publish_message` and `ntfy_fetch_messages` interpolates upstream-controlled strings straight into Markdown `content[]` text, so a message body becomes formatting instructions instead of data. The interpolated values are `title`, `message`, `click` (rendered as `[url](url)`), `attachment.name` / `url` / `type` (r

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/28) · 2026-08-17 · open · 0 comments

### bug(ntfy_fetch_messages): malformed NDJSON is silently omitted

### Server version

2.3.0

### mcp-ts-core version

0.11.0

### Runtime

Bun

### Runtime version

Bun 1.3.14

### Transport

HTTP (Streamable HTTP)

### Description

`parseNdjson()` in `NtfyService` drops every non-empty line that fails `JSON.parse` and returns the surviving lines as a complete success, so a degraded upstream response is indistinguishable from an authoritative one. Two paths reach it:

- A partial or corrupted NDJSON body loses the unparseable lines; the response reports the su

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/24) · 2026-08-17 · open · 0 comments

### docs(design): output blocks type timestamps as Unix seconds and describe format() footers that do not exist

`docs/design.md` describes four things the code does not do. Each is a self-contained line in the `ntfy_fetch_messages` or `ntfy_search_emoji_tags` tool-detail section.

| Claim in `docs/design.md` | Actual |
|:---|:---|
| Fetch output block: `time: number; // Unix seconds when ntfy accepted the message` | `MessageSchema.time` is `z.string()` — an ISO 8601 string (`2026-05-26T06:30:11.000Z`) since the `shapeMessage` normalization |
| Same block: `expires?: number; // Unix seconds when the messag

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/23) · 2026-07-30 · closed · 1 comment

### feat(ntfy_fetch_messages): no way to retrieve message bodies truncated at 500 chars

### Use case

`ntfy_fetch_messages` truncates every message body to 500 characters and reports the dropped count in `messageTruncated`, but exposes no way to read the rest. Replaying a long alert — a stack trace, a CI failure summary, a build log excerpt — returns a body the agent can see is incomplete and cannot complete.

Every other truncation in the tool has an escape hatch: list-level truncation pages via `since=<last message id>`, which works. Body truncation has none.

The `ntfy://{topic}

[Read the thread](https://github.com/cyanheads/ntfy-mcp-server/issues/17) · 2026-07-28 · closed · 2 comments

The remaining reports are on [the project's issue tracker](https://github.com/cyanheads/ntfy-mcp-server/issues).
