Skip to main content
Glama
kutt-mcp

kutt-mcp

by si-essence

kutt-mcp

MCP connector for a self-hosted Kutt v3.2.6 instance. It speaks the official JSON APIv2 over stdio and over stateless Streamable HTTP on localhost.

Kutt is MIT software. This connector does not copy the application. It is not affiliated with thedevs-network/kutt.

spec/api-inventory.json is a compact extraction of every OpenAPI path in docs/api/api.js from Kutt v3.2.6, prefixed with /api/v2. It records the release version, the official package.json sha256, docs/api/api.js sha256, server/passport.js sha256, and routeTreeSha256. scripts/gen-operations.mjs maps that inventory onto tool actions or exclusions and writes src/operations.ts and kutt.routes.json. A route that is neither mapped nor excluded fails generation. Do not edit src/operations.ts by hand. Default generation reads only the committed inventory.

KUTT_URL and KUTT_API_KEY are required. There is no default origin. Hosted kutt.it and kutt.to origins are rejected. Each request sends X-API-KEY matching server/passport.js apiKeyHeader: "x-api-key".

Setup

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 of its own. Keep it on localhost.

{
  "mcpServers": {
    "kutt": {
      "command": "node",
      "args": ["dist/index.js"],
      "env": {
        "KUTT_URL": "http://127.0.0.1:8101",
        "KUTT_API_KEY": "...",
        "KUTT_ALLOW_WRITES": "false"
      }
    }
  }
}

The key is the dashboard API key from the self-hosted instance. It is not a session cookie. The connector never prints it.

HTTP mode has no authentication of its own. Keep it on localhost. Do not put secrets in a Docker image.

docker build -t kutt-mcp .
docker run -p 127.0.0.1:3000:3000 \
  -e KUTT_URL=http://127.0.0.1:8101 \
  -e KUTT_API_KEY=... \
  kutt-mcp

Regenerate the registry from the committed inventory.

npm run gen:operations
node scripts/gen-operations.mjs --check

Refresh the inventory from an official v3.2.6 tree. This is the only command that reads route source.

node scripts/gen-operations.mjs --source-root /path/to/kutt --write-inventory
node scripts/gen-operations.mjs --source-root /path/to/kutt --check

KUTT_SOURCE_ROOT is accepted by a write run. --check ignores that variable unless you also pass --source-root.

scripts/live-check.mjs talks to a real server over stdio. Set KUTT_URL. Set KUTT_API_KEY or KUTT_API_KEY_FILE to a chmod 600 file outside this repo. The script unsets DATABASE_URL. It prints pass or fail lines only, and it redacts the key. It does not run inside npm run ci. A missing key is a failure.

Related MCP server: XS Shlink MCP

Config

Variable

Meaning

KUTT_URL

Required. Origin only, http or https, no userinfo, query, hash, or path. Example http://127.0.0.1:8101.

KUTT_API_KEY

Required. Sent as X-API-KEY. Redacted in logs, errors, and tool results.

KUTT_ALLOW_WRITES

Must be the literal true before a confirmed write is sent.

KUTT_PER_MINUTE

Local request budget. Default 30. Cache hits do not spend it.

KUTT_TIMEOUT_MS

Upstream timeout. Default 20000.

KUTT_CACHE_TTL_SEC

Read cache TTL. Default 60. Writes clear the cache.

KUTT_CONTACT

Contact token in the User-Agent. Must not contain the API key.

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.

Calls go to /api/v2/... paths from the operations registry. Accept is application/json. JSON writes send Content-Type: application/json. Redirects are refused so the key is not forwarded. There are no automatic retries. kutt.it and kutt.to are rejected at config load.

Safety

Reads run without a write flag. A write is sent only when confirm is the boolean true and KUTT_ALLOW_WRITES=true. Omit confirm, or pass boolean false, and the tool returns a dry-run preview of method, path, query, headers, and body. It sends nothing. blocked is false on that preview, whether or not writes are enabled. confirm: true with writes disabled returns the same preview with blocked true and the reason KUTT_ALLOW_WRITES is not true. The write was not sent. The string "true" is rejected.

