Skip to main content
Glama

TaskForge

A multi-user task tracker built for humans and AI agents to collaborate on the same boards.

TaskForge is a full-stack task management application with three interfaces — REST API, MCP Server (for AI agents), and a Kanban SPA — all running in a single NestJS backend. It's designed so that any MCP-compatible agent (Claude Code, Cursor, GitHub Copilot, etc.) can do everything a human can: create boards, move tasks, assign work, comment, search, and more.

Docker Pulls


Features

  • Authentication — Email/password login, session tokens, invite-only signup, bot tokens for agents, admin/member roles

  • Onboarding — First-run setup creates the admin account and instance title

  • Kanban Board — Drag-and-drop columns with Backlog → To Do → In Progress → Review → Done → Duplicate

  • List View — Table view for quick scanning across all tasks

  • Task Detail — Edit title, description, priority, assignee, due date, estimate, labels; dedicated route per task

  • Sub-tasks — Nest tasks under a parent task

  • Task Relations — Link tasks with blocks / related_to / duplicate_of relationships

  • Duplicate Merge — Mark a task as a duplicate of another; auto-moves it to a Duplicate status and stamps doneAt

  • Per-board Task Numbers — Each task gets a sequential board-scoped id (e.g. TF-12)

  • Comments — Discussion on any task, attributed to the authenticated user

  • Documents — Markdown documents attached to tasks, with publish/unpublish for public sharing

  • Labels — Color-coded tags per board, assignable to tasks

  • Activity Log — Full audit trail per task and per board

  • Subscriptions & Notifications — Subscribe to tasks; inbox with unread notifications for comments, mentions, and activity

  • Public Sharing — Publish tasks and documents to read-only public URLs (no auth required, noindex)

  • Estimates — Freeform numeric effort estimates on tasks

  • Real-time Updates — WebSocket events push changes to all connected clients instantly (auth-required)

  • Full-text Search — Search across task titles and descriptions, or by task number

  • MCP Protocol — AI agents connect via the Streamable HTTP transport to do everything humans can

  • Priority System — Low / Medium / High / Urgent with visual indicators

  • WIP Limits — Optional per-status work-in-progress limits

  • Single Container — Everything (API + SPA + WebSocket) in one Docker image

  • Installable PWA — Install as a desktop/mobile app; cosmetic service worker (no offline caching)


Related MCP server: kanban-lite

Screenshots

Home Page

Kanban Board

Home Page

Kanban Board

List View

Task Detail

List View

Task Detail


Architecture

