snboard-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +404 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# snboard-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for Kanban board management via REST API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install snboard-mcp
|
|
9
|
+
# or
|
|
10
|
+
npx snboard-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Add to your Claude Code settings (`~/.claude/settings.json`):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"kanban": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["snboard-mcp@latest"],
|
|
23
|
+
"env": {
|
|
24
|
+
"KANBAN_BASE_URL": "https://katool.epheria.fr",
|
|
25
|
+
"KANBAN_API_KEY": "your-api-key-here"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Environment Variables
|
|
33
|
+
|
|
34
|
+
| Variable | Description | Required |
|
|
35
|
+
|----------|-------------|----------|
|
|
36
|
+
| `KANBAN_BASE_URL` | Base URL of the Kanban API | Yes |
|
|
37
|
+
| `KANBAN_API_KEY` | API key for authentication | Yes (if API is protected) |
|
|
38
|
+
|
|
39
|
+
## Available Tools
|
|
40
|
+
|
|
41
|
+
### Folders
|
|
42
|
+
- `list_folders` - List all folders with their projects
|
|
43
|
+
- `create_folder` - Create a new folder
|
|
44
|
+
- `update_folder` - Update a folder
|
|
45
|
+
- `delete_folder` - Delete a folder
|
|
46
|
+
- `move_project_to_folder` - Move a project to a folder
|
|
47
|
+
|
|
48
|
+
### Projects
|
|
49
|
+
- `list_projects` - List all projects
|
|
50
|
+
- `create_project` - Create a new project
|
|
51
|
+
- `get_project` - Get a project with its boards
|
|
52
|
+
- `update_project` - Update a project
|
|
53
|
+
- `delete_project` - Delete a project
|
|
54
|
+
|
|
55
|
+
### Boards
|
|
56
|
+
- `list_boards` - List boards for a project
|
|
57
|
+
- `create_board` - Create a new board
|
|
58
|
+
- `get_board` - Get a board with columns and cards
|
|
59
|
+
- `update_board` - Update a board
|
|
60
|
+
- `delete_board` - Delete a board
|
|
61
|
+
|
|
62
|
+
### Columns
|
|
63
|
+
- `create_column` - Create a column
|
|
64
|
+
- `update_column` - Update a column
|
|
65
|
+
- `delete_column` - Delete a column
|
|
66
|
+
|
|
67
|
+
### Cards
|
|
68
|
+
- `create_card` - Create a card
|
|
69
|
+
- `update_card` - Update a card
|
|
70
|
+
- `delete_card` - Delete a card
|
|
71
|
+
- `move_card` - Move a card to a different column
|
|
72
|
+
- `add_comment` - Add a comment to a card
|
|
73
|
+
- `get_branch_name` - Generate a GitHub branch name for a card
|
|
74
|
+
- `search_cards` - Search cards
|
|
75
|
+
|
|
76
|
+
### Tags
|
|
77
|
+
- `list_tags` - List all tags
|
|
78
|
+
- `create_tag` - Create a tag
|
|
79
|
+
- `delete_tag` - Delete a tag
|
|
80
|
+
- `add_tag_to_card` - Add a tag to a card
|
|
81
|
+
- `remove_tag_from_card` - Remove a tag from a card
|
|
82
|
+
|
|
83
|
+
### Epics
|
|
84
|
+
- `list_epics` - List all epics
|
|
85
|
+
- `create_epic` - Create an epic
|
|
86
|
+
- `get_epic` - Get an epic with its cards
|
|
87
|
+
- `update_epic` - Update an epic
|
|
88
|
+
- `delete_epic` - Delete an epic
|
|
89
|
+
- `add_card_to_epic` - Add a card to an epic
|
|
90
|
+
- `remove_card_from_epic` - Remove a card from an epic
|
|
91
|
+
- `get_epic_timeline` - Get epic timeline
|
|
92
|
+
|
|
93
|
+
### Card Links
|
|
94
|
+
- `link_cards` - Link two cards
|
|
95
|
+
- `unlink_cards` - Unlink two cards
|
|
96
|
+
- `get_linked_cards` - Get linked cards
|
|
97
|
+
- `get_feature_cards` - Get feature bundle
|
|
98
|
+
|
|
99
|
+
### GitHub Integration
|
|
100
|
+
- `setup_github_integration` - Setup GitHub webhook
|
|
101
|
+
- `get_github_integration` - Get GitHub settings
|
|
102
|
+
- `update_github_integration` - Update GitHub settings
|
|
103
|
+
- `remove_github_integration` - Remove GitHub integration
|
|
104
|
+
- `link_card_to_branch` - Link card to branch
|
|
105
|
+
- `link_card_to_pr` - Link card to PR
|
|
106
|
+
- `github_list_repos` - List user's GitHub repos
|
|
107
|
+
- `github_create_branch` - Create a GitHub branch
|
|
108
|
+
- `github_create_pr` - Create a GitHub PR
|
|
109
|
+
- `github_auto_setup_webhook` - Auto-setup webhook
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Install dependencies
|
|
115
|
+
pnpm install
|
|
116
|
+
|
|
117
|
+
# Run in development mode
|
|
118
|
+
pnpm dev
|
|
119
|
+
|
|
120
|
+
# Build for production
|
|
121
|
+
pnpm build
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Publishing
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Build and publish to GitHub Packages
|
|
128
|
+
pnpm build
|
|
129
|
+
npm publish
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
var BASE_URL = process.env.KANBAN_BASE_URL?.replace(/\/$/, "") || "http://localhost:3006";
|
|
11
|
+
var API_KEY = process.env.KANBAN_API_KEY || "";
|
|
12
|
+
async function api(endpoint, options = {}) {
|
|
13
|
+
const url = `${BASE_URL}${endpoint}`;
|
|
14
|
+
const headers = {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
...API_KEY && { "X-API-Key": API_KEY },
|
|
17
|
+
...options.headers
|
|
18
|
+
};
|
|
19
|
+
const response = await fetch(url, {
|
|
20
|
+
...options,
|
|
21
|
+
headers
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
25
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
26
|
+
}
|
|
27
|
+
return response.json();
|
|
28
|
+
}
|
|
29
|
+
var kanbanApi = {
|
|
30
|
+
// Folders
|
|
31
|
+
listFolders: () => api("/api/folders"),
|
|
32
|
+
createFolder: (data) => api("/api/folders", { method: "POST", body: JSON.stringify(data) }),
|
|
33
|
+
updateFolder: (id, data) => api(`/api/folders/${id}`, { method: "PATCH", body: JSON.stringify(data) }),
|
|
34
|
+
deleteFolder: (id) => api(`/api/folders/${id}`, { method: "DELETE" }),
|
|
35
|
+
moveProjectToFolder: (projectId, folderId) => api(`/api/projects/${projectId}`, { method: "PATCH", body: JSON.stringify({ folderId }) }),
|
|
36
|
+
// Projects
|
|
37
|
+
listProjects: (params) => {
|
|
38
|
+
const searchParams = new URLSearchParams();
|
|
39
|
+
if (params?.folderId) searchParams.set("folderId", params.folderId);
|
|
40
|
+
if (params?.limit) searchParams.set("limit", params.limit.toString());
|
|
41
|
+
if (params?.compact) searchParams.set("compact", "true");
|
|
42
|
+
const query = searchParams.toString();
|
|
43
|
+
return api(`/api/projects${query ? `?${query}` : ""}`);
|
|
44
|
+
},
|
|
45
|
+
createProject: (data) => api("/api/projects", { method: "POST", body: JSON.stringify(data) }),
|
|
46
|
+
getProject: (id) => api(`/api/projects/${id}`),
|
|
47
|
+
updateProject: (id, data) => api(`/api/projects/${id}`, { method: "PATCH", body: JSON.stringify(data) }),
|
|
48
|
+
deleteProject: (id) => api(`/api/projects/${id}`, { method: "DELETE" }),
|
|
49
|
+
// Boards
|
|
50
|
+
listBoards: (projectId, params) => {
|
|
51
|
+
const searchParams = new URLSearchParams();
|
|
52
|
+
searchParams.set("projectId", projectId);
|
|
53
|
+
if (params?.compact) searchParams.set("compact", "true");
|
|
54
|
+
if (params?.limit) searchParams.set("limit", params.limit.toString());
|
|
55
|
+
return api(`/api/boards?${searchParams.toString()}`);
|
|
56
|
+
},
|
|
57
|
+
createBoard: (data) => api("/api/boards", { method: "POST", body: JSON.stringify(data) }),
|
|
58
|
+
getBoard: (id, params) => {
|
|
59
|
+
const searchParams = new URLSearchParams();
|
|
60
|
+
if (params?.columnId) searchParams.set("columnId", params.columnId);
|
|
61
|
+
if (params?.includeCards === false) searchParams.set("includeCards", "false");
|
|
62
|
+
if (params?.includeTags) searchParams.set("includeTags", "true");
|
|
63
|
+
if (params?.includeComments) searchParams.set("includeComments", "true");
|
|
64
|
+
if (params?.cardLimit) searchParams.set("cardLimit", params.cardLimit.toString());
|
|
65
|
+
if (params?.compact) searchParams.set("compact", "true");
|
|
66
|
+
const query = searchParams.toString();
|
|
67
|
+
return api(`/api/boards/${id}${query ? `?${query}` : ""}`);
|
|
68
|
+
},
|
|
69
|
+
updateBoard: (id, data) => api(`/api/boards/${id}`, { method: "PATCH", body: JSON.stringify(data) }),
|
|
70
|
+
deleteBoard: (id) => api(`/api/boards/${id}`, { method: "DELETE" }),
|
|
71
|
+
// Columns
|
|
72
|
+
createColumn: (data) => api("/api/columns", { method: "POST", body: JSON.stringify(data) }),
|
|
73
|
+
updateColumn: (id, data) => api(`/api/columns/${id}`, { method: "PATCH", body: JSON.stringify(data) }),
|
|
74
|
+
deleteColumn: (id) => api(`/api/columns/${id}`, { method: "DELETE" }),
|
|
75
|
+
// Cards
|
|
76
|
+
createCard: (data) => api("/api/cards", { method: "POST", body: JSON.stringify(data) }),
|
|
77
|
+
updateCard: (id, data) => api(`/api/cards/${id}`, { method: "PATCH", body: JSON.stringify(data) }),
|
|
78
|
+
deleteCard: (id) => api(`/api/cards/${id}`, { method: "DELETE" }),
|
|
79
|
+
moveCard: (id, targetColumnId) => api("/api/cards/move", { method: "POST", body: JSON.stringify({ cardId: id, targetColumnId }) }),
|
|
80
|
+
getBranchName: (id, type) => api(`/api/cards/${id}/branch?type=${type}`),
|
|
81
|
+
searchCards: (params) => {
|
|
82
|
+
const searchParams = new URLSearchParams();
|
|
83
|
+
searchParams.set("query", params.query);
|
|
84
|
+
if (params.projectId) searchParams.set("projectId", params.projectId);
|
|
85
|
+
if (params.boardId) searchParams.set("boardId", params.boardId);
|
|
86
|
+
if (params.columnId) searchParams.set("columnId", params.columnId);
|
|
87
|
+
if (params.epicId) searchParams.set("epicId", params.epicId);
|
|
88
|
+
if (params.hasEpic !== void 0) searchParams.set("hasEpic", params.hasEpic.toString());
|
|
89
|
+
if (params.status) searchParams.set("status", params.status);
|
|
90
|
+
if (params.tagIds?.length) searchParams.set("tagIds", params.tagIds.join(","));
|
|
91
|
+
if (params.limit) searchParams.set("limit", params.limit.toString());
|
|
92
|
+
if (params.compact) searchParams.set("compact", "true");
|
|
93
|
+
return api(`/api/search?${searchParams.toString()}`);
|
|
94
|
+
},
|
|
95
|
+
// Comments
|
|
96
|
+
addComment: (cardId, content) => api("/api/comments", { method: "POST", body: JSON.stringify({ cardId, content }) }),
|
|
97
|
+
// Tags
|
|
98
|
+
listTags: () => api("/api/tags"),
|
|
99
|
+
createTag: (data) => api("/api/tags", { method: "POST", body: JSON.stringify(data) }),
|
|
100
|
+
deleteTag: (id) => api(`/api/tags/${id}`, { method: "DELETE" }),
|
|
101
|
+
addTagToCard: (cardId, tagId) => api(`/api/cards/${cardId}/tags`, { method: "POST", body: JSON.stringify({ tagId }) }),
|
|
102
|
+
removeTagFromCard: (cardId, tagId) => api(`/api/cards/${cardId}/tags/${tagId}`, { method: "DELETE" }),
|
|
103
|
+
// Epics
|
|
104
|
+
listEpics: (params) => {
|
|
105
|
+
const searchParams = new URLSearchParams();
|
|
106
|
+
if (params?.status) searchParams.set("status", params.status);
|
|
107
|
+
if (params?.includeProgress === false) searchParams.set("includeProgress", "false");
|
|
108
|
+
if (params?.limit) searchParams.set("limit", params.limit.toString());
|
|
109
|
+
if (params?.compact) searchParams.set("compact", "true");
|
|
110
|
+
const query = searchParams.toString();
|
|
111
|
+
return api(`/api/epics${query ? `?${query}` : ""}`);
|
|
112
|
+
},
|
|
113
|
+
createEpic: (data) => api("/api/epics", { method: "POST", body: JSON.stringify(data) }),
|
|
114
|
+
getEpic: (id) => api(`/api/epics/${id}`),
|
|
115
|
+
updateEpic: (id, data) => api(`/api/epics/${id}`, { method: "PATCH", body: JSON.stringify(data) }),
|
|
116
|
+
deleteEpic: (id) => api(`/api/epics/${id}`, { method: "DELETE" }),
|
|
117
|
+
addCardToEpic: (epicId, cardId) => api(`/api/epics/${epicId}/cards`, { method: "POST", body: JSON.stringify({ cardId }) }),
|
|
118
|
+
removeCardFromEpic: (epicId, cardId) => api(`/api/epics/${epicId}/cards/${cardId}`, { method: "DELETE" }),
|
|
119
|
+
getEpicTimeline: () => api("/api/epics/timeline"),
|
|
120
|
+
// Card Links
|
|
121
|
+
linkCards: (cardId1, cardId2, label) => api("/api/card-links", { method: "POST", body: JSON.stringify({ cardId1, cardId2, label }) }),
|
|
122
|
+
unlinkCards: (cardId1, cardId2) => api("/api/card-links", { method: "DELETE", body: JSON.stringify({ cardId1, cardId2 }) }),
|
|
123
|
+
getLinkedCards: (cardId) => api(`/api/card-links?cardId=${cardId}`),
|
|
124
|
+
getFeatureCards: (cardId) => api(`/api/features?cardId=${cardId}`),
|
|
125
|
+
// GitHub Integration
|
|
126
|
+
setupGithubIntegration: (data) => api("/api/github/integration", { method: "POST", body: JSON.stringify(data) }),
|
|
127
|
+
getGithubIntegration: (projectId) => api(`/api/github/integration?projectId=${projectId}`),
|
|
128
|
+
updateGithubIntegration: (projectId, data) => api(`/api/github/integration/${projectId}`, { method: "PATCH", body: JSON.stringify(data) }),
|
|
129
|
+
removeGithubIntegration: (projectId) => api(`/api/github/integration/${projectId}`, { method: "DELETE" }),
|
|
130
|
+
linkCardToBranch: (cardId, branch) => api(`/api/cards/${cardId}`, { method: "PATCH", body: JSON.stringify({ githubBranch: branch }) }),
|
|
131
|
+
linkCardToPr: (cardId, prUrl) => api(`/api/cards/${cardId}`, { method: "PATCH", body: JSON.stringify({ githubPrUrl: prUrl }) }),
|
|
132
|
+
// GitHub API
|
|
133
|
+
githubListRepos: (userId, type) => api(`/api/github/repos?userId=${userId}${type ? `&type=${type}` : ""}`),
|
|
134
|
+
githubCreateBranch: (data) => api("/api/github/branches", { method: "POST", body: JSON.stringify(data) }),
|
|
135
|
+
githubCreatePr: (data) => api("/api/github/pull-requests", { method: "POST", body: JSON.stringify(data) }),
|
|
136
|
+
githubAutoSetupWebhook: (data) => api("/api/github/webhooks/auto-setup", { method: "POST", body: JSON.stringify(data) })
|
|
137
|
+
};
|
|
138
|
+
var server = new Server(
|
|
139
|
+
{ name: "kanban-mcp", version: "1.0.0" },
|
|
140
|
+
{ capabilities: { tools: {} } }
|
|
141
|
+
);
|
|
142
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
143
|
+
tools: [
|
|
144
|
+
// FOLDERS
|
|
145
|
+
{ name: "list_folders", description: "List all folders with their projects", inputSchema: { type: "object", properties: {} } },
|
|
146
|
+
{ name: "create_folder", description: "Create a new folder to organize projects", inputSchema: { type: "object", properties: { name: { type: "string", description: "Folder name" }, icon: { type: "string", description: "Folder icon (optional)" }, color: { type: "string", description: "Folder color hex (optional)" } }, required: ["name"] } },
|
|
147
|
+
{ name: "update_folder", description: "Update a folder (name, icon, color)", inputSchema: { type: "object", properties: { folderId: { type: "string", description: "Folder ID" }, name: { type: "string" }, icon: { type: "string" }, color: { type: "string" } }, required: ["folderId"] } },
|
|
148
|
+
{ name: "delete_folder", description: "Delete a folder (projects inside become orphans, not deleted)", inputSchema: { type: "object", properties: { folderId: { type: "string", description: "Folder ID" } }, required: ["folderId"] } },
|
|
149
|
+
{ name: "move_project_to_folder", description: "Move a project to a folder (or remove from folder if folderId is null)", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" }, folderId: { type: "string", description: "Target folder ID (or null)" } }, required: ["projectId"] } },
|
|
150
|
+
// PROJECTS
|
|
151
|
+
{ name: "list_projects", description: "List all Kanban projects with optional filters to reduce token usage", inputSchema: { type: "object", properties: { folderId: { type: "string", description: "Filter by folder ID" }, limit: { type: "number", description: "Max results" }, compact: { type: "boolean", description: "Return minimal fields" } } } },
|
|
152
|
+
{ name: "create_project", description: "Create a new Kanban project", inputSchema: { type: "object", properties: { name: { type: "string", description: "Project name" }, description: { type: "string" }, emoji: { type: "string" }, color: { type: "string" }, folderId: { type: "string" } }, required: ["name"] } },
|
|
153
|
+
{ name: "get_project", description: "Get a project with its boards", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" } }, required: ["projectId"] } },
|
|
154
|
+
{ name: "update_project", description: "Update a project (name, description, emoji, color)", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" }, name: { type: "string" }, description: { type: "string" }, emoji: { type: "string" }, color: { type: "string" } }, required: ["projectId"] } },
|
|
155
|
+
{ name: "delete_project", description: "Delete a project and all its boards, columns, and cards", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" } }, required: ["projectId"] } },
|
|
156
|
+
// BOARDS
|
|
157
|
+
{ name: "list_boards", description: "List boards for a project with optional filters to reduce token usage", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" }, compact: { type: "boolean" }, includeColumnStats: { type: "boolean" }, limit: { type: "number" } }, required: ["projectId"] } },
|
|
158
|
+
{ name: "create_board", description: "Create a new board in a project with default columns (To Do, In Progress, Done)", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" }, name: { type: "string", description: "Board name" } }, required: ["projectId", "name"] } },
|
|
159
|
+
{ name: "get_board", description: "Get a board with columns and cards. Use filters to reduce token usage.", inputSchema: { type: "object", properties: { boardId: { type: "string", description: "Board ID" }, columnId: { type: "string" }, includeCards: { type: "boolean" }, includeTags: { type: "boolean" }, includeComments: { type: "boolean" }, cardLimit: { type: "number" }, compact: { type: "boolean" } }, required: ["boardId"] } },
|
|
160
|
+
{ name: "update_board", description: "Update a board (rename)", inputSchema: { type: "object", properties: { boardId: { type: "string", description: "Board ID" }, name: { type: "string", description: "New board name" } }, required: ["boardId", "name"] } },
|
|
161
|
+
{ name: "delete_board", description: "Delete a board and all its columns and cards", inputSchema: { type: "object", properties: { boardId: { type: "string", description: "Board ID" } }, required: ["boardId"] } },
|
|
162
|
+
// COLUMNS
|
|
163
|
+
{ name: "create_column", description: "Create a new column in a board", inputSchema: { type: "object", properties: { boardId: { type: "string", description: "Board ID" }, name: { type: "string", description: "Column name" }, emoji: { type: "string" } }, required: ["boardId", "name"] } },
|
|
164
|
+
{ name: "update_column", description: "Update a column (name, emoji)", inputSchema: { type: "object", properties: { columnId: { type: "string", description: "Column ID" }, name: { type: "string" }, emoji: { type: "string" } }, required: ["columnId"] } },
|
|
165
|
+
{ name: "delete_column", description: "Delete a column and all its cards", inputSchema: { type: "object", properties: { columnId: { type: "string", description: "Column ID" } }, required: ["columnId"] } },
|
|
166
|
+
// CARDS
|
|
167
|
+
{ name: "create_card", description: "Create a new card in a column. Supports auto-assign to epic if title starts with [SLUG] or SLUG -.", inputSchema: { type: "object", properties: { columnId: { type: "string", description: "Column ID" }, title: { type: "string", description: "Card title" }, description: { type: "string" }, priority: { type: "string", enum: ["P1", "P2", "P3"] }, dueDate: { type: "string" }, epicId: { type: "string" }, tagIds: { type: "array", items: { type: "string" } }, linkedCardIds: { type: "array", items: { type: "string" } }, linkLabel: { type: "string" } }, required: ["columnId", "title"] } },
|
|
168
|
+
{ name: "update_card", description: "Update an existing card", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" }, title: { type: "string" }, description: { type: "string" } }, required: ["cardId"] } },
|
|
169
|
+
{ name: "delete_card", description: "Delete a card", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" } }, required: ["cardId"] } },
|
|
170
|
+
{ name: "move_card", description: "Move a card to a different column", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" }, targetColumnId: { type: "string", description: "Target column ID" } }, required: ["cardId", "targetColumnId"] } },
|
|
171
|
+
{ name: "add_comment", description: "Add a comment to a card", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" }, content: { type: "string", description: "Comment content" } }, required: ["cardId", "content"] } },
|
|
172
|
+
{ name: "get_branch_name", description: "Generate a GitHub branch name for a card", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" }, type: { type: "string", enum: ["feature", "fix", "chore", "refactor", "docs"], description: "Branch type" } }, required: ["cardId"] } },
|
|
173
|
+
{ name: "search_cards", description: "Search cards by title or description with filters to reduce token usage", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query" }, projectId: { type: "string" }, boardId: { type: "string" }, columnId: { type: "string" }, epicId: { type: "string" }, hasEpic: { type: "boolean" }, status: { type: "string", enum: ["todo", "in_progress", "done"] }, tagIds: { type: "array", items: { type: "string" } }, limit: { type: "number" }, compact: { type: "boolean" } }, required: ["query"] } },
|
|
174
|
+
// TAGS
|
|
175
|
+
{ name: "list_tags", description: "List all available tags", inputSchema: { type: "object", properties: {} } },
|
|
176
|
+
{ name: "create_tag", description: "Create a new tag", inputSchema: { type: "object", properties: { name: { type: "string", description: "Tag name" }, color: { type: "string", description: "Tag color hex" } }, required: ["name", "color"] } },
|
|
177
|
+
{ name: "delete_tag", description: "Delete a tag", inputSchema: { type: "object", properties: { tagId: { type: "string", description: "Tag ID" } }, required: ["tagId"] } },
|
|
178
|
+
{ name: "add_tag_to_card", description: "Add a tag to a card", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" }, tagId: { type: "string", description: "Tag ID" } }, required: ["cardId", "tagId"] } },
|
|
179
|
+
{ name: "remove_tag_from_card", description: "Remove a tag from a card", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" }, tagId: { type: "string", description: "Tag ID" } }, required: ["cardId", "tagId"] } },
|
|
180
|
+
// EPICS
|
|
181
|
+
{ name: "list_epics", description: "List all epics with optional filters to reduce token usage", inputSchema: { type: "object", properties: { status: { type: "string" }, includeProgress: { type: "boolean" }, limit: { type: "number" }, compact: { type: "boolean" } } } },
|
|
182
|
+
{ name: "create_epic", description: "Create a new epic for grouping cards", inputSchema: { type: "object", properties: { slug: { type: "string", description: 'Short unique identifier (e.g., "AUTH", "PERF")' }, name: { type: "string", description: "Epic name" }, description: { type: "string" }, emoji: { type: "string" }, color: { type: "string" }, startDate: { type: "string" }, targetDate: { type: "string" } }, required: ["slug", "name"] } },
|
|
183
|
+
{ name: "get_epic", description: "Get an epic with all its cards and progress", inputSchema: { type: "object", properties: { epicId: { type: "string", description: "Epic ID or slug" } }, required: ["epicId"] } },
|
|
184
|
+
{ name: "update_epic", description: "Update an epic", inputSchema: { type: "object", properties: { epicId: { type: "string", description: "Epic ID" }, slug: { type: "string" }, name: { type: "string" }, description: { type: "string" }, emoji: { type: "string" }, color: { type: "string" }, status: { type: "string" }, startDate: { type: "string" }, targetDate: { type: "string" } }, required: ["epicId"] } },
|
|
185
|
+
{ name: "delete_epic", description: "Delete an epic (cards are not deleted, just unlinked)", inputSchema: { type: "object", properties: { epicId: { type: "string", description: "Epic ID" } }, required: ["epicId"] } },
|
|
186
|
+
{ name: "add_card_to_epic", description: "Add a card to an epic", inputSchema: { type: "object", properties: { epicId: { type: "string", description: "Epic ID or slug" }, cardId: { type: "string", description: "Card ID" } }, required: ["epicId", "cardId"] } },
|
|
187
|
+
{ name: "remove_card_from_epic", description: "Remove a card from an epic", inputSchema: { type: "object", properties: { epicId: { type: "string", description: "Epic ID" }, cardId: { type: "string", description: "Card ID" } }, required: ["epicId", "cardId"] } },
|
|
188
|
+
{ name: "get_epic_timeline", description: "Get timeline view of all epics with dates and progress", inputSchema: { type: "object", properties: {} } },
|
|
189
|
+
// CARD LINKS
|
|
190
|
+
{ name: "link_cards", description: "Create a bidirectional link between two cards (for cross-project features)", inputSchema: { type: "object", properties: { cardId1: { type: "string", description: "First card ID" }, cardId2: { type: "string", description: "Second card ID" }, label: { type: "string", description: 'Link label (e.g., "depends on")' } }, required: ["cardId1", "cardId2"] } },
|
|
191
|
+
{ name: "unlink_cards", description: "Remove a link between two cards", inputSchema: { type: "object", properties: { cardId1: { type: "string", description: "First card ID" }, cardId2: { type: "string", description: "Second card ID" } }, required: ["cardId1", "cardId2"] } },
|
|
192
|
+
{ name: "get_linked_cards", description: "Get all cards linked to a specific card (shows cross-project relationships)", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" } }, required: ["cardId"] } },
|
|
193
|
+
{ name: "get_feature_cards", description: 'Get a card and all its linked cards as a "feature bundle" with progress tracking', inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Starting card ID" } }, required: ["cardId"] } },
|
|
194
|
+
// GITHUB INTEGRATION
|
|
195
|
+
{ name: "setup_github_integration", description: "Configure GitHub webhook integration for a project", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" }, repoOwner: { type: "string", description: "GitHub repo owner" }, repoName: { type: "string", description: "GitHub repo name" }, inProgressColumnName: { type: "string" }, doneColumnName: { type: "string" } }, required: ["projectId", "repoOwner", "repoName"] } },
|
|
196
|
+
{ name: "get_github_integration", description: "Get GitHub integration settings for a project", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" } }, required: ["projectId"] } },
|
|
197
|
+
{ name: "update_github_integration", description: "Update GitHub integration settings", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" }, autoMoveOnPush: { type: "boolean" }, autoMoveOnPrMerge: { type: "boolean" }, autoLinkPr: { type: "boolean" }, inProgressColumnName: { type: "string" }, doneColumnName: { type: "string" } }, required: ["projectId"] } },
|
|
198
|
+
{ name: "remove_github_integration", description: "Remove GitHub integration from a project", inputSchema: { type: "object", properties: { projectId: { type: "string", description: "Project ID" } }, required: ["projectId"] } },
|
|
199
|
+
{ name: "link_card_to_branch", description: "Link a card to a GitHub branch", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" }, branch: { type: "string", description: "GitHub branch name" } }, required: ["cardId", "branch"] } },
|
|
200
|
+
{ name: "link_card_to_pr", description: "Link a card to a GitHub Pull Request", inputSchema: { type: "object", properties: { cardId: { type: "string", description: "Card ID" }, prUrl: { type: "string", description: "GitHub PR URL" } }, required: ["cardId", "prUrl"] } },
|
|
201
|
+
// GITHUB API
|
|
202
|
+
{ name: "github_list_repos", description: "List GitHub repositories accessible by the authenticated user", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID (from session)" }, type: { type: "string", enum: ["all", "owner", "member"] } }, required: ["userId"] } },
|
|
203
|
+
{ name: "github_create_branch", description: "Create a new branch on a GitHub repository", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID" }, owner: { type: "string", description: "Repository owner" }, repo: { type: "string", description: "Repository name" }, branchName: { type: "string", description: "New branch name" }, sourceBranch: { type: "string", description: "Source branch (default: main)" } }, required: ["userId", "owner", "repo", "branchName"] } },
|
|
204
|
+
{ name: "github_create_pr", description: "Create a pull request on a GitHub repository", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID" }, owner: { type: "string", description: "Repository owner" }, repo: { type: "string", description: "Repository name" }, title: { type: "string", description: "PR title" }, head: { type: "string", description: "Branch containing changes" }, base: { type: "string", description: "Target branch" }, body: { type: "string", description: "PR description" } }, required: ["userId", "owner", "repo", "title", "head"] } },
|
|
205
|
+
{ name: "github_auto_setup_webhook", description: "Automatically setup GitHub webhook for a Kanban project (requires user auth)", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID" }, projectId: { type: "string", description: "Kanban project ID" }, owner: { type: "string", description: "Repository owner" }, repo: { type: "string", description: "Repository name" } }, required: ["userId", "projectId", "owner", "repo"] } }
|
|
206
|
+
]
|
|
207
|
+
}));
|
|
208
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
209
|
+
const { name, arguments: args = {} } = request.params;
|
|
210
|
+
try {
|
|
211
|
+
let result;
|
|
212
|
+
switch (name) {
|
|
213
|
+
// FOLDERS
|
|
214
|
+
case "list_folders":
|
|
215
|
+
result = await kanbanApi.listFolders();
|
|
216
|
+
break;
|
|
217
|
+
case "create_folder":
|
|
218
|
+
result = await kanbanApi.createFolder(args);
|
|
219
|
+
break;
|
|
220
|
+
case "update_folder":
|
|
221
|
+
result = await kanbanApi.updateFolder(args.folderId, args);
|
|
222
|
+
break;
|
|
223
|
+
case "delete_folder":
|
|
224
|
+
result = await kanbanApi.deleteFolder(args.folderId);
|
|
225
|
+
break;
|
|
226
|
+
case "move_project_to_folder":
|
|
227
|
+
result = await kanbanApi.moveProjectToFolder(args.projectId, args.folderId);
|
|
228
|
+
break;
|
|
229
|
+
// PROJECTS
|
|
230
|
+
case "list_projects":
|
|
231
|
+
result = await kanbanApi.listProjects(args);
|
|
232
|
+
break;
|
|
233
|
+
case "create_project":
|
|
234
|
+
result = await kanbanApi.createProject(args);
|
|
235
|
+
break;
|
|
236
|
+
case "get_project":
|
|
237
|
+
result = await kanbanApi.getProject(args.projectId);
|
|
238
|
+
break;
|
|
239
|
+
case "update_project":
|
|
240
|
+
result = await kanbanApi.updateProject(args.projectId, args);
|
|
241
|
+
break;
|
|
242
|
+
case "delete_project":
|
|
243
|
+
result = await kanbanApi.deleteProject(args.projectId);
|
|
244
|
+
break;
|
|
245
|
+
// BOARDS
|
|
246
|
+
case "list_boards":
|
|
247
|
+
result = await kanbanApi.listBoards(args.projectId, args);
|
|
248
|
+
break;
|
|
249
|
+
case "create_board":
|
|
250
|
+
result = await kanbanApi.createBoard(args);
|
|
251
|
+
break;
|
|
252
|
+
case "get_board":
|
|
253
|
+
result = await kanbanApi.getBoard(args.boardId, args);
|
|
254
|
+
break;
|
|
255
|
+
case "update_board":
|
|
256
|
+
result = await kanbanApi.updateBoard(args.boardId, { name: args.name });
|
|
257
|
+
break;
|
|
258
|
+
case "delete_board":
|
|
259
|
+
result = await kanbanApi.deleteBoard(args.boardId);
|
|
260
|
+
break;
|
|
261
|
+
// COLUMNS
|
|
262
|
+
case "create_column":
|
|
263
|
+
result = await kanbanApi.createColumn(args);
|
|
264
|
+
break;
|
|
265
|
+
case "update_column":
|
|
266
|
+
result = await kanbanApi.updateColumn(args.columnId, args);
|
|
267
|
+
break;
|
|
268
|
+
case "delete_column":
|
|
269
|
+
result = await kanbanApi.deleteColumn(args.columnId);
|
|
270
|
+
break;
|
|
271
|
+
// CARDS
|
|
272
|
+
case "create_card":
|
|
273
|
+
result = await kanbanApi.createCard(args);
|
|
274
|
+
break;
|
|
275
|
+
case "update_card":
|
|
276
|
+
result = await kanbanApi.updateCard(args.cardId, args);
|
|
277
|
+
break;
|
|
278
|
+
case "delete_card":
|
|
279
|
+
result = await kanbanApi.deleteCard(args.cardId);
|
|
280
|
+
break;
|
|
281
|
+
case "move_card":
|
|
282
|
+
result = await kanbanApi.moveCard(args.cardId, args.targetColumnId);
|
|
283
|
+
break;
|
|
284
|
+
case "add_comment":
|
|
285
|
+
result = await kanbanApi.addComment(args.cardId, args.content);
|
|
286
|
+
break;
|
|
287
|
+
case "get_branch_name":
|
|
288
|
+
result = await kanbanApi.getBranchName(args.cardId, args.type || "feature");
|
|
289
|
+
break;
|
|
290
|
+
case "search_cards":
|
|
291
|
+
result = await kanbanApi.searchCards(args);
|
|
292
|
+
break;
|
|
293
|
+
// TAGS
|
|
294
|
+
case "list_tags":
|
|
295
|
+
result = await kanbanApi.listTags();
|
|
296
|
+
break;
|
|
297
|
+
case "create_tag":
|
|
298
|
+
result = await kanbanApi.createTag(args);
|
|
299
|
+
break;
|
|
300
|
+
case "delete_tag":
|
|
301
|
+
result = await kanbanApi.deleteTag(args.tagId);
|
|
302
|
+
break;
|
|
303
|
+
case "add_tag_to_card":
|
|
304
|
+
result = await kanbanApi.addTagToCard(args.cardId, args.tagId);
|
|
305
|
+
break;
|
|
306
|
+
case "remove_tag_from_card":
|
|
307
|
+
result = await kanbanApi.removeTagFromCard(args.cardId, args.tagId);
|
|
308
|
+
break;
|
|
309
|
+
// EPICS
|
|
310
|
+
case "list_epics":
|
|
311
|
+
result = await kanbanApi.listEpics(args);
|
|
312
|
+
break;
|
|
313
|
+
case "create_epic":
|
|
314
|
+
result = await kanbanApi.createEpic(args);
|
|
315
|
+
break;
|
|
316
|
+
case "get_epic":
|
|
317
|
+
result = await kanbanApi.getEpic(args.epicId);
|
|
318
|
+
break;
|
|
319
|
+
case "update_epic":
|
|
320
|
+
result = await kanbanApi.updateEpic(args.epicId, args);
|
|
321
|
+
break;
|
|
322
|
+
case "delete_epic":
|
|
323
|
+
result = await kanbanApi.deleteEpic(args.epicId);
|
|
324
|
+
break;
|
|
325
|
+
case "add_card_to_epic":
|
|
326
|
+
result = await kanbanApi.addCardToEpic(args.epicId, args.cardId);
|
|
327
|
+
break;
|
|
328
|
+
case "remove_card_from_epic":
|
|
329
|
+
result = await kanbanApi.removeCardFromEpic(args.epicId, args.cardId);
|
|
330
|
+
break;
|
|
331
|
+
case "get_epic_timeline":
|
|
332
|
+
result = await kanbanApi.getEpicTimeline();
|
|
333
|
+
break;
|
|
334
|
+
// CARD LINKS
|
|
335
|
+
case "link_cards":
|
|
336
|
+
result = await kanbanApi.linkCards(args.cardId1, args.cardId2, args.label);
|
|
337
|
+
break;
|
|
338
|
+
case "unlink_cards":
|
|
339
|
+
result = await kanbanApi.unlinkCards(args.cardId1, args.cardId2);
|
|
340
|
+
break;
|
|
341
|
+
case "get_linked_cards":
|
|
342
|
+
result = await kanbanApi.getLinkedCards(args.cardId);
|
|
343
|
+
break;
|
|
344
|
+
case "get_feature_cards":
|
|
345
|
+
result = await kanbanApi.getFeatureCards(args.cardId);
|
|
346
|
+
break;
|
|
347
|
+
// GITHUB INTEGRATION
|
|
348
|
+
case "setup_github_integration":
|
|
349
|
+
result = await kanbanApi.setupGithubIntegration(args);
|
|
350
|
+
break;
|
|
351
|
+
case "get_github_integration":
|
|
352
|
+
result = await kanbanApi.getGithubIntegration(args.projectId);
|
|
353
|
+
break;
|
|
354
|
+
case "update_github_integration":
|
|
355
|
+
result = await kanbanApi.updateGithubIntegration(args.projectId, args);
|
|
356
|
+
break;
|
|
357
|
+
case "remove_github_integration":
|
|
358
|
+
result = await kanbanApi.removeGithubIntegration(args.projectId);
|
|
359
|
+
break;
|
|
360
|
+
case "link_card_to_branch":
|
|
361
|
+
result = await kanbanApi.linkCardToBranch(args.cardId, args.branch);
|
|
362
|
+
break;
|
|
363
|
+
case "link_card_to_pr":
|
|
364
|
+
result = await kanbanApi.linkCardToPr(args.cardId, args.prUrl);
|
|
365
|
+
break;
|
|
366
|
+
// GITHUB API
|
|
367
|
+
case "github_list_repos":
|
|
368
|
+
result = await kanbanApi.githubListRepos(args.userId, args.type);
|
|
369
|
+
break;
|
|
370
|
+
case "github_create_branch":
|
|
371
|
+
result = await kanbanApi.githubCreateBranch(args);
|
|
372
|
+
break;
|
|
373
|
+
case "github_create_pr":
|
|
374
|
+
result = await kanbanApi.githubCreatePr(args);
|
|
375
|
+
break;
|
|
376
|
+
case "github_auto_setup_webhook":
|
|
377
|
+
result = await kanbanApi.githubAutoSetupWebhook(args);
|
|
378
|
+
break;
|
|
379
|
+
default:
|
|
380
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
content: [{
|
|
384
|
+
type: "text",
|
|
385
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
386
|
+
}]
|
|
387
|
+
};
|
|
388
|
+
} catch (error) {
|
|
389
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
390
|
+
return {
|
|
391
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
392
|
+
isError: true
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
async function main() {
|
|
397
|
+
const transport = new StdioServerTransport();
|
|
398
|
+
await server.connect(transport);
|
|
399
|
+
console.error("Kanban MCP Server running on stdio");
|
|
400
|
+
}
|
|
401
|
+
main().catch((error) => {
|
|
402
|
+
console.error("Fatal error:", error);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "snboard-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for SnBoard Kanban board management via REST API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"snboard-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.10.5",
|
|
23
|
+
"tsup": "^8.3.5",
|
|
24
|
+
"tsx": "^4.19.2",
|
|
25
|
+
"typescript": "^5.7.3"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"registry": "https://registry.npmjs.org",
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/Epheria-events/epheria-workspace.git",
|
|
37
|
+
"directory": "services/kanban-mcp"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"mcp",
|
|
41
|
+
"kanban",
|
|
42
|
+
"snboard",
|
|
43
|
+
"claude",
|
|
44
|
+
"anthropic"
|
|
45
|
+
],
|
|
46
|
+
"author": "Epheria Events",
|
|
47
|
+
"license": "MIT"
|
|
48
|
+
}
|