Omitted fields are dropped. reuse: false, skip: 0, and an empty description are sent when you set them. null is rejected. A field that is not valid for the selected action is rejected before any request is sent. Unknown fields are rejected.

HTTP 401 is unauthorized. HTTP 403 is forbidden. HTTP 400 and 422 are invalid input. HTTP 404 is not found. HTTP 409 is conflict. HTTP 429 includes Retry-After when the header is present. Other failures are server, network, or timeout. Response bodies are capped at 1,000,000 bytes.

The API key, password, and session fields are redacted in logs, errors, and tool results. Upstream JSON error bodies are parsed and those fields are replaced before the text is returned or logged. A password you set on that call is removed from the success result, the error text, the preview, and the log, including a password of 3 to 7 characters. Percent-encoded and JSON-escaped copies of that password are removed too. Other short words are left in place. The API key still has to be at least 8 characters. GET /users is projected to email and domains. apikey is never returned.

Tools

Three tools. Each takes action plus the fields for that action. Fields for a different action are rejected.

Tool

Actions

Routes

kutt_links

list, create, update, delete, stats

GET/POST /api/v2/links, PATCH/DELETE /api/v2/links/{id}, GET /api/v2/links/{id}/stats

kutt_domains

list, create, delete

GET /api/v2/users (domains only), POST /api/v2/domains, DELETE /api/v2/domains/{id}

kutt_info

health, me

GET /api/v2/health, GET /api/v2/users

kutt_links list is GET /api/v2/links with limit, skip, documented all, and source-supported search. v3.2.6 GET /links still scopes to the user when all is set. There is no native GET /links/{id}. Stats is the per-id read.

create needs target. Optional description, expire_in, password, customurl, reuse, and domain are sent when set, including reuse: false and empty description.

update needs id and at least one of target, address, description, expire_in, or password. Official editLink treats a falsy description as unchanged.

kutt_domains list is derived from GET /users after scrubbing apikey. Create needs address. Optional homepage is sent when set. Official addDomain does not DNS-check.

kutt_info me returns email and domains. Health returns { status } from the plain text OK body.

9 of 9 official documented APIv2 operations are mapped. 0 are excluded. The registry has 9 actions, one for each HTTP operation. Callers see 10 actions. kutt_domains list is derived from GET /users and is not another HTTP route. Auth, admin HTML, ban, report, and user-deletion routes are outside the OpenAPI inventory. See kutt.routes.json.

Other MCP servers

reklis/kutt-mcp (MIT, dev branch read 7 Nov 2026) registers nine tools in server.py: health_check, list_links, create_link, update_link, delete_link, get_link_stats, create_domain, delete_domain, get_user_info. Stats, domains, and user info already exist there. Those reads are not unique here.

create_link (lines 150-165) builds a payload and calls client.post("/links", json=payload) with no confirm argument and no write-environment guard. update_link (188-197), delete_link (210-213), create_domain (253-260), and delete_domain (273-276) call patch/post/delete the same way. A search of server.py found no confirm input and no ALLOW_WRITES check.

get_user_info (280-292) returns GET /users JSON. The docstring lists apikey. Optional description fields are added only if description:. list_links takes limit, skip, and all_links only. KUTT_BASE_URL defaults to https://kutt.it/api/v2.

This connector does not add local filesystem or external download tools. Every write is sent only when confirm is true and KUTT_ALLOW_WRITES=true. Omit confirm or pass false and the tool returns a preview and sends nothing. Hosted kutt.it/kutt.to origins are rejected. me never returns the API key. Empty strings and reuse: false are sent when the caller sets them. list can pass source-supported search. The route inventory is pinned to v3.2.6.

