backlog-mcp-server 0.4.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.ja.md +440 -0
- package/README.md +501 -0
- package/build/backlog/backlogErrorHandler.js +8 -0
- package/build/backlog/customFields.js +16 -0
- package/build/backlog/parseBacklogAPIError.js +38 -0
- package/build/createTranslationHelper.js +28 -0
- package/build/handlers/builders/composeToolHandler.js +26 -0
- package/build/handlers/transformers/wrapWithErrorHandling.js +4 -0
- package/build/handlers/transformers/wrapWithFieldPicking.js +55 -0
- package/build/handlers/transformers/wrapWithTokenLimit.js +21 -0
- package/build/handlers/transformers/wrapWithToolResult.js +39 -0
- package/build/index.js +102 -0
- package/build/registerTools.js +37 -0
- package/build/tools/addIssue.js +94 -0
- package/build/tools/addIssueComment.js +44 -0
- package/build/tools/addProject.js +39 -0
- package/build/tools/addPullRequest.js +65 -0
- package/build/tools/addPullRequestComment.js +52 -0
- package/build/tools/addWiki.js +29 -0
- package/build/tools/countIssues.js +111 -0
- package/build/tools/deleteIssue.js +29 -0
- package/build/tools/deleteProject.js +29 -0
- package/build/tools/downloadDocumentAttachment.js +1 -0
- package/build/tools/dynamicTools/toolsets.js +103 -0
- package/build/tools/getCategories.js +30 -0
- package/build/tools/getCustomFields.js +37 -0
- package/build/tools/getDocument.js +20 -0
- package/build/tools/getDocumentTree.js +20 -0
- package/build/tools/getDocuments.js +25 -0
- package/build/tools/getGitRepositories.js +29 -0
- package/build/tools/getGitRepository.js +41 -0
- package/build/tools/getIssue.js +29 -0
- package/build/tools/getIssueComments.js +45 -0
- package/build/tools/getIssueTypes.js +30 -0
- package/build/tools/getIssues.js +155 -0
- package/build/tools/getMyself.js +14 -0
- package/build/tools/getNotifications.js +35 -0
- package/build/tools/getNotificationsCount.js +20 -0
- package/build/tools/getPriorities.js +13 -0
- package/build/tools/getProject.js +29 -0
- package/build/tools/getProjectList.js +23 -0
- package/build/tools/getPullRequest.js +44 -0
- package/build/tools/getPullRequestComments.js +60 -0
- package/build/tools/getPullRequests.js +65 -0
- package/build/tools/getPullRequestsCount.js +57 -0
- package/build/tools/getResolutions.js +13 -0
- package/build/tools/getSpace.js +14 -0
- package/build/tools/getUsers.js +14 -0
- package/build/tools/getWatchingListCount.js +17 -0
- package/build/tools/getWatchingListItems.js +17 -0
- package/build/tools/getWiki.js +21 -0
- package/build/tools/getWikiPages.js +37 -0
- package/build/tools/getWikisCount.js +29 -0
- package/build/tools/markNotificationAsRead.js +26 -0
- package/build/tools/resetUnreadNotificationCount.js +13 -0
- package/build/tools/tools.js +143 -0
- package/build/tools/updateIssue.js +116 -0
- package/build/tools/updateProject.js +57 -0
- package/build/tools/updatePullRequest.js +68 -0
- package/build/tools/updatePullRequestComment.js +51 -0
- package/build/types/mcp.js +1 -0
- package/build/types/result.js +3 -0
- package/build/types/tool.js +1 -0
- package/build/types/toolsets.js +1 -0
- package/build/types/zod/backlogOutputDefinition.js +468 -0
- package/build/utils/generateFieldsDescription.js +47 -0
- package/build/utils/resolveIdOrKey.js +25 -0
- package/build/utils/runToolSafely.js +18 -0
- package/build/utils/tokenCounter.js +11 -0
- package/build/utils/toolRegistrar.js +12 -0
- package/build/utils/toolsetUtils.js +48 -0
- package/build/utils/wrapServerWithToolRegistry.js +16 -0
- package/build/version.js +1 -0
- package/build/version.template.js +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { countTokens } from '../../utils/tokenCounter.js';
|
|
2
|
+
export function wrapWithTokenLimit(fn, maxTokens) {
|
|
3
|
+
return async (input) => {
|
|
4
|
+
const result = await fn(input);
|
|
5
|
+
if (result == null ||
|
|
6
|
+
typeof result !== 'object' ||
|
|
7
|
+
result.kind == 'error') {
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
const fullText = JSON.stringify(result.data, null, 2);
|
|
11
|
+
const tokenCount = countTokens(fullText);
|
|
12
|
+
if (tokenCount > maxTokens) {
|
|
13
|
+
const roughCut = fullText.slice(0, Math.floor(maxTokens * 4));
|
|
14
|
+
return {
|
|
15
|
+
kind: 'ok',
|
|
16
|
+
data: `${roughCut}\n...(output truncated due to token limit)`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return { kind: 'ok', data: fullText };
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isErrorLike } from '../../types/result.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert SafeResult<T> to CallToolResult
|
|
4
|
+
*/
|
|
5
|
+
export function wrapWithToolResult(fn) {
|
|
6
|
+
return async (input, _extra) => {
|
|
7
|
+
const result = await fn(input);
|
|
8
|
+
if (isErrorLike(result)) {
|
|
9
|
+
return {
|
|
10
|
+
isError: true,
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: 'text',
|
|
14
|
+
text: result.message,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const data = result.data;
|
|
20
|
+
if (typeof data === 'string') {
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: data,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: 'text',
|
|
34
|
+
text: JSON.stringify(data, null, 2),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright (c) 2025 Nulab inc.
|
|
3
|
+
// Licensed under the MIT License.
|
|
4
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
+
import * as backlogjs from 'backlog-js';
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
import { default as env } from 'env-var';
|
|
9
|
+
import yargs from 'yargs';
|
|
10
|
+
import { hideBin } from 'yargs/helpers';
|
|
11
|
+
import { createTranslationHelper } from './createTranslationHelper.js';
|
|
12
|
+
import { registerDyamicTools, registerTools } from './registerTools.js';
|
|
13
|
+
import { dynamicTools } from './tools/dynamicTools/toolsets.js';
|
|
14
|
+
import { createToolRegistrar } from './utils/toolRegistrar.js';
|
|
15
|
+
import { buildToolsetGroup } from './utils/toolsetUtils.js';
|
|
16
|
+
import { wrapServerWithToolRegistry } from './utils/wrapServerWithToolRegistry.js';
|
|
17
|
+
import { VERSION } from './version.js';
|
|
18
|
+
dotenv.config();
|
|
19
|
+
const domain = env.get('BACKLOG_DOMAIN').required().asString();
|
|
20
|
+
const apiKey = env.get('BACKLOG_API_KEY').required().asString();
|
|
21
|
+
const backlog = new backlogjs.Backlog({ host: domain, apiKey: apiKey });
|
|
22
|
+
const argv = yargs(hideBin(process.argv))
|
|
23
|
+
.option('max-tokens', {
|
|
24
|
+
type: 'number',
|
|
25
|
+
describe: 'Maximum number of tokens allowed in the response',
|
|
26
|
+
default: env.get('MAX_TOKENS').default('50000').asIntPositive(),
|
|
27
|
+
})
|
|
28
|
+
.option('optimize-response', {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
describe: 'Enable GraphQL-style response optimization to include only requested fields',
|
|
31
|
+
default: env.get('OPTIMIZE_RESPONSE').default('false').asBool(),
|
|
32
|
+
})
|
|
33
|
+
.option('prefix', {
|
|
34
|
+
type: 'string',
|
|
35
|
+
describe: 'Optional string prefix to prepend to all generated outputs',
|
|
36
|
+
default: env.get('PREFIX').default('').asString(),
|
|
37
|
+
})
|
|
38
|
+
.option('export-translations', {
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
describe: 'Export translations and exit',
|
|
41
|
+
default: false,
|
|
42
|
+
})
|
|
43
|
+
.option('enable-toolsets', {
|
|
44
|
+
type: 'array',
|
|
45
|
+
describe: `Specify which toolsets to enable. Defaults to 'all'.
|
|
46
|
+
Available toolsets:
|
|
47
|
+
- space: Tools for managing Backlog space settings and general information
|
|
48
|
+
- project: Tools for managing projects, categories, custom fields, and issue types
|
|
49
|
+
- issue: Tools for managing issues and their comments
|
|
50
|
+
- wiki: Tools for managing wiki pages
|
|
51
|
+
- git: Tools for managing Git repositories and pull requests
|
|
52
|
+
- notifications: Tools for managing user notifications`,
|
|
53
|
+
default: env.get('ENABLE_TOOLSETS').default('all').asArray(','),
|
|
54
|
+
})
|
|
55
|
+
.option('dynamic-toolsets', {
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
describe: 'Enable dynamic toolsets such as enable_toolset, list_available_toolsets, etc.',
|
|
58
|
+
default: env.get('ENABLE_DYNAMIC_TOOLSETS').default('false').asBool(),
|
|
59
|
+
})
|
|
60
|
+
.parseSync();
|
|
61
|
+
const useFields = argv.optimizeResponse;
|
|
62
|
+
const server = wrapServerWithToolRegistry(new McpServer({
|
|
63
|
+
name: 'backlog',
|
|
64
|
+
description: useFields
|
|
65
|
+
? `You can include only the fields you need using GraphQL-style syntax.
|
|
66
|
+
Start with the example above and customize freely.`
|
|
67
|
+
: undefined,
|
|
68
|
+
version: VERSION,
|
|
69
|
+
}));
|
|
70
|
+
const transHelper = createTranslationHelper();
|
|
71
|
+
const maxTokens = argv.maxTokens;
|
|
72
|
+
const prefix = argv.prefix;
|
|
73
|
+
let enabledToolsets = argv.enableToolsets;
|
|
74
|
+
// If dynamic toolsets are enabled, remove "all" to allow for selective enabling via commands
|
|
75
|
+
if (argv.dynamicToolsets) {
|
|
76
|
+
enabledToolsets = enabledToolsets.filter((a) => a != 'all');
|
|
77
|
+
}
|
|
78
|
+
const mcpOption = { useFields: useFields, maxTokens, prefix };
|
|
79
|
+
const toolsetGroup = buildToolsetGroup(backlog, transHelper, enabledToolsets);
|
|
80
|
+
// Register all tools
|
|
81
|
+
registerTools(server, toolsetGroup, mcpOption);
|
|
82
|
+
// Register dynamic tool management tools if enabled
|
|
83
|
+
if (argv.dynamicToolsets) {
|
|
84
|
+
const registrar = createToolRegistrar(server, toolsetGroup, mcpOption);
|
|
85
|
+
const dynamicToolsetGroup = dynamicTools(registrar, transHelper, toolsetGroup);
|
|
86
|
+
registerDyamicTools(server, dynamicToolsetGroup, prefix);
|
|
87
|
+
}
|
|
88
|
+
if (argv.exportTranslations) {
|
|
89
|
+
const data = transHelper.dump();
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.log(JSON.stringify(data, null, 2));
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
async function main() {
|
|
95
|
+
const transport = new StdioServerTransport();
|
|
96
|
+
await server.connect(transport);
|
|
97
|
+
console.error('Backlog MCP Server running on stdio');
|
|
98
|
+
}
|
|
99
|
+
main().catch((error) => {
|
|
100
|
+
console.error('Fatal error in main():', error);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { backlogErrorHandler } from './backlog/backlogErrorHandler.js';
|
|
2
|
+
import { composeToolHandler } from './handlers/builders/composeToolHandler.js';
|
|
3
|
+
export function registerTools(server, toolsetGroup, options) {
|
|
4
|
+
const { useFields, maxTokens, prefix } = options;
|
|
5
|
+
registerToolsets({
|
|
6
|
+
server,
|
|
7
|
+
toolsetGroup,
|
|
8
|
+
prefix,
|
|
9
|
+
handlerStrategy: (tool) =>
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
composeToolHandler(tool, {
|
|
12
|
+
useFields,
|
|
13
|
+
errorHandler: backlogErrorHandler,
|
|
14
|
+
maxTokens,
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function registerDyamicTools(server, dynamicToolsetGroup, prefix) {
|
|
19
|
+
registerToolsets({
|
|
20
|
+
server,
|
|
21
|
+
toolsetGroup: dynamicToolsetGroup,
|
|
22
|
+
prefix,
|
|
23
|
+
handlerStrategy: (tool) => tool.handler,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function registerToolsets({ server, toolsetGroup, prefix, handlerStrategy, }) {
|
|
27
|
+
for (const toolset of toolsetGroup.toolsets) {
|
|
28
|
+
if (!toolset.enabled) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
for (const tool of toolset.tools) {
|
|
32
|
+
const toolNameWithPrefix = `${prefix}${tool.name}`;
|
|
33
|
+
const handler = handlerStrategy(tool);
|
|
34
|
+
server.registerOnce(toolNameWithPrefix, tool.description, tool.schema.shape, handler);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { IssueSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
3
|
+
import { buildToolSchema } from '../types/tool.js';
|
|
4
|
+
import { customFieldsToPayload } from '../backlog/customFields.js';
|
|
5
|
+
const addIssueSchema = buildToolSchema((t) => ({
|
|
6
|
+
projectId: z.number().describe(t('TOOL_ADD_ISSUE_PROJECT_ID', 'Project ID')),
|
|
7
|
+
summary: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe(t('TOOL_ADD_ISSUE_SUMMARY', 'Summary of the issue')),
|
|
10
|
+
issueTypeId: z
|
|
11
|
+
.number()
|
|
12
|
+
.describe(t('TOOL_ADD_ISSUE_ISSUE_TYPE_ID', 'Issue type ID')),
|
|
13
|
+
priorityId: z
|
|
14
|
+
.number()
|
|
15
|
+
.describe(t('TOOL_ADD_ISSUE_PRIORITY_ID', 'Priority ID')),
|
|
16
|
+
description: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe(t('TOOL_ADD_ISSUE_DESCRIPTION', 'Detailed description of the issue')),
|
|
20
|
+
startDate: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe(t('TOOL_ADD_ISSUE_START_DATE', 'Scheduled start date (yyyy-MM-dd)')),
|
|
24
|
+
dueDate: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe(t('TOOL_ADD_ISSUE_DUE_DATE', 'Scheduled due date (yyyy-MM-dd)')),
|
|
28
|
+
estimatedHours: z
|
|
29
|
+
.number()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe(t('TOOL_ADD_ISSUE_ESTIMATED_HOURS', 'Estimated work hours')),
|
|
32
|
+
actualHours: z
|
|
33
|
+
.number()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe(t('TOOL_ADD_ISSUE_ACTUAL_HOURS', 'Actual work hours')),
|
|
36
|
+
categoryId: z
|
|
37
|
+
.array(z.number())
|
|
38
|
+
.optional()
|
|
39
|
+
.describe(t('TOOL_ADD_ISSUE_CATEGORY_ID', 'Category IDs')),
|
|
40
|
+
versionId: z
|
|
41
|
+
.array(z.number())
|
|
42
|
+
.optional()
|
|
43
|
+
.describe(t('TOOL_ADD_ISSUE_VERSION_ID', 'Version IDs')),
|
|
44
|
+
milestoneId: z
|
|
45
|
+
.array(z.number())
|
|
46
|
+
.optional()
|
|
47
|
+
.describe(t('TOOL_ADD_ISSUE_MILESTONE_ID', 'Milestone IDs')),
|
|
48
|
+
assigneeId: z
|
|
49
|
+
.number()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe(t('TOOL_ADD_ISSUE_ASSIGNEE_ID', 'User ID of the assignee')),
|
|
52
|
+
notifiedUserId: z
|
|
53
|
+
.array(z.number())
|
|
54
|
+
.optional()
|
|
55
|
+
.describe(t('TOOL_ADD_ISSUE_NOTIFIED_USER_ID', 'User IDs to notify')),
|
|
56
|
+
attachmentId: z
|
|
57
|
+
.array(z.number())
|
|
58
|
+
.optional()
|
|
59
|
+
.describe(t('TOOL_ADD_ISSUE_ATTACHMENT_ID', 'Attachment IDs')),
|
|
60
|
+
parentIssueId: z
|
|
61
|
+
.number()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe(t('TOOL_ADD_ISSUE_PARENT_ISSUE_ID', 'Parent issue ID')),
|
|
64
|
+
customFields: z
|
|
65
|
+
.array(z.object({
|
|
66
|
+
id: z
|
|
67
|
+
.number()
|
|
68
|
+
.describe(t('TOOL_ADD_ISSUE_CUSTOM_FIELD_ID', 'The ID of the custom field (e.g., 12345)')),
|
|
69
|
+
value: z.union([z.string().max(255), z.number(), z.array(z.string())]),
|
|
70
|
+
otherValue: z
|
|
71
|
+
.string()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe(t('TOOL_ADD_ISSUE_CUSTOM_FIELD_OTHER_VALUE', 'Other value for list type fields')),
|
|
74
|
+
}))
|
|
75
|
+
.optional()
|
|
76
|
+
.describe(t('TOOL_ADD_ISSUE_CUSTOM_FIELDS', 'List of custom fields to set on the issue')),
|
|
77
|
+
}));
|
|
78
|
+
export const addIssueTool = (backlog, { t }) => {
|
|
79
|
+
return {
|
|
80
|
+
name: 'add_issue',
|
|
81
|
+
description: t('TOOL_ADD_ISSUE_DESCRIPTION', 'Creates a new issue in the specified project.'),
|
|
82
|
+
schema: z.object(addIssueSchema(t)),
|
|
83
|
+
outputSchema: IssueSchema,
|
|
84
|
+
importantFields: ['summary', 'issueKey', 'description', 'createdUser'],
|
|
85
|
+
handler: async ({ customFields, ...params }) => {
|
|
86
|
+
const customFieldPayload = customFieldsToPayload(customFields);
|
|
87
|
+
const finalPayload = {
|
|
88
|
+
...params,
|
|
89
|
+
...customFieldPayload,
|
|
90
|
+
};
|
|
91
|
+
return backlog.postIssue(finalPayload);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildToolSchema } from '../types/tool.js';
|
|
3
|
+
import { IssueCommentSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
4
|
+
import { resolveIdOrKey } from '../utils/resolveIdOrKey.js';
|
|
5
|
+
const addIssueCommentSchema = buildToolSchema((t) => ({
|
|
6
|
+
issueId: z
|
|
7
|
+
.number()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe(t('TOOL_ADD_ISSUE_COMMENT_ID', 'The numeric ID of the issue (e.g., 12345)')),
|
|
10
|
+
issueKey: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(t('TOOL_ADD_ISSUE_COMMENT_KEY', "The key of the issue (e.g., 'PROJ-123')")),
|
|
14
|
+
content: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe(t('TOOL_ADD_ISSUE_COMMENT_CONTENT', 'Comment content')),
|
|
17
|
+
notifiedUserId: z
|
|
18
|
+
.array(z.number())
|
|
19
|
+
.optional()
|
|
20
|
+
.describe(t('TOOL_ADD_ISSUE_COMMENT_NOTIFIED_USER_ID', 'User IDs to notify')),
|
|
21
|
+
attachmentId: z
|
|
22
|
+
.array(z.number())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe(t('TOOL_ADD_ISSUE_COMMENT_ATTACHMENT_ID', 'Attachment IDs')),
|
|
25
|
+
}));
|
|
26
|
+
export const addIssueCommentTool = (backlog, { t }) => {
|
|
27
|
+
return {
|
|
28
|
+
name: 'add_issue_comment',
|
|
29
|
+
description: t('TOOL_ADD_ISSUE_COMMENT_DESCRIPTION', 'Adds a comment to an issue'),
|
|
30
|
+
schema: z.object(addIssueCommentSchema(t)),
|
|
31
|
+
outputSchema: IssueCommentSchema,
|
|
32
|
+
handler: async ({ issueId, issueKey, content, notifiedUserId, attachmentId, }) => {
|
|
33
|
+
const result = resolveIdOrKey('issue', { id: issueId, key: issueKey }, t);
|
|
34
|
+
if (!result.ok) {
|
|
35
|
+
throw result.error;
|
|
36
|
+
}
|
|
37
|
+
return backlog.postIssueComments(result.value, {
|
|
38
|
+
content,
|
|
39
|
+
notifiedUserId,
|
|
40
|
+
attachmentId,
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildToolSchema } from '../types/tool.js';
|
|
3
|
+
import { ProjectSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
4
|
+
const addProjectSchema = buildToolSchema((t) => ({
|
|
5
|
+
name: z.string().describe(t('TOOL_ADD_PROJECT_NAME', 'Project name')),
|
|
6
|
+
key: z.string().describe(t('TOOL_ADD_PROJECT_KEY', 'Project key')),
|
|
7
|
+
chartEnabled: z
|
|
8
|
+
.boolean()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe(t('TOOL_ADD_PROJECT_CHART_ENABLED', 'Whether to enable chart (default: false)')),
|
|
11
|
+
subtaskingEnabled: z
|
|
12
|
+
.boolean()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe(t('TOOL_ADD_PROJECT_SUBTASKING_ENABLED', 'Whether to enable subtasking (default: false)')),
|
|
15
|
+
projectLeaderCanEditProjectLeader: z
|
|
16
|
+
.boolean()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe(t('TOOL_ADD_PROJECT_LEADER_CAN_EDIT', 'Whether project leaders can edit other project leaders (default: false)')),
|
|
19
|
+
textFormattingRule: z
|
|
20
|
+
.enum(['backlog', 'markdown'])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe(t('TOOL_ADD_PROJECT_TEXT_FORMATTING', "Text formatting rule (default: 'backlog')")),
|
|
23
|
+
}));
|
|
24
|
+
export const addProjectTool = (backlog, { t }) => {
|
|
25
|
+
return {
|
|
26
|
+
name: 'add_project',
|
|
27
|
+
description: t('TOOL_ADD_PROJECT_DESCRIPTION', 'Creates a new project'),
|
|
28
|
+
schema: z.object(addProjectSchema(t)),
|
|
29
|
+
outputSchema: ProjectSchema,
|
|
30
|
+
handler: async ({ name, key, chartEnabled, subtaskingEnabled, projectLeaderCanEditProjectLeader, textFormattingRule, }) => backlog.postProject({
|
|
31
|
+
name,
|
|
32
|
+
key,
|
|
33
|
+
chartEnabled: chartEnabled ?? false,
|
|
34
|
+
subtaskingEnabled: subtaskingEnabled ?? false,
|
|
35
|
+
projectLeaderCanEditProjectLeader: projectLeaderCanEditProjectLeader ?? false,
|
|
36
|
+
textFormattingRule: textFormattingRule ?? 'backlog',
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildToolSchema } from '../types/tool.js';
|
|
3
|
+
import { PullRequestSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
4
|
+
import { resolveIdOrKey, resolveIdOrName } from '../utils/resolveIdOrKey.js';
|
|
5
|
+
const addPullRequestSchema = buildToolSchema((t) => ({
|
|
6
|
+
projectId: z
|
|
7
|
+
.number()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_PROJECT_ID', 'The numeric ID of the project (e.g., 12345)')),
|
|
10
|
+
projectKey: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_PROJECT_KEY', "The key of the project (e.g., 'PROJECT')")),
|
|
14
|
+
repoId: z
|
|
15
|
+
.number()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_REPO_ID', 'Repository ID')),
|
|
18
|
+
repoName: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_REPO_NAME', 'Repository name')),
|
|
22
|
+
summary: z
|
|
23
|
+
.string()
|
|
24
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_SUMMARY', 'Summary of the pull request')),
|
|
25
|
+
description: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_DESCRIPTION', 'Description of the pull request')),
|
|
28
|
+
base: z
|
|
29
|
+
.string()
|
|
30
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_BASE', 'Base branch name')),
|
|
31
|
+
branch: z
|
|
32
|
+
.string()
|
|
33
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_BRANCH', 'Branch name to merge')),
|
|
34
|
+
issueId: z
|
|
35
|
+
.number()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_ISSUE_ID', 'Issue ID to link')),
|
|
38
|
+
assigneeId: z
|
|
39
|
+
.number()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_ASSIGNEE_ID', 'User ID of the assignee')),
|
|
42
|
+
notifiedUserId: z
|
|
43
|
+
.array(z.number())
|
|
44
|
+
.optional()
|
|
45
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_NOTIFIED_USER_ID', 'User IDs to notify')),
|
|
46
|
+
}));
|
|
47
|
+
export const addPullRequestTool = (backlog, { t }) => {
|
|
48
|
+
return {
|
|
49
|
+
name: 'add_pull_request',
|
|
50
|
+
description: t('TOOL_ADD_PULL_REQUEST_DESCRIPTION', 'Creates a new pull request'),
|
|
51
|
+
schema: z.object(addPullRequestSchema(t)),
|
|
52
|
+
outputSchema: PullRequestSchema,
|
|
53
|
+
handler: async ({ projectId, projectKey, repoId, repoName, ...params }) => {
|
|
54
|
+
const result = resolveIdOrKey('project', { id: projectId, key: projectKey }, t);
|
|
55
|
+
if (!result.ok) {
|
|
56
|
+
throw result.error;
|
|
57
|
+
}
|
|
58
|
+
const repoRes = resolveIdOrName('repository', { id: repoId, name: repoName }, t);
|
|
59
|
+
if (!repoRes.ok) {
|
|
60
|
+
throw repoRes.error;
|
|
61
|
+
}
|
|
62
|
+
return backlog.postPullRequest(result.value, String(repoRes.value), params);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildToolSchema } from '../types/tool.js';
|
|
3
|
+
import { PullRequestCommentSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
4
|
+
import { resolveIdOrKey, resolveIdOrName } from '../utils/resolveIdOrKey.js';
|
|
5
|
+
const addPullRequestCommentSchema = buildToolSchema((t) => ({
|
|
6
|
+
projectId: z
|
|
7
|
+
.number()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_COMMENT_PROJECT_ID', 'The numeric ID of the project (e.g., 12345)')),
|
|
10
|
+
projectKey: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_COMMENT_PROJECT_KEY', "The key of the project (e.g., 'PROJECT')")),
|
|
14
|
+
repoId: z
|
|
15
|
+
.number()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_REPO_ID', 'Repository ID')),
|
|
18
|
+
repoName: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_REPO_NAME', 'Repository name')),
|
|
22
|
+
number: z
|
|
23
|
+
.number()
|
|
24
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_COMMENT_NUMBER', 'Pull request number')),
|
|
25
|
+
content: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_COMMENT_CONTENT', 'Comment content')),
|
|
28
|
+
notifiedUserId: z
|
|
29
|
+
.array(z.number())
|
|
30
|
+
.optional()
|
|
31
|
+
.describe(t('TOOL_ADD_PULL_REQUEST_COMMENT_NOTIFIED_USER_ID', 'User IDs to notify')),
|
|
32
|
+
}));
|
|
33
|
+
export const addPullRequestCommentTool = (backlog, { t }) => {
|
|
34
|
+
return {
|
|
35
|
+
name: 'add_pull_request_comment',
|
|
36
|
+
description: t('TOOL_ADD_PULL_REQUEST_COMMENT_DESCRIPTION', 'Adds a comment to a pull request'),
|
|
37
|
+
schema: z.object(addPullRequestCommentSchema(t)),
|
|
38
|
+
outputSchema: PullRequestCommentSchema,
|
|
39
|
+
importantFields: ['id', 'content', 'createdUser'],
|
|
40
|
+
handler: async ({ projectId, projectKey, repoId, repoName, number, ...params }) => {
|
|
41
|
+
const result = resolveIdOrKey('project', { id: projectId, key: projectKey }, t);
|
|
42
|
+
if (!result.ok) {
|
|
43
|
+
throw result.error;
|
|
44
|
+
}
|
|
45
|
+
const repoRes = resolveIdOrName('repository', { id: repoId, name: repoName }, t);
|
|
46
|
+
if (!repoRes.ok) {
|
|
47
|
+
throw repoRes.error;
|
|
48
|
+
}
|
|
49
|
+
return backlog.postPullRequestComments(result.value, String(repoRes.value), number, params);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildToolSchema } from '../types/tool.js';
|
|
3
|
+
import { WikiSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
4
|
+
const addWikiSchema = buildToolSchema((t) => ({
|
|
5
|
+
projectId: z.number().describe(t('TOOL_ADD_WIKI_PROJECT_ID', 'Project ID')),
|
|
6
|
+
name: z.string().describe(t('TOOL_ADD_WIKI_NAME', 'Name of the wiki page')),
|
|
7
|
+
content: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe(t('TOOL_ADD_WIKI_CONTENT', 'Content of the wiki page')),
|
|
10
|
+
mailNotify: z
|
|
11
|
+
.boolean()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(t('TOOL_ADD_WIKI_MAIL_NOTIFY', 'Whether to send notification emails (default: false)')),
|
|
14
|
+
}));
|
|
15
|
+
export const addWikiTool = (backlog, { t }) => {
|
|
16
|
+
return {
|
|
17
|
+
name: 'add_wiki',
|
|
18
|
+
description: t('TOOL_ADD_WIKI_DESCRIPTION', 'Creates a new wiki page'),
|
|
19
|
+
schema: z.object(addWikiSchema(t)),
|
|
20
|
+
outputSchema: WikiSchema,
|
|
21
|
+
importantFields: ['id', 'name', 'content', 'createdUser'],
|
|
22
|
+
handler: async ({ projectId, name, content, mailNotify }) => backlog.postWiki({
|
|
23
|
+
projectId,
|
|
24
|
+
name,
|
|
25
|
+
content,
|
|
26
|
+
mailNotify,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildToolSchema } from '../types/tool.js';
|
|
3
|
+
import { IssueCountSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
4
|
+
import { customFieldsToPayload } from '../backlog/customFields.js';
|
|
5
|
+
const countIssuesSchema = buildToolSchema((t) => ({
|
|
6
|
+
projectId: z
|
|
7
|
+
.array(z.number())
|
|
8
|
+
.optional()
|
|
9
|
+
.describe(t('TOOL_COUNT_ISSUES_PROJECT_ID', 'Project IDs')),
|
|
10
|
+
issueTypeId: z
|
|
11
|
+
.array(z.number())
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(t('TOOL_COUNT_ISSUES_ISSUE_TYPE_ID', 'Issue type IDs')),
|
|
14
|
+
categoryId: z
|
|
15
|
+
.array(z.number())
|
|
16
|
+
.optional()
|
|
17
|
+
.describe(t('TOOL_COUNT_ISSUES_CATEGORY_ID', 'Category IDs')),
|
|
18
|
+
versionId: z
|
|
19
|
+
.array(z.number())
|
|
20
|
+
.optional()
|
|
21
|
+
.describe(t('TOOL_COUNT_ISSUES_VERSION_ID', 'Version IDs')),
|
|
22
|
+
milestoneId: z
|
|
23
|
+
.array(z.number())
|
|
24
|
+
.optional()
|
|
25
|
+
.describe(t('TOOL_COUNT_ISSUES_MILESTONE_ID', 'Milestone IDs')),
|
|
26
|
+
statusId: z
|
|
27
|
+
.array(z.number())
|
|
28
|
+
.optional()
|
|
29
|
+
.describe(t('TOOL_COUNT_ISSUES_STATUS_ID', 'Status IDs')),
|
|
30
|
+
priorityId: z
|
|
31
|
+
.array(z.number())
|
|
32
|
+
.optional()
|
|
33
|
+
.describe(t('TOOL_COUNT_ISSUES_PRIORITY_ID', 'Priority IDs')),
|
|
34
|
+
assigneeId: z
|
|
35
|
+
.array(z.number())
|
|
36
|
+
.optional()
|
|
37
|
+
.describe(t('TOOL_COUNT_ISSUES_ASSIGNEE_ID', 'Assignee user IDs')),
|
|
38
|
+
createdUserId: z
|
|
39
|
+
.array(z.number())
|
|
40
|
+
.optional()
|
|
41
|
+
.describe(t('TOOL_COUNT_ISSUES_CREATED_USER_ID', 'Created user IDs')),
|
|
42
|
+
resolutionId: z
|
|
43
|
+
.array(z.number())
|
|
44
|
+
.optional()
|
|
45
|
+
.describe(t('TOOL_COUNT_ISSUES_RESOLUTION_ID', 'Resolution IDs')),
|
|
46
|
+
parentIssueId: z
|
|
47
|
+
.array(z.number())
|
|
48
|
+
.optional()
|
|
49
|
+
.describe(t('TOOL_COUNT_ISSUES_PARENT_ISSUE_ID', 'Parent issue IDs')),
|
|
50
|
+
keyword: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe(t('TOOL_COUNT_ISSUES_KEYWORD', 'Keyword to search for in issues')),
|
|
54
|
+
startDateSince: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe(t('TOOL_COUNT_ISSUES_START_DATE_SINCE', 'Start date since (yyyy-MM-dd)')),
|
|
58
|
+
startDateUntil: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe(t('TOOL_COUNT_ISSUES_START_DATE_UNTIL', 'Start date until (yyyy-MM-dd)')),
|
|
62
|
+
dueDateSince: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe(t('TOOL_COUNT_ISSUES_DUE_DATE_SINCE', 'Due date since (yyyy-MM-dd)')),
|
|
66
|
+
dueDateUntil: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe(t('TOOL_COUNT_ISSUES_DUE_DATE_UNTIL', 'Due date until (yyyy-MM-dd)')),
|
|
70
|
+
createdSince: z
|
|
71
|
+
.string()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe(t('TOOL_COUNT_ISSUES_CREATED_SINCE', 'Created since (yyyy-MM-dd)')),
|
|
74
|
+
createdUntil: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe(t('TOOL_COUNT_ISSUES_CREATED_UNTIL', 'Created until (yyyy-MM-dd)')),
|
|
78
|
+
updatedSince: z
|
|
79
|
+
.string()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe(t('TOOL_COUNT_ISSUES_UPDATED_SINCE', 'Updated since (yyyy-MM-dd)')),
|
|
82
|
+
updatedUntil: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe(t('TOOL_COUNT_ISSUES_UPDATED_UNTIL', 'Updated until (yyyy-MM-dd)')),
|
|
86
|
+
customFields: z
|
|
87
|
+
.array(z.object({
|
|
88
|
+
id: z
|
|
89
|
+
.number()
|
|
90
|
+
.describe(t('TOOL_COUNT_ISSUES_CUSTOM_FIELD_ID', 'Custom field ID')),
|
|
91
|
+
value: z
|
|
92
|
+
.union([z.string(), z.number(), z.array(z.string())])
|
|
93
|
+
.describe(t('TOOL_COUNT_ISSUES_CUSTOM_FIELD_VALUE', 'Custom field value')),
|
|
94
|
+
}))
|
|
95
|
+
.optional()
|
|
96
|
+
.describe(t('TOOL_COUNT_ISSUES_CUSTOM_FIELDS', 'Custom fields')),
|
|
97
|
+
}));
|
|
98
|
+
export const countIssuesTool = (backlog, { t }) => {
|
|
99
|
+
return {
|
|
100
|
+
name: 'count_issues',
|
|
101
|
+
description: t('TOOL_COUNT_ISSUES_DESCRIPTION', 'Returns count of issues'),
|
|
102
|
+
schema: z.object(countIssuesSchema(t)),
|
|
103
|
+
outputSchema: IssueCountSchema,
|
|
104
|
+
handler: async ({ customFields, ...rest }) => {
|
|
105
|
+
return backlog.getIssuesCount({
|
|
106
|
+
...rest,
|
|
107
|
+
...customFieldsToPayload(customFields),
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildToolSchema } from '../types/tool.js';
|
|
3
|
+
import { IssueSchema } from '../types/zod/backlogOutputDefinition.js';
|
|
4
|
+
import { resolveIdOrKey } from '../utils/resolveIdOrKey.js';
|
|
5
|
+
const deleteIssueSchema = buildToolSchema((t) => ({
|
|
6
|
+
issueId: z
|
|
7
|
+
.number()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe(t('TOOL_DELETE_ISSUE_ISSUE_ID', 'The numeric ID of the issue (e.g., 12345)')),
|
|
10
|
+
issueKey: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(t('TOOL_GET_ISSUE_ISSUE_KEY', "The key of the issue (e.g., 'PROJ-123')")),
|
|
14
|
+
}));
|
|
15
|
+
export const deleteIssueTool = (backlog, { t }) => {
|
|
16
|
+
return {
|
|
17
|
+
name: 'delete_issue',
|
|
18
|
+
description: t('TOOL_DELETE_ISSUE_DESCRIPTION', 'Deletes an issue'),
|
|
19
|
+
schema: z.object(deleteIssueSchema(t)),
|
|
20
|
+
outputSchema: IssueSchema,
|
|
21
|
+
handler: async ({ issueId, issueKey }) => {
|
|
22
|
+
const result = resolveIdOrKey('issue', { id: issueId, key: issueKey }, t);
|
|
23
|
+
if (!result.ok) {
|
|
24
|
+
throw result.error;
|
|
25
|
+
}
|
|
26
|
+
return backlog.deleteIssue(result.value);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|