Skip to content

Commit 5b6503d

Browse files
committed
feat(kanban-mcp): task-board MCP server shipped as a kagent UI plugin (#2048)
Add go/plugins/kanban-mcp: a self-contained MCP server exposing task/board/ subtask/attachment tools for agents, with dual SQLite/Postgres support, embedded migrations, an embedded board SPA, a real-time task-progress MCP app, and SSE live updates. Add contrib/plugins/kanban-mcp Helm chart that deploys it and registers its web UI as a kagent plugin via RemoteMCPServer.spec.ui. Depends conceptually on #2047 (plugin registration) and #2046 (chat MCP UI widget); this PR is self-contained and builds independently. Includes design/EP-2048-kanban-mcp-plugin.md and specs/mcp-kanban-server. Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
1 parent e9e4e34 commit 5b6503d

69 files changed

Lines changed: 12066 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: v2
2+
name: kanban-mcp
3+
description: A Helm chart for the MCP Kanban server (Postgres-backed board with MCP, REST, SSE and an embedded UI)
4+
type: application
5+
version: 0.1.0
6+
appVersion: "1.0.0"
7+
sources:
8+
- https://github.com/kagent-dev/kagent
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{{/*
2+
Expand the name of the chart.
3+
*/}}
4+
{{- define "kanban-mcp.name" -}}
5+
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
6+
{{- end }}
7+
8+
{{/*
9+
Create a default fully qualified app name.
10+
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
11+
*/}}
12+
{{- define "kanban-mcp.fullname" -}}
13+
{{- if .Values.fullnameOverride }}
14+
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
15+
{{- else }}
16+
{{- $name := default .Chart.Name .Values.nameOverride }}
17+
{{- if contains $name .Release.Name }}
18+
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
19+
{{- else }}
20+
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
21+
{{- end }}
22+
{{- end }}
23+
{{- end }}
24+
25+
{{/*
26+
Create chart name and version as used by the chart label.
27+
*/}}
28+
{{- define "kanban-mcp.chart" -}}
29+
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
30+
{{- end }}
31+
32+
{{/*
33+
Common labels
34+
*/}}
35+
{{- define "kanban-mcp.labels" -}}
36+
helm.sh/chart: {{ include "kanban-mcp.chart" . }}
37+
{{ include "kanban-mcp.selectorLabels" . }}
38+
{{- if .Chart.AppVersion }}
39+
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
40+
{{- end }}
41+
app.kubernetes.io/managed-by: {{ .Release.Service }}
42+
{{- end }}
43+
44+
{{/*
45+
Selector labels
46+
*/}}
47+
{{- define "kanban-mcp.selectorLabels" -}}
48+
app.kubernetes.io/name: {{ include "kanban-mcp.name" . }}
49+
app.kubernetes.io/instance: {{ .Release.Name }}
50+
{{- end }}
51+
52+
{{/*
53+
Name of the Secret that holds the database URL.
54+
Uses database.existingSecret when set, otherwise a generated "<fullname>-db" Secret.
55+
*/}}
56+
{{- define "kanban-mcp.dbSecretName" -}}
57+
{{- if .Values.database.existingSecret -}}
58+
{{- .Values.database.existingSecret -}}
59+
{{- else -}}
60+
{{- printf "%s-db" (include "kanban-mcp.fullname" .) -}}
61+
{{- end -}}
62+
{{- end }}
63+
64+
{{/*
65+
In-cluster URL of the kanban MCP endpoint, used as RemoteMCPServer spec.url.
66+
The kanban server serves MCP over Streamable HTTP at /mcp, with the web UI at /.
67+
*/}}
68+
{{- define "kanban-mcp.serverUrl" -}}
69+
{{- printf "http://%s.%s:%d/mcp" (include "kanban-mcp.fullname" .) .Release.Namespace (.Values.service.port | int) }}
70+
{{- end }}
71+
72+
{{/*
73+
Key within the database Secret that holds the URL.
74+
*/}}
75+
{{- define "kanban-mcp.dbSecretKey" -}}
76+
{{- if .Values.database.existingSecret -}}
77+
{{- default "url" .Values.database.existingSecretKey -}}
78+
{{- else -}}
79+
url
80+
{{- end -}}
81+
{{- end }}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
{{- if .Values.agent.enabled }}
2+
apiVersion: kagent.dev/v1alpha2
3+
kind: Agent
4+
metadata:
5+
name: {{ include "kanban-mcp.fullname" . }}-agent
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
{{- include "kanban-mcp.labels" . | nindent 4 }}
9+
spec:
10+
description: {{ .Values.agent.description | quote }}
11+
type: Declarative
12+
declarative:
13+
runtime: {{ .Values.agent.runtime | default "python" }}
14+
systemMessage: |
15+
# Kanban Board AI Agent System Prompt
16+
17+
You are KanbanAssist, an AI agent that manages a Kanban board through the kanban MCP server. You help users plan, track, and move work across the board's workflow stages while keeping task state accurate and up to date.
18+
19+
## Core Capabilities
20+
21+
- **Board Awareness**: You can read the full board and individual cards to understand the current state of work.
22+
- **Work Hierarchy**: You organize work as Features → Tasks → Subtasks (see below) and keep that structure clean.
23+
- **Task Management**: You can create Features and Tasks, break a Feature into child Tasks, update their fields, assign owners, and move them through workflow stages.
24+
- **Checklists**: You can add checklist subtasks (title + done flag) to a Task and tick them off as work progresses.
25+
- **Workflow Discipline**: You respect each board's own columns and move cards only into columns that belong to the card's board.
26+
- **Attachments & Attributes**: You can attach files (md, html, txt, yaml, csv, pdf, docx, xlsx — base64-encoded) or links to any card (Feature or Task), and set simple key/value attributes on a card, to capture supporting context and metadata.
27+
- **Clear Communication**: You explain what you changed and why, and surface tasks that are blocked on human input.
28+
29+
## Work Hierarchy
30+
31+
Work is organized in three levels:
32+
33+
- **Feature**: a top-level card (an epic / unit of work). Created with `create_task` without a `parent_id`.
34+
- **Task**: a child card under a Feature — the unit a worker owns. Both Features and Tasks are full kanban cards with their own status, assignee, labels, and board column, and both move independently across columns. Create a Task with `create_task` and the Feature's id as `parent_id`.
35+
- **Subtask**: a lightweight **checklist item** (just a title and a done flag) attached to a **Task** (not a Feature). Use `create_subtask`, `toggle_subtask`, `update_subtask`, and `delete_subtask` to manage the checklist.
36+
37+
## Boards
38+
39+
The server hosts multiple boards. Each board has its own ordered set of
40+
columns, and a task can only be placed in a column that belongs to its board.
41+
{{- if .Values.agent.board }}
42+
Unless told otherwise, operate on the board with key `{{ .Values.agent.board }}`:
43+
pass `board: "{{ .Values.agent.board }}"` to `list_tasks`, `create_task`, and
44+
`get_board`.
45+
{{- else }}
46+
Board-scoped tools (`list_tasks`, `create_task`, `get_board`) take an optional
47+
`board` key and default to the `default` board. Use `list_boards` to discover
48+
available boards and their columns before creating or moving tasks.
49+
{{- end }}
50+
51+
## Available Tools
52+
53+
You have access to the following kanban tools:
54+
55+
### Board Tools
56+
- `list_boards`: List all boards with their columns, scope, and owner.
57+
- `create_board`: Create a new board with its own ordered column set.
58+
59+
### Read Tools
60+
- `get_board`: Get a board's full state (its columns + cards grouped by column); Features and Tasks appear as flat cards, each with its checklist subtasks and attachments.
61+
- `list_tasks`: List cards on a board, optionally filtered by status, assignee, or label; set `parent_id` to list a Feature's child Tasks.
62+
- `get_task`: Get a single card by ID, including its checklist subtasks, attachments, and (for a Feature) its child Tasks.
63+
- `show_task_progress`: Render an interactive progress widget for a card (Feature or Task) inline in the chat — completion percent, plus per-column child-task counts and an individual progress bar per child Task (Feature) or checklist progress (Task).
64+
65+
### Write Tools
66+
- `create_task`: Create a Feature (no `parent_id`) or a child Task under a Feature (`parent_id` set). Status defaults to the board's first column.
67+
- `create_subtask`: Add a checklist subtask (title) to a Task. Subtasks attach to Tasks only, not Features.
68+
- `toggle_subtask`: Set or clear the done flag on a checklist subtask.
69+
- `update_subtask`: Rename a checklist subtask.
70+
- `delete_subtask`: Delete a checklist subtask.
71+
- `update_task`: Update one or more fields of a card; unset fields are left unchanged.
72+
- `assign_task`: Assign a card to someone; an empty assignee clears it.
73+
- `move_task`: Move a card to a different workflow status.
74+
- `set_user_input_needed`: Set or clear the human-in-the-loop flag on a card.
75+
- `delete_task`: Delete a card; its checklist subtasks and attachments are removed with it, and deleting a Feature also deletes its child Tasks.
76+
- `add_attachment`: Add a file (base64-encoded content; allowed types: md, markdown, html, htm, txt, yaml, yml, csv, pdf, docx, xlsx) or link attachment to a card (Feature or Task).
77+
- `delete_attachment`: Delete a file or link attachment by ID.
78+
- `set_attribute`: Set (upsert) a key/value attribute on a card; setting an existing key replaces its value.
79+
- `delete_attribute`: Remove a key/value attribute from a card by its key.
80+
81+
## Operating Guidelines
82+
83+
1. **Read before you write**: Inspect the board or task before making changes so updates are accurate.
84+
2. **Least surprise**: Confirm destructive actions (delete_task, delete_attachment) with the user before running them.
85+
3. **Keep status honest**: Move tasks to reflect real progress; flag blocked tasks with set_user_input_needed.
86+
4. **Be concise**: Summarize what you did, referencing task IDs and the resulting status.
87+
5. **Status/progress → widget only**: When the user asks for the **status or progress** of a specific Feature or Task, respond with the progress widget **only** — call `show_task_progress` with that card's id (it renders both Features and Tasks) — and nothing else. Do not add a separate textual status summary or call other read tools; the rendered widget is the complete answer.
88+
89+
## Response Format
90+
91+
When responding to user queries:
92+
93+
1. **Assessment**: Briefly restate what the user wants.
94+
2. **Action**: State which tools you will use and on which tasks.
95+
3. **Result**: Summarize the changes, including task IDs and new statuses.
96+
97+
Always start with the least intrusive approach, and ask for clarification when a request is ambiguous.
98+
modelConfig: {{ .Values.agent.modelConfig | default "default-model-config" }}
99+
tools:
100+
- type: McpServer
101+
mcpServer:
102+
name: {{ include "kanban-mcp.fullname" . }}
103+
kind: RemoteMCPServer
104+
apiGroup: kagent.dev
105+
toolNames:
106+
- list_boards
107+
- create_board
108+
- get_board
109+
- list_tasks
110+
- get_task
111+
- show_task_progress
112+
- create_task
113+
- create_subtask
114+
- toggle_subtask
115+
- update_subtask
116+
- delete_subtask
117+
- update_task
118+
- assign_task
119+
- move_task
120+
- set_user_input_needed
121+
- delete_task
122+
- add_attachment
123+
- delete_attachment
124+
- set_attribute
125+
- delete_attribute
126+
a2aConfig:
127+
skills:
128+
- id: task-management
129+
name: Task Management
130+
description: Create, update, assign, and move tasks across the board.
131+
tags:
132+
- kanban
133+
- tasks
134+
examples:
135+
- "Create a task to investigate the failing CI pipeline."
136+
- "Move task 12 to Testing and assign it to Alice."
137+
- "What is currently in the Develop column?"
138+
- id: board-reporting
139+
name: Board Reporting
140+
description: Summarize board state and surface blocked work.
141+
tags:
142+
- kanban
143+
- reporting
144+
examples:
145+
- "Give me a summary of the board."
146+
- "Which tasks are blocked waiting on human input?"
147+
- "List all tasks assigned to Bob."
148+
{{- with .Values.agent.resources }}
149+
deployment:
150+
resources:
151+
{{- toYaml . | nindent 8 }}
152+
{{- end }}
153+
{{- end }}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{{- if .Values.boards }}
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: {{ include "kanban-mcp.fullname" . }}-boards
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
{{- include "kanban-mcp.labels" . | nindent 4 }}
9+
data:
10+
# Board definitions seeded by the server at startup via KANBAN_BOARDS_FILE.
11+
# The server upserts each entry, so redeploys reconcile board names/columns.
12+
boards.json: |
13+
{{ .Values.boards | toJson }}
14+
{{- end }}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: {{ include "kanban-mcp.fullname" . }}
5+
labels:
6+
{{- include "kanban-mcp.labels" . | nindent 4 }}
7+
spec:
8+
replicas: {{ .Values.replicaCount }}
9+
selector:
10+
matchLabels:
11+
{{- include "kanban-mcp.selectorLabels" . | nindent 6 }}
12+
template:
13+
metadata:
14+
{{- with .Values.podAnnotations }}
15+
annotations:
16+
{{- toYaml . | nindent 8 }}
17+
{{- end }}
18+
labels:
19+
{{- include "kanban-mcp.selectorLabels" . | nindent 8 }}
20+
spec:
21+
{{- with .Values.imagePullSecrets }}
22+
imagePullSecrets:
23+
{{- toYaml . | nindent 8 }}
24+
{{- end }}
25+
{{- with .Values.podSecurityContext }}
26+
securityContext:
27+
{{- toYaml . | nindent 8 }}
28+
{{- end }}
29+
containers:
30+
- name: kanban-mcp
31+
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
32+
imagePullPolicy: {{ .Values.image.pullPolicy }}
33+
{{- with .Values.securityContext }}
34+
securityContext:
35+
{{- toYaml . | nindent 12 }}
36+
{{- end }}
37+
args:
38+
- "--addr={{ .Values.config.addr }}"
39+
- "--transport={{ .Values.config.transport }}"
40+
- "--log-level={{ .Values.config.logLevel }}"
41+
- "--readonly={{ .Values.config.readonly }}"
42+
env:
43+
- name: KANBAN_ADDR
44+
value: {{ .Values.config.addr | quote }}
45+
- name: KANBAN_TRANSPORT
46+
value: {{ .Values.config.transport | quote }}
47+
- name: KANBAN_LOG_LEVEL
48+
value: {{ .Values.config.logLevel | quote }}
49+
- name: KANBAN_READONLY
50+
value: {{ .Values.config.readonly | quote }}
51+
{{- if or .Values.database.url .Values.database.existingSecret }}
52+
# The connection URL is mounted as a file from a Secret and read via
53+
# KANBAN_DB_URL_FILE so the URL never appears in the pod env / spec.
54+
- name: KANBAN_DB_URL_FILE
55+
value: /etc/kanban/db/{{ include "kanban-mcp.dbSecretKey" . }}
56+
{{- end }}
57+
{{- if .Values.boards }}
58+
# Board definitions are mounted from a ConfigMap and seeded at startup.
59+
- name: KANBAN_BOARDS_FILE
60+
value: /etc/kanban/boards/boards.json
61+
{{- end }}
62+
ports:
63+
- name: http
64+
containerPort: 8080
65+
protocol: TCP
66+
{{- if eq .Values.config.transport "http" }}
67+
readinessProbe:
68+
httpGet:
69+
path: /api/board
70+
port: http
71+
initialDelaySeconds: 5
72+
periodSeconds: 10
73+
livenessProbe:
74+
httpGet:
75+
path: /api/board
76+
port: http
77+
initialDelaySeconds: 10
78+
periodSeconds: 20
79+
{{- end }}
80+
{{- with .Values.resources }}
81+
resources:
82+
{{- toYaml . | nindent 12 }}
83+
{{- end }}
84+
{{- if or .Values.database.url .Values.database.existingSecret .Values.boards }}
85+
volumeMounts:
86+
{{- if or .Values.database.url .Values.database.existingSecret }}
87+
- name: db-url
88+
mountPath: /etc/kanban/db
89+
readOnly: true
90+
{{- end }}
91+
{{- if .Values.boards }}
92+
- name: boards
93+
mountPath: /etc/kanban/boards
94+
readOnly: true
95+
{{- end }}
96+
{{- end }}
97+
{{- if or .Values.database.url .Values.database.existingSecret .Values.boards }}
98+
volumes:
99+
{{- if or .Values.database.url .Values.database.existingSecret }}
100+
- name: db-url
101+
secret:
102+
secretName: {{ include "kanban-mcp.dbSecretName" . }}
103+
items:
104+
- key: {{ include "kanban-mcp.dbSecretKey" . }}
105+
path: {{ include "kanban-mcp.dbSecretKey" . }}
106+
{{- end }}
107+
{{- if .Values.boards }}
108+
- name: boards
109+
configMap:
110+
name: {{ include "kanban-mcp.fullname" . }}-boards
111+
{{- end }}
112+
{{- end }}
113+
{{- with .Values.nodeSelector }}
114+
nodeSelector:
115+
{{- toYaml . | nindent 8 }}
116+
{{- end }}
117+
{{- with .Values.affinity }}
118+
affinity:
119+
{{- toYaml . | nindent 8 }}
120+
{{- end }}
121+
{{- with .Values.tolerations }}
122+
tolerations:
123+
{{- toYaml . | nindent 8 }}
124+
{{- end }}

0 commit comments

Comments
 (0)