Limitations

  • There is no documented GET /links/{id}. This connector does not invent one. Use list or stats.

  • Official editLink (server/handlers/links.handler.js) applies description only when it is truthy. Sending description: "" does not clear a stored description.

  • Official createLink description uses checkFalsy: true and min length 1, so an empty create description is skipped by validators.

  • Documented all on GET /links is unused by v3.2.6 links.get. The handler always scopes to req.user.id.

  • Missing-link handlers often throw CustomError without a status, which is HTTP 500 in official code. Live missing-id rows record the actual status.

  • Auth, admin tables, ban, report, signup, password reset, and API key rotation are outside the documented APIv2 inventory.

  • Official app.listen(PORT) has no host argument, so a local process binds every interface on that port.

  • The local Kutt process in LIVE-CHECK.md runs on Node 22. better-sqlite3 11.8.1 has no Node 24 prebuild here, and this environment has no make.

Live check

See LIVE-CHECK.md for the checks that actually ran, including first-start failures. npm run live-check is not part of npm run ci.

npm run ci typechecks, lints, tests, and builds. node scripts/gen-operations.mjs --check reports 9 mapped routes, 0 excluded, 9 registry actions for the documented HTTP operations, 10 exposed actions including the derived domain list, and 9 routes in the spec.

Run unset DATABASE_URL, set KUTT_URL, and set KUTT_API_KEY or KUTT_API_KEY_FILE. Then run npm run live-check. The script never prints the API key.

License

MIT. Kutt itself is MIT. This connector only talks to the published JSON API.

Available Tools

3 tools
kutt_domainsA
Destructive

List, create, and delete custom Kutt domains. List is derived from GET /users domains. Writes run only when confirm is true and KUTT_ALLOW_WRITES=true. Omit confirm or pass false for a preview that sends nothing. Actions: list, create, delete.

Inputs: action (required), id, address, confirm, +1 more.

kutt_infoA
Read-only

Read Kutt health and the authenticated user profile. me never returns apikey. Actions: health, me.

Inputs: action (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 3 tools

    Disambiguation4/5

    The three tools map to clearly separate resources: short links, custom domains, and server/user info, so there is little risk of choosing the wrong one. The only residual ambiguity is inside each tool, where the action parameter selects among list/create/update/delete/stats, so an agent must read the enum rather than the tool name alone.

    Naming Consistency5/5

    All three tools follow a uniform kutt_<resource> convention with plural noun resources (kutt_links, kutt_domains, kutt_info). There is no mixing of casing or verb styles, and the sub-actions are consistently expressed through an action parameter instead of proliferating verb-prefixed names.

    Tool Count4/5

    Three tools is on the thin side but is justified here because each aggregates a whole CRUD surface via an action dispatcher, keeping the surface compact rather than fragmenting into a dozen verb tools. It sits at the low end of the comfortable range, which is acceptable but leaves little granularity.

    Completeness4/5

    Links cover list/create/update/delete/stats and domains cover list/create/delete, plus health and authenticated profile reads, which matches the core Kutt workflow. The explicit absence of a single-link GET and the lack of domain update/verification or user-profile update are minor gaps an agent can work around.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      F
      maintenance
      Enables AI assistants to interact with a Shlink URL shortening instance, allowing creation, listing, retrieval, and deletion of short URLs, as well as tag management.
      5
      4
      Apache 2.0
    • A
      license
      A
      quality
      C
      maintenance
      Enables to manage and analyze a Shlink URL-shortener instance with tools for short URLs, visits, tags, domains, redirect rules, QR codes, and Mercure integration.
      23
      18 npm
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables shortening URLs, expanding short links, and retrieving link and database statistics from a self-hosted link-shortener via its HTTP API, with confirm-gated writes and caching.
      9 npm
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI assistants to shorten links, read click statistics, and manage signed webhook subscriptions for a kua.lat installation through its API.
      11
      292 npm
      MIT