Skip to content

Commit a11f859

Browse files
authored
Add version/milestone management tools (#16)
* feat: plan version/milestone management tools implementation Planning to implement: - get_version_milestone_list: retrieve versions/milestones for a project - add_version_milestone: create new versions/milestones - update_version_milestone: update existing versions/milestones - delete_version: delete versions * feat: add get version milestone list tool * feat(tools): add version milestone creation tool with tests * feat(tools): add update version milestone tool with tests * feat(tools): add delete version milestone tool with tests * refactor(tools): apply formatting to version milestone tools. - src/tools/addVersionMilestone.test.ts - src/tools/addVersionMilestone.ts - src/tools/getVersionMilestoneList.test.ts - src/tools/getVersionMilestoneList.ts - src/tools/updateVersionMilestone.test.ts - src/tools/updateVersionMilestone.ts * feat(tools): register version milestone toolset in allTools * docs(progress): add version/milestone tools. * refactor(tools): move version milestone tool to issue. * fix(tools): remove unnecessary id verification checks. - id parameters are already specified as required in the schema. * docs(readme): add version milestone tool to project tool list.
1 parent 269dcc3 commit a11f859

12 files changed

+669
-7
lines changed

README.ja.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Backlog API とやり取りするための Model Context Protocol(MCP)サー
1212

1313
- プロジェクトツール(作成、読み取り、更新、削除)
1414
- 課題とコメントの追跡(作成、更新、削除、一覧表示)
15+
- 発生バージョン/マイルストーンの管理(作成、読み取り、更新、削除)
1516
- Wikiページサポート
1617
- Gitリポジトリとプルリクエストツール
1718
- 通知ツール
@@ -132,7 +133,7 @@ docker pull ghcr.io/nulab/backlog-mcp-server:latest
132133
|-----------------|--------------------------------------------------------------------------------------|
133134
| `space` | Backlogスペース設定と一般情報を管理するためのツール |
134135
| `project` | プロジェクト、カテゴリ、カスタムフィールド、課題タイプを管理するためのツール |
135-
| `issue` | 課題とそのコメントを管理するためのツール |
136+
| `issue` | 課題とそのコメント、発生バージョン/マイルストーンを管理するためのツール |
136137
| `wiki` | Wikiページを管理するためのツール |
137138
| `git` | Gitリポジトリとプルリクエストを管理するためのツール |
138139
| `notifications` | ユーザー通知を管理するためのツール |

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A Model Context Protocol (MCP) server for interacting with the Backlog API. This
1212

1313
- Project tools (create, read, update, delete)
1414
- Issue tracking and comments (create, update, delete, list)
15+
- Version/Milestone management (create, read, update, delete)
1516
- Wiki page support
1617
- Git repository and pull request tools
1718
- Notification tools
@@ -132,7 +133,7 @@ The following toolsets are available (enabled by default when `"all"` is used):
132133
|-----------------|--------------------------------------------------------------------------------------|
133134
| `space` | Tools for managing Backlog space settings and general information |
134135
| `project` | Tools for managing projects, categories, custom fields, and issue types |
135-
| `issue` | Tools for managing issues and their comments |
136+
| `issue` | Tools for managing issues and their comments, version milestones |
136137
| `wiki` | Tools for managing wiki pages |
137138
| `git` | Tools for managing Git repositories and pull requests |
138139
| `notifications` | Tools for managing user notifications |
@@ -211,6 +212,10 @@ Tools for managing issues, their comments, and related items like priorities, ca
211212
- `get_resolutions`: Returns list of issue resolutions.
212213
- `get_watching_list_items`: Returns list of watching items for a user.
213214
- `get_watching_list_count`: Returns count of watching items for a user.
215+
- `get_version_milestone_list`: Returns list of version milestones for a project.
216+
- `add_version_milestone`: Creates a new version milestone for a project.
217+
- `update_version_milestone`: Updates an existing version milestone.
218+
- `delete_version_milestone`: Deletes a version milestone.
214219

215220
### Toolset: `wiki`
216221
Tools for managing wiki pages.

memory-bank/progress.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@
5757
- ✅ Adding pull request comments (`add_pull_request_comment`)
5858
- ✅ Updating pull request comments (`update_pull_request_comment`)
5959

60+
### Version/Milestone-related
61+
- ✅ Retrieving version/milestone lists (`get_version_milestone_list`)
62+
- ✅ Adding versions/milestones (`add_version_milestone`)
63+
- ✅ Updating versions/milestones (`update_version_milestone`)
64+
- ✅ Deleting versions (`delete_version`)
65+
6066
### Watch-related
6167
- ✅ Retrieving watched item lists (`get_watching_list_items`)
6268
- ✅ Retrieving watch counts (`get_watching_list_count`)
@@ -131,10 +137,6 @@
131137
- ❌ Adding categories (`add_category`)
132138
- ❌ Updating categories (`update_category`)
133139
- ❌ Deleting categories (`delete_category`)
134-
- ❌ Retrieving version/milestone lists (`get_version_milestone_list`)
135-
- ❌ Adding versions/milestones (`add_version_milestone`)
136-
- ❌ Updating versions/milestones (`update_version_milestone`)
137-
- ❌ Deleting versions (`delete_version`)
138140
- ❌ Retrieving custom field lists (`get_custom_field_list`)
139141
- ❌ Adding custom fields (`add_custom_field`)
140142
- ❌ Updating custom fields (`update_custom_field`)
@@ -218,7 +220,6 @@ This allows access to Backlog's main features from Claude, with optimizations fo
218220

219221
2. **Medium-term Goals**
220222
- Custom field-related features
221-
- Version/milestone-related features
222223
- Webhook-related features
223224
- Further performance optimizations
224225

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { addVersionMilestoneTool } from './addVersionMilestone.js';
2+
import { jest, describe, it, expect } from '@jest/globals';
3+
import type { Backlog } from 'backlog-js';
4+
import { createTranslationHelper } from '../createTranslationHelper.js';
5+
6+
describe('addVersionMilestoneTool', () => {
7+
const mockBacklog: Partial<Backlog> = {
8+
postVersions: jest.fn<() => Promise<any>>().mockResolvedValue({
9+
id: 1,
10+
projectId: 100,
11+
name: 'Version 1.0.0',
12+
description: 'Initial release version',
13+
startDate: '2023-01-01T00:00:00Z',
14+
releaseDueDate: '2023-03-31T00:00:00Z',
15+
archived: false,
16+
displayOrder: 1,
17+
}),
18+
};
19+
20+
const mockTranslationHelper = createTranslationHelper();
21+
const tool = addVersionMilestoneTool(
22+
mockBacklog as Backlog,
23+
mockTranslationHelper
24+
);
25+
26+
it('returns created version milestone as formatted JSON text', async () => {
27+
const result = await tool.handler({
28+
projectKey: 'TEST',
29+
name: 'Version 1.0.0',
30+
description: 'Initial release version',
31+
startDate: '2023-01-01T00:00:00Z',
32+
releaseDueDate: '2023-03-31T00:00:00Z',
33+
});
34+
35+
if (Array.isArray(result)) {
36+
throw new Error('Unexpected array result');
37+
}
38+
expect(result.name).toEqual('Version 1.0.0');
39+
expect(result.description).toEqual('Initial release version');
40+
expect(result.startDate).toEqual('2023-01-01T00:00:00Z');
41+
expect(result.releaseDueDate).toEqual('2023-03-31T00:00:00Z');
42+
});
43+
44+
it('calls backlog.postVersions with correct params when using projectKey', async () => {
45+
const params = {
46+
projectKey: 'TEST',
47+
name: 'Version 1.0.0',
48+
description: 'Initial release version',
49+
startDate: '2023-01-01T00:00:00Z',
50+
releaseDueDate: '2024-03-31T00:00:00Z',
51+
};
52+
53+
await tool.handler(params);
54+
55+
expect(mockBacklog.postVersions).toHaveBeenCalledWith('TEST', {
56+
name: 'Version 1.0.0',
57+
description: 'Initial release version',
58+
startDate: '2023-01-01T00:00:00Z',
59+
releaseDueDate: '2024-03-31T00:00:00Z',
60+
});
61+
});
62+
63+
it('calls backlog.postVersions with correct params when using projectId', async () => {
64+
const params = {
65+
projectId: 100,
66+
name: 'Version 2.0.0',
67+
description: 'Major release',
68+
startDate: '2023-04-01T00:00:00Z',
69+
releaseDueDate: '2023-06-30T00:00:00Z',
70+
};
71+
72+
await tool.handler(params);
73+
74+
expect(mockBacklog.postVersions).toHaveBeenCalledWith(100, {
75+
name: 'Version 2.0.0',
76+
description: 'Major release',
77+
startDate: '2023-04-01T00:00:00Z',
78+
releaseDueDate: '2023-06-30T00:00:00Z',
79+
});
80+
});
81+
82+
it('calls backlog.postVersions with minimal required params', async () => {
83+
const params = {
84+
projectKey: 'TEST',
85+
name: 'Quick Version',
86+
};
87+
88+
await tool.handler(params);
89+
90+
expect(mockBacklog.postVersions).toHaveBeenCalledWith('TEST', {
91+
name: 'Quick Version',
92+
});
93+
});
94+
95+
it('throws an error if neither projectId nor projectKey is provided', async () => {
96+
const params = {
97+
// projectId and projectKey are missing
98+
name: 'Version without project',
99+
description: 'This should fail',
100+
};
101+
102+
await expect(tool.handler(params as any)).rejects.toThrow(Error);
103+
});
104+
});

src/tools/addVersionMilestone.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { z } from 'zod';
2+
import { Backlog } from 'backlog-js';
3+
import { buildToolSchema, ToolDefinition } from '../types/tool.js';
4+
import { TranslationHelper } from '../createTranslationHelper.js';
5+
import { VersionSchema } from '../types/zod/backlogOutputDefinition.js';
6+
import { resolveIdOrKey } from '../utils/resolveIdOrKey.js';
7+
8+
const addVersionMilestoneSchema = buildToolSchema((t) => ({
9+
projectId: z
10+
.number()
11+
.optional()
12+
.describe(t('TOOL_ADD_VERSION_MILESTONE_PROJECT_ID', 'Project ID')),
13+
projectKey: z
14+
.string()
15+
.optional()
16+
.describe(t('TOOL_ADD_VERSION_MILESTONE_PROJECT_KEY', 'Project key')),
17+
name: z
18+
.string()
19+
.describe(t('TOOL_ADD_VERSION_MILESTONE_NAME', 'Version name')),
20+
description: z
21+
.string()
22+
.optional()
23+
.describe(
24+
t('TOOL_ADD_VERSION_MILESTONE_DESCRIPTION', 'Version description')
25+
),
26+
startDate: z
27+
.string()
28+
.optional()
29+
.describe(
30+
t('TOOL_ADD_VERSION_MILESTONE_START_DATE', 'Start date of the version')
31+
),
32+
releaseDueDate: z
33+
.string()
34+
.optional()
35+
.describe(
36+
t(
37+
'TOOL_ADD_VERSION_MILESTONE_RELEASE_DUE_DATE',
38+
'Release due date of the version'
39+
)
40+
),
41+
}));
42+
43+
export const addVersionMilestoneTool = (
44+
backlog: Backlog,
45+
{ t }: TranslationHelper
46+
): ToolDefinition<
47+
ReturnType<typeof addVersionMilestoneSchema>,
48+
(typeof VersionSchema)['shape']
49+
> => {
50+
return {
51+
name: 'add_version_milestone',
52+
description: t(
53+
'TOOL_ADD_VERSION_MILESTONE_DESCRIPTION',
54+
'Creates a new version milestone'
55+
),
56+
schema: z.object(addVersionMilestoneSchema(t)),
57+
outputSchema: VersionSchema,
58+
importantFields: [
59+
'id',
60+
'name',
61+
'description',
62+
'startDate',
63+
'releaseDueDate',
64+
],
65+
handler: async ({ projectId, projectKey, ...params }) => {
66+
const result = resolveIdOrKey(
67+
'project',
68+
{ id: projectId, key: projectKey },
69+
t
70+
);
71+
if (!result.ok) {
72+
throw result.error;
73+
}
74+
return backlog.postVersions(result.value, params);
75+
},
76+
};
77+
};

src/tools/deleteVersion.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { deleteVersionTool } from './deleteVersion.js';
2+
import { jest, describe, it, expect } from '@jest/globals';
3+
import type { Backlog } from 'backlog-js';
4+
import { createTranslationHelper } from '../createTranslationHelper.js';
5+
6+
describe('deleteVersionTool', () => {
7+
const mockBacklog: Partial<Backlog> = {
8+
deleteVersions: jest.fn<() => Promise<any>>().mockResolvedValue({
9+
id: 1,
10+
projectId: 100,
11+
name: 'Test Version',
12+
description: '',
13+
startDate: null,
14+
releaseDueDate: null,
15+
archived: false,
16+
displayOrder: 0,
17+
}),
18+
};
19+
20+
const mockTranslationHelper = createTranslationHelper();
21+
const tool = deleteVersionTool(mockBacklog as Backlog, mockTranslationHelper);
22+
23+
it('returns deleted version information', async () => {
24+
const result = await tool.handler({
25+
projectKey: 'TEST',
26+
id: 1,
27+
});
28+
29+
expect(result).toHaveProperty('id', 1);
30+
expect(result).toHaveProperty('name', 'Test Version');
31+
});
32+
33+
it('calls backlog.deleteVersions with correct params when using project key', async () => {
34+
await tool.handler({
35+
projectKey: 'TEST',
36+
id: 1,
37+
});
38+
39+
expect(mockBacklog.deleteVersions).toHaveBeenCalledWith('TEST', 1);
40+
});
41+
42+
it('calls backlog.deleteVersions with correct params when using project ID', async () => {
43+
await tool.handler({
44+
projectId: 100,
45+
id: 1,
46+
});
47+
48+
expect(mockBacklog.deleteVersions).toHaveBeenCalledWith(100, 1);
49+
});
50+
51+
it('throws an error if neither projectId nor projectKey is provided', async () => {
52+
const params = { id: 1 }; // No identifier provided
53+
54+
await expect(tool.handler(params)).rejects.toThrowError(Error);
55+
});
56+
});

src/tools/deleteVersion.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { z } from 'zod';
2+
import { Backlog } from 'backlog-js';
3+
import { buildToolSchema, ToolDefinition } from '../types/tool.js';
4+
import { TranslationHelper } from '../createTranslationHelper.js';
5+
import { VersionSchema } from '../types/zod/backlogOutputDefinition.js';
6+
import { resolveIdOrKey } from '../utils/resolveIdOrKey.js';
7+
8+
const deleteVersionSchema = buildToolSchema((t) => ({
9+
projectId: z
10+
.number()
11+
.optional()
12+
.describe(
13+
t(
14+
'TOOL_DELETE_VERSION_PROJECT_ID',
15+
'The numeric ID of the project (e.g., 12345)'
16+
)
17+
),
18+
projectKey: z
19+
.string()
20+
.optional()
21+
.describe(
22+
t(
23+
'TOOL_DELETE_VERSION_PROJECT_KEY',
24+
"The key of the project (e.g., 'PROJECT')"
25+
)
26+
),
27+
id: z
28+
.number()
29+
.describe(
30+
t(
31+
'TOOL_DELETE_VERSION_ID',
32+
'The numeric ID of the version to delete (e.g., 67890)'
33+
)
34+
),
35+
}));
36+
37+
export const deleteVersionTool = (
38+
backlog: Backlog,
39+
{ t }: TranslationHelper
40+
): ToolDefinition<
41+
ReturnType<typeof deleteVersionSchema>,
42+
(typeof VersionSchema)['shape']
43+
> => {
44+
return {
45+
name: 'delete_version',
46+
description: t(
47+
'TOOL_DELETE_VERSION_DESCRIPTION',
48+
'Deletes a version from a project'
49+
),
50+
schema: z.object(deleteVersionSchema(t)),
51+
outputSchema: VersionSchema,
52+
handler: async ({ projectId, projectKey, id }) => {
53+
const result = resolveIdOrKey(
54+
'project',
55+
{ id: projectId, key: projectKey },
56+
t
57+
);
58+
if (!result.ok) {
59+
throw result.error;
60+
}
61+
if (!id) {
62+
throw new Error(
63+
t('TOOL_DELETE_VERSION_MISSING_ID', 'Version ID is required')
64+
);
65+
}
66+
return backlog.deleteVersions(result.value, id);
67+
},
68+
};
69+
};

0 commit comments

Comments
 (0)