#!/usr/bin/env node /** * HTTP/SSE transport entry point for remote deployment (e.g. Fly.io). * * The MCP server is identical to the stdio version — only the transport differs. * Clients connect via the MCP Streamable HTTP transport: * POST /mcp — send JSON-RPC messages * GET /mcp — open SSE stream * * The bridge still uses the local filesystem temp directory, so the CEP plugin * must be reachable from the same machine OR you must set PREMIERE_TEMP_DIR to * a shared volume mount that the CEP plugin also writes to. * * Environment variables: * PORT HTTP port to listen on (default: 3000) * PREMIERE_TEMP_DIR Shared temp directory for the file bridge * PREMIERE_TIMEOUT_MS Command timeout in ms (default: 30000) * MCP_AUTH_TOKEN Bearer token required on every /mcp request. REQUIRED — the * server refuses to start without it, because this transport * binds 0.0.0.0 and can drive Premiere. * MCP_OAUTH_* Alternatively configure an OAuth issuer, JWKS URI, * audience, public URL, and required scopes for per-user auth. * MCP_MAX_REQUEST_BYTES, MCP_*_TIMEOUT_MS, MCP_RATE_LIMIT_*, * MCP_MAX_CONCURRENT_REQUESTS, and MCP_MAX_CONCURRENT_STREAMS bound public * HTTP resource use. See README. */ import http from "node:http"; import fs from "node:fs"; import path from "node:path"; import { randomBytes } from "node:crypto"; import { fileURLToPath } from "node:url"; import { toNodeHandler } from "@modelcontextprotocol/node"; import { createMcpHandler } from "@modelcontextprotocol/server"; import { createServer } from "./server.js"; import { cleanupTempDir, getTempDir } from "./bridge/file-bridge.js"; import { getTelemetry } from "./telemetry.js"; import { applyHttpSecurityHeaders } from "./http-security.js"; import { OAuthResourceServer } from "./oauth-resource-server.js"; import { ProjectContextRepository } from "./context/project-context-store.js"; import { MediaWatchRegistry } from "./tools/media-watch.js"; import { HttpAdmissionController, MCP_HTTP_METHODS, exceedsRequestBodyLimit, getRequestPathname, isAuthorizedBearer, isSupportedMcpMethod, readBoundedRequestBody, rateLimitIdentity, readHttpAdmissionSettings, readHttpAuthConfiguration, RequestBodyTooLargeError, } from "./http-admission.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const LANDING_DIR = path.resolve(__dirname, "../landing-dist"); const MIME: Record = { ".html": "text/html; charset=utf-8", ".js": "application/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json", ".png": "image/png", ".mp4": "video/mp4", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".woff2":"font/woff2", ".woff": "font/woff", ".ttf": "font/ttf", ".txt": "text/plain", ".xml": "application/xml", }; function cacheControlForLandingAsset(urlPath: string, contentType: string): string { if (contentType.startsWith("text/html")) return "no-cache, must-revalidate"; if (urlPath.startsWith("/_next/static/")) return "public, max-age=31536000, immutable"; return "public, max-age=86400, stale-while-revalidate=604800"; } function injectScriptNonce(document: string, nonce: string): string { return document.replace(/)/gi, `