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

Function executorApiPlugin

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

Source from the content-addressed store, hash-verified

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

Callers 1

vite.config.tsFile · 0.70

Calls

no outgoing calls

Tested by

no test coverage detected