@gusnips/vite 0.8.43 → 0.8.44

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/README.md CHANGED
@@ -63,6 +63,87 @@ Four rules in there are load-bearing:
63
63
  points back at the others, which is why you pass the full list to every page rather than "the
64
64
  other two".
65
65
 
66
+ ## Discovery files
67
+
68
+ ```ts
69
+ import { jsonLd } from "@gusnips/vite";
70
+
71
+ jsonLd({ "@type": "SoftwareApplication", name: "Example" });
72
+ // A <script type="application/ld+json"> tag containing the object.
73
+ ```
74
+
75
+ Pass the result to `bakeHead` as `headExtra`. `jsonLd` escapes HTML control text while keeping
76
+ valid JSON. `faqJsonLd` uses the same serializer. You supply the schema.org fields, including
77
+ `SoftwareApplication` for an app or `WebAPI` for a public API. A local command is not a public API.
78
+
79
+ ```ts
80
+ import { llmsTxt, mcpDiscoveryJson, writeDist } from "@gusnips/vite";
81
+
82
+ await writeDist(
83
+ "dist",
84
+ "llms.txt",
85
+ llmsTxt({
86
+ title: "Example",
87
+ summary: "Read and update tasks.",
88
+ sections: [
89
+ {
90
+ title: "Reference",
91
+ links: [{ title: "Connect", url: "https://example.com/docs/mcp" }],
92
+ },
93
+ ],
94
+ }),
95
+ );
96
+
97
+ await writeDist(
98
+ "dist",
99
+ "llms-full.txt",
100
+ llmsTxt({
101
+ title: "Example reference",
102
+ body: referenceMarkdown,
103
+ }),
104
+ );
105
+
106
+ await writeDist(
107
+ "dist",
108
+ ".well-known/mcp.json",
109
+ mcpDiscoveryJson({
110
+ name: "Example",
111
+ homepage: "https://example.com",
112
+ documentation: "https://example.com/docs/mcp",
113
+ llmsTxt: "https://example.com/llms.txt",
114
+ llmsFullTxt: "https://example.com/llms-full.txt",
115
+ servers: [
116
+ {
117
+ name: "tasks",
118
+ transport: "streamable-http",
119
+ url: "https://api.example.com/mcp",
120
+ authentication: [
121
+ {
122
+ type: "oauth2",
123
+ protectedResourceMetadata:
124
+ "https://api.example.com/.well-known/oauth-protected-resource/mcp",
125
+ },
126
+ ],
127
+ tools: ["list_tasks", "update_task"],
128
+ },
129
+ ],
130
+ }),
131
+ );
132
+ ```
133
+
134
+ Build `referenceMarkdown` from your existing docs. The formatter adds no content or access rules.
135
+ Each server lists only its own tools, as names or objects with `name`, `title`, `description`,
136
+ `inputSchema` and `annotations`. Supplied schemas and annotations are kept unchanged.
137
+
138
+ For a local server, use `transport: "stdio"`, `command` and optional `args` instead of `url`.
139
+ `authentication` is a list of accepted methods: `oauth2` with `protectedResourceMetadata`, `api-key`
140
+ with `in` (`header` or `environment`), `name` and optional `prefix`, `session` with a `description`,
141
+ or `none` for public access. Methods can also include `description` and `documentation`.
142
+
143
+ `/.well-known/mcp.json` is discovery metadata, not a universal MCP standard or a client config.
144
+ It does not replace the live server's `tools/list`. These files do not promise search rankings or
145
+ support in a particular client.
146
+
66
147
  ## Rendering
67
148
 
