#
tokens: 3203/50000 4/4 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
└── server.js
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules
2 | .env
3 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Redmine MCP Server for Cline
 2 | 
 3 | This is a custom MCP (Model Context Protocol) server that integrates with Redmine, allowing you to interact with your Redmine projects and issues through the Cline VS Code extension.
 4 | 
 5 | ## Prerequisites
 6 | 
 7 | *   **Node.js:** You need Node.js (version 18 or newer) installed on your system.
 8 | *   **Redmine Instance:** You need a running Redmine instance with the REST API enabled.
 9 | *   **Redmine API Key:** You need an API key for your Redmine user account. You can find this in your Redmine account settings (usually under "My Account" -> "API access key").
10 | * **Cline:** You need the Cline VS Code extension installed and configured.
11 | 
12 | ## Installation
13 | 
14 | 1.  **Clone the repository:**
15 |     ```bash
16 |     git clone https://github.com/ilask/Redmine-MCP.git
17 |     cd Redmine-MCP
18 |     ```
19 | 2.  **Install dependencies:**
20 |     ```bash
21 |     npm install
22 |     ```
23 | 
24 | ## Configuration
25 | 
26 | 1.  **Set environment variables:**
27 |     Create a `.env` file in the root of the project directory and add the following, replacing the placeholders with your actual Redmine hostname and API key:
28 | 
29 |     ```
30 |     REDMINE_HOST=your-redmine-host.com
31 |     REDMINE_API_KEY=your-redmine-api-key
32 |     ```
33 |     **Important:** Do not commit your `.env` file to version control! It contains sensitive information. The `.gitignore` file included in this repository should prevent it from being committed.
34 | 
35 | ## Adding to Cline
36 | 
37 | 1.  **Open Cline Settings:** In VS Code, open the Cline extension and go to the MCP Server tab.
38 | 2.  **Edit MCP Settings:** Click "Edit MCP Settings" to open the `cline_mcp_settings.json` file.
39 | 3.  **Add the server:** Add the following entry to the `mcpServers` object, replacing the `args` path with the *absolute* path to the `server.js` file on your system:
40 | 
41 |     ```json
42 |     {
43 |       "mcpServers": {
44 |         "redmine-server": {
45 |           "command": "node",
46 |           "args": ["C:\\Users\\yourusername\\path\\to\\Redmine-MCP\\server.js"],
47 |           "disabled": false,
48 |           "autoApprove": []
49 |         }
50 |       }
51 |     }
52 |     ```
53 |     **Important:** Make sure to use double backslashes (`\\`) in the path on Windows.
54 | 4. **Save:** Save the `cline_mcp_settings.json` file. Cline should automatically detect the changes and start the server.
55 | 
56 | ## Available Resources and Tools
57 | 
58 | ### Resources
59 | 
60 | *   **`redmine://projects/{project_id}`:** This resource represents a Redmine project. Replace `{project_id}` with the actual ID of a project in your Redmine instance.  You can use the `access_mcp_resource` tool in Cline to read the details of a project.  For example:
61 | 
62 |     ```
63 |     <access_mcp_resource>
64 |     <server_name>redmine-server</server_name>
65 |     <uri>redmine://projects/123</uri>
66 |     </access_mcp_resource>
67 |     ```
68 |    (Replace `123` with a valid project ID). This will return the project details as JSON.
69 | 
70 | ### Tools
71 | 
72 | *   **`create_issue`:** This tool allows you to create a new issue in Redmine. It takes the following parameters:
73 |     *   `project_id` (string, required): The ID of the project where the issue should be created.
74 |     *   `subject` (string, required): The subject of the issue.
75 |     *   `description` (string, required): The description of the issue.
76 | 
77 |     You can use the `use_mcp_tool` tool in Cline to call this tool. For example:
78 | 
79 |     ```
80 |     <use_mcp_tool>
81 |     <server_name>redmine-server</server_name>
82 |     <tool_name>create_issue</tool_name>
83 |     <arguments>
84 |     {
85 |       "project_id": "456",
86 |       "subject": "My New Issue",
87 |       "description": "This is a test issue created via Cline."
88 |     }
89 |     </arguments>
90 |     </use_mcp_tool>
91 |     ```
92 |     (Replace `456` with a valid project ID). This will create a new issue in the specified project and return the issue details as JSON.
93 | 
94 | ## Troubleshooting
95 | * **Connection closed error:** If you see an error like "MCP error -1: Connection closed", make sure that your `REDMINE_HOST` and `REDMINE_API_KEY` environment variables are correctly set. Also, ensure that your Redmine instance is accessible from your computer.
96 | * **Check server logs:** If you encounter issues, check the server's output in the VS Code terminal for any error messages. The server logs errors to the console.
97 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "redmine-mcp-server",
 3 |   "version": "1.0.0",
 4 |   "main": "index.js",
 5 |   "scripts": {
 6 |     "test": "echo \"Error: no test specified\" && exit 1"
 7 |   },
 8 |   "keywords": [],
 9 |   "author": "",
