This is a fork of jrejaud/vikunja-mcp by Jordan Rejaud. The original project is an excellent, cleanly architected MCP server for Vikunja with minimal dependencies, strict TypeScript, and Zod-based input validation. All credit for the original design and implementation belongs to Jordan. This fork applies security hardening for use in DoD and enterprise environments.
A Model Context Protocol server that connects AI assistants to Vikunja task management. 16 tools for managing projects, tasks, and labels — hardened for DoD and enterprise deployment.
# Pull the latest release
docker pull ghcr.io/darkhonor/vikunja-mcp:latest
# Or a specific version
docker pull ghcr.io/darkhonor/vikunja-mcp:1.0.0Multi-architecture images are available for linux/amd64 and linux/arm64.
# Podman (recommended for DoD environments — rootless, daemonless)
podman build --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) -t vikunja-mcp .
# Docker
docker build --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) -t vikunja-mcp .The multi-stage build runs npm audit, type checking, and all 81 unit tests as
build gates. A failing gate blocks image creation (NIST CM-5, SA-11).
Create a .env file with non-secret configuration:
VIKUNJA_URL=https://vikunja.example.com
VIKUNJA_LOG_LEVEL=info
VIKUNJA_RATE_LIMIT=30Create a token file containing your Vikunja API token:
mkdir -p ~/.config/vikunja-mcp
echo "your-api-token" > ~/.config/vikunja-mcp/token
chmod 604 ~/.config/vikunja-mcp/tokenNote: The token file must be readable by the container user (UID 998). Use
chmod 604(owner read-write, others read) for local Docker/Podman bind mounts. In Kubernetes, usefsGroupin the pod security context to match the file group to the container user.
Add to ~/.claude.json:
Podman:
{
"mcpServers": {
"vikunja": {
"command": "podman",
"args": [
"run", "--rm", "-i",
"--read-only",
"--cap-drop=ALL",
"--security-opt=no-new-privileges",
"--env-file", "/path/to/.env",
"-v", "/path/to/token:/run/secrets/vikunja-token:ro",
"ghcr.io/darkhonor/vikunja-mcp:latest"
]
}
}
}Docker:
{
"mcpServers": {
"vikunja": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--read-only",
"--cap-drop=ALL",
"--security-opt=no-new-privileges",
"--env-file", "/path/to/.env",
"-v", "/path/to/token:/run/secrets/vikunja-token:ro",
"ghcr.io/darkhonor/vikunja-mcp:latest"
]
}
}
}| Flag | Purpose |
|---|---|
-i |
Keeps stdin open — required for MCP stdio transport |
--rm |
Auto-remove container when MCP session ends |
--read-only |
Read-only root filesystem — container writes nothing to disk |
--cap-drop=ALL |
Drop all Linux capabilities (DoD Container STIG) |
--security-opt=no-new-privileges |
Prevent privilege escalation via setuid/setgid |
--env-file |
Non-secret configuration (URL, log level, rate limit) |
-v ...:ro |
Mount token file read-only into container |
Warning: Never use
-t(TTY allocation). TTY escape sequences corrupt the JSON-RPC protocol used by MCP stdio transport.
The container expects the API token mounted as a file at
/run/secrets/vikunja-token. This path is fixed and not configurable.
The .env file contains only non-secret configuration — the token is
never passed as an environment variable.
Environment variables are visible in process listings (ps auxe) and container
inspect output (podman inspect). File-based secrets avoid this exposure.
If you prefer to run the MCP server directly without a container:
| Variable | Required | Description |
|---|---|---|
VIKUNJA_URL |
Yes | Your Vikunja instance URL (HTTPS required) |
VIKUNJA_API_TOKEN_FILE |
Preferred | Path to file containing API token |
VIKUNJA_API_TOKEN |
Fallback | Direct API token value |
VIKUNJA_LOG_LEVEL |
No | Logging verbosity: error, warn, info (default), debug |
VIKUNJA_RATE_LIMIT |
No | Max requests per minute (default: 30) |
NODE_EXTRA_CA_CERTS |
No | Path to custom CA certificate bundle for private PKI |
Add to ~/.claude.json:
{
"mcpServers": {
"vikunja": {
"command": "node",
"args": ["/path/to/vikunja-mcp/dist/index.js"],
"env": {
"VIKUNJA_URL": "https://vikunja.example.com",
"VIKUNJA_API_TOKEN_FILE": "/path/to/vikunja-token",
"NODE_EXTRA_CA_CERTS": "/path/to/ca-chain.crt"
}
}
}
}git clone https://github.com/darkhonor/vikunja-mcp.git
cd vikunja-mcp
nvm use # Uses .nvmrc (Node 22)
npm install
npm run build
npm test # 81 tests across 5 modulesIf your Vikunja instance uses a TLS certificate signed by a local or private Certificate Authority (e.g., DoD PKI, corporate CA, self-signed root), you must provide the CA certificate bundle so Node.js can verify the connection. This is not required if your server uses a certificate from a publicly trusted CA such as Let's Encrypt, DigiCert, or any other provider whose root is included in the system trust store.
Mount your CA certificate bundle into the container and set the
NODE_EXTRA_CA_CERTS environment variable. Add these flags to your
docker run or podman run args:
{
"mcpServers": {
"vikunja": {
"command": "podman",
"args": [
"run", "--rm", "-i",
"--read-only",
"--cap-drop=ALL",
"--security-opt=no-new-privileges",
"--env-file", "/path/to/.env",
"-v", "/path/to/token:/run/secrets/vikunja-token:ro",
"-v", "/path/to/ca-chain.crt:/etc/ssl/certs/custom-ca.crt:ro",
"-e", "NODE_EXTRA_CA_CERTS=/etc/ssl/certs/custom-ca.crt",
"ghcr.io/darkhonor/vikunja-mcp:latest"
]
}
}
}Set the NODE_EXTRA_CA_CERTS environment variable to the path of your CA
certificate bundle:
{
"mcpServers": {
"vikunja": {
"command": "node",
"args": ["/path/to/vikunja-mcp/dist/index.js"],
"env": {
"VIKUNJA_URL": "https://vikunja.example.com",
"VIKUNJA_API_TOKEN_FILE": "/path/to/vikunja-token",
"NODE_EXTRA_CA_CERTS": "/path/to/ca-chain.crt"
}
}
}
}The CA bundle file must be PEM-encoded and can contain one or more certificates in the chain (root CA, intermediate CAs). Concatenate them in order from leaf to root:
cat intermediate-ca.crt root-ca.crt > ca-chain.crtNote:
NODE_EXTRA_CA_CERTSadds your custom CAs alongside the default Node.js trust store — it does not replace it. Public CA certificates will continue to work normally.
- vikunja_list_projects — List all projects
- vikunja_create_project — Create a project (supports nesting via
parent_project_id) - vikunja_update_project — Update project title, description, archive status
- vikunja_delete_project — Delete a project and all its tasks
- vikunja_list_tasks — List tasks across all projects (search, filter, sort, paginate)
- vikunja_list_project_tasks — List tasks in a specific project
- vikunja_get_task — Get full task details
- vikunja_create_task — Create a task in a project
- vikunja_update_task — Update task fields
- vikunja_complete_task — Mark a task as done
- vikunja_delete_task — Delete a task
- vikunja_bulk_create_tasks — Create multiple tasks at once (1-50, rate-limited)
- vikunja_list_labels — List all labels
- vikunja_create_label — Create a label with optional color
- vikunja_add_label_to_task — Assign a label to a task
- vikunja_remove_label_from_task — Remove a label from a task
This MCP wraps the Vikunja REST API v1. A few quirks:
- Vikunja uses PUT for creation and POST for updates (opposite of typical REST)
- Listing tasks in a project requires a View ID — the MCP handles this automatically by fetching the first view
- Dates use ISO 8601 format:
2026-03-15T00:00:00Z - API tokens can be created in Vikunja under Settings > API Tokens
This fork implements the following DoD STIG and NIST SP800-53 Rev 5 security controls:
- HTTPS enforced at startup — plaintext
http://URLs are rejected - Custom CA support via
NODE_EXTRA_CA_CERTSfor private PKI (DoD PKI, internal CAs)
- File-based token loading via
VIKUNJA_API_TOKEN_FILE(Docker/K8s secrets compatible) - Token file permission validation — warns on world-readable files
- Tokens never appear in logs or error messages
- Structured JSON audit entries written to stderr (MCP convention)
- Mutating operations (create/update/delete) logged at
infolevel - Read operations logged at
debuglevel for low-noise production use - Configurable via
VIKUNJA_LOG_LEVELenvironment variable - Fields: timestamp (ISO 8601), level, operation, resource, resourceId, status, error
- Typed error hierarchy:
AuthenticationError,AuthorizationError,NotFoundError,ValidationError,RateLimitError,ServerError - Raw API response bodies stored privately for debug — never surfaced to MCP output
- Bulk operations report per-item success/failure for forensic traceability
- Zod schemas enforce whitelist validation on all tool inputs
- Validated types: positive IDs, hex color codes (
#RRGGBB), ISO 8601 datetimes, priority (0-4), pagination bounds (1-200), sort order - Bulk create bounded to 1-50 tasks per call
- Token-bucket rate limiter — configurable via
VIKUNJA_RATE_LIMIT(default: 30 req/min) - Automatic retry on 429 (Too Many Requests) and 503 (Service Unavailable)
- Exponential backoff with jitter: base 1s, max 30s, up to 3 retries
- Respects
Retry-Afterheader from Vikunja
- 81 unit tests across 5 modules (Vitest)
- CI pipeline: build, lint, test on Node 20 and 22
- npm audit on every build (critical threshold)
- CycloneDX SBOM generated on each release (SR-4)
- Dependabot for weekly dependency monitoring
- Branch protection with required reviews
- All source files carry DoD STIG/NIST SP800-53 compliance headers
- SECURITY.md with vulnerability disclosure process
- CODEOWNERS for review enforcement on security-critical files
MIT — See LICENSE for details.
Original work Copyright (c) 2026 Jordan Rejaud. Hardened fork maintained by Alex Ackerman (darkhonor).