@mmtr-tech/yandex-tracker-mcp 0.1.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/LICENSE +21 -0
- package/README.md +142 -0
- package/README.ru.md +142 -0
- package/dist/client/tracker-client.d.ts +185 -0
- package/dist/client/tracker-client.js +459 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +39 -0
- package/dist/tools/boards.d.ts +2 -0
- package/dist/tools/boards.js +46 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.js +97 -0
- package/dist/tools/issue-extras.d.ts +2 -0
- package/dist/tools/issue-extras.js +256 -0
- package/dist/tools/issues.d.ts +2 -0
- package/dist/tools/issues.js +437 -0
- package/dist/tools/queues.d.ts +2 -0
- package/dist/tools/queues.js +103 -0
- package/dist/tools/users.d.ts +2 -0
- package/dist/tools/users.js +91 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/truncate.d.ts +20 -0
- package/dist/utils/truncate.js +38 -0
- package/package.json +60 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import https from 'https';
|
|
3
|
+
const DEFAULT_BASE_URL = 'https://api.tracker.yandex.net/v3';
|
|
4
|
+
/**
|
|
5
|
+
* HTTP client for Yandex Tracker API v3 (OAuth + X-Org-ID / Yandex 360).
|
|
6
|
+
*/
|
|
7
|
+
export class TrackerClient {
|
|
8
|
+
http;
|
|
9
|
+
config;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
const httpsAgent = new https.Agent({
|
|
13
|
+
rejectUnauthorized: config.sslVerify ?? true,
|
|
14
|
+
});
|
|
15
|
+
this.http = axios.create({
|
|
16
|
+
baseURL: config.baseUrl.replace(/\/$/, ''),
|
|
17
|
+
timeout: config.timeoutMs ?? 30_000,
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `OAuth ${config.token}`,
|
|
20
|
+
'X-Org-ID': config.orgId,
|
|
21
|
+
Accept: 'application/json',
|
|
22
|
+
},
|
|
23
|
+
httpsAgent,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async getMyself() {
|
|
27
|
+
try {
|
|
28
|
+
const { data } = await this.http.get('/myself');
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw this.wrapError(error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async listQueues(params = {}) {
|
|
36
|
+
try {
|
|
37
|
+
const { data } = await this.http.get('/queues/', {
|
|
38
|
+
params: {
|
|
39
|
+
perPage: params.perPage,
|
|
40
|
+
page: params.page,
|
|
41
|
+
expand: params.expand,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw this.wrapError(error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async getQueue(params) {
|
|
51
|
+
try {
|
|
52
|
+
const { data } = await this.http.get(`/queues/${encodeURIComponent(params.queueId)}`, {
|
|
53
|
+
params: { expand: params.expand },
|
|
54
|
+
});
|
|
55
|
+
return data;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw this.wrapError(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async getIssue(params) {
|
|
62
|
+
try {
|
|
63
|
+
const { data } = await this.http.get(`/issues/${encodeURIComponent(params.issueId)}`, {
|
|
64
|
+
params: {
|
|
65
|
+
expand: params.expand,
|
|
66
|
+
fields: params.fields,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw this.wrapError(error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async searchIssues(params) {
|
|
76
|
+
try {
|
|
77
|
+
const body = {};
|
|
78
|
+
if (params.query !== undefined)
|
|
79
|
+
body.query = params.query;
|
|
80
|
+
if (params.filter !== undefined)
|
|
81
|
+
body.filter = params.filter;
|
|
82
|
+
if (params.keys !== undefined)
|
|
83
|
+
body.keys = params.keys;
|
|
84
|
+
if (params.queue !== undefined)
|
|
85
|
+
body.queue = params.queue;
|
|
86
|
+
if (params.order !== undefined)
|
|
87
|
+
body.order = params.order;
|
|
88
|
+
const { data } = await this.http.post('/issues/_search', body, {
|
|
89
|
+
params: {
|
|
90
|
+
perPage: params.perPage,
|
|
91
|
+
page: params.page,
|
|
92
|
+
expand: params.expand,
|
|
93
|
+
fields: params.fields,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
return data;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
throw this.wrapError(error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async getIssueComments(params) {
|
|
103
|
+
try {
|
|
104
|
+
const { data } = await this.http.get(`/issues/${encodeURIComponent(params.issueId)}/comments`, {
|
|
105
|
+
params: {
|
|
106
|
+
perPage: params.perPage,
|
|
107
|
+
expand: params.expand,
|
|
108
|
+
id: params.afterId,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
return data;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw this.wrapError(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async getIssueLinks(issueId) {
|
|
118
|
+
try {
|
|
119
|
+
const { data } = await this.http.get(`/issues/${encodeURIComponent(issueId)}/links`);
|
|
120
|
+
return data;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
throw this.wrapError(error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async getIssueTransitions(issueId) {
|
|
127
|
+
try {
|
|
128
|
+
const { data } = await this.http.get(`/issues/${encodeURIComponent(issueId)}/transitions`);
|
|
129
|
+
return data;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
throw this.wrapError(error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async getIssueChangelog(params) {
|
|
136
|
+
try {
|
|
137
|
+
const { data } = await this.http.get(`/issues/${encodeURIComponent(params.issueId)}/changelog`, {
|
|
138
|
+
params: {
|
|
139
|
+
perPage: params.perPage,
|
|
140
|
+
id: params.afterId,
|
|
141
|
+
field: params.field,
|
|
142
|
+
type: params.type,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw this.wrapError(error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async getIssueAttachments(issueId) {
|
|
152
|
+
try {
|
|
153
|
+
const { data } = await this.http.get(`/issues/${encodeURIComponent(issueId)}`, {
|
|
154
|
+
params: { expand: 'attachments' },
|
|
155
|
+
});
|
|
156
|
+
const attachments = data?.attachments;
|
|
157
|
+
return Array.isArray(attachments) ? attachments : [];
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw this.wrapError(error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async getUser(params) {
|
|
164
|
+
try {
|
|
165
|
+
const { data } = await this.http.get(`/users/${encodeURIComponent(params.userId)}`, {
|
|
166
|
+
params: { expand: params.expand },
|
|
167
|
+
});
|
|
168
|
+
return data;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
throw this.wrapError(error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async listUsers(params = {}) {
|
|
175
|
+
try {
|
|
176
|
+
const { data } = await this.http.get('/users', {
|
|
177
|
+
params: {
|
|
178
|
+
perPage: params.perPage,
|
|
179
|
+
id: params.id,
|
|
180
|
+
email: params.email,
|
|
181
|
+
expand: params.expand,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
return data;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw this.wrapError(error);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async createIssue(params) {
|
|
191
|
+
try {
|
|
192
|
+
const body = {
|
|
193
|
+
queue: params.queue,
|
|
194
|
+
summary: params.summary,
|
|
195
|
+
...(params.fields ?? {}),
|
|
196
|
+
};
|
|
197
|
+
if (params.description !== undefined)
|
|
198
|
+
body.description = params.description;
|
|
199
|
+
if (params.type !== undefined)
|
|
200
|
+
body.type = params.type;
|
|
201
|
+
if (params.priority !== undefined)
|
|
202
|
+
body.priority = params.priority;
|
|
203
|
+
if (params.assignee !== undefined)
|
|
204
|
+
body.assignee = params.assignee;
|
|
205
|
+
if (params.parent !== undefined)
|
|
206
|
+
body.parent = params.parent;
|
|
207
|
+
if (params.tags !== undefined)
|
|
208
|
+
body.tags = params.tags;
|
|
209
|
+
if (params.markupType !== undefined)
|
|
210
|
+
body.markupType = params.markupType;
|
|
211
|
+
const { data } = await this.http.post('/issues/', body);
|
|
212
|
+
return data;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
throw this.wrapError(error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async updateIssue(params) {
|
|
219
|
+
try {
|
|
220
|
+
const body = { ...(params.fields ?? {}) };
|
|
221
|
+
if (params.summary !== undefined)
|
|
222
|
+
body.summary = params.summary;
|
|
223
|
+
if (params.description !== undefined)
|
|
224
|
+
body.description = params.description;
|
|
225
|
+
if (params.type !== undefined)
|
|
226
|
+
body.type = params.type;
|
|
227
|
+
if (params.priority !== undefined)
|
|
228
|
+
body.priority = params.priority;
|
|
229
|
+
if (params.assignee !== undefined)
|
|
230
|
+
body.assignee = params.assignee;
|
|
231
|
+
if (params.parent !== undefined)
|
|
232
|
+
body.parent = params.parent;
|
|
233
|
+
if (params.tags !== undefined)
|
|
234
|
+
body.tags = params.tags;
|
|
235
|
+
if (params.markupType !== undefined)
|
|
236
|
+
body.markupType = params.markupType;
|
|
237
|
+
const { data } = await this.http.patch(`/issues/${encodeURIComponent(params.issueId)}`, body, { params: { version: params.version } });
|
|
238
|
+
return data;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
throw this.wrapError(error);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async createIssueLink(params) {
|
|
245
|
+
try {
|
|
246
|
+
const { data } = await this.http.post(`/issues/${encodeURIComponent(params.issueId)}/links`, { relationship: params.relationship, issue: params.linkedIssueId });
|
|
247
|
+
return data;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
throw this.wrapError(error);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async deleteIssueLink(params) {
|
|
254
|
+
try {
|
|
255
|
+
const { data } = await this.http.delete(`/issues/${encodeURIComponent(params.issueId)}/links/${encodeURIComponent(String(params.linkId))}`);
|
|
256
|
+
return data ?? { ok: true };
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
throw this.wrapError(error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async addIssueFollowers(params) {
|
|
263
|
+
try {
|
|
264
|
+
const { data } = await this.http.patch(`/issues/${encodeURIComponent(params.issueId)}`, { followers: { add: params.users } }, { params: { version: params.version } });
|
|
265
|
+
return data;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
throw this.wrapError(error);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async removeIssueFollowers(params) {
|
|
272
|
+
try {
|
|
273
|
+
const { data } = await this.http.patch(`/issues/${encodeURIComponent(params.issueId)}`, { followers: { remove: params.users } }, { params: { version: params.version } });
|
|
274
|
+
return data;
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
throw this.wrapError(error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async getIssueWorklog(params) {
|
|
281
|
+
try {
|
|
282
|
+
const { data } = await this.http.get(`/issues/${encodeURIComponent(params.issueId)}/worklog`, {
|
|
283
|
+
params: {
|
|
284
|
+
perPage: params.perPage,
|
|
285
|
+
id: params.afterId,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
return data;
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
throw this.wrapError(error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async addIssueWorklog(params) {
|
|
295
|
+
try {
|
|
296
|
+
const body = { duration: params.duration };
|
|
297
|
+
if (params.start !== undefined)
|
|
298
|
+
body.start = params.start;
|
|
299
|
+
if (params.comment !== undefined)
|
|
300
|
+
body.comment = params.comment;
|
|
301
|
+
const { data } = await this.http.post(`/issues/${encodeURIComponent(params.issueId)}/worklog`, body);
|
|
302
|
+
return data;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
throw this.wrapError(error);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async getQueueVersions(queueId) {
|
|
309
|
+
try {
|
|
310
|
+
const { data } = await this.http.get(`/queues/${encodeURIComponent(queueId)}/versions`);
|
|
311
|
+
return data;
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
throw this.wrapError(error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async getQueueComponents(queueId) {
|
|
318
|
+
try {
|
|
319
|
+
const { data } = await this.http.get(`/queues/${encodeURIComponent(queueId)}`, {
|
|
320
|
+
params: { expand: 'components' },
|
|
321
|
+
});
|
|
322
|
+
const components = data?.components;
|
|
323
|
+
return Array.isArray(components) ? components : [];
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
throw this.wrapError(error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async countIssues(params) {
|
|
330
|
+
try {
|
|
331
|
+
const body = {};
|
|
332
|
+
if (params.query !== undefined)
|
|
333
|
+
body.query = params.query;
|
|
334
|
+
if (params.filter !== undefined)
|
|
335
|
+
body.filter = params.filter;
|
|
336
|
+
const { data } = await this.http.post('/issues/_count', body);
|
|
337
|
+
return typeof data === 'number' ? { count: data } : data;
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
throw this.wrapError(error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async getIssueChecklist(issueId) {
|
|
344
|
+
try {
|
|
345
|
+
const { data } = await this.http.get(`/issues/${encodeURIComponent(issueId)}/checklistItems`);
|
|
346
|
+
return data;
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
throw this.wrapError(error);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async updateChecklistItem(params) {
|
|
353
|
+
try {
|
|
354
|
+
const body = {};
|
|
355
|
+
if (params.checked !== undefined)
|
|
356
|
+
body.checked = params.checked;
|
|
357
|
+
if (params.text !== undefined)
|
|
358
|
+
body.text = params.text;
|
|
359
|
+
const { data } = await this.http.patch(`/issues/${encodeURIComponent(params.issueId)}/checklistItems/${encodeURIComponent(params.itemId)}`, body);
|
|
360
|
+
return data;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
throw this.wrapError(error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async listBoards(params = {}) {
|
|
367
|
+
try {
|
|
368
|
+
const { data } = await this.http.get('/boards/', {
|
|
369
|
+
params: { perPage: params.perPage, page: params.page },
|
|
370
|
+
});
|
|
371
|
+
return data;
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
throw this.wrapError(error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async listBoardSprints(params) {
|
|
378
|
+
try {
|
|
379
|
+
const { data } = await this.http.get(`/boards/${encodeURIComponent(String(params.boardId))}/sprints`);
|
|
380
|
+
return data;
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
throw this.wrapError(error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async uploadIssueAttachment(params) {
|
|
387
|
+
try {
|
|
388
|
+
const { readFile } = await import('fs/promises');
|
|
389
|
+
const path = await import('path');
|
|
390
|
+
const { Blob } = await import('buffer');
|
|
391
|
+
const fileBytes = await readFile(params.filePath);
|
|
392
|
+
const filename = params.filename || path.basename(params.filePath);
|
|
393
|
+
const form = new FormData();
|
|
394
|
+
form.append('file', new Blob([new Uint8Array(fileBytes)]), filename);
|
|
395
|
+
const { data } = await this.http.post(`/issues/${encodeURIComponent(params.issueId)}/attachments/`, form);
|
|
396
|
+
return data;
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
throw this.wrapError(error);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async addIssueComment(params) {
|
|
403
|
+
try {
|
|
404
|
+
const body = { text: params.text };
|
|
405
|
+
if (params.markupType !== undefined)
|
|
406
|
+
body.markupType = params.markupType;
|
|
407
|
+
if (params.summonees !== undefined)
|
|
408
|
+
body.summonees = params.summonees;
|
|
409
|
+
const { data } = await this.http.post(`/issues/${encodeURIComponent(params.issueId)}/comments`, body);
|
|
410
|
+
return data;
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
throw this.wrapError(error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async executeTransition(params) {
|
|
417
|
+
try {
|
|
418
|
+
const body = { ...(params.fields ?? {}) };
|
|
419
|
+
if (params.comment !== undefined)
|
|
420
|
+
body.comment = params.comment;
|
|
421
|
+
if (params.resolution !== undefined)
|
|
422
|
+
body.resolution = params.resolution;
|
|
423
|
+
const { data } = await this.http.post(`/issues/${encodeURIComponent(params.issueId)}/transitions/${encodeURIComponent(params.transitionId)}/_execute`, body);
|
|
424
|
+
return data;
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
throw this.wrapError(error);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
wrapError(error) {
|
|
431
|
+
if (axios.isAxiosError(error)) {
|
|
432
|
+
const ax = error;
|
|
433
|
+
const status = ax.response?.status;
|
|
434
|
+
const data = ax.response?.data;
|
|
435
|
+
const detail = data?.errorMessages?.join('; ') ||
|
|
436
|
+
(data?.errors ? JSON.stringify(data.errors) : undefined) ||
|
|
437
|
+
data?.message ||
|
|
438
|
+
data?.error ||
|
|
439
|
+
ax.message;
|
|
440
|
+
return new Error(status ? `API ${status}: ${detail}` : `API error: ${detail}`);
|
|
441
|
+
}
|
|
442
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
export function createTrackerClient() {
|
|
446
|
+
const baseUrl = process.env.API_BASE_URL || DEFAULT_BASE_URL;
|
|
447
|
+
const token = process.env.API_TOKEN;
|
|
448
|
+
const orgId = process.env.X_ORG_ID;
|
|
449
|
+
if (!token || !orgId) {
|
|
450
|
+
throw new Error('Missing required environment variables: API_TOKEN and X_ORG_ID');
|
|
451
|
+
}
|
|
452
|
+
return new TrackerClient({
|
|
453
|
+
baseUrl,
|
|
454
|
+
token,
|
|
455
|
+
orgId,
|
|
456
|
+
sslVerify: process.env.API_SSL_VERIFY !== 'false',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
//# sourceMappingURL=tracker-client.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
+
import { createTrackerClient } from './client/tracker-client.js';
|
|
9
|
+
import { registerAllTools } from './tools/index.js';
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
function readPackageVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
|
|
14
|
+
return pkg.version ?? '0.0.0';
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return '0.0.0';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const server = new Server({
|
|
21
|
+
name: 'yandex-tracker-mcp',
|
|
22
|
+
version: readPackageVersion(),
|
|
23
|
+
}, {
|
|
24
|
+
capabilities: {
|
|
25
|
+
tools: {},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const client = createTrackerClient();
|
|
29
|
+
registerAllTools(server, () => client);
|
|
30
|
+
async function main() {
|
|
31
|
+
const transport = new StdioServerTransport();
|
|
32
|
+
await server.connect(transport);
|
|
33
|
+
console.error('@mmtr-tech/yandex-tracker-mcp started');
|
|
34
|
+
}
|
|
35
|
+
main().catch((error) => {
|
|
36
|
+
console.error('Fatal error:', error);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { registerTool } from './index.js';
|
|
3
|
+
const listBoardsSchema = z.object({
|
|
4
|
+
perPage: z.coerce.number().int().positive().max(100).optional(),
|
|
5
|
+
page: z.coerce.number().int().positive().optional(),
|
|
6
|
+
});
|
|
7
|
+
const boardIdSchema = z.object({
|
|
8
|
+
boardId: z.union([z.string().min(1), z.coerce.number()]).describe('Board id'),
|
|
9
|
+
});
|
|
10
|
+
export function registerBoardTools() {
|
|
11
|
+
registerTool({
|
|
12
|
+
zodSchema: listBoardsSchema,
|
|
13
|
+
schema: {
|
|
14
|
+
name: 'list_boards',
|
|
15
|
+
description: 'List boards (GET /boards/). Paginated.',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
perPage: { type: 'number' },
|
|
20
|
+
page: { type: 'number' },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
execute: async (args, getClient) => {
|
|
25
|
+
return await getClient().listBoards(args);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
registerTool({
|
|
29
|
+
zodSchema: boardIdSchema,
|
|
30
|
+
schema: {
|
|
31
|
+
name: 'list_board_sprints',
|
|
32
|
+
description: 'List sprints for a board (GET /boards/{boardId}/sprints).',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
boardId: { description: 'Board id', oneOf: [{ type: 'string' }, { type: 'number' }] },
|
|
37
|
+
},
|
|
38
|
+
required: ['boardId'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
execute: async (args, getClient) => {
|
|
42
|
+
return await getClient().listBoardSprints(args);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=boards.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import type { ZodTypeAny } from 'zod';
|
|
4
|
+
import type { TrackerClient } from '../client/tracker-client.js';
|
|
5
|
+
export interface ToolDefinition {
|
|
6
|
+
schema: Tool;
|
|
7
|
+
zodSchema: ZodTypeAny;
|
|
8
|
+
execute: (args: any, getClient: () => TrackerClient) => Promise<any>;
|
|
9
|
+
}
|
|
10
|
+
export declare function registerTool(definition: ToolDefinition): void;
|
|
11
|
+
export declare function getToolSchemas(): Tool[];
|
|
12
|
+
export declare function getTool(name: string): ToolDefinition | undefined;
|
|
13
|
+
export declare function clearToolRegistry(): void;
|
|
14
|
+
export declare function initToolRegistry(): void;
|
|
15
|
+
export declare function registerAllTools(server: Server, getClient: () => TrackerClient): void;
|
|
16
|
+
/** Validate + execute (unit helpers). */
|
|
17
|
+
export declare function callTool(name: string, args: unknown, getClient: () => TrackerClient): Promise<{
|
|
18
|
+
ok: true;
|
|
19
|
+
data: unknown;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
error: string;
|
|
23
|
+
}>;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { truncatePayload } from '../utils/truncate.js';
|
|
3
|
+
import { registerUserTools } from './users.js';
|
|
4
|
+
import { registerQueueTools } from './queues.js';
|
|
5
|
+
import { registerIssueTools } from './issues.js';
|
|
6
|
+
import { registerIssueExtraTools } from './issue-extras.js';
|
|
7
|
+
import { registerBoardTools } from './boards.js';
|
|
8
|
+
const toolRegistry = new Map();
|
|
9
|
+
export function registerTool(definition) {
|
|
10
|
+
toolRegistry.set(definition.schema.name, definition);
|
|
11
|
+
}
|
|
12
|
+
export function getToolSchemas() {
|
|
13
|
+
return Array.from(toolRegistry.values()).map((t) => t.schema);
|
|
14
|
+
}
|
|
15
|
+
export function getTool(name) {
|
|
16
|
+
return toolRegistry.get(name);
|
|
17
|
+
}
|
|
18
|
+
export function clearToolRegistry() {
|
|
19
|
+
toolRegistry.clear();
|
|
20
|
+
}
|
|
21
|
+
export function initToolRegistry() {
|
|
22
|
+
toolRegistry.clear();
|
|
23
|
+
registerUserTools();
|
|
24
|
+
registerQueueTools();
|
|
25
|
+
registerIssueTools();
|
|
26
|
+
registerIssueExtraTools();
|
|
27
|
+
registerBoardTools();
|
|
28
|
+
}
|
|
29
|
+
function formatZodError(error) {
|
|
30
|
+
return error.issues
|
|
31
|
+
.map((issue) => {
|
|
32
|
+
const path = issue.path.length ? issue.path.join('.') : '(root)';
|
|
33
|
+
return `${path}: ${issue.message}`;
|
|
34
|
+
})
|
|
35
|
+
.join('; ');
|
|
36
|
+
}
|
|
37
|
+
export function registerAllTools(server, getClient) {
|
|
38
|
+
initToolRegistry();
|
|
39
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
40
|
+
return { tools: getToolSchemas() };
|
|
41
|
+
});
|
|
42
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
43
|
+
const { name, arguments: args } = request.params;
|
|
44
|
+
const tool = getTool(name);
|
|
45
|
+
if (!tool) {
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
48
|
+
isError: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const parsed = tool.zodSchema.safeParse(args ?? {});
|
|
52
|
+
if (!parsed.success) {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: 'text',
|
|
57
|
+
text: `Invalid arguments for ${name}: ${formatZodError(parsed.error)}`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
isError: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const result = await tool.execute(parsed.data, getClient);
|
|
65
|
+
const safe = truncatePayload(result);
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: 'text', text: JSON.stringify(safe, null, 2) }],
|
|
68
|
+
isError: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Validate + execute (unit helpers). */
|
|
80
|
+
export async function callTool(name, args, getClient) {
|
|
81
|
+
const tool = getTool(name);
|
|
82
|
+
if (!tool) {
|
|
83
|
+
return { ok: false, error: `Unknown tool: ${name}` };
|
|
84
|
+
}
|
|
85
|
+
const parsed = tool.zodSchema.safeParse(args ?? {});
|
|
86
|
+
if (!parsed.success) {
|
|
87
|
+
return { ok: false, error: `Invalid arguments for ${name}: ${formatZodError(parsed.error)}` };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const data = await tool.execute(parsed.data, getClient);
|
|
91
|
+
return { ok: true, data };
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return { ok: false, error: error.message };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=index.js.map
|