MCP server: One MCP server.

craaft

@craaftRun by its publisher: served from their own domain, namespace checked by the registry.MCP server · External

Read and update your craaft kanban boards: add and move cards, comment, and tick off checklists.

Freeno charge per call

One address for any MCP client. You sign in at craaft.io once, after adding it.

What it does

  1. Reads
  2. Writes
  3. High impact

At most: moves money, runs code or commands, deletes data, or sends messages for you.

What it costs

Free

Odel charges nothing per call. The publisher may charge for its own service.

What it asks for at sign-in

Access toReadChange
CraaftNot stated: craaft

From the sign-in at craaft.io, as the endpoint publishes it. You approve it there.

Tools

None listed · checked 12 Oct 2026

Odel could not list its tools without signing in.

Try it in the Inspector
README11 min read

Craaft MCP Server

Model Context Protocol server for Craaft - lets AI assistants read and write your Kanban boards.

Deployed as an HTTP service. Each request authenticates via the caller's Authorization: Bearer header - either a cra_* personal access token (same ones used by the Craaft REST API) or a cro_* OAuth access token minted by signing in through the connector flow below.

Two-factor sign-in on a craaft account doesn't affect either kind of token. Account security settings (password, two-factor, API keys) are browser-only on the platform, so no tool changes them.

Deploy

bash
cd mcp
gcloud run deploy craaft-mcp \
  --source . \
  --region us-central1 \
  --allow-unauthenticated \
  --set-env-vars CRAAFT_BASE_URL=https://craaft.io,MCP_PUBLIC_URL=https://ai.craaft.io/mcp

The PORT env var is set automatically by Cloud Run.

Connect from Claude (OAuth)

With MCP_PUBLIC_URL set, the server is an OAuth 2.1 protected resource: no token needs to be copied by hand.

craaft is in the Claude directory: connect it from there and skip to step 3. Or add it as a custom connector:

  1. In Claude Desktop or claude.ai, go to Settings > Connectors > Add custom connector.
  2. Paste https://ai.craaft.io/mcp.
  3. Sign in to craaft and approve the connection.

To revoke access later, go to Craaft Settings > Connected apps.

Configure (Claude Desktop, cra_ token)

  1. Sign in to Craaft → Settings → API keys → create a token
  2. Add to ~/.config/claude/claude_desktop_config.json:
json
{
  "mcpServers": {
    "craaft": {
      "url": "https://craaft-mcp-xxxxx.a.run.app/mcp",
      "headers": {
        "Authorization": "Bearer cra_your_token_here"
      }
    }
  }
}

MCP registry

craaft is listed in the official MCP registry as io.craaft/mcp, described by server.json. craaft.io proves it owns the io.craaft namespace by serving a public key at https://craaft.io/.well-known/mcp-registry-auth (platform repo, internal/server/mcpregistry.go). The matching private key is not in this repo.

