redmine-mcp-proxy
Enables multi-user, network-reachable MCP interaction with Redmine, with per-user impersonation and identity-based access control.
README
redmine-mcp-proxy
A Streamable HTTP → stdio bridge that turns a stdio Model Context Protocol Redmine server into a multi-user, network-reachable MCP endpoint — with per-user impersonation baked in.
Point your AI platform (Ozwell, Claude, etc.) at a single URL:
https://rm.os.mieweb.org/mcp
…and every logged-in user transparently acts as themselves in Redmine — no shared service account, no per-user API keys, no prompt-injectable identity.
Why this exists
MCP Redmine servers speak stdio (one process, one identity). That's great for a single desktop user, but it doesn't work when:
- Many people share one AI assistant, and each must act as their own Redmine user (correct "assigned to me", correct author, correct permissions).
- The MCP client is a hosted web platform that can only reach an HTTP URL, not a local subprocess.
This proxy solves both: it exposes Streamable HTTP on /mcp, and for every
session it spawns a dedicated backend subprocess locked to the requesting
user's identity.
How the Proxy works
sequenceDiagram
participant AI as AI Platform (Ozwell / Claude)
participant Proxy as redmine-mcp-proxy (HTTP :80/mcp)
participant Sub as redmine-mcp-rs subprocess (stdio)
participant RM as Redmine REST API
AI->>Proxy: POST /mcp (initialize)<br/>x-ozwell-user-email: rgara@mieweb.com
Note over Proxy: resolveUser() extracts identity
Proxy->>Sub: spawn(redmine-mcp-rs)<br/>REDMINE_ON_BEHALF_OF=rgara@mieweb.com<br/>REDMINE_LOCK_ON_BEHALF_OF=1
Proxy-->>AI: mcp-session-id: <uuid>
AI->>Proxy: POST /mcp (tools/call)<br/>mcp-session-id: <uuid>
Proxy->>Sub: forward JSON-RPC (stdin)
Sub->>RM: GET /issues.json<br/>X-Redmine-Switch-User: rgara
RM-->>Sub: issues visible to rgara
Sub-->>Proxy: JSON-RPC result (stdout)
Proxy-->>AI: Streamable HTTP response
Step by step:
-
Identity resolution. On the first request of a session,
resolveUser(req)determines who the caller is, in priority order:Priority Source Sent by 1 x-redmine-userheaderExplicit override 2 x-ozwell-user-emailheaderOzwell AI platform (auto) 3 x-ozwell-user-nameheaderOzwell AI platform (auto) 4 ?user=query parameterManual / testing 5 Basic-auth username Clients that send credentials No identity → HTTP 401. Nobody gets an anonymous or admin session by accident.
-
Per-session subprocess. A new session spawns one backend MCP process (the Rust
redmine-mcp-rsbinary) with the environment locked to that user:env: { REDMINE_URL, REDMINE_API_KEY, // admin key, server-side only REDMINE_ON_BEHALF_OF: onBehalfOf, // the resolved user REDMINE_LOCK_ON_BEHALF_OF: "1", // AI cannot override this }REDMINE_LOCK_ON_BEHALF_OF=1is the security keystone: even if the model is tricked ("act as the admin", "switch to user X"), the backend ignores any per-call identity and stays pinned to the header-derived user. -
Bidirectional bridge. The proxy wires the two transports together:
StreamableHTTPServerTransport(facing the AI) ⇄StdioClientTransport(facing the subprocess). Messages are forwarded verbatim in both directions, so tool names and arguments pass through unchanged. -
Session tracking. Each session is keyed by the
mcp-session-idheader. Subsequent requests reuse the same subprocess; closing either transport tears down the pair and frees the process. -
Impersonation on the wire. The backend translates
REDMINE_ON_BEHALF_OFinto Redmine'sX-Redmine-Switch-Userheader on every REST call, so Redmine enforces that user's real permissions and ownership.
Configuration
Create /opt/redmine-mcp-proxy/.env (chmod 600 — it holds the admin key):
PORT=80
REDMINE_URL=https://pm.mieweb.com/
REDMINE_API_KEY=<redmine-admin-api-key>
| Variable | Required | Description |
|---|---|---|
PORT |
no | Listen port (default 80) |
REDMINE_URL |
yes | Redmine base URL |
REDMINE_API_KEY |
yes | Admin API key (needed for impersonation) |
The path to the backend binary is set in server.mjs (the command: field of
the StdioClientTransport). Swap it for node index.js to use the Node
redmine-mcp server instead of the
Rust one.
Run
npm install
node server.mjs
As a systemd service
/etc/systemd/system/redmine-mcp-proxy.service:
[Unit]
Description=Redmine MCP Streamable HTTP proxy
After=network.target
[Service]
Type=simple
User=pralambomanarivo
EnvironmentFile=/opt/redmine-mcp-proxy/.env
ExecStart=/usr/bin/node /opt/redmine-mcp-proxy/server.mjs
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now redmine-mcp-proxy
Binding to port 80 as a non-root user requires
sudo setcap cap_net_bind_service=+ep "$(command -v node)".
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
{ status, sessions, users } liveness + who's on |
POST/GET |
/mcp |
Streamable HTTP MCP endpoint |
DELETE |
/mcp |
Terminate a session (mcp-session-id header) |
curl -s https://rm.os.mieweb.org/health
# {"status":"ok","sessions":1,"users":["rgara@mieweb.com"]}
Security model
- Admin key never leaves the server. The AI platform only sends a user
identity header; the key lives in
.envand is injected into the subprocess. - Locked impersonation.
REDMINE_LOCK_ON_BEHALF_OF=1makes identity header-driven and prompt-injection-proof. - Fail-closed. No resolvable identity →
401, never an anonymous/admin fallback. - Least privilege on the wire. Redmine evaluates every request against the impersonated user's actual permissions.
Related
redmine-mcp-rs— the Rust MCP backend this proxy spawns (single static binary, fast startup).redmine-mcp— the original Node MCP server (fully compatible backend).
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.