MCPcopy Create free account
hub / github.com/UsefulSoftwareCo/executor / executorApiPlugin

Function executorApiPlugin

apps/host-selfhost/vite.config.ts:48–180  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

46// stripping — the self-host API is served under /api by the prefixed router, so
47// the handler expects the full path. Handler rebuilds when src/ changes.
48function executorApiPlugin(): Plugin {
49 let handlerPromise: Promise<{ handler: (request: Request) => Promise<Response> }> | null = null;
50 const getHandler = async () => {
51 if (!handlerPromise) {
52 // Computed specifier so Vite's Node-based config loader does NOT statically
53 // follow this into ./src/api/api (which imports @executor-js/host-mcp, whose
54 // extensionless re-exports resolve under Bun but not Node ESM). It only runs
55 // at dev-server request time, under `bunx --bun vite dev`.
56 const apiModule = new URL("./src/api/api.ts", import.meta.url).href;
57 handlerPromise = import(apiModule).then((m) => m.makeSelfHostApiHandler());
58 }
59 return handlerPromise;
60 };
61
62 return {
63 name: "executor-selfhost-api",
64 apply: "serve",
65 configureServer(server) {
66 server.watcher.on("change", (path) => {
67 if (path.includes("/src/") || path.endsWith("/executor.config.ts")) handlerPromise = null;
68 });
69 server.middlewares.use(async (req, res, next) => {
70 let rawUrl = req.url ?? "/";
71 // The "Connect an agent" card prints `/<organizationId>/mcp`; self-host
72 // serves the bare `/mcp`, so rewrite it here (prod does the same in
73 // serve.ts) — otherwise this org-pinned path isn't recognized as an MCP
74 // path and falls through to the SPA as a 404. Mirrors ./src/mcp/org-path.
75 const devOrigin = `http://${req.headers.host ?? `localhost:${DEV_PORT}`}`;
76 const originalPathname = new URL(rawUrl, devOrigin).pathname;
77 const pathname = stripMcpOrgSegment(originalPathname) ?? "";
78 // Carries the ORIGINAL org-scoped pathname through to the handler (see
79 // ./src/mcp/auth.ts) so the protected-resource metadata can echo it
80 // back to a client that dialed org-scoped — mirrors serve.ts's prod
81 // middleware. Set only when we ourselves rewrote this request; any
82 // client-supplied value is dropped below so it can't be spoofed.
83 let originalPathHeader: string | null = null;
84 if (pathname !== "") {
85 const original = new URL(rawUrl, devOrigin);
86 rawUrl = `${pathname}${original.search}`;
87 originalPathHeader = originalPathname;
88 }
89 // Match on PATHNAME, not a raw-URL prefix: `/mcp` must NOT swallow the
90 // SPA route `/mcp-consent`, or the dev server misroutes it to the API
91 // handler and returns a 404.
92 const path = new URL(rawUrl, devOrigin).pathname;
93 const handled =
94 path === "/api" ||
95 path.startsWith("/api/") ||
96 path === "/mcp" ||
97 path.startsWith("/mcp/") ||
98 path === "/docs" ||
99 path.startsWith("/docs/") ||
100 // Un-prefixed app-level routes (e.g. `/v1/app/npm/dist-tags`, which the
101 // shell's update check fetches). Served by the Effect router in prod;
102 // without this the SPA index.html fallback answers 200-with-HTML and
103 // the JSON parse fails, so the UpdateCard never appears.
104 path === "/v1" ||
105 path.startsWith("/v1/") ||

Callers 1

vite.config.tsFile · 0.70

Calls

no outgoing calls

Tested by

no test coverage detected