@pschroee/redmine-mcp 0.3.2 → 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/README.md +60 -3
- package/dist/index.js +5 -3
- package/dist/redmine/client.d.ts +50 -1
- package/dist/redmine/client.js +68 -2
- package/dist/redmine/types.d.ts +37 -0
- package/dist/server.js +1 -1
- package/dist/tools/agile.d.ts +3 -0
- package/dist/tools/agile.js +103 -0
- package/dist/tools/checklists.d.ts +3 -0
- package/dist/tools/checklists.js +65 -0
- package/dist/tools/core.js +1 -1
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +17 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,18 @@ npx @pschroee/redmine-mcp@latest --url=https://your-redmine.com --api-key=your-a
|
|
|
44
44
|
| `roles` | 2 | Roles listing and details |
|
|
45
45
|
| `admin` | 11 | Users & Groups management (admin only) |
|
|
46
46
|
|
|
47
|
-
**Total: 70 Tools**
|
|
47
|
+
**Total: 70 Core Tools**
|
|
48
|
+
|
|
49
|
+
### Plugin Groups (enabled by default, require RedmineUP plugins)
|
|
50
|
+
|
|
51
|
+
| Group | Tools | Description |
|
|
52
|
+
|-------|-------|-------------|
|
|
53
|
+
| `plugin_checklists` | 5 | Checklist items (requires [redmine_checklists](https://www.redmineup.com/pages/plugins/checklists) plugin) |
|
|
54
|
+
| `plugin_agile` | 7 | Agile sprints & story points (requires [redmine_agile](https://www.redmineup.com/pages/plugins/agile) plugin) |
|
|
55
|
+
|
|
56
|
+
**Total: 12 Plugin Tools**
|
|
57
|
+
|
|
58
|
+
> **Note:** Plugin tools are enabled by default but require the corresponding RedmineUP plugins to be installed on your Redmine server. If a plugin is not installed, the tools will return errors when used. Use `--exclude=plugin_checklists,plugin_agile` to disable them.
|
|
48
59
|
|
|
49
60
|
## Usage Examples
|
|
50
61
|
|
|
@@ -66,6 +77,12 @@ npx @pschroee/redmine-mcp@latest --tools=core,metadata
|
|
|
66
77
|
npx @pschroee/redmine-mcp@latest --exclude=wiki,files
|
|
67
78
|
```
|
|
68
79
|
|
|
80
|
+
### Exclude plugin tools (if plugins not installed)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx @pschroee/redmine-mcp@latest --exclude=plugin_checklists,plugin_agile
|
|
84
|
+
```
|
|
85
|
+
|
|
69
86
|
## Claude Configuration
|
|
70
87
|
|
|
71
88
|
### Quick Setup with Claude CLI
|
|
@@ -102,6 +119,24 @@ claude mcp add redmine -s user -- cmd /c npx -y @pschroee/redmine-mcp \
|
|
|
102
119
|
--tools=core,metadata,search
|
|
103
120
|
```
|
|
104
121
|
|
|
122
|
+
### Without Plugin Tools
|
|
123
|
+
|
|
124
|
+
**macOS / Linux:**
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
claude mcp add redmine -s user -- npx -y @pschroee/redmine-mcp@latest \
|
|
128
|
+
--url=https://your-redmine.com --api-key=your-api-key \
|
|
129
|
+
--exclude=plugin_checklists,plugin_agile
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Windows:**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
claude mcp add redmine -s user -- cmd /c npx -y @pschroee/redmine-mcp@latest \
|
|
136
|
+
--url=https://your-redmine.com --api-key=your-api-key \
|
|
137
|
+
--exclude=plugin_checklists,plugin_agile
|
|
138
|
+
```
|
|
139
|
+
|
|
105
140
|
### Manual Configuration (Claude Desktop)
|
|
106
141
|
|
|
107
142
|
#### macOS / Linux
|
|
@@ -292,6 +327,28 @@ Add to your Claude Desktop config (`%APPDATA%\Claude\claude_desktop_config.json`
|
|
|
292
327
|
- `add_user_to_group` - Add user to group
|
|
293
328
|
- `remove_user_from_group` - Remove user from group
|
|
294
329
|
|
|
330
|
+
### Plugin: Checklists (plugin_checklists)
|
|
331
|
+
|
|
332
|
+
> Requires [redmine_checklists](https://www.redmineup.com/pages/plugins/checklists) plugin
|
|
333
|
+
|
|
334
|
+
- `list_checklists` - List checklist items for an issue
|
|
335
|
+
- `get_checklist` - Get checklist item details
|
|
336
|
+
- `create_checklist` - Create checklist item
|
|
337
|
+
- `update_checklist` - Update checklist item (text, done status)
|
|
338
|
+
- `delete_checklist` - Delete checklist item
|
|
339
|
+
|
|
340
|
+
### Plugin: Agile (plugin_agile)
|
|
341
|
+
|
|
342
|
+
> Requires [redmine_agile](https://www.redmineup.com/pages/plugins/agile) plugin
|
|
343
|
+
|
|
344
|
+
- `list_agile_sprints` - List sprints for a project
|
|
345
|
+
- `get_agile_sprint` - Get sprint details
|
|
346
|
+
- `create_agile_sprint` - Create sprint
|
|
347
|
+
- `update_agile_sprint` - Update sprint
|
|
348
|
+
- `delete_agile_sprint` - Delete sprint
|
|
349
|
+
- `get_issue_agile_data` - Get agile data for issue (position, story points, sprint)
|
|
350
|
+
- `update_issue_agile_data` - Update agile data for issue
|
|
351
|
+
|
|
295
352
|
## Development
|
|
296
353
|
|
|
297
354
|
```bash
|
|
@@ -329,12 +386,12 @@ npm test
|
|
|
329
386
|
|
|
330
387
|
### Test Coverage
|
|
331
388
|
|
|
332
|
-
The test suite includes
|
|
389
|
+
The test suite includes 198 tests across 12 test files:
|
|
333
390
|
|
|
334
391
|
| Test File | Tests | Description |
|
|
335
392
|
|-----------|-------|-------------|
|
|
336
393
|
| `account.test.ts` | 1 | Current user account |
|
|
337
|
-
| `core.test.ts` |
|
|
394
|
+
| `core.test.ts` | 51 | Projects and Issues CRUD |
|
|
338
395
|
| `metadata.test.ts` | 14 | Trackers, Statuses, Categories |
|
|
339
396
|
| `relations.test.ts` | 24 | Versions and Issue Relations |
|
|
340
397
|
| `wiki.test.ts` | 14 | Wiki Pages CRUD |
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { RedmineClient } from "./redmine/client.js";
|
|
4
4
|
import { createServer } from "./server.js";
|
|
5
|
-
import { resolveGroups, ALL_GROUPS } from "./tools/index.js";
|
|
5
|
+
import { resolveGroups, ALL_GROUPS, PLUGIN_GROUPS } from "./tools/index.js";
|
|
6
6
|
function printHelp() {
|
|
7
7
|
console.log(`
|
|
8
8
|
Redmine MCP Server
|
|
@@ -16,13 +16,15 @@ Options:
|
|
|
16
16
|
--exclude=<groups> Comma-separated list of tool groups to exclude
|
|
17
17
|
--help Show this help message
|
|
18
18
|
|
|
19
|
-
Tool Groups:
|
|
19
|
+
Tool Groups (all enabled by default):
|
|
20
20
|
${ALL_GROUPS.join(", ")}
|
|
21
21
|
|
|
22
|
+
Note: Plugin groups (${PLUGIN_GROUPS.join(", ")}) require RedmineUP plugins installed.
|
|
23
|
+
|
|
22
24
|
Examples:
|
|
23
25
|
redmine-mcp --url=https://redmine.example.com --api-key=abc123
|
|
24
26
|
redmine-mcp --tools=core,metadata
|
|
25
|
-
redmine-mcp --exclude=wiki,files
|
|
27
|
+
redmine-mcp --exclude=wiki,files,plugin_checklists,plugin_agile
|
|
26
28
|
`);
|
|
27
29
|
}
|
|
28
30
|
function parseArgs() {
|
package/dist/redmine/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RedmineIssue, RedmineIssuesResponse, RedmineProject, RedmineProjectsResponse, RedmineResult, RedmineWikiPage, RedmineWikiPagesResponse, RedmineAttachment, RedmineUploadResponse, RedmineFilesResponse, RedmineRelation, RedmineRelationsResponse, RedmineVersion, RedmineVersionsResponse, RedmineTrackersResponse, RedmineIssueStatusesResponse, RedmineCategory, RedmineCategoriesResponse, RedmineCustomFieldsResponse, RedmineQueriesResponse, RedmineSearchResponse, RedmineMyAccountResponse, RedmineTimeEntry, RedmineTimeEntriesResponse, RedmineIssuePrioritiesResponse, RedmineTimeEntryActivitiesResponse, RedmineDocumentCategoriesResponse, RedmineUser, RedmineUsersResponse, RedmineGroup, RedmineGroupsResponse, RedmineMembership, RedmineMembershipsResponse, RedmineRole, RedmineRolesResponse } from "./types.js";
|
|
1
|
+
import type { RedmineIssue, RedmineIssuesResponse, RedmineProject, RedmineProjectsResponse, RedmineResult, RedmineWikiPage, RedmineWikiPagesResponse, RedmineAttachment, RedmineUploadResponse, RedmineFilesResponse, RedmineRelation, RedmineRelationsResponse, RedmineVersion, RedmineVersionsResponse, RedmineTrackersResponse, RedmineIssueStatusesResponse, RedmineCategory, RedmineCategoriesResponse, RedmineCustomFieldsResponse, RedmineQueriesResponse, RedmineSearchResponse, RedmineMyAccountResponse, RedmineTimeEntry, RedmineTimeEntriesResponse, RedmineIssuePrioritiesResponse, RedmineTimeEntryActivitiesResponse, RedmineDocumentCategoriesResponse, RedmineUser, RedmineUsersResponse, RedmineGroup, RedmineGroupsResponse, RedmineMembership, RedmineMembershipsResponse, RedmineRole, RedmineRolesResponse, RedmineChecklist, RedmineChecklistsResponse, RedmineAgileSprint, RedmineAgileSprintsResponse, RedmineAgileData } from "./types.js";
|
|
2
2
|
export declare class RedmineClient {
|
|
3
3
|
private baseUrl;
|
|
4
4
|
private apiKey;
|
|
@@ -347,4 +347,53 @@ export declare class RedmineClient {
|
|
|
347
347
|
getRole(id: number): Promise<RedmineResult<{
|
|
348
348
|
role: RedmineRole;
|
|
349
349
|
}>>;
|
|
350
|
+
listChecklists(issueId: number): Promise<RedmineResult<RedmineChecklistsResponse>>;
|
|
351
|
+
getChecklist(id: number): Promise<RedmineResult<{
|
|
352
|
+
checklist: RedmineChecklist;
|
|
353
|
+
}>>;
|
|
354
|
+
createChecklist(data: {
|
|
355
|
+
issue_id: number;
|
|
356
|
+
subject: string;
|
|
357
|
+
is_done?: boolean;
|
|
358
|
+
position?: number;
|
|
359
|
+
}): Promise<RedmineResult<{
|
|
360
|
+
checklist: RedmineChecklist;
|
|
361
|
+
}>>;
|
|
362
|
+
updateChecklist(id: number, data: {
|
|
363
|
+
subject?: string;
|
|
364
|
+
is_done?: boolean;
|
|
365
|
+
position?: number;
|
|
366
|
+
}): Promise<RedmineResult<void>>;
|
|
367
|
+
deleteChecklist(id: number): Promise<RedmineResult<void>>;
|
|
368
|
+
listAgileSprints(projectId: string | number): Promise<RedmineResult<RedmineAgileSprintsResponse>>;
|
|
369
|
+
getAgileSprint(projectId: string | number, sprintId: number): Promise<RedmineResult<{
|
|
370
|
+
agile_sprint: RedmineAgileSprint;
|
|
371
|
+
}>>;
|
|
372
|
+
createAgileSprint(projectId: string | number, data: {
|
|
373
|
+
name: string;
|
|
374
|
+
status?: string;
|
|
375
|
+
start_date?: string;
|
|
376
|
+
end_date?: string;
|
|
377
|
+
description?: string;
|
|
378
|
+
sharing?: string;
|
|
379
|
+
}): Promise<RedmineResult<{
|
|
380
|
+
agile_sprint: RedmineAgileSprint;
|
|
381
|
+
}>>;
|
|
382
|
+
updateAgileSprint(projectId: string | number, sprintId: number, data: {
|
|
383
|
+
name?: string;
|
|
384
|
+
status?: string;
|
|
385
|
+
start_date?: string;
|
|
386
|
+
end_date?: string;
|
|
387
|
+
description?: string;
|
|
388
|
+
sharing?: string;
|
|
389
|
+
}): Promise<RedmineResult<void>>;
|
|
390
|
+
deleteAgileSprint(projectId: string | number, sprintId: number): Promise<RedmineResult<void>>;
|
|
391
|
+
getIssueAgileData(issueId: number): Promise<RedmineResult<{
|
|
392
|
+
agile_data: RedmineAgileData;
|
|
393
|
+
}>>;
|
|
394
|
+
updateIssueAgileData(issueId: number, data: {
|
|
395
|
+
position?: number;
|
|
396
|
+
story_points?: number;
|
|
397
|
+
agile_sprint_id?: number | null;
|
|
398
|
+
}): Promise<RedmineResult<void>>;
|
|
350
399
|
}
|
package/dist/redmine/client.js
CHANGED
|
@@ -48,9 +48,29 @@ export class RedmineClient {
|
|
|
48
48
|
}
|
|
49
49
|
// ==================== ISSUES ====================
|
|
50
50
|
async listIssues(params) {
|
|
51
|
+
let projectId = params?.project_id;
|
|
52
|
+
// Auto-fetch project_id from query if query_id is provided without project_id
|
|
53
|
+
if (params?.query_id && !projectId) {
|
|
54
|
+
const queriesResult = await this.listQueries();
|
|
55
|
+
if ("error" in queriesResult) {
|
|
56
|
+
return queriesResult;
|
|
57
|
+
}
|
|
58
|
+
const savedQuery = queriesResult.queries.find(q => q.id === params.query_id);
|
|
59
|
+
if (!savedQuery) {
|
|
60
|
+
return {
|
|
61
|
+
error: true,
|
|
62
|
+
status: 404,
|
|
63
|
+
message: `Query with ID ${params.query_id} not found`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Use the query's project_id if it has one (project-specific query)
|
|
67
|
+
if (savedQuery.project_id) {
|
|
68
|
+
projectId = savedQuery.project_id;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
51
71
|
const query = new URLSearchParams();
|
|
52
|
-
if (
|
|
53
|
-
query.set("project_id", String(
|
|
72
|
+
if (projectId)
|
|
73
|
+
query.set("project_id", String(projectId));
|
|
54
74
|
if (params?.subproject_id)
|
|
55
75
|
query.set("subproject_id", params.subproject_id);
|
|
56
76
|
if (params?.tracker_id)
|
|
@@ -457,4 +477,50 @@ export class RedmineClient {
|
|
|
457
477
|
async getRole(id) {
|
|
458
478
|
return this.request("GET", `/roles/${id}.json`);
|
|
459
479
|
}
|
|
480
|
+
// ==================== CHECKLISTS (Plugin) ====================
|
|
481
|
+
async listChecklists(issueId) {
|
|
482
|
+
return this.request("GET", `/issues/${issueId}/checklists.json`);
|
|
483
|
+
}
|
|
484
|
+
async getChecklist(id) {
|
|
485
|
+
return this.request("GET", `/checklists/${id}.json`);
|
|
486
|
+
}
|
|
487
|
+
async createChecklist(data) {
|
|
488
|
+
return this.request("POST", "/checklists.json", {
|
|
489
|
+
checklist: data,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
async updateChecklist(id, data) {
|
|
493
|
+
return this.request("PUT", `/checklists/${id}.json`, {
|
|
494
|
+
checklist: data,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
async deleteChecklist(id) {
|
|
498
|
+
return this.request("DELETE", `/checklists/${id}.json`);
|
|
499
|
+
}
|
|
500
|
+
// ==================== AGILE (Plugin) ====================
|
|
501
|
+
async listAgileSprints(projectId) {
|
|
502
|
+
return this.request("GET", `/projects/${projectId}/agile_sprints.json`);
|
|
503
|
+
}
|
|
504
|
+
async getAgileSprint(projectId, sprintId) {
|
|
505
|
+
return this.request("GET", `/projects/${projectId}/agile_sprints/${sprintId}.json`);
|
|
506
|
+
}
|
|
507
|
+
async createAgileSprint(projectId, data) {
|
|
508
|
+
return this.request("POST", `/projects/${projectId}/agile_sprints.json`, { agile_sprint: data });
|
|
509
|
+
}
|
|
510
|
+
async updateAgileSprint(projectId, sprintId, data) {
|
|
511
|
+
return this.request("PUT", `/projects/${projectId}/agile_sprints/${sprintId}.json`, { agile_sprint: data });
|
|
512
|
+
}
|
|
513
|
+
async deleteAgileSprint(projectId, sprintId) {
|
|
514
|
+
return this.request("DELETE", `/projects/${projectId}/agile_sprints/${sprintId}.json`);
|
|
515
|
+
}
|
|
516
|
+
async getIssueAgileData(issueId) {
|
|
517
|
+
return this.request("GET", `/issues/${issueId}/agile_data.json`);
|
|
518
|
+
}
|
|
519
|
+
async updateIssueAgileData(issueId, data) {
|
|
520
|
+
return this.request("PUT", `/issues/${issueId}.json`, {
|
|
521
|
+
issue: {
|
|
522
|
+
agile_data_attributes: data,
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
}
|
|
460
526
|
}
|
package/dist/redmine/types.d.ts
CHANGED
|
@@ -487,3 +487,40 @@ export interface RedmineRole {
|
|
|
487
487
|
export interface RedmineRolesResponse {
|
|
488
488
|
roles: RedmineRole[];
|
|
489
489
|
}
|
|
490
|
+
export interface RedmineChecklist {
|
|
491
|
+
id: number;
|
|
492
|
+
issue_id: number;
|
|
493
|
+
subject: string;
|
|
494
|
+
is_done: boolean;
|
|
495
|
+
position: number;
|
|
496
|
+
created_at: string;
|
|
497
|
+
updated_at: string;
|
|
498
|
+
}
|
|
499
|
+
export interface RedmineChecklistsResponse {
|
|
500
|
+
checklists: RedmineChecklist[];
|
|
501
|
+
}
|
|
502
|
+
export interface RedmineAgileSprint {
|
|
503
|
+
id: number;
|
|
504
|
+
name: string;
|
|
505
|
+
status: string;
|
|
506
|
+
start_date?: string;
|
|
507
|
+
end_date?: string;
|
|
508
|
+
description?: string;
|
|
509
|
+
sharing?: string;
|
|
510
|
+
is_active?: boolean;
|
|
511
|
+
created_at?: string;
|
|
512
|
+
updated_at?: string;
|
|
513
|
+
}
|
|
514
|
+
export interface RedmineAgileSprintsResponse {
|
|
515
|
+
agile_sprints: RedmineAgileSprint[];
|
|
516
|
+
}
|
|
517
|
+
export interface RedmineAgileData {
|
|
518
|
+
id: number;
|
|
519
|
+
issue_id: number;
|
|
520
|
+
position: number;
|
|
521
|
+
story_points?: number;
|
|
522
|
+
agile_sprint_id?: number;
|
|
523
|
+
}
|
|
524
|
+
export interface RedmineAgileDataResponse {
|
|
525
|
+
agile_data: RedmineAgileData;
|
|
526
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { registerTools } from "./tools/index.js";
|
|
|
3
3
|
export function createServer(redmineClient, toolGroups) {
|
|
4
4
|
const server = new McpServer({
|
|
5
5
|
name: "redmine-mcp",
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.4.0",
|
|
7
7
|
});
|
|
8
8
|
registerTools(server, redmineClient, toolGroups);
|
|
9
9
|
return server;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAgileTools(server, client) {
|
|
3
|
+
// === SPRINTS ===
|
|
4
|
+
server.registerTool("list_agile_sprints", {
|
|
5
|
+
description: "List all agile sprints for a project (requires redmine_agile plugin)",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
8
|
+
},
|
|
9
|
+
}, async (params) => {
|
|
10
|
+
const result = await client.listAgileSprints(params.project_id);
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
server.registerTool("get_agile_sprint", {
|
|
16
|
+
description: "Get details of a specific agile sprint (requires redmine_agile plugin)",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
19
|
+
sprint_id: z.number().describe("The sprint ID"),
|
|
20
|
+
},
|
|
21
|
+
}, async (params) => {
|
|
22
|
+
const result = await client.getAgileSprint(params.project_id, params.sprint_id);
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
server.registerTool("create_agile_sprint", {
|
|
28
|
+
description: "Create a new agile sprint for a project (requires redmine_agile plugin)",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
31
|
+
name: z.string().describe("Sprint name"),
|
|
32
|
+
status: z.string().optional().describe("Sprint status: open, active, closed"),
|
|
33
|
+
start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
34
|
+
end_date: z.string().optional().describe("End date (YYYY-MM-DD)"),
|
|
35
|
+
description: z.string().optional().describe("Sprint description"),
|
|
36
|
+
sharing: z.string().optional().describe("Sharing level"),
|
|
37
|
+
},
|
|
38
|
+
}, async (params) => {
|
|
39
|
+
const { project_id, ...data } = params;
|
|
40
|
+
const result = await client.createAgileSprint(project_id, data);
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
server.registerTool("update_agile_sprint", {
|
|
46
|
+
description: "Update an existing agile sprint (requires redmine_agile plugin)",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
49
|
+
sprint_id: z.number().describe("The sprint ID to update"),
|
|
50
|
+
name: z.string().optional().describe("New sprint name"),
|
|
51
|
+
status: z.string().optional().describe("Sprint status: open, active, closed"),
|
|
52
|
+
start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
53
|
+
end_date: z.string().optional().describe("End date (YYYY-MM-DD)"),
|
|
54
|
+
description: z.string().optional().describe("Sprint description"),
|
|
55
|
+
sharing: z.string().optional().describe("Sharing level"),
|
|
56
|
+
},
|
|
57
|
+
}, async (params) => {
|
|
58
|
+
const { project_id, sprint_id, ...data } = params;
|
|
59
|
+
const result = await client.updateAgileSprint(project_id, sprint_id, data);
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
server.registerTool("delete_agile_sprint", {
|
|
65
|
+
description: "Delete an agile sprint (requires redmine_agile plugin)",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
68
|
+
sprint_id: z.number().describe("The sprint ID to delete"),
|
|
69
|
+
},
|
|
70
|
+
}, async (params) => {
|
|
71
|
+
const result = await client.deleteAgileSprint(params.project_id, params.sprint_id);
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
// === AGILE DATA (Issue attributes) ===
|
|
77
|
+
server.registerTool("get_issue_agile_data", {
|
|
78
|
+
description: "Get agile data for an issue (position, story points, sprint) (requires redmine_agile plugin)",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
issue_id: z.number().describe("The issue ID"),
|
|
81
|
+
},
|
|
82
|
+
}, async (params) => {
|
|
83
|
+
const result = await client.getIssueAgileData(params.issue_id);
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
server.registerTool("update_issue_agile_data", {
|
|
89
|
+
description: "Update agile data for an issue (position, story points, sprint assignment) (requires redmine_agile plugin)",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
issue_id: z.number().describe("The issue ID"),
|
|
92
|
+
position: z.number().optional().describe("Position on the agile board"),
|
|
93
|
+
story_points: z.number().optional().describe("Story points for the issue"),
|
|
94
|
+
agile_sprint_id: z.number().nullable().optional().describe("Sprint ID to assign (null to unassign)"),
|
|
95
|
+
},
|
|
96
|
+
}, async (params) => {
|
|
97
|
+
const { issue_id, ...data } = params;
|
|
98
|
+
const result = await client.updateIssueAgileData(issue_id, data);
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerChecklistsTools(server, client) {
|
|
3
|
+
server.registerTool("list_checklists", {
|
|
4
|
+
description: "List all checklist items for an issue (requires redmine_checklists plugin)",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
issue_id: z.number().describe("The issue ID to get checklists for"),
|
|
7
|
+
},
|
|
8
|
+
}, async (params) => {
|
|
9
|
+
const result = await client.listChecklists(params.issue_id);
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
server.registerTool("get_checklist", {
|
|
15
|
+
description: "Get a specific checklist item by ID (requires redmine_checklists plugin)",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
checklist_id: z.number().describe("The checklist item ID"),
|
|
18
|
+
},
|
|
19
|
+
}, async (params) => {
|
|
20
|
+
const result = await client.getChecklist(params.checklist_id);
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
server.registerTool("create_checklist", {
|
|
26
|
+
description: "Create a new checklist item for an issue (requires redmine_checklists plugin)",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
issue_id: z.number().describe("The issue ID to add the checklist item to"),
|
|
29
|
+
subject: z.string().describe("The checklist item text"),
|
|
30
|
+
is_done: z.boolean().optional().describe("Whether the item is completed (default: false)"),
|
|
31
|
+
position: z.number().optional().describe("Position in the checklist (1-based)"),
|
|
32
|
+
},
|
|
33
|
+
}, async (params) => {
|
|
34
|
+
const result = await client.createChecklist(params);
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
server.registerTool("update_checklist", {
|
|
40
|
+
description: "Update an existing checklist item (requires redmine_checklists plugin)",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
checklist_id: z.number().describe("The checklist item ID to update"),
|
|
43
|
+
subject: z.string().optional().describe("New text for the checklist item"),
|
|
44
|
+
is_done: z.boolean().optional().describe("Mark as done/undone"),
|
|
45
|
+
position: z.number().optional().describe("New position in the checklist"),
|
|
46
|
+
},
|
|
47
|
+
}, async (params) => {
|
|
48
|
+
const { checklist_id, ...data } = params;
|
|
49
|
+
const result = await client.updateChecklist(checklist_id, data);
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
server.registerTool("delete_checklist", {
|
|
55
|
+
description: "Delete a checklist item (requires redmine_checklists plugin)",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
checklist_id: z.number().describe("The checklist item ID to delete"),
|
|
58
|
+
},
|
|
59
|
+
}, async (params) => {
|
|
60
|
+
const result = await client.deleteChecklist(params.checklist_id);
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
package/dist/tools/core.js
CHANGED
|
@@ -19,7 +19,7 @@ export function registerCoreTools(server, client) {
|
|
|
19
19
|
include: z.string().optional().describe("Include associated data: attachments, relations, journals, watchers, children"),
|
|
20
20
|
limit: z.number().optional().describe("Maximum results (default 25, max 100)"),
|
|
21
21
|
offset: z.number().optional().describe("Skip first N results"),
|
|
22
|
-
query_id: z.number().optional().describe("Use a saved query ID to filter issues (get IDs from list_queries)"),
|
|
22
|
+
query_id: z.number().optional().describe("Use a saved query ID to filter issues (get IDs from list_queries). For project-specific queries, project_id is automatically fetched from the query if not provided."),
|
|
23
23
|
},
|
|
24
24
|
}, async (params) => {
|
|
25
25
|
const result = await client.listIssues(params);
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { RedmineClient } from "../redmine/client.js";
|
|
3
3
|
export type ToolRegistrationFn = (server: McpServer, client: RedmineClient) => void;
|
|
4
|
+
export declare const coreToolGroups: Record<string, ToolRegistrationFn>;
|
|
5
|
+
export declare const pluginToolGroups: Record<string, ToolRegistrationFn>;
|
|
4
6
|
export declare const toolGroups: Record<string, ToolRegistrationFn>;
|
|
5
7
|
export type ToolGroup = keyof typeof toolGroups;
|
|
6
8
|
export declare const ALL_GROUPS: ToolGroup[];
|
|
9
|
+
export declare const PLUGIN_GROUPS: ToolGroup[];
|
|
7
10
|
export declare function isValidToolGroup(group: string): group is ToolGroup;
|
|
8
11
|
export declare function validateToolGroups(groups: string[]): ToolGroup[];
|
|
9
12
|
export declare function resolveGroups(include?: string[], exclude?: string[]): ToolGroup[];
|
package/dist/tools/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { registerAccountTools } from "./account.js";
|
|
2
2
|
import { registerAdminTools } from "./admin.js";
|
|
3
|
+
import { registerAgileTools } from "./agile.js";
|
|
4
|
+
import { registerChecklistsTools } from "./checklists.js";
|
|
3
5
|
import { registerCoreTools } from "./core.js";
|
|
4
6
|
import { registerEnumerationsTools } from "./enumerations.js";
|
|
5
7
|
import { registerFilesTools } from "./files.js";
|
|
@@ -10,7 +12,8 @@ import { registerRolesTools } from "./roles.js";
|
|
|
10
12
|
import { registerSearchTools } from "./search.js";
|
|
11
13
|
import { registerTimeTools } from "./time.js";
|
|
12
14
|
import { registerWikiTools } from "./wiki.js";
|
|
13
|
-
|
|
15
|
+
// Core tool groups
|
|
16
|
+
export const coreToolGroups = {
|
|
14
17
|
core: registerCoreTools,
|
|
15
18
|
metadata: registerMetadataTools,
|
|
16
19
|
wiki: registerWikiTools,
|
|
@@ -24,7 +27,20 @@ export const toolGroups = {
|
|
|
24
27
|
roles: registerRolesTools,
|
|
25
28
|
admin: registerAdminTools,
|
|
26
29
|
};
|
|
30
|
+
// Plugin tool groups (require RedmineUP plugins to be installed)
|
|
31
|
+
export const pluginToolGroups = {
|
|
32
|
+
plugin_checklists: registerChecklistsTools,
|
|
33
|
+
plugin_agile: registerAgileTools,
|
|
34
|
+
};
|
|
35
|
+
// All tool groups combined
|
|
36
|
+
export const toolGroups = {
|
|
37
|
+
...coreToolGroups,
|
|
38
|
+
...pluginToolGroups,
|
|
39
|
+
};
|
|
40
|
+
// All groups including plugins are enabled by default
|
|
27
41
|
export const ALL_GROUPS = Object.keys(toolGroups);
|
|
42
|
+
// Plugin groups (for documentation purposes)
|
|
43
|
+
export const PLUGIN_GROUPS = Object.keys(pluginToolGroups);
|
|
28
44
|
export function isValidToolGroup(group) {
|
|
29
45
|
return group in toolGroups;
|
|
30
46
|
}
|