feat: opt-in shared-secret auth on HTTP/SSE transport - #61
Open
Nshuti7 wants to merge 5 commits into
Open
Conversation
Add MCP_AUTH_TOKEN as an optional env var. When set, every request to /mcp, /sse, and /messages must present the token as either 'Authorization: Bearer <token>' (RFC 6750-clean, preferred) or '?token=<token>' query param. The query-string path exists so this server works with MCP client UIs that only accept a URL — the Cowork 'Add custom connector' dialog and equivalents — which have no way to attach a custom header. Comparison is timing-safe. When MCP_AUTH_TOKEN is unset, behavior is unchanged (endpoints open) and a startup warning is logged. /health stays open regardless so orchestrators can probe.
Move the shared-secret gate into src/utils/auth.ts as a makeAuthMiddleware(token) factory so it can be exercised directly with different token values, without module-load env-var gymnastics. Add 12 vitest cases covering: unset/empty token (no-op), header-only accept/reject, query-only accept/reject, mismatched Authorization scheme, wrong-length token (guards the timing-safe compare), and header-and-query both present.
Without this, setting MCP_AUTH_TOKEN in the shell (or in Dokploy's environment editor) wouldn't reach the running process and the gate would be silently disabled.
Pre-existing upstream issue (Dokploy#59, PR Dokploy#60): the Dockerfile COPYs pnpm-lock.yaml but .dockerignore excludes it, so builds fail from a fresh clone. Also pin pnpm to 10.33.0 to avoid 'latest' drift between build stages, and include .npmrc so registry/config is consistent with the local install. Mirrors the upstream fix from Dokploy#60.
Publishing 3000:3000 on the host conflicts with Dokploy's own UI (which binds :3000) and adds nothing when a reverse proxy is in front — the proxy reaches the container over the shared Docker network on its internal port. Left commented for anyone running this compose standalone without a proxy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MCP_AUTH_TOKENenv var. When set,/mcp,/sse, and/messagesrequire the token as eitherAuthorization: Bearer <token>(RFC 6750-clean, preferred) or?token=<token>query param.auth: "bearer token required"or a warning when unset./healthstays open so container orchestrators can probe.src/utils/auth.tsasmakeAuthMiddleware(token), exercised by 12 vitest cases.Why
Exposing the HTTP transport publicly today means anyone who can reach the URL can call every tool against the linked Dokploy instance, including destructive ones. IP-allowlisting at a reverse proxy works, but breaks down the moment the client's egress isn't a stable set of IPs — which is the case for hosted Claude clients (Cowork's "Add custom connector" dialog, Claude Desktop's URL-based custom connectors, and similar). Those UIs also can't attach a custom
Authorizationheader, which is why the query-string form exists alongside the header form.Same shape as #38 (opt-in security feature via env var, backwards compatible) and #40 (opt-in HTTP-transport enabler for hosted deployments).
RFC 6750 note on the query-string form
RFC 6750 discourages tokens in URLs because they can leak via server access logs, referer headers, or browser history. None of those apply here: the endpoint is only ever POST'd by an MCP client (never navigated in a browser), and the server does not log request URLs. The header form remains preferred; the query form exists specifically for URL-only client UIs where it's the only path to any auth at all. Documented in the code and README.
Test cases covered
WWW-Authenticate: Bearer realm="dokploy-mcp"and a JSON-RPC-32001 UnauthorizedbodyAuthorization: Bearer→ 200Authorization: Bearer→ 401?token=→ 200?token=→ 401Verification
pnpm test— 37/37 pass (12 new for auth middleware)pnpm type-check— cleanpnpm lint— clean (1 pre-existing warning unrelated to this change)pnpm build— succeeds;build/utils/auth.jsproduced/healthopen (200), no/bad token → 401 on/mcp, valid header → 200, valid?token=→ 200@Siumauricio @andradehenrique — would appreciate a look when time allows. Backwards-compatible opt-in and I'd love to stop maintaining a fork for this.