@delorenj/mcp-server-trello 1.6.1 → 1.7.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/build/index.js +42106 -773
- package/package.json +24 -16
- package/build/evals/evals.d.ts +0 -5
- package/build/evals/evals.js +0 -72
- package/build/evals/evals.js.map +0 -1
- package/build/health/health-endpoints.d.ts +0 -131
- package/build/health/health-endpoints.js +0 -491
- package/build/health/health-endpoints.js.map +0 -1
- package/build/health/health-monitor.d.ts +0 -136
- package/build/health/health-monitor.js +0 -510
- package/build/health/health-monitor.js.map +0 -1
- package/build/index.d.ts +0 -2
- package/build/index.js.map +0 -1
- package/build/rate-limiter.d.ts +0 -32
- package/build/rate-limiter.js +0 -73
- package/build/rate-limiter.js.map +0 -1
- package/build/trello-client.d.ts +0 -105
- package/build/trello-client.js +0 -699
- package/build/trello-client.js.map +0 -1
- package/build/types.d.ts +0 -224
- package/build/types.js +0 -2
- package/build/types.js.map +0 -1
- package/build/validators.d.ts +0 -75
- package/build/validators.js +0 -192
- package/build/validators.js.map +0 -1
package/build/trello-client.js
DELETED
|
@@ -1,699 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import FormData from 'form-data';
|
|
3
|
-
import { createTrelloRateLimiters } from './rate-limiter.js';
|
|
4
|
-
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
-
import * as fs from 'fs/promises';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import { createReadStream } from 'fs';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
// Path for storing active board/workspace configuration
|
|
10
|
-
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.trello-mcp');
|
|
11
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
12
|
-
export class TrelloClient {
|
|
13
|
-
constructor(config) {
|
|
14
|
-
this.config = config;
|
|
15
|
-
this.defaultBoardId = config.defaultBoardId;
|
|
16
|
-
this.activeConfig = { ...config };
|
|
17
|
-
// If boardId is provided in config, use it as the active board
|
|
18
|
-
if (config.boardId && !this.activeConfig.boardId) {
|
|
19
|
-
this.activeConfig.boardId = config.boardId;
|
|
20
|
-
}
|
|
21
|
-
// If defaultBoardId is provided but boardId is not, use defaultBoardId
|
|
22
|
-
if (this.defaultBoardId && !this.activeConfig.boardId) {
|
|
23
|
-
this.activeConfig.boardId = this.defaultBoardId;
|
|
24
|
-
}
|
|
25
|
-
this.axiosInstance = axios.create({
|
|
26
|
-
baseURL: 'https://api.trello.com/1',
|
|
27
|
-
params: {
|
|
28
|
-
key: config.apiKey,
|
|
29
|
-
token: config.token,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
this.rateLimiter = createTrelloRateLimiters();
|
|
33
|
-
// Add rate limiting interceptor
|
|
34
|
-
this.axiosInstance.interceptors.request.use(async (config) => {
|
|
35
|
-
await this.rateLimiter.waitForAvailableToken();
|
|
36
|
-
return config;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Load saved configuration from disk
|
|
41
|
-
*/
|
|
42
|
-
async loadConfig() {
|
|
43
|
-
try {
|
|
44
|
-
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
45
|
-
const data = await fs.readFile(CONFIG_FILE, 'utf8');
|
|
46
|
-
const savedConfig = JSON.parse(data);
|
|
47
|
-
// Only update boardId and workspaceId, keep credentials from env
|
|
48
|
-
if (savedConfig.boardId) {
|
|
49
|
-
this.activeConfig.boardId = savedConfig.boardId;
|
|
50
|
-
}
|
|
51
|
-
if (savedConfig.workspaceId) {
|
|
52
|
-
this.activeConfig.workspaceId = savedConfig.workspaceId;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
// File might not exist yet, that's okay
|
|
57
|
-
if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') {
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Save current configuration to disk
|
|
64
|
-
*/
|
|
65
|
-
async saveConfig() {
|
|
66
|
-
try {
|
|
67
|
-
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
68
|
-
const configToSave = {
|
|
69
|
-
boardId: this.activeConfig.boardId,
|
|
70
|
-
workspaceId: this.activeConfig.workspaceId,
|
|
71
|
-
};
|
|
72
|
-
await fs.writeFile(CONFIG_FILE, JSON.stringify(configToSave, null, 2));
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
// Failed to save configuration
|
|
76
|
-
throw new Error('Failed to save configuration');
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Get the current active board ID
|
|
81
|
-
*/
|
|
82
|
-
get activeBoardId() {
|
|
83
|
-
return this.activeConfig.boardId;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Get the current active workspace ID
|
|
87
|
-
*/
|
|
88
|
-
get activeWorkspaceId() {
|
|
89
|
-
return this.activeConfig.workspaceId;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Set the active board
|
|
93
|
-
*/
|
|
94
|
-
async setActiveBoard(boardId) {
|
|
95
|
-
// Verify the board exists
|
|
96
|
-
const board = await this.getBoardById(boardId);
|
|
97
|
-
this.activeConfig.boardId = boardId;
|
|
98
|
-
await this.saveConfig();
|
|
99
|
-
return board;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Set the active workspace
|
|
103
|
-
*/
|
|
104
|
-
async setActiveWorkspace(workspaceId) {
|
|
105
|
-
// Verify the workspace exists
|
|
106
|
-
const workspace = await this.getWorkspaceById(workspaceId);
|
|
107
|
-
this.activeConfig.workspaceId = workspaceId;
|
|
108
|
-
await this.saveConfig();
|
|
109
|
-
return workspace;
|
|
110
|
-
}
|
|
111
|
-
async handleRequest(requestFn) {
|
|
112
|
-
try {
|
|
113
|
-
return await requestFn();
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
if (axios.isAxiosError(error)) {
|
|
117
|
-
if (error.response?.status === 429) {
|
|
118
|
-
// Rate limit exceeded, wait and retry
|
|
119
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
120
|
-
return this.handleRequest(requestFn);
|
|
121
|
-
}
|
|
122
|
-
// Trello API Error
|
|
123
|
-
// Customize error handling based on Trello's error structure if needed
|
|
124
|
-
throw new McpError(ErrorCode.InternalError, `Trello API Error: ${error.response?.status} ${error.message}`, error.response?.data);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
// Unexpected Error
|
|
128
|
-
throw new McpError(ErrorCode.InternalError, 'An unexpected error occurred');
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* List all boards the user has access to
|
|
134
|
-
*/
|
|
135
|
-
async listBoards() {
|
|
136
|
-
return this.handleRequest(async () => {
|
|
137
|
-
const response = await this.axiosInstance.get('/members/me/boards');
|
|
138
|
-
return response.data;
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Get a specific board by ID
|
|
143
|
-
*/
|
|
144
|
-
async getBoardById(boardId) {
|
|
145
|
-
return this.handleRequest(async () => {
|
|
146
|
-
const response = await this.axiosInstance.get(`/boards/${boardId}`);
|
|
147
|
-
return response.data;
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* List all workspaces the user has access to
|
|
152
|
-
*/
|
|
153
|
-
async listWorkspaces() {
|
|
154
|
-
return this.handleRequest(async () => {
|
|
155
|
-
const response = await this.axiosInstance.get('/members/me/organizations');
|
|
156
|
-
return response.data;
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Get a specific workspace by ID
|
|
161
|
-
*/
|
|
162
|
-
async getWorkspaceById(workspaceId) {
|
|
163
|
-
return this.handleRequest(async () => {
|
|
164
|
-
const response = await this.axiosInstance.get(`/organizations/${workspaceId}`);
|
|
165
|
-
return response.data;
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* List boards in a specific workspace
|
|
170
|
-
*/
|
|
171
|
-
async listBoardsInWorkspace(workspaceId) {
|
|
172
|
-
return this.handleRequest(async () => {
|
|
173
|
-
const response = await this.axiosInstance.get(`/organizations/${workspaceId}/boards`);
|
|
174
|
-
return response.data;
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Create a new board
|
|
179
|
-
*/
|
|
180
|
-
async createBoard(params) {
|
|
181
|
-
return this.handleRequest(async () => {
|
|
182
|
-
const response = await this.axiosInstance.post('/boards', {
|
|
183
|
-
name: params.name,
|
|
184
|
-
desc: params.desc,
|
|
185
|
-
idOrganization: params.idOrganization ?? this.activeConfig.workspaceId,
|
|
186
|
-
defaultLabels: params.defaultLabels,
|
|
187
|
-
defaultLists: params.defaultLists,
|
|
188
|
-
});
|
|
189
|
-
return response.data;
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
async getCardsByList(boardId, listId) {
|
|
193
|
-
return this.handleRequest(async () => {
|
|
194
|
-
const response = await this.axiosInstance.get(`/lists/${listId}/cards`);
|
|
195
|
-
return response.data;
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
async getLists(boardId) {
|
|
199
|
-
const effectiveBoardId = boardId || this.activeConfig.boardId || this.defaultBoardId;
|
|
200
|
-
if (!effectiveBoardId) {
|
|
201
|
-
throw new McpError(ErrorCode.InvalidParams, 'boardId is required when no default board is configured');
|
|
202
|
-
}
|
|
203
|
-
return this.handleRequest(async () => {
|
|
204
|
-
const response = await this.axiosInstance.get(`/boards/${effectiveBoardId}/lists`);
|
|
205
|
-
return response.data;
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
async getRecentActivity(boardId, limit = 10) {
|
|
209
|
-
const effectiveBoardId = boardId || this.activeConfig.boardId || this.defaultBoardId;
|
|
210
|
-
if (!effectiveBoardId) {
|
|
211
|
-
throw new McpError(ErrorCode.InvalidParams, 'boardId is required when no default board is configured');
|
|
212
|
-
}
|
|
213
|
-
return this.handleRequest(async () => {
|
|
214
|
-
const response = await this.axiosInstance.get(`/boards/${effectiveBoardId}/actions`, {
|
|
215
|
-
params: { limit },
|
|
216
|
-
});
|
|
217
|
-
return response.data;
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
async addCard(boardId, params) {
|
|
221
|
-
return this.handleRequest(async () => {
|
|
222
|
-
const response = await this.axiosInstance.post('/cards', {
|
|
223
|
-
idList: params.listId,
|
|
224
|
-
name: params.name,
|
|
225
|
-
desc: params.description,
|
|
226
|
-
due: params.dueDate,
|
|
227
|
-
start: params.start,
|
|
228
|
-
idLabels: params.labels,
|
|
229
|
-
});
|
|
230
|
-
return response.data;
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
async updateCard(boardId, params) {
|
|
234
|
-
return this.handleRequest(async () => {
|
|
235
|
-
const response = await this.axiosInstance.put(`/cards/${params.cardId}`, {
|
|
236
|
-
name: params.name,
|
|
237
|
-
desc: params.description,
|
|
238
|
-
due: params.dueDate,
|
|
239
|
-
start: params.start,
|
|
240
|
-
dueComplete: params.dueComplete,
|
|
241
|
-
idLabels: params.labels,
|
|
242
|
-
});
|
|
243
|
-
return response.data;
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
async archiveCard(boardId, cardId) {
|
|
247
|
-
return this.handleRequest(async () => {
|
|
248
|
-
const response = await this.axiosInstance.put(`/cards/${cardId}`, {
|
|
249
|
-
closed: true,
|
|
250
|
-
});
|
|
251
|
-
return response.data;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
async moveCard(boardId, cardId, listId) {
|
|
255
|
-
const effectiveBoardId = boardId || this.defaultBoardId;
|
|
256
|
-
return this.handleRequest(async () => {
|
|
257
|
-
const response = await this.axiosInstance.put(`/cards/${cardId}`, {
|
|
258
|
-
idList: listId,
|
|
259
|
-
...(effectiveBoardId && { idBoard: effectiveBoardId }),
|
|
260
|
-
});
|
|
261
|
-
return response.data;
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
async addList(boardId, name) {
|
|
265
|
-
const effectiveBoardId = boardId || this.activeConfig.boardId || this.defaultBoardId;
|
|
266
|
-
if (!effectiveBoardId) {
|
|
267
|
-
throw new McpError(ErrorCode.InvalidParams, 'boardId is required when no default board is configured');
|
|
268
|
-
}
|
|
269
|
-
return this.handleRequest(async () => {
|
|
270
|
-
const response = await this.axiosInstance.post('/lists', {
|
|
271
|
-
name,
|
|
272
|
-
idBoard: effectiveBoardId,
|
|
273
|
-
});
|
|
274
|
-
return response.data;
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
async archiveList(boardId, listId) {
|
|
278
|
-
return this.handleRequest(async () => {
|
|
279
|
-
const response = await this.axiosInstance.put(`/lists/${listId}/closed`, {
|
|
280
|
-
value: true,
|
|
281
|
-
});
|
|
282
|
-
return response.data;
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
async getMyCards() {
|
|
286
|
-
return this.handleRequest(async () => {
|
|
287
|
-
const response = await this.axiosInstance.get('/members/me/cards');
|
|
288
|
-
return response.data;
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
async attachImageToCard(boardId, cardId, imageUrl, name) {
|
|
292
|
-
// Simply delegate to attachFileToCard - it will auto-detect MIME type for images
|
|
293
|
-
return this.attachFileToCard(boardId, cardId, imageUrl, name || 'Image Attachment', undefined);
|
|
294
|
-
}
|
|
295
|
-
async attachFileToCard(boardId, cardId, fileUrl, name, mimeType) {
|
|
296
|
-
return this.handleRequest(async () => {
|
|
297
|
-
// Check if fileUrl is a local file path (starts with file://)
|
|
298
|
-
if (fileUrl.startsWith('file://')) {
|
|
299
|
-
// Handle local file upload
|
|
300
|
-
const localPath = fileURLToPath(fileUrl);
|
|
301
|
-
let effectiveMimeType = mimeType;
|
|
302
|
-
if (!effectiveMimeType) {
|
|
303
|
-
const ext = path.extname(localPath).toLowerCase();
|
|
304
|
-
effectiveMimeType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
305
|
-
}
|
|
306
|
-
// Check if file exists
|
|
307
|
-
try {
|
|
308
|
-
await fs.access(localPath);
|
|
309
|
-
}
|
|
310
|
-
catch (error) {
|
|
311
|
-
throw new McpError(ErrorCode.InvalidRequest, `File not found: ${localPath}`);
|
|
312
|
-
}
|
|
313
|
-
// Create form data for multipart upload
|
|
314
|
-
const form = new FormData();
|
|
315
|
-
const fileStream = createReadStream(localPath);
|
|
316
|
-
const fileName = name || path.basename(localPath);
|
|
317
|
-
form.append('file', fileStream, {
|
|
318
|
-
filename: fileName,
|
|
319
|
-
contentType: effectiveMimeType,
|
|
320
|
-
});
|
|
321
|
-
// Add name and mimeType to form
|
|
322
|
-
form.append('name', fileName);
|
|
323
|
-
form.append('mimeType', effectiveMimeType);
|
|
324
|
-
// Upload file directly to Trello using the configured axios instance
|
|
325
|
-
const response = await this.axiosInstance.post(`/cards/${cardId}/attachments`, form, {
|
|
326
|
-
headers: {
|
|
327
|
-
...form.getHeaders(),
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
return response.data;
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
// Handle URL attachment
|
|
334
|
-
const remoteUrlPath = new URL(fileUrl).pathname;
|
|
335
|
-
let effectiveMimeType = mimeType;
|
|
336
|
-
if (!effectiveMimeType) {
|
|
337
|
-
const ext = path.extname(remoteUrlPath).toLowerCase();
|
|
338
|
-
effectiveMimeType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
339
|
-
}
|
|
340
|
-
const response = await this.axiosInstance.post(`/cards/${cardId}/attachments`, {
|
|
341
|
-
url: fileUrl,
|
|
342
|
-
name: name || 'File Attachment',
|
|
343
|
-
mimeType: effectiveMimeType,
|
|
344
|
-
});
|
|
345
|
-
return response.data;
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
async getCard(cardId, includeMarkdown = false) {
|
|
350
|
-
return this.handleRequest(async () => {
|
|
351
|
-
const response = await this.axiosInstance.get(`/cards/${cardId}`, {
|
|
352
|
-
params: {
|
|
353
|
-
attachments: true,
|
|
354
|
-
checklists: 'all',
|
|
355
|
-
checkItemStates: true,
|
|
356
|
-
members: true,
|
|
357
|
-
membersVoted: true,
|
|
358
|
-
labels: true,
|
|
359
|
-
actions: 'commentCard',
|
|
360
|
-
actions_limit: 100,
|
|
361
|
-
fields: 'all',
|
|
362
|
-
customFieldItems: true,
|
|
363
|
-
list: true,
|
|
364
|
-
board: true,
|
|
365
|
-
stickers: true,
|
|
366
|
-
pluginData: true,
|
|
367
|
-
},
|
|
368
|
-
});
|
|
369
|
-
const cardData = response.data;
|
|
370
|
-
if (includeMarkdown) {
|
|
371
|
-
return this.formatCardAsMarkdown(cardData);
|
|
372
|
-
}
|
|
373
|
-
return cardData;
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
// Add Comment on Card
|
|
377
|
-
async addCommentToCard(cardId, text) {
|
|
378
|
-
return this.handleRequest(async () => {
|
|
379
|
-
const response = await this.axiosInstance.post(`cards/${cardId}/actions/comments?text=${encodeURIComponent(text)}`);
|
|
380
|
-
return response.data;
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
// Update Comment
|
|
384
|
-
async updateCommentOnCard(commentId, text) {
|
|
385
|
-
return this.handleRequest(async () => {
|
|
386
|
-
const response = await this.axiosInstance.put(`/actions/${commentId}?text=${encodeURIComponent(text)}`);
|
|
387
|
-
if (response.status !== 200) {
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
|
-
return true;
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
// Delete Comment
|
|
394
|
-
async deleteCommentFromCard(commentId) {
|
|
395
|
-
return this.handleRequest(async () => {
|
|
396
|
-
const response = await this.axiosInstance.delete(`/actions/${commentId}`);
|
|
397
|
-
return response.status === 200;
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
// Get Card Comments
|
|
401
|
-
async getCardComments(cardId, limit = 100) {
|
|
402
|
-
return this.handleRequest(async () => {
|
|
403
|
-
const response = await this.axiosInstance.get(`/cards/${cardId}/actions`, {
|
|
404
|
-
params: {
|
|
405
|
-
filter: 'commentCard',
|
|
406
|
-
limit: limit,
|
|
407
|
-
},
|
|
408
|
-
});
|
|
409
|
-
return response.data;
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
// Checklist methods
|
|
413
|
-
async getChecklistItems(name, boardId) {
|
|
414
|
-
const effectiveBoardId = boardId || this.activeConfig.boardId;
|
|
415
|
-
if (!effectiveBoardId) {
|
|
416
|
-
throw new McpError(ErrorCode.InvalidParams, 'No board ID provided and no active board set');
|
|
417
|
-
}
|
|
418
|
-
// Get all checklists from the board directly
|
|
419
|
-
const response = await this.axiosInstance.get(`/boards/${effectiveBoardId}/checklists`);
|
|
420
|
-
const allCheckItems = [];
|
|
421
|
-
for (const checklist of response.data) {
|
|
422
|
-
if (checklist.name.toLowerCase() === name.toLowerCase()) {
|
|
423
|
-
const convertedItems = checklist.checkItems.map(item => this.convertToCheckListItem(item, checklist.id));
|
|
424
|
-
allCheckItems.push(...convertedItems);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return allCheckItems;
|
|
428
|
-
}
|
|
429
|
-
async addChecklistItem(text, checkListName, boardId) {
|
|
430
|
-
const effectiveBoardId = boardId || this.activeConfig.boardId;
|
|
431
|
-
if (!effectiveBoardId) {
|
|
432
|
-
throw new McpError(ErrorCode.InvalidParams, 'No board ID provided and no active board set');
|
|
433
|
-
}
|
|
434
|
-
// Find the checklist by name using the more efficient endpoint
|
|
435
|
-
const checklistsResponse = await this.axiosInstance.get(`/boards/${effectiveBoardId}/checklists`);
|
|
436
|
-
const checklists = checklistsResponse.data;
|
|
437
|
-
const targetChecklist = checklists.find(checklist => checklist.name.toLowerCase() === checkListName.toLowerCase());
|
|
438
|
-
if (!targetChecklist) {
|
|
439
|
-
throw new McpError(ErrorCode.InvalidParams, `Checklist "${checkListName}" not found`);
|
|
440
|
-
}
|
|
441
|
-
// Add the check item to the checklist
|
|
442
|
-
const itemResponse = await this.axiosInstance.post(`/checklists/${targetChecklist.id}/checkItems`, {
|
|
443
|
-
name: text,
|
|
444
|
-
});
|
|
445
|
-
return this.convertToCheckListItem(itemResponse.data, targetChecklist.id);
|
|
446
|
-
}
|
|
447
|
-
async findChecklistItemsByDescription(description, boardId) {
|
|
448
|
-
const effectiveBoardId = boardId || this.activeConfig.boardId;
|
|
449
|
-
if (!effectiveBoardId) {
|
|
450
|
-
throw new McpError(ErrorCode.InvalidParams, 'No board ID provided and no active board set');
|
|
451
|
-
}
|
|
452
|
-
// Get all checklists from the board directly
|
|
453
|
-
const response = await this.axiosInstance.get(`/boards/${effectiveBoardId}/checklists`);
|
|
454
|
-
const matchingItems = [];
|
|
455
|
-
const searchTerm = description.toLowerCase();
|
|
456
|
-
for (const checklist of response.data) {
|
|
457
|
-
for (const checkItem of checklist.checkItems) {
|
|
458
|
-
if (checkItem.name.toLowerCase().includes(searchTerm)) {
|
|
459
|
-
matchingItems.push(this.convertToCheckListItem(checkItem, checklist.id));
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return matchingItems;
|
|
464
|
-
}
|
|
465
|
-
async getAcceptanceCriteria(boardId) {
|
|
466
|
-
return this.getChecklistItems('Acceptance Criteria', boardId);
|
|
467
|
-
}
|
|
468
|
-
async getChecklistByName(name, boardId) {
|
|
469
|
-
const effectiveBoardId = boardId || this.activeConfig.boardId;
|
|
470
|
-
if (!effectiveBoardId) {
|
|
471
|
-
throw new McpError(ErrorCode.InvalidParams, 'No board ID provided and no active board set');
|
|
472
|
-
}
|
|
473
|
-
// Get all checklists from the board directly
|
|
474
|
-
const response = await this.axiosInstance.get(`/boards/${effectiveBoardId}/checklists`);
|
|
475
|
-
const targetChecklist = response.data.find(checklist => checklist.name.toLowerCase() === name.toLowerCase());
|
|
476
|
-
if (targetChecklist) {
|
|
477
|
-
return this.convertToCheckList(targetChecklist);
|
|
478
|
-
}
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
formatCardAsMarkdown(card) {
|
|
482
|
-
let markdown = '';
|
|
483
|
-
// Title and basic info
|
|
484
|
-
markdown += `# ${card.name}\n\n`;
|
|
485
|
-
// Board and List context
|
|
486
|
-
if (card.board && card.list) {
|
|
487
|
-
markdown += `📍 **Board**: [${card.board.name}](${card.board.url}) > **List**: ${card.list.name}\n\n`;
|
|
488
|
-
}
|
|
489
|
-
// Labels
|
|
490
|
-
if (card.labels && card.labels.length > 0) {
|
|
491
|
-
markdown += `## 🏷️ Labels\n`;
|
|
492
|
-
card.labels.forEach(label => {
|
|
493
|
-
markdown += `- \`${label.color}\` ${label.name || '(no name)'}\n`;
|
|
494
|
-
});
|
|
495
|
-
markdown += '\n';
|
|
496
|
-
}
|
|
497
|
-
// Due date
|
|
498
|
-
if (card.due) {
|
|
499
|
-
const dueDate = new Date(card.due);
|
|
500
|
-
const status = card.dueComplete ? '✅ Complete' : '⏰ Due';
|
|
501
|
-
markdown += `## 📅 Due Date\n${status}: ${dueDate.toLocaleString()}\n\n`;
|
|
502
|
-
}
|
|
503
|
-
// Members
|
|
504
|
-
if (card.members && card.members.length > 0) {
|
|
505
|
-
markdown += `## 👥 Members\n`;
|
|
506
|
-
card.members.forEach(member => {
|
|
507
|
-
markdown += `- @${member.username} (${member.fullName})\n`;
|
|
508
|
-
});
|
|
509
|
-
markdown += '\n';
|
|
510
|
-
}
|
|
511
|
-
// Description
|
|
512
|
-
if (card.desc) {
|
|
513
|
-
markdown += `## 📝 Description\n`;
|
|
514
|
-
markdown += `${card.desc}\n\n`;
|
|
515
|
-
// Parse for inline images (Trello uses markdown-like syntax)
|
|
516
|
-
// Look for patterns like 
|
|
517
|
-
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
518
|
-
const images = card.desc.match(imageRegex);
|
|
519
|
-
if (images) {
|
|
520
|
-
markdown += `### Inline Images in Description\n`;
|
|
521
|
-
images.forEach((img, index) => {
|
|
522
|
-
const match = img.match(/!\[([^\]]*)\]\(([^)]+)\)/);
|
|
523
|
-
if (match) {
|
|
524
|
-
markdown += `${index + 1}. ${match[1] || 'Image'}: ${match[2]}\n`;
|
|
525
|
-
}
|
|
526
|
-
});
|
|
527
|
-
markdown += '\n';
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// Checklists
|
|
531
|
-
if (card.checklists && card.checklists.length > 0) {
|
|
532
|
-
markdown += `## ✅ Checklists\n`;
|
|
533
|
-
card.checklists.forEach(checklist => {
|
|
534
|
-
const completed = checklist.checkItems.filter(item => item.state === 'complete').length;
|
|
535
|
-
const total = checklist.checkItems.length;
|
|
536
|
-
markdown += `### ${checklist.name} (${completed}/${total})\n`;
|
|
537
|
-
// Sort by position
|
|
538
|
-
const sortedItems = [...checklist.checkItems].sort((a, b) => a.pos - b.pos);
|
|
539
|
-
sortedItems.forEach(item => {
|
|
540
|
-
const checkbox = item.state === 'complete' ? '[x]' : '[ ]';
|
|
541
|
-
markdown += `- ${checkbox} ${item.name}`;
|
|
542
|
-
if (item.due) {
|
|
543
|
-
const itemDue = new Date(item.due);
|
|
544
|
-
markdown += ` (Due: ${itemDue.toLocaleDateString()})`;
|
|
545
|
-
}
|
|
546
|
-
if (item.idMember) {
|
|
547
|
-
const member = card.members?.find(m => m.id === item.idMember);
|
|
548
|
-
if (member) {
|
|
549
|
-
markdown += ` - @${member.username}`;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
markdown += '\n';
|
|
553
|
-
});
|
|
554
|
-
markdown += '\n';
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
// Attachments
|
|
558
|
-
if (card.attachments && card.attachments.length > 0) {
|
|
559
|
-
markdown += `## 📎 Attachments (${card.attachments.length})\n`;
|
|
560
|
-
card.attachments.forEach((attachment, index) => {
|
|
561
|
-
markdown += `### ${index + 1}. ${attachment.name}\n`;
|
|
562
|
-
markdown += `- **URL**: ${attachment.url}\n`;
|
|
563
|
-
if (attachment.fileName) {
|
|
564
|
-
markdown += `- **File**: ${attachment.fileName}`;
|
|
565
|
-
if (attachment.bytes) {
|
|
566
|
-
const size = this.formatFileSize(attachment.bytes);
|
|
567
|
-
markdown += ` (${size})`;
|
|
568
|
-
}
|
|
569
|
-
markdown += '\n';
|
|
570
|
-
}
|
|
571
|
-
if (attachment.mimeType) {
|
|
572
|
-
markdown += `- **Type**: ${attachment.mimeType}\n`;
|
|
573
|
-
}
|
|
574
|
-
markdown += `- **Added**: ${new Date(attachment.date).toLocaleString()}\n`;
|
|
575
|
-
// Image preview
|
|
576
|
-
if (attachment.previews && attachment.previews.length > 0) {
|
|
577
|
-
const preview = attachment.previews[0];
|
|
578
|
-
markdown += `- **Preview**: \n`;
|
|
579
|
-
}
|
|
580
|
-
markdown += '\n';
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
// Comments
|
|
584
|
-
if (card.comments && card.comments.length > 0) {
|
|
585
|
-
markdown += `## 💬 Comments (${card.comments.length})\n`;
|
|
586
|
-
card.comments.forEach(comment => {
|
|
587
|
-
const date = new Date(comment.date);
|
|
588
|
-
markdown += `### ${comment.memberCreator.fullName} (@${comment.memberCreator.username}) - ${date.toLocaleString()}\n`;
|
|
589
|
-
markdown += `${comment.data.text}\n\n`;
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
// Statistics
|
|
593
|
-
if (card.badges) {
|
|
594
|
-
markdown += `## 📊 Statistics\n`;
|
|
595
|
-
if (card.badges.checkItems > 0) {
|
|
596
|
-
markdown += `- **Checklist Items**: ${card.badges.checkItemsChecked}/${card.badges.checkItems} completed\n`;
|
|
597
|
-
}
|
|
598
|
-
if (card.badges.comments > 0) {
|
|
599
|
-
markdown += `- **Comments**: ${card.badges.comments}\n`;
|
|
600
|
-
}
|
|
601
|
-
if (card.badges.attachments > 0) {
|
|
602
|
-
markdown += `- **Attachments**: ${card.badges.attachments}\n`;
|
|
603
|
-
}
|
|
604
|
-
if (card.badges.votes > 0) {
|
|
605
|
-
markdown += `- **Votes**: ${card.badges.votes}\n`;
|
|
606
|
-
}
|
|
607
|
-
markdown += '\n';
|
|
608
|
-
}
|
|
609
|
-
// Links
|
|
610
|
-
markdown += `## 🔗 Links\n`;
|
|
611
|
-
markdown += `- **Card URL**: ${card.url}\n`;
|
|
612
|
-
markdown += `- **Short URL**: ${card.shortUrl}\n\n`;
|
|
613
|
-
// Metadata
|
|
614
|
-
markdown += `---\n`;
|
|
615
|
-
markdown += `*Last Activity: ${new Date(card.dateLastActivity).toLocaleString()}*\n`;
|
|
616
|
-
markdown += `*Card ID: ${card.id}*\n`;
|
|
617
|
-
return markdown;
|
|
618
|
-
}
|
|
619
|
-
formatFileSize(bytes) {
|
|
620
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
621
|
-
if (bytes === 0)
|
|
622
|
-
return '0 Bytes';
|
|
623
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
624
|
-
return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + ' ' + sizes[i];
|
|
625
|
-
}
|
|
626
|
-
// Helper methods to convert between Trello types and MCP types
|
|
627
|
-
convertToCheckListItem(trelloItem, parentCheckListId) {
|
|
628
|
-
return {
|
|
629
|
-
id: trelloItem.id,
|
|
630
|
-
text: trelloItem.name,
|
|
631
|
-
complete: trelloItem.state === 'complete',
|
|
632
|
-
parentCheckListId,
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
convertToCheckList(trelloChecklist) {
|
|
636
|
-
const completedItems = trelloChecklist.checkItems.filter(item => item.state === 'complete').length;
|
|
637
|
-
const totalItems = trelloChecklist.checkItems.length;
|
|
638
|
-
const percentComplete = totalItems > 0 ? Math.round((completedItems / totalItems) * 100) : 0;
|
|
639
|
-
return {
|
|
640
|
-
id: trelloChecklist.id,
|
|
641
|
-
name: trelloChecklist.name,
|
|
642
|
-
items: trelloChecklist.checkItems.map(item => this.convertToCheckListItem(item, trelloChecklist.id)),
|
|
643
|
-
percentComplete,
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
const MIME_TYPES = Object.freeze({
|
|
648
|
-
// Images
|
|
649
|
-
'.jpg': 'image/jpeg',
|
|
650
|
-
'.jpeg': 'image/jpeg',
|
|
651
|
-
'.png': 'image/png',
|
|
652
|
-
'.gif': 'image/gif',
|
|
653
|
-
'.bmp': 'image/bmp',
|
|
654
|
-
'.svg': 'image/svg+xml',
|
|
655
|
-
'.webp': 'image/webp',
|
|
656
|
-
'.ico': 'image/x-icon',
|
|
657
|
-
// Documents
|
|
658
|
-
'.pdf': 'application/pdf',
|
|
659
|
-
'.doc': 'application/msword',
|
|
660
|
-
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
661
|
-
'.xls': 'application/vnd.ms-excel',
|
|
662
|
-
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
663
|
-
'.ppt': 'application/vnd.ms-powerpoint',
|
|
664
|
-
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
665
|
-
// Text
|
|
666
|
-
'.txt': 'text/plain',
|
|
667
|
-
'.md': 'text/markdown',
|
|
668
|
-
'.csv': 'text/csv',
|
|
669
|
-
'.log': 'text/plain',
|
|
670
|
-
// Code
|
|
671
|
-
'.html': 'text/html',
|
|
672
|
-
'.htm': 'text/html',
|
|
673
|
-
'.css': 'text/css',
|
|
674
|
-
'.js': 'application/javascript',
|
|
675
|
-
'.mjs': 'application/javascript',
|
|
676
|
-
'.ts': 'application/typescript',
|
|
677
|
-
'.tsx': 'application/typescript',
|
|
678
|
-
'.jsx': 'application/javascript',
|
|
679
|
-
'.json': 'application/json',
|
|
680
|
-
'.xml': 'application/xml',
|
|
681
|
-
'.yaml': 'text/yaml',
|
|
682
|
-
'.yml': 'text/yaml',
|
|
683
|
-
// Archives
|
|
684
|
-
'.zip': 'application/zip',
|
|
685
|
-
'.tar': 'application/x-tar',
|
|
686
|
-
'.gz': 'application/gzip',
|
|
687
|
-
'.rar': 'application/vnd.rar',
|
|
688
|
-
'.7z': 'application/x-7z-compressed',
|
|
689
|
-
// Media
|
|
690
|
-
'.mp3': 'audio/mpeg',
|
|
691
|
-
'.wav': 'audio/wav',
|
|
692
|
-
'.mp4': 'video/mp4',
|
|
693
|
-
'.avi': 'video/x-msvideo',
|
|
694
|
-
'.mov': 'video/quicktime',
|
|
695
|
-
'.wmv': 'video/x-ms-wmv',
|
|
696
|
-
'.flv': 'video/x-flv',
|
|
697
|
-
'.webm': 'video/webm',
|
|
698
|
-
});
|
|
699
|
-
//# sourceMappingURL=trello-client.js.map
|