@pschroee/redmine-mcp 0.4.4 → 0.5.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.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Format a single time entry as complete Markdown
3
+ */
4
+ export function formatTimeEntry(response) {
5
+ const entry = response.time_entry;
6
+ const lines = [];
7
+ // Header
8
+ lines.push(`# Time Entry #${entry.id}`);
9
+ lines.push("");
10
+ // Metadata table
11
+ lines.push("| Field | Value |");
12
+ lines.push("|-------|-------|");
13
+ lines.push(`| Project | ${entry.project.name} |`);
14
+ if (entry.issue) {
15
+ lines.push(`| Issue | #${entry.issue.id} |`);
16
+ }
17
+ lines.push(`| User | ${entry.user.name} |`);
18
+ lines.push(`| Activity | ${entry.activity.name} |`);
19
+ lines.push(`| Hours | ${entry.hours} |`);
20
+ lines.push(`| Date | ${entry.spent_on} |`);
21
+ if (entry.comments) {
22
+ lines.push(`| Comments | ${entry.comments} |`);
23
+ }
24
+ lines.push("");
25
+ return lines.join("\n");
26
+ }
27
+ /**
28
+ * Format a list of time entries as Markdown
29
+ */
30
+ export function formatTimeEntryList(response) {
31
+ const lines = [];
32
+ lines.push(`# Time Entries (${response.total_count})`);
33
+ lines.push("");
34
+ if (response.time_entries.length === 0) {
35
+ lines.push("No time entries found.");
36
+ return lines.join("\n");
37
+ }
38
+ // Table header
39
+ lines.push("| ID | Date | Hours | User | Activity | Issue | Comments |");
40
+ lines.push("|----|------|-------|------|----------|-------|----------|");
41
+ for (const entry of response.time_entries) {
42
+ const issue = entry.issue ? `#${entry.issue.id}` : "-";
43
+ const comments = entry.comments || "-";
44
+ lines.push(`| ${entry.id} | ${entry.spent_on} | ${entry.hours} | ${entry.user.name} | ${entry.activity.name} | ${issue} | ${comments} |`);
45
+ }
46
+ return lines.join("\n");
47
+ }
@@ -0,0 +1,11 @@
1
+ import type { RedmineUser, RedmineUsersResponse } from "../redmine/types.js";
2
+ /**
3
+ * Format a single user as complete Markdown
4
+ */
5
+ export declare function formatUser(response: {
6
+ user: RedmineUser;
7
+ }): string;
8
+ /**
9
+ * Format a list of users as Markdown table
10
+ */
11
+ export declare function formatUserList(response: RedmineUsersResponse): string;
@@ -0,0 +1,88 @@
1
+ const USER_STATUS = {
2
+ 1: "Active",
3
+ 2: "Registered",
4
+ 3: "Locked",
5
+ };
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
+ /**
13
+ * Format a single user as complete Markdown
14
+ */
15
+ export function formatUser(response) {
16
+ const user = response.user;
17
+ const lines = [];
18
+ // Header
19
+ lines.push(`# ${user.firstname} ${user.lastname}`);
20
+ lines.push("");
21
+ // Status line
22
+ const statusParts = [];
23
+ statusParts.push(`**Login:** ${user.login}`);
24
+ const status = user.status !== undefined ? USER_STATUS[user.status] || "Unknown" : "Unknown";
25
+ statusParts.push(`**Status:** ${status}`);
26
+ if (user.admin) {
27
+ statusParts.push(`**Role:** Admin`);
28
+ }
29
+ lines.push(statusParts.join(" | "));
30
+ lines.push("");
31
+ // Metadata table
32
+ lines.push("| Field | Value |");
33
+ lines.push("|-------|-------|");
34
+ if (user.mail) {
35
+ lines.push(`| Email | ${user.mail} |`);
36
+ }
37
+ lines.push(`| Created | ${formatDate(user.created_on)} |`);
38
+ if (user.last_login_on) {
39
+ lines.push(`| Last Login | ${formatDate(user.last_login_on)} |`);
40
+ }
41
+ lines.push("");
42
+ // Groups
43
+ if (user.groups && user.groups.length > 0) {
44
+ lines.push("## Groups");
45
+ lines.push("");
46
+ for (const group of user.groups) {
47
+ lines.push(`- ${group.name}`);
48
+ }
49
+ lines.push("");
50
+ }
51
+ // Project Memberships
52
+ if (user.memberships && user.memberships.length > 0) {
53
+ lines.push("## Project Memberships");
54
+ lines.push("");
55
+ for (const membership of user.memberships) {
56
+ const roles = membership.roles.map((r) => r.name).join(", ");
57
+ lines.push(`- **${membership.project.name}:** ${roles}`);
58
+ }
59
+ lines.push("");
60
+ }
61
+ return lines.join("\n").trimEnd();
62
+ }
63
+ /**
64
+ * Format a list of users as Markdown table
65
+ */
66
+ export function formatUserList(response) {
67
+ const lines = [];
68
+ // Header
69
+ lines.push(`# Users (${response.total_count})`);
70
+ lines.push("");
71
+ // Empty case
72
+ if (response.users.length === 0) {
73
+ lines.push("No users found.");
74
+ return lines.join("\n");
75
+ }
76
+ // Table header
77
+ lines.push("| Name | Login | Email | Status | Role |");
78
+ lines.push("|------|-------|-------|--------|------|");
79
+ // Table rows
80
+ for (const user of response.users) {
81
+ const name = `${user.firstname} ${user.lastname}`;
82
+ const email = user.mail || "";
83
+ const status = user.status !== undefined ? USER_STATUS[user.status] || "Unknown" : "Unknown";
84
+ const role = user.admin ? "Admin" : "User";
85
+ lines.push(`| ${name} | ${user.login} | ${email} | ${status} | ${role} |`);
86
+ }
87
+ return lines.join("\n");
88
+ }
@@ -0,0 +1,11 @@
1
+ import type { RedmineVersion, RedmineVersionsResponse } from "../redmine/types.js";
2
+ /**
3
+ * Format a single version as complete Markdown
4
+ */
5
+ export declare function formatVersion(response: {
6
+ version: RedmineVersion;
7
+ }): string;
8
+ /**
9
+ * Format a list of versions as Markdown table
10
+ */
11
+ export declare function formatVersionList(response: RedmineVersionsResponse): string;
@@ -0,0 +1,75 @@
1
+ const STATUS_BADGE = {
2
+ open: "Open",
3
+ locked: "Locked",
4
+ closed: "Closed",
5
+ };
6
+ /**
7
+ * Format a status badge
8
+ */
9
+ function formatStatusBadge(status) {
10
+ return STATUS_BADGE[status] || status;
11
+ }
12
+ /**
13
+ * Calculate progress percentage from estimated/spent hours
14
+ */
15
+ function formatProgress(estimated, spent) {
16
+ if (!estimated || estimated === 0) {
17
+ return spent ? `${spent}h spent` : "-";
18
+ }
19
+ const percentage = Math.round((spent || 0) / estimated * 100);
20
+ return `${percentage}%`;
21
+ }
22
+ /**
23
+ * Format a single version as complete Markdown
24
+ */
25
+ export function formatVersion(response) {
26
+ const version = response.version;
27
+ const lines = [];
28
+ // Title with status badge
29
+ lines.push(`# ${version.name} [${formatStatusBadge(version.status)}]`);
30
+ lines.push("");
31
+ // Metadata table
32
+ lines.push("| Field | Value |");
33
+ lines.push("|-------|-------|");
34
+ lines.push(`| Project | ${version.project.name} |`);
35
+ if (version.due_date) {
36
+ lines.push(`| Due Date | ${version.due_date} |`);
37
+ }
38
+ lines.push(`| Status | ${formatStatusBadge(version.status)} |`);
39
+ if (version.estimated_hours !== undefined || version.spent_hours !== undefined) {
40
+ const est = version.estimated_hours !== undefined ? `${version.estimated_hours}h` : "-";
41
+ const spent = version.spent_hours !== undefined ? `${version.spent_hours}h` : "-";
42
+ lines.push(`| Est/Spent Hours | ${est} / ${spent} |`);
43
+ }
44
+ if (version.description) {
45
+ lines.push(`| Description | ${version.description} |`);
46
+ }
47
+ if (version.wiki_page_title) {
48
+ lines.push(`| Wiki Page | ${version.wiki_page_title} |`);
49
+ }
50
+ lines.push(`| Sharing | ${version.sharing} |`);
51
+ lines.push("");
52
+ return lines.join("\n");
53
+ }
54
+ /**
55
+ * Format a list of versions as Markdown table
56
+ */
57
+ export function formatVersionList(response) {
58
+ const lines = [];
59
+ lines.push(`# Versions (${response.total_count})`);
60
+ lines.push("");
61
+ if (response.versions.length === 0) {
62
+ lines.push("No versions found.");
63
+ return lines.join("\n");
64
+ }
65
+ // Table header
66
+ lines.push("| Name | Status | Due Date | Progress |");
67
+ lines.push("|------|--------|----------|----------|");
68
+ for (const version of response.versions) {
69
+ const status = formatStatusBadge(version.status);
70
+ const dueDate = version.due_date || "-";
71
+ const progress = formatProgress(version.estimated_hours, version.spent_hours);
72
+ lines.push(`| ${version.name} | ${status} | ${dueDate} | ${progress} |`);
73
+ }
74
+ return lines.join("\n");
75
+ }
@@ -0,0 +1,11 @@
1
+ import type { RedmineWikiPage, RedmineWikiPagesResponse } from "../redmine/types.js";
2
+ /**
3
+ * Format a single wiki page as Markdown
4
+ */
5
+ export declare function formatWikiPage(response: {
6
+ wiki_page: RedmineWikiPage;
7
+ }): string;
8
+ /**
9
+ * Format wiki pages list as Markdown
10
+ */
11
+ export declare function formatWikiPageList(response: RedmineWikiPagesResponse): string;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Format a date string to readable format (YYYY-MM-DD)
3
+ */
4
+ function formatDate(isoDate) {
5
+ return new Date(isoDate).toISOString().slice(0, 10);
6
+ }
7
+ /**
8
+ * Format a single wiki page as Markdown
9
+ */
10
+ export function formatWikiPage(response) {
11
+ const page = response.wiki_page;
12
+ const lines = [];
13
+ // Title
14
+ lines.push(`# ${page.title}`);
15
+ lines.push("");
16
+ // Metadata line
17
+ const metaParts = [];
18
+ metaParts.push(`**Version:** ${page.version}`);
19
+ metaParts.push(`**Author:** ${page.author.name}`);
20
+ metaParts.push(`**Updated:** ${formatDate(page.updated_on)}`);
21
+ if (page.parent) {
22
+ metaParts.push(`**Parent:** ${page.parent.title}`);
23
+ }
24
+ lines.push(metaParts.join(" | "));
25
+ lines.push("");
26
+ // Separator
27
+ lines.push("---");
28
+ lines.push("");
29
+ // Content
30
+ lines.push(page.text);
31
+ lines.push("");
32
+ // Attachments
33
+ if (page.attachments && page.attachments.length > 0) {
34
+ lines.push("## Attachments");
35
+ lines.push("");
36
+ for (const att of page.attachments) {
37
+ lines.push(`- [${att.filename}](${att.content_url}) (${att.filesize} bytes)`);
38
+ }
39
+ lines.push("");
40
+ }
41
+ return lines.join("\n");
42
+ }
43
+ /**
44
+ * Build a tree structure from flat wiki page list
45
+ */
46
+ function buildWikiTree(pages) {
47
+ const tree = new Map();
48
+ for (const page of pages) {
49
+ const parentTitle = page.parent?.title ?? null;
50
+ if (!tree.has(parentTitle)) {
51
+ tree.set(parentTitle, []);
52
+ }
53
+ tree.get(parentTitle).push(page);
54
+ }
55
+ return tree;
56
+ }
57
+ /**
58
+ * Recursively format wiki pages as tree
59
+ */
60
+ function formatWikiTree(tree, parentTitle, indent) {
61
+ const lines = [];
62
+ const children = tree.get(parentTitle) || [];
63
+ for (const page of children) {
64
+ const prefix = " ".repeat(indent);
65
+ lines.push(`${prefix}- **${page.title}** (v${page.version})`);
66
+ // Recursively add children
67
+ const childLines = formatWikiTree(tree, page.title, indent + 1);
68
+ lines.push(...childLines);
69
+ }
70
+ return lines;
71
+ }
72
+ /**
73
+ * Format wiki pages list as Markdown
74
+ */
75
+ export function formatWikiPageList(response) {
76
+ const pages = response.wiki_pages;
77
+ const lines = [];
78
+ // Header
79
+ lines.push(`# Wiki Pages (${pages.length})`);
80
+ lines.push("");
81
+ // Empty case
82
+ if (pages.length === 0) {
83
+ lines.push("No wiki pages found.");
84
+ return lines.join("\n");
85
+ }
86
+ // Build tree and format
87
+ const tree = buildWikiTree(pages);
88
+ const treeLines = formatWikiTree(tree, null, 0);
89
+ lines.push(...treeLines);
90
+ return lines.join("\n");
91
+ }
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.4",
6
+ version: "0.5.0",
7
7
  });
