Atlas / MCP servers / jztan / Redmine

RedmineCAUTION

mcp/jztan/redmine-6

MCP server connecting AI assistants to Redmine: issues, projects, wikis, time tracking, agile boards, and CRM, with OAuth2 multi-user support

Verdict
CAUTION
Grade
D
Trust score
65 /100
Exposed tools
26 21r · 4w · 1d
Transport
stdio · streamable-http
License
MIT
Stars
82
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

[](https://pypi.org/project/redmine-mcp-server/) [](LICENSE) [](https://pypi.org/project/redmine-mcp-server/) [](#redmine-compatibility) [](https://github.com/jztan/redmine-mcp-server/issues) [](https://github.com/jztan/redmine-mcp-server/actions/workflows/pr-tests.yml) [](https://codecov.io/gh/jztan/redmine-mcp-server) [](https://pepy.tech/project/redmine-mcp-server)

A Model Context Protocol (MCP) server that connects AI assistants to Redmine. It exposes your Redmine instance's projects, issues, time tracking, wiki pages, and files as MCP tools.

mcp-name: io.github.jztan/redmine-mcp-server

An AI agent triaging a Redmine sprint through redmine-mcp-server. Try the live demo →

Tool reference | Changelog | Contributing | Troubleshooting

Features

  • 53 MCP tools on a stock Redmine, 68 with the RedmineUP and DMSF plugins (plus 1 operator tool gated by `R
Read from source at commit 80f7f21d60d6OBSERVED · 2026-10-07
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code (pypi)
claude mcp add redmine-mcp-server --env REDMINE_PASSWORD=${REDMINE_PASSWORD} --env REDMINE_API_KEY=${REDMINE_API_KEY} -- None redmine-mcp-server==2.18.1
03

Exposed tools (26)

21 read · 4 write · 1 destructive. Blast radius: 1 tool can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.

ToolRiskDescription
create_redmine_issuewriteCreate a new issue in Redmine. Open a ticket, file a bug,
create_upload_ticketwriteReserve a slot for a file on the caller
delete_redmine_issuedestructiveHard-delete an issue via ``DELETE /issues/{id}.json``.
error_with_empty_codereadreturn {
get_current_userreadreturn {
get_mcp_server_inforeadReturn the MCP server
get_redmine_attachmentreadDownload a Redmine attachment and return a usable reference to it.
get_redmine_issuereadreturn {
list_project_issue_custom_fieldsreadList the ids and names of the issue custom fields enabled for a project.
list_redmine_issuesreadreturn {
list_redmine_versionsreadList versions (roadmap milestones) for a Redmine project.
list_time_entriesreadreturn {
manage_documentreadreturn {
needs_intreadreturn {
needs_int_or_sentinelreadreturn {
not_in_map_toolreadreturn {
null_errorreadreturn {
plain_errorreadreturn {
plain_error_without_codereadreturn {
successreadreturn {
union_returnreadreturn [{
update_redmine_issuewriteUpdate an existing Redmine issue.
upload_filewriteUpload a file to a Redmine project
wrapped_errorreadreturn {
wrapped_error_without_codereadreturn {
wrapped_successreadreturn [{
04

Trust audit

CAUTIONgrade D · trust 65/100 Install with care. The audit found things worth knowing before you trust its output.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeWARN
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfaceWARN
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (2 observation(s))
Network
declared (10 observation(s))
Shell
declared (1 observation(s))
Dependencies
pinned
Secrets in source
none-found

Findings (25)

MEDIUMCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
src/redmine_mcp_server/main.py:261
importlib.import_module(name)
MEDIUMInformation disclosure · disclose.log_secret · CWE-209, CWE-532
src/redmine_mcp_server/_api_key_login_routes.py:318
logger.info("api-key-login: bound key %s", _fingerprint(api_key))
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
.env.example:91
# Unset = loopback only (http://localhost:* and http://127.0.0.1:*), which suits
MEDIUMFilesystem / path · mcp.destructive_tools · CWE-22, CWE-59
delete_redmine_issue
Why it matters. 1 tool(s) can delete or overwrite
Fix. prefer a read-only mode or scoped tokens; the page states the blast radius
LOWInventory / provenance · inv.hidden_file · CWE-1104
.env.docker.example
.env.docker.example
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.flake8
.flake8
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.pre-commit-config.yaml
.pre-commit-config.yaml
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/run_tests.py:100
__import__(package.replace("-", "_"))
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_extensions.py:220
importlib.import_module(name)
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_extensions.py:245
importlib.import_module(name)
LOWCode injection · code.dynamic_import · CWE-78, CWE-94, CWE-95
tests/test_extensions.py:299
importlib.import_module(module)
LOWInformation disclosure · disclose.log_secret · CWE-209, CWE-532
tests/test_connection.py:151
print(f"Password: {'*' * len(password) if password else 'Not set'}")
LOWInformation disclosure · disclose.log_secret · CWE-209, CWE-532
tests/test_connection.py:152
print(f"API Key: {'*' * len(api_key) if api_key else 'Not set'}")
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
tests/test_security_hardening.py:312
assert _sanitize_filename("../../etc/passwd") == "passwd"
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
tests/test_security_hardening.py:356
'attachment; filename="../../../etc/passwd"'
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
tests/test_security_validation.py:54
filename="../../etc/passwd"
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
tests/test_upload_ticket.py:72
ticket = _upload_store.create_ticket(filename="../../etc/passwd")
LOWNetwork egress · net.beacon_words · CWE-200, CWE-319
tests/test_caller_filter_validation.py:107
filters={"uploads": [{"path": str(secret), "filename": "exfil"}]}
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
CHANGELOG.md:1734
- **SSRF protection for `upload_file(source_url=...)`:** The server now resolves every URL hop and rejects non-public destinations (loopback, RFC1918, link-local including cloud metadata services like
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
tests/test_security_hardening.py:112
return_value=[(2, 1, 6, "", ("169.254.169.254", 0))],
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
tests/test_security_hardening.py:190
return_value=[(2, 1, 6, "", ("169.254.169.254", 0))],
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
tests/test_security_hardening.py:194
source_url="http://169.254.169.254/latest/meta-data/iam/",
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
tests/test_security_hardening.py:226
return [(2, 1, 6, "", ("169.254.169.254", 0))]  # metadata
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
CHANGELOG.md:1456
- `oauth-proxy` mode restricts client redirect URIs to loopback by default (`http://localhost:*`, `http://127.0.0.1:*`). Since MCP clients register their own redirect URI via DCR, this prevents a regi
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
README.md:143
| `REDMINE_MCP_ALLOWED_CLIENT_REDIRECT_URIS` | No | loopback only | `oauth-proxy` and `api-key-login` client redirect-URI allowlist (glob patterns, comma/space separated). Unset = `http://localhost:*`

Gates applied: no_behavioural_pass.

Audited 2026-10-07 · audit v0.4.1 · source sha 80f7f21d60d6full audit observations/trust-audit/mcp-server/jztan__redmine-6.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-0780f7f21d60d6CAUTIOND65first audit
06

Questions

What is the Redmine MCP server?

MCP server connecting AI assistants to Redmine: issues, projects, wikis, time tracking, agile boards, and CRM, with OAuth2 multi-user support

What tools does Redmine expose?

26 in total: 21 read-only, 4 that write, and 1 that can delete or overwrite (delete_redmine_issue). Every one is listed on this page with its risk.

Is Redmine safe to connect to an agent?

With care. The audit graded it D (65/100) and found 25 things worth knowing before you trust this server, listed below with the exact line each was found on. Separately from the audit: 1 of its tools can destroy data, so scope the token you give it to what you actually need.

What credentials does Redmine need?

It reads REDMINE_API_KEY, REDMINE_API_KEY_LOGIN_BINDING_CRYPTO, REDMINE_API_KEY_LOGIN_TEST_ADMIN_KEY, REDMINE_API_KEY_LOGIN_TEST_KEY, REDMINE_AUTH_MODE, REDMINE_INTROSPECT_CLIENT_SECRET, REDMINE_MCP_ACCESS_TOKEN_EXPIRY_SECONDS, REDMINE_OAUTH_CLIENT_ID, REDMINE_OAUTH_DISCOVERY_AS, REDMINE_OAUTH_TEST_TOKEN and REDMINE_PASSWORD from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How does Redmine run?

It speaks stdio and streamable-http, so it runs as a local process your client starts. It is published on PyPI as redmine-mcp-server.

How current is this page?

The grade is for one exact copy of the source (80f7f21d60d6), read on 2026-10-07. The repository is watched and re-audited when it changes.

Advertisement