@lifeaitools/clauth 1.5.13 → 1.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/serve.js +20 -36
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -2880,17 +2880,19 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
2880
2880
|
|
|
2881
2881
|
// ── OAuth Discovery (RFC 9728 + RFC 8414) ──────────────
|
|
2882
2882
|
if (reqPath.startsWith("/.well-known/oauth-protected-resource")) {
|
|
2883
|
-
|
|
2884
|
-
//
|
|
2885
|
-
//
|
|
2886
|
-
// /.well-known/oauth-protected-resource/mcp → /mcp
|
|
2887
|
-
// /.well-known/oauth-protected-resource/gws → /gws
|
|
2888
|
-
// /.well-known/oauth-protected-resource/clauth → /clauth
|
|
2883
|
+
// Only advertise OAuth for /mcp — /gws and /clauth are open (no OAuth).
|
|
2884
|
+
// Advertising OAuth on open paths causes claude.ai to do an OAuth dance,
|
|
2885
|
+
// get a token, then have no retry context (since the original 200 wasn't a 401).
|
|
2889
2886
|
const suffix = reqPath.replace("/.well-known/oauth-protected-resource", "").replace(/^\//, "") || "mcp";
|
|
2890
|
-
|
|
2887
|
+
if (suffix !== "mcp" && suffix !== "") {
|
|
2888
|
+
// Path-specific OAuth metadata requested for a non-mcp path — 404 it
|
|
2889
|
+
res.writeHead(404, { "Content-Type": "application/json", ...CORS });
|
|
2890
|
+
return res.end(JSON.stringify({ error: "not_found" }));
|
|
2891
|
+
}
|
|
2892
|
+
const base = oauthBase();
|
|
2891
2893
|
res.writeHead(200, { "Content-Type": "application/json", ...CORS });
|
|
2892
2894
|
return res.end(JSON.stringify({
|
|
2893
|
-
resource: `${base}
|
|
2895
|
+
resource: `${base}/mcp`,
|
|
2894
2896
|
authorization_servers: [base],
|
|
2895
2897
|
scopes_supported: ["mcp:tools"],
|
|
2896
2898
|
bearer_methods_supported: ["header"],
|
|
@@ -3029,41 +3031,23 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3029
3031
|
return "clauth";
|
|
3030
3032
|
}
|
|
3031
3033
|
|
|
3032
|
-
// ── MCP
|
|
3033
|
-
//
|
|
3034
|
+
// ── MCP endpoint auth — log all incoming POSTs, accept with or without token ──
|
|
3035
|
+
// /gws and /clauth are open (no OAuth gate) — tunnel URL is the shared secret.
|
|
3036
|
+
// OAuth flow is still supported (tokens accepted when present) but not required.
|
|
3034
3037
|
if (method === "POST" && isMcpPath) {
|
|
3035
3038
|
const authHeader = req.headers.authorization;
|
|
3036
|
-
// Detailed auth logging — helps diagnose claude.ai post-token failures
|
|
3037
3039
|
const authLogMsg = [
|
|
3038
3040
|
`[${new Date().toISOString()}] MCP POST ${reqPath}`,
|
|
3039
3041
|
` Authorization: ${authHeader ? (authHeader.startsWith("Bearer ") ? `Bearer ${authHeader.slice(7, 15)}… (known=${oauthTokens.has(authHeader.slice(7))})` : authHeader.slice(0, 30) + "…") : "(none)"}`,
|
|
3040
3042
|
` mcp-protocol-version: ${req.headers["mcp-protocol-version"] || "(not set)"}`,
|
|
3041
3043
|
` accept: ${req.headers["accept"] || "(not set)"}`,
|
|
3042
|
-
` x-forwarded-for: ${req.headers["x-forwarded-for"] || "(not set)"}`,
|
|
3043
3044
|
].join("\n") + "\n";
|
|
3044
3045
|
try { fs.appendFileSync(LOG_FILE, authLogMsg); } catch {}
|
|
3045
|
-
|
|
3046
|
-
if (
|
|
3047
|
-
|
|
3048
|
-
// Path-specific resource metadata URL so claude.ai gets the right resource URI
|
|
3049
|
-
const pathName = reqPath === "/mcp" ? "mcp" : reqPath.slice(1);
|
|
3050
|
-
res.writeHead(401, {
|
|
3051
|
-
"Content-Type": "application/json",
|
|
3052
|
-
"WWW-Authenticate": `Bearer resource_metadata="${base}/.well-known/oauth-protected-resource/${pathName}"`,
|
|
3053
|
-
...CORS,
|
|
3054
|
-
});
|
|
3055
|
-
return res.end(JSON.stringify({ error: "unauthorized" }));
|
|
3056
|
-
}
|
|
3057
|
-
const token = authHeader.slice(7);
|
|
3058
|
-
if (!oauthTokens.has(token)) {
|
|
3059
|
-
const badTokenLog = `[${new Date().toISOString()}] OAuth: REJECTED token ${token.slice(0,8)}… (pool size=${oauthTokens.size})\n`;
|
|
3060
|
-
try { fs.appendFileSync(LOG_FILE, badTokenLog); } catch {}
|
|
3061
|
-
res.writeHead(401, { "Content-Type": "application/json", ...CORS });
|
|
3062
|
-
return res.end(JSON.stringify({ error: "invalid_token" }));
|
|
3046
|
+
// Mark as remote if a valid token is present (for vault access scoping)
|
|
3047
|
+
if (authHeader?.startsWith("Bearer ") && oauthTokens.has(authHeader.slice(7))) {
|
|
3048
|
+
req._clauthRemote = true;
|
|
3063
3049
|
}
|
|
3064
|
-
//
|
|
3065
|
-
req._clauthRemote = true;
|
|
3066
|
-
// fall through to MCP handling below
|
|
3050
|
+
// fall through to MCP handling — no 401 gate on these paths
|
|
3067
3051
|
}
|
|
3068
3052
|
|
|
3069
3053
|
// ── MCP Streamable HTTP transport (2025-03-26 spec) ──
|
|
@@ -3125,9 +3109,9 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3125
3109
|
return res.end(JSON.stringify({ jsonrpc: "2.0", id, error: { code: -32601, message: `Unknown method: ${rpcMethod}` } }));
|
|
3126
3110
|
}
|
|
3127
3111
|
|
|
3128
|
-
// ── MCP SSE transport
|
|
3129
|
-
// GET /sse — open SSE stream, receive endpoint event
|
|
3130
|
-
if (method === "GET" && reqPath === "/sse") {
|
|
3112
|
+
// ── MCP SSE transport — /sse and namespaced paths ────
|
|
3113
|
+
// GET /sse|/gws|/clauth — open SSE stream, receive endpoint event
|
|
3114
|
+
if (method === "GET" && (reqPath === "/sse" || isMcpPath)) {
|
|
3131
3115
|
const sessionId = `ses_${++sseCounter}_${Date.now()}`;
|
|
3132
3116
|
|
|
3133
3117
|
res.writeHead(200, {
|