68
149
  ```ts
@@ -0,0 +1,76 @@
1
+ /** Static discovery files. Content, addresses and access rules stay in the app. */
2
+ export interface LlmsLink {
3
+ title: string;
4
+ url: string;
5
+ description?: string;
6
+ }
7
+ export interface LlmsSection {
8
+ title: string;
9
+ links: readonly LlmsLink[];
10
+ }
11
+ export interface LlmsTxtOptions {
12
+ title: string;
13
+ /** A short description, written as a blockquote. */
14
+ summary?: string;
15
+ /** Markdown kept as supplied. Use this for the text in llms-full.txt. */
16
+ body?: string;
17
+ sections?: readonly LlmsSection[];
18
+ }
19
+ /** llms.txt or llms-full.txt, as Markdown with a trailing newline. */
20
+ export declare function llmsTxt({ title, summary, body, sections }: LlmsTxtOptions): string;
21
+ /** Declare each accepted method. This is documentation, not an access check. */
22
+ export type McpDiscoveryAuthentication = ({
23
+ type: "oauth2";
24
+ protectedResourceMetadata: string;
25
+ } | {
26
+ type: "api-key";
27
+ in: "header" | "environment";
28
+ name: string;
29
+ prefix?: string;
30
+ } | {
31
+ type: "none";
32
+ } | {
33
+ type: "session";
34
+ description: string;
35
+ }) & {
36
+ description?: string;
37
+ documentation?: string;
38
+ };
39
+ /** A tool as this server exposes it, not the combined catalog of every server. */
40
+ export interface McpDiscoveryTool {
41
+ name: string;
42
+ title?: string;
43
+ description?: string;
44
+ inputSchema?: object;
45
+ annotations?: object;
46
+ }
47
+ export type McpDiscoveryServer = {
48
+ name: string;
49
+ /** Several entries mean the server accepts any one of these methods. */
50
+ authentication: readonly McpDiscoveryAuthentication[];
51
+ tools?: readonly (string | McpDiscoveryTool)[];
52
+ } & ({
53
+ transport: "streamable-http";
54
+ url: string;
55
+ } | {
56
+ transport: "stdio";
57
+ command: string;
58
+ args?: readonly string[];
59
+ });
60
+ export interface McpDiscoveryDocument {
61
+ name: string;
62
+ description?: string;
63
+ homepage: string;
64
+ documentation: string;
65
+ llmsTxt?: string;
66
+ llmsFullTxt?: string;
67
+ openApi?: string;
68
+ servers: readonly McpDiscoveryServer[];
69
+ }
70
+ /**
71
+ * /.well-known/mcp.json, as JSON with a trailing newline. This is a discovery convention,
72
+ * not a standard client configuration or a replacement for MCP tools/list.
73
+ * No transport, access rule or tool is inferred, and supplied schemas and annotations are kept.
74
+ */
75
+ export declare function mcpDiscoveryJson(document: McpDiscoveryDocument): string;
76
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,mFAAmF;AAEnF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACnC;AAED,sEAAsE;AACtE,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAa,EAAE,EAAE,cAAc,GAAG,MAAM,CAuBvF;AAED,gFAAgF;AAChF,MAAM,MAAM,0BAA0B,GAAG,CACrC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,yBAAyB,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,QAAQ,GAAG,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAChF;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAC3C,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,cAAc,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACtD,KAAK,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,gBAAgB,CAAC,EAAE,CAAC;CAChD,GAAG,CACA;IAAE,SAAS,EAAE,iBAAiB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CACpE,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,CAWvE"}
@@ -0,0 +1,37 @@
1
+ /** Static discovery files. Content, addresses and access rules stay in the app. */
2
+ /** llms.txt or llms-full.txt, as Markdown with a trailing newline. */
3
+ export function llmsTxt({ title, summary, body, sections = [] }) {
4
+ if (title.trim() === "")
5
+ throw new Error("llms: `title` is empty. Name the site or package.");
6
+ const parts = [`# ${title}`];
7
+ if (summary)
8
+ parts.push(summary
9
+ .split(/\r?\n/)
10
+ .map((line) => `> ${line}`)
11
+ .join("\n"));
12
+ if (body)
13
+ parts.push(body);
14
+ for (const section of sections) {
15
+ parts.push(`## ${section.title}`);
16
+ parts.push(section.links
17
+ .map((link) => `- [${link.title}](${link.url})${link.description ? `: ${link.description}` : ""}`)
18
+ .join("\n"));
19
+ }
20
+ return `${parts.join("\n\n").trimEnd()}\n`;
21
+ }
22
+ /**
23
+ * /.well-known/mcp.json, as JSON with a trailing newline. This is a discovery convention,
24
+ * not a standard client configuration or a replacement for MCP tools/list.
25
+ * No transport, access rule or tool is inferred, and supplied schemas and annotations are kept.
26
+ */
27
+ export function mcpDiscoveryJson(document) {
28
+ if (document.name.trim() === "")
29
+ throw new Error("mcp discovery: `name` is empty. Name the site or package.");
30
+ if (document.servers.length === 0)
31
+ throw new Error("mcp discovery: `servers` is empty. List the servers this file describes.");
32
+ for (const server of document.servers)
33
+ if (server.authentication.length === 0)
34
+ throw new Error(`mcp discovery: "${server.name}" has no authentication entry. Declare how access works, including type "none" when it is public.`);
35
+ return `${JSON.stringify(document, null, 2)}\n`;
36
+ }
37
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,mFAAmF;AAsBnF,sEAAsE;AACtE,MAAM,UAAU,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,EAAE,EAAkB;IAC7E,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAC9F,MAAM,KAAK,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IAC7B,IAAI,OAAO;QACT,KAAK,CAAC,IAAI,CACR,OAAO;aACJ,KAAK,CAAC,OAAO,CAAC;aACd,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;aAC1B,IAAI,CAAC,IAAI,CAAC,CACd,CAAC;IACJ,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAClC,KAAK,CAAC,IAAI,CACR,OAAO,CAAC,KAAK;aACV,GAAG,CACF,CAAC,IAAI,EAAE,EAAE,CACP,MAAM,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACrF;aACA,IAAI,CAAC,IAAI,CAAC,CACd,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC;AAC7C,CAAC;AAwCD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAA8B;IAC7D,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAC7B,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC/E,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,OAAO;QACnC,IAAI,MAAM,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC;YACpC,MAAM,IAAI,KAAK,CACb,mBAAmB,MAAM,CAAC,IAAI,mGAAmG,CAClI,CAAC;IACN,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC"}
package/dist/head.d.ts CHANGED
@@ -83,14 +83,14 @@ export interface FaqEntry {
83
83
  answer: string;
84
84
  }
