@delorenj/mcp-server-trello 1.7.0 → 1.8.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 +185 -138
- package/build/index.js +8594 -1011
- package/package.json +19 -6
- package/src/index.ts +1878 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,1878 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
// The SDK types its schemas against zod/v4; importing bare 'zod' yields the v3 API,
|
|
6
|
+
// which makes registerTool's inference explode (TS2589) and OOMs tsc.
|
|
7
|
+
import { z } from 'zod/v4';
|
|
8
|
+
import { TrelloClient } from './trello-client.js';
|
|
9
|
+
import { TrelloHealthEndpoints, HealthEndpointSchemas } from './health/health-endpoints.js';
|
|
10
|
+
import { formatCardListResponse } from './card-list-preview.js';
|
|
11
|
+
|
|
12
|
+
class TrelloServer {
|
|
13
|
+
private server: McpServer;
|
|
14
|
+
private trelloClient: TrelloClient;
|
|
15
|
+
private healthEndpoints: TrelloHealthEndpoints;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
const apiKey = process.env.TRELLO_API_KEY;
|
|
19
|
+
const token = process.env.TRELLO_TOKEN;
|
|
20
|
+
const defaultBoardId = process.env.TRELLO_BOARD_ID;
|
|
21
|
+
const allowedWorkspacesEnv = process.env.TRELLO_ALLOWED_WORKSPACES;
|
|
22
|
+
|
|
23
|
+
if (!apiKey || !token) {
|
|
24
|
+
throw new Error('TRELLO_API_KEY and TRELLO_TOKEN environment variables are required');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Parse allowed workspaces from comma-separated string
|
|
28
|
+
const allowedWorkspaceIds = allowedWorkspacesEnv
|
|
29
|
+
? allowedWorkspacesEnv.split(',').map(id => id.trim()).filter(id => id.length > 0)
|
|
30
|
+
: undefined;
|
|
31
|
+
|
|
32
|
+
this.trelloClient = new TrelloClient({
|
|
33
|
+
apiKey,
|
|
34
|
+
token,
|
|
35
|
+
defaultBoardId,
|
|
36
|
+
boardId: defaultBoardId,
|
|
37
|
+
allowedWorkspaceIds,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.healthEndpoints = new TrelloHealthEndpoints(this.trelloClient);
|
|
41
|
+
|
|
42
|
+
this.server = new McpServer({
|
|
43
|
+
name: 'trello-server',
|
|
44
|
+
version: '1.8.0',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.setupTools();
|
|
48
|
+
this.setupHealthEndpoints();
|
|
49
|
+
|
|
50
|
+
// Error handling
|
|
51
|
+
process.on('SIGINT', async () => {
|
|
52
|
+
await this.server.close();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private handleError(error: unknown) {
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: 'text' as const,
|
|
62
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private setupTools() {
|
|
70
|
+
// Get cards from a specific list
|
|
71
|
+
this.server.registerTool(
|
|
72
|
+
'get_cards_by_list_id',
|
|
73
|
+
{
|
|
74
|
+
title: 'Get Cards by List ID',
|
|
75
|
+
description:
|
|
76
|
+
'Fetch cards from a specific Trello list on a specific board. Descriptions are previewed by default to keep responses compact; set fields without "desc" to omit descriptions, or increase descMaxLength/omitDescThresholdBytes and use get_card for full details.',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
boardId: z
|
|
79
|
+
.string()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
82
|
+
listId: z.string().describe('ID of the Trello list'),
|
|
83
|
+
fields: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('Comma-separated list of fields to return (e.g., "name,idShort,labels,due,dueComplete"). Omit for all fields.'),
|
|
87
|
+
nameFilter: z
|
|
88
|
+
.string()
|
|
89
|
+
.trim()
|
|
90
|
+
.min(1, 'nameFilter must not be empty')
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Optional substring to filter cards by name (case-insensitive)'),
|
|
93
|
+
descMaxLength: z
|
|
94
|
+
.number()
|
|
95
|
+
.int()
|
|
96
|
+
.min(0)
|
|
97
|
+
.optional()
|
|
98
|
+
.describe(
|
|
99
|
+
'Maximum description preview length per card. Defaults to 200. Increase for fuller descriptions.'
|
|
100
|
+
),
|
|
101
|
+
omitDescThresholdBytes: z
|
|
102
|
+
.number()
|
|
103
|
+
.int()
|
|
104
|
+
.positive()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe(
|
|
107
|
+
'Approximate response size threshold before descriptions are omitted. Defaults to 50000 bytes.'
|
|
108
|
+
),
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
async ({ listId, fields, nameFilter, descMaxLength, omitDescThresholdBytes }) => {
|
|
112
|
+
try {
|
|
113
|
+
const cards = await this.trelloClient.getCardsByList(listId, fields, nameFilter);
|
|
114
|
+
return formatCardListResponse(cards, { descMaxLength, omitDescThresholdBytes });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return this.handleError(error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Get all lists from a board
|
|
122
|
+
this.server.registerTool(
|
|
123
|
+
'get_lists',
|
|
124
|
+
{
|
|
125
|
+
title: 'Get Lists',
|
|
126
|
+
description: 'Retrieve all lists from the specified board',
|
|
127
|
+
inputSchema: {
|
|
128
|
+
boardId: z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
async ({ boardId }) => {
|
|
135
|
+
try {
|
|
136
|
+
const lists = await this.trelloClient.getLists(boardId);
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: 'text' as const, text: JSON.stringify(lists, null, 2) }],
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return this.handleError(error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Get recent activity
|
|
147
|
+
this.server.registerTool(
|
|
148
|
+
'get_recent_activity',
|
|
149
|
+
{
|
|
150
|
+
title: 'Get Recent Activity',
|
|
151
|
+
description: 'Fetch recent activity on the Trello board',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
boardId: z
|
|
154
|
+
.string()
|
|
155
|
+
.optional()
|
|
156
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
157
|
+
limit: z
|
|
158
|
+
.number()
|
|
159
|
+
.optional()
|
|
160
|
+
.default(10)
|
|
161
|
+
.describe('Number of activities to fetch (default: 10)'),
|
|
162
|
+
since: z
|
|
163
|
+
.string()
|
|
164
|
+
.optional()
|
|
165
|
+
.describe('Only return actions after this date (ISO 8601) or action ID'),
|
|
166
|
+
before: z
|
|
167
|
+
.string()
|
|
168
|
+
.optional()
|
|
169
|
+
.describe('Only return actions before this date (ISO 8601) or action ID'),
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
async ({ boardId, limit, since, before }) => {
|
|
173
|
+
try {
|
|
174
|
+
const activity = await this.trelloClient.getRecentActivity(boardId, limit, since, before);
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: 'text' as const, text: JSON.stringify(activity, null, 2) }],
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return this.handleError(error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Add a new card to a list
|
|
185
|
+
this.server.registerTool(
|
|
186
|
+
'add_card_to_list',
|
|
187
|
+
{
|
|
188
|
+
title: 'Add Card to List',
|
|
189
|
+
description: 'Add a new card to a specified list on a specific board',
|
|
190
|
+
inputSchema: {
|
|
191
|
+
boardId: z
|
|
192
|
+
.string()
|
|
193
|
+
.optional()
|
|
194
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
195
|
+
listId: z.string().describe('ID of the list to add the card to'),
|
|
196
|
+
name: z.string().describe('Name of the card'),
|
|
197
|
+
description: z.string().optional().describe('Description of the card'),
|
|
198
|
+
dueDate: z.string().optional().describe('Due date for the card (ISO 8601 format)'),
|
|
199
|
+
dueReminder: z
|
|
200
|
+
.number()
|
|
201
|
+
.int()
|
|
202
|
+
.nullable()
|
|
203
|
+
.optional()
|
|
204
|
+
.describe(
|
|
205
|
+
'Due date reminder in minutes before due date (e.g., null to remove reminder, 0 at due time, 1440 one day before)'
|
|
206
|
+
),
|
|
207
|
+
start: z
|
|
208
|
+
.string()
|
|
209
|
+
.optional()
|
|
210
|
+
.describe('Start date for the card (YYYY-MM-DD format, date only)'),
|
|
211
|
+
labels: z
|
|
212
|
+
.array(z.string())
|
|
213
|
+
.optional()
|
|
214
|
+
.describe('Array of label IDs to apply to the card'),
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
async args => {
|
|
218
|
+
try {
|
|
219
|
+
const card = await this.trelloClient.addCard(args.boardId, args);
|
|
220
|
+
return {
|
|
221
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
222
|
+
};
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return this.handleError(error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Update card details
|
|
230
|
+
this.server.registerTool(
|
|
231
|
+
'update_card_details',
|
|
232
|
+
{
|
|
233
|
+
title: 'Update Card Details',
|
|
234
|
+
description: "Update an existing card's details on a specific board",
|
|
235
|
+
inputSchema: {
|
|
236
|
+
boardId: z
|
|
237
|
+
.string()
|
|
238
|
+
.optional()
|
|
239
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
240
|
+
cardId: z.string().describe('ID of the card to update'),
|
|
241
|
+
name: z.string().optional().describe('New name for the card'),
|
|
242
|
+
description: z.string().optional().describe('New description for the card'),
|
|
243
|
+
dueDate: z.string().optional().describe('New due date for the card (ISO 8601 format)'),
|
|
244
|
+
dueReminder: z
|
|
245
|
+
.number()
|
|
246
|
+
.int()
|
|
247
|
+
.nullable()
|
|
248
|
+
.optional()
|
|
249
|
+
.describe(
|
|
250
|
+
'New due date reminder in minutes before due date (e.g., null to remove reminder, 0 at due time, 1440 one day before)'
|
|
251
|
+
),
|
|
252
|
+
start: z
|
|
253
|
+
.string()
|
|
254
|
+
.optional()
|
|
255
|
+
.describe('New start date for the card (YYYY-MM-DD format, date only)'),
|
|
256
|
+
dueComplete: z
|
|
257
|
+
.boolean()
|
|
258
|
+
.optional()
|
|
259
|
+
.describe('Mark the due date as complete (true) or incomplete (false)'),
|
|
260
|
+
labels: z.array(z.string()).optional().describe('New array of label IDs for the card'),
|
|
261
|
+
pos: z
|
|
262
|
+
.union([z.string(), z.number()])
|
|
263
|
+
.optional()
|
|
264
|
+
.describe(
|
|
265
|
+
'Position of the card in the list. Accepts "top", "bottom", or a positive number'
|
|
266
|
+
),
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
async args => {
|
|
270
|
+
try {
|
|
271
|
+
const card = await this.trelloClient.updateCard(args.boardId, args);
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
274
|
+
};
|
|
275
|
+
} catch (error) {
|
|
276
|
+
return this.handleError(error);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// Archive a card
|
|
282
|
+
this.server.registerTool(
|
|
283
|
+
'archive_card',
|
|
284
|
+
{
|
|
285
|
+
title: 'Archive Card',
|
|
286
|
+
description: 'Send a card to the archive on a specific board',
|
|
287
|
+
inputSchema: {
|
|
288
|
+
boardId: z
|
|
289
|
+
.string()
|
|
290
|
+
.optional()
|
|
291
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
292
|
+
cardId: z.string().describe('ID of the card to archive'),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
async ({ boardId, cardId }) => {
|
|
296
|
+
try {
|
|
297
|
+
const card = await this.trelloClient.archiveCard(boardId, cardId);
|
|
298
|
+
return {
|
|
299
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
300
|
+
};
|
|
301
|
+
} catch (error) {
|
|
302
|
+
return this.handleError(error);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// ─── Watch Card (subscribe/unsubscribe) ──
|
|
308
|
+
this.server.registerTool(
|
|
309
|
+
'watch_card',
|
|
310
|
+
{
|
|
311
|
+
title: 'Watch Card',
|
|
312
|
+
description: 'Subscribe or unsubscribe from watching a card for activity notifications',
|
|
313
|
+
inputSchema: {
|
|
314
|
+
cardId: z.string().describe('ID of the card to watch/unwatch'),
|
|
315
|
+
subscribed: z.boolean().describe('Set to true to start watching, false to stop'),
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
async ({ cardId, subscribed }) => {
|
|
319
|
+
try {
|
|
320
|
+
const card = await this.trelloClient.watchCard(cardId, subscribed);
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
323
|
+
};
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return this.handleError(error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// ─── Watch List (subscribe/unsubscribe) ──
|
|
331
|
+
this.server.registerTool(
|
|
332
|
+
'watch_list',
|
|
333
|
+
{
|
|
334
|
+
title: 'Watch List',
|
|
335
|
+
description: 'Subscribe or unsubscribe from watching a list for activity notifications',
|
|
336
|
+
inputSchema: {
|
|
337
|
+
listId: z.string().describe('ID of the list to watch/unwatch'),
|
|
338
|
+
subscribed: z.boolean().describe('Set to true to start watching, false to stop'),
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
async ({ listId, subscribed }) => {
|
|
342
|
+
try {
|
|
343
|
+
const list = await this.trelloClient.watchList(listId, subscribed);
|
|
344
|
+
return {
|
|
345
|
+
content: [{ type: 'text' as const, text: JSON.stringify(list, null, 2) }],
|
|
346
|
+
};
|
|
347
|
+
} catch (error) {
|
|
348
|
+
return this.handleError(error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Move a card
|
|
354
|
+
this.server.registerTool(
|
|
355
|
+
'move_card',
|
|
356
|
+
{
|
|
357
|
+
title: 'Move Card',
|
|
358
|
+
description: 'Move a card to a different list, potentially on a different board',
|
|
359
|
+
inputSchema: {
|
|
360
|
+
boardId: z
|
|
361
|
+
.string()
|
|
362
|
+
.optional()
|
|
363
|
+
.describe(
|
|
364
|
+
'ID of the target Trello board (where the listId resides, uses default if not provided)'
|
|
365
|
+
),
|
|
366
|
+
cardId: z.string().describe('ID of the card to move'),
|
|
367
|
+
listId: z.string().describe('ID of the target list'),
|
|
368
|
+
pos: z
|
|
369
|
+
.union([z.string(), z.number()])
|
|
370
|
+
.optional()
|
|
371
|
+
.describe(
|
|
372
|
+
'Position of the card in the target list. Accepts "top", "bottom", or a positive number'
|
|
373
|
+
),
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
async ({ boardId, cardId, listId, pos }) => {
|
|
377
|
+
try {
|
|
378
|
+
const card = await this.trelloClient.moveCard(boardId, cardId, listId, pos);
|
|
379
|
+
return {
|
|
380
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
381
|
+
};
|
|
382
|
+
} catch (error) {
|
|
383
|
+
return this.handleError(error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// Add a new list to a board
|
|
389
|
+
this.server.registerTool(
|
|
390
|
+
'add_list_to_board',
|
|
391
|
+
{
|
|
392
|
+
title: 'Add List to Board',
|
|
393
|
+
description: 'Add a new list to the specified board',
|
|
394
|
+
inputSchema: {
|
|
395
|
+
boardId: z
|
|
396
|
+
.string()
|
|
397
|
+
.optional()
|
|
398
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
399
|
+
name: z.string().describe('Name of the new list'),
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
async ({ boardId, name }) => {
|
|
403
|
+
try {
|
|
404
|
+
const list = await this.trelloClient.addList(boardId, name);
|
|
405
|
+
return {
|
|
406
|
+
content: [{ type: 'text' as const, text: JSON.stringify(list, null, 2) }],
|
|
407
|
+
};
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return this.handleError(error);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
// Archive a list
|
|
415
|
+
this.server.registerTool(
|
|
416
|
+
'archive_list',
|
|
417
|
+
{
|
|
418
|
+
title: 'Archive List',
|
|
419
|
+
description: 'Send a list to the archive on a specific board',
|
|
420
|
+
inputSchema: {
|
|
421
|
+
boardId: z
|
|
422
|
+
.string()
|
|
423
|
+
.optional()
|
|
424
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
425
|
+
listId: z.string().describe('ID of the list to archive'),
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
async ({ boardId, listId }) => {
|
|
429
|
+
try {
|
|
430
|
+
const list = await this.trelloClient.archiveList(boardId, listId);
|
|
431
|
+
return {
|
|
432
|
+
content: [{ type: 'text' as const, text: JSON.stringify(list, null, 2) }],
|
|
433
|
+
};
|
|
434
|
+
} catch (error) {
|
|
435
|
+
return this.handleError(error);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// Update a list
|
|
441
|
+
this.server.registerTool(
|
|
442
|
+
'update_list',
|
|
443
|
+
{
|
|
444
|
+
title: 'Update List',
|
|
445
|
+
description:
|
|
446
|
+
'Update a list name, archive state, subscription state, or board. Use update_list_position for moving a list within a board.',
|
|
447
|
+
inputSchema: {
|
|
448
|
+
listId: z.string().describe('ID of the Trello list to update'),
|
|
449
|
+
name: z.string().optional().describe('New name for the list'),
|
|
450
|
+
closed: z.boolean().optional().describe('Whether to close (archive) the list'),
|
|
451
|
+
subscribed: z
|
|
452
|
+
.boolean()
|
|
453
|
+
.optional()
|
|
454
|
+
.describe('Whether the authenticated user is subscribed to the list'),
|
|
455
|
+
idBoard: z.string().optional().describe('ID of a board to move the list to'),
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
async ({ listId, name, closed, subscribed, idBoard }) => {
|
|
459
|
+
try {
|
|
460
|
+
const params: {
|
|
461
|
+
name?: string;
|
|
462
|
+
closed?: boolean;
|
|
463
|
+
subscribed?: boolean;
|
|
464
|
+
idBoard?: string;
|
|
465
|
+
} = {};
|
|
466
|
+
if (name !== undefined) params.name = name;
|
|
467
|
+
if (closed !== undefined) params.closed = closed;
|
|
468
|
+
if (subscribed !== undefined) params.subscribed = subscribed;
|
|
469
|
+
if (idBoard !== undefined) params.idBoard = idBoard;
|
|
470
|
+
if (Object.keys(params).length === 0) {
|
|
471
|
+
throw new McpError(
|
|
472
|
+
ErrorCode.InvalidParams,
|
|
473
|
+
'At least one of name, closed, subscribed, or idBoard must be provided'
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
const list = await this.trelloClient.updateList(listId, params);
|
|
477
|
+
return {
|
|
478
|
+
content: [{ type: 'text' as const, text: JSON.stringify(list, null, 2) }],
|
|
479
|
+
};
|
|
480
|
+
} catch (error) {
|
|
481
|
+
return this.handleError(error);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
// Update list position
|
|
487
|
+
this.server.registerTool(
|
|
488
|
+
'update_list_position',
|
|
489
|
+
{
|
|
490
|
+
title: 'Update List Position',
|
|
491
|
+
description:
|
|
492
|
+
'Update the position of a list on the board. Trello uses fractional indexing: each list has a float position, and to place a list between two others, use the average of their positions (e.g., between pos 1024 and 2048, use 1536). Use "top"/"bottom" shortcuts to move to the edges.',
|
|
493
|
+
inputSchema: {
|
|
494
|
+
listId: z.string().describe('ID of the list to reposition'),
|
|
495
|
+
position: z
|
|
496
|
+
.string()
|
|
497
|
+
.refine(
|
|
498
|
+
(val) => {
|
|
499
|
+
if (val === 'top' || val === 'bottom') return true;
|
|
500
|
+
const num = Number(val);
|
|
501
|
+
return num > 0 && isFinite(num);
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
message: "Position must be 'top', 'bottom', or a positive finite numeric string.",
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
.describe(
|
|
508
|
+
'New position: "top" (move to leftmost), "bottom" (move to rightmost), or a numeric string (e.g. "1536"). To place between two lists, use the average of their pos values.'
|
|
509
|
+
),
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
async ({ listId, position }) => {
|
|
513
|
+
try {
|
|
514
|
+
const parsedPosition =
|
|
515
|
+
position === 'top' || position === 'bottom' ? position : Number(position);
|
|
516
|
+
const list = await this.trelloClient.updateListPosition(listId, parsedPosition);
|
|
517
|
+
return {
|
|
518
|
+
content: [{ type: 'text' as const, text: JSON.stringify(list, null, 2) }],
|
|
519
|
+
};
|
|
520
|
+
} catch (error) {
|
|
521
|
+
return this.handleError(error);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// Get cards assigned to current user
|
|
527
|
+
this.server.registerTool(
|
|
528
|
+
'get_my_cards',
|
|
529
|
+
{
|
|
530
|
+
title: 'Get My Cards',
|
|
531
|
+
description: 'Fetch all cards assigned to the current user',
|
|
532
|
+
inputSchema: {},
|
|
533
|
+
},
|
|
534
|
+
async () => {
|
|
535
|
+
try {
|
|
536
|
+
const cards = await this.trelloClient.getMyCards();
|
|
537
|
+
return {
|
|
538
|
+
content: [{ type: 'text' as const, text: JSON.stringify(cards, null, 2) }],
|
|
539
|
+
};
|
|
540
|
+
} catch (error) {
|
|
541
|
+
return this.handleError(error);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
// Attach image to card (kept for backward compatibility)
|
|
547
|
+
this.server.registerTool(
|
|
548
|
+
'attach_image_to_card',
|
|
549
|
+
{
|
|
550
|
+
title: 'Attach Image to Card',
|
|
551
|
+
description: 'Attach an image to a card from a URL on a specific board',
|
|
552
|
+
inputSchema: {
|
|
553
|
+
boardId: z
|
|
554
|
+
.string()
|
|
555
|
+
.optional()
|
|
556
|
+
.describe(
|
|
557
|
+
'ID of the Trello board where the card exists (uses default if not provided)'
|
|
558
|
+
),
|
|
559
|
+
cardId: z.string().describe('ID of the card to attach the image to'),
|
|
560
|
+
imageUrl: z.string().describe('URL of the image to attach'),
|
|
561
|
+
name: z
|
|
562
|
+
.string()
|
|
563
|
+
.optional()
|
|
564
|
+
.default('Image Attachment')
|
|
565
|
+
.describe('Optional name for the attachment (defaults to "Image Attachment")'),
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
async ({ boardId, cardId, imageUrl, name }) => {
|
|
569
|
+
try {
|
|
570
|
+
const attachment = await this.trelloClient.attachImageToCard(
|
|
571
|
+
boardId,
|
|
572
|
+
cardId,
|
|
573
|
+
imageUrl,
|
|
574
|
+
name
|
|
575
|
+
);
|
|
576
|
+
return {
|
|
577
|
+
content: [{ type: 'text' as const, text: JSON.stringify(attachment, null, 2) }],
|
|
578
|
+
};
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return this.handleError(error);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
// Attach file to card (generic file attachment)
|
|
586
|
+
this.server.registerTool(
|
|
587
|
+
'attach_file_to_card',
|
|
588
|
+
{
|
|
589
|
+
title: 'Attach File to Card',
|
|
590
|
+
description: 'Attach any file to a card from a URL on a specific board',
|
|
591
|
+
inputSchema: {
|
|
592
|
+
boardId: z
|
|
593
|
+
.string()
|
|
594
|
+
.optional()
|
|
595
|
+
.describe(
|
|
596
|
+
'ID of the Trello board where the card exists (uses default if not provided)'
|
|
597
|
+
),
|
|
598
|
+
cardId: z.string().describe('ID of the card to attach the file to'),
|
|
599
|
+
fileUrl: z.string().describe('URL of the file to attach'),
|
|
600
|
+
name: z
|
|
601
|
+
.string()
|
|
602
|
+
.optional()
|
|
603
|
+
.default('File Attachment')
|
|
604
|
+
.describe('Optional name for the attachment (defaults to "File Attachment")'),
|
|
605
|
+
mimeType: z
|
|
606
|
+
.string()
|
|
607
|
+
.optional()
|
|
608
|
+
.describe(
|
|
609
|
+
'Optional MIME type of the file (e.g., "application/pdf", "text/plain", "video/mp4")'
|
|
610
|
+
),
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
async ({ boardId, cardId, fileUrl, name, mimeType }) => {
|
|
614
|
+
try {
|
|
615
|
+
const attachment = await this.trelloClient.attachFileToCard(
|
|
616
|
+
boardId,
|
|
617
|
+
cardId,
|
|
618
|
+
fileUrl,
|
|
619
|
+
name,
|
|
620
|
+
mimeType
|
|
621
|
+
);
|
|
622
|
+
return {
|
|
623
|
+
content: [{ type: 'text' as const, text: JSON.stringify(attachment, null, 2) }],
|
|
624
|
+
};
|
|
625
|
+
} catch (error) {
|
|
626
|
+
return {
|
|
627
|
+
content: [
|
|
628
|
+
{
|
|
629
|
+
type: 'text' as const,
|
|
630
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}`,
|
|
631
|
+
},
|
|
632
|
+
],
|
|
633
|
+
isError: true,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
// Attach arbitrary binary data to a card (base64 or data URL)
|
|
640
|
+
this.server.registerTool(
|
|
641
|
+
'attach_data_to_card',
|
|
642
|
+
{
|
|
643
|
+
title: 'Attach Data to Card',
|
|
644
|
+
description:
|
|
645
|
+
'Attach binary data (image, markdown, PDF, text, etc.) to a card from base64-encoded data or a data URL. Use this for any non-image content. For image/screenshot uploads with PNG defaults, see attach_image_data_to_card.',
|
|
646
|
+
inputSchema: {
|
|
647
|
+
boardId: z
|
|
648
|
+
.string()
|
|
649
|
+
.optional()
|
|
650
|
+
.describe(
|
|
651
|
+
'ID of the Trello board where the card exists (uses default if not provided)'
|
|
652
|
+
),
|
|
653
|
+
cardId: z.string().describe('ID of the card to attach the data to'),
|
|
654
|
+
data: z
|
|
655
|
+
.string()
|
|
656
|
+
.describe(
|
|
657
|
+
'Base64-encoded data or a data URL (e.g. data:text/markdown;base64,...). Any content type, not just images.'
|
|
658
|
+
),
|
|
659
|
+
name: z
|
|
660
|
+
.string()
|
|
661
|
+
.optional()
|
|
662
|
+
.describe(
|
|
663
|
+
'Filename for the attachment, including extension (e.g. "notes.md", "report.pdf"). Defaults to "attachment-<timestamp>".'
|
|
664
|
+
),
|
|
665
|
+
mimeType: z
|
|
666
|
+
.string()
|
|
667
|
+
.optional()
|
|
668
|
+
.describe(
|
|
669
|
+
'MIME type of the data (e.g. "text/markdown", "application/pdf", "image/png"). Recommended for correct rendering in Trello. If omitted, inferred from a data URL prefix or the filename extension; falls back to "application/octet-stream".'
|
|
670
|
+
),
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
async ({ boardId, cardId, data, name, mimeType }) => {
|
|
674
|
+
try {
|
|
675
|
+
const attachment = await this.trelloClient.attachDataToCard(
|
|
676
|
+
boardId,
|
|
677
|
+
cardId,
|
|
678
|
+
data,
|
|
679
|
+
name,
|
|
680
|
+
mimeType
|
|
681
|
+
);
|
|
682
|
+
return {
|
|
683
|
+
content: [{ type: 'text' as const, text: JSON.stringify(attachment, null, 2) }],
|
|
684
|
+
};
|
|
685
|
+
} catch (error) {
|
|
686
|
+
return this.handleError(error);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
// Attach image data to card (image-flavored convenience over attach_data_to_card)
|
|
692
|
+
this.server.registerTool(
|
|
693
|
+
'attach_image_data_to_card',
|
|
694
|
+
{
|
|
695
|
+
title: 'Attach Image Data to Card',
|
|
696
|
+
description:
|
|
697
|
+
'Attach an image to a card from base64 data or a data URL. Image-flavored convenience over attach_data_to_card: defaults assume PNG when mimeType/name are omitted, suitable for screenshot pasting. For non-image content, use attach_data_to_card.',
|
|
698
|
+
inputSchema: {
|
|
699
|
+
boardId: z
|
|
700
|
+
.string()
|
|
701
|
+
.optional()
|
|
702
|
+
.describe(
|
|
703
|
+
'ID of the Trello board where the card exists (uses default if not provided)'
|
|
704
|
+
),
|
|
705
|
+
cardId: z.string().describe('ID of the card to attach the image to'),
|
|
706
|
+
imageData: z.string().describe('Base64 encoded image data or data URL (e.g., data:image/png;base64,...)'),
|
|
707
|
+
name: z
|
|
708
|
+
.string()
|
|
709
|
+
.optional()
|
|
710
|
+
.describe('Optional name for the attachment'),
|
|
711
|
+
mimeType: z
|
|
712
|
+
.string()
|
|
713
|
+
.optional()
|
|
714
|
+
.default('image/png')
|
|
715
|
+
.describe('Optional MIME type (default: image/png)'),
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
async ({ boardId, cardId, imageData, name, mimeType }) => {
|
|
719
|
+
try {
|
|
720
|
+
const attachment = await this.trelloClient.attachImageDataToCard(
|
|
721
|
+
boardId,
|
|
722
|
+
cardId,
|
|
723
|
+
imageData,
|
|
724
|
+
name,
|
|
725
|
+
mimeType
|
|
726
|
+
);
|
|
727
|
+
return {
|
|
728
|
+
content: [{ type: 'text' as const, text: JSON.stringify(attachment, null, 2) }],
|
|
729
|
+
};
|
|
730
|
+
} catch (error) {
|
|
731
|
+
return this.handleError(error);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
// List all boards
|
|
737
|
+
this.server.registerTool(
|
|
738
|
+
'list_boards',
|
|
739
|
+
{
|
|
740
|
+
title: 'List Boards',
|
|
741
|
+
description: 'List all boards the user has access to',
|
|
742
|
+
inputSchema: {},
|
|
743
|
+
},
|
|
744
|
+
async () => {
|
|
745
|
+
try {
|
|
746
|
+
const boards = await this.trelloClient.listBoards();
|
|
747
|
+
return {
|
|
748
|
+
content: [{ type: 'text' as const, text: JSON.stringify(boards, null, 2) }],
|
|
749
|
+
};
|
|
750
|
+
} catch (error) {
|
|
751
|
+
return this.handleError(error);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
// Set active board
|
|
757
|
+
this.server.registerTool(
|
|
758
|
+
'set_active_board',
|
|
759
|
+
{
|
|
760
|
+
title: 'Set Active Board',
|
|
761
|
+
description: 'Set the active board for future operations',
|
|
762
|
+
inputSchema: {
|
|
763
|
+
boardId: z.string().describe('ID of the board to set as active'),
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
async ({ boardId }) => {
|
|
767
|
+
try {
|
|
768
|
+
const board = await this.trelloClient.setActiveBoard(boardId);
|
|
769
|
+
return {
|
|
770
|
+
content: [
|
|
771
|
+
{
|
|
772
|
+
type: 'text' as const,
|
|
773
|
+
text: `Successfully set active board to "${board.name}" (${board.id})`,
|
|
774
|
+
},
|
|
775
|
+
],
|
|
776
|
+
};
|
|
777
|
+
} catch (error) {
|
|
778
|
+
return this.handleError(error);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
// List workspaces
|
|
784
|
+
this.server.registerTool(
|
|
785
|
+
'list_workspaces',
|
|
786
|
+
{
|
|
787
|
+
title: 'List Workspaces',
|
|
788
|
+
description:
|
|
789
|
+
'List workspaces the user has access to. If TRELLO_ALLOWED_WORKSPACES is configured, only allowed workspaces are returned.',
|
|
790
|
+
inputSchema: {},
|
|
791
|
+
},
|
|
792
|
+
async () => {
|
|
793
|
+
try {
|
|
794
|
+
const workspaces = await this.trelloClient.listWorkspaces();
|
|
795
|
+
return {
|
|
796
|
+
content: [{ type: 'text' as const, text: JSON.stringify(workspaces, null, 2) }],
|
|
797
|
+
};
|
|
798
|
+
} catch (error) {
|
|
799
|
+
return this.handleError(error);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
// Create a new board
|
|
805
|
+
this.server.registerTool(
|
|
806
|
+
'create_board',
|
|
807
|
+
{
|
|
808
|
+
title: 'Create Board',
|
|
809
|
+
description: 'Create a new Trello board optionally within a workspace',
|
|
810
|
+
inputSchema: {
|
|
811
|
+
name: z.string().describe('Name of the board'),
|
|
812
|
+
desc: z.string().optional().describe('Description of the board'),
|
|
813
|
+
idOrganization: z
|
|
814
|
+
.string()
|
|
815
|
+
.min(1)
|
|
816
|
+
.optional()
|
|
817
|
+
.describe('Workspace ID to create the board in (uses active if not provided)'),
|
|
818
|
+
defaultLabels: z
|
|
819
|
+
.boolean()
|
|
820
|
+
.optional()
|
|
821
|
+
.default(true)
|
|
822
|
+
.describe('Create default labels (true by default)'),
|
|
823
|
+
defaultLists: z
|
|
824
|
+
.boolean()
|
|
825
|
+
.optional()
|
|
826
|
+
.default(true)
|
|
827
|
+
.describe('Create default lists (true by default)'),
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
async ({ name, desc, idOrganization, defaultLabels, defaultLists }) => {
|
|
831
|
+
try {
|
|
832
|
+
const board = await this.trelloClient.createBoard({
|
|
833
|
+
name,
|
|
834
|
+
desc,
|
|
835
|
+
idOrganization,
|
|
836
|
+
defaultLabels,
|
|
837
|
+
defaultLists,
|
|
838
|
+
});
|
|
839
|
+
return {
|
|
840
|
+
content: [{ type: 'text' as const, text: JSON.stringify(board, null, 2) }],
|
|
841
|
+
};
|
|
842
|
+
} catch (error) {
|
|
843
|
+
return this.handleError(error);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
// Set active workspace
|
|
849
|
+
this.server.registerTool(
|
|
850
|
+
'set_active_workspace',
|
|
851
|
+
{
|
|
852
|
+
title: 'Set Active Workspace',
|
|
853
|
+
description: 'Set the active workspace for future operations',
|
|
854
|
+
inputSchema: {
|
|
855
|
+
workspaceId: z.string().describe('ID of the workspace to set as active'),
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
async ({ workspaceId }) => {
|
|
859
|
+
try {
|
|
860
|
+
const workspace = await this.trelloClient.setActiveWorkspace(workspaceId);
|
|
861
|
+
return {
|
|
862
|
+
content: [
|
|
863
|
+
{
|
|
864
|
+
type: 'text' as const,
|
|
865
|
+
text: `Successfully set active workspace to "${workspace.displayName}" (${workspace.id})`,
|
|
866
|
+
},
|
|
867
|
+
],
|
|
868
|
+
};
|
|
869
|
+
} catch (error) {
|
|
870
|
+
return this.handleError(error);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
// List boards in workspace
|
|
876
|
+
this.server.registerTool(
|
|
877
|
+
'list_boards_in_workspace',
|
|
878
|
+
{
|
|
879
|
+
title: 'List Boards in Workspace',
|
|
880
|
+
description: 'List all boards in a specific workspace',
|
|
881
|
+
inputSchema: {
|
|
882
|
+
workspaceId: z.string().describe('ID of the workspace to list boards from'),
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
async ({ workspaceId }) => {
|
|
886
|
+
try {
|
|
887
|
+
const boards = await this.trelloClient.listBoardsInWorkspace(workspaceId);
|
|
888
|
+
return {
|
|
889
|
+
content: [{ type: 'text' as const, text: JSON.stringify(boards, null, 2) }],
|
|
890
|
+
};
|
|
891
|
+
} catch (error) {
|
|
892
|
+
return this.handleError(error);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
// Get active board info
|
|
898
|
+
this.server.registerTool(
|
|
899
|
+
'get_active_board_info',
|
|
900
|
+
{
|
|
901
|
+
title: 'Get Active Board Info',
|
|
902
|
+
description: 'Get information about the currently active board',
|
|
903
|
+
inputSchema: {},
|
|
904
|
+
},
|
|
905
|
+
async () => {
|
|
906
|
+
try {
|
|
907
|
+
const boardId = this.trelloClient.activeBoardId;
|
|
908
|
+
if (!boardId) {
|
|
909
|
+
return {
|
|
910
|
+
content: [{ type: 'text' as const, text: 'No active board set' }],
|
|
911
|
+
isError: true,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
const board = await this.trelloClient.getBoardById(boardId);
|
|
915
|
+
return {
|
|
916
|
+
content: [
|
|
917
|
+
{
|
|
918
|
+
type: 'text' as const,
|
|
919
|
+
text: JSON.stringify(
|
|
920
|
+
{
|
|
921
|
+
...board,
|
|
922
|
+
isActive: true,
|
|
923
|
+
activeWorkspaceId: this.trelloClient.activeWorkspaceId || 'Not set',
|
|
924
|
+
},
|
|
925
|
+
null,
|
|
926
|
+
2
|
|
927
|
+
),
|
|
928
|
+
},
|
|
929
|
+
],
|
|
930
|
+
};
|
|
931
|
+
} catch (error) {
|
|
932
|
+
return this.handleError(error);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
// Get card details
|
|
938
|
+
this.server.registerTool(
|
|
939
|
+
'get_card',
|
|
940
|
+
{
|
|
941
|
+
title: 'Get Card',
|
|
942
|
+
description: 'Get detailed information about a specific Trello card',
|
|
943
|
+
inputSchema: {
|
|
944
|
+
cardId: z.string().describe('ID of the card to fetch'),
|
|
945
|
+
includeMarkdown: z
|
|
946
|
+
.boolean()
|
|
947
|
+
.optional()
|
|
948
|
+
.default(false)
|
|
949
|
+
.describe('Whether to return card description in markdown format (default: false)'),
|
|
950
|
+
},
|
|
951
|
+
},
|
|
952
|
+
async ({ cardId, includeMarkdown }) => {
|
|
953
|
+
try {
|
|
954
|
+
const card = await this.trelloClient.getCard(cardId, includeMarkdown);
|
|
955
|
+
return {
|
|
956
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
957
|
+
};
|
|
958
|
+
} catch (error) {
|
|
959
|
+
return this.handleError(error);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
// Add a comment to a card
|
|
965
|
+
this.server.registerTool(
|
|
966
|
+
'add_comment',
|
|
967
|
+
{
|
|
968
|
+
title: 'Add Comment to Card',
|
|
969
|
+
description: 'Add the given text as a new comment to the given card',
|
|
970
|
+
inputSchema: {
|
|
971
|
+
cardId: z.string().describe('ID of the card to comment on'),
|
|
972
|
+
text: z.string().describe('The text of the comment to add'),
|
|
973
|
+
},
|
|
974
|
+
},
|
|
975
|
+
async ({ cardId, text }) => {
|
|
976
|
+
try {
|
|
977
|
+
const comment = await this.trelloClient.addCommentToCard(cardId, text);
|
|
978
|
+
return {
|
|
979
|
+
content: [{ type: 'text' as const, text: JSON.stringify(comment, null, 2) }],
|
|
980
|
+
};
|
|
981
|
+
} catch (error) {
|
|
982
|
+
return this.handleError(error);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
// Update a comment to a card
|
|
988
|
+
this.server.registerTool(
|
|
989
|
+
'update_comment',
|
|
990
|
+
{
|
|
991
|
+
title: 'Update Comment on Card',
|
|
992
|
+
description: 'Update the given comment with the new text',
|
|
993
|
+
inputSchema: {
|
|
994
|
+
commentId: z.string().describe('ID of the comment to change'),
|
|
995
|
+
text: z.string().describe('The new text of the comment'),
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
async ({ commentId, text }) => {
|
|
999
|
+
try {
|
|
1000
|
+
const success = await this.trelloClient.updateCommentOnCard(commentId, text);
|
|
1001
|
+
return {
|
|
1002
|
+
content: [{ type: 'text' as const, text: success ? 'success' : 'failure' }],
|
|
1003
|
+
};
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
return this.handleError(error);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
// Delete a comment from a card
|
|
1011
|
+
this.server.registerTool(
|
|
1012
|
+
'delete_comment',
|
|
1013
|
+
{
|
|
1014
|
+
title: 'Delete Comment from Card',
|
|
1015
|
+
description: 'Delete a comment from a Trello card',
|
|
1016
|
+
inputSchema: {
|
|
1017
|
+
commentId: z.string().describe('ID of the comment to delete'),
|
|
1018
|
+
},
|
|
1019
|
+
},
|
|
1020
|
+
async ({ commentId }) => {
|
|
1021
|
+
try {
|
|
1022
|
+
const success = await this.trelloClient.deleteCommentFromCard(commentId);
|
|
1023
|
+
return {
|
|
1024
|
+
content: [{ type: 'text' as const, text: success ? 'success' : 'failure' }],
|
|
1025
|
+
};
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
return this.handleError(error);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
// Get comments from a card
|
|
1033
|
+
this.server.registerTool(
|
|
1034
|
+
'get_card_comments',
|
|
1035
|
+
{
|
|
1036
|
+
title: 'Get Card Comments',
|
|
1037
|
+
description: 'Retrieve all comments from a specific Trello card',
|
|
1038
|
+
inputSchema: {
|
|
1039
|
+
cardId: z.string().describe('ID of the card to get comments from'),
|
|
1040
|
+
limit: z
|
|
1041
|
+
.number()
|
|
1042
|
+
.optional()
|
|
1043
|
+
.default(100)
|
|
1044
|
+
.describe('Maximum number of comments to retrieve (default: 100)'),
|
|
1045
|
+
},
|
|
1046
|
+
},
|
|
1047
|
+
async ({ cardId, limit }) => {
|
|
1048
|
+
try {
|
|
1049
|
+
const comments = await this.trelloClient.getCardComments(cardId, limit);
|
|
1050
|
+
return {
|
|
1051
|
+
content: [{ type: 'text' as const, text: JSON.stringify(comments, null, 2) }],
|
|
1052
|
+
};
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
return this.handleError(error);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
// Checklist tools
|
|
1060
|
+
this.server.registerTool(
|
|
1061
|
+
'create_checklist',
|
|
1062
|
+
{
|
|
1063
|
+
title: 'Create Checklist',
|
|
1064
|
+
description: 'Create a new checklist',
|
|
1065
|
+
inputSchema: {
|
|
1066
|
+
name: z.string().describe('Name of the checklist to create'),
|
|
1067
|
+
cardId: z.string().describe('ID of the Trello card'),
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
1070
|
+
async ({ name, cardId }) => {
|
|
1071
|
+
try {
|
|
1072
|
+
const items = await this.trelloClient.createChecklist(name, cardId);
|
|
1073
|
+
return {
|
|
1074
|
+
content: [{ type: 'text' as const, text: JSON.stringify(items, null, 2) }],
|
|
1075
|
+
};
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
return this.handleError(error);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
// Checklist tools
|
|
1083
|
+
this.server.registerTool(
|
|
1084
|
+
'get_checklist_items',
|
|
1085
|
+
{
|
|
1086
|
+
title: 'Get Checklist Items',
|
|
1087
|
+
description: 'Get all items from a checklist by name',
|
|
1088
|
+
inputSchema: {
|
|
1089
|
+
name: z.string().describe('Name of the checklist to retrieve items from'),
|
|
1090
|
+
cardId: z
|
|
1091
|
+
.string()
|
|
1092
|
+
.optional()
|
|
1093
|
+
.describe('ID of the card to scope checklist search to (recommended to avoid ambiguity)'),
|
|
1094
|
+
boardId: z
|
|
1095
|
+
.string()
|
|
1096
|
+
.optional()
|
|
1097
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1098
|
+
},
|
|
1099
|
+
},
|
|
1100
|
+
async ({ name, cardId, boardId }) => {
|
|
1101
|
+
try {
|
|
1102
|
+
const items = await this.trelloClient.getChecklistItems(name, cardId, boardId);
|
|
1103
|
+
return {
|
|
1104
|
+
content: [{ type: 'text' as const, text: JSON.stringify(items, null, 2) }],
|
|
1105
|
+
};
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
return this.handleError(error);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
);
|
|
1111
|
+
|
|
1112
|
+
this.server.registerTool(
|
|
1113
|
+
'add_checklist_item',
|
|
1114
|
+
{
|
|
1115
|
+
title: 'Add Checklist Item',
|
|
1116
|
+
description: 'Add a new item to a checklist',
|
|
1117
|
+
inputSchema: {
|
|
1118
|
+
text: z.string().describe('Text content of the checklist item'),
|
|
1119
|
+
checkListName: z.string().describe('Name of the checklist to add the item to'),
|
|
1120
|
+
cardId: z
|
|
1121
|
+
.string()
|
|
1122
|
+
.optional()
|
|
1123
|
+
.describe('ID of the card to scope checklist search to (recommended to avoid ambiguity)'),
|
|
1124
|
+
boardId: z
|
|
1125
|
+
.string()
|
|
1126
|
+
.optional()
|
|
1127
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
async ({ text, checkListName, cardId, boardId }) => {
|
|
1131
|
+
try {
|
|
1132
|
+
const item = await this.trelloClient.addChecklistItem(text, checkListName, cardId, boardId);
|
|
1133
|
+
return {
|
|
1134
|
+
content: [{ type: 'text' as const, text: JSON.stringify(item, null, 2) }],
|
|
1135
|
+
};
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
return this.handleError(error);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
this.server.registerTool(
|
|
1143
|
+
'find_checklist_items_by_description',
|
|
1144
|
+
{
|
|
1145
|
+
title: 'Find Checklist Items by Description',
|
|
1146
|
+
description: 'Search for checklist items containing specific text in their description',
|
|
1147
|
+
inputSchema: {
|
|
1148
|
+
description: z.string().describe('Text to search for in checklist item descriptions'),
|
|
1149
|
+
cardId: z
|
|
1150
|
+
.string()
|
|
1151
|
+
.optional()
|
|
1152
|
+
.describe('ID of the card to scope checklist search to (recommended to avoid ambiguity)'),
|
|
1153
|
+
boardId: z
|
|
1154
|
+
.string()
|
|
1155
|
+
.optional()
|
|
1156
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
async ({ description, cardId, boardId }) => {
|
|
1160
|
+
try {
|
|
1161
|
+
const items = await this.trelloClient.findChecklistItemsByDescription(
|
|
1162
|
+
description,
|
|
1163
|
+
cardId,
|
|
1164
|
+
boardId
|
|
1165
|
+
);
|
|
1166
|
+
return {
|
|
1167
|
+
content: [{ type: 'text' as const, text: JSON.stringify(items, null, 2) }],
|
|
1168
|
+
};
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
return this.handleError(error);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
this.server.registerTool(
|
|
1176
|
+
'get_acceptance_criteria',
|
|
1177
|
+
{
|
|
1178
|
+
title: 'Get Acceptance Criteria',
|
|
1179
|
+
description: 'Get all items from the "Acceptance Criteria" checklist',
|
|
1180
|
+
inputSchema: {
|
|
1181
|
+
cardId: z
|
|
1182
|
+
.string()
|
|
1183
|
+
.optional()
|
|
1184
|
+
.describe('ID of the card to scope checklist search to (recommended to avoid ambiguity)'),
|
|
1185
|
+
boardId: z
|
|
1186
|
+
.string()
|
|
1187
|
+
.optional()
|
|
1188
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1189
|
+
},
|
|
1190
|
+
},
|
|
1191
|
+
async ({ cardId, boardId }) => {
|
|
1192
|
+
try {
|
|
1193
|
+
const items = await this.trelloClient.getAcceptanceCriteria(cardId, boardId);
|
|
1194
|
+
return {
|
|
1195
|
+
content: [{ type: 'text' as const, text: JSON.stringify(items, null, 2) }],
|
|
1196
|
+
};
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
return this.handleError(error);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
);
|
|
1202
|
+
|
|
1203
|
+
this.server.registerTool(
|
|
1204
|
+
'get_checklist_by_name',
|
|
1205
|
+
{
|
|
1206
|
+
title: 'Get Checklist by Name',
|
|
1207
|
+
description: 'Get a complete checklist with all its items and completion percentage',
|
|
1208
|
+
inputSchema: {
|
|
1209
|
+
name: z.string().describe('Name of the checklist to retrieve'),
|
|
1210
|
+
cardId: z
|
|
1211
|
+
.string()
|
|
1212
|
+
.optional()
|
|
1213
|
+
.describe('ID of the card to scope checklist search to (recommended to avoid ambiguity)'),
|
|
1214
|
+
boardId: z
|
|
1215
|
+
.string()
|
|
1216
|
+
.optional()
|
|
1217
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
async ({ name, cardId, boardId }) => {
|
|
1221
|
+
try {
|
|
1222
|
+
const checklist = await this.trelloClient.getChecklistByName(name, cardId, boardId);
|
|
1223
|
+
if (!checklist) {
|
|
1224
|
+
return {
|
|
1225
|
+
content: [{ type: 'text' as const, text: `Checklist "${name}" not found` }],
|
|
1226
|
+
isError: true,
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
content: [{ type: 'text' as const, text: JSON.stringify(checklist, null, 2) }],
|
|
1231
|
+
};
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
return this.handleError(error);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
this.server.registerTool(
|
|
1239
|
+
'update_checklist_item',
|
|
1240
|
+
{
|
|
1241
|
+
title: 'Update Checklist Item',
|
|
1242
|
+
description: 'Update a checklist item name, state, position, due date, reminder, or assigned member',
|
|
1243
|
+
inputSchema: {
|
|
1244
|
+
cardId: z.string().describe('ID of the card containing the checklist item'),
|
|
1245
|
+
checkItemId: z.string().describe('ID of the checklist item to update'),
|
|
1246
|
+
state: z
|
|
1247
|
+
.enum(['complete', 'incomplete'])
|
|
1248
|
+
.optional()
|
|
1249
|
+
.describe('New state for the checklist item'),
|
|
1250
|
+
name: z.string().optional().describe('New text for the checklist item'),
|
|
1251
|
+
pos: z
|
|
1252
|
+
.union([z.number(), z.enum(['top', 'bottom'])])
|
|
1253
|
+
.optional()
|
|
1254
|
+
.describe('New position for the checklist item'),
|
|
1255
|
+
due: z
|
|
1256
|
+
.string()
|
|
1257
|
+
.nullable()
|
|
1258
|
+
.optional()
|
|
1259
|
+
.describe('New due date for the checklist item in ISO 8601 format, or null to clear it'),
|
|
1260
|
+
dueReminder: z
|
|
1261
|
+
.number()
|
|
1262
|
+
.nullable()
|
|
1263
|
+
.optional()
|
|
1264
|
+
.describe('Reminder offset in minutes before due date, or null to clear it'),
|
|
1265
|
+
idMember: z
|
|
1266
|
+
.string()
|
|
1267
|
+
.nullable()
|
|
1268
|
+
.optional()
|
|
1269
|
+
.describe('Member ID to assign to the checklist item, or null to clear it'),
|
|
1270
|
+
},
|
|
1271
|
+
},
|
|
1272
|
+
async ({ cardId, checkItemId, name, state, pos, due, dueReminder, idMember }) => {
|
|
1273
|
+
try {
|
|
1274
|
+
const item = await this.trelloClient.updateChecklistItem(cardId, checkItemId, {
|
|
1275
|
+
name,
|
|
1276
|
+
state,
|
|
1277
|
+
pos,
|
|
1278
|
+
due,
|
|
1279
|
+
dueReminder,
|
|
1280
|
+
idMember,
|
|
1281
|
+
});
|
|
1282
|
+
return {
|
|
1283
|
+
content: [{ type: 'text' as const, text: JSON.stringify(item, null, 2) }],
|
|
1284
|
+
};
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
return this.handleError(error);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
);
|
|
1290
|
+
|
|
1291
|
+
this.server.registerTool(
|
|
1292
|
+
'delete_checklist_item',
|
|
1293
|
+
{
|
|
1294
|
+
title: 'Delete Checklist Item',
|
|
1295
|
+
description: 'Delete a checklist item from a card',
|
|
1296
|
+
inputSchema: {
|
|
1297
|
+
cardId: z.string().describe('ID of the card containing the checklist item'),
|
|
1298
|
+
checkItemId: z.string().describe('ID of the checklist item to delete'),
|
|
1299
|
+
},
|
|
1300
|
+
},
|
|
1301
|
+
async ({ cardId, checkItemId }) => {
|
|
1302
|
+
try {
|
|
1303
|
+
const deleted = await this.trelloClient.deleteChecklistItem(cardId, checkItemId);
|
|
1304
|
+
return {
|
|
1305
|
+
content: [{ type: 'text' as const, text: JSON.stringify({ deleted }, null, 2) }],
|
|
1306
|
+
};
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
return this.handleError(error);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
// Member management tools
|
|
1314
|
+
this.server.registerTool(
|
|
1315
|
+
'get_board_members',
|
|
1316
|
+
{
|
|
1317
|
+
title: 'Get Board Members',
|
|
1318
|
+
description: 'Get all members of a specific board',
|
|
1319
|
+
inputSchema: {
|
|
1320
|
+
boardId: z
|
|
1321
|
+
.string()
|
|
1322
|
+
.optional()
|
|
1323
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1324
|
+
},
|
|
1325
|
+
},
|
|
1326
|
+
async ({ boardId }) => {
|
|
1327
|
+
try {
|
|
1328
|
+
const members = await this.trelloClient.getBoardMembers(boardId);
|
|
1329
|
+
return {
|
|
1330
|
+
content: [{ type: 'text' as const, text: JSON.stringify(members, null, 2) }],
|
|
1331
|
+
};
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
return this.handleError(error);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
this.server.registerTool(
|
|
1339
|
+
'assign_member_to_card',
|
|
1340
|
+
{
|
|
1341
|
+
title: 'Assign Member to Card',
|
|
1342
|
+
description: 'Assign a member to a specific card',
|
|
1343
|
+
inputSchema: {
|
|
1344
|
+
cardId: z.string().describe('ID of the card to assign the member to'),
|
|
1345
|
+
memberId: z.string().describe('ID of the member to assign to the card'),
|
|
1346
|
+
},
|
|
1347
|
+
},
|
|
1348
|
+
async ({ cardId, memberId }) => {
|
|
1349
|
+
try {
|
|
1350
|
+
const card = await this.trelloClient.assignMemberToCard(cardId, memberId);
|
|
1351
|
+
return {
|
|
1352
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
1353
|
+
};
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
return this.handleError(error);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
this.server.registerTool(
|
|
1361
|
+
'remove_member_from_card',
|
|
1362
|
+
{
|
|
1363
|
+
title: 'Remove Member from Card',
|
|
1364
|
+
description: 'Remove a member from a specific card',
|
|
1365
|
+
inputSchema: {
|
|
1366
|
+
cardId: z.string().describe('ID of the card to remove the member from'),
|
|
1367
|
+
memberId: z.string().describe('ID of the member to remove from the card'),
|
|
1368
|
+
},
|
|
1369
|
+
},
|
|
1370
|
+
async ({ cardId, memberId }) => {
|
|
1371
|
+
try {
|
|
1372
|
+
const card = await this.trelloClient.removeMemberFromCard(cardId, memberId);
|
|
1373
|
+
return {
|
|
1374
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
1375
|
+
};
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
return this.handleError(error);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
// Label management tools
|
|
1383
|
+
this.server.registerTool(
|
|
1384
|
+
'get_board_labels',
|
|
1385
|
+
{
|
|
1386
|
+
title: 'Get Board Labels',
|
|
1387
|
+
description: 'Get all labels of a specific board',
|
|
1388
|
+
inputSchema: {
|
|
1389
|
+
boardId: z
|
|
1390
|
+
.string()
|
|
1391
|
+
.optional()
|
|
1392
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1393
|
+
},
|
|
1394
|
+
},
|
|
1395
|
+
async ({ boardId }) => {
|
|
1396
|
+
try {
|
|
1397
|
+
const labels = await this.trelloClient.getBoardLabels(boardId);
|
|
1398
|
+
return {
|
|
1399
|
+
content: [{ type: 'text' as const, text: JSON.stringify(labels, null, 2) }],
|
|
1400
|
+
};
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
return this.handleError(error);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
);
|
|
1406
|
+
|
|
1407
|
+
this.server.registerTool(
|
|
1408
|
+
'create_label',
|
|
1409
|
+
{
|
|
1410
|
+
title: 'Create Label',
|
|
1411
|
+
description: 'Create a new label on a board',
|
|
1412
|
+
inputSchema: {
|
|
1413
|
+
boardId: z
|
|
1414
|
+
.string()
|
|
1415
|
+
.optional()
|
|
1416
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1417
|
+
name: z.string().describe('Name of the label'),
|
|
1418
|
+
color: z
|
|
1419
|
+
.string()
|
|
1420
|
+
.optional()
|
|
1421
|
+
.describe(
|
|
1422
|
+
'Color of the label (e.g., "red", "blue", "green", "yellow", "orange", "purple", "pink", "sky", "lime", "black", "null")'
|
|
1423
|
+
),
|
|
1424
|
+
},
|
|
1425
|
+
},
|
|
1426
|
+
async ({ boardId, name, color }) => {
|
|
1427
|
+
try {
|
|
1428
|
+
const label = await this.trelloClient.createLabel(boardId, name, color);
|
|
1429
|
+
return {
|
|
1430
|
+
content: [{ type: 'text' as const, text: JSON.stringify(label, null, 2) }],
|
|
1431
|
+
};
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
return this.handleError(error);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
);
|
|
1437
|
+
|
|
1438
|
+
this.server.registerTool(
|
|
1439
|
+
'update_label',
|
|
1440
|
+
{
|
|
1441
|
+
title: 'Update Label',
|
|
1442
|
+
description: 'Update an existing label',
|
|
1443
|
+
inputSchema: {
|
|
1444
|
+
labelId: z.string().describe('ID of the label to update'),
|
|
1445
|
+
name: z.string().optional().describe('New name for the label'),
|
|
1446
|
+
color: z.string().optional().describe('New color for the label'),
|
|
1447
|
+
},
|
|
1448
|
+
},
|
|
1449
|
+
async ({ labelId, name, color }) => {
|
|
1450
|
+
try {
|
|
1451
|
+
const label = await this.trelloClient.updateLabel(labelId, name, color);
|
|
1452
|
+
return {
|
|
1453
|
+
content: [{ type: 'text' as const, text: JSON.stringify(label, null, 2) }],
|
|
1454
|
+
};
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
return this.handleError(error);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
);
|
|
1460
|
+
|
|
1461
|
+
this.server.registerTool(
|
|
1462
|
+
'delete_label',
|
|
1463
|
+
{
|
|
1464
|
+
title: 'Delete Label',
|
|
1465
|
+
description: 'Delete a label from a board',
|
|
1466
|
+
inputSchema: {
|
|
1467
|
+
labelId: z.string().describe('ID of the label to delete'),
|
|
1468
|
+
},
|
|
1469
|
+
},
|
|
1470
|
+
async ({ labelId }) => {
|
|
1471
|
+
try {
|
|
1472
|
+
await this.trelloClient.deleteLabel(labelId);
|
|
1473
|
+
return {
|
|
1474
|
+
content: [{ type: 'text' as const, text: 'Label deleted successfully' }],
|
|
1475
|
+
};
|
|
1476
|
+
} catch (error) {
|
|
1477
|
+
return this.handleError(error);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
);
|
|
1481
|
+
|
|
1482
|
+
// Copy a card (supports cross-board copy)
|
|
1483
|
+
this.server.registerTool(
|
|
1484
|
+
'copy_card',
|
|
1485
|
+
{
|
|
1486
|
+
title: 'Copy Card',
|
|
1487
|
+
description:
|
|
1488
|
+
'Copy/duplicate a Trello card to any list (even on a different board). Copies all properties by default including checklists, attachments, comments, labels, etc.',
|
|
1489
|
+
inputSchema: {
|
|
1490
|
+
sourceCardId: z.string().describe('ID of the source card to copy'),
|
|
1491
|
+
listId: z
|
|
1492
|
+
.string()
|
|
1493
|
+
.describe('ID of the destination list (can be on a different board)'),
|
|
1494
|
+
name: z
|
|
1495
|
+
.string()
|
|
1496
|
+
.optional()
|
|
1497
|
+
.describe('Override the name of the copied card (defaults to source card name)'),
|
|
1498
|
+
description: z
|
|
1499
|
+
.string()
|
|
1500
|
+
.optional()
|
|
1501
|
+
.describe('Override the description of the copied card'),
|
|
1502
|
+
keepFromSource: z
|
|
1503
|
+
.string()
|
|
1504
|
+
.optional()
|
|
1505
|
+
.describe(
|
|
1506
|
+
'Comma-separated list of properties to copy: "all" (default), or any combination of: attachments, checklists, comments, customFields, due, start, labels, members, stickers'
|
|
1507
|
+
),
|
|
1508
|
+
pos: z
|
|
1509
|
+
.string()
|
|
1510
|
+
.optional()
|
|
1511
|
+
.describe('Position of the new card: "top", "bottom", or a positive float'),
|
|
1512
|
+
},
|
|
1513
|
+
},
|
|
1514
|
+
async ({ sourceCardId, listId, name, description, keepFromSource, pos }) => {
|
|
1515
|
+
try {
|
|
1516
|
+
const card = await this.trelloClient.copyCard({
|
|
1517
|
+
sourceCardId,
|
|
1518
|
+
listId,
|
|
1519
|
+
name,
|
|
1520
|
+
description,
|
|
1521
|
+
keepFromSource,
|
|
1522
|
+
pos,
|
|
1523
|
+
});
|
|
1524
|
+
return {
|
|
1525
|
+
content: [{ type: 'text' as const, text: JSON.stringify(card, null, 2) }],
|
|
1526
|
+
};
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
return this.handleError(error);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
);
|
|
1532
|
+
|
|
1533
|
+
// Copy a checklist from one card to another
|
|
1534
|
+
this.server.registerTool(
|
|
1535
|
+
'copy_checklist',
|
|
1536
|
+
{
|
|
1537
|
+
title: 'Copy Checklist',
|
|
1538
|
+
description:
|
|
1539
|
+
'Copy a checklist (with all its items) from one card to another. Works across different boards.',
|
|
1540
|
+
inputSchema: {
|
|
1541
|
+
sourceChecklistId: z
|
|
1542
|
+
.string()
|
|
1543
|
+
.describe('ID of the source checklist to copy'),
|
|
1544
|
+
cardId: z
|
|
1545
|
+
.string()
|
|
1546
|
+
.describe('ID of the destination card to copy the checklist to'),
|
|
1547
|
+
name: z
|
|
1548
|
+
.string()
|
|
1549
|
+
.optional()
|
|
1550
|
+
.describe('Override the name of the copied checklist (defaults to source checklist name)'),
|
|
1551
|
+
pos: z
|
|
1552
|
+
.string()
|
|
1553
|
+
.optional()
|
|
1554
|
+
.describe('Position of the new checklist: "top", "bottom", or a positive number'),
|
|
1555
|
+
},
|
|
1556
|
+
},
|
|
1557
|
+
async ({ sourceChecklistId, cardId, name, pos }) => {
|
|
1558
|
+
try {
|
|
1559
|
+
const checklist = await this.trelloClient.copyChecklist({
|
|
1560
|
+
sourceChecklistId,
|
|
1561
|
+
cardId,
|
|
1562
|
+
name,
|
|
1563
|
+
pos,
|
|
1564
|
+
});
|
|
1565
|
+
return {
|
|
1566
|
+
content: [{ type: 'text' as const, text: JSON.stringify(checklist, null, 2) }],
|
|
1567
|
+
};
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
return this.handleError(error);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
);
|
|
1573
|
+
|
|
1574
|
+
// Add multiple cards to a list
|
|
1575
|
+
this.server.registerTool(
|
|
1576
|
+
'add_cards_to_list',
|
|
1577
|
+
{
|
|
1578
|
+
title: 'Add Cards to List',
|
|
1579
|
+
description:
|
|
1580
|
+
'Add multiple cards to a list in one operation. Cards are created sequentially (Trello API does not support batch writes). Rate limiting is handled automatically.',
|
|
1581
|
+
inputSchema: {
|
|
1582
|
+
listId: z.string().describe('ID of the list to add cards to'),
|
|
1583
|
+
cards: z
|
|
1584
|
+
.array(
|
|
1585
|
+
z.object({
|
|
1586
|
+
name: z.string().describe('Name of the card'),
|
|
1587
|
+
description: z.string().optional().describe('Description of the card'),
|
|
1588
|
+
dueDate: z
|
|
1589
|
+
.string()
|
|
1590
|
+
.optional()
|
|
1591
|
+
.describe('Due date for the card (ISO 8601 format)'),
|
|
1592
|
+
start: z
|
|
1593
|
+
.string()
|
|
1594
|
+
.optional()
|
|
1595
|
+
.describe('Start date for the card (YYYY-MM-DD format)'),
|
|
1596
|
+
labels: z
|
|
1597
|
+
.array(z.string())
|
|
1598
|
+
.optional()
|
|
1599
|
+
.describe('Array of label IDs to apply to the card'),
|
|
1600
|
+
})
|
|
1601
|
+
)
|
|
1602
|
+
.describe('Array of cards to create (max 50)'),
|
|
1603
|
+
},
|
|
1604
|
+
},
|
|
1605
|
+
async ({ listId, cards }) => {
|
|
1606
|
+
try {
|
|
1607
|
+
const results = await this.trelloClient.batchAddCards(listId, cards);
|
|
1608
|
+
return {
|
|
1609
|
+
content: [
|
|
1610
|
+
{
|
|
1611
|
+
type: 'text' as const,
|
|
1612
|
+
text: JSON.stringify(results, null, 2),
|
|
1613
|
+
},
|
|
1614
|
+
],
|
|
1615
|
+
};
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
return this.handleError(error);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
);
|
|
1621
|
+
|
|
1622
|
+
// Custom field management tools
|
|
1623
|
+
this.server.registerTool(
|
|
1624
|
+
'get_board_custom_fields',
|
|
1625
|
+
{
|
|
1626
|
+
title: 'Get Board Custom Fields',
|
|
1627
|
+
description:
|
|
1628
|
+
'Get all custom field definitions on a board. Returns field IDs, names, and types. ' +
|
|
1629
|
+
'For dropdown/list fields, also returns available options with their IDs. ' +
|
|
1630
|
+
'Requires Trello Standard plan or higher.',
|
|
1631
|
+
inputSchema: {
|
|
1632
|
+
boardId: z
|
|
1633
|
+
.string()
|
|
1634
|
+
.optional()
|
|
1635
|
+
.describe('ID of the Trello board (uses default if not provided)'),
|
|
1636
|
+
},
|
|
1637
|
+
},
|
|
1638
|
+
async ({ boardId }) => {
|
|
1639
|
+
try {
|
|
1640
|
+
const fields = await this.trelloClient.getBoardCustomFields(boardId);
|
|
1641
|
+
|
|
1642
|
+
const fieldsWithOptions = await Promise.all(
|
|
1643
|
+
fields.map(async (field) => {
|
|
1644
|
+
if (field.type !== 'list') {
|
|
1645
|
+
return field;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
try {
|
|
1649
|
+
const options = await this.trelloClient.getCustomFieldOptions(field.id);
|
|
1650
|
+
return { ...field, options };
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
return {
|
|
1653
|
+
...field,
|
|
1654
|
+
optionsError: error instanceof Error ? error.message : 'Failed to fetch options',
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
})
|
|
1658
|
+
);
|
|
1659
|
+
|
|
1660
|
+
return {
|
|
1661
|
+
content: [
|
|
1662
|
+
{ type: 'text' as const, text: JSON.stringify(fieldsWithOptions, null, 2) },
|
|
1663
|
+
],
|
|
1664
|
+
};
|
|
1665
|
+
} catch (error) {
|
|
1666
|
+
return this.handleError(error);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
);
|
|
1670
|
+
|
|
1671
|
+
this.server.registerTool(
|
|
1672
|
+
'update_card_custom_field',
|
|
1673
|
+
{
|
|
1674
|
+
title: 'Update Card Custom Field',
|
|
1675
|
+
description:
|
|
1676
|
+
'Set or clear a custom field value on a card. Requires Trello Standard plan or higher. ' +
|
|
1677
|
+
'Use get_board_custom_fields first to find field IDs and types. ' +
|
|
1678
|
+
'Value format depends on type: text=any string, number=numeric string, ' +
|
|
1679
|
+
'checkbox="true"/"false", date=ISO 8601 string, list=option ID from get_board_custom_fields. ' +
|
|
1680
|
+
'To clear a field, set type to "clear" and omit value.',
|
|
1681
|
+
inputSchema: {
|
|
1682
|
+
cardId: z.string().describe('ID of the card to update'),
|
|
1683
|
+
customFieldId: z.string().describe('ID of the custom field definition'),
|
|
1684
|
+
type: z
|
|
1685
|
+
.enum(['text', 'number', 'checkbox', 'date', 'list', 'clear'])
|
|
1686
|
+
.describe('The custom field type. Use "clear" to remove the value from the field.'),
|
|
1687
|
+
value: z
|
|
1688
|
+
.string()
|
|
1689
|
+
.optional()
|
|
1690
|
+
.describe(
|
|
1691
|
+
'The value to set. For text: any string. For number: numeric string (e.g. "42.5"). ' +
|
|
1692
|
+
'For checkbox: "true" or "false". For date: ISO 8601 (e.g. "2025-12-31T00:00:00.000Z"). ' +
|
|
1693
|
+
'For list: the option ID. Not needed when type is "clear".'
|
|
1694
|
+
),
|
|
1695
|
+
},
|
|
1696
|
+
},
|
|
1697
|
+
async ({ cardId, customFieldId, type, value }) => {
|
|
1698
|
+
try {
|
|
1699
|
+
if (type !== 'clear' && !value) {
|
|
1700
|
+
return {
|
|
1701
|
+
content: [
|
|
1702
|
+
{
|
|
1703
|
+
type: 'text' as const,
|
|
1704
|
+
text: 'Error: value is required when type is not "clear"',
|
|
1705
|
+
},
|
|
1706
|
+
],
|
|
1707
|
+
isError: true,
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
const result = await this.trelloClient.updateCardCustomField(cardId, customFieldId, {
|
|
1712
|
+
type,
|
|
1713
|
+
value,
|
|
1714
|
+
});
|
|
1715
|
+
return {
|
|
1716
|
+
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
|
|
1717
|
+
};
|
|
1718
|
+
} catch (error) {
|
|
1719
|
+
return this.handleError(error);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
);
|
|
1723
|
+
|
|
1724
|
+
// Card history tool
|
|
1725
|
+
this.server.registerTool(
|
|
1726
|
+
'get_card_history',
|
|
1727
|
+
{
|
|
1728
|
+
title: 'Get Card History',
|
|
1729
|
+
description: 'Get the history/actions of a specific card',
|
|
1730
|
+
inputSchema: {
|
|
1731
|
+
cardId: z.string().describe('ID of the card to get history for'),
|
|
1732
|
+
filter: z
|
|
1733
|
+
.string()
|
|
1734
|
+
.optional()
|
|
1735
|
+
.describe(
|
|
1736
|
+
'Optional: Filter actions by type (e.g., "all", "updateCard:idList", "addAttachmentToCard", "commentCard", "updateCard:name", "updateCard:desc", "updateCard:due", "addMemberToCard", "removeMemberFromCard", "addLabelToCard", "removeLabelFromCard")'
|
|
1737
|
+
),
|
|
1738
|
+
limit: z
|
|
1739
|
+
.number()
|
|
1740
|
+
.optional()
|
|
1741
|
+
.describe('Optional: Number of actions to fetch (default: all)'),
|
|
1742
|
+
},
|
|
1743
|
+
},
|
|
1744
|
+
async ({ cardId, filter, limit }) => {
|
|
1745
|
+
try {
|
|
1746
|
+
const history = await this.trelloClient.getCardHistory(cardId, filter, limit);
|
|
1747
|
+
return {
|
|
1748
|
+
content: [{ type: 'text' as const, text: JSON.stringify(history, null, 2) }],
|
|
1749
|
+
};
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
return this.handleError(error);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
);
|
|
1755
|
+
|
|
1756
|
+
// Download attachment tool
|
|
1757
|
+
this.server.registerTool(
|
|
1758
|
+
'download_attachment',
|
|
1759
|
+
{
|
|
1760
|
+
title: 'Download Attachment',
|
|
1761
|
+
description: 'Download an attachment from a card. Returns base64-encoded data that can be saved or viewed.',
|
|
1762
|
+
inputSchema: {
|
|
1763
|
+
cardId: z.string().describe('ID of the card containing the attachment'),
|
|
1764
|
+
attachmentId: z.string().describe('ID of the attachment to download'),
|
|
1765
|
+
},
|
|
1766
|
+
},
|
|
1767
|
+
async ({ cardId, attachmentId }) => {
|
|
1768
|
+
try {
|
|
1769
|
+
const result = await this.trelloClient.downloadAttachment(cardId, attachmentId);
|
|
1770
|
+
|
|
1771
|
+
if (result.mimeType.startsWith('image/')) {
|
|
1772
|
+
return {
|
|
1773
|
+
content: [
|
|
1774
|
+
{
|
|
1775
|
+
type: 'image' as const,
|
|
1776
|
+
data: result.data,
|
|
1777
|
+
mimeType: result.mimeType,
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
type: 'text' as const,
|
|
1781
|
+
text: `Downloaded: ${result.fileName} (${result.mimeType})`,
|
|
1782
|
+
},
|
|
1783
|
+
],
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
return {
|
|
1788
|
+
content: [
|
|
1789
|
+
{
|
|
1790
|
+
type: 'text' as const,
|
|
1791
|
+
text: JSON.stringify(
|
|
1792
|
+
{ fileName: result.fileName, mimeType: result.mimeType, data: result.data },
|
|
1793
|
+
null,
|
|
1794
|
+
2
|
|
1795
|
+
),
|
|
1796
|
+
},
|
|
1797
|
+
],
|
|
1798
|
+
};
|
|
1799
|
+
} catch (error) {
|
|
1800
|
+
return this.handleError(error);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
private setupHealthEndpoints() {
|
|
1807
|
+
// Basic health check endpoint
|
|
1808
|
+
this.server.registerTool('get_health', HealthEndpointSchemas.basicHealth, async () => {
|
|
1809
|
+
try {
|
|
1810
|
+
return await this.healthEndpoints.getBasicHealth();
|
|
1811
|
+
} catch (error) {
|
|
1812
|
+
return this.handleError(error);
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
// Detailed health diagnostic endpoint
|
|
1817
|
+
this.server.registerTool(
|
|
1818
|
+
'get_health_detailed',
|
|
1819
|
+
HealthEndpointSchemas.detailedHealth,
|
|
1820
|
+
async () => {
|
|
1821
|
+
try {
|
|
1822
|
+
return await this.healthEndpoints.getDetailedHealth();
|
|
1823
|
+
} catch (error) {
|
|
1824
|
+
return this.handleError(error);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
);
|
|
1828
|
+
|
|
1829
|
+
// Metadata consistency check endpoint
|
|
1830
|
+
this.server.registerTool(
|
|
1831
|
+
'get_health_metadata',
|
|
1832
|
+
HealthEndpointSchemas.metadataHealth,
|
|
1833
|
+
async () => {
|
|
1834
|
+
try {
|
|
1835
|
+
return await this.healthEndpoints.getMetadataHealth();
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
return this.handleError(error);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
);
|
|
1841
|
+
|
|
1842
|
+
// Performance metrics endpoint
|
|
1843
|
+
this.server.registerTool(
|
|
1844
|
+
'get_health_performance',
|
|
1845
|
+
HealthEndpointSchemas.performanceHealth,
|
|
1846
|
+
async () => {
|
|
1847
|
+
try {
|
|
1848
|
+
return await this.healthEndpoints.getPerformanceHealth();
|
|
1849
|
+
} catch (error) {
|
|
1850
|
+
return this.handleError(error);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
);
|
|
1854
|
+
|
|
1855
|
+
// System repair endpoint
|
|
1856
|
+
this.server.registerTool('perform_system_repair', HealthEndpointSchemas.repair, async () => {
|
|
1857
|
+
try {
|
|
1858
|
+
return await this.healthEndpoints.performRepair();
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
return this.handleError(error);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
async run() {
|
|
1866
|
+
const transport = new StdioServerTransport();
|
|
1867
|
+
// Load configuration before starting the server
|
|
1868
|
+
await this.trelloClient.loadConfig().catch(() => {
|
|
1869
|
+
// Continue with default config if loading fails
|
|
1870
|
+
});
|
|
1871
|
+
await this.server.connect(transport);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
const server = new TrelloServer();
|
|
1876
|
+
server.run().catch(() => {
|
|
1877
|
+
// Silently handle errors to avoid interfering with MCP protocol
|
|
1878
|
+
});
|