mirror of
https://github.com/shadoll/redmine-mcp.git
synced 2026-08-28 03:27:56 +00:00
feat: add Bearer token authentication support for MCP
This commit is contained in:
@@ -60,6 +60,9 @@ Edit `.env`:
|
|||||||
```
|
```
|
||||||
REDMINE_URL=https://your.redmine.com
|
REDMINE_URL=https://your.redmine.com
|
||||||
REDMINE_API_KEY=your_personal_api_key_here
|
REDMINE_API_KEY=your_personal_api_key_here
|
||||||
|
|
||||||
|
# Optional: enable Bearer token auth for remote access
|
||||||
|
# MCP_AUTH_TOKEN=your_secret_token_here
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Run the container
|
### 3. Run the container
|
||||||
@@ -77,7 +80,12 @@ The server will be available at `http://localhost:8765`.
|
|||||||
### 4. Register with Claude Code
|
### 4. Register with Claude Code
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Without auth:
|
||||||
claude mcp add --scope user --transport http redmine http://localhost:8765/mcp
|
claude mcp add --scope user --transport http redmine http://localhost:8765/mcp
|
||||||
|
|
||||||
|
# With Bearer token auth:
|
||||||
|
claude mcp add --scope user --transport http redmine http://localhost:8765/mcp \
|
||||||
|
--header "Authorization: Bearer your_secret_token_here"
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify it was added:
|
Verify it was added:
|
||||||
@@ -98,7 +106,8 @@ curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
|||||||
```bash
|
```bash
|
||||||
helm install redmine-mcp ./helm \
|
helm install redmine-mcp ./helm \
|
||||||
--set redmine.url=https://your.redmine.com \
|
--set redmine.url=https://your.redmine.com \
|
||||||
--set redmine.apiKey=your_api_key
|
--set redmine.apiKey=your_api_key \
|
||||||
|
--set auth.token=your_secret_token_here # optional
|
||||||
```
|
```
|
||||||
|
|
||||||
**3. Register with Claude Code** (using port-forward):
|
**3. Register with Claude Code** (using port-forward):
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||||
|
from starlette.responses import Response
|
||||||
|
|
||||||
|
|
||||||
|
class BearerAuthMiddleware:
|
||||||
|
"""
|
||||||
|
ASGI middleware that enforces Bearer token authentication.
|
||||||
|
Only active when MCP_AUTH_TOKEN is set — if empty the request passes through.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app: ASGIApp, token: str) -> None:
|
||||||
|
self.app = app
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||||
|
if scope["type"] == "http":
|
||||||
|
headers = dict(scope.get("headers", []))
|
||||||
|
auth = headers.get(b"authorization", b"").decode()
|
||||||
|
if not auth.startswith("Bearer ") or auth[7:].strip() != self.token:
|
||||||
|
response = Response("Unauthorized", status_code=401, media_type="text/plain")
|
||||||
|
await response(scope, receive, send)
|
||||||
|
return
|
||||||
|
await self.app(scope, receive, send)
|
||||||
@@ -9,4 +9,5 @@ services:
|
|||||||
MCP_TRANSPORT: http
|
MCP_TRANSPORT: http
|
||||||
MCP_HOST: 0.0.0.0
|
MCP_HOST: 0.0.0.0
|
||||||
MCP_PORT: 8000
|
MCP_PORT: 8000
|
||||||
|
MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN:-}
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
|
|||||||
@@ -9,4 +9,7 @@ type: Opaque
|
|||||||
stringData:
|
stringData:
|
||||||
REDMINE_URL: {{ .Values.redmine.url | required "redmine.url is required" | quote }}
|
REDMINE_URL: {{ .Values.redmine.url | required "redmine.url is required" | quote }}
|
||||||
REDMINE_API_KEY: {{ .Values.redmine.apiKey | required "redmine.apiKey is required" | quote }}
|
REDMINE_API_KEY: {{ .Values.redmine.apiKey | required "redmine.apiKey is required" | quote }}
|
||||||
|
{{- if .Values.auth.token }}
|
||||||
|
MCP_AUTH_TOKEN: {{ .Values.auth.token | quote }}
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ redmine:
|
|||||||
# -- Or reference an existing Secret with keys REDMINE_URL and REDMINE_API_KEY
|
# -- Or reference an existing Secret with keys REDMINE_URL and REDMINE_API_KEY
|
||||||
existingSecret: ""
|
existingSecret: ""
|
||||||
|
|
||||||
|
auth:
|
||||||
|
# Bearer token for HTTP transport (leave empty to disable auth)
|
||||||
|
token: ""
|
||||||
|
|
||||||
service:
|
service:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
port: 8000
|
port: 8000
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ Environment variables:
|
|||||||
MCP_TRANSPORT — "stdio" (default) or "http"
|
MCP_TRANSPORT — "stdio" (default) or "http"
|
||||||
MCP_HOST — bind host for HTTP transport (default: 0.0.0.0)
|
MCP_HOST — bind host for HTTP transport (default: 0.0.0.0)
|
||||||
MCP_PORT — bind port for HTTP transport (default: 8000)
|
MCP_PORT — bind port for HTTP transport (default: 8000)
|
||||||
|
MCP_AUTH_TOKEN — Bearer token for HTTP transport auth (optional)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import uvicorn
|
||||||
from app.config import validate
|
from app.config import validate
|
||||||
from app import mcp # imports entities as a side effect via __init__
|
from app import mcp # imports entities as a side effect via __init__
|
||||||
|
|
||||||
@@ -18,8 +20,19 @@ if __name__ == "__main__":
|
|||||||
validate()
|
validate()
|
||||||
transport = os.environ.get("MCP_TRANSPORT", "stdio")
|
transport = os.environ.get("MCP_TRANSPORT", "stdio")
|
||||||
if transport == "http":
|
if transport == "http":
|
||||||
mcp.settings.host = os.environ.get("MCP_HOST", "0.0.0.0")
|
host = os.environ.get("MCP_HOST", "0.0.0.0")
|
||||||
mcp.settings.port = int(os.environ.get("MCP_PORT", "8000"))
|
port = int(os.environ.get("MCP_PORT", "8000"))
|
||||||
mcp.run(transport="streamable-http")
|
token = os.environ.get("MCP_AUTH_TOKEN", "")
|
||||||
|
|
||||||
|
app = mcp.streamable_http_app()
|
||||||
|
|
||||||
|
if token:
|
||||||
|
from app.auth import BearerAuthMiddleware
|
||||||
|
app = BearerAuthMiddleware(app, token)
|
||||||
|
print("Auth: Bearer token enabled.", flush=True)
|
||||||
|
else:
|
||||||
|
print("Auth: disabled (MCP_AUTH_TOKEN not set).", flush=True)
|
||||||
|
|
||||||
|
uvicorn.run(app, host=host, port=port)
|
||||||
else:
|
else:
|
||||||
mcp.run(transport="stdio")
|
mcp.run(transport="stdio")
|
||||||
|
|||||||
Reference in New Issue
Block a user