Skip to main content
Glama

habitica-mcp

An MCP server for a self-hosted Habitica instance, served over Streamable HTTP so it can run as a normal networked service rather than a per-client stdio subprocess.

Why this exists

The existing community server (iBreaker/habitica-mcp-server) hardcodes https://habitica.com/api/v3, is stdio-only, and has been unmaintained since three days after it was created. None of that works for a self-hosted instance behind an ingress.

Here, HABITICA_BASE_URL is required with no default — pointing at the wrong instance is made impossible rather than merely discouraged.

Related MCP server: habitca-mcp

Tools

Tool

Notes

list_tasks

Optional type filter; history excluded (see below)

get_task

create_task

Not idempotent — Habitica has no idempotency key

update_task

Partial update

delete_task

Destructive

score_task

Destructive — mutates gold/XP/streaks, cannot be undone

list_tags / create_tag

add_tag_to_task / remove_tag_from_task

Take a tag name, resolved to its UUID

get_user_stats

Server-side projection, not the full user document

Configuration

Variable

Required

Default

Purpose

HABITICA_BASE_URL

yes

e.g. http://habitica.tools.svc.cluster.local:3000

HABITICA_USER_ID

yes

x-api-user

HABITICA_API_TOKEN

yes

x-api-key

MCP_ALLOWED_HOSTS

no

(empty — validation off)

Comma-separated Host allowlist for /mcp

MCP_HOST / MCP_PORT

no

0.0.0.0 / 8080

HABITICA_TIMEOUT_MS

no

15000

LOG_LEVEL

no

info

Endpoints: POST/GET/DELETE /mcp, and GET /healthz.

Design notes

Four decisions that are load-bearing and non-obvious:

Response projection, not pagination. Habitica's GET /tasks/user returns history: [{date, value}] on every habit and daily — one entry per scoring event for the life of the account, and it is on by default. The API offers no limit/offset, so the fix is projection: this server always sends history=false and additionally projects each task to a fixed field set, so an upstream schema change cannot silently reintroduce hundreds of KB into a model's context. get_user_stats uses ?userFields= for the same reason.

The list filter is plural and irregular. GET /tasks/user?type= accepts habits | dailys | todos | rewards | completedTodos (note dailys), while the create body takes the singular habit | daily | todo | reward. Tools expose the singular form and map internally; passing the singular form to the list endpoint 400s.

Host validation is scoped to /mcp, never app-wide. createMcpExpressApp applies it globally, which would break both kubelet probes (an httpGet probe sends Host: <podIP>, and pod IPs can never be allowlisted) and blackbox monitoring (which sends Host: <svc>.<ns>.svc). /healthz therefore sits outside the guard; it exposes nothing, and DNS-rebinding protection only matters for the JSON-RPC surface.

/healthz reports process liveness only — never Habitica reachability. A connectivity check would turn a Habitica restart into a CrashLoopBackOff here, and the liveness probe would then keep killing a process that is perfectly healthy and simply has nothing to talk to. Habitica outages surface as clean per-tool JSON-RPC errors instead.

Transport

Stateless Streamable HTTP (sessionIdGenerator: undefined), built on @modelcontextprotocol/server v2 — the current stable major, whose HTTP transport lives in the separate @modelcontextprotocol/express / @modelcontextprotocol/node adapters. The negotiated protocol version is 2025-11-25 (LATEST_PROTOCOL_VERSION in the SDK); v1.x is now security-and-bugfix only.

A fresh McpServer + transport is created per request, torn down on the response's close event. Per-request construction is required rather than tidy: SDK v1 throws outright on stateless transport reuse ("Stateless transport cannot be reused across requests"), because reuse causes message-ID collisions between concurrent clients.

The cost is real and worth knowing: each request rebuilds 11 zod→JSON-Schema conversions, which measured at roughly 0.5 MB of garbage per call. It is reclaimed under GC pressure (1500 sequential calls settled at ~193 MiB with a 96 MB heap cap) rather than leaking, but it is why the deployment requests more memory than the idle footprint suggests.

GET /mcp returns 405 with Allow: POST. This is spec-legal (a server may refuse the standalone stream) and is what the MCP client explicitly expects — it special-cases 405 as "no server stream here" and stops.

An earlier version tried to be accommodating by returning an empty SSE stream instead. That caused an infinite reconnect loop: the client treats a cleanly-ended stream that carried no response as a dropped connection and reschedules, but its retry counter only advances on failure, so a successful empty stream reset nothing. Measured at ~1 req/s forever — 1 → 4 → 8 → 12 GETs over 12s idle, roughly 86k requests/day per connected client, with no error surfaced anywhere. Returning 405 holds it at exactly 1.

Stateless has a real cost, not just upside: server→client round-trips (sampling, elicitation) and unsolicited *ListChanged notifications cannot work, because the client's reply arrives as a new HTTP request that lands on a fresh server instance with no memory of the pending call. Progress notifications do work — they ride the originating request's own stream. None of that matters for a CRUD tool surface, but do not build on those capabilities here.

Security

The /mcp endpoint is unauthenticated. The Habitica credential lives server-side, so anyone who can reach the endpoint can read and write the account's entire task list. This is deliberate — an auth proxy in front of an MCP endpoint breaks MCP clients — and it is why the deployment is restricted to a private network and a single replica.

The API token is a user-level Habitica credential (stored in plaintext by Habitica itself), so leaking it is full account compromise. All log output passes through a redacting logger, with a test asserting the token never appears in any emitted line.

Development

npm ci
npm test
npm run lint && npm run typecheck
npm run build && node dist/index.js

License

MIT

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A standalone MCP server for managing habits and quit trackers through a jhabit instance. It enables users to list trackers, log entries, and retrieve detailed statistics like streaks and abstinence time.
    -
  • A
    license
    C
    quality
    C
    maintenance
    Habitica MCP server built with Effect v4, currently exposing a hello-world tool, resource, and prompt over stdio for early development and testing.
    1
    15 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server for managing Habitica as a daily execution layer, enabling agents to read and (with explicit confirmation) create, complete, and score tasks via the Habitica API.
    15 npm
    MIT