Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions contrib/plugins/kanban-mcp/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v2
name: kanban-mcp
description: A Helm chart for the MCP Kanban server (Postgres-backed board with MCP, REST, SSE and an embedded UI)
type: application
version: 0.1.0
appVersion: "1.0.0"
sources:
- https://github.com/kagent-dev/kagent
81 changes: 81 additions & 0 deletions contrib/plugins/kanban-mcp/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "kanban-mcp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "kanban-mcp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "kanban-mcp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "kanban-mcp.labels" -}}
helm.sh/chart: {{ include "kanban-mcp.chart" . }}
{{ include "kanban-mcp.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "kanban-mcp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "kanban-mcp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Name of the Secret that holds the database URL.
Uses database.existingSecret when set, otherwise a generated "<fullname>-db" Secret.
*/}}
{{- define "kanban-mcp.dbSecretName" -}}
{{- if .Values.database.existingSecret -}}
{{- .Values.database.existingSecret -}}
{{- else -}}
{{- printf "%s-db" (include "kanban-mcp.fullname" .) -}}
{{- end -}}
{{- end }}

{{/*
In-cluster URL of the kanban MCP endpoint, used as RemoteMCPServer spec.url.
The kanban server serves MCP over Streamable HTTP at /mcp, with the web UI at /.
*/}}
{{- define "kanban-mcp.serverUrl" -}}
{{- printf "http://%s.%s:%d/mcp" (include "kanban-mcp.fullname" .) .Release.Namespace (.Values.service.port | int) }}
{{- end }}

{{/*
Key within the database Secret that holds the URL.
*/}}
{{- define "kanban-mcp.dbSecretKey" -}}
{{- if .Values.database.existingSecret -}}
{{- default "url" .Values.database.existingSecretKey -}}
{{- else -}}
url
{{- end -}}
{{- end }}
153 changes: 153 additions & 0 deletions contrib/plugins/kanban-mcp/templates/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
{{- if .Values.agent.enabled }}
apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: {{ include "kanban-mcp.fullname" . }}-agent
namespace: {{ .Release.Namespace }}
labels:
{{- include "kanban-mcp.labels" . | nindent 4 }}
spec:
description: {{ .Values.agent.description | quote }}
type: Declarative
declarative:
runtime: {{ .Values.agent.runtime | default "python" }}
systemMessage: |
# Kanban Board AI Agent System Prompt

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.

## Core Capabilities

- **Board Awareness**: You can read the full board and individual cards to understand the current state of work.
- **Work Hierarchy**: You organize work as Features → Tasks → Subtasks (see below) and keep that structure clean.
- **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.
- **Checklists**: You can add checklist subtasks (title + done flag) to a Task and tick them off as work progresses.
- **Workflow Discipline**: You respect each board's own columns and move cards only into columns that belong to the card's board.
- **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.
- **Clear Communication**: You explain what you changed and why, and surface tasks that are blocked on human input.

## Work Hierarchy

Work is organized in three levels:

- **Feature**: a top-level card (an epic / unit of work). Created with `create_task` without a `parent_id`.
- **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`.
- **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.

## Boards

The server hosts multiple boards. Each board has its own ordered set of
columns, and a task can only be placed in a column that belongs to its board.
{{- if .Values.agent.board }}
Unless told otherwise, operate on the board with key `{{ .Values.agent.board }}`:
pass `board: "{{ .Values.agent.board }}"` to `list_tasks`, `create_task`, and
`get_board`.
{{- else }}
Board-scoped tools (`list_tasks`, `create_task`, `get_board`) take an optional
`board` key and default to the `default` board. Use `list_boards` to discover
available boards and their columns before creating or moving tasks.
{{- end }}

## Available Tools

You have access to the following kanban tools:

### Board Tools
- `list_boards`: List all boards with their columns, scope, and owner.
- `create_board`: Create a new board with its own ordered column set.

### Read Tools
- `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.
- `list_tasks`: List cards on a board, optionally filtered by status, assignee, or label; set `parent_id` to list a Feature's child Tasks.
- `get_task`: Get a single card by ID, including its checklist subtasks, attachments, and (for a Feature) its child Tasks.
- `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).

### Write Tools
- `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.
- `create_subtask`: Add a checklist subtask (title) to a Task. Subtasks attach to Tasks only, not Features.
- `toggle_subtask`: Set or clear the done flag on a checklist subtask.
- `update_subtask`: Rename a checklist subtask.
- `delete_subtask`: Delete a checklist subtask.
- `update_task`: Update one or more fields of a card; unset fields are left unchanged.
- `assign_task`: Assign a card to someone; an empty assignee clears it.
- `move_task`: Move a card to a different workflow status.
- `set_user_input_needed`: Set or clear the human-in-the-loop flag on a card.
- `delete_task`: Delete a card; its checklist subtasks and attachments are removed with it, and deleting a Feature also deletes its child Tasks.
- `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).
- `delete_attachment`: Delete a file or link attachment by ID.
- `set_attribute`: Set (upsert) a key/value attribute on a card; setting an existing key replaces its value.
- `delete_attribute`: Remove a key/value attribute from a card by its key.

## Operating Guidelines

1. **Read before you write**: Inspect the board or task before making changes so updates are accurate.
2. **Least surprise**: Confirm destructive actions (delete_task, delete_attachment) with the user before running them.
3. **Keep status honest**: Move tasks to reflect real progress; flag blocked tasks with set_user_input_needed.
4. **Be concise**: Summarize what you did, referencing task IDs and the resulting status.
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.

## Response Format

When responding to user queries:

1. **Assessment**: Briefly restate what the user wants.
2. **Action**: State which tools you will use and on which tasks.
3. **Result**: Summarize the changes, including task IDs and new statuses.

Always start with the least intrusive approach, and ask for clarification when a request is ambiguous.
modelConfig: {{ .Values.agent.modelConfig | default "default-model-config" }}
tools:
- type: McpServer
mcpServer:
name: {{ include "kanban-mcp.fullname" . }}
kind: RemoteMCPServer
apiGroup: kagent.dev
toolNames:
- list_boards
- create_board
- get_board
- list_tasks
- get_task
- show_task_progress
- create_task
- create_subtask
- toggle_subtask
- update_subtask
- delete_subtask
- update_task
- assign_task
- move_task
- set_user_input_needed
- delete_task
- add_attachment
- delete_attachment
- set_attribute
- delete_attribute
a2aConfig:
skills:
- id: task-management
name: Task Management
description: Create, update, assign, and move tasks across the board.
tags:
- kanban
- tasks
examples:
- "Create a task to investigate the failing CI pipeline."
- "Move task 12 to Testing and assign it to Alice."
- "What is currently in the Develop column?"
- id: board-reporting
name: Board Reporting
description: Summarize board state and surface blocked work.
tags:
- kanban
- reporting
examples:
- "Give me a summary of the board."
- "Which tasks are blocked waiting on human input?"
- "List all tasks assigned to Bob."
{{- with .Values.agent.resources }}
deployment:
resources:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
14 changes: 14 additions & 0 deletions contrib/plugins/kanban-mcp/templates/configmap-boards.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{- if .Values.boards }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "kanban-mcp.fullname" . }}-boards
namespace: {{ .Release.Namespace }}
labels:
{{- include "kanban-mcp.labels" . | nindent 4 }}
data:
# Board definitions seeded by the server at startup via KANBAN_BOARDS_FILE.
# The server upserts each entry, so redeploys reconcile board names/columns.
boards.json: |
{{ .Values.boards | toJson }}
{{- end }}
124 changes: 124 additions & 0 deletions contrib/plugins/kanban-mcp/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "kanban-mcp.fullname" . }}
labels:
{{- include "kanban-mcp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "kanban-mcp.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "kanban-mcp.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: kanban-mcp
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
args:
- "--addr={{ .Values.config.addr }}"
- "--transport={{ .Values.config.transport }}"
- "--log-level={{ .Values.config.logLevel }}"
- "--readonly={{ .Values.config.readonly }}"
env:
- name: KANBAN_ADDR
value: {{ .Values.config.addr | quote }}
- name: KANBAN_TRANSPORT
value: {{ .Values.config.transport | quote }}
- name: KANBAN_LOG_LEVEL
value: {{ .Values.config.logLevel | quote }}
- name: KANBAN_READONLY
value: {{ .Values.config.readonly | quote }}
{{- if or .Values.database.url .Values.database.existingSecret }}
# The connection URL is mounted as a file from a Secret and read via
# KANBAN_DB_URL_FILE so the URL never appears in the pod env / spec.
- name: KANBAN_DB_URL_FILE
value: /etc/kanban/db/{{ include "kanban-mcp.dbSecretKey" . }}
{{- end }}
{{- if .Values.boards }}
# Board definitions are mounted from a ConfigMap and seeded at startup.
- name: KANBAN_BOARDS_FILE
value: /etc/kanban/boards/boards.json
{{- end }}
ports:
- name: http
containerPort: 8080
protocol: TCP
{{- if eq .Values.config.transport "http" }}
readinessProbe:
httpGet:
path: /api/board
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /api/board
port: http
initialDelaySeconds: 10
periodSeconds: 20
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or .Values.database.url .Values.database.existingSecret .Values.boards }}
volumeMounts:
{{- if or .Values.database.url .Values.database.existingSecret }}
- name: db-url
mountPath: /etc/kanban/db
readOnly: true
{{- end }}
{{- if .Values.boards }}
- name: boards
mountPath: /etc/kanban/boards
readOnly: true
{{- end }}
{{- end }}
{{- if or .Values.database.url .Values.database.existingSecret .Values.boards }}
volumes:
{{- if or .Values.database.url .Values.database.existingSecret }}
- name: db-url
secret:
secretName: {{ include "kanban-mcp.dbSecretName" . }}
items:
- key: {{ include "kanban-mcp.dbSecretKey" . }}
path: {{ include "kanban-mcp.dbSecretKey" . }}
{{- end }}
{{- if .Values.boards }}
- name: boards
configMap:
name: {{ include "kanban-mcp.fullname" . }}-boards
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
Loading
Loading