#!/usr/bin/env node

/**
 * Figma Console MCP Server
 * Entry point for the MCP server that enables AI assistants to access
 * Figma plugin console logs and screenshots.
 *
 * This implementation uses Cloudflare's McpAgent pattern for deployment
 * on Cloudflare Workers with Browser Rendering API support.
 */

import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import { z } from "zod";
import { BrowserManager, type Env } from "./browser-manager.js";
import { ConsoleMonitor } from "./core/console-monitor.js";
import { getConfig } from "./core/config.js";
import { createChildLogger } from "./core/logger.js";
import { FigmaAPI, extractFileKey, formatVariables, formatComponentData } from "./core/figma-api.js";
import { registerFigmaAPITools } from "./core/figma-tools.js";
import { registerDesignCodeTools } from "./core/design-code-tools.js";
import { registerCommentTools } from "./core/comment-tools.js";
import { registerVersionTools } from "./core/version-tools.js";
import { registerAnnotationTools } from "./core/annotation-tools.js";
import { registerDeepComponentTools } from "./core/deep-component-tools.js";
import { registerDesignSystemTools } from "./core/design-system-tools.js";
import { registerAccessibilityTools } from "./core/accessibility-tools.js";
import { registerDiagnoseTool } from "./core/diagnose-tool.js";
import { wrapServerForIdentity } from "./core/identity.js";
import { PluginRelayDO, generatePairingCode } from "./core/cloud-websocket-relay.js";
import { CloudWebSocketConnector } from "./core/cloud-websocket-connector.js";
import { registerWriteTools } from "./core/write-tools.js";
import { registerTokensTools } from "./core/tokens-tools.js";
import { registerFigJamTools } from "./core/figjam-tools.js";
import { registerSlidesTools } from "./core/slides-tools.js";

// Re-export PluginRelayDO so Cloudflare Workers can bind it as a Durable Object
export { PluginRelayDO } from "./core/cloud-websocket-relay.js";
// Note: MCP Apps (Token Browser, Dashboard) are only available in local mode
// They require Node.js file system APIs for serving HTML that don't work in Cloudflare Workers

const logger = createChildLogger({ component: "mcp-server" });

/**
 * Validate a Figma Personal Access Token (PAT) by calling the Figma API.
 * PATs start with 'figd_' and require the X-Figma-Token header (not Bearer).
 * Returns the user info if valid, null if invalid/expired.
 */
async function validateFigmaPAT(token: string): Promise<{ id: string; handle: string; email: string } | null> {
	try {
		const response = await fetch("https://api.figma.com/v1/me", {
			headers: { "X-Figma-Token": token },
		});
		if (!response.ok) return null;
		const data = await response.json() as { id: string; handle: string; email: string };
		return data?.id ? data : null;
	} catch {
		return null;
	}
}

/**
 * Check if a token is a Figma Personal Access Token.
 * PATs start with 'figd_' — OAuth tokens start with 'figu_'.
 */
function isFigmaPAT(token: string): boolean {
	return token.startsWith("figd_");
}

/**
 * Figma Console MCP Agent
 * Extends McpAgent to provide Figma-specific debugging tools
 */
export class FigmaConsoleMCPv3 extends McpAgent {
	server = (() => {
		const s = new McpServer({
			name: "Figma Console MCP",
			version: "1.28.1",
		});
		// Identity wrap — every tool's response and thrown error gets stamped
		// with our MCP name so cross-MCP attribution is unambiguous.
		wrapServerForIdentity(s);
		return s;
	})();

	private browserManager: BrowserManager | null = null;
	private consoleMonitor: ConsoleMonitor | null = null;
	private figmaAPI: FigmaAPI | null = null;
	private config = getConfig();
	private sessionId: string | null = null;

	/**
	 * Refresh an expired OAuth token using the refresh token
	 */
	private async refreshOAuthToken(sessionId: string, refreshToken: string): Promise<{
		accessToken: string;
		refreshToken?: string;
		expiresAt: number;
	}> {
		const env = this.env as Env;

		if (!env.FIGMA_OAUTH_CLIENT_ID || !env.FIGMA_OAUTH_CLIENT_SECRET) {
			throw new Error("OAuth not configured on server");
		}

		logger.info({ sessionId }, "Attempting to refresh OAuth token");

		const credentials = btoa(`${env.FIGMA_OAUTH_CLIENT_ID}:${env.FIGMA_OAUTH_CLIENT_SECRET}`);

		const tokenParams = new URLSearchParams({
			grant_type: "refresh_token",
			refresh_token: refreshToken
		});

		const tokenResponse = await fetch("https://api.figma.com/v1/oauth/token", {
			method: "POST",
			headers: {
				"Content-Type": "application/x-www-form-urlencoded",
				"Authorization": `Basic ${credentials}`
			},
			body: tokenParams.toString()
		});

		if (!tokenResponse.ok) {
			const errorData = await tokenResponse.json().catch(() => ({}));
			logger.error({ errorData, status: tokenResponse.status }, "Token refresh failed");
			throw new Error(`Token refresh failed: ${JSON.stringify(errorData)}`);
		}

		const tokenData = await tokenResponse.json() as {
			access_token: string;
			refresh_token?: string;
			expires_in: number;
		};

		// Store refreshed token in KV
		const tokenKey = `oauth_token:${sessionId}`;
		const storedToken = {
			accessToken: tokenData.access_token,
			refreshToken: tokenData.refresh_token || refreshToken, // Use new refresh token or keep existing
			expiresAt: Date.now() + (tokenData.expires_in * 1000)
		};

		await env.OAUTH_TOKENS.put(tokenKey, JSON.stringify(storedToken), {
			expirationTtl: tokenData.expires_in
		});

		// Store reverse lookup for Bearer token validation on SSE endpoint
		const bearerKey = `bearer_token:${tokenData.access_token}`;
		await env.OAUTH_TOKENS.put(bearerKey, JSON.stringify({
			sessionId,
			expiresAt: storedToken.expiresAt
		}), {
			expirationTtl: tokenData.expires_in
		});

		logger.info({ sessionId }, "OAuth token refreshed successfully");

		return storedToken;
	}

	/**
	 * Generate a cryptographically secure random state token for CSRF protection
	 */
	public static generateStateToken(): string {
		const array = new Uint8Array(32);
		crypto.getRandomValues(array);
		return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
	}

	/**
	 * Load or create persistent session ID from Durable Object storage
	 * Uses a fixed session ID for the MCP server to ensure OAuth tokens persist across reconnections
	 */
	private async ensureSessionId(): Promise<void> {
		if (this.sessionId) {
			return; // Already loaded
		}

		// IMPORTANT: Use a fixed session ID for all MCP connections
		// This ensures OAuth tokens persist across MCP server reconnections
		// Each user of this MCP server will share the same OAuth token
		const FIXED_SESSION_ID = "figma-console-mcp-default-session";

		// Try to load from Durable Object storage
		// @ts-ignore - this.ctx is available in Durable Object context
		const storage = this.ctx?.storage;

		if (storage) {
			try {
				const storedSessionId = await storage.get<string>('sessionId');
				if (storedSessionId) {
					this.sessionId = storedSessionId;
					logger.info({ sessionId: this.sessionId }, "Loaded persistent session ID from storage");
					return;
				} else {
					// Store the fixed session ID
					this.sessionId = FIXED_SESSION_ID;
					await storage.put('sessionId', this.sessionId);
					logger.info({ sessionId: this.sessionId }, "Initialized fixed session ID");
					return;
				}
			} catch (e) {
				logger.warn({ error: e }, "Failed to access Durable Object storage for session ID");
			}
		}

		// Fallback: use fixed session ID directly
		this.sessionId = FIXED_SESSION_ID;
		logger.info({ sessionId: this.sessionId }, "Using fixed session ID (storage unavailable)");
	}

	/**
	 * Get session ID for this Durable Object instance
	 * Returns the session ID loaded by ensureSessionId()
	 */
	public getSessionId(): string {
		if (!this.sessionId) {
			// This shouldn't happen if ensureSessionId() was called, but provide fallback
			this.sessionId = FigmaConsoleMCPv3.generateStateToken();
			logger.warn({ sessionId: this.sessionId }, "Session ID not initialized, generated ephemeral ID");
		}
		return this.sessionId;
	}

	/**
	 * Get or create Figma API client with OAuth token from session
	 */
	private async getFigmaAPI(): Promise<FigmaAPI> {
		// Ensure session ID is loaded from storage
		await this.ensureSessionId();

		// @ts-ignore - this.env is available in Agent/Durable Object context
		const env = this.env as Env;

		// Try OAuth first (per-user authentication)
		try {
			const sessionId = this.getSessionId();
			logger.info({ sessionId }, "Attempting to retrieve OAuth token from KV");

			// Retrieve token from KV (accessible across all Durable Object instances)
			const tokenKey = `oauth_token:${sessionId}`;
			const tokenJson = await env.OAUTH_TOKENS.get(tokenKey);

			if (!tokenJson) {
				logger.warn({ sessionId, tokenKey }, "No OAuth token found in KV");
				throw new Error("No token found");
			}

			let tokenData = JSON.parse(tokenJson) as {
				accessToken: string;
				refreshToken?: string;
				expiresAt: number;
			};

			logger.info({
				sessionId,
				hasToken: !!tokenData?.accessToken,
				expiresAt: tokenData?.expiresAt,
				isExpired: tokenData?.expiresAt ? Date.now() > tokenData.expiresAt : null
			}, "Token retrieval result from KV");

			if (tokenData?.accessToken) {
				// Check if token is expired or will expire soon (within 5 minutes)
				const isExpired = tokenData.expiresAt && Date.now() > tokenData.expiresAt;
				const willExpireSoon = tokenData.expiresAt && Date.now() > (tokenData.expiresAt - 5 * 60 * 1000);

				if (isExpired || willExpireSoon) {
					if (tokenData.refreshToken) {
						try {
							// Attempt to refresh the token
							tokenData = await this.refreshOAuthToken(sessionId, tokenData.refreshToken);
							logger.info({ sessionId }, "Successfully refreshed expired/expiring token");
						} catch (refreshError) {
							logger.error({ sessionId, refreshError }, "Failed to refresh token");
							throw new Error("Token expired and refresh failed. Please re-authenticate.");
						}
					} else {
						logger.warn({ sessionId }, "Token expired but no refresh token available");
						throw new Error("Token expired. Please re-authenticate.");
					}
				}

				logger.info({ sessionId }, "Using OAuth token from KV for Figma API");
				return new FigmaAPI({ accessToken: tokenData.accessToken });
			}

			logger.warn({ sessionId }, "OAuth token exists in KV but missing accessToken");
			throw new Error("Invalid token data");
		} catch (error) {
			const errorMessage = error instanceof Error ? error.message : String(error);
			const sessionId = this.getSessionId();

			// Check if this is a "no token found" error (user hasn't authenticated yet)
			if (errorMessage.includes("No token found")) {
				logger.info({ sessionId }, "No OAuth token found - user needs to authenticate");

				// No authentication available - direct user to OAuth flow
				const authUrl = `https://figma-console-mcp.southleft.com/oauth/authorize?session_id=${sessionId}`;

				// Only use PAT fallback if explicitly configured AND no OAuth token exists
				if (env?.FIGMA_ACCESS_TOKEN) {
					logger.warn(
						"FIGMA_ACCESS_TOKEN fallback is deprecated. User should authenticate via OAuth for proper per-user authentication."
					);
					return new FigmaAPI({ accessToken: env.FIGMA_ACCESS_TOKEN });
				}

				throw new Error(
					JSON.stringify({
						error: "authentication_required",
						message: "Please authenticate with Figma to use API features",
						auth_url: authUrl,
						instructions: "Your browser will open automatically to complete authentication. If it doesn't, copy the auth_url and open it manually."
					})
				);
			}

			// For other OAuth errors (expired token, refresh failed, etc.), do NOT fall back to PAT
			logger.error({ error, sessionId }, "OAuth token retrieval failed - re-authentication required");

			const authUrl = `https://figma-console-mcp.southleft.com/oauth/authorize?session_id=${sessionId}`;

			throw new Error(
				JSON.stringify({
					error: "oauth_error",
					message: errorMessage,
					auth_url: authUrl,
					instructions: "Please re-authenticate with Figma. Your browser will open automatically."
				})
			);
		}
	}

