@muzzamil-khan/redmine-agent-mcp 1.0.0 → 1.2.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.
Files changed (119) hide show
  1. package/dist/config/env.d.ts +2 -0
  2. package/dist/config/env.d.ts.map +1 -1
  3. package/dist/config/env.js +8 -0
  4. package/dist/config/env.js.map +1 -1
  5. package/dist/mcp/server.js +719 -147
  6. package/dist/mcp/server.js.map +1 -1
  7. package/dist/services/assigneeDirectory.d.ts +34 -0
  8. package/dist/services/assigneeDirectory.d.ts.map +1 -0
  9. package/dist/services/assigneeDirectory.js +138 -0
  10. package/dist/services/assigneeDirectory.js.map +1 -0
  11. package/dist/services/googleSheets.d.ts +32 -0
  12. package/dist/services/googleSheets.d.ts.map +1 -0
  13. package/dist/services/googleSheets.js +109 -0
  14. package/dist/services/googleSheets.js.map +1 -0
  15. package/dist/services/redmine.d.ts +72 -0
  16. package/dist/services/redmine.d.ts.map +1 -1
  17. package/dist/services/redmine.js +141 -0
  18. package/dist/services/redmine.js.map +1 -1
  19. package/dist/services/redmineConfig.d.ts +90 -0
  20. package/dist/services/redmineConfig.d.ts.map +1 -0
  21. package/dist/services/redmineConfig.js +300 -0
  22. package/dist/services/redmineConfig.js.map +1 -0
  23. package/dist/spreadsheet/batchColumnMapping.d.ts +93 -0
  24. package/dist/spreadsheet/batchColumnMapping.d.ts.map +1 -0
  25. package/dist/spreadsheet/batchColumnMapping.js +666 -0
  26. package/dist/spreadsheet/batchColumnMapping.js.map +1 -0
  27. package/dist/spreadsheet/batchCreationTypes.d.ts +61 -0
  28. package/dist/spreadsheet/batchCreationTypes.d.ts.map +1 -0
  29. package/dist/spreadsheet/batchCreationTypes.js +18 -0
  30. package/dist/spreadsheet/batchCreationTypes.js.map +1 -0
  31. package/dist/spreadsheet/batchIssueConfig.d.ts +26 -0
  32. package/dist/spreadsheet/batchIssueConfig.d.ts.map +1 -0
  33. package/dist/spreadsheet/batchIssueConfig.js +106 -0
  34. package/dist/spreadsheet/batchIssueConfig.js.map +1 -0
  35. package/dist/spreadsheet/batchPreviewFormat.d.ts +41 -0
  36. package/dist/spreadsheet/batchPreviewFormat.d.ts.map +1 -0
  37. package/dist/spreadsheet/batchPreviewFormat.js +194 -0
  38. package/dist/spreadsheet/batchPreviewFormat.js.map +1 -0
  39. package/dist/spreadsheet/batchSheetHeuristics.d.ts +13 -0
  40. package/dist/spreadsheet/batchSheetHeuristics.d.ts.map +1 -0
  41. package/dist/spreadsheet/batchSheetHeuristics.js +30 -0
  42. package/dist/spreadsheet/batchSheetHeuristics.js.map +1 -0
  43. package/dist/spreadsheet/bulkUpdateExtract.d.ts +14 -0
  44. package/dist/spreadsheet/bulkUpdateExtract.d.ts.map +1 -0
  45. package/dist/spreadsheet/bulkUpdateExtract.js +83 -0
  46. package/dist/spreadsheet/bulkUpdateExtract.js.map +1 -0
  47. package/dist/spreadsheet/csvUtils.d.ts +14 -0
  48. package/dist/spreadsheet/csvUtils.d.ts.map +1 -0
  49. package/dist/spreadsheet/csvUtils.js +113 -0
  50. package/dist/spreadsheet/csvUtils.js.map +1 -0
  51. package/dist/spreadsheet/extractRawBatchSheet.d.ts +14 -0
  52. package/dist/spreadsheet/extractRawBatchSheet.d.ts.map +1 -0
  53. package/dist/spreadsheet/extractRawBatchSheet.js +33 -0
  54. package/dist/spreadsheet/extractRawBatchSheet.js.map +1 -0
  55. package/dist/spreadsheet/featureRow.d.ts +15 -0
  56. package/dist/spreadsheet/featureRow.d.ts.map +1 -0
  57. package/dist/spreadsheet/featureRow.js +119 -0
  58. package/dist/spreadsheet/featureRow.js.map +1 -0
  59. package/dist/spreadsheet/parseBatchCreationCsv.d.ts +8 -0
  60. package/dist/spreadsheet/parseBatchCreationCsv.d.ts.map +1 -0
  61. package/dist/spreadsheet/parseBatchCreationCsv.js +22 -0
  62. package/dist/spreadsheet/parseBatchCreationCsv.js.map +1 -0
  63. package/dist/spreadsheet/parseBatchCreationCsvV1.d.ts +3 -0
  64. package/dist/spreadsheet/parseBatchCreationCsvV1.d.ts.map +1 -0
  65. package/dist/spreadsheet/parseBatchCreationCsvV1.js +226 -0
  66. package/dist/spreadsheet/parseBatchCreationCsvV1.js.map +1 -0
  67. package/dist/spreadsheet/parseBatchCreationCsvV2.d.ts +4 -0
  68. package/dist/spreadsheet/parseBatchCreationCsvV2.d.ts.map +1 -0
  69. package/dist/spreadsheet/parseBatchCreationCsvV2.js +180 -0
  70. package/dist/spreadsheet/parseBatchCreationCsvV2.js.map +1 -0
  71. package/dist/spreadsheet/redmineIssueCell.d.ts +38 -0
  72. package/dist/spreadsheet/redmineIssueCell.d.ts.map +1 -0
  73. package/dist/spreadsheet/redmineIssueCell.js +176 -0
  74. package/dist/spreadsheet/redmineIssueCell.js.map +1 -0
  75. package/dist/spreadsheet/sheetColumnMap.d.ts +38 -0
  76. package/dist/spreadsheet/sheetColumnMap.d.ts.map +1 -0
  77. package/dist/spreadsheet/sheetColumnMap.js +75 -0
  78. package/dist/spreadsheet/sheetColumnMap.js.map +1 -0
  79. package/dist/spreadsheet/sheetGridToCsv.d.ts +28 -0
  80. package/dist/spreadsheet/sheetGridToCsv.d.ts.map +1 -0
  81. package/dist/spreadsheet/sheetGridToCsv.js +88 -0
  82. package/dist/spreadsheet/sheetGridToCsv.js.map +1 -0
  83. package/dist/spreadsheet/sheetRedmineFields.d.ts +7 -0
  84. package/dist/spreadsheet/sheetRedmineFields.d.ts.map +1 -0
  85. package/dist/spreadsheet/sheetRedmineFields.js +22 -0
  86. package/dist/spreadsheet/sheetRedmineFields.js.map +1 -0
  87. package/dist/spreadsheet/sheetWriteBack.d.ts +27 -0
  88. package/dist/spreadsheet/sheetWriteBack.d.ts.map +1 -0
  89. package/dist/spreadsheet/sheetWriteBack.js +90 -0
  90. package/dist/spreadsheet/sheetWriteBack.js.map +1 -0
  91. package/dist/workflows/batchCreateIssuesWorkflow.d.ts +69 -0
  92. package/dist/workflows/batchCreateIssuesWorkflow.d.ts.map +1 -0
  93. package/dist/workflows/batchCreateIssuesWorkflow.js +1102 -0
  94. package/dist/workflows/batchCreateIssuesWorkflow.js.map +1 -0
  95. package/dist/workflows/bulkUpdateIssuesWorkflow.d.ts +21 -0
  96. package/dist/workflows/bulkUpdateIssuesWorkflow.d.ts.map +1 -0
  97. package/dist/workflows/bulkUpdateIssuesWorkflow.js +384 -0
  98. package/dist/workflows/bulkUpdateIssuesWorkflow.js.map +1 -0
  99. package/dist/workflows/createIssueWorkflow.d.ts +41 -0
  100. package/dist/workflows/createIssueWorkflow.d.ts.map +1 -0
  101. package/dist/workflows/createIssueWorkflow.js +697 -0
  102. package/dist/workflows/createIssueWorkflow.js.map +1 -0
  103. package/dist/workflows/getAllUsersWorkflow.d.ts +27 -0
  104. package/dist/workflows/getAllUsersWorkflow.d.ts.map +1 -0
  105. package/dist/workflows/getAllUsersWorkflow.js +48 -0
  106. package/dist/workflows/getAllUsersWorkflow.js.map +1 -0
  107. package/dist/workflows/issueCreationHelpers.d.ts +54 -0
  108. package/dist/workflows/issueCreationHelpers.d.ts.map +1 -0
  109. package/dist/workflows/issueCreationHelpers.js +272 -0
  110. package/dist/workflows/issueCreationHelpers.js.map +1 -0
  111. package/dist/workflows/logTimeWorkflow.d.ts +6 -1
  112. package/dist/workflows/logTimeWorkflow.d.ts.map +1 -1
  113. package/dist/workflows/logTimeWorkflow.js +139 -20
  114. package/dist/workflows/logTimeWorkflow.js.map +1 -1
  115. package/dist/workflows/sheetToCsvWorkflow.d.ts +15 -0
  116. package/dist/workflows/sheetToCsvWorkflow.d.ts.map +1 -0
  117. package/dist/workflows/sheetToCsvWorkflow.js +65 -0
  118. package/dist/workflows/sheetToCsvWorkflow.js.map +1 -0
  119. package/package.json +2 -2
