A self-hosted, real-time project management board with built-in MCP server — so AI assistants like Claude can read and write your tasks directly.
Built with AI tools: This project was developed using Claude Code, RTK-AI, and code-review-graph.
- Features
- Screenshots
- Tech Stack
- Getting Started
- Demo Data
- MCP Integration (Claude / AI)
- Second-Brain Agent Installer
- Second-Brain Testing
- Architecture
- Environment Variables
- Running Tests
- Email + password registration with email verification (SMTP or console fallback in dev)
- Two-factor authentication (TOTP) — Google Authenticator, Authy, 1Password, any TOTP app
- Two-step login — email/password then TOTP code when 2FA is enabled
- JWT access tokens (short-lived) + HTTP-only refresh token cookies (rotated on each use)
- Rate Limiting — brute-force protection on sensitive routes (login, register, forgot-password)
- Account Lockout — accounts are temporarily locked after 5 failed attempts
- API keys — long-lived bearer tokens for MCP / programmatic access
- Create multiple organizations per account
- Invite members via single-use invitation links (shareable URL)
- Role system: owner and member
- Owner can transfer ownership to any member
- Owner can delete the organization (requires at least one other org)
- Rename organization and set optional website
- Multiple projects per organization
- Create, rename, and delete projects
- All members of the org have access to all projects
- Four columns: Ideas → To Do → Doing → Done
- Tasks sorted by due date ascending (overdue first)
- Drag-and-drop tasks between columns
- Ideas column collapsible to save horizontal space
- Color-coded task cards — set a custom background colour per task
- Auto-assign the moving user as doer when dragging a task to Doing (no doer set)
- Auto-clear doer when moving back to Todo (drag-and-drop only — MCP
move_tasknever touches the doer, and refuses a move to Doing without one) - Stacking task panels — open multiple tasks side-by-side; inactive panels collapse to vertical title strips
- Side-by-side comparison — hover any task card and click the ⊞ split icon to pin it as a comparison panel alongside your current task
- Integrated Wiki — Keep documentation right alongside your projects
- Collaborative Markdown Editor — Custom-built real-time editor with live sync (no heavy libraries like TipTap/Yjs)
- Split View — Work on two pages side-by-side; perfect for cross-referencing documentation
- Tabbed Interface — Keep multiple pages open and switch between them instantly
- Page Metadata (Properties) — Add frontmatter-style JSON properties to any page for structured data
- Revision History — View previous versions of any page to track changes over time
- Intelligent Markdown Links — Link to other wiki pages or tasks directly in your content
- Unified Search — Find wiki pages and tasks from the same bottom search bar (
Cmd+K)
See the Wiki Documentation for more details.
Each task has a rich detail sidebar with:
- Title and description (multi-line)
- Objective — group tasks under a goal
- Global subject — cross-cutting label
- Start date and end date
- Reporter, Doer, Validator — assignable to any org member
- Watchers and Advisors — additional stakeholders
- Tags — free-form, with autocomplete from existing tags
- Linked tasks — bidirectional links between tasks
- Custom background colour — colour picker
- History log — every field change recorded with who changed it and when, ordered newest-first deterministically (ties broken by insertion order)
- Conflict detection — if two users edit the same field simultaneously, the second user gets a warning with merge options
- Filter by tag — click any tag chip on a card
- Filter by objective
- Filter by doer — click an avatar
- Multiple active filters combine (AND)
- Active filters shown as removable chips with "Clear all"
- Multi-select Done tasks with checkboxes
- Archive in bulk with one click
- Resizable archive panel at the bottom — drag to resize
- Search archived tasks by title
- Paginated results
- Restore archived tasks back to Todo
- Upload a
.csvfile to bulk-create tasks - Columns:
title,description,startDate,endDate,column - Invalid rows are skipped and reported; valid rows are imported atomically
- WebSocket connection per board — live dot indicator (green = connected)
- Task creates, updates, deletes, moves, and reorders pushed to all connected users instantly
- Conflict-aware editing — if you are typing in a field and a remote update arrives, the change is held until you blur, then you are shown a diff and asked to choose
- View and update display name
- Manage TOTP 2FA — enable with QR code scan, disable with code confirmation
- Resend email verification link
- Generate and revoke MCP API keys with labels
- Exposes your board as an MCP server for AI tools
- Streamable HTTP transport
- Authenticated via API key (
Authorization: Bearer <key>) - Tools available to AI:
list_organizations,create_organization,list_memberslist_projects,create_projectget_boardlist_tasks,get_task,get_task_history,create_task,update_task,append_task_note,move_task,delete_tasklist_wiki_pages,get_wiki_page,create_wiki_page,update_wiki_page,delete_wiki_pageget_wiki_history,search_wikiset_wiki_page_propertysearch_knowledge,audit_knowledge_freshness,create_capture,promote_capture_to_task,promote_capture_to_wiki_page
Tasks sorted by due date, colour-coded, with tags and assignees visible at a glance. The Ideas column is expanded on the left.
Click any card to open the full detail panel — description, dates, assignees, tags, linked tasks, colour picker, and history.
Open multiple tasks simultaneously. Inactive panels stack to the right as thin 48px vertical title strips for quick switching. The board remains accessible via horizontal scrolling.
Hover any task card and click the ⊞ split icon to open it as a pinned comparison panel alongside your current task. Clicking other cards replaces only the right panel. Close the comparison with the dashed-split icon in the left panel header.
Click any tag chip to filter the board. Active filters shown as removable chips.
Archived tasks are searchable and restorable from the collapsible panel at the bottom.
Set up 2FA, generate API keys, copy the MCP config snippet for Claude.
| Layer | Technology |
|---|---|
| API | Hono on Node.js |
| Database | SQLite via Drizzle ORM + better-sqlite3 |
| Real-time | WebSockets (@hono/node-ws) |
| Web | Next.js 16 App Router (Server Components + Server Actions) |
| Auth | JWT access tokens, HTTP-only refresh cookies, Argon2 password hashing |
| 2FA | otplib (TOTP) + qrcode |
| nodemailer (SMTP) | |
| Drag & Drop | @dnd-kit |
| MCP | @modelcontextprotocol/sdk |
| Monorepo | pnpm workspaces + Turborepo |
| Tests | Vitest + in-memory SQLite |
The application uses an agnostic approach to configuration. Copy .env.example to .env and configure your secrets.
The core of the "Plug & Play" setup is the URL configuration:
API_URL: Base URL for the API (server-side).WS_URL: WebSocket URL for the browser.
The project is designed so you never have to change your .env when switching between Docker and Local development:
- Set your
.envfor Local Dev (localhost, relative paths). docker-compose.ymlautomatically overrides these values with specific container-internal addresses (api:3001, absolute paths) when running in Docker.
Prerequisites: Node.js ≥ 22, pnpm ≥ 10
# Clone and install
git clone https://github.com/sherault/kanban
cd kanban
./scripts/setup.sh # Initialize .env and generate secrets
pnpm install
# Start all apps via Turborepo
pnpm devOpen http://localhost:3000 (Web) and the API will be available on http://localhost:3010.
The primary docker-compose.yml is used for "production-like" deployments on your local machine.
# Initialise environment
./scripts/setup.sh
# Build and start
docker compose up -d --build- Unified Entry Point: http://localhost:3000
/wsand/mcp/*are automatically routed to the API.- Everything else is routed to the Web app (SSR).
For a real deployment on a server with a domain name and automatic HTTPS:
# Initialise environment
./scripts/setup.sh
# Set your domain and start with the production config
DOMAIN=kanban.example.com docker compose -f docker-compose.prod.yml up -d --buildCaddy will automatically generate and renew SSL certificates via Let's Encrypt for your domain. Ensure ports 80 and 443 are open.
The SQLite database is persisted to ./data/kanban.db on the host.
If you want to run the whole stack in Docker with Hot Module Replacement (HMR) and source code volumes:
# Start in dev mode
docker compose -f docker-compose.dev.yml up --build- Web: http://localhost:3000
- API: http://localhost:3010
This setup mounts your local project directory into the containers and runs pnpm dev.
A seed script creates three demo users, one organization, two projects, and a realistic set of tasks across all columns.
Requirements: both pnpm dev servers must be running.
node scripts/seed.mjsUser accounts: alice@acmecorp.io, bob@acmecorp.io, carol@acmecorp.io. Password: demo1234.
Connect Claude (or any other MCP client) to your board from the Profile page by generating an API key. The page provides ready-to-use config snippets that automatically use the correct URL for your environment (Local vs Docker/Hosted).
Config Snippet Example:
{
"mcpServers": {
"kanban": {
"type": "http",
"url": "http://localhost:3000/mcp/",
"headers": { "Authorization": "Bearer <your-key>" }
}
}
}list_organizations: List all organizations the current user belongs to.list_members: List the members of an organization with their id, display name, and role — use the returned ids to assign tasks.create_organization: Create a new organization.list_projects: List all projects within an organization.create_project: Create a new project.get_board: Full board snapshot in one call — every column with each task's doer, deadline, tags, and colour. Use this at session start instead of several paginatedlist_taskscalls. Archived tasks are excluded.limitPerColumnis configurable (1–200, default 50) with an overflow indicator per column.list_tasks: List tasks in a project with filtering and pagination. Filters:column,tags(a task must carry every listed tag),doerId,validatorId,search. Always ordered by due date ascending.tagremains as a deprecated single-tag alias.get_task: Get a single task by ID, including tags, links, watchers, advisors, and assignments. Use it instead oflist_taskswhen the ID is already known; archived tasks come back with an extraarchived: truefield.get_task_history: Get the change history of a task, newest first — who changed which field, from what to what, when, and from where (source:"mcp","web", ornullfor entries recorded before source tracking). Supports afieldfilter and pagination; long values are truncated to 500 characters.field: "note"yields the task log book — one entry perappend_task_notecall, holding only the appended text.create_task: Create a new task (supports tags, doer, and validator assignment).update_task: Update task details. Tags can be set wholesale withtags, or edited incrementally withaddTags/removeTags(mutually exclusive withtags).append_task_note: Append a note to the end of a task description, atomically server-side. Use it instead of read +update_task(description)for traceability blocks: it never overwrites concurrent edits and needs no prior read. The server prefixes the note with a UTC timestamp heading naming the current doer, and records the note text alone as anotehistory entry (read it back withget_task_history(field: "note")).move_task: Change task column. Unlike drag-and-drop, it never auto-assigns or auto-clears the doer — and because of that it rejects (422) a move todoingon a task with no doer. Assign one withupdate_taskfirst.delete_task: Remove a task.link_tasks: Add a bidirectional link between two tasks.unlink_tasks: Remove a link between two tasks.archive_task: Archive a task in the 'done' column.list_archived_tasks: Retrieve and search through archived tasks.restore_task: Restore an archived task back to the 'todo' column.list_wiki_pages: List all wiki pages in an organization.get_wiki_page: Get the full content and metadata of a wiki page.create_wiki_page: Create a new wiki page with markdown content and optional properties.update_wiki_page: Update page title, content, parent, or properties.set_wiki_page_property: Update one page metadata property without rewriting the whole page.delete_wiki_page: Remove a wiki page.get_wiki_history: View the full revision history of a wiki page.search_wiki: Search wiki pages by title across the organization.search_knowledge: Search wiki content, wiki properties, and tasks together.audit_knowledge_freshness: Find stale, draft, unsourced, expired, or incomplete wiki pages and optionally create review tasks.create_capture: Create a second-brain inbox capture as a wiki page, optionally with a linked triage task.promote_capture_to_task: Turn a capture page into an actionable task and mark the capture triaged.promote_capture_to_wiki_page: Turn a capture page into durable wiki knowledge and mark the capture triaged.
Kanban supports durable interlinks in normal Markdown:
[A wiki page](wiki://<WIKI_PAGE_UUID>)
[A task](task://<TASK_UUID>)AI clients should search or fetch the target first and then link to the returned id. They should not invent UUIDs.
Wiki page properties are the frontmatter-like details/attributes layer for structured metadata such as doc_type, jurisdiction, validation_status, source_urls, freshness, cite_required, related_wiki_ids, and related_task_ids. Use page content for human-readable Markdown.
Kanban includes an interactive installer that can provision a personal second-brain instance and configure coding agents to use it through MCP.
Prerequisites:
- Node.js 22 or newer.
- Docker Desktop or Docker Engine if you want the installer to run Kanban locally.
git, orcurlplustar.- A workspace folder for the agent config, usually the project where you run Codex or Claude Code.
First do a dry run. The bootstrap downloads the installer source into
~/.kanban/app, but the dry run does not start containers, create Kanban data,
or write agent config into your workspace:
curl -fsSL https://raw.githubusercontent.com/sherault/kanban/main/scripts/install-kanban-agent.sh \
| sh -s -- --dry-run --mode docker --clients codex,claude --workspace "$PWD"Then run the interactive local install:
curl -fsSL https://raw.githubusercontent.com/sherault/kanban/main/scripts/install-kanban-agent.sh \
| sh -s -- --mode docker --clients codex,claude --workspace "$PWD"What this does:
- downloads Kanban into
~/.kanban/app; - writes Docker files and SQLite data under
~/.kanban; - starts Kanban with Docker;
- asks for an MCP/skill connection name (default:
Kanban) and your workspace; - asks for your email, display name, organization, and project names;
- creates a verified user, default organization, default project, Second Brain Index, Second Brain Inbox, and triage task;
- shows your generated login credentials once;
- creates an MCP API key and stores it in the OS keychain when possible;
- installs agent instructions, skills, and MCP config into the workspace you passed with
--workspace.
If you want to test without touching the default ~/.kanban folder, set a custom install home:
curl -fsSL https://raw.githubusercontent.com/sherault/kanban/main/scripts/install-kanban-agent.sh \
| KANBAN_HOME="$HOME/.kanban-test" sh -s -- --mode docker --clients codex,claude --workspace "$PWD"If you already have a hosted Kanban instance, use external mode instead:
curl -fsSL https://raw.githubusercontent.com/sherault/kanban/main/scripts/install-kanban-agent.sh \
| sh -s -- --mode external --url https://kanban.example.com \
--clients codex,claude --workspace "$PWD" \
--integration-name "Personal Kanban" \
--skip-provision --mcp-key kbk_your_existing_keyFrom a local checkout:
pnpm agent:installThe installer can:
- run Kanban locally with Docker in
~/.kanban; - connect to an external Kanban URL;
- create a verified default user, organization, project, inbox wiki page, and triage task for Docker installs;
- generate an MCP API key without printing it;
- store the key in the OS keychain when available, with lookup metadata in the connection env file (
~/.kanban/agent.envby default); - fall back to storing the key in that private connection env file;
- install the
kanban-second-brainskill and MCP configuration for Codex and Claude Code; - add experimental Antigravity MCP/instruction files when requested.
The installer is conservative when files already exist:
- Existing
AGENTS.mdandCLAUDE.mdcontent is preserved; the installer adds or updates only its named managed block. - Existing
.mcp.jsonand.antigravity/mcp.jsonkeep unrelated MCP servers; the installer adds its selected connection id. - Existing
.codex/config.tomlkeeps unrelated content; the installer adds its named managed MCP block. - All generated MCP configs use an environment-variable placeholder for the API key, never a raw key.
- If the chosen connection name already exists, the installer stops instead of replacing it. Choose another name to keep both, or pass
--replace-existingwhen intentionally updating that named installation. - If an unmanaged Codex MCP section already uses the chosen id, choose another connection name or edit the TOML manually; the installer will not create a duplicate section.
- Docker mode stops when local Kanban Docker/config/data files already exist under
KANBAN_HOME. To test separately, setKANBAN_HOME="$HOME/.kanban-test". To configure agents against an existing instance, use--mode external. Use--overwrite-local-instanceonly after backing up data and accepting that local configuration may be rewritten.
A name such as Personal Kanban becomes:
- MCP config id:
personal-kanban - skill id:
personal-kanban-second-brain - secret environment variable:
PERSONAL_KANBAN_MCP_API_KEY - secret metadata file:
~/.kanban/agent-personal-kanban.env
It also supports non-interactive installs:
pnpm agent:install -- --mode external --url https://kanban.example.com \
--email you@example.com --mcp-key kbk_... --skip-provision \
--integration-name "Personal Kanban" \
--clients codex,claude --workspace /path/to/workspaceUseful flags:
--mode docker|external|native--url,--api-url,--port--email,--password,--display-name,--org,--project--clients codex,claude,antigravity--workspace /path/to/workspace--integration-name "Personal Kanban"to use a distinct MCP/skill identity--mcp-key kbk_... --skip-provisionfor an existing key--replace-existingto update an already installed named connection--overwrite-local-instanceto allow Docker mode to reuse an existingKANBAN_HOMEafter backup--no-startto write Docker files without starting containers--no-keychainto store only in the connection env file--dry-runto print the plan without writing files
Generated config files use an API-key environment variable. From a local checkout, launch agents through:
pnpm agent:env -- codex
pnpm agent:env -- claudeFrom a curl install, launch through the downloaded helper instead:
node ~/.kanban/app/scripts/kanban-agent-env.mjs -- codex
node ~/.kanban/app/scripts/kanban-agent-env.mjs -- claudeFor a non-default name, pass its env file:
node ~/.kanban/app/scripts/kanban-agent-env.mjs \
--env ~/.kanban/agent-personal-kanban.env -- codexWhen a workspace has multiple named Kanban connections, load each env file:
node ~/.kanban/app/scripts/kanban-agent-env.mjs \
--env ~/.kanban/agent-work-kanban.env \
--env ~/.kanban/agent-personal-kanban.env -- codexThe env launcher resolves keys stored in macOS Keychain or Linux secret-tool.
Installer smoke test:
pnpm agent:install:smokeThe reusable skill template lives in scripts/agent-install/skills/kanban-second-brain/SKILL.md.
See Second-Brain Testing Guide for the full automated and manual test flow covering installer smoke tests, MCP capture/search/promote/audit tools, UI inbox triage, freshness review, and secret handling.
apps/api: Hono API. Stateless domain logic and SQLite.apps/web: Next.js 16. Pure BFF, no direct DB access. Handles SSR and Server Actions.packages/shared: Shared DTO types between front and back.
The web layer is a pure consumer of the API. It uses API_URL for internal calls and NEXT_PUBLIC_API_URL for client-side hooks.
| Variable | Required | Description |
|---|---|---|
JWT_SECRET |
Yes | Secret for signing access tokens (≥ 32 chars) |
REFRESH_SECRET |
Yes | Secret for refresh tokens (≥ 32 chars) |
DATABASE_URL |
No | Path to SQLite file (default: ./data/kanban.db) |
PORT |
No | Internal API port (default: 3001) |
APP_URL |
No | Public URL of the frontend (for email links, CSRF, and CORS) |
API_URL |
No | API URL for server-to-server calls (Internal Docker: http://api:3001) |
LOG_LEVEL |
No | Log level: debug, info, warn, error, none (default: debug in dev, error in prod) |
ENABLE_HSTS |
No | Set to true to force HTTPS HSTS (default: true if production, false otherwise) |
WS_URL |
Yes | WebSocket URL for the browser (ws://localhost:3010) |
Tests in apps/api use an in-memory database.
pnpm test









