@lovrozagar/honey 0.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.
- package/README.md +51 -0
- package/docs/api-ref/go.md +399 -0
- package/docs/api-ref/python.md +399 -0
- package/docs/api-ref/rust.md +399 -0
- package/docs/api-ref/typescript.md +399 -0
- package/docs/invalidate-spec.md +303 -0
- package/docs/sdk.md +722 -0
- package/package.json +120 -0
- package/src/accepts.ts +78 -0
- package/src/body-limit.ts +100 -0
- package/src/build/index.ts +174 -0
- package/src/cli-go/auth.go +15 -0
- package/src/cli-go/config.go +165 -0
- package/src/cli-go/errors.go +57 -0
- package/src/cli-go/multipart.go +55 -0
- package/src/cli-go/output.go +109 -0
- package/src/cli-go/root.go +30 -0
- package/src/cli-go/stream.go +88 -0
- package/src/cli-go/version.go +5 -0
- package/src/cli.ts +204 -0
- package/src/client/error.ts +27 -0
- package/src/client/http.ts +502 -0
- package/src/client/index.ts +127 -0
- package/src/client/sdk.ts +265 -0
- package/src/client/sse.ts +102 -0
- package/src/client/types.ts +211 -0
- package/src/client/ws.ts +115 -0
- package/src/client-go/errors.go +207 -0
- package/src/client-go/invalidation.go +373 -0
- package/src/client-go/realtime.go +317 -0
- package/src/client-go/result.go +20 -0
- package/src/client-go/runtime.go +413 -0
- package/src/client-go/sse.go +149 -0
- package/src/client-go/transport.go +452 -0
- package/src/client-go/ws.go +104 -0
- package/src/client-mcp/README.md +10 -0
- package/src/client-mcp/server.ts +94 -0
- package/src/client-mcp/types.ts +19 -0
- package/src/client-python/_errors.py +115 -0
- package/src/client-python/_invalidation.py +578 -0
- package/src/client-python/_realtime.py +156 -0
- package/src/client-python/_runtime.py +469 -0
- package/src/client-python/_sse.py +99 -0
- package/src/client-python/_transport.py +335 -0
- package/src/client-python/_ws.py +131 -0
- package/src/client-python/py.typed +0 -0
- package/src/client-rust/errors.rs +200 -0
- package/src/client-rust/invalidation.rs +441 -0
- package/src/client-rust/invalidation_sync.rs +306 -0
- package/src/client-rust/realtime.rs +695 -0
- package/src/client-rust/result.rs +22 -0
- package/src/client-rust/runtime.rs +547 -0
- package/src/client-rust/runtime_sync.rs +452 -0
- package/src/client-rust/sse.rs +167 -0
- package/src/client-rust/ws.rs +143 -0
- package/src/cloudflare/realtime.ts +236 -0
- package/src/codegen-go-cli.ts +1180 -0
- package/src/codegen-go.ts +1010 -0
- package/src/codegen-hash.ts +8 -0
- package/src/codegen-ir.ts +517 -0
- package/src/codegen-loaders.ts +53 -0
- package/src/codegen-mcp.ts +364 -0
- package/src/codegen-python.ts +1028 -0
- package/src/codegen-rust.ts +1652 -0
- package/src/codegen-sanitize.ts +67 -0
- package/src/codegen-schema-naming.ts +146 -0
- package/src/codegen.ts +5015 -0
- package/src/context.ts +210 -0
- package/src/cookie-sign.ts +58 -0
- package/src/cookie.ts +89 -0
- package/src/cors.ts +96 -0
- package/src/crypto.ts +19 -0
- package/src/csrf.ts +54 -0
- package/src/curl-logger.ts +235 -0
- package/src/detect-runtime.ts +19 -0
- package/src/error.ts +52 -0
- package/src/errors.ts +87 -0
- package/src/etag.ts +74 -0
- package/src/feature-load.ts +23 -0
- package/src/go-type-emitter.ts +404 -0
- package/src/honey-response.ts +354 -0
- package/src/i18n-slot.ts +20 -0
- package/src/i18n.ts +234 -0
- package/src/index.ts +3843 -0
- package/src/init.ts +149 -0
- package/src/input.ts +8 -0
- package/src/ip-restrict.ts +192 -0
- package/src/logger.ts +149 -0
- package/src/middleware.ts +199 -0
- package/src/node-request.ts +240 -0
- package/src/node.ts +212 -0
- package/src/openapi/register.ts +19 -0
- package/src/openapi/scalar.ts +33 -0
- package/src/openapi/spec-factory.ts +46 -0
- package/src/openapi/spec.ts +31 -0
- package/src/openapi/swagger.ts +34 -0
- package/src/plugin.ts +771 -0
- package/src/powered-by.ts +17 -0
- package/src/pretty-json.ts +39 -0
- package/src/proxy.ts +155 -0
- package/src/python-type-emitter.ts +144 -0
- package/src/realtime/buffer.ts +101 -0
- package/src/realtime/bus.ts +93 -0
- package/src/realtime/longpoll.ts +148 -0
- package/src/realtime/protocol.ts +128 -0
- package/src/realtime/route.ts +104 -0
- package/src/realtime/runtime.ts +430 -0
- package/src/request-id.ts +23 -0
- package/src/request-to-curl.ts +34 -0
- package/src/response.ts +414 -0
- package/src/rust-type-emitter.ts +412 -0
- package/src/secure-headers.ts +69 -0
- package/src/serve-register.ts +8 -0
- package/src/serve-slot.ts +20 -0
- package/src/serve.ts +141 -0
- package/src/server-timing.ts +66 -0
- package/src/static.ts +60 -0
- package/src/telemetry/otel.ts +139 -0
- package/src/testing.ts +114 -0
- package/src/timeout.ts +36 -0
- package/src/tree.ts +464 -0
- package/src/ts-type-emitter.ts +106 -0
- package/src/type-emitter.ts +524 -0
- package/src/type-extractor.ts +749 -0
- package/src/types.ts +638 -0
- package/src/validation.ts +300 -0
- package/src/ws/bun.ts +89 -0
- package/src/ws/cloudflare.ts +133 -0
- package/src/ws/deno.ts +109 -0
- package/src/ws/node.ts +161 -0
- package/src/yaml.ts +80 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/* MCP server code generator. Spec-driven (not IR-driven) — OpenAPI parameters[]
|
|
2
|
+
* + requestBody.content['application/json'].schema are already JSON Schema,
|
|
3
|
+
* no translation layer needed. Mirrors codegen-python.ts structure. */
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "node:fs"
|
|
6
|
+
import type { OpenApiSpecInput } from "./codegen.ts"
|
|
7
|
+
import { toIR } from "./codegen-ir.ts"
|
|
8
|
+
|
|
9
|
+
type MCPOptions = {
|
|
10
|
+
projectName: string
|
|
11
|
+
sdkPackageName: string
|
|
12
|
+
sdkClassName: string
|
|
13
|
+
version?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type MCPResult = {
|
|
17
|
+
files: Record<string, string>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type MCPOp = {
|
|
21
|
+
operationId: string
|
|
22
|
+
method: string
|
|
23
|
+
path: string
|
|
24
|
+
summary?: string
|
|
25
|
+
description?: string
|
|
26
|
+
parameters: Array<Record<string, unknown>>
|
|
27
|
+
bodySchema: Record<string, unknown> | undefined
|
|
28
|
+
bodyRequired: boolean
|
|
29
|
+
toolName: string
|
|
30
|
+
pathSegments: string[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type SpecRecord = Record<string, unknown>
|
|
34
|
+
|
|
35
|
+
const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"] as const
|
|
36
|
+
|
|
37
|
+
const TEMPLATE_FILE_NAMES = ["server.ts", "types.ts"] as const
|
|
38
|
+
const TEMPLATE_CACHE = new Map<string, string>()
|
|
39
|
+
|
|
40
|
+
function loadMCPRuntimeTemplates(): Map<string, string> {
|
|
41
|
+
if (TEMPLATE_CACHE.size > 0) return TEMPLATE_CACHE
|
|
42
|
+
for (const name of TEMPLATE_FILE_NAMES) {
|
|
43
|
+
const url = new URL(`./client-mcp/${name}`, import.meta.url)
|
|
44
|
+
TEMPLATE_CACHE.set(name, readFileSync(url, "utf8"))
|
|
45
|
+
}
|
|
46
|
+
return TEMPLATE_CACHE
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function envVarNames(projectName: string): { apiKey: string; baseUrl: string } {
|
|
50
|
+
const upper = projectName.toUpperCase()
|
|
51
|
+
return { apiKey: `${upper}_API_KEY`, baseUrl: `${upper}_BASE_URL` }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasSSEResponse(op: SpecRecord): boolean {
|
|
55
|
+
const responses = op.responses as Record<string, SpecRecord> | undefined
|
|
56
|
+
if (!responses) return false
|
|
57
|
+
for (const resp of Object.values(responses)) {
|
|
58
|
+
const content = resp.content as SpecRecord | undefined
|
|
59
|
+
if (content && "text/event-stream" in content) return true
|
|
60
|
+
}
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toSnakeCase(id: string): string {
|
|
65
|
+
/* camelCase + dots -> snake_case. "createUser" -> "create_user",
|
|
66
|
+
* "docs.extract" -> "docs_extract", "docs.listAll" -> "docs_list_all". */
|
|
67
|
+
return id
|
|
68
|
+
.replace(/\./g, "_")
|
|
69
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
70
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1_$2")
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveSchema(
|
|
75
|
+
spec: SpecRecord,
|
|
76
|
+
schema: SpecRecord | undefined,
|
|
77
|
+
): SpecRecord | undefined {
|
|
78
|
+
if (!schema) return undefined
|
|
79
|
+
if (!schema.$ref) return schema
|
|
80
|
+
const ref = schema.$ref as string
|
|
81
|
+
const parts = ref.replace(/^#\//, "").split("/")
|
|
82
|
+
let cur: unknown = spec
|
|
83
|
+
for (const part of parts) {
|
|
84
|
+
if (cur === null || typeof cur !== "object") return undefined
|
|
85
|
+
cur = (cur as SpecRecord)[part]
|
|
86
|
+
}
|
|
87
|
+
if (cur === undefined) return undefined
|
|
88
|
+
/* recurse — resolved schema may itself be a ref */
|
|
89
|
+
return resolveSchema(spec, cur as SpecRecord)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function deepResolveSchema(
|
|
93
|
+
spec: SpecRecord,
|
|
94
|
+
schema: SpecRecord | undefined,
|
|
95
|
+
seen: Set<string> = new Set(),
|
|
96
|
+
): SpecRecord | undefined {
|
|
97
|
+
if (!schema) return undefined
|
|
98
|
+
if (schema.$ref) {
|
|
99
|
+
const ref = schema.$ref as string
|
|
100
|
+
if (seen.has(ref)) return { type: "object" }
|
|
101
|
+
const next = new Set(seen)
|
|
102
|
+
next.add(ref)
|
|
103
|
+
return deepResolveSchema(spec, resolveSchema(spec, schema), next)
|
|
104
|
+
}
|
|
105
|
+
const out: SpecRecord = {}
|
|
106
|
+
for (const [k, v] of Object.entries(schema)) {
|
|
107
|
+
if (v === null || v === undefined) {
|
|
108
|
+
out[k] = v
|
|
109
|
+
} else if (Array.isArray(v)) {
|
|
110
|
+
out[k] = v.map((item) =>
|
|
111
|
+
item !== null && typeof item === "object"
|
|
112
|
+
? deepResolveSchema(spec, item as SpecRecord, seen)
|
|
113
|
+
: item,
|
|
114
|
+
)
|
|
115
|
+
} else if (typeof v === "object") {
|
|
116
|
+
out[k] = deepResolveSchema(spec, v as SpecRecord, seen)
|
|
117
|
+
} else {
|
|
118
|
+
out[k] = v
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function extractJsonBodySchema(op: SpecRecord): SpecRecord | undefined {
|
|
125
|
+
const requestBody = op.requestBody as SpecRecord | undefined
|
|
126
|
+
const content = requestBody?.content as Record<string, SpecRecord> | undefined
|
|
127
|
+
return content?.["application/json"]?.schema as SpecRecord | undefined
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function collectMCPOps(spec: OpenApiSpecInput): MCPOp[] {
|
|
131
|
+
const paths = spec.paths as Record<string, SpecRecord> | undefined
|
|
132
|
+
if (!paths) return []
|
|
133
|
+
const ops: MCPOp[] = []
|
|
134
|
+
|
|
135
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
136
|
+
for (const method of HTTP_METHODS) {
|
|
137
|
+
const op = pathItem[method] as SpecRecord | undefined
|
|
138
|
+
if (!op) continue
|
|
139
|
+
if (op["x-mcp"] !== true) continue
|
|
140
|
+
/* skip websocket / realtime / SSE operations — MCP is request/response only */
|
|
141
|
+
if (op["x-websocket"] === true) continue
|
|
142
|
+
if (op["x-realtime"] === true) continue
|
|
143
|
+
if (hasSSEResponse(op)) continue
|
|
144
|
+
|
|
145
|
+
const operationId = op.operationId as string | undefined
|
|
146
|
+
if (!operationId) continue
|
|
147
|
+
|
|
148
|
+
ops.push({
|
|
149
|
+
bodyRequired: (op.requestBody as SpecRecord | undefined)?.required === true,
|
|
150
|
+
bodySchema: extractJsonBodySchema(op),
|
|
151
|
+
description: op.description as string | undefined,
|
|
152
|
+
method: method.toUpperCase(),
|
|
153
|
+
operationId,
|
|
154
|
+
parameters: (op.parameters as Array<SpecRecord> | undefined) ?? [],
|
|
155
|
+
path,
|
|
156
|
+
pathSegments: operationId.split("."),
|
|
157
|
+
summary: op.summary as string | undefined,
|
|
158
|
+
toolName: "",
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return ops
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
type ParamGroup = { props: SpecRecord; required: string[] }
|
|
167
|
+
|
|
168
|
+
function emptyParamGroup(): ParamGroup {
|
|
169
|
+
return { props: {}, required: [] }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function assignGroup(
|
|
173
|
+
properties: SpecRecord,
|
|
174
|
+
required: string[],
|
|
175
|
+
key: string,
|
|
176
|
+
group: ParamGroup,
|
|
177
|
+
): void {
|
|
178
|
+
if (Object.keys(group.props).length === 0) return
|
|
179
|
+
const out: SpecRecord = { properties: group.props, type: "object" }
|
|
180
|
+
if (group.required.length > 0) {
|
|
181
|
+
out.required = group.required
|
|
182
|
+
required.push(key)
|
|
183
|
+
}
|
|
184
|
+
properties[key] = out
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildToolInputSchema(
|
|
188
|
+
op: MCPOp,
|
|
189
|
+
spec: OpenApiSpecInput,
|
|
190
|
+
): SpecRecord {
|
|
191
|
+
const properties: SpecRecord = {}
|
|
192
|
+
const required: string[] = []
|
|
193
|
+
const specRec = spec as unknown as SpecRecord
|
|
194
|
+
|
|
195
|
+
const path = emptyParamGroup()
|
|
196
|
+
const query = emptyParamGroup()
|
|
197
|
+
const header = emptyParamGroup()
|
|
198
|
+
const groupByLoc: Record<string, ParamGroup> = { header, path, query }
|
|
199
|
+
|
|
200
|
+
for (const p of op.parameters) {
|
|
201
|
+
const name = p.name as string
|
|
202
|
+
const inLoc = p.in as string
|
|
203
|
+
const group = groupByLoc[inLoc]
|
|
204
|
+
if (!group) continue
|
|
205
|
+
group.props[name] = deepResolveSchema(specRec, (p.schema ?? {}) as SpecRecord) ?? {}
|
|
206
|
+
if (p.required === true) group.required.push(name)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
assignGroup(properties, required, "params", path)
|
|
210
|
+
assignGroup(properties, required, "search", query)
|
|
211
|
+
assignGroup(properties, required, "headers", header)
|
|
212
|
+
|
|
213
|
+
if (op.bodySchema) {
|
|
214
|
+
properties.json = deepResolveSchema(specRec, op.bodySchema) ?? { type: "object" }
|
|
215
|
+
if (op.bodyRequired) required.push("json")
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const schema: SpecRecord = { properties, type: "object" }
|
|
219
|
+
if (required.length > 0) schema.required = required
|
|
220
|
+
return schema
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function buildToolsGen(
|
|
224
|
+
ops: MCPOp[],
|
|
225
|
+
spec: OpenApiSpecInput,
|
|
226
|
+
projectName: string,
|
|
227
|
+
): string {
|
|
228
|
+
const l: string[] = []
|
|
229
|
+
l.push(`/* Generated by honey codegen-mcp.ts — do not edit by hand. */`)
|
|
230
|
+
l.push(``)
|
|
231
|
+
l.push(`import type { Tool } from "./types"`)
|
|
232
|
+
l.push(``)
|
|
233
|
+
l.push(`export function buildTools(sdk: Record<string, unknown>): Tool[] {`)
|
|
234
|
+
l.push(`\treturn [`)
|
|
235
|
+
|
|
236
|
+
for (const op of ops) {
|
|
237
|
+
const desc = op.description ?? op.summary ?? ""
|
|
238
|
+
const inputSchema = buildToolInputSchema(op, spec)
|
|
239
|
+
l.push(`\t\t{`)
|
|
240
|
+
l.push(`\t\t\tname: ${JSON.stringify(op.toolName)},`)
|
|
241
|
+
if (desc.length > 0) {
|
|
242
|
+
l.push(`\t\t\tdescription: ${JSON.stringify(desc)},`)
|
|
243
|
+
}
|
|
244
|
+
l.push(`\t\t\tinputSchema: ${JSON.stringify(inputSchema)},`)
|
|
245
|
+
/* handler dispatches by walking all path segments; pass args verbatim.
|
|
246
|
+
* arguments shape already matches TS SDK method signature: { params?, search?, headers?, json? }. */
|
|
247
|
+
const callExpr = op.pathSegments.reduce(
|
|
248
|
+
(acc, seg, i) =>
|
|
249
|
+
i === 0
|
|
250
|
+
? `(sdk as Record<string, unknown>)[${JSON.stringify(seg)}]`
|
|
251
|
+
: `(${acc} as Record<string, unknown>)[${JSON.stringify(seg)}]`,
|
|
252
|
+
"",
|
|
253
|
+
)
|
|
254
|
+
l.push(`\t\t\thandler: async (args) => {`)
|
|
255
|
+
l.push(`\t\t\t\tconst method = ${callExpr}`)
|
|
256
|
+
l.push(`\t\t\t\tif (typeof method !== "function") {`)
|
|
257
|
+
l.push(`\t\t\t\t\treturn { content: [{ type: "text", text: JSON.stringify({ message: "SDK missing method ${op.operationId}" }) }], isError: true }`)
|
|
258
|
+
l.push(`\t\t\t\t}`)
|
|
259
|
+
l.push(`\t\t\t\tconst result = await method.call(sdk, args)`)
|
|
260
|
+
l.push(`\t\t\t\treturn { content: [{ type: "text", text: JSON.stringify(result) }] }`)
|
|
261
|
+
l.push(`\t\t\t},`)
|
|
262
|
+
l.push(`\t\t},`)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
l.push(`\t]`)
|
|
266
|
+
l.push(`}`)
|
|
267
|
+
l.push(``)
|
|
268
|
+
/* project name embedded for tool-naming contract visibility — consumed by tests */
|
|
269
|
+
l.push(`/* project: ${projectName} */`)
|
|
270
|
+
l.push(``)
|
|
271
|
+
return l.join("\n")
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function buildServerEntry(
|
|
275
|
+
projectName: string,
|
|
276
|
+
sdkPackageName: string,
|
|
277
|
+
sdkClassName: string,
|
|
278
|
+
serverName: string,
|
|
279
|
+
version: string,
|
|
280
|
+
): string {
|
|
281
|
+
const { apiKey: envVar, baseUrl: baseUrlEnvVar } = envVarNames(projectName)
|
|
282
|
+
const defaultBaseUrl = `https://api.${projectName}.com`
|
|
283
|
+
const l: string[] = []
|
|
284
|
+
l.push(`/* Generated by honey codegen-mcp.ts — do not edit by hand. */`)
|
|
285
|
+
l.push(``)
|
|
286
|
+
l.push(`import { ${sdkClassName} } from ${JSON.stringify(sdkPackageName)}`)
|
|
287
|
+
l.push(`import { createMCPServer } from "./_runtime"`)
|
|
288
|
+
l.push(`import { buildTools } from "./tools.gen"`)
|
|
289
|
+
l.push(``)
|
|
290
|
+
l.push(`const apiKey = process.env[${JSON.stringify(envVar)}] ?? ""`)
|
|
291
|
+
l.push(`const baseURL = process.env[${JSON.stringify(baseUrlEnvVar)}] ?? ${JSON.stringify(defaultBaseUrl)}`)
|
|
292
|
+
l.push(``)
|
|
293
|
+
l.push(`const sdk = new ${sdkClassName}({`)
|
|
294
|
+
l.push(`\tbaseURL,`)
|
|
295
|
+
l.push(`\theaders: apiKey.length > 0 ? { Authorization: \`Bearer \${apiKey}\` } : {},`)
|
|
296
|
+
l.push(`\tthrowOnError: true,`)
|
|
297
|
+
l.push(`})`)
|
|
298
|
+
l.push(``)
|
|
299
|
+
l.push(`const tools = buildTools(sdk as unknown as Record<string, unknown>)`)
|
|
300
|
+
l.push(``)
|
|
301
|
+
l.push(`await createMCPServer({`)
|
|
302
|
+
l.push(`\tsdk,`)
|
|
303
|
+
l.push(`\tserverInfo: { name: ${JSON.stringify(serverName)}, version: ${JSON.stringify(version)} },`)
|
|
304
|
+
l.push(`\ttools,`)
|
|
305
|
+
l.push(`})`)
|
|
306
|
+
l.push(``)
|
|
307
|
+
return l.join("\n")
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function buildTsconfig(): string {
|
|
311
|
+
return JSON.stringify(
|
|
312
|
+
{
|
|
313
|
+
compilerOptions: {
|
|
314
|
+
allowImportingTsExtensions: false,
|
|
315
|
+
declaration: true,
|
|
316
|
+
esModuleInterop: true,
|
|
317
|
+
module: "ESNext",
|
|
318
|
+
moduleResolution: "bundler",
|
|
319
|
+
noEmit: false,
|
|
320
|
+
outDir: "./dist",
|
|
321
|
+
rootDir: "./src",
|
|
322
|
+
skipLibCheck: true,
|
|
323
|
+
strict: true,
|
|
324
|
+
target: "ES2022",
|
|
325
|
+
types: ["node"],
|
|
326
|
+
},
|
|
327
|
+
include: ["src/**/*"],
|
|
328
|
+
},
|
|
329
|
+
null,
|
|
330
|
+
2,
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function generateMCPServer(
|
|
335
|
+
spec: OpenApiSpecInput,
|
|
336
|
+
options: MCPOptions,
|
|
337
|
+
): MCPResult {
|
|
338
|
+
toIR(spec)
|
|
339
|
+
const { projectName, sdkClassName, sdkPackageName } = options
|
|
340
|
+
const version = options.version ?? "0.1.0"
|
|
341
|
+
|
|
342
|
+
const ops = collectMCPOps(spec)
|
|
343
|
+
if (ops.length === 0) {
|
|
344
|
+
throw new Error("No operations marked x-mcp: true; nothing to emit")
|
|
345
|
+
}
|
|
346
|
+
for (const op of ops) {
|
|
347
|
+
op.toolName = `${projectName}_${toSnakeCase(op.operationId)}`
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const info = (spec as { info?: { title?: string } }).info ?? {}
|
|
351
|
+
const serverName = info.title ?? `@${projectName}/mcp-server`
|
|
352
|
+
|
|
353
|
+
const templates = loadMCPRuntimeTemplates()
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
files: {
|
|
357
|
+
"src/_runtime.ts": templates.get("server.ts") ?? "",
|
|
358
|
+
"src/server.ts": buildServerEntry(projectName, sdkPackageName, sdkClassName, serverName, version),
|
|
359
|
+
"src/tools.gen.ts": buildToolsGen(ops, spec, projectName),
|
|
360
|
+
"src/types.ts": templates.get("types.ts") ?? "",
|
|
361
|
+
"tsconfig.json": buildTsconfig(),
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
}
|