┌─────────────────────────────────────────────────────┐
│                   TaskForge Container               │
│                                                      │
│  ┌──────────────────────────────────────────────┐   │
│  │              NestJS Backend (:3000)            │   │
│  │                                               │   │
│  │  ┌──────────┐  ┌──────────┐  ┌────────────┐  │   │
│  │  │ REST API │  │ MCP API  │  │ WebSocket  │  │   │
│  │  │ /api/*   │  │ /api/mcp │  │ /ws        │  │   │
│  │  └──────────┘  └──────────┘  └────────────┘  │   │
│  │                                               │   │
│  │  ┌──────────────────────────────────────────┐ │   │
│  │  │  AuthGuard (Bearer session tokens,        │ │   │
│  │  │  @Public exceptions, @Admin routes)        │ │   │
│  │  └──────────────────────────────────────────┘ │   │
│  │                                               │   │
│  │  ┌──────────────────────────────────────────┐ │   │
│  │  │         Prisma ORM → SQLite              │ │   │
│  │  └──────────────────────────────────────────┘ │   │
│  │                                               │   │
│  │  ┌──────────────────────────────────────────┐ │   │
│  │  │  React SPA (served as static assets)    │ │   │
│  │  └──────────────────────────────────────────┘ │   │
│  └──────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

Data Model

User
  ├── Sessions (Bearer tokens; bot sessions flagged)
  ├── InviteTokens (created by admin, single-use)
  └── Memberships (per-board role: admin/member/viewer)

Settings (singleton — instance title, onboarded flag)

Board
  ├── identifier (3-letter prefix for task numbers, e.g. TF)
  ├── nextTaskNum (sequential counter)
  ├── Statuses (ordered by position; isDone/isDuplicate flags, WIP limit, progress %)
  │    └── Tasks (ordered by position, board-scoped number)
  │         ├── Comments (attributed to a User)
  │         ├── Activity (audit log, attributed to a User)
  │         ├── Labels (many-to-many via TaskLabel)
  │         ├── Sub-tasks (self-relation via parentId)
  │         ├── Relations (blocks / related_to / duplicate_of via TaskRelation)
  │         ├── Documents (markdown, publishable to public URL)
  │         └── Subscriptions (User → Task, drives notifications)
  ├── Labels (board-level)
  ├── Documents (board-level)
  └── Members (board-level)

Quick Start

Prerequisites

  • Node.js >= 20

  • pnpm >= 10 (install with corepack enable && corepack prepare pnpm@10.12.1 --activate)

Docker (fastest)

docker run -d --name taskforge -p 3000:3000 -v taskforge-data:/data emreyc/taskforge:latest

Open http://localhost:3000 and follow the onboarding prompt to create the admin account. The SQLite database is persisted in the taskforge-data volume at /data/taskforge.db.

Local Development

# Clone
git clone https://github.com/emreycolakoglu/taskforge.git
cd taskforge

# Install dependencies
pnpm install

# Generate Prisma client and apply migrations
cd apps/api
pnpm prisma:generate
pnpm prisma:migrate
cd ../..

# Start development servers (API on :3000, Web on :5173 with proxy)
pnpm dev

The API runs on http://localhost:3000 and the Vite dev server on http://localhost:5173 (proxied to the API). On first visit the SPA redirects to /onboarding to create the admin account.

Docker (Production, from source)

# Build and run (exposes :4321 by default via docker-compose)
docker compose up --build

# Or build manually
docker build -t taskforge .
docker run -p 3000:3000 -v taskforge-data:/data taskforge

Then open http://localhost:4321 (compose) or http://localhost:3000 (manual run) in your browser. The first visit triggers onboarding.


Configuration

All configuration is via environment variables:

Variable

Default

Description

PORT

3000

HTTP server port

DATABASE_URL

file:./prisma/dev.db

SQLite database path. In Docker, use file:/data/taskforge.db for persistence

CORS_ORIGIN

*

Allowed CORS origin(s)

MCP_ALLOWED_ORIGINS

http://localhost:3000,http://localhost:5173,http://127.0.0.1:3000,http://127.0.0.1:5173

Comma-separated origins allowed for browser MCP requests (DNS-rebinding protection)

MCP_REQUIRE_ORIGIN

1

Set to 0 to skip origin checks entirely

NODE_ENV

development

Set to production for production mode

.env file

PORT=3000
DATABASE_URL=file:./prisma/dev.db
CORS_ORIGIN=*

Authentication

All REST endpoints (except a few @Public ones) require a Authorization: Bearer <token> header. Sessions are UUID tokens stored in the DB with a 90-day expiry (365 days for bot tokens).

Auth Endpoints (/api/auth)

Method

Endpoint

Auth

Description

GET

/api/auth/status

Public

Whether instance is onboarded + instance title

POST

/api/auth/onboard

Public

First-run setup; creates admin user + settings

POST

/api/auth/login

Public

Login with email/password, returns session token

POST

/api/auth/logout

Authenticated

Revokes current session

POST

/api/auth/invite

Admin

Create a single-use invite token (7-day expiry)

POST

/api/auth/signup/:token

Public

Sign up via invite token; returns session

POST

/api/auth/bot-token

Admin

Create a long-lived bot session token for agents

GET

/api/auth/me

Authenticated

Current user

PATCH

/api/auth/me

Authenticated

Update display name / change password

GET

/api/auth/users

Admin

List all users

GET

/api/auth/invites

Admin

List all invite tokens

DELETE

/api/auth/invites/:id

Admin

Revoke an invite token

Settings Endpoints (/api/settings)

Method

Endpoint

Auth

Description

GET

/api/settings

Admin

Full settings

GET

/api/settings/initialized

Public

{ initialized } (used by health check)

GET

/api/settings/title

Public

Instance title

PUT

/api/settings

Admin

Update settings


REST API

All endpoints are under /api and require a Bearer token (see Authentication). Request and response bodies are JSON.

Boards

Method

Endpoint

Description

GET

/api/boards

List all boards

GET

/api/boards/:id

Get board with statuses and labels

GET

/api/boards/:id/full

Get board with statuses, tasks, labels, members

POST

/api/boards

Create a board (auto-creates 6 default statuses)

PUT

/api/boards/:id

Update board name/slug/identifier/description

DELETE

/api/boards/:id

Delete board and all its data

Create a board:

{ "name": "My Project", "slug": "my-project", "identifier": "MYP", "description": "Optional" }

identifier is a 3-letter uppercase prefix used for per-board task numbers (e.g. MYP-1).

Statuses

Method

Endpoint

Description

GET

/api/statuses/board/:boardId

List all statuses in a board (ordered by position)

GET

/api/statuses/:id

Get a single status

POST

/api/statuses

Create a status

PUT

/api/statuses/reorder

Reorder statuses

PUT

/api/statuses/:id

Update status name/color/wipLimit/position/progress

DELETE

/api/statuses/:id

Delete status and its tasks

POST

/api/statuses/:id/toggle-done

Set a status as the board's Done column

POST

/api/statuses/board/:boardId/unset-done

Clear the board's Done column

Create a status:

{ "boardId": "...", "name": "In Progress", "color": "#f59e0b", "wipLimit": 5, "progress": 50 }

Tasks

Method

Endpoint

Description

GET

/api/tasks/board/:boardId

List tasks in a board (?include=all|top|sub, ?parentId=)

GET

/api/tasks/status/:statusId

List tasks in a specific status (same query params)

GET

/api/tasks/search?q=query

Full-text search across tasks (also matches task numbers like TF-12)

GET

/api/tasks/:id

Get task with comments, activity, labels, sub-tasks, relations

POST

/api/tasks

Create a task

PUT

/api/tasks/:id

Update task fields

PUT

/api/tasks/:id/move

Move task to another status

PUT

/api/tasks/reorder

Reorder tasks within a status

POST

/api/tasks/:taskId/labels/:labelId

Attach a label to a task

DELETE

/api/tasks/:taskId/labels/:labelId

Detach a label from a task

PUT

/api/tasks/:id/publish

Publish a task to a public URL (rejects bot sessions)

DELETE

/api/tasks/:id/publish

Unpublish a task

DELETE

/api/tasks/:id

Hard-delete a task (cleans up relations)

Create a task:

{
  "statusId": "...",
  "title": "Implement login page",
  "description": "Add email/password and OAuth login",
  "priority": "high",
  "assigneeId": "user-id",
  "dueDate": "2026-07-01T00:00:00Z",
  "estimate": 5,
  "parentId": "parent-task-id",
  "labelIds": ["label-id-1", "label-id-2"],
  "metadata": "any JSON string"
}

Move a task:

{ "statusId": "new-status-id", "position": 0 }

Task Relations

Relations are scoped under a task. blocks and duplicate_of are directed; related_to is undirected (canonicalized).

Method

Endpoint

Description

GET

/api/tasks/:taskId/relations

List relations for a task

POST

/api/tasks/:taskId/relations

Create a relation

DELETE

/api/tasks/:taskId/relations/:relationId

Delete a relation

Create a relation:

{ "otherTaskId": "other-task-id", "type": "duplicate_of", "direction": "source" }

direction: "source" means the path task is the source. For blocks: source blocks other. For duplicate_of: source is the duplicate of other (the canonical). "target" reverses. Defaults to "source". Ignored for related_to.

Comments

Method

Endpoint

Description

GET

/api/comments/task/:taskId

List comments on a task

POST

/api/comments

Add a comment (attributed to the authenticated user)

DELETE

/api/comments/:id

Delete a comment

Add a comment:

{ "taskId": "...", "body": "Looks good to me!" }

Documents

Documents are markdown documents attached to tasks, scoped to the task's board.

Method

Endpoint

Description

GET

/api/boards/:boardId/documents

List documents on a board

GET

/api/tasks/:taskId/documents

List documents on a task

POST

/api/tasks/:taskId/documents

Create a document on a task

GET

/api/documents/:id

Get a document with its body

PUT

/api/documents/:id

Update document title or body

DELETE

/api/documents/:id

Delete a document

PUT

/api/documents/:id/publish

Publish a document to a public URL

DELETE

/api/documents/:id/publish

Unpublish a document

Labels

Labels are nested under a board for creation/listing.

Method

Endpoint

Description

GET

/api/boards/:boardId/labels

List labels on a board

POST

/api/boards/:boardId/labels

Create a label

PATCH

/api/labels/:id

Update label name/color

DELETE

/api/labels/:id

Delete a label

Create a label:

{ "name": "bug", "color": "#ef4444" }

Activity

Method

Endpoint

Description

GET

/api/activity/task/:taskId

Activity log for a task

GET

/api/activity/board/:boardId

Activity log for an entire board

Subscriptions & Notifications

Method

Endpoint

Description

POST

/api/tasks/:taskId/subscription

Subscribe to a task (idempotent)

DELETE

/api/tasks/:taskId/subscription

Unsubscribe from a task

GET

/api/tasks/:taskId/subscription

Check if subscribed ({ subscribed })

GET

/api/notifications

List inbox notifications (newest first)

GET

/api/notifications/unread-count

Get unread notification count

POST

/api/notifications/:id/read

Mark a single notification as read

POST

/api/notifications/read-all

Mark all notifications as read

Board Members

Method

Endpoint

Description

GET

/api/boards/:boardId/members

List members of a board

POST

/api/boards/:boardId/members

Add a member (requires board admin)

DELETE

/api/boards/:boardId/members/:userId

Remove a member (requires board admin)

POST

/api/boards/:boardId/join

Join a board as a member

POST

/api/boards/:boardId/leave

Leave a board

Public Sharing

Published tasks and documents are accessible at read-only public URLs without authentication.

Method

Endpoint

Description

GET

/api/public/tasks/:identifier/:number

Get a published task (public, no auth)

GET

/api/public/docs/:identifier/:number

Get a published document (public, no auth)


MCP Server (AI Agent Interface)

TaskForge implements the MCP (Model Context Protocol) over the Streamable HTTP transport (2025-03-26 spec) at POST /api/mcp. Any MCP-compatible agent (Claude Code, Cursor, GitHub Copilot, opencode, etc.) can connect and perform all the same operations a human can.

Authentication

The MCP endpoint is behind the global AuthGuard. Agents must send a Bearer session token. The token can be:

  • A user session token (from POST /api/auth/login)

  • A bot token created by an admin via POST /api/auth/bot-token (365-day expiry, recommended for agents)

# Admin creates a bot token
curl -X POST http://localhost:3000/api/auth/bot-token \
  -H "Authorization: Bearer <admin-token>"
# → { "id": "...", "token": "bot-uuid", "expiresAt": "..." }

How Agents Connect

Claude Code / Cursor / Copilot — Add to your MCP config:

{
  "mcpServers": {
    "taskforge": {
      "url": "http://localhost:3000/api/mcp",
      "headers": { "Authorization": "Bearer <bot-token>" }
    }
  }
}

The Streamable HTTP transport requires an initialize handshake that returns an Mcp-Session-Id header; subsequent requests must include that header. Most MCP clients handle this automatically.

Available MCP Tools

Boards

Tool

Params

Description

boards_list

{}

List all boards with status and member counts

boards_get

{ id }

Get board with statuses, tasks, labels

boards_create

{ name, slug, identifier?, description? }

Create board with 6 default statuses

boards_update

{ id, name?, slug?, identifier?, description?, icon? }

Update a board

boards_delete

{ id }

Delete board

Statuses

Tool

Params

Description

statuses_list

{ boardId }

List all statuses in a board

statuses_create

{ boardId, name, position?, color?, wipLimit?, progress? }

Create a status

statuses_update

{ id, name?, color?, wipLimit?, progress? }

Update a status

statuses_delete

{ id }

Delete a status and its tasks

statuses_toggle_done

{ id }

Set a status as the board's Done column

statuses_unset_done

{ boardId }

Clear the board's Done column

Tasks

Tool

Params

Description

tasks_list

{ boardId?, statusId?, assigneeId?, status?, parentId?, include?, limit? }

List tasks with filters

tasks_get

{ id }

Get task with comments, activity, labels, sub-tasks, relations

tasks_search

{ query }

Full-text search or task-number lookup (e.g. TF-12)

tasks_create

{ statusId, title, description?, priority?, assigneeId?, dueDate?, estimate?, parentId?, labelIds?, position?, metadata? }

Create a task (assignee defaults to caller)

tasks_update

{ id, title?, description?, priority?, status?, assigneeId?, dueDate?, estimate?, statusId?, position?, parentId?, labelIds? }

Update a task (parentId: null un-nests)

tasks_move

{ id, statusId, position? }

Move task to another status

tasks_delete

{ id }

Hard-delete a task (cleans up relations)

Comments

Tool

Params

Description

comments_list

{ taskId }

List comments on a task

comments_create

{ taskId, body }

Add a comment (attributed to caller)

Labels

Tool

Params

Description

labels_list

{ boardId }

List labels on a board

labels_create

{ boardId, name, color? }

Create a label

labels_delete

{ id }

Delete a label

Activity

Tool

Params

Description

activity_list

{ taskId?, boardId?, limit? }

Get activity log

Documents

Tool

Params

Description

documents_list

{ boardId?, taskId?, limit? }

List documents (board, task, or all)

documents_get

{ id }

Get a document with its body

documents_create

{ taskId, title, body? }

Create a document on a task

documents_update

{ id, title?, body? }

Update a document

documents_delete

{ id }

Delete a document

Subscriptions & Notifications

Tool

Params

Description

task_subscribe

{ taskId }

Subscribe to a task (idempotent)

task_unsubscribe

{ taskId }

Unsubscribe from a task

inbox_list

{ filter?, limit? }

List inbox notifications

notifications_mark_read

{ id? }

Mark notification(s) as read

Members

Tool

Params

Description

members_list

{ boardId }

List members of a board

members_add

{ boardId, userId, role? }

Add a member (requires board admin)

members_remove

{ boardId, userId }

Remove a member (requires board admin)

members_join

{ boardId }

Join a board as a member

members_leave

{ boardId }

Leave a board

Relations

Tool

Params

Description

relations_list

{ taskId }

List blocking/blockedBy/relatedTo/duplicateOf/duplicates relations for a task

relations_create

{ taskId, otherTaskId, type, direction? }

Create a relation. direction: 'source' = URL task is the source; 'target' = other is the source. For blocks: source blocks other. For duplicate_of: source is the duplicate of other. Defaults to 'source'.

relations_delete

{ relationId }

Delete a relation

Example: Agent Creates a Board and Tasks

// 1. Create a board
→ {"method":"tools/call","params":{"name":"boards_create","arguments":{"name":"Sprint 24","slug":"sprint-24","identifier":"SPR"}},"id":1}
← {"jsonrpc":"2.0","id":1,"result":{...}}

// 2. Create a task in the "To Do" status
→ {"method":"tools/call","params":{"name":"tasks_create","arguments":{"statusId":"...","title":"Design API schema","priority":"high","assigneeId":"alice-id"}},"id":2}
← {"jsonrpc":"2.0","id":2,"result":{...}}

// 3. Move task to "In Progress"
→ {"method":"tools/call","params":{"name":"tasks_move","arguments":{"id":"...","statusId":"in-progress-status-id"}},"id":3}
← {"jsonrpc":"2.0","id":3,"result":{...}}

// 4. Search for tasks
→ {"method":"tools/call","params":{"name":"tasks_search","arguments":{"query":"API"}},"id":4}
← {"jsonrpc":"2.0","id":4,"result":[{...}]}

WebSocket Events

The WebSocket server at /ws pushes real-time events to connected clients. Authentication is required — clients must emit an auth message with a session token within 5 seconds of connecting:

const ws = new WebSocket('ws://localhost:3000/ws');
ws.onopen = () => {
  ws.send(
    JSON.stringify({ event: 'auth', data: { token: '<session-token>', boardId: 'board-123' } }),
  );
};
ws.onmessage = (event) => {
  const { event: name, data } = JSON.parse(event.data);
  console.log(name, data);
};

On success the server emits auth_success; on failure it emits auth_error and disconnects. Providing boardId joins the board's event room.

Event Types

Event

Payload

When

board:created

Board object

A new board is created

board:updated

Board object

A board is renamed/updated

board:deleted

{ id }

A board is deleted

status:created

Status object

A new status is added

status:updated

Status object

A status is renamed/recolored

status:reordered

Reorder result

Statuses are reordered

status:deleted

{ id }

A status is deleted

status:doneToggled

Status object

Done column is set or unset

task:created

Task object

A new task is created

task:updated

Task object

A task is edited

task:moved

Task object

A task is moved to another status

task:deleted

{ id }

A task is deleted

task.label.attached

Task object

A label is attached to a task

task.label.detached

Task object

A label is detached from a task

comment:created

Comment object

A comment is added

comment:deleted

{ id }

A comment is deleted

label:created

Label object

A new label is created

label:updated

Label object

A label is renamed/recolored

label:deleted

{ id }

A label is deleted

relation:created

Relation object

A task relation is created

relation:deleted

{ id }

A task relation is deleted

document:created

Document object

A document is created

document:updated

Document object

A document is edited

document:deleted

{ id }

A document is deleted

notification:created

Notification object

A notification is created (user-scoped)


Frontend (SPA)

The React SPA is served by the NestJS backend in production. In development, Vite proxies API and WebSocket requests to the backend.

Routes

Route

View

/onboarding

First-run admin setup

/login

Login form

/signup/:token

Invite-based signup

/

Home — board list with create/delete

/board/:id

Kanban board with task cards, labels, priority indicators, assignees

/board/:id/settings

Board settings (labels, members)

/board/:boardId/docs

Board documents list

/board/:boardId/doc/:docId

Document editor page

/board/:boardId/task/:taskId

Task detail page (edit fields, activity log, comments, sub-tasks, relations)

/tasks

List view — sortable table across all tasks

/settings

Admin settings

/account

Account settings (display name, password)

/inbox

Notification inbox

/inbox/:notificationId

Notification detail

/public/:identifier/:number

Public task view (no auth required)

/public/docs/:identifier/:number

Public document view (no auth required)

Tech Stack

  • React 19 + TypeScript (strict)

  • React Router 7

  • Vite 6 (dev server with proxy)

  • Tailwind CSS 4 + shadcn/ui (Radix primitives)

  • TanStack Query (server state)

  • @hello-pangea/dnd (drag and drop)

  • Socket.IO client (real-time events)

  • Sonner (toasts), Lucide icons

The SPA follows a dark "midnight command deck" design system with a single Acid Lime accent. See design.md before any frontend change.


Docker

Building

# Using docker-compose (recommended — exposes :4321)
docker compose up --build

# Manual build
docker build -t taskforge .

The Dockerfile uses multi-stage builds:

  1. base — Node 23 Alpine + pnpm

  2. deps — Install all dependencies

  3. builder — Generate Prisma client, build API and SPA

  4. runner — Minimal production image with SQLite persistence at /data

Volumes

Data loss warning: The SQLite database lives at /data/taskforge.db inside the container. Without a persistent volume mounted at /data, every redeploy recreates the container and wipes the database. This is true for docker run, docker compose up, Coolify, and any container orchestrator.

Mount a volume at /data to persist the SQLite database:

volumes:
  - taskforge-data:/data

The Dockerfile declares VOLUME ["/data"] so anonymous Docker storage is created automatically on docker run without -v — but anonymous volumes are per-container and do not survive docker rm or image redeploys. For real persistence, use a named volume or a bind mount:

# Named volume (recommended)
docker run -d --name taskforge -p 3000:3000 -v taskforge-data:/data emreyc/taskforge:latest

# Bind mount (for backups / host access)
docker run -d --name taskforge -p 3000:3000 -v /opt/taskforge-data:/data emreyc/taskforge:latest

Health Check

The container includes a health check that pings GET /api/settings/initialized every 30 seconds.

Migrations on Startup

docker-entrypoint.sh runs prisma migrate deploy before starting the app, so schema changes ship with the image. To ship a schema change: run pnpm --filter @taskforge/api prisma:migrate -- --name <desc> locally, commit the new migration file, and push.


Development

Project Structure

taskforge/
├── apps/
│   ├── api/                       # NestJS backend (CommonJS)
│   │   ├── prisma/
│   │   │   ├── schema.prisma      # Database schema
│   │   │   └── migrations/        # Prisma migrations
│   │   ├── docker-entrypoint.sh   # Runs migrations then starts node
│   │   └── src/
│   │       ├── main.ts            # Entry (SPA serving + CORS + validation)
│   │       ├── app.module.ts       # Root module
│   │       ├── prisma/            # Prisma client service (@Global)
│   │       ├── auth/              # Users, sessions, invites, bot tokens, AuthGuard
│   │       ├── settings/          # Instance settings (singleton)
│   │       ├── boards/            # Boards module (REST)
│   │       ├── statuses/          # Statuses module (REST)
│   │       ├── tasks/             # Tasks module (REST)
│   │       ├── relations/         # Task relations (blocks / related_to / duplicate_of)
│   │       ├── comments/          # Comments module (REST)
│   │       ├── labels/            # Labels module (REST)
│   │       ├── activity/          # Activity log module (REST)
│   │       ├── documents/         # Documents module (REST)
│   │       ├── members/           # Board members
│   │       ├── subscriptions/     # Task subscriptions
│   │       ├── notifications/     # Notifications + inbox
│   │       ├── public/            # Public task/document sharing
│   │       ├── events/            # WebSocket gateway + event bus
│   │       └── mcp/               # MCP Streamable HTTP server + tool defs
│   └── web/                       # React SPA (ESM, strict)
│       └── src/
│           ├── app.tsx            # Routes
│           ├── contexts/          # AuthContext
│           ├── pages/             # Route components
│           ├── components/        # KanbanBoard, TaskCard, TaskDetail, dialogs, UI primitives
│           ├── hooks/             # api.ts, use-auth, use-socket, use-tasks, use-relations, ...
│           ├── lib/               # constants, utils
│           └── types/             # TypeScript interfaces
├── Dockerfile
├── docker-compose.yml
├── package.json                   # Root workspace config
├── pnpm-workspace.yaml
└── turbo.json                     # Turborepo pipeline

Commands

pnpm dev              # Start both API and web in dev mode
pnpm build            # Build both apps
pnpm lint             # Lint all apps (note: eslint not installed — see AGENTS.md)
pnpm clean            # Clean build artifacts

# Database
pnpm db:generate      # Generate Prisma client
pnpm db:migrate       # Run Prisma migrations (add -- --name <desc> to create one)

# Tests
pnpm --filter @taskforge/api test    # API (Jest)
pnpm --filter @taskforge/web test    # Web (Vitest)

# Docker
pnpm docker:build     # Build Docker image
pnpm docker:run       # Run Docker container

Adding a New Module

  1. Create apps/api/src/<module>/ with controller, service, module, and DTO files

  2. Register the module in apps/api/src/app.module.ts

  3. Add MCP tool definitions in apps/api/src/mcp/tool-definitions.ts and handlers in mcp.service.ts

  4. Add API client methods in apps/web/src/hooks/api.ts

  5. Add WebSocket event handling in apps/web/src/hooks/use-socket.ts


Use Cases

Solo Developer

Run locally with SQLite. Use the SPA for daily work, the MCP server to let your AI coding agent create and manage tasks automatically via a bot token.

Small Team

Deploy on a single VPS with Docker. Admins create invite tokens; members sign up and use the SPA. CI/CD pipelines use the REST API (via bot tokens) to create release tasks. AI agents join standups and update boards.

Agent-First Workflow

Your AI agent manages the entire board. The agent creates tasks from PR descriptions, moves them through review stages, assigns reviewers, links blockers, and archives completed work — all via MCP. Humans check in via the SPA when needed.

Hybrid

Humans use the Kanban board. AI agents use MCP to:

  • Create tasks from bug reports

  • Move tasks through pipeline stages

  • Assign work based on team capacity

  • Search and report on task status

  • Add comments with analysis results

  • Link blocking relationships


FAQ

Q: Can I use a different database? A: Yes. Change the provider in prisma/schema.prisma from sqlite to postgresql or mysql, update DATABASE_URL, and run pnpm db:migrate. Prisma handles the rest.

Q: How do agents authenticate? A: An admin creates a bot token via POST /api/auth/bot-token (365-day expiry). The agent sends it as Authorization: Bearer <token> on every REST and MCP request, and in the auth WebSocket message. User session tokens (90-day expiry) also work.

Q: Can I deploy to Fly.io / Railway / Render? A: Yes. The Docker image is self-contained. Set DATABASE_URL to a persistent volume path. For SQLite, ensure the volume persists across restarts. For production, consider PostgreSQL.

Q: How do I add custom fields to tasks? A: Use the metadata field — it's a JSON string that accepts arbitrary data. Parse it in your frontend or agent logic.

Q: Can multiple agents connect simultaneously? A: Yes. The MCP endpoint is session-based and handles concurrent requests. WebSocket events broadcast to all connected clients.

Q: How are task numbers assigned? A: Each board has an identifier (3-letter prefix) and a nextTaskNum counter. Every new task gets the next number (e.g. TF-1, TF-2), displayed and searchable as TF-12.


License

MIT


Contributing

  1. Fork the repository

  2. Create a feature branch (git checkout -b feature/amazing)

  3. Commit your changes (git commit -m 'Add amazing feature')

  4. Push to the branch (git push origin feature/amazing)

  5. Open a Pull Request

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    An MCP server that provides a database-backed kanban board with 40+ tools for AI agents to track issues, features, todos, epics, and diary entries across projects, including status workflows, relationships, and semantic search.
    45
    80
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for kanban-tui, a terminal-based task manager, that exposes task, board, and column management commands for AI agents to interact with.
    283
    MIT