@martin4455/redmine-mcp-ro 0.3.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.
- package/CONFIG.md +242 -0
- package/LICENSE +21 -0
- package/README.md +196 -0
- package/dist/api-key-filter.d.ts +6 -0
- package/dist/api-key-filter.js +16 -0
- package/dist/attachment-downloader.d.ts +15 -0
- package/dist/attachment-downloader.js +190 -0
- package/dist/attachment-parser.d.ts +27 -0
- package/dist/attachment-parser.js +138 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +144 -0
- package/dist/config-file.d.ts +18 -0
- package/dist/config-file.js +192 -0
- package/dist/config-loader.d.ts +14 -0
- package/dist/config-loader.js +58 -0
- package/dist/credential-store.d.ts +17 -0
- package/dist/credential-store.js +71 -0
- package/dist/download-directory.d.ts +3 -0
- package/dist/download-directory.js +51 -0
- package/dist/http-client.d.ts +14 -0
- package/dist/http-client.js +99 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/local-configuration.d.ts +35 -0
- package/dist/local-configuration.js +218 -0
- package/dist/redmine-client.d.ts +100 -0
- package/dist/redmine-client.js +184 -0
- package/dist/safe-error.d.ts +2 -0
- package/dist/safe-error.js +18 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +222 -0
- package/dist/tool-definitions.d.ts +2 -0
- package/dist/tool-definitions.js +460 -0
- package/dist/tool-validation.d.ts +7 -0
- package/dist/tool-validation.js +20 -0
- package/dist/types.d.ts +247 -0
- package/dist/types.js +1 -0
- package/dist/user-resolver.d.ts +24 -0
- package/dist/user-resolver.js +140 -0
- package/package.json +78 -0
package/CONFIG.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Local configuration and system credentials
|
|
2
|
+
|
|
3
|
+
Version 0.3.0 stores a complete connection JSON in the operating system credential
|
|
4
|
+
store. The local configuration uses format `3.0` and holds references and optional
|
|
5
|
+
request limits and download settings. The package version, local file format and
|
|
6
|
+
protected JSON version are independent values.
|
|
7
|
+
|
|
8
|
+
## Select a directory
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
redmine-mcp configure --config-dir /absolute/path/to/project
|
|
12
|
+
redmine-mcp-ro --config-dir /absolute/path/to/project
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The directory must already exist. Selection uses `--config-dir`, then
|
|
16
|
+
`REDMINE_CONFIG_DIR`, then the current working directory. There is no parent or
|
|
17
|
+
home search, global fallback or `--global` option. To use a former home configuration,
|
|
18
|
+
select that directory explicitly. Multiple clients can select the same directory.
|
|
19
|
+
|
|
20
|
+
For a source checkout, build first and replace `redmine-mcp` with `node dist/cli.js`
|
|
21
|
+
and `redmine-mcp-ro` with `node dist/index.js`. On Windows use a path such as
|
|
22
|
+
`C:\projects\example`; inside JSON escape it as `C:\\projects\\example`.
|
|
23
|
+
|
|
24
|
+
The application does not read `.env`. `REDMINE_URL` and `REDMINE_API_KEY` are not
|
|
25
|
+
configuration sources. Use `configure` to set or change a connection.
|
|
26
|
+
|
|
27
|
+
## Local file format
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"version": "3.0",
|
|
32
|
+
"defaultProfile": "default",
|
|
33
|
+
"profiles": {
|
|
34
|
+
"default": {
|
|
35
|
+
"credentialId": "08a93a80-42bc-4c88-a7d9-a10c24985de3"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"settings": {
|
|
39
|
+
"requestTimeoutMs": 30000,
|
|
40
|
+
"maxAttachmentBytes": 52428800,
|
|
41
|
+
"maxContentBytes": 5242880
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`settings` is optional. A profile accepts only `credentialId`. Adding `url`,
|
|
47
|
+
`apiKey` or other unknown fields is rejected. Profile names, UUIDs and limits remain
|
|
48
|
+
visible on disk. The example UUID above is a placeholder, not a working credential.
|
|
49
|
+
|
|
50
|
+
New profile names contain 1-64 ASCII letters, digits, dots, underscores or hyphens,
|
|
51
|
+
starting with a letter or digit; `constructor` and `prototype` are reserved.
|
|
52
|
+
|
|
53
|
+
A UUID identifies an entry; it does not contain or decrypt its secret. The system
|
|
54
|
+
store uses service `@martin4455/redmine-mcp-ro` and the UUID as the entry account.
|
|
55
|
+
The secret value has this shape (example values only):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"version": 1,
|
|
60
|
+
"url": "https://redmine.example.com",
|
|
61
|
+
"apiKey": "EXAMPLE_ONLY_REPLACE_WITH_YOUR_TOKEN"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Both URL and token are read from that protected value. The URL cannot be overridden
|
|
66
|
+
by local metadata or environment credentials. A reference does not isolate programs
|
|
67
|
+
running under the same OS account; access is governed by the system store.
|
|
68
|
+
Tokens and URLs are present in process memory during use. Redmine itself may return
|
|
69
|
+
URLs and personal data as ordinary API content.
|
|
70
|
+
|
|
71
|
+
## Platform setup
|
|
72
|
+
|
|
73
|
+
| Platform | Store and requirements |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| macOS | Login Keychain. Unlock it and allow the Node.js executable when prompted. |
|
|
76
|
+
| Windows | Credential Manager under the account running MCP. |
|
|
77
|
+
| Linux / Ubuntu | Secret Service, such as GNOME Keyring, unlocked in the same user D-Bus session. |
|
|
78
|
+
|
|
79
|
+
The adapter uses pinned `@napi-rs/keyring` 2.1.0 and native APIs, without shell
|
|
80
|
+
commands containing credentials. Linux explicitly selects `secret-service`, so
|
|
81
|
+
there is no kernel-keyring or file fallback. Locked, denied or unavailable stores
|
|
82
|
+
cause an error. Native optional dependencies for the target platform must be installed.
|
|
83
|
+
|
|
84
|
+
Ubuntu Desktop normally provides GNOME Keyring. Where it is absent, install it
|
|
85
|
+
with `sudo apt install gnome-keyring dbus`. SSH, Docker and CI require an explicitly
|
|
86
|
+
provisioned and unlocked Secret Service in the session used by the launcher.
|
|
87
|
+
Preserve `DBUS_SESSION_BUS_ADDRESS` and the relevant `XDG_RUNTIME_DIR`. The isolated
|
|
88
|
+
Ubuntu test harness is an example for synthetic test data, not a persistent vault.
|
|
89
|
+
|
|
90
|
+
Windows Credential Manager limits the credential value to 2560 bytes. This adapter
|
|
91
|
+
checks the UTF-16 size of the complete JSON before writing, including the URL, token,
|
|
92
|
+
field names and escaping. An oversized value fails without a plaintext fallback.
|
|
93
|
+
WSL uses the Linux backend, not Windows Credential Manager.
|
|
94
|
+
|
|
95
|
+
Configure and run under the same OS account. A service account or another machine
|
|
96
|
+
requires its own accessible credential store.
|
|
97
|
+
|
|
98
|
+
## Configure, inspect and rotate
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
redmine-mcp configure --profile default --config-dir /path/to/project
|
|
102
|
+
redmine-mcp list --config-dir /path/to/project
|
|
103
|
+
redmine-mcp status --profile default --config-dir /path/to/project
|
|
104
|
+
redmine-mcp use default --config-dir /path/to/project
|
|
105
|
+
redmine-mcp remove default --config-dir /path/to/project
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`configure` masks token input. The URL is shown in its interactive prompt, including
|
|
109
|
+
as the default when updating an existing profile. The optional connection check
|
|
110
|
+
reports success without printing a user's name. `list` reads metadata without
|
|
111
|
+
accessing the keyring; `status` verifies access but displays neither URL nor token.
|
|
112
|
+
|
|
113
|
+
For rotation, run `configure` again with the same profile and enter the new token.
|
|
114
|
+
It creates a new UUID, writes the complete JSON, verifies exact read-back, commits
|
|
115
|
+
the local file, then deletes the old entry. A failure before the file commit leaves
|
|
116
|
+
the old configuration usable. Restart MCP to reload the credential held in memory.
|
|
117
|
+
`remove` commits removal of the local reference before deleting the entry. Cleanup
|
|
118
|
+
failures return a warning with the UUID and a nonzero exit code for manual removal.
|
|
119
|
+
|
|
120
|
+
A copied file under the same OS account can refer to the same credential. Rotation
|
|
121
|
+
or deletion invalidates that old reference in other copies. Prefer selecting one
|
|
122
|
+
shared configuration directory or configuring independent profiles.
|
|
123
|
+
|
|
124
|
+
Server profile selection uses `--profile`, then `REDMINE_PROFILE`, then
|
|
125
|
+
`defaultProfile`, then the first profile. An unknown explicit selection is rejected.
|
|
126
|
+
|
|
127
|
+
## Migration
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
redmine-mcp migrate --config-dir /path/to/project
|
|
131
|
+
redmine-mcp status --config-dir /path/to/project
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The server and ordinary CLI commands reject older formats until migration. The
|
|
135
|
+
migration command supports:
|
|
136
|
+
|
|
137
|
+
- Format `1.0`: plaintext URLs and tokens are written to new protected entries.
|
|
138
|
+
- Format `2.0` with a URL and UUID: the existing protected JSON is checked against
|
|
139
|
+
the local URL, then the URL is removed from the local file. Existing entries in
|
|
140
|
+
the current service retain their UUIDs.
|
|
141
|
+
- Format `2.0` with only a UUID: the current service is checked first. Only a missing
|
|
142
|
+
entry permits lookup in the historical `redmine-mcp-ro` service. Its complete JSON
|
|
143
|
+
is copied and verified under a new UUID in `@martin4455/redmine-mcp-ro`.
|
|
144
|
+
|
|
145
|
+
A denied, locked or invalid current-store entry does not trigger fallback to another
|
|
146
|
+
namespace. Historical entries are retained because other installations may use them.
|
|
147
|
+
Migrating one copy does not update other files. Remove obsolete historical entries
|
|
148
|
+
separately once no installation needs them.
|
|
149
|
+
|
|
150
|
+
All profiles are validated before replacing the original file with format `3.0`.
|
|
151
|
+
Legacy names outside the new naming rules are converted to unique valid names.
|
|
152
|
+
Existing valid names are preserved; collisions receive numeric suffixes. The
|
|
153
|
+
default profile follows its renamed entry, and the CLI prints each name mapping.
|
|
154
|
+
Update explicit `--profile` or `REDMINE_PROFILE` selections to the reported name.
|
|
155
|
+
Format `1.0` tokens have surrounding whitespace trimmed during migration; embedded
|
|
156
|
+
line breaks and NUL bytes are rejected. URLs with embedded credentials, query
|
|
157
|
+
strings or fragments still require manual correction, with a diagnostic identifying
|
|
158
|
+
the profile by its position without printing the URL or token.
|
|
159
|
+
|
|
160
|
+
Migration does not create a plaintext backup. On failure before the commit, the
|
|
161
|
+
original file is retained and newly created entries are deleted where possible.
|
|
162
|
+
Migration does not erase backups, Git history, copies or previously exposed data;
|
|
163
|
+
rotate tokens that were exposed. Repeating migration on format `3.0` is a no-op.
|
|
164
|
+
|
|
165
|
+
## File writes and limits
|
|
166
|
+
|
|
167
|
+
Writes use a private temporary metadata file, flush it and rename it over the
|
|
168
|
+
configuration. A lock and an original-content check prevent normal concurrent
|
|
169
|
+
configurators from overwriting one another. Configuration symlinks, files larger
|
|
170
|
+
than 1 MiB, unknown fields and duplicate UUID references are rejected. On POSIX,
|
|
171
|
+
new metadata files have mode `0600`; on Windows, directory ACLs apply.
|
|
172
|
+
|
|
173
|
+
The numeric `settings` keys accept positive safe integers. Environment settings take
|
|
174
|
+
precedence over corresponding local settings; defaults apply if both are absent.
|
|
175
|
+
|
|
176
|
+
| Local setting | Environment variable | Default |
|
|
177
|
+
| --- | --- | --- |
|
|
178
|
+
| `requestTimeoutMs` | `REDMINE_REQUEST_TIMEOUT_MS` | `30000` |
|
|
179
|
+
| `maxAttachmentBytes` | `REDMINE_MAX_ATTACHMENT_BYTES` | `52428800` |
|
|
180
|
+
| `maxContentBytes` | `REDMINE_MAX_CONTENT_BYTES` | `5242880` |
|
|
181
|
+
| `downloadDir` | `REDMINE_DOWNLOAD_DIR` | Unset; local downloads disabled |
|
|
182
|
+
|
|
183
|
+
Timeout cannot exceed `2147483647` ms. JSON API responses have a fixed 10 MiB limit.
|
|
184
|
+
If no configurator is running but a previous process crashed, remove a stale
|
|
185
|
+
`.redmine-mcp.lock` in the selected directory before retrying.
|
|
186
|
+
|
|
187
|
+
## Local attachment downloads
|
|
188
|
+
|
|
189
|
+
`download_attachment` and `download_issue_attachments` write local files and are
|
|
190
|
+
marked accordingly in MCP tool annotations. They require an existing, dedicated
|
|
191
|
+
download directory selected by the operator. For example, create
|
|
192
|
+
`/absolute/path/to/redmine-downloads` and add `"downloadDir":
|
|
193
|
+
"/absolute/path/to/redmine-downloads"` to `settings`, or set `REDMINE_DOWNLOAD_DIR`
|
|
194
|
+
in the MCP launcher's environment. The environment value takes precedence. Paths
|
|
195
|
+
must be absolute; `~` is not expanded. This setting is non-secret and visible on disk.
|
|
196
|
+
|
|
197
|
+
Use a directory separate from source projects, home configuration and locations
|
|
198
|
+
used to load scripts or other executable code. Downloaded content remains untrusted.
|
|
199
|
+
The tool's optional `output_dir` accepts only a relative subdirectory such as
|
|
200
|
+
`issue-123/documents`, using forward slashes. It defaults to the configured root,
|
|
201
|
+
never the server's working directory. Missing subdirectories are created privately.
|
|
202
|
+
Absolute paths, parent traversal, symlink subdirectories and dot-prefixed filenames
|
|
203
|
+
are rejected. Existing files and symlinks are never overwritten. The root's canonical
|
|
204
|
+
location is trusted; directory checks do not isolate a hostile process running as
|
|
205
|
+
the same OS user and concurrently changing the filesystem.
|
|
206
|
+
|
|
207
|
+
`get_attachment_content` returns bounded base64 content without filesystem writes
|
|
208
|
+
and remains available without this setting.
|
|
209
|
+
|
|
210
|
+
## Response filtering and diagnostics
|
|
211
|
+
|
|
212
|
+
Both the API client and MCP boundary recursively remove structured API-key fields
|
|
213
|
+
(`api_key`, `apiKey`, `api-key`, `X-Redmine-API-Key`, case-insensitively). Other user
|
|
214
|
+
data, issue text and attachments are returned normally. This filter is not a general
|
|
215
|
+
secret scanner. Earlier client history and tool results are not rewritten.
|
|
216
|
+
|
|
217
|
+
Request errors exclude response bodies and request headers. Native errors are
|
|
218
|
+
replaced with generic platform-specific guidance. API requests and redirects are
|
|
219
|
+
restricted to the configured origin, including every redirect through a proxy.
|
|
220
|
+
Redirect handling avoids `follow-redirects` request logging, so `DEBUG=*` and
|
|
221
|
+
`DEBUG=follow-redirects` do not expose the API-key header. Use HTTPS outside isolated
|
|
222
|
+
local tests.
|
|
223
|
+
|
|
224
|
+
## Verification
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm run typecheck
|
|
228
|
+
npm run lint
|
|
229
|
+
npm test
|
|
230
|
+
npm run test:keyring
|
|
231
|
+
npm run test:keyring:ubuntu
|
|
232
|
+
npm run test:integration
|
|
233
|
+
docker compose -f test/docker-compose.test.yml down --volumes
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Unit tests use test doubles for credentials. The native smoke test uses synthetic
|
|
237
|
+
credentials, separate processes and a loopback HTTP fixture, then removes its test
|
|
238
|
+
entries. Run it on each target OS, including Windows. The Ubuntu container also
|
|
239
|
+
checks that an unavailable D-Bus session fails without fallback. The Redmine suite
|
|
240
|
+
uses disposable Docker containers and an in-memory credential-store test double;
|
|
241
|
+
it does not access the host keyring. With images already present, use
|
|
242
|
+
`REDMINE_TEST_OFFLINE=1 npm run test:integration` to prohibit image pulls.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 martin4455
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Redmine MCP Server (Read-Only)
|
|
2
|
+
|
|
3
|
+
`@martin4455/redmine-mcp-ro` provides MCP tools for reading the Redmine REST API
|
|
4
|
+
through the stdio transport. Version 0.3.0 stores the complete connection JSON,
|
|
5
|
+
including the Redmine URL and API token, in the operating system credential store.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node.js 24.15 or later in the 24.x line, or Node.js 26 and later.
|
|
10
|
+
- Redmine with its REST API enabled and an API key for the intended account.
|
|
11
|
+
- An accessible macOS Keychain, Windows Credential Manager, or Linux Secret Service.
|
|
12
|
+
|
|
13
|
+
Access to Redmine resources follows the permissions of the configured account.
|
|
14
|
+
Some endpoints require administrator permissions.
|
|
15
|
+
|
|
16
|
+
## Installation and configuration
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @martin4455/redmine-mcp-ro@0.3.0
|
|
20
|
+
redmine-mcp configure --config-dir /absolute/path/to/project
|
|
21
|
+
redmine-mcp status --config-dir /absolute/path/to/project
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The selected directory must already exist. The configurator masks token input and
|
|
25
|
+
can test the connection before saving. It stores `{version, url, apiKey}` together
|
|
26
|
+
in the system credential store and verifies the saved value before committing the
|
|
27
|
+
local reference. The URL appears in the interactive configuration prompt; `list`
|
|
28
|
+
and `status` do not display the URL or token.
|
|
29
|
+
|
|
30
|
+
The local `.redmine-mcp` file uses format `3.0` and contains profile names, random
|
|
31
|
+
credential identifiers, and optional request limits and download settings. It contains neither the URL
|
|
32
|
+
nor the API token. Configure and run MCP under the same OS account.
|
|
33
|
+
|
|
34
|
+
On macOS, unlock the login Keychain and allow access for the Node.js executable
|
|
35
|
+
when prompted. Windows uses Credential Manager for the account running MCP.
|
|
36
|
+
Linux uses an unlocked Secret Service provider, such as GNOME Keyring, in the
|
|
37
|
+
same user D-Bus session. A headless Linux process needs that session set up
|
|
38
|
+
explicitly; see [platform setup](./CONFIG.md#platform-setup).
|
|
39
|
+
|
|
40
|
+
Without `--config-dir`, configuration resolves from `REDMINE_CONFIG_DIR`, then the
|
|
41
|
+
current working directory. There is no parent-directory or home-directory search.
|
|
42
|
+
The server does not load `.env` and does not use `REDMINE_URL` or `REDMINE_API_KEY`.
|
|
43
|
+
An unavailable system store stops the operation; there is no plaintext fallback.
|
|
44
|
+
|
|
45
|
+
## Upgrade and token rotation
|
|
46
|
+
|
|
47
|
+
Older local configuration formats `1.0` and `2.0` require explicit migration:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
redmine-mcp migrate --config-dir /absolute/path/to/project
|
|
51
|
+
redmine-mcp status --config-dir /absolute/path/to/project
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Migration supports plaintext legacy profiles, profiles with a local URL and UUID,
|
|
55
|
+
and the earlier reference-only format using service `redmine-mcp-ro`. It writes
|
|
56
|
+
format `3.0` and uses service `@martin4455/redmine-mcp-ro`. Historical-service entries
|
|
57
|
+
are retained because another local installation may still use them. No plaintext
|
|
58
|
+
backup is created. Existing backups and copies are not erased.
|
|
59
|
+
|
|
60
|
+
After rotating the token in Redmine, run `configure` for the existing profile and
|
|
61
|
+
enter the replacement token. The command creates and verifies a new credential,
|
|
62
|
+
commits the reference, then deletes the previous entry. Restart the MCP client to
|
|
63
|
+
load the new connection. Copies of the old reference will need reconfiguration.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
redmine-mcp configure --profile default --config-dir /absolute/path/to/project
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
See [CONFIG.md](./CONFIG.md) for the file format, platform setup and migration details.
|
|
70
|
+
|
|
71
|
+
## MCP client configuration
|
|
72
|
+
|
|
73
|
+
After configuring the directory, add a stdio server to your MCP client:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"redmine": {
|
|
79
|
+
"command": "redmine-mcp-ro",
|
|
80
|
+
"args": ["--config-dir", "/absolute/path/to/project"]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use an absolute executable path if the client does not inherit your terminal's
|
|
87
|
+
PATH. A source checkout can use `node` with the absolute path to `dist/index.js`
|
|
88
|
+
as the first argument. Windows JSON paths need escaped backslashes, for example
|
|
89
|
+
`C:\\projects\\example`. No URL or token belongs in the MCP client's JSON.
|
|
90
|
+
|
|
91
|
+
This package implements stdio. It does not provide an HTTP MCP server.
|
|
92
|
+
|
|
93
|
+
## Profiles
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
redmine-mcp configure --profile staging --config-dir /absolute/path/to/project
|
|
97
|
+
redmine-mcp list --config-dir /absolute/path/to/project
|
|
98
|
+
redmine-mcp use staging --config-dir /absolute/path/to/project
|
|
99
|
+
redmine-mcp-ro --profile staging --config-dir /absolute/path/to/project
|
|
100
|
+
redmine-mcp remove staging --config-dir /absolute/path/to/project
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Server profile selection uses `--profile`, then `REDMINE_PROFILE`, then the local
|
|
104
|
+
default. An unknown explicitly selected profile fails rather than selecting another.
|
|
105
|
+
`remove` deletes the selected profile and its corresponding credential.
|
|
106
|
+
|
|
107
|
+
## Available tools
|
|
108
|
+
|
|
109
|
+
| Resources | Tools |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| Issues | `list_issues`, `get_issue`, `list_issue_relations`, `get_relation` |
|
|
112
|
+
| Projects | `list_projects`, `get_project`, `list_project_versions`, `get_version`, `list_project_memberships`, `get_membership`, `list_project_issue_categories`, `get_issue_category`, `list_project_files` |
|
|
113
|
+
| Users and groups | `list_users`, `get_user`, `get_current_user`, `list_groups`, `get_group` |
|
|
114
|
+
| Time entries | `list_time_entries`, `get_time_entry`, `list_time_entry_activities` |
|
|
115
|
+
| Wiki and news | `list_wiki_pages`, `get_wiki_page`, `list_news` |
|
|
116
|
+
| Metadata | `list_issue_statuses`, `list_trackers`, `list_issue_priorities`, `list_roles`, `get_role`, `list_custom_fields`, `list_queries` |
|
|
117
|
+
| Search | `search` |
|
|
118
|
+
| Attachments | `get_attachment`, `get_attachment_content`, `download_attachment`, `download_issue_attachments` |
|
|
119
|
+
|
|
120
|
+
Local attachment downloads are disabled by default. To enable them, create a
|
|
121
|
+
dedicated directory and set `REDMINE_DOWNLOAD_DIR` or `settings.downloadDir` to its
|
|
122
|
+
absolute path. Use a directory for untrusted downloads, separate from source
|
|
123
|
+
projects, home configuration and executable code. `output_dir` may select only a
|
|
124
|
+
relative subdirectory; omitting it uses the configured root. Absolute paths, parent
|
|
125
|
+
traversal, symlink subdirectories and dot-prefixed filenames are rejected. Existing
|
|
126
|
+
files are never overwritten. `get_attachment_content` does not write files and
|
|
127
|
+
does not require a download directory. See [CONFIG.md](./CONFIG.md#local-attachment-downloads).
|
|
128
|
+
|
|
129
|
+
## Security behavior
|
|
130
|
+
|
|
131
|
+
Structured API-key fields returned by Redmine are removed recursively in the API
|
|
132
|
+
client and at the MCP output boundary. This includes `api_key`, `apiKey`, `api-key`
|
|
133
|
+
and `X-Redmine-API-Key`, case-insensitively. Other Redmine data, including names,
|
|
134
|
+
URLs, issue text and attachments, remains available as requested. The filter does
|
|
135
|
+
not scan arbitrary text for secrets.
|
|
136
|
+
|
|
137
|
+
Use HTTPS and an account with the permissions required for your use case. URLs
|
|
138
|
+
with embedded credentials, query strings or fragments are rejected. API requests,
|
|
139
|
+
attachments and redirects must remain on the configured origin. Downloaded filenames
|
|
140
|
+
are validated against path traversal. Errors do not serialize request headers or
|
|
141
|
+
response bodies. HTTP redirects use the native transport with explicit origin
|
|
142
|
+
checks, so `DEBUG=*` and `DEBUG=follow-redirects` do not log the API-key header.
|
|
143
|
+
|
|
144
|
+
Embedded attachment discovery ignores links outside the configured origin and
|
|
145
|
+
does not execute document scripts or load remote HTML resources. Parser diagnostics
|
|
146
|
+
containing document text are not forwarded to logs. Downloaded files are saved,
|
|
147
|
+
never executed by this package; their contents remain untrusted.
|
|
148
|
+
|
|
149
|
+
Default limits are 30 seconds per request, 50 MiB per downloaded file, 5 MiB for
|
|
150
|
+
attachment content returned through MCP, and 10 MiB for JSON API responses. The
|
|
151
|
+
first three can be changed through the documented local settings or environment
|
|
152
|
+
variables in [CONFIG.md](./CONFIG.md).
|
|
153
|
+
|
|
154
|
+
## Development and verification
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npm ci --ignore-scripts
|
|
158
|
+
npm run typecheck
|
|
159
|
+
npm run lint
|
|
160
|
+
npm test
|
|
161
|
+
npm run build
|
|
162
|
+
npm run check:package
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The build cleans generated `dist/`, then compiles production TypeScript without
|
|
166
|
+
source maps or test helpers. The npm archive contains compiled JavaScript, type
|
|
167
|
+
declarations, `README.md`, `CONFIG.md`, `LICENSE` and `package.json`. The `prepack`
|
|
168
|
+
script builds and checks this list automatically. There are no install lifecycle
|
|
169
|
+
scripts in this package. Dependencies are installed according to normal npm rules.
|
|
170
|
+
Direct runtime dependency versions are pinned. The repository lockfile fixes
|
|
171
|
+
development and test installs; consumers can still resolve different transitive
|
|
172
|
+
dependency versions within the dependencies' own ranges.
|
|
173
|
+
|
|
174
|
+
Additional tests use synthetic credentials and disposable test resources:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npm run test:keyring
|
|
178
|
+
npm run test:keyring:ubuntu
|
|
179
|
+
npm run test:integration
|
|
180
|
+
docker compose -f test/docker-compose.test.yml down --volumes
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The native keyring test verifies persistence across processes, interactive CLI,
|
|
184
|
+
MCP authentication, migration, rotation and cleanup. Run it on each target OS to
|
|
185
|
+
verify native behavior. The Ubuntu test runs GNOME Keyring and D-Bus in Docker.
|
|
186
|
+
The Redmine integration suite requires Docker and an accessible system store; use
|
|
187
|
+
`REDMINE_TEST_PORT=18080` if port 8080 is occupied. Unit tests use test doubles for
|
|
188
|
+
credential storage and local HTTP fixtures.
|
|
189
|
+
|
|
190
|
+
## License and project links
|
|
191
|
+
|
|
192
|
+
MIT; see [LICENSE](./LICENSE).
|
|
193
|
+
|
|
194
|
+
- [Source repository](https://github.com/luskan/redmine-mcp)
|
|
195
|
+
- [Issue tracker](https://github.com/luskan/redmine-mcp/issues)
|
|
196
|
+
- [npm package](https://www.npmjs.com/package/@martin4455/redmine-mcp-ro)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redmine includes api_key in user responses for the account owner and admins.
|
|
3
|
+
* Remove these fields at every depth, including users embedded by extensions.
|
|
4
|
+
* This filters structured fields, not arbitrary secrets in issue text or files.
|
|
5
|
+
*/
|
|
6
|
+
export declare function stripApiKeys(value: unknown): unknown;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const API_KEY_FIELD = /^(?:api[_-]?key|x-redmine-api-key)$/i;
|
|
2
|
+
/**
|
|
3
|
+
* Redmine includes api_key in user responses for the account owner and admins.
|
|
4
|
+
* Remove these fields at every depth, including users embedded by extensions.
|
|
5
|
+
* This filters structured fields, not arbitrary secrets in issue text or files.
|
|
6
|
+
*/
|
|
7
|
+
export function stripApiKeys(value) {
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return value.map(stripApiKeys);
|
|
10
|
+
if (value !== null && typeof value === 'object') {
|
|
11
|
+
return Object.fromEntries(Object.entries(value)
|
|
12
|
+
.filter(([key]) => !API_KEY_FIELD.test(key))
|
|
13
|
+
.map(([key, entry]) => [key, stripApiKeys(entry)]));
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AttachmentContent, AttachmentDownloadOptions, DownloadedAttachment, DownloadedIssueAttachment, RedmineConfig } from './types.js';
|
|
2
|
+
export declare class AttachmentDownloader {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly parser;
|
|
6
|
+
private readonly limits;
|
|
7
|
+
constructor(config: RedmineConfig);
|
|
8
|
+
private metadata;
|
|
9
|
+
private contentUrl;
|
|
10
|
+
downloadAttachment(attachmentId: number, outputDir?: string, options?: AttachmentDownloadOptions): Promise<DownloadedAttachment>;
|
|
11
|
+
private saveFile;
|
|
12
|
+
downloadIssueAttachments(issueId: number, outputDir?: string): Promise<DownloadedIssueAttachment[]>;
|
|
13
|
+
downloadAllIssueAttachments(issueId: number, outputDir?: string, includeEmbedded?: boolean): Promise<DownloadedIssueAttachment[]>;
|
|
14
|
+
getAttachmentContent(attachmentId: number): Promise<AttachmentContent>;
|
|
15
|
+
}
|