@vitalyostanin/youtrack-mcp 0.8.0 → 0.9.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-ru.md +11 -0
- package/README.md +11 -0
- package/dist/package.json +1 -1
- package/dist/src/tools/article-search-tools.d.ts +8 -0
- package/dist/src/tools/article-search-tools.js +11 -0
- package/dist/src/tools/attachment-tools.js +12 -2
- package/dist/src/tools/issue-activity-tools.js +11 -0
- package/dist/src/tools/issue-search-tools.d.ts +8 -0
- package/dist/src/tools/issue-search-tools.js +15 -0
- package/dist/src/tools/issue-tools.js +43 -8
- package/dist/src/tools/user-tools.js +15 -3
- package/dist/src/tools/users-activity-tools.d.ts +8 -0
- package/dist/src/tools/users-activity-tools.js +13 -0
- package/dist/src/tools/workitem-report-tools.js +33 -6
- package/dist/src/tools/workitem-tools.js +46 -8
- package/dist/src/utils/file-storage.d.ts +37 -0
- package/dist/src/utils/file-storage.js +64 -0
- package/package.json +1 -1
package/README-ru.md
CHANGED
|
@@ -15,6 +15,7 @@ MCP сервер для полноценной интеграции с YouTrack
|
|
|
15
15
|
- **База знаний** - создание, обновление, поиск статей с иерархической структурой
|
|
16
16
|
- **Доступ к пользователям и проектам** - получение информации о пользователях и проектах
|
|
17
17
|
- **Пакетные операции** - эффективная обработка до 50 задач одновременно
|
|
18
|
+
- **Сохранение в файлы** - сохранение больших результатов инструментов в JSON файлы вместо прямого возврата
|
|
18
19
|
- **Дополнительные возможности** - поддержка Markdown, сворачиваемые секции, конфигурация праздников, алиасы пользователей
|
|
19
20
|
|
|
20
21
|
## Содержание
|
|
@@ -38,6 +39,7 @@ MCP сервер для полноценной интеграции с YouTrack
|
|
|
38
39
|
- [Конфигурация для Claude Code CLI](#конфигурация-для-claude-code-cli)
|
|
39
40
|
- [Конфигурация для VS Code Cline](#конфигурация-для-vs-code-cline)
|
|
40
41
|
- [Инструменты MCP](#инструменты-mcp)
|
|
42
|
+
- [Параметры сохранения файлов](#параметры-сохранения-файлов)
|
|
41
43
|
- [Сервис](#сервис)
|
|
42
44
|
- [Задачи](#задачи)
|
|
43
45
|
- [Связи задач](#связи-задач)
|
|
@@ -272,6 +274,15 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
272
274
|
|
|
273
275
|
Инструменты возвращают либо `structuredContent` (по умолчанию), либо текстовый элемент `content` — в зависимости от `YOUTRACK_USE_STRUCTURED_CONTENT`.
|
|
274
276
|
|
|
277
|
+
### Параметры сохранения файлов
|
|
278
|
+
|
|
279
|
+
Многие инструменты поддерживают опциональные параметры `saveToFile` и `filePath` для работы с большими датасетами:
|
|
280
|
+
|
|
281
|
+
- `saveToFile` — boolean, сохраняет результаты в JSON файл вместо возврата напрямую (полезно для больших датасетов)
|
|
282
|
+
- `filePath` — string, пользовательский путь к файлу (опционально, генерируется автоматически если не указан, директория создается при необходимости)
|
|
283
|
+
|
|
284
|
+
Когда `saveToFile` установлено в `true`, инструменты возвращают метаданные о сохраненном файле вместо полных данных.
|
|
285
|
+
|
|
275
286
|
### Сервис
|
|
276
287
|
|
|
277
288
|
| Tool | Описание | Основные параметры |
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ MCP server for comprehensive YouTrack integration with the following capabilitie
|
|
|
17
17
|
- **Knowledge base** - create, update, search articles with hierarchical structure
|
|
18
18
|
- **User and project access** - retrieve information about users and projects
|
|
19
19
|
- **Batch operations** - efficient processing of up to 50 issues simultaneously
|
|
20
|
+
- **File storage** - save large tool results to JSON files instead of returning directly
|
|
20
21
|
- **Additional features** - Markdown support, folded sections, holiday configuration, user aliases
|
|
21
22
|
|
|
22
23
|
## Table of Contents
|
|
@@ -40,6 +41,7 @@ MCP server for comprehensive YouTrack integration with the following capabilitie
|
|
|
40
41
|
- [Configuration for Claude Code CLI](#configuration-for-claude-code-cli)
|
|
41
42
|
- [Configuration for VS Code Cline](#configuration-for-vs-code-cline)
|
|
42
43
|
- [MCP Tools](#mcp-tools)
|
|
44
|
+
- [File Storage Parameters](#file-storage-parameters)
|
|
43
45
|
- [Service](#service)
|
|
44
46
|
- [Issues](#issues)
|
|
45
47
|
- [Issue Links](#issue-links)
|
|
@@ -276,6 +278,15 @@ To use this MCP server with [Cline](https://github.com/cline/cline) extension in
|
|
|
276
278
|
|
|
277
279
|
Tools return either `structuredContent` (default) or a text `content` item, depending on `YOUTRACK_USE_STRUCTURED_CONTENT`.
|
|
278
280
|
|
|
281
|
+
### File Storage Parameters
|
|
282
|
+
|
|
283
|
+
Many tools support optional `saveToFile` and `filePath` parameters for handling large datasets:
|
|
284
|
+
|
|
285
|
+
- `saveToFile` — boolean, saves results to a JSON file instead of returning directly (useful for large datasets)
|
|
286
|
+
- `filePath` — string, custom file path (optional, auto-generated if not provided, directory created if needed)
|
|
287
|
+
|
|
288
|
+
When `saveToFile` is `true`, tools return metadata about the saved file instead of the full data.
|
|
289
|
+
|
|
279
290
|
### Service
|
|
280
291
|
|
|
281
292
|
| Tool | Description | Main Parameters |
|
package/dist/package.json
CHANGED
|
@@ -6,6 +6,8 @@ export declare const articlesSearchArgs: {
|
|
|
6
6
|
skip: z.ZodDefault<z.ZodNumber>;
|
|
7
7
|
projectId: z.ZodOptional<z.ZodString>;
|
|
8
8
|
parentArticleId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
saveToFile: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
9
11
|
};
|
|
10
12
|
export declare const articlesSearchSchema: z.ZodObject<{
|
|
11
13
|
query: z.ZodString;
|
|
@@ -13,16 +15,22 @@ export declare const articlesSearchSchema: z.ZodObject<{
|
|
|
13
15
|
skip: z.ZodDefault<z.ZodNumber>;
|
|
14
16
|
projectId: z.ZodOptional<z.ZodString>;
|
|
15
17
|
parentArticleId: z.ZodOptional<z.ZodString>;
|
|
18
|
+
saveToFile: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
16
20
|
}, "strip", z.ZodTypeAny, {
|
|
17
21
|
query: string;
|
|
18
22
|
skip: number;
|
|
19
23
|
limit: number;
|
|
24
|
+
filePath?: string | undefined;
|
|
25
|
+
saveToFile?: boolean | undefined;
|
|
20
26
|
projectId?: string | undefined;
|
|
21
27
|
parentArticleId?: string | undefined;
|
|
22
28
|
}, {
|
|
23
29
|
query: string;
|
|
24
30
|
skip?: number | undefined;
|
|
25
31
|
limit?: number | undefined;
|
|
32
|
+
filePath?: string | undefined;
|
|
33
|
+
saveToFile?: boolean | undefined;
|
|
26
34
|
projectId?: string | undefined;
|
|
27
35
|
parentArticleId?: string | undefined;
|
|
28
36
|
}>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { toolSuccess, toolError } from "../utils/tool-response.js";
|
|
3
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
3
4
|
export const articlesSearchArgs = {
|
|
4
5
|
query: z.string().min(2).describe("Search string for articles (e.g., 'API token')"),
|
|
5
6
|
limit: z.number().int().positive().max(200).default(50).describe("Max results per page"),
|
|
6
7
|
skip: z.number().int().nonnegative().default(0).describe("Offset for pagination"),
|
|
7
8
|
projectId: z.string().optional().describe("Filter by project ID"),
|
|
8
9
|
parentArticleId: z.string().optional().describe("Filter by parent article ID"),
|
|
10
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
11
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
9
12
|
};
|
|
10
13
|
export const articlesSearchSchema = z.object(articlesSearchArgs);
|
|
11
14
|
export async function articlesSearchHandler(client, rawInput) {
|
|
@@ -31,6 +34,14 @@ export async function articlesSearchHandler(client, rawInput) {
|
|
|
31
34
|
webUrl: `${baseUrl}/articles/${article.idReadable}`,
|
|
32
35
|
}))
|
|
33
36
|
: data;
|
|
37
|
+
const processedResult = processWithFileStorage(articlesWithLinks, input.saveToFile, input.filePath);
|
|
38
|
+
if (processedResult.savedToFile) {
|
|
39
|
+
return toolSuccess({
|
|
40
|
+
savedToFile: true,
|
|
41
|
+
filePath: processedResult.filePath,
|
|
42
|
+
articleCount: Array.isArray(articlesWithLinks) ? articlesWithLinks.length : 0,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
34
45
|
return toolSuccess(articlesWithLinks);
|
|
35
46
|
}
|
|
36
47
|
catch (error) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
3
4
|
const issueIdArgs = {
|
|
4
5
|
issueId: z.string().min(1).describe("Issue code (e.g., PROJ-123)"),
|
|
6
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
7
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
5
8
|
};
|
|
6
9
|
const issueIdSchema = z.object(issueIdArgs);
|
|
7
10
|
const attachmentGetArgs = {
|
|
@@ -32,8 +35,15 @@ export function registerAttachmentTools(server, client) {
|
|
|
32
35
|
try {
|
|
33
36
|
const payload = issueIdSchema.parse(rawInput);
|
|
34
37
|
const result = await client.listAttachments(payload.issueId);
|
|
35
|
-
const
|
|
36
|
-
|
|
38
|
+
const processedResult = processWithFileStorage(result, payload.saveToFile, payload.filePath);
|
|
39
|
+
if (processedResult.savedToFile) {
|
|
40
|
+
return toolSuccess({
|
|
41
|
+
savedToFile: true,
|
|
42
|
+
filePath: processedResult.filePath,
|
|
43
|
+
attachmentsCount: result.attachments.length,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return toolSuccess(result);
|
|
37
47
|
}
|
|
38
48
|
catch (error) {
|
|
39
49
|
const errorResponse = toolError(error);
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
3
|
import { mapActivityItems } from "../utils/mappers.js";
|
|
4
4
|
import { toIsoDateString } from "../utils/date.js";
|
|
5
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
5
6
|
// Zod args definition
|
|
6
7
|
const issueActivitiesArgs = {
|
|
7
8
|
issueId: z.string().min(1).describe("Issue code (e.g., PROJ-123)"),
|
|
@@ -26,6 +27,8 @@ const issueActivitiesArgs = {
|
|
|
26
27
|
.optional()
|
|
27
28
|
.describe("Maximum number of activities to return (default: no limit, max: 200)"),
|
|
28
29
|
skip: z.number().int().min(0).optional().describe("Number of activities to skip for pagination (default: 0)"),
|
|
30
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
31
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
29
32
|
};
|
|
30
33
|
const issueActivitiesSchema = z.object(issueActivitiesArgs);
|
|
31
34
|
export function registerIssueActivityTools(server, client) {
|
|
@@ -68,6 +71,14 @@ export function registerIssueActivityTools(server, client) {
|
|
|
68
71
|
skip: input.skip ?? 0,
|
|
69
72
|
},
|
|
70
73
|
};
|
|
74
|
+
const processedResult = processWithFileStorage(payload, input.saveToFile, input.filePath);
|
|
75
|
+
if (processedResult.savedToFile) {
|
|
76
|
+
return toolSuccess({
|
|
77
|
+
savedToFile: true,
|
|
78
|
+
filePath: processedResult.filePath,
|
|
79
|
+
activityCount: paginatedActivities.length,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
71
82
|
return toolSuccess(payload);
|
|
72
83
|
}
|
|
73
84
|
catch (error) {
|
|
@@ -9,6 +9,8 @@ export declare const issuesSearchArgs: {
|
|
|
9
9
|
reporter: z.ZodOptional<z.ZodString>;
|
|
10
10
|
state: z.ZodOptional<z.ZodString>;
|
|
11
11
|
type: z.ZodOptional<z.ZodString>;
|
|
12
|
+
saveToFile: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
12
14
|
};
|
|
13
15
|
export declare const issuesSearchSchema: z.ZodDefault<z.ZodObject<{
|
|
14
16
|
query: z.ZodOptional<z.ZodString>;
|
|
@@ -19,6 +21,8 @@ export declare const issuesSearchSchema: z.ZodDefault<z.ZodObject<{
|
|
|
19
21
|
reporter: z.ZodOptional<z.ZodString>;
|
|
20
22
|
state: z.ZodOptional<z.ZodString>;
|
|
21
23
|
type: z.ZodOptional<z.ZodString>;
|
|
24
|
+
saveToFile: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
22
26
|
}, "strip", z.ZodTypeAny, {
|
|
23
27
|
skip: number;
|
|
24
28
|
limit: number;
|
|
@@ -27,6 +31,8 @@ export declare const issuesSearchSchema: z.ZodDefault<z.ZodObject<{
|
|
|
27
31
|
type?: string | undefined;
|
|
28
32
|
query?: string | undefined;
|
|
29
33
|
projects?: string[] | undefined;
|
|
34
|
+
filePath?: string | undefined;
|
|
35
|
+
saveToFile?: boolean | undefined;
|
|
30
36
|
state?: string | undefined;
|
|
31
37
|
}, {
|
|
32
38
|
reporter?: string | undefined;
|
|
@@ -36,6 +42,8 @@ export declare const issuesSearchSchema: z.ZodDefault<z.ZodObject<{
|
|
|
36
42
|
skip?: number | undefined;
|
|
37
43
|
projects?: string[] | undefined;
|
|
38
44
|
limit?: number | undefined;
|
|
45
|
+
filePath?: string | undefined;
|
|
46
|
+
saveToFile?: boolean | undefined;
|
|
39
47
|
state?: string | undefined;
|
|
40
48
|
}>>;
|
|
41
49
|
export declare function issuesSearchHandler(client: YoutrackClient, rawInput: unknown): Promise<{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { toolSuccess, toolError } from "../utils/tool-response.js";
|
|
3
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
3
4
|
export const issuesSearchArgs = {
|
|
4
5
|
query: z
|
|
5
6
|
.string()
|
|
@@ -12,6 +13,8 @@ export const issuesSearchArgs = {
|
|
|
12
13
|
reporter: z.string().optional().describe("Filter by reporter/author login (e.g., 'john.doe' or 'me')"),
|
|
13
14
|
state: z.string().optional().describe("Filter by state/status (e.g., 'Open', 'In Progress', 'Fixed')"),
|
|
14
15
|
type: z.string().optional().describe("Filter by issue type (e.g., 'Bug', 'Feature', 'Task')"),
|
|
16
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
17
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
15
18
|
};
|
|
16
19
|
export const issuesSearchSchema = z
|
|
17
20
|
.object({
|
|
@@ -26,6 +29,8 @@ export const issuesSearchSchema = z
|
|
|
26
29
|
reporter: z.string().optional().describe("Filter by reporter/author login (e.g., 'john.doe' or 'me')"),
|
|
27
30
|
state: z.string().optional().describe("Filter by state/status (e.g., 'Open', 'In Progress', 'Fixed')"),
|
|
28
31
|
type: z.string().optional().describe("Filter by issue type (e.g., 'Bug', 'Feature', 'Task')"),
|
|
32
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
33
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
29
34
|
})
|
|
30
35
|
.default({});
|
|
31
36
|
export async function issuesSearchHandler(client, rawInput) {
|
|
@@ -82,6 +87,16 @@ export async function issuesSearchHandler(client, rawInput) {
|
|
|
82
87
|
byProject: Object.entries(byProject).map(([project, count]) => ({ project, count })),
|
|
83
88
|
items: allIssues,
|
|
84
89
|
};
|
|
90
|
+
const processedResult = processWithFileStorage(result, input.saveToFile, input.filePath);
|
|
91
|
+
if (processedResult.savedToFile) {
|
|
92
|
+
return toolSuccess({
|
|
93
|
+
savedToFile: true,
|
|
94
|
+
filePath: processedResult.filePath,
|
|
95
|
+
total,
|
|
96
|
+
byProject: Object.entries(byProject).map(([project, count]) => ({ project, count })),
|
|
97
|
+
itemCount: allIssues.length,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
85
100
|
return toolSuccess(result);
|
|
86
101
|
}
|
|
87
102
|
catch (error) {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
3
4
|
const issueIdArgs = {
|
|
4
5
|
issueId: z.string().min(1).describe("Issue code (e.g., PROJ-123)"),
|
|
5
6
|
briefOutput: z
|
|
6
7
|
.boolean()
|
|
7
8
|
.optional()
|
|
8
9
|
.describe("Brief mode (default: true). When false, include all available customFields including State."),
|
|
10
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
11
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
9
12
|
};
|
|
10
13
|
const issueIdSchema = z.object(issueIdArgs);
|
|
11
14
|
const issueIdsArgs = {
|
|
@@ -18,6 +21,8 @@ const issueIdsArgs = {
|
|
|
18
21
|
.boolean()
|
|
19
22
|
.optional()
|
|
20
23
|
.describe("Brief mode (default: true). When false, include all available customFields for each issue."),
|
|
24
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
25
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
21
26
|
};
|
|
22
27
|
const issueIdsSchema = z.object(issueIdsArgs);
|
|
23
28
|
const issueCreateArgs = {
|
|
@@ -134,8 +139,15 @@ export function registerIssueTools(server, client) {
|
|
|
134
139
|
try {
|
|
135
140
|
const payload = issueIdSchema.parse(rawInput);
|
|
136
141
|
const comments = await client.getIssueComments(payload.issueId);
|
|
137
|
-
const
|
|
138
|
-
|
|
142
|
+
const processedResult = processWithFileStorage(comments, payload.saveToFile, payload.filePath);
|
|
143
|
+
if (processedResult.savedToFile) {
|
|
144
|
+
return toolSuccess({
|
|
145
|
+
savedToFile: true,
|
|
146
|
+
filePath: processedResult.filePath,
|
|
147
|
+
commentCount: comments.comments.length,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return toolSuccess(comments);
|
|
139
151
|
}
|
|
140
152
|
catch (error) {
|
|
141
153
|
const errorResponse = toolError(error);
|
|
@@ -242,8 +254,16 @@ export function registerIssueTools(server, client) {
|
|
|
242
254
|
try {
|
|
243
255
|
const payload = issueIdsSchema.parse(rawInput);
|
|
244
256
|
const result = await client.getIssues(payload.issueIds);
|
|
245
|
-
const
|
|
246
|
-
|
|
257
|
+
const processedResult = processWithFileStorage(result, payload.saveToFile, payload.filePath);
|
|
258
|
+
if (processedResult.savedToFile) {
|
|
259
|
+
return toolSuccess({
|
|
260
|
+
savedToFile: true,
|
|
261
|
+
filePath: processedResult.filePath,
|
|
262
|
+
issueCount: result.issues.length,
|
|
263
|
+
errorsCount: result.errors?.length ?? 0,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return toolSuccess(result);
|
|
247
267
|
}
|
|
248
268
|
catch (error) {
|
|
249
269
|
const errorResponse = toolError(error);
|
|
@@ -255,8 +275,16 @@ export function registerIssueTools(server, client) {
|
|
|
255
275
|
const payload = issueIdsSchema.parse(rawInput);
|
|
256
276
|
const brief = payload.briefOutput ?? true;
|
|
257
277
|
const result = await client.getIssuesDetails(payload.issueIds, !brief);
|
|
258
|
-
const
|
|
259
|
-
|
|
278
|
+
const processedResult = processWithFileStorage(result, payload.saveToFile, payload.filePath);
|
|
279
|
+
if (processedResult.savedToFile) {
|
|
280
|
+
return toolSuccess({
|
|
281
|
+
savedToFile: true,
|
|
282
|
+
filePath: processedResult.filePath,
|
|
283
|
+
issueCount: result.issues.length,
|
|
284
|
+
errorsCount: result.errors?.length ?? 0,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return toolSuccess(result);
|
|
260
288
|
}
|
|
261
289
|
catch (error) {
|
|
262
290
|
const errorResponse = toolError(error);
|
|
@@ -267,8 +295,15 @@ export function registerIssueTools(server, client) {
|
|
|
267
295
|
try {
|
|
268
296
|
const payload = issueIdsSchema.parse(rawInput);
|
|
269
297
|
const result = await client.getMultipleIssuesComments(payload.issueIds);
|
|
270
|
-
const
|
|
271
|
-
|
|
298
|
+
const processedResult = processWithFileStorage(result, payload.saveToFile, payload.filePath);
|
|
299
|
+
if (processedResult.savedToFile) {
|
|
300
|
+
return toolSuccess({
|
|
301
|
+
savedToFile: true,
|
|
302
|
+
filePath: processedResult.filePath,
|
|
303
|
+
totalComments: Object.values(result.commentsByIssue).flat().length,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return toolSuccess(result);
|
|
272
307
|
}
|
|
273
308
|
catch (error) {
|
|
274
309
|
const errorResponse = toolError(error);
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
3
4
|
const userLookupArgs = {
|
|
4
5
|
login: z.string().min(1).describe("User login"),
|
|
5
6
|
};
|
|
6
7
|
const userLookupSchema = z.object(userLookupArgs);
|
|
7
8
|
export function registerUserTools(server, client) {
|
|
8
|
-
server.tool("users_list", "List all YouTrack users. Note: Returns predefined fields only - id, login, name, fullName, email.", {
|
|
9
|
+
server.tool("users_list", "List all YouTrack users. Note: Returns predefined fields only - id, login, name, fullName, email.", {
|
|
10
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
11
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
12
|
+
}, async (rawInput) => {
|
|
9
13
|
try {
|
|
14
|
+
const payload = rawInput;
|
|
10
15
|
const users = await client.listUsers();
|
|
11
|
-
const
|
|
12
|
-
|
|
16
|
+
const processedResult = processWithFileStorage(users, payload.saveToFile, payload.filePath);
|
|
17
|
+
if (processedResult.savedToFile) {
|
|
18
|
+
return toolSuccess({
|
|
19
|
+
savedToFile: true,
|
|
20
|
+
filePath: processedResult.filePath,
|
|
21
|
+
usersCount: users.users.length,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return toolSuccess(users);
|
|
13
25
|
}
|
|
14
26
|
catch (error) {
|
|
15
27
|
const errorResponse = toolError(error);
|
|
@@ -9,6 +9,8 @@ export declare const usersActivityArgs: {
|
|
|
9
9
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
10
10
|
skip: z.ZodDefault<z.ZodNumber>;
|
|
11
11
|
fields: z.ZodOptional<z.ZodString>;
|
|
12
|
+
saveToFile: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
12
14
|
};
|
|
13
15
|
export declare const usersActivitySchema: z.ZodObject<{
|
|
14
16
|
author: z.ZodString;
|
|
@@ -19,6 +21,8 @@ export declare const usersActivitySchema: z.ZodObject<{
|
|
|
19
21
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
20
22
|
skip: z.ZodDefault<z.ZodNumber>;
|
|
21
23
|
fields: z.ZodOptional<z.ZodString>;
|
|
24
|
+
saveToFile: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
22
26
|
}, "strip", z.ZodTypeAny, {
|
|
23
27
|
author: string;
|
|
24
28
|
skip: number;
|
|
@@ -28,6 +32,8 @@ export declare const usersActivitySchema: z.ZodObject<{
|
|
|
28
32
|
fields?: string | undefined;
|
|
29
33
|
start?: string | number | Date | undefined;
|
|
30
34
|
end?: string | number | Date | undefined;
|
|
35
|
+
filePath?: string | undefined;
|
|
36
|
+
saveToFile?: boolean | undefined;
|
|
31
37
|
}, {
|
|
32
38
|
author: string;
|
|
33
39
|
categories: string;
|
|
@@ -37,6 +43,8 @@ export declare const usersActivitySchema: z.ZodObject<{
|
|
|
37
43
|
start?: string | number | Date | undefined;
|
|
38
44
|
end?: string | number | Date | undefined;
|
|
39
45
|
limit?: number | undefined;
|
|
46
|
+
filePath?: string | undefined;
|
|
47
|
+
saveToFile?: boolean | undefined;
|
|
40
48
|
}>;
|
|
41
49
|
export declare function usersActivityHandler(client: YoutrackClient, rawInput: unknown): Promise<{
|
|
42
50
|
[x: string]: unknown;
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { parseDateInput, toIsoDateString, unixMsToDate, getCurrentDate } from "../utils/date.js";
|
|
3
3
|
import { mapActivityItems } from "../utils/mappers.js";
|
|
4
4
|
import { toolSuccess, toolError } from "../utils/tool-response.js";
|
|
5
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
5
6
|
export const usersActivityArgs = {
|
|
6
7
|
author: z
|
|
7
8
|
.string()
|
|
@@ -39,6 +40,8 @@ export const usersActivityArgs = {
|
|
|
39
40
|
.string()
|
|
40
41
|
.optional()
|
|
41
42
|
.describe("Override fields parameter passed to /api/activities (advanced). Defaults to issue idReadable, author, added/removed, category, timestamps."),
|
|
43
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
44
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
42
45
|
};
|
|
43
46
|
export const usersActivitySchema = z.object(usersActivityArgs);
|
|
44
47
|
export async function usersActivityHandler(client, rawInput) {
|
|
@@ -75,6 +78,16 @@ export async function usersActivityHandler(client, rawInput) {
|
|
|
75
78
|
skip: input.skip,
|
|
76
79
|
},
|
|
77
80
|
};
|
|
81
|
+
const processedResult = processWithFileStorage(payload, input.saveToFile, input.filePath);
|
|
82
|
+
if (processedResult.savedToFile) {
|
|
83
|
+
return toolSuccess({
|
|
84
|
+
savedToFile: true,
|
|
85
|
+
filePath: processedResult.filePath,
|
|
86
|
+
activityCount: mappedActivities.length,
|
|
87
|
+
filters: payload.filters,
|
|
88
|
+
pagination: payload.pagination,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
78
91
|
return toolSuccess(payload);
|
|
79
92
|
}
|
|
80
93
|
catch (error) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
3
4
|
const sharedDate = z.union([z.string().regex(/\d{4}-\d{2}-\d{2}/), z.number(), z.date()]);
|
|
4
5
|
const reportBaseArgs = {
|
|
5
6
|
author: z.string().optional().describe("Work item author login"),
|
|
@@ -12,6 +13,8 @@ const reportBaseArgs = {
|
|
|
12
13
|
holidays: z.array(sharedDate).optional().describe("List of holiday dates"),
|
|
13
14
|
preHolidays: z.array(sharedDate).optional().describe("List of pre-holiday dates"),
|
|
14
15
|
allUsers: z.boolean().optional().describe("Include work items for all users"),
|
|
16
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
17
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
15
18
|
};
|
|
16
19
|
const reportArgsSchema = z.object(reportBaseArgs);
|
|
17
20
|
const reportUsersArgsSchema = z.object({
|
|
@@ -23,8 +26,18 @@ export function registerWorkitemReportTools(server, client) {
|
|
|
23
26
|
try {
|
|
24
27
|
const payload = reportArgsSchema.parse(rawInput);
|
|
25
28
|
const report = await client.generateWorkItemReport(payload);
|
|
26
|
-
const
|
|
27
|
-
|
|
29
|
+
const processedResult = processWithFileStorage({ report }, payload.saveToFile, payload.filePath);
|
|
30
|
+
if (processedResult.savedToFile) {
|
|
31
|
+
return toolSuccess({
|
|
32
|
+
savedToFile: true,
|
|
33
|
+
filePath: processedResult.filePath,
|
|
34
|
+
reportSummary: {
|
|
35
|
+
totalMinutes: report.summary.totalMinutes,
|
|
36
|
+
workDays: report.summary.workDays,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return toolSuccess({ report });
|
|
28
41
|
}
|
|
29
42
|
catch (error) {
|
|
30
43
|
const errorResponse = toolError(error);
|
|
@@ -35,8 +48,15 @@ export function registerWorkitemReportTools(server, client) {
|
|
|
35
48
|
try {
|
|
36
49
|
const payload = reportArgsSchema.parse(rawInput);
|
|
37
50
|
const invalidDays = await client.generateInvalidWorkItemReport(payload);
|
|
38
|
-
const
|
|
39
|
-
|
|
51
|
+
const processedResult = processWithFileStorage({ invalidDays }, payload.saveToFile, payload.filePath);
|
|
52
|
+
if (processedResult.savedToFile) {
|
|
53
|
+
return toolSuccess({
|
|
54
|
+
savedToFile: true,
|
|
55
|
+
filePath: processedResult.filePath,
|
|
56
|
+
invalidDaysCount: invalidDays.length,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return toolSuccess({ invalidDays });
|
|
40
60
|
}
|
|
41
61
|
catch (error) {
|
|
42
62
|
const errorResponse = toolError(error);
|
|
@@ -50,8 +70,15 @@ export function registerWorkitemReportTools(server, client) {
|
|
|
50
70
|
try {
|
|
51
71
|
const payload = reportUsersArgsSchema.parse(rawInput);
|
|
52
72
|
const report = await client.generateUsersWorkItemReports(payload.users, payload);
|
|
53
|
-
const
|
|
54
|
-
|
|
73
|
+
const processedResult = processWithFileStorage(report, payload.saveToFile, payload.filePath);
|
|
74
|
+
if (processedResult.savedToFile) {
|
|
75
|
+
return toolSuccess({
|
|
76
|
+
savedToFile: true,
|
|
77
|
+
filePath: processedResult.filePath,
|
|
78
|
+
usersCount: report.reports.length,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return toolSuccess(report);
|
|
55
82
|
}
|
|
56
83
|
catch (error) {
|
|
57
84
|
const errorResponse = toolError(error);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { mapWorkItem, mapWorkItems } from "../utils/mappers.js";
|
|
3
3
|
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
4
|
+
import { processWithFileStorage } from "../utils/file-storage.js";
|
|
4
5
|
const isoDate = z
|
|
5
6
|
.string()
|
|
6
7
|
.regex(/\d{4}-\d{2}-\d{2}/)
|
|
@@ -12,6 +13,8 @@ const baseFilterArgs = {
|
|
|
12
13
|
startDate: dateInput.optional().describe("Period start date"),
|
|
13
14
|
endDate: dateInput.optional().describe("Period end date"),
|
|
14
15
|
allUsers: z.boolean().optional().describe("Get work items for all users"),
|
|
16
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
17
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
15
18
|
};
|
|
16
19
|
const workItemsListSchema = z.object(baseFilterArgs);
|
|
17
20
|
const workItemsForUsersArgs = {
|
|
@@ -93,6 +96,8 @@ const workItemsReportSchema = z.object(workItemsReportArgs);
|
|
|
93
96
|
const workItemsRecentArgs = {
|
|
94
97
|
users: z.array(z.string().min(1)).optional().describe("User logins (defaults to current user)"),
|
|
95
98
|
limit: z.number().int().positive().max(200).optional().describe("Maximum number of items (default 50)"),
|
|
99
|
+
saveToFile: z.boolean().optional().describe("Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts."),
|
|
100
|
+
filePath: z.string().optional().describe("Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist."),
|
|
96
101
|
};
|
|
97
102
|
const workItemsRecentSchema = z.object(workItemsRecentArgs);
|
|
98
103
|
export function registerWorkitemTools(server, client) {
|
|
@@ -100,8 +105,16 @@ export function registerWorkitemTools(server, client) {
|
|
|
100
105
|
try {
|
|
101
106
|
const payload = workItemsListSchema.parse(rawInput);
|
|
102
107
|
const items = await client.listWorkItems(payload);
|
|
103
|
-
const
|
|
104
|
-
|
|
108
|
+
const result = { items: mapWorkItems(items) };
|
|
109
|
+
const processedResult = processWithFileStorage(result, payload.saveToFile, payload.filePath);
|
|
110
|
+
if (processedResult.savedToFile) {
|
|
111
|
+
return toolSuccess({
|
|
112
|
+
savedToFile: true,
|
|
113
|
+
filePath: processedResult.filePath,
|
|
114
|
+
itemCount: items.length,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return toolSuccess(result);
|
|
105
118
|
}
|
|
106
119
|
catch (error) {
|
|
107
120
|
const errorResponse = toolError(error);
|
|
@@ -112,8 +125,16 @@ export function registerWorkitemTools(server, client) {
|
|
|
112
125
|
try {
|
|
113
126
|
const payload = workItemsListSchema.parse(rawInput);
|
|
114
127
|
const items = await client.listAllUsersWorkItems(payload);
|
|
115
|
-
const
|
|
116
|
-
|
|
128
|
+
const result = { items: mapWorkItems(items) };
|
|
129
|
+
const processedResult = processWithFileStorage(result, payload.saveToFile, payload.filePath);
|
|
130
|
+
if (processedResult.savedToFile) {
|
|
131
|
+
return toolSuccess({
|
|
132
|
+
savedToFile: true,
|
|
133
|
+
filePath: processedResult.filePath,
|
|
134
|
+
itemCount: items.length,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return toolSuccess(result);
|
|
117
138
|
}
|
|
118
139
|
catch (error) {
|
|
119
140
|
const errorResponse = toolError(error);
|
|
@@ -124,8 +145,17 @@ export function registerWorkitemTools(server, client) {
|
|
|
124
145
|
try {
|
|
125
146
|
const payload = workItemsUsersSchema.parse(rawInput);
|
|
126
147
|
const items = await client.getWorkItemsForUsers(payload.users, payload);
|
|
127
|
-
const
|
|
128
|
-
|
|
148
|
+
const result = { items: mapWorkItems(items), users: payload.users };
|
|
149
|
+
const processedResult = processWithFileStorage(result, payload.saveToFile, payload.filePath);
|
|
150
|
+
if (processedResult.savedToFile) {
|
|
151
|
+
return toolSuccess({
|
|
152
|
+
savedToFile: true,
|
|
153
|
+
filePath: processedResult.filePath,
|
|
154
|
+
itemCount: items.length,
|
|
155
|
+
users: payload.users,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return toolSuccess(result);
|
|
129
159
|
}
|
|
130
160
|
catch (error) {
|
|
131
161
|
const errorResponse = toolError(error);
|
|
@@ -247,8 +277,16 @@ export function registerWorkitemTools(server, client) {
|
|
|
247
277
|
try {
|
|
248
278
|
const payload = workItemsRecentSchema.parse(rawInput);
|
|
249
279
|
const items = await client.listRecentWorkItems(payload);
|
|
250
|
-
const
|
|
251
|
-
|
|
280
|
+
const result = { items: mapWorkItems(items), count: items.length };
|
|
281
|
+
const processedResult = processWithFileStorage(result, payload.saveToFile, payload.filePath);
|
|
282
|
+
if (processedResult.savedToFile) {
|
|
283
|
+
return toolSuccess({
|
|
284
|
+
savedToFile: true,
|
|
285
|
+
filePath: processedResult.filePath,
|
|
286
|
+
itemCount: items.length,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return toolSuccess(result);
|
|
252
290
|
}
|
|
253
291
|
catch (error) {
|
|
254
292
|
const errorResponse = toolError(error);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface FileStorageOptions {
|
|
2
|
+
data: unknown;
|
|
3
|
+
filePath?: string;
|
|
4
|
+
baseDir?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Saves data to a JSON file and returns the file path
|
|
8
|
+
*/
|
|
9
|
+
export declare function saveDataToFile(options: FileStorageOptions): string;
|
|
10
|
+
/**
|
|
11
|
+
* Common file storage arguments for tools
|
|
12
|
+
*/
|
|
13
|
+
export declare const fileStorageArgs: {
|
|
14
|
+
saveToFile: {
|
|
15
|
+
type: "boolean";
|
|
16
|
+
optional: boolean;
|
|
17
|
+
describe: string;
|
|
18
|
+
};
|
|
19
|
+
filePath: {
|
|
20
|
+
type: "string";
|
|
21
|
+
optional: boolean;
|
|
22
|
+
describe: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Result of file storage operation
|
|
27
|
+
*/
|
|
28
|
+
export interface FileStorageResult<T> {
|
|
29
|
+
data: T;
|
|
30
|
+
savedToFile?: boolean;
|
|
31
|
+
filePath?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Processes tool result with optional file storage
|
|
35
|
+
*/
|
|
36
|
+
export declare function processWithFileStorage<T>(data: T, saveToFile?: boolean, filePath?: string): FileStorageResult<T>;
|
|
37
|
+
//# sourceMappingURL=file-storage.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Saves data to a JSON file and returns the file path
|
|
5
|
+
*/
|
|
6
|
+
export function saveDataToFile(options) {
|
|
7
|
+
const { data, filePath, baseDir = "data" } = options;
|
|
8
|
+
let finalPath;
|
|
9
|
+
if (filePath) {
|
|
10
|
+
// Use explicit file path
|
|
11
|
+
finalPath = filePath;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
// Generate unique file path
|
|
15
|
+
const timestamp = Date.now();
|
|
16
|
+
const randomId = Math.random().toString(36).substring(2, 8);
|
|
17
|
+
const fileName = `youtrack-data-${timestamp}-${randomId}.json`;
|
|
18
|
+
finalPath = join(baseDir, fileName);
|
|
19
|
+
}
|
|
20
|
+
// Ensure directory exists
|
|
21
|
+
const dir = dirname(finalPath);
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
// Check if file already exists
|
|
24
|
+
if (existsSync(finalPath)) {
|
|
25
|
+
throw new Error(`File already exists: ${finalPath}. Choose a different file path or remove the existing file.`);
|
|
26
|
+
}
|
|
27
|
+
// Write data to file
|
|
28
|
+
const jsonData = JSON.stringify(data, null, 2);
|
|
29
|
+
writeFileSync(finalPath, jsonData, "utf-8");
|
|
30
|
+
return finalPath;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Common file storage arguments for tools
|
|
34
|
+
*/
|
|
35
|
+
export const fileStorageArgs = {
|
|
36
|
+
saveToFile: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
optional: true,
|
|
39
|
+
describe: "Save results to a file instead of returning them directly. Useful for large datasets that can be analyzed by scripts.",
|
|
40
|
+
},
|
|
41
|
+
filePath: {
|
|
42
|
+
type: "string",
|
|
43
|
+
optional: true,
|
|
44
|
+
describe: "Explicit path to save the file (optional, auto-generated if not provided). Directory will be created if it doesn't exist.",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Processes tool result with optional file storage
|
|
49
|
+
*/
|
|
50
|
+
export function processWithFileStorage(data, saveToFile, filePath) {
|
|
51
|
+
if (saveToFile) {
|
|
52
|
+
const savedPath = saveDataToFile({
|
|
53
|
+
data,
|
|
54
|
+
filePath,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
data,
|
|
58
|
+
savedToFile: true,
|
|
59
|
+
filePath: savedPath,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return { data };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=file-storage.js.map
|