@thelabnyc/redmine-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +227 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +13 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/config.d.ts +5 -0
  7. package/dist/config.js +17 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +4 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/redmine.d.ts +188 -0
  13. package/dist/redmine.js +165 -0
  14. package/dist/redmine.js.map +1 -0
  15. package/dist/server.d.ts +9 -0
  16. package/dist/server.js +27 -0
  17. package/dist/server.js.map +1 -0
  18. package/dist/test-utils.d.ts +107 -0
  19. package/dist/test-utils.js +66 -0
  20. package/dist/test-utils.js.map +1 -0
  21. package/dist/tools/__tests__/get-issue.test.d.ts +1 -0
  22. package/dist/tools/__tests__/get-issue.test.js +204 -0
  23. package/dist/tools/__tests__/get-issue.test.js.map +1 -0
  24. package/dist/tools/__tests__/list-issue-statuses.test.d.ts +1 -0
  25. package/dist/tools/__tests__/list-issue-statuses.test.js +75 -0
  26. package/dist/tools/__tests__/list-issue-statuses.test.js.map +1 -0
  27. package/dist/tools/__tests__/list-project-members.test.d.ts +1 -0
  28. package/dist/tools/__tests__/list-project-members.test.js +109 -0
  29. package/dist/tools/__tests__/list-project-members.test.js.map +1 -0
  30. package/dist/tools/__tests__/update-issue.test.d.ts +1 -0
  31. package/dist/tools/__tests__/update-issue.test.js +455 -0
  32. package/dist/tools/__tests__/update-issue.test.js.map +1 -0
  33. package/dist/tools/__tests__/whoami.test.d.ts +1 -0
  34. package/dist/tools/__tests__/whoami.test.js +122 -0
  35. package/dist/tools/__tests__/whoami.test.js.map +1 -0
  36. package/dist/tools/get-issue.d.ts +3 -0
  37. package/dist/tools/get-issue.js +71 -0
  38. package/dist/tools/get-issue.js.map +1 -0
  39. package/dist/tools/index.d.ts +9 -0
  40. package/dist/tools/index.js +14 -0
  41. package/dist/tools/index.js.map +1 -0
  42. package/dist/tools/list-issue-statuses.d.ts +3 -0
  43. package/dist/tools/list-issue-statuses.js +32 -0
  44. package/dist/tools/list-issue-statuses.js.map +1 -0
  45. package/dist/tools/list-project-members.d.ts +3 -0
  46. package/dist/tools/list-project-members.js +48 -0
  47. package/dist/tools/list-project-members.js.map +1 -0
  48. package/dist/tools/update-issue.d.ts +3 -0
  49. package/dist/tools/update-issue.js +192 -0
  50. package/dist/tools/update-issue.js.map +1 -0
  51. package/dist/tools/utils.d.ts +12 -0
  52. package/dist/tools/utils.js +14 -0
  53. package/dist/tools/utils.js.map +1 -0
  54. package/dist/tools/whoami.d.ts +3 -0
  55. package/dist/tools/whoami.js +32 -0
  56. package/dist/tools/whoami.js.map +1 -0
  57. package/package.json +41 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2025 - 2025 thelab
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # @thelabnyc/redmine-mcp
2
+
3
+ An MCP (Model Context Protocol) server that allows AI agents like Claude to interact with Redmine project management data.
4
+
5
+ ## Features
6
+
7
+ - Fetch issue details by ID, including subject, description, status, priority, and assignee
8
+ - Update issues: change status, assign users, add notes, and more
9
+ - Log time spent on issues with integrated time tracking
10
+ - List project members to find user IDs for assignments
11
+ - List available issue statuses to find valid status IDs
12
+ - Retrieve change history (journals) with every request
13
+ - Optionally include attachments, watchers, relations, and child issues
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @thelabnyc/redmine-mcp
19
+ ```
20
+
21
+ Or clone and build from source:
22
+
23
+ ```bash
24
+ git clone https://gitlab.com/thelabnyc/redmine-mcp.git
25
+ cd redmine-mcp
26
+ npm install
27
+ npm run build
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ The server requires two environment variables:
33
+
34
+ | Variable | Description | Example |
35
+ | ----------------- | --------------------------------- | --------------------------- |
36
+ | `REDMINE_URL` | Base URL of your Redmine instance | `https://mycompany.plan.io` |
37
+ | `REDMINE_API_KEY` | Your Redmine API key | `abc123def456...` |
38
+
39
+ ### Getting your Redmine API Key
40
+
41
+ 1. Log into your Redmine instance
42
+ 2. Go to **My Account** (click your name in the top right)
43
+ 3. In the right sidebar, find **API access key**
44
+ 4. Click **Show** to reveal your key, or **Reset** to generate a new one
45
+
46
+ ## Usage with Claude Code
47
+
48
+ Add the server to your Claude Code configuration:
49
+
50
+ ### Project-level configuration
51
+
52
+ Create or edit `.claude/settings.json` in your project:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "redmine": {
58
+ "command": "npx",
59
+ "args": ["@thelabnyc/redmine-mcp"],
60
+ "env": {
61
+ "REDMINE_URL": "https://your-instance.plan.io",
62
+ "REDMINE_API_KEY": "your-api-key-here"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### User-level configuration
70
+
71
+ Add to `~/.claude/settings.json` to make available across all projects:
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "redmine": {
77
+ "command": "npx",
78
+ "args": ["@thelabnyc/redmine-mcp"],
79
+ "env": {
80
+ "REDMINE_URL": "https://your-instance.plan.io",
81
+ "REDMINE_API_KEY": "your-api-key-here"
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### Using a local build
89
+
90
+ If you've cloned the repository:
91
+
92
+ ```json
93
+ {
94
+ "mcpServers": {
95
+ "redmine": {
96
+ "command": "node",
97
+ "args": ["/path/to/redmine-mcp/dist/cli.js"],
98
+ "env": {
99
+ "REDMINE_URL": "https://your-instance.plan.io",
100
+ "REDMINE_API_KEY": "your-api-key-here"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## Available Tools
108
+
109
+ ### get-issue
110
+
111
+ Fetch details about a Redmine issue by ID.
112
+
113
+ **Parameters:**
114
+
115
+ | Parameter | Type | Required | Description |
116
+ | -------------------- | ------- | -------- | ------------------------------------ |
117
+ | `issueId` | string | Yes | Issue ID (e.g., `#12345` or `12345`) |
118
+ | `includeAttachments` | boolean | No | Include file attachments |
119
+ | `includeWatchers` | boolean | No | Include watchers list |
120
+ | `includeRelations` | boolean | No | Include related issues |
121
+ | `includeChildren` | boolean | No | Include child issues |
122
+
123
+ **Note:** Change history (journals) is always included by default.
124
+
125
+ **Example usage in Claude:**
126
+
127
+ > "Look up Redmine issue #12345 and summarize the recent activity"
128
+
129
+ Or:
130
+
131
+ > "What's the status of issue 6789? Include any attachments."
132
+
133
+ ### update-issue
134
+
135
+ Update a Redmine issue. Can change fields, add notes, and log time spent.
136
+
137
+ **Parameters:**
138
+
139
+ | Parameter | Type | Required | Description |
140
+ | ---------------- | ------- | -------- | ---------------------------------------------------- |
141
+ | `issueId` | string | Yes | Issue ID (e.g., `#12345` or `12345`) |
142
+ | `subject` | string | No | New issue subject/title |
143
+ | `description` | string | No | New issue description |
144
+ | `statusId` | number | No | Status ID to set |
145
+ | `priorityId` | number | No | Priority ID to set |
146
+ | `assignedToId` | number | No | User ID to assign (use 0 to unassign) |
147
+ | `trackerId` | number | No | Tracker ID to set |
148
+ | `parentIssueId` | number | No | Parent issue ID |
149
+ | `startDate` | string | No | Start date (YYYY-MM-DD format) |
150
+ | `dueDate` | string | No | Due date (YYYY-MM-DD format) |
151
+ | `doneRatio` | number | No | Percent done (0-100) |
152
+ | `estimatedHours` | number | No | Estimated hours for the issue |
153
+ | `notes` | string | No | Comment/note to add to the issue journal |
154
+ | `privateNotes` | boolean | No | Make the notes private |
155
+ | `logHours` | number | No | Hours to log as a time entry |
156
+ | `logActivityId` | number | No | Activity ID for time entry (uses default if omitted) |
157
+ | `logComments` | string | No | Comments for the time entry |
158
+ | `logSpentOn` | string | No | Date for time entry (YYYY-MM-DD, defaults to today) |
159
+
160
+ **Example usage in Claude:**
161
+
162
+ > "Update issue #12345 to status 2 and assign to user 5"
163
+
164
+ Or:
165
+
166
+ > "Add a note to issue #6789 saying 'Fixed the bug' and log 1.5 hours"
167
+
168
+ Or:
169
+
170
+ > "Mark issue #12345 as 75% done and log 2 hours of development time"
171
+
172
+ ### list-project-members
173
+
174
+ List all members of a Redmine project. Use this to find user IDs for assigning issues.
175
+
176
+ **Parameters:**
177
+
178
+ | Parameter | Type | Required | Description |
179
+ | ----------- | ------ | -------- | ---------------------------------------------------- |
180
+ | `projectId` | string | Yes | Project ID or identifier (e.g., `my-project` or `1`) |
181
+ | `limit` | number | No | Maximum number of members to return (default 25) |
182
+ | `offset` | number | No | Number of members to skip for pagination |
183
+
184
+ **Example usage in Claude:**
185
+
186
+ > "List all members of the 'my-project' project"
187
+
188
+ Or:
189
+
190
+ > "Who can I assign issues to in project #1?"
191
+
192
+ ### list-issue-statuses
193
+
194
+ List all available issue statuses. Use this to find valid status IDs when updating issues.
195
+
196
+ **Parameters:** None
197
+
198
+ **Example usage in Claude:**
199
+
200
+ > "What statuses can I set for issues?"
201
+
202
+ Or:
203
+
204
+ > "List the available issue statuses so I can update issue #123"
205
+
206
+ ## Development
207
+
208
+ ```bash
209
+ # Install dependencies
210
+ npm install
211
+
212
+ # Run tests
213
+ npm test
214
+
215
+ # Run tests in watch mode
216
+ npm run test:watch
217
+
218
+ # Build
219
+ npm run build
220
+
221
+ # Lint
222
+ npm run lint
223
+ ```
224
+
225
+ ## License
226
+
227
+ ISC
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { startServer } from "./server.js";
3
+ async function main() {
4
+ try {
5
+ await startServer();
6
+ }
7
+ catch (error) {
8
+ console.error("Failed to start Redmine MCP server:", error);
9
+ process.exit(1);
10
+ }
11
+ }
12
+ void main();
13
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,KAAK,UAAU,IAAI;IACf,IAAI,CAAC;QACD,MAAM,WAAW,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,KAAK,CAAC,CAAC;QAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface Config {
2
+ redmineUrl: string;
3
+ redmineApiKey: string;
4
+ }
5
+ export declare function getConfig(): Config;
package/dist/config.js ADDED
@@ -0,0 +1,17 @@
1
+ export function getConfig() {
2
+ const redmineUrl = process.env.REDMINE_URL;
3
+ const redmineApiKey = process.env.REDMINE_API_KEY;
4
+ if (!redmineUrl) {
5
+ throw new Error("REDMINE_URL environment variable is required");
6
+ }
7
+ if (!redmineApiKey) {
8
+ throw new Error("REDMINE_API_KEY environment variable is required");
9
+ }
10
+ // Remove trailing slash if present
11
+ const normalizedUrl = redmineUrl.replace(/\/$/, "");
12
+ return {
13
+ redmineUrl: normalizedUrl,
14
+ redmineApiKey,
15
+ };
16
+ }
17
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,SAAS;IACrB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAC3C,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAElD,IAAI,CAAC,UAAU,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,aAAa,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACxE,CAAC;IAED,mCAAmC;IACnC,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEpD,OAAO;QACH,UAAU,EAAE,aAAa;QACzB,aAAa;KAChB,CAAC;AACN,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { createServer, startServer } from "./server.js";
2
+ export { getConfig } from "./config.js";
3
+ export type { Config } from "./config.js";
4
+ export { RedmineClient } from "./redmine.js";
5
+ export type { RedmineIssue, RedmineJournal, RedmineAttachment, RedmineUser, RedmineProject, RedmineTracker, RedmineStatus, RedminePriority, GetIssueOptions, UpdateIssueData, CreateTimeEntryData, RedmineTimeEntry, RedmineActivity, RedmineMembership, ListProjectMembersOptions, RedmineIssueStatusDetail, RedmineCurrentUser, RedmineCustomField, RedmineJournalDetail, RedmineWatcher, RedmineRelation, } from "./redmine.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createServer, startServer } from "./server.js";
2
+ export { getConfig } from "./config.js";
3
+ export { RedmineClient } from "./redmine.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,188 @@
1
+ import type { Config } from "./config.js";
2
+ export interface RedmineUser {
3
+ id: number;
4
+ name: string;
5
+ }
6
+ export interface RedmineCustomField {
7
+ id: number;
8
+ name: string;
9
+ value: string | string[];
10
+ }
11
+ export interface RedmineCurrentUser {
12
+ id: number;
13
+ login: string;
14
+ firstname: string;
15
+ lastname: string;
16
+ mail: string;
17
+ created_on: string;
18
+ updated_on?: string;
19
+ last_login_on?: string;
20
+ passwd_changed_on?: string;
21
+ avatar_url?: string;
22
+ status: number;
23
+ custom_fields?: RedmineCustomField[];
24
+ }
25
+ export interface RedmineProject {
26
+ id: number;
27
+ name: string;
28
+ }
29
+ export interface RedmineTracker {
30
+ id: number;
31
+ name: string;
32
+ }
33
+ export interface RedmineStatus {
34
+ id: number;
35
+ name: string;
36
+ }
37
+ export interface RedminePriority {
38
+ id: number;
39
+ name: string;
40
+ }
41
+ export interface RedmineIssueStatusDetail {
42
+ id: number;
43
+ name: string;
44
+ is_closed: boolean;
45
+ }
46
+ export interface RedmineJournalDetail {
47
+ property: string;
48
+ name: string;
49
+ old_value?: string;
50
+ new_value?: string;
51
+ }
52
+ export interface RedmineJournal {
53
+ id: number;
54
+ user: RedmineUser;
55
+ notes: string;
56
+ created_on: string;
57
+ details: RedmineJournalDetail[];
58
+ }
59
+ export interface RedmineAttachment {
60
+ id: number;
61
+ filename: string;
62
+ filesize: number;
63
+ content_type: string;
64
+ description?: string;
65
+ author: RedmineUser;
66
+ created_on: string;
67
+ }
68
+ export interface RedmineWatcher {
69
+ id: number;
70
+ name: string;
71
+ }
72
+ export interface RedmineRelation {
73
+ id: number;
74
+ issue_id: number;
75
+ issue_to_id: number;
76
+ relation_type: string;
77
+ delay?: number;
78
+ }
79
+ export interface RedmineIssue {
80
+ id: number;
81
+ project: RedmineProject;
82
+ tracker: RedmineTracker;
83
+ status: RedmineStatus;
84
+ priority: RedminePriority;
85
+ author: RedmineUser;
86
+ assigned_to?: RedmineUser;
87
+ subject: string;
88
+ description?: string;
89
+ start_date?: string;
90
+ due_date?: string;
91
+ done_ratio: number;
92
+ estimated_hours?: number;
93
+ created_on: string;
94
+ updated_on: string;
95
+ journals?: RedmineJournal[];
96
+ attachments?: RedmineAttachment[];
97
+ watchers?: RedmineWatcher[];
98
+ relations?: RedmineRelation[];
99
+ children?: RedmineIssue[];
100
+ }
101
+ export interface GetIssueOptions {
102
+ includeAttachments?: boolean;
103
+ includeWatchers?: boolean;
104
+ includeRelations?: boolean;
105
+ includeChildren?: boolean;
106
+ }
107
+ export interface UpdateIssueData {
108
+ subject?: string;
109
+ description?: string;
110
+ status_id?: number;
111
+ priority_id?: number;
112
+ assigned_to_id?: number;
113
+ tracker_id?: number;
114
+ parent_issue_id?: number;
115
+ start_date?: string;
116
+ due_date?: string;
117
+ done_ratio?: number;
118
+ estimated_hours?: number;
119
+ notes?: string;
120
+ private_notes?: boolean;
121
+ }
122
+ export interface CreateTimeEntryData {
123
+ issue_id: number;
124
+ hours: number;
125
+ activity_id?: number;
126
+ spent_on?: string;
127
+ comments?: string;
128
+ }
129
+ export interface RedmineTimeEntry {
130
+ id: number;
131
+ project: RedmineProject;
132
+ issue?: {
133
+ id: number;
134
+ };
135
+ user: RedmineUser;
136
+ activity: {
137
+ id: number;
138
+ name: string;
139
+ };
140
+ hours: number;
141
+ comments?: string;
142
+ spent_on: string;
143
+ created_on: string;
144
+ updated_on: string;
145
+ }
146
+ export interface RedmineActivity {
147
+ id: number;
148
+ name: string;
149
+ is_default: boolean;
150
+ }
151
+ export interface RedmineMembership {
152
+ id: number;
153
+ project: RedmineProject;
154
+ user?: RedmineUser;
155
+ group?: {
156
+ id: number;
157
+ name: string;
158
+ };
159
+ roles: Array<{
160
+ id: number;
161
+ name: string;
162
+ inherited?: boolean;
163
+ }>;
164
+ }
165
+ export interface ListProjectMembersOptions {
166
+ limit?: number;
167
+ offset?: number;
168
+ }
169
+ export declare class RedmineClient {
170
+ private readonly config;
171
+ constructor(config: Config);
172
+ /**
173
+ * Extract error details from a failed response body
174
+ */
175
+ private extractErrorDetails;
176
+ getIssue(issueId: number, options?: GetIssueOptions): Promise<RedmineIssue>;
177
+ updateIssue(issueId: number, data: UpdateIssueData): Promise<RedmineIssue>;
178
+ createTimeEntry(data: CreateTimeEntryData): Promise<RedmineTimeEntry>;
179
+ getTimeEntryActivities(): Promise<RedmineActivity[]>;
180
+ listProjectMembers(projectId: string | number, options?: ListProjectMembersOptions): Promise<{
181
+ memberships: RedmineMembership[];
182
+ total_count: number;
183
+ offset: number;
184
+ limit: number;
185
+ }>;
186
+ listIssueStatuses(): Promise<RedmineIssueStatusDetail[]>;
187
+ getCurrentUser(): Promise<RedmineCurrentUser>;
188
+ }
@@ -0,0 +1,165 @@
1
+ export class RedmineClient {
2
+ config;
3
+ constructor(config) {
4
+ this.config = config;
5
+ }
6
+ /**
7
+ * Extract error details from a failed response body
8
+ */
9
+ async extractErrorDetails(response) {
10
+ try {
11
+ const body = (await response.json());
12
+ if (body.errors && Array.isArray(body.errors)) {
13
+ return ` - ${body.errors.join(", ")}`;
14
+ }
15
+ return "";
16
+ }
17
+ catch {
18
+ // Response body is not JSON or couldn't be parsed
19
+ return "";
20
+ }
21
+ }
22
+ async getIssue(issueId, options = {}) {
23
+ // Build include params - journals always included
24
+ const includes = ["journals"];
25
+ if (options.includeAttachments)
26
+ includes.push("attachments");
27
+ if (options.includeWatchers)
28
+ includes.push("watchers");
29
+ if (options.includeRelations)
30
+ includes.push("relations");
31
+ if (options.includeChildren)
32
+ includes.push("children");
33
+ const includeParam = includes.join(",");
34
+ const url = `${this.config.redmineUrl}/issues/${issueId}.json?include=${includeParam}`;
35
+ const response = await fetch(url, {
36
+ method: "GET",
37
+ headers: {
38
+ "X-Redmine-API-Key": this.config.redmineApiKey,
39
+ "Accept": "application/json",
40
+ },
41
+ });
42
+ if (!response.ok) {
43
+ const errorDetails = await this.extractErrorDetails(response);
44
+ throw new Error(`Failed to fetch issue ${issueId}: ${response.status} ${response.statusText}${errorDetails}`);
45
+ }
46
+ const data = (await response.json());
47
+ return data.issue;
48
+ }
49
+ async updateIssue(issueId, data) {
50
+ const url = `${this.config.redmineUrl}/issues/${issueId}.json`;
51
+ const response = await fetch(url, {
52
+ method: "PUT",
53
+ headers: {
54
+ "X-Redmine-API-Key": this.config.redmineApiKey,
55
+ "Accept": "application/json",
56
+ "Content-Type": "application/json",
57
+ },
58
+ body: JSON.stringify({ issue: data }),
59
+ });
60
+ if (!response.ok) {
61
+ const errorDetails = await this.extractErrorDetails(response);
62
+ throw new Error(`Failed to update issue ${issueId}: ${response.status} ${response.statusText}${errorDetails}`);
63
+ }
64
+ // Redmine PUT returns empty body on success, so fetch the updated issue
65
+ return this.getIssue(issueId);
66
+ }
67
+ async createTimeEntry(data) {
68
+ const url = `${this.config.redmineUrl}/time_entries.json`;
69
+ const response = await fetch(url, {
70
+ method: "POST",
71
+ headers: {
72
+ "X-Redmine-API-Key": this.config.redmineApiKey,
73
+ "Accept": "application/json",
74
+ "Content-Type": "application/json",
75
+ },
76
+ body: JSON.stringify({ time_entry: data }),
77
+ });
78
+ if (!response.ok) {
79
+ const errorDetails = await this.extractErrorDetails(response);
80
+ throw new Error(`Failed to create time entry: ${response.status} ${response.statusText}${errorDetails}`);
81
+ }
82
+ const result = (await response.json());
83
+ return result.time_entry;
84
+ }
85
+ async getTimeEntryActivities() {
86
+ const url = `${this.config.redmineUrl}/enumerations/time_entry_activities.json`;
87
+ const response = await fetch(url, {
88
+ method: "GET",
89
+ headers: {
90
+ "X-Redmine-API-Key": this.config.redmineApiKey,
91
+ "Accept": "application/json",
92
+ },
93
+ });
94
+ if (!response.ok) {
95
+ const errorDetails = await this.extractErrorDetails(response);
96
+ throw new Error(`Failed to fetch time entry activities: ${response.status} ${response.statusText}${errorDetails}`);
97
+ }
98
+ const data = (await response.json());
99
+ return data.time_entry_activities;
100
+ }
101
+ async listProjectMembers(projectId, options = {}) {
102
+ const params = new URLSearchParams();
103
+ if (options.limit !== undefined) {
104
+ params.set("limit", String(options.limit));
105
+ }
106
+ if (options.offset !== undefined) {
107
+ params.set("offset", String(options.offset));
108
+ }
109
+ const queryString = params.toString();
110
+ const url = `${this.config.redmineUrl}/projects/${projectId}/memberships.json${queryString ? `?${queryString}` : ""}`;
111
+ const response = await fetch(url, {
112
+ method: "GET",
113
+ headers: {
114
+ "X-Redmine-API-Key": this.config.redmineApiKey,
115
+ "Accept": "application/json",
116
+ },
117
+ });
118
+ if (!response.ok) {
119
+ const errorDetails = await this.extractErrorDetails(response);
120
+ throw new Error(`Failed to fetch project members: ${response.status} ${response.statusText}${errorDetails}`);
121
+ }
122
+ const data = (await response.json());
123
+ return {
124
+ memberships: data.memberships,
125
+ total_count: data.total_count,
126
+ offset: data.offset,
127
+ limit: data.limit,
128
+ };
129
+ }
130
+ async listIssueStatuses() {
131
+ const url = `${this.config.redmineUrl}/issue_statuses.json`;
132
+ const response = await fetch(url, {
133
+ method: "GET",
134
+ headers: {
135
+ "X-Redmine-API-Key": this.config.redmineApiKey,
136
+ "Accept": "application/json",
137
+ },
138
+ });
139
+ if (!response.ok) {
140
+ const errorDetails = await this.extractErrorDetails(response);
141
+ throw new Error(`Failed to fetch issue statuses: ${response.status} ${response.statusText}${errorDetails}`);
142
+ }
143
+ const data = (await response.json());
144
+ return data.issue_statuses;
145
+ }
146
+ async getCurrentUser() {
147
+ const url = `${this.config.redmineUrl}/users/current.json`;
148
+ const response = await fetch(url, {
149
+ method: "GET",
150
+ headers: {
151
+ "X-Redmine-API-Key": this.config.redmineApiKey,
152
+ "Accept": "application/json",
153
+ },
154
+ });
155
+ if (!response.ok) {
156
+ const errorDetails = await this.extractErrorDetails(response);
157
+ throw new Error(`Failed to fetch current user: ${response.status} ${response.statusText}${errorDetails}`);
158
+ }
159
+ const data = (await response.json());
160
+ // Exclude api_key from response - agent doesn't need it
161
+ const { api_key: _, ...user } = data.user;
162
+ return user;
163
+ }
164
+ }
165
+ //# sourceMappingURL=redmine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redmine.js","sourceRoot":"","sources":["../src/redmine.ts"],"names":[],"mappings":"AAiNA,MAAM,OAAO,aAAa;IACL,MAAM,CAAS;IAEhC,YAAY,MAAc;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,mBAAmB,CAAC,QAAkB;QAChD,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA0B,CAAC;YAC9D,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5C,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,CAAC;YACD,OAAO,EAAE,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACL,kDAAkD;YAClD,OAAO,EAAE,CAAC;QACd,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ,CACV,OAAe,EACf,UAA2B,EAAE;QAE7B,kDAAkD;QAClD,MAAM,QAAQ,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,OAAO,CAAC,kBAAkB;YAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC7D,IAAI,OAAO,CAAC,eAAe;YAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvD,IAAI,OAAO,CAAC,gBAAgB;YAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzD,IAAI,OAAO,CAAC,eAAe;YAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAEvD,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,WAAW,OAAO,iBAAiB,YAAY,EAAE,CAAC;QAEvF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACL,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;gBAC9C,QAAQ,EAAE,kBAAkB;aAC/B;SACJ,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACX,yBAAyB,OAAO,KAAK,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,YAAY,EAAE,CAC/F,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAyB,CAAC;QAC7D,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,WAAW,CACb,OAAe,EACf,IAAqB;QAErB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,WAAW,OAAO,OAAO,CAAC;QAE/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACL,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;gBAC9C,QAAQ,EAAE,kBAAkB;gBAC5B,cAAc,EAAE,kBAAkB;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACX,0BAA0B,OAAO,KAAK,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,YAAY,EAAE,CAChG,CAAC;QACN,CAAC;QAED,wEAAwE;QACxE,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,eAAe,CACjB,IAAyB;QAEzB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,oBAAoB,CAAC;QAE1D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACL,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;gBAC9C,QAAQ,EAAE,kBAAkB;gBAC5B,cAAc,EAAE,kBAAkB;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;SAC7C,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACX,gCAAgC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,YAAY,EAAE,CAC1F,CAAC;QACN,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA6B,CAAC;QACnE,OAAO,MAAM,CAAC,UAAU,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,sBAAsB;QACxB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,0CAA0C,CAAC;QAEhF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACL,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;gBAC9C,QAAQ,EAAE,kBAAkB;aAC/B;SACJ,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACX,0CAA0C,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,YAAY,EAAE,CACpG,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA8B,CAAC;QAClE,OAAO,IAAI,CAAC,qBAAqB,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,kBAAkB,CACpB,SAA0B,EAC1B,UAAqC,EAAE;QAOvC,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,aAAa,SAAS,oBAAoB,WAAW,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAEtH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACL,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;gBAC9C,QAAQ,EAAE,kBAAkB;aAC/B;SACJ,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACX,oCAAoC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,YAAY,EAAE,CAC9F,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA+B,CAAC;QACnE,OAAO;YACH,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK;SACpB,CAAC;IACN,CAAC;IAED,KAAK,CAAC,iBAAiB;QACnB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,sBAAsB,CAAC;QAE5D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACL,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;gBAC9C,QAAQ,EAAE,kBAAkB;aAC/B;SACJ,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACX,mCAAmC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,YAAY,EAAE,CAC7F,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAiC,CAAC;QACrE,OAAO,IAAI,CAAC,cAAc,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,cAAc;QAChB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,qBAAqB,CAAC;QAE3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACL,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;gBAC9C,QAAQ,EAAE,kBAAkB;aAC/B;SACJ,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACX,iCAAiC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,YAAY,EAAE,CAC3F,CAAC;QACN,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA+B,CAAC;QACnE,wDAAwD;QACxD,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC;QAC1C,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ"}
@@ -0,0 +1,9 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Creates and configures the Redmine MCP server instance
4
+ */
5
+ export declare function createServer(): McpServer;
6
+ /**
7
+ * Starts the MCP server with stdio transport
8
+ */
9
+ export declare function startServer(): Promise<void>;