rapidoc 12.0.0 → 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 (77) hide show
  1. package/README.md +127 -25
  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 +541 -1309
  5. package/dist/rapidoc-min.js.LICENSE.txt +381 -0
  6. package/package.json +21 -18
  7. package/src/README.md +150 -0
  8. package/src/components/README.md +91 -0
  9. package/src/components/api-request.ts +43 -0
  10. package/src/components/api-response.ts +9 -4
  11. package/src/components/schema-base-element.ts +18 -4
  12. package/src/components/schema-table.ts +58 -13
  13. package/src/components/schema-tree.ts +7 -0
  14. package/src/components/tag-input.ts +5 -9
  15. package/src/flow.js +3 -0
  16. package/src/rapi-flow.ts +1178 -0
  17. package/src/rapidoc-mini.ts +27 -6
  18. package/src/rapidoc.ts +108 -62
  19. package/src/styles/README.md +96 -0
  20. package/src/styles/css-bundle.js +2 -2
  21. package/src/styles/endpoint-styles.js +6 -1
  22. package/src/styles/flow-styles.js +248 -0
  23. package/src/styles/nav-styles.js +39 -15
  24. package/src/styles/schema-styles.js +13 -5
  25. package/src/styles/theme-tokens.js +2 -2
  26. package/src/templates/README.md +89 -0
  27. package/src/templates/api-response-template.js +14 -10
  28. package/src/templates/callback-template.js +1 -1
  29. package/src/templates/downloads-template.js +12 -27
  30. package/src/templates/endpoint-template.js +15 -24
  31. package/src/templates/expanded-endpoint-template.js +8 -4
  32. package/src/templates/focused-endpoint-template.js +4 -3
  33. package/src/templates/navbar-template.js +20 -11
  34. package/src/templates/request-body-template.js +29 -7
  35. package/src/templates/request-params-template.js +15 -4
  36. package/src/templates/security-scheme-template.js +11 -2
  37. package/src/templates/server-template.js +3 -2
  38. package/src/utils/README.md +146 -0
  39. package/src/utils/arazzo/README.md +119 -0
  40. package/src/utils/arazzo/components.js +15 -0
  41. package/src/utils/arazzo/graph.js +269 -0
  42. package/src/utils/arazzo/index.js +52 -0
  43. package/src/utils/arazzo/operations.js +106 -0
  44. package/src/utils/arazzo/parse.js +196 -0
  45. package/src/utils/arazzo/refs.js +40 -0
  46. package/src/utils/arazzo/svg.js +707 -0
  47. package/src/utils/curl-utils.js +5 -2
  48. package/src/utils/highlighter.js +3 -0
  49. package/src/utils/mcp/README.md +155 -0
  50. package/src/utils/mcp/common.js +45 -346
  51. package/src/utils/mcp/manifest.js +382 -0
  52. package/src/utils/mcp/project/server.ts +53 -0
  53. package/src/utils/mcp/project.js +88 -0
  54. package/src/utils/mcp/runtime.js +289 -0
  55. package/src/utils/mcp/schema.js +98 -0
  56. package/src/utils/mcp/workflow-runtime.js +374 -0
  57. package/src/utils/mcp/workflows.js +240 -0
  58. package/src/utils/mcp/zip.js +102 -0
  59. package/src/utils/mcp-generator.js +29 -125
  60. package/src/utils/mock-interceptor.js +134 -85
  61. package/src/utils/request-utils.js +61 -0
  62. package/src/utils/schema-utils.js +33 -6
  63. package/src/utils/slugger.js +5 -7
  64. package/src/utils/spec-parser.js +190 -164
  65. package/src/utils/url-utils.js +36 -1
  66. package/dist/rapidoc-static-min.js +0 -13542
  67. package/src/static/index.d.ts +0 -149
  68. package/src/static/index.js +0 -62
  69. package/src/static/navigation.js +0 -497
  70. package/src/static/render.js +0 -490
  71. package/src/static/serializer.js +0 -216
  72. package/src/utils/mcp/generators/csharp.js +0 -242
  73. package/src/utils/mcp/generators/go.js +0 -298
  74. package/src/utils/mcp/generators/java.js +0 -330
  75. package/src/utils/mcp/generators/python.js +0 -248
  76. package/src/utils/mcp/generators/rust.js +0 -281
  77. package/src/utils/mcp/generators/typescript.js +0 -290
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Builds the MCP manifest: a plain JSON description of an API's tools that a runtime executes.
3
+ * Pure, no dependencies; takes a raw OpenAPI 3.x / Swagger 2.0 object or the result of
4
+ * `ProcessSpec` (`tags[].paths[]`).
5
+ */
6
+ import {
7
+ AUTH_TRANSPORT_HEADERS,
8
+ deriveToolName,
9
+ sanitizeDescription,
10
+ toSnakeCase,
11
+ } from "./common.js";
12
+ import { resolvePointer, toJsonSchema } from "./schema.js";
13
+ import { compileWorkflows } from "./workflows.js";
14
+ import { indexOperations, indexResolvedSpec } from "../arazzo/operations.js";
15
+
16
+ export { toJsonSchema };
17
+
18
+ export const MANIFEST_VERSION = 1;
19
+
20
+ const METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace", "query"];
21
+ const RESOURCE_PATH_HINTS = [
22
+ "/reference",
23
+ "/currencies",
24
+ "/countries",
25
+ "/tiers",
26
+ "/categories",
27
+ "/status",
28
+ "/schema",
29
+ ];
30
+ const SECURITY_KEYS = [
31
+ "type",
32
+ "description",
33
+ "in",
34
+ "name",
35
+ "scheme",
36
+ "bearerFormat",
37
+ "openIdConnectUrl",
38
+ ];
39
+
40
+ function isProcessedSpec(spec) {
41
+ return (
42
+ "specLoadError" in spec ||
43
+ (Array.isArray(spec.tags) && spec.tags.some((t) => Array.isArray(t?.paths)))
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Follows a `#/...` $ref within `spec` (a few hops, for refs to refs); anything else is returned as is.
49
+ * A raw spec keeps its parameters, request bodies and path items as $refs to components; a processed
50
+ * one (`<rapi-doc>`'s resolvedSpec) is already dereferenced.
51
+ */
52
+ function follow(node, spec) {
53
+ for (
54
+ let hops = 0;
55
+ hops < 10 && typeof node?.$ref === "string" && node.$ref.startsWith("#/");
56
+ hops++
57
+ ) {
58
+ let target = spec;
59
+ for (const segment of node.$ref.slice(2).split("/")) {
60
+ const key = decodeURIComponent(segment).replace(/~1/g, "/").replace(/~0/g, "~");
61
+ target = target?.[key];
62
+ }
63
+ if (!target || typeof target !== "object") return node;
64
+ node = target;
65
+ }
66
+ return node;
67
+ }
68
+
69
+ /** Every operation with the fields the manifest needs, from either spec shape. */
70
+ function collectOperations(spec, processed) {
71
+ const operations = [];
72
+ if (processed) {
73
+ for (const tag of spec.tags || []) {
74
+ for (const p of tag.paths || []) {
75
+ operations.push({
76
+ method: String(p.method || "get").toLowerCase(),
77
+ path: p.path || "",
78
+ operationId: p.rawOperation?.operationId || "",
79
+ summary: p.summary || "",
80
+ description: p.description || "",
81
+ deprecated: p.deprecated === true,
82
+ isWebhook: p.isWebhook === true,
83
+ internal: p.xInternal === true || p.xHidden === true,
84
+ parameters: Array.isArray(p.parameters) ? p.parameters : [],
85
+ requestBody: p.requestBody || null,
86
+ security: p.security,
87
+ tag: tag.name || "",
88
+ });
89
+ }
90
+ }
91
+ return operations;
92
+ }
93
+ const params = (list) => (Array.isArray(list) ? list.map((p) => follow(p, spec)) : []);
94
+ for (const [path, rawItem] of Object.entries(spec.paths || {})) {
95
+ const item = follow(rawItem, spec);
96
+ if (!item || typeof item !== "object") continue;
97
+ const common = params(item.parameters);
98
+ for (const method of METHODS) {
99
+ const op = item[method];
100
+ if (!op || typeof op !== "object") continue;
101
+ const own = params(op.parameters);
102
+ // Path-level parameters apply unless the operation redefines them (same name and location)
103
+ const parameters = common
104
+ .filter((c) => !own.some((o) => o?.name === c?.name && o?.in === c?.in))
105
+ .concat(own);
106
+ operations.push({
107
+ method,
108
+ path,
109
+ operationId: op.operationId || "",
110
+ summary: op.summary || "",
111
+ description: op.description || "",
112
+ deprecated: op.deprecated === true,
113
+ isWebhook: false,
114
+ internal:
115
+ op["x-internal"] === true ||
116
+ op["x-hidden"] === true ||
117
+ item["x-internal"] === true ||
118
+ item["x-hidden"] === true,
119
+ parameters,
120
+ requestBody: follow(op.requestBody, spec) || null,
121
+ security: op.security ?? spec.security,
122
+ tag: Array.isArray(op.tags) ? op.tags[0] || "" : "",
123
+ });
124
+ }
125
+ }
126
+ return operations;
127
+ }
128
+
129
+ /** The spec's security schemes as `{ id: { type, in, name, scheme, ... } }`, from either shape. */
130
+ function collectSecuritySchemes(spec, processed) {
131
+ const out = {};
132
+ const add = (id, scheme) => {
133
+ if (!scheme || typeof scheme !== "object" || !scheme.type) return;
134
+ const entry = {};
135
+ for (const key of SECURITY_KEYS) if (scheme[key] !== undefined) entry[key] = scheme[key];
136
+ if (scheme.type === "oauth2" && scheme.flows && typeof scheme.flows === "object") {
137
+ entry.flows = scheme.flows;
138
+ }
139
+ out[id] = entry;
140
+ };
141
+ if (processed) {
142
+ for (const s of spec.securitySchemes || []) {
143
+ // The scheme <rapi-doc>'s api-key attributes inject is the page's, not the API's
144
+ if (s?.securitySchemeId && s.securitySchemeId !== "_rapidoc_api_key") {
145
+ add(s.securitySchemeId, s.type === "apiKey" ? s : { ...s, in: undefined, name: undefined });
146
+ }
147
+ }
148
+ } else {
149
+ const schemes = spec.components?.securitySchemes || spec.securityDefinitions || {};
150
+ for (const [id, scheme] of Object.entries(schemes)) {
151
+ add(id, scheme?.$ref ? resolvePointer(spec, scheme.$ref) : scheme);
152
+ }
153
+ }
154
+ return out;
155
+ }
156
+
157
+ function baseUrlOf(spec, processed) {
158
+ const server = spec.servers?.[0];
159
+ if (server?.url) {
160
+ let url = String(server.url);
161
+ for (const [name, v] of Object.entries(server.variables || {})) {
162
+ url = url.split(`{${name}}`).join(String(v?.default ?? v?.enum?.[0] ?? ""));
163
+ }
164
+ return url;
165
+ }
166
+ if (!processed && spec.host) {
167
+ return `${spec.schemes?.[0] || "https"}://${spec.host}${spec.basePath || ""}`;
168
+ }
169
+ return "http://localhost";
170
+ }
171
+
172
+ /** A parameter's schema, from `schema`, `content` or Swagger 2.0's inline type keywords. */
173
+ function parameterSchema(param, root) {
174
+ if (param.schema) return toJsonSchema(param.schema, root);
175
+ if (param.content && typeof param.content === "object") {
176
+ const first = Object.values(param.content)[0];
177
+ if (first?.schema) return toJsonSchema(first.schema, root);
178
+ }
179
+ const inline = {};
180
+ for (const key of [
181
+ "type",
182
+ "format",
183
+ "enum",
184
+ "items",
185
+ "default",
186
+ "minimum",
187
+ "maximum",
188
+ "pattern",
189
+ ]) {
190
+ if (param[key] !== undefined) inline[key] = param[key];
191
+ }
192
+ return inline.type ? toJsonSchema(inline, root) : { type: "string" };
193
+ }
194
+
195
+ function bodyContent(requestBody) {
196
+ const content = requestBody?.content;
197
+ if (!content || typeof content !== "object") return null;
198
+ const types = Object.keys(content);
199
+ const pick =
200
+ types.find((t) => t === "application/json") ||
201
+ types.find((t) => t.endsWith("+json")) ||
202
+ types.find((t) => t === "application/x-www-form-urlencoded") ||
203
+ types.find((t) => t === "multipart/form-data") ||
204
+ types[0];
205
+ return pick ? { contentType: pick, schema: content[pick]?.schema } : null;
206
+ }
207
+
208
+ /**
209
+ * @param {object} spec Raw OpenAPI / Swagger object, or `ProcessSpec`'s result
210
+ * @param {object} [options]
211
+ * @param {string} [options.serverName] the MCP server's name; defaults to the API title
212
+ * @param {object|string} [options.arazzo] a parsed Arazzo document (or JSON text) whose workflows
213
+ * become tools; its first OpenAPI source description is this API
214
+ * @param {(problem: { level: string, path: string, message: string }) => void} [options.onProblem]
215
+ * receives one entry per workflow the runtime cannot run, which is left out
216
+ * @param {string} [options.sourceName] the key of the API in `sources`: the Arazzo document's
217
+ * first OpenAPI source name, else `api`
218
+ * @param {Record<string, object>} [options.sources] the other OpenAPI documents the Arazzo
219
+ * document's workflows use, by source description name; each becomes a manifest source and its
220
+ * operations resolve for workflow steps
221
+ * @param {boolean} [options.includeInternal] keep `x-internal` / `x-hidden` operations
222
+ * @param {boolean} [options.includeDeprecated] keep deprecated operations
223
+ * @param {boolean} [options.includeOperations] `false` leaves the operations out: a manifest of the
224
+ * Arazzo document's workflow tools alone (the sources stay, the runtime needs them)
225
+ * @returns {object} the manifest: `{ manifestVersion, server, sources, tools, resources,
226
+ * workflows }`. A workflow tool has `workflow` (its id in `workflows`) instead of `operation`. Each tool has `name`, `description`, `inputSchema` (JSON Schema; parameters by
227
+ * name plus `body`), `annotations` (`readOnlyHint`, `destructiveHint`, `idempotentHint`) and
228
+ * `operation` (`source`, `method`, `path`, `params` [{ name, in, arg?, contentType? }],
229
+ * `security`).
230
+ */
231
+ export function buildMcpManifest(spec, options = {}) {
232
+ const src = spec && typeof spec === "object" ? spec : {};
233
+ const processed = isProcessedSpec(src);
234
+ const { includeInternal = false, includeDeprecated = false, includeOperations = true } = options;
235
+ const info = src.info || {};
236
+ const title = info.title || "OpenAPI MCP Server";
237
+ const arazzo = typeof options.arazzo === "string" ? JSON.parse(options.arazzo) : options.arazzo;
238
+ const firstSource = (arazzo?.sourceDescriptions || []).find(
239
+ (s) => s?.name && (!s.type || s.type === "openapi"),
240
+ );
241
+ const sourceName = options.sourceName || firstSource?.name || "api";
242
+ const securitySchemes = collectSecuritySchemes(src, processed);
243
+ // Header, query and cookie parameters that carry a credential are the runtime's business
244
+ const credentialParams = new Set(
245
+ Object.values(securitySchemes)
246
+ .filter((s) => s.type === "apiKey" && s.name)
247
+ .map((s) => `${s.in || "header"}:${String(s.name).toLowerCase()}`),
248
+ );
249
+
250
+ const tools = [];
251
+ const resources = [];
252
+ const usedNames = new Map();
253
+
254
+ for (const op of includeOperations ? collectOperations(src, processed) : []) {
255
+ if (op.isWebhook) continue;
256
+ if (op.internal && !includeInternal) continue;
257
+ if (op.deprecated && !includeDeprecated) continue;
258
+
259
+ const properties = {};
260
+ const required = [];
261
+ const params = [];
262
+ for (const p of op.parameters) {
263
+ if (!p?.name) continue;
264
+ const where = p.in || "query";
265
+ const lower = String(p.name).toLowerCase();
266
+ if (where === "header" && AUTH_TRANSPORT_HEADERS.has(lower)) continue;
267
+ if (credentialParams.has(`${where}:${lower}`)) continue;
268
+ if (where === "body") {
269
+ // Swagger 2.0 body parameter
270
+ const schema = parameterSchema(p, src);
271
+ if (p.description) schema.description = sanitizeDescription(p.description);
272
+ properties.body = schema;
273
+ if (p.required) required.push("body");
274
+ params.push({ name: "body", in: "body", contentType: "application/json" });
275
+ continue;
276
+ }
277
+ if (where === "formData") continue;
278
+ let arg = p.name;
279
+ if (arg in properties) arg = `${p.name}_${where}`;
280
+ const schema = parameterSchema(p, src);
281
+ if (p.description) schema.description = sanitizeDescription(p.description);
282
+ properties[arg] = schema;
283
+ if (p.required || where === "path") required.push(arg);
284
+ params.push(arg === p.name ? { name: p.name, in: where } : { name: p.name, in: where, arg });
285
+ }
286
+ const body = bodyContent(op.requestBody);
287
+ if (body) {
288
+ const schema = body.schema ? toJsonSchema(body.schema, src) : { type: "object" };
289
+ if (op.requestBody.description) {
290
+ schema.description = sanitizeDescription(op.requestBody.description);
291
+ }
292
+ properties.body = schema;
293
+ if (op.requestBody.required) required.push("body");
294
+ params.push({ name: "body", in: "body", contentType: body.contentType });
295
+ }
296
+ const inputSchema = { type: "object", properties };
297
+ if (required.length) inputSchema.required = required;
298
+
299
+ let name = toSnakeCase(op.operationId || deriveToolName(op.method, op.path));
300
+ const seen = usedNames.get(name) || 0;
301
+ usedNames.set(name, seen + 1);
302
+ if (seen) name = `${name}_${seen + 1}`;
303
+
304
+ const description = sanitizeDescription(
305
+ op.summary || op.description || `${op.method.toUpperCase()} ${op.path}`,
306
+ );
307
+ const operation = {
308
+ source: sourceName,
309
+ method: op.method.toUpperCase(),
310
+ path: op.path,
311
+ params,
312
+ security: Array.isArray(op.security)
313
+ ? op.security.filter((s) => s && typeof s === "object")
314
+ : [],
315
+ };
316
+ const isGet = op.method === "get";
317
+ const isResource =
318
+ isGet && !required.length && RESOURCE_PATH_HINTS.some((hint) => op.path.includes(hint));
319
+ if (isResource) {
320
+ resources.push({
321
+ name,
322
+ uri: `reference://${name.replace(/^get_/, "").replace(/_/g, "-")}`,
323
+ description,
324
+ mimeType: "application/json",
325
+ operation,
326
+ });
327
+ continue;
328
+ }
329
+ tools.push({
330
+ name,
331
+ description,
332
+ inputSchema,
333
+ annotations: {
334
+ readOnlyHint: isGet,
335
+ // POST adds; PUT, PATCH and DELETE change or remove what is there
336
+ destructiveHint: ["put", "patch", "delete"].includes(op.method),
337
+ idempotentHint: ["get", "put", "delete"].includes(op.method),
338
+ },
339
+ operation,
340
+ });
341
+ }
342
+
343
+ const workflows = {};
344
+ const sources = { [sourceName]: { baseUrl: baseUrlOf(src, processed), securitySchemes } };
345
+ if (arazzo && typeof arazzo === "object") {
346
+ const indexes = new Map([
347
+ [sourceName, processed ? indexResolvedSpec(src) : indexOperations(src)],
348
+ ]);
349
+ for (const [name, doc] of Object.entries(options.sources || {})) {
350
+ if (name === sourceName || !doc || typeof doc !== "object") continue;
351
+ const docProcessed = isProcessedSpec(doc);
352
+ indexes.set(name, docProcessed ? indexResolvedSpec(doc) : indexOperations(doc));
353
+ sources[name] = {
354
+ baseUrl: baseUrlOf(doc, docProcessed),
355
+ securitySchemes: collectSecuritySchemes(doc, docProcessed),
356
+ };
357
+ }
358
+ const compiled = compileWorkflows(arazzo, {
359
+ sources: indexes,
360
+ onProblem: options.onProblem,
361
+ });
362
+ for (const tool of compiled.tools) {
363
+ const seen = usedNames.get(tool.name) || 0;
364
+ usedNames.set(tool.name, seen + 1);
365
+ tools.push(seen ? { ...tool, name: `${tool.name}_${seen + 1}` } : tool);
366
+ }
367
+ Object.assign(workflows, compiled.workflows);
368
+ }
369
+
370
+ return {
371
+ manifestVersion: MANIFEST_VERSION,
372
+ server: {
373
+ name: toSnakeCase(options.serverName || title),
374
+ version: info.version || "1.0.0",
375
+ description: sanitizeDescription(info.description || ""),
376
+ },
377
+ sources,
378
+ tools,
379
+ resources,
380
+ workflows,
381
+ };
382
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * MCP server over stdio for the API described in manifest.json. Exported by RapiDoc.
3
+ *
4
+ * The tools, resources and the API's base URL come from the manifest and the requests are made
5
+ * by runtime.js, so this file is the same for every API. Credentials come from environment
6
+ * variables named after the API's security schemes (see claude_desktop_config.json);
7
+ * API_BASE_URL overrides the base URL.
8
+ */
9
+ import { readFileSync } from "node:fs";
10
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import {
13
+ CallToolRequestSchema,
14
+ ListResourcesRequestSchema,
15
+ ListToolsRequestSchema,
16
+ ReadResourceRequestSchema,
17
+ } from "@modelcontextprotocol/sdk/types.js";
18
+ import { createMcpRuntime, credentialsFromEnv } from "./runtime.js";
19
+
20
+ // workflow-runtime.js is part of the export only when the manifest has workflow tools
21
+ const { runWorkflow } = await import("./workflow-runtime.js").catch(() => ({
22
+ runWorkflow: undefined,
23
+ }));
24
+
25
+ const manifest = JSON.parse(readFileSync(new URL("./manifest.json", import.meta.url), "utf8"));
26
+
27
+ const baseUrls: Record<string, string> = {};
28
+ if (process.env.API_BASE_URL) {
29
+ for (const name of Object.keys(manifest.sources)) baseUrls[name] = process.env.API_BASE_URL;
30
+ }
31
+ const runtime = createMcpRuntime(manifest, {
32
+ credentials: credentialsFromEnv(manifest, process.env),
33
+ baseUrls,
34
+ runWorkflow,
35
+ });
36
+
37
+ const server = new Server(
38
+ { name: manifest.server.name, version: manifest.server.version },
39
+ { capabilities: { tools: {}, resources: {} } },
40
+ );
41
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: runtime.listTools() }));
42
+ server.setRequestHandler(CallToolRequestSchema, async (request) =>
43
+ runtime.callTool(request.params.name, request.params.arguments ?? {}),
44
+ );
45
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
46
+ resources: runtime.listResources(),
47
+ }));
48
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) =>
49
+ runtime.readResource(request.params.uri),
50
+ );
51
+
52
+ await server.connect(new StdioServerTransport());
53
+ console.error(`[MCP] ${manifest.server.name}: ${manifest.tools.length} tools on stdio`);
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Writes the exported MCP server project: the manifest as data, plus fixed files that are the
3
+ * same for every API (the runtime and a stdio server that reads the manifest).
4
+ */
5
+ import runtimeSource from "./runtime.js?raw";
6
+ import serverSource from "./project/server.ts?raw";
7
+ import { envNameFor, generateHostConfigs, toSnakeCase } from "./common.js";
8
+
9
+ /**
10
+ * @param {object} manifest from buildMcpManifest()
11
+ * @param {object} [options]
12
+ * @param {string} [options.serverName] the package name; defaults to the manifest's server name
13
+ * @param {string} [options.workflowRuntime] the text of workflow-runtime.js, written as
14
+ * `workflow-runtime.js` when the manifest has workflows (@rapidoc/mcp and <rapi-flow> supply it;
15
+ * dist/rapidoc-min.js does not carry it)
16
+ * @returns {Record<string, string>} file path → content
17
+ */
18
+ export function writeMcpProject(manifest, options = {}) {
19
+ const name = manifest.server.name || "api_mcp_server";
20
+ const hasWorkflows = Object.keys(manifest.workflows || {}).length > 0;
21
+ const schemeIds = Object.values(manifest.sources || {}).flatMap((s) =>
22
+ Object.keys(s.securitySchemes || {}),
23
+ );
24
+ const hostConfigs = generateHostConfigs(options.serverName || name, { schemes: schemeIds });
25
+ const packageJson = {
26
+ name: toSnakeCase(options.serverName || name).replace(/_/g, "-"),
27
+ version: manifest.server.version || "1.0.0",
28
+ private: true,
29
+ type: "module",
30
+ scripts: { start: "tsx server.ts" },
31
+ dependencies: { "@modelcontextprotocol/sdk": "^1.12.0" },
32
+ devDependencies: { tsx: "^4.19.0" },
33
+ };
34
+ const envLines = schemeIds.length
35
+ ? schemeIds.map((id) => {
36
+ const scheme = Object.values(manifest.sources)
37
+ .map((s) => s.securitySchemes?.[id])
38
+ .find(Boolean);
39
+ const what =
40
+ scheme?.type === "apiKey"
41
+ ? `the API key sent as ${scheme.in || "header"} \`${scheme.name}\``
42
+ : scheme?.type === "http" && String(scheme.scheme).toLowerCase() === "basic"
43
+ ? "`user:password` for HTTP Basic"
44
+ : "the bearer token";
45
+ return `- \`${envNameFor(id)}\`: ${what} (security scheme \`${id}\`)`;
46
+ })
47
+ : ["- `API_KEY`: not needed; this API declares no security scheme"];
48
+ const readme = `# ${manifest.server.name} MCP server
49
+
50
+ Exported by RapiDoc from the API's OpenAPI document. \`manifest.json\` lists ${manifest.tools.length} tools${
51
+ hasWorkflows ? ` (${Object.keys(manifest.workflows).length} of them workflows)` : ""
52
+ }${manifest.resources.length ? ` and ${manifest.resources.length} resources` : ""}; \`server.ts\` serves them over stdio and \`runtime.js\` makes the requests${
53
+ hasWorkflows && options.workflowRuntime
54
+ ? ", with workflow-runtime.js running the workflows"
55
+ : ""
56
+ }.
57
+
58
+ ## Run
59
+
60
+ \`\`\`bash
61
+ npm install
62
+ ${schemeIds.map((id) => `${envNameFor(id)}="..." `).join("")}npm start
63
+ \`\`\`
64
+
65
+ Environment variables:
66
+
67
+ ${envLines.join("\n")}
68
+ - \`API_BASE_URL\`: overrides the base URL in the manifest (${
69
+ Object.values(manifest.sources)[0]?.baseUrl || ""
70
+ })
71
+
72
+ ## Use with Claude Desktop or Cursor
73
+
74
+ Copy the \`mcpServers\` entry from \`claude_desktop_config.json\` into your host's configuration
75
+ and fill in the credentials.
76
+ `;
77
+ return {
78
+ "manifest.json": JSON.stringify(manifest, null, 2),
79
+ "server.ts": serverSource,
80
+ "runtime.js": runtimeSource,
81
+ ...(hasWorkflows && options.workflowRuntime
82
+ ? { "workflow-runtime.js": options.workflowRuntime }
83
+ : {}),
84
+ "package.json": JSON.stringify(packageJson, null, 2),
85
+ "README.md": readme,
86
+ "claude_desktop_config.json": hostConfigs.claudeDesktopConfig,
87
+ };
88
+ }