vikunja-mcp
This MCP server connects to a Vikunja instance and exposes task management operations as typed tools for AI clients like Claude. There is no delete tool by design — tasks can only be created, updated, or toggled, never destroyed.
check_connection: Verify connectivity to your Vikunja instance. With a project ID, confirms task read access; without one, lists all projects the token can see — useful for diagnosing auth/scope issues.list_tasks: Retrieve tasks from a project (open only by default, or all including completed), sorted by open status → priority → ID.get_task: Fetch a single task by its global ID, including its full description (converted from stored HTML to markdown).add_task: Create a new task with optional title, description (markdown), priority (0–5), due date (yyyy-MM-dd), and labels (auto-created if missing).update_task: Modify specific fields of an existing task (only passed fields change); supports clearing due date or description, and appending to descriptions in chunks for long content.complete_task: Mark a task as done.reopen_task: Mark a completed task as not done.
Click on "Install 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., "@vikunja-mcplist my open tasks"
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.
vikunja-mcp
An MCP server that exposes tasks in a Vikunja instance as typed tools — list / get / add / update / complete / reopen — for Claude Code, Claude Desktop, or any MCP client. Runs locally over stdio (the client launches it as a subprocess). Write-only — there is no delete tool, by design.
The API token comes only from the session environment (never a config file) and the project is mandatory (a default via env, or passed per-call).
Prerequisites
Installation is a couple of uv commands — no OS installer, no background service (the MCP client
launches the server on demand). You need three things:
uv — the only hard dependency. It provisions a matching Python (
requires-python >=3.11) and installs the deps (mcp,httpx,truststore,markdown) itself, so a system Python/pip is optional. Install it once:winget install astral-sh.uv # Windows (or: irm https://astral.sh/uv/install.ps1 | iex)curl -LsSf https://astral.sh/uv/install.sh | sh # Linux / macOSAn MCP client to register the server with — Claude Code or Claude Desktop.
A Vikunja
service-account API token (scoped minimally: task read/create/update, project-views + view-tasks read, label read/create) plus your instance URL. With a project ID the token needs no 'read all projects' scope. Share the target project with that account.
The first install (uv tool install or uv sync) needs network access — uv fetches a matching
Python and the packages; after that the server runs offline against your instance.
Related MCP server: Tasks MCP Server
Install
Two ways, by intent — use the server, or work on it.
Use it: install as a standalone tool
uv tool install builds the package into an isolated, uv-managed environment and drops a
vikunja-mcp launcher on your tool bin dir (%USERPROFILE%\.local\bin on Windows, ~/.local/bin
elsewhere). The source is disposable once installed — this is what keeps the server installed with
no checkout on the machine. Run uv tool update-shell once to add that bin dir to your
user PATH (a permanent, user-scope change), then relaunch your shell so it takes effect — after
that the vikunja-mcp launcher resolves by name, with no path needed anywhere.
Install from the git remote — uv clones to a temp dir, builds, and discards it, so the source
never lands on disk (note the url form is host/path, not the host:path an SSH remote prints):
uv tool install git+ssh://git@your-git-host/you/vikunja-mcp.git
uv tool upgrade vikunja-mcp # update later (re-fetches the remote)Two things to get right when installing/upgrading from the remote:
Push first. The build uses the remote's committed state, so
git pushyour work before you install or upgrade — a run before the push silently builds the previous version, not your latest.Release the launcher first. Before a reinstall/upgrade, make sure nothing is still running the server — quit Claude Desktop especially, since it keeps the
vikunja-mcpexecutable open. A running launcher can't be overwritten, and a half-finished reinstall can leave the tool environment broken (recover by quitting the holders and re-running the install).
…or from a wheel you build yourself, if you'd rather not reach the remote to install or update:
uv build # -> dist/vikunja_mcp-<ver>-py3-none-any.whl
uv tool install ./dist/vikunja_mcp-<ver>-py3-none-any.whl
uv tool install --reinstall ./dist/vikunja_mcp-<newver>-py3-none-any.whl # update laterRemove it with uv tool uninstall vikunja-mcp. Upgrades keep the launcher path stable, so the MCP
registration below never has to change — just relaunch the client.
Work on it: dev checkout
For hacking on the code, keep the checkout and run out of it — no install, edits take effect live:
cd vikunja-mcp
uv sync # creates the venv, provisions Python if needed, installs deps
uv run pytest # optional: run the client testsConfigure (environment only)
Setting | Env var | Notes |
Instance URL |
| required; the http(s) base URL of your instance |
Default project by ID |
| optional, preferred; set it per repo (below); no |
Default project by name |
| optional; its name→ID lookup needs 'read all projects' |
Token |
| secret — session env only, see below |
Token — session env only, never persisted. Set it in the shell/session you launch the MCP client from; the server the client spawns inherits it. Do not put it in
.mcp.json. If it's missing,check_connection(and every write) reports the fix and stops.$env:VIKUNJA_API_TOKEN = Read-Host -AsSecureString "Vikunja API token" | ConvertFrom-SecureString -AsPlainText # PowerShell 7+read -rs -p "Vikunja API token: " VIKUNJA_API_TOKEN && export VIKUNJA_API_TOKEN # bash read -rs "VIKUNJA_API_TOKEN?Vikunja API token: " && export VIKUNJA_API_TOKEN # zsh (its -p means coprocess)Each prompts with echo off, so the token never reaches the command line or your shell history. A running client captured its environment at launch, so after setting it you must relaunch it.
Default project is optional; a tool's
project_idargument overrides it. Leave it unset if you work across several projects on this machine — thencheck_connection,list_tasksandadd_tasktake the project per call, and ask you which project to use (prefer the numeric id) when you haven't said. Set a default only if one project dominates, to skip that prompt.get_task,update_task,complete_taskandreopen_taskneed no project at all: a task id is global and identifies the task on its own.
Register with an MCP client
Each setting has one right home, because each changes at a different rate:
Setting | Where | Why there |
| the shell you launch from | secret; never in any file |
| user scope — register the server once | one instance per machine |
| per repo — | differs for every repo |
Register the server once, at user scope, with only the URL. For a standalone-tool install, register
the launcher by name — this works because uv tool update-shell (above) put its bin dir on
PATH; relaunch first if you haven't since. A later uv tool upgrade keeps the launcher name, so
this registration never changes on update:
claude mcp add vikunja --scope user --env VIKUNJA_URL=https://your-vikunja-host -- vikunja-mcpFrom a dev checkout instead, run it out of the tree — no install needed, but the checkout must stay:
claude mcp add vikunja --scope user --env VIKUNJA_URL=https://your-vikunja-host -- uv run --directory /abs/path/to/vikunja-mcp vikunja-mcpThen, in each repo whose tasks live in a Vikunja project, name that project in
.claude/settings.json — Claude Code applies its env to the MCP servers it spawns, so this adds
the default without redefining the server:
{ "env": { "VIKUNJA_PROJECT_ID": "11" } }Do not put VIKUNJA_PROJECT_ID at user scope: one machine spans several projects, and a global
default silently sends every repo's tasks to whichever project you named first. Omit it entirely and
the tools ask which project to use — the intended fallback, not a failure. Use
.claude/settings.local.json instead if the id shouldn't be committed (it is gitignored).
Prefer a project-scope .mcp.json only if a repo needs a wholly different
server (a second instance, say). Scopes do not merge — Claude Code takes the entire entry from
the highest-precedence scope (local → project → user), so a project-scope entry must restate
command, args and VIKUNJA_URL, or it will lose them. The token is deliberately absent from
that file — set it in your shell (above).
Then relaunch Claude and run /mcp (or claude mcp list) to confirm vikunja is connected.
Claude Desktop / cowork — the .mcpb bundle
Claude Desktop (and cowork in local mode, which shares Desktop's config) doesn't use
claude mcp add; it installs an MCP bundle. A ready-to-build manifest lives in
packaging/mcpb/ — see its README for the full build + install steps. In short:
Build the
.mcpbwith Anthropic's official@anthropic-ai/mcpbCLI (mcpb validate+mcpb pack), so the manifest is schema-checked — not a hand-zipped archive.Drag it onto Settings → Extensions. It prompts for the URL and token at install; the token field is
sensitive, so Desktop keeps it in the OS keychain and injects it at launch. This is the one place the token model differs from the CLI — Desktop has no launching shell to inherit it from — and it still never touches a config file.cowork remote can reach neither a LAN instance nor your local launcher, so the bundle only works in local mode on the machine where the server is installed.
Verify
Ask Claude to run check_connection. Name a project ("check the Vikunja connection for project 7")
and it returns { ready: true, url, project, ... } once a task read succeeds — this verifies
exactly the scope list_tasks/add_task need, using only project-scoped permissions. Run it with
no project and it returns { ready: true, url, projects: [...] }, listing the projects the token
can see — handy for discovering which id to pass. That no-project path uses GET /projects, so a
token scoped to specific projects gets a 403 there and the result says to pass a project_id
instead (not a broken setup). Either way, { ready: false, issues: [...] } names the specific cause
(token / URL / 401 bad-token / 403 missing-scope). Then "list my open Vikunja tasks".
Tools
Tool | Does |
| readiness probe. With a project: verifies a task read. Without: lists the projects the token can see (see below) |
| open tasks (or all), sorted open→priority→id |
| one task, including its description (returned as markdown — see below) |
| create (priority 0..5; |
| change only the passed fields; |
| mark done / not done |
Tasks come back as structured JSON. Priority is 0..5: 0=Unset 1=Low 2=Medium 3=High
4=Urgent 5=DO NOW.
Field | Notes |
| global; this is what every |
| the per-project number the UI shows ( |
| which project the task is in; the task-id tools take no project, so this is the only way to tell |
| writable via |
|
|
| RFC3339; |
| usernames, read-only (see below) |
|
|
|
|
| seconds; |
|
|
Everything above rides in the payload Vikunja already sends, so none of it costs an extra request.
bucket_id, position, cover_image_attachment_id and reactions are dropped: kanban/UI state
with nothing in it for a caller. Unset dates arrive as 0001-01-01T00:00:00Z and are normalized to
"", so nothing renders a year-1 timestamp as real.
Only title/description/priority/due/labels are writable; the rest are reported as-is.
assignees are usernames rather than ids because a project-scoped token gets 401 on GET /user
— it can read the assignees embedded in a task, but cannot look a user up, so an id would be a
handle nothing here can resolve. related_tasks is trimmed to references on purpose: Vikunja nests
the entire related task, description included, which would put a task's whole body inside every
task linking to it.
Descriptions: markdown both ways. Vikunja's description field stores the HTML its WYSIWYG editor
produces — HTML is the storage format — but the tools present markdown at both ends.
add_task/update_task take markdown and convert it to HTML on the way in; get_task converts the
stored HTML back to markdown on the way out. So you read and write the same format. Feeding a
description from get_task straight into update_task is safe (it just converts back to HTML). The
read-side conversion is a rendering convenience, so unusual editor markup (tables, checkboxes) can
come back as slightly rough markdown — expected, not a failure. Note description_append still
concatenates on the stored HTML internally, so it stays exact no matter how reads render.
Long descriptions: description_append. Neither this server nor Vikunja limits description
length in practice (~1MB round-trips fine). The real ceiling is the calling agent's output budget
for a single tool call: the description is text the model has to emit, and an over-long call is
truncated before it reaches this server — so the failure looks like the tool erroring, not Vikunja
rejecting anything. Writing the text to a file first does not help; that costs the same tokens.
Instead, send the first part, then grow it:
add_task(title="Report", description="# Report\n\nOpening.") -> id 42
update_task(42, description_append="## Findings\n\n...")
update_task(42, description_append="## Conclusion\n\n...")Vikunja has no append endpoint, so this is a read-modify-write: the chunk is converted to HTML and concatenated onto the stored HTML (which is never re-converted). Each call costs only its own chunk, so a description can grow far past what one call could carry. Split on block boundaries — each chunk is converted as standalone markdown, so a chunk cut mid-block (half a code fence, a split table) converts wrongly and the next chunk cannot repair it.
This MCP server was built with Claude Code.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceAn OAuth-authenticated MCP server that bridges Claude AI with a task management system, allowing users to list, create, and update tasks through natural language commands.1
- Flicense-qualityDmaintenanceA task management MCP server that provides tools to create, list, complete, and delete tasks using pluggable storage backends. It enables users to interact with their task lists through natural language using MCP-compatible clients like Claude Desktop.
- AlicenseAqualityDmaintenanceAn MCP server that enables AI assistants to interact with the Taskwarrior command-line task management tool. It allows users to list, create, modify, and organize tasks using projects, tags, and annotations through natural language.132MIT
- Alicense-qualityBmaintenanceMCP server that wraps a personal task manager REST API, enabling Claude to read and manage tasks via tools like get_tasks and add_task.9MIT
Related MCP Connectors
MCP server for generating rough-draft project plans from natural-language prompts.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Markdown-first MCP server for Notion API with 8 composite tools and 39 actions.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/skrimokst/vikunja-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server