rapidoc 11.0.0 → 12.0.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 (80) hide show
  1. package/README.md +46 -31
  2. package/dist/rapidoc-min.js +1424 -213
  3. package/dist/rapidoc-static-min.js +13542 -0
  4. package/package.json +48 -34
  5. package/src/components/api-request.ts +869 -0
  6. package/src/components/api-response.ts +509 -0
  7. package/src/components/json-tree.ts +171 -0
  8. package/src/components/schema-base-element.ts +315 -0
  9. package/src/components/schema-table.ts +430 -0
  10. package/src/components/schema-tree.ts +386 -0
  11. package/src/components/tag-input.ts +184 -0
  12. package/src/index.js +6 -0
  13. package/src/oauth-receiver.ts +29 -0
  14. package/src/rapidoc-mini.ts +582 -0
  15. package/src/rapidoc.ts +1808 -0
  16. package/src/static/index.d.ts +149 -0
  17. package/src/static/index.js +62 -0
  18. package/src/static/navigation.js +497 -0
  19. package/src/static/render.js +490 -0
  20. package/src/static/serializer.js +216 -0
  21. package/src/styles/auth-styles.js +318 -0
  22. package/src/styles/border-styles.js +24 -0
  23. package/src/styles/css-bundle.js +129 -0
  24. package/src/styles/custom-styles.js +8 -0
  25. package/src/styles/dialog-box-styles.js +102 -0
  26. package/src/styles/endpoint-styles.js +559 -0
  27. package/src/styles/flex-styles.js +16 -0
  28. package/src/styles/font-styles.js +432 -0
  29. package/src/styles/info-styles.js +36 -0
  30. package/src/styles/input-styles.js +555 -0
  31. package/src/styles/layout-styles.js +396 -0
  32. package/src/styles/microlighter-styles.js +99 -0
  33. package/src/styles/nav-styles.js +609 -0
  34. package/src/styles/param-list-styles.js +261 -0
  35. package/src/styles/schema-styles.js +288 -0
  36. package/src/styles/tab-styles.js +106 -0
  37. package/src/styles/theme-tokens.js +582 -0
  38. package/src/templates/advanced-search-dialog-template.js +94 -0
  39. package/src/templates/api-response-template.js +219 -0
  40. package/src/templates/callback-template.js +86 -0
  41. package/src/templates/code-samples-template.js +52 -0
  42. package/src/templates/components-template.js +86 -0
  43. package/src/templates/downloads-template.js +85 -0
  44. package/src/templates/endpoint-template.js +386 -0
  45. package/src/templates/expanded-endpoint-template.js +266 -0
  46. package/src/templates/focused-endpoint-template.js +145 -0
  47. package/src/templates/header-template.js +92 -0
  48. package/src/templates/logo-template.js +21 -0
  49. package/src/templates/main-body-template.js +102 -0
  50. package/src/templates/navbar-template.js +489 -0
  51. package/src/templates/overview-template.js +104 -0
  52. package/src/templates/request-body-template.js +651 -0
  53. package/src/templates/request-params-template.js +474 -0
  54. package/src/templates/security-scheme-template.js +1263 -0
  55. package/src/templates/server-template.js +152 -0
  56. package/src/utils/auto-color-scheme.js +99 -0
  57. package/src/utils/bind-navbar.js +175 -0
  58. package/src/utils/color-utils.js +59 -0
  59. package/src/utils/common-utils.js +191 -0
  60. package/src/utils/curl-utils.js +123 -0
  61. package/src/utils/highlighter.js +416 -0
  62. package/src/utils/markdown-utils.js +110 -0
  63. package/src/utils/mcp/common.js +467 -0
  64. package/src/utils/mcp/generators/csharp.js +242 -0
  65. package/src/utils/mcp/generators/go.js +298 -0
  66. package/src/utils/mcp/generators/java.js +330 -0
  67. package/src/utils/mcp/generators/python.js +248 -0
  68. package/src/utils/mcp/generators/rust.js +281 -0
  69. package/src/utils/mcp/generators/typescript.js +290 -0
  70. package/src/utils/mcp-generator.js +143 -0
  71. package/src/utils/mock-interceptor.js +508 -0
  72. package/src/utils/oauth-utils.js +87 -0
  73. package/src/utils/request-utils.js +203 -0
  74. package/src/utils/response-utils.js +203 -0
  75. package/src/utils/sanitize.js +57 -0
  76. package/src/utils/schema-utils.js +1927 -0
  77. package/src/utils/slugger.js +189 -0
  78. package/src/utils/spec-parser.js +915 -0
  79. package/src/utils/url-utils.js +211 -0
  80. package/src/utils/xml-utils.js +61 -0
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Single-file Python FastMCP Server Generator
3
+ * Uses fastmcp with zero external networking dependencies (urllib.request).
4
+ */
5
+ import { toSnakeCase, estimateTokenCost } from "../common.js";
6
+
7
+ export function generatePythonFastMcpServer(metadata, tools, resources, workflows, options = {}) {
8
+ const serverName = toSnakeCase(options.serverName || metadata.title || "rapidoc_mcp_server");
9
+ const budget = estimateTokenCost(tools, resources, workflows);
10
+ const defaultBaseUrl = metadata.servers?.[0]?.url || "http://localhost:3000";
11
+
12
+ const toolFunctions = tools
13
+ .map((t) => {
14
+ const pyArgs = t.parameters
15
+ .map((p) => {
16
+ let pyType = "str";
17
+ if (p.type === "integer") pyType = "int";
18
+ else if (p.type === "number") pyType = "float";
19
+ else if (p.type === "boolean") pyType = "bool";
20
+ else if (p.type === "array") {
21
+ const itemType = p.items?.type;
22
+ if (itemType === "integer") pyType = "List[int]";
23
+ else if (itemType === "number") pyType = "List[float]";
24
+ else if (itemType === "boolean") pyType = "List[bool]";
25
+ else if (itemType === "object") pyType = "List[Dict[str, Any]]";
26
+ else pyType = "List[str]";
27
+ } else if (p.type === "object") pyType = "Dict[str, Any]";
28
+
29
+ if (!p.required) {
30
+ return `${toSnakeCase(p.name)}: Optional[${pyType}] = None`;
31
+ }
32
+ return `${toSnakeCase(p.name)}: ${pyType}`;
33
+ })
34
+ .join(", ");
35
+
36
+ const allPyArgs = [pyArgs, "api_key: Optional[str] = None"].filter(Boolean).join(", ");
37
+ const pathParams = t.parameters.filter((p) => p.in === "path");
38
+ const safePathCode = pathParams
39
+ .map(
40
+ (p) =>
41
+ ` safe_${toSnakeCase(p.name)} = urllib.parse.quote(str(${toSnakeCase(p.name)}))`,
42
+ )
43
+ .join("\n");
44
+ const pathSub = t.path.replace(/\{([^}]+)\}/g, (match, p1) => `{safe_${toSnakeCase(p1)}}`);
45
+
46
+ return `
47
+ @mcp.tool(
48
+ name="${t.name}",
49
+ description="${t.description.replace(/"/g, '\\"')}"
50
+ )
51
+ def ${t.name}(${allPyArgs}) -> str:
52
+ """${t.description}${t.isConsequential ? " (⚠️ Consequential action - requires confirmation)" : ""}"""
53
+ ${safePathCode ? safePathCode + "\n" : ""} url = f"{BASE_URL.rstrip('/')}${pathSub}"
54
+ headers = {"Accept": "application/json"}
55
+ ${t.parameters
56
+ .filter((p) => p.in === "header")
57
+ .map(
58
+ (p) =>
59
+ ` if ${toSnakeCase(p.name)} is not None:\n headers["${p.name}"] = str(${toSnakeCase(p.name)})`,
60
+ )
61
+ .join("\n")}
62
+ resolved_key = api_key or API_KEY
63
+ if resolved_key:
64
+ headers["Authorization"] = f"Bearer {resolved_key}"
65
+
66
+ params = {}
67
+ ${t.parameters
68
+ .filter((p) => p.in === "query")
69
+ .map(
70
+ (p) =>
71
+ ` if ${toSnakeCase(p.name)} is not None:\n params["${p.name}"] = ${toSnakeCase(p.name)}`,
72
+ )
73
+ .join("\n")}
74
+ if params:
75
+ url += ("&" if "?" in url else "?") + urllib.parse.urlencode(params)
76
+
77
+ body_bytes = None
78
+ ${
79
+ ["POST", "PUT", "PATCH"].includes(t.method)
80
+ ? ` body_payload = {}
81
+ ${t.parameters
82
+ .filter((p) => p.in === "body")
83
+ .map(
84
+ (p) =>
85
+ ` if ${toSnakeCase(p.name)} is not None:\n body_payload["${p.name}"] = ${toSnakeCase(p.name)}`,
86
+ )
87
+ .join("\n")}
88
+ if body_payload:
89
+ headers["Content-Type"] = "application/json"
90
+ body_bytes = json.dumps(body_payload).encode("utf-8")`
91
+ : ""
92
+ }
93
+
94
+ req = urllib.request.Request(url, data=body_bytes, headers=headers, method="${t.method}")
95
+ try:
96
+ with urllib.request.urlopen(req, timeout=30.0) as resp:
97
+ return resp.read().decode("utf-8")
98
+ except urllib.error.HTTPError as e:
99
+ return e.read().decode("utf-8")
100
+ except Exception as e:
101
+ return f"Error invoking ${t.name}: {str(e)}"
102
+ `;
103
+ })
104
+ .join("\n");
105
+
106
+ const resourceFunctions = resources
107
+ .map((r) => {
108
+ return `
109
+ @mcp.resource("${r.uri}")
110
+ def ${toSnakeCase(r.name)}(uri: str = "") -> str:
111
+ """${r.description}"""
112
+ url = f"{BASE_URL.rstrip('/')}${r.path}"
113
+ headers = {"Accept": "${r.mimeType || "application/json"}"}
114
+
115
+ resolved_key = API_KEY
116
+ if uri and "?" in uri:
117
+ parsed_query = urllib.parse.parse_qs(urllib.parse.urlparse(uri).query)
118
+ passed_key = parsed_query.get("apiKey") or parsed_query.get("api_key")
119
+ if passed_key and passed_key[0].strip():
120
+ resolved_key = passed_key[0].strip()
121
+
122
+ if resolved_key:
123
+ headers["Authorization"] = f"Bearer {resolved_key}"
124
+
125
+ if uri and "?" in uri:
126
+ parsed_query = urllib.parse.parse_qs(urllib.parse.urlparse(uri).query)
127
+ params = {k: v[0] for k, v in parsed_query.items() if k.lower() not in ["apikey", "api_key"]}
128
+ if params:
129
+ url += ("&" if "?" in url else "?") + urllib.parse.urlencode(params)
130
+
131
+ req = urllib.request.Request(url, headers=headers, method="GET")
132
+ try:
133
+ with urllib.request.urlopen(req, timeout=30.0) as resp:
134
+ return resp.read().decode("utf-8")
135
+ except urllib.error.HTTPError as e:
136
+ return e.read().decode("utf-8")
137
+ except Exception as e:
138
+ return f"Error loading resource: {str(e)}"
139
+ `;
140
+ })
141
+ .join("\n");
142
+
143
+ const workflowFunctions = workflows
144
+ .map((w) => {
145
+ const paramKeys = w.parameters ? Object.keys(w.parameters) : [];
146
+ const pyArgs = paramKeys
147
+ .map((k) => `${toSnakeCase(k)}: str`)
148
+ .concat(["api_key: Optional[str] = None"])
149
+ .join(", ");
150
+
151
+ return `
152
+ @mcp.tool(
153
+ name="${w.name}",
154
+ description="${(w.description || "Composite workflow").replace(/"/g, '\\"')}"
155
+ )
156
+ def ${toSnakeCase(w.name)}(${pyArgs}) -> str:
157
+ """Composite workflow: ${(w.description || "").replace(/"/g, '\\"')}"""
158
+ results = {}
159
+ headers = {"Accept": "application/json"}
160
+ resolved_key = api_key or API_KEY
161
+ if resolved_key:
162
+ headers["Authorization"] = f"Bearer {resolved_key}"
163
+
164
+ try:
165
+ ${(w.steps || [])
166
+ .map((s, idx) => {
167
+ const argKey = paramKeys[0] || "id";
168
+ return ` # Step ${idx + 1}: ${s.call}
169
+ step_url_${idx} = f"{BASE_URL.rstrip('/')}/api/{urllib.parse.quote(str(${toSnakeCase(argKey)}))}"
170
+ req_${idx} = urllib.request.Request(step_url_${idx}, headers=headers, method="GET")
171
+ with urllib.request.urlopen(req_${idx}, timeout=30.0) as resp_${idx}:
172
+ results["${s.call}"] = json.loads(resp_${idx}.read().decode("utf-8"))`;
173
+ })
174
+ .join("\n")}
175
+ return json.dumps(results, indent=2)
176
+ except Exception as e:
177
+ return f"Workflow ${w.name} failed: {str(e)}"
178
+ `;
179
+ })
180
+ .join("\n");
181
+
182
+ return `"""
183
+ Auto-generated by RapiDoc Curated MCP Generator
184
+ Spec: ${metadata.title} (v${metadata.version})
185
+
186
+ [MULTI-TENANCY & AUTHENTICATION]
187
+ - Local Stdio Mode: Claude Desktop, Cursor, and local agents use API_KEY from environment.
188
+ - Multi-Tenant / Agent Mode: When running in a shared service, the calling AI agent can pass
189
+ per-session credentials directly in the \`api_key\` parameter of any tool.
190
+
191
+ [TOKEN BUDGET DIAGNOSTIC]
192
+ - Total Tools: ${budget.totalTools}
193
+ - Total Resources: ${budget.totalResources}
194
+ - Total Workflows: ${budget.totalWorkflows}
195
+ - Estimated Prompt Context Overhead: ~${budget.estimatedTokens.toLocaleString()} tokens/turn
196
+ ${budget.warnings.map((w) => `- ${w}`).join("\n")}
197
+
198
+ [HOW TO RUN]
199
+ 1. pip install fastmcp
200
+ 2. fastmcp run server.py
201
+ """
202
+
203
+ import os
204
+ import json
205
+ import urllib.request
206
+ import urllib.parse
207
+ import urllib.error
208
+ from typing import Optional, List, Dict, Any
209
+ from fastmcp import FastMCP
210
+
211
+ API_KEY = os.environ.get("API_KEY", "")
212
+ BASE_URL = os.environ.get("API_BASE_URL", "${defaultBaseUrl}")
213
+
214
+ mcp = FastMCP("${serverName}")
215
+
216
+ ${resourceFunctions}
217
+ ${toolFunctions}
218
+ ${workflowFunctions}
219
+
220
+ if __name__ == "__main__":
221
+ mcp.run()
222
+ `;
223
+ }
224
+
225
+ export function generatePythonFiles(serverCode, metadata) {
226
+ const readme = `# ${metadata.title} - MCP Server (Python FastMCP)
227
+
228
+ Auto-generated by RapiDoc Curated MCP Generator.
229
+
230
+ Zero external networking dependencies: uses Python standard library (\`urllib.request\`) + FastMCP.
231
+
232
+ ### Prerequisites
233
+ - Python 3.10+
234
+ - \`pip install -r requirements.txt\`
235
+
236
+ ### Running the Server
237
+ \`\`\`bash
238
+ export API_KEY="your-api-key"
239
+ fastmcp run server.py
240
+ \`\`\`
241
+ `;
242
+
243
+ return {
244
+ "server.py": serverCode,
245
+ "requirements.txt": "fastmcp>=0.4.0\n",
246
+ "README.md": readme,
247
+ };
248
+ }
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Single-file Rust MCP Server Generator
3
+ * Uses mcp_sdk / rmcp conventions.
4
+ */
5
+ import { toSnakeCase, estimateTokenCost } from "../common.js";
6
+
7
+ export function generateRustMcpServer(metadata, tools, resources, workflows, options = {}) {
8
+ const serverName = toSnakeCase(options.serverName || metadata.title || "rapidoc_mcp_server");
9
+ const budget = estimateTokenCost(tools, resources, workflows);
10
+ const defaultBaseUrl = metadata.servers?.[0]?.url || "http://localhost:3000";
11
+
12
+ const toolRegistrations = tools
13
+ .map((t) => {
14
+ const propCalls = t.parameters
15
+ .map((p) => {
16
+ let rustSchemaType = "string";
17
+ if (p.type === "integer" || p.type === "number") rustSchemaType = "number";
18
+ else if (p.type === "boolean") rustSchemaType = "boolean";
19
+ else if (p.type === "array") rustSchemaType = "array";
20
+ else if (p.type === "object") rustSchemaType = "object";
21
+ return `\n .property("${toSnakeCase(p.name)}", "${rustSchemaType}", ${Boolean(p.required)}, ${JSON.stringify(p.description || "")})`;
22
+ })
23
+ .join("");
24
+
25
+ return ` server.add_tool(
26
+ Tool::new("${t.name}", ${JSON.stringify(t.description)})
27
+ ${propCalls ? propCalls + "\n" : ""} .property("api_key", "string", false, "Optional Bearer API key override for multi-tenant sessions")
28
+ );`;
29
+ })
30
+ .join("\n\n");
31
+
32
+ const workflowRegistrations = workflows
33
+ .map((w) => {
34
+ const paramKeys = w.parameters ? Object.keys(w.parameters) : [];
35
+ const propCalls = paramKeys
36
+ .map(
37
+ (k) =>
38
+ `\n .property("${toSnakeCase(k)}", "string", true, ${JSON.stringify(w.parameters[k].description || "")})`,
39
+ )
40
+ .join("");
41
+
42
+ return ` server.add_tool(
43
+ Tool::new("${w.name}", ${JSON.stringify(w.description || "Composite workflow")})
44
+ ${propCalls ? propCalls + "\n" : ""} .property("api_key", "string", false, "Optional Bearer API key override for multi-tenant sessions")
45
+ );`;
46
+ })
47
+ .join("\n\n");
48
+
49
+ const toolArms = tools
50
+ .map((t) => {
51
+ const pathSub = t.path.replace(/\{[^}]+\}/g, "{}");
52
+ const pathParams = t.parameters.filter((p) => p.in === "path");
53
+ const queryParams = t.parameters.filter((p) => p.in === "query");
54
+ const bodyParams = t.parameters.filter((p) => p.in === "body");
55
+ const hasBody = ["POST", "PUT", "PATCH"].includes(t.method) && bodyParams.length > 0;
56
+
57
+ const pathArgs = pathParams
58
+ .map(
59
+ (p) =>
60
+ `urlencoding::encode(args.get("${toSnakeCase(p.name)}").and_then(|v| v.as_str()).unwrap_or("")).as_ref()`,
61
+ )
62
+ .join(", ");
63
+
64
+ const queryCode =
65
+ queryParams.length > 0
66
+ ? ` let mut query_params: Vec<(&str, String)> = Vec::new();
67
+ ${queryParams
68
+ .map(
69
+ (p) => ` if let Some(val) = args.get("${toSnakeCase(p.name)}") {
70
+ let s = val.as_str().map(String::from).unwrap_or_else(|| val.to_string());
71
+ query_params.push(("${p.name}", s));
72
+ }`,
73
+ )
74
+ .join("\n")}
75
+ if !query_params.is_empty() {
76
+ req = req.query(&query_params);
77
+ }\n`
78
+ : "";
79
+
80
+ const bodyCode = hasBody
81
+ ? ` let mut body_map = serde_json::Map::new();
82
+ ${bodyParams
83
+ .map(
84
+ (p) => ` if let Some(val) = args.get("${toSnakeCase(p.name)}") {
85
+ body_map.insert("${p.name}".to_string(), val.clone());
86
+ }`,
87
+ )
88
+ .join("\n")}
89
+ if !body_map.is_empty() {
90
+ req = req.json(&body_map);
91
+ }\n`
92
+ : "";
93
+
94
+ return ` "${t.name}" => {
95
+ let url = format!("{}${pathSub}", base_url.trim_end_matches('/')${pathArgs ? `, ${pathArgs}` : ""});
96
+ let mut req = client.request(reqwest::Method::from_bytes(b"${t.method}")?, &url)
97
+ .header("Accept", "application/json");
98
+ ${t.parameters
99
+ .filter((p) => p.in === "header")
100
+ .map(
101
+ (p) =>
102
+ ` if let Some(h) = args.get("${p.name}").or_else(|| args.get("${toSnakeCase(p.name)}")).and_then(|v| v.as_str()) {\n req = req.header("${p.name}", h);\n }`,
103
+ )
104
+ .join("\n")}
105
+ let resolved_key = args.get("api_key").and_then(|v| v.as_str()).map(String::from).or_else(|| api_key.clone());
106
+ if let Some(ref key) = resolved_key {
107
+ req = req.header("Authorization", format!("Bearer {}", key));
108
+ }
109
+ ${queryCode}${bodyCode} let resp = req.send().await?;
110
+ let text = resp.text().await?;
111
+ Ok(ToolResponse::text(text))
112
+ }`;
113
+ })
114
+ .join("\n");
115
+
116
+ const workflowArms = workflows
117
+ .map((w) => {
118
+ const paramKeys = w.parameters ? Object.keys(w.parameters) : [];
119
+ const argKey = paramKeys[0] || "id";
120
+
121
+ return ` "${w.name}" => {
122
+ let resolved_key = args.get("api_key").and_then(|v| v.as_str()).map(String::from).or_else(|| api_key.clone());
123
+ let param_val = args.get("${toSnakeCase(argKey)}").and_then(|v| v.as_str()).unwrap_or("");
124
+ let mut results = serde_json::Map::new();
125
+ ${(w.steps || [])
126
+ .map(
127
+ (s, idx) => ` // Step ${idx + 1}: ${s.call}
128
+ let step_url_${idx} = format!("{}/api/{}", base_url.trim_end_matches('/'), urlencoding::encode(param_val));
129
+ let mut step_req_${idx} = client.get(&step_url_${idx}).header("Accept", "application/json");
130
+ if let Some(ref key) = resolved_key {
131
+ step_req_${idx} = step_req_${idx}.header("Authorization", format!("Bearer {}", key));
132
+ }
133
+ if let Ok(resp) = step_req_${idx}.send().await {
134
+ if let Ok(json_res) = resp.json::<serde_json::Value>().await {
135
+ results.insert("${s.call}".to_string(), json_res);
136
+ }
137
+ }`,
138
+ )
139
+ .join("\n")}
140
+ Ok(ToolResponse::text(serde_json::to_string_pretty(&results)?))
141
+ }`;
142
+ })
143
+ .join("\n");
144
+
145
+ const resourceArms = resources
146
+ .map((r) => {
147
+ return ` "${r.uri}" => {
148
+ let mut req = client.get(format!("{}{}", base_url.trim_end_matches('/'), "${r.path}")).header("Accept", "${r.mimeType}");
149
+ if !query_params.is_empty() {
150
+ req = req.query(&query_params);
151
+ }
152
+ if let Some(ref key) = resolved_key {
153
+ req = req.header("Authorization", format!("Bearer {}", key));
154
+ }
155
+ let resp = req.send().await.map_err(|e| Box::new(e) as Box<dyn Error>)?;
156
+ let text = resp.text().await.map_err(|e| Box::new(e) as Box<dyn Error>)?;
157
+ Ok(vec![mcp_sdk::server::ResourceContents::text("${r.uri}", "${r.mimeType}", text)])
158
+ }`;
159
+ })
160
+ .join("\n");
161
+
162
+ const hasResources = resources.length > 0;
163
+ const resourceRegistration = hasResources
164
+ ? `
165
+ server.on_read_resource(move |uri| {
166
+ let client = client.clone();
167
+ let api_key = api_key.clone();
168
+ let base_url = base_url.clone();
169
+ async move {
170
+ let (base_uri, query_str) = uri.split_once('?').unwrap_or((uri.as_str(), ""));
171
+ let mut resolved_key = api_key.clone();
172
+ let mut query_params: Vec<(&str, &str)> = Vec::new();
173
+
174
+ if !query_str.is_empty() {
175
+ for pair in query_str.split('&') {
176
+ if let Some((k, v)) = pair.split_once('=') {
177
+ if k.eq_ignore_ascii_case("apikey") || k.eq_ignore_ascii_case("api_key") {
178
+ if !v.is_empty() {
179
+ resolved_key = Some(v.to_string());
180
+ }
181
+ } else if !k.is_empty() {
182
+ query_params.push((k, v));
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ match base_uri {
189
+ ${resourceArms}
190
+ _ => Err(format!("Unknown resource: {}", uri).into()),
191
+ }
192
+ }
193
+ });`
194
+ : "";
195
+
196
+ return `/*
197
+ Auto-generated by RapiDoc Curated MCP Generator
198
+ Spec: ${metadata.title} (v${metadata.version})
199
+
200
+ [MULTI-TENANCY & AUTHENTICATION]
201
+ - Local Stdio Mode: Claude Desktop, Cursor, and local agents use API_KEY from environment.
202
+ - Multi-Tenant / Agent Mode: When running in a shared service, the calling AI agent can pass
203
+ per-session credentials directly in the \`api_key\` parameter of any tool.
204
+
205
+ [TOKEN BUDGET DIAGNOSTIC]
206
+ - Total Tools: ${budget.totalTools}
207
+ - Total Resources: ${budget.totalResources}
208
+ - Total Workflows: ${budget.totalWorkflows}
209
+ - Estimated Prompt Context Overhead: ~${budget.estimatedTokens.toLocaleString()} tokens/turn
210
+ ${budget.warnings.map((w) => `- ${w}`).join("\n")}
211
+
212
+ [HOW TO RUN]
213
+ 1. cargo build --release
214
+ 2. cargo run
215
+ */
216
+
217
+ use std::env;
218
+ use std::error::Error;
219
+ use mcp_sdk::server::{Server, StdioTransport, Tool, ToolResponse};
220
+ use serde_json::Value;
221
+
222
+ #[tokio::main]
223
+ async fn main() -> Result<(), Box<dyn Error>> {
224
+ let api_key = env::var("API_KEY").ok();
225
+ let base_url = env::var("API_BASE_URL").unwrap_or_else(|_| "${defaultBaseUrl}".to_string());
226
+ let client = reqwest::Client::new();
227
+
228
+ let mut server = Server::new("${serverName}", "${metadata.version || "1.0.0"}");
229
+
230
+ ${toolRegistrations}
231
+ ${workflowRegistrations ? "\n" + workflowRegistrations : ""}
232
+
233
+ server.on_call_tool(move |name, args: Value| {
234
+ let client = client.clone();
235
+ let api_key = api_key.clone();
236
+ let base_url = base_url.clone();
237
+ async move {
238
+ let result: Result<ToolResponse, Box<dyn Error>> = async {
239
+ match name.as_str() {
240
+ ${toolArms}
241
+ ${workflowArms}
242
+ _ => Err(format!("Unknown tool or workflow: {}", name).into()),
243
+ }
244
+ }.await;
245
+
246
+ match result {
247
+ Ok(resp) => Ok(resp),
248
+ Err(e) => Ok(ToolResponse::error(format!("Execution failed: {}", e))),
249
+ }
250
+ }
251
+ });${resourceRegistration}
252
+
253
+ server.listen(StdioTransport::new()).await?;
254
+ Ok(())
255
+ }
256
+ `;
257
+ }
258
+
259
+ export function generateRustFiles(serverCode, metadata, options = {}) {
260
+ const cargoToml = `[package]\nname = "${toSnakeCase(options.serverName || metadata.title || "rapidoc_mcp")}"\nversion = "0.1.0"\nedition = "2021"\n\n[dependencies]\nmcp-sdk = "0.1"\ntokio = { version = "1.0", features = ["full"] }\nreqwest = { version = "0.11", features = ["json"] }\nserde_json = "1.0"\nurlencoding = "2.1"\n`;
261
+ const readme = `# ${metadata.title} - MCP Server (Rust)
262
+
263
+ Auto-generated by RapiDoc Curated MCP Generator.
264
+
265
+ ### Prerequisites
266
+ - Rust & Cargo (1.75+)
267
+
268
+ ### Building & Running
269
+ \`\`\`bash
270
+ cargo build --release
271
+ export API_KEY="your-api-key"
272
+ ./target/release/${toSnakeCase(options.serverName || metadata.title || "rapidoc_mcp")}
273
+ \`\`\`
274
+ `;
275
+
276
+ return {
277
+ "src/main.rs": serverCode,
278
+ "Cargo.toml": cargoToml,
279
+ "README.md": readme,
280
+ };
281
+ }