10 |   "license": "ISC",
11 |   "description": "",
12 |   "dependencies": {
13 |     "@modelcontextprotocol/sdk": "^1.5.0",
14 |     "dotenv": "^16.4.7",
15 |     "node-redmine": "^0.2.2",
16 |     "zod": "^3.24.2"
17 |   }
18 | }
19 | 
```

--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------

```javascript
  1 | const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
  2 | const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
  3 | const {
  4 |   CallToolRequestSchema,
  5 |   ErrorCode,
  6 |   ListResourcesRequestSchema,
  7 |   ListResourceTemplatesRequestSchema,
  8 |   ListToolsRequestSchema,
  9 |   McpError,
 10 |   ReadResourceRequestSchema,
 11 | } = require('@modelcontextprotocol/sdk/types.js');
 12 | const Redmine = require('node-redmine');
 13 | const z = require('zod');
 14 | require('dotenv').config();
 15 | 
 16 | const redmineHost = process.env.REDMINE_HOST;
 17 | const redmineApiKey = process.env.REDMINE_API_KEY;
 18 | 
 19 | if (!redmineHost || !redmineApiKey) {
 20 |     throw new Error('REDMINE_HOST and REDMINE_API_KEY environment variables must be set');
 21 | }
 22 | 
 23 | const redmine = new Redmine(redmineHost, { apiKey: redmineApiKey });
 24 | 
 25 | const isValidCreateIssueArgs = (
 26 |   args
 27 | ) => typeof args === 'object' && args !== null && typeof args.project_id === 'string' && typeof args.subject === 'string' && typeof args.description === 'string';
 28 |   
 29 | class RedmineServer {
 30 |   server;
 31 | 
 32 |   constructor() {
 33 |     this.server = new Server(
 34 |       {
 35 |         name: 'redmine-mcp-server',
 36 |         version: '0.1.0',
 37 |       },
 38 |       {
 39 |         capabilities: {
 40 |           resources: {},
 41 |           tools: {},
 42 |         },
 43 |       }
 44 |     );
 45 | 
 46 |     this.setupResourceHandlers();
 47 |     this.setupToolHandlers();
 48 | 
 49 |     // Error handling
 50 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
 51 |     process.on('SIGINT', async () => {
 52 |       await this.server.close();
 53 |       process.exit(0);
 54 |     });
 55 |   }
 56 | 
 57 |   setupResourceHandlers() {
 58 |     this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
 59 |       resources: [
 60 |       ],
 61 |     }));
 62 | 
 63 |     this.server.setRequestHandler(
 64 |       ListResourceTemplatesRequestSchema,
 65 |       async () => ({
 66 |         resourceTemplates: [
 67 |           {
 68 |             uriTemplate: 'redmine://projects/{project_id}',
 69 |             name: 'Redmine Project',
 70 |             mimeType: 'application/json',
 71 |             description: 'Details of a Redmine project',
 72 |           },
 73 |         ],
 74 |       })
 75 |     );
 76 | 
 77 |     this.server.setRequestHandler(
 78 |       ReadResourceRequestSchema,
 79 |       async (request) => {
 80 |         const match = request.params.uri.match(
 81 |           /^redmine:\/\/projects\/([^/]+)$/
 82 |         );
 83 |         if (!match) {
 84 |           throw new McpError(
 85 |             ErrorCode.InvalidRequest,
 86 |             `Invalid URI format: ${request.params.uri}`
 87 |           );
 88 |         }
 89 |         const projectId = match[1];
 90 | 
 91 |         try {
 92 |           const project = await new Promise((resolve, reject) => {
 93 |             redmine.getProject(projectId, (err, data) => {
 94 |               if (err) {
 95 |                 reject(err);
 96 |               } else {
 97 |                 resolve(data);
 98 |               }
 99 |             });
100 |           });
101 | 
102 |           return {
103 |             contents: [
104 |               {
105 |                 uri: request.params.uri,
106 |                 mimeType: 'application/json',
107 |                 text: JSON.stringify(project),
108 |               },
109 |             ],
110 |           };
111 |         } catch (error) {
112 |             throw new McpError(
113 |               ErrorCode.InternalError,
114 |               `Redmine API error: ${error}`
115 |             );
116 |         }
117 |       }
118 |     );
119 |   }
120 | 
121 |   setupToolHandlers() {
122 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
123 |       tools: [
124 |         {
125 |           name: 'create_issue',
126 |           description: 'Create a new Redmine issue',
127 |           inputSchema: {
128 |             type: 'object',
129 |             properties: {
130 |               project_id: {
131 |                 type: 'string',
132 |                 description: 'Project ID',
133 |               },
134 |               subject: {
135 |                 type: 'string',
136 |                 description: 'Issue subject',
137 |               },
138 |               description: {
139 |                 type: 'string',
140 |                 description: 'Issue description',
141 |               },
142 |             },
143 |             required: ['project_id', 'subject', 'description'],
144 |           },
145 |         },
146 |       ],
147 |     }));
148 | 
149 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
150 |       if (request.params.name !== 'create_issue') {
151 |         throw new McpError(
152 |           ErrorCode.MethodNotFound,
153 |           `Unknown tool: ${request.params.name}`
154 |         );
155 |       }
156 | 
157 |       if (!isValidCreateIssueArgs(request.params.arguments)) {
158 |         throw new McpError(
159 |           ErrorCode.InvalidParams,
160 |           'Invalid create_issue arguments'
161 |         );
162 |       }
163 | 
164 |       const { project_id, subject, description } = request.params.arguments;
165 | 
166 |       try {
167 |         const issue = await new Promise((resolve, reject) => {
168 |             redmine.create_issue({
169 |             project_id: project_id,
170 |             subject: subject,
171 |             description: description,
172 |           }, (err, data) => {
173 |             if (err) {
174 |               reject(err);
175 |             } else {
176 |               resolve(data);
177 |             }
178 |           });
179 |         });
180 | 
181 |         return {
182 |           content: [
183 |             {
184 |               type: 'text',
185 |               text: JSON.stringify(issue),
186 |             },
187 |           ],
188 |         };
189 |       } catch (error) {
190 |         return {
191 |           content: [
192 |             {
193 |               type: 'text',
194 |               text: `Redmine API error: ${error}`,
195 |             },
196 |           ],
197 |           isError: true,
198 |         };
199 |       }
200 |     });
201 |   }
202 | 
203 |   async run() {
204 |     const transport = new StdioServerTransport();
205 |     await this.server.connect(transport);
206 |     console.error('Redmine MCP server running on stdio');
207 |   }
208 | }
209 | 
210 | const server = new RedmineServer();
211 | server.run().catch(console.error);
212 | 
```