@eaperezc/mcpgen 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.
@@ -0,0 +1,625 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var commander = require('commander');
5
+ var promises = require('fs/promises');
6
+ var path = require('path');
7
+
8
+ function toPascalCase(str) {
9
+ return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
10
+ }
11
+ function toKebabCase(str) {
12
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
13
+ }
14
+ async function exists(path) {
15
+ try {
16
+ await promises.access(path);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+ function toolTemplate(className, toolName) {
23
+ return `import { BaseTool } from 'mcpkit';
24
+ import { z } from 'zod';
25
+
26
+ /**
27
+ * ${className} - Description of what this tool does
28
+ */
29
+ export class ${className} extends BaseTool {
30
+ name = '${toolName}';
31
+ description = 'TODO: Add description';
32
+
33
+ schema = z.object({
34
+ // TODO: Define your input schema
35
+ input: z.string().describe('Input parameter'),
36
+ });
37
+
38
+ async execute(input: z.infer<typeof this.schema>) {
39
+ // TODO: Implement your tool logic
40
+ return {
41
+ content: {
42
+ result: \`Processed: \${input.input}\`,
43
+ },
44
+ };
45
+ }
46
+ }
47
+ `;
48
+ }
49
+ function resourceTemplate(className, resourceName) {
50
+ return `import { BaseResource } from 'mcpkit';
51
+
52
+ /**
53
+ * ${className} - Description of what this resource provides
54
+ */
55
+ export class ${className} extends BaseResource {
56
+ uri = '${resourceName}://data';
57
+ name = '${className}';
58
+ description = 'TODO: Add description';
59
+
60
+ async read() {
61
+ // TODO: Implement your resource logic
62
+ return {
63
+ contents: [
64
+ this.json({
65
+ // Your data here
66
+ example: 'data',
67
+ }),
68
+ ],
69
+ };
70
+ }
71
+ }
72
+ `;
73
+ }
74
+ function promptTemplate(className, promptName) {
75
+ return `import { BasePrompt } from 'mcpkit';
76
+ import { z } from 'zod';
77
+
78
+ /**
79
+ * ${className} - Description of what this prompt does
80
+ */
81
+ export class ${className} extends BasePrompt {
82
+ name = '${promptName}';
83
+ description = 'TODO: Add description';
84
+
85
+ arguments = z.object({
86
+ // TODO: Define your arguments schema
87
+ topic: z.string().describe('The topic to discuss'),
88
+ });
89
+
90
+ async render(args: z.infer<typeof this.arguments>) {
91
+ // TODO: Implement your prompt logic
92
+ return {
93
+ messages: [
94
+ this.user(\`Please help me with: \${args.topic}\`),
95
+ ],
96
+ };
97
+ }
98
+ }
99
+ `;
100
+ }
101
+ async function updateIndexFile(indexPath, className, fileName) {
102
+ let content = "";
103
+ if (await exists(indexPath)) {
104
+ content = await promises.readFile(indexPath, "utf-8");
105
+ }
106
+ const exportLine = `export { ${className} } from './${fileName}.js';`;
107
+ if (!content.includes(exportLine)) {
108
+ const lines = content.split("\n").filter(
109
+ (line) => line.trim() && !line.trim().startsWith("//")
110
+ );
111
+ lines.push(exportLine);
112
+ content = lines.join("\n") + "\n";
113
+ await promises.writeFile(indexPath, content);
114
+ }
115
+ }
116
+ async function generateComponent(options) {
117
+ const { type, name } = options;
118
+ const className = toPascalCase(name) + (type === "tool" ? "Tool" : type === "resource" ? "Resource" : "Prompt");
119
+ const fileName = className;
120
+ const kebabName = toKebabCase(name);
121
+ const typeDir = type === "tool" ? "tools" : type === "resource" ? "resources" : "prompts";
122
+ const srcDir = path.join(process.cwd(), "src", typeDir);
123
+ const filePath = path.join(srcDir, `${fileName}.ts`);
124
+ const indexPath = path.join(srcDir, "index.ts");
125
+ if (!await exists(path.join(process.cwd(), "src"))) {
126
+ throw new Error('No src/ directory found. Are you in an mcpkit project? Run "mcpkit init" first.');
127
+ }
128
+ await promises.mkdir(srcDir, { recursive: true });
129
+ if (await exists(filePath)) {
130
+ throw new Error(`${type} '${fileName}' already exists at ${filePath}`);
131
+ }
132
+ let content;
133
+ switch (type) {
134
+ case "tool":
135
+ content = toolTemplate(className, kebabName);
136
+ break;
137
+ case "resource":
138
+ content = resourceTemplate(className, kebabName);
139
+ break;
140
+ case "prompt":
141
+ content = promptTemplate(className, kebabName);
142
+ break;
143
+ default:
144
+ throw new Error(`Unknown component type: ${type}`);
145
+ }
146
+ await promises.writeFile(filePath, content);
147
+ console.log(`
148
+ \u2705 Created ${type}: ${filePath.replace(process.cwd(), ".")}`);
149
+ await updateIndexFile(indexPath, className, fileName);
150
+ console.log(` Updated: ${indexPath.replace(process.cwd(), ".")}`);
151
+ console.log(`
152
+ Next steps:
153
+
154
+ 1. Edit src/${typeDir}/${fileName}.ts to implement your ${type}
155
+ 2. Register it in src/index.ts:
156
+
157
+ import { ${className} } from './tools/${fileName}.js';
158
+ server.${type}(new ${className}());
159
+ `);
160
+ }
161
+
162
+ // src/cli/templates/index.ts
163
+ var packageJsonTemplate = (name, description) => `{
164
+ "name": "${name}",
165
+ "version": "0.1.0",
166
+ "description": "${description}",
167
+ "type": "module",
168
+ "main": "dist/index.js",
169
+ "scripts": {
170
+ "build": "tsc",
171
+ "dev": "tsx watch src/index.ts",
172
+ "start": "node dist/index.js",
173
+ "test": "vitest run",
174
+ "test:watch": "vitest",
175
+ "lint": "eslint src",
176
+ "typecheck": "tsc --noEmit"
177
+ },
178
+ "keywords": ["mcp", "model-context-protocol"],
179
+ "license": "MIT",
180
+ "dependencies": {
181
+ "@eaperezc/mcpgen": "^0.1.0",
182
+ "@modelcontextprotocol/sdk": "^1.11.0",
183
+ "zod": "^3.23.8"
184
+ },
185
+ "devDependencies": {
186
+ "@types/node": "^22.10.0",
187
+ "typescript": "^5.7.2",
188
+ "tsx": "^4.19.2",
189
+ "vitest": "^2.1.8"
190
+ },
191
+ "engines": {
192
+ "node": ">=18.0.0"
193
+ }
194
+ }
195
+ `;
196
+ var tsconfigTemplate = () => `{
197
+ "compilerOptions": {
198
+ "target": "ES2022",
199
+ "module": "ESNext",
200
+ "moduleResolution": "bundler",
201
+ "lib": ["ES2022"],
202
+ "strict": true,
203
+ "esModuleInterop": true,
204
+ "skipLibCheck": true,
205
+ "forceConsistentCasingInFileNames": true,
206
+ "declaration": true,
207
+ "outDir": "./dist",
208
+ "rootDir": "./src",
209
+ "resolveJsonModule": true
210
+ },
211
+ "include": ["src/**/*"],
212
+ "exclude": ["node_modules", "dist"]
213
+ }
214
+ `;
215
+ var gitignoreTemplate = () => `# Dependencies
216
+ node_modules/
217
+
218
+ # Build output
219
+ dist/
220
+
221
+ # Environment files
222
+ .env
223
+ .env.local
224
+ .env.*.local
225
+
226
+ # IDE
227
+ .idea/
228
+ .vscode/
229
+ *.swp
230
+ *.swo
231
+
232
+ # OS
233
+ .DS_Store
234
+ Thumbs.db
235
+
236
+ # Logs
237
+ *.log
238
+ `;
239
+ var mainIndexTemplate = (name, transport) => {
240
+ if (transport === "http") {
241
+ return `import { McpServer } from '@eaperezc/mcpgen';
242
+ import { GreetTool } from './tools/GreetTool.js';
243
+ import { ConfigResource } from './resources/ConfigResource.js';
244
+
245
+ const server = new McpServer({
246
+ name: '${name}',
247
+ version: '0.1.0',
248
+ transport: {
249
+ type: 'http',
250
+ port: 3000,
251
+ cors: { origin: '*' },
252
+ },
253
+ logging: { level: 'info' },
254
+ });
255
+
256
+ // Register tools
257
+ server.tool(new GreetTool());
258
+
259
+ // Register resources
260
+ server.resource(new ConfigResource());
261
+
262
+ // Start the server
263
+ server.start().then(() => {
264
+ console.log('MCP server running at http://localhost:3000/mcp');
265
+ });
266
+ `;
267
+ }
268
+ return `import { McpServer } from '@eaperezc/mcpgen';
269
+ import { GreetTool } from './tools/GreetTool.js';
270
+ import { ConfigResource } from './resources/ConfigResource.js';
271
+
272
+ const server = new McpServer({
273
+ name: '${name}',
274
+ version: '0.1.0',
275
+ logging: { level: 'info' },
276
+ });
277
+
278
+ // Register tools
279
+ server.tool(new GreetTool());
280
+
281
+ // Register resources
282
+ server.resource(new ConfigResource());
283
+
284
+ // Start the server
285
+ server.start();
286
+ `;
287
+ };
288
+ var toolsIndexTemplate = () => `export { GreetTool } from './GreetTool.js';
289
+ `;
290
+ var greetToolTemplate = () => `import { BaseTool } from '@eaperezc/mcpgen';
291
+ import { z } from 'zod';
292
+
293
+ /**
294
+ * Example tool that greets a user
295
+ */
296
+ export class GreetTool extends BaseTool {
297
+ name = 'greet';
298
+ description = 'Greets a user by name';
299
+
300
+ schema = z.object({
301
+ name: z.string().describe('The name of the person to greet'),
302
+ });
303
+
304
+ async execute(input: z.infer<typeof this.schema>) {
305
+ return {
306
+ content: {
307
+ greeting: \`Hello, \${input.name}! Welcome to your MCP server.\`,
308
+ },
309
+ };
310
+ }
311
+ }
312
+ `;
313
+ var resourcesIndexTemplate = () => `export { ConfigResource } from './ConfigResource.js';
314
+ `;
315
+ var configResourceTemplate = (name) => `import { BaseResource } from '@eaperezc/mcpgen';
316
+
317
+ /**
318
+ * Example resource that provides server configuration
319
+ */
320
+ export class ConfigResource extends BaseResource {
321
+ uri = 'config://server';
322
+ name = 'Server Configuration';
323
+ description = 'Provides server configuration information';
324
+
325
+ async read() {
326
+ return {
327
+ contents: [
328
+ this.json({
329
+ serverName: '${name}',
330
+ version: '0.1.0',
331
+ environment: process.env.NODE_ENV ?? 'development',
332
+ }),
333
+ ],
334
+ };
335
+ }
336
+ }
337
+ `;
338
+ var promptsIndexTemplate = () => `// Export your prompts here
339
+ // export { SummarizePrompt } from './SummarizePrompt.js';
340
+ `;
341
+ var testExampleTemplate = () => `import { describe, it, expect } from 'vitest';
342
+ import { createTestClient } from '@eaperezc/mcpgen/testing';
343
+ import { GreetTool } from '../src/tools/GreetTool.js';
344
+
345
+ describe('GreetTool', () => {
346
+ it('should greet the user', async () => {
347
+ const client = createTestClient();
348
+ client.registerTool(new GreetTool());
349
+
350
+ const result = await client.callTool('greet', { name: 'Alice' });
351
+
352
+ expect(result.content).toHaveProperty('greeting');
353
+ expect((result.content as { greeting: string }).greeting).toContain('Alice');
354
+ });
355
+
356
+ it('should require a name', async () => {
357
+ const client = createTestClient();
358
+ client.registerTool(new GreetTool());
359
+
360
+ await expect(client.callTool('greet', {})).rejects.toThrow();
361
+ });
362
+ });
363
+ `;
364
+ var readmeTemplate = (name, description) => `# ${name}
365
+
366
+ ${description}
367
+
368
+ ## Getting Started
369
+
370
+ ### Installation
371
+
372
+ \`\`\`bash
373
+ npm install
374
+ \`\`\`
375
+
376
+ ### Development
377
+
378
+ Run the server in development mode with hot reload:
379
+
380
+ \`\`\`bash
381
+ npm run dev
382
+ \`\`\`
383
+
384
+ ### Production
385
+
386
+ Build and run:
387
+
388
+ \`\`\`bash
389
+ npm run build
390
+ npm start
391
+ \`\`\`
392
+
393
+ ### Testing
394
+
395
+ \`\`\`bash
396
+ npm test
397
+ \`\`\`
398
+
399
+ ## Project Structure
400
+
401
+ \`\`\`
402
+ ${name}/
403
+ \u251C\u2500\u2500 src/
404
+ \u2502 \u251C\u2500\u2500 tools/ # MCP tools
405
+ \u2502 \u2502 \u2514\u2500\u2500 GreetTool.ts
406
+ \u2502 \u251C\u2500\u2500 resources/ # MCP resources
407
+ \u2502 \u2502 \u2514\u2500\u2500 ConfigResource.ts
408
+ \u2502 \u251C\u2500\u2500 prompts/ # MCP prompts
409
+ \u2502 \u2514\u2500\u2500 index.ts # Server entry point
410
+ \u251C\u2500\u2500 tests/
411
+ \u2502 \u2514\u2500\u2500 tools.test.ts
412
+ \u251C\u2500\u2500 package.json
413
+ \u2514\u2500\u2500 tsconfig.json
414
+ \`\`\`
415
+
416
+ ## Adding New Components
417
+
418
+ ### Tools
419
+
420
+ Create a new file in \`src/tools/\`:
421
+
422
+ \`\`\`typescript
423
+ import { BaseTool } from '@eaperezc/mcpgen';
424
+ import { z } from 'zod';
425
+
426
+ export class MyTool extends BaseTool {
427
+ name = 'my-tool';
428
+ description = 'Description of what this tool does';
429
+
430
+ schema = z.object({
431
+ // Define your input schema
432
+ });
433
+
434
+ async execute(input: z.infer<typeof this.schema>) {
435
+ // Implement your tool logic
436
+ return { content: { result: 'success' } };
437
+ }
438
+ }
439
+ \`\`\`
440
+
441
+ ### Resources
442
+
443
+ Create a new file in \`src/resources/\`:
444
+
445
+ \`\`\`typescript
446
+ import { BaseResource } from '@eaperezc/mcpgen';
447
+
448
+ export class MyResource extends BaseResource {
449
+ uri = 'my-resource://example';
450
+ name = 'My Resource';
451
+
452
+ async read() {
453
+ return { contents: [this.json({ data: 'example' })] };
454
+ }
455
+ }
456
+ \`\`\`
457
+
458
+ ## License
459
+
460
+ MIT
461
+ `;
462
+ var vitestConfigTemplate = () => `import { defineConfig } from 'vitest/config';
463
+
464
+ export default defineConfig({
465
+ test: {
466
+ globals: true,
467
+ environment: 'node',
468
+ },
469
+ });
470
+ `;
471
+
472
+ // src/cli/commands/init.ts
473
+ async function exists2(path) {
474
+ try {
475
+ await promises.access(path);
476
+ return true;
477
+ } catch {
478
+ return false;
479
+ }
480
+ }
481
+ async function initProject(options) {
482
+ const {
483
+ name,
484
+ description = `An MCP server built with @eaperezc/mcpgen`,
485
+ transport = "stdio"
486
+ } = options;
487
+ const projectDir = path.join(process.cwd(), name);
488
+ if (await exists2(projectDir)) {
489
+ throw new Error(`Directory '${name}' already exists`);
490
+ }
491
+ console.log(`
492
+ Creating new MCP server: ${name}
493
+ `);
494
+ const dirs = [
495
+ projectDir,
496
+ path.join(projectDir, "src"),
497
+ path.join(projectDir, "src", "tools"),
498
+ path.join(projectDir, "src", "resources"),
499
+ path.join(projectDir, "src", "prompts"),
500
+ path.join(projectDir, "tests")
501
+ ];
502
+ for (const dir of dirs) {
503
+ await promises.mkdir(dir, { recursive: true });
504
+ console.log(` Created: ${dir.replace(process.cwd(), ".")}`);
505
+ }
506
+ const files = [
507
+ { path: "package.json", content: packageJsonTemplate(name, description) },
508
+ { path: "tsconfig.json", content: tsconfigTemplate() },
509
+ { path: ".gitignore", content: gitignoreTemplate() },
510
+ { path: "vitest.config.ts", content: vitestConfigTemplate() },
511
+ { path: "README.md", content: readmeTemplate(name, description) },
512
+ { path: "src/index.ts", content: mainIndexTemplate(name, transport) },
513
+ { path: "src/tools/index.ts", content: toolsIndexTemplate() },
514
+ { path: "src/tools/GreetTool.ts", content: greetToolTemplate() },
515
+ { path: "src/resources/index.ts", content: resourcesIndexTemplate() },
516
+ { path: "src/resources/ConfigResource.ts", content: configResourceTemplate(name) },
517
+ { path: "src/prompts/index.ts", content: promptsIndexTemplate() },
518
+ { path: "tests/tools.test.ts", content: testExampleTemplate() }
519
+ ];
520
+ for (const file of files) {
521
+ const filePath = path.join(projectDir, file.path);
522
+ await promises.writeFile(filePath, file.content);
523
+ console.log(` Created: ./${name}/${file.path}`);
524
+ }
525
+ console.log(`
526
+ \u2705 Project created successfully!
527
+
528
+ Next steps:
529
+
530
+ cd ${name}
531
+ npm install
532
+ npm run dev
533
+
534
+ ${transport === "http" ? "Server will run at http://localhost:3000/mcp" : "Server will run via stdio"}
535
+
536
+ To add new components:
537
+ - Tools: Create files in src/tools/
538
+ - Resources: Create files in src/resources/
539
+ - Prompts: Create files in src/prompts/
540
+
541
+ Happy building! \u{1F680}
542
+ `);
543
+ }
544
+
545
+ // src/cli/index.ts
546
+ var program = new commander.Command();
547
+ program.name("mcpgen").description("CLI for building MCP servers").version("0.1.0");
548
+ program.command("init").description("Initialize a new MCP server project").argument("<name>", "Project name").option("-d, --description <desc>", "Project description").option("-t, --transport <type>", "Transport type (stdio or http)", "stdio").option("--skip-install", "Skip npm install").action(async (name, options) => {
549
+ try {
550
+ await initProject({
551
+ name,
552
+ description: options.description,
553
+ transport: options.transport,
554
+ skipInstall: options.skipInstall
555
+ });
556
+ } catch (error) {
557
+ console.error(`
558
+ \u274C Error: ${error instanceof Error ? error.message : error}
559
+ `);
560
+ process.exit(1);
561
+ }
562
+ });
563
+ program.command("make:tool").description("Create a new tool").argument("<name>", "Tool name").action(async (name) => {
564
+ try {
565
+ await generateComponent({ type: "tool", name });
566
+ } catch (error) {
567
+ console.error(`
568
+ \u274C Error: ${error instanceof Error ? error.message : error}
569
+ `);
570
+ process.exit(1);
571
+ }
572
+ });
573
+ program.command("make:resource").description("Create a new resource").argument("<name>", "Resource name").action(async (name) => {
574
+ try {
575
+ await generateComponent({ type: "resource", name });
576
+ } catch (error) {
577
+ console.error(`
578
+ \u274C Error: ${error instanceof Error ? error.message : error}
579
+ `);
580
+ process.exit(1);
581
+ }
582
+ });
583
+ program.command("make:prompt").description("Create a new prompt").argument("<name>", "Prompt name").action(async (name) => {
584
+ try {
585
+ await generateComponent({ type: "prompt", name });
586
+ } catch (error) {
587
+ console.error(`
588
+ \u274C Error: ${error instanceof Error ? error.message : error}
589
+ `);
590
+ process.exit(1);
591
+ }
592
+ });
593
+ program.addHelpText(
594
+ "after",
595
+ `
596
+ Examples:
597
+ $ mcpgen init my-mcp-server Create a new MCP server project
598
+ $ mcpgen init my-api --transport http Create with HTTP transport
599
+ $ mcpgen make:tool search Create a new tool
600
+ $ mcpgen make:resource database Create a new resource
601
+ $ mcpgen make:prompt summarize Create a new prompt
602
+ `
603
+ );
604
+ if (process.argv.length <= 2) {
605
+ const cyan = "\x1B[36m";
606
+ const dim = "\x1B[2m";
607
+ const bold = "\x1B[1m";
608
+ const reset = "\x1B[0m";
609
+ console.log(`
610
+ ${dim}+-------------------------------------+${reset}
611
+ ${dim}|${reset} ${dim}|${reset}
612
+ ${dim}|${reset} ${bold}${cyan}MCPGEN${reset} ${dim}v0.1.0${reset} ${dim}|${reset}
613
+ ${dim}|${reset} ${dim}|${reset}
614
+ ${dim}|${reset} A TypeScript framework for ${dim}|${reset}
615
+ ${dim}|${reset} building MCP servers ${dim}|${reset}
616
+ ${dim}|${reset} ${dim}|${reset}
617
+ ${dim}|${reset} Run ${cyan}mcpgen --help${reset} for commands ${dim}|${reset}
618
+ ${dim}|${reset} ${dim}|${reset}
619
+ ${dim}+-------------------------------------+${reset}
620
+ `);
621
+ } else {
622
+ program.parse();
623
+ }
624
+ //# sourceMappingURL=index.cjs.map
625
+ //# sourceMappingURL=index.cjs.map