UNPKG

@rsc-kit/mcp

Version:

An MCP server over what an rsc-kit build decided: the routes, why each one is static or not, and what it costs the browser.

56 lines • 2.63 kB
// Reading what the build wrote down. // // Everything this server answers comes from one file: `build-report.json`, in // the build's own output directory. Nothing here runs a build, imports the // app, or re-derives the route tree — the build already decided all of this, // and a second implementation would be a second thing that can be wrong. // // The consequence to be honest about: an answer is only as fresh as the last // build. So every answer says when it was built, and a missing report says // "run a build" rather than "there are no routes". import { existsSync, readFileSync, statSync } from "node:fs"; import { join, resolve } from "node:path"; /** Where a build leaves its report, in the order worth looking. */ const LIKELY = [".rsc-kit", "build", ".rsc", "dist", ".output"]; export class NoReport extends Error { constructor(root) { super(`No build report under ${root}. This server answers from what the last build ` + "decided, so there has to have been one — run the build and ask again."); this.name = "NoReport"; } } /** The report, and how old it is. */ export function loadReport(root) { const base = resolve(root); for (const dir of LIKELY) { const file = join(base, dir, "build-report.json"); if (!existsSync(file)) continue; return { report: JSON.parse(readFileSync(file, "utf-8")), // The file's own mtime rather than a timestamp inside it: a stamp written // into the file changes the file on every build even when nothing else // did, which defeats every cache keyed on its contents. builtAt: statSync(file).mtime, from: file, }; } throw new NoReport(base); } /** What each classification means, in one line, for an answer that has to stand alone. */ export const MEANING = { frozen: "stored whole at build time and served as a file", shell: "a stored shell, with the rest rendered per request", blocked: "REFUSED — nothing could paint before it read the request, so the build did not finish", dynamic: "answered per request", error: "failed to render", }; /** A route by url, tolerating a trailing slash either way. */ export function routeFor(report, url) { const wanted = (url.split("?")[0].replace(/\/+$/, "") || "/").toLowerCase(); const matches = (candidate) => (candidate.replace(/\/+$/, "") || "/").toLowerCase() === wanted; return (report.routes.find((r) => matches(r.url)) ?? report.apis.find((a) => matches(a.url)) ?? null); } //# sourceMappingURL=report.js.map