@lil2good/nubis-mcp-server 1.0.52 → 1.0.54

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.
Files changed (3) hide show
  1. package/build/index.js +569 -275
  2. package/package.json +3 -1
  3. package/src/index.ts +779 -294
package/build/index.js CHANGED
@@ -8,6 +8,7 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
8
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
9
  const zod_1 = require("zod");
10
10
  const dotenv_1 = __importDefault(require("dotenv"));
11
+ const node_fetch_1 = __importDefault(require("node-fetch"));
11
12
  dotenv_1.default.config();
12
13
  // Register nubis tools
13
14
  const cliWorkspaceID = process.env.NUBIS_WORKSPACE_ID;
@@ -21,9 +22,100 @@ const server = new mcp_js_1.McpServer({
21
22
  tools: {},
22
23
  },
23
24
  });
24
- // Helper to get results from middleware
25
+ // ============================================================================
26
+ // Formatting Helpers
27
+ // ============================================================================
28
+ const BOARD_LABELS = {
29
+ 'backlog': '📋 Backlog',
30
+ 'bugs': '🐛 Bugs',
31
+ 'priority': '⚡ Priority',
32
+ 'in-progress': '🔄 In Progress',
33
+ 'reviewing': '👀 Reviewing',
34
+ 'completed': '✅ Completed'
35
+ };
36
+ function formatBoard(board) {
37
+ return BOARD_LABELS[board] || board;
38
+ }
39
+ function formatApiUsage(usage) {
40
+ return `📊 **API Usage:** ${usage.remaining_calls}/${usage.total_limit} calls remaining`;
41
+ }
42
+ function formatTask(task, options = {}) {
43
+ const lines = [
44
+ `## ${task.title}`,
45
+ '',
46
+ `| Property | Value |`,
47
+ `|----------|-------|`,
48
+ `| **ID** | \`${task.id}\` |`,
49
+ `| **Number** | #${task.task_number} |`,
50
+ `| **Board** | ${formatBoard(task.board)} |`,
51
+ `| **Bolt** | ${task.bolt?.name || '_Unassigned_'} |`,
52
+ ];
53
+ if (task.parent_task_id) {
54
+ lines.push(`| **Parent Task** | \`${task.parent_task_id}\` |`);
55
+ }
56
+ // Description
57
+ lines.push('', '### Description', task.description || '_No description provided_');
58
+ // GitHub info
59
+ if (task.github_file_path || task.github_repo_name) {
60
+ lines.push('', '### GitHub', `- **Repo:** ${task.github_repo_name || '_Not set_'}`, `- **Path:** \`${task.github_file_path || '_Not set_'}\``, `- **Type:** ${task.github_item_type || '_Not set_'}`);
61
+ }
62
+ // Blockers
63
+ if (task.pm_task_blockers && task.pm_task_blockers.length > 0) {
64
+ lines.push('', '### ⚠️ Blockers', ...task.pm_task_blockers.map(b => `- \`${b.blocker_task_id}\``));
65
+ }
66
+ // Detailed view includes subtasks and comments
67
+ if (options.detailed) {
68
+ if (task.subtasks && task.subtasks.length > 0) {
69
+ const flatSubtasks = Array.isArray(task.subtasks[0]) ? task.subtasks[0] : task.subtasks;
70
+ if (flatSubtasks.length > 0) {
71
+ lines.push('', '### Subtasks');
72
+ flatSubtasks.forEach((sub) => {
73
+ lines.push(`- [${sub.board === 'completed' ? 'x' : ' '}] **${sub.title}** (\`${sub.id}\`) - ${formatBoard(sub.board)}`);
74
+ });
75
+ }
76
+ }
77
+ if (task.comments && task.comments.length > 0) {
78
+ const flatComments = Array.isArray(task.comments[0]) ? task.comments[0] : task.comments;
79
+ if (flatComments.length > 0) {
80
+ lines.push('', '### Comments');
81
+ flatComments.forEach((c) => {
82
+ lines.push(`- ${c.content}`);
83
+ });
84
+ }
85
+ }
86
+ if (task.context) {
87
+ lines.push('', '### Context', task.context);
88
+ }
89
+ }
90
+ // Images
91
+ if (task.images && task.images.length > 0) {
92
+ lines.push('', '### Images');
93
+ task.images.forEach((img, i) => {
94
+ lines.push(`${i + 1}. ${img.url}`);
95
+ });
96
+ }
97
+ return lines.join('\n');
98
+ }
99
+ function formatTaskCompact(task) {
100
+ const blockerNote = task.pm_task_blockers && task.pm_task_blockers.length > 0
101
+ ? ` ⚠️ ${task.pm_task_blockers.length} blocker(s)`
102
+ : '';
103
+ return [
104
+ `### #${task.task_number}: ${task.title}${blockerNote}`,
105
+ `**ID:** \`${task.id}\` | **Board:** ${formatBoard(task.board)} | **Bolt:** ${task.bolt?.name || '_None_'}`,
106
+ task.description ? `> ${task.description.substring(0, 150)}${task.description.length > 150 ? '...' : ''}` : '',
107
+ task.github_file_path ? `📁 \`${task.github_file_path}\`` : '',
108
+ '---'
109
+ ].filter(Boolean).join('\n');
110
+ }
111
+ function formatBolt(bolt) {
112
+ return `- **${bolt.name}** (\`${bolt.id}\`)${bolt.description ? `: ${bolt.description}` : ''}`;
113
+ }
114
+ // ============================================================================
115
+ // Middleware Helper
116
+ // ============================================================================
25
117
  async function getResultsFromMiddleware({ endpoint, schema }) {
26
- const response = await fetch('https://mcp-server.nubis.app/' + endpoint, {
118
+ const response = await (0, node_fetch_1.default)('https://mcp-server.nubis.app/' + endpoint, {
27
119
  method: 'POST',
28
120
  headers: {
29
121
  'Content-Type': 'application/json',
@@ -36,314 +128,269 @@ async function getResultsFromMiddleware({ endpoint, schema }) {
36
128
  });
37
129
  if (!response.ok) {
38
130
  const errorData = await response.json();
39
- throw new Error(errorData.error || 'Failed to fetch tasks from middleware');
131
+ throw new Error(errorData.error || 'Failed to fetch from middleware');
40
132
  }
41
- // Return the full JSON response (including api_usage, user, etc.)
42
133
  const json = await response.json();
43
134
  if (!json.data)
44
135
  throw new Error('No data returned from middleware');
45
136
  return json;
46
137
  }
47
- // Get Boltz -> to save IDs for use in tasks later
48
- server.tool("get_boltz", "Fetch all boltz for a workspace", async () => {
138
+ // ============================================================================
139
+ // Tools
140
+ // ============================================================================
141
+ /**
142
+ * Get Boltz
143
+ */
144
+ server.tool("get_boltz", "Fetch all boltz (project branches) for the workspace. Use this to get bolt IDs for filtering tasks or assigning tasks to specific boltz.", async () => {
49
145
  const json = await getResultsFromMiddleware({
50
146
  endpoint: 'get_boltz',
51
147
  schema: {}
52
148
  });
53
- if (!json.data)
54
- throw new Error('No data returned from middleware');
149
+ const boltz = json.data;
150
+ const formatted = [
151
+ `# Boltz (${boltz.length} total)`,
152
+ '',
153
+ ...boltz.map(formatBolt),
154
+ '',
155
+ '---',
156
+ formatApiUsage(json.api_usage)
157
+ ].join('\n');
55
158
  return {
56
- content: [
57
- {
58
- type: "text",
59
- text: JSON.stringify(json.data),
60
- },
61
- {
62
- type: "text",
63
- text: `Always provide API Usage information separately. Usage: ${JSON.stringify(json.api_usage)}`,
64
- }
65
- ],
159
+ content: [{ type: "text", text: formatted }],
66
160
  };
67
161
  });
68
- // Get Tasks -> Get tasks for a workspace
69
- server.tool("get_tasks", "Get tasks for a workspace, including subtasks, boltz, and github details/file paths", {
70
- limit: zod_1.z.number().optional().default(5),
71
- board: zod_1.z.enum(['bugs', 'backlog', 'priority', 'in-progress', 'reviewing', 'completed']).optional(),
72
- bolt_id: zod_1.z.string().optional(),
162
+ /**
163
+ * Get Tasks
164
+ */
165
+ server.tool("get_tasks", "List tasks from the workspace with optional filtering. Returns task summaries including title, status, bolt assignment, blockers, and GitHub integration details. Use get_task_details for full information on a specific task.", {
166
+ limit: zod_1.z.number().optional().default(5).describe("Maximum number of tasks to return (default: 5)"),
167
+ board: zod_1.z.enum(['bugs', 'backlog', 'priority', 'in-progress', 'reviewing', 'completed']).optional().describe("Filter by board/status"),
168
+ bolt_id: zod_1.z.string().optional().describe("Filter by bolt ID (use get_boltz to find bolt IDs)"),
73
169
  }, async ({ limit, board, bolt_id }) => {
74
- try {
75
- const json = await getResultsFromMiddleware({
76
- endpoint: 'get_tasks',
77
- schema: {
78
- board,
79
- bolt_id,
80
- limit
81
- }
82
- });
83
- if (!json.data)
84
- throw new Error('No data returned from middleware');
85
- const taskContent = json.data.map((task) => ({
86
- type: "text",
87
- text: [
88
- `---`,
89
- `### ${task.title}`,
90
- `**Task ID:** ${task.id}`,
91
- `**Task Number:** ${task.task_number}`,
92
- `**Board:** ${task.board}`,
93
- `**Bolt:** ${task.bolt && typeof task.bolt === "object" && !Array.isArray(task.bolt) && "name" in task.bolt && task.bolt.name ? task.bolt.name : "_No bolt_"}`,
94
- `**Description:** ${task.description ? task.description : "_No description_"}`,
95
- `**Path Type:** ${task.github_item_type ? task.github_item_type : "_No file path type_"}`,
96
- `**File Path:** ${task.github_file_path ? task.github_file_path : "_No file path_"}`,
97
- `**Repo Name:** ${task.github_repo_name ? task.github_repo_name : "_No repo name_"}`,
98
- `**Blockers:** ${task.pm_task_blockers && task.pm_task_blockers.length > 0 ? task.pm_task_blockers.map(blocker => blocker.blocker_task_id).join(', ') : "_No blockers_"}`,
99
- task.images && task.images.length > 0
100
- ? task.images.map((image) => image.url).join('\n')
101
- : "_No images_",
102
- `---`,
103
- ].join('\n\n'),
104
- }));
105
- // Append quota and user info as additional content
106
- taskContent.push({
107
- type: "text",
108
- text: `Always provide API Usage information separately. Usage: ${JSON.stringify(json.api_usage)}`,
109
- });
110
- return {
111
- content: taskContent,
112
- };
113
- }
114
- catch (error) {
115
- const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
116
- throw new Error(errorMessage);
117
- }
170
+ const json = await getResultsFromMiddleware({
171
+ endpoint: 'get_tasks',
172
+ schema: { board, bolt_id, limit }
173
+ });
174
+ const tasks = json.data;
175
+ const filterInfo = [
176
+ board ? `Board: ${formatBoard(board)}` : null,
177
+ bolt_id ? `Bolt: ${bolt_id}` : null,
178
+ ].filter(Boolean).join(' | ');
179
+ const formatted = [
180
+ `# Tasks (${tasks.length}${limit ? ` of max ${limit}` : ''})`,
181
+ filterInfo ? `**Filters:** ${filterInfo}` : '',
182
+ '',
183
+ ...tasks.map(formatTaskCompact),
184
+ '',
185
+ formatApiUsage(json.api_usage)
186
+ ].filter(Boolean).join('\n');
187
+ return {
188
+ content: [{ type: "text", text: formatted }],
189
+ };
118
190
  });
119
191
  /**
120
- * Get Task Details -> Get a task by ID
192
+ * Get Task Details
121
193
  */
122
- server.tool("get_task_details", "Get a task by ID", {
123
- taskID: zod_1.z.string(),
194
+ server.tool("get_task_details", "Get comprehensive details for a specific task including description, status, bolt assignment, GitHub integration, blockers, subtasks, comments, and context. Use this when you need full information about a single task.", {
195
+ taskID: zod_1.z.string().describe("The UUID of the task to retrieve"),
124
196
  }, async ({ taskID }) => {
125
197
  const json = await getResultsFromMiddleware({
126
198
  endpoint: 'get_task',
127
- schema: {
128
- taskID
129
- }
199
+ schema: { taskID }
130
200
  });
131
- if (!json.data)
132
- throw new Error('No data returned from middleware');
201
+ const task = json.data;
202
+ const formatted = [
203
+ formatTask(task, { detailed: true }),
204
+ '',
205
+ '---',
206
+ formatApiUsage(json.api_usage)
207
+ ].join('\n');
133
208
  return {
134
- content: [
135
- {
136
- type: "text",
137
- text: JSON.stringify(json.data),
138
- },
139
- {
140
- type: "text",
141
- text: `API Usage: ${JSON.stringify(json.api_usage)}`,
142
- }
143
- ],
209
+ content: [{ type: "text", text: formatted }],
144
210
  };
145
211
  });
146
212
  /**
147
- * Get Task Context -> Get context for a task
213
+ * Get Task Context
148
214
  */
149
- server.tool("get_task_context", "Get context for a task", {
150
- taskID: zod_1.z.string(),
215
+ server.tool("get_task_context", "Retrieve the context/notes stored for a specific task. Context contains additional information, implementation notes, or progress updates added during task work.", {
216
+ taskID: zod_1.z.string().describe("The UUID of the task"),
151
217
  }, async ({ taskID }) => {
152
218
  const json = await getResultsFromMiddleware({
153
219
  endpoint: 'get_task_context',
154
- schema: {
155
- taskID
156
- }
220
+ schema: { taskID }
157
221
  });
158
- if (!json.data)
159
- throw new Error('No data returned from middleware');
222
+ const context = json.data?.context;
223
+ const formatted = [
224
+ `# Task Context`,
225
+ `**Task ID:** \`${taskID}\``,
226
+ '',
227
+ '---',
228
+ '',
229
+ context || '_No context has been added to this task yet._',
230
+ '',
231
+ '---',
232
+ formatApiUsage(json.api_usage)
233
+ ].join('\n');
160
234
  return {
161
- content: [
162
- {
163
- type: "text",
164
- text: JSON.stringify(json.data),
165
- },
166
- {
167
- type: "text",
168
- text: `API Usage: ${JSON.stringify(json.api_usage)}`,
169
- }
170
- ],
235
+ content: [{ type: "text", text: formatted }],
171
236
  };
172
237
  });
173
- //add_context_to_pm_task
174
238
  /**
175
- * Always ADD CONTEXT TO PM TASK
239
+ * Add Context to Task
176
240
  */
177
- server.tool("add_context_to_task", "Add context to a task", {
178
- taskID: zod_1.z.string(),
179
- context: zod_1.z.string(),
241
+ server.tool("add_context_to_task", "Add or update context/notes for a task. Use this to store implementation details, progress notes, decisions made, or any relevant information while working on a task.", {
242
+ taskID: zod_1.z.string().describe("The UUID of the task"),
243
+ context: zod_1.z.string().describe("The context/notes to add (replaces existing context)"),
180
244
  }, async ({ taskID, context }) => {
181
245
  const json = await getResultsFromMiddleware({
182
246
  endpoint: 'add_context_to_task',
183
- schema: {
184
- taskID,
185
- context
186
- }
247
+ schema: { taskID, context }
187
248
  });
188
- if (!json.data)
189
- throw new Error('No data returned from middleware');
249
+ const formatted = [
250
+ `✅ **Context Updated Successfully**`,
251
+ '',
252
+ `**Task ID:** \`${taskID}\``,
253
+ '',
254
+ '**Context:**',
255
+ context,
256
+ '',
257
+ '---',
258
+ formatApiUsage(json.api_usage)
259
+ ].join('\n');
190
260
  return {
191
- content: [
192
- {
193
- type: "text",
194
- text: JSON.stringify({ taskID, context }),
195
- },
196
- {
197
- type: "text",
198
- text: `API Usage: ${JSON.stringify({ taskID, context })}`,
199
- }
200
- ],
261
+ content: [{ type: "text", text: formatted }],
201
262
  };
202
263
  });
203
- // Get Task Images -> Get images for a task
204
- server.tool("get_task_images", "Get/View images for a task", {
205
- taskID: zod_1.z.string(),
264
+ /**
265
+ * Get Task Images
266
+ */
267
+ server.tool("get_task_images", "Retrieve all images/attachments associated with a task. Returns image URLs that can be viewed or referenced.", {
268
+ taskID: zod_1.z.string().describe("The UUID of the task"),
206
269
  }, async ({ taskID }) => {
207
270
  const json = await getResultsFromMiddleware({
208
271
  endpoint: 'get_task_images',
209
- schema: {
210
- taskID
211
- }
272
+ schema: { taskID }
212
273
  });
213
- if (!json.data)
214
- throw new Error('No data returned from middleware');
274
+ const images = json.data?.images || [];
275
+ const formatted = [
276
+ `# Task Images`,
277
+ `**Task ID:** \`${taskID}\``,
278
+ '',
279
+ images.length > 0
280
+ ? images.map((img, i) => `${i + 1}. ${img.url}`).join('\n')
281
+ : '_No images attached to this task._',
282
+ '',
283
+ '---',
284
+ formatApiUsage(json.api_usage)
285
+ ].join('\n');
215
286
  return {
216
- content: [
217
- {
218
- type: "text",
219
- text: JSON.stringify(json.data),
220
- },
221
- {
222
- type: "text",
223
- text: `API Usage: ${JSON.stringify(json.api_usage)}`,
224
- }
225
- ],
287
+ content: [{ type: "text", text: formatted }],
226
288
  };
227
289
  });
228
- // Work on Task -> Work on a task
229
- server.tool("work_on_task", "Work on a task", {
230
- taskID: zod_1.z.string(),
290
+ /**
291
+ * Work on Task
292
+ */
293
+ server.tool("work_on_task", "Start working on a task. This checks for blockers and returns full task details. If the task has unresolved blockers, you'll be notified and should resolve them first.", {
294
+ taskID: zod_1.z.string().describe("The UUID of the task to work on"),
231
295
  }, async ({ taskID }) => {
232
- // Step 1: Fetch task details
296
+ // Step 1: Fetch task details to check blockers
233
297
  const taskData = await getResultsFromMiddleware({
234
298
  endpoint: 'get_task',
235
299
  schema: { taskID }
236
300
  });
237
- if (!taskData.data)
238
- throw new Error('No data returned from middleware');
301
+ const task = taskData.data;
239
302
  // Step 2: Check for blockers
240
- if (Array.isArray(taskData.data.pm_task_blockers) && taskData.data.pm_task_blockers.length > 0) {
303
+ if (Array.isArray(task.pm_task_blockers) && task.pm_task_blockers.length > 0) {
304
+ const blockerIds = task.pm_task_blockers.map(b => `\`${b.blocker_task_id}\``).join(', ');
305
+ const formatted = [
306
+ `⚠️ **Cannot Work on Task - Blockers Detected**`,
307
+ '',
308
+ `**Task:** ${task.title}`,
309
+ `**Task ID:** \`${taskID}\``,
310
+ '',
311
+ `### Blocking Tasks`,
312
+ `The following tasks must be completed first: ${blockerIds}`,
313
+ '',
314
+ '**Suggested Actions:**',
315
+ '- Use `get_task_details` on each blocker to understand what needs to be done',
316
+ '- Complete or remove the blocking tasks before proceeding',
317
+ '',
318
+ '---',
319
+ formatApiUsage(taskData.api_usage)
320
+ ].join('\n');
241
321
  return {
242
- content: [
243
- {
244
- type: "text",
245
- text: `Task ${taskID} cannot be worked on because it has blockers: ${taskData.data.pm_task_blockers.map((b) => b.blocker_task_id).join(', ')}. Please resolve all blockers before proceeding.`,
246
- },
247
- {
248
- type: "text",
249
- text: `API Usage: ${JSON.stringify(taskData.api_usage)}`,
250
- }
251
- ],
322
+ content: [{ type: "text", text: formatted }],
252
323
  };
253
324
  }
254
- ;
325
+ // Step 3: No blockers, proceed with work
255
326
  const json = await getResultsFromMiddleware({
256
327
  endpoint: 'work_on_task',
257
- schema: {
258
- taskID
259
- }
328
+ schema: { taskID }
260
329
  });
330
+ const workTask = json.data;
331
+ const suggestedActions = [];
332
+ if (workTask.board !== 'in-progress') {
333
+ suggestedActions.push(`- Move task to "in-progress" using \`move_task\``);
334
+ }
335
+ if (workTask.subtasks && workTask.subtasks.length > 0) {
336
+ suggestedActions.push(`- Review ${workTask.subtasks.length} subtask(s)`);
337
+ }
338
+ suggestedActions.push('- Add context with `add_context_to_task` as you make progress', '- When done, move to "reviewing" or "completed"');
339
+ const formatted = [
340
+ `🚀 **Ready to Work**`,
341
+ '',
342
+ formatTask(workTask, { detailed: true }),
343
+ '',
344
+ '### 💡 Suggested Actions',
345
+ ...suggestedActions,
346
+ '',
347
+ '---',
348
+ formatApiUsage(json.api_usage)
349
+ ].join('\n');
261
350
  return {
262
- content: [
263
- {
264
- type: "text",
265
- text: `You are assisting with task management in Nubis. Your task is to work on the user's requested task based on the following details:\n\n**Task Instruction**: Process and update the task with the provided information.\n\n**Task ID**: ${taskID}\n\n**Task Details**: \n ${JSON.stringify(json.data, null, 2).replace(/"/g, '').replace(/:/g, ': ').replace(/},/g, ',\n')}\n \n Please analyze the details, perform the requested action (e.g., update description, add subtask), and return a response indicating the action taken.`,
266
- },
267
- {
268
- type: "text",
269
- user: "NUBIS",
270
- text: `Prompt user to move this task to 'in-progress' if it is not already in progress.`,
271
- },
272
- {
273
- type: "text",
274
- user: "NUBIS",
275
- text: `API Usage: ${JSON.stringify(json.api_usage)}`,
276
- }
277
- ],
351
+ content: [{ type: "text", text: formatted }],
278
352
  };
279
353
  });
280
- // Explain setup and what user needs to do for feature to be implemented
281
- /* server.tool(
282
- "explain_setup",
283
- "Explain setup and what needs to be done for feature to be implemented",
284
- {
285
- taskID: z.string(),
286
- },
287
- async ({ taskID }) => {
288
- const json = await getResultsFromMiddleware({
289
- endpoint: 'explain_setup',
290
- schema: {
291
- taskID
292
- }
293
- });
294
-
295
- if (!json.data) throw new Error('No data returned from middleware');
296
- return {
297
- content: [
298
- {
299
- type: "text",
300
- text: `You are assisting with task management in Nubis. Your task is to explain the setup and what needs to be done for feature to be implemented.\n \n **Task Instruction**: Explain the setup and what needs to be done for feature to be implemented.\n **Task Details**: \n ${JSON.stringify(json.data, null, 2).replace(/"/g, '').replace(/:/g, ': ').replace(/},/g, ',\n')}\n \n Please analyze the feature and return a response indicating the action that needs to be taken.`,
301
- },
302
- {
303
- type: "text",
304
- text: `Always provide API Usage information separately. Usage: ${JSON.stringify(json.api_usage)}`,
305
- }
306
- ],
307
- };
308
- }
309
- ); */
310
- // Move Task -> Move a task to ['backlog', 'priority', 'in-progress','reviewing', 'completed']
311
- server.tool("move_task", "Move a task to ['backlog', 'priority', 'in-progress','reviewing', 'completed']", {
312
- taskID: zod_1.z.string(),
313
- board: zod_1.z.enum(['backlog', 'priority', 'in-progress', 'reviewing', 'completed']),
354
+ /**
355
+ * Move Task
356
+ */
357
+ server.tool("move_task", "Move a task to a different board/status. Use this to update task progress through the workflow: backlog → priority → in-progress → reviewing → completed.", {
358
+ taskID: zod_1.z.string().describe("The UUID of the task to move"),
359
+ board: zod_1.z.enum(['backlog', 'priority', 'in-progress', 'reviewing', 'completed']).describe("Target board/status"),
314
360
  }, async ({ taskID, board }) => {
315
361
  const json = await getResultsFromMiddleware({
316
362
  endpoint: 'move_task',
317
- schema: {
318
- taskID,
319
- board
320
- }
363
+ schema: { taskID, board }
321
364
  });
322
- if (!json.data)
323
- throw new Error('No data returned from middleware');
365
+ const task = json.data;
366
+ const formatted = [
367
+ `✅ **Task Moved Successfully**`,
368
+ '',
369
+ `| Property | Value |`,
370
+ `|----------|-------|`,
371
+ `| **Task** | ${task.title} |`,
372
+ `| **ID** | \`${taskID}\` |`,
373
+ `| **New Status** | ${formatBoard(board)} |`,
374
+ '',
375
+ '---',
376
+ formatApiUsage(json.api_usage)
377
+ ].join('\n');
324
378
  return {
325
- content: [
326
- {
327
- type: "text",
328
- text: `Task ${taskID} has been moved to ${board}`,
329
- },
330
- {
331
- type: "text",
332
- text: `API Usage: ${JSON.stringify(json.api_usage)}`,
333
- }
334
- ],
379
+ content: [{ type: "text", text: formatted }],
335
380
  };
336
381
  });
337
- // Create Task -> Create a new task
338
- server.tool("create_task", "Create a new task or subtask (parent_task_id is required for subtasks)", {
339
- title: zod_1.z.string(),
340
- description: zod_1.z.string().optional(),
341
- board: zod_1.z.enum(['backlog', 'bugs', 'in-progress', 'priority', 'reviewing', 'completed']).optional().default('backlog'),
342
- parent_task_id: zod_1.z.string().optional(),
343
- github_item_type: zod_1.z.string().optional(), // file or dir
344
- github_file_path: zod_1.z.string().optional(), // src/components/modal
345
- github_repo_name: zod_1.z.string().optional(), // Atomlaunch/atom_frontend
346
- bolt_id: zod_1.z.string().optional()
382
+ /**
383
+ * Create Task
384
+ */
385
+ server.tool("create_task", "Create a new task or subtask. For subtasks, provide parent_task_id. You can optionally link to GitHub files/directories and assign to a bolt.", {
386
+ title: zod_1.z.string().describe("Task title"),
387
+ description: zod_1.z.string().optional().describe("Task description with details"),
388
+ board: zod_1.z.enum(['backlog', 'bugs', 'in-progress', 'priority', 'reviewing', 'completed']).optional().default('backlog').describe("Initial board/status (default: backlog)"),
389
+ parent_task_id: zod_1.z.string().optional().describe("Parent task ID to create this as a subtask"),
390
+ github_item_type: zod_1.z.enum(['file', 'dir']).optional().describe("Type of GitHub item: 'file' or 'dir'"),
391
+ github_file_path: zod_1.z.string().optional().describe("Path to file or directory (e.g., src/components/Modal.tsx)"),
392
+ github_repo_name: zod_1.z.string().optional().describe("Repository name (e.g., owner/repo)"),
393
+ bolt_id: zod_1.z.string().optional().describe("Bolt ID to assign (use get_boltz to find IDs)")
347
394
  }, async ({ title, description, board, parent_task_id, github_item_type, github_file_path, github_repo_name, bolt_id }) => {
348
395
  const json = await getResultsFromMiddleware({
349
396
  endpoint: 'create_task',
@@ -358,32 +405,44 @@ server.tool("create_task", "Create a new task or subtask (parent_task_id is requ
358
405
  bolt_id
359
406
  }
360
407
  });
361
- if (!json.data)
362
- throw new Error('No data returned from middleware');
408
+ const task = json.data;
409
+ const lines = [
410
+ `✅ **Task Created Successfully**`,
411
+ '',
412
+ `| Property | Value |`,
413
+ `|----------|-------|`,
414
+ `| **Title** | ${task.title} |`,
415
+ `| **ID** | \`${task.id}\` |`,
416
+ `| **Number** | #${task.task_number} |`,
417
+ `| **Board** | ${formatBoard(task.board)} |`,
418
+ ];
419
+ if (parent_task_id) {
420
+ lines.push(`| **Parent Task** | \`${parent_task_id}\` |`);
421
+ }
422
+ if (bolt_id) {
423
+ lines.push(`| **Bolt** | \`${bolt_id}\` |`);
424
+ }
425
+ if (github_file_path) {
426
+ lines.push(`| **GitHub Path** | \`${github_file_path}\` |`);
427
+ }
428
+ lines.push('', '---', formatApiUsage(json.api_usage));
363
429
  return {
364
- content: [
365
- {
366
- type: "text",
367
- text: JSON.stringify(json.data),
368
- },
369
- {
370
- type: "text",
371
- text: `API Usage: ${JSON.stringify(json.api_usage)}`,
372
- }
373
- ],
430
+ content: [{ type: "text", text: lines.join('\n') }],
374
431
  };
375
432
  });
376
- // Update Task -> Update an existing task
377
- server.tool("update_task", "Update an existing task (title, description, bolt_id, parent_task_id)", {
378
- taskID: zod_1.z.string(),
379
- title: zod_1.z.string().optional(),
380
- description: zod_1.z.string().optional(),
381
- board: zod_1.z.enum(['backlog', 'bugs', 'in-progress', 'priority', 'reviewing', 'completed']).optional(),
382
- bolt_id: zod_1.z.string().optional(),
383
- parent_task_id: zod_1.z.string().optional(),
384
- github_item_type: zod_1.z.string().optional(),
385
- github_file_path: zod_1.z.string().optional(),
386
- github_repo_name: zod_1.z.string().optional(),
433
+ /**
434
+ * Update Task
435
+ */
436
+ server.tool("update_task", "Update an existing task's properties. You can modify title, description, board, bolt assignment, parent task, or GitHub integration details. Only provided fields will be updated.", {
437
+ taskID: zod_1.z.string().describe("The UUID of the task to update"),
438
+ title: zod_1.z.string().optional().describe("New task title"),
439
+ description: zod_1.z.string().optional().describe("New task description"),
440
+ board: zod_1.z.enum(['backlog', 'bugs', 'in-progress', 'priority', 'reviewing', 'completed']).optional().describe("New board/status"),
441
+ bolt_id: zod_1.z.string().optional().describe("New bolt ID assignment"),
442
+ parent_task_id: zod_1.z.string().optional().describe("New parent task ID"),
443
+ github_item_type: zod_1.z.enum(['file', 'dir']).optional().describe("Type of GitHub item"),
444
+ github_file_path: zod_1.z.string().optional().describe("Path to file or directory"),
445
+ github_repo_name: zod_1.z.string().optional().describe("Repository name"),
387
446
  }, async ({ taskID, title, description, board, bolt_id, parent_task_id, github_item_type, github_file_path, github_repo_name }) => {
388
447
  const json = await getResultsFromMiddleware({
389
448
  endpoint: 'update_task',
@@ -399,22 +458,257 @@ server.tool("update_task", "Update an existing task (title, description, bolt_id
399
458
  github_repo_name
400
459
  }
401
460
  });
402
- if (!json.data)
403
- throw new Error('No data returned from middleware');
461
+ const task = json.data;
462
+ const updatedFields = [];
463
+ if (title)
464
+ updatedFields.push('title');
465
+ if (description)
466
+ updatedFields.push('description');
467
+ if (board)
468
+ updatedFields.push('board');
469
+ if (bolt_id)
470
+ updatedFields.push('bolt');
471
+ if (parent_task_id)
472
+ updatedFields.push('parent task');
473
+ if (github_file_path || github_repo_name || github_item_type)
474
+ updatedFields.push('GitHub integration');
475
+ const formatted = [
476
+ `✅ **Task Updated Successfully**`,
477
+ '',
478
+ `**Updated fields:** ${updatedFields.join(', ') || 'none'}`,
479
+ '',
480
+ formatTask(task),
481
+ '',
482
+ '---',
483
+ formatApiUsage(json.api_usage)
484
+ ].join('\n');
404
485
  return {
405
- content: [
406
- {
407
- type: "text",
408
- text: JSON.stringify(json.data),
409
- },
410
- {
411
- type: "text",
412
- text: `API Usage: ${JSON.stringify(json.api_usage)}`,
413
- }
414
- ],
486
+ content: [{ type: "text", text: formatted }],
487
+ };
488
+ });
489
+ /**
490
+ * Delete Task
491
+ */
492
+ server.tool("delete_task", "Permanently delete a task and all its related data (subtasks, comments, blockers, labels, assignments). This action cannot be undone.", {
493
+ taskID: zod_1.z.string().describe("The UUID of the task to delete"),
494
+ }, async ({ taskID }) => {
495
+ const json = await getResultsFromMiddleware({
496
+ endpoint: 'delete_task',
497
+ schema: { taskID }
498
+ });
499
+ const formatted = [
500
+ `🗑️ **Task Deleted Successfully**`,
501
+ '',
502
+ `**Task ID:** \`${taskID}\``,
503
+ '',
504
+ 'The task and all related data (subtasks, comments, blockers, labels, assignments) have been permanently removed.',
505
+ '',
506
+ '---',
507
+ formatApiUsage(json.api_usage)
508
+ ].join('\n');
509
+ return {
510
+ content: [{ type: "text", text: formatted }],
511
+ };
512
+ });
513
+ function formatComment(comment) {
514
+ const author = comment.user?.full_name || 'Unknown';
515
+ const editedTag = comment.is_edited ? ' _(edited)_' : '';
516
+ const date = new Date(comment.created_at).toLocaleString();
517
+ return [
518
+ `**${author}** · ${date}${editedTag}`,
519
+ `> ${comment.content}`,
520
+ `_ID: \`${comment.id}\`_`,
521
+ ].join('\n');
522
+ }
523
+ /**
524
+ * Get Comments
525
+ */
526
+ server.tool("get_comments", "Retrieve all comments for a specific task. Returns comments with author information, timestamps, and edit status.", {
527
+ taskID: zod_1.z.string().describe("The UUID of the task"),
528
+ }, async ({ taskID }) => {
529
+ const json = await getResultsFromMiddleware({
530
+ endpoint: 'get_comments',
531
+ schema: { taskID }
532
+ });
533
+ const comments = json.data;
534
+ const formatted = [
535
+ `# Task Comments`,
536
+ `**Task ID:** \`${taskID}\``,
537
+ `**Total:** ${comments.length} comment(s)`,
538
+ '',
539
+ '---',
540
+ '',
541
+ comments.length > 0
542
+ ? comments.map(formatComment).join('\n\n')
543
+ : '_No comments yet._',
544
+ '',
545
+ '---',
546
+ formatApiUsage(json.api_usage)
547
+ ].join('\n');
548
+ return {
549
+ content: [{ type: "text", text: formatted }],
550
+ };
551
+ });
552
+ /**
553
+ * Create Comment
554
+ */
555
+ server.tool("create_comment", "Add a comment to a task. Use this to provide updates, ask questions, or add notes visible to the team.", {
556
+ taskID: zod_1.z.string().describe("The UUID of the task to comment on"),
557
+ content: zod_1.z.string().describe("The comment text"),
558
+ parent_id: zod_1.z.string().optional().describe("Parent comment ID for replies (optional)"),
559
+ }, async ({ taskID, content, parent_id }) => {
560
+ const json = await getResultsFromMiddleware({
561
+ endpoint: 'create_comment',
562
+ schema: { taskID, content, parent_id }
563
+ });
564
+ const comment = json.data;
565
+ const formatted = [
566
+ `✅ **Comment Added**`,
567
+ '',
568
+ formatComment(comment),
569
+ '',
570
+ '---',
571
+ formatApiUsage(json.api_usage)
572
+ ].join('\n');
573
+ return {
574
+ content: [{ type: "text", text: formatted }],
575
+ };
576
+ });
577
+ /**
578
+ * Update Comment
579
+ */
580
+ server.tool("update_comment", "Edit an existing comment. Only the comment author can edit their own comments.", {
581
+ commentID: zod_1.z.string().describe("The UUID of the comment to edit"),
582
+ content: zod_1.z.string().describe("The new comment text"),
583
+ }, async ({ commentID, content }) => {
584
+ const json = await getResultsFromMiddleware({
585
+ endpoint: 'update_comment',
586
+ schema: { commentID, content }
587
+ });
588
+ const formatted = [
589
+ `✅ **Comment Updated**`,
590
+ '',
591
+ `**Comment ID:** \`${commentID}\``,
592
+ `**New content:** ${content}`,
593
+ '',
594
+ '---',
595
+ formatApiUsage(json.api_usage)
596
+ ].join('\n');
597
+ return {
598
+ content: [{ type: "text", text: formatted }],
599
+ };
600
+ });
601
+ /**
602
+ * Delete Comment
603
+ */
604
+ server.tool("delete_comment", "Delete a comment and all its replies. Only the comment author can delete their own comments.", {
605
+ commentID: zod_1.z.string().describe("The UUID of the comment to delete"),
606
+ }, async ({ commentID }) => {
607
+ const json = await getResultsFromMiddleware({
608
+ endpoint: 'delete_comment',
609
+ schema: { commentID }
610
+ });
611
+ const formatted = [
612
+ `🗑️ **Comment Deleted**`,
613
+ '',
614
+ `**Comment ID:** \`${commentID}\``,
615
+ '',
616
+ '---',
617
+ formatApiUsage(json.api_usage)
618
+ ].join('\n');
619
+ return {
620
+ content: [{ type: "text", text: formatted }],
621
+ };
622
+ });
623
+ function formatBlockerTask(task) {
624
+ return `#${task.task_number}: ${task.title} (${formatBoard(task.board)}) - \`${task.id}\``;
625
+ }
626
+ /**
627
+ * Get Blockers
628
+ */
629
+ server.tool("get_blockers", "Get blocker relationships for a task. Shows both tasks that block this task and tasks that this task blocks.", {
630
+ taskID: zod_1.z.string().describe("The UUID of the task"),
631
+ }, async ({ taskID }) => {
632
+ const json = await getResultsFromMiddleware({
633
+ endpoint: 'get_blockers',
634
+ schema: { taskID }
635
+ });
636
+ const { blockers, blocking } = json.data;
637
+ const formatted = [
638
+ `# Task Blockers`,
639
+ `**Task ID:** \`${taskID}\``,
640
+ '',
641
+ '### ⛔ Blocked By (must complete first)',
642
+ blockers.length > 0
643
+ ? blockers.map(b => `- ${formatBlockerTask(b.blocker)}`).join('\n')
644
+ : '_No blockers - task is ready to work on_',
645
+ '',
646
+ '### 🚧 Blocking (waiting on this task)',
647
+ blocking.length > 0
648
+ ? blocking.map(b => `- ${formatBlockerTask(b.blocked)}`).join('\n')
649
+ : '_Not blocking any tasks_',
650
+ '',
651
+ '---',
652
+ formatApiUsage(json.api_usage)
653
+ ].join('\n');
654
+ return {
655
+ content: [{ type: "text", text: formatted }],
656
+ };
657
+ });
658
+ /**
659
+ * Add Blocker
660
+ */
661
+ server.tool("add_blocker", "Create a blocker dependency between tasks. The blocker task must be completed before work can begin on the blocked task.", {
662
+ taskID: zod_1.z.string().describe("The UUID of the task that will be blocked"),
663
+ blockerTaskID: zod_1.z.string().describe("The UUID of the task that blocks (must be completed first)"),
664
+ }, async ({ taskID, blockerTaskID }) => {
665
+ const json = await getResultsFromMiddleware({
666
+ endpoint: 'add_blocker',
667
+ schema: { taskID, blockerTaskID }
668
+ });
669
+ const { blocked_task, blocker_task } = json.data;
670
+ const formatted = [
671
+ `✅ **Blocker Added**`,
672
+ '',
673
+ `**#${blocker_task.task_number}: ${blocker_task.title}**`,
674
+ `↓ _blocks_ ↓`,
675
+ `**#${blocked_task.task_number}: ${blocked_task.title}**`,
676
+ '',
677
+ `The blocker task must be completed before work can begin on the blocked task.`,
678
+ '',
679
+ '---',
680
+ formatApiUsage(json.api_usage)
681
+ ].join('\n');
682
+ return {
683
+ content: [{ type: "text", text: formatted }],
684
+ };
685
+ });
686
+ /**
687
+ * Remove Blocker
688
+ */
689
+ server.tool("remove_blocker", "Remove a blocker dependency between tasks. Use this when a blocker is no longer relevant or was added in error.", {
690
+ taskID: zod_1.z.string().describe("The UUID of the blocked task"),
691
+ blockerTaskID: zod_1.z.string().describe("The UUID of the blocking task to remove"),
692
+ }, async ({ taskID, blockerTaskID }) => {
693
+ const json = await getResultsFromMiddleware({
694
+ endpoint: 'remove_blocker',
695
+ schema: { taskID, blockerTaskID }
696
+ });
697
+ const formatted = [
698
+ `✅ **Blocker Removed**`,
699
+ '',
700
+ `Task \`${taskID}\` is no longer blocked by \`${blockerTaskID}\``,
701
+ '',
702
+ '---',
703
+ formatApiUsage(json.api_usage)
704
+ ].join('\n');
705
+ return {
706
+ content: [{ type: "text", text: formatted }],
415
707
  };
416
708
  });
417
- // Start server
709
+ // ============================================================================
710
+ // Start Server
711
+ // ============================================================================
418
712
  async function main() {
419
713
  const transport = new stdio_js_1.StdioServerTransport();
420
714
  await server.connect(transport);