rapidoc 11.0.1 → 12.1.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.
Files changed (96) hide show
  1. package/README.md +171 -54
  2. package/dist/rapidoc-flow-min.js +580 -0
  3. package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
  4. package/dist/rapidoc-min.js +645 -202
  5. package/dist/rapidoc-min.js.LICENSE.txt +381 -0
  6. package/package.json +57 -42
  7. package/src/README.md +150 -0
  8. package/src/components/README.md +91 -0
  9. package/src/components/api-request.ts +912 -0
  10. package/src/components/api-response.ts +514 -0
  11. package/src/components/json-tree.ts +171 -0
  12. package/src/components/schema-base-element.ts +329 -0
  13. package/src/components/schema-table.ts +475 -0
  14. package/src/components/schema-tree.ts +393 -0
  15. package/src/components/tag-input.ts +180 -0
  16. package/src/flow.js +3 -0
  17. package/src/index.js +6 -0
  18. package/src/oauth-receiver.ts +29 -0
  19. package/src/rapi-flow.ts +1178 -0
  20. package/src/rapidoc-mini.ts +603 -0
  21. package/src/rapidoc.ts +1854 -0
  22. package/src/styles/README.md +96 -0
  23. package/src/styles/auth-styles.js +318 -0
  24. package/src/styles/border-styles.js +24 -0
  25. package/src/styles/css-bundle.js +129 -0
  26. package/src/styles/custom-styles.js +8 -0
  27. package/src/styles/dialog-box-styles.js +102 -0
  28. package/src/styles/endpoint-styles.js +564 -0
  29. package/src/styles/flex-styles.js +16 -0
  30. package/src/styles/flow-styles.js +248 -0
  31. package/src/styles/font-styles.js +432 -0
  32. package/src/styles/info-styles.js +36 -0
  33. package/src/styles/input-styles.js +555 -0
  34. package/src/styles/layout-styles.js +396 -0
  35. package/src/styles/microlighter-styles.js +99 -0
  36. package/src/styles/nav-styles.js +633 -0
  37. package/src/styles/param-list-styles.js +261 -0
  38. package/src/styles/schema-styles.js +296 -0
  39. package/src/styles/tab-styles.js +106 -0
  40. package/src/styles/theme-tokens.js +582 -0
  41. package/src/templates/README.md +89 -0
  42. package/src/templates/advanced-search-dialog-template.js +94 -0
  43. package/src/templates/api-response-template.js +223 -0
  44. package/src/templates/callback-template.js +86 -0
  45. package/src/templates/code-samples-template.js +52 -0
  46. package/src/templates/components-template.js +86 -0
  47. package/src/templates/downloads-template.js +70 -0
  48. package/src/templates/endpoint-template.js +377 -0
  49. package/src/templates/expanded-endpoint-template.js +270 -0
  50. package/src/templates/focused-endpoint-template.js +146 -0
  51. package/src/templates/header-template.js +92 -0
  52. package/src/templates/logo-template.js +21 -0
  53. package/src/templates/main-body-template.js +102 -0
  54. package/src/templates/navbar-template.js +498 -0
  55. package/src/templates/overview-template.js +104 -0
  56. package/src/templates/request-body-template.js +673 -0
  57. package/src/templates/request-params-template.js +485 -0
  58. package/src/templates/security-scheme-template.js +1272 -0
  59. package/src/templates/server-template.js +153 -0
  60. package/src/utils/README.md +146 -0
  61. package/src/utils/arazzo/README.md +119 -0
  62. package/src/utils/arazzo/components.js +15 -0
  63. package/src/utils/arazzo/graph.js +269 -0
  64. package/src/utils/arazzo/index.js +52 -0
  65. package/src/utils/arazzo/operations.js +106 -0
  66. package/src/utils/arazzo/parse.js +196 -0
  67. package/src/utils/arazzo/refs.js +40 -0
  68. package/src/utils/arazzo/svg.js +707 -0
  69. package/src/utils/auto-color-scheme.js +99 -0
  70. package/src/utils/bind-navbar.js +175 -0
  71. package/src/utils/color-utils.js +59 -0
  72. package/src/utils/common-utils.js +191 -0
  73. package/src/utils/curl-utils.js +126 -0
  74. package/src/utils/highlighter.js +419 -0
  75. package/src/utils/markdown-utils.js +110 -0
  76. package/src/utils/mcp/README.md +155 -0
  77. package/src/utils/mcp/common.js +166 -0
  78. package/src/utils/mcp/manifest.js +382 -0
  79. package/src/utils/mcp/project/server.ts +53 -0
  80. package/src/utils/mcp/project.js +88 -0
  81. package/src/utils/mcp/runtime.js +289 -0
  82. package/src/utils/mcp/schema.js +98 -0
  83. package/src/utils/mcp/workflow-runtime.js +374 -0
  84. package/src/utils/mcp/workflows.js +240 -0
  85. package/src/utils/mcp/zip.js +102 -0
  86. package/src/utils/mcp-generator.js +47 -0
  87. package/src/utils/mock-interceptor.js +557 -0
  88. package/src/utils/oauth-utils.js +87 -0
  89. package/src/utils/request-utils.js +264 -0
  90. package/src/utils/response-utils.js +203 -0
  91. package/src/utils/sanitize.js +57 -0
  92. package/src/utils/schema-utils.js +1954 -0
  93. package/src/utils/slugger.js +187 -0
  94. package/src/utils/spec-parser.js +941 -0
  95. package/src/utils/url-utils.js +246 -0
  96. package/src/utils/xml-utils.js +61 -0
