@vitalyostanin/youtrack-mcp 0.2.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 +283 -0
- package/README.md +290 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +13 -0
- package/dist/src/config.d.ts +10 -0
- package/dist/src/config.js +68 -0
- package/dist/src/server.d.ts +8 -0
- package/dist/src/server.js +48 -0
- package/dist/src/tools/article-search-tools.d.ts +4 -0
- package/dist/src/tools/article-search-tools.js +31 -0
- package/dist/src/tools/article-tools.d.ts +4 -0
- package/dist/src/tools/article-tools.js +98 -0
- package/dist/src/tools/attachment-tools.d.ts +4 -0
- package/dist/src/tools/attachment-tools.js +100 -0
- package/dist/src/tools/issue-search-tools.d.ts +4 -0
- package/dist/src/tools/issue-search-tools.js +56 -0
- package/dist/src/tools/issue-tools.d.ts +4 -0
- package/dist/src/tools/issue-tools.js +190 -0
- package/dist/src/tools/project-tools.d.ts +4 -0
- package/dist/src/tools/project-tools.js +35 -0
- package/dist/src/tools/service-info.d.ts +4 -0
- package/dist/src/tools/service-info.js +61 -0
- package/dist/src/tools/user-tools.d.ts +4 -0
- package/dist/src/tools/user-tools.js +46 -0
- package/dist/src/tools/workitem-report-tools.d.ts +4 -0
- package/dist/src/tools/workitem-report-tools.js +62 -0
- package/dist/src/tools/workitem-tools.d.ts +4 -0
- package/dist/src/tools/workitem-tools.js +247 -0
- package/dist/src/types.d.ts +433 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils/date.d.ts +34 -0
- package/dist/src/utils/date.js +147 -0
- package/dist/src/utils/mappers.d.ts +76 -0
- package/dist/src/utils/mappers.js +126 -0
- package/dist/src/utils/tool-response.d.ts +4 -0
- package/dist/src/utils/tool-response.js +61 -0
- package/dist/src/youtrack-client.d.ts +86 -0
- package/dist/src/youtrack-client.js +1220 -0
- package/package.json +65 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const configSchema = z.object({
|
|
3
|
+
YOUTRACK_URL: z.string().url(),
|
|
4
|
+
YOUTRACK_TOKEN: z.string().min(1),
|
|
5
|
+
YOUTRACK_TIMEZONE: z.string().optional(),
|
|
6
|
+
YOUTRACK_HOLIDAYS: z.string().optional(),
|
|
7
|
+
YOUTRACK_PRE_HOLIDAYS: z.string().optional(),
|
|
8
|
+
YOUTRACK_USER_ALIASES: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
export function loadConfig(env = process.env) {
|
|
11
|
+
const parsed = configSchema.safeParse(env);
|
|
12
|
+
if (!parsed.success) {
|
|
13
|
+
const { fieldErrors } = parsed.error.flatten();
|
|
14
|
+
const missingFields = Object.entries(fieldErrors)
|
|
15
|
+
.filter(([, issues]) => Array.isArray(issues) && issues.length > 0)
|
|
16
|
+
.map(([field]) => field);
|
|
17
|
+
const errorMessage = missingFields.length
|
|
18
|
+
? `missing environment variables: ${missingFields.join(", ")}`
|
|
19
|
+
: "invalid configuration";
|
|
20
|
+
throw new Error(`YouTrack configuration error: ${errorMessage}`);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
baseUrl: parsed.data.YOUTRACK_URL,
|
|
24
|
+
token: parsed.data.YOUTRACK_TOKEN,
|
|
25
|
+
timezone: parsed.data.YOUTRACK_TIMEZONE ?? "Europe/Moscow",
|
|
26
|
+
holidays: parsed.data.YOUTRACK_HOLIDAYS
|
|
27
|
+
? parseCsvList(parsed.data.YOUTRACK_HOLIDAYS)
|
|
28
|
+
: undefined,
|
|
29
|
+
preHolidays: parsed.data.YOUTRACK_PRE_HOLIDAYS
|
|
30
|
+
? parseCsvList(parsed.data.YOUTRACK_PRE_HOLIDAYS)
|
|
31
|
+
: undefined,
|
|
32
|
+
userAliases: parsed.data.YOUTRACK_USER_ALIASES
|
|
33
|
+
? parseAliasMap(parsed.data.YOUTRACK_USER_ALIASES)
|
|
34
|
+
: undefined,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function enrichConfigWithRedaction(config) {
|
|
38
|
+
return {
|
|
39
|
+
baseUrl: config.baseUrl,
|
|
40
|
+
hasToken: config.token.length > 0,
|
|
41
|
+
timezone: config.timezone,
|
|
42
|
+
holidays: config.holidays,
|
|
43
|
+
preHolidays: config.preHolidays,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function parseCsvList(value) {
|
|
47
|
+
const items = value
|
|
48
|
+
.split(",")
|
|
49
|
+
.map((item) => item.trim())
|
|
50
|
+
.filter((item) => item.length);
|
|
51
|
+
return items;
|
|
52
|
+
}
|
|
53
|
+
function parseAliasMap(value) {
|
|
54
|
+
const aliasMap = value
|
|
55
|
+
.split(",")
|
|
56
|
+
.map((pair) => pair.trim())
|
|
57
|
+
.filter((pair) => pair.length)
|
|
58
|
+
.reduce((acc, pair) => {
|
|
59
|
+
const [alias, login] = pair.split(":").map((part) => part.trim());
|
|
60
|
+
if (!(alias && login)) {
|
|
61
|
+
throw new Error("Invalid YOUTRACK_USER_ALIASES format. Expected comma-separated list of alias:login pairs.");
|
|
62
|
+
}
|
|
63
|
+
acc[alias] = login;
|
|
64
|
+
return acc;
|
|
65
|
+
}, {});
|
|
66
|
+
return aliasMap;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare class YoutrackServer {
|
|
3
|
+
private readonly server;
|
|
4
|
+
private readonly client;
|
|
5
|
+
constructor();
|
|
6
|
+
connect(transport: Parameters<McpServer["connect"]>[0]): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerServiceInfoTool } from "./tools/service-info.js";
|
|
3
|
+
import { registerIssueTools } from "./tools/issue-tools.js";
|
|
4
|
+
import { registerIssueSearchTools } from "./tools/issue-search-tools.js";
|
|
5
|
+
import { registerWorkitemTools } from "./tools/workitem-tools.js";
|
|
6
|
+
import { registerWorkitemReportTools } from "./tools/workitem-report-tools.js";
|
|
7
|
+
import { registerArticleTools } from "./tools/article-tools.js";
|
|
8
|
+
import { registerArticleSearchTools } from "./tools/article-search-tools.js";
|
|
9
|
+
import { registerUserTools } from "./tools/user-tools.js";
|
|
10
|
+
import { registerProjectTools } from "./tools/project-tools.js";
|
|
11
|
+
import { registerAttachmentTools } from "./tools/attachment-tools.js";
|
|
12
|
+
import { YoutrackClient } from "./youtrack-client.js";
|
|
13
|
+
import { loadConfig } from "./config.js";
|
|
14
|
+
import { initializeTimezone } from "./utils/date.js";
|
|
15
|
+
export class YoutrackServer {
|
|
16
|
+
server;
|
|
17
|
+
client;
|
|
18
|
+
constructor() {
|
|
19
|
+
this.server = new McpServer({
|
|
20
|
+
name: "youtrack-mcp",
|
|
21
|
+
version: "0.2.0",
|
|
22
|
+
}, {
|
|
23
|
+
capabilities: {
|
|
24
|
+
tools: {
|
|
25
|
+
listChanged: false,
|
|
26
|
+
},
|
|
27
|
+
logging: {},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
initializeTimezone(config.timezone);
|
|
32
|
+
this.client = new YoutrackClient(config);
|
|
33
|
+
registerServiceInfoTool(this.server, this.client);
|
|
34
|
+
registerIssueTools(this.server, this.client);
|
|
35
|
+
registerIssueSearchTools(this.server, this.client);
|
|
36
|
+
registerWorkitemTools(this.server, this.client);
|
|
37
|
+
registerWorkitemReportTools(this.server, this.client);
|
|
38
|
+
registerArticleTools(this.server, this.client);
|
|
39
|
+
registerArticleSearchTools(this.server, this.client);
|
|
40
|
+
registerUserTools(this.server, this.client);
|
|
41
|
+
registerProjectTools(this.server, this.client);
|
|
42
|
+
registerAttachmentTools(this.server, this.client);
|
|
43
|
+
}
|
|
44
|
+
async connect(transport) {
|
|
45
|
+
await this.server.connect(transport);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
export declare function registerArticleSearchTools(server: McpServer, client: YoutrackClient): void;
|
|
4
|
+
//# sourceMappingURL=article-search-tools.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
const articleSearchArgs = {
|
|
4
|
+
query: z.string().min(2).describe("Search string for summary and content"),
|
|
5
|
+
projectId: z.string().optional().describe("Filter by project ID"),
|
|
6
|
+
parentArticleId: z.string().optional().describe("Filter by parent article"),
|
|
7
|
+
limit: z.number().int().positive().max(200).optional().describe("Maximum number of results"),
|
|
8
|
+
returnRendered: z.boolean().optional().describe("Return rendered content preview"),
|
|
9
|
+
};
|
|
10
|
+
const articleSearchSchema = z.object(articleSearchArgs);
|
|
11
|
+
export function registerArticleSearchTools(server, client) {
|
|
12
|
+
server.tool("article_search", "Search articles in knowledge base by text. Note: Returns predefined fields only - id, idReadable, summary, usesMarkdown, contentPreview (when returnRendered is true), parentArticle (id, idReadable), project (id, shortName, name). Content field is not included for performance reasons.", articleSearchArgs, async (rawInput) => {
|
|
13
|
+
try {
|
|
14
|
+
const payload = articleSearchSchema.parse(rawInput);
|
|
15
|
+
const articles = await client.searchArticles({
|
|
16
|
+
query: payload.query,
|
|
17
|
+
projectId: payload.projectId,
|
|
18
|
+
parentArticleId: payload.parentArticleId,
|
|
19
|
+
limit: payload.limit,
|
|
20
|
+
returnRendered: payload.returnRendered,
|
|
21
|
+
});
|
|
22
|
+
const response = toolSuccess(articles);
|
|
23
|
+
return response;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const errorResponse = toolError(error);
|
|
27
|
+
return errorResponse;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=article-search-tools.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
export declare function registerArticleTools(server: McpServer, client: YoutrackClient): void;
|
|
4
|
+
//# sourceMappingURL=article-tools.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
const articleLookupArgs = {
|
|
4
|
+
articleId: z.string().min(1).describe("Article ID"),
|
|
5
|
+
};
|
|
6
|
+
const articleListArgs = {
|
|
7
|
+
parentArticleId: z.string().optional().describe("Parent article ID"),
|
|
8
|
+
projectId: z.string().optional().describe("Project ID"),
|
|
9
|
+
};
|
|
10
|
+
const articleCreateArgs = {
|
|
11
|
+
summary: z.string().min(1).describe("Article title"),
|
|
12
|
+
content: z.string().optional().describe("Article content"),
|
|
13
|
+
parentArticleId: z.string().optional().describe("Parent article ID"),
|
|
14
|
+
projectId: z.string().optional().describe("Project ID"),
|
|
15
|
+
usesMarkdown: z.boolean().optional().describe("Use Markdown formatting"),
|
|
16
|
+
returnRendered: z.boolean().optional().describe("Return rendered content preview"),
|
|
17
|
+
};
|
|
18
|
+
const articleUpdateArgs = {
|
|
19
|
+
articleId: z.string().min(1).describe("Article ID"),
|
|
20
|
+
summary: z.string().optional().describe("New title"),
|
|
21
|
+
content: z.string().optional().describe("New content"),
|
|
22
|
+
usesMarkdown: z.boolean().optional().describe("Use Markdown formatting"),
|
|
23
|
+
returnRendered: z.boolean().optional().describe("Return rendered content preview"),
|
|
24
|
+
};
|
|
25
|
+
const articleLookupSchema = z.object(articleLookupArgs);
|
|
26
|
+
const articleListSchema = z.object(articleListArgs);
|
|
27
|
+
const articleCreateSchema = z.object(articleCreateArgs);
|
|
28
|
+
const articleUpdateSchema = z.object(articleUpdateArgs);
|
|
29
|
+
export function registerArticleTools(server, client) {
|
|
30
|
+
server.tool("article_get", "Get YouTrack article by ID. Note: Returns predefined fields only - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name).", articleLookupArgs, async (rawInput) => {
|
|
31
|
+
try {
|
|
32
|
+
const payload = articleLookupSchema.parse(rawInput);
|
|
33
|
+
const article = await client.getArticle(payload.articleId);
|
|
34
|
+
const response = toolSuccess(article);
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const errorResponse = toolError(error);
|
|
39
|
+
return errorResponse;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
server.tool("article_list", "List Knowledge Base articles. Note: Returns predefined fields only - id, idReadable, summary, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name). Content field is not included for performance reasons.", articleListArgs, async (rawInput) => {
|
|
43
|
+
try {
|
|
44
|
+
const payload = articleListSchema.parse(rawInput);
|
|
45
|
+
const articles = await client.listArticles({
|
|
46
|
+
parentArticleId: payload.parentArticleId,
|
|
47
|
+
projectId: payload.projectId,
|
|
48
|
+
});
|
|
49
|
+
const response = toolSuccess(articles);
|
|
50
|
+
return response;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const errorResponse = toolError(error);
|
|
54
|
+
return errorResponse;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
server.tool("article_create", "Create article in YouTrack knowledge base. Note: Response includes predefined fields only - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name).", articleCreateArgs, async (rawInput) => {
|
|
58
|
+
try {
|
|
59
|
+
const payload = articleCreateSchema.parse(rawInput);
|
|
60
|
+
const article = await client.createArticle({
|
|
61
|
+
summary: payload.summary,
|
|
62
|
+
content: payload.content,
|
|
63
|
+
parentArticleId: payload.parentArticleId,
|
|
64
|
+
projectId: payload.projectId,
|
|
65
|
+
usesMarkdown: payload.usesMarkdown,
|
|
66
|
+
returnRendered: payload.returnRendered,
|
|
67
|
+
});
|
|
68
|
+
const response = toolSuccess(article);
|
|
69
|
+
return response;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const errorResponse = toolError(error);
|
|
73
|
+
return errorResponse;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
server.tool("article_update", "Update existing article. Note: Response includes predefined fields only - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name).", articleUpdateArgs, async (rawInput) => {
|
|
77
|
+
try {
|
|
78
|
+
const payload = articleUpdateSchema.parse(rawInput);
|
|
79
|
+
if (payload.summary === undefined && payload.content === undefined) {
|
|
80
|
+
throw new Error("At least one field must be provided for update");
|
|
81
|
+
}
|
|
82
|
+
const article = await client.updateArticle({
|
|
83
|
+
articleId: payload.articleId,
|
|
84
|
+
summary: payload.summary,
|
|
85
|
+
content: payload.content,
|
|
86
|
+
usesMarkdown: payload.usesMarkdown,
|
|
87
|
+
returnRendered: payload.returnRendered,
|
|
88
|
+
});
|
|
89
|
+
const response = toolSuccess(article);
|
|
90
|
+
return response;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const errorResponse = toolError(error);
|
|
94
|
+
return errorResponse;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=article-tools.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
export declare function registerAttachmentTools(server: McpServer, client: YoutrackClient): void;
|
|
4
|
+
//# sourceMappingURL=attachment-tools.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
const issueIdArgs = {
|
|
4
|
+
issueId: z.string().min(1).describe("Issue code (e.g., PROJ-123)"),
|
|
5
|
+
};
|
|
6
|
+
const issueIdSchema = z.object(issueIdArgs);
|
|
7
|
+
const attachmentGetArgs = {
|
|
8
|
+
...issueIdArgs,
|
|
9
|
+
attachmentId: z.string().min(1).describe("Attachment ID"),
|
|
10
|
+
};
|
|
11
|
+
const attachmentGetSchema = z.object(attachmentGetArgs);
|
|
12
|
+
const attachmentUploadArgs = {
|
|
13
|
+
...issueIdArgs,
|
|
14
|
+
filePaths: z
|
|
15
|
+
.array(z.string().min(1))
|
|
16
|
+
.min(1)
|
|
17
|
+
.max(10)
|
|
18
|
+
.describe("Array of absolute file paths to upload (max 10 files)"),
|
|
19
|
+
muteUpdateNotifications: z.boolean().optional().describe("If true, do not send update notifications"),
|
|
20
|
+
};
|
|
21
|
+
const attachmentUploadSchema = z.object(attachmentUploadArgs);
|
|
22
|
+
const attachmentDeleteArgs = {
|
|
23
|
+
...issueIdArgs,
|
|
24
|
+
attachmentId: z.string().min(1).describe("Attachment ID to delete"),
|
|
25
|
+
confirmation: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.describe("Must be explicitly set to 'true' to confirm deletion. This is a required safety parameter for destructive operations."),
|
|
28
|
+
};
|
|
29
|
+
const attachmentDeleteSchema = z.object(attachmentDeleteArgs);
|
|
30
|
+
export function registerAttachmentTools(server, client) {
|
|
31
|
+
server.tool("issue_attachments_list", "Get list of attachments for a YouTrack issue. Returns metadata for all files attached to the issue.", issueIdArgs, async (rawInput) => {
|
|
32
|
+
try {
|
|
33
|
+
const payload = issueIdSchema.parse(rawInput);
|
|
34
|
+
const result = await client.listAttachments(payload.issueId);
|
|
35
|
+
const response = toolSuccess(result);
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const errorResponse = toolError(error);
|
|
40
|
+
return errorResponse;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
server.tool("issue_attachment_get", "Get detailed information about a specific attachment in a YouTrack issue.", attachmentGetArgs, async (rawInput) => {
|
|
44
|
+
try {
|
|
45
|
+
const payload = attachmentGetSchema.parse(rawInput);
|
|
46
|
+
const result = await client.getAttachment(payload.issueId, payload.attachmentId);
|
|
47
|
+
const response = toolSuccess(result);
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const errorResponse = toolError(error);
|
|
52
|
+
return errorResponse;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
server.tool("issue_attachment_download", "Get download information for an attachment. Returns attachment metadata and a signed URL for downloading the file. The signed URL can be used directly without additional authentication.", attachmentGetArgs, async (rawInput) => {
|
|
56
|
+
try {
|
|
57
|
+
const payload = attachmentGetSchema.parse(rawInput);
|
|
58
|
+
const result = await client.getAttachmentDownloadInfo(payload.issueId, payload.attachmentId);
|
|
59
|
+
const response = toolSuccess(result);
|
|
60
|
+
return response;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const errorResponse = toolError(error);
|
|
64
|
+
return errorResponse;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
server.tool("issue_attachment_upload", "Upload one or more files to a YouTrack issue. Files must exist on the local filesystem. Note: Can only attach files to existing issues, not during issue creation.", attachmentUploadArgs, async (rawInput) => {
|
|
68
|
+
try {
|
|
69
|
+
const payload = attachmentUploadSchema.parse(rawInput);
|
|
70
|
+
const result = await client.uploadAttachments({
|
|
71
|
+
issueId: payload.issueId,
|
|
72
|
+
filePaths: payload.filePaths,
|
|
73
|
+
muteUpdateNotifications: payload.muteUpdateNotifications,
|
|
74
|
+
});
|
|
75
|
+
const response = toolSuccess(result);
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const errorResponse = toolError(error);
|
|
80
|
+
return errorResponse;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
server.tool("issue_attachment_delete", "Delete an attachment from a YouTrack issue. IMPORTANT: Requires explicit confirmation via the 'confirmation' parameter to prevent accidental deletion. This is a destructive operation that cannot be undone.", attachmentDeleteArgs, async (rawInput) => {
|
|
84
|
+
try {
|
|
85
|
+
const payload = attachmentDeleteSchema.parse(rawInput);
|
|
86
|
+
const result = await client.deleteAttachment({
|
|
87
|
+
issueId: payload.issueId,
|
|
88
|
+
attachmentId: payload.attachmentId,
|
|
89
|
+
confirmation: payload.confirmation,
|
|
90
|
+
});
|
|
91
|
+
const response = toolSuccess(result);
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const errorResponse = toolError(error);
|
|
96
|
+
return errorResponse;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=attachment-tools.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
export declare function registerIssueSearchTools(server: McpServer, client: YoutrackClient): void;
|
|
4
|
+
//# sourceMappingURL=issue-search-tools.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
const issueSearchByUserActivityArgs = {
|
|
4
|
+
userLogins: z
|
|
5
|
+
.array(z.string().min(1))
|
|
6
|
+
.min(1)
|
|
7
|
+
.describe("Array of user logins to search for activity (updater, mentions, reporter, assignee, commenter)"),
|
|
8
|
+
startDate: z
|
|
9
|
+
.string()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Start date for period filter (YYYY-MM-DD format or timestamp)"),
|
|
12
|
+
endDate: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("End date for period filter (YYYY-MM-DD format or timestamp)"),
|
|
16
|
+
dateFilterMode: z
|
|
17
|
+
.enum(["issue_updated", "user_activity"])
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Date filter mode: 'issue_updated' (default, fast) filters by issue.updated field; 'user_activity' (slow, precise) filters by actual user activity dates including comments, mentions, and field changes history. Use 'user_activity' when you need exact date of user's involvement, e.g., when user was assignee but later changed."),
|
|
20
|
+
limit: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.positive()
|
|
24
|
+
.max(200)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Maximum number of results (default: 100, max: 200)"),
|
|
27
|
+
skip: z
|
|
28
|
+
.number()
|
|
29
|
+
.int()
|
|
30
|
+
.nonnegative()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Number of results to skip for pagination (default: 0)"),
|
|
33
|
+
};
|
|
34
|
+
const issueSearchByUserActivitySchema = z.object(issueSearchByUserActivityArgs);
|
|
35
|
+
export function registerIssueSearchTools(server, client) {
|
|
36
|
+
server.tool("issue_search_by_user_activity", "Search for issues where specified users had activity (updated, mentioned, reported, assigned, commented) within a given time period. Supports two filter modes: 'issue_updated' (default, fast) uses issue.updated field, 'user_activity' (slow, precise) checks actual user activity dates including comments, mentions, and field changes history. Results are sorted by activity time (most recent first). When 'user_activity' mode is used, each issue includes 'lastActivityDate' field with exact timestamp of user's last activity. Supports pagination via limit and skip parameters. Note: Each issue includes predefined fields only - id, idReadable, summary, project (id, shortName, name), parent (id, idReadable), assignee (id, login, name). Description is not included to reduce response size - use issue_details or issues_details to get full details. Custom fields are not included.", issueSearchByUserActivityArgs, async (rawInput) => {
|
|
37
|
+
try {
|
|
38
|
+
const payload = issueSearchByUserActivitySchema.parse(rawInput);
|
|
39
|
+
const results = await client.searchIssuesByUserActivity({
|
|
40
|
+
userLogins: payload.userLogins,
|
|
41
|
+
startDate: payload.startDate,
|
|
42
|
+
endDate: payload.endDate,
|
|
43
|
+
dateFilterMode: payload.dateFilterMode,
|
|
44
|
+
limit: payload.limit,
|
|
45
|
+
skip: payload.skip,
|
|
46
|
+
});
|
|
47
|
+
const response = toolSuccess(results);
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const errorResponse = toolError(error);
|
|
52
|
+
return errorResponse;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=issue-search-tools.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
export declare function registerIssueTools(server: McpServer, client: YoutrackClient): void;
|
|
4
|
+
//# sourceMappingURL=issue-tools.d.ts.map
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { toolError, toolSuccess } from "../utils/tool-response.js";
|
|
3
|
+
const issueIdArgs = {
|
|
4
|
+
issueId: z.string().min(1).describe("Issue code (e.g., PROJ-123)"),
|
|
5
|
+
};
|
|
6
|
+
const issueIdSchema = z.object(issueIdArgs);
|
|
7
|
+
const issueIdsArgs = {
|
|
8
|
+
issueIds: z
|
|
9
|
+
.array(z.string().min(1))
|
|
10
|
+
.min(1)
|
|
11
|
+
.max(50)
|
|
12
|
+
.describe("Array of issue codes (e.g., ['PROJ-123', 'PROJ-124']), max 50"),
|
|
13
|
+
};
|
|
14
|
+
const issueIdsSchema = z.object(issueIdsArgs);
|
|
15
|
+
const issueCreateArgs = {
|
|
16
|
+
projectId: z.string().min(1).describe("Project ID (YouTrack internal id)"),
|
|
17
|
+
summary: z.string().min(1).describe("Brief issue description"),
|
|
18
|
+
description: z.string().optional().describe("Full description"),
|
|
19
|
+
parentIssueId: z.string().optional().describe("Parent issue ID"),
|
|
20
|
+
assigneeLogin: z.string().optional().describe("Assignee login or me"),
|
|
21
|
+
usesMarkdown: z.boolean().optional().describe("Use Markdown formatting"),
|
|
22
|
+
};
|
|
23
|
+
const issueCreateSchema = z.object(issueCreateArgs);
|
|
24
|
+
const issueUpdateArgs = {
|
|
25
|
+
issueId: z.string().min(1).describe("Issue ID or code"),
|
|
26
|
+
summary: z.string().optional().describe("New summary"),
|
|
27
|
+
description: z.string().optional().describe("New description"),
|
|
28
|
+
parentIssueId: z.string().optional().describe("New parent or empty string to remove"),
|
|
29
|
+
usesMarkdown: z.boolean().optional().describe("Use Markdown formatting"),
|
|
30
|
+
};
|
|
31
|
+
const issueUpdateSchema = z.object(issueUpdateArgs);
|
|
32
|
+
const issueAssignArgs = {
|
|
33
|
+
issueId: z.string().min(1).describe("Issue ID or code"),
|
|
34
|
+
assigneeLogin: z.string().min(1).describe("Assignee login or me"),
|
|
35
|
+
};
|
|
36
|
+
const issueAssignSchema = z.object(issueAssignArgs);
|
|
37
|
+
const issueCommentCreateArgs = {
|
|
38
|
+
issueId: z.string().min(1).describe("Issue ID or code"),
|
|
39
|
+
text: z.string().min(1).describe("Comment text"),
|
|
40
|
+
usesMarkdown: z.boolean().optional().describe("Use Markdown formatting"),
|
|
41
|
+
};
|
|
42
|
+
const issueCommentCreateSchema = z.object(issueCommentCreateArgs);
|
|
43
|
+
export function registerIssueTools(server, client) {
|
|
44
|
+
server.tool("issue_lookup", "Get brief information about YouTrack issue. Note: Returns predefined fields only - id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project (id, shortName, name), parent (id, idReadable), assignee (id, login, name). Custom fields are not included.", issueIdArgs, async (rawInput) => {
|
|
45
|
+
try {
|
|
46
|
+
const payload = issueIdSchema.parse(rawInput);
|
|
47
|
+
const issue = await client.getIssue(payload.issueId);
|
|
48
|
+
const response = toolSuccess(issue);
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const errorResponse = toolError(error);
|
|
53
|
+
return errorResponse;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
server.tool("issue_details", "Get detailed information about YouTrack issue. Note: Returns predefined fields only - id, idReadable, summary, description, wikifiedDescription, usesMarkdown, created, updated, resolved, project (id, shortName, name), parent (id, idReadable), assignee (id, login, name), reporter (id, login, name), updater (id, login, name). Custom fields are not included.", issueIdArgs, async (rawInput) => {
|
|
57
|
+
try {
|
|
58
|
+
const payload = issueIdSchema.parse(rawInput);
|
|
59
|
+
const details = await client.getIssueDetails(payload.issueId);
|
|
60
|
+
const response = toolSuccess(details);
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const errorResponse = toolError(error);
|
|
65
|
+
return errorResponse;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
server.tool("issue_comments", "Get issue comments. Note: Returns predefined fields only - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated.", issueIdArgs, async (rawInput) => {
|
|
69
|
+
try {
|
|
70
|
+
const payload = issueIdSchema.parse(rawInput);
|
|
71
|
+
const comments = await client.getIssueComments(payload.issueId);
|
|
72
|
+
const response = toolSuccess(comments);
|
|
73
|
+
return response;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const errorResponse = toolError(error);
|
|
77
|
+
return errorResponse;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
server.tool("issue_create", "Create new issue in YouTrack. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included.", issueCreateArgs, async (rawInput) => {
|
|
81
|
+
try {
|
|
82
|
+
const payload = issueCreateSchema.parse(rawInput);
|
|
83
|
+
const issue = await client.createIssue({
|
|
84
|
+
project: payload.projectId,
|
|
85
|
+
summary: payload.summary,
|
|
86
|
+
description: payload.description,
|
|
87
|
+
parentIssueId: payload.parentIssueId,
|
|
88
|
+
assigneeLogin: payload.assigneeLogin,
|
|
89
|
+
usesMarkdown: payload.usesMarkdown,
|
|
90
|
+
});
|
|
91
|
+
const response = toolSuccess(issue);
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const errorResponse = toolError(error);
|
|
96
|
+
return errorResponse;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
server.tool("issue_update", "Update existing issue. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included.", issueUpdateArgs, async (rawInput) => {
|
|
100
|
+
try {
|
|
101
|
+
const payload = issueUpdateSchema.parse(rawInput);
|
|
102
|
+
if (payload.summary === undefined &&
|
|
103
|
+
payload.description === undefined &&
|
|
104
|
+
payload.parentIssueId === undefined) {
|
|
105
|
+
throw new Error("At least one field must be provided for update");
|
|
106
|
+
}
|
|
107
|
+
const issue = await client.updateIssue({
|
|
108
|
+
issueId: payload.issueId,
|
|
109
|
+
summary: payload.summary,
|
|
110
|
+
description: payload.description,
|
|
111
|
+
parentIssueId: payload.parentIssueId,
|
|
112
|
+
usesMarkdown: payload.usesMarkdown,
|
|
113
|
+
});
|
|
114
|
+
const response = toolSuccess(issue);
|
|
115
|
+
return response;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const errorResponse = toolError(error);
|
|
119
|
+
return errorResponse;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
server.tool("issue_assign", "Assign assignee to issue. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included.", issueAssignArgs, async (rawInput) => {
|
|
123
|
+
try {
|
|
124
|
+
const payload = issueAssignSchema.parse(rawInput);
|
|
125
|
+
const issue = await client.assignIssue({
|
|
126
|
+
issueId: payload.issueId,
|
|
127
|
+
assigneeLogin: payload.assigneeLogin,
|
|
128
|
+
});
|
|
129
|
+
const response = toolSuccess(issue);
|
|
130
|
+
return response;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
const errorResponse = toolError(error);
|
|
134
|
+
return errorResponse;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
server.tool("issue_comment_create", "Add comment to issue. Note: Response includes comment fields - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated.", issueCommentCreateArgs, async (rawInput) => {
|
|
138
|
+
try {
|
|
139
|
+
const payload = issueCommentCreateSchema.parse(rawInput);
|
|
140
|
+
const comment = await client.createIssueComment({
|
|
141
|
+
issueId: payload.issueId,
|
|
142
|
+
text: payload.text,
|
|
143
|
+
usesMarkdown: payload.usesMarkdown,
|
|
144
|
+
});
|
|
145
|
+
const response = toolSuccess(comment);
|
|
146
|
+
return response;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
const errorResponse = toolError(error);
|
|
150
|
+
return errorResponse;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
server.tool("issues_lookup", "Get brief information about multiple YouTrack issues (batch mode, max 50). Note: Returns predefined fields only - id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project (id, shortName, name), parent (id, idReadable), assignee (id, login, name). Custom fields are not included.", issueIdsArgs, async (rawInput) => {
|
|
154
|
+
try {
|
|
155
|
+
const payload = issueIdsSchema.parse(rawInput);
|
|
156
|
+
const result = await client.getIssues(payload.issueIds);
|
|
157
|
+
const response = toolSuccess(result);
|
|
158
|
+
return response;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
const errorResponse = toolError(error);
|
|
162
|
+
return errorResponse;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
server.tool("issues_details", "Get detailed information about multiple YouTrack issues (batch mode, max 50). Note: Returns predefined fields only - id, idReadable, summary, description, wikifiedDescription, usesMarkdown, created, updated, resolved, project (id, shortName, name), parent (id, idReadable), assignee (id, login, name), reporter (id, login, name), updater (id, login, name). Custom fields are not included.", issueIdsArgs, async (rawInput) => {
|
|
166
|
+
try {
|
|
167
|
+
const payload = issueIdsSchema.parse(rawInput);
|
|
168
|
+
const result = await client.getIssuesDetails(payload.issueIds);
|
|
169
|
+
const response = toolSuccess(result);
|
|
170
|
+
return response;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const errorResponse = toolError(error);
|
|
174
|
+
return errorResponse;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
server.tool("issues_comments", "Get comments for multiple YouTrack issues (batch mode, max 50). Note: Returns predefined fields only - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated.", issueIdsArgs, async (rawInput) => {
|
|
178
|
+
try {
|
|
179
|
+
const payload = issueIdsSchema.parse(rawInput);
|
|
180
|
+
const result = await client.getMultipleIssuesComments(payload.issueIds);
|
|
181
|
+
const response = toolSuccess(result);
|
|
182
|
+
return response;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
const errorResponse = toolError(error);
|
|
186
|
+
return errorResponse;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=issue-tools.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
export declare function registerProjectTools(server: McpServer, client: YoutrackClient): void;
|
|
4
|
+
//# sourceMappingURL=project-tools.d.ts.map
|