@ubuligan/codegen 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jsznpm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,209 @@
1
+ // src/emit.ts
2
+ import { compile } from "json-schema-to-typescript";
3
+
4
+ // src/naming.ts
5
+ function toCamelCase(raw) {
6
+ const parts = raw.split(/[^a-zA-Z0-9]+/).filter(Boolean);
7
+ if (parts.length === 0) return "_";
8
+ const [first, ...rest] = parts;
9
+ const head = /^[0-9]/.test(first) ? `_${first}` : first;
10
+ return head.charAt(0).toLowerCase() + head.slice(1) + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
11
+ }
12
+ function toPascalCase(raw) {
13
+ const camel = toCamelCase(raw);
14
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
15
+ }
16
+ function dedupe(names) {
17
+ const seen = /* @__PURE__ */ new Map();
18
+ return names.map((name) => {
19
+ const count = seen.get(name) ?? 0;
20
+ seen.set(name, count + 1);
21
+ return count === 0 ? name : `${name}${count + 1}`;
22
+ });
23
+ }
24
+
25
+ // src/emit.ts
26
+ async function emitSdk(intro, opts = {}) {
27
+ const className = opts.className ?? "GeneratedMCPClient";
28
+ const methods = dedupe(intro.tools.map((t) => toCamelCase(t.name)));
29
+ const prepared = [];
30
+ const typeBlocks = [];
31
+ for (let i = 0; i < intro.tools.length; i++) {
32
+ const tool = intro.tools[i];
33
+ const method = methods[i];
34
+ const inputType = `${toPascalCase(tool.name)}Input`;
35
+ const props = tool.inputSchema?.properties ?? {};
36
+ const hasInput = Object.keys(props).length > 0;
37
+ if (hasInput) {
38
+ const ts = await compileSchema(tool.inputSchema, inputType);
39
+ typeBlocks.push(ts);
40
+ }
41
+ prepared.push({ tool, method, inputType, hasInput });
42
+ }
43
+ return [
44
+ header(intro),
45
+ `import { MCPClient, type MCPClientOptions } from "@ubuligan/client";`,
46
+ "",
47
+ typeBlocks.join("\n").trim(),
48
+ "",
49
+ renderClass(className, intro, prepared),
50
+ "",
51
+ renderFactory(className)
52
+ ].filter((s) => s !== "").join("\n");
53
+ }
54
+ async function compileSchema(schema, name) {
55
+ const out = await compile(schema, name, {
56
+ bannerComment: "",
57
+ additionalProperties: false,
58
+ style: { singleQuote: false }
59
+ });
60
+ return out.trim();
61
+ }
62
+ function header(intro) {
63
+ return [
64
+ "/* eslint-disable */",
65
+ "// ---------------------------------------------------------------------------",
66
+ `// Auto-generated by @ubuligan/codegen from "${intro.serverName}@${intro.serverVersion}".`,
67
+ "// Do not edit by hand \xE2\u20AC\u201D re-run `mcp-codegen` to regenerate.",
68
+ "// ---------------------------------------------------------------------------"
69
+ ].join("\n");
70
+ }
71
+ function renderClass(className, intro, tools) {
72
+ const toolMethods = tools.map((p) => renderToolMethod(p)).join("\n\n");
73
+ const resourceConsts = renderResourceConsts(intro);
74
+ const promptMethods = renderPromptMethods(intro);
75
+ return `export class ${className} extends MCPClient {${resourceConsts}
76
+
77
+ ${indent(toolMethods, 2)}${promptMethods ? "\n\n" + indent(promptMethods, 2) : ""}
78
+ }`;
79
+ }
80
+ function renderToolMethod(p) {
81
+ const doc = p.tool.description ? `/** ${escapeBlock(p.tool.description)} */
82
+ ` : "";
83
+ const arg = p.hasInput ? `input: ${p.inputType}` : "input?: Record<string, never>";
84
+ const pass = p.hasInput ? "input as unknown as Record<string, unknown>" : "{}";
85
+ return `${doc}async ${p.method}(${arg}) {
86
+ return this.callTool(${JSON.stringify(p.tool.name)}, ${pass});
87
+ }`;
88
+ }
89
+ function renderResourceConsts(intro) {
90
+ if (intro.resources.length === 0) return "";
91
+ const entries = intro.resources.map((r) => ` ${JSON.stringify(toCamelCase(r.name ?? r.uri))}: ${JSON.stringify(r.uri)},`).join("\n");
92
+ return `
93
+ /** Known resource URIs advertised by the server. */
94
+ static readonly resources = {
95
+ ${entries}
96
+ } as const;
97
+ `;
98
+ }
99
+ function renderPromptMethods(intro) {
100
+ if (intro.prompts.length === 0) return "";
101
+ return intro.prompts.map((prompt) => {
102
+ const method = toCamelCase(`get-${prompt.name}-prompt`);
103
+ const doc = prompt.description ? `/** ${escapeBlock(prompt.description)} */
104
+ ` : "";
105
+ const argType = prompt.arguments && prompt.arguments.length > 0 ? "{ " + prompt.arguments.map((a) => `${JSON.stringify(a.name)}${a.required ? "" : "?"}: string`).join("; ") + " }" : "Record<string, string>";
106
+ return `${doc}async ${method}(args?: ${argType}) {
107
+ return this.getPrompt(${JSON.stringify(prompt.name)}, args ?? {});
108
+ }`;
109
+ }).join("\n\n");
110
+ }
111
+ function renderFactory(className) {
112
+ return `/** Create and connect a typed ${className}. */
113
+ export async function create(options: MCPClientOptions): Promise<${className}> {
114
+ const client = new ${className}(options);
115
+ await client.connect();
116
+ return client;
117
+ }`;
118
+ }
119
+ function indent(text, spaces) {
120
+ const pad = " ".repeat(spaces);
121
+ return text.split("\n").map((line) => line ? pad + line : line).join("\n");
122
+ }
123
+ function escapeBlock(text) {
124
+ return text.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
125
+ }
126
+
127
+ // src/introspect.ts
128
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
129
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
130
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
131
+ function parseTarget(raw) {
132
+ if (/^https?:\/\//i.test(raw)) {
133
+ return { type: "http", url: raw };
134
+ }
135
+ const parts = raw.trim().split(/\s+/);
136
+ const [command, ...args] = parts;
137
+ if (!command) throw new Error("Empty target");
138
+ return { type: "stdio", command, args };
139
+ }
140
+ function buildTransport(target) {
141
+ if (target.type === "http") {
142
+ return new StreamableHTTPClientTransport(new URL(target.url), {
143
+ requestInit: target.headers ? { headers: target.headers } : void 0
144
+ });
145
+ }
146
+ return new StdioClientTransport({
147
+ command: target.command,
148
+ args: target.args,
149
+ env: process.env
150
+ });
151
+ }
152
+ async function introspect(target) {
153
+ const client = new Client({ name: "mcp-codegen", version: "0.1.0" });
154
+ await client.connect(buildTransport(target));
155
+ try {
156
+ const version = client.getServerVersion();
157
+ const tools = await safeList(() => client.listTools(), "tools");
158
+ const resources = await safeList(() => client.listResources(), "resources");
159
+ const prompts = await safeList(() => client.listPrompts(), "prompts");
160
+ return {
161
+ serverName: version?.name ?? "mcp-server",
162
+ serverVersion: version?.version ?? "0.0.0",
163
+ tools: (tools?.tools ?? []).map((t) => ({
164
+ name: t.name,
165
+ description: t.description,
166
+ inputSchema: t.inputSchema ?? { type: "object" },
167
+ outputSchema: t.outputSchema
168
+ })),
169
+ resources: (resources?.resources ?? []).map((r) => ({
170
+ uri: r.uri,
171
+ name: r.name,
172
+ description: r.description,
173
+ mimeType: r.mimeType
174
+ })),
175
+ prompts: (prompts?.prompts ?? []).map((p) => ({
176
+ name: p.name,
177
+ description: p.description,
178
+ arguments: p.arguments
179
+ }))
180
+ };
181
+ } finally {
182
+ await client.close();
183
+ }
184
+ }
185
+ async function safeList(fn, label) {
186
+ try {
187
+ return await fn();
188
+ } catch (error) {
189
+ const msg = error instanceof Error ? error.message : String(error);
190
+ if (/method not found|-32601/i.test(msg)) return void 0;
191
+ throw new Error(`Failed to list ${label}: ${msg}`);
192
+ }
193
+ }
194
+
195
+ // src/index.ts
196
+ async function generateSdk(options) {
197
+ const target = typeof options.target === "string" ? parseTarget(options.target) : options.target;
198
+ const introspection = await introspect(target);
199
+ const code = await emitSdk(introspection, options);
200
+ return { code, introspection };
201
+ }
202
+
203
+ export {
204
+ emitSdk,
205
+ parseTarget,
206
+ introspect,
207
+ generateSdk
208
+ };
209
+ //# sourceMappingURL=chunk-5WSAJTXT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/emit.ts","../src/naming.ts","../src/introspect.ts","../src/index.ts"],"sourcesContent":["import { compile } from \"json-schema-to-typescript\";\r\nimport type { JSONSchema7 } from \"json-schema\";\r\nimport type { Introspection, ToolInfo } from \"./introspect.js\";\r\nimport { dedupe, toCamelCase, toPascalCase } from \"./naming.js\";\r\n\r\nexport interface EmitOptions {\r\n /** Class name for the generated SDK. Default \"GeneratedMCPClient\". */\r\n className?: string;\r\n}\r\n\r\ninterface PreparedTool {\r\n tool: ToolInfo;\r\n method: string;\r\n inputType: string;\r\n hasInput: boolean;\r\n}\r\n\r\n/**\r\n * Generate a self-contained, type-safe TypeScript SDK from an introspected\r\n * server. The output wraps `@ubuligan/client` and exposes one typed method\r\n * per tool, plus typed resource/prompt helpers.\r\n */\r\nexport async function emitSdk(intro: Introspection, opts: EmitOptions = {}): Promise<string> {\r\n const className = opts.className ?? \"GeneratedMCPClient\";\r\n\r\n const methods = dedupe(intro.tools.map((t) => toCamelCase(t.name)));\r\n const prepared: PreparedTool[] = [];\r\n const typeBlocks: string[] = [];\r\n\r\n for (let i = 0; i < intro.tools.length; i++) {\r\n const tool = intro.tools[i]!;\r\n const method = methods[i]!;\r\n const inputType = `${toPascalCase(tool.name)}Input`;\r\n const props = (tool.inputSchema?.properties ?? {}) as Record<string, unknown>;\r\n const hasInput = Object.keys(props).length > 0;\r\n\r\n if (hasInput) {\r\n const ts = await compileSchema(tool.inputSchema, inputType);\r\n typeBlocks.push(ts);\r\n }\r\n prepared.push({ tool, method, inputType, hasInput });\r\n }\r\n\r\n return [\r\n header(intro),\r\n `import { MCPClient, type MCPClientOptions } from \"@ubuligan/client\";`,\r\n \"\",\r\n typeBlocks.join(\"\\n\").trim(),\r\n \"\",\r\n renderClass(className, intro, prepared),\r\n \"\",\r\n renderFactory(className),\r\n ]\r\n .filter((s) => s !== \"\")\r\n .join(\"\\n\");\r\n}\r\n\r\nasync function compileSchema(schema: JSONSchema7, name: string): Promise<string> {\r\n const out = await compile(schema as Parameters<typeof compile>[0], name, {\r\n bannerComment: \"\",\r\n additionalProperties: false,\r\n style: { singleQuote: false },\r\n });\r\n return out.trim();\r\n}\r\n\r\nfunction header(intro: Introspection): string {\r\n return [\r\n \"/* eslint-disable */\",\r\n \"// ---------------------------------------------------------------------------\",\r\n `// Auto-generated by @ubuligan/codegen from \"${intro.serverName}@${intro.serverVersion}\".`,\r\n \"// Do not edit by hand — re-run `mcp-codegen` to regenerate.\",\r\n \"// ---------------------------------------------------------------------------\",\r\n ].join(\"\\n\");\r\n}\r\n\r\nfunction renderClass(className: string, intro: Introspection, tools: PreparedTool[]): string {\r\n const toolMethods = tools.map((p) => renderToolMethod(p)).join(\"\\n\\n\");\r\n const resourceConsts = renderResourceConsts(intro);\r\n const promptMethods = renderPromptMethods(intro);\r\n\r\n return `export class ${className} extends MCPClient {${resourceConsts}\r\n\r\n${indent(toolMethods, 2)}${promptMethods ? \"\\n\\n\" + indent(promptMethods, 2) : \"\"}\r\n}`;\r\n}\r\n\r\nfunction renderToolMethod(p: PreparedTool): string {\r\n const doc = p.tool.description ? `/** ${escapeBlock(p.tool.description)} */\\n` : \"\";\r\n const arg = p.hasInput ? `input: ${p.inputType}` : \"input?: Record<string, never>\";\r\n const pass = p.hasInput ? \"input as unknown as Record<string, unknown>\" : \"{}\";\r\n return `${doc}async ${p.method}(${arg}) {\r\n return this.callTool(${JSON.stringify(p.tool.name)}, ${pass});\r\n}`;\r\n}\r\n\r\nfunction renderResourceConsts(intro: Introspection): string {\r\n if (intro.resources.length === 0) return \"\";\r\n const entries = intro.resources\r\n .map((r) => ` ${JSON.stringify(toCamelCase(r.name ?? r.uri))}: ${JSON.stringify(r.uri)},`)\r\n .join(\"\\n\");\r\n return `\\n /** Known resource URIs advertised by the server. */\\n static readonly resources = {\\n${entries}\\n } as const;\\n`;\r\n}\r\n\r\nfunction renderPromptMethods(intro: Introspection): string {\r\n if (intro.prompts.length === 0) return \"\";\r\n return intro.prompts\r\n .map((prompt) => {\r\n const method = toCamelCase(`get-${prompt.name}-prompt`);\r\n const doc = prompt.description ? `/** ${escapeBlock(prompt.description)} */\\n` : \"\";\r\n const argType =\r\n prompt.arguments && prompt.arguments.length > 0\r\n ? \"{ \" +\r\n prompt.arguments\r\n .map((a) => `${JSON.stringify(a.name)}${a.required ? \"\" : \"?\"}: string`)\r\n .join(\"; \") +\r\n \" }\"\r\n : \"Record<string, string>\";\r\n return `${doc}async ${method}(args?: ${argType}) {\r\n return this.getPrompt(${JSON.stringify(prompt.name)}, args ?? {});\r\n}`;\r\n })\r\n .join(\"\\n\\n\");\r\n}\r\n\r\nfunction renderFactory(className: string): string {\r\n return `/** Create and connect a typed ${className}. */\r\nexport async function create(options: MCPClientOptions): Promise<${className}> {\r\n const client = new ${className}(options);\r\n await client.connect();\r\n return client;\r\n}`;\r\n}\r\n\r\nfunction indent(text: string, spaces: number): string {\r\n const pad = \" \".repeat(spaces);\r\n return text\r\n .split(\"\\n\")\r\n .map((line) => (line ? pad + line : line))\r\n .join(\"\\n\");\r\n}\r\n\r\nfunction escapeBlock(text: string): string {\r\n return text.replace(/\\*\\//g, \"*\\\\/\").replace(/\\r?\\n/g, \" \");\r\n}\r\n","/** Convert an arbitrary identifier into a safe camelCase method name. */\nexport function toCamelCase(raw: string): string {\n const parts = raw.split(/[^a-zA-Z0-9]+/).filter(Boolean);\n if (parts.length === 0) return \"_\";\n const [first, ...rest] = parts;\n const head = /^[0-9]/.test(first!) ? `_${first}` : first!;\n return (\n head.charAt(0).toLowerCase() +\n head.slice(1) +\n rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(\"\")\n );\n}\n\n/** Convert an arbitrary identifier into a safe PascalCase type name. */\nexport function toPascalCase(raw: string): string {\n const camel = toCamelCase(raw);\n return camel.charAt(0).toUpperCase() + camel.slice(1);\n}\n\n/** Ensure generated method names don't collide after normalization. */\nexport function dedupe(names: string[]): string[] {\n const seen = new Map<string, number>();\n return names.map((name) => {\n const count = seen.get(name) ?? 0;\n seen.set(name, count + 1);\n return count === 0 ? name : `${name}${count + 1}`;\n });\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { JSONSchema7 } from \"json-schema\";\n\nexport interface ToolInfo {\n name: string;\n description?: string;\n inputSchema: JSONSchema7;\n outputSchema?: JSONSchema7;\n}\n\nexport interface ResourceInfo {\n uri: string;\n name?: string;\n description?: string;\n mimeType?: string;\n}\n\nexport interface PromptInfo {\n name: string;\n description?: string;\n arguments?: { name: string; description?: string; required?: boolean }[];\n}\n\nexport interface Introspection {\n serverName: string;\n serverVersion: string;\n tools: ToolInfo[];\n resources: ResourceInfo[];\n prompts: PromptInfo[];\n}\n\n/** How to reach the server being introspected. */\nexport type Target =\n | { type: \"http\"; url: string; headers?: Record<string, string> }\n | { type: \"stdio\"; command: string; args?: string[] };\n\n/**\n * Parse a CLI target string. An http(s):// value becomes an HTTP target;\n * anything else is treated as a stdio command line (first token = command).\n */\nexport function parseTarget(raw: string): Target {\n if (/^https?:\\/\\//i.test(raw)) {\n return { type: \"http\", url: raw };\n }\n const parts = raw.trim().split(/\\s+/);\n const [command, ...args] = parts;\n if (!command) throw new Error(\"Empty target\");\n return { type: \"stdio\", command, args };\n}\n\nfunction buildTransport(target: Target): Transport {\n if (target.type === \"http\") {\n return new StreamableHTTPClientTransport(new URL(target.url), {\n requestInit: target.headers ? { headers: target.headers } : undefined,\n });\n }\n return new StdioClientTransport({\n command: target.command,\n args: target.args,\n env: process.env as Record<string, string>,\n });\n}\n\n/** Connect to a server and collect everything needed for codegen. */\nexport async function introspect(target: Target): Promise<Introspection> {\n const client = new Client({ name: \"mcp-codegen\", version: \"0.1.0\" });\n await client.connect(buildTransport(target));\n\n try {\n const version = client.getServerVersion();\n const tools = await safeList(() => client.listTools(), \"tools\");\n const resources = await safeList(() => client.listResources(), \"resources\");\n const prompts = await safeList(() => client.listPrompts(), \"prompts\");\n\n return {\n serverName: version?.name ?? \"mcp-server\",\n serverVersion: version?.version ?? \"0.0.0\",\n tools: (tools?.tools ?? []).map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: (t.inputSchema ?? { type: \"object\" }) as JSONSchema7,\n outputSchema: t.outputSchema as JSONSchema7 | undefined,\n })),\n resources: (resources?.resources ?? []).map((r) => ({\n uri: r.uri,\n name: r.name,\n description: r.description,\n mimeType: r.mimeType,\n })),\n prompts: (prompts?.prompts ?? []).map((p) => ({\n name: p.name,\n description: p.description,\n arguments: p.arguments,\n })),\n };\n } finally {\n await client.close();\n }\n}\n\n/**\n * Some servers don't implement every capability (e.g. no prompts). Treat a\n * \"Method not found\" as an empty list rather than a hard failure.\n */\nasync function safeList<T>(fn: () => Promise<T>, label: string): Promise<T | undefined> {\n try {\n return await fn();\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (/method not found|-32601/i.test(msg)) return undefined;\n throw new Error(`Failed to list ${label}: ${msg}`);\n }\n}\n","import { emitSdk, type EmitOptions } from \"./emit.js\";\nimport { introspect, parseTarget, type Introspection, type Target } from \"./introspect.js\";\n\nexport { introspect, parseTarget } from \"./introspect.js\";\nexport type { Introspection, Target, ToolInfo, ResourceInfo, PromptInfo } from \"./introspect.js\";\nexport { emitSdk } from \"./emit.js\";\nexport type { EmitOptions } from \"./emit.js\";\n\nexport interface GenerateOptions extends EmitOptions {\n /** Server target: an http(s) URL string or a stdio command line, or a parsed Target. */\n target: string | Target;\n}\n\n/**\n * High-level entry point: connect to a server, introspect it, and return the\n * generated SDK source as a string.\n */\nexport async function generateSdk(options: GenerateOptions): Promise<{ code: string; introspection: Introspection }> {\n const target = typeof options.target === \"string\" ? parseTarget(options.target) : options.target;\n const introspection = await introspect(target);\n const code = await emitSdk(introspection, options);\n return { code, introspection };\n}\n"],"mappings":"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","names":[]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generateSdk
4
+ } from "./chunk-5WSAJTXT.js";
5
+
6
+ // src/cli.ts
7
+ import { mkdir, writeFile } from "fs/promises";
8
+ import { dirname, resolve } from "path";
9
+ import { Command } from "commander";
10
+ import pc from "picocolors";
11
+ var program = new Command();
12
+ program.name("mcp-codegen").description("Generate a type-safe TypeScript SDK from a live MCP server").argument("<target>", 'Server URL (http://...) or stdio command (e.g. "node dist/index.js")').option("-o, --out <file>", "Output file path", "mcp-sdk.ts").option("-c, --class-name <name>", "Generated class name", "GeneratedMCPClient").option("--stdout", "Print to stdout instead of writing a file", false).action(async (target, opts) => {
13
+ try {
14
+ process.stderr.write(pc.dim(`Connecting to ${target} ...
15
+ `));
16
+ const { code, introspection } = await generateSdk({
17
+ target,
18
+ className: opts.className
19
+ });
20
+ process.stderr.write(
21
+ pc.green(
22
+ `\u2713 ${introspection.tools.length} tools, ${introspection.resources.length} resources, ${introspection.prompts.length} prompts
23
+ `
24
+ )
25
+ );
26
+ if (opts.stdout) {
27
+ process.stdout.write(code + "\n");
28
+ return;
29
+ }
30
+ const outPath = resolve(process.cwd(), opts.out);
31
+ await mkdir(dirname(outPath), { recursive: true });
32
+ await writeFile(outPath, code + "\n", "utf8");
33
+ process.stderr.write(pc.green(`\u2713 Wrote ${pc.bold(opts.out)}
34
+ `));
35
+ } catch (error) {
36
+ const msg = error instanceof Error ? error.message : String(error);
37
+ process.stderr.write(pc.red(`\u2717 ${msg}
38
+ `));
39
+ process.exitCode = 1;
40
+ }
41
+ });
42
+ program.parseAsync();
43
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { generateSdk } from \"./index.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"mcp-codegen\")\n .description(\"Generate a type-safe TypeScript SDK from a live MCP server\")\n .argument(\"<target>\", \"Server URL (http://...) or stdio command (e.g. \\\"node dist/index.js\\\")\")\n .option(\"-o, --out <file>\", \"Output file path\", \"mcp-sdk.ts\")\n .option(\"-c, --class-name <name>\", \"Generated class name\", \"GeneratedMCPClient\")\n .option(\"--stdout\", \"Print to stdout instead of writing a file\", false)\n .action(async (target: string, opts: { out: string; className: string; stdout: boolean }) => {\n try {\n process.stderr.write(pc.dim(`Connecting to ${target} ...\\n`));\n const { code, introspection } = await generateSdk({\n target,\n className: opts.className,\n });\n\n process.stderr.write(\n pc.green(\n `✓ ${introspection.tools.length} tools, ${introspection.resources.length} resources, ${introspection.prompts.length} prompts\\n`,\n ),\n );\n\n if (opts.stdout) {\n process.stdout.write(code + \"\\n\");\n return;\n }\n\n const outPath = resolve(process.cwd(), opts.out);\n await mkdir(dirname(outPath), { recursive: true });\n await writeFile(outPath, code + \"\\n\", \"utf8\");\n process.stderr.write(pc.green(`✓ Wrote ${pc.bold(opts.out)}\\n`));\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n process.stderr.write(pc.red(`✗ ${msg}\\n`));\n process.exitCode = 1;\n }\n });\n\nprogram.parseAsync();\n"],"mappings":";;;;;;AACA,SAAS,OAAO,iBAAiB;AACjC,SAAS,SAAS,eAAe;AACjC,SAAS,eAAe;AACxB,OAAO,QAAQ;AAGf,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,aAAa,EAClB,YAAY,4DAA4D,EACxE,SAAS,YAAY,sEAAwE,EAC7F,OAAO,oBAAoB,oBAAoB,YAAY,EAC3D,OAAO,2BAA2B,wBAAwB,oBAAoB,EAC9E,OAAO,YAAY,6CAA6C,KAAK,EACrE,OAAO,OAAO,QAAgB,SAA8D;AAC3F,MAAI;AACF,YAAQ,OAAO,MAAM,GAAG,IAAI,iBAAiB,MAAM;AAAA,CAAQ,CAAC;AAC5D,UAAM,EAAE,MAAM,cAAc,IAAI,MAAM,YAAY;AAAA,MAChD;AAAA,MACA,WAAW,KAAK;AAAA,IAClB,CAAC;AAED,YAAQ,OAAO;AAAA,MACb,GAAG;AAAA,QACD,UAAK,cAAc,MAAM,MAAM,WAAW,cAAc,UAAU,MAAM,eAAe,cAAc,QAAQ,MAAM;AAAA;AAAA,MACrH;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ;AACf,cAAQ,OAAO,MAAM,OAAO,IAAI;AAChC;AAAA,IACF;AAEA,UAAM,UAAU,QAAQ,QAAQ,IAAI,GAAG,KAAK,GAAG;AAC/C,UAAM,MAAM,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AACjD,UAAM,UAAU,SAAS,OAAO,MAAM,MAAM;AAC5C,YAAQ,OAAO,MAAM,GAAG,MAAM,gBAAW,GAAG,KAAK,KAAK,GAAG,CAAC;AAAA,CAAI,CAAC;AAAA,EACjE,SAAS,OAAO;AACd,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,YAAQ,OAAO,MAAM,GAAG,IAAI,UAAK,GAAG;AAAA,CAAI,CAAC;AACzC,YAAQ,WAAW;AAAA,EACrB;AACF,CAAC;AAEH,QAAQ,WAAW;","names":[]}
@@ -0,0 +1,73 @@
1
+ import { JSONSchema7 } from 'json-schema';
2
+
3
+ interface ToolInfo {
4
+ name: string;
5
+ description?: string;
6
+ inputSchema: JSONSchema7;
7
+ outputSchema?: JSONSchema7;
8
+ }
9
+ interface ResourceInfo {
10
+ uri: string;
11
+ name?: string;
12
+ description?: string;
13
+ mimeType?: string;
14
+ }
15
+ interface PromptInfo {
16
+ name: string;
17
+ description?: string;
18
+ arguments?: {
19
+ name: string;
20
+ description?: string;
21
+ required?: boolean;
22
+ }[];
23
+ }
24
+ interface Introspection {
25
+ serverName: string;
26
+ serverVersion: string;
27
+ tools: ToolInfo[];
28
+ resources: ResourceInfo[];
29
+ prompts: PromptInfo[];
30
+ }
31
+ /** How to reach the server being introspected. */
32
+ type Target = {
33
+ type: "http";
34
+ url: string;
35
+ headers?: Record<string, string>;
36
+ } | {
37
+ type: "stdio";
38
+ command: string;
39
+ args?: string[];
40
+ };
41
+ /**
42
+ * Parse a CLI target string. An http(s):// value becomes an HTTP target;
43
+ * anything else is treated as a stdio command line (first token = command).
44
+ */
45
+ declare function parseTarget(raw: string): Target;
46
+ /** Connect to a server and collect everything needed for codegen. */
47
+ declare function introspect(target: Target): Promise<Introspection>;
48
+
49
+ interface EmitOptions {
50
+ /** Class name for the generated SDK. Default "GeneratedMCPClient". */
51
+ className?: string;
52
+ }
53
+ /**
54
+ * Generate a self-contained, type-safe TypeScript SDK from an introspected
55
+ * server. The output wraps `@ubuligan/client` and exposes one typed method
56
+ * per tool, plus typed resource/prompt helpers.
57
+ */
58
+ declare function emitSdk(intro: Introspection, opts?: EmitOptions): Promise<string>;
59
+
60
+ interface GenerateOptions extends EmitOptions {
61
+ /** Server target: an http(s) URL string or a stdio command line, or a parsed Target. */
62
+ target: string | Target;
63
+ }
64
+ /**
65
+ * High-level entry point: connect to a server, introspect it, and return the
66
+ * generated SDK source as a string.
67
+ */
68
+ declare function generateSdk(options: GenerateOptions): Promise<{
69
+ code: string;
70
+ introspection: Introspection;
71
+ }>;
72
+
73
+ export { type EmitOptions, type GenerateOptions, type Introspection, type PromptInfo, type ResourceInfo, type Target, type ToolInfo, emitSdk, generateSdk, introspect, parseTarget };
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ emitSdk,
3
+ generateSdk,
4
+ introspect,
5
+ parseTarget
6
+ } from "./chunk-5WSAJTXT.js";
7
+ export {
8
+ emitSdk,
9
+ generateSdk,
10
+ introspect,
11
+ parseTarget
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@ubuligan/codegen",
3
+ "version": "0.1.0",
4
+ "description": "Generate a type-safe TypeScript SDK from a live MCP server's tool/resource/prompt schemas",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "mcp-codegen": "./dist/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist",
20
+ "LICENSE"
21
+ ],
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^1.29.0",
24
+ "commander": "^12.1.0",
25
+ "json-schema-to-typescript": "^15.0.3",
26
+ "picocolors": "^1.1.1"
27
+ },
28
+ "devDependencies": {
29
+ "@types/json-schema": "^7.0.15",
30
+ "typescript": "^5.7.2",
31
+ "tsup": "^8.3.5"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "author": "jsznpm",
37
+ "homepage": "https://github.com/jsznpm/create-mcp-toolkit#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/jsznpm/create-mcp-toolkit/issues"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/jsznpm/create-mcp-toolkit.git",
44
+ "directory": "packages/codegen"
45
+ },
46
+ "keywords": [
47
+ "mcp",
48
+ "model-context-protocol",
49
+ "ai",
50
+ "llm",
51
+ "codegen",
52
+ "typescript",
53
+ "type-safe",
54
+ "openapi"
55
+ ],
56
+ "scripts": {
57
+ "build": "tsup",
58
+ "typecheck": "tsc --noEmit"
59
+ }
60
+ }