@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.
- package/dist/config/env.d.ts +2 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +8 -0
- package/dist/config/env.js.map +1 -1
- package/dist/mcp/server.js +719 -147
- package/dist/mcp/server.js.map +1 -1
- package/dist/services/assigneeDirectory.d.ts +34 -0
- package/dist/services/assigneeDirectory.d.ts.map +1 -0
- package/dist/services/assigneeDirectory.js +138 -0
- package/dist/services/assigneeDirectory.js.map +1 -0
- package/dist/services/googleSheets.d.ts +32 -0
- package/dist/services/googleSheets.d.ts.map +1 -0
- package/dist/services/googleSheets.js +109 -0
- package/dist/services/googleSheets.js.map +1 -0
- package/dist/services/redmine.d.ts +72 -0
- package/dist/services/redmine.d.ts.map +1 -1
- package/dist/services/redmine.js +141 -0
- package/dist/services/redmine.js.map +1 -1
- package/dist/services/redmineConfig.d.ts +90 -0
- package/dist/services/redmineConfig.d.ts.map +1 -0
- package/dist/services/redmineConfig.js +300 -0
- package/dist/services/redmineConfig.js.map +1 -0
- package/dist/spreadsheet/batchColumnMapping.d.ts +93 -0
- package/dist/spreadsheet/batchColumnMapping.d.ts.map +1 -0
- package/dist/spreadsheet/batchColumnMapping.js +666 -0
- package/dist/spreadsheet/batchColumnMapping.js.map +1 -0
- package/dist/spreadsheet/batchCreationTypes.d.ts +61 -0
- package/dist/spreadsheet/batchCreationTypes.d.ts.map +1 -0
- package/dist/spreadsheet/batchCreationTypes.js +18 -0
- package/dist/spreadsheet/batchCreationTypes.js.map +1 -0
- package/dist/spreadsheet/batchIssueConfig.d.ts +26 -0
- package/dist/spreadsheet/batchIssueConfig.d.ts.map +1 -0
- package/dist/spreadsheet/batchIssueConfig.js +106 -0
- package/dist/spreadsheet/batchIssueConfig.js.map +1 -0
- package/dist/spreadsheet/batchPreviewFormat.d.ts +41 -0
- package/dist/spreadsheet/batchPreviewFormat.d.ts.map +1 -0
- package/dist/spreadsheet/batchPreviewFormat.js +194 -0
- package/dist/spreadsheet/batchPreviewFormat.js.map +1 -0
- package/dist/spreadsheet/batchSheetHeuristics.d.ts +13 -0
- package/dist/spreadsheet/batchSheetHeuristics.d.ts.map +1 -0
- package/dist/spreadsheet/batchSheetHeuristics.js +30 -0
- package/dist/spreadsheet/batchSheetHeuristics.js.map +1 -0
- package/dist/spreadsheet/bulkUpdateExtract.d.ts +14 -0
- package/dist/spreadsheet/bulkUpdateExtract.d.ts.map +1 -0
- package/dist/spreadsheet/bulkUpdateExtract.js +83 -0
- package/dist/spreadsheet/bulkUpdateExtract.js.map +1 -0
- package/dist/spreadsheet/csvUtils.d.ts +14 -0
- package/dist/spreadsheet/csvUtils.d.ts.map +1 -0
- package/dist/spreadsheet/csvUtils.js +113 -0
- package/dist/spreadsheet/csvUtils.js.map +1 -0
- package/dist/spreadsheet/extractRawBatchSheet.d.ts +14 -0
- package/dist/spreadsheet/extractRawBatchSheet.d.ts.map +1 -0
- package/dist/spreadsheet/extractRawBatchSheet.js +33 -0
- package/dist/spreadsheet/extractRawBatchSheet.js.map +1 -0
- package/dist/spreadsheet/featureRow.d.ts +15 -0
- package/dist/spreadsheet/featureRow.d.ts.map +1 -0
- package/dist/spreadsheet/featureRow.js +119 -0
- package/dist/spreadsheet/featureRow.js.map +1 -0
- package/dist/spreadsheet/parseBatchCreationCsv.d.ts +8 -0
- package/dist/spreadsheet/parseBatchCreationCsv.d.ts.map +1 -0
- package/dist/spreadsheet/parseBatchCreationCsv.js +22 -0
- package/dist/spreadsheet/parseBatchCreationCsv.js.map +1 -0
- package/dist/spreadsheet/parseBatchCreationCsvV1.d.ts +3 -0
- package/dist/spreadsheet/parseBatchCreationCsvV1.d.ts.map +1 -0
- package/dist/spreadsheet/parseBatchCreationCsvV1.js +226 -0
- package/dist/spreadsheet/parseBatchCreationCsvV1.js.map +1 -0
- package/dist/spreadsheet/parseBatchCreationCsvV2.d.ts +4 -0
- package/dist/spreadsheet/parseBatchCreationCsvV2.d.ts.map +1 -0
- package/dist/spreadsheet/parseBatchCreationCsvV2.js +180 -0
- package/dist/spreadsheet/parseBatchCreationCsvV2.js.map +1 -0
- package/dist/spreadsheet/redmineIssueCell.d.ts +38 -0
- package/dist/spreadsheet/redmineIssueCell.d.ts.map +1 -0
- package/dist/spreadsheet/redmineIssueCell.js +176 -0
- package/dist/spreadsheet/redmineIssueCell.js.map +1 -0
- package/dist/spreadsheet/sheetColumnMap.d.ts +38 -0
- package/dist/spreadsheet/sheetColumnMap.d.ts.map +1 -0
- package/dist/spreadsheet/sheetColumnMap.js +75 -0
- package/dist/spreadsheet/sheetColumnMap.js.map +1 -0
- package/dist/spreadsheet/sheetGridToCsv.d.ts +28 -0
- package/dist/spreadsheet/sheetGridToCsv.d.ts.map +1 -0
- package/dist/spreadsheet/sheetGridToCsv.js +88 -0
- package/dist/spreadsheet/sheetGridToCsv.js.map +1 -0
- package/dist/spreadsheet/sheetRedmineFields.d.ts +7 -0
- package/dist/spreadsheet/sheetRedmineFields.d.ts.map +1 -0
- package/dist/spreadsheet/sheetRedmineFields.js +22 -0
- package/dist/spreadsheet/sheetRedmineFields.js.map +1 -0
- package/dist/spreadsheet/sheetWriteBack.d.ts +27 -0
- package/dist/spreadsheet/sheetWriteBack.d.ts.map +1 -0
- package/dist/spreadsheet/sheetWriteBack.js +90 -0
- package/dist/spreadsheet/sheetWriteBack.js.map +1 -0
- package/dist/workflows/batchCreateIssuesWorkflow.d.ts +69 -0
- package/dist/workflows/batchCreateIssuesWorkflow.d.ts.map +1 -0
- package/dist/workflows/batchCreateIssuesWorkflow.js +1102 -0
- package/dist/workflows/batchCreateIssuesWorkflow.js.map +1 -0
- package/dist/workflows/bulkUpdateIssuesWorkflow.d.ts +21 -0
- package/dist/workflows/bulkUpdateIssuesWorkflow.d.ts.map +1 -0
- package/dist/workflows/bulkUpdateIssuesWorkflow.js +384 -0
- package/dist/workflows/bulkUpdateIssuesWorkflow.js.map +1 -0
- package/dist/workflows/createIssueWorkflow.d.ts +41 -0
- package/dist/workflows/createIssueWorkflow.d.ts.map +1 -0
- package/dist/workflows/createIssueWorkflow.js +697 -0
- package/dist/workflows/createIssueWorkflow.js.map +1 -0
- package/dist/workflows/getAllUsersWorkflow.d.ts +27 -0
- package/dist/workflows/getAllUsersWorkflow.d.ts.map +1 -0
- package/dist/workflows/getAllUsersWorkflow.js +48 -0
- package/dist/workflows/getAllUsersWorkflow.js.map +1 -0
- package/dist/workflows/issueCreationHelpers.d.ts +54 -0
- package/dist/workflows/issueCreationHelpers.d.ts.map +1 -0
- package/dist/workflows/issueCreationHelpers.js +272 -0
- package/dist/workflows/issueCreationHelpers.js.map +1 -0
- package/dist/workflows/logTimeWorkflow.d.ts +6 -1
- package/dist/workflows/logTimeWorkflow.d.ts.map +1 -1
- package/dist/workflows/logTimeWorkflow.js +139 -20
- package/dist/workflows/logTimeWorkflow.js.map +1 -1
- package/dist/workflows/sheetToCsvWorkflow.d.ts +15 -0
- package/dist/workflows/sheetToCsvWorkflow.d.ts.map +1 -0
- package/dist/workflows/sheetToCsvWorkflow.js +65 -0
- package/dist/workflows/sheetToCsvWorkflow.js.map +1 -0
- package/package.json +2 -2
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.optional()
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.optional(),
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
.optional()
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.optional(),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
? {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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_data → sheet_to_csv → redmine_agent_bulk_update_issues. Steps: pick_project → map_columns (use suggestedColumnMapping) → confirm_mapping → fix_status_gaps if needed → preview_gate → previewAction 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
|
}
|