@@ -3,155 +3,727 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import * as z from "zod/v4";
5
5
  import { logTimeWorkflow } from "../workflows/logTimeWorkflow.js";
6
+ import { createIssueWorkflow } from "../workflows/createIssueWorkflow.js";
7
+ import { batchCreateIssuesWorkflow } from "../workflows/batchCreateIssuesWorkflow.js";
8
+ import { bulkUpdateIssuesWorkflow } from "../workflows/bulkUpdateIssuesWorkflow.js";
9
+ import { sheetToCsvWorkflow } from "../workflows/sheetToCsvWorkflow.js";
10
+ import { getAllUsersWorkflow } from "../workflows/getAllUsersWorkflow.js";
6
11
  import { workflowToMcpContent } from "../utils/mcpResponse.js";
7
- const server = new McpServer({
8
- name: "redmine-agent",
9
- version: "3.4.1",
10
- }, {
11
- instructions: `Local Redmine tool: batch log time only (redmine_agent_log_time).
12
- When needsInput and data.batchMode is true, show ALL items in questions[] in ONE AskQuestion. dateIssues ids are YYYY-MM-DD; pairHours/pairComments ids are YYYY-MM-DD:issueId.
13
- Flow: datesbatch dateIssuesbatch pairHoursbatch pairCommentspreviewconfirmSave=yes (no yes/no pair step).`,
14
- });
15
- server.registerTool("redmine_agent_log_time", {
16
- description: "Batch log time: dates batch dateIssues (per date, one page) batch pairHours → batch pairComments → preview → confirmSave=yes. Selecting issues per date defines what to log; no pairInclude step.",
17
- inputSchema: {
18
- promptHint: z
19
- .string()
20
- .optional()
21
- .describe("User message text; if it mentions 'today', date step is skipped"),
22
- dates: z
23
- .array(z.string())
24
- .optional()
25
- .describe("Step 1 — Today | Multiple | Other, or YYYY-MM-DD list"),
26
- spentOn: z
27
- .string()
28
- .optional()
29
- .describe("Custom date YYYY-MM-DD when Other was chosen"),
30
- pendingDates: z.array(z.string()).optional(),
31
- activeDate: z.string().optional(),
32
- dateIssues: z
33
- .record(z.string(), z.union([
34
- z.array(z.number().int().positive()),
35
- z.string(),
36
- ]))
37
- .optional()
38
- .describe("Step 2 batch — issue IDs per date (number[] or comma-separated string)"),
39
- pendingIssueIds: z.array(z.number().int().positive()).optional(),
40
- activeIssueId: z.number().int().positive().optional(),
41
- issueIds: z
42
- .array(z.number().int().positive())
43
- .optional()
44
- .describe("Legacy — fills all dates if dateIssues omitted (single date only)"),
45
- pairInclude: z
46
- .enum(["yes", "no"])
47
- .optional()
48
- .describe("Legacy single pair; prefer pairIncludes"),
49
- pairIncludes: z
50
- .record(z.string(), z.string())
51
- .optional()
52
- .describe('Step 3 batch — all yes/no keyed by "YYYY-MM-DD:issueId"'),
53
- pairHours: z
54
- .record(z.string(), z.union([z.number(), z.string()]))
55
- .optional()
56
- .describe("Step 4 batch — hours per included pair key"),
57
- pairComments: z
58
- .record(z.string(), z.string())
59
- .optional()
60
- .describe('Step 5 batch — "default" or custom text per pair key'),
61
- issueSubjects: z
62
- .record(z.string(), z.string())
63
- .optional()
64
- .describe("issueId → subject; pass back from data for question labels"),
65
- issueOptions: z
66
- .array(z.object({
67
- issueId: z.number().int().positive(),
68
- subject: z.string().optional(),
69
- label: z.string().optional(),
70
- }))
71
- .optional(),
72
- skippedPairs: z
73
- .array(z.string())
74
- .optional()
75
- .describe('Skipped pair keys "YYYY-MM-DD:issueId"'),
76
- issueId: z.number().int().positive().optional(),
77
- hours: z.number().positive().optional().describe("Legacy single pair hours"),
78
- commentChoice: z.enum(["default", "custom"]).optional(),
79
- comments: z.string().optional(),
80
- entries: z
81
- .array(z.object({
82
- issueId: z.number().int().positive(),
83
- hours: z.number().positive(),
84
- spentOn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
85
- comments: z.string(),
86
- }))
87
- .optional(),
88
- confirmSave: z.enum(["yes", "no"]).optional(),
89
- },
90
- }, async (input) => workflowToMcpContent(await logTimeWorkflow({
91
- ...(input.promptHint !== undefined
92
- ? { promptHint: input.promptHint }
93
- : {}),
94
- ...(input.dates !== undefined ? { dates: input.dates } : {}),
95
- ...(input.spentOn !== undefined
96
- ? { spentOn: input.spentOn }
97
- : {}),
98
- ...(input.pendingDates !== undefined
99
- ? { pendingDates: input.pendingDates }
100
- : {}),
101
- ...(input.activeDate !== undefined
102
- ? { activeDate: input.activeDate }
103
- : {}),
104
- ...(input.dateIssues !== undefined
105
- ? { dateIssues: input.dateIssues }
106
- : {}),
107
- ...(input.pendingIssueIds !== undefined
108
- ? { pendingIssueIds: input.pendingIssueIds }
109
- : {}),
110
- ...(input.activeIssueId !== undefined
111
- ? { activeIssueId: input.activeIssueId }
112
- : {}),
113
- ...(input.issueIds !== undefined
114
- ? { issueIds: input.issueIds }
115
- : {}),
116
- ...(input.issueId !== undefined
117
- ? { issueId: input.issueId }
118
- : {}),
119
- ...(input.hours !== undefined ? { hours: input.hours } : {}),
120
- ...(input.pairInclude !== undefined
121
- ? { pairInclude: input.pairInclude }
122
- : {}),
123
- ...(input.pairIncludes !== undefined
124
- ? { pairIncludes: input.pairIncludes }
125
- : {}),
126
- ...(input.pairHours !== undefined
127
- ? { pairHours: input.pairHours }
128
- : {}),
129
- ...(input.pairComments !== undefined
130
- ? { pairComments: input.pairComments }
131
- : {}),
132
- ...(input.issueSubjects !== undefined
133
- ? { issueSubjects: input.issueSubjects }
134
- : {}),
135
- ...(input.issueOptions !== undefined
136
- ? { issueOptions: input.issueOptions }
137
- : {}),
138
- ...(input.skippedPairs !== undefined
139
- ? { skippedPairs: input.skippedPairs }
140
- : {}),
141
- ...(input.commentChoice !== undefined
142
- ? { commentChoice: input.commentChoice }
143
- : {}),
144
- ...(input.comments !== undefined
145
- ? { comments: input.comments }
146
- : {}),
147
- ...(input.entries !== undefined
148
- ? { entries: input.entries }
149
- : {}),
150
- ...(input.confirmSave !== undefined
151
- ? { confirmSave: input.confirmSave }
152
- : {}),
153
- })));
12
+ import { getCurrentUserWithMemberships, getRolePermissions, } from "../services/redmine.js";
13
+ import { configOptionsPayload, formatConfigForInstructions, formatEnumerationLine, loadRedmineConfig, } from "../services/redmineConfig.js";
14
+ const BASE_MCP_INSTRUCTIONS = `Local Redmine tools: redmine_agent_log_time, redmine_agent_create_issue, redmine_agent_sheet_to_csv, redmine_agent_batch_create_issues, redmine_agent_bulk_update_issues, redmine_agent_get_all_users, redmine_agent_get_config.
15
+ Log time: when needsInput and data.batchMode is true, show ALL items in questions[] in ONE AskQuestion. Flow: dates → dateIssues → pairHours → pairActivities (mandatory; options from server config cache, not a separate MCP call) → pairComments → confirmSave=yes.
16
+ Create issue: when needsInput and data.batchMode is true, show ALL questions[] in ONE AskQuestion. Flow: full intake preview → confirmSave=yes. Use tracker/status/priority names or ids from loaded config; tracker aliases CR→Change Request, bug→Bug, story→User Story.
17
+ Sheet batch: get_sheet_data → sheet_to_csv → redmine_agent_get_config → batch_create_issues. Steps: pick_project → pick_target_version (always AskQuestion; select existing, Other Create: <name>, or Skip) map_columns (use suggestedColumnMapping) confirm_mapping → fix_batch_gaps (priorityNameFixes/trackerNameFixes/statusNameFixes from config) → fix_assignees → preview_gate → proceed. Never upload on first batch call.
18
+ Bulk update: get_sheet_datasheet_to_csv redmine_agent_bulk_update_issues. Steps: pick_project map_columns (use suggestedColumnMapping) confirm_mapping fix_status_gaps if needed preview_gatepreviewAction proceed. Matches tasks by Ticket:#id or exact task title (subtask column); updates description + status from sheet.
19
+ Get users: redmine_agent_get_all_users lists members from projects you belong to (project memberships), not global /users. Use assigneeOptions for spreadsheet Assigned To matching.
20
+ Get config: redmine_agent_get_config returns live trackers, activities, priorities, statuses (same cache as MCP connect).
21
+ Do NOT use team redmine create_issue for full task creation use redmine_agent_create_issue or redmine_agent_batch_create_issues.`;
22
+ function enumerationDescribe(label, rows, aliasHint) {
23
+ const line = formatEnumerationLine(label, rows).replace(`${label}: `, "");
24
+ return `${label} name or id. Options: ${line}${aliasHint ? `. ${aliasHint}` : ""}`;
25
+ }
26
+ function registerTools(server, config) {
27
+ const trackerDescribe = `${enumerationDescribe("Tracker", config.trackers, "Aliases: CR→Change Request, bug→Bug, story→User Story")}`;
28
+ const statusDescribe = `${enumerationDescribe("Status", config.statuses, "Aliases: on hold→Waiting for Support, dev done→Ready For QA, done→Closed, deployed on preprod→Pre-production, ready for preprod→Move to preprod, verified preprod→Ok for Release, to do→New, review→Code Review")}`;
29
+ const priorityDescribe = `${enumerationDescribe("Priority", config.priorities, "Aliases: medium→Normal, highest→Immediate, lowest→Low")}`;
30
+ server.registerTool("redmine_agent_get_config", {
31
+ description: "Redmine enumerations loaded at MCP connect (activities, trackers, priorities, statuses) with id↔name mapping and aliases. Optional force=true refreshes cache.",
32
+ inputSchema: {
33
+ force: z
34
+ .boolean()
35
+ .optional()
36
+ .describe("Bypass TTL and refetch from Redmine API"),
37
+ },
38
+ }, async (input) => {
39
+ const fresh = await loadRedmineConfig({
40
+ ...(input.force ? { force: true } : {}),
41
+ });
42
+ return workflowToMcpContent({
43
+ success: true,
44
+ data: configOptionsPayload(fresh),
45
+ });
46
+ });
47
+ server.registerTool("redmine_agent_create_issue", {
48
+ description: "Create a Redmine issue end-to-end: batched intake (all fields), preview, confirmSave=yes upload. Supports tracker, status, priority, dates, estimate, assignee, target version, parent task, and custom fields. Use this instead of team redmine create_issue.",
49
+ inputSchema: {
50
+ project: z
51
+ .string()
52
+ .optional()
53
+ .describe("Redmine project identifier or numeric project ID"),
54
+ tracker: z
55
+ .string()
56
+ .optional()
57
+ .describe(trackerDescribe),
58
+ subjectMode: z.enum(["default", "blank", "custom"]).optional(),
59
+ subjectCustom: z.string().optional(),
60
+ descriptionMode: z.enum(["default", "blank", "custom"]).optional(),
61
+ descriptionCustom: z.string().optional(),
62
+ status: z
63
+ .string()
64
+ .optional()
65
+ .describe(statusDescribe),
66
+ priority: z
67
+ .string()
68
+ .optional()
69
+ .describe(priorityDescribe),
70
+ assignedToMode: z.enum(["default", "blank", "custom"]).optional(),
71
+ assignedToCustom: z.string().optional(),
72
+ targetVersionMode: z.enum(["default", "blank", "custom"]).optional(),
73
+ targetVersionCustom: z.string().optional(),
74
+ parentTaskMode: z.enum(["default", "blank", "custom"]).optional(),
75
+ parentTaskCustom: z.string().optional(),
76
+ startDateMode: z.enum(["default", "blank", "custom"]).optional(),
77
+ startDateCustom: z.string().optional(),
78
+ dueDateMode: z.enum(["default", "blank", "custom"]).optional(),
79
+ dueDateCustom: z.string().optional(),
80
+ estimatedTimeMode: z.enum(["default", "blank", "custom"]).optional(),
81
+ estimatedTimeCustom: z.string().optional(),
82
+ qaStatusMode: z.enum(["default", "blank", "custom"]).optional(),
83
+ qaStatusCustom: z.string().optional(),
84
+ gitLinkMode: z.enum(["default", "blank", "custom"]).optional(),
85
+ gitLinkCustom: z.string().optional(),
86
+ gitLink1Mode: z.enum(["default", "blank", "custom"]).optional(),
87
+ gitLink1Custom: z.string().optional(),
88
+ gitLink2Mode: z.enum(["default", "blank", "custom"]).optional(),
89
+ gitLink2Custom: z.string().optional(),
90
+ expectedTimeMode: z.enum(["default", "blank", "custom"]).optional(),
91
+ expectedTimeCustom: z.string().optional(),
92
+ prLinkMode: z.enum(["default", "blank", "custom"]).optional(),
93
+ prLinkCustom: z.string().optional(),
94
+ storyPointsMode: z.enum(["default", "blank", "custom"]).optional(),
95
+ storyPointsCustom: z.string().optional(),
96
+ confirmSave: z.enum(["yes", "no"]).optional(),
97
+ },
98
+ }, async (input) => workflowToMcpContent(await createIssueWorkflow({
99
+ ...(input.project !== undefined ? { project: input.project } : {}),
100
+ ...(input.tracker !== undefined ? { tracker: input.tracker } : {}),
101
+ ...(input.subjectMode !== undefined
102
+ ? { subjectMode: input.subjectMode }
103
+ : {}),
104
+ ...(input.subjectCustom !== undefined
105
+ ? { subjectCustom: input.subjectCustom }
106
+ : {}),
107
+ ...(input.descriptionMode !== undefined
108
+ ? { descriptionMode: input.descriptionMode }
109
+ : {}),
110
+ ...(input.descriptionCustom !== undefined
111
+ ? { descriptionCustom: input.descriptionCustom }
112
+ : {}),
113
+ ...(input.status !== undefined ? { status: input.status } : {}),
114
+ ...(input.priority !== undefined
115
+ ? { priority: input.priority }
116
+ : {}),
117
+ ...(input.assignedToMode !== undefined
118
+ ? { assignedToMode: input.assignedToMode }
119
+ : {}),
120
+ ...(input.assignedToCustom !== undefined
121
+ ? { assignedToCustom: input.assignedToCustom }
122
+ : {}),
123
+ ...(input.targetVersionMode !== undefined
124
+ ? { targetVersionMode: input.targetVersionMode }
125
+ : {}),
126
+ ...(input.targetVersionCustom !== undefined
127
+ ? { targetVersionCustom: input.targetVersionCustom }
128
+ : {}),
129
+ ...(input.parentTaskMode !== undefined
130
+ ? { parentTaskMode: input.parentTaskMode }
131
+ : {}),
132
+ ...(input.parentTaskCustom !== undefined
133
+ ? { parentTaskCustom: input.parentTaskCustom }
134
+ : {}),
135
+ ...(input.startDateMode !== undefined
136
+ ? { startDateMode: input.startDateMode }
137
+ : {}),
138
+ ...(input.startDateCustom !== undefined
139
+ ? { startDateCustom: input.startDateCustom }
140
+ : {}),
141
+ ...(input.dueDateMode !== undefined
142
+ ? { dueDateMode: input.dueDateMode }
143
+ : {}),
144
+ ...(input.dueDateCustom !== undefined
145
+ ? { dueDateCustom: input.dueDateCustom }
146
+ : {}),
147
+ ...(input.estimatedTimeMode !== undefined
148
+ ? { estimatedTimeMode: input.estimatedTimeMode }
149
+ : {}),
150
+ ...(input.estimatedTimeCustom !== undefined
151
+ ? { estimatedTimeCustom: input.estimatedTimeCustom }
152
+ : {}),
153
+ ...(input.qaStatusMode !== undefined
154
+ ? { qaStatusMode: input.qaStatusMode }
155
+ : {}),
156
+ ...(input.qaStatusCustom !== undefined
157
+ ? { qaStatusCustom: input.qaStatusCustom }
158
+ : {}),
159
+ ...(input.gitLinkMode !== undefined
160
+ ? { gitLinkMode: input.gitLinkMode }
161
+ : {}),
162
+ ...(input.gitLinkCustom !== undefined
163
+ ? { gitLinkCustom: input.gitLinkCustom }
164
+ : {}),
165
+ ...(input.gitLink1Mode !== undefined
166
+ ? { gitLink1Mode: input.gitLink1Mode }
167
+ : {}),
168
+ ...(input.gitLink1Custom !== undefined
169
+ ? { gitLink1Custom: input.gitLink1Custom }
170
+ : {}),
171
+ ...(input.gitLink2Mode !== undefined
172
+ ? { gitLink2Mode: input.gitLink2Mode }
173
+ : {}),
174
+ ...(input.gitLink2Custom !== undefined
175
+ ? { gitLink2Custom: input.gitLink2Custom }
176
+ : {}),
177
+ ...(input.expectedTimeMode !== undefined
178
+ ? { expectedTimeMode: input.expectedTimeMode }
179
+ : {}),
180
+ ...(input.expectedTimeCustom !== undefined
181
+ ? { expectedTimeCustom: input.expectedTimeCustom }
182
+ : {}),
183
+ ...(input.prLinkMode !== undefined
184
+ ? { prLinkMode: input.prLinkMode }
185
+ : {}),
186
+ ...(input.prLinkCustom !== undefined
187
+ ? { prLinkCustom: input.prLinkCustom }
188
+ : {}),
189
+ ...(input.storyPointsMode !== undefined
190
+ ? { storyPointsMode: input.storyPointsMode }
191
+ : {}),
192
+ ...(input.storyPointsCustom !== undefined
193
+ ? { storyPointsCustom: input.storyPointsCustom }
194
+ : {}),
195
+ ...(input.confirmSave !== undefined
196
+ ? { confirmSave: input.confirmSave }
197
+ : {}),
198
+ })));
199
+ server.registerTool("redmine_agent_get_all_users", {
200
+ description: "List assignable users from projects the API user belongs to (via project memberships). Use assigneeOptions to match spreadsheet Assigned To before batch create. Optional projectId limits to one project. Returns source, projects[], users (id, name, label, roles, projectIds), assigneeOptions.",
201
+ inputSchema: {
202
+ projectId: z
203
+ .number()
204
+ .int()
205
+ .optional()
206
+ .describe("Limit to members of this project (must be in your memberships)"),
207
+ status: z
208
+ .enum(["active", "all"])
209
+ .optional()
210
+ .describe("Legacy field; membership listing does not use global /users."),
211
+ },
212
+ }, async (input) => workflowToMcpContent(await getAllUsersWorkflow({
213
+ ...(input.projectId !== undefined ? { projectId: input.projectId } : {}),
214
+ ...(input.status !== undefined ? { status: input.status } : {}),
215
+ })));
216
+ server.registerTool("redmine_agent_sheet_to_csv", {
217
+ description: "Convert Google Sheets MCP get_sheet_data output to csvContent for batch create. Pass sheetsPayload from get_sheet_data (or rows[][]). Returns csvContent, optional csvPath cache, sheetSource (for agent write-back via Google Sheets MCP), and header preview. Does not call Google APIs — fetch the sheet via Google Sheets MCP first.",
218
+ inputSchema: {
219
+ sheetsPayload: z
220
+ .unknown()
221
+ .optional()
222
+ .describe("Full get_sheet_data result (object or JSON string)"),
223
+ rows: z
224
+ .array(z.array(z.string()))
225
+ .optional()
226
+ .describe("Raw grid when not passing sheetsPayload"),
227
+ spreadsheetId: z
228
+ .string()
229
+ .optional()
230
+ .describe("Spreadsheet id (override or required with rows-only)"),
231
+ sheet: z
232
+ .string()
233
+ .optional()
234
+ .describe("Tab name (override or from sheetsPayload range)"),
235
+ writeCache: z
236
+ .boolean()
237
+ .optional()
238
+ .describe("Write .cursor/cache/last-sheet.csv (default true)"),
239
+ },
240
+ }, async (input) => workflowToMcpContent(await sheetToCsvWorkflow({
241
+ ...(input.sheetsPayload !== undefined
242
+ ? { sheetsPayload: input.sheetsPayload }
243
+ : {}),
244
+ ...(input.rows !== undefined ? { rows: input.rows } : {}),
245
+ ...(input.spreadsheetId !== undefined
246
+ ? { spreadsheetId: input.spreadsheetId }
247
+ : {}),
248
+ ...(input.sheet !== undefined ? { sheet: input.sheet } : {}),
249
+ ...(input.writeCache !== undefined
250
+ ? { writeCache: input.writeCache }
251
+ : {}),
252
+ })));
253
+ server.registerTool("redmine_agent_batch_create_issues", {
254
+ description: "Feature → User Stories → Tasks from csvContent (from redmine_agent_sheet_to_csv) or csvPath. First call: pick_project (no default project). Then pick_target_version → map_columns → confirm_mapping → fix_assignees → preview_gate → proceed. Pass sheetSource from sheet_to_csv when batch started from Google Sheets (agent uses it for write-back hints only). User must confirm before previewAction proceed.",
255
+ inputSchema: {
256
+ csvPath: z
257
+ .string()
258
+ .optional()
259
+ .describe("Workspace path to CSV (e.g. batch_creation.csv)"),
260
+ csvContent: z.string().optional().describe("Raw CSV text if not using csvPath"),
261
+ project: z
262
+ .string()
263
+ .optional()
264
+ .describe("Project id or 'id — name' from getProjects. Required before column mapping — first batch call returns pick_project if omitted."),
265
+ status: z.string().optional().describe('Default "New"'),
266
+ priority: z.string().optional().describe('Default "Normal"'),
267
+ skipNodeIds: z
268
+ .array(z.string())
269
+ .optional()
270
+ .describe('nodeIds to skip (e.g. "row-12-subtask")'),
271
+ rowFixes: z
272
+ .record(z.string(), z.object({
273
+ subject: z.string().optional(),
274
+ assignedTo: z.string().optional(),
275
+ project: z.string().optional(),
276
+ targetVersion: z.string().optional(),
277
+ }))
278
+ .optional()
279
+ .describe("Per-row overrides keyed by nodeId"),
280
+ confirmSave: z.enum(["yes", "no"]).optional(),
281
+ previewAction: z
282
+ .enum(["show_preview", "proceed", "cancel"])
283
+ .optional()
284
+ .describe("After preview_gate: show_preview = full line list; proceed = post; cancel = abort"),
285
+ sheetSource: z
286
+ .object({
287
+ spreadsheetId: z.string(),
288
+ sheetTitle: z.string(),
289
+ })
290
+ .optional()
291
+ .describe("From redmine_agent_sheet_to_csv — metadata for agent sheet write-back (Google Sheets MCP); not written by this tool"),
292
+ existingNodeIds: z
293
+ .record(z.string(), z.number())
294
+ .optional()
295
+ .describe("nodeId → Redmine issue id from a prior upload (feature-root, row-N-userStory); links parents without duplicating"),
296
+ assigneeNameFixes: z
297
+ .record(z.string(), z.string())
298
+ .optional()
299
+ .describe('Invalid sheet assignee label → member label (e.g. "Muzamil khan" → "1590 — Name"), "Leave unassigned" to create without assignee, or "Skip this row" to skip all rows with that label'),
300
+ priorityNameFixes: z
301
+ .record(z.string(), z.string())
302
+ .optional()
303
+ .describe('Invalid sheet priority label → priority from priorityOptions (e.g. "Highest" → "1 — Immediate")'),
304
+ trackerNameFixes: z
305
+ .record(z.string(), z.string())
306
+ .optional()
307
+ .describe('Invalid sheet Type label → tracker from trackerOptions (e.g. "CR" → "5 — Change Request")'),
308
+ statusNameFixes: z
309
+ .record(z.string(), z.string())
310
+ .optional()
311
+ .describe('Invalid sheet Status label → status from statusOptions (e.g. "On Hold" → "19 — Waiting for Support")'),
312
+ missingTargetVersionFix: z
313
+ .string()
314
+ .optional()
315
+ .describe('Default target version for stories without Target Version in sheet: project version "id — name", "Create: <name>" (new version; name from AskQuestion Other), or "Skip target version". Required after project, before column mapping — returns pick_target_version if omitted.'),
316
+ sheetParseMode: z
317
+ .enum(["structure_only"])
318
+ .optional()
319
+ .describe("structure_only: raw rows + fieldSchema for agent column mapping; then retry with columnMapping. Omitted without columnMapping also returns map_columns."),
320
+ columnMapping: z
321
+ .object({
322
+ headerRowIndex: z
323
+ .number()
324
+ .int()
325
+ .describe("0-based row index of header line in sheet"),
326
+ columns: z
327
+ .record(z.string(), z.union([z.string(), z.number()]))
328
+ .describe("Role → header label or 0-based column index (userStory, subtask, description, hours, targetVersion, assignedTo, comments, redmineId, …)"),
329
+ })
330
+ .optional()
331
+ .describe("Agent-provided mapping after reading detectedHeaders (required: userStory + subtask)"),
332
+ confirmMappingAction: z
333
+ .enum(["proceed"])
334
+ .optional()
335
+ .describe('After confirm_mapping: "proceed" to parse sheet and continue to preview_gate'),
336
+ },
337
+ }, async (input) => {
338
+ const batchInput = {};
339
+ if (input.sheetSource !== undefined) {
340
+ batchInput.sheetSource = input.sheetSource;
341
+ }
342
+ if (input.csvPath !== undefined)
343
+ batchInput.csvPath = input.csvPath;
344
+ if (input.csvContent !== undefined)
345
+ batchInput.csvContent = input.csvContent;
346
+ if (input.project !== undefined)
347
+ batchInput.project = input.project;
348
+ if (input.status !== undefined)
349
+ batchInput.status = input.status;
350
+ if (input.priority !== undefined)
351
+ batchInput.priority = input.priority;
352
+ if (input.skipNodeIds !== undefined)
353
+ batchInput.skipNodeIds = input.skipNodeIds;
354
+ if (input.confirmSave !== undefined)
355
+ batchInput.confirmSave = input.confirmSave;
356
+ if (input.previewAction !== undefined) {
357
+ batchInput.previewAction = input.previewAction;
358
+ }
359
+ if (input.existingNodeIds !== undefined) {
360
+ batchInput.existingNodeIds = input.existingNodeIds;
361
+ }
362
+ if (input.assigneeNameFixes !== undefined) {
363
+ batchInput.assigneeNameFixes = input.assigneeNameFixes;
364
+ }
365
+ if (input.priorityNameFixes !== undefined) {
366
+ batchInput.priorityNameFixes = input.priorityNameFixes;
367
+ }
368
+ if (input.trackerNameFixes !== undefined) {
369
+ batchInput.trackerNameFixes = input.trackerNameFixes;
370
+ }
371
+ if (input.statusNameFixes !== undefined) {
372
+ batchInput.statusNameFixes = input.statusNameFixes;
373
+ }
374
+ if (input.missingTargetVersionFix !== undefined) {
375
+ batchInput.missingTargetVersionFix = input.missingTargetVersionFix;
376
+ }
377
+ if (input.sheetParseMode !== undefined) {
378
+ batchInput.sheetParseMode = input.sheetParseMode;
379
+ }
380
+ if (input.columnMapping !== undefined) {
381
+ batchInput.columnMapping = input.columnMapping;
382
+ }
383
+ if (input.confirmMappingAction !== undefined) {
384
+ batchInput.confirmMappingAction = input.confirmMappingAction;
385
+ }
386
+ if (input.rowFixes !== undefined) {
387
+ batchInput.rowFixes = {};
388
+ for (const [nodeId, fix] of Object.entries(input.rowFixes)) {
389
+ const entry = {};
390
+ if (fix.subject !== undefined)
391
+ entry.subject = fix.subject;
392
+ if (fix.assignedTo !== undefined)
393
+ entry.assignedTo = fix.assignedTo;
394
+ if (fix.project !== undefined)
395
+ entry.project = fix.project;
396
+ if (fix.targetVersion !== undefined) {
397
+ entry.targetVersion = fix.targetVersion;
398
+ }
399
+ batchInput.rowFixes[nodeId] = entry;
400
+ }
401
+ }
402
+ return workflowToMcpContent(await batchCreateIssuesWorkflow(batchInput));
403
+ });
404
+ server.registerTool("redmine_agent_bulk_update_issues", {
405
+ description: "Bulk-update Redmine task description and status from a Google Sheet/CSV or updates[] list. Match by Ticket:#id (preferred) or exact task title (subtask/Tasks column). Steps: pick_project → map_columns → confirm_mapping → preview_gate → previewAction proceed. User must confirm before apply.",
406
+ inputSchema: {
407
+ csvPath: z
408
+ .string()
409
+ .optional()
410
+ .describe("Workspace path to CSV from redmine_agent_sheet_to_csv"),
411
+ csvContent: z.string().optional().describe("Raw CSV text"),
412
+ project: z
413
+ .string()
414
+ .optional()
415
+ .describe("Project id or 'id — name'. First call returns pick_project if omitted."),
416
+ columnMapping: z
417
+ .object({
418
+ headerRowIndex: z.number().int(),
419
+ columns: z.record(z.string(), z.union([z.string(), z.number()])),
420
+ })
421
+ .optional()
422
+ .describe("From map_columns suggestedColumnMapping"),
423
+ confirmMappingAction: z
424
+ .enum(["proceed"])
425
+ .optional()
426
+ .describe("After confirm_mapping"),
427
+ updates: z
428
+ .array(z.object({
429
+ subject: z
430
+ .string()
431
+ .describe("Task title to match when issueId omitted"),
432
+ issueId: z.number().int().optional(),
433
+ description: z.string().optional(),
434
+ status: z.string().optional().describe(statusDescribe),
435
+ }))
436
+ .optional()
437
+ .describe("Direct updates without CSV — match by issueId or exact subject in project"),
438
+ statusNameFixes: z
439
+ .record(z.string(), z.string())
440
+ .optional()
441
+ .describe('Sheet status label → "id — name" from statusOptions'),
442
+ previewAction: z
443
+ .enum(["show_preview", "proceed", "cancel"])
444
+ .optional(),
445
+ confirmSave: z.enum(["yes", "no"]).optional(),
446
+ },
447
+ }, async (input) => {
448
+ const bulkInput = {};
449
+ if (input.csvPath !== undefined)
450
+ bulkInput.csvPath = input.csvPath;
451
+ if (input.csvContent !== undefined)
452
+ bulkInput.csvContent = input.csvContent;
453
+ if (input.project !== undefined)
454
+ bulkInput.project = input.project;
455
+ if (input.confirmMappingAction !== undefined) {
456
+ bulkInput.confirmMappingAction = input.confirmMappingAction;
457
+ }
458
+ if (input.updates !== undefined) {
459
+ bulkInput.updates = input.updates.map((u) => ({
460
+ subject: u.subject,
461
+ ...(u.issueId !== undefined ? { issueId: u.issueId } : {}),
462
+ ...(u.description !== undefined
463
+ ? { description: u.description }
464
+ : {}),
465
+ ...(u.status !== undefined ? { status: u.status } : {}),
466
+ }));
467
+ }
468
+ if (input.statusNameFixes !== undefined) {
469
+ bulkInput.statusNameFixes = input.statusNameFixes;
470
+ }
471
+ if (input.previewAction !== undefined) {
472
+ bulkInput.previewAction = input.previewAction;
473
+ }
474
+ if (input.confirmSave !== undefined)
475
+ bulkInput.confirmSave = input.confirmSave;
476
+ if (input.columnMapping !== undefined) {
477
+ bulkInput.columnMapping = {
478
+ headerRowIndex: input.columnMapping.headerRowIndex,
479
+ columns: input.columnMapping.columns,
480
+ };
481
+ }
482
+ return workflowToMcpContent(await bulkUpdateIssuesWorkflow(bulkInput));
483
+ });
484
+ server.registerTool("redmine_agent_list_permissions", {
485
+ description: "List effective Redmine permissions for the API key’s current user (union across their role permissions). Optionally filter by a single project ID.",
486
+ inputSchema: {
487
+ projectId: z.number().int().optional().describe("Filter by project ID"),
488
+ },
489
+ }, async (input) => {
490
+ const current = await getCurrentUserWithMemberships();
491
+ const user = current.user ?? current;
492
+ const memberships = (user.memberships ?? []);
493
+ const filteredMemberships = input?.projectId
494
+ ? memberships.filter((m) => m.project?.id === input.projectId)
495
+ : memberships;
496
+ const roleIds = new Set();
497
+ for (const m of filteredMemberships) {
498
+ for (const r of m.roles ?? [])
499
+ roleIds.add(r.id);
500
+ }
501
+ const permissionsUnion = new Set();
502
+ const rolesExpanded = [];
503
+ for (const roleId of [...roleIds].sort((a, b) => a - b)) {
504
+ try {
505
+ const role = (await getRolePermissions(roleId));
506
+ const permissions = (Array.isArray(role.permissions)
507
+ ? role.permissions
508
+ : []);
509
+ permissions.forEach((p) => permissionsUnion.add(p));
510
+ rolesExpanded.push({
511
+ roleId,
512
+ roleName: role.name ?? null,
513
+ projectNames: [
514
+ ...new Set(filteredMemberships
515
+ .filter((m) => (m.roles ?? []).some((r) => r.id === roleId))
516
+ .map((m) => m.project?.name)
517
+ .filter(Boolean)),
518
+ ],
519
+ permissions,
520
+ });
521
+ }
522
+ catch (e) {
523
+ const status = e?.response?.status;
524
+ rolesExpanded.push({
525
+ roleId,
526
+ roleName: null,
527
+ projectNames: [
528
+ ...new Set(filteredMemberships
529
+ .filter((m) => (m.roles ?? []).some((r) => r.id === roleId))
530
+ .map((m) => m.project?.name)
531
+ .filter(Boolean)),
532
+ ],
533
+ permissions: [],
534
+ fetchError: status ? `HTTP ${status}` : e.message,
535
+ });
536
+ }
537
+ }
538
+ return workflowToMcpContent({
539
+ success: true,
540
+ data: {
541
+ user: {
542
+ id: user.id,
543
+ login: user.login,
544
+ name: user.name ?? user.firstname,
545
+ },
546
+ memberships: filteredMemberships.map((m) => ({
547
+ project: m.project ?? null,
548
+ roles: (m.roles ?? []).map((r) => ({ id: r.id, name: r.name })),
549
+ })),
550
+ permissions: [...permissionsUnion].sort(),
551
+ permissionsCount: permissionsUnion.size,
552
+ rolesExpanded,
553
+ },
554
+ });
555
+ });
556
+ server.registerTool("redmine_agent_log_time", {
557
+ description: "Batch log time: dates → dateIssues → pairHours → pairActivities (from config cache) → pairComments → preview → confirmSave=yes. Activity options are preloaded at MCP connect; Step 4 requires activityBatchId from prior response.",
558
+ inputSchema: {
559
+ promptHint: z
560
+ .string()
561
+ .optional()
562
+ .describe("User message text; if it mentions 'today', date step is skipped"),
563
+ dates: z
564
+ .array(z.string())
565
+ .optional()
566
+ .describe("Step 1 — Today | Multiple | Other, or YYYY-MM-DD list"),
567
+ spentOn: z
568
+ .string()
569
+ .optional()
570
+ .describe("Custom date YYYY-MM-DD when Other was chosen"),
571
+ pendingDates: z.array(z.string()).optional(),
572
+ activeDate: z.string().optional(),
573
+ dateIssues: z
574
+ .record(z.string(), z.union([
575
+ z.array(z.number().int().positive()),
576
+ z.string(),
577
+ ]))
578
+ .optional()
579
+ .describe("Step 2 batch — issue IDs per date (number[] or comma-separated string)"),
580
+ pendingIssueIds: z.array(z.number().int().positive()).optional(),
581
+ activeIssueId: z.number().int().positive().optional(),
582
+ issueIds: z
583
+ .array(z.number().int().positive())
584
+ .optional()
585
+ .describe("Legacy — fills all dates if dateIssues omitted (single date only)"),
586
+ pairInclude: z
587
+ .enum(["yes", "no"])
588
+ .optional()
589
+ .describe("Legacy single pair; prefer pairIncludes"),
590
+ pairIncludes: z
591
+ .record(z.string(), z.string())
592
+ .optional()
593
+ .describe('Step 3 batch — all yes/no keyed by "YYYY-MM-DD:issueId"'),
594
+ pairHours: z
595
+ .record(z.string(), z.union([z.number(), z.string()]))
596
+ .optional()
597
+ .describe("Step 3 batch — hours per included pair key"),
598
+ pairActivities: z
599
+ .record(z.string(), z.string())
600
+ .optional()
601
+ .describe('Step 4 batch — activity per pair key: "default" or "id — name". Requires activityBatchId from prior Step 4 response.'),
602
+ activityBatchId: z
603
+ .string()
604
+ .optional()
605
+ .describe("Pass back from data.activityBatchId after user answers Step 4 activity AskQuestion (mandatory before pairActivities is accepted)"),
606
+ pairComments: z
607
+ .record(z.string(), z.string())
608
+ .optional()
609
+ .describe('Step 5 batch — "default" or custom text per pair key'),
610
+ issueSubjects: z
611
+ .record(z.string(), z.string())
612
+ .optional()
613
+ .describe("issueId → subject; pass back from data for question labels"),
614
+ issueOptions: z
615
+ .array(z.object({
616
+ issueId: z.number().int().positive(),
617
+ subject: z.string().optional(),
618
+ label: z.string().optional(),
619
+ }))
620
+ .optional(),
621
+ skippedPairs: z
622
+ .array(z.string())
623
+ .optional()
624
+ .describe('Skipped pair keys "YYYY-MM-DD:issueId"'),
625
+ issueId: z.number().int().positive().optional(),
626
+ hours: z.number().positive().optional().describe("Legacy single pair hours"),
627
+ commentChoice: z.enum(["default", "custom"]).optional(),
628
+ comments: z.string().optional(),
629
+ entries: z
630
+ .array(z.object({
631
+ issueId: z.number().int().positive(),
632
+ hours: z.number().positive(),
633
+ spentOn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
634
+ comments: z.string(),
635
+ activityId: z.number().int().positive().optional(),
636
+ }))
637
+ .optional(),
638
+ confirmSave: z.enum(["yes", "no"]).optional(),
639
+ },
640
+ }, async (input) => workflowToMcpContent(await logTimeWorkflow({
641
+ ...(input.promptHint !== undefined
642
+ ? { promptHint: input.promptHint }
643
+ : {}),
644
+ ...(input.dates !== undefined ? { dates: input.dates } : {}),
645
+ ...(input.spentOn !== undefined
646
+ ? { spentOn: input.spentOn }
647
+ : {}),
648
+ ...(input.pendingDates !== undefined
649
+ ? { pendingDates: input.pendingDates }
650
+ : {}),
651
+ ...(input.activeDate !== undefined
652
+ ? { activeDate: input.activeDate }
653
+ : {}),
654
+ ...(input.dateIssues !== undefined
655
+ ? {
656
+ // Accept string/number[] in MCP schema; workflow normalizes this shape.
657
+ dateIssues: input.dateIssues,
658
+ }
659
+ : {}),
660
+ ...(input.pendingIssueIds !== undefined
661
+ ? { pendingIssueIds: input.pendingIssueIds }
662
+ : {}),
663
+ ...(input.activeIssueId !== undefined
664
+ ? { activeIssueId: input.activeIssueId }
665
+ : {}),
666
+ ...(input.issueIds !== undefined
667
+ ? { issueIds: input.issueIds }
668
+ : {}),
669
+ ...(input.issueId !== undefined
670
+ ? { issueId: input.issueId }
671
+ : {}),
672
+ ...(input.hours !== undefined ? { hours: input.hours } : {}),
673
+ ...(input.pairInclude !== undefined
674
+ ? { pairInclude: input.pairInclude }
675
+ : {}),
676
+ ...(input.pairIncludes !== undefined
677
+ ? { pairIncludes: input.pairIncludes }
678
+ : {}),
679
+ ...(input.pairHours !== undefined
680
+ ? { pairHours: input.pairHours }
681
+ : {}),
682
+ ...(input.pairActivities !== undefined
683
+ ? { pairActivities: input.pairActivities }
684
+ : {}),
685
+ ...(input.activityBatchId !== undefined
686
+ ? { activityBatchId: input.activityBatchId }
687
+ : {}),
688
+ ...(input.pairComments !== undefined
689
+ ? { pairComments: input.pairComments }
690
+ : {}),
691
+ ...(input.issueSubjects !== undefined
692
+ ? { issueSubjects: input.issueSubjects }
693
+ : {}),
694
+ ...(input.issueOptions !== undefined
695
+ ? { issueOptions: input.issueOptions }
696
+ : {}),
697
+ ...(input.skippedPairs !== undefined
698
+ ? { skippedPairs: input.skippedPairs }
699
+ : {}),
700
+ ...(input.commentChoice !== undefined
701
+ ? { commentChoice: input.commentChoice }
702
+ : {}),
703
+ ...(input.comments !== undefined
704
+ ? { comments: input.comments }
705
+ : {}),
706
+ ...(input.entries !== undefined
707
+ ? { entries: input.entries }
708
+ : {}),
709
+ ...(input.confirmSave !== undefined
710
+ ? { confirmSave: input.confirmSave }
711
+ : {}),
712
+ })));
713
+ }
714
+ function createMcpServer(config) {
715
+ const server = new McpServer({
716
+ name: "redmine-agent",
717
+ version: "3.9.0",
718
+ }, {
719
+ instructions: `${BASE_MCP_INSTRUCTIONS}\n\n${formatConfigForInstructions(config)}`,
720
+ });
721
+ registerTools(server, config);
722
+ return server;
723
+ }
154
724
  async function main() {
725
+ const config = await loadRedmineConfig();
726
+ const server = createMcpServer(config);
155
727
  const transport = new StdioServerTransport();
156
728
  await server.connect(transport);
157
729
  }