8
8
  registerTools(server, redmineClient, toolGroups);
9
9
  return server;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { formatUser, formatUserList, formatGroup, formatGroupList } from "../formatters/index.js";
2
3
  export function registerAdminTools(server, client) {
3
4
  // === USERS ===
4
5
  server.registerTool("list_users", {
@@ -12,8 +13,13 @@ export function registerAdminTools(server, client) {
12
13
  },
13
14
  }, async (params) => {
14
15
  const result = await client.listUsers(params);
16
+ if ("error" in result) {
17
+ return {
18
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
19
+ };
20
+ }
15
21
  return {
16
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
22
+ content: [{ type: "text", text: formatUserList(result) }],
17
23
  };
18
24
  });
19
25
  server.registerTool("get_user", {
@@ -24,8 +30,13 @@ export function registerAdminTools(server, client) {
24
30
  },
25
31
  }, async (params) => {
26
32
  const result = await client.getUser(params.user_id, params.include);
33
+ if ("error" in result) {
34
+ return {
35
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
36
+ };
37
+ }
27
38
  return {
28
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
39
+ content: [{ type: "text", text: formatUser(result) }],
29
40
  };
30
41
  });
31
42
  server.registerTool("create_user", {
@@ -84,8 +95,13 @@ export function registerAdminTools(server, client) {
84
95
  description: "List all groups (admin only)",
85
96
  }, async () => {
86
97
  const result = await client.listGroups();
98
+ if ("error" in result) {
99
+ return {
100
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
101
+ };
102
+ }
87
103
  return {
88
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
104
+ content: [{ type: "text", text: formatGroupList(result) }],
89
105
  };
90
106
  });
91
107
  server.registerTool("get_group", {
@@ -96,8 +112,13 @@ export function registerAdminTools(server, client) {
96
112
  },
97
113
  }, async (params) => {
98
114
  const result = await client.getGroup(params.group_id, params.include);
115
+ if ("error" in result) {
116
+ return {
117
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
118
+ };
119
+ }
99
120
  return {
100
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
121
+ content: [{ type: "text", text: formatGroup(result) }],
101
122
  };
102
123
  });
103
124
  server.registerTool("create_group", {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { formatIssueResponse } from "../formatters/index.js";
2
+ import { formatIssueResponse, formatIssueList, formatProject, formatProjectList } from "../formatters/index.js";
3
3
  export function registerCoreTools(server, client) {
4
4
  // === ISSUES ===
5
5
  server.registerTool("list_issues", {
@@ -24,8 +24,13 @@ export function registerCoreTools(server, client) {
24
24
  },
25
25
  }, async (params) => {
26
26
  const result = await client.listIssues(params);
27
+ if ("error" in result) {
28
+ return {
29
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
30
+ };
31
+ }
27
32
  return {
28
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
33
+ content: [{ type: "text", text: formatIssueList(result) }],
29
34
  };
30
35
  });
31
36
  server.registerTool("get_issue", {
@@ -181,8 +186,13 @@ export function registerCoreTools(server, client) {
181
186
  },
182
187
  }, async (params) => {
183
188
  const result = await client.listProjects(params);
189
+ if ("error" in result) {
190
+ return {
191
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
192
+ };
193
+ }
184
194
  return {
185
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
195
+ content: [{ type: "text", text: formatProjectList(result) }],
186
196
  };
187
197
  });
188
198
  server.registerTool("get_project", {
@@ -193,8 +203,13 @@ export function registerCoreTools(server, client) {
193
203
  },
194
204
  }, async (params) => {
195
205
  const result = await client.getProject(params.project_id, params.include);
206
+ if ("error" in result) {
207
+ return {
208
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
209
+ };
210
+ }
196
211
  return {
197
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
212
+ content: [{ type: "text", text: formatProject(result) }],
198
213
  };
199
214
  });
200
215
  server.registerTool("create_project", {
@@ -1,18 +1,29 @@
1
+ import { formatPriorityList, formatActivityList } from "../formatters/index.js";
1
2
  export function registerEnumerationsTools(server, client) {
2
3
  server.registerTool("list_issue_priorities", {
3
4
  description: "List all issue priorities with their IDs (Low, Normal, High, Urgent, Immediate)",
4
5
  }, async () => {
5
6
  const result = await client.listIssuePriorities();
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: JSON.stringify(result, null, 2) }],
13
+ content: [{ type: "text", text: formatPriorityList(result) }],
8
14
  };
9
15
  });
10
16
  server.registerTool("list_time_entry_activities", {
11
17
  description: "List all time entry activities with their IDs (Design, Development, etc.)",
12
18
  }, async () => {
13
19
  const result = await client.listTimeEntryActivities();
20
+ if ("error" in result) {
21
+ return {
22
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
23
+ };
24
+ }
14
25
  return {
15
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
26
+ content: [{ type: "text", text: formatActivityList(result) }],
16
27
  };
17
28
  });
18
29
  server.registerTool("list_document_categories", {
@@ -1,12 +1,18 @@
1
1
  import { z } from "zod";
2
+ import { formatTrackerList, formatStatusList, formatCategoryList } from "../formatters/index.js";
2
3
  export function registerMetadataTools(server, client) {
3
4
  // === TRACKERS ===
4
5
  server.registerTool("list_trackers", {
5
6
  description: "List all available trackers (issue types like Bug, Feature, etc.)",
6
7
  }, async () => {
7
8
  const result = await client.listTrackers();
9
+ if ("error" in result) {
10
+ return {
11
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
12
+ };
13
+ }
8
14
  return {
9
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
15
+ content: [{ type: "text", text: formatTrackerList(result) }],
10
16
  };
11
17
  });
12
18
  // === ISSUE STATUSES ===
@@ -14,8 +20,13 @@ export function registerMetadataTools(server, client) {
14
20
  description: "List all available issue statuses (New, In Progress, Closed, etc.)",
15
21
  }, async () => {
16
22
  const result = await client.listIssueStatuses();
23
+ if ("error" in result) {
24
+ return {
25
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
26
+ };
27
+ }
17
28
  return {
18
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
29
+ content: [{ type: "text", text: formatStatusList(result) }],
19
30
  };
20
31
  });
21
32
  // === ISSUE CATEGORIES ===
@@ -26,8 +37,13 @@ export function registerMetadataTools(server, client) {
26
37
  },
27
38
  }, async (params) => {
28
39
  const result = await client.listIssueCategories(params.project_id);
40
+ if ("error" in result) {
41
+ return {
42
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
43
+ };
44
+ }
29
45
  return {
30
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
46
+ content: [{ type: "text", text: formatCategoryList(result) }],
31
47
  };
32
48
  });
33
49
  server.registerTool("get_issue_category", {
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { formatVersion, formatVersionList } from "../formatters/index.js";
2
3
  export function registerRelationsTools(server, client) {
3
4
  // === ISSUE RELATIONS ===
4
5
  server.registerTool("list_issue_relations", {
@@ -76,8 +77,13 @@ export function registerRelationsTools(server, client) {
76
77
  },
77
78
  }, async (params) => {
78
79
  const result = await client.listVersions(params.project_id);
80
+ if ("error" in result) {
81
+ return {
82
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
83
+ };
84
+ }
79
85
  return {
80
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
86
+ content: [{ type: "text", text: formatVersionList(result) }],
81
87
  };
82
88
  });
83
89
  server.registerTool("get_version", {
@@ -87,8 +93,13 @@ export function registerRelationsTools(server, client) {
87
93
  },
88
94
  }, async (params) => {
89
95
  const result = await client.getVersion(params.version_id);
96
+ if ("error" in result) {
97
+ return {
98
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
99
+ };
100
+ }
90
101
  return {
91
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
102
+ content: [{ type: "text", text: formatVersion(result) }],
92
103
  };
93
104
  });
94
105
  server.registerTool("create_version", {
@@ -1,11 +1,17 @@
1
1
  import { z } from "zod";
2
+ import { formatRoleList, formatRole } from "../formatters/index.js";
2
3
  export function registerRolesTools(server, client) {
3
4
  server.registerTool("list_roles", {
4
5
  description: "List all available roles (Manager, Developer, Reporter, etc.)",
5
6
  }, async () => {
6
7
  const result = await client.listRoles();
8
+ if ("error" in result) {
9
+ return {
10
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
11
+ };
12
+ }
7
13
  return {
8
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
14
+ content: [{ type: "text", text: formatRoleList(result) }],
9
15
  };
10
16
  });
11
17
  server.registerTool("get_role", {
@@ -15,8 +21,13 @@ export function registerRolesTools(server, client) {
15
21
  },
16
22
  }, async (params) => {
17
23
  const result = await client.getRole(params.role_id);
24
+ if ("error" in result) {
25
+ return {
26
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
27
+ };
28
+ }
18
29
  return {
19
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
30
+ content: [{ type: "text", text: formatRole(result) }],
20
31
  };
21
32
  });
22
33
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { formatSearchResults } from "../formatters/index.js";
2
3
  export function registerSearchTools(server, client) {
3
4
  server.registerTool("search", {
4
5
  description: "Search across Redmine (issues, wiki, news, etc.)",
@@ -21,8 +22,13 @@ export function registerSearchTools(server, client) {
21
22
  },
22
23
  }, async (params) => {
23
24
  const result = await client.search(params);
25
+ if ("error" in result) {
26
+ return {
27
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
28
+ };
29
+ }
24
30
  return {
25
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
31
+ content: [{ type: "text", text: formatSearchResults(result) }],
26
32
  };
27
33
  });
28
34
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { formatTimeEntry, formatTimeEntryList } from "../formatters/index.js";
2
3
  export function registerTimeTools(server, client) {
3
4
  server.registerTool("list_time_entries", {
4
5
  description: "List time entries with optional filters",
@@ -13,8 +14,13 @@ export function registerTimeTools(server, client) {
13
14
  },
14
15
  }, async (params) => {
15
16
  const result = await client.listTimeEntries(params);
17
+ if ("error" in result) {
18
+ return {
19
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
20
+ };
21
+ }
16
22
  return {
17
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
23
+ content: [{ type: "text", text: formatTimeEntryList(result) }],
18
24
  };
19
25
  });
20
26
  server.registerTool("get_time_entry", {
@@ -24,8 +30,13 @@ export function registerTimeTools(server, client) {
24
30
  },
25
31
  }, async (params) => {
26
32
  const result = await client.getTimeEntry(params.time_entry_id);
33
+ if ("error" in result) {
34
+ return {
35
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
36
+ };
37
+ }
27
38
  return {
28
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
39
+ content: [{ type: "text", text: formatTimeEntry(result) }],
29
40
  };
30
41
  });
31
42
  server.registerTool("create_time_entry", {