@webbywisp/create-mcp-server 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/dist/cli.js ADDED
@@ -0,0 +1,1806 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import chalk3 from "chalk";
6
+
7
+ // src/commands/init.ts
8
+ import path2 from "path";
9
+ import chalk2 from "chalk";
10
+ import ora from "ora";
11
+ import fs2 from "fs-extra";
12
+
13
+ // src/utils/prompts.ts
14
+ import inquirer from "inquirer";
15
+ async function runWizard(defaults) {
16
+ return inquirer.prompt([
17
+ {
18
+ type: "input",
19
+ name: "serverName",
20
+ message: "Server name (used in MCP config):",
21
+ default: defaults?.serverName ?? "my-mcp-server",
22
+ validate: (input) => {
23
+ if (!input.trim()) return "Server name cannot be empty";
24
+ if (!/^[a-z0-9-]+$/.test(input)) return "Use lowercase letters, numbers, and hyphens only";
25
+ return true;
26
+ }
27
+ },
28
+ {
29
+ type: "input",
30
+ name: "serverDescription",
31
+ message: "Short description:",
32
+ default: defaults?.serverDescription ?? "An MCP server"
33
+ },
34
+ {
35
+ type: "list",
36
+ name: "template",
37
+ message: "Choose a template:",
38
+ default: defaults?.template ?? "tool-server",
39
+ choices: [
40
+ {
41
+ name: "tool-server \u2014 expose tools (functions AI can call)",
42
+ value: "tool-server",
43
+ short: "tool-server"
44
+ },
45
+ {
46
+ name: "resource-server \u2014 expose data/files as addressable resources",
47
+ value: "resource-server",
48
+ short: "resource-server"
49
+ },
50
+ {
51
+ name: "prompt-server \u2014 provide structured prompt templates",
52
+ value: "prompt-server",
53
+ short: "prompt-server"
54
+ }
55
+ ]
56
+ },
57
+ {
58
+ type: "input",
59
+ name: "packageName",
60
+ message: "npm package name:",
61
+ default: (answers) => defaults?.packageName ?? answers.serverName,
62
+ validate: (input) => {
63
+ if (!input.trim()) return "Package name cannot be empty";
64
+ return true;
65
+ }
66
+ },
67
+ {
68
+ type: "input",
69
+ name: "author",
70
+ message: "Author name:",
71
+ default: defaults?.author ?? ""
72
+ },
73
+ {
74
+ type: "confirm",
75
+ name: "gitInit",
76
+ message: "Initialize a git repository?",
77
+ default: defaults?.gitInit ?? true
78
+ }
79
+ ]);
80
+ }
81
+ function buildDefaultConfig(targetDir, dirName, overrides = {}) {
82
+ return {
83
+ targetDir,
84
+ dirName,
85
+ serverName: dirName,
86
+ serverDescription: `An MCP server`,
87
+ template: "tool-server",
88
+ packageName: dirName,
89
+ author: "",
90
+ gitInit: true,
91
+ dryRun: false,
92
+ yes: false,
93
+ ...overrides
94
+ };
95
+ }
96
+
97
+ // src/utils/files.ts
98
+ import fs from "fs-extra";
99
+ import path from "path";
100
+ import chalk from "chalk";
101
+ async function writeFiles(targetDir, files, force) {
102
+ const results = [];
103
+ for (const file of files) {
104
+ const filePath = path.join(targetDir, file.path);
105
+ if (file.isDirectory) {
106
+ await fs.ensureDir(filePath);
107
+ results.push({ path: file.path, status: "created" });
108
+ continue;
109
+ }
110
+ await fs.ensureDir(path.dirname(filePath));
111
+ const exists = await fs.pathExists(filePath);
112
+ if (exists && !force) {
113
+ results.push({ path: file.path, status: "skipped" });
114
+ continue;
115
+ }
116
+ await fs.writeFile(filePath, file.content, "utf8");
117
+ results.push({ path: file.path, status: exists ? "overwritten" : "created" });
118
+ }
119
+ return results;
120
+ }
121
+ function printDryRunTree(targetDir, files) {
122
+ console.log(chalk.bold(`\u{1F4C1} ${targetDir}/`));
123
+ for (const file of files) {
124
+ const icon = file.isDirectory ? "\u{1F4C1}" : "\u{1F4C4}";
125
+ console.log(chalk.dim(` ${icon} ${file.path}`));
126
+ }
127
+ console.log();
128
+ }
129
+ function printWriteResults(results) {
130
+ for (const result of results) {
131
+ if (result.status === "created") {
132
+ console.log(` ${chalk.green("+")} ${result.path}`);
133
+ } else if (result.status === "overwritten") {
134
+ console.log(` ${chalk.yellow("~")} ${result.path} ${chalk.dim("(overwritten)")}`);
135
+ } else {
136
+ console.log(` ${chalk.dim("-")} ${result.path} ${chalk.dim("(skipped)")}`);
137
+ }
138
+ }
139
+ }
140
+
141
+ // src/templates/tool-server.ts
142
+ function generateToolServer(config) {
143
+ const { serverName, serverDescription, packageName, author } = config;
144
+ return [
145
+ {
146
+ path: "src/index.ts",
147
+ content: `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
148
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
149
+ import { z } from 'zod';
150
+ import { readFileTool } from './tools/readFile.js';
151
+ import { writeFileTool } from './tools/writeFile.js';
152
+ import { httpFetchTool } from './tools/httpFetch.js';
153
+ import { execCommandTool } from './tools/execCommand.js';
154
+
155
+ const server = new McpServer({
156
+ name: '${serverName}',
157
+ version: '0.1.0',
158
+ });
159
+
160
+ // Register all tools
161
+ server.tool(
162
+ readFileTool.name,
163
+ readFileTool.description,
164
+ readFileTool.inputSchema,
165
+ readFileTool.handler,
166
+ );
167
+
168
+ server.tool(
169
+ writeFileTool.name,
170
+ writeFileTool.description,
171
+ writeFileTool.inputSchema,
172
+ writeFileTool.handler,
173
+ );
174
+
175
+ server.tool(
176
+ httpFetchTool.name,
177
+ httpFetchTool.description,
178
+ httpFetchTool.inputSchema,
179
+ httpFetchTool.handler,
180
+ );
181
+
182
+ server.tool(
183
+ execCommandTool.name,
184
+ execCommandTool.description,
185
+ execCommandTool.inputSchema,
186
+ execCommandTool.handler,
187
+ );
188
+
189
+ async function main() {
190
+ const transport = new StdioServerTransport();
191
+ await server.connect(transport);
192
+ console.error('${serverName} MCP server running on stdio');
193
+ }
194
+
195
+ main().catch((err) => {
196
+ console.error('Fatal error:', err);
197
+ process.exit(1);
198
+ });
199
+ `
200
+ },
201
+ {
202
+ path: "src/tools/readFile.ts",
203
+ content: `import { z } from 'zod';
204
+ import fs from 'fs/promises';
205
+ import path from 'path';
206
+
207
+ export const readFileTool = {
208
+ name: 'read_file',
209
+ description: 'Read the contents of a file. Returns the file content as a string.',
210
+ inputSchema: {
211
+ path: z.string().describe('Absolute or relative path to the file'),
212
+ encoding: z.enum(['utf8', 'base64']).optional().default('utf8').describe('File encoding'),
213
+ maxBytes: z.number().optional().default(1048576).describe('Max bytes to read (default 1MB)'),
214
+ },
215
+ handler: async ({ path: filePath, encoding, maxBytes }: {
216
+ path: string;
217
+ encoding?: 'utf8' | 'base64';
218
+ maxBytes?: number;
219
+ }) => {
220
+ const resolvedPath = path.resolve(filePath);
221
+
222
+ try {
223
+ const stat = await fs.stat(resolvedPath);
224
+
225
+ if (!stat.isFile()) {
226
+ return {
227
+ content: [{ type: 'text' as const, text: \`Error: \${filePath} is not a file\` }],
228
+ isError: true,
229
+ };
230
+ }
231
+
232
+ if (stat.size > (maxBytes ?? 1048576)) {
233
+ return {
234
+ content: [{
235
+ type: 'text' as const,
236
+ text: \`Error: File too large (\${stat.size} bytes). Max is \${maxBytes} bytes.\`
237
+ }],
238
+ isError: true,
239
+ };
240
+ }
241
+
242
+ const content = await fs.readFile(resolvedPath, encoding ?? 'utf8');
243
+
244
+ return {
245
+ content: [{ type: 'text' as const, text: String(content) }],
246
+ };
247
+ } catch (err: any) {
248
+ return {
249
+ content: [{ type: 'text' as const, text: \`Error reading file: \${err.message}\` }],
250
+ isError: true,
251
+ };
252
+ }
253
+ },
254
+ };
255
+ `
256
+ },
257
+ {
258
+ path: "src/tools/writeFile.ts",
259
+ content: `import { z } from 'zod';
260
+ import fs from 'fs/promises';
261
+ import path from 'path';
262
+
263
+ export const writeFileTool = {
264
+ name: 'write_file',
265
+ description: 'Write content to a file. Creates parent directories if needed.',
266
+ inputSchema: {
267
+ path: z.string().describe('Absolute or relative path to write to'),
268
+ content: z.string().describe('Content to write'),
269
+ encoding: z.enum(['utf8', 'base64']).optional().default('utf8').describe('File encoding'),
270
+ createDirs: z.boolean().optional().default(true).describe('Create parent directories if missing'),
271
+ overwrite: z.boolean().optional().default(true).describe('Overwrite if file exists'),
272
+ },
273
+ handler: async ({ path: filePath, content, encoding, createDirs, overwrite }: {
274
+ path: string;
275
+ content: string;
276
+ encoding?: 'utf8' | 'base64';
277
+ createDirs?: boolean;
278
+ overwrite?: boolean;
279
+ }) => {
280
+ const resolvedPath = path.resolve(filePath);
281
+
282
+ try {
283
+ // Check if file exists
284
+ if (!overwrite) {
285
+ const exists = await fs.access(resolvedPath).then(() => true).catch(() => false);
286
+ if (exists) {
287
+ return {
288
+ content: [{ type: 'text' as const, text: \`Error: File already exists at \${filePath}. Set overwrite=true to overwrite.\` }],
289
+ isError: true,
290
+ };
291
+ }
292
+ }
293
+
294
+ if (createDirs) {
295
+ await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
296
+ }
297
+
298
+ await fs.writeFile(resolvedPath, content, encoding ?? 'utf8');
299
+
300
+ return {
301
+ content: [{ type: 'text' as const, text: \`Successfully wrote \${content.length} bytes to \${filePath}\` }],
302
+ };
303
+ } catch (err: any) {
304
+ return {
305
+ content: [{ type: 'text' as const, text: \`Error writing file: \${err.message}\` }],
306
+ isError: true,
307
+ };
308
+ }
309
+ },
310
+ };
311
+ `
312
+ },
313
+ {
314
+ path: "src/tools/httpFetch.ts",
315
+ content: `import { z } from 'zod';
316
+
317
+ export const httpFetchTool = {
318
+ name: 'http_fetch',
319
+ description: 'Make an HTTP request and return the response. Supports GET, POST, PUT, DELETE.',
320
+ inputSchema: {
321
+ url: z.string().url().describe('URL to fetch'),
322
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).optional().default('GET').describe('HTTP method'),
323
+ headers: z.record(z.string()).optional().describe('Request headers as key-value pairs'),
324
+ body: z.string().optional().describe('Request body (for POST/PUT/PATCH)'),
325
+ timeoutMs: z.number().optional().default(30000).describe('Timeout in milliseconds'),
326
+ maxBytes: z.number().optional().default(524288).describe('Max response bytes (default 512KB)'),
327
+ },
328
+ handler: async ({ url, method, headers, body, timeoutMs, maxBytes }: {
329
+ url: string;
330
+ method?: string;
331
+ headers?: Record<string, string>;
332
+ body?: string;
333
+ timeoutMs?: number;
334
+ maxBytes?: number;
335
+ }) => {
336
+ try {
337
+ const controller = new AbortController();
338
+ const timeout = setTimeout(() => controller.abort(), timeoutMs ?? 30000);
339
+
340
+ const response = await fetch(url, {
341
+ method: method ?? 'GET',
342
+ headers: headers,
343
+ body: body,
344
+ signal: controller.signal,
345
+ });
346
+
347
+ clearTimeout(timeout);
348
+
349
+ const responseHeaders: Record<string, string> = {};
350
+ response.headers.forEach((value, key) => {
351
+ responseHeaders[key] = value;
352
+ });
353
+
354
+ const contentType = response.headers.get('content-type') ?? '';
355
+ let responseText: string;
356
+
357
+ if (contentType.includes('application/json') || contentType.includes('text/')) {
358
+ const arrayBuffer = await response.arrayBuffer();
359
+ const bytes = arrayBuffer.byteLength;
360
+ if (bytes > (maxBytes ?? 524288)) {
361
+ responseText = \`[Response truncated: \${bytes} bytes exceeds maxBytes limit]\`;
362
+ } else {
363
+ responseText = new TextDecoder().decode(arrayBuffer);
364
+ }
365
+ } else {
366
+ responseText = \`[Binary content: \${contentType}]\`;
367
+ }
368
+
369
+ const summary = [
370
+ \`Status: \${response.status} \${response.statusText}\`,
371
+ \`Content-Type: \${contentType}\`,
372
+ \`\\n\${responseText}\`,
373
+ ].join('\\n');
374
+
375
+ return {
376
+ content: [{ type: 'text' as const, text: summary }],
377
+ isError: !response.ok,
378
+ };
379
+ } catch (err: any) {
380
+ return {
381
+ content: [{ type: 'text' as const, text: \`HTTP request failed: \${err.message}\` }],
382
+ isError: true,
383
+ };
384
+ }
385
+ },
386
+ };
387
+ `
388
+ },
389
+ {
390
+ path: "src/tools/execCommand.ts",
391
+ content: `import { z } from 'zod';
392
+ import { exec } from 'child_process';
393
+ import { promisify } from 'util';
394
+
395
+ const execAsync = promisify(exec);
396
+
397
+ // Safety: block dangerous commands by default
398
+ const BLOCKED_PATTERNS = [
399
+ /rm\\s+-rf\\s+\\//,
400
+ /mkfs/,
401
+ /dd\\s+if=/,
402
+ /:(\\s*)\\{\\s*:\\|:&\\s*\\}/, // fork bomb
403
+ />\\/dev\\/(sd|hd|nvme)/,
404
+ ];
405
+
406
+ export const execCommandTool = {
407
+ name: 'exec_command',
408
+ description: 'Execute a shell command and return stdout/stderr. Use with caution.',
409
+ inputSchema: {
410
+ command: z.string().describe('Shell command to execute'),
411
+ cwd: z.string().optional().describe('Working directory (defaults to process cwd)'),
412
+ timeoutMs: z.number().optional().default(30000).describe('Timeout in milliseconds'),
413
+ env: z.record(z.string()).optional().describe('Additional environment variables'),
414
+ },
415
+ handler: async ({ command, cwd, timeoutMs, env }: {
416
+ command: string;
417
+ cwd?: string;
418
+ timeoutMs?: number;
419
+ env?: Record<string, string>;
420
+ }) => {
421
+ // Safety check
422
+ for (const pattern of BLOCKED_PATTERNS) {
423
+ if (pattern.test(command)) {
424
+ return {
425
+ content: [{ type: 'text' as const, text: \`Error: Command blocked for safety: \${command}\` }],
426
+ isError: true,
427
+ };
428
+ }
429
+ }
430
+
431
+ try {
432
+ const { stdout, stderr } = await execAsync(command, {
433
+ cwd: cwd ?? process.cwd(),
434
+ timeout: timeoutMs ?? 30000,
435
+ env: { ...process.env, ...env },
436
+ maxBuffer: 1024 * 1024, // 1MB
437
+ });
438
+
439
+ const output = [
440
+ stdout && \`stdout:\\n\${stdout.trim()}\`,
441
+ stderr && \`stderr:\\n\${stderr.trim()}\`,
442
+ ].filter(Boolean).join('\\n\\n') || '(no output)';
443
+
444
+ return {
445
+ content: [{ type: 'text' as const, text: output }],
446
+ };
447
+ } catch (err: any) {
448
+ const output = [
449
+ \`Exit code: \${err.code ?? 'unknown'}\`,
450
+ err.stdout && \`stdout:\\n\${err.stdout.trim()}\`,
451
+ err.stderr && \`stderr:\\n\${err.stderr.trim()}\`,
452
+ \`Error: \${err.message}\`,
453
+ ].filter(Boolean).join('\\n');
454
+
455
+ return {
456
+ content: [{ type: 'text' as const, text: output }],
457
+ isError: true,
458
+ };
459
+ }
460
+ },
461
+ };
462
+ `
463
+ },
464
+ {
465
+ path: "package.json",
466
+ content: JSON.stringify({
467
+ name: packageName,
468
+ version: "0.1.0",
469
+ description: serverDescription,
470
+ type: "module",
471
+ scripts: {
472
+ build: "tsc",
473
+ dev: "tsc --watch",
474
+ start: "node dist/index.js",
475
+ prepublishOnly: "npm run build"
476
+ },
477
+ engines: { node: ">=18.0.0" },
478
+ dependencies: {
479
+ "@modelcontextprotocol/sdk": "^1.0.0",
480
+ zod: "^3.22.0"
481
+ },
482
+ devDependencies: {
483
+ "@types/node": "^22.0.0",
484
+ typescript: "^5.0.0"
485
+ }
486
+ }, null, 2)
487
+ },
488
+ {
489
+ path: "tsconfig.json",
490
+ content: JSON.stringify({
491
+ compilerOptions: {
492
+ target: "ES2022",
493
+ module: "NodeNext",
494
+ moduleResolution: "NodeNext",
495
+ lib: ["ES2022"],
496
+ outDir: "dist",
497
+ rootDir: "src",
498
+ strict: true,
499
+ esModuleInterop: true,
500
+ skipLibCheck: true,
501
+ declaration: true,
502
+ sourceMap: true,
503
+ resolveJsonModule: true
504
+ },
505
+ include: ["src/**/*"],
506
+ exclude: ["node_modules", "dist"]
507
+ }, null, 2)
508
+ },
509
+ {
510
+ path: ".gitignore",
511
+ content: `node_modules/
512
+ dist/
513
+ *.js.map
514
+ *.d.ts.map
515
+ .env
516
+ .env.local
517
+ *.log
518
+ .DS_Store
519
+ `
520
+ },
521
+ {
522
+ path: "README.md",
523
+ content: `# ${serverName}
524
+
525
+ ${serverDescription}
526
+
527
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) tool server that exposes file system, HTTP, and shell execution capabilities to AI models.
528
+
529
+ ## Tools
530
+
531
+ | Tool | Description |
532
+ |------|-------------|
533
+ | \`read_file\` | Read file contents with encoding and size limits |
534
+ | \`write_file\` | Write files, creating directories as needed |
535
+ | \`http_fetch\` | Make HTTP requests (GET, POST, PUT, DELETE) |
536
+ | \`exec_command\` | Execute shell commands with safety checks |
537
+
538
+ ## Setup
539
+
540
+ \`\`\`bash
541
+ npm install
542
+ npm run build
543
+ \`\`\`
544
+
545
+ ## Usage with Claude Desktop
546
+
547
+ Add to your Claude Desktop config (\`~/Library/Application Support/Claude/claude_desktop_config.json\`):
548
+
549
+ \`\`\`json
550
+ {
551
+ "mcpServers": {
552
+ "${serverName}": {
553
+ "command": "node",
554
+ "args": ["${config.targetDir}/dist/index.js"]
555
+ }
556
+ }
557
+ }
558
+ \`\`\`
559
+
560
+ ## Development
561
+
562
+ \`\`\`bash
563
+ npm run dev # Watch mode
564
+ npm start # Run the server
565
+ \`\`\`
566
+
567
+ ## Adding Tools
568
+
569
+ Create a new file in \`src/tools/\` following the existing pattern:
570
+
571
+ \`\`\`typescript
572
+ import { z } from 'zod';
573
+
574
+ export const myTool = {
575
+ name: 'my_tool',
576
+ description: 'Description of what this tool does',
577
+ inputSchema: {
578
+ param1: z.string().describe('First parameter'),
579
+ param2: z.number().optional().describe('Optional number'),
580
+ },
581
+ handler: async ({ param1, param2 }) => {
582
+ // Your implementation
583
+ return {
584
+ content: [{ type: 'text' as const, text: 'result' }],
585
+ };
586
+ },
587
+ };
588
+ \`\`\`
589
+
590
+ Then register it in \`src/index.ts\`.
591
+
592
+ ## Author
593
+
594
+ ${author}
595
+ `
596
+ }
597
+ ];
598
+ }
599
+
600
+ // src/templates/resource-server.ts
601
+ function generateResourceServer(config) {
602
+ const { serverName, serverDescription, packageName, author } = config;
603
+ return [
604
+ {
605
+ path: "src/index.ts",
606
+ content: `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
607
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
608
+ import { fileSystemResources } from './resources/fileSystem.js';
609
+ import { databaseResources } from './resources/database.js';
610
+ import { configResources } from './resources/config.js';
611
+
612
+ const server = new McpServer({
613
+ name: '${serverName}',
614
+ version: '0.1.0',
615
+ });
616
+
617
+ // Register resource providers
618
+ fileSystemResources.register(server);
619
+ databaseResources.register(server);
620
+ configResources.register(server);
621
+
622
+ async function main() {
623
+ const transport = new StdioServerTransport();
624
+ await server.connect(transport);
625
+ console.error('${serverName} MCP resource server running on stdio');
626
+ }
627
+
628
+ main().catch((err) => {
629
+ console.error('Fatal error:', err);
630
+ process.exit(1);
631
+ });
632
+ `
633
+ },
634
+ {
635
+ path: "src/resources/fileSystem.ts",
636
+ content: `import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
637
+ import fs from 'fs/promises';
638
+ import path from 'path';
639
+
640
+ /**
641
+ * File system resources \u2014 expose files and directories as MCP resources.
642
+ * URI pattern: file:///absolute/path/to/file
643
+ */
644
+ export const fileSystemResources = {
645
+ register(server: McpServer) {
646
+ // Static resource: list files in a directory
647
+ server.resource(
648
+ 'workspace-files',
649
+ 'file:///workspace',
650
+ async (uri) => {
651
+ const dir = process.cwd();
652
+ try {
653
+ const entries = await fs.readdir(dir, { withFileTypes: true });
654
+ const listing = entries
655
+ .map((e) => \`\${e.isDirectory() ? '[dir]' : '[file]'} \${e.name}\`)
656
+ .join('\\n');
657
+
658
+ return {
659
+ contents: [{
660
+ uri: uri.href,
661
+ mimeType: 'text/plain',
662
+ text: \`Files in \${dir}:\\n\\n\${listing}\`,
663
+ }],
664
+ };
665
+ } catch (err: any) {
666
+ throw new Error(\`Failed to list directory: \${err.message}\`);
667
+ }
668
+ },
669
+ );
670
+
671
+ // Dynamic resource template: read any file by URI
672
+ server.resource(
673
+ 'file',
674
+ new ResourceTemplate('file://{path}', { list: undefined }),
675
+ async (uri, params) => {
676
+ const filePath = decodeURIComponent(params.path as string);
677
+ const resolvedPath = path.isAbsolute(filePath)
678
+ ? filePath
679
+ : path.resolve(process.cwd(), filePath);
680
+
681
+ try {
682
+ const stat = await fs.stat(resolvedPath);
683
+
684
+ if (stat.isDirectory()) {
685
+ const entries = await fs.readdir(resolvedPath, { withFileTypes: true });
686
+ const listing = entries
687
+ .map((e) => \`\${e.isDirectory() ? '[dir] ' : '[file]'} \${e.name}\`)
688
+ .join('\\n');
689
+ return {
690
+ contents: [{
691
+ uri: uri.href,
692
+ mimeType: 'text/plain',
693
+ text: listing,
694
+ }],
695
+ };
696
+ }
697
+
698
+ const ext = path.extname(resolvedPath).toLowerCase();
699
+ const textExtensions = new Set([
700
+ '.ts', '.tsx', '.js', '.jsx', '.json', '.md', '.txt',
701
+ '.yaml', '.yml', '.toml', '.env', '.sh', '.bash', '.zsh',
702
+ '.py', '.rb', '.go', '.rs', '.java', '.c', '.cpp', '.h',
703
+ '.html', '.css', '.scss', '.less', '.xml', '.svg',
704
+ ]);
705
+
706
+ if (textExtensions.has(ext) || stat.size < 100000) {
707
+ const content = await fs.readFile(resolvedPath, 'utf8');
708
+ const mimeType = getMimeType(ext);
709
+
710
+ return {
711
+ contents: [{
712
+ uri: uri.href,
713
+ mimeType,
714
+ text: content,
715
+ }],
716
+ };
717
+ } else {
718
+ const data = await fs.readFile(resolvedPath);
719
+ return {
720
+ contents: [{
721
+ uri: uri.href,
722
+ mimeType: 'application/octet-stream',
723
+ blob: data.toString('base64'),
724
+ }],
725
+ };
726
+ }
727
+ } catch (err: any) {
728
+ throw new Error(\`Failed to read file \${resolvedPath}: \${err.message}\`);
729
+ }
730
+ },
731
+ );
732
+ },
733
+ };
734
+
735
+ function getMimeType(ext: string): string {
736
+ const map: Record<string, string> = {
737
+ '.ts': 'text/typescript',
738
+ '.tsx': 'text/typescript',
739
+ '.js': 'text/javascript',
740
+ '.jsx': 'text/javascript',
741
+ '.json': 'application/json',
742
+ '.md': 'text/markdown',
743
+ '.txt': 'text/plain',
744
+ '.yaml': 'text/yaml',
745
+ '.yml': 'text/yaml',
746
+ '.html': 'text/html',
747
+ '.css': 'text/css',
748
+ '.svg': 'image/svg+xml',
749
+ '.xml': 'application/xml',
750
+ '.py': 'text/x-python',
751
+ '.sh': 'text/x-sh',
752
+ };
753
+ return map[ext] ?? 'text/plain';
754
+ }
755
+ `
756
+ },
757
+ {
758
+ path: "src/resources/database.ts",
759
+ content: `import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
760
+
761
+ // Example: In-memory key-value store as a resource.
762
+ // Replace with your actual data source (SQLite, Postgres, Redis, etc.)
763
+ const store = new Map<string, unknown>([
764
+ ['example:config', { version: '1.0', feature_flags: { new_ui: true } }],
765
+ ['example:stats', { requests: 0, errors: 0, uptime: Date.now() }],
766
+ ]);
767
+
768
+ /**
769
+ * Database/store resources \u2014 expose structured data as MCP resources.
770
+ * URI pattern: db://namespace/key
771
+ *
772
+ * Replace the in-memory store with your actual database client.
773
+ */
774
+ export const databaseResources = {
775
+ register(server: McpServer) {
776
+ // List all available keys
777
+ server.resource(
778
+ 'db-index',
779
+ 'db://index',
780
+ async (uri) => {
781
+ const keys = Array.from(store.keys());
782
+ const listing = keys.map(k => \` \${k}\`).join('\\n');
783
+
784
+ return {
785
+ contents: [{
786
+ uri: uri.href,
787
+ mimeType: 'text/plain',
788
+ text: \`Available keys (\${keys.length}):\\n\${listing}\`,
789
+ }],
790
+ };
791
+ },
792
+ );
793
+
794
+ // Dynamic resource: fetch any key
795
+ server.resource(
796
+ 'db-record',
797
+ new ResourceTemplate('db://{namespace}/{key}', { list: undefined }),
798
+ async (uri, params) => {
799
+ const recordKey = \`\${params.namespace}:\${params.key}\`;
800
+ const value = store.get(recordKey);
801
+
802
+ if (value === undefined) {
803
+ throw new Error(\`Key not found: \${recordKey}\`);
804
+ }
805
+
806
+ return {
807
+ contents: [{
808
+ uri: uri.href,
809
+ mimeType: 'application/json',
810
+ text: JSON.stringify(value, null, 2),
811
+ }],
812
+ };
813
+ },
814
+ );
815
+ },
816
+ };
817
+ `
818
+ },
819
+ {
820
+ path: "src/resources/config.ts",
821
+ content: `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
822
+ import fs from 'fs/promises';
823
+ import path from 'path';
824
+
825
+ /**
826
+ * Config resources \u2014 expose runtime config, env vars, and app settings.
827
+ *
828
+ * These are useful for giving the AI context about the environment
829
+ * without exposing sensitive credentials.
830
+ */
831
+ export const configResources = {
832
+ register(server: McpServer) {
833
+ // Expose safe environment info
834
+ server.resource(
835
+ 'environment',
836
+ 'config://environment',
837
+ async (uri) => {
838
+ const safeEnv: Record<string, string> = {};
839
+
840
+ // Only expose non-sensitive env vars
841
+ const allowedPrefixes = ['NODE_', 'npm_', 'PATH', 'HOME', 'USER', 'SHELL', 'LANG'];
842
+ const blockedKeys = ['TOKEN', 'SECRET', 'PASSWORD', 'KEY', 'PRIVATE', 'AUTH', 'CREDENTIAL'];
843
+
844
+ for (const [key, value] of Object.entries(process.env)) {
845
+ if (value === undefined) continue;
846
+
847
+ const isBlocked = blockedKeys.some(b => key.toUpperCase().includes(b));
848
+ if (isBlocked) continue;
849
+
850
+ safeEnv[key] = value;
851
+ }
852
+
853
+ return {
854
+ contents: [{
855
+ uri: uri.href,
856
+ mimeType: 'application/json',
857
+ text: JSON.stringify({
858
+ nodeVersion: process.version,
859
+ platform: process.platform,
860
+ arch: process.arch,
861
+ cwd: process.cwd(),
862
+ env: safeEnv,
863
+ }, null, 2),
864
+ }],
865
+ };
866
+ },
867
+ );
868
+
869
+ // Expose package.json info
870
+ server.resource(
871
+ 'package-info',
872
+ 'config://package',
873
+ async (uri) => {
874
+ try {
875
+ const pkgPath = path.resolve(process.cwd(), 'package.json');
876
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
877
+
878
+ // Strip sensitive fields
879
+ const { _authToken, publishConfig, ...safePkg } = pkg;
880
+
881
+ return {
882
+ contents: [{
883
+ uri: uri.href,
884
+ mimeType: 'application/json',
885
+ text: JSON.stringify(safePkg, null, 2),
886
+ }],
887
+ };
888
+ } catch {
889
+ return {
890
+ contents: [{
891
+ uri: uri.href,
892
+ mimeType: 'text/plain',
893
+ text: 'No package.json found in current directory',
894
+ }],
895
+ };
896
+ }
897
+ },
898
+ );
899
+ },
900
+ };
901
+ `
902
+ },
903
+ {
904
+ path: "package.json",
905
+ content: JSON.stringify({
906
+ name: packageName,
907
+ version: "0.1.0",
908
+ description: serverDescription,
909
+ type: "module",
910
+ scripts: {
911
+ build: "tsc",
912
+ dev: "tsc --watch",
913
+ start: "node dist/index.js",
914
+ prepublishOnly: "npm run build"
915
+ },
916
+ engines: { node: ">=18.0.0" },
917
+ dependencies: {
918
+ "@modelcontextprotocol/sdk": "^1.0.0"
919
+ },
920
+ devDependencies: {
921
+ "@types/node": "^22.0.0",
922
+ typescript: "^5.0.0"
923
+ }
924
+ }, null, 2)
925
+ },
926
+ {
927
+ path: "tsconfig.json",
928
+ content: JSON.stringify({
929
+ compilerOptions: {
930
+ target: "ES2022",
931
+ module: "NodeNext",
932
+ moduleResolution: "NodeNext",
933
+ lib: ["ES2022"],
934
+ outDir: "dist",
935
+ rootDir: "src",
936
+ strict: true,
937
+ esModuleInterop: true,
938
+ skipLibCheck: true,
939
+ declaration: true,
940
+ sourceMap: true,
941
+ resolveJsonModule: true
942
+ },
943
+ include: ["src/**/*"],
944
+ exclude: ["node_modules", "dist"]
945
+ }, null, 2)
946
+ },
947
+ {
948
+ path: ".gitignore",
949
+ content: `node_modules/
950
+ dist/
951
+ *.js.map
952
+ *.d.ts.map
953
+ .env
954
+ .env.local
955
+ *.log
956
+ .DS_Store
957
+ `
958
+ },
959
+ {
960
+ path: "README.md",
961
+ content: `# ${serverName}
962
+
963
+ ${serverDescription}
964
+
965
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) resource server that exposes file system, database, and configuration data as addressable resources.
966
+
967
+ ## Resources
968
+
969
+ | URI Pattern | Description |
970
+ |-------------|-------------|
971
+ | \`file:///workspace\` | List files in working directory |
972
+ | \`file://{path}\` | Read any file by path |
973
+ | \`db://index\` | List all database keys |
974
+ | \`db://{namespace}/{key}\` | Fetch a specific record |
975
+ | \`config://environment\` | Safe environment info |
976
+ | \`config://package\` | Package.json info |
977
+
978
+ ## Setup
979
+
980
+ \`\`\`bash
981
+ npm install
982
+ npm run build
983
+ \`\`\`
984
+
985
+ ## Usage with Claude Desktop
986
+
987
+ Add to your Claude Desktop config:
988
+
989
+ \`\`\`json
990
+ {
991
+ "mcpServers": {
992
+ "${serverName}": {
993
+ "command": "node",
994
+ "args": ["${config.targetDir}/dist/index.js"]
995
+ }
996
+ }
997
+ }
998
+ \`\`\`
999
+
1000
+ ## Development
1001
+
1002
+ \`\`\`bash
1003
+ npm run dev # Watch mode
1004
+ npm start # Run the server
1005
+ \`\`\`
1006
+
1007
+ ## Connecting a Real Database
1008
+
1009
+ Replace the in-memory store in \`src/resources/database.ts\` with your actual data source:
1010
+
1011
+ \`\`\`typescript
1012
+ // Example with SQLite
1013
+ import Database from 'better-sqlite3';
1014
+ const db = new Database('./data.db');
1015
+
1016
+ // Example with Postgres
1017
+ import { Pool } from 'pg';
1018
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
1019
+ \`\`\`
1020
+
1021
+ ## Author
1022
+
1023
+ ${author}
1024
+ `
1025
+ }
1026
+ ];
1027
+ }
1028
+
1029
+ // src/templates/prompt-server.ts
1030
+ function generatePromptServer(config) {
1031
+ const { serverName, serverDescription, packageName, author } = config;
1032
+ return [
1033
+ {
1034
+ path: "src/index.ts",
1035
+ content: `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1036
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
1037
+ import { codeReviewPrompts } from './prompts/codeReview.js';
1038
+ import { documentationPrompts } from './prompts/documentation.js';
1039
+ import { debuggingPrompts } from './prompts/debugging.js';
1040
+ import { refactoringPrompts } from './prompts/refactoring.js';
1041
+
1042
+ const server = new McpServer({
1043
+ name: '${serverName}',
1044
+ version: '0.1.0',
1045
+ });
1046
+
1047
+ // Register prompt collections
1048
+ codeReviewPrompts.register(server);
1049
+ documentationPrompts.register(server);
1050
+ debuggingPrompts.register(server);
1051
+ refactoringPrompts.register(server);
1052
+
1053
+ async function main() {
1054
+ const transport = new StdioServerTransport();
1055
+ await server.connect(transport);
1056
+ console.error('${serverName} MCP prompt server running on stdio');
1057
+ }
1058
+
1059
+ main().catch((err) => {
1060
+ console.error('Fatal error:', err);
1061
+ process.exit(1);
1062
+ });
1063
+ `
1064
+ },
1065
+ {
1066
+ path: "src/prompts/codeReview.ts",
1067
+ content: `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1068
+ import { z } from 'zod';
1069
+
1070
+ /**
1071
+ * Code review prompt templates.
1072
+ * Structured prompts that guide thorough, consistent code reviews.
1073
+ */
1074
+ export const codeReviewPrompts = {
1075
+ register(server: McpServer) {
1076
+ server.prompt(
1077
+ 'code-review',
1078
+ 'Comprehensive code review with focus areas',
1079
+ {
1080
+ code: z.string().describe('The code to review'),
1081
+ language: z.string().optional().describe('Programming language (e.g. TypeScript, Python)'),
1082
+ context: z.string().optional().describe('What does this code do? Any relevant context?'),
1083
+ focus: z.enum(['security', 'performance', 'readability', 'correctness', 'all'])
1084
+ .optional()
1085
+ .default('all')
1086
+ .describe('Area to focus the review on'),
1087
+ },
1088
+ ({ code, language, context, focus }) => {
1089
+ const lang = language ?? 'unknown';
1090
+ const focusInstructions: Record<string, string> = {
1091
+ security: \`Focus specifically on security vulnerabilities:
1092
+ - Input validation and sanitization
1093
+ - Authentication and authorization flaws
1094
+ - Injection vulnerabilities (SQL, XSS, command)
1095
+ - Sensitive data exposure
1096
+ - Insecure dependencies\`,
1097
+ performance: \`Focus specifically on performance:
1098
+ - Time complexity of algorithms (Big O)
1099
+ - Unnecessary re-renders or recomputations
1100
+ - Memory leaks or excessive allocations
1101
+ - N+1 query problems
1102
+ - Missing indexes or caching opportunities\`,
1103
+ readability: \`Focus specifically on readability and maintainability:
1104
+ - Naming clarity (variables, functions, classes)
1105
+ - Function/method length and single responsibility
1106
+ - Comment quality (explain why, not what)
1107
+ - Consistent style and conventions
1108
+ - Dead code or confusing abstractions\`,
1109
+ correctness: \`Focus specifically on correctness:
1110
+ - Edge cases and boundary conditions
1111
+ - Error handling completeness
1112
+ - Race conditions or concurrency issues
1113
+ - Incorrect assumptions in logic
1114
+ - Missing null/undefined checks\`,
1115
+ all: \`Review all aspects: security, performance, readability, correctness, and design.\`,
1116
+ };
1117
+
1118
+ return {
1119
+ messages: [
1120
+ {
1121
+ role: 'user',
1122
+ content: {
1123
+ type: 'text',
1124
+ text: \`Please perform a detailed code review of the following \${lang} code.
1125
+
1126
+ \${context ? \`Context: \${context}\\n\\n\` : ''}\${focusInstructions[focus ?? 'all']}
1127
+
1128
+ For each issue found:
1129
+ 1. Quote the specific code
1130
+ 2. Explain the problem
1131
+ 3. Provide a concrete fix
1132
+
1133
+ Rate severity as: \u{1F534} Critical | \u{1F7E0} High | \u{1F7E1} Medium | \u{1F535} Low | \u{1F4A1} Suggestion
1134
+
1135
+ Code to review:
1136
+ \\\`\\\`\\\`\${lang}
1137
+ \${code}
1138
+ \\\`\\\`\\\`\`,
1139
+ },
1140
+ },
1141
+ ],
1142
+ };
1143
+ },
1144
+ );
1145
+
1146
+ server.prompt(
1147
+ 'security-audit',
1148
+ 'Security-focused code audit',
1149
+ {
1150
+ code: z.string().describe('Code to audit'),
1151
+ threat_model: z.string().optional().describe('Who are the potential attackers? What are they after?'),
1152
+ },
1153
+ ({ code, threat_model }) => ({
1154
+ messages: [
1155
+ {
1156
+ role: 'user',
1157
+ content: {
1158
+ type: 'text',
1159
+ text: \`Perform a security audit on this code.
1160
+
1161
+ \${threat_model ? \`Threat model: \${threat_model}\\n\\n\` : ''}
1162
+ Check for:
1163
+ - OWASP Top 10 vulnerabilities
1164
+ - Authentication/authorization bypasses
1165
+ - Data validation and sanitization
1166
+ - Cryptographic weaknesses
1167
+ - Dependency vulnerabilities
1168
+ - Secrets or credentials in code
1169
+ - Information disclosure
1170
+
1171
+ For each finding, provide:
1172
+ - CVE category (if applicable)
1173
+ - Severity (Critical/High/Medium/Low)
1174
+ - Attack vector
1175
+ - Remediation steps
1176
+
1177
+ Code:
1178
+ \\\`\\\`\\\`
1179
+ \${code}
1180
+ \\\`\\\`\\\`\`,
1181
+ },
1182
+ },
1183
+ ],
1184
+ }),
1185
+ );
1186
+ },
1187
+ };
1188
+ `
1189
+ },
1190
+ {
1191
+ path: "src/prompts/documentation.ts",
1192
+ content: `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1193
+ import { z } from 'zod';
1194
+
1195
+ /**
1196
+ * Documentation generation prompts.
1197
+ */
1198
+ export const documentationPrompts = {
1199
+ register(server: McpServer) {
1200
+ server.prompt(
1201
+ 'generate-docs',
1202
+ 'Generate comprehensive documentation for code',
1203
+ {
1204
+ code: z.string().describe('Code to document'),
1205
+ style: z.enum(['jsdoc', 'tsdoc', 'docstring', 'markdown']).optional().default('tsdoc'),
1206
+ audience: z.enum(['internal', 'public-api', 'tutorial']).optional().default('public-api'),
1207
+ },
1208
+ ({ code, style, audience }) => {
1209
+ const styleGuides: Record<string, string> = {
1210
+ jsdoc: 'Use JSDoc format with @param, @returns, @throws, @example tags',
1211
+ tsdoc: 'Use TSDoc format with @param, @returns, @throws, @example, @remarks tags',
1212
+ docstring: 'Use Python docstring format (Google style)',
1213
+ markdown: 'Generate a Markdown documentation section',
1214
+ };
1215
+
1216
+ const audienceGuides: Record<string, string> = {
1217
+ internal: 'This is for internal developers. Focus on implementation details and gotchas.',
1218
+ 'public-api': 'This is a public API. Focus on usage, parameters, and examples. Hide implementation details.',
1219
+ tutorial: 'This is for beginners. Explain concepts, provide step-by-step examples.',
1220
+ };
1221
+
1222
+ return {
1223
+ messages: [
1224
+ {
1225
+ role: 'user',
1226
+ content: {
1227
+ type: 'text',
1228
+ text: \`Generate documentation for the following code.
1229
+
1230
+ Format: \${styleGuides[style ?? 'tsdoc']}
1231
+ Audience: \${audienceGuides[audience ?? 'public-api']}
1232
+
1233
+ Include:
1234
+ - Overview/description
1235
+ - Parameter documentation with types
1236
+ - Return value documentation
1237
+ - Thrown errors/exceptions
1238
+ - At least one usage example
1239
+ - Any important notes or caveats
1240
+
1241
+ Code:
1242
+ \\\`\\\`\\\`
1243
+ \${code}
1244
+ \\\`\\\`\\\`\`,
1245
+ },
1246
+ },
1247
+ ],
1248
+ };
1249
+ },
1250
+ );
1251
+
1252
+ server.prompt(
1253
+ 'readme-generator',
1254
+ 'Generate a README.md for a project',
1255
+ {
1256
+ project_name: z.string().describe('Project name'),
1257
+ description: z.string().describe('What does this project do?'),
1258
+ tech_stack: z.string().describe('Technologies used (e.g. Node.js, TypeScript, React)'),
1259
+ main_features: z.string().describe('Key features, one per line'),
1260
+ },
1261
+ ({ project_name, description, tech_stack, main_features }) => ({
1262
+ messages: [
1263
+ {
1264
+ role: 'user',
1265
+ content: {
1266
+ type: 'text',
1267
+ text: \`Generate a professional README.md for this project.
1268
+
1269
+ Project: \${project_name}
1270
+ Description: \${description}
1271
+ Tech Stack: \${tech_stack}
1272
+ Features:
1273
+ \${main_features}
1274
+
1275
+ Include these sections:
1276
+ 1. Title and badges (placeholder)
1277
+ 2. Description
1278
+ 3. Features list
1279
+ 4. Installation
1280
+ 5. Quick start / usage example
1281
+ 6. API reference (if applicable)
1282
+ 7. Configuration
1283
+ 8. Contributing
1284
+ 9. License
1285
+
1286
+ Make it clear, engaging, and developer-friendly.\`,
1287
+ },
1288
+ },
1289
+ ],
1290
+ }),
1291
+ );
1292
+ },
1293
+ };
1294
+ `
1295
+ },
1296
+ {
1297
+ path: "src/prompts/debugging.ts",
1298
+ content: `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1299
+ import { z } from 'zod';
1300
+
1301
+ /**
1302
+ * Debugging assistance prompts.
1303
+ */
1304
+ export const debuggingPrompts = {
1305
+ register(server: McpServer) {
1306
+ server.prompt(
1307
+ 'debug-error',
1308
+ 'Debug an error with full context analysis',
1309
+ {
1310
+ error_message: z.string().describe('The error message or stack trace'),
1311
+ code: z.string().optional().describe('Relevant code where the error occurs'),
1312
+ expected: z.string().optional().describe('What you expected to happen'),
1313
+ actual: z.string().optional().describe('What actually happened'),
1314
+ environment: z.string().optional().describe('Runtime environment (e.g. Node 18, Chrome 120)'),
1315
+ },
1316
+ ({ error_message, code, expected, actual, environment }) => ({
1317
+ messages: [
1318
+ {
1319
+ role: 'user',
1320
+ content: {
1321
+ type: 'text',
1322
+ text: \`Help me debug this error.
1323
+
1324
+ Error:
1325
+ \\\`\\\`\\\`
1326
+ \${error_message}
1327
+ \\\`\\\`\\\`
1328
+ \${code ? \`\\nRelevant code:\\n\\\`\\\`\\\`\\n\${code}\\n\\\`\\\`\\\`\` : ''}
1329
+ \${expected ? \`\\nExpected: \${expected}\` : ''}
1330
+ \${actual ? \`\\nActual: \${actual}\` : ''}
1331
+ \${environment ? \`\\nEnvironment: \${environment}\` : ''}
1332
+
1333
+ Please:
1334
+ 1. Identify the root cause
1335
+ 2. Explain why this error occurs
1336
+ 3. Provide a step-by-step fix
1337
+ 4. Suggest how to prevent this in the future\`,
1338
+ },
1339
+ },
1340
+ ],
1341
+ }),
1342
+ );
1343
+
1344
+ server.prompt(
1345
+ 'rubber-duck',
1346
+ 'Explain code logic to find bugs through explanation',
1347
+ {
1348
+ code: z.string().describe('Code you want to explain/debug'),
1349
+ problem: z.string().describe('What problem are you trying to solve with this code?'),
1350
+ },
1351
+ ({ code, problem }) => ({
1352
+ messages: [
1353
+ {
1354
+ role: 'user',
1355
+ content: {
1356
+ type: 'text',
1357
+ text: \`I'll explain my code to you and you help me find bugs through questioning.
1358
+
1359
+ Problem I'm trying to solve: \${problem}
1360
+
1361
+ My code:
1362
+ \\\`\\\`\\\`
1363
+ \${code}
1364
+ \\\`\\\`\\\`
1365
+
1366
+ As I explain my code, please:
1367
+ 1. Ask clarifying questions about assumptions I'm making
1368
+ 2. Point out any logical inconsistencies
1369
+ 3. Highlight edge cases I might not be handling
1370
+ 4. Don't give me the answer directly \u2014 help me discover it through questions
1371
+
1372
+ Let's start: what questions do you have about my approach?\`,
1373
+ },
1374
+ },
1375
+ ],
1376
+ }),
1377
+ );
1378
+ },
1379
+ };
1380
+ `
1381
+ },
1382
+ {
1383
+ path: "src/prompts/refactoring.ts",
1384
+ content: `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1385
+ import { z } from 'zod';
1386
+
1387
+ /**
1388
+ * Code refactoring prompts.
1389
+ */
1390
+ export const refactoringPrompts = {
1391
+ register(server: McpServer) {
1392
+ server.prompt(
1393
+ 'refactor',
1394
+ 'Refactor code with specific goals',
1395
+ {
1396
+ code: z.string().describe('Code to refactor'),
1397
+ goals: z.string().describe('What do you want to improve? (e.g. reduce complexity, improve testability)'),
1398
+ constraints: z.string().optional().describe('Any constraints (e.g. must keep same API, cannot use classes)'),
1399
+ language: z.string().optional().describe('Programming language'),
1400
+ },
1401
+ ({ code, goals, constraints, language }) => ({
1402
+ messages: [
1403
+ {
1404
+ role: 'user',
1405
+ content: {
1406
+ type: 'text',
1407
+ text: \`Refactor this \${language ?? ''} code to achieve the following goals:
1408
+
1409
+ Goals: \${goals}
1410
+ \${constraints ? \`Constraints: \${constraints}\` : ''}
1411
+
1412
+ Original code:
1413
+ \\\`\\\`\\\`\${language ?? ''}
1414
+ \${code}
1415
+ \\\`\\\`\\\`
1416
+
1417
+ Please:
1418
+ 1. Explain your refactoring strategy
1419
+ 2. Provide the refactored code
1420
+ 3. Highlight what changed and why
1421
+ 4. Note any tradeoffs\`,
1422
+ },
1423
+ },
1424
+ ],
1425
+ }),
1426
+ );
1427
+
1428
+ server.prompt(
1429
+ 'extract-function',
1430
+ 'Extract logic into well-named functions',
1431
+ {
1432
+ code: z.string().describe('Code containing logic to extract'),
1433
+ description: z.string().describe('Describe the logic you want to extract'),
1434
+ },
1435
+ ({ code, description }) => ({
1436
+ messages: [
1437
+ {
1438
+ role: 'user',
1439
+ content: {
1440
+ type: 'text',
1441
+ text: \`Help me extract the following logic into a well-named function:
1442
+
1443
+ Logic to extract: \${description}
1444
+
1445
+ From this code:
1446
+ \\\`\\\`\\\`
1447
+ \${code}
1448
+ \\\`\\\`\\\`
1449
+
1450
+ Please provide:
1451
+ 1. The extracted function with a clear, descriptive name
1452
+ 2. The modified original code calling the new function
1453
+ 3. Any parameters the function needs
1454
+ 4. Proper TypeScript types if applicable\`,
1455
+ },
1456
+ },
1457
+ ],
1458
+ }),
1459
+ );
1460
+ },
1461
+ };
1462
+ `
1463
+ },
1464
+ {
1465
+ path: "package.json",
1466
+ content: JSON.stringify({
1467
+ name: packageName,
1468
+ version: "0.1.0",
1469
+ description: serverDescription,
1470
+ type: "module",
1471
+ scripts: {
1472
+ build: "tsc",
1473
+ dev: "tsc --watch",
1474
+ start: "node dist/index.js",
1475
+ prepublishOnly: "npm run build"
1476
+ },
1477
+ engines: { node: ">=18.0.0" },
1478
+ dependencies: {
1479
+ "@modelcontextprotocol/sdk": "^1.0.0",
1480
+ zod: "^3.22.0"
1481
+ },
1482
+ devDependencies: {
1483
+ "@types/node": "^22.0.0",
1484
+ typescript: "^5.0.0"
1485
+ }
1486
+ }, null, 2)
1487
+ },
1488
+ {
1489
+ path: "tsconfig.json",
1490
+ content: JSON.stringify({
1491
+ compilerOptions: {
1492
+ target: "ES2022",
1493
+ module: "NodeNext",
1494
+ moduleResolution: "NodeNext",
1495
+ lib: ["ES2022"],
1496
+ outDir: "dist",
1497
+ rootDir: "src",
1498
+ strict: true,
1499
+ esModuleInterop: true,
1500
+ skipLibCheck: true,
1501
+ declaration: true,
1502
+ sourceMap: true,
1503
+ resolveJsonModule: true
1504
+ },
1505
+ include: ["src/**/*"],
1506
+ exclude: ["node_modules", "dist"]
1507
+ }, null, 2)
1508
+ },
1509
+ {
1510
+ path: ".gitignore",
1511
+ content: `node_modules/
1512
+ dist/
1513
+ *.js.map
1514
+ *.d.ts.map
1515
+ .env
1516
+ .env.local
1517
+ *.log
1518
+ .DS_Store
1519
+ `
1520
+ },
1521
+ {
1522
+ path: "README.md",
1523
+ content: `# ${serverName}
1524
+
1525
+ ${serverDescription}
1526
+
1527
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) prompt server that provides structured, high-quality prompts for software development tasks.
1528
+
1529
+ ## Prompts
1530
+
1531
+ ### Code Review
1532
+ | Prompt | Description |
1533
+ |--------|-------------|
1534
+ | \`code-review\` | Comprehensive review with severity ratings |
1535
+ | \`security-audit\` | Security-focused code audit |
1536
+
1537
+ ### Documentation
1538
+ | Prompt | Description |
1539
+ |--------|-------------|
1540
+ | \`generate-docs\` | Generate JSDoc/TSDoc/docstrings |
1541
+ | \`readme-generator\` | Generate a full README.md |
1542
+
1543
+ ### Debugging
1544
+ | Prompt | Description |
1545
+ |--------|-------------|
1546
+ | \`debug-error\` | Debug errors with full context |
1547
+ | \`rubber-duck\` | Guided explanation-based debugging |
1548
+
1549
+ ### Refactoring
1550
+ | Prompt | Description |
1551
+ |--------|-------------|
1552
+ | \`refactor\` | Refactor with specific goals and constraints |
1553
+ | \`extract-function\` | Extract logic into named functions |
1554
+
1555
+ ## Setup
1556
+
1557
+ \`\`\`bash
1558
+ npm install
1559
+ npm run build
1560
+ \`\`\`
1561
+
1562
+ ## Usage with Claude Desktop
1563
+
1564
+ \`\`\`json
1565
+ {
1566
+ "mcpServers": {
1567
+ "${serverName}": {
1568
+ "command": "node",
1569
+ "args": ["${config.targetDir}/dist/index.js"]
1570
+ }
1571
+ }
1572
+ }
1573
+ \`\`\`
1574
+
1575
+ ## Development
1576
+
1577
+ \`\`\`bash
1578
+ npm run dev # Watch mode
1579
+ npm start # Run the server
1580
+ \`\`\`
1581
+
1582
+ ## Adding Prompts
1583
+
1584
+ Create a new file in \`src/prompts/\` and register it in \`src/index.ts\`:
1585
+
1586
+ \`\`\`typescript
1587
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1588
+ import { z } from 'zod';
1589
+
1590
+ export const myPrompts = {
1591
+ register(server: McpServer) {
1592
+ server.prompt(
1593
+ 'my-prompt-name',
1594
+ 'Description of what this prompt does',
1595
+ {
1596
+ input: z.string().describe('User input'),
1597
+ },
1598
+ ({ input }) => ({
1599
+ messages: [
1600
+ {
1601
+ role: 'user',
1602
+ content: { type: 'text', text: \`Do something with: \${input}\` },
1603
+ },
1604
+ ],
1605
+ }),
1606
+ );
1607
+ },
1608
+ };
1609
+ \`\`\`
1610
+
1611
+ ## Author
1612
+
1613
+ ${author}
1614
+ `
1615
+ }
1616
+ ];
1617
+ }
1618
+
1619
+ // src/commands/init.ts
1620
+ async function initCommand(dirArg, opts) {
1621
+ const targetName = dirArg ?? ".";
1622
+ const targetDir = path2.resolve(process.cwd(), targetName);
1623
+ const dirName = path2.basename(targetDir);
1624
+ console.log();
1625
+ console.log(chalk2.bold.cyan("\u26A1 create-mcp-server") + chalk2.dim(" \u2014 scaffold your MCP server"));
1626
+ console.log();
1627
+ const targetExists = await fs2.pathExists(targetDir);
1628
+ if (targetExists && !opts.dryRun) {
1629
+ const entries = await fs2.readdir(targetDir).catch(() => []);
1630
+ const hasContent = entries.some((e) => !e.startsWith("."));
1631
+ if (hasContent && !opts.force) {
1632
+ console.log(chalk2.yellow(`\u26A0\uFE0F Directory ${chalk2.bold(targetDir)} already has files.`));
1633
+ console.log(chalk2.dim(" Existing files will be skipped. Use --force to overwrite."));
1634
+ console.log();
1635
+ }
1636
+ }
1637
+ let config;
1638
+ const isInteractive = !opts.yes && process.stdin.isTTY;
1639
+ if (isInteractive) {
1640
+ const answers = await runWizard({
1641
+ serverName: dirName !== "." ? dirName : void 0,
1642
+ template: opts.template
1643
+ });
1644
+ config = buildDefaultConfig(targetDir, dirName, {
1645
+ serverName: answers.serverName,
1646
+ serverDescription: answers.serverDescription,
1647
+ template: answers.template,
1648
+ packageName: answers.packageName,
1649
+ author: answers.author,
1650
+ gitInit: answers.gitInit,
1651
+ dryRun: opts.dryRun ?? false
1652
+ });
1653
+ } else {
1654
+ config = buildDefaultConfig(targetDir, dirName, {
1655
+ serverName: dirName !== "." ? dirName : "my-mcp-server",
1656
+ packageName: dirName !== "." ? dirName : "my-mcp-server",
1657
+ template: opts.template ?? "tool-server",
1658
+ dryRun: opts.dryRun ?? false,
1659
+ yes: true
1660
+ });
1661
+ console.log(chalk2.dim(`Running with defaults. Template: ${config.template}`));
1662
+ console.log();
1663
+ }
1664
+ const files = getTemplateFiles(config);
1665
+ if (config.dryRun) {
1666
+ console.log(chalk2.bold.yellow("\u{1F50D} Dry run \u2014 no files will be written\n"));
1667
+ printDryRunTree(config.targetDir, files);
1668
+ console.log(chalk2.dim(` ${files.length} files would be created`));
1669
+ console.log();
1670
+ console.log(chalk2.dim("Run without --dry-run to scaffold the server."));
1671
+ return;
1672
+ }
1673
+ const spinner = ora("Scaffolding MCP server...").start();
1674
+ let results;
1675
+ try {
1676
+ results = await writeFiles(config.targetDir, files, opts.force ?? false);
1677
+ spinner.succeed("Server scaffolded");
1678
+ } catch (err) {
1679
+ spinner.fail("Failed to write files");
1680
+ throw err;
1681
+ }
1682
+ if (config.gitInit) {
1683
+ const gitSpinner = ora("Initializing git...").start();
1684
+ try {
1685
+ const { simpleGit } = await import("simple-git");
1686
+ const git = simpleGit(config.targetDir);
1687
+ const isRepo = await git.checkIsRepo().catch(() => false);
1688
+ if (!isRepo) {
1689
+ await git.init();
1690
+ gitSpinner.succeed("Git repository initialized");
1691
+ } else {
1692
+ gitSpinner.info("Git already initialized, skipped");
1693
+ }
1694
+ } catch {
1695
+ gitSpinner.warn("Git init failed (git may not be installed)");
1696
+ }
1697
+ }
1698
+ const created = results.filter((r) => r.status === "created");
1699
+ const skipped = results.filter((r) => r.status === "skipped");
1700
+ console.log();
1701
+ console.log(chalk2.bold.green("\u2705 MCP server scaffolded at ") + chalk2.bold(config.targetDir));
1702
+ console.log();
1703
+ printWriteResults(results);
1704
+ console.log();
1705
+ console.log(chalk2.dim(` ${created.length} file(s) created${skipped.length > 0 ? `, ${skipped.length} skipped` : ""}`));
1706
+ console.log();
1707
+ console.log(chalk2.bold("Next steps:"));
1708
+ const rel = path2.relative(process.cwd(), config.targetDir);
1709
+ if (rel && rel !== ".") {
1710
+ console.log(` ${chalk2.cyan(`cd ${rel}`)}`);
1711
+ }
1712
+ console.log(` ${chalk2.cyan("npm install")}`);
1713
+ console.log(` ${chalk2.cyan("npm run build")}`);
1714
+ console.log(` ${chalk2.cyan("npm start")}`);
1715
+ console.log();
1716
+ console.log(chalk2.dim(" Then add to Claude Desktop config:"));
1717
+ console.log(chalk2.dim(` "command": "node", "args": ["${config.targetDir}/dist/index.js"]`));
1718
+ console.log();
1719
+ console.log(chalk2.dim(" See README.md for full setup instructions."));
1720
+ console.log();
1721
+ }
1722
+ function getTemplateFiles(config) {
1723
+ switch (config.template) {
1724
+ case "tool-server":
1725
+ return generateToolServer(config);
1726
+ case "resource-server":
1727
+ return generateResourceServer(config);
1728
+ case "prompt-server":
1729
+ return generatePromptServer(config);
1730
+ default:
1731
+ return generateToolServer(config);
1732
+ }
1733
+ }
1734
+
1735
+ // src/cli.ts
1736
+ var VERSION = "0.1.0";
1737
+ var program = new Command();
1738
+ program.name("create-mcp-server").description("Scaffold production-ready MCP (Model Context Protocol) servers").version(VERSION, "-v, --version", "Show version").addHelpText(
1739
+ "after",
1740
+ `
1741
+ ${chalk3.bold("Examples:")}
1742
+ ${chalk3.cyan("npx @webbywisp/create-mcp-server my-server")} Scaffold with prompts
1743
+ ${chalk3.cyan("npx @webbywisp/create-mcp-server my-server --yes")} Use all defaults (tool-server)
1744
+ ${chalk3.cyan("npx @webbywisp/create-mcp-server my-server -t resource-server")} Use resource-server template
1745
+ ${chalk3.cyan("npx @webbywisp/create-mcp-server my-server --dry-run")} Preview without writing files
1746
+
1747
+ ${chalk3.bold("Templates:")}
1748
+ ${chalk3.cyan("tool-server")} Expose tools (functions AI can call) \u2014 file I/O, HTTP, exec
1749
+ ${chalk3.cyan("resource-server")} Expose data as addressable URIs \u2014 files, DB, config
1750
+ ${chalk3.cyan("prompt-server")} Provide structured prompt templates \u2014 review, debug, refactor
1751
+ `
1752
+ );
1753
+ program.command("init [directory]", { isDefault: true }).description("Scaffold a new MCP server (default command)").option("-t, --template <name>", "Template: tool-server | resource-server | prompt-server", "tool-server").option("-y, --yes", "Skip prompts, use all defaults").option("--dry-run", "Preview what would be created without writing files").option("--force", "Overwrite existing files").action(async (dir, opts) => {
1754
+ try {
1755
+ await initCommand(dir, {
1756
+ template: opts.template,
1757
+ yes: opts.yes,
1758
+ dryRun: opts.dryRun,
1759
+ force: opts.force
1760
+ });
1761
+ } catch (err) {
1762
+ handleError(err);
1763
+ }
1764
+ });
1765
+ program.command("list").description("Show available templates").action(() => {
1766
+ console.log();
1767
+ console.log(chalk3.bold("Available Templates:"));
1768
+ console.log();
1769
+ const templates = [
1770
+ {
1771
+ name: "tool-server",
1772
+ desc: "Expose callable tools to AI models",
1773
+ includes: "read_file, write_file, http_fetch, exec_command"
1774
+ },
1775
+ {
1776
+ name: "resource-server",
1777
+ desc: "Expose data as addressable resources",
1778
+ includes: "file://, db://, config:// URI schemes"
1779
+ },
1780
+ {
1781
+ name: "prompt-server",
1782
+ desc: "Provide structured prompt templates",
1783
+ includes: "code-review, debug-error, generate-docs, refactor"
1784
+ }
1785
+ ];
1786
+ templates.forEach(({ name, desc, includes }) => {
1787
+ console.log(` ${chalk3.cyan.bold(name.padEnd(18))} ${desc}`);
1788
+ console.log(` ${" ".repeat(18)} ${chalk3.dim("Includes: " + includes)}`);
1789
+ console.log();
1790
+ });
1791
+ });
1792
+ function handleError(err) {
1793
+ if (err instanceof Error) {
1794
+ console.error(chalk3.red(`
1795
+ \u274C Error: ${err.message}`));
1796
+ if (process.env.DEBUG) {
1797
+ console.error(chalk3.dim(err.stack ?? ""));
1798
+ }
1799
+ } else {
1800
+ console.error(chalk3.red("\n\u274C An unexpected error occurred"));
1801
+ console.error(err);
1802
+ }
1803
+ process.exit(1);
1804
+ }
1805
+ program.parseAsync(process.argv).catch(handleError);
1806
+ //# sourceMappingURL=cli.js.map