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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityActive
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

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.
    280
    MIT

Latest Blog Posts

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/emreycolakoglu/taskforge'

If you have feedback or need assistance with the MCP directory API, please join our Discord server