@xferops/forge-mcp 2.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/dist/index.js ADDED
@@ -0,0 +1,739 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Forge MCP Server
4
+ *
5
+ * MCP (Model Context Protocol) server for interacting with the Forge API.
6
+ * Forge is XferOps' project management system (formerly Flower/Kanban).
7
+ *
8
+ * Works with: Claude Code, OpenAI Codex, OpenClaw/mcporter, and any MCP consumer.
9
+ *
10
+ * @see https://forge.xferops.dev/docs for API documentation
11
+ */
12
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ // Configuration
17
+ // Accepts FORGE_* (preferred) or FLOWER_* (legacy, backward compat)
18
+ // ─────────────────────────────────────────────────────────────────────────────
19
+ const FORGE_URL = process.env.FORGE_URL ||
20
+ process.env.FLOWER_URL ||
21
+ process.env.KANBAN_URL ||
22
+ "https://forge.xferops.dev";
23
+ const FORGE_TOKEN = process.env.FORGE_TOKEN ||
24
+ process.env.FLOWER_TOKEN ||
25
+ process.env.KANBAN_TOKEN ||
26
+ "";
27
+ if (!FORGE_TOKEN) {
28
+ console.error("⚠️ Warning: FORGE_TOKEN environment variable not set");
29
+ console.error(" Get your token at: https://forge.xferops.dev/settings");
30
+ }
31
+ async function apiCall(path, options = {}) {
32
+ const url = `${FORGE_URL}${path}`;
33
+ const res = await fetch(url, {
34
+ ...options,
35
+ headers: {
36
+ "Content-Type": "application/json",
37
+ Authorization: `Bearer ${FORGE_TOKEN}`,
38
+ ...options.headers,
39
+ },
40
+ });
41
+ const data = await res.json();
42
+ if (!res.ok) {
43
+ const apiError = data;
44
+ const message = apiError.details
45
+ ? apiError.details.map((d) => `${d.field}: ${d.message}`).join(", ")
46
+ : apiError.error || `HTTP ${res.status}`;
47
+ throw new Error(message);
48
+ }
49
+ return data;
50
+ }
51
+ // ─────────────────────────────────────────────────────────────────────────────
52
+ // Tool Implementations - Health
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ async function healthCheck() {
55
+ const data = await apiCall("/api/health");
56
+ return { ...data, url: FORGE_URL };
57
+ }
58
+ // ─────────────────────────────────────────────────────────────────────────────
59
+ // Tool Implementations - Teams
60
+ // ─────────────────────────────────────────────────────────────────────────────
61
+ async function listTeams() {
62
+ return apiCall("/api/teams");
63
+ }
64
+ async function createTeam(name) {
65
+ return apiCall("/api/teams", {
66
+ method: "POST",
67
+ body: JSON.stringify({ name }),
68
+ });
69
+ }
70
+ async function listTeamMembers(teamId) {
71
+ return apiCall(`/api/teams/${teamId}/members`);
72
+ }
73
+ async function addTeamMember(teamId, email, role) {
74
+ return apiCall(`/api/teams/${teamId}/members`, {
75
+ method: "POST",
76
+ body: JSON.stringify({ email, role: role || "MEMBER" }),
77
+ });
78
+ }
79
+ // ─────────────────────────────────────────────────────────────────────────────
80
+ // Tool Implementations - Projects
81
+ // ─────────────────────────────────────────────────────────────────────────────
82
+ async function listProjects(teamId) {
83
+ return apiCall(`/api/teams/${teamId}/projects`);
84
+ }
85
+ async function getProject(projectId) {
86
+ return apiCall(`/api/projects/${projectId}`);
87
+ }
88
+ async function createProject(teamId, name, prefix) {
89
+ return apiCall(`/api/teams/${teamId}/projects`, {
90
+ method: "POST",
91
+ body: JSON.stringify({ name, prefix }),
92
+ });
93
+ }
94
+ async function updateProject(projectId, updates) {
95
+ return apiCall(`/api/projects/${projectId}`, {
96
+ method: "PATCH",
97
+ body: JSON.stringify(updates),
98
+ });
99
+ }
100
+ async function deleteProject(projectId) {
101
+ await apiCall(`/api/projects/${projectId}`, { method: "DELETE" });
102
+ return { success: true };
103
+ }
104
+ // ─────────────────────────────────────────────────────────────────────────────
105
+ // Tool Implementations - Columns
106
+ // ─────────────────────────────────────────────────────────────────────────────
107
+ async function createColumn(projectId, name) {
108
+ return apiCall("/api/columns", {
109
+ method: "POST",
110
+ body: JSON.stringify({ projectId, name }),
111
+ });
112
+ }
113
+ async function updateColumn(columnId, updates) {
114
+ return apiCall(`/api/columns/${columnId}`, {
115
+ method: "PATCH",
116
+ body: JSON.stringify(updates),
117
+ });
118
+ }
119
+ async function deleteColumn(columnId, moveTasksTo) {
120
+ const url = moveTasksTo
121
+ ? `/api/columns/${columnId}?moveTasksTo=${moveTasksTo}`
122
+ : `/api/columns/${columnId}`;
123
+ await apiCall(url, { method: "DELETE" });
124
+ return { success: true };
125
+ }
126
+ async function reorderColumns(projectId, columnIds) {
127
+ await apiCall("/api/columns/reorder", {
128
+ method: "POST",
129
+ body: JSON.stringify({ projectId, columnIds }),
130
+ });
131
+ return { success: true };
132
+ }
133
+ // ─────────────────────────────────────────────────────────────────────────────
134
+ // Tool Implementations - Tasks
135
+ // ─────────────────────────────────────────────────────────────────────────────
136
+ async function getTask(taskId) {
137
+ return apiCall(`/api/tasks/${taskId}`);
138
+ }
139
+ async function listTasks(projectId) {
140
+ const project = await apiCall(`/api/projects/${projectId}`);
141
+ const tasks = [];
142
+ for (const column of project.columns || []) {
143
+ if (column.tasks) {
144
+ tasks.push(...column.tasks);
145
+ }
146
+ }
147
+ return tasks;
148
+ }
149
+ async function searchTasks(query, projectId) {
150
+ const params = new URLSearchParams({ q: query });
151
+ if (projectId)
152
+ params.append("projectId", projectId);
153
+ const result = await apiCall(`/api/tasks/search?${params}`);
154
+ return result.tasks;
155
+ }
156
+ async function createTask(params) {
157
+ return apiCall("/api/tasks", {
158
+ method: "POST",
159
+ body: JSON.stringify(params),
160
+ });
161
+ }
162
+ async function updateTask(taskId, updates) {
163
+ return apiCall(`/api/tasks/${taskId}`, {
164
+ method: "PATCH",
165
+ body: JSON.stringify(updates),
166
+ });
167
+ }
168
+ async function moveTask(taskId, columnId) {
169
+ return apiCall(`/api/tasks/${taskId}`, {
170
+ method: "PATCH",
171
+ body: JSON.stringify({ columnId }),
172
+ });
173
+ }
174
+ async function deleteTask(taskId) {
175
+ await apiCall(`/api/tasks/${taskId}`, { method: "DELETE" });
176
+ return { success: true };
177
+ }
178
+ async function reorderTask(taskId, columnId, position) {
179
+ await apiCall("/api/tasks/reorder", {
180
+ method: "POST",
181
+ body: JSON.stringify({ taskId, columnId, position }),
182
+ });
183
+ return { success: true };
184
+ }
185
+ // ─────────────────────────────────────────────────────────────────────────────
186
+ // Tool Implementations - Comments
187
+ // ─────────────────────────────────────────────────────────────────────────────
188
+ async function listComments(taskId) {
189
+ return apiCall(`/api/comments?taskId=${taskId}`);
190
+ }
191
+ async function createComment(taskId, content) {
192
+ return apiCall("/api/comments", {
193
+ method: "POST",
194
+ body: JSON.stringify({ taskId, content }),
195
+ });
196
+ }
197
+ async function updateComment(commentId, content) {
198
+ return apiCall(`/api/comments/${commentId}`, {
199
+ method: "PATCH",
200
+ body: JSON.stringify({ content }),
201
+ });
202
+ }
203
+ async function deleteComment(commentId) {
204
+ await apiCall(`/api/comments/${commentId}`, { method: "DELETE" });
205
+ return { success: true };
206
+ }
207
+ // ─────────────────────────────────────────────────────────────────────────────
208
+ // MCP Server Setup
209
+ // ─────────────────────────────────────────────────────────────────────────────
210
+ const server = new Server({
211
+ name: "forge-mcp",
212
+ version: "2.0.0",
213
+ }, {
214
+ capabilities: {
215
+ tools: {},
216
+ },
217
+ });
218
+ // ─────────────────────────────────────────────────────────────────────────────
219
+ // Tool Definitions
220
+ // ─────────────────────────────────────────────────────────────────────────────
221
+ const tools = [
222
+ // Health
223
+ {
224
+ name: "forge_health_check",
225
+ description: "Check if the Forge API is healthy and reachable. Returns the API status and configured URL.",
226
+ inputSchema: { type: "object", properties: {} },
227
+ },
228
+ // Teams
229
+ {
230
+ name: "forge_list_teams",
231
+ description: "List all teams you belong to. Returns team IDs needed for other operations.",
232
+ inputSchema: { type: "object", properties: {} },
233
+ },
234
+ {
235
+ name: "forge_create_team",
236
+ description: "Create a new team. You become the owner.",
237
+ inputSchema: {
238
+ type: "object",
239
+ properties: {
240
+ name: {
241
+ type: "string",
242
+ description: "Team name (e.g., 'Engineering', 'Product')",
243
+ },
244
+ },
245
+ required: ["name"],
246
+ },
247
+ },
248
+ {
249
+ name: "forge_list_team_members",
250
+ description: "List all members of a team with their roles. Useful for finding user IDs for task assignment.",
251
+ inputSchema: {
252
+ type: "object",
253
+ properties: {
254
+ teamId: { type: "string", description: "Team ID" },
255
+ },
256
+ required: ["teamId"],
257
+ },
258
+ },
259
+ {
260
+ name: "forge_add_team_member",
261
+ description: "Add a user to a team by their email address.",
262
+ inputSchema: {
263
+ type: "object",
264
+ properties: {
265
+ teamId: { type: "string", description: "Team ID" },
266
+ email: {
267
+ type: "string",
268
+ description: "Email address of the user to add",
269
+ },
270
+ role: {
271
+ type: "string",
272
+ enum: ["ADMIN", "MEMBER"],
273
+ description: "Role to assign (default: MEMBER)",
274
+ },
275
+ },
276
+ required: ["teamId", "email"],
277
+ },
278
+ },
279
+ // Projects
280
+ {
281
+ name: "forge_list_projects",
282
+ description: "List all projects in a team.",
283
+ inputSchema: {
284
+ type: "object",
285
+ properties: {
286
+ teamId: { type: "string", description: "Team ID" },
287
+ },
288
+ required: ["teamId"],
289
+ },
290
+ },
291
+ {
292
+ name: "forge_get_project",
293
+ description: "Get full project details including all columns and tasks. This is the main way to see the board state.",
294
+ inputSchema: {
295
+ type: "object",
296
+ properties: {
297
+ projectId: { type: "string", description: "Project ID" },
298
+ },
299
+ required: ["projectId"],
300
+ },
301
+ },
302
+ {
303
+ name: "forge_create_project",
304
+ description: "Create a new project (board) in a team.",
305
+ inputSchema: {
306
+ type: "object",
307
+ properties: {
308
+ teamId: { type: "string", description: "Team ID" },
309
+ name: {
310
+ type: "string",
311
+ description: "Project name (e.g., 'Q1 Sprint', 'Product Roadmap')",
312
+ },
313
+ prefix: {
314
+ type: "string",
315
+ description: "Optional ticket prefix (e.g., 'PROD' for PROD-123 style tickets)",
316
+ },
317
+ },
318
+ required: ["teamId", "name"],
319
+ },
320
+ },
321
+ {
322
+ name: "forge_update_project",
323
+ description: "Update project name or ticket prefix.",
324
+ inputSchema: {
325
+ type: "object",
326
+ properties: {
327
+ projectId: { type: "string", description: "Project ID" },
328
+ name: { type: "string", description: "New project name" },
329
+ prefix: { type: "string", description: "New ticket prefix" },
330
+ },
331
+ required: ["projectId"],
332
+ },
333
+ },
334
+ {
335
+ name: "forge_delete_project",
336
+ description: "Delete a project and all its columns/tasks. This cannot be undone!",
337
+ inputSchema: {
338
+ type: "object",
339
+ properties: {
340
+ projectId: { type: "string", description: "Project ID to delete" },
341
+ },
342
+ required: ["projectId"],
343
+ },
344
+ },
345
+ // Columns
346
+ {
347
+ name: "forge_create_column",
348
+ description: "Create a new column in a project board (e.g., 'Backlog', 'In Progress', 'Done').",
349
+ inputSchema: {
350
+ type: "object",
351
+ properties: {
352
+ projectId: { type: "string", description: "Project ID" },
353
+ name: { type: "string", description: "Column name" },
354
+ },
355
+ required: ["projectId", "name"],
356
+ },
357
+ },
358
+ {
359
+ name: "forge_update_column",
360
+ description: "Rename a column.",
361
+ inputSchema: {
362
+ type: "object",
363
+ properties: {
364
+ columnId: { type: "string", description: "Column ID" },
365
+ name: { type: "string", description: "New column name" },
366
+ },
367
+ required: ["columnId", "name"],
368
+ },
369
+ },
370
+ {
371
+ name: "forge_delete_column",
372
+ description: "Delete a column. If the column has tasks, you must specify where to move them.",
373
+ inputSchema: {
374
+ type: "object",
375
+ properties: {
376
+ columnId: { type: "string", description: "Column ID to delete" },
377
+ moveTasksTo: {
378
+ type: "string",
379
+ description: "Column ID to move existing tasks to (required if column has tasks)",
380
+ },
381
+ },
382
+ required: ["columnId"],
383
+ },
384
+ },
385
+ {
386
+ name: "forge_reorder_columns",
387
+ description: "Reorder columns in a project by providing the column IDs in the desired order.",
388
+ inputSchema: {
389
+ type: "object",
390
+ properties: {
391
+ projectId: { type: "string", description: "Project ID" },
392
+ columnIds: {
393
+ type: "array",
394
+ items: { type: "string" },
395
+ description: "Array of column IDs in the desired order",
396
+ },
397
+ },
398
+ required: ["projectId", "columnIds"],
399
+ },
400
+ },
401
+ // Tasks
402
+ {
403
+ name: "forge_get_task",
404
+ description: "Get a single task by ID with all its details.",
405
+ inputSchema: {
406
+ type: "object",
407
+ properties: {
408
+ taskId: { type: "string", description: "Task ID" },
409
+ },
410
+ required: ["taskId"],
411
+ },
412
+ },
413
+ {
414
+ name: "forge_list_tasks",
415
+ description: "List all tasks in a project (from all columns).",
416
+ inputSchema: {
417
+ type: "object",
418
+ properties: {
419
+ projectId: { type: "string", description: "Project ID" },
420
+ },
421
+ required: ["projectId"],
422
+ },
423
+ },
424
+ {
425
+ name: "forge_search_tasks",
426
+ description: "Search tasks by title, description, or ticket ID (e.g., '#123' or 'PROJ-123').",
427
+ inputSchema: {
428
+ type: "object",
429
+ properties: {
430
+ query: { type: "string", description: "Search query" },
431
+ projectId: {
432
+ type: "string",
433
+ description: "Optional: limit search to a specific project",
434
+ },
435
+ },
436
+ required: ["query"],
437
+ },
438
+ },
439
+ {
440
+ name: "forge_create_task",
441
+ description: "Create a new task in a project column.",
442
+ inputSchema: {
443
+ type: "object",
444
+ properties: {
445
+ projectId: { type: "string", description: "Project ID" },
446
+ columnId: {
447
+ type: "string",
448
+ description: "Column ID to place the task in",
449
+ },
450
+ title: { type: "string", description: "Task title" },
451
+ description: {
452
+ type: "string",
453
+ description: "Task description (supports markdown)",
454
+ },
455
+ type: {
456
+ type: "string",
457
+ enum: ["TASK", "BUG", "STORY"],
458
+ description: "Task type (default: TASK)",
459
+ },
460
+ priority: {
461
+ type: "string",
462
+ enum: ["LOW", "MEDIUM", "HIGH", "URGENT"],
463
+ description: "Priority level (default: MEDIUM)",
464
+ },
465
+ assigneeId: {
466
+ type: "string",
467
+ description: "User ID to assign the task to",
468
+ },
469
+ },
470
+ required: ["projectId", "columnId", "title"],
471
+ },
472
+ },
473
+ {
474
+ name: "forge_update_task",
475
+ description: "Update a task. Use columnId to move it to a different column.",
476
+ inputSchema: {
477
+ type: "object",
478
+ properties: {
479
+ taskId: { type: "string", description: "Task ID" },
480
+ title: { type: "string", description: "New title" },
481
+ description: { type: "string", description: "New description" },
482
+ columnId: { type: "string", description: "Move to this column ID" },
483
+ assigneeId: {
484
+ type: "string",
485
+ description: "Assign to this user ID (use null to unassign)",
486
+ },
487
+ priority: {
488
+ type: "string",
489
+ enum: ["LOW", "MEDIUM", "HIGH", "URGENT"],
490
+ description: "New priority",
491
+ },
492
+ type: {
493
+ type: "string",
494
+ enum: ["TASK", "BUG", "STORY"],
495
+ description: "New type",
496
+ },
497
+ prUrl: { type: "string", description: "Link to a Pull Request" },
498
+ prNumber: { type: "number", description: "PR number" },
499
+ prRepo: {
500
+ type: "string",
501
+ description: "PR repository (e.g., 'XferOps/nexus')",
502
+ },
503
+ },
504
+ required: ["taskId"],
505
+ },
506
+ },
507
+ {
508
+ name: "forge_move_task",
509
+ description: "Move a task to a different column. Convenience wrapper around forge_update_task.",
510
+ inputSchema: {
511
+ type: "object",
512
+ properties: {
513
+ taskId: { type: "string", description: "Task ID to move" },
514
+ columnId: { type: "string", description: "Target column ID" },
515
+ },
516
+ required: ["taskId", "columnId"],
517
+ },
518
+ },
519
+ {
520
+ name: "forge_delete_task",
521
+ description: "Delete a task and all its comments. This cannot be undone!",
522
+ inputSchema: {
523
+ type: "object",
524
+ properties: {
525
+ taskId: { type: "string", description: "Task ID to delete" },
526
+ },
527
+ required: ["taskId"],
528
+ },
529
+ },
530
+ {
531
+ name: "forge_reorder_task",
532
+ description: "Move a task to a specific position within a column (for drag-and-drop reordering).",
533
+ inputSchema: {
534
+ type: "object",
535
+ properties: {
536
+ taskId: { type: "string", description: "Task ID to move" },
537
+ columnId: { type: "string", description: "Target column ID" },
538
+ position: {
539
+ type: "number",
540
+ description: "Target position (0-indexed)",
541
+ },
542
+ },
543
+ required: ["taskId", "columnId", "position"],
544
+ },
545
+ },
546
+ // Comments
547
+ {
548
+ name: "forge_list_comments",
549
+ description: "List all comments on a task.",
550
+ inputSchema: {
551
+ type: "object",
552
+ properties: {
553
+ taskId: { type: "string", description: "Task ID" },
554
+ },
555
+ required: ["taskId"],
556
+ },
557
+ },
558
+ {
559
+ name: "forge_create_comment",
560
+ description: "Add a comment to a task.",
561
+ inputSchema: {
562
+ type: "object",
563
+ properties: {
564
+ taskId: { type: "string", description: "Task ID" },
565
+ content: {
566
+ type: "string",
567
+ description: "Comment content (supports markdown)",
568
+ },
569
+ },
570
+ required: ["taskId", "content"],
571
+ },
572
+ },
573
+ {
574
+ name: "forge_update_comment",
575
+ description: "Update a comment you authored.",
576
+ inputSchema: {
577
+ type: "object",
578
+ properties: {
579
+ commentId: { type: "string", description: "Comment ID" },
580
+ content: { type: "string", description: "New comment content" },
581
+ },
582
+ required: ["commentId", "content"],
583
+ },
584
+ },
585
+ {
586
+ name: "forge_delete_comment",
587
+ description: "Delete a comment you authored.",
588
+ inputSchema: {
589
+ type: "object",
590
+ properties: {
591
+ commentId: { type: "string", description: "Comment ID" },
592
+ },
593
+ required: ["commentId"],
594
+ },
595
+ },
596
+ ];
597
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
598
+ // ─────────────────────────────────────────────────────────────────────────────
599
+ // Tool Call Handler
600
+ // ─────────────────────────────────────────────────────────────────────────────
601
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
602
+ const { name, arguments: args } = request.params;
603
+ try {
604
+ let result;
605
+ switch (name) {
606
+ // Health
607
+ case "forge_health_check":
608
+ result = await healthCheck();
609
+ break;
610
+ // Teams
611
+ case "forge_list_teams":
612
+ result = await listTeams();
613
+ break;
614
+ case "forge_create_team":
615
+ result = await createTeam(args?.name);
616
+ break;
617
+ case "forge_list_team_members":
618
+ result = await listTeamMembers(args?.teamId);
619
+ break;
620
+ case "forge_add_team_member":
621
+ result = await addTeamMember(args?.teamId, args?.email, args?.role);
622
+ break;
623
+ // Projects
624
+ case "forge_list_projects":
625
+ result = await listProjects(args?.teamId);
626
+ break;
627
+ case "forge_get_project":
628
+ result = await getProject(args?.projectId);
629
+ break;
630
+ case "forge_create_project":
631
+ result = await createProject(args?.teamId, args?.name, args?.prefix);
632
+ break;
633
+ case "forge_update_project":
634
+ result = await updateProject(args?.projectId, {
635
+ name: args?.name,
636
+ prefix: args?.prefix,
637
+ });
638
+ break;
639
+ case "forge_delete_project":
640
+ result = await deleteProject(args?.projectId);
641
+ break;
642
+ // Columns
643
+ case "forge_create_column":
644
+ result = await createColumn(args?.projectId, args?.name);
645
+ break;
646
+ case "forge_update_column":
647
+ result = await updateColumn(args?.columnId, {
648
+ name: args?.name,
649
+ });
650
+ break;
651
+ case "forge_delete_column":
652
+ result = await deleteColumn(args?.columnId, args?.moveTasksTo);
653
+ break;
654
+ case "forge_reorder_columns":
655
+ result = await reorderColumns(args?.projectId, args?.columnIds);
656
+ break;
657
+ // Tasks
658
+ case "forge_get_task":
659
+ result = await getTask(args?.taskId);
660
+ break;
661
+ case "forge_list_tasks":
662
+ result = await listTasks(args?.projectId);
663
+ break;
664
+ case "forge_search_tasks":
665
+ result = await searchTasks(args?.query, args?.projectId);
666
+ break;
667
+ case "forge_create_task":
668
+ result = await createTask({
669
+ projectId: args?.projectId,
670
+ columnId: args?.columnId,
671
+ title: args?.title,
672
+ description: args?.description,
673
+ type: args?.type,
674
+ priority: args?.priority,
675
+ assigneeId: args?.assigneeId,
676
+ });
677
+ break;
678
+ case "forge_update_task":
679
+ result = await updateTask(args?.taskId, {
680
+ title: args?.title,
681
+ description: args?.description,
682
+ columnId: args?.columnId,
683
+ assigneeId: args?.assigneeId,
684
+ priority: args?.priority,
685
+ type: args?.type,
686
+ prUrl: args?.prUrl,
687
+ prNumber: args?.prNumber,
688
+ prRepo: args?.prRepo,
689
+ });
690
+ break;
691
+ case "forge_move_task":
692
+ result = await moveTask(args?.taskId, args?.columnId);
693
+ break;
694
+ case "forge_delete_task":
695
+ result = await deleteTask(args?.taskId);
696
+ break;
697
+ case "forge_reorder_task":
698
+ result = await reorderTask(args?.taskId, args?.columnId, args?.position);
699
+ break;
700
+ // Comments
701
+ case "forge_list_comments":
702
+ result = await listComments(args?.taskId);
703
+ break;
704
+ case "forge_create_comment":
705
+ result = await createComment(args?.taskId, args?.content);
706
+ break;
707
+ case "forge_update_comment":
708
+ result = await updateComment(args?.commentId, args?.content);
709
+ break;
710
+ case "forge_delete_comment":
711
+ result = await deleteComment(args?.commentId);
712
+ break;
713
+ default:
714
+ throw new Error(`Unknown tool: ${name}`);
715
+ }
716
+ return {
717
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
718
+ };
719
+ }
720
+ catch (error) {
721
+ const message = error instanceof Error ? error.message : String(error);
722
+ return {
723
+ content: [{ type: "text", text: `Error: ${message}` }],
724
+ isError: true,
725
+ };
726
+ }
727
+ });
728
+ // ─────────────────────────────────────────────────────────────────────────────
729
+ // Start Server
730
+ // ─────────────────────────────────────────────────────────────────────────────
731
+ async function main() {
732
+ const transport = new StdioServerTransport();
733
+ await server.connect(transport);
734
+ console.error("⚒️ Forge MCP server running on stdio");
735
+ }
736
+ main().catch((error) => {
737
+ console.error("Failed to start Forge MCP server:", error);
738
+ process.exit(1);
739
+ });