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
@@ -1,298 +0,0 @@
1
- /**
2
- * Single-file Go MCP Server Generator
3
- * Uses mark3labs/mcp-go framework.
4
- */
5
- import { toSnakeCase, estimateTokenCost } from "../common.js";
6
-
7
- export function generateGoMcpServer(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 paramModifiers = t.parameters
15
- .map((p) => {
16
- const isReq = p.required ? "mcp.Required()" : "";
17
- const desc = p.description ? `mcp.Description(${JSON.stringify(p.description)})` : "";
18
- const mods = [isReq, desc].filter(Boolean).join(", ");
19
- if (p.type === "integer" || p.type === "number") {
20
- return `\t\tmcp.WithNumber("${toSnakeCase(p.name)}"${mods ? `, ${mods}` : ""}),`;
21
- }
22
- if (p.type === "boolean") {
23
- return `\t\tmcp.WithBoolean("${toSnakeCase(p.name)}"${mods ? `, ${mods}` : ""}),`;
24
- }
25
- if (p.type === "array") {
26
- return `\t\tmcp.WithList("${toSnakeCase(p.name)}"${mods ? `, ${mods}` : ""}),`;
27
- }
28
- if (p.type === "object") {
29
- return `\t\tmcp.WithObject("${toSnakeCase(p.name)}"${mods ? `, ${mods}` : ""}),`;
30
- }
31
- return `\t\tmcp.WithString("${toSnakeCase(p.name)}"${mods ? `, ${mods}` : ""}),`;
32
- })
33
- .join("\n");
34
-
35
- const queryParams = t.parameters.filter((p) => p.in === "query");
36
- const bodyParams = t.parameters.filter((p) => p.in === "body");
37
- const hasBody = ["POST", "PUT", "PATCH"].includes(t.method) && bodyParams.length > 0;
38
-
39
- const allParamModifiers = paramModifiers
40
- ? `${paramModifiers}\n\t\tmcp.WithString("api_key", mcp.Description("Optional Bearer API key override for multi-tenant sessions")),`
41
- : `\t\tmcp.WithString("api_key", mcp.Description("Optional Bearer API key override for multi-tenant sessions")),`;
42
-
43
- const pathSub = t.path.replace(/\{[^}]+\}/g, "%s");
44
- const pathParams = t.parameters
45
- .filter((p) => p.in === "path")
46
- .map((p) => `url.PathEscape(fmt.Sprintf("%v", args["${toSnakeCase(p.name)}"]))`)
47
- .join(", ");
48
-
49
- const queryCode =
50
- queryParams.length > 0
51
- ? `\t\tqueryParams := url.Values{}
52
- ${queryParams.map((p) => `\t\tif v, ok := args["${toSnakeCase(p.name)}"]; ok && v != nil {\n\t\t\tqueryParams.Set("${p.name}", fmt.Sprintf("%v", v))\n\t\t}`).join("\n")}
53
- \t\tif len(queryParams) > 0 {
54
- \t\t\tif strings.Contains(urlStr, "?") {
55
- \t\t\t\turlStr += "&" + queryParams.Encode()
56
- \t\t\t} else {
57
- \t\t\t\turlStr += "?" + queryParams.Encode()
58
- \t\t\t}
59
- \t\t}\n`
60
- : "";
61
-
62
- const bodyCode = hasBody
63
- ? `\t\tvar bodyReader io.Reader
64
- \t\tbodyPayload := make(map[string]any)
65
- ${bodyParams.map((p) => `\t\tif v, ok := args["${toSnakeCase(p.name)}"]; ok && v != nil {\n\t\t\tbodyPayload["${p.name}"] = v\n\t\t}`).join("\n")}
66
- \t\tif len(bodyPayload) > 0 {
67
- \t\t\tb, _ := json.Marshal(bodyPayload)
68
- \t\t\tbodyReader = bytes.NewBuffer(b)
69
- \t\t}
70
- \t\treq, err := http.NewRequestWithContext(ctx, "${t.method}", urlStr, bodyReader)
71
- \t\tif err != nil {
72
- \t\t\treturn mcp.NewToolResultError(fmt.Sprintf("Failed to create request: %v", err)), nil
73
- \t\t}
74
- \t\tif bodyReader != nil {
75
- \t\t\treq.Header.Set("Content-Type", "application/json")
76
- \t\t}\n`
77
- : `\t\treq, err := http.NewRequestWithContext(ctx, "${t.method}", urlStr, nil)
78
- \t\tif err != nil {
79
- \t\t\treturn mcp.NewToolResultError(fmt.Sprintf("Failed to create request: %v", err)), nil
80
- \t\t}\n`;
81
-
82
- return `
83
- \t// Tool: ${t.name} (${t.method} ${t.path})
84
- \ts.AddTool(mcp.NewTool("${t.name}",
85
- \t\tmcp.WithDescription(${JSON.stringify(t.description)}),
86
- ${allParamModifiers}
87
- \t), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
88
- \t\targs := request.Params.Arguments
89
- \t\turlStr := fmt.Sprintf("%s${pathSub}", strings.TrimRight(baseURL, "/")${pathParams ? `, ${pathParams}` : ""})
90
- ${queryCode}${bodyCode}\t\treq.Header.Set("Accept", "application/json")
91
- ${t.parameters
92
- .filter((p) => p.in === "header")
93
- .map(
94
- (p) =>
95
- `\t\tif v, ok := args["${toSnakeCase(p.name)}"]; ok && v != nil {\n\t\t\treq.Header.Set("${p.name}", fmt.Sprintf("%v", v))\n\t\t}`,
96
- )
97
- .join("\n")}
98
- \t\tresolvedKey := apiKey
99
- \t\tif keyVal, ok := args["api_key"].(string); ok && keyVal != "" {
100
- \t\t\tresolvedKey = keyVal
101
- \t\t}
102
- \t\tif resolvedKey != "" {
103
- \t\t\treq.Header.Set("Authorization", "Bearer "+resolvedKey)
104
- \t\t}
105
- \t\tresp, err := client.Do(req)
106
- \t\tif err != nil {
107
- \t\t\treturn mcp.NewToolResultError(fmt.Sprintf("API request failed: %v", err)), nil
108
- \t\t}
109
- \t\tdefer resp.Body.Close()
110
- \t\tbody, _ := io.ReadAll(resp.Body)
111
- \t\treturn mcp.NewToolResultText(string(body)), nil
112
- \t})
113
- `;
114
- })
115
- .join("\n");
116
-
117
- const resourceRegistrations = resources
118
- .map((r) => {
119
- return `
120
- \t// Resource: ${r.name} (${r.uri})
121
- \ts.AddResource(mcp.NewResource("${r.uri}", "${toSnakeCase(r.name)}", mcp.WithResourceDescription(${JSON.stringify(r.description)})),
122
- \t\tfunc(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
123
- \t\t\ttargetURL, err := url.Parse(strings.TrimRight(baseURL, "/") + "${r.path}")
124
- \t\t\tif err != nil {
125
- \t\t\t\treturn nil, err
126
- \t\t\t}
127
-
128
- \t\t\tresolvedKey := apiKey
129
- \t\t\tif incomingURL, err := url.Parse(request.Params.URI); err == nil {
130
- \t\t\t\tq := incomingURL.Query()
131
- \t\t\t\tif passedKey := q.Get("apiKey"); passedKey != "" {
132
- \t\t\t\t\tresolvedKey = passedKey
133
- \t\t\t\t} else if passedKey := q.Get("api_key"); passedKey != "" {
134
- \t\t\t\t\tresolvedKey = passedKey
135
- \t\t\t\t}
136
-
137
- \t\t\t\ttargetQuery := targetURL.Query()
138
- \t\t\t\tfor k, v := range q {
139
- \t\t\t\t\tif strings.ToLower(k) != "apikey" && strings.ToLower(k) != "api_key" && len(v) > 0 {
140
- \t\t\t\t\t\ttargetQuery.Set(k, v[0])
141
- \t\t\t\t\t}
142
- \t\t\t\t}
143
- \t\t\t\ttargetURL.RawQuery = targetQuery.Encode()
144
- \t\t\t}
145
-
146
- \t\t\treq, err := http.NewRequestWithContext(ctx, "GET", targetURL.String(), nil)
147
- \t\t\tif err != nil {
148
- \t\t\t\treturn nil, err
149
- \t\t\t}
150
- \t\t\treq.Header.Set("Accept", "${r.mimeType || "application/json"}")
151
- \t\t\tif resolvedKey != "" {
152
- \t\t\t\treq.Header.Set("Authorization", "Bearer "+resolvedKey)
153
- \t\t\t}
154
- \t\t\tresp, err := client.Do(req)
155
- \t\t\tif err != nil {
156
- \t\t\t\treturn nil, err
157
- \t\t\t}
158
- \t\t\tdefer resp.Body.Close()
159
- \t\tbody, _ := io.ReadAll(resp.Body)
160
- \t\treturn []mcp.ResourceContents{mcp.TextResourceContents{URI: request.Params.URI, MIMEType: "${r.mimeType || "application/json"}", Text: string(body)}}, nil
161
- \t\t})`;
162
- })
163
- .join("\n");
164
-
165
- const workflowRegistrations = workflows
166
- .map((w) => {
167
- const paramKeys = w.parameters ? Object.keys(w.parameters) : [];
168
- const paramMods = paramKeys
169
- .map(
170
- (k) =>
171
- `\t\tmcp.WithString("${toSnakeCase(k)}", mcp.Required(), mcp.Description(${JSON.stringify(w.parameters[k].description || "")})),`,
172
- )
173
- .concat([
174
- `\t\tmcp.WithString("api_key", mcp.Description("Optional Bearer API key override for multi-tenant sessions")),`,
175
- ])
176
- .join("\n");
177
-
178
- return `
179
- \t// Composite Workflow: ${w.name}
180
- \ts.AddTool(mcp.NewTool("${w.name}",
181
- \t\tmcp.WithDescription(${JSON.stringify(w.description || "Composite workflow")}),
182
- ${paramMods}
183
- \t), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
184
- \t\tresults := make(map[string]any)
185
- \t\tparamVal, _ := request.Params.Arguments["${toSnakeCase(paramKeys[0] || "id")}"].(string)
186
- \t\tresolvedKey := apiKey
187
- \t\tif keyVal, ok := request.Params.Arguments["api_key"].(string); ok && keyVal != "" {
188
- \t\t\tresolvedKey = keyVal
189
- \t\t}
190
- ${(w.steps || [])
191
- .map((s, idx) => {
192
- return `\t\t// Step ${idx + 1}: ${s.call}
193
- \t\tstepReq_${idx}, _ := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/api/%s", strings.TrimRight(baseURL, "/"), url.QueryEscape(paramVal)), nil)
194
- \t\tstepReq_${idx}.Header.Set("Accept", "application/json")
195
- \t\tif resolvedKey != "" { stepReq_${idx}.Header.Set("Authorization", "Bearer "+resolvedKey) }
196
- \t\tif resp_${idx}, err := client.Do(stepReq_${idx}); err == nil {
197
- \t\t\tvar jsonRes any
198
- \t\t\tjson.NewDecoder(resp_${idx}.Body).Decode(&jsonRes)
199
- \t\t\tresults["${s.call}"] = jsonRes
200
- \t\t\tresp_${idx}.Body.Close()
201
- \t\t}`;
202
- })
203
- .join("\n")}
204
- \t\toutBytes, _ := json.MarshalIndent(results, "", " ")
205
- \t\treturn mcp.NewToolResultText(string(outBytes)), nil
206
- \t})`;
207
- })
208
- .join("\n");
209
-
210
- return `/*
211
- Auto-generated by RapiDoc Curated MCP Generator
212
- Spec: ${metadata.title} (v${metadata.version})
213
-
214
- [MULTI-TENANCY & AUTHENTICATION]
215
- - Local Stdio Mode: Claude Desktop, Cursor, and local agents use API_KEY from environment.
216
- - Multi-Tenant / Agent Mode: When running in a shared service, the calling AI agent can pass
217
- per-session credentials directly in the \`api_key\` parameter of any tool.
218
-
219
- [TOKEN BUDGET DIAGNOSTIC]
220
- - Total Tools: ${budget.totalTools}
221
- - Total Resources: ${budget.totalResources}
222
- - Total Workflows: ${budget.totalWorkflows}
223
- - Estimated Prompt Context Overhead: ~${budget.estimatedTokens.toLocaleString()} tokens/turn
224
- ${budget.warnings.map((w) => `- ${w}`).join("\n")}
225
-
226
- [HOW TO RUN]
227
- 1. go mod tidy
228
- 2. go run main.go
229
- */
230
-
231
- package main
232
-
233
- import (
234
- \t"bytes"
235
- \t"context"
236
- \t"encoding/json"
237
- \t"fmt"
238
- \t"io"
239
- \t"net/http"
240
- \t"net/url"
241
- \t"os"
242
- \t"strings"
243
- \t"time"
244
-
245
- \t"github.com/mark3labs/mcp-go/mcp"
246
- \t"github.com/mark3labs/mcp-go/server"
247
- )
248
-
249
- var (
250
- \tapiKey = os.Getenv("API_KEY")
251
- \tbaseURL = strings.TrimRight("${defaultBaseUrl}", "/")
252
- \tclient = &http.Client{Timeout: 30 * time.Second}
253
- )
254
-
255
- func main() {
256
- \tif envURL := os.Getenv("API_BASE_URL"); envURL != "" {
257
- \t\tbaseURL = strings.TrimRight(envURL, "/")
258
- \t}
259
- \tbaseURL = strings.TrimRight(baseURL, "/")
260
-
261
- \ts := server.NewMCPServer("${serverName}", "${metadata.version || "1.0.0"}", server.WithResourceCapabilities(true, true))
262
-
263
- ${resourceRegistrations}
264
- ${toolRegistrations}
265
- ${workflowRegistrations}
266
-
267
- \tif err := server.ServeStdio(s); err != nil {
268
- \t\tfmt.Fprintf(os.Stderr, "Server error: %v\\n", err)
269
- \t\tos.Exit(1)
270
- \t}
271
- }
272
- `;
273
- }
274
-
275
- export function generateGoFiles(serverCode, metadata, options = {}) {
276
- const modFile = `module ${toSnakeCase(options.serverName || metadata.title || "rapidoc_mcp")}\n\ngo 1.22\n\nrequire github.com/mark3labs/mcp-go v0.8.4\n`;
277
- const readme = `# ${metadata.title} - MCP Server (Go)
278
-
279
- Auto-generated by RapiDoc Curated MCP Generator using mark3labs/mcp-go.
280
-
281
- ### Prerequisites
282
- - Go 1.22+
283
-
284
- ### Building & Running
285
- \`\`\`bash
286
- go mod tidy
287
- go build -o server main.go
288
- export API_KEY="your-api-key"
289
- ./server
290
- \`\`\`
291
- `;
292
-
293
- return {
294
- "main.go": serverCode,
295
- "go.mod": modFile,
296
- "README.md": readme,
297
- };
298
- }
@@ -1,330 +0,0 @@
1
- /**
2
- * Single-file Java / Spring AI MCP Server Generator
3
- * Uses Spring AI MCP annotations, strongly typed records, and RestClient.
4
- */
5
- import { toPascalCase, toCamelCase, estimateTokenCost } from "../common.js";
6
-
7
- /**
8
- * Helper to map OpenAPI / JSON schema parameter type to idiomatic Java type.
9
- */
10
- function toJavaType(p) {
11
- if (p.type === "integer") return "Integer";
12
- if (p.type === "number") return "Double";
13
- if (p.type === "boolean") return "Boolean";
14
- if (p.type === "array") return "java.util.List<String>";
15
- if (p.type === "object") return "java.util.Map<String, Object>";
16
- return "String";
17
- }
18
-
19
- export function generateJavaMcpServer(metadata, tools, resources, workflows, options = {}) {
20
- const className = toPascalCase(options.serverName || "RapiDocMcpServer");
21
- const budget = estimateTokenCost(tools, resources, workflows);
22
- const defaultBaseUrl = metadata.servers?.[0]?.url || "http://localhost:3000";
23
-
24
- const toolRecords = [];
25
- const toolBeans = tools
26
- .map((t) => {
27
- const recordName = `${toPascalCase(t.name)}Request`;
28
- const pathParams = t.parameters.filter((p) => p.in === "path");
29
- const queryParams = t.parameters.filter((p) => p.in === "query");
30
- const headerParams = t.parameters.filter((p) => p.in === "header");
31
- const bodyParams = t.parameters.filter((p) => p.in === "body");
32
-
33
- // 1. Build typed request record
34
- const recordFields = t.parameters.map((p) => {
35
- const desc = ` @JsonPropertyDescription(${JSON.stringify(p.description || p.name)})\n`;
36
- const propAnno = p.required
37
- ? ` @JsonProperty(value = ${JSON.stringify(p.name)}, required = true)\n`
38
- : ` @JsonProperty(${JSON.stringify(p.name)})\n`;
39
- return `${desc}${propAnno} ${toJavaType(p)} ${toCamelCase(p.name)}`;
40
- });
41
- // Always provide per-session dynamic auth override field for multi-tenancy
42
- recordFields.push(
43
- ' @JsonPropertyDescription("Optional Bearer API key override for multi-tenant sessions")\n @JsonProperty("apiKey")\n String apiKey',
44
- );
45
-
46
- toolRecords.push(` @JsonClassDescription(${JSON.stringify(t.description || t.name)})
47
- public record ${recordName}(
48
- ${recordFields.join(",\n")}
49
- ) {}`);
50
-
51
- // 2. Build Bean function
52
- const pathReplacements = pathParams
53
- .map(
54
- (p) =>
55
- ` .replace("{${p.name}}", request.${toCamelCase(p.name)}() != null ? UriUtils.encodePath(String.valueOf(request.${toCamelCase(p.name)}()), StandardCharsets.UTF_8) : "")`,
56
- )
57
- .join("\n");
58
-
59
- const queryAppends = queryParams
60
- .map(
61
- (p) =>
62
- ` if (request.${toCamelCase(p.name)}() != null) uriBuilder.queryParam("${p.name}", request.${toCamelCase(p.name)}());`,
63
- )
64
- .join("\n");
65
-
66
- const headerAppends = headerParams
67
- .map(
68
- (p) =>
69
- ` if (request.${toCamelCase(p.name)}() != null && !String.valueOf(request.${toCamelCase(p.name)}()).isBlank()) {\n spec.header("${p.name}", String.valueOf(request.${toCamelCase(p.name)}()));\n }`,
70
- )
71
- .join("\n");
72
-
73
- const hasBody = ["POST", "PUT", "PATCH"].includes(t.method) && bodyParams.length > 0;
74
- const bodyAppends = hasBody
75
- ? ` Map<String, Object> bodyPayload = new java.util.LinkedHashMap<>();
76
- ${bodyParams
77
- .map(
78
- (p) =>
79
- ` if (request.${toCamelCase(p.name)}() != null) bodyPayload.put("${p.name}", request.${toCamelCase(p.name)}());`,
80
- )
81
- .join("\n")}`
82
- : "";
83
-
84
- return ` // Tool: ${t.name} (${t.method} ${t.path})
85
- // ${t.isConsequential ? "⚠️ Consequential/Destructive action" : "Safe/Read-only action"}
86
- @Bean
87
- @Description(${JSON.stringify(t.description + (t.isConsequential ? " (Requires confirmation)" : ""))})
88
- public Function<${recordName}, String> ${toCamelCase(t.name)}() {
89
- return request -> {
90
- String resolvedKey = request.apiKey() != null && !request.apiKey().isBlank() ? request.apiKey() : this.apiKey;
91
- String resolvedPath = "${t.path}"${pathReplacements ? "\n" + pathReplacements : ""};
92
- // Spring's builder automatically normalizes matching slashes between baseUrl and paths
93
- UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(this.baseUrl);
94
- uriBuilder.path(resolvedPath);
95
- ${queryAppends ? queryAppends + "\n" : ""}${bodyAppends ? bodyAppends + "\n" : ""} try {
96
- RestClient.RequestBodySpec spec = restClient.method(HttpMethod.${t.method})
97
- .uri(uriBuilder.build().toUri())
98
- .accept(MediaType.APPLICATION_JSON);
99
- if (resolvedKey != null && !resolvedKey.isBlank()) {
100
- spec.header("Authorization", "Bearer " + resolvedKey);
101
- }
102
- ${headerAppends ? headerAppends + "\n" : ""}${
103
- hasBody
104
- ? ` if (!bodyPayload.isEmpty()) {
105
- spec.contentType(MediaType.APPLICATION_JSON).body(bodyPayload);
106
- }\n`
107
- : ""
108
- } return spec.retrieve().body(String.class);
109
- } catch (Exception e) {
110
- return "Error invoking ${t.name}: " + e.getMessage();
111
- }
112
- };
113
- }`;
114
- })
115
- .join("\n\n");
116
-
117
- const hasWorkflows = workflows.length > 0;
118
-
119
- const resourceBeans = resources
120
- .map((r) => {
121
- const uriTemplate = `${r.uri}{?apiKey,api_key}`;
122
- return ` // Resource: ${r.name} (${uriTemplate})
123
- @Bean
124
- @Description(${JSON.stringify(`Resource: ${r.name} (${uriTemplate})`)})
125
- public Function<Map<String, Object>, String> ${toCamelCase(r.name)}() {
126
- return args -> {
127
- String resolvedKey = this.apiKey;
128
- if (args != null && args.containsKey("apiKey") && args.get("apiKey") != null) {
129
- String passedKey = String.valueOf(args.get("apiKey"));
130
- if (!passedKey.isBlank()) {
131
- resolvedKey = passedKey;
132
- }
133
- } else if (args != null && args.containsKey("api_key") && args.get("api_key") != null) {
134
- String passedKey = String.valueOf(args.get("api_key"));
135
- if (!passedKey.isBlank()) {
136
- resolvedKey = passedKey;
137
- }
138
- }
139
-
140
- // Spring's builder automatically normalizes matching slashes between baseUrl and paths
141
- UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(this.baseUrl);
142
- uriBuilder.path("${r.path}");
143
- if (args != null) {
144
- args.forEach((key, value) -> {
145
- if (!"apiKey".equalsIgnoreCase(key) && !"api_key".equalsIgnoreCase(key) && value != null) {
146
- uriBuilder.queryParam(key, value);
147
- }
148
- });
149
- }
150
- try {
151
- RestClient.RequestHeadersSpec<?> spec = restClient.get()
152
- .uri(uriBuilder.build().toUri())
153
- .accept(MediaType.parseMediaType("${r.mimeType || "application/json"}"));
154
- if (resolvedKey != null && !resolvedKey.isBlank()) {
155
- spec.header("Authorization", "Bearer " + resolvedKey);
156
- }
157
- return spec.retrieve().body(String.class);
158
- } catch (Exception e) {
159
- return "Error loading resource: " + e.getMessage();
160
- }
161
- };
162
- }`;
163
- })
164
- .join("\n\n");
165
-
166
- const workflowBeans = workflows
167
- .map((w) => {
168
- const recordName = `${toPascalCase(w.name)}Request`;
169
- const paramKeys = w.parameters ? Object.keys(w.parameters) : [];
170
- const recordFields = paramKeys.map(
171
- (k) =>
172
- ` @JsonPropertyDescription(${JSON.stringify(w.parameters[k].description || "")})\n @JsonProperty(required = true)\n String ${toCamelCase(k)}`,
173
- );
174
- recordFields.push(
175
- ' @JsonPropertyDescription("Optional Bearer API key override for multi-tenant sessions")\n String apiKey',
176
- );
177
-
178
- toolRecords.push(` @JsonClassDescription(${JSON.stringify(w.description || "Composite workflow")})
179
- public record ${recordName}(
180
- ${recordFields.join(",\n")}
181
- ) {}`);
182
-
183
- const firstParam = paramKeys[0] || "id";
184
-
185
- return ` // Composite Workflow: ${w.name}
186
- @Bean
187
- @Description(${JSON.stringify(w.description || "Composite workflow")})
188
- public Function<${recordName}, String> ${toCamelCase(w.name)}() {
189
- return request -> {
190
- String resolvedKey = request.apiKey() != null && !request.apiKey().isBlank() ? request.apiKey() : this.apiKey;
191
- Map<String, Object> results = new java.util.LinkedHashMap<>();
192
- ObjectMapper mapper = new ObjectMapper();
193
- try {
194
- ${(w.steps || [])
195
- .map((s, idx) => {
196
- return ` // Step ${idx + 1}: ${s.call}
197
- UriComponentsBuilder stepUri_${idx} = UriComponentsBuilder.fromUriString(this.baseUrl);
198
- stepUri_${idx}.path("/api/" + UriUtils.encodePath(String.valueOf(request.${toCamelCase(firstParam)}()), StandardCharsets.UTF_8));
199
- RestClient.RequestHeadersSpec<?> stepSpec_${idx} = restClient.get().uri(stepUri_${idx}.build().toUri()).accept(MediaType.APPLICATION_JSON);
200
- if (resolvedKey != null && !resolvedKey.isBlank()) { stepSpec_${idx}.header("Authorization", "Bearer " + resolvedKey); }
201
- String stepBody_${idx} = stepSpec_${idx}.retrieve().body(String.class);
202
- try {
203
- results.put("${s.call}", mapper.readValue(stepBody_${idx}, Object.class));
204
- } catch (Exception parseEx) {
205
- results.put("${s.call}", stepBody_${idx});
206
- }`;
207
- })
208
- .join("\n")}
209
- return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(results);
210
- } catch (Exception e) {
211
- return "Workflow ${w.name} failed: " + e.getMessage();
212
- }
213
- };
214
- }`;
215
- })
216
- .join("\n\n");
217
-
218
- return `/*
219
- Auto-generated by RapiDoc Curated MCP Generator
220
- Spec: ${metadata.title} (v${metadata.version})
221
-
222
- [MULTI-TENANCY & AUTHENTICATION]
223
- - Local Stdio Mode: Runs locally (e.g. Claude Desktop, Cursor). Uses \`API_KEY\` from environment.
224
- - Multi-Tenant / Agent Mode: When running in a shared service, the calling AI agent can pass
225
- per-session credentials directly in the \`apiKey\` request field of any tool record.
226
-
227
- [TOKEN BUDGET DIAGNOSTIC]
228
- - Total Tools: ${budget.totalTools}
229
- - Total Resources: ${budget.totalResources}
230
- - Total Workflows: ${budget.totalWorkflows}
231
- - Estimated Prompt Context Overhead: ~${budget.estimatedTokens.toLocaleString()} tokens/turn
232
- ${budget.warnings.map((w) => `- ${w}`).join("\n")}
233
-
234
- [HOW TO RUN]
235
- 1. export API_KEY="your-api-key"
236
- 2. ./mvnw spring-boot:run
237
- */
238
-
239
- package com.rapidoc.mcp;
240
-
241
- import org.springframework.boot.SpringApplication;
242
- import org.springframework.boot.autoconfigure.SpringBootApplication;
243
- import org.springframework.context.annotation.Bean;
244
- import org.springframework.context.annotation.Description;
245
- import org.springframework.http.HttpMethod;
246
- import org.springframework.http.MediaType;
247
- import org.springframework.web.client.RestClient;
248
- import org.springframework.web.util.UriComponentsBuilder;
249
- import org.springframework.web.util.UriUtils;
250
- import com.fasterxml.jackson.annotation.JsonProperty;
251
- import com.fasterxml.jackson.annotation.JsonPropertyDescription;
252
- import com.fasterxml.jackson.annotation.JsonClassDescription;
253
- ${hasWorkflows ? "import com.fasterxml.jackson.databind.ObjectMapper;\n" : ""}
254
- import java.nio.charset.StandardCharsets;
255
- import java.util.Map;
256
- import java.util.function.Function;
257
-
258
- @SpringBootApplication
259
- public class ${className} {
260
-
261
- private final String baseUrl = System.getenv().getOrDefault("API_BASE_URL", "${defaultBaseUrl}");
262
- private final String apiKey = System.getenv().getOrDefault("API_KEY", "");
263
- private final RestClient restClient = RestClient.builder().build();
264
-
265
- public static void main(String[] args) {
266
- SpringApplication.run(${className}.class, args);
267
- }
268
-
269
- // ==========================================
270
- // TOOL REQUEST RECORDS (Strongly Typed for Spring AI MCP)
271
- // ==========================================
272
- ${toolRecords.join("\n\n")}
273
-
274
- // ==========================================
275
- // MCP TOOLS (@Bean Function Callbacks)
276
- // ==========================================
277
- ${toolBeans}
278
- ${resourceBeans ? "\n // ==========================================\n // MCP RESOURCES\n // ==========================================\n" + resourceBeans : ""}
279
- ${workflowBeans ? "\n // ==========================================\n // COMPOSITE WORKFLOWS\n // ==========================================\n" + workflowBeans : ""}
280
- }
281
- `;
282
- }
283
-
284
- export function generateJavaFiles(serverCode, metadata) {
285
- const pomXml = `<?xml version="1.0" encoding="UTF-8"?>
286
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
287
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
288
- <modelVersion>4.0.0</modelVersion>
289
- <parent>
290
- <groupId>org.springframework.boot</groupId>
291
- <artifactId>spring-boot-starter-parent</artifactId>
292
- <version>3.3.0</version>
293
- <relativePath/>
294
- </parent>
295
- <groupId>com.rapidoc</groupId>
296
- <artifactId>mcp-server</artifactId>
297
- <version>1.0.0</version>
298
- <dependencies>
299
- <dependency>
300
- <groupId>org.springframework.boot</groupId>
301
- <artifactId>spring-boot-starter</artifactId>
302
- </dependency>
303
- <dependency>
304
- <groupId>org.springframework.boot</groupId>
305
- <artifactId>spring-boot-starter-web</artifactId>
306
- </dependency>
307
- </dependencies>
308
- </project>\n`;
309
-
310
- const readme = `# ${metadata.title} - MCP Server (Java Spring AI)
311
-
312
- Auto-generated by RapiDoc Curated MCP Generator.
313
-
314
- ### Prerequisites
315
- - Java 17+
316
- - Maven 3.8+
317
-
318
- ### Building & Running
319
- \`\`\`bash
320
- export API_KEY="your-api-key"
321
- ./mvnw spring-boot:run
322
- \`\`\`
323
- `;
324
-
325
- return {
326
- "src/main/java/com/rapidoc/mcp/RapiDocMcpServer.java": serverCode,
327
- "pom.xml": pomXml,
328
- "README.md": readme,
329
- };
330
- }