@@ -0,0 +1,419 @@
1
+ // CSS Custom Highlight API-based Syntax Highlighter using bundled TextMate grammars
2
+
3
+ import astro from "microlighter/grammars/astro.js";
4
+ import bash from "microlighter/grammars/bash.js";
5
+ import c from "microlighter/grammars/c.js";
6
+ import cpp from "microlighter/grammars/cpp.js";
7
+ import csharp from "microlighter/grammars/csharp.js";
8
+ import cssGrammar from "microlighter/grammars/css.js";
9
+ import go from "microlighter/grammars/go.js";
10
+ import html from "microlighter/grammars/html.js";
11
+ import java from "microlighter/grammars/java.js";
12
+ import javascript from "microlighter/grammars/javascript.js";
13
+ import json from "microlighter/grammars/json.js";
14
+ import kotlin from "microlighter/grammars/kotlin.js";
15
+ import markdown from "microlighter/grammars/markdown.js";
16
+ import objectiveC from "microlighter/grammars/objective-c.js";
17
+ import perl from "microlighter/grammars/perl.js";
18
+ import php from "microlighter/grammars/php.js";
19
+ import powershell from "microlighter/grammars/powershell.js";
20
+ import python from "microlighter/grammars/python.js";
21
+ import r from "microlighter/grammars/r.js";
22
+ import ruby from "microlighter/grammars/ruby.js";
23
+ import rust from "microlighter/grammars/rust.js";
24
+ import svelte from "microlighter/grammars/svelte.js";
25
+ import toml from "microlighter/grammars/toml.js";
26
+ import tsx from "microlighter/grammars/tsx.js";
27
+ import typescript from "microlighter/grammars/typescript.js";
28
+ import vue from "microlighter/grammars/vue.js";
29
+ import yaml from "microlighter/grammars/yaml.js";
30
+
31
+ const httpGrammar = {
32
+ scopeName: "source.http",
33
+ patterns: [
34
+ {
35
+ match:
36
+ "^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)\\s+([^\\s]+)\\s+(HTTP\\/[0-9.]+)",
37
+ captures: {
38
+ 1: { name: "keyword.control.http" },
39
+ 2: { name: "string.unquoted.http" },
40
+ 3: { name: "keyword.other.http" },
41
+ },
42
+ },
43
+ {
44
+ match: "^(HTTP\\/[0-9.]+)\\s+([0-9]{3})(?:\\s+(.*))?",
45
+ captures: {
46
+ 1: { name: "keyword.other.http" },
47
+ 2: { name: "constant.numeric.http" },
48
+ 3: { name: "string.unquoted.http" },
49
+ },
50
+ },
51
+ {
52
+ match: "^([a-zA-Z0-9_-]+):\\s*(.*)",
53
+ captures: {
54
+ 1: { name: "support.type.property-name.http" },
55
+ 2: { name: "string.unquoted.http" },
56
+ },
57
+ },
58
+ ],
59
+ };
60
+
61
+ const grammars = {
62
+ astro,
63
+ bash,
64
+ c,
65
+ cpp,
66
+ csharp,
67
+ css: cssGrammar,
68
+ go,
69
+ html,
70
+ http: httpGrammar,
71
+ java,
72
+ javascript,
73
+ json,
74
+ kotlin,
75
+ markdown,
76
+ "objective-c": objectiveC,
77
+ perl,
78
+ php,
79
+ powershell,
80
+ python,
81
+ r,
82
+ ruby,
83
+ rust,
84
+ svelte,
85
+ toml,
86
+ tsx,
87
+ typescript,
88
+ vue,
89
+ yaml,
90
+ };
91
+
92
+ const scopes = new Map();
93
+ Object.values(grammars).forEach((grammar) => {
94
+ if (grammar?.scopeName) {
95
+ scopes.set(grammar.scopeName, grammar);
96
+ }
97
+ });
98
+
99
+ const languageAliases = {
100
+ "c++": "cpp",
101
+ cs: "csharp",
102
+ curl: "bash",
103
+ js: "javascript",
104
+ jsx: "javascript",
105
+ markup: "html",
106
+ md: "markdown",
107
+ objc: "objective-c",
108
+ ps1: "powershell",
109
+ py: "python",
110
+ rb: "ruby",
111
+ sh: "bash",
112
+ shell: "bash",
113
+ svg: "html",
114
+ ts: "typescript",
115
+ xml: "html",
116
+ yml: "yaml",
117
+ zsh: "bash",
118
+ };
119
+
120
+ const noMatch = { indices: [[Infinity, Infinity]] };
121
+ const highlights = new Map();
122
+
123
+ const getCategory = (scope) => {
124
+ const parts = scope.split(".");
125
+ const [first, second, third] = parts;
126
+ const last = parts.at(-1);
127
+
128
+ if (first === "markup" && ["quote", "inserted", "deleted", "raw"].includes(second)) return second;
129
+ if (first === "entity" && second === "name") return third;
130
+ if (scope.startsWith("constant.character.entity")) return "character-entity";
131
+ if (parts.includes("numeric")) return "numeric";
132
+ if (scope.startsWith("support.type.property-name")) return "property";
133
+ if (parts.includes("attribute-value")) return "attribute-value";
134
+ if (scope.startsWith("string.other.link")) return "link";
135
+
136
+ if (
137
+ [
138
+ "doctype",
139
+ "at-rule",
140
+ "important",
141
+ "regexp",
142
+ "boolean",
143
+ "symbol",
144
+ "operator",
145
+ "attribute-name",
146
+ ].includes(last)
147
+ )
148
+ return last;
149
+
150
+ if (
151
+ [
152
+ "comment",
153
+ "string",
154
+ "constant",
155
+ "storage",
156
+ "keyword",
157
+ "variable",
158
+ "punctuation",
159
+ "entity",
160
+ "support",
161
+ ].includes(first)
162
+ )
163
+ return first;
164
+ };
165
+
166
+ const addRange = (node, start, end, scope) => {
167
+ const category = getCategory(scope);
168
+ if (!category || start === end) return;
169
+
170
+ const range = new Range();
171
+ range.setStart(node, start);
172
+ range.setEnd(node, end);
173
+
174
+ if (!highlights.has(category)) {
175
+ highlights.set(category, new Highlight());
176
+ }
177
+ highlights.get(category).add(range);
178
+ };
179
+
180
+ const addCaptures = (node, match, captures = {}) => {
181
+ Object.entries(captures).forEach(([index, capture]) => {
182
+ const offsets = match.indices[+index];
183
+ if (offsets) addRange(node, ...offsets, capture.name);
184
+ });
185
+ };
186
+
187
+ const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
188
+
189
+ const expandEnd = (pattern, beginMatch) =>
190
+ pattern.replace(/\\(\d+)/g, (reference, index) =>
191
+ beginMatch[index] === undefined ? reference : escapeRegex(beginMatch[index]),
192
+ );
193
+
194
+ const getRules = (rule) => {
195
+ if (!rule) return [];
196
+ if (Array.isArray(rule)) return rule;
197
+ if (rule.match || rule.begin || rule.include) return [rule];
198
+ return rule.patterns || [];
199
+ };
200
+
201
+ const resolveInclude = (include, grammar, baseGrammar) => {
202
+ if (include === "$self") return { grammar, rules: grammar.patterns };
203
+ if (include === "$base") return { grammar: baseGrammar, rules: baseGrammar.patterns };
204
+
205
+ if (include[0] === "#") {
206
+ return { grammar, rules: getRules(grammar.repository?.[include.slice(1)]) };
207
+ }
208
+
209
+ const [scopeName, repositoryName] = include.split("#");
210
+ const includedGrammar = scopes.get(scopeName);
211
+ if (!includedGrammar) return null;
212
+
213
+ return {
214
+ grammar: includedGrammar,
215
+ rules: repositoryName
216
+ ? getRules(includedGrammar.repository?.[repositoryName])
217
+ : includedGrammar.patterns,
218
+ };
219
+ };
220
+
221
+ const expandRules = (rules, grammar, baseGrammar, activeIncludes = new Set()) => {
222
+ const expanded = [];
223
+ rules.forEach((rule) => {
224
+ if (rule.include) {
225
+ const includeKey = `${grammar.scopeName}:${rule.include}`;
226
+ if (activeIncludes.has(includeKey)) return;
227
+
228
+ const included = resolveInclude(rule.include, grammar, baseGrammar);
229
+ if (!included) return;
230
+
231
+ const nestedIncludes = new Set(activeIncludes);
232
+ nestedIncludes.add(includeKey);
233
+ expanded.push(...expandRules(included.rules, included.grammar, baseGrammar, nestedIncludes));
234
+ return;
235
+ }
236
+
237
+ if (rule.match || (rule.begin && rule.end)) expanded.push({ grammar, rule });
238
+ });
239
+
240
+ return expanded;
241
+ };
242
+
243
+ const regexes = new Map();
244
+ let matches = new Map();
245
+
246
+ const exec = (pattern, node, start, end) => {
247
+ let match = matches.get(pattern);
248
+ if (!match || match.indices[0][0] < start) {
249
+ if (!regexes.has(pattern)) regexes.set(pattern, new RegExp(pattern, "dgm"));
250
+ const regex = regexes.get(pattern);
251
+ regex.lastIndex = start;
252
+ match = regex.exec(node.data) || noMatch;
253
+ matches.set(pattern, match);
254
+ }
255
+
256
+ return match.indices[0][0] < end && match.indices[0][1] <= end ? match : null;
257
+ };
258
+
259
+ const nextRule = (node, contexts, start, end) => {
260
+ let winner = null;
261
+ contexts.forEach((context) => {
262
+ const pattern = context.rule.match || context.rule.begin;
263
+ const match = exec(pattern, node, start, end);
264
+ if (!match) return;
265
+
266
+ if (!winner || match.indices[0][0] < winner.match.indices[0][0]) {
267
+ winner = { ...context, match };
268
+ }
269
+ });
270
+
271
+ return winner;
272
+ };
273
+
274
+ const scanRegion = (node, rules, start, end, grammar, baseGrammar = grammar, closing = null) => {
275
+ const contexts = expandRules(rules, grammar, baseGrammar);
276
+ let cursor = start;
277
+
278
+ while (cursor < end) {
279
+ const candidate = nextRule(node, contexts, cursor, end);
280
+ const endMatch = closing ? exec(closing.pattern, node, cursor, end) : null;
281
+ const candidateStart = candidate?.match.indices[0][0] ?? Infinity;
282
+ const endStart = endMatch?.indices[0][0] ?? Infinity;
283
+
284
+ if (
285
+ endMatch &&
286
+ (endStart < candidateStart || (endStart === candidateStart && !closing?.applyEndPatternLast))
287
+ ) {
288
+ return { contentEnd: endStart, end: endMatch.indices[0][1], match: endMatch };
289
+ }
290
+
291
+ if (!candidate) return { contentEnd: end, end, match: null };
292
+
293
+ const { rule, match, grammar: ruleGrammar } = candidate;
294
+ if (rule.match) {
295
+ if (rule.name) addRange(node, ...match.indices[0], rule.name);
296
+ addCaptures(node, match, rule.captures);
297
+ cursor = match.indices[0][1] > cursor ? match.indices[0][1] : cursor + 1;
298
+ continue;
299
+ }
300
+
301
+ const nested = scanRegion(
302
+ node,
303
+ rule.patterns || [],
304
+ match.indices[0][1],
305
+ end,
306
+ ruleGrammar,
307
+ baseGrammar,
308
+ {
309
+ pattern: expandEnd(rule.end, match),
310
+ applyEndPatternLast: rule.applyEndPatternLast,
311
+ },
312
+ );
313
+
314
+ if (rule.name) addRange(node, match.indices[0][0], nested.end, rule.name);
315
+ if (rule.contentName) addRange(node, match.indices[0][1], nested.contentEnd, rule.contentName);
316
+ addCaptures(node, match, rule.beginCaptures || rule.captures);
317
+ if (nested.match) addCaptures(node, nested.match, rule.endCaptures || rule.captures);
318
+
319
+ cursor = nested.end > cursor ? nested.end : cursor + 1;
320
+ }
321
+
322
+ return { contentEnd: end, end, match: null };
323
+ };
324
+
325
+ export const getLanguage = (codeBlock) => {
326
+ const pre = codeBlock.parentElement;
327
+ const getLangClass = (el) => {
328
+ if (!el || !el.classList) return null;
329
+ return [...el.classList].find((c) => c.startsWith("language-"))?.slice("language-".length);
330
+ };
331
+ const lang =
332
+ getLangClass(codeBlock) ||
333
+ codeBlock.dataset?.language ||
334
+ getLangClass(pre) ||
335
+ pre?.dataset?.language ||
336
+ pre?.getAttribute?.("lang") ||
337
+ "";
338
+ return lang.toLowerCase();
339
+ };
340
+
341
+ export const normalizeLanguage = (lang) => languageAliases[lang] || lang;
342
+
343
+ export const getAllCodeBlocks = (rootNode = document) => {
344
+ const blocks = [];
345
+ const visitedRoots = new Set();
346
+
347
+ const traverse = (node) => {
348
+ if (!node || visitedRoots.has(node)) return;
349
+ visitedRoots.add(node);
350
+
351
+ if (node.querySelectorAll) {
352
+ const codeEls = node.querySelectorAll("pre > code");
353
+ blocks.push(...codeEls);
354
+
355
+ const allChildren = node.querySelectorAll("*");
356
+ for (let i = 0; i < allChildren.length; i++) {
357
+ const child = allChildren[i];
358
+ if (child.shadowRoot) {
359
+ traverse(child.shadowRoot);
360
+ }
361
+ }
362
+ }
363
+ };
364
+
365
+ traverse(rootNode);
366
+ return blocks;
367
+ };
368
+
369
+ let highlightScheduled = false;
370
+
371
+ export const highlightAllCode = (rootNode = document) => {
372
+ if (typeof window === "undefined" || typeof CSS === "undefined" || !CSS.highlights) {
373
+ return;
374
+ }
375
+
376
+ // Find all code blocks across the root and any nested shadow roots
377
+ const codeBlocks = getAllCodeBlocks(rootNode).filter(getLanguage);
378
+ if (codeBlocks.length === 0) return;
379
+
380
+ // Clear previous ranges
381
+ highlights.forEach((ranges, category) => {
382
+ if (CSS.highlights.get(category) === ranges) CSS.highlights.delete(category);
383
+ ranges.clear();
384
+ });
385
+
386
+ codeBlocks.forEach((codeBlock) => {
387
+ const rawLang = getLanguage(codeBlock);
388
+ const lang = normalizeLanguage(rawLang);
389
+ const grammar = grammars[lang];
390
+ if (!grammar) return;
391
+
392
+ // Scan all text nodes without mutating or removing Lit marker comment nodes
393
+ const textNodes = Array.from(codeBlock.childNodes).filter(
394
+ (child) => child.nodeType === Node.TEXT_NODE && child.data?.length > 0,
395
+ );
396
+ if (textNodes.length === 0) return;
397
+
398
+ matches = new Map();
399
+ textNodes.forEach((node) => {
400
+ scanRegion(node, grammar.patterns, 0, node.data.length, grammar);
401
+ });
402
+ });
403
+
404
+ highlights.forEach((ranges, category) => {
405
+ if (ranges.size) CSS.highlights.set(category, ranges);
406
+ });
407
+ };
408
+
409
+ export const scheduleHighlight = (rootNode = document) => {
410
+ if (typeof window === "undefined" || typeof CSS === "undefined" || !CSS.highlights) {
411
+ return;
412
+ }
413
+ if (highlightScheduled) return;
414
+ highlightScheduled = true;
415
+ requestAnimationFrame(() => {
416
+ highlightScheduled = false;
417
+ highlightAllCode(rootNode);
418
+ });
419
+ };
@@ -0,0 +1,110 @@
1
+ import { marked } from "marked";
2
+
3
+ let configured = false;
4
+
5
+ function escapeAttribute(value) {
6
+ return value
7
+ .replace(/&/g, "&amp;")
8
+ .replace(/"/g, "&quot;")
9
+ .replace(/</g, "&lt;")
10
+ .replace(/>/g, "&gt;");
11
+ }
12
+
13
+ /**
14
+ * When an HTML `<base>` tag is present in the page, a markdown in-page anchor link (e.g. `#link-to-a-section`) would
15
+ * resolve against the base URL instead of the current page. This renders such links with an absolute URL
16
+ * built from the current document location, and tags them with the `anchor-link` CSS class.
17
+ *
18
+ * @param {import('marked').Renderer} renderer
19
+ * @param {import('marked').Tokens.Link} token
20
+ * @returns {string | false} the rendered link, or `false` when the link is not an in-page anchor (use the default renderer)
21
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#in-page_anchors
22
+ */
23
+ function renderAnchorLink(renderer, { href, title, tokens }) {
24
+ if (!href?.startsWith("#") || typeof location === "undefined") {
25
+ return false;
26
+ }
27
+ const absoluteHref = `${location.href.replace(/#.*/, "")}${href}`;
28
+ const titleAttribute = title ? ` title="${escapeAttribute(title)}"` : "";
29
+ return `<a class="anchor-link" href="${escapeAttribute(absoluteHref)}"${titleAttribute}>${renderer.parser.parseInline(tokens)}</a>`;
30
+ }
31
+
32
+ /**
33
+ * Applies the in-page anchor fix to a custom renderer. A renderer passed per call, `marked(text, { renderer })`,
34
+ * replaces the global one configured by {@link configureMarkdown}, so custom renderers need it too.
35
+ *
36
+ * @param {import('marked').Renderer} renderer
37
+ */
38
+ export function fixRenderedAnchorLinks(renderer) {
39
+ const defaultLink = renderer.link;
40
+ renderer.link = function link(token) {
41
+ const anchorLink = renderAnchorLink(this, token);
42
+ return anchorLink === false ? defaultLink.call(this, token) : anchorLink;
43
+ };
44
+ }
45
+
46
+ /** Inline HTML for the first line of a paragraph-like token (paragraph, heading, list), with line breaks as spaces. */
47
+ export function firstLineHtml(token) {
48
+ // a list item's own text, without any nested list under it
49
+ const text =
50
+ token.type === "list"
51
+ ? token.items[0]?.tokens?.find((t) => t.type === "text" || t.type === "paragraph")?.text
52
+ : token.text;
53
+ return marked.parseInline((text || "").replace(/\s*\n\s*/g, " ").trim());
54
+ }
55
+
56
+ /**
57
+ * One-line HTML stand-in for a markdown block that cannot be shown on a single line: a short placeholder
58
+ * for tables and code blocks, the plain text of an HTML block, and the first line of a blockquote.
59
+ *
60
+ * @param {import('marked').Token} token - a block token from `marked.lexer`
61
+ * @returns {string} HTML for the summary line, or '' when the block can be shown inline as it is
62
+ */
63
+ export function blockSummary(token) {
64
+ switch (token?.type) {
65
+ case "table": {
66
+ const rows = token.rows.length;
67
+ return `<span class="descr-placeholder">Table · ${rows} ${rows === 1 ? "row" : "rows"}</span>`;
68
+ }
69
+ case "code":
70
+ return `<span class="descr-placeholder">Code block${token.lang ? ` · ${escapeAttribute(token.lang)}` : ""}</span>`;
71
+ case "html": {
72
+ const text = token.text
73
+ .replace(/<(script|style)\b[\s\S]*?<\/\1>/gi, "")
74
+ .replace(/<[^>]*>/g, " ")
75
+ .replace(/[<>]/g, "")
76
+ .replace(/\s+/g, " ")
77
+ .trim();
78
+ return text || '<span class="descr-placeholder">HTML content</span>';
79
+ }
80
+ case "blockquote": {
81
+ const first = token.tokens.find((t) => t.type !== "space");
82
+ if (!first) return "";
83
+ return ["paragraph", "heading", "list", "text"].includes(first.type)
84
+ ? firstLineHtml(first)
85
+ : blockSummary(first);
86
+ }
87
+ default:
88
+ return "";
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Configures the global `marked` instance used by every `marked(...)` call that has no custom renderer.
94
+ * Idempotent: safe to import and call from several entry points.
95
+ */
96
+ export function configureMarkdown() {
97
+ if (configured) {
98
+ return;
99
+ }
100
+ configured = true;
101
+ marked.use({
102
+ renderer: {
103
+ link(token) {
104
+ return renderAnchorLink(this, token); // `false` falls back to marked's default link renderer
105
+ },
106
+ },
107
+ });
108
+ }
109
+
110
+ configureMarkdown();
@@ -0,0 +1,155 @@
1
+ # MCP Manifest, Runtime and Export
2
+
3
+ ## Overview
4
+
5
+ Turns an OpenAPI document into an **MCP manifest** (plain JSON: one tool per operation, with
6
+ JSON Schema arguments and MCP safety annotations) and executes that manifest with `fetch`. The
7
+ exported MCP server is the manifest plus two fixed files: `runtime.js` (this folder's runtime,
8
+ copied verbatim) and `project/server.ts` (a stdio server on `@modelcontextprotocol/sdk` that
9
+ registers the manifest's tools and hands calls to the runtime). The entry point is
10
+ `src/utils/mcp-generator.js`; `<rapi-doc>` (`src/rapidoc.ts`, `src/templates/downloads-template.js`)
11
+ uses it for its Export MCP Server button, and `packages/rapidoc-mcp/src/index.js` re-exports it as `@rapidoc/mcp`
12
+ (a separate package, Node only, no Lit) for the portal build, `@rapidoc/server` and the CLI.
13
+
14
+ Arazzo workflows become tools too: `workflows.js` compiles a parsed Arazzo document into
15
+ `manifest.workflows` (steps resolved to operations) and one tool per workflow, and
16
+ `workflow-runtime.js` runs them on top of `runtime.js`. The two runtimes are separate files so
17
+ `dist/rapidoc-min.js` (operation tools only) carries no Arazzo runtime, while
18
+ `dist/rapidoc-flow-min.js` and `@rapidoc/mcp` include both. Not owned here: Arazzo parsing and validation (`../arazzo/`), the `<rapi-doc>` attribute and
19
+ methods, and the network calls themselves (the runtime only builds and sends requests).
20
+
21
+ ## Interface
22
+
23
+ - `compileWorkflows(arazzo, { sources, onProblem }) -> { tools, workflows }` [workflows.js]:
24
+ `sources` are operation indexes by Arazzo source name (`../arazzo/operations.js`). A workflow the
25
+ runtime cannot run is reported once through `onProblem` and left out. `buildMcpManifest` calls
26
+ it for `options.arazzo`, naming the manifest's source after the Arazzo document's first OpenAPI
27
+ source so steps resolve; `generateMcpServer` collects the problems into its result.
28
+
29
+ - `buildMcpManifest(spec, options) -> manifest` [manifest.js]: `spec` is a raw OpenAPI 3.x /
30
+ Swagger 2.0 object or `ProcessSpec`'s result (`tags[].paths[]`). Options: `serverName`,
31
+ `sourceName` (default `api`), `includeInternal`, `includeDeprecated`, `includeOperations` (`false`:
32
+ only the Arazzo workflows' tools, for a manifest of workflows). `toJsonSchema(schema,
33
+ root)` is the schema converter on its own; `MANIFEST_VERSION` is `1`.
34
+ - `createMcpRuntime(manifest, { fetch, credentials, baseUrls, runWorkflow })` [runtime.js]:
35
+ `listTools()`, `listResources()`, `callTool(name, args, hooks) -> { content, structuredContent?,
36
+ isError? }`, `execute(operation, args, { headers? })` (one request, for workflow runners; the
37
+ headers are added to the request). A workflow tool is handed to `runWorkflow`; without it the
38
+ call is an error result. `callTool`'s hooks reach the workflow runner: `onStepStart`, `onStep`,
39
+ `sleep`, and `requestHeaders({ workflowId, stepId, attempt, operation })`, whose result is sent
40
+ with that step's request (how `<rapi-flow>` asks `rapidoc mock` for a status with
41
+ `Prefer: code=`).
42
+ - `runWorkflow(runtime, tool, args, { onStepStart, onStep, sleep })` and
43
+ `createMcpWorkflowRuntime(manifest, options)` (createMcpRuntime with `runWorkflow` plugged in;
44
+ `options.sleep` waits between retries) [workflow-runtime.js], plus `evaluateExpression`,
45
+ `resolveValue` and `evaluateCondition`. `@rapidoc/mcp` exports `createMcpWorkflowRuntime` as its
46
+ `createMcpRuntime`.
47
+ `readResource(uri) -> { contents }`. `buildRequest(manifest, operation, args, options)` returns
48
+ the request without sending it; `credentialsFromEnv(manifest, env)` and `envNameFor(id)` map
49
+ security schemes to environment variables.
50
+ - `writeMcpProject(manifest, { serverName, workflowRuntime }) -> { [path]: text }` [project.js]:
51
+ `manifest.json`, `server.ts`, `runtime.js`, `package.json`, `README.md`,
52
+ `claude_desktop_config.json`, and `workflow-runtime.js` when the manifest has workflows and
53
+ `workflowRuntime` (its text) is given: `@rapidoc/mcp` and `<rapi-flow>` supply it,
54
+ `dist/rapidoc-min.js` does not carry it. `server.ts` imports it dynamically when present.
55
+ - `createZip(files) -> Uint8Array` [zip.js]: a stored (uncompressed) ZIP of text files.
56
+ - `generateMcpServer(spec, options) -> { manifest, files, budget, hostConfig }`
57
+ [../mcp-generator.js]: the three above in one call.
58
+ - `common.js`: `parseMcpEnabled(val)` (the `mcp` attribute), `toSnakeCase`, `sanitizeDescription`,
59
+ `deriveToolName`, `AUTH_TRANSPORT_HEADERS`, `estimateTokenCost(manifest)` and
60
+ `generateHostConfigs(serverName, { schemes })`.
61
+
62
+ ## Invariants
63
+
64
+ - **Manifest shape.** `{ manifestVersion, server: { name, version, description }, sources:
65
+ { [name]: { baseUrl, securitySchemes } }, tools, resources, workflows }`. A workflow tool has
66
+ `workflow` (its id) instead of `operation`; `workflows[id]` is `{ workflowId, summary,
67
+ description, inputs, steps, outputs }`, each step `{ stepId, operation, parameters, requestBody,
68
+ successCriteria, onSuccess, onFailure, outputs }` with the operation resolved (source, method,
69
+ path, params from the step's parameters and body, security from the OpenAPI operation) and
70
+ values kept as Arazzo expressions. A tool is
71
+ `{ name, description, inputSchema, annotations, operation }` with `operation = { source, method,
72
+ path, params: [{ name, in, arg?, contentType? }], security }`; `arg` names the argument when it
73
+ differs from the wire name, `contentType` is set for `in: "body"`. A resource has `uri`
74
+ (`reference://...`) and `mimeType` instead of `inputSchema` and `annotations`.
75
+ - **Exclusion** (`[manifest.js:buildMcpManifest]`): an operation is skipped when `x-internal` or
76
+ `x-hidden` is `true` on it or its path item (unless `includeInternal`), when `deprecated` (unless
77
+ `includeDeprecated`), or when it is a webhook.
78
+ - **Arguments.** Path, query, header and cookie parameters become properties by name (a clash
79
+ across locations gets `_<in>`); path parameters are required. The request body is one `body`
80
+ property with the first JSON content type's schema (else form-urlencoded, multipart, or the first
81
+ listed). Path-level parameters are merged unless the operation redefines them. Headers in
82
+ `AUTH_TRANSPORT_HEADERS` and any parameter that matches an `apiKey` scheme's `in` and `name`
83
+ are never arguments: the runtime sends them.
84
+ - **JSON Schema** (`[manifest.js:toJsonSchema]`): `$ref`s are inlined from the raw document;
85
+ a schema met again on the same branch is cut to `{ type }`; `nullable` folds into `type`;
86
+ `readOnly` properties (and their `required` entries) are dropped; OpenAPI 3.0 boolean
87
+ `exclusiveMinimum/Maximum` become numbers; `example`, `xml`, `x-*` and other non-schema keys
88
+ are dropped.
89
+ - **Naming**: `toSnakeCase(operationId)`, else `deriveToolName(method, path)`; workflow tools are
90
+ `toSnakeCase(workflowId)`, after the operations; a repeated name gets `_2`, `_3`, ... in that
91
+ order. `sanitizeDescription` deletes every `_`.
92
+ - **Annotations**: `readOnlyHint` for GET; `destructiveHint` for PUT, PATCH and DELETE;
93
+ `idempotentHint` for GET, PUT and DELETE. A workflow tool is read-only when every step is a
94
+ GET, destructive when any step is PUT, PATCH or DELETE, never idempotent. `estimateTokenCost.consequentialCount` counts tools
95
+ without `readOnlyHint`.
96
+ - **Resources**: a GET with no required argument whose path contains one of `/reference`,
97
+ `/currencies`, `/countries`, `/tiers`, `/categories`, `/status`, `/schema`.
98
+ - **Security.** `securitySchemes` keeps `type, description, in, name, scheme, bearerFormat,
99
+ openIdConnectUrl` and OAuth 2 `flows`; `<rapi-doc>`'s injected `_rapidoc_api_key` scheme is
100
+ left out. A tool's `security` is the operation's, else the document's. The runtime uses the
101
+ first alternative all of whose credentials are known; with none, nothing is sent when an
102
+ alternative is empty, else the first alternative as far as credentials go.
103
+ - **Runtime.** `runtime.js` imports nothing and uses only `fetch`, `URLSearchParams`,
104
+ `TextEncoder` and `btoa`, and `workflow-runtime.js` imports only `./runtime.js`, because the
105
+ project writer embeds their text (`?raw` imports, supported by Vite and
106
+ `scripts/test-loader.mjs`). Path parameters are percent-encoded; array query values
107
+ repeat the key; objects elsewhere are JSON; `Accept: application/json` unless set; a missing path
108
+ parameter is an error result, as is a transport failure or a non-2xx status (text `HTTP <status>`
109
+ plus the body). JSON responses are pretty-printed and, for objects, also `structuredContent`.
110
+ - **Workflows the runtime runs** (`[workflows.js:compileWorkflows]`): steps that name an
111
+ operation found in the sources, parameters in path, query, header or cookie, a request body
112
+ with `payload` and `replacements`, simple criteria only (`regex`, `jsonpath` and `xpath` are
113
+ reported), actions `end`, `goto` to a step and `retry` (`retryLimit` default 1, `retryAfter`
114
+ seconds) with simple criteria, and `$inputs` / `$steps` expressions. Steps that call another
115
+ workflow, actions that go to one, and `$workflows` expressions are reported. A step's actions
116
+ are its own `onSuccess` / `onFailure` followed by the workflow's it does not override by name.
117
+ - **Running a workflow** (`[workflow-runtime.js:runWorkflow]`): required inputs are checked first.
118
+ Each step's parameters and payload are evaluated against `$inputs` and earlier steps' outputs
119
+ (`resolveValue`: a whole-string expression keeps its type, `{$expr}` inside text is
120
+ interpolated), the request is sent, and every criterion must hold (`evaluateCondition`: `==`,
121
+ `!=`, `<`, `>`, `<=`, `>=`, `&&`, `||`, `!`, parentheses, quoted strings, numbers, `true`,
122
+ `false`, `null`; `==` compares numbers and strings as text and treats a missing value as
123
+ `null`). A step without criteria succeeds on a 2xx response. The first action whose criteria all hold runs: on success
124
+ `end` finishes, `goto` jumps, none falls through; on failure none or `end` fails the workflow
125
+ (the result names the step, the criterion and the response), `goto` jumps, `retry` re-runs the
126
+ step (or `stepId`) after `retryAfter` seconds up to `retryLimit` times. A transport error is a
127
+ failure with no response. More than 1000 step runs is an error. The result is the workflow's
128
+ `outputs` as `structuredContent`; `onStepStart` gets `{ workflowId, stepId, attempt }` before
129
+ each request and `onStep` gets `{ workflowId, stepId, attempt, outcome, request, response,
130
+ outputs, failedCriterion, action }` after it.
131
+ - **Environment.** `envNameFor("petstore_auth")` is `PETSTORE_AUTH`; `API_KEY` is the fallback
132
+ for every scheme; HTTP Basic reads `<NAME>` as `user:password` or `<NAME>_USERNAME` /
133
+ `<NAME>_PASSWORD`. `API_BASE_URL` in `server.ts` overrides every source's base URL.
134
+ - **Project.** `server.ts` and `runtime.js` are identical for every API; only `manifest.json`,
135
+ `package.json`, `README.md` and `claude_desktop_config.json` vary. The zip stores entries
136
+ uncompressed with a fixed 1980-01-01 timestamp and UTF-8 names.
137
+
138
+ ## Side Effects
139
+
140
+ - None in this folder: no network, DOM, storage or console. The exported `server.ts` reads
141
+ `manifest.json`, the environment and stdio, and the runtime it calls makes HTTP requests.
142
+
143
+ ## Verification
144
+
145
+ - `npm run test:unit` from the repo root. `tests/unit/mcp-generator.test.js` covers the manifest
146
+ (from `docs/public/specs/petstore.yaml` and small documents), the schema converter, the runtime
147
+ with a fake `fetch`, credentials from the environment, the project files and the zip.
148
+ `tests/unit/mcp-workflows.test.js` covers compiling `docs/public/specs/petstore.arazzo.yaml`,
149
+ expressions, conditions, and runs (outputs between steps, retry, goto, failure) with a fake
150
+ `fetch`. `tests/unit/mcp-project.test.js` runs the exported project for real: it starts `server.ts`
151
+ (through `scripts/test-loader.mjs` instead of `tsx`) under `.test-tmp/`, drives it with the MCP
152
+ SDK's stdio client (`@modelcontextprotocol/sdk`, a root dev dependency) and points
153
+ `API_BASE_URL` at `@rapidoc/mock` serving the same Petstore spec, workflow tools included.
154
+ - `/examples/mcp-server` on the docs site shows the manifest, `server.ts` and `runtime.js` and
155
+ downloads the project. After editing, `npm run lint` and `npm run format-fix`.