	/**
	 * Initialize browser and console monitoring
	 */
	private async ensureInitialized(): Promise<void> {
		try {
			// Ensure session ID is loaded from storage first
			await this.ensureSessionId();

			if (!this.browserManager) {
				logger.info("Initializing BrowserManager");

				// Access env from Durable Object context
				// @ts-ignore - this.env is available in Agent/Durable Object context
				const env = this.env as Env;

				if (!env) {
					throw new Error("Environment not available - this.env is undefined");
				}

				if (!env.BROWSER) {
					throw new Error("BROWSER binding not found in environment. Check wrangler.jsonc configuration.");
				}

				logger.info("Creating BrowserManager with BROWSER binding");
				this.browserManager = new BrowserManager(env, this.config.browser);
			}

			if (!this.consoleMonitor) {
				logger.info("Initializing ConsoleMonitor");
				this.consoleMonitor = new ConsoleMonitor(this.config.console);

				// Start browser and begin monitoring
				logger.info("Getting browser page");
				const page = await this.browserManager.getPage();

				logger.info("Starting console monitoring");
				await this.consoleMonitor.startMonitoring(page);

				logger.info("Browser and console monitor initialized successfully");
			}
		} catch (error) {
			logger.error({ error }, "Failed to initialize browser/monitor");
			throw new Error(`Initialization failed: ${error instanceof Error ? error.message : String(error)}`);
		}
	}

	async init() {
		// Tool 1: Get Console Logs
		this.server.tool(
			"figma_get_console_logs",
			"Retrieve console logs from a Cloudflare Browser Rendering session opened with figma_navigate. Captures plugin console output ([Main], [Swapper], etc. prefixes) and page-level logs. NOTE: cloud mode does NOT currently capture logs from a paired Desktop Bridge plugin — for plugin sandbox logs, use Local mode. Call figma_navigate first to open the file and start browser monitoring.",
			{
				count: z.number().optional().default(100).describe("Number of recent logs to retrieve"),
				level: z
					.enum(["log", "info", "warn", "error", "debug", "all"])
					.optional()
					.default("all")
					.describe("Filter by log level"),
				since: z
					.number()
					.optional()
					.describe("Only logs after this timestamp (Unix ms)"),
			},
			async ({ count, level, since }) => {
				try {
					await this.ensureInitialized();

					if (!this.consoleMonitor) {
						throw new Error("Console monitor not initialized");
					}

					const logs = this.consoleMonitor.getLogs({
						count,
						level,
						since,
					});

					// Add AI instruction when no logs are found
					const responseData: any = {
						logs,
						totalCount: logs.length,
						oldestTimestamp: logs[0]?.timestamp,
						newestTimestamp: logs[logs.length - 1]?.timestamp,
						status: this.consoleMonitor.getStatus(),
					};

					// If no logs found, add helpful AI instruction
					if (logs.length === 0) {
						responseData.ai_instruction = "No console logs found. This usually means the Figma plugin hasn't run since monitoring started. Please inform the user: 'No console logs found yet. Try running your Figma plugin now, then I'll check for logs again.' The MCP only captures logs AFTER monitoring starts - it cannot retrieve historical logs from before the browser connected.";
					}

					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									responseData,
									null,
									2,
								),
							},
						],
					};
				} catch (error) {
					logger.error({ error }, "Failed to get console logs");
					const errorMessage = error instanceof Error ? error.message : String(error);
					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										error: errorMessage,
										message: "Failed to retrieve console logs. Make sure to call figma_navigate first to initialize the browser.",
										hint: "Try: figma_navigate({ url: 'https://www.figma.com/design/your-file' })",
									},
									null,
									2,
								),
							},
						],
						isError: true,
					};
				}
			},
		);

		// Tool 2: Take Screenshot (using Figma REST API)
		// Note: For screenshots of specific components, use figma_get_component_image instead
		this.server.tool(
			"figma_take_screenshot",
			"Export an image of the currently viewed Figma page or specific node using Figma's REST API. Returns an image URL (valid for 30 days). For specific components, use figma_get_component_image instead.",
			{
				nodeId: z
					.string()
					.optional()
					.describe("Optional node ID to screenshot (e.g., '123:456'). If omitted, uses the node-id query param from the Cloudflare Browser Rendering page's current URL."),
				scale: z
					.number()
					.min(0.01)
					.max(4)
					.optional()
					.default(2)
					.describe("Image scale factor (0.01-4, default: 2 for high quality)"),
				format: z
					.enum(["png", "jpg", "svg", "pdf"])
					.optional()
					.default("png")
					.describe("Image format (default: png)"),
			},
			async ({ nodeId, scale, format }) => {
				try {
					const api = await this.getFigmaAPI();

					// Get current URL to extract file key and node ID if not provided
					const currentUrl = this.browserManager?.getCurrentUrl() || null;

					if (!currentUrl) {
						throw new Error(
							"No Figma file open. Either provide a nodeId parameter or call figma_navigate first to open a Figma file."
						);
					}

					const fileKey = extractFileKey(currentUrl);
					if (!fileKey) {
						throw new Error(`Invalid Figma URL: ${currentUrl}`);
					}

					// Extract node ID from URL if not provided
					let targetNodeId = nodeId;
					if (!targetNodeId) {
						const urlObj = new URL(currentUrl);
						const nodeIdParam = urlObj.searchParams.get('node-id');
						if (nodeIdParam) {
							// Convert 123-456 to 123:456
							targetNodeId = nodeIdParam.replace(/-/g, ':');
						} else {
							throw new Error(
								"No node ID found. Either provide nodeId parameter or ensure the Figma URL contains a node-id parameter (e.g., ?node-id=123-456)"
							);
						}
					}

					logger.info({ fileKey, nodeId: targetNodeId, scale, format }, "Rendering image via Figma API");

					// Use Figma REST API to get image
					const result = await api.getImages(fileKey, targetNodeId, {
						scale,
						format: format === 'jpg' ? 'jpg' : format, // normalize jpeg -> jpg
						contents_only: true,
					});

					const imageUrl = result.images[targetNodeId];

					if (!imageUrl) {
						throw new Error(
							`Failed to render image for node ${targetNodeId}. The node may not exist or may not be renderable.`
						);
					}

					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										fileKey,
										nodeId: targetNodeId,
										imageUrl,
										scale,
										format,
										expiresIn: "30 days",
										note: "Image URL provided above. Use this URL to view or download the screenshot. URLs expire after 30 days.",
									},
									null,
									2
								),
							},
						],
					};
				} catch (error) {
					logger.error({ error }, "Failed to capture screenshot");
					const errorMessage = error instanceof Error ? error.message : String(error);
					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										error: errorMessage,
										message: "Failed to capture screenshot via Figma API",
										hint: "Make sure you've called figma_navigate to open a file, or provide a valid nodeId parameter",
									},
									null,
									2
								),
							},
						],
						isError: true,
					};
				}
			},
		);

		// Tool 3: Watch Console (Real-time streaming)
		this.server.tool(
			"figma_watch_console",
			{
				duration: z
					.number()
					.optional()
					.default(30)
					.describe("How long to watch in seconds"),
				level: z
					.enum(["log", "info", "warn", "error", "debug", "all"])
					.optional()
					.default("all")
					.describe("Filter by log level"),
			},
			async ({ duration, level }) => {
				await this.ensureInitialized();

				if (!this.consoleMonitor) {
					throw new Error("Console monitor not initialized. Call figma_navigate first.");
				}

				const consoleMonitor = this.consoleMonitor;

				if (!consoleMonitor.getStatus().isMonitoring) {
					throw new Error("Console monitoring not active. Call figma_navigate first.");
				}

				const startTime = Date.now();
				const endTime = startTime + duration * 1000;
				const startLogCount = consoleMonitor.getStatus().logCount;

				// Wait for the specified duration while collecting logs
				await new Promise(resolve => setTimeout(resolve, duration * 1000));

				// Get logs captured during watch period
				const watchedLogs = consoleMonitor.getLogs({
					level: level === 'all' ? undefined : level,
					since: startTime,
				});

				const endLogCount = consoleMonitor.getStatus().logCount;
				const newLogsCount = endLogCount - startLogCount;

				return {
					content: [
						{
							type: "text",
							text: JSON.stringify(
								{
									status: "completed",
									duration: `${duration} seconds`,
									startTime: new Date(startTime).toISOString(),
									endTime: new Date(endTime).toISOString(),
									filter: level,
									statistics: {
										totalLogsInBuffer: endLogCount,
										logsAddedDuringWatch: newLogsCount,
										logsMatchingFilter: watchedLogs.length,
									},
									logs: watchedLogs,
								},
								null,
								2,
							),
						},
					],
				};
			},
		);

		// Tool 4: Reload Plugin
		this.server.tool(
			"figma_reload_plugin",
			{
				clearConsole: z
					.boolean()
					.optional()
					.default(true)
					.describe("Clear console logs before reload"),
			},
			async ({ clearConsole: clearConsoleBefore }) => {
				try {
					await this.ensureInitialized();

					if (!this.browserManager) {
						throw new Error("Browser manager not initialized");
					}

					// Clear console buffer if requested
					let clearedCount = 0;
					if (clearConsoleBefore && this.consoleMonitor) {
						clearedCount = this.consoleMonitor.clear();
					}

					// Reload the page
					await this.browserManager.reload();

					const currentUrl = this.browserManager.getCurrentUrl();

					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										status: "reloaded",
										timestamp: Date.now(),
										url: currentUrl,
										consoleCleared: clearConsoleBefore,
										clearedCount: clearConsoleBefore ? clearedCount : 0,
									},
									null,
									2,
								),
							},
						],
					};
				} catch (error) {
					logger.error({ error }, "Failed to reload plugin");
					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										error: String(error),
										message: "Failed to reload plugin",
									},
									null,
									2,
								),
							},
						],
						isError: true,
					};
				}
			},
		);

		// Tool 5: Clear Console
		this.server.tool(
			"figma_clear_console",
			{},
			async () => {
				try {
					await this.ensureInitialized();

					if (!this.consoleMonitor) {
						throw new Error("Console monitor not initialized");
					}

					const clearedCount = this.consoleMonitor.clear();

					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										status: "cleared",
										clearedCount,
										timestamp: Date.now(),
										ai_instruction:
											"CRITICAL: Console cleared successfully, but this operation disrupts the monitoring connection. You MUST reconnect the MCP server using `/mcp reconnect figma-console` before calling figma_get_console_logs again. Best practice: Avoid clearing console - filter/parse logs instead to maintain monitoring connection.",
									},
									null,
									2,
								),
							},
						],
					};
				} catch (error) {
					logger.error({ error }, "Failed to clear console");
					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										error: String(error),
										message: "Failed to clear console buffer",
									},
									null,
									2,
								),
							},
						],
						isError: true,
					};
				}
			},
		);

		// Tool 6: Navigate to Figma
		this.server.tool(
			"figma_navigate",
			{
				url: z
					.string()
					.url()
					.describe(
						"Figma URL to navigate to (e.g., https://www.figma.com/design/abc123)",
					),
			},
			async ({ url }) => {
				try {
					await this.ensureInitialized();

					if (!this.browserManager) {
						throw new Error("Browser manager not initialized");
					}

					// Navigate to the URL (may switch to existing tab in local mode)
					const result = await this.browserManager.navigateToFigma(url);

					if (result.action === 'switched_to_existing') {
						// Switch console monitor to the page
						if (this.consoleMonitor) {
							this.consoleMonitor.stopMonitoring();
							await this.consoleMonitor.startMonitoring(result.page);
						}

						const currentUrl = this.browserManager.getCurrentUrl();

						return {
							content: [
								{
									type: "text",
									text: JSON.stringify(
										{
											status: "switched_to_existing",
											url: currentUrl,
											timestamp: Date.now(),
											message: "Switched to existing tab for this Figma file. Console monitoring is active.",
										},
										null,
										2,
									),
								},
							],
						};
					}

					// Give page time to load and start capturing logs
					await new Promise((resolve) => setTimeout(resolve, 2000));

					const currentUrl = this.browserManager.getCurrentUrl();

					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										status: "navigated",
										url: currentUrl,
										timestamp: Date.now(),
										message: "Browser navigated to Figma. Console monitoring is active.",
									},
									null,
									2,
								),
							},
						],
					};
				} catch (error) {
					logger.error({ error }, "Failed to navigate to Figma");
					const errorMessage = error instanceof Error ? error.message : String(error);
					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										error: errorMessage,
										message: "Failed to navigate to Figma URL",
										details: errorMessage.includes("BROWSER")
											? "Browser Rendering API binding is missing. This is a configuration issue."
											: "Unable to launch browser or navigate to URL.",
										troubleshooting: [
											"Verify the Figma URL is valid and accessible",
											"Check that the Browser Rendering API is properly configured in wrangler.jsonc",
											"Try again in a few moments if this is a temporary issue"
										]
									},
									null,
									2,
								),
							},
						],
						isError: true,
					};
				}
			},
		);

		// Tool 7: Get Status
		this.server.tool(
			"figma_get_status",
			{},
			async () => {
				try {
					const browserRunning = this.browserManager?.isRunning() ?? false;
					const monitorStatus = this.consoleMonitor?.getStatus() ?? null;
					const currentUrl = this.browserManager?.getCurrentUrl() ?? null;

					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										browser: {
											running: browserRunning,
											currentUrl,
										},
										consoleMonitor: monitorStatus,
										initialized: this.browserManager !== null && this.consoleMonitor !== null,
										timestamp: Date.now(),
									},
									null,
									2,
								),
							},
						],
					};
				} catch (error) {
					logger.error({ error }, "Failed to get status");
					return {
						content: [
							{
								type: "text",
								text: JSON.stringify(
									{
										error: String(error),
										message: "Failed to retrieve status",
									},
									null,
									2,
								),
							},
						],
						isError: true,
					};
				}
			},
		);

		// ================================================================
		// Cloud Write Relay — Pairing Tool
		// ================================================================
		this.server.tool(
			"figma_pair_plugin",
			"Pair the Figma Desktop Bridge plugin to this cloud session for write access. Returns a 6-character code the user enters in the plugin's Cloud Mode section.",
			{},
			async () => {
				try {
					const env = this.env as Env;
					const code = generatePairingCode();

					// Create a unique DO ID for this relay session
					const relayDoId = env.PLUGIN_RELAY.newUniqueId().toString();

					// Store pairing code → relay DO ID in KV (5-min TTL, one-time use)
					await env.OAUTH_TOKENS.put(`pairing:${code}`, relayDoId, {
						expirationTtl: 300,
					});

					// Store relay DO ID in this MCP DO's storage for session persistence
					await this.ctx.storage.put('relayDoId', relayDoId);

					return {
						content: [{
							type: "text" as const,
							text: JSON.stringify({
								pairingCode: code,
								expiresIn: "5 minutes",
								instructions: [
									"1. Open the Desktop Bridge plugin in Figma Desktop",
									"2. Click the 'Cloud Mode' toggle in the plugin UI",
									`3. Enter pairing code: ${code}`,
									"4. Click 'Connect' — the plugin will connect to the cloud relay",
									"5. Once paired, write tools (variables, components, nodes) work through the cloud"
								],
							}, null, 2),
						}],
					};
				} catch (error) {
					const errorMessage = error instanceof Error ? error.message : String(error);
					return {
						content: [{ type: "text" as const, text: JSON.stringify({ error: errorMessage }) }],
						isError: true,
					};
				}
			},
		);

		// ================================================================
		// Cloud Desktop Connector factory
		// ================================================================
		const getCloudDesktopConnector = async (): Promise<any> => {
			const env = this.env as Env;
			const relayDoId = await this.ctx.storage.get<string>('relayDoId');
			if (!relayDoId) {
				throw new Error('No cloud relay session. Call figma_pair_plugin first to pair the Desktop Bridge plugin.');
			}
			const doId = env.PLUGIN_RELAY.idFromString(relayDoId);
			const stub = env.PLUGIN_RELAY.get(doId);
			const connector = new CloudWebSocketConnector(stub);
			await connector.initialize();
			return connector;
		};

		// Register all write/manipulation tools via shared function
		registerWriteTools(this.server, getCloudDesktopConnector);

		// Register token sync tools — figma_export_tokens and figma_import_tokens.
		registerTokensTools(this.server, getCloudDesktopConnector, { isRemoteMode: true });

		// Register FigJam-specific tools (sticky notes, connectors, tables, etc.)
		registerFigJamTools(this.server, getCloudDesktopConnector);

		// Register Annotation tools (read/write design annotations via Desktop Bridge)
		registerAnnotationTools(this.server, getCloudDesktopConnector);

		// Register Deep Component tools (Plugin API tree extraction for code generation)
		registerDeepComponentTools(this.server, getCloudDesktopConnector);

		// Register Figma Slides tools (slide management, transitions, content)
		registerSlidesTools(this.server, getCloudDesktopConnector);

		// Register Figma API tools (Tools 8-14)
		// Pass isRemoteMode: true to suppress Desktop Bridge mentions in tool descriptions
		registerFigmaAPITools(
			this.server,
			async () => await this.getFigmaAPI(),
			() => this.browserManager?.getCurrentUrl() || null,
			undefined, // variablesCache
			{ isRemoteMode: true },
			getCloudDesktopConnector,
		);

		// Register Design-Code Parity & Documentation tools
		registerDesignCodeTools(
			this.server,
			async () => await this.getFigmaAPI(),
			() => this.browserManager?.getCurrentUrl() || null,
			undefined, // variablesCache
			{ isRemoteMode: true },
			getCloudDesktopConnector,
		);

		// Register Comment tools
		registerCommentTools(
			this.server,
			async () => await this.getFigmaAPI(),
			() => this.browserManager?.getCurrentUrl() || null,
			{ isRemoteMode: true },
		);

		// Register Version History tools
		registerVersionTools(
			this.server,
			async () => await this.getFigmaAPI(),
			() => this.browserManager?.getCurrentUrl() || null,
			{ isRemoteMode: true },
		);

		// Register Design System Kit tool
		registerDesignSystemTools(
			this.server,
			async () => await this.getFigmaAPI(),
			() => this.browserManager?.getCurrentUrl() || null,
			undefined, // variablesCache
			{ isRemoteMode: true },
		);

		// Register code-side accessibility scanning (axe-core + JSDOM)
		// Note: May not work in Cloudflare Workers due to JSDOM dependency
		try {
			registerAccessibilityTools(this.server);
		} catch (e) {
			// Silently skip if axe-core/jsdom not available in Workers environment
		}

		// Register figma_diagnose for cloud mode. Plugin state isn't directly
		// observable from here (the paired plugin's WS lives in the relay DO),
		// so we report mode and let the cross-MCP disclaimer do most of the work.
		registerDiagnoseTool(this.server, {
			mode: "cloud",
			getServerVersion: () => "cloud",
			getPluginState: () => null,
			getTokenState: () => ({ hasToken: false }),
		});

		// Note: MCP Apps (Token Browser, Dashboard) are registered in local.ts only
		// They require Node.js file system APIs that don't work in Cloudflare Workers
	}
}

