@pschroee/redmine-mcp 0.3.1
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 +360 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +99 -0
- package/dist/redmine/client.d.ts +349 -0
- package/dist/redmine/client.js +458 -0
- package/dist/redmine/types.d.ts +489 -0
- package/dist/redmine/types.js +2 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +10 -0
- package/dist/tools/account.d.ts +3 -0
- package/dist/tools/account.js +10 -0
- package/dist/tools/admin.d.ts +3 -0
- package/dist/tools/admin.js +150 -0
- package/dist/tools/core.d.ts +3 -0
- package/dist/tools/core.js +242 -0
- package/dist/tools/enumerations.d.ts +3 -0
- package/dist/tools/enumerations.js +26 -0
- package/dist/tools/files.d.ts +3 -0
- package/dist/tools/files.js +70 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +59 -0
- package/dist/tools/issues.d.ts +3 -0
- package/dist/tools/issues.js +61 -0
- package/dist/tools/memberships.d.ts +3 -0
- package/dist/tools/memberships.js +66 -0
- package/dist/tools/metadata.d.ts +3 -0
- package/dist/tools/metadata.js +102 -0
- package/dist/tools/projects.d.ts +3 -0
- package/dist/tools/projects.js +49 -0
- package/dist/tools/relations.d.ts +3 -0
- package/dist/tools/relations.js +157 -0
- package/dist/tools/roles.d.ts +3 -0
- package/dist/tools/roles.js +22 -0
- package/dist/tools/search.d.ts +3 -0
- package/dist/tools/search.js +28 -0
- package/dist/tools/time.d.ts +3 -0
- package/dist/tools/time.js +75 -0
- package/dist/tools/wiki.d.ts +3 -0
- package/dist/tools/wiki.js +75 -0
- package/package.json +55 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerMetadataTools(server, client) {
|
|
3
|
+
// === TRACKERS ===
|
|
4
|
+
server.registerTool("list_trackers", {
|
|
5
|
+
description: "List all available trackers (issue types like Bug, Feature, etc.)",
|
|
6
|
+
}, async () => {
|
|
7
|
+
const result = await client.listTrackers();
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
// === ISSUE STATUSES ===
|
|
13
|
+
server.registerTool("list_issue_statuses", {
|
|
14
|
+
description: "List all available issue statuses (New, In Progress, Closed, etc.)",
|
|
15
|
+
}, async () => {
|
|
16
|
+
const result = await client.listIssueStatuses();
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
// === ISSUE CATEGORIES ===
|
|
22
|
+
server.registerTool("list_issue_categories", {
|
|
23
|
+
description: "List all issue categories for a project",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
26
|
+
},
|
|
27
|
+
}, async (params) => {
|
|
28
|
+
const result = await client.listIssueCategories(params.project_id);
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
server.registerTool("get_issue_category", {
|
|
34
|
+
description: "Get details of a specific issue category",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
category_id: z.number().describe("The category ID"),
|
|
37
|
+
},
|
|
38
|
+
}, async (params) => {
|
|
39
|
+
const result = await client.getIssueCategory(params.category_id);
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
server.registerTool("create_issue_category", {
|
|
45
|
+
description: "Create a new issue category in a project",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
48
|
+
name: z.string().describe("Category name"),
|
|
49
|
+
assigned_to_id: z.number().optional().describe("Default assignee user ID for this category"),
|
|
50
|
+
},
|
|
51
|
+
}, async (params) => {
|
|
52
|
+
const { project_id, ...data } = params;
|
|
53
|
+
const result = await client.createIssueCategory(project_id, data);
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
server.registerTool("update_issue_category", {
|
|
59
|
+
description: "Update an existing issue category",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
category_id: z.number().describe("The category ID to update"),
|
|
62
|
+
name: z.string().optional().describe("New category name"),
|
|
63
|
+
assigned_to_id: z.number().optional().describe("New default assignee user ID"),
|
|
64
|
+
},
|
|
65
|
+
}, async (params) => {
|
|
66
|
+
const { category_id, ...data } = params;
|
|
67
|
+
const result = await client.updateIssueCategory(category_id, data);
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
server.registerTool("delete_issue_category", {
|
|
73
|
+
description: "Delete an issue category",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
category_id: z.number().describe("The category ID to delete"),
|
|
76
|
+
reassign_to_id: z.number().optional().describe("Category ID to reassign issues to before deletion"),
|
|
77
|
+
},
|
|
78
|
+
}, async (params) => {
|
|
79
|
+
const result = await client.deleteIssueCategory(params.category_id, params.reassign_to_id);
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
// === CUSTOM FIELDS ===
|
|
85
|
+
server.registerTool("list_custom_fields", {
|
|
86
|
+
description: "List all custom field definitions (requires admin privileges)",
|
|
87
|
+
}, async () => {
|
|
88
|
+
const result = await client.listCustomFields();
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
// === QUERIES ===
|
|
94
|
+
server.registerTool("list_queries", {
|
|
95
|
+
description: "List all saved issue queries (public and private)",
|
|
96
|
+
}, async () => {
|
|
97
|
+
const result = await client.listQueries();
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerProjectTools(server, client) {
|
|
3
|
+
server.tool("list_projects", "List all accessible projects from Redmine", {
|
|
4
|
+
limit: z.number().optional().describe("Maximum results (default 25, max 100)"),
|
|
5
|
+
offset: z.number().optional().describe("Skip first N results"),
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
const result = await client.listProjects(params);
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
server.tool("get_project", "Get details of a specific project", {
|
|
13
|
+
project_id: z
|
|
14
|
+
.union([z.string(), z.number()])
|
|
15
|
+
.describe("Project ID or identifier"),
|
|
16
|
+
}, async (params) => {
|
|
17
|
+
const result = await client.getProject(params.project_id);
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
server.tool("create_project", "Create a new project in Redmine", {
|
|
23
|
+
name: z.string().describe("Project name"),
|
|
24
|
+
identifier: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe("Unique identifier (lowercase, no spaces, used in URLs)"),
|
|
27
|
+
description: z.string().optional().describe("Project description"),
|
|
28
|
+
is_public: z.boolean().optional().describe("Whether project is public (default true)"),
|
|
29
|
+
}, async (params) => {
|
|
30
|
+
const result = await client.createProject(params);
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
server.tool("update_project", "Update an existing project", {
|
|
36
|
+
project_id: z
|
|
37
|
+
.union([z.string(), z.number()])
|
|
38
|
+
.describe("Project ID or identifier"),
|
|
39
|
+
name: z.string().optional().describe("New project name"),
|
|
40
|
+
description: z.string().optional().describe("New description"),
|
|
41
|
+
is_public: z.boolean().optional().describe("Change public visibility"),
|
|
42
|
+
}, async (params) => {
|
|
43
|
+
const { project_id, ...data } = params;
|
|
44
|
+
const result = await client.updateProject(project_id, data);
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerRelationsTools(server, client) {
|
|
3
|
+
// === ISSUE RELATIONS ===
|
|
4
|
+
server.registerTool("list_issue_relations", {
|
|
5
|
+
description: "List all relations for an issue",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
issue_id: z.number().describe("The issue ID"),
|
|
8
|
+
},
|
|
9
|
+
}, async (params) => {
|
|
10
|
+
const result = await client.listIssueRelations(params.issue_id);
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
server.registerTool("get_relation", {
|
|
16
|
+
description: "Get details of a specific relation",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
relation_id: z.number().describe("The relation ID"),
|
|
19
|
+
},
|
|
20
|
+
}, async (params) => {
|
|
21
|
+
const result = await client.getRelation(params.relation_id);
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
server.registerTool("create_issue_relation", {
|
|
27
|
+
description: "Create a relation between two issues",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
issue_id: z.number().describe("The source issue ID"),
|
|
30
|
+
issue_to_id: z.number().describe("The target issue ID"),
|
|
31
|
+
relation_type: z
|
|
32
|
+
.enum([
|
|
33
|
+
"relates",
|
|
34
|
+
"duplicates",
|
|
35
|
+
"duplicated",
|
|
36
|
+
"blocks",
|
|
37
|
+
"blocked",
|
|
38
|
+
"precedes",
|
|
39
|
+
"follows",
|
|
40
|
+
"copied_to",
|
|
41
|
+
"copied_from",
|
|
42
|
+
])
|
|
43
|
+
.describe("Type of relation"),
|
|
44
|
+
delay: z
|
|
45
|
+
.number()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Delay in days (only for precedes/follows)"),
|
|
48
|
+
},
|
|
49
|
+
}, async (params) => {
|
|
50
|
+
const { issue_id, ...data } = params;
|
|
51
|
+
const result = await client.createIssueRelation(issue_id, data);
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
server.registerTool("delete_relation", {
|
|
57
|
+
description: "Delete an issue relation",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
relation_id: z.number().describe("The relation ID to delete"),
|
|
60
|
+
},
|
|
61
|
+
}, async (params) => {
|
|
62
|
+
const result = await client.deleteRelation(params.relation_id);
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) },
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
// === VERSIONS ===
|
|
70
|
+
server.registerTool("list_versions", {
|
|
71
|
+
description: "List all versions (milestones) for a project",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
project_id: z
|
|
74
|
+
.union([z.string(), z.number()])
|
|
75
|
+
.describe("Project ID or identifier"),
|
|
76
|
+
},
|
|
77
|
+
}, async (params) => {
|
|
78
|
+
const result = await client.listVersions(params.project_id);
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
server.registerTool("get_version", {
|
|
84
|
+
description: "Get details of a specific version",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
version_id: z.number().describe("The version ID"),
|
|
87
|
+
},
|
|
88
|
+
}, async (params) => {
|
|
89
|
+
const result = await client.getVersion(params.version_id);
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
server.registerTool("create_version", {
|
|
95
|
+
description: "Create a new version (milestone) in a project",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
project_id: z
|
|
98
|
+
.union([z.string(), z.number()])
|
|
99
|
+
.describe("Project ID or identifier"),
|
|
100
|
+
name: z.string().describe("Version name"),
|
|
101
|
+
status: z
|
|
102
|
+
.enum(["open", "locked", "closed"])
|
|
103
|
+
.optional()
|
|
104
|
+
.describe("Version status"),
|
|
105
|
+
sharing: z
|
|
106
|
+
.enum(["none", "descendants", "hierarchy", "tree", "system"])
|
|
107
|
+
.optional()
|
|
108
|
+
.describe("Sharing scope: 'none' (this project only), 'descendants' (with subprojects), 'hierarchy' (with parent/children), 'tree' (whole project tree), 'system' (all projects)"),
|
|
109
|
+
due_date: z.string().optional().describe("Due date (YYYY-MM-DD)"),
|
|
110
|
+
description: z.string().optional().describe("Version description"),
|
|
111
|
+
wiki_page_title: z.string().optional().describe("Associated wiki page"),
|
|
112
|
+
},
|
|
113
|
+
}, async (params) => {
|
|
114
|
+
const { project_id, ...data } = params;
|
|
115
|
+
const result = await client.createVersion(project_id, data);
|
|
116
|
+
return {
|
|
117
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
server.registerTool("update_version", {
|
|
121
|
+
description: "Update an existing version",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
version_id: z.number().describe("The version ID to update"),
|
|
124
|
+
name: z.string().optional().describe("New version name"),
|
|
125
|
+
status: z
|
|
126
|
+
.enum(["open", "locked", "closed"])
|
|
127
|
+
.optional()
|
|
128
|
+
.describe("New status"),
|
|
129
|
+
sharing: z
|
|
130
|
+
.enum(["none", "descendants", "hierarchy", "tree", "system"])
|
|
131
|
+
.optional()
|
|
132
|
+
.describe("Sharing scope: 'none' (this project only), 'descendants' (with subprojects), 'hierarchy' (with parent/children), 'tree' (whole project tree), 'system' (all projects)"),
|
|
133
|
+
due_date: z.string().optional().describe("New due date"),
|
|
134
|
+
description: z.string().optional().describe("New description"),
|
|
135
|
+
wiki_page_title: z.string().optional().describe("New associated wiki page"),
|
|
136
|
+
},
|
|
137
|
+
}, async (params) => {
|
|
138
|
+
const { version_id, ...data } = params;
|
|
139
|
+
const result = await client.updateVersion(version_id, data);
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
server.registerTool("delete_version", {
|
|
145
|
+
description: "Delete a version",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
version_id: z.number().describe("The version ID to delete"),
|
|
148
|
+
},
|
|
149
|
+
}, async (params) => {
|
|
150
|
+
const result = await client.deleteVersion(params.version_id);
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) },
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerRolesTools(server, client) {
|
|
3
|
+
server.registerTool("list_roles", {
|
|
4
|
+
description: "List all available roles (Manager, Developer, Reporter, etc.)",
|
|
5
|
+
}, async () => {
|
|
6
|
+
const result = await client.listRoles();
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
server.registerTool("get_role", {
|
|
12
|
+
description: "Get role details including permissions",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
role_id: z.number().describe("Role ID"),
|
|
15
|
+
},
|
|
16
|
+
}, async (params) => {
|
|
17
|
+
const result = await client.getRole(params.role_id);
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerSearchTools(server, client) {
|
|
3
|
+
server.registerTool("search", {
|
|
4
|
+
description: "Search across Redmine (issues, wiki, news, etc.)",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
q: z.string().describe("Search query"),
|
|
7
|
+
scope: z.enum(["all", "my_projects", "subprojects"]).optional().describe("Search scope"),
|
|
8
|
+
all_words: z.boolean().optional().describe("Match all words (default true)"),
|
|
9
|
+
titles_only: z.boolean().optional().describe("Search only in titles"),
|
|
10
|
+
open_issues: z.boolean().optional().describe("Only open issues"),
|
|
11
|
+
attachments: z.enum(["0", "1", "only"]).optional().describe("Search attachments: 0=no, 1=yes, only=only attachments"),
|
|
12
|
+
issues: z.boolean().optional().describe("Include issues in results"),
|
|
13
|
+
news: z.boolean().optional().describe("Include news in results"),
|
|
14
|
+
documents: z.boolean().optional().describe("Include documents in results"),
|
|
15
|
+
changesets: z.boolean().optional().describe("Include changesets in results"),
|
|
16
|
+
wiki_pages: z.boolean().optional().describe("Include wiki pages in results"),
|
|
17
|
+
messages: z.boolean().optional().describe("Include forum messages in results"),
|
|
18
|
+
projects: z.boolean().optional().describe("Include projects in results"),
|
|
19
|
+
limit: z.number().optional().describe("Maximum results"),
|
|
20
|
+
offset: z.number().optional().describe("Skip first N results"),
|
|
21
|
+
},
|
|
22
|
+
}, async (params) => {
|
|
23
|
+
const result = await client.search(params);
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerTimeTools(server, client) {
|
|
3
|
+
server.registerTool("list_time_entries", {
|
|
4
|
+
description: "List time entries with optional filters",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
project_id: z.union([z.string(), z.number()]).optional().describe("Filter by project ID or identifier"),
|
|
7
|
+
user_id: z.union([z.number(), z.string()]).optional().describe("Filter by user ID or 'me'"),
|
|
8
|
+
spent_on: z.string().optional().describe("Filter by exact date (YYYY-MM-DD)"),
|
|
9
|
+
from: z.string().optional().describe("Filter from date (YYYY-MM-DD)"),
|
|
10
|
+
to: z.string().optional().describe("Filter to date (YYYY-MM-DD)"),
|
|
11
|
+
limit: z.number().optional().describe("Maximum results (default 25, max 100)"),
|
|
12
|
+
offset: z.number().optional().describe("Skip first N results"),
|
|
13
|
+
},
|
|
14
|
+
}, async (params) => {
|
|
15
|
+
const result = await client.listTimeEntries(params);
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
server.registerTool("get_time_entry", {
|
|
21
|
+
description: "Get details of a specific time entry",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
time_entry_id: z.number().describe("The time entry ID"),
|
|
24
|
+
},
|
|
25
|
+
}, async (params) => {
|
|
26
|
+
const result = await client.getTimeEntry(params.time_entry_id);
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
server.registerTool("create_time_entry", {
|
|
32
|
+
description: "Log time on an issue or project",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
issue_id: z.number().optional().describe("Issue ID to log time on (either issue_id or project_id required)"),
|
|
35
|
+
project_id: z.union([z.string(), z.number()]).optional().describe("Project ID to log time on (either issue_id or project_id required)"),
|
|
36
|
+
hours: z.number().describe("Number of hours spent"),
|
|
37
|
+
activity_id: z.number().optional().describe("Activity ID (use list_time_entry_activities to get IDs)"),
|
|
38
|
+
spent_on: z.string().optional().describe("Date spent (YYYY-MM-DD, defaults to today)"),
|
|
39
|
+
comments: z.string().optional().describe("Description of work done (max 255 chars)"),
|
|
40
|
+
user_id: z.number().optional().describe("User ID to log time for (admin only)"),
|
|
41
|
+
},
|
|
42
|
+
}, async (params) => {
|
|
43
|
+
const result = await client.createTimeEntry(params);
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
server.registerTool("update_time_entry", {
|
|
49
|
+
description: "Update an existing time entry",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
time_entry_id: z.number().describe("The time entry ID to update"),
|
|
52
|
+
hours: z.number().optional().describe("New hours value"),
|
|
53
|
+
activity_id: z.number().optional().describe("New activity ID"),
|
|
54
|
+
spent_on: z.string().optional().describe("New date (YYYY-MM-DD)"),
|
|
55
|
+
comments: z.string().optional().describe("New comments"),
|
|
56
|
+
},
|
|
57
|
+
}, async (params) => {
|
|
58
|
+
const { time_entry_id, ...data } = params;
|
|
59
|
+
const result = await client.updateTimeEntry(time_entry_id, data);
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
server.registerTool("delete_time_entry", {
|
|
65
|
+
description: "Delete a time entry",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
time_entry_id: z.number().describe("The time entry ID to delete"),
|
|
68
|
+
},
|
|
69
|
+
}, async (params) => {
|
|
70
|
+
const result = await client.deleteTimeEntry(params.time_entry_id);
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerWikiTools(server, client) {
|
|
3
|
+
server.registerTool("list_wiki_pages", {
|
|
4
|
+
description: "List all wiki pages in a project",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
7
|
+
},
|
|
8
|
+
}, async (params) => {
|
|
9
|
+
const result = await client.listWikiPages(params.project_id);
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
server.registerTool("get_wiki_page", {
|
|
15
|
+
description: "Get content of a specific wiki page",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
18
|
+
page_name: z.string().describe("Wiki page name/title"),
|
|
19
|
+
version: z.number().optional().describe("Specific version number to retrieve"),
|
|
20
|
+
include: z.string().optional().describe("Include: attachments"),
|
|
21
|
+
},
|
|
22
|
+
}, async (params) => {
|
|
23
|
+
const result = await client.getWikiPage(params.project_id, params.page_name, {
|
|
24
|
+
version: params.version,
|
|
25
|
+
include: params.include,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
server.registerTool("create_wiki_page", {
|
|
32
|
+
description: "Create a new wiki page in a project",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
35
|
+
page_name: z.string().describe("Wiki page name/title (used in URL)"),
|
|
36
|
+
text: z.string().describe("Page content (supports Textile/Markdown)"),
|
|
37
|
+
comments: z.string().optional().describe("Edit comment for version history"),
|
|
38
|
+
parent_title: z.string().optional().describe("Parent page title for hierarchy"),
|
|
39
|
+
},
|
|
40
|
+
}, async (params) => {
|
|
41
|
+
const { project_id, page_name, ...data } = params;
|
|
42
|
+
const result = await client.createOrUpdateWikiPage(project_id, page_name, data);
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
server.registerTool("update_wiki_page", {
|
|
48
|
+
description: "Update an existing wiki page",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
51
|
+
page_name: z.string().describe("Wiki page name/title"),
|
|
52
|
+
text: z.string().describe("New page content"),
|
|
53
|
+
comments: z.string().optional().describe("Edit comment for version history"),
|
|
54
|
+
version: z.number().optional().describe("Expected version for conflict detection"),
|
|
55
|
+
},
|
|
56
|
+
}, async (params) => {
|
|
57
|
+
const { project_id, page_name, ...data } = params;
|
|
58
|
+
const result = await client.createOrUpdateWikiPage(project_id, page_name, data);
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
server.registerTool("delete_wiki_page", {
|
|
64
|
+
description: "Delete a wiki page and all its history",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
|
|
67
|
+
page_name: z.string().describe("Wiki page name/title to delete"),
|
|
68
|
+
},
|
|
69
|
+
}, async (params) => {
|
|
70
|
+
const result = await client.deleteWikiPage(params.project_id, params.page_name);
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }],
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pschroee/redmine-mcp",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "MCP server for Redmine - full API access with configurable tool groups",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"redmine-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"lint": "eslint src/",
|
|
13
|
+
"lint:fix": "eslint src/ --fix",
|
|
14
|
+
"prepublishOnly": "npm run lint && npm run build",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"redmine",
|
|
21
|
+
"claude"
|
|
22
|
+
],
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/pschroee/redmine-mcp.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/pschroee/redmine-mcp#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/pschroee/redmine-mcp/issues"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
44
|
+
"zod": "^3.23.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@eslint/js": "^9.39.2",
|
|
48
|
+
"@types/node": "^20.0.0",
|
|
49
|
+
"dotenv": "^17.2.3",
|
|
50
|
+
"eslint": "^9.39.2",
|
|
51
|
+
"typescript": "^5.0.0",
|
|
52
|
+
"typescript-eslint": "^8.52.0",
|
|
53
|
+
"vitest": "^4.0.16"
|
|
54
|
+
}
|
|
55
|
+
}
|