To publish a change, raise version in server.json (a published version can't be changed or republished), then:

bash
PRIVATE_KEY="$(openssl pkey -in ~/.config/craaft/mcp-registry-key.pem -noout -text | grep -A3 'priv:' | tail -n +2 | tr -d ' :\n')"
mcp-publisher login http --domain craaft.io --private-key "$PRIVATE_KEY"
mcp-publisher publish

Install mcp-publisher from the registry's releases.

Tools

ToolDescription
list_projectsList all accessible Kanban projects
get_boardGet a board's columns and cards (descriptions omitted)
get_cardGet a single card by ID, including description
get_card_detailCard plus comments, events, checklist and attachments in one call
search_cardsCross-project card search (description is a ≤180-char snippet)
create_cardCreate a card at the bottom of a column, with optional metadata (assigned to you unless you name someone)
update_cardUpdate a card's title, description, column, position, priority, assignee, tags, etc.
move_cardMove a card to another column or board
delete_cardPermanently delete a card
add_commentAdd a comment to a card (supports @mentions)
get_commentsList comments on a card
get_focusGet focus overview: due cards, attention cards, hygiene counters
list_notificationsYour notification inbox, newest first, plus the unseen count
mark_notifications_readMark notifications read by id, by card, or all of them
get_meGet the authenticated user's profile
bulk_create_cardsCreate up to 100 cards in one transaction
bulk_update_cardsApply up to 100 partial card updates in one transaction
bulk_move_cardsSweep up to 100 cards into a column, optionally on another board
rebalance_cardsReorder a whole column in one transaction (positions 1, 2, 3, ...)
get_checklistList a card's checklist items
add_checklist_itemAppend an item to a card's checklist
update_checklist_itemTick, untick or retitle a checklist item
delete_checklist_itemDelete a checklist item
get_card_activityA card's activity log: column and board moves, priority and assignee changes
get_hygieneThe cards behind a get_focus hygiene counter (ghosts, stuck, mine_no_date)
list_attachmentsList the files attached to a card
list_board_filesSearch every file on a board's cards, with the card each is on
download_attachmentFetch an attachment's bytes (text inline, binary base64)
upload_attachmentAttach a file to a card from inline content
delete_attachmentDelete an attachment and its stored bytes
get_projectOne project with columns, counts and your role
create_projectCreate a board, optionally from a template (starts with To Do / In Progress / Done otherwise)
update_projectRename, favourite, change visibility, colour scheme, background, text colour or top-bar buttons
delete_projectDelete a board and everything in it
export_projectExport a board as JSON or CSV
preview_importDry run of an import: what board a Trello, Jira, Asana, CSV or craaft export would create
import_boardCreate a new board from a Trello, Jira, Asana, CSV or craaft export
list_board_templatesThe fixed catalogue of board templates (kanban, sprint, bug tracker, ...)
add_columnAdd a column to a board
update_columnRename, recolour, reorder, or set a WIP limit
delete_columnDelete an empty column
archive_columnArchive every card in a done column, returns the archived ids
archive_cardArchive one card without deleting it
restore_cardRestore an archived card to its original column and position
list_archived_cardsA board's archived cards, newest first
list_webhooksA board's outbound webhook subscriptions plus the event catalogue
create_webhookRegister an outbound webhook (craaft signed / Slack / Discord)
update_webhookPause/resume a webhook, or change its URL, format or event filter
delete_webhookDelete a webhook subscription
get_inbound_emailA board's email-to-card intake address, if configured, and the month's AI card usage
enable_inbound_emailTurn on email-to-card and mint the intake address
update_inbound_emailChange active state, target column, or rotate the token
disable_inbound_emailTurn off email-to-card for a board
list_membersWorkspace members, roles and board access
update_member_roleChange a member's workspace role
remove_memberRemove someone from the workspace
list_invitationsPending invitations
invite_memberInvite by email, optionally with board grants
revoke_invitationRevoke a pending invitation
list_board_membersWho can see a board, and whether access is explicit or implied
grant_board_accessGive a member access to a specific board, or change their board role
revoke_board_accessRemove an explicit board grant
update_commentEdit a comment (author only)
delete_commentDelete a comment (author, or workspace owner/admin)
set_card_followFollow or unfollow a card
list_tagsDistinct tags on a board's cards, sorted, including archived
list_milestonesA board's milestones, earliest due first
add_milestoneAdd a milestone (board admins)
update_milestoneRename, re-date, or mark achieved
delete_milestoneDelete a milestone
enable_sharingTurn on read-only public sharing, returns token + URL
disable_sharingRevoke the public link
get_avatarA user's avatar bytes, or the cue to render a placeholder

The three bulk_* tools are all-or-nothing: one bad item rolls the whole batch back and the error names the offending index, so a failed call never leaves a partially applied board. They send no notification emails (activity events and realtime updates still fire), and each call spends one rate-limit token against a 60-burst budget - which is why they should be preferred over looping the single-card tools.

Endpoints

PathPurpose
POST /mcpMCP Streamable HTTP endpoint
GET /healthzHealth check (returns {"status":"ok"})
GET /.well-known/oauth-protected-resource, GET /.well-known/oauth-protected-resource/mcpRFC 9728 protected-resource metadata (only served when MCP_PUBLIC_URL is set)

Env vars

VarDefaultDescription
PORT8080Listen port (Cloud Run sets this automatically)
CRAAFT_BASE_URLhttps://craaft.ioUpstream Craaft API URL
MCP_PUBLIC_URLunset (OAuth metadata off)This server's own public /mcp URL, e.g. https://ai.craaft.io/mcp. When set, serves RFC 9728 protected-resource metadata and 401-challenges missing/invalid tokens to start the OAuth flow. Accepts both cra_ personal access tokens and cro_ OAuth access tokens either way. Must equal the platform's MCP_RESOURCE_URL (both sides lowercase the scheme and host and trim a trailing slash before comparing).

Attachments

This server runs remotely, so it cannot read the caller's filesystem: upload_attachment takes the contents inline (content_text for text, content_base64 for binary), not a path.

download_attachment returns text-ish content types as readable text and everything else base64-encoded. Attachments can be up to 25 MiB, which no conversation wants inline, so it refuses anything over max_bytes (default 256 KiB) and reports the real size instead. Text is truncated rather than refused, since truncated text is still readable; truncated base64 is not, so binary over the limit is skipped entirely.

Attachments are a paid feature. On a Free workspace the upload returns 402 and the error names the current plan. Check canUploadAttachments on the project first - it reflects the board's workspace plan, not the caller's.

Import

preview_import and import_board take the same arguments: filename, the file inline (content_text or content_base64), and optionally source (trello, jira, asana, csv or craaft; detected when left out), name for the new board, and a CSV mapping from craaft fields to headers. Preview first: it writes nothing and, for a CSV, returns the headers, the suggested mapping and sample rows. A mapping replaces the suggestion, so send every field you want. import_board always creates a new private board and counts toward the workspace's board limit (402 when full). Limits: 5 MB, 2,000 cards, 200 columns; attachments aren't imported.

Known platform quirk: archive then delete

archive_column only archives cards in a column flagged isDone, and returns 200 with archived: 0 (and an empty id list) on any other column rather than erroring - check the count. Its returned ids let you undo the sweep with restore_card on each one.

delete_column is blocked by live cards only. Archived cards no longer block it - they are reassigned to the board's first column and the delete proceeds, so they keep existing and stay archived. (This used to fail: the cards_project_column_fk constraint still saw the archived rows, so the delete returned 409 forever. Fixed on the platform side.)

Archive and restore

archive_card takes a card off its board without deleting it; it stays searchable and shows up in list_archived_cards until restore_card puts it back in the exact column and position it was archived from. Archiving an already-archived card, or restoring one that isn't archived, is an error. list_archived_cards uses the same lean board projection as get_board (description omitted), plus archivedAt, capped at 200 rows (newest first) - older archived cards stay reachable through search_cards.

Board templates

list_board_templates is the same fixed catalogue for every caller, no plan gate. Pass a template's key as create_project's template argument to start a board with that column layout. create_project's response never includes columns at all, template or not - that field is only populated by get_project / get_board. Only the default kanban template seeds the historical todo / doing / done column keys anyway, every other template generates its own, so call get_project on the new board before creating cards on it.

Webhooks and email-to-card (board admin, Pro)

Both surfaces are gated the same way: board not visible → not found; visible but you're not a board admin → forbidden; Free workspace → a 402 naming "integrations" as the limit. delete_webhook and disable_inbound_email are not plan-gated, so a workspace that downgraded after setting either up can still turn them off.

A webhook subscription's secret is returned in full on every read (list, create, update) - it is never redacted after creation. It signs craaft format deliveries via the X-Craaft-Signature header; slack and discord deliveries are unsigned chat messages, authenticated only by the receiving service's secret URL. update_webhook's subscription_id argument is the subscription id, not the workspace-level endpoint_id the response also carries.

Email-to-card mints one intake address per board (<token>@<inbound-domain>). Mail becomes a card only when the sender is a workspace member and SPF/DKIM pass; everything else is dropped silently, no error surfaces to the sender. update_inbound_email's rotate flag mints a new token and invalidates the old address immediately - there's no grace period.

Not exposed, and why

GET /projects/{id}/events is the board's SSE realtime stream, not an activity log - a long-lived text/event-stream that doesn't fit a request/response tool. For per-card history use get_card_activity (/cards/{id}/events), which is a real activity log.

Also absent: POST /projects/{id}/cards/rebalance (internal position maintenance), the board background-image endpoints, and the unauthenticated /public/projects/{token} read - a token-holding agent should just use get_board.

Similar modules

Browse Task management