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,289 @@
1
+ /**
2
+ * RapiDoc MCP runtime: executes the operation tools of an MCP manifest (see manifest.js) with
3
+ * `fetch`, so the same code runs in Node, browsers and edge workers. Workflow tools are run by
4
+ * workflow-runtime.js, plugged in through the `runWorkflow` option, so pages and exports without
5
+ * workflows carry no Arazzo code.
6
+ *
7
+ * This file is copied verbatim into every exported MCP server project as `runtime.js`, so it
8
+ * imports nothing and relies only on `fetch`, `URLSearchParams`, `TextEncoder` and `btoa`.
9
+ */
10
+
11
+ /** @param {string} text UTF-8 text as base64 */
12
+ function base64(text) {
13
+ const bytes = new TextEncoder().encode(text);
14
+ let binary = "";
15
+ for (const b of bytes) binary += String.fromCharCode(b);
16
+ return btoa(binary);
17
+ }
18
+
19
+ function serialize(value) {
20
+ if (typeof value === "string") return value;
21
+ if (value && typeof value === "object") return JSON.stringify(value);
22
+ return String(value);
23
+ }
24
+
25
+ function parseJson(text) {
26
+ try {
27
+ return JSON.parse(text);
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ /** The environment variable name that holds a scheme's credential: `petstore_auth` → `PETSTORE_AUTH`. */
34
+ export function envNameFor(schemeId) {
35
+ const name = String(schemeId || "")
36
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
37
+ .replace(/[^a-zA-Z0-9]+/g, "_")
38
+ .replace(/^_|_$/g, "")
39
+ .toUpperCase();
40
+ return name || "API_KEY";
41
+ }
42
+
43
+ /**
44
+ * Credentials for every security scheme in the manifest, read from environment variables:
45
+ * `<SCHEME>` (the scheme id in upper snake case), else `API_KEY` for any scheme. HTTP Basic takes
46
+ * `<SCHEME>` as `user:password`, or `<SCHEME>_USERNAME` and `<SCHEME>_PASSWORD`.
47
+ *
48
+ * @param {object} manifest
49
+ * @param {Record<string, string|undefined>} env such as `process.env`
50
+ * @returns {Record<string, string|{ username: string, password: string }>}
51
+ */
52
+ export function credentialsFromEnv(manifest, env = {}) {
53
+ const credentials = {};
54
+ for (const source of Object.values(manifest.sources || {})) {
55
+ for (const [id, scheme] of Object.entries(source.securitySchemes || {})) {
56
+ const key = envNameFor(id);
57
+ if (scheme.type === "http" && String(scheme.scheme).toLowerCase() === "basic") {
58
+ if (env[`${key}_USERNAME`] !== undefined) {
59
+ credentials[id] = {
60
+ username: env[`${key}_USERNAME`],
61
+ password: env[`${key}_PASSWORD`] || "",
62
+ };
63
+ continue;
64
+ }
65
+ }
66
+ const value = env[key] ?? env.API_KEY;
67
+ if (value !== undefined && value !== "") credentials[id] = value;
68
+ }
69
+ }
70
+ return credentials;
71
+ }
72
+
73
+ /**
74
+ * Adds the credentials a security requirement asks for. `security` is a list of alternatives,
75
+ * each `{ schemeId: scopes }`; the first alternative every credential of which is known is used.
76
+ * With none fully known, nothing is sent when an alternative is empty (anonymous access), else
77
+ * the first alternative is applied as far as credentials go, so the API's 401 says what is missing.
78
+ */
79
+ function applySecurity(security, schemes, credentials, target) {
80
+ if (!Array.isArray(security) || !security.length) return;
81
+ const known = (alt) => Object.keys(alt).every((id) => credentials[id] !== undefined);
82
+ let chosen = security.find(known);
83
+ if (!chosen) {
84
+ if (security.some((alt) => !Object.keys(alt).length)) return;
85
+ chosen = security[0];
86
+ }
87
+ for (const id of Object.keys(chosen)) {
88
+ const scheme = schemes?.[id];
89
+ const credential = credentials[id];
90
+ if (!scheme || credential === undefined) continue;
91
+ const token =
92
+ typeof credential === "string" ? credential : credential?.token || credential?.accessToken;
93
+ if (scheme.type === "apiKey") {
94
+ const value = serialize(credential);
95
+ if (scheme.in === "query") target.query.set(scheme.name, value);
96
+ else if (scheme.in === "cookie")
97
+ target.cookies.push(`${scheme.name}=${encodeURIComponent(value)}`);
98
+ else target.headers[scheme.name || "Authorization"] = value;
99
+ } else if (scheme.type === "http" && String(scheme.scheme).toLowerCase() === "basic") {
100
+ const pair =
101
+ typeof credential === "string"
102
+ ? credential
103
+ : `${credential?.username ?? ""}:${credential?.password ?? ""}`;
104
+ target.headers.Authorization = `Basic ${base64(pair)}`;
105
+ } else if (token) {
106
+ // http bearer, oauth2 and openIdConnect all send a bearer token
107
+ target.headers.Authorization = `Bearer ${token}`;
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * The HTTP request a tool call makes, without sending it.
114
+ *
115
+ * @param {object} manifest
116
+ * @param {object} operation a tool's or resource's `operation`
117
+ * @param {object} args the tool arguments
118
+ * @param {object} [options]
119
+ * @param {object} [options.credentials] by security scheme id
120
+ * @param {Record<string, string>} [options.baseUrls] by source name, overriding the manifest
121
+ * @returns {{ url: string, method: string, headers: Record<string, string>, body?: string }}
122
+ */
123
+ export function buildRequest(manifest, operation, args = {}, options = {}) {
124
+ const source = manifest.sources?.[operation.source] || {};
125
+ const base = String(options.baseUrls?.[operation.source] || source.baseUrl || "").replace(
126
+ /\/$/,
127
+ "",
128
+ );
129
+ const headers = {};
130
+ const query = new URLSearchParams();
131
+ const cookies = [];
132
+ let path = operation.path || "";
133
+ let body;
134
+ for (const p of operation.params || []) {
135
+ const value = args[p.arg || p.name];
136
+ if (value === undefined || value === null) continue;
137
+ if (p.in === "path") {
138
+ path = path.split(`{${p.name}}`).join(encodeURIComponent(serialize(value)));
139
+ } else if (p.in === "query") {
140
+ for (const v of Array.isArray(value) ? value : [value]) query.append(p.name, serialize(v));
141
+ } else if (p.in === "header") {
142
+ headers[p.name] = serialize(value);
143
+ } else if (p.in === "cookie") {
144
+ cookies.push(`${p.name}=${encodeURIComponent(serialize(value))}`);
145
+ } else if (p.in === "body") {
146
+ body = { contentType: p.contentType || "application/json", value };
147
+ }
148
+ }
149
+ const missing = path.match(/\{[^}]+\}/g);
150
+ if (missing) throw new Error(`Missing path parameter ${missing.join(", ")}`);
151
+
152
+ applySecurity(operation.security, source.securitySchemes, options.credentials || {}, {
153
+ headers,
154
+ query,
155
+ cookies,
156
+ });
157
+
158
+ const request = { url: base + path, method: operation.method || "GET", headers };
159
+ const qs = query.toString();
160
+ if (qs) request.url += (request.url.includes("?") ? "&" : "?") + qs;
161
+ if (cookies.length) headers.Cookie = cookies.join("; ");
162
+ if (!Object.keys(headers).some((h) => h.toLowerCase() === "accept")) {
163
+ headers.Accept = "application/json";
164
+ }
165
+ if (body) {
166
+ if (body.contentType === "application/x-www-form-urlencoded") {
167
+ const form = new URLSearchParams();
168
+ for (const [k, v] of Object.entries(body.value || {})) form.append(k, serialize(v));
169
+ request.body = form.toString();
170
+ headers["Content-Type"] = body.contentType;
171
+ } else {
172
+ request.body = typeof body.value === "string" ? body.value : JSON.stringify(body.value);
173
+ headers["Content-Type"] = body.contentType.startsWith("multipart/")
174
+ ? "application/json"
175
+ : body.contentType;
176
+ }
177
+ }
178
+ return request;
179
+ }
180
+
181
+ function errorResult(text) {
182
+ return { isError: true, content: [{ type: "text", text }] };
183
+ }
184
+
185
+ /**
186
+ * @param {object} manifest from buildMcpManifest()
187
+ * @param {object} [options]
188
+ * @param {typeof fetch} [options.fetch] defaults to the global fetch
189
+ * @param {object} [options.credentials] by security scheme id: an API key or token string, or
190
+ * `{ username, password }` for HTTP Basic
191
+ * @param {Record<string, string>} [options.baseUrls] by source name, overriding the manifest
192
+ * @param {Function} [options.runWorkflow] `(runtime, tool, args, hooks) => Promise<result>` from
193
+ * workflow-runtime.js; without it a workflow tool answers with an error result
194
+ * @returns {{ manifest: object, listTools(): object[], listResources(): object[],
195
+ * callTool(name: string, args?: object, hooks?: object): Promise<object>,
196
+ * readResource(uri: string): Promise<object>, execute(operation: object, args: object,
197
+ * extra?: { headers?: object }): Promise<object> }} MCP results: `callTool` returns `{ content, structuredContent?,
198
+ * isError? }`, `readResource` returns `{ contents }`. `execute` sends one operation and returns
199
+ * `{ request, response, headers, text, data }` or `{ request?, error }`, for workflow runners.
200
+ */
201
+ export function createMcpRuntime(manifest, options = {}) {
202
+ const fetchImpl = options.fetch || globalThis.fetch;
203
+ const requestOptions = {
204
+ credentials: options.credentials || {},
205
+ baseUrls: options.baseUrls || {},
206
+ };
207
+ const tools = new Map((manifest.tools || []).map((t) => [t.name, t]));
208
+ const resources = new Map((manifest.resources || []).map((r) => [r.uri, r]));
209
+
210
+ async function send(operation, args, extra = {}) {
211
+ let request;
212
+ try {
213
+ request = buildRequest(manifest, operation, args, requestOptions);
214
+ if (extra.headers) request.headers = { ...request.headers, ...extra.headers };
215
+ } catch (err) {
216
+ return { error: errorResult(err.message) };
217
+ }
218
+ let response;
219
+ try {
220
+ response = await fetchImpl(request.url, {
221
+ method: request.method,
222
+ headers: request.headers,
223
+ body: request.body,
224
+ });
225
+ } catch (err) {
226
+ return { request, error: errorResult(`Request failed: ${err.message}`) };
227
+ }
228
+ const text = await response.text();
229
+ const headers = {};
230
+ if (typeof response.headers?.forEach === "function") {
231
+ response.headers.forEach((v, k) => (headers[k] = v));
232
+ }
233
+ return { request, response, headers, text, data: parseJson(text) };
234
+ }
235
+
236
+ const runtime = {
237
+ manifest,
238
+ execute: send,
239
+ listTools() {
240
+ return [...tools.values()].map(({ name, description, inputSchema, annotations }) => ({
241
+ name,
242
+ description,
243
+ inputSchema,
244
+ annotations,
245
+ }));
246
+ },
247
+ listResources() {
248
+ return [...resources.values()].map(({ name, uri, description, mimeType }) => ({
249
+ name,
250
+ uri,
251
+ description,
252
+ mimeType,
253
+ }));
254
+ },
255
+ async callTool(name, args = {}, hooks = {}) {
256
+ const tool = tools.get(name);
257
+ if (!tool) return errorResult(`Unknown tool "${name}"`);
258
+ if (tool.workflow) {
259
+ if (!options.runWorkflow) {
260
+ return errorResult(
261
+ `Tool "${name}" is a workflow; this runtime was created without workflow support`,
262
+ );
263
+ }
264
+ return options.runWorkflow(runtime, tool, args, hooks);
265
+ }
266
+ if (!tool.operation) return errorResult(`Tool "${name}" has nothing to run`);
267
+ const { error, response, text, data } = await send(tool.operation, args || {});
268
+ if (error) return error;
269
+ const pretty = data === undefined ? text : JSON.stringify(data, null, 2);
270
+ if (!response.ok) {
271
+ return errorResult(
272
+ `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}\n${pretty}`,
273
+ );
274
+ }
275
+ const result = { content: [{ type: "text", text: pretty }] };
276
+ if (data && typeof data === "object" && !Array.isArray(data)) result.structuredContent = data;
277
+ return result;
278
+ },
279
+ async readResource(uri) {
280
+ const resource = resources.get(uri);
281
+ if (!resource) throw new Error(`Unknown resource "${uri}"`);
282
+ const { error, response, text } = await send(resource.operation, {});
283
+ if (error) throw new Error(error.content[0].text);
284
+ if (!response.ok) throw new Error(`HTTP ${response.status} reading ${uri}: ${text}`);
285
+ return { contents: [{ uri, mimeType: resource.mimeType || "application/json", text }] };
286
+ },
287
+ };
288
+ return runtime;
289
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * OpenAPI schemas as the JSON Schema an MCP tool argument carries.
3
+ */
4
+
5
+ // JSON Schema keywords copied as they are; everything else (examples, xml, x-*, ...) is dropped
6
+ const SCHEMA_KEYWORDS = new Set([
7
+ "type",
8
+ "title",
9
+ "description",
10
+ "default",
11
+ "enum",
12
+ "const",
13
+ "format",
14
+ "multipleOf",
15
+ "maximum",
16
+ "exclusiveMaximum",
17
+ "minimum",
18
+ "exclusiveMinimum",
19
+ "maxLength",
20
+ "minLength",
21
+ "pattern",
22
+ "maxItems",
23
+ "minItems",
24
+ "uniqueItems",
25
+ "maxProperties",
26
+ "minProperties",
27
+ "required",
28
+ ]);
29
+ const SCHEMA_SUBSCHEMAS = new Set(["items", "additionalProperties", "not"]);
30
+ const SCHEMA_LISTS = new Set(["allOf", "anyOf", "oneOf"]);
31
+
32
+ /** Follows a `#/...` JSON pointer inside `root`. */
33
+ export function resolvePointer(root, ref) {
34
+ if (typeof ref !== "string" || !ref.startsWith("#/")) return undefined;
35
+ return ref
36
+ .slice(2)
37
+ .split("/")
38
+ .map((s) => decodeURIComponent(s).replace(/~1/g, "/").replace(/~0/g, "~"))
39
+ .reduce((node, key) => (node && typeof node === "object" ? node[key] : undefined), root);
40
+ }
41
+
42
+ /**
43
+ * An OpenAPI schema as JSON Schema for a tool argument: `$ref`s inlined, `nullable` folded into
44
+ * `type`, read-only properties dropped (they never go in a request), and non-schema keys removed.
45
+ * A schema reached again on the same branch (a cycle) is cut to its bare type.
46
+ */
47
+ export function toJsonSchema(schema, root, stack = new Set()) {
48
+ if (!schema || typeof schema !== "object") return {};
49
+ if (schema.$ref) {
50
+ const target = resolvePointer(root, schema.$ref);
51
+ if (!target || typeof target !== "object") {
52
+ return { description: `See ${schema.$ref}` };
53
+ }
54
+ return toJsonSchema(target, root, stack);
55
+ }
56
+ if (stack.has(schema)) return schema.type ? { type: schema.type } : {};
57
+ stack.add(schema);
58
+ const out = {};
59
+ let nullable = false;
60
+ for (const [key, value] of Object.entries(schema)) {
61
+ if (key === "properties" && value && typeof value === "object") {
62
+ out.properties = {};
63
+ for (const [name, prop] of Object.entries(value)) {
64
+ if (prop?.readOnly === true) continue;
65
+ out.properties[name] = toJsonSchema(prop, root, stack);
66
+ }
67
+ } else if (SCHEMA_SUBSCHEMAS.has(key)) {
68
+ out[key] = typeof value === "object" ? toJsonSchema(value, root, stack) : value;
69
+ } else if (SCHEMA_LISTS.has(key) && Array.isArray(value)) {
70
+ out[key] = value.map((s) => toJsonSchema(s, root, stack));
71
+ } else if (key === "nullable") {
72
+ nullable = value === true;
73
+ } else if (SCHEMA_KEYWORDS.has(key)) {
74
+ out[key] = value;
75
+ }
76
+ }
77
+ stack.delete(schema);
78
+ if (out.properties && Array.isArray(out.required)) {
79
+ out.required = out.required.filter((name) => name in out.properties);
80
+ if (!out.required.length) delete out.required;
81
+ }
82
+ // OpenAPI 3.0 booleans: exclusiveMinimum: true means "minimum is exclusive"
83
+ for (const [flag, bound] of [
84
+ ["exclusiveMinimum", "minimum"],
85
+ ["exclusiveMaximum", "maximum"],
86
+ ]) {
87
+ if (typeof out[flag] === "boolean") {
88
+ if (out[flag] && out[bound] !== undefined) {
89
+ out[flag] = out[bound];
90
+ delete out[bound];
91
+ } else delete out[flag];
92
+ }
93
+ }
94
+ if (nullable && out.type && ![].concat(out.type).includes("null")) {
95
+ out.type = [].concat(out.type, "null");
96
+ }
97
+ return out;
98
+ }