import { AUTH_SERVER_URL, MCP_PORT, MCP_SERVER_URL } from "./config";
// RFC 9728 OAuth Protected Resource Metadata
// https://datatracker.ietf.org/doc/html/rfc9728
export const protectedResourceMetadata = {
resource: MCP_SERVER_URL,
authorization_servers: [AUTH_SERVER_URL],
scopes_supported: ["gmail.readonly", "gmail.compose"],
bearer_methods_supported: ["header"],
resource_name: "Gmail MCP Server",
resource_documentation: "https://github.com/example/gmail-mcp-server",
};
// Helper to create 401 response with WWW-Authenticate header per RFC 9728 Section 5.1
export function unauthorized401Response(
error?: string,
errorDescription?: string,
): Response {
const resourceMetadataUrl = `${MCP_SERVER_URL}/.well-known/oauth-protected-resource`;
let wwwAuthenticate = `Bearer resource_metadata="${resourceMetadataUrl}"`;
if (error) {
wwwAuthenticate += `, error="${error}"`;
}
if (errorDescription) {
wwwAuthenticate += `, error_description="${errorDescription}"`;
}
return new Response(
JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: errorDescription || "Unauthorized",
},
id: null,
}),
{
status: 401,
headers: {
"Content-Type": "application/json",
"WWW-Authenticate": wwwAuthenticate,
},
},
);
}
export type TokenValidationResult = {
valid: boolean;
scopes?: string[];
googleAccessToken?: string;
};
// Validate bearer token against the auth server's introspection endpoint
export async function validateToken(
token: string,
): Promise<TokenValidationResult> {
try {
const response = await fetch(`${AUTH_SERVER_URL}/introspect`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({ token }),
});
if (!response.ok) {
return { valid: false };
}
const data = (await response.json()) as {
active?: boolean;
scope?: string;
google_access_token?: string;
};
if (data.active) {
return {
valid: true,
scopes: data.scope?.split(" ") || [],
googleAccessToken: data.google_access_token,
};
}
return { valid: false };
} catch {
return { valid: false };
}
}
// Get bearer token from auth header
export function extractBearerToken(authHeader: string | null): string | null {
if (!authHeader?.startsWith("Bearer ")) {
return null;
}
return authHeader.slice(7);
}
// Proxy request to auth server
export async function proxyToAuthServer(
req: Request,
url: URL,
): Promise<Response> {
const targetUrl = new URL(url.pathname + url.search, AUTH_SERVER_URL);
const response = await fetch(targetUrl.toString(), {
method: req.method,
headers: req.headers,
body: req.method !== "GET" ? req.body : undefined,
});
return new Response(response.body, {
status: response.status,
headers: response.headers,
});
}
// OAuth endpoints that should be proxied to auth server
export const OAUTH_PROXY_PATHS = ["/register", "/authorize", "/token"];
export { AUTH_SERVER_URL, MCP_PORT, MCP_SERVER_URL };