@pschroee/redmine-mcp 0.4.3 → 0.4.4
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.
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { formatJournals } from "./journals.js";
|
|
1
|
+
export { formatJournals, type NameLookup } from "./journals.js";
|
|
2
2
|
export { formatIssue, formatIssueResponse } from "./issue.js";
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { RedmineIssue } from "../redmine/types.js";
|
|
2
|
+
import { type NameLookup } from "./journals.js";
|
|
2
3
|
/**
|
|
3
4
|
* Format an issue as complete Markdown
|
|
4
5
|
*/
|
|
5
|
-
export declare function formatIssue(issue: RedmineIssue): string;
|
|
6
|
+
export declare function formatIssue(issue: RedmineIssue, lookup?: NameLookup): string;
|
|
6
7
|
/**
|
|
7
8
|
* Format an issue API response as Markdown
|
|
8
9
|
*/
|
|
9
10
|
export declare function formatIssueResponse(response: {
|
|
10
11
|
issue: RedmineIssue;
|
|
11
|
-
}): string;
|
|
12
|
+
}, lookup?: NameLookup): string;
|
package/dist/formatters/issue.js
CHANGED
|
@@ -8,7 +8,7 @@ function formatDate(isoDate) {
|
|
|
8
8
|
/**
|
|
9
9
|
* Format an issue as complete Markdown
|
|
10
10
|
*/
|
|
11
|
-
export function formatIssue(issue) {
|
|
11
|
+
export function formatIssue(issue, lookup = {}) {
|
|
12
12
|
const lines = [];
|
|
13
13
|
// Title
|
|
14
14
|
lines.push(`# #${issue.id}: ${issue.subject}`);
|
|
@@ -116,13 +116,13 @@ export function formatIssue(issue) {
|
|
|
116
116
|
}
|
|
117
117
|
// Journals (history)
|
|
118
118
|
if (issue.journals && issue.journals.length > 0) {
|
|
119
|
-
lines.push(formatJournals(issue.journals));
|
|
119
|
+
lines.push(formatJournals(issue.journals, lookup));
|
|
120
120
|
}
|
|
121
121
|
return lines.join("\n");
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
124
124
|
* Format an issue API response as Markdown
|
|
125
125
|
*/
|
|
126
|
-
export function formatIssueResponse(response) {
|
|
127
|
-
return formatIssue(response.issue);
|
|
126
|
+
export function formatIssueResponse(response, lookup = {}) {
|
|
127
|
+
return formatIssue(response.issue, lookup);
|
|
128
128
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { RedmineJournal } from "../redmine/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Lookup map for resolving IDs to names
|
|
4
|
+
* Key: field name (e.g., "status_id"), Value: map of ID -> name
|
|
5
|
+
*/
|
|
6
|
+
export type NameLookup = Record<string, Record<string, string>>;
|
|
2
7
|
/**
|
|
3
8
|
* Format an array of journal entries as Markdown
|
|
4
9
|
*/
|
|
5
|
-
export declare function formatJournals(journals: RedmineJournal[]): string;
|
|
10
|
+
export declare function formatJournals(journals: RedmineJournal[], lookup?: NameLookup): string;
|
|
@@ -3,6 +3,19 @@ import { diffLines } from "diff";
|
|
|
3
3
|
* Fields that contain large text and should use diff formatting
|
|
4
4
|
*/
|
|
5
5
|
const LARGE_TEXT_FIELDS = ["description"];
|
|
6
|
+
/**
|
|
7
|
+
* Field name mappings for display (remove _id suffix)
|
|
8
|
+
*/
|
|
9
|
+
const FIELD_DISPLAY_NAMES = {
|
|
10
|
+
status_id: "status",
|
|
11
|
+
tracker_id: "tracker",
|
|
12
|
+
priority_id: "priority",
|
|
13
|
+
assigned_to_id: "assigned_to",
|
|
14
|
+
category_id: "category",
|
|
15
|
+
fixed_version_id: "version",
|
|
16
|
+
parent_id: "parent",
|
|
17
|
+
project_id: "project",
|
|
18
|
+
};
|
|
6
19
|
/**
|
|
7
20
|
* Format a date string to a readable format
|
|
8
21
|
*/
|
|
@@ -32,10 +45,26 @@ function generateDiff(oldValue, newValue) {
|
|
|
32
45
|
}
|
|
33
46
|
return lines.join("\n");
|
|
34
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve an ID value to a display string with name
|
|
50
|
+
*/
|
|
51
|
+
function resolveValue(fieldName, value, lookup) {
|
|
52
|
+
if (!value)
|
|
53
|
+
return "_(empty)_";
|
|
54
|
+
const fieldLookup = lookup[fieldName];
|
|
55
|
+
if (fieldLookup && fieldLookup[value]) {
|
|
56
|
+
return `${fieldLookup[value]} (${value})`;
|
|
57
|
+
}
|
|
58
|
+
// For parent_id, show as issue reference
|
|
59
|
+
if (fieldName === "parent_id") {
|
|
60
|
+
return `#${value}`;
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
35
64
|
/**
|
|
36
65
|
* Format a single journal detail (field change)
|
|
37
66
|
*/
|
|
38
|
-
function formatDetail(detail) {
|
|
67
|
+
function formatDetail(detail, lookup) {
|
|
39
68
|
const { property, name, old_value, new_value } = detail;
|
|
40
69
|
// Handle attachment additions/removals
|
|
41
70
|
if (property === "attachment") {
|
|
@@ -73,10 +102,18 @@ function formatDetail(detail) {
|
|
|
73
102
|
const diff = generateDiff(old_value, new_value);
|
|
74
103
|
return `- ${name}:\n\`\`\`diff\n${diff}\n\`\`\``;
|
|
75
104
|
}
|
|
105
|
+
// Get display name for the field
|
|
106
|
+
const displayName = FIELD_DISPLAY_NAMES[name] || name;
|
|
107
|
+
// Resolve ID fields to names
|
|
108
|
+
if (name.endsWith("_id") && lookup[name]) {
|
|
109
|
+
const oldDisplay = resolveValue(name, old_value, lookup);
|
|
110
|
+
const newDisplay = resolveValue(name, new_value, lookup);
|
|
111
|
+
return `- ${displayName}: ${oldDisplay} → ${newDisplay}`;
|
|
112
|
+
}
|
|
76
113
|
// Simple field changes
|
|
77
114
|
const oldDisplay = old_value || "_(empty)_";
|
|
78
115
|
const newDisplay = new_value || "_(empty)_";
|
|
79
|
-
return `- ${
|
|
116
|
+
return `- ${displayName}: ${oldDisplay} → ${newDisplay}`;
|
|
80
117
|
}
|
|
81
118
|
// Fallback for unknown property types
|
|
82
119
|
return `- ${property}.${name}: ${old_value || "_(empty)_"} → ${new_value || "_(empty)_"}`;
|
|
@@ -84,7 +121,7 @@ function formatDetail(detail) {
|
|
|
84
121
|
/**
|
|
85
122
|
* Format a single journal entry as Markdown
|
|
86
123
|
*/
|
|
87
|
-
function formatJournalEntry(journal) {
|
|
124
|
+
function formatJournalEntry(journal, lookup) {
|
|
88
125
|
const lines = [];
|
|
89
126
|
// Header with date and user
|
|
90
127
|
const date = formatDate(journal.created_on);
|
|
@@ -101,7 +138,7 @@ function formatJournalEntry(journal) {
|
|
|
101
138
|
if (journal.details && journal.details.length > 0) {
|
|
102
139
|
lines.push("**Changes:**");
|
|
103
140
|
for (const detail of journal.details) {
|
|
104
|
-
lines.push(formatDetail(detail));
|
|
141
|
+
lines.push(formatDetail(detail, lookup));
|
|
105
142
|
}
|
|
106
143
|
lines.push("");
|
|
107
144
|
}
|
|
@@ -110,11 +147,11 @@ function formatJournalEntry(journal) {
|
|
|
110
147
|
/**
|
|
111
148
|
* Format an array of journal entries as Markdown
|
|
112
149
|
*/
|
|
113
|
-
export function formatJournals(journals) {
|
|
150
|
+
export function formatJournals(journals, lookup = {}) {
|
|
114
151
|
if (!journals || journals.length === 0) {
|
|
115
152
|
return "";
|
|
116
153
|
}
|
|
117
154
|
const header = `## History (${journals.length} entries)\n\n`;
|
|
118
|
-
const entries = journals.map(formatJournalEntry).join("\n---\n\n");
|
|
155
|
+
const entries = journals.map(j => formatJournalEntry(j, lookup)).join("\n---\n\n");
|
|
119
156
|
return header + entries;
|
|
120
157
|
}
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { registerTools } from "./tools/index.js";
|
|
|
3
3
|
export function createServer(redmineClient, toolGroups) {
|
|
4
4
|
const server = new McpServer({
|
|
5
5
|
name: "redmine-mcp",
|
|
6
|
-
version: "0.4.
|
|
6
|
+
version: "0.4.4",
|
|
7
7
|
});
|
|
8
8
|
registerTools(server, redmineClient, toolGroups);
|
|
9
9
|
return server;
|
package/dist/tools/core.js
CHANGED
|
@@ -35,16 +35,43 @@ export function registerCoreTools(server, client) {
|
|
|
35
35
|
include: z.string().optional().describe("Include: attachments, relations, journals, watchers, children, changesets, allowed_statuses"),
|
|
36
36
|
},
|
|
37
37
|
}, async (params) => {
|
|
38
|
-
|
|
38
|
+
// Fetch issue and enumerations in parallel
|
|
39
|
+
const [result, statusesResult, trackersResult, prioritiesResult] = await Promise.all([
|
|
40
|
+
client.getIssue(params.issue_id, params.include),
|
|
41
|
+
client.listIssueStatuses(),
|
|
42
|
+
client.listTrackers(),
|
|
43
|
+
client.listIssuePriorities(),
|
|
44
|
+
]);
|
|
39
45
|
// Check if this is an error response
|
|
40
46
|
if ("error" in result) {
|
|
41
47
|
return {
|
|
42
48
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
43
49
|
};
|
|
44
50
|
}
|
|
51
|
+
// Build lookup map for ID -> name resolution
|
|
52
|
+
const lookup = {
|
|
53
|
+
status_id: {},
|
|
54
|
+
tracker_id: {},
|
|
55
|
+
priority_id: {},
|
|
56
|
+
};
|
|
57
|
+
if (!("error" in statusesResult)) {
|
|
58
|
+
for (const s of statusesResult.issue_statuses) {
|
|
59
|
+
lookup.status_id[String(s.id)] = s.name;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!("error" in trackersResult)) {
|
|
63
|
+
for (const t of trackersResult.trackers) {
|
|
64
|
+
lookup.tracker_id[String(t.id)] = t.name;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!("error" in prioritiesResult)) {
|
|
68
|
+
for (const p of prioritiesResult.issue_priorities) {
|
|
69
|
+
lookup.priority_id[String(p.id)] = p.name;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
45
72
|
// Format response as Markdown
|
|
46
73
|
return {
|
|
47
|
-
content: [{ type: "text", text: formatIssueResponse(result) }],
|
|
74
|
+
content: [{ type: "text", text: formatIssueResponse(result, lookup) }],
|
|
48
75
|
};
|
|
49
76
|
});
|
|
50
77
|
server.registerTool("create_issue", {
|