85
85
  /**
86
- * FAQ structured data for `headExtra`, with the script-closing sequence escaped.
86
+ * Structured data for `headExtra`. The caller owns the schema and its claims.
87
87
  *
88
- * A `</script>` inside an answer ends the element early and ships the rest of the JSON as markup.
89
- * `<\/` is the same string to a JSON parser and inert to an HTML one, so every `</` becomes `<\/`
90
- * and `<!--` becomes `<\!--`. `JSON.stringify` never emits `<` outside a string, since the
91
- * structural characters are all brackets, quotes and digits. The replacement only ever touches
92
- * copy.
88
+ * Escape every less-than character as JSON, not HTML: `</script>` can close the element, and
89
+ * `<!--<script>` can make the HTML parser swallow the real closing tag. Escaping only a slash
90
+ * misses that second case; adding a backslash before `!` makes the JSON invalid.
93
91
  */
92
+ export declare function jsonLd(data: object): string;
93
+ /** FAQ structured data from the same strings the page renders. */
94
94
  export declare function faqJsonLd(faqs: readonly FaqEntry[]): string;
95
95
  /**
96
96
  * One page's `<head>` and body, written into the built template.
@@ -1 +1 @@
1
- {"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"AAiBA,0FAA0F;AAC1F,eAAO,MAAM,UAAU,4BAA0B,CAAC;AA0FlD,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB;yBACqB;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wIAAwI;IACxI,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;gDAC4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG5C;AAED;;mFAEmF;AACnF,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAY3D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAuHjE;AAED,MAAM,WAAW,cAAc;IAC7B,wFAAwF;IACxF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;uDACmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,cAAmB,GAC1B,IAAI,CAgEN"}
1
+ {"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"AAiBA,0FAA0F;AAC1F,eAAO,MAAM,UAAU,4BAA0B,CAAC;AA0FlD,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB;yBACqB;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wIAAwI;IACxI,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;gDAC4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG5C;AAED;;mFAEmF;AACnF,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAU3D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAuHjE;AAED,MAAM,WAAW,cAAc;IAC7B,wFAAwF;IACxF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;uDACmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,cAAmB,GAC1B,IAAI,CAgEN"}
package/dist/head.js CHANGED
@@ -113,16 +113,19 @@ export function ogLocale(tag) {
113
113
  return TERRITORY[tag] ?? tag.replace("-", "_");
114
114
  }
115
115
  /**
116
- * FAQ structured data for `headExtra`, with the script-closing sequence escaped.
116
+ * Structured data for `headExtra`. The caller owns the schema and its claims.
117
117
  *
118
- * A `</script>` inside an answer ends the element early and ships the rest of the JSON as markup.
119
- * `<\/` is the same string to a JSON parser and inert to an HTML one, so every `</` becomes `<\/`
120
- * and `<!--` becomes `<\!--`. `JSON.stringify` never emits `<` outside a string, since the
121
- * structural characters are all brackets, quotes and digits. The replacement only ever touches
122
- * copy.
118
+ * Escape every less-than character as JSON, not HTML: `</script>` can close the element, and
119
+ * `<!--<script>` can make the HTML parser swallow the real closing tag. Escaping only a slash
120
+ * misses that second case; adding a backslash before `!` makes the JSON invalid.
123
121
  */
