kutt-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@kutt-mcp create a short link for https://example.com/docs on my Kutt instance"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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:httpnpm 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-mcpRegenerate the registry from the committed inventory.
npm run gen:operations
node scripts/gen-operations.mjs --checkRefresh 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 --checkKUTT_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 |
| Required. Origin only, |
| Required. Sent as |
| Must be the literal |
| Local request budget. Default 30. Cache hits do not spend it. |
| Upstream timeout. Default 20000. |
| Read cache TTL. Default 60. Writes clear the cache. |
| Contact token in the User-Agent. Must not contain the API key. |
|
|
| 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 |
| list, create, update, delete, stats | GET/POST |
| list, create, delete | GET |
| health, me | GET |
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. Uselistorstats.Official
editLink(server/handlers/links.handler.js) applies description only when it is truthy. Sendingdescription: ""does not clear a stored description.Official
createLinkdescription usescheckFalsy: trueand min length 1, so an empty create description is skipped by validators.Documented
allon GET/linksis unused by v3.2.6links.get. The handler always scopes toreq.user.id.Missing-link handlers often throw
CustomErrorwithout 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.mdruns on Node 22.better-sqlite311.8.1 has no Node 24 prebuild here, and this environment has nomake.
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 toolskutt_domainsADestructive
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_infoARead-only
Read Kutt health and the authenticated user profile. me never returns apikey. Actions: health, me.
Inputs: action (required).
kutt_linksADestructive
List, create, update, delete, and read stats for Kutt short links. List query is limit, skip, all, and source-supported search. There is no native GET /links/{id}. 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, update, delete, stats.
Inputs: action (required), id, all, skip, +11 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 3 tools
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.
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.
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.
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
Related MCP Connectors
Create and manage short links, track clicks, and automate URL management
Create and manage short links and QR codes, and read click analytics, on your own domain.
Make and manage short links on your own domain, with analytics and routing rules.
Shorten links with Cuttly, read click stats, and edit, expire, redirect or delete short links.
Related MCP Servers
- AlicenseBqualityFmaintenanceEnables AI assistants to interact with a Shlink URL shortening instance, allowing creation, listing, retrieval, and deletion of short URLs, as well as tag management.54Apache 2.0
- AlicenseAqualityCmaintenanceEnables to manage and analyze a Shlink URL-shortener instance with tools for short URLs, visits, tags, domains, redirect rules, QR codes, and Mercure integration.2318 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables 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 npmMIT
- AlicenseAqualityBmaintenanceEnables AI assistants to shorten links, read click statistics, and manage signed webhook subscriptions for a kua.lat installation through its API.11292 npmMIT