@pschroee/redmine-mcp 0.5.7 → 0.5.10

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.
@@ -7,7 +7,7 @@ export { formatWikiPage, formatWikiPageList } from "./wiki.js";
7
7
  export { formatVersion, formatVersionList } from "./version.js";
8
8
  export { formatTimeEntry, formatTimeEntryList } from "./time.js";
9
9
  export { formatGroup, formatGroupList } from "./group.js";
10
- export { formatTrackerList, formatStatusList, formatCategoryList, formatPriorityList, formatActivityList, formatRoleList, formatRole, formatCategory, formatCustomFieldList, formatQueryList, formatDocumentCategoryList, } from "./metadata.js";
10
+ export { formatTrackerList, formatStatusList, formatCategoryList, formatPriorityList, formatActivityList, formatRoleList, formatRole, formatCategory, formatCustomFieldList, formatQueryList, formatDocumentCategoryList, type ProjectLookup, } from "./metadata.js";
11
11
  export { formatRelation, formatRelationList } from "./relation.js";
12
12
  export { formatAttachment, formatFileList } from "./file.js";
13
13
  export { formatMembership, formatMembershipList } from "./membership.js";
@@ -124,10 +124,14 @@ export declare function formatCategory(response: {
124
124
  * Format a list of custom fields as a Markdown table
125
125
  */
126
126
  export declare function formatCustomFieldList(response: RedmineCustomFieldsResponse): string;
127
+ /**
128
+ * Project lookup map: ID -> name
129
+ */
130
+ export type ProjectLookup = Record<number, string>;
127
131
  /**
128
132
  * Format a list of saved queries as a Markdown table
129
133
  */
130
- export declare function formatQueryList(response: RedmineQueriesResponse): string;
134
+ export declare function formatQueryList(response: RedmineQueriesResponse, projectLookup?: ProjectLookup): string;
131
135
  /**
132
136
  * Format a list of document categories as a Markdown table
133
137
  */
@@ -176,7 +176,7 @@ export function formatCustomFieldList(response) {
176
176
  /**
177
177
  * Format a list of saved queries as a Markdown table
178
178
  */
179
- export function formatQueryList(response) {
179
+ export function formatQueryList(response, projectLookup = {}) {
180
180
  const queries = response.queries;
181
181
  if (queries.length === 0) {
182
182
  return "No queries found.";
@@ -188,7 +188,11 @@ export function formatQueryList(response) {
188
188
  lines.push("|----|------|---------|------------|");
189
189
  for (const query of queries) {
190
190
  const visibility = query.is_public ? "Public" : "Private";
191
- const project = query.project_id ? `#${query.project_id}` : "Global";
191
+ let project = "Global";
192
+ if (query.project_id) {
193
+ const projectName = projectLookup[query.project_id];
194
+ project = projectName ? `${projectName} (${query.project_id})` : `#${query.project_id}`;
195
+ }
192
196
  lines.push(`| ${query.id} | ${query.name} | ${project} | ${visibility} |`);
193
197
  }
194
198
  return lines.join("\n");
@@ -230,9 +230,7 @@ export declare class RedmineClient {
230
230
  }>>;
231
231
  deleteIssueCategory(id: number, reassignToId?: number): Promise<RedmineResult<void>>;
232
232
  listCustomFields(): Promise<RedmineResult<RedmineCustomFieldsResponse>>;
233
- listQueries(params?: {
234
- project_id?: string | number;
235
- }): Promise<RedmineResult<RedmineQueriesResponse>>;
233
+ listQueries(): Promise<RedmineResult<RedmineQueriesResponse>>;
236
234
  search(params: {
237
235
  q: string;
238
236
  scope?: string;
@@ -305,10 +305,7 @@ export class RedmineClient {
305
305
  return this.request("GET", "/custom_fields.json");
306
306
  }
307
307
  // ==================== QUERIES ====================
308
- async listQueries(params) {
309
- if (params?.project_id) {
310
- return this.request("GET", `/projects/${params.project_id}/queries.json`);
311
- }
308
+ async listQueries() {
312
309
  return this.request("GET", "/queries.json");
313
310
  }
314
311
  // ==================== SEARCH ====================
@@ -5,7 +5,7 @@ export function registerAgileTools(server, client) {
5
5
  server.registerTool("list_agile_sprints", {
6
6
  description: "List all agile sprints for a project (requires redmine_agile plugin)",
7
7
  inputSchema: {
8
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
8
+ project_id: z.string().describe("Project identifier"),
9
9
  },
10
10
  }, async (params) => {
11
11
  const result = await client.listAgileSprints(params.project_id);
@@ -21,7 +21,7 @@ export function registerAgileTools(server, client) {
21
21
  server.registerTool("get_agile_sprint", {
22
22
  description: "Get details of a specific agile sprint (requires redmine_agile plugin)",
23
23
  inputSchema: {
24
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
24
+ project_id: z.string().describe("Project identifier"),
25
25
  sprint_id: z.number().describe("The sprint ID"),
26
26
  },
27
27
  }, async (params) => {
@@ -38,7 +38,7 @@ export function registerAgileTools(server, client) {
38
38
  server.registerTool("create_agile_sprint", {
39
39
  description: "Create a new agile sprint for a project (requires redmine_agile plugin)",
40
40
  inputSchema: {
41
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
41
+ project_id: z.string().describe("Project identifier"),
42
42
  name: z.string().describe("Sprint name"),
43
43
  status: z.string().optional().describe("Sprint status: open, active, closed"),
44
44
  start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
@@ -56,7 +56,7 @@ export function registerAgileTools(server, client) {
56
56
  server.registerTool("update_agile_sprint", {
57
57
  description: "Update an existing agile sprint (requires redmine_agile plugin)",
58
58
  inputSchema: {
59
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
59
+ project_id: z.string().describe("Project identifier"),
60
60
  sprint_id: z.number().describe("The sprint ID to update"),
61
61
  name: z.string().optional().describe("New sprint name"),
62
62
  status: z.string().optional().describe("Sprint status: open, active, closed"),
@@ -75,7 +75,7 @@ export function registerAgileTools(server, client) {
75
75
  server.registerTool("delete_agile_sprint", {
76
76
  description: "Delete an agile sprint (requires redmine_agile plugin)",
77
77
  inputSchema: {
78
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
78
+ project_id: z.string().describe("Project identifier"),
79
79
  sprint_id: z.number().describe("The sprint ID to delete"),
80
80
  },
81
81
  }, async (params) => {
@@ -5,7 +5,7 @@ export function registerCoreTools(server, client) {
5
5
  server.registerTool("list_issues", {
6
6
  description: "List issues from Redmine with optional filters and sorting",
7
7
  inputSchema: {
8
- project_id: z.union([z.string(), z.number()]).optional().describe("Filter by project ID or identifier"),
8
+ project_id: z.string().optional().describe("Filter by project identifier"),
9
9
  tracker_id: z.number().optional().describe("Filter by tracker ID"),
10
10
  status_id: z.union([z.string(), z.number()]).optional().describe("Filter by status: 'open', 'closed', '*', or status ID"),
11
11
  assigned_to_id: z.union([z.number(), z.string()]).optional().describe("Filter by assigned user ID or 'me'"),
@@ -85,7 +85,7 @@ export function registerCoreTools(server, client) {
85
85
  server.registerTool("create_issue", {
86
86
  description: "Create a new issue in Redmine",
87
87
  inputSchema: {
88
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
88
+ project_id: z.string().describe("Project identifier"),
89
89
  subject: z.string().describe("Issue subject/title"),
90
90
  description: z.string().optional().describe("Issue description (supports Textile/Markdown)"),
91
91
  tracker_id: z.number().optional().describe("Tracker ID (e.g., Bug, Feature)"),
@@ -117,7 +117,7 @@ export function registerCoreTools(server, client) {
117
117
  issue_id: z.number().describe("The issue ID to update"),
118
118
  subject: z.string().optional().describe("New subject/title"),
119
119
  description: z.string().optional().describe("New description"),
120
- project_id: z.union([z.string(), z.number()]).optional().describe("Move to different project"),
120
+ project_id: z.string().optional().describe("Move to different project"),
121
121
  tracker_id: z.number().optional().describe("Change tracker"),
122
122
  status_id: z.number().optional().describe("Change status"),
123
123
  priority_id: z.number().optional().describe("Change priority"),
@@ -201,7 +201,7 @@ export function registerCoreTools(server, client) {
201
201
  server.registerTool("get_project", {
202
202
  description: "Get details of a specific project",
203
203
  inputSchema: {
204
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
204
+ project_id: z.string().describe("Project identifier"),
205
205
  include: z.string().optional().describe("Include: trackers, issue_categories, enabled_modules, time_entry_activities, issue_custom_fields"),
206
206
  },
207
207
  }, async (params) => {
@@ -240,7 +240,7 @@ export function registerCoreTools(server, client) {
240
240
  server.registerTool("update_project", {
241
241
  description: "Update an existing project",
242
242
  inputSchema: {
243
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
243
+ project_id: z.string().describe("Project identifier"),
244
244
  name: z.string().optional().describe("New project name"),
245
245
  description: z.string().optional().describe("New description"),
246
246
  homepage: z.string().optional().describe("New homepage URL"),
@@ -263,7 +263,7 @@ export function registerCoreTools(server, client) {
263
263
  server.registerTool("delete_project", {
264
264
  description: "Delete a project permanently (requires admin privileges)",
265
265
  inputSchema: {
266
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier to delete"),
266
+ project_id: z.string().describe("Project identifier to delete"),
267
267
  },
268
268
  }, async (params) => {
269
269
  const result = await client.deleteProject(params.project_id);
@@ -274,7 +274,7 @@ export function registerCoreTools(server, client) {
274
274
  server.registerTool("archive_project", {
275
275
  description: "Archive a project (Redmine 5.0+)",
276
276
  inputSchema: {
277
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier to archive"),
277
+ project_id: z.string().describe("Project identifier to archive"),
278
278
  },
279
279
  }, async (params) => {
280
280
  const result = await client.archiveProject(params.project_id);
@@ -285,7 +285,7 @@ export function registerCoreTools(server, client) {
285
285
  server.registerTool("unarchive_project", {
286
286
  description: "Unarchive a project (Redmine 5.0+)",
287
287
  inputSchema: {
288
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier to unarchive"),
288
+ project_id: z.string().describe("Project identifier to unarchive"),
289
289
  },
290
290
  }, async (params) => {
291
291
  const result = await client.unarchiveProject(params.project_id);
@@ -49,7 +49,7 @@ export function registerFilesTools(server, client) {
49
49
  server.registerTool("list_project_files", {
50
50
  description: "List all files attached to a project",
51
51
  inputSchema: {
52
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
52
+ project_id: z.string().describe("Project identifier"),
53
53
  },
54
54
  }, async (params) => {
55
55
  const result = await client.listProjectFiles(params.project_id);
@@ -65,7 +65,7 @@ export function registerFilesTools(server, client) {
65
65
  server.registerTool("upload_project_file", {
66
66
  description: "Attach an uploaded file to a project",
67
67
  inputSchema: {
68
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
68
+ project_id: z.string().describe("Project identifier"),
69
69
  token: z.string().describe("Upload token from upload_file"),
70
70
  version_id: z.number().optional().describe("Associated version ID"),
71
71
  filename: z.string().optional().describe("Override filename"),
@@ -4,7 +4,7 @@ export function registerMembershipsTools(server, client) {
4
4
  server.registerTool("list_project_memberships", {
5
5
  description: "List all memberships (users and groups) for a project",
6
6
  inputSchema: {
7
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
7
+ project_id: z.string().describe("Project identifier"),
8
8
  limit: z.number().optional().describe("Maximum results"),
9
9
  offset: z.number().optional().describe("Skip first N results"),
10
10
  },
@@ -39,7 +39,7 @@ export function registerMembershipsTools(server, client) {
39
39
  server.registerTool("create_project_membership", {
40
40
  description: "Add a user or group to a project with specified roles",
41
41
  inputSchema: {
42
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
42
+ project_id: z.string().describe("Project identifier"),
43
43
  user_id: z.number().describe("User ID or Group ID to add"),
44
44
  role_ids: z.array(z.number()).describe("Role IDs to assign (use list_roles to get IDs)"),
45
45
  },
@@ -33,7 +33,7 @@ export function registerMetadataTools(server, client) {
33
33
  server.registerTool("list_issue_categories", {
34
34
  description: "List all issue categories for a project",
35
35
  inputSchema: {
36
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
36
+ project_id: z.string().describe("Project identifier"),
37
37
  },
38
38
  }, async (params) => {
39
39
  const result = await client.listIssueCategories(params.project_id);
@@ -65,7 +65,7 @@ export function registerMetadataTools(server, client) {
65
65
  server.registerTool("create_issue_category", {
66
66
  description: "Create a new issue category in a project",
67
67
  inputSchema: {
68
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
68
+ project_id: z.string().describe("Project identifier"),
69
69
  name: z.string().describe("Category name"),
70
70
  assigned_to_id: z.number().optional().describe("Default assignee user ID for this category"),
71
71
  },
@@ -120,17 +120,40 @@ export function registerMetadataTools(server, client) {
120
120
  server.registerTool("list_queries", {
121
121
  description: "List all saved issue queries (public and private)",
122
122
  inputSchema: {
123
- project_id: z.union([z.string(), z.number()]).optional().describe("Filter queries by project ID or identifier"),
123
+ project_id: z.string().optional().describe("Filter queries by project identifier"),
124
124
  },
125
125
  }, async (params) => {
126
- const result = await client.listQueries(params.project_id ? { project_id: params.project_id } : undefined);
126
+ const result = await client.listQueries();
127
127
  if ("error" in result) {
128
128
  return {
129
129
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
130
130
  };
131
131
  }
132
+ // Build project lookup for resolving project names
133
+ const projectLookup = {};
134
+ const projectsResult = await client.listProjects({ limit: 100 });
135
+ let filterProjectId;
136
+ if (!("error" in projectsResult)) {
137
+ for (const project of projectsResult.projects) {
138
+ projectLookup[project.id] = project.name;
139
+ // Find the numeric ID for the filter project identifier
140
+ if (params.project_id && project.identifier === params.project_id) {
141
+ filterProjectId = project.id;
142
+ }
143
+ }
144
+ }
145
+ // Filter queries by project if requested
146
+ let queries = result.queries;
147
+ if (params.project_id) {
148
+ if (filterProjectId === undefined) {
149
+ return {
150
+ content: [{ type: "text", text: `Project "${params.project_id}" not found.` }],
151
+ };
152
+ }
153
+ queries = queries.filter(q => q.project_id === filterProjectId);
154
+ }
132
155
  return {
133
- content: [{ type: "text", text: formatQueryList(result) }],
156
+ content: [{ type: "text", text: formatQueryList({ queries }, projectLookup) }],
134
157
  };
135
158
  });
136
159
  }
@@ -4,7 +4,7 @@ export function registerTimeTools(server, client) {
4
4
  server.registerTool("list_time_entries", {
5
5
  description: "List time entries with optional filters",
6
6
  inputSchema: {
7
- project_id: z.union([z.string(), z.number()]).optional().describe("Filter by project ID or identifier"),
7
+ project_id: z.string().optional().describe("Filter by project identifier"),
8
8
  user_id: z.union([z.number(), z.string()]).optional().describe("Filter by user ID or 'me'"),
9
9
  spent_on: z.string().optional().describe("Filter by exact date (YYYY-MM-DD)"),
10
10
  from: z.string().optional().describe("Filter from date (YYYY-MM-DD)"),
@@ -43,7 +43,7 @@ export function registerTimeTools(server, client) {
43
43
  description: "Log time on an issue or project",
44
44
  inputSchema: {
45
45
  issue_id: z.number().optional().describe("Issue ID to log time on (either issue_id or project_id required)"),
46
- project_id: z.union([z.string(), z.number()]).optional().describe("Project ID to log time on (either issue_id or project_id required)"),
46
+ project_id: z.string().optional().describe("Project identifier to log time on (either issue_id or project_id required)"),
47
47
  hours: z.number().describe("Number of hours spent"),
48
48
  activity_id: z.number().optional().describe("Activity ID (use list_time_entry_activities to get IDs)"),
49
49
  spent_on: z.string().optional().describe("Date spent (YYYY-MM-DD, defaults to today)"),
@@ -4,7 +4,7 @@ export function registerWikiTools(server, client) {
4
4
  server.registerTool("list_wiki_pages", {
5
5
  description: "List all wiki pages in a project",
6
6
  inputSchema: {
7
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
7
+ project_id: z.string().describe("Project identifier"),
8
8
  },
9
9
  }, async (params) => {
10
10
  const result = await client.listWikiPages(params.project_id);
@@ -20,7 +20,7 @@ export function registerWikiTools(server, client) {
20
20
  server.registerTool("get_wiki_page", {
21
21
  description: "Get content of a specific wiki page",
22
22
  inputSchema: {
23
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
23
+ project_id: z.string().describe("Project identifier"),
24
24
  page_name: z.string().describe("Wiki page name/title"),
25
25
  version: z.number().optional().describe("Specific version number to retrieve"),
26
26
  include: z.string().optional().describe("Include: attachments"),
@@ -42,7 +42,7 @@ export function registerWikiTools(server, client) {
42
42
  server.registerTool("create_wiki_page", {
43
43
  description: "Create a new wiki page in a project",
44
44
  inputSchema: {
45
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
45
+ project_id: z.string().describe("Project identifier"),
46
46
  page_name: z.string().describe("Wiki page name/title (used in URL)"),
47
47
  text: z.string().describe("Page content (supports Textile/Markdown)"),
48
48
  comments: z.string().optional().describe("Edit comment for version history"),
@@ -58,7 +58,7 @@ export function registerWikiTools(server, client) {
58
58
  server.registerTool("update_wiki_page", {
59
59
  description: "Update an existing wiki page",
60
60
  inputSchema: {
61
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
61
+ project_id: z.string().describe("Project identifier"),
62
62
  page_name: z.string().describe("Wiki page name/title"),
63
63
  text: z.string().describe("New page content"),
64
64
  comments: z.string().optional().describe("Edit comment for version history"),
@@ -74,7 +74,7 @@ export function registerWikiTools(server, client) {
74
74
  server.registerTool("delete_wiki_page", {
75
75
  description: "Delete a wiki page and all its history",
76
76
  inputSchema: {
77
- project_id: z.union([z.string(), z.number()]).describe("Project ID or identifier"),
77
+ project_id: z.string().describe("Project identifier"),
78
78
  page_name: z.string().describe("Wiki page name/title to delete"),
79
79
  },
80
80
  }, async (params) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pschroee/redmine-mcp",
3
- "version": "0.5.7",
3
+ "version": "0.5.10",
4
4
  "description": "MCP server for Redmine - full API access with configurable tool groups",
5
5
  "type": "module",
6
6
  "bin": {