122
+ export function jsonLd(data) {
123
+ const json = JSON.stringify(data).replace(/</g, "\\u003c");
124
+ return `<script type="application/ld+json">${json}</script>`;
125
+ }
126
+ /** FAQ structured data from the same strings the page renders. */
124
127
  export function faqJsonLd(faqs) {
125
- const data = {
128
+ return jsonLd({
126
129
  "@context": "https://schema.org",
127
130
  "@type": "FAQPage",
128
131
  mainEntity: faqs.map((faq) => ({
@@ -130,9 +133,7 @@ export function faqJsonLd(faqs) {
130
133
  name: faq.question,
131
134
  acceptedAnswer: { "@type": "Answer", text: faq.answer },
132
135
  })),
133
- };
134
- const json = JSON.stringify(data).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
135
- return `<script type="application/ld+json">${json}</script>`;
136
+ });
136
137
  }
137
138
  /**
138
139
  * One page's `<head>` and body, written into the built template.
package/dist/head.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"head.js","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"head.js","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -18,8 +18,9 @@
18
18
  * and a repo that only wants a sitemap all install exactly what they use.
19
19
  */
20
20
  export { resolveKey } from "./catalog.ts";
21
+ export { llmsTxt, mcpDiscoveryJson, type LlmsLink, type LlmsSection, type LlmsTxtOptions, type McpDiscoveryAuthentication, type McpDiscoveryDocument, type McpDiscoveryServer, type McpDiscoveryTool, } from "./discovery.ts";
21
22
  export { escapeAttr } from "./escape.ts";
22
- export { assertRendered, bakeHead, EMPTY_ROOT, faqJsonLd, ogLocale, type Alternate, type FaqEntry, type HeadTags, type RenderedChecks, } from "./head.ts";
23
+ export { assertRendered, bakeHead, EMPTY_ROOT, faqJsonLd, jsonLd, ogLocale, type Alternate, type FaqEntry, type HeadTags, type RenderedChecks, } from "./head.ts";
23
24
  export { assertOgImages, loadRenderer, loadTemplate, writeDist, writeOgCards, type OgCard, type WriteOgCardsOptions, } from "./node.ts";
24
25
  export { webManifest, writeIconSet, type ManifestIcon, type PackIco, type RenderIcon, type WebManifestOptions, type WriteIconSetOptions, } from "./icons.ts";
25
26
  export { describeOverflow, fitText, OG_CANVAS, type FitOptions, type FitResult, type OgOverflow, } from "./og.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,cAAc,EACd,QAAQ,EACR,UAAU,EACV,SAAS,EACT,QAAQ,EACR,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,cAAc,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,KAAK,MAAM,EACX,KAAK,mBAAmB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAK5E,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,UAAU,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,cAAc,EACd,QAAQ,EACR,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,cAAc,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,KAAK,MAAM,EACX,KAAK,mBAAmB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAK5E,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,UAAU,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -18,12 +18,13 @@
18
18
  * and a repo that only wants a sitemap all install exactly what they use.
19
19
  */
20
20
  export { resolveKey } from "./catalog.js";
21
+ export { llmsTxt, mcpDiscoveryJson, } from "./discovery.js";
21
22
  // `headExtra` takes raw markup, so the moment an adopter builds a tag out of its own copy it
22
23
  // needs the same escaping every tag this package writes already gets. Exported rather than
23
24
  // re-invented per app: an adopter's hand-rolled copy is one that can drift from ours, and the
24
25
  // first one to need it was building a `<link title="…">` out of a translated page title.
25
26
  export { escapeAttr } from "./escape.js";
26
- export { assertRendered, bakeHead, EMPTY_ROOT, faqJsonLd, ogLocale, } from "./head.js";
27
+ export { assertRendered, bakeHead, EMPTY_ROOT, faqJsonLd, jsonLd, ogLocale, } from "./head.js";
27
28
  export { assertOgImages, loadRenderer, loadTemplate, writeDist, writeOgCards, } from "./node.js";
28
29
  export { webManifest, writeIconSet, } from "./icons.js";
29
30
  export { describeOverflow, fitText, OG_CANVAS, } from "./og.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,6FAA6F;AAC7F,2FAA2F;AAC3F,8FAA8F;AAC9F,yFAAyF;AACzF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,cAAc,EACd,QAAQ,EACR,UAAU,EACV,SAAS,EACT,QAAQ,GAKT,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,YAAY,GAGb,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,YAAY,GAMb,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,OAAO,EACP,SAAS,GAIV,MAAM,SAAS,CAAC;AACjB,0FAA0F;AAC1F,OAAO,EAAE,cAAc,EAA8B,MAAM,gBAAgB,CAAC;AAM5E,OAAO,EACL,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,UAAU,EACV,UAAU,EACV,UAAU,EACV,cAAc,GAIf,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,OAAO,EACP,gBAAgB,GAQjB,MAAM,gBAAgB,CAAC;AACxB,6FAA6F;AAC7F,2FAA2F;AAC3F,8FAA8F;AAC9F,yFAAyF;AACzF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,cAAc,EACd,QAAQ,EACR,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,GAKT,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,YAAY,GAGb,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,YAAY,GAMb,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,OAAO,EACP,SAAS,GAIV,MAAM,SAAS,CAAC;AACjB,0FAA0F;AAC1F,OAAO,EAAE,cAAc,EAA8B,MAAM,gBAAgB,CAAC;AAM5E,OAAO,EACL,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,UAAU,EACV,UAAU,EACV,UAAU,EACV,cAAc,GAIf,MAAM,cAAc,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gusnips/vite",
3
- "version": "0.8.43",
3
+ "version": "0.8.44",
4
4
  "description": "The build-time half of a prerendered Vite SPA: head baking, sitemap, robots, the prerender driver, a shared vite preset, and the translation catalog check.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,120 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { llmsTxt, mcpDiscoveryJson, type McpDiscoveryDocument } from "./discovery.ts";
3
+
4
+ describe("llmsTxt", () => {
5
+ it("writes the title, summary and links in the supplied order", () => {
6
+ expect(
7
+ llmsTxt({
8
+ title: "Example",
9
+ summary: "Read and update tasks.\nSign in before making changes.",
10
+ sections: [
11
+ {
12
+ title: "Reference",
13
+ links: [
14
+ {
15
+ title: "Connect",
16
+ url: "https://example.com/docs/mcp",
17
+ description: "Set up access.",
18
+ },
19
+ { title: "Full reference", url: "https://example.com/llms-full.txt" },
20
+ ],
21
+ },
22
+ ],
23
+ }),
24
+ ).toBe(
25
+ "# Example\n\n" +
26
+ "> Read and update tasks.\n> Sign in before making changes.\n\n" +
27
+ "## Reference\n\n" +
28
+ "- [Connect](https://example.com/docs/mcp): Set up access.\n" +
29
+ "- [Full reference](https://example.com/llms-full.txt)\n",
30
+ );
31
+ });
32
+
33
+ it("keeps app-owned Markdown and writes exactly one final newline", () => {
34
+ const body = '## Tools\n\n```json\n{"name":"list_tasks"}\n```\n\n';
35
+ expect(llmsTxt({ title: "Example", body })).toBe(`# Example\n\n${body.trimEnd()}\n`);
36
+ expect(llmsTxt({ title: "Example" })).toBe("# Example\n");
37
+ });
38
+
39
+ it("refuses an empty title", () => {
40
+ expect(() => llmsTxt({ title: " " })).toThrow(/title.*empty.*Name/);
41
+ });
42
+ });
43
+
44
+ const DOCUMENT: McpDiscoveryDocument = {
45
+ name: "Example",
46
+ homepage: "https://example.com",
47
+ documentation: "https://example.com/docs/mcp",
48
+ llmsTxt: "https://example.com/llms.txt",
49
+ llmsFullTxt: "https://example.com/llms-full.txt",
50
+ openApi: "https://api.example.com/openapi.json",
51
+ servers: [
52
+ {
53
+ name: "full",
54
+ transport: "streamable-http",
55
+ url: "https://api.example.com/mcp",
56
+ authentication: [
57
+ {
58
+ type: "oauth2",
59
+ protectedResourceMetadata:
60
+ "https://api.example.com/.well-known/oauth-protected-resource/mcp",
61
+ },
62
+ { type: "api-key", in: "header", name: "Authorization", prefix: "Bearer " },
63
+ ],
64
+ tools: [
65
+ {
66
+ name: "update_task",
67
+ title: "Update a task",
68
+ description: "Change a task's title.",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: { id: { type: "string" }, title: { type: "string", minLength: 1 } },
72
+ required: ["id", "title"],
73
+ additionalProperties: false,
74
+ },
75
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ name: "read-only",
81
+ transport: "streamable-http",
82
+ url: "https://api.example.com/mcp/read",
83
+ authentication: [{ type: "none" }],
84
+ tools: ["list_tasks"],
85
+ },
86
+ {
87
+ name: "local",
88
+ transport: "stdio",
89
+ command: "bunx",
90
+ args: ["example-mcp"],
91
+ authentication: [
92
+ { type: "session", description: "Connect the local app before you run a tool." },
93
+ { type: "api-key", in: "environment", name: "EXAMPLE_API_KEY" },
94
+ ],
95
+ },
96
+ ],
97
+ };
98
+
99
+ describe("mcpDiscoveryJson", () => {
100
+ it("keeps per-server transports, access methods and full tool cards unchanged", () => {
101
+ const output = mcpDiscoveryJson(DOCUMENT);
102
+ expect(JSON.parse(output)).toEqual(DOCUMENT);
103
+ expect(output.startsWith('{\n "name": "Example",')).toBe(true);
104
+ expect(output.endsWith("\n")).toBe(true);
105
+ expect(output.endsWith("\n\n")).toBe(false);
106
+ expect(output).not.toContain('"url": "stdio');
107
+ expect(output).not.toContain('"tools": []');
108
+ });
109
+
110
+ it("refuses empty names, server lists or access declarations", () => {
111
+ expect(() => mcpDiscoveryJson({ ...DOCUMENT, name: " " })).toThrow(/name.*empty/);
112
+ expect(() => mcpDiscoveryJson({ ...DOCUMENT, servers: [] })).toThrow(/servers.*empty/);
113
+ expect(() =>
114
+ mcpDiscoveryJson({
115
+ ...DOCUMENT,
116
+ servers: [{ name: "local", transport: "stdio", command: "bunx", authentication: [] }],
117
+ }),
118
+ ).toThrow(/local.*no authentication.*Declare/);
119
+ });
120
+ });
@@ -0,0 +1,103 @@
1
+ /** Static discovery files. Content, addresses and access rules stay in the app. */
2
+
3
+ export interface LlmsLink {
4
+ title: string;
5
+ url: string;
6
+ description?: string;
7
+ }
8
+
9
+ export interface LlmsSection {
10
+ title: string;
11
+ links: readonly LlmsLink[];
12
+ }
13
+
14
+ export interface LlmsTxtOptions {
15
+ title: string;
16
+ /** A short description, written as a blockquote. */
17
+ summary?: string;
18
+ /** Markdown kept as supplied. Use this for the text in llms-full.txt. */
19
+ body?: string;
20
+ sections?: readonly LlmsSection[];
21
+ }
22
+
23
+ /** llms.txt or llms-full.txt, as Markdown with a trailing newline. */
24
+ export function llmsTxt({ title, summary, body, sections = [] }: LlmsTxtOptions): string {
25
+ if (title.trim() === "") throw new Error("llms: `title` is empty. Name the site or package.");
26
+ const parts = [`# ${title}`];
27
+ if (summary)
28
+ parts.push(
29
+ summary
30
+ .split(/\r?\n/)
31
+ .map((line) => `> ${line}`)
32
+ .join("\n"),
33
+ );
34
+ if (body) parts.push(body);
35
+ for (const section of sections) {
36
+ parts.push(`## ${section.title}`);
37
+ parts.push(
38
+ section.links
39
+ .map(
40
+ (link) =>
41
+ `- [${link.title}](${link.url})${link.description ? `: ${link.description}` : ""}`,
42
+ )
43
+ .join("\n"),
44
+ );
45
+ }
46
+ return `${parts.join("\n\n").trimEnd()}\n`;
47
+ }
48
+
49
+ /** Declare each accepted method. This is documentation, not an access check. */
50
+ export type McpDiscoveryAuthentication = (
51
+ | { type: "oauth2"; protectedResourceMetadata: string }
52
+ | { type: "api-key"; in: "header" | "environment"; name: string; prefix?: string }
53
+ | { type: "none" }
54
+ | { type: "session"; description: string }
55
+ ) & { description?: string; documentation?: string };
56
+
57
+ /** A tool as this server exposes it, not the combined catalog of every server. */
58
+ export interface McpDiscoveryTool {
59
+ name: string;
60
+ title?: string;
61
+ description?: string;
62
+ inputSchema?: object;
63
+ annotations?: object;
64
+ }
65
+
66
+ export type McpDiscoveryServer = {
67
+ name: string;
68
+ /** Several entries mean the server accepts any one of these methods. */
69
+ authentication: readonly McpDiscoveryAuthentication[];
70
+ tools?: readonly (string | McpDiscoveryTool)[];
71
+ } & (
72
+ | { transport: "streamable-http"; url: string }
73
+ | { transport: "stdio"; command: string; args?: readonly string[] }
74
+ );
75
+
76
+ export interface McpDiscoveryDocument {
77
+ name: string;
78
+ description?: string;
79
+ homepage: string;
80
+ documentation: string;
81
+ llmsTxt?: string;
82
+ llmsFullTxt?: string;
83
+ openApi?: string;
84
+ servers: readonly McpDiscoveryServer[];
85
+ }
86
+
87
+ /**
88
+ * /.well-known/mcp.json, as JSON with a trailing newline. This is a discovery convention,
89
+ * not a standard client configuration or a replacement for MCP tools/list.
90
+ * No transport, access rule or tool is inferred, and supplied schemas and annotations are kept.
91
+ */
92
+ export function mcpDiscoveryJson(document: McpDiscoveryDocument): string {
93
+ if (document.name.trim() === "")
94
+ throw new Error("mcp discovery: `name` is empty. Name the site or package.");
95
+ if (document.servers.length === 0)
96
+ throw new Error("mcp discovery: `servers` is empty. List the servers this file describes.");
97
+ for (const server of document.servers)
98
+ if (server.authentication.length === 0)
99
+ throw new Error(
100
+ `mcp discovery: "${server.name}" has no authentication entry. Declare how access works, including type "none" when it is public.`,
101
+ );
102
+ return `${JSON.stringify(document, null, 2)}\n`;
103
+ }
package/src/head.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { PRERENDERED_ROUTE_ATTR } from "@gusnips/react";
3
- import { assertRendered, bakeHead, faqJsonLd, ogLocale } from "./head.ts";
3
+ import { assertRendered, bakeHead, faqJsonLd, jsonLd, ogLocale } from "./head.ts";
4
4
 
5
5
  /**
6
6
  * Every failure `bakeHead` has is invisible until somebody reads a search result months later
@@ -433,6 +433,37 @@ describe("assertRendered", () => {
433
433
  });
434
434
  });
435
435
 
436
+ describe("jsonLd", () => {
437
+ it("keeps the caller's schema and fields", () => {
438
+ const data = {
439
+ "@context": "https://schema.org",
440
+ "@graph": [
441
+ {
442
+ "@type": "SoftwareApplication",
443
+ name: "Example",
444
+ applicationCategory: "DeveloperApplication",
445
+ },
446
+ { "@type": "WebAPI", name: "Example API", documentation: "https://example.com/docs" },
447
+ ],
448
+ };
449
+ const script = jsonLd(data);
450
+ expect(script.startsWith('<script type="application/ld+json">')).toBe(true);
451
+ expect(JSON.parse(script.replace(/^<script[^>]*>/, "").replace(/<\/script>$/, ""))).toEqual(
452
+ data,
453
+ );
454
+ });
455
+
456
+ it("round-trips HTML parser control text without exposing markup", () => {
457
+ const data = { name: '</ScRiPt><img src=x onerror="x"> <!-- <script> $$ \\u003c' };
458
+ const script = jsonLd(data);
459
+ const inner = script.replace(/^<script[^>]*>/, "").replace(/<\/script>$/, "");
460
+ expect(inner).not.toContain("<");
461
+ expect(JSON.parse(inner)).toEqual(data);
462
+ expect(script.match(/<script/g)).toHaveLength(1);
463
+ expect(script.match(/<\/script>/g)).toHaveLength(1);
464
+ });
465
+ });
466
+
436
467
  describe("faqJsonLd", () => {
437
468
  // One donor's template carried four questions where the page renders seven: the fix is to
438
469
  // generate the block from the same strings, not to keep a second copy beside them.
@@ -448,15 +479,12 @@ describe("faqJsonLd", () => {
448
479
  expect(script.match(/"@type":"Question"/g)).toHaveLength(2);
449
480
  });
450
481
 
451
- // A `</script>` inside an answer ends the element early and ships the rest of the JSON as
452
- // markup. `<\/` is the same string to a JSON parser and inert to an HTML one.
453
482
  it("escapes a script-closing sequence inside an answer", () => {
454
483
  const script = faqJsonLd([
455
484
  { question: "What if I write code?", answer: "Wrap it: </script> ends the block." },
456
485
  ]);
457
486
  expect(script).not.toContain("</script> ends");
458
- expect(script).toContain("<\\/script> ends");
459
- // …and what it emits still parses, back to the original copy.
487
+ expect(script).toContain("\\u003c/script> ends");
460
488
  const json = script.replace(/^<script[^>]*>/, "").replace(/<\/script>$/, "");
461
489
  const parsed = JSON.parse(json) as {
462
490
  mainEntity: Array<{ acceptedAnswer: { text: string } }>;
@@ -464,11 +492,13 @@ describe("faqJsonLd", () => {
464
492
  expect(parsed.mainEntity[0]?.acceptedAnswer.text).toBe("Wrap it: </script> ends the block.");
465
493
  });
466
494
 
467
- // The positive control for the direction that flatters: an answer WITH markup must fail this
468
- // test if the escaping ever stops running, not pass because the assertion went missing.
469
- it("leaves no raw `</` sequence anywhere in the block", () => {
470
- const script = faqJsonLd([{ question: "q", answer: "a </b> b <!-- c" }]);
471
- const inner = script.replace(/<script[^>]*>/g, "").replace(/<\/script>/g, "");
472
- expect(inner).not.toMatch(/<\//);
495
+ it("keeps comments valid JSON and inert to the HTML parser", () => {
496
+ const answer = "a </b> b <!-- <script> c";
497
+ const script = faqJsonLd([{ question: "q", answer }]);
498
+ const inner = script.replace(/^<script[^>]*>/, "").replace(/<\/script>$/, "");
499
+ expect(inner).not.toContain("<");
500
+ expect(JSON.parse(inner)).toMatchObject({
501
+ mainEntity: [{ acceptedAnswer: { text: answer } }],
502
+ });
473
503
  });
474
504
  });
package/src/head.ts CHANGED
@@ -193,16 +193,20 @@ export interface FaqEntry {
193
193
  }
194
194
 
195
195
  /**
196
- * FAQ structured data for `headExtra`, with the script-closing sequence escaped.
196
+ * Structured data for `headExtra`. The caller owns the schema and its claims.
197
197
  *
198
- * A `</script>` inside an answer ends the element early and ships the rest of the JSON as markup.
199
- * `<\/` is the same string to a JSON parser and inert to an HTML one, so every `</` becomes `<\/`
200
- * and `<!--` becomes `<\!--`. `JSON.stringify` never emits `<` outside a string, since the
201
- * structural characters are all brackets, quotes and digits. The replacement only ever touches
202
- * copy.
198
+ * Escape every less-than character as JSON, not HTML: `</script>` can close the element, and
199
+ * `<!--<script>` can make the HTML parser swallow the real closing tag. Escaping only a slash
200
+ * misses that second case; adding a backslash before `!` makes the JSON invalid.
203
201
  */
202
+ export function jsonLd(data: object): string {
203
+ const json = JSON.stringify(data).replace(/</g, "\\u003c");
204
+ return `<script type="application/ld+json">${json}</script>`;
205
+ }
206
+
207
+ /** FAQ structured data from the same strings the page renders. */
204
208
  export function faqJsonLd(faqs: readonly FaqEntry[]): string {
205
- const data = {
209
+ return jsonLd({
206
210
  "@context": "https://schema.org",
207
211
  "@type": "FAQPage",
208
212
  mainEntity: faqs.map((faq) => ({
@@ -210,9 +214,7 @@ export function faqJsonLd(faqs: readonly FaqEntry[]): string {
210
214
  name: faq.question,
211
215
  acceptedAnswer: { "@type": "Answer", text: faq.answer },
212
216
  })),
213
- };
214
- const json = JSON.stringify(data).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
215
- return `<script type="application/ld+json">${json}</script>`;
217
+ });
216
218
  }
217
219
 
218
220
  /**
package/src/index.ts CHANGED
@@ -18,6 +18,17 @@
18
18
  * and a repo that only wants a sitemap all install exactly what they use.
19
19
  */
20
20
  export { resolveKey } from "./catalog.ts";
21
+ export {
22
+ llmsTxt,
23
+ mcpDiscoveryJson,
24
+ type LlmsLink,
25
+ type LlmsSection,
26
+ type LlmsTxtOptions,
27
+ type McpDiscoveryAuthentication,
28
+ type McpDiscoveryDocument,
29
+ type McpDiscoveryServer,
30
+ type McpDiscoveryTool,
31
+ } from "./discovery.ts";
21
32
  // `headExtra` takes raw markup, so the moment an adopter builds a tag out of its own copy it
22
33
  // needs the same escaping every tag this package writes already gets. Exported rather than
23
34
  // re-invented per app: an adopter's hand-rolled copy is one that can drift from ours, and the
@@ -28,6 +39,7 @@ export {
28
39
  bakeHead,
29
40
  EMPTY_ROOT,
30
41
  faqJsonLd,
42
+ jsonLd,
31
43
  ogLocale,
32
44
  type Alternate,
33
45
  type FaqEntry,