@rsc-kit/mcp 0.16.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/answers.d.ts +14 -1
- package/dist/answers.js +76 -34
- package/dist/answers.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/recipes.js +434 -14
- package/dist/recipes.js.map +1 -1
- package/dist/report.d.ts +12 -0
- package/dist/report.js +17 -15
- package/dist/report.js.map +1 -1
- package/guides/api-routes.md +106 -6
- package/guides/authorization.md +39 -0
- package/guides/backend-answered-pages.md +163 -0
- package/guides/coming-from-next.md +12 -4
- package/guides/deployment.md +129 -0
- package/guides/domains.md +129 -0
- package/guides/emails.md +90 -0
- package/guides/errors.md +16 -2
- package/guides/go.md +194 -0
- package/guides/images.md +10 -4
- package/guides/index.json +50 -0
- package/guides/installation.md +31 -13
- package/guides/instrumentation.md +91 -0
- package/guides/introduction.md +4 -0
- package/guides/laravel.md +406 -0
- package/guides/mcp.md +1 -1
- package/guides/metadata.md +1 -1
- package/guides/offline.md +8 -0
- package/guides/queries.md +138 -8
- package/guides/quick-start.md +31 -0
- package/guides/redirects.md +14 -0
- package/guides/response-headers.md +22 -0
- package/guides/routing.md +5 -1
- package/guides/seo-files.md +140 -0
- package/guides/typed-routes.md +26 -0
- package/guides/view-transitions.md +53 -7
- package/guides/where-it-runs.md +155 -0
- package/guides/your-own-backend.md +238 -0
- package/package.json +2 -2
package/dist/answers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BuildReport } from
|
|
1
|
+
import type { BuildReport } from "./report.js";
|
|
2
2
|
/**
|
|
3
3
|
* Every answer says how old it is.
|
|
4
4
|
*
|
|
@@ -25,4 +25,17 @@ export declare function heaviestRoutes(report: BuildReport, builtAt: Date, now:
|
|
|
25
25
|
* middleware; an agent adding a delete button needs to know that before it
|
|
26
26
|
* trusts the id it was handed.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* Server files still importing cache from React. React's dedupes only inside
|
|
30
|
+
* a component render; in a guard, an action or an api route it calls straight
|
|
31
|
+
* through, silently - the helper runs twice and nothing says so but this.
|
|
32
|
+
*/
|
|
33
|
+
export declare function reactCacheLines(report: BuildReport): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Server files importing a client library. Legal for a server component; a
|
|
36
|
+
* file with no "use client" that only wraps client components (a shadcn ui/
|
|
37
|
+
* file that lost its directive) wants the directive back, so the library's
|
|
38
|
+
* internals stop running on the server.
|
|
39
|
+
*/
|
|
40
|
+
export declare function clientImportLines(report: BuildReport): string[];
|
|
28
41
|
export declare function actionLines(report: BuildReport): string[];
|
package/dist/answers.js
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
// them, so a vague sentence here becomes a wrong edit somewhere else — "this
|
|
6
6
|
// route is dynamic" invites a fix, "this route reads cookies, which is why"
|
|
7
7
|
// invites the right one.
|
|
8
|
-
import { MEANING, routeFor } from
|
|
8
|
+
import { MEANING, routeFor } from "./report.js";
|
|
9
9
|
const kb = (bytes) => `${bytes < 10_000 ? (bytes / 1000).toFixed(1) : Math.round(bytes / 1000)} kB`;
|
|
10
10
|
const age = (builtAt, now) => {
|
|
11
11
|
const minutes = Math.round((now - builtAt.getTime()) / 60_000);
|
|
12
12
|
if (minutes < 1)
|
|
13
|
-
return
|
|
13
|
+
return "just now";
|
|
14
14
|
if (minutes < 60)
|
|
15
|
-
return `${minutes} minute${minutes === 1 ?
|
|
15
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
16
16
|
const hours = Math.round(minutes / 60);
|
|
17
17
|
if (hours < 24)
|
|
18
|
-
return `${hours} hour${hours === 1 ?
|
|
18
|
+
return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
19
19
|
return `${Math.round(hours / 24)} days ago`;
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
@@ -31,15 +31,15 @@ export function asOf(builtAt, now) {
|
|
|
31
31
|
export function listRoutes(report, builtAt, now) {
|
|
32
32
|
const lines = [
|
|
33
33
|
`${report.routes.length} routes and ${report.apis.length} api routes ${asOf(builtAt, now)}`,
|
|
34
|
-
|
|
34
|
+
"",
|
|
35
35
|
];
|
|
36
36
|
// First, before the table, because it changes what the table means: these
|
|
37
37
|
// rows are from a build that did not finish, and nothing below is deployed.
|
|
38
38
|
if (report.totals.failed > 0) {
|
|
39
|
-
lines.unshift(`THE LAST BUILD FAILED: ${report.totals.failed} route${report.totals.failed === 1 ?
|
|
39
|
+
lines.unshift(`THE LAST BUILD FAILED: ${report.totals.failed} route${report.totals.failed === 1 ? "" : "s"} refused. Each one's line below says what to change. Fix it and build again.`, "");
|
|
40
40
|
}
|
|
41
41
|
for (const route of report.routes) {
|
|
42
|
-
const size = route.clientJs === null ?
|
|
42
|
+
const size = route.clientJs === null ? "" : ` ${kb(route.clientJs)}`;
|
|
43
43
|
lines.push(`${route.url}${size} — ${MEANING[route.type] ?? route.type}`);
|
|
44
44
|
if (route.reason)
|
|
45
45
|
lines.push(` ${route.reason}`);
|
|
@@ -47,20 +47,22 @@ export function listRoutes(report, builtAt, now) {
|
|
|
47
47
|
lines.push(` ${route.note}`);
|
|
48
48
|
}
|
|
49
49
|
if (report.apis.length) {
|
|
50
|
-
lines.push(
|
|
50
|
+
lines.push("", "api routes:");
|
|
51
51
|
for (const api of report.apis) {
|
|
52
52
|
lines.push(`${api.url} — ${MEANING[api.type] ?? api.type}`);
|
|
53
53
|
if (api.reason)
|
|
54
54
|
lines.push(` ${api.reason}`);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
lines.push(
|
|
58
|
-
(report.totals.failed ? `, ${report.totals.failed} failed` :
|
|
59
|
-
lines.push(
|
|
60
|
-
|
|
57
|
+
lines.push("", `${report.totals.static} static, ${report.totals.partial} partial prerender, ${report.totals.dynamic} dynamic` +
|
|
58
|
+
(report.totals.failed ? `, ${report.totals.failed} failed` : ""));
|
|
59
|
+
lines.push("", ...actionLines(report));
|
|
60
|
+
lines.push(...reactCacheLines(report));
|
|
61
|
+
lines.push(...clientImportLines(report));
|
|
62
|
+
return lines.join("\n");
|
|
61
63
|
}
|
|
62
64
|
function isPage(route) {
|
|
63
|
-
return
|
|
65
|
+
return "component" in route;
|
|
64
66
|
}
|
|
65
67
|
export function explainRoute(report, url, builtAt, now) {
|
|
66
68
|
const route = routeFor(report, url);
|
|
@@ -68,10 +70,12 @@ export function explainRoute(report, url, builtAt, now) {
|
|
|
68
70
|
// The urls, not just "not found": the caller has a url that does not exist,
|
|
69
71
|
// and the most useful next thing is the ones that do.
|
|
70
72
|
return (`No route for ${url} ${asOf(builtAt, now)}.\n\n` +
|
|
71
|
-
|
|
72
|
-
[...report.routes, ...report.apis].map((r) => ` ${r.url}`).join(
|
|
73
|
+
"Known urls:\n" +
|
|
74
|
+
[...report.routes, ...report.apis].map((r) => ` ${r.url}`).join("\n"));
|
|
73
75
|
}
|
|
74
|
-
const lines = [
|
|
76
|
+
const lines = [
|
|
77
|
+
`${route.url} — ${MEANING[route.type] ?? route.type} ${asOf(builtAt, now)}`,
|
|
78
|
+
];
|
|
75
79
|
if (isPage(route)) {
|
|
76
80
|
lines.push(`Rendered by ${route.component}.`);
|
|
77
81
|
if (route.clientJs !== null) {
|
|
@@ -79,13 +83,13 @@ export function explainRoute(report, url, builtAt, now) {
|
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
if (route.reason)
|
|
82
|
-
lines.push(
|
|
86
|
+
lines.push("", `Why it is not stored whole: ${route.reason}`);
|
|
83
87
|
if (isPage(route) && route.warning)
|
|
84
|
-
lines.push(
|
|
85
|
-
if (route.type ===
|
|
86
|
-
lines.push(
|
|
88
|
+
lines.push("", `Warning: ${route.warning}`);
|
|
89
|
+
if (route.type === "frozen") {
|
|
90
|
+
lines.push("", "Nothing to fix. It is rendered once at build time and served as a file.");
|
|
87
91
|
}
|
|
88
|
-
return lines.join(
|
|
92
|
+
return lines.join("\n");
|
|
89
93
|
}
|
|
90
94
|
/**
|
|
91
95
|
* The routes that are not stored, and why.
|
|
@@ -95,20 +99,20 @@ export function explainRoute(report, url, builtAt, now) {
|
|
|
95
99
|
* out entirely rather than listed and dismissed.
|
|
96
100
|
*/
|
|
97
101
|
export function whatIsDynamic(report, builtAt, now) {
|
|
98
|
-
const pages = report.routes.filter((r) => r.type !==
|
|
99
|
-
const apis = report.apis.filter((a) => a.type !==
|
|
102
|
+
const pages = report.routes.filter((r) => r.type !== "frozen");
|
|
103
|
+
const apis = report.apis.filter((a) => a.type !== "frozen");
|
|
100
104
|
if (pages.length === 0 && apis.length === 0) {
|
|
101
105
|
return `Every route is stored at build time ${asOf(builtAt, now)}. Nothing renders per request.`;
|
|
102
106
|
}
|
|
103
107
|
const lines = [
|
|
104
108
|
`${pages.length + apis.length} of ${report.routes.length + report.apis.length} routes render per request ${asOf(builtAt, now)}:`,
|
|
105
|
-
|
|
109
|
+
"",
|
|
106
110
|
];
|
|
107
111
|
for (const route of [...pages, ...apis]) {
|
|
108
112
|
lines.push(`${route.url} — ${route.reason ?? MEANING[route.type] ?? route.type}`);
|
|
109
113
|
}
|
|
110
|
-
lines.push(
|
|
111
|
-
return lines.join(
|
|
114
|
+
lines.push("", "Reading the request is what makes a route dynamic: cookies(), headers(), searchParams(),", "or connection() said deliberately. That is usually correct — a page whose content depends", "on who is asking cannot be one stored file. Change it only if the read was accidental.");
|
|
115
|
+
return lines.join("\n");
|
|
112
116
|
}
|
|
113
117
|
/** The heaviest routes, for the question that follows the size column. */
|
|
114
118
|
export function heaviestRoutes(report, builtAt, now, top = 10) {
|
|
@@ -121,13 +125,13 @@ export function heaviestRoutes(report, builtAt, now, top = 10) {
|
|
|
121
125
|
const lightest = weighed[weighed.length - 1].clientJs ?? 0;
|
|
122
126
|
return [
|
|
123
127
|
`Heaviest routes ${asOf(builtAt, now)}:`,
|
|
124
|
-
|
|
128
|
+
"",
|
|
125
129
|
...weighed.slice(0, top).map((r) => `${kb(r.clientJs ?? 0)} ${r.url}`),
|
|
126
|
-
|
|
130
|
+
"",
|
|
127
131
|
`The lightest route ships ${kb(lightest)}, so the difference between them is`,
|
|
128
132
|
`${kb((weighed[0].clientJs ?? 0) - lightest)} of client components — most of the rest is React itself,`,
|
|
129
|
-
|
|
130
|
-
].join(
|
|
133
|
+
"which every route pays for.",
|
|
134
|
+
].join("\n");
|
|
131
135
|
}
|
|
132
136
|
/**
|
|
133
137
|
* The actions, and the one fact about each that nothing else states: whether
|
|
@@ -135,17 +139,55 @@ export function heaviestRoutes(report, builtAt, now, top = 10) {
|
|
|
135
139
|
* middleware; an agent adding a delete button needs to know that before it
|
|
136
140
|
* trusts the id it was handed.
|
|
137
141
|
*/
|
|
142
|
+
/**
|
|
143
|
+
* Server files still importing cache from React. React's dedupes only inside
|
|
144
|
+
* a component render; in a guard, an action or an api route it calls straight
|
|
145
|
+
* through, silently - the helper runs twice and nothing says so but this.
|
|
146
|
+
*/
|
|
147
|
+
export function reactCacheLines(report) {
|
|
148
|
+
const files = report.reactCache;
|
|
149
|
+
if (!files || files.length === 0)
|
|
150
|
+
return [];
|
|
151
|
+
return [
|
|
152
|
+
"",
|
|
153
|
+
`${files.length} server ${files.length === 1 ? "file imports" : "files import"} cache from 'react': ${files.join(", ")}`,
|
|
154
|
+
"React's cache() dedupes only inside a component render; in a guard, an action or an api route it calls straight through. Import cache from @rsc-kit/core/cache, which spans the request.",
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Server files importing a client library. Legal for a server component; a
|
|
159
|
+
* file with no "use client" that only wraps client components (a shadcn ui/
|
|
160
|
+
* file that lost its directive) wants the directive back, so the library's
|
|
161
|
+
* internals stop running on the server.
|
|
162
|
+
*/
|
|
163
|
+
export function clientImportLines(report) {
|
|
164
|
+
const found = report.clientImports;
|
|
165
|
+
if (!found || found.length === 0)
|
|
166
|
+
return [];
|
|
167
|
+
return [
|
|
168
|
+
"",
|
|
169
|
+
`${found.length} server ${found.length === 1 ? "file imports" : "files import"} a client library: ` +
|
|
170
|
+
found
|
|
171
|
+
.map((c) => `${c.file} (${c.packages.join(", ")}${c.from ? `; imported by ${c.from}` : ""})`)
|
|
172
|
+
.join("; "),
|
|
173
|
+
'Legal for a server component. A file that only wraps client components wants "use client" - as shadcn ships it - so the server stops at the boundary.',
|
|
174
|
+
];
|
|
175
|
+
}
|
|
138
176
|
export function actionLines(report) {
|
|
139
177
|
const actions = report.actions;
|
|
140
178
|
if (!actions)
|
|
141
|
-
return [
|
|
179
|
+
return ["actions: not audited by this build (older @rsc-kit/core)"];
|
|
142
180
|
if (actions.length === 0)
|
|
143
|
-
return [
|
|
181
|
+
return ["actions: none"];
|
|
144
182
|
const bare = actions.filter((a) => !a.client);
|
|
145
|
-
const lines = [
|
|
183
|
+
const lines = [
|
|
184
|
+
`actions: ${actions.length}, ${actions.length - bare.length} built from an action client`,
|
|
185
|
+
];
|
|
146
186
|
if (bare.length > 0) {
|
|
147
187
|
lines.push(`${bare.length} run NO middleware — nothing checks who calls them: ` +
|
|
148
|
-
bare
|
|
188
|
+
bare
|
|
189
|
+
.map((a) => `${a.name}${a.query ? " (query)" : ""} in ${a.file}`)
|
|
190
|
+
.join(", "), 'Fine for a public action. For anything else, build it from an action client so the check cannot be forgotten — how_to({ topic: "action-client" }).');
|
|
149
191
|
}
|
|
150
192
|
return lines;
|
|
151
193
|
}
|
package/dist/answers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"answers.js","sourceRoot":"","sources":["../src/answers.ts"],"names":[],"mappings":"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","sourcesContent":["// The answers, as text, with no protocol in them.\n//\n// Separated from the server so they can be tested by calling them, and so the\n// wording is reviewable in one place. An agent reads these as prose and acts on\n// them, so a vague sentence here becomes a wrong edit somewhere else — \"this\n// route is dynamic\" invites a fix, \"this route reads cookies, which is why\"\n// invites the right one.\n\nimport type { BuildReport, ReportedApiRoute, ReportedRoute } from './report.js'\nimport { MEANING, routeFor } from './report.js'\n\nconst kb = (bytes: number) => `${bytes < 10_000 ? (bytes / 1000).toFixed(1) : Math.round(bytes / 1000)} kB`\n\nconst age = (builtAt: Date, now: number): string => {\n const minutes = Math.round((now - builtAt.getTime()) / 60_000)\n\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes} minute${minutes === 1 ? '' : 's'} ago`\n\n const hours = Math.round(minutes / 60)\n\n if (hours < 24) return `${hours} hour${hours === 1 ? '' : 's'} ago`\n\n return `${Math.round(hours / 24)} days ago`\n}\n\n/**\n * Every answer says how old it is.\n *\n * The one way this server misleads is by being confidently stale: it reports\n * the last build, and the file on disk may have changed since. Saying so on\n * every answer is cheaper than being wrong once.\n */\nexport function asOf(builtAt: Date, now: number): string {\n return `(from the last build, ${age(builtAt, now)})`\n}\n\nexport function listRoutes(report: BuildReport, builtAt: Date, now: number): string {\n const lines = [\n `${report.routes.length} routes and ${report.apis.length} api routes ${asOf(builtAt, now)}`,\n '',\n ]\n\n // First, before the table, because it changes what the table means: these\n // rows are from a build that did not finish, and nothing below is deployed.\n if (report.totals.failed > 0) {\n lines.unshift(\n `THE LAST BUILD FAILED: ${report.totals.failed} route${report.totals.failed === 1 ? '' : 's'} refused. Each one's line below says what to change. Fix it and build again.`,\n '',\n )\n }\n\n for (const route of report.routes) {\n const size = route.clientJs === null ? '' : ` ${kb(route.clientJs)}`\n\n lines.push(`${route.url}${size} — ${MEANING[route.type] ?? route.type}`)\n\n if (route.reason) lines.push(` ${route.reason}`)\n if (route.note) lines.push(` ${route.note}`)\n }\n\n if (report.apis.length) {\n lines.push('', 'api routes:')\n\n for (const api of report.apis) {\n lines.push(`${api.url} — ${MEANING[api.type] ?? api.type}`)\n\n if (api.reason) lines.push(` ${api.reason}`)\n }\n }\n\n lines.push(\n '',\n `${report.totals.static} static, ${report.totals.partial} partial prerender, ${report.totals.dynamic} dynamic` +\n (report.totals.failed ? `, ${report.totals.failed} failed` : ''),\n )\n\n lines.push('', ...actionLines(report))\n\n return lines.join('\\n')\n}\n\nfunction isPage(route: ReportedRoute | ReportedApiRoute): route is ReportedRoute {\n return 'component' in route\n}\n\nexport function explainRoute(\n report: BuildReport,\n url: string,\n builtAt: Date,\n now: number,\n): string {\n const route = routeFor(report, url)\n\n if (!route) {\n // The urls, not just \"not found\": the caller has a url that does not exist,\n // and the most useful next thing is the ones that do.\n return (\n `No route for ${url} ${asOf(builtAt, now)}.\\n\\n` +\n 'Known urls:\\n' +\n [...report.routes, ...report.apis].map((r) => ` ${r.url}`).join('\\n')\n )\n }\n\n const lines = [`${route.url} — ${MEANING[route.type] ?? route.type} ${asOf(builtAt, now)}`]\n\n if (isPage(route)) {\n lines.push(`Rendered by ${route.component}.`)\n\n if (route.clientJs !== null) {\n lines.push(`Ships ${kb(route.clientJs)} of javascript, gzipped.`)\n }\n }\n\n if (route.reason) lines.push('', `Why it is not stored whole: ${route.reason}`)\n\n if (isPage(route) && route.warning) lines.push('', `Warning: ${route.warning}`)\n\n if (route.type === 'frozen') {\n lines.push(\n '',\n 'Nothing to fix. It is rendered once at build time and served as a file.',\n )\n }\n\n return lines.join('\\n')\n}\n\n/**\n * The routes that are not stored, and why.\n *\n * The question behind most of the others — someone asking \"why is my site\n * slow\" wants this list, not the whole table. Routes that are fine are left\n * out entirely rather than listed and dismissed.\n */\nexport function whatIsDynamic(report: BuildReport, builtAt: Date, now: number): string {\n const pages = report.routes.filter((r) => r.type !== 'frozen')\n const apis = report.apis.filter((a) => a.type !== 'frozen')\n\n if (pages.length === 0 && apis.length === 0) {\n return `Every route is stored at build time ${asOf(builtAt, now)}. Nothing renders per request.`\n }\n\n const lines = [\n `${pages.length + apis.length} of ${report.routes.length + report.apis.length} routes render per request ${asOf(builtAt, now)}:`,\n '',\n ]\n\n for (const route of [...pages, ...apis]) {\n lines.push(`${route.url} — ${route.reason ?? MEANING[route.type] ?? route.type}`)\n }\n\n lines.push(\n '',\n 'Reading the request is what makes a route dynamic: cookies(), headers(), searchParams(),',\n 'or connection() said deliberately. That is usually correct — a page whose content depends',\n 'on who is asking cannot be one stored file. Change it only if the read was accidental.',\n )\n\n return lines.join('\\n')\n}\n\n/** The heaviest routes, for the question that follows the size column. */\nexport function heaviestRoutes(report: BuildReport, builtAt: Date, now: number, top = 10): string {\n const weighed = report.routes\n .filter((r) => r.clientJs !== null)\n .sort((a, b) => (b.clientJs ?? 0) - (a.clientJs ?? 0))\n\n if (weighed.length === 0) {\n return `No route shipped measurable javascript ${asOf(builtAt, now)}.`\n }\n\n const lightest = weighed[weighed.length - 1].clientJs ?? 0\n\n return [\n `Heaviest routes ${asOf(builtAt, now)}:`,\n '',\n ...weighed.slice(0, top).map((r) => `${kb(r.clientJs ?? 0)} ${r.url}`),\n '',\n `The lightest route ships ${kb(lightest)}, so the difference between them is`,\n `${kb((weighed[0].clientJs ?? 0) - lightest)} of client components — most of the rest is React itself,`,\n 'which every route pays for.',\n ].join('\\n')\n}\n\n/**\n * The actions, and the one fact about each that nothing else states: whether\n * anything checks who calls it. A bare \"use server\" export runs with no\n * middleware; an agent adding a delete button needs to know that before it\n * trusts the id it was handed.\n */\nexport function actionLines(report: BuildReport): string[] {\n const actions = report.actions\n\n if (!actions) return ['actions: not audited by this build (older @rsc-kit/core)']\n if (actions.length === 0) return ['actions: none']\n\n const bare = actions.filter((a) => !a.client)\n const lines = [`actions: ${actions.length}, ${actions.length - bare.length} built from an action client`]\n\n if (bare.length > 0) {\n lines.push(\n `${bare.length} run NO middleware — nothing checks who calls them: ` +\n bare.map((a) => `${a.name}${a.query ? ' (query)' : ''} in ${a.file}`).join(', '),\n 'Fine for a public action. For anything else, build it from an action client so the check cannot be forgotten — how_to({ topic: \"action-client\" }).',\n )\n }\n\n return lines\n}\n"]}
|
|
1
|
+
{"version":3,"file":"answers.js","sourceRoot":"","sources":["../src/answers.ts"],"names":[],"mappings":"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","sourcesContent":["// The answers, as text, with no protocol in them.\n//\n// Separated from the server so they can be tested by calling them, and so the\n// wording is reviewable in one place. An agent reads these as prose and acts on\n// them, so a vague sentence here becomes a wrong edit somewhere else — \"this\n// route is dynamic\" invites a fix, \"this route reads cookies, which is why\"\n// invites the right one.\n\nimport type { BuildReport, ReportedApiRoute, ReportedRoute } from \"./report.js\";\nimport { MEANING, routeFor } from \"./report.js\";\n\nconst kb = (bytes: number) =>\n `${bytes < 10_000 ? (bytes / 1000).toFixed(1) : Math.round(bytes / 1000)} kB`;\n\nconst age = (builtAt: Date, now: number): string => {\n const minutes = Math.round((now - builtAt.getTime()) / 60_000);\n\n if (minutes < 1) return \"just now\";\n if (minutes < 60) return `${minutes} minute${minutes === 1 ? \"\" : \"s\"} ago`;\n\n const hours = Math.round(minutes / 60);\n\n if (hours < 24) return `${hours} hour${hours === 1 ? \"\" : \"s\"} ago`;\n\n return `${Math.round(hours / 24)} days ago`;\n};\n\n/**\n * Every answer says how old it is.\n *\n * The one way this server misleads is by being confidently stale: it reports\n * the last build, and the file on disk may have changed since. Saying so on\n * every answer is cheaper than being wrong once.\n */\nexport function asOf(builtAt: Date, now: number): string {\n return `(from the last build, ${age(builtAt, now)})`;\n}\n\nexport function listRoutes(\n report: BuildReport,\n builtAt: Date,\n now: number,\n): string {\n const lines = [\n `${report.routes.length} routes and ${report.apis.length} api routes ${asOf(builtAt, now)}`,\n \"\",\n ];\n\n // First, before the table, because it changes what the table means: these\n // rows are from a build that did not finish, and nothing below is deployed.\n if (report.totals.failed > 0) {\n lines.unshift(\n `THE LAST BUILD FAILED: ${report.totals.failed} route${report.totals.failed === 1 ? \"\" : \"s\"} refused. Each one's line below says what to change. Fix it and build again.`,\n \"\",\n );\n }\n\n for (const route of report.routes) {\n const size = route.clientJs === null ? \"\" : ` ${kb(route.clientJs)}`;\n\n lines.push(`${route.url}${size} — ${MEANING[route.type] ?? route.type}`);\n\n if (route.reason) lines.push(` ${route.reason}`);\n if (route.note) lines.push(` ${route.note}`);\n }\n\n if (report.apis.length) {\n lines.push(\"\", \"api routes:\");\n\n for (const api of report.apis) {\n lines.push(`${api.url} — ${MEANING[api.type] ?? api.type}`);\n\n if (api.reason) lines.push(` ${api.reason}`);\n }\n }\n\n lines.push(\n \"\",\n `${report.totals.static} static, ${report.totals.partial} partial prerender, ${report.totals.dynamic} dynamic` +\n (report.totals.failed ? `, ${report.totals.failed} failed` : \"\"),\n );\n\n lines.push(\"\", ...actionLines(report));\n lines.push(...reactCacheLines(report));\n lines.push(...clientImportLines(report));\n\n return lines.join(\"\\n\");\n}\n\nfunction isPage(\n route: ReportedRoute | ReportedApiRoute,\n): route is ReportedRoute {\n return \"component\" in route;\n}\n\nexport function explainRoute(\n report: BuildReport,\n url: string,\n builtAt: Date,\n now: number,\n): string {\n const route = routeFor(report, url);\n\n if (!route) {\n // The urls, not just \"not found\": the caller has a url that does not exist,\n // and the most useful next thing is the ones that do.\n return (\n `No route for ${url} ${asOf(builtAt, now)}.\\n\\n` +\n \"Known urls:\\n\" +\n [...report.routes, ...report.apis].map((r) => ` ${r.url}`).join(\"\\n\")\n );\n }\n\n const lines = [\n `${route.url} — ${MEANING[route.type] ?? route.type} ${asOf(builtAt, now)}`,\n ];\n\n if (isPage(route)) {\n lines.push(`Rendered by ${route.component}.`);\n\n if (route.clientJs !== null) {\n lines.push(`Ships ${kb(route.clientJs)} of javascript, gzipped.`);\n }\n }\n\n if (route.reason)\n lines.push(\"\", `Why it is not stored whole: ${route.reason}`);\n\n if (isPage(route) && route.warning)\n lines.push(\"\", `Warning: ${route.warning}`);\n\n if (route.type === \"frozen\") {\n lines.push(\n \"\",\n \"Nothing to fix. It is rendered once at build time and served as a file.\",\n );\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * The routes that are not stored, and why.\n *\n * The question behind most of the others — someone asking \"why is my site\n * slow\" wants this list, not the whole table. Routes that are fine are left\n * out entirely rather than listed and dismissed.\n */\nexport function whatIsDynamic(\n report: BuildReport,\n builtAt: Date,\n now: number,\n): string {\n const pages = report.routes.filter((r) => r.type !== \"frozen\");\n const apis = report.apis.filter((a) => a.type !== \"frozen\");\n\n if (pages.length === 0 && apis.length === 0) {\n return `Every route is stored at build time ${asOf(builtAt, now)}. Nothing renders per request.`;\n }\n\n const lines = [\n `${pages.length + apis.length} of ${report.routes.length + report.apis.length} routes render per request ${asOf(builtAt, now)}:`,\n \"\",\n ];\n\n for (const route of [...pages, ...apis]) {\n lines.push(\n `${route.url} — ${route.reason ?? MEANING[route.type] ?? route.type}`,\n );\n }\n\n lines.push(\n \"\",\n \"Reading the request is what makes a route dynamic: cookies(), headers(), searchParams(),\",\n \"or connection() said deliberately. That is usually correct — a page whose content depends\",\n \"on who is asking cannot be one stored file. Change it only if the read was accidental.\",\n );\n\n return lines.join(\"\\n\");\n}\n\n/** The heaviest routes, for the question that follows the size column. */\nexport function heaviestRoutes(\n report: BuildReport,\n builtAt: Date,\n now: number,\n top = 10,\n): string {\n const weighed = report.routes\n .filter((r) => r.clientJs !== null)\n .sort((a, b) => (b.clientJs ?? 0) - (a.clientJs ?? 0));\n\n if (weighed.length === 0) {\n return `No route shipped measurable javascript ${asOf(builtAt, now)}.`;\n }\n\n const lightest = weighed[weighed.length - 1].clientJs ?? 0;\n\n return [\n `Heaviest routes ${asOf(builtAt, now)}:`,\n \"\",\n ...weighed.slice(0, top).map((r) => `${kb(r.clientJs ?? 0)} ${r.url}`),\n \"\",\n `The lightest route ships ${kb(lightest)}, so the difference between them is`,\n `${kb((weighed[0].clientJs ?? 0) - lightest)} of client components — most of the rest is React itself,`,\n \"which every route pays for.\",\n ].join(\"\\n\");\n}\n\n/**\n * The actions, and the one fact about each that nothing else states: whether\n * anything checks who calls it. A bare \"use server\" export runs with no\n * middleware; an agent adding a delete button needs to know that before it\n * trusts the id it was handed.\n */\n/**\n * Server files still importing cache from React. React's dedupes only inside\n * a component render; in a guard, an action or an api route it calls straight\n * through, silently - the helper runs twice and nothing says so but this.\n */\nexport function reactCacheLines(report: BuildReport): string[] {\n const files = report.reactCache;\n\n if (!files || files.length === 0) return [];\n\n return [\n \"\",\n `${files.length} server ${files.length === 1 ? \"file imports\" : \"files import\"} cache from 'react': ${files.join(\", \")}`,\n \"React's cache() dedupes only inside a component render; in a guard, an action or an api route it calls straight through. Import cache from @rsc-kit/core/cache, which spans the request.\",\n ];\n}\n\n/**\n * Server files importing a client library. Legal for a server component; a\n * file with no \"use client\" that only wraps client components (a shadcn ui/\n * file that lost its directive) wants the directive back, so the library's\n * internals stop running on the server.\n */\nexport function clientImportLines(report: BuildReport): string[] {\n const found = report.clientImports;\n\n if (!found || found.length === 0) return [];\n\n return [\n \"\",\n `${found.length} server ${found.length === 1 ? \"file imports\" : \"files import\"} a client library: ` +\n found\n .map(\n (c) =>\n `${c.file} (${c.packages.join(\", \")}${c.from ? `; imported by ${c.from}` : \"\"})`,\n )\n .join(\"; \"),\n 'Legal for a server component. A file that only wraps client components wants \"use client\" - as shadcn ships it - so the server stops at the boundary.',\n ];\n}\n\nexport function actionLines(report: BuildReport): string[] {\n const actions = report.actions;\n\n if (!actions)\n return [\"actions: not audited by this build (older @rsc-kit/core)\"];\n if (actions.length === 0) return [\"actions: none\"];\n\n const bare = actions.filter((a) => !a.client);\n const lines = [\n `actions: ${actions.length}, ${actions.length - bare.length} built from an action client`,\n ];\n\n if (bare.length > 0) {\n lines.push(\n `${bare.length} run NO middleware — nothing checks who calls them: ` +\n bare\n .map((a) => `${a.name}${a.query ? \" (query)\" : \"\"} in ${a.file}`)\n .join(\", \"),\n 'Fine for a public action. For anything else, build it from an action client so the check cannot be forgotten — how_to({ topic: \"action-client\" }).',\n );\n }\n\n return lines;\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -97,7 +97,7 @@ server.registerTool('heaviest_routes', {
|
|
|
97
97
|
}));
|
|
98
98
|
server.registerTool('how_to', {
|
|
99
99
|
title: 'How to build it',
|
|
100
|
-
description: 'How to do something in an rsc-kit app — forms, prefetching, validation, the action client, data loading with TanStack Query or SWR, Suspense boundaries, offline, PWA, api routes, authorization, and why a page is dynamic. Read this BEFORE writing the code: the patterns here differ from Next and plain React in ways that compile either way. The short answer; read_guide has the full one.',
|
|
100
|
+
description: 'How to do something in an rsc-kit app — forms, prefetching, validation, the action client, data loading with TanStack Query or SWR, Suspense boundaries, offline, PWA, api routes, authorization, a Laravel or other backend, once-per-process startup, and why a page is dynamic. Read this BEFORE writing the code: the patterns here differ from Next and plain React in ways that compile either way. The short answer; read_guide has the full one.',
|
|
101
101
|
inputSchema: TOPIC_ARG,
|
|
102
102
|
annotations: { readOnlyHint: true },
|
|
103
103
|
}, (async ({ topic }) => text(howTo(topic))));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// An MCP server over what an rsc-kit build decided.\n//\n// claude mcp add rsc-kit -- npx -y @rsc-kit/mcp\n//\n// Four questions, all answered from `build-report.json` and none of them\n// requiring the app to be running:\n//\n// what routes exist, and what happened to each\n// why is this one not stored\n// which ones render per request\n// which ones cost the browser the most\n//\n// Why a server rather than letting an agent read the file: the file is JSON\n// with a `type` field whose values mean nothing without the documentation, and\n// an agent reading it guesses — \"shell\" invites being treated as a failure when\n// it is the normal, correct outcome for a page with data in it. These answers\n// say what each state means, in the same words the build printed.\n//\n// Everything is read-only. There is no tool here that edits, builds or deploys,\n// deliberately: an agent already has a shell for those, and a server that can\n// change the project is one that can change it while answering a question.\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport { explainRoute, heaviestRoutes, listRoutes, whatIsDynamic } from './answers.js'\nimport { NoReport, loadReport } from './report.js'\nimport { howTo, listTopics } from './recipes.js'\nimport { listGuides, readGuide, searchGuides } from './bundleGuides.js'\n\n/** The project to read, from the argument or the working directory. */\nconst root = process.argv[2] ?? process.cwd()\n\n/**\n * Loaded per call, never cached.\n *\n * A build happens while this server is running — that is the normal case, not\n * the exception — and an answer from the build before it is worse than a slow\n * one. The file is small and this is not a hot path.\n */\nconst read = () => loadReport(root)\n\n/**\n * Declared once, outside the call.\n *\n * Inline, TypeScript walks the sdk's tool generics against the zod shape and\n * gives up with \"type instantiation is excessively deep\" — a compiler limit\n * rather than anything wrong with the schema. A named const with the handler's\n * argument annotated stops the inference chain before it gets there.\n */\nconst URL_ARG = { url: z.string().describe('The url, e.g. /orders or /posts/hello') }\nconst TOPIC_ARG = { topic: z.string().describe('One of the topics from list_topics, e.g. forms or validation') }\nconst SLUG_ARG = { slug: z.string().describe('One of the slugs from list_guides, e.g. forms or server-actions') }\nconst PHRASE_ARG = { phrase: z.string().describe('A word or phrase, e.g. fieldErrors or metadataBase') }\n\nconst text = (body: string) => ({ content: [{ type: 'text' as const, text: body }] })\n\n/** A missing report is an answer, not a crash: it says to run a build. */\nconst answering = (produce: () => string) => {\n try {\n return text(produce())\n } catch (error) {\n if (error instanceof NoReport) return text(error.message)\n\n throw error\n }\n}\n\n/**\n * The registration surface, named rather than inferred.\n *\n * `registerTool` is generic over the zod shape, and TypeScript walks those\n * generics until it gives up — \"type instantiation is excessively deep\", which\n * is a compiler limit rather than anything wrong with the schema. Describing\n * the one method used, with the argument type written out, stops the inference\n * before it gets there and costs nothing: the schemas below are still real zod\n * and still validate at runtime.\n */\ninterface Registrar {\n registerTool(\n name: string,\n config: {\n title?: string\n description?: string\n inputSchema?: Record<string, unknown>\n annotations?: { readOnlyHint?: boolean }\n },\n handler: (args: never) => Promise<{ content: { type: 'text'; text: string }[] }>,\n ): unknown\n connect(transport: StdioServerTransport): Promise<void>\n}\n\nconst server = new McpServer({ name: 'rsc-kit', version: '0.1.0' }) as unknown as Registrar\n\nserver.registerTool(\n 'list_routes',\n {\n title: 'List routes',\n description:\n 'Every route in this rsc-kit app, what the build did with each one, and how much javascript it ships. Start here when you need to know what exists.',\n annotations: { readOnlyHint: true },\n },\n async () =>\n answering(() => {\n const { report, builtAt } = read()\n\n return listRoutes(report, builtAt, Date.now())\n }),\n)\n\nserver.registerTool(\n 'explain_route',\n {\n title: 'Explain a route',\n description:\n 'Why one url is stored at build time or rendered per request, what renders it, and what it costs the browser. Use this before changing a page to make it faster — the reason is recorded, not guessed.',\n inputSchema: URL_ARG,\n annotations: { readOnlyHint: true },\n },\n (async ({ url }: { url: string }) =>\n answering(() => {\n const { report, builtAt } = read()\n\n return explainRoute(report, url, builtAt, Date.now())\n })) as never,\n)\n\nserver.registerTool(\n 'what_is_dynamic',\n {\n title: 'What renders per request',\n description:\n 'The routes that render per request rather than being stored, each with the reason. This is the answer to \"why is this site not static\".',\n annotations: { readOnlyHint: true },\n },\n async () =>\n answering(() => {\n const { report, builtAt } = read()\n\n return whatIsDynamic(report, builtAt, Date.now())\n }),\n)\n\nserver.registerTool(\n 'heaviest_routes',\n {\n title: 'Heaviest routes',\n description: 'The routes that make the browser download the most javascript, largest first.',\n annotations: { readOnlyHint: true },\n },\n async () =>\n answering(() => {\n const { report, builtAt } = read()\n\n return heaviestRoutes(report, builtAt, Date.now())\n }),\n)\n\nserver.registerTool(\n 'how_to',\n {\n title: 'How to build it',\n description:\n 'How to do something in an rsc-kit app — forms, prefetching, validation, the action client, data loading with TanStack Query or SWR, Suspense boundaries, offline, PWA, api routes, authorization, and why a page is dynamic. Read this BEFORE writing the code: the patterns here differ from Next and plain React in ways that compile either way. The short answer; read_guide has the full one.',\n inputSchema: TOPIC_ARG,\n annotations: { readOnlyHint: true },\n },\n (async ({ topic }: { topic: string }) => text(howTo(topic))) as never,\n)\n\nserver.registerTool(\n 'list_topics',\n {\n title: 'What this server can explain',\n description: 'Every topic how_to knows about, one line each.',\n annotations: { readOnlyHint: true },\n },\n async () => text(listTopics()),\n)\n\nserver.registerTool(\n 'list_guides',\n {\n title: 'The guides',\n description: 'Every guide from docs.rsc-kit.dev, bundled with this server — the full text behind how_to, one line each.',\n annotations: { readOnlyHint: true },\n },\n async () => text(listGuides()),\n)\n\nserver.registerTool(\n 'read_guide',\n {\n title: 'Read a guide',\n description:\n 'The complete guide for one topic, as published at docs.rsc-kit.dev — routing, forms, server-actions, validation, metadata, testing, deployment and the rest. Use it when how_to is not enough or names something it does not explain.',\n inputSchema: SLUG_ARG,\n annotations: { readOnlyHint: true },\n },\n (async ({ slug }: { slug: string }) => text(readGuide(slug))) as never,\n)\n\nserver.registerTool(\n 'search_guides',\n {\n title: 'Search the guides',\n description: 'Every line in the guides that mentions a word or phrase, with the guide it is in. Use it to find which guide covers something before reading it.',\n inputSchema: PHRASE_ARG,\n annotations: { readOnlyHint: true },\n },\n (async ({ phrase }: { phrase: string }) => text(searchGuides(phrase))) as never,\n)\n\nawait server.connect(new StdioServerTransport())\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,oDAAoD;AACpD,EAAE;AACF,kDAAkD;AAClD,EAAE;AACF,yEAAyE;AACzE,mCAAmC;AACnC,EAAE;AACF,iDAAiD;AACjD,+BAA+B;AAC/B,kCAAkC;AAClC,yCAAyC;AACzC,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,kEAAkE;AAClE,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,2EAA2E;AAE3E,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACtF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEvE,uEAAuE;AACvE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;AAE7C;;;;;;GAMG;AACH,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AAEnC;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC,EAAE,CAAA;AACrF,MAAM,SAAS,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8DAA8D,CAAC,EAAE,CAAA;AAChH,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iEAAiE,CAAC,EAAE,CAAA;AACjH,MAAM,UAAU,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC,EAAE,CAAA;AAExG,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;AAErF,0EAA0E;AAC1E,MAAM,SAAS,GAAG,CAAC,OAAqB,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,QAAQ;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAEzD,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AA0BD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAyB,CAAA;AAE3F,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;IACE,KAAK,EAAE,aAAa;IACpB,WAAW,EACT,oJAAoJ;IACtJ,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,KAAK,IAAI,EAAE,CACT,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAA;IAElC,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;AAChD,CAAC,CAAC,CACL,CAAA;AAED,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;IACE,KAAK,EAAE,iBAAiB;IACxB,WAAW,EACT,uMAAuM;IACzM,WAAW,EAAE,OAAO;IACpB,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,CAAC,KAAK,EAAE,EAAE,GAAG,EAAmB,EAAE,EAAE,CAClC,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAA;IAElC,OAAO,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;AACvD,CAAC,CAAC,CAAU,CACf,CAAA;AAED,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;IACE,KAAK,EAAE,0BAA0B;IACjC,WAAW,EACT,yIAAyI;IAC3I,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,KAAK,IAAI,EAAE,CACT,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAA;IAElC,OAAO,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;AACnD,CAAC,CAAC,CACL,CAAA;AAED,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;IACE,KAAK,EAAE,iBAAiB;IACxB,WAAW,EAAE,+EAA+E;IAC5F,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,KAAK,IAAI,EAAE,CACT,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAA;IAElC,OAAO,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;AACpD,CAAC,CAAC,CACL,CAAA;AAED,MAAM,CAAC,YAAY,CACjB,QAAQ,EACR;IACE,KAAK,EAAE,iBAAiB;IACxB,WAAW,EACT,0bAA0b;IAC5b,WAAW,EAAE,SAAS;IACtB,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,CAAC,KAAK,EAAE,EAAE,KAAK,EAAqB,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAU,CACtE,CAAA;AAED,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;IACE,KAAK,EAAE,8BAA8B;IACrC,WAAW,EAAE,gDAAgD;IAC7D,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAC/B,CAAA;AAED,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;IACE,KAAK,EAAE,YAAY;IACnB,WAAW,EAAE,2GAA2G;IACxH,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAC/B,CAAA;AAED,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;IACE,KAAK,EAAE,cAAc;IACrB,WAAW,EACT,uOAAuO;IACzO,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,CAAC,KAAK,EAAE,EAAE,IAAI,EAAoB,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAU,CACvE,CAAA;AAED,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;IACE,KAAK,EAAE,mBAAmB;IAC1B,WAAW,EAAE,kJAAkJ;IAC/J,WAAW,EAAE,UAAU;IACvB,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;CACpC,EACD,CAAC,KAAK,EAAE,EAAE,MAAM,EAAsB,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAU,CAChF,CAAA;AAED,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAA","sourcesContent":["#!/usr/bin/env node\n// An MCP server over what an rsc-kit build decided.\n//\n// claude mcp add rsc-kit -- npx -y @rsc-kit/mcp\n//\n// Four questions, all answered from `build-report.json` and none of them\n// requiring the app to be running:\n//\n// what routes exist, and what happened to each\n// why is this one not stored\n// which ones render per request\n// which ones cost the browser the most\n//\n// Why a server rather than letting an agent read the file: the file is JSON\n// with a `type` field whose values mean nothing without the documentation, and\n// an agent reading it guesses — \"shell\" invites being treated as a failure when\n// it is the normal, correct outcome for a page with data in it. These answers\n// say what each state means, in the same words the build printed.\n//\n// Everything is read-only. There is no tool here that edits, builds or deploys,\n// deliberately: an agent already has a shell for those, and a server that can\n// change the project is one that can change it while answering a question.\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport { explainRoute, heaviestRoutes, listRoutes, whatIsDynamic } from './answers.js'\nimport { NoReport, loadReport } from './report.js'\nimport { howTo, listTopics } from './recipes.js'\nimport { listGuides, readGuide, searchGuides } from './bundleGuides.js'\n\n/** The project to read, from the argument or the working directory. */\nconst root = process.argv[2] ?? process.cwd()\n\n/**\n * Loaded per call, never cached.\n *\n * A build happens while this server is running — that is the normal case, not\n * the exception — and an answer from the build before it is worse than a slow\n * one. The file is small and this is not a hot path.\n */\nconst read = () => loadReport(root)\n\n/**\n * Declared once, outside the call.\n *\n * Inline, TypeScript walks the sdk's tool generics against the zod shape and\n * gives up with \"type instantiation is excessively deep\" — a compiler limit\n * rather than anything wrong with the schema. A named const with the handler's\n * argument annotated stops the inference chain before it gets there.\n */\nconst URL_ARG = { url: z.string().describe('The url, e.g. /orders or /posts/hello') }\nconst TOPIC_ARG = { topic: z.string().describe('One of the topics from list_topics, e.g. forms or validation') }\nconst SLUG_ARG = { slug: z.string().describe('One of the slugs from list_guides, e.g. forms or server-actions') }\nconst PHRASE_ARG = { phrase: z.string().describe('A word or phrase, e.g. fieldErrors or metadataBase') }\n\nconst text = (body: string) => ({ content: [{ type: 'text' as const, text: body }] })\n\n/** A missing report is an answer, not a crash: it says to run a build. */\nconst answering = (produce: () => string) => {\n try {\n return text(produce())\n } catch (error) {\n if (error instanceof NoReport) return text(error.message)\n\n throw error\n }\n}\n\n/**\n * The registration surface, named rather than inferred.\n *\n * `registerTool` is generic over the zod shape, and TypeScript walks those\n * generics until it gives up — \"type instantiation is excessively deep\", which\n * is a compiler limit rather than anything wrong with the schema. Describing\n * the one method used, with the argument type written out, stops the inference\n * before it gets there and costs nothing: the schemas below are still real zod\n * and still validate at runtime.\n */\ninterface Registrar {\n registerTool(\n name: string,\n config: {\n title?: string\n description?: string\n inputSchema?: Record<string, unknown>\n annotations?: { readOnlyHint?: boolean }\n },\n handler: (args: never) => Promise<{ content: { type: 'text'; text: string }[] }>,\n ): unknown\n connect(transport: StdioServerTransport): Promise<void>\n}\n\nconst server = new McpServer({ name: 'rsc-kit', version: '0.1.0' }) as unknown as Registrar\n\nserver.registerTool(\n 'list_routes',\n {\n title: 'List routes',\n description:\n 'Every route in this rsc-kit app, what the build did with each one, and how much javascript it ships. Start here when you need to know what exists.',\n annotations: { readOnlyHint: true },\n },\n async () =>\n answering(() => {\n const { report, builtAt } = read()\n\n return listRoutes(report, builtAt, Date.now())\n }),\n)\n\nserver.registerTool(\n 'explain_route',\n {\n title: 'Explain a route',\n description:\n 'Why one url is stored at build time or rendered per request, what renders it, and what it costs the browser. Use this before changing a page to make it faster — the reason is recorded, not guessed.',\n inputSchema: URL_ARG,\n annotations: { readOnlyHint: true },\n },\n (async ({ url }: { url: string }) =>\n answering(() => {\n const { report, builtAt } = read()\n\n return explainRoute(report, url, builtAt, Date.now())\n })) as never,\n)\n\nserver.registerTool(\n 'what_is_dynamic',\n {\n title: 'What renders per request',\n description:\n 'The routes that render per request rather than being stored, each with the reason. This is the answer to \"why is this site not static\".',\n annotations: { readOnlyHint: true },\n },\n async () =>\n answering(() => {\n const { report, builtAt } = read()\n\n return whatIsDynamic(report, builtAt, Date.now())\n }),\n)\n\nserver.registerTool(\n 'heaviest_routes',\n {\n title: 'Heaviest routes',\n description: 'The routes that make the browser download the most javascript, largest first.',\n annotations: { readOnlyHint: true },\n },\n async () =>\n answering(() => {\n const { report, builtAt } = read()\n\n return heaviestRoutes(report, builtAt, Date.now())\n }),\n)\n\nserver.registerTool(\n 'how_to',\n {\n title: 'How to build it',\n description:\n 'How to do something in an rsc-kit app — forms, prefetching, validation, the action client, data loading with TanStack Query or SWR, Suspense boundaries, offline, PWA, api routes, authorization, a Laravel or other backend, once-per-process startup, and why a page is dynamic. Read this BEFORE writing the code: the patterns here differ from Next and plain React in ways that compile either way. The short answer; read_guide has the full one.',\n inputSchema: TOPIC_ARG,\n annotations: { readOnlyHint: true },\n },\n (async ({ topic }: { topic: string }) => text(howTo(topic))) as never,\n)\n\nserver.registerTool(\n 'list_topics',\n {\n title: 'What this server can explain',\n description: 'Every topic how_to knows about, one line each.',\n annotations: { readOnlyHint: true },\n },\n async () => text(listTopics()),\n)\n\nserver.registerTool(\n 'list_guides',\n {\n title: 'The guides',\n description: 'Every guide from docs.rsc-kit.dev, bundled with this server — the full text behind how_to, one line each.',\n annotations: { readOnlyHint: true },\n },\n async () => text(listGuides()),\n)\n\nserver.registerTool(\n 'read_guide',\n {\n title: 'Read a guide',\n description:\n 'The complete guide for one topic, as published at docs.rsc-kit.dev — routing, forms, server-actions, validation, metadata, testing, deployment and the rest. Use it when how_to is not enough or names something it does not explain.',\n inputSchema: SLUG_ARG,\n annotations: { readOnlyHint: true },\n },\n (async ({ slug }: { slug: string }) => text(readGuide(slug))) as never,\n)\n\nserver.registerTool(\n 'search_guides',\n {\n title: 'Search the guides',\n description: 'Every line in the guides that mentions a word or phrase, with the guide it is in. Use it to find which guide covers something before reading it.',\n inputSchema: PHRASE_ARG,\n annotations: { readOnlyHint: true },\n },\n (async ({ phrase }: { phrase: string }) => text(searchGuides(phrase))) as never,\n)\n\nawait server.connect(new StdioServerTransport())\n"]}
|