@pschroee/redmine-mcp 0.5.0 → 0.5.2
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/formatters/account.d.ts +2 -0
- package/dist/formatters/account.js +48 -0
- package/dist/formatters/agile.d.ts +6 -0
- package/dist/formatters/agile.js +53 -0
- package/dist/formatters/checklist.d.ts +5 -0
- package/dist/formatters/checklist.js +30 -0
- package/dist/formatters/file.d.ts +5 -0
- package/dist/formatters/file.js +61 -0
- package/dist/formatters/index.d.ts +7 -1
- package/dist/formatters/index.js +7 -1
- package/dist/formatters/issue.d.ts +8 -2
- package/dist/formatters/issue.js +7 -16
- package/dist/formatters/journals.d.ts +9 -1
- package/dist/formatters/journals.js +17 -15
- package/dist/formatters/membership.d.ts +5 -0
- package/dist/formatters/membership.js +41 -0
- package/dist/formatters/metadata.d.ts +47 -0
- package/dist/formatters/metadata.js +76 -0
- package/dist/formatters/project.js +1 -6
- package/dist/formatters/relation.d.ts +5 -0
- package/dist/formatters/relation.js +55 -0
- package/dist/formatters/search.js +1 -15
- package/dist/formatters/user.js +1 -6
- package/dist/formatters/utils.d.ts +12 -0
- package/dist/formatters/utils.js +31 -0
- package/dist/formatters/wiki.js +1 -6
- package/dist/redmine/client.d.ts +4 -0
- package/dist/redmine/client.js +6 -0
- package/dist/server.js +1 -1
- package/dist/tools/account.js +7 -1
- package/dist/tools/agile.js +19 -3
- package/dist/tools/checklists.js +13 -2
- package/dist/tools/core.js +4 -1
- package/dist/tools/enumerations.js +7 -2
- package/dist/tools/files.js +13 -2
- package/dist/tools/memberships.js +13 -2
- package/dist/tools/metadata.js +19 -4
- package/dist/tools/relations.js +13 -3
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { formatDate } from "./utils.js";
|
|
2
|
+
const USER_STATUS = {
|
|
3
|
+
1: "Active",
|
|
4
|
+
2: "Registered",
|
|
5
|
+
3: "Locked",
|
|
6
|
+
};
|
|
7
|
+
export function formatMyAccount(response) {
|
|
8
|
+
const user = response.user;
|
|
9
|
+
const lines = [];
|
|
10
|
+
lines.push(`# ${user.firstname} ${user.lastname}`);
|
|
11
|
+
lines.push("");
|
|
12
|
+
const statusParts = [];
|
|
13
|
+
statusParts.push(`**Login:** ${user.login}`);
|
|
14
|
+
if (user.status !== undefined) {
|
|
15
|
+
statusParts.push(`**Status:** ${USER_STATUS[user.status] || "Unknown"}`);
|
|
16
|
+
}
|
|
17
|
+
if (user.admin) {
|
|
18
|
+
statusParts.push(`**Role:** Admin`);
|
|
19
|
+
}
|
|
20
|
+
lines.push(statusParts.join(" | "));
|
|
21
|
+
lines.push("");
|
|
22
|
+
lines.push("| Field | Value |");
|
|
23
|
+
lines.push("|-------|-------|");
|
|
24
|
+
lines.push(`| Email | ${user.mail} |`);
|
|
25
|
+
lines.push(`| Created | ${formatDate(user.created_on)} |`);
|
|
26
|
+
if (user.last_login_on) {
|
|
27
|
+
lines.push(`| Last Login | ${formatDate(user.last_login_on)} |`);
|
|
28
|
+
}
|
|
29
|
+
if (user.twofa_scheme) {
|
|
30
|
+
lines.push(`| 2FA | ${user.twofa_scheme} |`);
|
|
31
|
+
}
|
|
32
|
+
if (user.api_key) {
|
|
33
|
+
lines.push(`| API Key | ${user.api_key} |`);
|
|
34
|
+
}
|
|
35
|
+
lines.push("");
|
|
36
|
+
if (user.custom_fields && user.custom_fields.length > 0) {
|
|
37
|
+
lines.push("## Custom Fields");
|
|
38
|
+
lines.push("");
|
|
39
|
+
lines.push("| Field | Value |");
|
|
40
|
+
lines.push("|-------|-------|");
|
|
41
|
+
for (const cf of user.custom_fields) {
|
|
42
|
+
const value = Array.isArray(cf.value) ? cf.value.join(", ") : cf.value;
|
|
43
|
+
lines.push(`| ${cf.name} | ${value} |`);
|
|
44
|
+
}
|
|
45
|
+
lines.push("");
|
|
46
|
+
}
|
|
47
|
+
return lines.join("\n").trimEnd();
|
|
48
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RedmineAgileSprint, RedmineAgileSprintsResponse, RedmineAgileDataResponse } from "../redmine/types.js";
|
|
2
|
+
export declare function formatSprint(response: {
|
|
3
|
+
agile_sprint: RedmineAgileSprint;
|
|
4
|
+
}): string;
|
|
5
|
+
export declare function formatSprintList(response: RedmineAgileSprintsResponse): string;
|
|
6
|
+
export declare function formatAgileData(response: RedmineAgileDataResponse): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function formatSprint(response) {
|
|
2
|
+
const sprint = response.agile_sprint;
|
|
3
|
+
const lines = [];
|
|
4
|
+
lines.push(`# ${sprint.name}`);
|
|
5
|
+
lines.push("");
|
|
6
|
+
lines.push("| Field | Value |");
|
|
7
|
+
lines.push("|-------|-------|");
|
|
8
|
+
lines.push(`| ID | ${sprint.id} |`);
|
|
9
|
+
lines.push(`| Status | ${sprint.status} |`);
|
|
10
|
+
if (sprint.start_date) {
|
|
11
|
+
lines.push(`| Start Date | ${sprint.start_date} |`);
|
|
12
|
+
}
|
|
13
|
+
if (sprint.end_date) {
|
|
14
|
+
lines.push(`| End Date | ${sprint.end_date} |`);
|
|
15
|
+
}
|
|
16
|
+
if (sprint.description) {
|
|
17
|
+
lines.push(`| Description | ${sprint.description} |`);
|
|
18
|
+
}
|
|
19
|
+
return lines.join("\n");
|
|
20
|
+
}
|
|
21
|
+
export function formatSprintList(response) {
|
|
22
|
+
const sprints = response.agile_sprints;
|
|
23
|
+
if (sprints.length === 0) {
|
|
24
|
+
return "No sprints found.";
|
|
25
|
+
}
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(`# Agile Sprints (${sprints.length})`);
|
|
28
|
+
lines.push("");
|
|
29
|
+
lines.push("| ID | Name | Status | Start | End |");
|
|
30
|
+
lines.push("|----|------|--------|-------|-----|");
|
|
31
|
+
for (const sprint of sprints) {
|
|
32
|
+
const start = sprint.start_date || "-";
|
|
33
|
+
const end = sprint.end_date || "-";
|
|
34
|
+
lines.push(`| ${sprint.id} | ${sprint.name} | ${sprint.status} | ${start} | ${end} |`);
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
export function formatAgileData(response) {
|
|
39
|
+
const data = response.agile_data;
|
|
40
|
+
const lines = [];
|
|
41
|
+
lines.push(`# Agile Data for Issue #${data.issue_id}`);
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push("| Field | Value |");
|
|
44
|
+
lines.push("|-------|-------|");
|
|
45
|
+
lines.push(`| Position | ${data.position} |`);
|
|
46
|
+
if (data.story_points !== undefined) {
|
|
47
|
+
lines.push(`| Story Points | ${data.story_points} |`);
|
|
48
|
+
}
|
|
49
|
+
if (data.agile_sprint_id !== undefined) {
|
|
50
|
+
lines.push(`| Sprint ID | ${data.agile_sprint_id} |`);
|
|
51
|
+
}
|
|
52
|
+
return lines.join("\n");
|
|
53
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RedmineChecklist, RedmineChecklistsResponse } from "../redmine/types.js";
|
|
2
|
+
export declare function formatChecklist(response: {
|
|
3
|
+
checklist: RedmineChecklist;
|
|
4
|
+
}): string;
|
|
5
|
+
export declare function formatChecklistList(response: RedmineChecklistsResponse): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function formatChecklist(response) {
|
|
2
|
+
const item = response.checklist;
|
|
3
|
+
const lines = [];
|
|
4
|
+
lines.push(`# Checklist Item #${item.id}`);
|
|
5
|
+
lines.push("");
|
|
6
|
+
lines.push("| Field | Value |");
|
|
7
|
+
lines.push("|-------|-------|");
|
|
8
|
+
lines.push(`| Subject | ${item.subject} |`);
|
|
9
|
+
lines.push(`| Status | ${item.is_done ? "Done" : "Pending"} |`);
|
|
10
|
+
lines.push(`| Issue | #${item.issue_id} |`);
|
|
11
|
+
lines.push(`| Position | ${item.position} |`);
|
|
12
|
+
return lines.join("\n");
|
|
13
|
+
}
|
|
14
|
+
export function formatChecklistList(response) {
|
|
15
|
+
const items = response.checklists;
|
|
16
|
+
if (items.length === 0) {
|
|
17
|
+
return "No checklist items found.";
|
|
18
|
+
}
|
|
19
|
+
const sorted = [...items].sort((a, b) => a.position - b.position);
|
|
20
|
+
const doneCount = items.filter((i) => i.is_done).length;
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push(`# Checklist (${items.length} items)`);
|
|
23
|
+
lines.push(`_${doneCount}/${items.length} completed_`);
|
|
24
|
+
lines.push("");
|
|
25
|
+
for (const item of sorted) {
|
|
26
|
+
const checkbox = item.is_done ? "[x]" : "[ ]";
|
|
27
|
+
lines.push(`- ${checkbox} ${item.subject}`);
|
|
28
|
+
}
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { formatDate, formatDateShort } from "./utils.js";
|
|
2
|
+
function formatFileSize(bytes) {
|
|
3
|
+
if (bytes < 1024)
|
|
4
|
+
return `${bytes} B`;
|
|
5
|
+
if (bytes < 1024 * 1024)
|
|
6
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
7
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
8
|
+
}
|
|
9
|
+
export function formatAttachment(response) {
|
|
10
|
+
const att = response.attachment;
|
|
11
|
+
const lines = [];
|
|
12
|
+
lines.push(`# ${att.filename}`);
|
|
13
|
+
lines.push("");
|
|
14
|
+
lines.push("| Field | Value |");
|
|
15
|
+
lines.push("|-------|-------|");
|
|
16
|
+
lines.push(`| ID | ${att.id} |`);
|
|
17
|
+
lines.push(`| Size | ${formatFileSize(att.filesize)} |`);
|
|
18
|
+
lines.push(`| Type | ${att.content_type} |`);
|
|
19
|
+
lines.push(`| Author | ${att.author.name} |`);
|
|
20
|
+
lines.push(`| Created | ${formatDate(att.created_on)} |`);
|
|
21
|
+
if (att.description) {
|
|
22
|
+
lines.push(`| Description | ${att.description} |`);
|
|
23
|
+
}
|
|
24
|
+
lines.push("");
|
|
25
|
+
lines.push(`**Download:** ${att.content_url}`);
|
|
26
|
+
if (att.thumbnail_url) {
|
|
27
|
+
lines.push("");
|
|
28
|
+
lines.push(`**Thumbnail:** ${att.thumbnail_url}`);
|
|
29
|
+
}
|
|
30
|
+
return lines.join("\n");
|
|
31
|
+
}
|
|
32
|
+
export function formatFileList(response) {
|
|
33
|
+
const files = response.files;
|
|
34
|
+
if (files.length === 0) {
|
|
35
|
+
return "No files found.";
|
|
36
|
+
}
|
|
37
|
+
const lines = [];
|
|
38
|
+
const hasVersion = files.some((f) => f.version !== undefined);
|
|
39
|
+
lines.push(`# Project Files (${files.length})`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
if (hasVersion) {
|
|
42
|
+
lines.push("| Filename | Size | Version | Downloads | Author | Date |");
|
|
43
|
+
lines.push("|----------|------|---------|-----------|--------|------|");
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
lines.push("| Filename | Size | Downloads | Author | Date |");
|
|
47
|
+
lines.push("|----------|------|-----------|--------|------|");
|
|
48
|
+
}
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
const size = formatFileSize(file.filesize);
|
|
51
|
+
const date = formatDateShort(file.created_on);
|
|
52
|
+
if (hasVersion) {
|
|
53
|
+
const version = file.version?.name || "";
|
|
54
|
+
lines.push(`| ${file.filename} | ${size} | ${version} | ${file.downloads} | ${file.author.name} | ${date} |`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
lines.push(`| ${file.filename} | ${size} | ${file.downloads} | ${file.author.name} | ${date} |`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
@@ -7,4 +7,10 @@ export { formatWikiPage, formatWikiPageList } from "./wiki.js";
|
|
|
7
7
|
export { formatVersion, formatVersionList } from "./version.js";
|
|
8
8
|
export { formatTimeEntry, formatTimeEntryList } from "./time.js";
|
|
9
9
|
export { formatGroup, formatGroupList } from "./group.js";
|
|
10
|
-
export { formatTrackerList, formatStatusList, formatCategoryList, formatPriorityList, formatActivityList, formatRoleList, formatRole, } from "./metadata.js";
|
|
10
|
+
export { formatTrackerList, formatStatusList, formatCategoryList, formatPriorityList, formatActivityList, formatRoleList, formatRole, formatCategory, formatCustomFieldList, formatQueryList, formatDocumentCategoryList, } from "./metadata.js";
|
|
11
|
+
export { formatRelation, formatRelationList } from "./relation.js";
|
|
12
|
+
export { formatAttachment, formatFileList } from "./file.js";
|
|
13
|
+
export { formatMembership, formatMembershipList } from "./membership.js";
|
|
14
|
+
export { formatChecklist, formatChecklistList } from "./checklist.js";
|
|
15
|
+
export { formatSprint, formatSprintList, formatAgileData } from "./agile.js";
|
|
16
|
+
export { formatMyAccount } from "./account.js";
|
package/dist/formatters/index.js
CHANGED
|
@@ -7,4 +7,10 @@ export { formatWikiPage, formatWikiPageList } from "./wiki.js";
|
|
|
7
7
|
export { formatVersion, formatVersionList } from "./version.js";
|
|
8
8
|
export { formatTimeEntry, formatTimeEntryList } from "./time.js";
|
|
9
9
|
export { formatGroup, formatGroupList } from "./group.js";
|
|
10
|
-
export { formatTrackerList, formatStatusList, formatCategoryList, formatPriorityList, formatActivityList, formatRoleList, formatRole, } from "./metadata.js";
|
|
10
|
+
export { formatTrackerList, formatStatusList, formatCategoryList, formatPriorityList, formatActivityList, formatRoleList, formatRole, formatCategory, formatCustomFieldList, formatQueryList, formatDocumentCategoryList, } from "./metadata.js";
|
|
11
|
+
export { formatRelation, formatRelationList } from "./relation.js";
|
|
12
|
+
export { formatAttachment, formatFileList } from "./file.js";
|
|
13
|
+
export { formatMembership, formatMembershipList } from "./membership.js";
|
|
14
|
+
export { formatChecklist, formatChecklistList } from "./checklist.js";
|
|
15
|
+
export { formatSprint, formatSprintList, formatAgileData } from "./agile.js";
|
|
16
|
+
export { formatMyAccount } from "./account.js";
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type { RedmineIssue, RedmineIssuesResponse } from "../redmine/types.js";
|
|
2
2
|
import { type NameLookup } from "./journals.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options for formatting issues
|
|
5
|
+
*/
|
|
6
|
+
export interface IssueFormatOptions {
|
|
7
|
+
includeDescriptionDiffs?: boolean;
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* Format an issue as complete Markdown
|
|
5
11
|
*/
|
|
6
|
-
export declare function formatIssue(issue: RedmineIssue, lookup?: NameLookup): string;
|
|
12
|
+
export declare function formatIssue(issue: RedmineIssue, lookup?: NameLookup, options?: IssueFormatOptions): string;
|
|
7
13
|
/**
|
|
8
14
|
* Format an issue API response as Markdown
|
|
9
15
|
*/
|
|
10
16
|
export declare function formatIssueResponse(response: {
|
|
11
17
|
issue: RedmineIssue;
|
|
12
|
-
}, lookup?: NameLookup): string;
|
|
18
|
+
}, lookup?: NameLookup, options?: IssueFormatOptions): string;
|
|
13
19
|
/**
|
|
14
20
|
* Format a list of issues as a Markdown table
|
|
15
21
|
*/
|
package/dist/formatters/issue.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { formatJournals } from "./journals.js";
|
|
2
|
-
|
|
3
|
-
* Format a date string to readable format
|
|
4
|
-
*/
|
|
5
|
-
function formatDate(isoDate) {
|
|
6
|
-
return new Date(isoDate).toISOString().slice(0, 16).replace("T", " ");
|
|
7
|
-
}
|
|
2
|
+
import { formatDate, formatDateShort } from "./utils.js";
|
|
8
3
|
/**
|
|
9
4
|
* Format an issue as complete Markdown
|
|
10
5
|
*/
|
|
11
|
-
export function formatIssue(issue, lookup = {}) {
|
|
6
|
+
export function formatIssue(issue, lookup = {}, options = {}) {
|
|
12
7
|
const lines = [];
|
|
13
8
|
// Title
|
|
14
9
|
lines.push(`# #${issue.id}: ${issue.subject}`);
|
|
@@ -116,21 +111,17 @@ export function formatIssue(issue, lookup = {}) {
|
|
|
116
111
|
}
|
|
117
112
|
// Journals (history)
|
|
118
113
|
if (issue.journals && issue.journals.length > 0) {
|
|
119
|
-
lines.push(formatJournals(issue.journals, lookup
|
|
114
|
+
lines.push(formatJournals(issue.journals, lookup, {
|
|
115
|
+
includeDescriptionDiffs: options.includeDescriptionDiffs,
|
|
116
|
+
}));
|
|
120
117
|
}
|
|
121
118
|
return lines.join("\n");
|
|
122
119
|
}
|
|
123
120
|
/**
|
|
124
121
|
* Format an issue API response as Markdown
|
|
125
122
|
*/
|
|
126
|
-
export function formatIssueResponse(response, lookup = {}) {
|
|
127
|
-
return formatIssue(response.issue, lookup);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Format a date string to YYYY-MM-DD format
|
|
131
|
-
*/
|
|
132
|
-
function formatDateShort(isoDate) {
|
|
133
|
-
return new Date(isoDate).toISOString().slice(0, 10);
|
|
123
|
+
export function formatIssueResponse(response, lookup = {}, options = {}) {
|
|
124
|
+
return formatIssue(response.issue, lookup, options);
|
|
134
125
|
}
|
|
135
126
|
/**
|
|
136
127
|
* Format a list of issues as a Markdown table
|
|
@@ -4,7 +4,15 @@ import type { RedmineJournal } from "../redmine/types.js";
|
|
|
4
4
|
* Key: field name (e.g., "status_id"), Value: map of ID -> name
|
|
5
5
|
*/
|
|
6
6
|
export type NameLookup = Record<string, Record<string, string>>;
|
|
7
|
+
/**
|
|
8
|
+
* Options for formatting journals
|
|
9
|
+
*/
|
|
10
|
+
export interface JournalFormatOptions {
|
|
11
|
+
includeDescriptionDiffs?: boolean;
|
|
12
|
+
}
|
|
7
13
|
/**
|
|
8
14
|
* Format an array of journal entries as Markdown
|
|
15
|
+
* Entries are displayed in reverse chronological order (newest first)
|
|
16
|
+
* Note numbers are preserved (1 = oldest, highest = newest)
|
|
9
17
|
*/
|
|
10
|
-
export declare function formatJournals(journals: RedmineJournal[], lookup?: NameLookup): string;
|
|
18
|
+
export declare function formatJournals(journals: RedmineJournal[], lookup?: NameLookup, options?: JournalFormatOptions): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { diffLines } from "diff";
|
|
2
|
+
import { formatDate } from "./utils.js";
|
|
2
3
|
/**
|
|
3
4
|
* Fields that contain large text and should use diff formatting
|
|
4
5
|
*/
|
|
@@ -68,13 +69,6 @@ const FIELD_DISPLAY_NAMES = {
|
|
|
68
69
|
parent_id: "parent",
|
|
69
70
|
project_id: "project",
|
|
70
71
|
};
|
|
71
|
-
/**
|
|
72
|
-
* Format a date string to a readable format
|
|
73
|
-
*/
|
|
74
|
-
function formatDate(isoDate) {
|
|
75
|
-
const date = new Date(isoDate);
|
|
76
|
-
return date.toISOString().slice(0, 16).replace("T", " ");
|
|
77
|
-
}
|
|
78
72
|
/**
|
|
79
73
|
* Generate a unified diff for large text changes
|
|
80
74
|
*/
|
|
@@ -116,7 +110,7 @@ function resolveValue(fieldName, value, lookup) {
|
|
|
116
110
|
/**
|
|
117
111
|
* Format a single journal detail (field change)
|
|
118
112
|
*/
|
|
119
|
-
function formatDetail(detail, lookup) {
|
|
113
|
+
function formatDetail(detail, lookup, options = {}) {
|
|
120
114
|
const { property, name, old_value, new_value } = detail;
|
|
121
115
|
// Handle attachment additions/removals
|
|
122
116
|
if (property === "attachment") {
|
|
@@ -157,8 +151,11 @@ function formatDetail(detail, lookup) {
|
|
|
157
151
|
}
|
|
158
152
|
// Fall through to default formatting if parsing failed
|
|
159
153
|
}
|
|
160
|
-
// Large text fields get diff formatting
|
|
154
|
+
// Large text fields get diff formatting (only if option enabled)
|
|
161
155
|
if (LARGE_TEXT_FIELDS.includes(name) && old_value && new_value) {
|
|
156
|
+
if (!options.includeDescriptionDiffs) {
|
|
157
|
+
return `- ${name}: _(changed - use include_description_diffs to see diff)_`;
|
|
158
|
+
}
|
|
162
159
|
const diff = generateDiff(old_value, new_value);
|
|
163
160
|
return `- ${name}:\n\`\`\`diff\n${diff}\n\`\`\``;
|
|
164
161
|
}
|
|
@@ -181,13 +178,14 @@ function formatDetail(detail, lookup) {
|
|
|
181
178
|
/**
|
|
182
179
|
* Format a single journal entry as Markdown
|
|
183
180
|
*/
|
|
184
|
-
function formatJournalEntry(journal, lookup) {
|
|
181
|
+
function formatJournalEntry(journal, index, lookup, options = {}) {
|
|
185
182
|
const lines = [];
|
|
186
|
-
// Header with date and user
|
|
183
|
+
// Header with note number, date and user
|
|
184
|
+
const noteNum = index + 1;
|
|
187
185
|
const date = formatDate(journal.created_on);
|
|
188
186
|
const user = journal.user.name;
|
|
189
187
|
const privateTag = journal.private_notes ? " 🔒" : "";
|
|
190
|
-
lines.push(`### ${date} - ${user}${privateTag}`);
|
|
188
|
+
lines.push(`### #${noteNum} - ${date} - ${user}${privateTag}`);
|
|
191
189
|
lines.push("");
|
|
192
190
|
// Notes (if any)
|
|
193
191
|
if (journal.notes && journal.notes.trim()) {
|
|
@@ -198,7 +196,7 @@ function formatJournalEntry(journal, lookup) {
|
|
|
198
196
|
if (journal.details && journal.details.length > 0) {
|
|
199
197
|
lines.push("**Changes:**");
|
|
200
198
|
for (const detail of journal.details) {
|
|
201
|
-
lines.push(formatDetail(detail, lookup));
|
|
199
|
+
lines.push(formatDetail(detail, lookup, options));
|
|
202
200
|
}
|
|
203
201
|
lines.push("");
|
|
204
202
|
}
|
|
@@ -206,12 +204,16 @@ function formatJournalEntry(journal, lookup) {
|
|
|
206
204
|
}
|
|
207
205
|
/**
|
|
208
206
|
* Format an array of journal entries as Markdown
|
|
207
|
+
* Entries are displayed in reverse chronological order (newest first)
|
|
208
|
+
* Note numbers are preserved (1 = oldest, highest = newest)
|
|
209
209
|
*/
|
|
210
|
-
export function formatJournals(journals, lookup = {}) {
|
|
210
|
+
export function formatJournals(journals, lookup = {}, options = {}) {
|
|
211
211
|
if (!journals || journals.length === 0) {
|
|
212
212
|
return "";
|
|
213
213
|
}
|
|
214
214
|
const header = `## History (${journals.length} entries)\n\n`;
|
|
215
|
-
|
|
215
|
+
// Format entries with their original indices (for note numbers), then reverse for display
|
|
216
|
+
const formattedEntries = journals.map((j, i) => formatJournalEntry(j, i, lookup, options));
|
|
217
|
+
const entries = formattedEntries.reverse().join("\n---\n\n");
|
|
216
218
|
return header + entries;
|
|
217
219
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RedmineMembership, RedmineMembershipsResponse } from "../redmine/types.js";
|
|
2
|
+
export declare function formatMembership(response: {
|
|
3
|
+
membership: RedmineMembership;
|
|
4
|
+
}): string;
|
|
5
|
+
export declare function formatMembershipList(response: RedmineMembershipsResponse): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function formatRoles(roles) {
|
|
2
|
+
return roles
|
|
3
|
+
.map((r) => (r.inherited ? `${r.name} (inherited)` : r.name))
|
|
4
|
+
.join(", ");
|
|
5
|
+
}
|
|
6
|
+
export function formatMembership(response) {
|
|
7
|
+
const mem = response.membership;
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push(`# Membership #${mem.id}`);
|
|
10
|
+
lines.push("");
|
|
11
|
+
lines.push("| Field | Value |");
|
|
12
|
+
lines.push("|-------|-------|");
|
|
13
|
+
lines.push(`| Project | ${mem.project.name} |`);
|
|
14
|
+
if (mem.user) {
|
|
15
|
+
lines.push(`| User | ${mem.user.name} |`);
|
|
16
|
+
}
|
|
17
|
+
if (mem.group) {
|
|
18
|
+
lines.push(`| Group | ${mem.group.name} |`);
|
|
19
|
+
}
|
|
20
|
+
lines.push(`| Roles | ${formatRoles(mem.roles)} |`);
|
|
21
|
+
return lines.join("\n");
|
|
22
|
+
}
|
|
23
|
+
export function formatMembershipList(response) {
|
|
24
|
+
const memberships = response.memberships;
|
|
25
|
+
const totalCount = response.total_count;
|
|
26
|
+
if (memberships.length === 0) {
|
|
27
|
+
return "No memberships found.";
|
|
28
|
+
}
|
|
29
|
+
const lines = [];
|
|
30
|
+
lines.push(`# Project Memberships (${totalCount})`);
|
|
31
|
+
lines.push("");
|
|
32
|
+
lines.push("| ID | User/Group | Type | Roles |");
|
|
33
|
+
lines.push("|----|------------|------|-------|");
|
|
34
|
+
for (const mem of memberships) {
|
|
35
|
+
const name = mem.user?.name || mem.group?.name || "Unknown";
|
|
36
|
+
const type = mem.user ? "User" : "Group";
|
|
37
|
+
const roles = formatRoles(mem.roles);
|
|
38
|
+
lines.push(`| ${mem.id} | ${name} | ${type} | ${roles} |`);
|
|
39
|
+
}
|
|
40
|
+
return lines.join("\n");
|
|
41
|
+
}
|
|
@@ -55,6 +55,35 @@ interface RedmineRole {
|
|
|
55
55
|
interface RedmineRolesResponse {
|
|
56
56
|
roles: RedmineRole[];
|
|
57
57
|
}
|
|
58
|
+
interface RedmineCustomField {
|
|
59
|
+
id: number;
|
|
60
|
+
name: string;
|
|
61
|
+
customized_type: string;
|
|
62
|
+
field_format: string;
|
|
63
|
+
is_required: boolean;
|
|
64
|
+
is_filter: boolean;
|
|
65
|
+
searchable: boolean;
|
|
66
|
+
}
|
|
67
|
+
interface RedmineCustomFieldsResponse {
|
|
68
|
+
custom_fields: RedmineCustomField[];
|
|
69
|
+
}
|
|
70
|
+
interface RedmineQuery {
|
|
71
|
+
id: number;
|
|
72
|
+
name: string;
|
|
73
|
+
is_public: boolean;
|
|
74
|
+
project_id?: number;
|
|
75
|
+
}
|
|
76
|
+
interface RedmineQueriesResponse {
|
|
77
|
+
queries: RedmineQuery[];
|
|
78
|
+
}
|
|
79
|
+
interface RedmineDocumentCategory {
|
|
80
|
+
id: number;
|
|
81
|
+
name: string;
|
|
82
|
+
is_default: boolean;
|
|
83
|
+
}
|
|
84
|
+
interface RedmineDocumentCategoriesResponse {
|
|
85
|
+
document_categories: RedmineDocumentCategory[];
|
|
86
|
+
}
|
|
58
87
|
/**
|
|
59
88
|
* Format a list of trackers as a Markdown table
|
|
60
89
|
*/
|
|
@@ -85,4 +114,22 @@ export declare function formatRoleList(response: RedmineRolesResponse): string;
|
|
|
85
114
|
export declare function formatRole(response: {
|
|
86
115
|
role: RedmineRole;
|
|
87
116
|
}): string;
|
|
117
|
+
/**
|
|
118
|
+
* Format a single issue category as Markdown
|
|
119
|
+
*/
|
|
120
|
+
export declare function formatCategory(response: {
|
|
121
|
+
issue_category: RedmineCategory;
|
|
122
|
+
}): string;
|
|
123
|
+
/**
|
|
124
|
+
* Format a list of custom fields as a Markdown table
|
|
125
|
+
*/
|
|
126
|
+
export declare function formatCustomFieldList(response: RedmineCustomFieldsResponse): string;
|
|
127
|
+
/**
|
|
128
|
+
* Format a list of saved queries as a Markdown table
|
|
129
|
+
*/
|
|
130
|
+
export declare function formatQueryList(response: RedmineQueriesResponse): string;
|
|
131
|
+
/**
|
|
132
|
+
* Format a list of document categories as a Markdown table
|
|
133
|
+
*/
|
|
134
|
+
export declare function formatDocumentCategoryList(response: RedmineDocumentCategoriesResponse): string;
|
|
88
135
|
export {};
|
|
@@ -135,3 +135,79 @@ export function formatRole(response) {
|
|
|
135
135
|
}
|
|
136
136
|
return lines.join("\n").trimEnd();
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Format a single issue category as Markdown
|
|
140
|
+
*/
|
|
141
|
+
export function formatCategory(response) {
|
|
142
|
+
const cat = response.issue_category;
|
|
143
|
+
const lines = [];
|
|
144
|
+
lines.push(`# ${cat.name}`);
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push("| Field | Value |");
|
|
147
|
+
lines.push("|-------|-------|");
|
|
148
|
+
lines.push(`| ID | ${cat.id} |`);
|
|
149
|
+
if (cat.project) {
|
|
150
|
+
lines.push(`| Project | ${cat.project.name} |`);
|
|
151
|
+
}
|
|
152
|
+
if (cat.assigned_to) {
|
|
153
|
+
lines.push(`| Default Assignee | ${cat.assigned_to.name} |`);
|
|
154
|
+
}
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Format a list of custom fields as a Markdown table
|
|
159
|
+
*/
|
|
160
|
+
export function formatCustomFieldList(response) {
|
|
161
|
+
const fields = response.custom_fields;
|
|
162
|
+
if (fields.length === 0) {
|
|
163
|
+
return "No custom fields found.";
|
|
164
|
+
}
|
|
165
|
+
const lines = [];
|
|
166
|
+
lines.push(`# Custom Fields (${fields.length})`);
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push("| ID | Name | Type | Format | Required |");
|
|
169
|
+
lines.push("|----|------|------|--------|----------|");
|
|
170
|
+
for (const field of fields) {
|
|
171
|
+
const required = field.is_required ? "Yes" : "No";
|
|
172
|
+
lines.push(`| ${field.id} | ${field.name} | ${field.customized_type} | ${field.field_format} | ${required} |`);
|
|
173
|
+
}
|
|
174
|
+
return lines.join("\n");
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Format a list of saved queries as a Markdown table
|
|
178
|
+
*/
|
|
179
|
+
export function formatQueryList(response) {
|
|
180
|
+
const queries = response.queries;
|
|
181
|
+
if (queries.length === 0) {
|
|
182
|
+
return "No queries found.";
|
|
183
|
+
}
|
|
184
|
+
const lines = [];
|
|
185
|
+
lines.push(`# Saved Queries (${queries.length})`);
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push("| ID | Name | Visibility |");
|
|
188
|
+
lines.push("|----|------|------------|");
|
|
189
|
+
for (const query of queries) {
|
|
190
|
+
const visibility = query.is_public ? "Public" : "Private";
|
|
191
|
+
lines.push(`| ${query.id} | ${query.name} | ${visibility} |`);
|
|
192
|
+
}
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Format a list of document categories as a Markdown table
|
|
197
|
+
*/
|
|
198
|
+
export function formatDocumentCategoryList(response) {
|
|
199
|
+
const categories = response.document_categories;
|
|
200
|
+
if (categories.length === 0) {
|
|
201
|
+
return "No document categories found.";
|
|
202
|
+
}
|
|
203
|
+
const lines = [];
|
|
204
|
+
lines.push(`# Document Categories (${categories.length})`);
|
|
205
|
+
lines.push("");
|
|
206
|
+
lines.push("| ID | Name | Default |");
|
|
207
|
+
lines.push("|----|------|---------|");
|
|
208
|
+
for (const cat of categories) {
|
|
209
|
+
const isDefault = cat.is_default ? "Yes" : "No";
|
|
210
|
+
lines.push(`| ${cat.id} | ${cat.name} | ${isDefault} |`);
|
|
211
|
+
}
|
|
212
|
+
return lines.join("\n");
|
|
213
|
+
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
+
import { formatDate } from "./utils.js";
|
|
1
2
|
const PROJECT_STATUS = {
|
|
2
3
|
1: "Active",
|
|
3
4
|
5: "Closed",
|
|
4
5
|
9: "Archived",
|
|
5
6
|
};
|
|
6
|
-
/**
|
|
7
|
-
* Format a date string to readable format
|
|
8
|
-
*/
|
|
9
|
-
function formatDate(isoDate) {
|
|
10
|
-
return new Date(isoDate).toISOString().slice(0, 16).replace("T", " ");
|
|
11
|
-
}
|
|
12
7
|
/**
|
|
13
8
|
* Format a single project as complete Markdown
|
|
14
9
|
*/
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const RELATION_LABELS = {
|
|
2
|
+
relates: "relates",
|
|
3
|
+
duplicates: "duplicates",
|
|
4
|
+
duplicated: "duplicated by",
|
|
5
|
+
blocks: "blocks",
|
|
6
|
+
blocked: "blocked by",
|
|
7
|
+
precedes: "precedes",
|
|
8
|
+
follows: "follows",
|
|
9
|
+
copied_to: "copied to",
|
|
10
|
+
copied_from: "copied from",
|
|
11
|
+
};
|
|
12
|
+
export function formatRelation(response) {
|
|
13
|
+
const rel = response.relation;
|
|
14
|
+
const lines = [];
|
|
15
|
+
lines.push(`# Relation #${rel.id}`);
|
|
16
|
+
lines.push("");
|
|
17
|
+
lines.push("| Field | Value |");
|
|
18
|
+
lines.push("|-------|-------|");
|
|
19
|
+
lines.push(`| Issue | #${rel.issue_id} |`);
|
|
20
|
+
lines.push(`| Type | ${RELATION_LABELS[rel.relation_type] || rel.relation_type} |`);
|
|
21
|
+
lines.push(`| Related To | #${rel.issue_to_id} |`);
|
|
22
|
+
if (rel.delay !== undefined && rel.delay > 0) {
|
|
23
|
+
lines.push(`| Delay | ${rel.delay} days |`);
|
|
24
|
+
}
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
export function formatRelationList(response) {
|
|
28
|
+
const relations = response.relations;
|
|
29
|
+
if (relations.length === 0) {
|
|
30
|
+
return "No relations found.";
|
|
31
|
+
}
|
|
32
|
+
const lines = [];
|
|
33
|
+
const hasDelay = relations.some((r) => r.delay !== undefined && r.delay > 0);
|
|
34
|
+
lines.push(`# Issue Relations (${relations.length})`);
|
|
35
|
+
lines.push("");
|
|
36
|
+
if (hasDelay) {
|
|
37
|
+
lines.push("| ID | Issue | Type | Related To | Delay |");
|
|
38
|
+
lines.push("|----|-------|------|------------|-------|");
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
lines.push("| ID | Issue | Type | Related To |");
|
|
42
|
+
lines.push("|----|-------|------|------------|");
|
|
43
|
+
}
|
|
44
|
+
for (const rel of relations) {
|
|
45
|
+
const type = RELATION_LABELS[rel.relation_type] || rel.relation_type;
|
|
46
|
+
if (hasDelay) {
|
|
47
|
+
const delay = rel.delay ? `${rel.delay} days` : "";
|
|
48
|
+
lines.push(`| ${rel.id} | #${rel.issue_id} | ${type} | #${rel.issue_to_id} | ${delay} |`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
lines.push(`| ${rel.id} | #${rel.issue_id} | ${type} | #${rel.issue_to_id} |`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatDateShort, truncate } from "./utils.js";
|
|
1
2
|
/**
|
|
2
3
|
* Get icon for search result type
|
|
3
4
|
*/
|
|
@@ -13,21 +14,6 @@ function getTypeIcon(type) {
|
|
|
13
14
|
return "\u{1F4CB}"; // clipboard
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Format a date string to YYYY-MM-DD format
|
|
18
|
-
*/
|
|
19
|
-
function formatDateShort(isoDate) {
|
|
20
|
-
return new Date(isoDate).toISOString().slice(0, 10);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Truncate text to a maximum length with ellipsis
|
|
24
|
-
*/
|
|
25
|
-
function truncate(text, maxLength) {
|
|
26
|
-
if (text.length <= maxLength) {
|
|
27
|
-
return text;
|
|
28
|
-
}
|
|
29
|
-
return text.slice(0, maxLength) + "...";
|
|
30
|
-
}
|
|
31
17
|
/**
|
|
32
18
|
* Format a single search result as Markdown
|
|
33
19
|
*/
|
package/dist/formatters/user.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
+
import { formatDate } from "./utils.js";
|
|
1
2
|
const USER_STATUS = {
|
|
2
3
|
1: "Active",
|
|
3
4
|
2: "Registered",
|
|
4
5
|
3: "Locked",
|
|
5
6
|
};
|
|
6
|
-
/**
|
|
7
|
-
* Format a date string to readable format
|
|
8
|
-
*/
|
|
9
|
-
function formatDate(isoDate) {
|
|
10
|
-
return new Date(isoDate).toISOString().slice(0, 16).replace("T", " ");
|
|
11
|
-
}
|
|
12
7
|
/**
|
|
13
8
|
* Format a single user as complete Markdown
|
|
14
9
|
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format an ISO date string to readable local datetime format (YYYY-MM-DD HH:mm)
|
|
3
|
+
*/
|
|
4
|
+
export declare function formatDate(isoDate: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Format an ISO date string to short date format (YYYY-MM-DD)
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatDateShort(isoDate: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Truncate text to a maximum length with ellipsis
|
|
11
|
+
*/
|
|
12
|
+
export declare function truncate(text: string, maxLength: number): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format an ISO date string to readable local datetime format (YYYY-MM-DD HH:mm)
|
|
3
|
+
*/
|
|
4
|
+
export function formatDate(isoDate) {
|
|
5
|
+
const date = new Date(isoDate);
|
|
6
|
+
const year = date.getFullYear();
|
|
7
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
8
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
9
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
10
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
11
|
+
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Format an ISO date string to short date format (YYYY-MM-DD)
|
|
15
|
+
*/
|
|
16
|
+
export function formatDateShort(isoDate) {
|
|
17
|
+
const date = new Date(isoDate);
|
|
18
|
+
const year = date.getFullYear();
|
|
19
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
20
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
21
|
+
return `${year}-${month}-${day}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Truncate text to a maximum length with ellipsis
|
|
25
|
+
*/
|
|
26
|
+
export function truncate(text, maxLength) {
|
|
27
|
+
if (text.length <= maxLength) {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
return text.slice(0, maxLength) + "...";
|
|
31
|
+
}
|
package/dist/formatters/wiki.js
CHANGED
package/dist/redmine/client.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export declare class RedmineClient {
|
|
|
3
3
|
private baseUrl;
|
|
4
4
|
private apiKey;
|
|
5
5
|
constructor(baseUrl: string, apiKey: string);
|
|
6
|
+
/**
|
|
7
|
+
* Get the base URL of the Redmine instance
|
|
8
|
+
*/
|
|
9
|
+
getBaseUrl(): string;
|
|
6
10
|
private request;
|
|
7
11
|
listIssues(params?: {
|
|
8
12
|
project_id?: string | number;
|
package/dist/redmine/client.js
CHANGED
|
@@ -7,6 +7,12 @@ export class RedmineClient {
|
|
|
7
7
|
// Remove trailing slash if present
|
|
8
8
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Get the base URL of the Redmine instance
|
|
12
|
+
*/
|
|
13
|
+
getBaseUrl() {
|
|
14
|
+
return this.baseUrl;
|
|
15
|
+
}
|
|
10
16
|
async request(method, path, body) {
|
|
11
17
|
try {
|
|
12
18
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
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.5.
|
|
6
|
+
version: "0.5.2",
|
|
7
7
|
});
|
|
8
8
|
registerTools(server, redmineClient, toolGroups);
|
|
9
9
|
return server;
|
package/dist/tools/account.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { formatMyAccount } from "../formatters/index.js";
|
|
1
2
|
export function registerAccountTools(server, client) {
|
|
2
3
|
server.registerTool("get_my_account", {
|
|
3
4
|
description: "Get current user account information",
|
|
4
5
|
}, async () => {
|
|
5
6
|
const result = await client.getMyAccount();
|
|
7
|
+
if ("error" in result) {
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
6
12
|
return {
|
|
7
|
-
content: [{ type: "text", text:
|
|
13
|
+
content: [{ type: "text", text: formatMyAccount(result) }],
|
|
8
14
|
};
|
|
9
15
|
});
|
|
10
16
|
}
|
package/dist/tools/agile.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { formatSprint, formatSprintList, formatAgileData } from "../formatters/index.js";
|
|
2
3
|
export function registerAgileTools(server, client) {
|
|
3
4
|
// === SPRINTS ===
|
|
4
5
|
server.registerTool("list_agile_sprints", {
|
|
@@ -8,8 +9,13 @@ export function registerAgileTools(server, client) {
|
|
|
8
9
|
},
|
|
9
10
|
}, async (params) => {
|
|
10
11
|
const result = await client.listAgileSprints(params.project_id);
|
|
12
|
+
if ("error" in result) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
11
17
|
return {
|
|
12
|
-
content: [{ type: "text", text:
|
|
18
|
+
content: [{ type: "text", text: formatSprintList(result) }],
|
|
13
19
|
};
|
|
14
20
|
});
|
|
15
21
|
server.registerTool("get_agile_sprint", {
|
|
@@ -20,8 +26,13 @@ export function registerAgileTools(server, client) {
|
|
|
20
26
|
},
|
|
21
27
|
}, async (params) => {
|
|
22
28
|
const result = await client.getAgileSprint(params.project_id, params.sprint_id);
|
|
29
|
+
if ("error" in result) {
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
23
34
|
return {
|
|
24
|
-
content: [{ type: "text", text:
|
|
35
|
+
content: [{ type: "text", text: formatSprint(result) }],
|
|
25
36
|
};
|
|
26
37
|
});
|
|
27
38
|
server.registerTool("create_agile_sprint", {
|
|
@@ -81,8 +92,13 @@ export function registerAgileTools(server, client) {
|
|
|
81
92
|
},
|
|
82
93
|
}, async (params) => {
|
|
83
94
|
const result = await client.getIssueAgileData(params.issue_id);
|
|
95
|
+
if ("error" in result) {
|
|
96
|
+
return {
|
|
97
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
84
100
|
return {
|
|
85
|
-
content: [{ type: "text", text:
|
|
101
|
+
content: [{ type: "text", text: formatAgileData(result) }],
|
|
86
102
|
};
|
|
87
103
|
});
|
|
88
104
|
server.registerTool("update_issue_agile_data", {
|
package/dist/tools/checklists.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { formatChecklist, formatChecklistList } from "../formatters/index.js";
|
|
2
3
|
export function registerChecklistsTools(server, client) {
|
|
3
4
|
server.registerTool("list_checklist_items", {
|
|
4
5
|
description: "List all checklist items for an issue (requires redmine_checklists plugin)",
|
|
@@ -7,8 +8,13 @@ export function registerChecklistsTools(server, client) {
|
|
|
7
8
|
},
|
|
8
9
|
}, async (params) => {
|
|
9
10
|
const result = await client.listChecklists(params.issue_id);
|
|
11
|
+
if ("error" in result) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
10
16
|
return {
|
|
11
|
-
content: [{ type: "text", text:
|
|
17
|
+
content: [{ type: "text", text: formatChecklistList(result) }],
|
|
12
18
|
};
|
|
13
19
|
});
|
|
14
20
|
server.registerTool("get_checklist_item", {
|
|
@@ -18,8 +24,13 @@ export function registerChecklistsTools(server, client) {
|
|
|
18
24
|
},
|
|
19
25
|
}, async (params) => {
|
|
20
26
|
const result = await client.getChecklist(params.checklist_id);
|
|
27
|
+
if ("error" in result) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
21
32
|
return {
|
|
22
|
-
content: [{ type: "text", text:
|
|
33
|
+
content: [{ type: "text", text: formatChecklist(result) }],
|
|
23
34
|
};
|
|
24
35
|
});
|
|
25
36
|
server.registerTool("create_checklist_item", {
|
package/dist/tools/core.js
CHANGED
|
@@ -38,6 +38,7 @@ export function registerCoreTools(server, client) {
|
|
|
38
38
|
inputSchema: {
|
|
39
39
|
issue_id: z.number().describe("The issue ID"),
|
|
40
40
|
include: z.string().optional().describe("Include: attachments, relations, journals, watchers, children, changesets, allowed_statuses"),
|
|
41
|
+
include_description_diffs: z.boolean().optional().default(false).describe("Include full description diffs in history (can be verbose)"),
|
|
41
42
|
},
|
|
42
43
|
}, async (params) => {
|
|
43
44
|
// Fetch issue and enumerations in parallel
|
|
@@ -76,7 +77,9 @@ export function registerCoreTools(server, client) {
|
|
|
76
77
|
}
|
|
77
78
|
// Format response as Markdown
|
|
78
79
|
return {
|
|
79
|
-
content: [{ type: "text", text: formatIssueResponse(result, lookup
|
|
80
|
+
content: [{ type: "text", text: formatIssueResponse(result, lookup, {
|
|
81
|
+
includeDescriptionDiffs: params.include_description_diffs,
|
|
82
|
+
}) }],
|
|
80
83
|
};
|
|
81
84
|
});
|
|
82
85
|
server.registerTool("create_issue", {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatPriorityList, formatActivityList } from "../formatters/index.js";
|
|
1
|
+
import { formatPriorityList, formatActivityList, formatDocumentCategoryList } from "../formatters/index.js";
|
|
2
2
|
export function registerEnumerationsTools(server, client) {
|
|
3
3
|
server.registerTool("list_issue_priorities", {
|
|
4
4
|
description: "List all issue priorities with their IDs (Low, Normal, High, Urgent, Immediate)",
|
|
@@ -30,8 +30,13 @@ export function registerEnumerationsTools(server, client) {
|
|
|
30
30
|
description: "List all document categories with their IDs",
|
|
31
31
|
}, async () => {
|
|
32
32
|
const result = await client.listDocumentCategories();
|
|
33
|
+
if ("error" in result) {
|
|
34
|
+
return {
|
|
35
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
33
38
|
return {
|
|
34
|
-
content: [{ type: "text", text:
|
|
39
|
+
content: [{ type: "text", text: formatDocumentCategoryList(result) }],
|
|
35
40
|
};
|
|
36
41
|
});
|
|
37
42
|
}
|
package/dist/tools/files.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
|
+
import { formatAttachment, formatFileList } from "../formatters/index.js";
|
|
3
4
|
export function registerFilesTools(server, client) {
|
|
4
5
|
// === ATTACHMENTS ===
|
|
5
6
|
server.registerTool("get_attachment", {
|
|
@@ -9,8 +10,13 @@ export function registerFilesTools(server, client) {
|
|
|
9
10
|
},
|
|
10
11
|
}, async (params) => {
|
|
11
12
|
const result = await client.getAttachment(params.attachment_id);
|
|
13
|
+
if ("error" in result) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
12
18
|
return {
|
|
13
|
-
content: [{ type: "text", text:
|
|
19
|
+
content: [{ type: "text", text: formatAttachment(result) }],
|
|
14
20
|
};
|
|
15
21
|
});
|
|
16
22
|
server.registerTool("delete_attachment", {
|
|
@@ -47,8 +53,13 @@ export function registerFilesTools(server, client) {
|
|
|
47
53
|
},
|
|
48
54
|
}, async (params) => {
|
|
49
55
|
const result = await client.listProjectFiles(params.project_id);
|
|
56
|
+
if ("error" in result) {
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
50
61
|
return {
|
|
51
|
-
content: [{ type: "text", text:
|
|
62
|
+
content: [{ type: "text", text: formatFileList(result) }],
|
|
52
63
|
};
|
|
53
64
|
});
|
|
54
65
|
server.registerTool("upload_project_file", {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { formatMembership, formatMembershipList } from "../formatters/index.js";
|
|
2
3
|
export function registerMembershipsTools(server, client) {
|
|
3
4
|
server.registerTool("list_project_memberships", {
|
|
4
5
|
description: "List all memberships (users and groups) for a project",
|
|
@@ -10,8 +11,13 @@ export function registerMembershipsTools(server, client) {
|
|
|
10
11
|
}, async (params) => {
|
|
11
12
|
const { project_id, ...rest } = params;
|
|
12
13
|
const result = await client.listProjectMemberships(project_id, rest);
|
|
14
|
+
if ("error" in result) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
13
19
|
return {
|
|
14
|
-
content: [{ type: "text", text:
|
|
20
|
+
content: [{ type: "text", text: formatMembershipList(result) }],
|
|
15
21
|
};
|
|
16
22
|
});
|
|
17
23
|
server.registerTool("get_membership", {
|
|
@@ -21,8 +27,13 @@ export function registerMembershipsTools(server, client) {
|
|
|
21
27
|
},
|
|
22
28
|
}, async (params) => {
|
|
23
29
|
const result = await client.getMembership(params.membership_id);
|
|
30
|
+
if ("error" in result) {
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
24
35
|
return {
|
|
25
|
-
content: [{ type: "text", text:
|
|
36
|
+
content: [{ type: "text", text: formatMembership(result) }],
|
|
26
37
|
};
|
|
27
38
|
});
|
|
28
39
|
server.registerTool("create_project_membership", {
|
package/dist/tools/metadata.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatTrackerList, formatStatusList, formatCategoryList } from "../formatters/index.js";
|
|
2
|
+
import { formatTrackerList, formatStatusList, formatCategoryList, formatCategory, formatCustomFieldList, formatQueryList } from "../formatters/index.js";
|
|
3
3
|
export function registerMetadataTools(server, client) {
|
|
4
4
|
// === TRACKERS ===
|
|
5
5
|
server.registerTool("list_trackers", {
|
|
@@ -53,8 +53,13 @@ export function registerMetadataTools(server, client) {
|
|
|
53
53
|
},
|
|
54
54
|
}, async (params) => {
|
|
55
55
|
const result = await client.getIssueCategory(params.category_id);
|
|
56
|
+
if ("error" in result) {
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
56
61
|
return {
|
|
57
|
-
content: [{ type: "text", text:
|
|
62
|
+
content: [{ type: "text", text: formatCategory(result) }],
|
|
58
63
|
};
|
|
59
64
|
});
|
|
60
65
|
server.registerTool("create_issue_category", {
|
|
@@ -102,8 +107,13 @@ export function registerMetadataTools(server, client) {
|
|
|
102
107
|
description: "List all custom field definitions (requires admin privileges)",
|
|
103
108
|
}, async () => {
|
|
104
109
|
const result = await client.listCustomFields();
|
|
110
|
+
if ("error" in result) {
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
105
115
|
return {
|
|
106
|
-
content: [{ type: "text", text:
|
|
116
|
+
content: [{ type: "text", text: formatCustomFieldList(result) }],
|
|
107
117
|
};
|
|
108
118
|
});
|
|
109
119
|
// === QUERIES ===
|
|
@@ -111,8 +121,13 @@ export function registerMetadataTools(server, client) {
|
|
|
111
121
|
description: "List all saved issue queries (public and private)",
|
|
112
122
|
}, async () => {
|
|
113
123
|
const result = await client.listQueries();
|
|
124
|
+
if ("error" in result) {
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
114
129
|
return {
|
|
115
|
-
content: [{ type: "text", text:
|
|
130
|
+
content: [{ type: "text", text: formatQueryList(result) }],
|
|
116
131
|
};
|
|
117
132
|
});
|
|
118
133
|
}
|
package/dist/tools/relations.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatVersion, formatVersionList } from "../formatters/index.js";
|
|
2
|
+
import { formatVersion, formatVersionList, formatRelation, formatRelationList } from "../formatters/index.js";
|
|
3
3
|
export function registerRelationsTools(server, client) {
|
|
4
4
|
// === ISSUE RELATIONS ===
|
|
5
5
|
server.registerTool("list_issue_relations", {
|
|
@@ -9,8 +9,13 @@ export function registerRelationsTools(server, client) {
|
|
|
9
9
|
},
|
|
10
10
|
}, async (params) => {
|
|
11
11
|
const result = await client.listIssueRelations(params.issue_id);
|
|
12
|
+
if ("error" in result) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
12
17
|
return {
|
|
13
|
-
content: [{ type: "text", text:
|
|
18
|
+
content: [{ type: "text", text: formatRelationList(result) }],
|
|
14
19
|
};
|
|
15
20
|
});
|
|
16
21
|
server.registerTool("get_relation", {
|
|
@@ -20,8 +25,13 @@ export function registerRelationsTools(server, client) {
|
|
|
20
25
|
},
|
|
21
26
|
}, async (params) => {
|
|
22
27
|
const result = await client.getRelation(params.relation_id);
|
|
28
|
+
if ("error" in result) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
23
33
|
return {
|
|
24
|
-
content: [{ type: "text", text:
|
|
34
|
+
content: [{ type: "text", text: formatRelation(result) }],
|
|
25
35
|
};
|
|
26
36
|
});
|
|
27
37
|
server.registerTool("create_issue_relation", {
|