Mas cosas

This commit is contained in:
Jordan Diaz
2026-05-06 07:07:57 +00:00
parent 8875cb29cb
commit 06ce51a9c1
9 changed files with 643 additions and 80 deletions

View File

@@ -77,14 +77,17 @@ export async function validateMcpToken(secret) {
} catch {
return null;
}
if (!meta || !meta.user || !meta.project) return null;
// Solo exigimos `user`. `project` puede ser "" (token user-wide que
// autoriza todos los proyectos del usuario, ver handlers/mcp_tokens.py
// del backend Python para los detalles del modelo).
if (!meta || !meta.user) return null;
// Actualizacion asincrona de lastUsedAt — no bloqueamos la request.
updateLastUsedAt(key, meta).catch((e) => {
console.error("[mcp-tokens] lastUsedAt update failed:", e.message);
});
return { user: meta.user, project: meta.project, id: meta.id || "" };
return { user: meta.user, project: meta.project || "", id: meta.id || "" };
}
async function updateLastUsedAt(key, meta) {

View File

@@ -195,6 +195,13 @@ export function startHttpServer() {
// identifica manualmente con X-Acai-User + X-Project-Name).
//=============================================================================
app.use(async (req, res, next) => {
// DEBUG temporal: loguear TODA request que llegue. Quitar cuando este
// claro el flujo del cliente.
const secretPresent = !!req.headers["x-mcp-secret"];
const authPresent = !!req.headers["authorization"];
console.error(
`[MCP req] ${req.method} ${req.url} - X-MCP-Secret=${secretPresent ? "yes" : "MISSING"}, Authorization=${authPresent ? "yes" : "MISSING"}, UA=${(req.headers["user-agent"] || "").substring(0, 60)}`,
);
const secret = req.headers["x-mcp-secret"];
if (!secret) {
return next();
@@ -202,6 +209,7 @@ export function startHttpServer() {
try {
const auth = await validateMcpToken(secret);
if (!auth) {
console.error("[MCP middleware] Invalid X-MCP-Secret rejected");
res.status(401)
.setHeader("Content-Type", "application/json")
.end(JSON.stringify({ error: "Invalid MCP token" }));
@@ -209,7 +217,18 @@ export function startHttpServer() {
}
// Sobrescribe los headers de identidad con los del token validado.
req.headers["x-acai-user"] = auth.user;
req.headers["x-project-name"] = auth.project;
// `auth.project` solo se sobrescribe si el token es project-scoped.
// Si es user-wide (auth.project === ""), preservamos el
// `X-Project-Name` que el cliente envio (la extension VS Code
// Acai Forge lo manda con el slug del proyecto descargado).
if (auth.project) {
req.headers["x-project-name"] = auth.project;
}
console.error(
`[MCP middleware] Auth OK user=${auth.user} ` +
`tokenScope=${auth.project || "user-wide"} ` +
`clientProject=${req.headers["x-project-name"] || "(none)"}`,
);
return next();
} catch (err) {
console.error("[MCP] mcpSecretMiddleware error:", err.message);
@@ -580,11 +599,42 @@ export function startHttpServer() {
});
//=============================================================================
// OAUTH2 ENDPOINTS
// OAUTH2 ENDPOINTS — DESHABILITADOS
//=============================================================================
// El flujo OAuth se diseno a medida (client_secret = nombre de proyecto)
// y no funciona con clientes MCP estandar (Claude Code, etc.) que usan
// PKCE puro. El unico cliente "oficial" es la extension VS Code Acai
// Forge, que NO usa OAuth — autentica con header X-MCP-Secret directo.
//
// Devolver 404 en `.well-known/oauth-authorization-server` hace que los
// clientes que hacen OAuth discovery hagan fallback a header auth, lo
// cual usa X-MCP-Secret (validado en el middleware de las lineas ~197).
// Los handlers `/register`, `/authorize`, `/token` y los helpers `signJwt`
// / `verifyJwt` / `resolveProjectCredentials` se mantienen porque son
// usados internamente por el transport SSE legacy (lineas ~113, ~265).
//=============================================================================
// OAuth2 Authorization Server Metadata endpoint (per RFC8414)
app.get('/.well-known/oauth-authorization-server', (req, res) => {
// Rutas OAuth/OIDC discovery deshabilitadas — devuelven 404 JSON limpio
// para que el cliente fallback a header auth (X-MCP-Secret) en vez de
// intentar OAuth flow. Cubrimos ambas paths comunes y sus variantes
// anidadas bajo /mcp/ porque algunos clientes (Claude Code) prueban
// ambas: en la raiz Y bajo el endpoint MCP.
const _disabledOauthPaths = [
'/.well-known/oauth-authorization-server',
'/.well-known/openid-configuration',
'/.well-known/oauth-protected-resource',
'/mcp/.well-known/oauth-authorization-server',
'/mcp/.well-known/openid-configuration',
'/mcp/.well-known/oauth-protected-resource',
];
for (const _p of _disabledOauthPaths) {
app.get(_p, (req, res) => {
res.status(404).json({ error: "OAuth not available; use X-MCP-Secret header" });
});
}
// OAuth2 Authorization Server Metadata endpoint (per RFC8414) — REMOVED
app.get('/.well-known/oauth-authorization-server-DISABLED', (req, res) => {
const baseUrl = `https://${req.headers.host}`;
res.json({
issuer: baseUrl,