/**
 * Cloudflare Workers fetch handler
 * Routes requests to appropriate MCP endpoints
 */
export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext) {
		const url = new URL(request.url);

		// Use canonical origin for OAuth redirect URIs so they match the Figma OAuth app config
		// regardless of whether the request comes via workers.dev or custom domain
		const oauthOrigin = env.CANONICAL_ORIGIN || url.origin;

		// Redirect /docs to subdomain
		if (url.pathname === "/docs" || url.pathname.startsWith("/docs/")) {
			const newPath = url.pathname.replace(/^\/docs\/?/, "/");
			const redirectUrl = `https://docs.figma-console-mcp.southleft.com${newPath}${url.search}`;
			return Response.redirect(redirectUrl, 301);
		}

		// ================================================================
		// Cloud Write Relay — Plugin WebSocket pairing endpoint
		// ================================================================
		if (url.pathname === "/ws/pair") {
			const code = url.searchParams.get("code");
			if (!code) {
				return new Response(JSON.stringify({ error: "Missing pairing code" }), {
					status: 400,
					headers: { "Content-Type": "application/json" },
				});
			}

			// Look up pairing code in KV
			const pairingKey = `pairing:${code.toUpperCase()}`;
			const relayDoId = await env.OAUTH_TOKENS.get(pairingKey);

			if (!relayDoId) {
				return new Response(JSON.stringify({ error: "Invalid or expired pairing code" }), {
					status: 404,
					headers: { "Content-Type": "application/json" },
				});
			}

			// Delete used code (one-time use)
			await env.OAUTH_TOKENS.delete(pairingKey);

			// Forward WebSocket upgrade to the relay DO
			const doId = env.PLUGIN_RELAY.idFromString(relayDoId);
			const stub = env.PLUGIN_RELAY.get(doId);

			// Rewrite URL to the relay DO's /ws/connect path
			const relayUrl = new URL(request.url);
			relayUrl.pathname = "/ws/connect";
			const relayRequest = new Request(relayUrl.toString(), request);

			return stub.fetch(relayRequest);
		}

		// SSE endpoint for remote MCP clients
		// Per MCP spec, we MUST validate Bearer tokens on every HTTP request
		if (url.pathname === "/sse" || url.pathname === "/sse/message") {
			// Validate Authorization header per MCP OAuth 2.1 spec
			const authHeader = request.headers.get("Authorization");

			if (!authHeader || !authHeader.startsWith("Bearer ")) {
				logger.warn({ pathname: url.pathname }, "SSE request missing Authorization header - returning 401 with resource_metadata");
				// MCP spec requires resource_metadata URL in WWW-Authenticate header (RFC9728)
				const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
				return new Response(JSON.stringify({
					error: "unauthorized",
					error_description: "Authorization header with Bearer token is required"
				}), {
					status: 401,
					headers: {
						"Content-Type": "application/json",
						"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}"`
					}
				});
			}

			const bearerToken = authHeader.substring(7); // Remove "Bearer " prefix

			// PAT support: Figma Personal Access Tokens (figd_*) are passed as Bearer
			// tokens by MCP clients like Lovable, but they aren't stored in our OAuth KV.
			// Validate them directly against Figma's API instead.
			if (isFigmaPAT(bearerToken)) {
				logger.info({ pathname: url.pathname }, "SSE request with Figma PAT — validating against Figma API");
				const patUser = await validateFigmaPAT(bearerToken);
				if (!patUser) {
					logger.warn({ pathname: url.pathname }, "SSE request with invalid Figma PAT");
					const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
					return new Response(JSON.stringify({
						error: "invalid_token",
						error_description: "Figma Personal Access Token is invalid or expired"
					}), {
						status: 401,
						headers: {
							"Content-Type": "application/json",
							"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
						}
					});
				}

				// Store PAT in KV so the Durable Object's getFigmaAPI() can retrieve it
				const patSessionId = "figma-console-mcp-default-session";
				const patTokenKey = `oauth_token:${patSessionId}`;
				await env.OAUTH_TOKENS.put(patTokenKey, JSON.stringify({
					accessToken: bearerToken,
					expiresAt: Date.now() + 3600_000, // 1-hour TTL for PAT session
				}), { expirationTtl: 3600 });

				logger.info({ pathname: url.pathname, user: patUser.handle }, "SSE request authenticated via Figma PAT");

				// Proceed with SSE connection
				return FigmaConsoleMCPv3.serveSSE("/sse").fetch(request, env, ctx);
			}

			// OAuth token path: look up in KV store
			const bearerKey = `bearer_token:${bearerToken}`;

			try {
				const tokenDataJson = await env.OAUTH_TOKENS.get(bearerKey);

				if (!tokenDataJson) {
					logger.warn({ pathname: url.pathname }, "SSE request with invalid Bearer token");
					const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
					return new Response(JSON.stringify({
						error: "invalid_token",
						error_description: "Bearer token is invalid or expired"
					}), {
						status: 401,
						headers: {
							"Content-Type": "application/json",
							"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
						}
					});
				}

				const tokenData = JSON.parse(tokenDataJson) as { sessionId: string; expiresAt: number };

				// Check if token is expired
				if (tokenData.expiresAt < Date.now()) {
					logger.warn({ pathname: url.pathname, sessionId: tokenData.sessionId }, "SSE request with expired Bearer token");
					const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
					return new Response(JSON.stringify({
						error: "invalid_token",
						error_description: "Bearer token has expired"
					}), {
						status: 401,
						headers: {
							"Content-Type": "application/json",
							"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
						}
					});
				}

				logger.info({ pathname: url.pathname, sessionId: tokenData.sessionId }, "SSE request authenticated successfully");
			} catch (error) {
				logger.error({ error, pathname: url.pathname }, "Error validating Bearer token");
				return new Response(JSON.stringify({
					error: "server_error",
					error_description: "Failed to validate authorization"
				}), {
					status: 500,
					headers: { "Content-Type": "application/json" }
				});
			}

			// Token is valid, proceed with SSE connection
			return FigmaConsoleMCPv3.serveSSE("/sse").fetch(request, env, ctx);
		}

		// Streamable HTTP endpoint for MCP communication (current spec)
		// Supports POST (client→server) and optional GET (server→client SSE)
		if (url.pathname === "/mcp") {
			// Validate Authorization header per MCP OAuth 2.1 spec
			const authHeader = request.headers.get("Authorization");

			if (!authHeader || !authHeader.startsWith("Bearer ")) {
				logger.warn({ pathname: url.pathname }, "MCP request missing Authorization header - returning 401 with resource_metadata");
				const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
				return new Response(JSON.stringify({
					error: "unauthorized",
					error_description: "Authorization header with Bearer token is required"
				}), {
					status: 401,
					headers: {
						"Content-Type": "application/json",
						"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}"`
					}
				});
			}

			const bearerToken = authHeader.substring(7);

			// PAT support: Figma Personal Access Tokens (figd_*) are passed as Bearer
			// tokens by MCP clients like Lovable, v0, and Replit. They bypass OAuth
			// and aren't stored in KV — validate directly against Figma's API.
			if (isFigmaPAT(bearerToken)) {
				logger.info({ pathname: url.pathname }, "MCP request with Figma PAT — validating against Figma API");
				const patUser = await validateFigmaPAT(bearerToken);
				if (!patUser) {
					logger.warn({ pathname: url.pathname }, "MCP request with invalid Figma PAT");
					const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
					return new Response(JSON.stringify({
						error: "invalid_token",
						error_description: "Figma Personal Access Token is invalid or expired. Ensure your PAT (figd_...) is valid and has not been revoked."
					}), {
						status: 401,
						headers: {
							"Content-Type": "application/json",
							"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
						}
					});
				}
				logger.info({ pathname: url.pathname, user: patUser.handle }, "MCP request authenticated via Figma PAT");
			} else {
				// OAuth token path: look up in KV store
				const bearerKey = `bearer_token:${bearerToken}`;

				try {
					const tokenDataJson = await env.OAUTH_TOKENS.get(bearerKey);

					if (!tokenDataJson) {
						logger.warn({ pathname: url.pathname }, "MCP request with invalid Bearer token");
						const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
						return new Response(JSON.stringify({
							error: "invalid_token",
							error_description: "Bearer token is invalid or expired"
						}), {
							status: 401,
							headers: {
								"Content-Type": "application/json",
								"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
							}
						});
					}

					const tokenData = JSON.parse(tokenDataJson) as { sessionId: string; expiresAt: number };

					if (tokenData.expiresAt < Date.now()) {
						logger.warn({ pathname: url.pathname, sessionId: tokenData.sessionId }, "MCP request with expired Bearer token");
						const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
						return new Response(JSON.stringify({
							error: "invalid_token",
							error_description: "Bearer token has expired"
						}), {
							status: 401,
							headers: {
								"Content-Type": "application/json",
								"WWW-Authenticate": `Bearer resource_metadata="${resourceMetadataUrl}", error="invalid_token"`
							}
						});
					}

					logger.info({ pathname: url.pathname, sessionId: tokenData.sessionId }, "MCP request authenticated successfully");
				} catch (error) {
					logger.error({ error, pathname: url.pathname }, "Error validating Bearer token for MCP endpoint");
					return new Response(JSON.stringify({
						error: "server_error",
						error_description: "Failed to validate authorization"
					}), {
						status: 500,
						headers: { "Content-Type": "application/json" }
					});
				}
			}

			// Token is valid — use stateless transport (no Durable Objects)
			// The Bearer token IS the Figma access token, so we use it directly
			// FigmaAPI handles PAT vs OAuth header selection internally
			const figmaAccessToken = bearerToken;
			const statelessApi = new FigmaAPI({ accessToken: figmaAccessToken });

			const transport = new WebStandardStreamableHTTPServerTransport({
				sessionIdGenerator: undefined, // Stateless — no session persistence needed
			});

			const statelessServer = new McpServer({
				name: "Figma Console MCP",
				version: "1.28.1",
			});
			wrapServerForIdentity(statelessServer);

			// ================================================================
			// Cloud Write Relay — Pairing Tool (stateless /mcp path)
			// Uses KV keyed by bearer token instead of DO storage
			// ================================================================
			statelessServer.tool(
				"figma_pair_plugin",
				"Pair the Figma Desktop Bridge plugin to this cloud session for write access. Returns a 6-character code the user enters in the plugin's Cloud Mode section.",
				{},
				async () => {
					try {
						const code = generatePairingCode();
						const relayDoId = env.PLUGIN_RELAY.newUniqueId().toString();

						// Store pairing code → relay DO ID in KV (5-min TTL, one-time use)
						await env.OAUTH_TOKENS.put(`pairing:${code}`, relayDoId, {
							expirationTtl: 300,
						});

						// Store relay DO ID keyed by bearer token for session persistence
						await env.OAUTH_TOKENS.put(`relay:${bearerToken}`, relayDoId, {
							expirationTtl: 86400, // 24h — matches typical session length
						});

						return {
							content: [{
								type: "text" as const,
								text: JSON.stringify({
									pairingCode: code,
									expiresIn: "5 minutes",
									instructions: [
										"1. Open the MCP Bridge plugin in Figma Desktop",
										"2. Click the '▶ Cloud Mode' toggle in the plugin UI",
										`3. Enter pairing code: ${code}`,
										"4. Click 'Connect' — the plugin will connect to the cloud relay",
										"5. Once paired, write tools (variables, components, nodes) work through the cloud"
									],
								}, null, 2),
							}],
						};
					} catch (error) {
						const errorMessage = error instanceof Error ? error.message : String(error);
						return {
							content: [{ type: "text" as const, text: JSON.stringify({ error: errorMessage }) }],
							isError: true,
						};
					}
				},
			);

			// Cloud Desktop Connector factory (stateless /mcp path)
			const getCloudDesktopConnector = async (): Promise<any> => {
				const relayDoId = await env.OAUTH_TOKENS.get(`relay:${bearerToken}`);
				if (!relayDoId) {
					throw new Error('No cloud relay session. Call figma_pair_plugin first to pair the Desktop Bridge plugin.');
				}
				const doId = env.PLUGIN_RELAY.idFromString(relayDoId);
				const stub = env.PLUGIN_RELAY.get(doId);
				const connector = new CloudWebSocketConnector(stub);
				await connector.initialize();
				return connector;
			};

			// Build a getCurrentUrl that resolves from the relay DO's file info
			const getCloudFileUrl = (): string | null => {
				// This is synchronous — we cache the file URL after first relay status check
				return cloudFileUrlCache;
			};
			let cloudFileUrlCache: string | null = null;

			// Pre-fetch file info from relay if paired
			try {
				const relayDoId = await env.OAUTH_TOKENS.get(`relay:${bearerToken}`);
				if (relayDoId) {
					const doId = env.PLUGIN_RELAY.idFromString(relayDoId);
					const stub = env.PLUGIN_RELAY.get(doId);
					const statusRes = await stub.fetch('https://relay/relay/status');
					const status = await statusRes.json() as { connected?: boolean; fileInfo?: { fileKey?: string | null } };
					if (status.connected && status.fileInfo?.fileKey) {
						cloudFileUrlCache = `https://www.figma.com/design/${status.fileInfo.fileKey}`;
					}
				}
			} catch {
				// No relay session or not paired — cloudFileUrlCache stays null
			}

			// Register all write/manipulation tools via shared function
			registerWriteTools(statelessServer, getCloudDesktopConnector);
			registerTokensTools(statelessServer, getCloudDesktopConnector, { isRemoteMode: true });

			// Register FigJam-specific tools
			registerFigJamTools(statelessServer, getCloudDesktopConnector);

			// Register Annotation tools
			registerAnnotationTools(statelessServer, getCloudDesktopConnector);

			// Register Deep Component tools
			registerDeepComponentTools(statelessServer, getCloudDesktopConnector);

			// Register Figma Slides tools
			registerSlidesTools(statelessServer, getCloudDesktopConnector);

			// Register REST API tools with the authenticated Figma API
			registerFigmaAPITools(
				statelessServer,
				async () => statelessApi,
				getCloudFileUrl,
				new Map(),  // Fresh variables cache per request
				{ isRemoteMode: true },
				getCloudDesktopConnector,
			);

			registerDesignCodeTools(
				statelessServer,
				async () => statelessApi,
				getCloudFileUrl,
				new Map(), // Fresh variables cache per request
				{ isRemoteMode: true },
				getCloudDesktopConnector,
			);

			registerCommentTools(
				statelessServer,
				async () => statelessApi,
				getCloudFileUrl,
			);

			registerVersionTools(
				statelessServer,
				async () => statelessApi,
				getCloudFileUrl,
			);

			registerDesignSystemTools(
				statelessServer,
				async () => statelessApi,
				getCloudFileUrl,
				new Map(), // Fresh variables cache per request
				{ isRemoteMode: true },
			);

			await statelessServer.connect(transport);
			const response = await transport.handleRequest(request);

			if (response) {
				return response;
			}
			return new Response("No response from MCP transport", { status: 500 });
		}

		// ============================================================
		// MCP OAuth 2.1 Spec-Compliant Endpoints
		// These endpoints follow the MCP Authorization specification
		// for compatibility with mcp-remote and Claude Code
		// ============================================================

		// Protected Resource Metadata (RFC9728)
		// Required by MCP spec for OAuth discovery - tells clients where to find authorization server
		if (url.pathname === "/.well-known/oauth-protected-resource" ||
			url.pathname.startsWith("/.well-known/oauth-protected-resource/")) {
			const metadata = {
				resource: url.origin,
				authorization_servers: [`${url.origin}/`],
				scopes_supported: ["file_content:read", "file_versions:read", "file_variables:read", "file_comments:read", "file_comments:write", "library_content:read"],
				bearer_methods_supported: ["header"],
				resource_signing_alg_values_supported: ["RS256"]
			};
			return new Response(JSON.stringify(metadata, null, 2), {
				headers: {
					"Content-Type": "application/json",
					"Cache-Control": "public, max-age=3600"
				}
			});
		}

		// OAuth 2.0 Authorization Server Metadata (RFC8414)
		// Required by MCP spec for client discovery
		if (url.pathname === "/.well-known/oauth-authorization-server") {
			const metadata = {
				issuer: url.origin,
				authorization_endpoint: `${url.origin}/authorize`,
				token_endpoint: `${url.origin}/token`,
				registration_endpoint: `${url.origin}/oauth/register`,
				scopes_supported: ["file_content:read", "file_versions:read", "file_variables:read", "file_comments:read", "file_comments:write", "library_content:read"],
				response_types_supported: ["code"],
				grant_types_supported: ["authorization_code", "refresh_token"],
				token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
				code_challenge_methods_supported: ["S256"],
				service_documentation: "https://docs.figma-console-mcp.southleft.com",
			};
			return new Response(JSON.stringify(metadata, null, 2), {
				headers: {
					"Content-Type": "application/json",
					"Cache-Control": "public, max-age=3600"
				}
			});
		}

		// MCP-compliant /authorize endpoint
		// Handles authorization requests from MCP clients
		if (url.pathname === "/authorize") {
			const clientId = url.searchParams.get("client_id");
			const redirectUri = url.searchParams.get("redirect_uri");
			const state = url.searchParams.get("state");
			const codeChallenge = url.searchParams.get("code_challenge");
			const codeChallengeMethod = url.searchParams.get("code_challenge_method");
			const scope = url.searchParams.get("scope");

			// For MCP clients, use the client_id as the session identifier
			// This allows token retrieval after the OAuth flow completes
			const sessionId = clientId || FigmaConsoleMCPv3.generateStateToken();

			// Store the MCP client's redirect_uri and state for the callback
			if (redirectUri && state) {
				const mcpAuthData = {
					redirectUri,
					state,
					codeChallenge,
					codeChallengeMethod,
					scope,
					clientId,
					sessionId
				};
				// Store with 10 minute expiration
				const mcpStateKey = `mcp_auth:${sessionId}`;
				await env.OAUTH_STATE.put(mcpStateKey, JSON.stringify(mcpAuthData), {
					expirationTtl: 600
				});
			}

			// Check if OAuth credentials are configured
			if (!env.FIGMA_OAUTH_CLIENT_ID) {
				return new Response(
					JSON.stringify({
						error: "server_error",
						error_description: "OAuth not configured on server"
					}),
					{
						status: 500,
						headers: { "Content-Type": "application/json" }
					}
				);
			}

			// Generate CSRF protection token
			const stateToken = FigmaConsoleMCPv3.generateStateToken();

			// Store state token with sessionId (10 minute expiration)
			await env.OAUTH_STATE.put(stateToken, sessionId, {
				expirationTtl: 600
			});

			// Redirect to Figma OAuth
			const figmaAuthUrl = new URL("https://www.figma.com/oauth");
			figmaAuthUrl.searchParams.set("client_id", env.FIGMA_OAUTH_CLIENT_ID);
			figmaAuthUrl.searchParams.set("redirect_uri", `${oauthOrigin}/oauth/callback`);
			figmaAuthUrl.searchParams.set("scope", "file_content:read,file_versions:read,file_variables:read,file_comments:read,file_comments:write,library_content:read");
			figmaAuthUrl.searchParams.set("state", stateToken);
			figmaAuthUrl.searchParams.set("response_type", "code");

			return Response.redirect(figmaAuthUrl.toString(), 302);
		}

		// MCP-compliant /token endpoint
		// Handles token exchange and refresh requests
		if (url.pathname === "/token" && request.method === "POST") {
			const contentType = request.headers.get("content-type") || "";
			let params: URLSearchParams;

			if (contentType.includes("application/x-www-form-urlencoded")) {
				params = new URLSearchParams(await request.text());
			} else if (contentType.includes("application/json")) {
				const body = await request.json() as Record<string, string>;
				params = new URLSearchParams(body);
			} else {
				params = new URLSearchParams(await request.text());
			}

			const grantType = params.get("grant_type");
			const clientId = params.get("client_id");
			const code = params.get("code");
			const refreshToken = params.get("refresh_token");

			// For authorization_code grant, exchange the code for tokens
			if (grantType === "authorization_code" && code) {
				// The code here is actually our session-based token
				// Look up the stored token by session/client ID
				const sessionId = clientId || code;
				const tokenKey = `oauth_token:${sessionId}`;

				logger.info({ grantType, clientId, code, sessionId, tokenKey }, "Token exchange request");

				const tokenJson = await env.OAUTH_TOKENS.get(tokenKey);

				logger.info({ tokenKey, hasToken: !!tokenJson }, "Token lookup result");

				if (tokenJson) {
					const tokenData = JSON.parse(tokenJson) as {
						accessToken: string;
						refreshToken?: string;
						expiresAt: number;
					};

					// Return tokens in OAuth 2.0 format
					return new Response(JSON.stringify({
						access_token: tokenData.accessToken,
						token_type: "Bearer",
						expires_in: Math.max(0, Math.floor((tokenData.expiresAt - Date.now()) / 1000)),
						refresh_token: tokenData.refreshToken,
						scope: "file_content:read file_versions:read file_variables:read file_comments:read file_comments:write library_content:read"
					}), {
						headers: {
							"Content-Type": "application/json",
							"Cache-Control": "no-store"
						}
					});
				}

				logger.error({ tokenKey, sessionId, clientId, code }, "Token not found for exchange");
				return new Response(JSON.stringify({
					error: "invalid_grant",
					error_description: "Authorization code not found or expired. Please re-authenticate."
				}), {
					status: 400,
					headers: { "Content-Type": "application/json" }
				});
			}

			// For refresh_token grant
			if (grantType === "refresh_token" && refreshToken) {
				if (!env.FIGMA_OAUTH_CLIENT_ID || !env.FIGMA_OAUTH_CLIENT_SECRET) {
					return new Response(JSON.stringify({
						error: "server_error",
						error_description: "OAuth not configured"
					}), {
						status: 500,
						headers: { "Content-Type": "application/json" }
					});
				}

				const credentials = btoa(`${env.FIGMA_OAUTH_CLIENT_ID}:${env.FIGMA_OAUTH_CLIENT_SECRET}`);

				const tokenParams = new URLSearchParams({
					grant_type: "refresh_token",
					refresh_token: refreshToken
				});

				const tokenResponse = await fetch("https://api.figma.com/v1/oauth/token", {
					method: "POST",
					headers: {
						"Content-Type": "application/x-www-form-urlencoded",
						"Authorization": `Basic ${credentials}`
					},
					body: tokenParams.toString()
				});

				if (!tokenResponse.ok) {
					return new Response(JSON.stringify({
						error: "invalid_grant",
						error_description: "Failed to refresh token"
					}), {
						status: 400,
						headers: { "Content-Type": "application/json" }
					});
				}

				const tokenData = await tokenResponse.json() as {
					access_token: string;
					refresh_token?: string;
					expires_in: number;
				};

				// Store the refreshed token
				if (clientId) {
					const tokenKey = `oauth_token:${clientId}`;
					const expiresAt = Date.now() + (tokenData.expires_in * 1000);
					const storedToken = {
						accessToken: tokenData.access_token,
						refreshToken: tokenData.refresh_token || refreshToken,
						expiresAt
					};
					await env.OAUTH_TOKENS.put(tokenKey, JSON.stringify(storedToken), {
						expirationTtl: tokenData.expires_in
					});

					// Store reverse lookup for Bearer token validation on SSE endpoint
					const bearerKey = `bearer_token:${tokenData.access_token}`;
					await env.OAUTH_TOKENS.put(bearerKey, JSON.stringify({
						sessionId: clientId,
						expiresAt
					}), {
						expirationTtl: tokenData.expires_in
					});
				}

				return new Response(JSON.stringify({
					access_token: tokenData.access_token,
					token_type: "Bearer",
					expires_in: tokenData.expires_in,
					refresh_token: tokenData.refresh_token || refreshToken,
					scope: "file_content:read file_versions:read file_variables:read file_comments:read file_comments:write library_content:read"
				}), {
					headers: {
						"Content-Type": "application/json",
						"Cache-Control": "no-store"
					}
				});
			}

			return new Response(JSON.stringify({
				error: "unsupported_grant_type",
				error_description: "Only authorization_code and refresh_token grants are supported"
			}), {
				status: 400,
				headers: { "Content-Type": "application/json" }
			});
		}

		// Dynamic Client Registration (RFC7591)
		// Required by MCP spec for clients to register
		if (url.pathname === "/oauth/register" && request.method === "POST") {
			const body = await request.json() as {
				client_name?: string;
				redirect_uris?: string[];
			};

			// Generate a client ID for this registration
			const clientId = `mcp_${FigmaConsoleMCPv3.generateStateToken().substring(0, 16)}`;

			// Store client registration (30 day expiration)
			await env.OAUTH_STATE.put(`client:${clientId}`, JSON.stringify({
				client_name: body.client_name || "MCP Client",
				redirect_uris: body.redirect_uris || [],
				created_at: Date.now()
			}), {
				expirationTtl: 30 * 24 * 60 * 60
			});

			return new Response(JSON.stringify({
				client_id: clientId,
				client_name: body.client_name || "MCP Client",
				redirect_uris: body.redirect_uris || [],
				token_endpoint_auth_method: "none",
				grant_types: ["authorization_code", "refresh_token"],
				response_types: ["code"]
			}), {
				status: 201,
				headers: { "Content-Type": "application/json" }
			});
		}

		// ============================================================
		// Original Figma OAuth Endpoints (kept for backwards compatibility)
		// ============================================================

		// OAuth authorization initiation
		if (url.pathname === "/oauth/authorize") {
			const sessionId = url.searchParams.get("session_id");

			if (!sessionId) {
				return new Response("Missing session_id parameter", { status: 400 });
			}

			// Check if OAuth credentials are configured
			if (!env.FIGMA_OAUTH_CLIENT_ID) {
				return new Response(
					JSON.stringify({
						error: "OAuth not configured",
						message: "Server administrator needs to configure FIGMA_OAUTH_CLIENT_ID",
						docs: "https://github.com/southleft/figma-console-mcp#oauth-setup"
					}),
					{
						status: 500,
						headers: { "Content-Type": "application/json" }
					}
				);
			}

			// Generate cryptographically secure state token for CSRF protection
			const stateToken = FigmaConsoleMCPv3.generateStateToken();

			// Store state token with sessionId in KV (10 minute expiration)
			await env.OAUTH_STATE.put(stateToken, sessionId, {
				expirationTtl: 600 // 10 minutes
			});

			const redirectUri = `${oauthOrigin}/oauth/callback`;

			const figmaAuthUrl = new URL("https://www.figma.com/oauth");
			figmaAuthUrl.searchParams.set("client_id", env.FIGMA_OAUTH_CLIENT_ID);
			figmaAuthUrl.searchParams.set("redirect_uri", redirectUri);
			figmaAuthUrl.searchParams.set("scope", "file_content:read,file_versions:read,file_variables:read,file_comments:read,file_comments:write,library_content:read");
			figmaAuthUrl.searchParams.set("state", stateToken);
			figmaAuthUrl.searchParams.set("response_type", "code");

			return Response.redirect(figmaAuthUrl.toString(), 302);
		}

		// OAuth callback handler
		if (url.pathname === "/oauth/callback") {
			const code = url.searchParams.get("code");
			const stateToken = url.searchParams.get("state");
			const error = url.searchParams.get("error");

			// Handle OAuth errors
			if (error) {
				return new Response(
					`<html><body>
						<h1>Authentication Failed</h1>
						<p>Error: ${error}</p>
						<p>Description: ${url.searchParams.get("error_description") || "Unknown error"}</p>
						<p>You can close this window and try again.</p>
					</body></html>`,
					{
						status: 400,
						headers: { "Content-Type": "text/html" }
					}
				);
			}

			if (!code || !stateToken) {
				return new Response("Missing code or state parameter", { status: 400 });
			}

			// Validate state token (CSRF protection)
			const sessionId = await env.OAUTH_STATE.get(stateToken);

			logger.info({ stateToken, sessionId, hasSessionId: !!sessionId }, "OAuth callback - state token lookup");

			if (!sessionId) {
				return new Response(
					`<html><body>
						<h1>Invalid or Expired Request</h1>
						<p>The authentication request has expired or is invalid.</p>
						<p>Please try authenticating again.</p>
					</body></html>`,
					{
						status: 400,
						headers: { "Content-Type": "text/html" }
					}
				);
			}

			// Delete state token after validation (one-time use)
			await env.OAUTH_STATE.delete(stateToken);

			try {
				// Exchange authorization code for access token
				// Use Basic auth in Authorization header (Figma's recommended method)
				const credentials = btoa(`${env.FIGMA_OAUTH_CLIENT_ID}:${env.FIGMA_OAUTH_CLIENT_SECRET}`);

				const tokenParams = new URLSearchParams({
					redirect_uri: `${oauthOrigin}/oauth/callback`,
					code,
					grant_type: "authorization_code"
				});

				const tokenResponse = await fetch("https://api.figma.com/v1/oauth/token", {
					method: "POST",
					headers: {
						"Content-Type": "application/x-www-form-urlencoded",
						"Authorization": `Basic ${credentials}`
					},
					body: tokenParams.toString()
				});

				if (!tokenResponse.ok) {
					const errorText = await tokenResponse.text();
					let errorData;
					try {
						errorData = JSON.parse(errorText);
					} catch {
						errorData = { error: "Unknown error", raw: errorText, status: tokenResponse.status };
					}
					logger.error({ errorData, status: tokenResponse.status }, "Token exchange failed");
					throw new Error(`Token exchange failed: ${JSON.stringify(errorData)}`);
				}

				const tokenData = await tokenResponse.json() as {
					access_token: string;
					refresh_token?: string;
					expires_in: number;
				};
				const accessToken = tokenData.access_token;
				const refreshToken = tokenData.refresh_token;
				const expiresIn = tokenData.expires_in;

				logger.info({
					sessionId,
					hasTokens: !!accessToken && !!refreshToken,
					expiresIn
				}, "Token exchange successful");

				// IMPORTANT: Use KV storage for tokens since Durable Object storage is instance-specific
				// Store token in Workers KV so it's accessible across all Durable Object instances
				const tokenKey = `oauth_token:${sessionId}`;
				const tokenExpiresAt = Date.now() + (expiresIn * 1000);
				const storedToken = {
					accessToken,
					refreshToken,
					expiresAt: tokenExpiresAt
				};

				// Store in KV with 90-day expiration (matching token lifetime)
				await env.OAUTH_TOKENS.put(tokenKey, JSON.stringify(storedToken), {
					expirationTtl: expiresIn
				});

				// CRITICAL: Also store under the fixed session ID that Durable Objects use
				// This ensures getFigmaAPI() can retrieve the token regardless of which
				// session ID was used during OAuth (e.g., mcp-remote's client_id)
				const fixedTokenKey = `oauth_token:figma-console-mcp-default-session`;
				if (tokenKey !== fixedTokenKey) {
					await env.OAUTH_TOKENS.put(fixedTokenKey, JSON.stringify(storedToken), {
						expirationTtl: expiresIn
					});
					logger.info({ fixedTokenKey }, "Token also stored under fixed session ID for Durable Object access");
				}

				// Store reverse lookup for Bearer token validation on SSE endpoint
				// This allows us to validate Authorization: Bearer <token> headers
				const bearerKey = `bearer_token:${accessToken}`;
				await env.OAUTH_TOKENS.put(bearerKey, JSON.stringify({
					sessionId,
					expiresAt: tokenExpiresAt
				}), {
					expirationTtl: expiresIn
				});

				// Verify the token was stored
				const verifyToken = await env.OAUTH_TOKENS.get(tokenKey);
				logger.info({ sessionId, tokenKey, storedSuccessfully: !!verifyToken }, "Token stored in KV");

				// Check if this flow came from an MCP client (like mcp-remote)
				// If so, we need to redirect back to the client with an authorization code
				const mcpStateKey = `mcp_auth:${sessionId}`;
				const mcpAuthJson = await env.OAUTH_STATE.get(mcpStateKey);

				if (mcpAuthJson) {
					// MCP client flow - redirect back with authorization code
					const mcpAuthData = JSON.parse(mcpAuthJson) as {
						redirectUri: string;
						state: string;
						codeChallenge?: string;
						codeChallengeMethod?: string;
						scope?: string;
						clientId?: string;
						sessionId: string;
					};

					// Clean up the MCP auth state
					await env.OAUTH_STATE.delete(mcpStateKey);

					// Generate an authorization code for the MCP client
					// We use the sessionId as the code since we've already stored the token
					const authCode = sessionId;

					// Build the redirect URL back to the MCP client
					const redirectUrl = new URL(mcpAuthData.redirectUri);
					redirectUrl.searchParams.set("code", authCode);
					redirectUrl.searchParams.set("state", mcpAuthData.state);

					logger.info({
						sessionId,
						redirectUri: mcpAuthData.redirectUri,
						state: mcpAuthData.state
					}, "Redirecting back to MCP client");

					return Response.redirect(redirectUrl.toString(), 302);
				}

				// Direct browser flow - show success page
				return new Response(
					`<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Authentication Successful</title>
	<link rel="icon" type="image/jpeg" href="https://p198.p4.n0.cdn.zight.com/items/Qwu1Dywx/b61b7b8f-05dc-4063-8a40-53fa4f8e3e97.jpg">
	<style>
		* {
			margin: 0;
			padding: 0;
			box-sizing: border-box;
		}
		body {
			font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
			background: #ffffff;
			color: #000000;
			display: flex;
			align-items: center;
			justify-content: center;
			min-height: 100vh;
			padding: 24px;
		}
		.container {
			max-width: 480px;
			text-align: center;
		}
		.icon {
			width: 64px;
			height: 64px;
			margin: 0 auto 24px;
			background: #18a0fb;
			border-radius: 50%;
			display: flex;
			align-items: center;
			justify-content: center;
			font-size: 32px;
			color: white;
		}
		h1 {
			font-size: 32px;
			font-weight: 700;
			margin-bottom: 16px;
			letter-spacing: -0.02em;
		}
		p {
			font-size: 16px;
			color: #666666;
			line-height: 1.6;
			margin-bottom: 32px;
		}
		.button {
			display: inline-block;
			padding: 12px 24px;
			background: #000000;
			color: #ffffff;
			text-decoration: none;
			border-radius: 8px;
			font-weight: 500;
			font-size: 16px;
			border: none;
			cursor: pointer;
			transition: background 0.2s;
		}
		.button:hover {
			background: #333333;
		}
		.footer {
			margin-top: 48px;
			font-size: 14px;
			color: #999999;
		}
	</style>
</head>
<body>
	<div class="container">
		<div class="icon">✓</div>
		<h1>Authentication successful</h1>
		<p>You've successfully connected Figma Console MCP to your Figma account. You can now close this window and return to Claude.</p>
		<button class="button" onclick="window.close()">Close this window</button>
		<div class="footer">This window will automatically close in 5 seconds</div>
	</div>
	<script>
		setTimeout(() => window.close(), 5000);
	</script>
</body>
</html>`,
					{
						headers: {
							"Content-Type": "text/html; charset=utf-8"
						}
					}
				);
			} catch (error) {
				logger.error({ error, sessionId }, "OAuth callback failed");
				return new Response(
					`<html><body>
						<h1>Authentication Error</h1>
						<p>Failed to complete authentication: ${error instanceof Error ? error.message : String(error)}</p>
						<p>Please try again or contact support.</p>
					</body></html>`,
					{
						status: 500,
						headers: { "Content-Type": "text/html" }
					}
				);
			}
		}

		// Health check endpoint
		if (url.pathname === "/health") {
			return new Response(
				JSON.stringify({
					status: "healthy",
					service: "Figma Console MCP",
					version: "1.28.1",
					endpoints: {
						mcp: ["/sse", "/mcp"],
						oauth_mcp_spec: ["/.well-known/oauth-authorization-server", "/authorize", "/token", "/oauth/register"],
						oauth_legacy: ["/oauth/authorize", "/oauth/callback"],
						utility: ["/health"]
					},
					oauth_configured: !!env.FIGMA_OAUTH_CLIENT_ID
				}),
				{
					headers: { "Content-Type": "application/json" },
				},
			);
		}

		// Serve favicon
	if (url.pathname === "/favicon.ico") {
		// Redirect to custom Figma Console icon
		return Response.redirect("https://p198.p4.n0.cdn.zight.com/items/Qwu1Dywx/b61b7b8f-05dc-4063-8a40-53fa4f8e3e97.jpg", 302);
	}

	// Proxy /docs to Mintlify
	if (/^\/docs/.test(url.pathname)) {
		// Try mintlify.app domain (Mintlify's standard hosting)
		const DOCS_URL = "southleftllc.mintlify.app";
		const CUSTOM_URL = "figma-console-mcp.southleft.com";

		const proxyUrl = new URL(request.url);
		proxyUrl.hostname = DOCS_URL;

		const proxyRequest = new Request(proxyUrl, request);
		proxyRequest.headers.set("Host", DOCS_URL);
		proxyRequest.headers.set("X-Forwarded-Host", CUSTOM_URL);
		proxyRequest.headers.set("X-Forwarded-Proto", "https");

		return await fetch(proxyRequest);
	}

	// Root path - serve landing page with editorial layout and light/dark mode
	if (url.pathname === "/") {
		return new Response(
			`<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Figma Console MCP - The Most Comprehensive MCP Server for Figma</title>
	<link rel="icon" type="image/svg+xml" href="https://docs.figma-console-mcp.southleft.com/favicon.svg">
	<meta name="description" content="Turn your Figma design system into a living API. 103+ tools give AI assistants deep access to design tokens, component specs, variables, and programmatic design creation.">

	<!-- Open Graph -->
	<meta property="og:type" content="website">
	<meta property="og:url" content="https://figma-console-mcp.southleft.com">
	<meta property="og:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
	<meta property="og:description" content="The most comprehensive MCP server for Figma. 103+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
	<meta property="og:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
	<meta property="og:image:width" content="1200">
	<meta property="og:image:height" content="630">

	<!-- Twitter -->
	<meta name="twitter:card" content="summary_large_image">
	<meta name="twitter:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
	<meta name="twitter:description" content="The most comprehensive MCP server for Figma. 103+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
	<meta name="twitter:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">

	<meta name="theme-color" content="#0D9488">
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
	<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
	<style>
		:root {
			--color-primary: #0D9488;
			--color-primary-light: #14B8A6;
			--color-primary-dark: #0F766E;
			--color-bg: #0F0F0F;
			--color-bg-elevated: #161616;
			--color-bg-card: #1A1A1A;
			--color-border: #2A2A2A;
			--color-border-hover: #3A3A3A;
			--color-rule: #2A2A2A;
			--color-text: #FAFAFA;
			--color-text-secondary: #A1A1A1;
			--color-text-tertiary: #666666;
			--font-mono: "JetBrains Mono", "SF Mono", Monaco, monospace;
			--radius-sm: 6px;
			--radius-md: 10px;
			--radius-lg: 16px;
			--transition: 0.2s ease;
		}

		[data-theme="light"] {
			--color-bg: #FAFAFA;
			--color-bg-elevated: #FFFFFF;
			--color-bg-card: #FFFFFF;
			--color-border: #E5E5E5;
			--color-border-hover: #D4D4D4;
			--color-rule: #E5E5E5;
			--color-text: #171717;
			--color-text-secondary: #525252;
			--color-text-tertiary: #A3A3A3;
		}

		* { margin: 0; padding: 0; box-sizing: border-box; }

		html {
			scroll-behavior: smooth;
		}

		body {
			font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
			background: var(--color-bg);
			color: var(--color-text);
			line-height: 1.6;
			min-height: 100vh;
			transition: background var(--transition), color var(--transition);
		}

		a { color: inherit; text-decoration: none; }

		/* Header */
		.header {
			position: sticky;
			top: 0;
			z-index: 100;
			padding: 16px 32px;
			display: flex;
			justify-content: space-between;
			align-items: center;
			background: var(--color-bg);
			border-bottom: 1px solid var(--color-rule);
			backdrop-filter: blur(12px);
			transition: background var(--transition), border-color var(--transition);
		}

		.logo img {
			height: 35px;
			transition: opacity var(--transition);
		}

		.logo img:hover { opacity: 0.8; }

		.header-right {
			display: flex;
			align-items: center;
			gap: 24px;
		}

		.nav {
			display: flex;
			gap: 24px;
			align-items: center;
		}

		.nav a {
			color: var(--color-text-secondary);
			font-size: 14px;
			font-weight: 500;
			transition: color var(--transition);
		}

		.nav a:hover { color: var(--color-text); }

		.nav a.sponsor-link {
			color: #db61a2;
			display: flex;
			align-items: center;
			gap: 5px;
		}
		.nav a.sponsor-link:hover { color: #ea4aaa; }
		.nav a.sponsor-link svg { width: 14px; height: 14px; fill: currentColor; }

		.theme-toggle {
			display: flex;
			align-items: center;
			justify-content: center;
			width: 36px;
			height: 36px;
			background: transparent;
			border: 1px solid var(--color-border);
			border-radius: var(--radius-sm);
			cursor: pointer;
			color: var(--color-text-secondary);
			transition: all var(--transition);
		}

		.theme-toggle:hover {
			border-color: var(--color-border-hover);
			color: var(--color-text);
		}

		.theme-toggle svg { width: 18px; height: 18px; }
		.theme-toggle .sun { display: none; }
		[data-theme="light"] .theme-toggle .moon { display: none; }
		[data-theme="light"] .theme-toggle .sun { display: block; }

		/* Main layout */
		.main {
			max-width: 1280px;
			margin: 0 auto;
			padding: 64px 32px 80px;
		}

		/* Section dividers */
		.section-rule {
			border: none;
			border-top: 1px solid var(--color-rule);
			margin: 72px 0;
		}

		/* Grid layout */
		.grid {
			display: grid;
			grid-template-columns: repeat(12, 1fr);
			gap: 48px 48px;
		}

		.grid-cell {
			transition: all var(--transition);
		}

		/* Rule-based separators for grid cells */
		.grid-cell.rule-left {
			padding-left: 48px;
			border-left: 1px solid var(--color-rule);
		}

		.grid-cell.rule-top {
			padding-top: 32px;
			border-top: 1px solid var(--color-rule);
		}

		/* Hero section */
		.hero-cell {
			grid-column: span 7;
			padding-right: 48px;
		}

		.badge {
			display: inline-block;
			width: fit-content;
			color: var(--color-text-secondary);
			font-size: 11px;
			font-weight: 600;
			text-transform: uppercase;
			letter-spacing: 0.5px;
			margin-bottom: 20px;
		}

		h1 {
			font-size: 48px;
			font-weight: 700;
			letter-spacing: -0.03em;
			line-height: 1.1;
			margin-bottom: 20px;
		}

		.highlight {
			color: var(--color-primary-light);
		}

		.hero-subtitle {
			font-size: 18px;
			color: var(--color-text-secondary);
			line-height: 1.7;
			max-width: 560px;
			margin-bottom: 32px;
		}

		.cta-row {
			display: flex;
			gap: 12px;
			flex-wrap: wrap;
		}

		.btn {
			display: inline-flex;
			align-items: center;
			gap: 8px;
			padding: 12px 20px;
			border-radius: var(--radius-sm);
			font-weight: 500;
			font-size: 14px;
			transition: all var(--transition);
			border: none;
			cursor: pointer;
		}

		.btn svg { width: 16px; height: 16px; }

		.btn-primary {
			background: var(--color-primary);
			color: #FFFFFF;
		}

		.btn-primary:hover { background: var(--color-primary-dark); }

		.btn-secondary {
			background: transparent;
			color: var(--color-text);
			border: 1px solid var(--color-border);
		}

		.btn-secondary:hover {
			border-color: var(--color-border-hover);
			background: var(--color-bg-elevated);
		}

		/* Hero right - capabilities showcase */
		.showcase-cell {
			grid-column: span 5;
		}

		.showcase-label {
			font-size: 11px;
			font-weight: 600;
			text-transform: uppercase;
			letter-spacing: 0.5px;
			color: var(--color-text-tertiary);
			margin-bottom: 16px;
		}

		.showcase-stat {
			display: flex;
			align-items: baseline;
			gap: 12px;
			margin-bottom: 24px;
		}

		.showcase-stat .number {
			font-size: 56px;
			font-weight: 700;
			color: var(--color-primary-light);
			line-height: 1;
		}

		.showcase-stat .label {
			font-size: 16px;
			color: var(--color-text-secondary);
		}

		.capability-list {
			display: grid;
			grid-template-columns: 1fr 1fr;
			gap: 10px;
		}

		.capability-item {
			display: flex;
			align-items: center;
			gap: 10px;
			padding: 10px 14px;
			background: var(--color-bg-elevated);
			border: 1px solid var(--color-border);
			border-radius: var(--radius-md);
			font-size: 13px;
			color: var(--color-text-secondary);
			transition: all var(--transition);
		}

		.capability-item:hover {
			border-color: var(--color-primary);
			color: var(--color-text);
		}

		.capability-item svg {
			width: 18px;
			height: 18px;
			color: var(--color-primary-light);
			flex-shrink: 0;
		}

		/* Value proposition section */
		.value-cell {
			grid-column: span 12;
			text-align: center;
			padding: 64px 48px;
			position: relative;
			background: linear-gradient(135deg, rgba(13, 148, 136, 0.06) 0%, rgba(13, 148, 136, 0.02) 50%, transparent 100%);
			border-radius: var(--radius-lg);
			border: 1px solid rgba(13, 148, 136, 0.1);
		}

		.value-cell::before {
			content: '';
			position: absolute;
			top: -1px;
			left: 50%;
			transform: translateX(-50%);
			width: 120px;
			height: 3px;
			background: linear-gradient(90deg, transparent, var(--color-primary-light), transparent);
			border-radius: 2px;
		}

		.value-cell h2 {
			font-size: 36px;
			font-weight: 700;
			margin-bottom: 16px;
			letter-spacing: -0.03em;
			background: linear-gradient(135deg, var(--color-text) 0%, var(--color-primary-light) 100%);
			-webkit-background-clip: text;
			-webkit-text-fill-color: transparent;
			background-clip: text;
		}

		[data-theme="light"] .value-cell h2 {
			background: linear-gradient(135deg, var(--color-text) 0%, var(--color-primary-dark) 100%);
			-webkit-background-clip: text;
			-webkit-text-fill-color: transparent;
			background-clip: text;
		}

		.value-cell p {
			font-size: 18px;
			color: var(--color-text-secondary);
			max-width: 680px;
			margin: 0 auto;
			line-height: 1.7;
		}

		/* Capabilities grid */
		.capability-card {
			grid-column: span 3;
		}

		.capability-icon {
			width: 44px;
			height: 44px;
			display: flex;
			align-items: center;
			justify-content: center;
			background: rgba(13, 148, 136, 0.12);
			border-radius: var(--radius-md);
			color: var(--color-primary-light);
			margin-bottom: 16px;
		}

		.capability-icon svg { width: 22px; height: 22px; }

		.capability-card h3 {
			font-size: 17px;
			font-weight: 600;
			margin-bottom: 10px;
		}

		.capability-card p {
			font-size: 14px;
			color: var(--color-text-secondary);
			line-height: 1.7;
		}

		/* Prompt showcase */
		.prompts-cell {
			grid-column: span 6;
		}

		.section-header {
			display: flex;
			align-items: center;
			gap: 12px;
			margin-bottom: 24px;
		}

		.section-header-icon {
			width: 40px;
			height: 40px;
			display: flex;
			align-items: center;
			justify-content: center;
			background: rgba(13, 148, 136, 0.12);
			border-radius: var(--radius-md);
			color: var(--color-primary-light);
		}

		.section-header-icon svg {
			width: 20px;
			height: 20px;
		}

		.section-header h3 {
			font-size: 18px;
			font-weight: 600;
		}

		.prompt-list {
			display: flex;
			flex-direction: column;
			gap: 12px;
		}

		.prompt-item {
			display: flex;
			align-items: center;
			gap: 12px;
			padding: 14px 16px;
			background: var(--color-bg-elevated);
			border: 1px solid var(--color-border);
			border-radius: var(--radius-md);
			font-family: var(--font-mono);
			font-size: 13px;
			color: var(--color-text-secondary);
			transition: all var(--transition);
		}

		.prompt-item:hover {
			border-color: var(--color-primary);
			color: var(--color-text);
		}

		.prompt-item svg {
			width: 16px;
			height: 16px;
			color: var(--color-primary-light);
			flex-shrink: 0;
		}

		/* Audience cells */
		.audience-cell {
			grid-column: span 6;
			padding: 24px 0;
		}

		.audience-header {
			display: flex;
			align-items: center;
			gap: 12px;
			margin-bottom: 28px;
		}

		.audience-icon {
			width: 40px;
			height: 40px;
			display: flex;
			align-items: center;
			justify-content: center;
			background: rgba(13, 148, 136, 0.12);
			border-radius: var(--radius-md);
			color: var(--color-primary-light);
		}

		.audience-icon svg { width: 20px; height: 20px; }

		.audience-header h3 {
			font-size: 18px;
			font-weight: 600;
		}

		.audience-list {
			list-style: none;
			display: flex;
			flex-direction: column;
			gap: 24px;
		}

		.audience-list li {
			display: flex;
			align-items: flex-start;
			gap: 12px;
			font-size: 14px;
			color: var(--color-text-secondary);
		}

		.audience-list li svg {
			width: 18px;
			height: 18px;
			color: var(--color-primary);
			flex-shrink: 0;
			margin-top: 1px;
		}

		/* Getting started CTA */
		.getting-started-cell {
			grid-column: span 12;
			display: flex;
			align-items: center;
			justify-content: space-between;
			padding: 40px 48px;
			background: var(--color-bg-elevated);
			border: 1px solid var(--color-border);
			border-radius: var(--radius-lg);
			margin-top: 32px;
		}

		.getting-started-content h3 {
			font-size: 20px;
			font-weight: 600;
			margin-bottom: 8px;
		}

		.getting-started-content p {
			font-size: 14px;
			color: var(--color-text-secondary);
			max-width: 480px;
		}

		.getting-started-actions {
			display: flex;
			gap: 12px;
		}

		/* Blog CTA */
		.blog-cell {
			grid-column: span 12;
			display: flex;
			align-items: center;
			justify-content: space-between;
			padding: 32px 0;
			border-top: 1px solid var(--color-rule);
			margin-top: 40px;
		}

		.blog-content {
			display: flex;
			align-items: center;
			gap: 16px;
		}

		.blog-icon {
			width: 44px;
			height: 44px;
			display: flex;
			align-items: center;
			justify-content: center;
			background: rgba(13, 148, 136, 0.12);
			border-radius: var(--radius-md);
			color: var(--color-primary-light);
		}

		.blog-icon svg { width: 22px; height: 22px; }

		.blog-text h4 {
			font-size: 15px;
			font-weight: 600;
			margin-bottom: 2px;
		}

		.blog-text p {
			font-size: 13px;
			color: var(--color-text-secondary);
		}

		.blog-link {
			display: flex;
			align-items: center;
			gap: 8px;
			color: var(--color-primary-light);
			font-size: 14px;
			font-weight: 500;
			transition: gap var(--transition);
		}

		.blog-link:hover { gap: 12px; }
		.blog-link svg { width: 16px; height: 16px; }

		/* Footer */
		.footer {
			max-width: 1280px;
			margin: 0 auto;
			padding: 24px 32px;
			display: flex;
			justify-content: space-between;
			align-items: center;
			border-top: 1px solid var(--color-rule);
			color: var(--color-text-tertiary);
			font-size: 13px;
		}

		.footer a {
			color: var(--color-text-secondary);
			transition: color var(--transition);
		}

		.footer a:hover { color: var(--color-text); }

		.footer-links {
			display: flex;
			gap: 24px;
		}

		/* Mobile nav */
		.mobile-menu-btn {
			display: none;
			align-items: center;
			justify-content: center;
			width: 36px;
			height: 36px;
			background: transparent;
			border: none;
			cursor: pointer;
			color: var(--color-text);
		}

		.mobile-menu-btn svg { width: 24px; height: 24px; }

		/* Mobile menu overlay */
		.mobile-menu {
			display: none;
			position: fixed;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			background: var(--color-bg);
			z-index: 1000;
			padding: 20px;
			flex-direction: column;
		}

		.mobile-menu.active {
			display: flex;
		}

		.mobile-menu-header {
			display: flex;
			justify-content: space-between;
			align-items: center;
			margin-bottom: 48px;
		}

		.mobile-menu-close {
			display: flex;
			align-items: center;
			justify-content: center;
			width: 36px;
			height: 36px;
			background: transparent;
			border: none;
			cursor: pointer;
			color: var(--color-text);
		}

		.mobile-menu-close svg { width: 24px; height: 24px; }

		.mobile-menu-nav {
			display: flex;
			flex-direction: column;
			gap: 8px;
		}

		.mobile-menu-nav a {
			display: block;
			padding: 16px 0;
			font-size: 18px;
			font-weight: 500;
			color: var(--color-text);
			border-bottom: 1px solid var(--color-border);
			transition: color var(--transition);
		}

		.mobile-menu-nav a:hover {
			color: var(--color-primary-light);
		}

		body.menu-open {
			overflow: hidden;
		}

		/* Responsive */
		@media (max-width: 1024px) {
			.hero-cell {
				grid-column: span 12;
				padding-right: 0;
				padding-bottom: 40px;
				border-bottom: 1px solid var(--color-rule);
			}
			.showcase-cell {
				grid-column: span 12;
				padding-left: 0;
				padding-top: 40px;
				border-left: none;
			}
			.capability-card {
				grid-column: span 6;
			}
			.capability-card.rule-left {
				padding-left: 0;
				border-left: none;
			}
			.capability-card:nth-child(odd) {
				padding-left: 0;
				border-left: none;
			}
			.capability-card:nth-child(even) {
				padding-left: 32px;
				border-left: 1px solid var(--color-rule);
			}
			.prompts-cell { grid-column: span 12; }
			.audience-cell {
				grid-column: span 6;
				padding-left: 0;
			}
			.audience-cell.rule-left {
				padding-left: 32px;
				border-left: 1px solid var(--color-rule);
			}
			h1 { font-size: 40px; }
			.value-cell { padding: 48px 32px; }
			.value-cell h2 { font-size: 30px; }
			.section-rule { margin: 56px 0; }
			.getting-started-cell {
				flex-direction: column;
				gap: 24px;
				text-align: center;
			}
			.getting-started-content p {
				max-width: 100%;
			}
		}

		@media (max-width: 768px) {
			.header { padding: 12px 20px; }
			.nav { display: none; }
			.mobile-menu-btn { display: flex; }
			.main { padding: 32px 20px; }
			.grid { gap: 32px; }

			/* Remove all vertical rules and left padding on mobile */
			.grid-cell.rule-left {
				padding-left: 0;
				border-left: none;
			}

			.hero-cell {
				padding-right: 0;
				padding-bottom: 32px;
			}
			.showcase-cell {
				padding-top: 32px;
			}
			.capability-list {
				grid-template-columns: 1fr;
			}
			.capability-card {
				grid-column: span 12;
				padding: 0 !important;
				border: none !important;
			}
			.audience-cell {
				grid-column: span 12;
				padding: 0 !important;
			}
			.audience-cell.rule-left {
				padding-top: 32px !important;
				margin-top: 8px;
				border-top: 1px solid var(--color-rule);
			}
			h1 { font-size: 34px; }
			.hero-subtitle { font-size: 16px; }
			.showcase-stat .number { font-size: 48px; }
			.value-cell { padding: 40px 24px; }
			.value-cell h2 { font-size: 26px; }
			.value-cell p { font-size: 16px; }
			.section-rule { margin: 48px 0; }
			.blog-cell {
				flex-direction: column;
				gap: 16px;
				text-align: center;
			}
			.blog-content { flex-direction: column; }
			.footer {
				flex-direction: column;
				gap: 16px;
				text-align: center;
			}
			.getting-started-cell {
				padding: 24px;
			}
			.getting-started-actions {
				flex-direction: column;
				width: 100%;
			}
			.getting-started-actions .btn {
				justify-content: center;
			}
		}

		@media (max-width: 480px) {
			.cta-row { flex-direction: column; }
			.btn { justify-content: center; }
		}
	</style>
</head>
<body>
	<header class="header">
		<a href="/" class="logo">
			<img src="https://docs.figma-console-mcp.southleft.com/logo/light.svg" alt="Figma Console MCP" class="logo-dark">
			<img src="https://docs.figma-console-mcp.southleft.com/logo/dark.svg" alt="Figma Console MCP" class="logo-light" style="display: none;">
		</a>
		<div class="header-right">
			<nav class="nav">
				<a href="https://docs.figma-console-mcp.southleft.com">Documentation</a>
				<a href="https://github.com/southleft/figma-console-mcp">GitHub</a>
				<a href="https://www.npmjs.com/package/figma-console-mcp">npm</a>
				<a href="https://southleft.com/insights/ai/figma-console-mcp-ai-powered-design-system-management/">Blog</a>
				<a href="https://github.com/sponsors/southleft" class="sponsor-link"><svg viewBox="0 0 16 16"><path d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.6 20.6 0 0 0 8 13.393a20.6 20.6 0 0 0 3.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.749.749 0 0 1-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5"/></svg>Sponsor</a>
			</nav>
			<button class="theme-toggle" aria-label="Toggle theme">
				<svg class="moon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
				<svg class="sun" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
			</button>
			<button class="mobile-menu-btn" aria-label="Menu">
				<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
			</button>
		</div>
	</header>

	<!-- Mobile Menu -->
	<div class="mobile-menu" id="mobileMenu">
		<div class="mobile-menu-header">
			<a href="/" class="logo">
				<img src="https://docs.figma-console-mcp.southleft.com/logo/light.svg" alt="Figma Console MCP" class="logo-dark">
				<img src="https://docs.figma-console-mcp.southleft.com/logo/dark.svg" alt="Figma Console MCP" class="logo-light" style="display: none;">
			</a>
			<button class="mobile-menu-close" aria-label="Close menu">
				<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12"/></svg>
			</button>
		</div>
		<nav class="mobile-menu-nav">
			<a href="https://docs.figma-console-mcp.southleft.com">Documentation</a>
			<a href="https://github.com/southleft/figma-console-mcp">GitHub</a>
			<a href="https://www.npmjs.com/package/figma-console-mcp">npm</a>
			<a href="https://southleft.com/insights/ai/figma-console-mcp-ai-powered-design-system-management/">Blog</a>
			<a href="https://github.com/sponsors/southleft" style="color: #db61a2;">♥ Sponsor</a>
		</nav>
	</div>

	<main class="main">
		<div class="grid">
			<!-- Hero -->
			<div class="grid-cell hero-cell">
				<div class="badge">Model Context Protocol</div>
				<h1>Your design system, now a <span class="highlight">living API</span></h1>
				<p class="hero-subtitle">The most comprehensive MCP server for Figma. Give AI assistants deep access to your design tokens, components, and variables. Read, query, and even create designs programmatically through natural language.</p>
				<div class="cta-row">
					<a href="https://docs.figma-console-mcp.southleft.com" class="btn btn-primary">
						Read the Docs
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
					</a>
					<a href="https://github.com/southleft/figma-console-mcp" class="btn btn-secondary">
						<svg fill="currentColor" viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
						View on GitHub
					</a>
				</div>
			</div>

			<!-- Capabilities showcase -->
			<div class="grid-cell showcase-cell rule-left">
				<div class="showcase-label">What AI Can Access</div>
				<div class="showcase-stat">
					<span class="number">103+</span>
					<span class="label">MCP tools for Figma</span>
				</div>
				<div class="capability-list">
					<div class="capability-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/></svg>
						<span>Design tokens and variables</span>
					</div>
					<div class="capability-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
						<span>Component specs and properties</span>
					</div>
					<div class="capability-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
						<span>Programmatic design creation</span>
					</div>
					<div class="capability-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
						<span>WCAG accessibility linting</span>
					</div>
					<div class="capability-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"/></svg>
						<span>Cloud relay for web AI clients</span>
					</div>
					<div class="capability-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
						<span>Visual debugging and screenshots</span>
					</div>
					<div class="capability-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"/></svg>
						<span>Design annotations and dev specs</span>
					</div>
					<div class="capability-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="4" rx="1"/><rect x="2" y="9" width="9" height="4" rx="1"/><rect x="13" y="9" width="9" height="4" rx="1"/><rect x="2" y="15" width="20" height="4" rx="1"/></svg>
						<span>FigJam boards and Slides presentations</span>
					</div>
				</div>
			</div>
		</div>

		<hr class="section-rule">

		<!-- Value proposition -->
		<div class="grid">
			<div class="grid-cell value-cell">
				<h2>Design system intelligence for AI assistants</h2>
				<p>Whether you're maintaining a component library, implementing designs in code, or building Figma plugins, this MCP server gives AI the deep context it needs.</p>
			</div>
		</div>

		<hr class="section-rule">

		<!-- Capabilities detail -->
		<div class="grid">
			<div class="grid-cell capability-card">
				<div class="capability-icon">
					<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/></svg>
				</div>
				<h3>Design Tokens</h3>
				<p>Extract colors, typography, spacing, and effects. Export as CSS custom properties, Tailwind config, or Sass variables.</p>
			</div>

			<div class="grid-cell capability-card rule-left">
				<div class="capability-icon">
					<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
				</div>
				<h3>Component Specs</h3>
				<p>Get detailed layout, spacing, variants, and property data for any component in your design system.</p>
			</div>

			<div class="grid-cell capability-card rule-left">
				<div class="capability-icon">
					<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
				</div>
				<h3>Programmatic Design</h3>
				<p>Create and modify variables, build component variants, and generate design elements through natural language.</p>
			</div>

			<div class="grid-cell capability-card rule-left">
				<div class="capability-icon">
					<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
				</div>
				<h3>Visual Debugging</h3>
				<p>Capture screenshots, inspect node properties, and track selection changes. Let AI analyze your designs and suggest improvements.</p>
			</div>
		</div>

		<hr class="section-rule">

		<div class="grid">
			<!-- Example Prompts -->
			<div class="grid-cell prompts-cell">
				<div class="section-header">
					<div class="section-header-icon">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
					</div>
					<h3>What you can ask</h3>
				</div>
				<div class="prompt-list">
					<div class="prompt-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
						<span>"Extract all color variables as Tailwind config"</span>
					</div>
					<div class="prompt-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
						<span>"Get the Button component specs from my design system"</span>
					</div>
					<div class="prompt-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
						<span>"Create a dark mode version of my color variables"</span>
					</div>
					<div class="prompt-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
						<span>"Check my design for accessibility issues"</span>
					</div>
					<div class="prompt-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
						<span>"Connect to my Figma plugin and create a card component"</span>
					</div>
					<div class="prompt-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
						<span>"Create a retrospective board with colored stickies on FigJam"</span>
					</div>
					<div class="prompt-item">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
						<span>"List my slides and set a dissolve transition on each one"</span>
					</div>
				</div>
			</div>

			<!-- For Designers -->
			<div class="grid-cell audience-cell rule-left">
				<div class="audience-header">
					<div class="audience-icon">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
					</div>
					<h3>For Designers</h3>
				</div>
				<ul class="audience-list">
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Generate design token documentation automatically</span>
					</li>
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Create component variants with AI assistance</span>
					</li>
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Lint designs for accessibility and quality issues</span>
					</li>
				</ul>
			</div>
		</div>

		<div class="grid" style="margin-top: 48px;">
			<!-- For Engineers -->
			<div class="grid-cell audience-cell">
				<div class="audience-header">
					<div class="audience-icon">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
					</div>
					<h3>For Engineers</h3>
				</div>
				<ul class="audience-list">
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Extract tokens as CSS, Tailwind, or Sass variables</span>
					</li>
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Get accurate component specs for implementation</span>
					</li>
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Query your design system via MCP-enabled AI tools</span>
					</li>
				</ul>
			</div>

			<!-- For Teams -->
			<div class="grid-cell audience-cell rule-left">
				<div class="audience-header">
					<div class="audience-icon">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
					</div>
					<h3>For Teams of All Sizes</h3>
				</div>
				<ul class="audience-list">
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Indie developers to enterprise design systems</span>
					</li>
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Local mode for development, remote for production</span>
					</li>
					<li>
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
						<span>Works with Claude, Cursor, and any MCP client</span>
					</li>
				</ul>
			</div>
		</div>

		<!-- Getting Started CTA -->
		<div class="grid">
			<div class="grid-cell getting-started-cell">
				<div class="getting-started-content">
					<h3>Ready to get started?</h3>
					<p>Three ways to connect: local mode for full capabilities, cloud mode for web AI clients like Claude.ai and v0, or remote mode for quick read-only access. Our docs will guide you through the right path.</p>
				</div>
				<div class="getting-started-actions">
					<a href="https://docs.figma-console-mcp.southleft.com/setup" class="btn btn-primary">
						View Setup Guide
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
					</a>
					<a href="https://docs.figma-console-mcp.southleft.com/tools" class="btn btn-secondary">
						Explore Tools
					</a>
				</div>
			</div>
		</div>

		<div class="grid">
			<!-- Blog CTA -->
			<a href="https://southleft.com/insights/ai/figma-console-mcp-ai-powered-design-system-management/" class="grid-cell blog-cell">
				<div class="blog-content">
					<div class="blog-icon">
						<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
					</div>
					<div class="blog-text">
						<h4>Read the announcement</h4>
						<p>AI-Powered Design System Management with Figma Console MCP</p>
					</div>
				</div>
				<span class="blog-link">
					Read article
					<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
				</span>
			</a>
		</div>
	</main>

	<footer class="footer">
		<p>MIT License. Built by <a href="https://southleft.com">Southleft</a></p>
		<div class="footer-links">
			<a href="https://docs.figma-console-mcp.southleft.com">Docs</a>
			<a href="https://github.com/southleft/figma-console-mcp">GitHub</a>
			<a href="https://www.npmjs.com/package/figma-console-mcp">npm</a>
			<a href="https://github.com/sponsors/southleft" style="color: #db61a2;">♥ Sponsor</a>
		</div>
	</footer>

	<script>
		// Theme toggle with system preference detection
		(function() {
			const html = document.documentElement;
			const toggle = document.querySelector('.theme-toggle');
			const logosDark = document.querySelectorAll('.logo-dark');
			const logosLight = document.querySelectorAll('.logo-light');

			function getSystemTheme() {
				return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
			}

			function getStoredTheme() {
				return localStorage.getItem('theme');
			}

			function setTheme(theme) {
				html.setAttribute('data-theme', theme);
				localStorage.setItem('theme', theme);
				updateLogos(theme);
			}

			function updateLogos(theme) {
				logosDark.forEach(logo => {
					logo.style.display = theme === 'light' ? 'none' : 'block';
				});
				logosLight.forEach(logo => {
					logo.style.display = theme === 'light' ? 'block' : 'none';
				});
			}

			// Initialize theme
			const storedTheme = getStoredTheme();
			const initialTheme = storedTheme || getSystemTheme();
			setTheme(initialTheme);

			// Listen for system theme changes
			window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
				if (!getStoredTheme()) {
					setTheme(e.matches ? 'light' : 'dark');
				}
			});

			// Toggle button
			toggle.addEventListener('click', () => {
				const currentTheme = html.getAttribute('data-theme');
				setTheme(currentTheme === 'light' ? 'dark' : 'light');
			});
		})();

		// Mobile menu toggle
		(function() {
			const menuBtn = document.querySelector('.mobile-menu-btn');
			const menu = document.getElementById('mobileMenu');
			const closeBtn = document.querySelector('.mobile-menu-close');
			const menuLinks = document.querySelectorAll('.mobile-menu-nav a');

			function openMenu() {
				menu.classList.add('active');
				document.body.classList.add('menu-open');
			}

			function closeMenu() {
				menu.classList.remove('active');
				document.body.classList.remove('menu-open');
			}

			menuBtn.addEventListener('click', openMenu);
			closeBtn.addEventListener('click', closeMenu);

			// Close menu when clicking a link
			menuLinks.forEach(link => {
				link.addEventListener('click', closeMenu);
			});

			// Close menu on escape key
			document.addEventListener('keydown', (e) => {
				if (e.key === 'Escape' && menu.classList.contains('active')) {
					closeMenu();
				}
			});
		})();
	</script>
</body>
</html>`,
			{
				headers: { "Content-Type": "text/html; charset=utf-8" }
			}
		);
	}

	return new Response("Not found", { status: 404 });
	},
};
