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 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
@@ -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
+ }