Skip to content

Commit 7a85c8a

Browse files
stackptrclaude
andauthored
feat(spore): add MCP reverse proxy with OAuth via Pocket ID (#407)
* feat(spore): add MCP reverse proxy with OAuth via Pocket ID Add mcp.zx.dev nginx vhost that proxies to MCPJungle on glyph with Bearer token authentication. Serves MCP-spec-compliant well-known endpoints and uses oauth2-proxy for JWT validation against Pocket ID. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * remove comments --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 712c7ec commit 7a85c8a

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

hosts/spore/services/web/auth.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,9 @@
3333
keyFile = config.age.secrets.oauth2-proxy-env.path;
3434
};
3535
};
36+
37+
services.oauth2-proxy.extraConfig = {
38+
skip-jwt-bearer-tokens = true;
39+
extra-jwt-issuers = "https://id.zx.dev=claude-mcp";
40+
};
3641
}

hosts/spore/services/web/default.nix

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,56 @@
117117
useACMEHost = "zx.dev";
118118
locations."/".proxyPass = "http://glyph.rove-duck.ts.net:8096";
119119
};
120+
"mcp.zx.dev" = {
121+
forceSSL = true;
122+
useACMEHost = "zx.dev";
123+
locations = {
124+
"= /.well-known/oauth-protected-resource" = {
125+
extraConfig = ''
126+
default_type application/json;
127+
add_header Access-Control-Allow-Origin '*' always;
128+
add_header Cache-Control 'public, max-age=3600' always;
129+
return 200 '${builtins.toJSON {
130+
resource = "https://mcp.zx.dev";
131+
authorization_servers = ["https://id.zx.dev"];
132+
scopes_supported = ["openid" "profile" "email"];
133+
bearer_methods_supported = ["header"];
134+
}}';
135+
'';
136+
};
137+
"= /oauth2/auth" = {
138+
proxyPass = "http://127.0.0.1:4180";
139+
extraConfig = ''
140+
proxy_set_header X-Original-URI $request_uri;
141+
proxy_set_header X-Real-IP $remote_addr;
142+
proxy_set_header X-Forwarded-Proto $scheme;
143+
proxy_set_header X-Forwarded-Host $host;
144+
proxy_set_header Content-Length "";
145+
proxy_pass_request_body off;
146+
'';
147+
};
148+
"@mcp_unauthorized" = {
149+
extraConfig = ''
150+
default_type application/json;
151+
add_header WWW-Authenticate 'Bearer resource_metadata="https://mcp.zx.dev/.well-known/oauth-protected-resource"' always;
152+
return 401 '{"error":"unauthorized","error_description":"Bearer token required"}';
153+
'';
154+
};
155+
"/" = {
156+
proxyPass = "http://glyph.rove-duck.ts.net:8090";
157+
extraConfig = ''
158+
auth_request /oauth2/auth;
159+
error_page 401 = @mcp_unauthorized;
160+
161+
# Pass auth info to upstream
162+
auth_request_set $auth_user $upstream_http_x_auth_request_user;
163+
auth_request_set $auth_email $upstream_http_x_auth_request_email;
164+
proxy_set_header X-Auth-User $auth_user;
165+
proxy_set_header X-Auth-Email $auth_email;
166+
'';
167+
};
168+
};
169+
};
120170
};
121171
};
122172

0 commit comments

Comments
 (0)