@agentailor/create-mcp-server 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,59 @@
1
1
  # @agentailor/create-mcp-server
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@agentailor/create-mcp-server.svg)](https://www.npmjs.com/package/@agentailor/create-mcp-server)
4
+ [![Test](https://github.com/agentailor/create-mcp-server/actions/workflows/test.yml/badge.svg)](https://github.com/agentailor/create-mcp-server/actions/workflows/test.yml)
5
+ [![npm downloads](https://img.shields.io/npm/dt/@agentailor/create-mcp-server.svg)](https://www.npmjs.com/package/@agentailor/create-mcp-server)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js Version](https://img.shields.io/node/v/@agentailor/create-mcp-server.svg)](https://nodejs.org/)
8
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/agentailor/create-mcp-server/pulls)
9
+
3
10
  Scaffold production-ready MCP servers in seconds.
4
11
 
5
12
  ## Quick Start
6
13
 
14
+ **Interactive mode** (guided prompts):
15
+
7
16
  ```bash
8
17
  npx @agentailor/create-mcp-server
9
18
  ```
10
19
 
20
+ **CLI mode** (all options via arguments):
21
+
22
+ ```bash
23
+ npx @agentailor/create-mcp-server --name=my-server
24
+ ```
25
+
26
+ ## CLI Options
27
+
28
+ | Option | Short | Default | Description |
29
+ |--------|-------|---------|-------------|
30
+ | `--name` | `-n` | — | Project name (required in CLI mode) |
31
+ | `--package-manager` | `-p` | `npm` | Package manager: npm, pnpm, yarn |
32
+ | `--framework` | `-f` | `sdk` | Framework: sdk, fastmcp |
33
+ | `--template` | `-t` | `stateless` | Server mode: stateless, stateful |
34
+ | `--oauth` | — | `false` | Enable OAuth (sdk+stateful only) |
35
+ | `--no-git` | — | `false` | Skip git initialization |
36
+ | `--help` | `-h` | — | Show help |
37
+ | `--version` | `-V` | — | Show version |
38
+
39
+ **Examples:**
40
+
41
+ ```bash
42
+ # Minimal - uses all defaults
43
+ npx @agentailor/create-mcp-server --name=my-server
44
+
45
+ # Full options
46
+ npx @agentailor/create-mcp-server \
47
+ --name=my-auth-server \
48
+ --package-manager=pnpm \
49
+ --framework=sdk \
50
+ --template=stateful \
51
+ --oauth
52
+
53
+ # Short flags
54
+ npx @agentailor/create-mcp-server -n my-server -p yarn -f fastmcp
55
+ ```
56
+
11
57
  ## Features
12
58
 
13
59
  - **Two frameworks** — Official MCP SDK or FastMCP
@@ -80,6 +126,25 @@ my-mcp-server/
80
126
  - `npm run dev` — build and start the server
81
127
  - `npm run inspect` — open MCP Inspector (update URL in `package.json` if needed)
82
128
 
129
+ ## Learning Resources
130
+
131
+ | Guide | Description |
132
+ |-------|-------------|
133
+ | [Create Your First MCP Server in 5 Minutes](https://blog.agentailor.com/posts/create-your-first-mcp-server-in-5-minutes?utm_source=github&utm_medium=readme&utm_campaign=create-mcp-server) | Build your first production-ready MCP server. A complete beginner guide to scaffolding a Fetch MCP server with TypeScript. |
134
+ | [Securing MCP Servers with Keycloak](https://blog.agentailor.com/posts/oauth-for-mcp-servers-practical-guide-keycloak?utm_source=github&utm_medium=readme&utm_campaign=create-mcp-server) | Learn how to secure your MCP servers with OAuth authentication using Keycloak. |
135
+ | [Getting Started with FastMCP](https://blog.agentailor.com/posts/getting-started-with-fastmcp?utm_source=github&utm_medium=readme&utm_campaign=create-mcp-server) | Build MCP servers faster with FastMCP — the TypeScript framework inspired by Python's most popular MCP library. |
136
+ | [OAuth for MCP Clients (Next.js + LangGraph.js)](https://blog.agentailor.com/posts/mcp-client-oauth-nextjs-langgraph?utm_source=github&utm_medium=readme&utm_campaign=create-mcp-server) | Implement OAuth authentication in your MCP client using Next.js and the MCP SDK. |
137
+
138
+ ## Need help building MCP servers or agent infrastructure?
139
+
140
+ I help teams design and ship production-ready AI agent systems (MCP, LangGraph, RAG, memory, performance).
141
+
142
+ If you’re building something serious on top of this:
143
+
144
+ → [DM me on LinkedIn](https://www.linkedin.com/in/ali-ibrahim-junior/)
145
+
146
+ Happy to jump on a short call.
147
+
83
148
  ## What is MCP?
84
149
 
85
150
  The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) is an open protocol that enables AI assistants to interact with external tools, data sources, and services.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { PackageManager, Framework } from './templates/common/types.js';
2
+ export type TemplateType = 'stateless' | 'stateful';
3
+ export interface CLIOptions {
4
+ name: string;
5
+ packageManager: PackageManager;
6
+ framework: Framework;
7
+ template: TemplateType;
8
+ oauth: boolean;
9
+ git: boolean;
10
+ }
11
+ export interface ParseResult {
12
+ mode: 'interactive' | 'cli';
13
+ options?: CLIOptions;
14
+ }
15
+ export declare function parseArguments(): ParseResult;
package/dist/cli.js ADDED
@@ -0,0 +1,60 @@
1
+ import { Command, Option, InvalidArgumentError } from 'commander';
2
+ const NAME_REGEX = /^[a-z0-9-_]+$/i;
3
+ const VERSION = '0.4.1';
4
+ function validateName(value) {
5
+ if (!NAME_REGEX.test(value)) {
6
+ throw new InvalidArgumentError('Project name can only contain letters, numbers, hyphens, and underscores');
7
+ }
8
+ return value;
9
+ }
10
+ export function parseArguments() {
11
+ const program = new Command();
12
+ program
13
+ .name('create-mcp-server')
14
+ .description('Create a new MCP (Model Context Protocol) server project')
15
+ .version(VERSION)
16
+ .option('-n, --name <name>', 'Project name', validateName)
17
+ .addOption(new Option('-p, --package-manager <manager>', 'Package manager')
18
+ .choices(['npm', 'pnpm', 'yarn'])
19
+ .default('npm'))
20
+ .addOption(new Option('-f, --framework <framework>', 'Framework to use')
21
+ .choices(['sdk', 'fastmcp'])
22
+ .default('sdk'))
23
+ .addOption(new Option('-t, --template <type>', 'Template type')
24
+ .choices(['stateless', 'stateful'])
25
+ .default('stateless'))
26
+ .option('--oauth', 'Enable OAuth authentication (sdk+stateful only)', false)
27
+ .option('--no-git', 'Skip git repository initialization');
28
+ program.parse();
29
+ const opts = program.opts();
30
+ // Detect mode based on whether any option was explicitly provided
31
+ // Check for CLI args (excluding node, script path, --help, --version)
32
+ const cliArgs = process.argv.slice(2);
33
+ const hasExplicitArgs = cliArgs.some((arg) => arg.startsWith('-') && !['--help', '-h', '--version', '-V'].includes(arg));
34
+ if (!hasExplicitArgs) {
35
+ return { mode: 'interactive' };
36
+ }
37
+ // CLI mode - validate required args
38
+ if (!opts.name) {
39
+ console.error('\nError: --name is required when using CLI arguments\n');
40
+ console.error('Usage: create-mcp-server --name=my-server [options]\n');
41
+ console.error('Run with --help for all options.\n');
42
+ process.exit(1);
43
+ }
44
+ // Validate OAuth constraint
45
+ if (opts.oauth && (opts.framework !== 'sdk' || opts.template !== 'stateful')) {
46
+ console.error('\nError: --oauth is only valid with --framework=sdk and --template=stateful\n');
47
+ process.exit(1);
48
+ }
49
+ return {
50
+ mode: 'cli',
51
+ options: {
52
+ name: opts.name,
53
+ packageManager: opts.packageManager,
54
+ framework: opts.framework,
55
+ template: opts.template,
56
+ oauth: opts.oauth,
57
+ git: opts.git, // Commander handles --no-git -> git: false
58
+ },
59
+ };
60
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,145 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ // We need to test the CLI argument parsing logic
3
+ // Commander modifies process.argv, so we need to mock it carefully
4
+ describe('CLI argument parsing', () => {
5
+ const originalArgv = process.argv;
6
+ const originalExit = process.exit;
7
+ beforeEach(() => {
8
+ // Reset modules to clear commander's state
9
+ vi.resetModules();
10
+ });
11
+ afterEach(() => {
12
+ process.argv = originalArgv;
13
+ process.exit = originalExit;
14
+ });
15
+ it('returns interactive mode when no arguments provided', async () => {
16
+ process.argv = ['node', 'create-mcp-server'];
17
+ const { parseArguments } = await import('./cli.js');
18
+ const result = parseArguments();
19
+ expect(result.mode).toBe('interactive');
20
+ expect(result.options).toBeUndefined();
21
+ });
22
+ it('returns cli mode with valid arguments', async () => {
23
+ process.argv = ['node', 'create-mcp-server', '--name=my-server'];
24
+ const { parseArguments } = await import('./cli.js');
25
+ const result = parseArguments();
26
+ expect(result.mode).toBe('cli');
27
+ expect(result.options?.name).toBe('my-server');
28
+ });
29
+ it('uses defaults for optional arguments', async () => {
30
+ process.argv = ['node', 'create-mcp-server', '--name=my-server'];
31
+ const { parseArguments } = await import('./cli.js');
32
+ const result = parseArguments();
33
+ expect(result.options?.packageManager).toBe('npm');
34
+ expect(result.options?.framework).toBe('sdk');
35
+ expect(result.options?.template).toBe('stateless');
36
+ expect(result.options?.oauth).toBe(false);
37
+ expect(result.options?.git).toBe(true);
38
+ });
39
+ it('parses all arguments correctly', async () => {
40
+ process.argv = [
41
+ 'node',
42
+ 'create-mcp-server',
43
+ '--name=test-project',
44
+ '--package-manager=pnpm',
45
+ '--framework=fastmcp',
46
+ '--template=stateful',
47
+ '--no-git',
48
+ ];
49
+ const { parseArguments } = await import('./cli.js');
50
+ const result = parseArguments();
51
+ expect(result.options).toEqual({
52
+ name: 'test-project',
53
+ packageManager: 'pnpm',
54
+ framework: 'fastmcp',
55
+ template: 'stateful',
56
+ oauth: false,
57
+ git: false,
58
+ });
59
+ });
60
+ it('parses short flags correctly', async () => {
61
+ process.argv = [
62
+ 'node',
63
+ 'create-mcp-server',
64
+ '-n',
65
+ 'my-project',
66
+ '-p',
67
+ 'yarn',
68
+ '-f',
69
+ 'sdk',
70
+ '-t',
71
+ 'stateful',
72
+ ];
73
+ const { parseArguments } = await import('./cli.js');
74
+ const result = parseArguments();
75
+ expect(result.options?.name).toBe('my-project');
76
+ expect(result.options?.packageManager).toBe('yarn');
77
+ expect(result.options?.framework).toBe('sdk');
78
+ expect(result.options?.template).toBe('stateful');
79
+ });
80
+ it('parses oauth flag correctly for sdk+stateful', async () => {
81
+ process.argv = [
82
+ 'node',
83
+ 'create-mcp-server',
84
+ '--name=my-auth-server',
85
+ '--framework=sdk',
86
+ '--template=stateful',
87
+ '--oauth',
88
+ ];
89
+ const { parseArguments } = await import('./cli.js');
90
+ const result = parseArguments();
91
+ expect(result.options?.oauth).toBe(true);
92
+ expect(result.options?.framework).toBe('sdk');
93
+ expect(result.options?.template).toBe('stateful');
94
+ });
95
+ it('exits with error when --name is missing in CLI mode', async () => {
96
+ process.argv = ['node', 'create-mcp-server', '--package-manager=npm'];
97
+ let exitCode;
98
+ process.exit = vi.fn((code) => {
99
+ exitCode = code;
100
+ throw new Error('process.exit called');
101
+ });
102
+ const consoleError = vi.spyOn(console, 'error').mockImplementation(() => { });
103
+ const { parseArguments } = await import('./cli.js');
104
+ expect(() => parseArguments()).toThrow('process.exit called');
105
+ expect(exitCode).toBe(1);
106
+ expect(consoleError).toHaveBeenCalledWith(expect.stringContaining('--name is required when using CLI arguments'));
107
+ consoleError.mockRestore();
108
+ });
109
+ it('exits with error when --oauth used without sdk+stateful', async () => {
110
+ process.argv = ['node', 'create-mcp-server', '--name=test', '--oauth', '--framework=fastmcp'];
111
+ let exitCode;
112
+ process.exit = vi.fn((code) => {
113
+ exitCode = code;
114
+ throw new Error('process.exit called');
115
+ });
116
+ const consoleError = vi.spyOn(console, 'error').mockImplementation(() => { });
117
+ const { parseArguments } = await import('./cli.js');
118
+ expect(() => parseArguments()).toThrow('process.exit called');
119
+ expect(exitCode).toBe(1);
120
+ expect(consoleError).toHaveBeenCalledWith(expect.stringContaining('--oauth is only valid with --framework=sdk and --template=stateful'));
121
+ consoleError.mockRestore();
122
+ });
123
+ it('exits with error for invalid project name', async () => {
124
+ process.argv = ['node', 'create-mcp-server', '--name=invalid name!'];
125
+ let exitCode;
126
+ process.exit = vi.fn((code) => {
127
+ exitCode = code;
128
+ throw new Error('process.exit called');
129
+ });
130
+ const consoleError = vi.spyOn(console, 'error').mockImplementation(() => { });
131
+ const { parseArguments } = await import('./cli.js');
132
+ expect(() => parseArguments()).toThrow('process.exit called');
133
+ expect(exitCode).toBe(1);
134
+ consoleError.mockRestore();
135
+ });
136
+ it('treats --help flag as interactive mode (does not require --name)', async () => {
137
+ // When --help is the only flag, commander handles it and exits
138
+ // We just verify that --help alone doesn't trigger CLI mode validation
139
+ process.argv = ['node', 'create-mcp-server'];
140
+ const { parseArguments } = await import('./cli.js');
141
+ const result = parseArguments();
142
+ // No args = interactive mode
143
+ expect(result.mode).toBe('interactive');
144
+ });
145
+ });
package/dist/index.js CHANGED
@@ -1,189 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import prompts from 'prompts';
3
- import { mkdir, writeFile } from 'node:fs/promises';
4
- import { join } from 'node:path';
5
- import { execSync } from 'node:child_process';
6
- import { getPackageJsonTemplate } from './templates/common/package.json.js';
7
- import { getTsconfigTemplate } from './templates/common/tsconfig.json.js';
8
- import { getGitignoreTemplate } from './templates/common/gitignore.js';
9
- import { getEnvExampleTemplate } from './templates/common/env.example.js';
10
- import { getServerTemplate as getSdkStatelessServerTemplate, getIndexTemplate as getSdkStatelessIndexTemplate, getReadmeTemplate as getSdkStatelessReadmeTemplate, } from './templates/sdk/stateless/index.js';
11
- import { getServerTemplate as getSdkStatefulServerTemplate, getIndexTemplate as getSdkStatefulIndexTemplate, getReadmeTemplate as getSdkStatefulReadmeTemplate, getAuthTemplate as getSdkAuthTemplate, } from './templates/sdk/stateful/index.js';
12
- import { getServerTemplate as getFastMCPServerTemplate, getIndexTemplate as getFastMCPIndexTemplate, getReadmeTemplate as getFastMCPReadmeTemplate, } from './templates/fastmcp/index.js';
13
- import { getDockerfileTemplate, getDockerignoreTemplate } from './templates/deployment/index.js';
14
- const sdkTemplateFunctions = {
15
- stateless: {
16
- getServerTemplate: getSdkStatelessServerTemplate,
17
- getIndexTemplate: getSdkStatelessIndexTemplate,
18
- getReadmeTemplate: getSdkStatelessReadmeTemplate,
19
- },
20
- stateful: {
21
- getServerTemplate: getSdkStatefulServerTemplate,
22
- getIndexTemplate: getSdkStatefulIndexTemplate,
23
- getReadmeTemplate: getSdkStatefulReadmeTemplate,
24
- getAuthTemplate: getSdkAuthTemplate,
25
- },
26
- };
27
- const fastmcpTemplateFunctions = {
28
- getServerTemplate: getFastMCPServerTemplate,
29
- getIndexTemplate: getFastMCPIndexTemplate,
30
- getReadmeTemplate: getFastMCPReadmeTemplate,
31
- };
32
- const packageManagerCommands = {
33
- npm: { install: 'npm install', dev: 'npm run dev' },
34
- pnpm: { install: 'pnpm install', dev: 'pnpm dev' },
35
- yarn: { install: 'yarn', dev: 'yarn dev' },
36
- };
2
+ import { parseArguments } from './cli.js';
3
+ import { runInteractiveMode } from './interactive.js';
4
+ import { generateProject } from './project-generator.js';
37
5
  async function main() {
38
- console.log('\n🚀 Create MCP Server\n');
39
- const onCancel = () => {
40
- console.log('\n❌ Operation cancelled\n');
41
- process.exit(0);
42
- };
43
- const projectNameResponse = await prompts({
44
- type: 'text',
45
- name: 'projectName',
46
- message: 'Project name:',
47
- initial: 'my-mcp-server',
48
- validate: (value) => {
49
- if (!value)
50
- return 'Project name is required';
51
- if (!/^[a-z0-9-_]+$/i.test(value)) {
52
- return 'Project name can only contain letters, numbers, hyphens, and underscores';
53
- }
54
- return true;
55
- },
56
- }, { onCancel });
57
- const { projectName } = projectNameResponse;
58
- if (!projectName) {
59
- console.log('\n❌ Project name is required\n');
60
- process.exit(1);
6
+ const { mode, options } = parseArguments();
7
+ if (mode === 'interactive') {
8
+ await runInteractiveMode();
61
9
  }
62
- const packageManagerResponse = await prompts({
63
- type: 'select',
64
- name: 'packageManager',
65
- message: 'Package manager:',
66
- choices: [
67
- { title: 'npm', value: 'npm' },
68
- { title: 'pnpm', value: 'pnpm' },
69
- { title: 'yarn', value: 'yarn' },
70
- ],
71
- initial: 0,
72
- }, { onCancel });
73
- const packageManager = packageManagerResponse.packageManager || 'npm';
74
- // Framework selection
75
- const frameworkResponse = await prompts({
76
- type: 'select',
77
- name: 'framework',
78
- message: 'Framework:',
79
- choices: [
80
- {
81
- title: 'Official MCP SDK',
82
- value: 'sdk',
83
- description: 'Full control with Express.js',
84
- },
85
- {
86
- title: 'FastMCP',
87
- value: 'fastmcp',
88
- description: 'Simpler API, less boilerplate',
89
- },
90
- ],
91
- initial: 0,
92
- }, { onCancel });
93
- const framework = frameworkResponse.framework || 'sdk';
94
- // Server mode selection
95
- const templateTypeResponse = await prompts({
96
- type: 'select',
97
- name: 'templateType',
98
- message: 'Server mode:',
99
- choices: [
100
- { title: 'Stateless', value: 'stateless', description: 'Simple HTTP server' },
101
- {
102
- title: 'Stateful',
103
- value: 'stateful',
104
- description: 'Session-based server with SSE support',
105
- },
106
- ],
107
- initial: 0,
108
- }, { onCancel });
109
- const templateType = templateTypeResponse.templateType || 'stateless';
110
- // OAuth prompt - only for SDK stateful template
111
- let withOAuth = false;
112
- if (framework === 'sdk' && templateType === 'stateful') {
113
- const oauthResponse = await prompts({
114
- type: 'confirm',
115
- name: 'withOAuth',
116
- message: 'Enable OAuth authentication?',
117
- initial: false,
118
- }, { onCancel });
119
- withOAuth = oauthResponse.withOAuth ?? false;
10
+ else {
11
+ // CLI mode - options is guaranteed to be defined
12
+ await generateProject({
13
+ projectName: options.name,
14
+ packageManager: options.packageManager,
15
+ framework: options.framework,
16
+ templateType: options.template,
17
+ withOAuth: options.oauth,
18
+ withGitInit: options.git,
19
+ });
120
20
  }
121
- // Git init prompt
122
- const gitInitResponse = await prompts({
123
- type: 'confirm',
124
- name: 'withGitInit',
125
- message: 'Initialize git repository?',
126
- initial: true,
127
- }, { onCancel });
128
- const withGitInit = gitInitResponse.withGitInit ?? false;
129
- const templateOptions = {
130
- withOAuth,
131
- packageManager,
132
- framework,
133
- stateless: templateType === 'stateless',
134
- };
135
- const projectPath = join(process.cwd(), projectName);
136
- const srcPath = join(projectPath, 'src');
137
- try {
138
- // Create directories
139
- await mkdir(srcPath, { recursive: true });
140
- // Build list of files to write
141
- const filesToWrite = [];
142
- if (framework === 'fastmcp') {
143
- // FastMCP templates
144
- filesToWrite.push(writeFile(join(srcPath, 'server.ts'), fastmcpTemplateFunctions.getServerTemplate(projectName)), writeFile(join(srcPath, 'index.ts'), fastmcpTemplateFunctions.getIndexTemplate(templateOptions)), writeFile(join(projectPath, 'README.md'), fastmcpTemplateFunctions.getReadmeTemplate(projectName, templateOptions)));
145
- }
146
- else {
147
- // SDK templates
148
- const templates = sdkTemplateFunctions[templateType];
149
- filesToWrite.push(writeFile(join(srcPath, 'server.ts'), templates.getServerTemplate(projectName)), writeFile(join(srcPath, 'index.ts'), templates.getIndexTemplate(templateOptions)), writeFile(join(projectPath, 'README.md'), templates.getReadmeTemplate(projectName, templateOptions)));
150
- // Conditionally add auth.ts for OAuth-enabled stateful template
151
- if (withOAuth && templates.getAuthTemplate) {
152
- filesToWrite.push(writeFile(join(srcPath, 'auth.ts'), templates.getAuthTemplate()));
153
- }
154
- }
155
- // Common files for all templates
156
- filesToWrite.push(writeFile(join(projectPath, 'package.json'), getPackageJsonTemplate(projectName, templateOptions)), writeFile(join(projectPath, 'tsconfig.json'), getTsconfigTemplate()), writeFile(join(projectPath, '.gitignore'), getGitignoreTemplate()), writeFile(join(projectPath, '.env.example'), getEnvExampleTemplate(templateOptions)));
157
- // Deployment files for all templates
158
- filesToWrite.push(writeFile(join(projectPath, 'Dockerfile'), getDockerfileTemplate(templateOptions)), writeFile(join(projectPath, '.dockerignore'), getDockerignoreTemplate()));
159
- // Write all template files
160
- await Promise.all(filesToWrite);
161
- // Initialize git repository if requested
162
- if (withGitInit) {
163
- try {
164
- execSync('git init', { cwd: projectPath, stdio: 'ignore' });
165
- }
166
- catch {
167
- console.log('\n⚠️ Could not initialize git repository (is git installed?)');
168
- }
169
- }
170
- const commands = packageManagerCommands[packageManager];
171
- const frameworkName = framework === 'fastmcp' ? 'FastMCP' : 'MCP SDK';
172
- console.log(`\n✅ Created ${projectName} with ${frameworkName} at ${projectPath}`);
173
- console.log(`\nNext steps:`);
174
- console.log(` cd ${projectName}`);
175
- console.log(` ${commands.install}`);
176
- console.log(` ${commands.dev}`);
177
- console.log(`\n`);
21
+ }
22
+ main().catch((error) => {
23
+ if (error instanceof Error) {
24
+ console.error(`\nError: ${error.message}\n`);
178
25
  }
179
- catch (error) {
180
- if (error instanceof Error) {
181
- console.error(`\n❌ Error: ${error.message}\n`);
182
- }
183
- else {
184
- console.error('\n❌ An unexpected error occurred\n');
185
- }
186
- process.exit(1);
26
+ else {
27
+ console.error('\nAn unexpected error occurred\n');
187
28
  }
188
- }
189
- main();
29
+ process.exit(1);
30
+ });
@@ -0,0 +1 @@
1
+ export declare function runInteractiveMode(): Promise<void>;
@@ -0,0 +1,103 @@
1
+ import prompts from 'prompts';
2
+ import { generateProject } from './project-generator.js';
3
+ export async function runInteractiveMode() {
4
+ console.log('\nCreate MCP Server\n');
5
+ const onCancel = () => {
6
+ console.log('\nOperation cancelled\n');
7
+ process.exit(0);
8
+ };
9
+ const projectNameResponse = await prompts({
10
+ type: 'text',
11
+ name: 'projectName',
12
+ message: 'Project name:',
13
+ initial: 'my-mcp-server',
14
+ validate: (value) => {
15
+ if (!value)
16
+ return 'Project name is required';
17
+ if (!/^[a-z0-9-_]+$/i.test(value)) {
18
+ return 'Project name can only contain letters, numbers, hyphens, and underscores';
19
+ }
20
+ return true;
21
+ },
22
+ }, { onCancel });
23
+ const { projectName } = projectNameResponse;
24
+ if (!projectName) {
25
+ console.log('\nProject name is required\n');
26
+ process.exit(1);
27
+ }
28
+ const packageManagerResponse = await prompts({
29
+ type: 'select',
30
+ name: 'packageManager',
31
+ message: 'Package manager:',
32
+ choices: [
33
+ { title: 'npm', value: 'npm' },
34
+ { title: 'pnpm', value: 'pnpm' },
35
+ { title: 'yarn', value: 'yarn' },
36
+ ],
37
+ initial: 0,
38
+ }, { onCancel });
39
+ const packageManager = packageManagerResponse.packageManager || 'npm';
40
+ // Framework selection
41
+ const frameworkResponse = await prompts({
42
+ type: 'select',
43
+ name: 'framework',
44
+ message: 'Framework:',
45
+ choices: [
46
+ {
47
+ title: 'Official MCP SDK',
48
+ value: 'sdk',
49
+ description: 'Full control with Express.js',
50
+ },
51
+ {
52
+ title: 'FastMCP',
53
+ value: 'fastmcp',
54
+ description: 'Simpler API, less boilerplate',
55
+ },
56
+ ],
57
+ initial: 0,
58
+ }, { onCancel });
59
+ const framework = frameworkResponse.framework || 'sdk';
60
+ // Server mode selection
61
+ const templateTypeResponse = await prompts({
62
+ type: 'select',
63
+ name: 'templateType',
64
+ message: 'Server mode:',
65
+ choices: [
66
+ { title: 'Stateless', value: 'stateless', description: 'Simple HTTP server' },
67
+ {
68
+ title: 'Stateful',
69
+ value: 'stateful',
70
+ description: 'Session-based server with SSE support',
71
+ },
72
+ ],
73
+ initial: 0,
74
+ }, { onCancel });
75
+ const templateType = templateTypeResponse.templateType || 'stateless';
76
+ // OAuth prompt - only for SDK stateful template
77
+ let withOAuth = false;
78
+ if (framework === 'sdk' && templateType === 'stateful') {
79
+ const oauthResponse = await prompts({
80
+ type: 'confirm',
81
+ name: 'withOAuth',
82
+ message: 'Enable OAuth authentication?',
83
+ initial: false,
84
+ }, { onCancel });
85
+ withOAuth = oauthResponse.withOAuth ?? false;
86
+ }
87
+ // Git init prompt
88
+ const gitInitResponse = await prompts({
89
+ type: 'confirm',
90
+ name: 'withGitInit',
91
+ message: 'Initialize git repository?',
92
+ initial: true,
93
+ }, { onCancel });
94
+ const withGitInit = gitInitResponse.withGitInit ?? false;
95
+ await generateProject({
96
+ projectName,
97
+ packageManager,
98
+ framework,
99
+ templateType,
100
+ withOAuth,
101
+ withGitInit,
102
+ });
103
+ }
@@ -0,0 +1,15 @@
1
+ import type { Framework, PackageManager } from './templates/common/types.js';
2
+ import type { TemplateType } from './cli.js';
3
+ export declare const packageManagerCommands: Record<PackageManager, {
4
+ install: string;
5
+ dev: string;
6
+ }>;
7
+ export interface ProjectConfig {
8
+ projectName: string;
9
+ packageManager: PackageManager;
10
+ framework: Framework;
11
+ templateType: TemplateType;
12
+ withOAuth: boolean;
13
+ withGitInit: boolean;
14
+ }
15
+ export declare function generateProject(config: ProjectConfig): Promise<void>;
@@ -0,0 +1,85 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { execSync } from 'node:child_process';
4
+ import { getPackageJsonTemplate } from './templates/common/package.json.js';
5
+ import { getTsconfigTemplate } from './templates/common/tsconfig.json.js';
6
+ import { getGitignoreTemplate } from './templates/common/gitignore.js';
7
+ import { getEnvExampleTemplate } from './templates/common/env.example.js';
8
+ import { getServerTemplate as getSdkStatelessServerTemplate, getIndexTemplate as getSdkStatelessIndexTemplate, getReadmeTemplate as getSdkStatelessReadmeTemplate, } from './templates/sdk/stateless/index.js';
9
+ import { getServerTemplate as getSdkStatefulServerTemplate, getIndexTemplate as getSdkStatefulIndexTemplate, getReadmeTemplate as getSdkStatefulReadmeTemplate, getAuthTemplate as getSdkAuthTemplate, } from './templates/sdk/stateful/index.js';
10
+ import { getServerTemplate as getFastMCPServerTemplate, getIndexTemplate as getFastMCPIndexTemplate, getReadmeTemplate as getFastMCPReadmeTemplate, } from './templates/fastmcp/index.js';
11
+ import { getDockerfileTemplate, getDockerignoreTemplate } from './templates/deployment/index.js';
12
+ const sdkTemplateFunctions = {
13
+ stateless: {
14
+ getServerTemplate: getSdkStatelessServerTemplate,
15
+ getIndexTemplate: getSdkStatelessIndexTemplate,
16
+ getReadmeTemplate: getSdkStatelessReadmeTemplate,
17
+ },
18
+ stateful: {
19
+ getServerTemplate: getSdkStatefulServerTemplate,
20
+ getIndexTemplate: getSdkStatefulIndexTemplate,
21
+ getReadmeTemplate: getSdkStatefulReadmeTemplate,
22
+ getAuthTemplate: getSdkAuthTemplate,
23
+ },
24
+ };
25
+ const fastmcpTemplateFunctions = {
26
+ getServerTemplate: getFastMCPServerTemplate,
27
+ getIndexTemplate: getFastMCPIndexTemplate,
28
+ getReadmeTemplate: getFastMCPReadmeTemplate,
29
+ };
30
+ export const packageManagerCommands = {
31
+ npm: { install: 'npm install', dev: 'npm run dev' },
32
+ pnpm: { install: 'pnpm install', dev: 'pnpm dev' },
33
+ yarn: { install: 'yarn', dev: 'yarn dev' },
34
+ };
35
+ export async function generateProject(config) {
36
+ const { projectName, packageManager, framework, templateType, withOAuth, withGitInit } = config;
37
+ const templateOptions = {
38
+ withOAuth,
39
+ packageManager,
40
+ framework,
41
+ stateless: templateType === 'stateless',
42
+ };
43
+ const projectPath = join(process.cwd(), projectName);
44
+ const srcPath = join(projectPath, 'src');
45
+ // Create directories
46
+ await mkdir(srcPath, { recursive: true });
47
+ // Build list of files to write
48
+ const filesToWrite = [];
49
+ if (framework === 'fastmcp') {
50
+ // FastMCP templates
51
+ filesToWrite.push(writeFile(join(srcPath, 'server.ts'), fastmcpTemplateFunctions.getServerTemplate(projectName)), writeFile(join(srcPath, 'index.ts'), fastmcpTemplateFunctions.getIndexTemplate(templateOptions)), writeFile(join(projectPath, 'README.md'), fastmcpTemplateFunctions.getReadmeTemplate(projectName, templateOptions)));
52
+ }
53
+ else {
54
+ // SDK templates
55
+ const templates = sdkTemplateFunctions[templateType];
56
+ filesToWrite.push(writeFile(join(srcPath, 'server.ts'), templates.getServerTemplate(projectName)), writeFile(join(srcPath, 'index.ts'), templates.getIndexTemplate(templateOptions)), writeFile(join(projectPath, 'README.md'), templates.getReadmeTemplate(projectName, templateOptions)));
57
+ // Conditionally add auth.ts for OAuth-enabled stateful template
58
+ if (withOAuth && templates.getAuthTemplate) {
59
+ filesToWrite.push(writeFile(join(srcPath, 'auth.ts'), templates.getAuthTemplate()));
60
+ }
61
+ }
62
+ // Common files for all templates
63
+ filesToWrite.push(writeFile(join(projectPath, 'package.json'), getPackageJsonTemplate(projectName, templateOptions)), writeFile(join(projectPath, 'tsconfig.json'), getTsconfigTemplate()), writeFile(join(projectPath, '.gitignore'), getGitignoreTemplate()), writeFile(join(projectPath, '.env.example'), getEnvExampleTemplate(templateOptions)));
64
+ // Deployment files for all templates
65
+ filesToWrite.push(writeFile(join(projectPath, 'Dockerfile'), getDockerfileTemplate(templateOptions)), writeFile(join(projectPath, '.dockerignore'), getDockerignoreTemplate()));
66
+ // Write all template files
67
+ await Promise.all(filesToWrite);
68
+ // Initialize git repository if requested
69
+ if (withGitInit) {
70
+ try {
71
+ execSync('git init', { cwd: projectPath, stdio: 'ignore' });
72
+ }
73
+ catch {
74
+ console.log('\n Could not initialize git repository (is git installed?)');
75
+ }
76
+ }
77
+ const commands = packageManagerCommands[packageManager];
78
+ const frameworkName = framework === 'fastmcp' ? 'FastMCP' : 'MCP SDK';
79
+ console.log(`\nCreated ${projectName} with ${frameworkName} at ${projectPath}`);
80
+ console.log(`\nNext steps:`);
81
+ console.log(` cd ${projectName}`);
82
+ console.log(` ${commands.install}`);
83
+ console.log(` ${commands.dev}`);
84
+ console.log(`\n`);
85
+ }
@@ -2,9 +2,27 @@ export function getReadmeTemplate(projectName, options) {
2
2
  const packageManager = options?.packageManager ?? 'npm';
3
3
  const stateless = options?.stateless ?? false;
4
4
  const commands = {
5
- npm: { install: 'npm install', dev: 'npm run dev', build: 'npm run build', start: 'npm start' },
6
- pnpm: { install: 'pnpm install', dev: 'pnpm dev', build: 'pnpm build', start: 'pnpm start' },
7
- yarn: { install: 'yarn', dev: 'yarn dev', build: 'yarn build', start: 'yarn start' },
5
+ npm: {
6
+ install: 'npm install',
7
+ dev: 'npm run dev',
8
+ build: 'npm run build',
9
+ start: 'npm start',
10
+ inspect: 'npm run inspect',
11
+ },
12
+ pnpm: {
13
+ install: 'pnpm install',
14
+ dev: 'pnpm dev',
15
+ build: 'pnpm build',
16
+ start: 'pnpm start',
17
+ inspect: 'pnpm inspect',
18
+ },
19
+ yarn: {
20
+ install: 'yarn',
21
+ dev: 'yarn dev',
22
+ build: 'yarn build',
23
+ start: 'yarn start',
24
+ inspect: 'yarn inspect',
25
+ },
8
26
  };
9
27
  const cmd = commands[packageManager];
10
28
  const modeDescription = stateless
@@ -34,6 +52,22 @@ ${cmd.start}
34
52
 
35
53
  The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable.
36
54
 
55
+ ## Testing with MCP Inspector
56
+
57
+ This project includes [MCP Inspector](https://github.com/modelcontextprotocol/inspector) as a dev dependency for testing and debugging.
58
+
59
+ First, start the server in one terminal:
60
+
61
+ \`\`\`bash
62
+ ${cmd.dev}
63
+ \`\`\`
64
+
65
+ Then, in another terminal, launch the inspector:
66
+
67
+ \`\`\`bash
68
+ ${cmd.inspect}
69
+ \`\`\`
70
+
37
71
  ## API Endpoints
38
72
 
39
73
  - **POST /mcp** - Main MCP endpoint for JSON-RPC messages
@@ -2,9 +2,27 @@ export function getReadmeTemplate(projectName, options) {
2
2
  const withOAuth = options?.withOAuth ?? false;
3
3
  const packageManager = options?.packageManager ?? 'npm';
4
4
  const commands = {
5
- npm: { install: 'npm install', dev: 'npm run dev', build: 'npm run build', start: 'npm start' },
6
- pnpm: { install: 'pnpm install', dev: 'pnpm dev', build: 'pnpm build', start: 'pnpm start' },
7
- yarn: { install: 'yarn', dev: 'yarn dev', build: 'yarn build', start: 'yarn start' },
5
+ npm: {
6
+ install: 'npm install',
7
+ dev: 'npm run dev',
8
+ build: 'npm run build',
9
+ start: 'npm start',
10
+ inspect: 'npm run inspect',
11
+ },
12
+ pnpm: {
13
+ install: 'pnpm install',
14
+ dev: 'pnpm dev',
15
+ build: 'pnpm build',
16
+ start: 'pnpm start',
17
+ inspect: 'pnpm inspect',
18
+ },
19
+ yarn: {
20
+ install: 'yarn',
21
+ dev: 'yarn dev',
22
+ build: 'yarn build',
23
+ start: 'yarn start',
24
+ inspect: 'yarn inspect',
25
+ },
8
26
  }[packageManager];
9
27
  const description = withOAuth
10
28
  ? 'A stateful streamable HTTP MCP (Model Context Protocol) server with session management and OAuth authentication.'
@@ -116,6 +134,22 @@ ${commands.start}
116
134
  \`\`\`
117
135
 
118
136
  The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable.
137
+
138
+ ## Testing with MCP Inspector
139
+
140
+ This project includes [MCP Inspector](https://github.com/modelcontextprotocol/inspector) as a dev dependency for testing and debugging.
141
+
142
+ First, start the server in one terminal:
143
+
144
+ \`\`\`bash
145
+ ${commands.dev}
146
+ \`\`\`
147
+
148
+ Then, in another terminal, launch the inspector:
149
+
150
+ \`\`\`bash
151
+ ${commands.inspect}
152
+ \`\`\`
119
153
  ${oauthSection}
120
154
  ## API Endpoints
121
155
 
@@ -1,9 +1,27 @@
1
1
  export function getReadmeTemplate(projectName, options) {
2
2
  const packageManager = options?.packageManager ?? 'npm';
3
3
  const commands = {
4
- npm: { install: 'npm install', dev: 'npm run dev', build: 'npm run build', start: 'npm start' },
5
- pnpm: { install: 'pnpm install', dev: 'pnpm dev', build: 'pnpm build', start: 'pnpm start' },
6
- yarn: { install: 'yarn', dev: 'yarn dev', build: 'yarn build', start: 'yarn start' },
4
+ npm: {
5
+ install: 'npm install',
6
+ dev: 'npm run dev',
7
+ build: 'npm run build',
8
+ start: 'npm start',
9
+ inspect: 'npm run inspect',
10
+ },
11
+ pnpm: {
12
+ install: 'pnpm install',
13
+ dev: 'pnpm dev',
14
+ build: 'pnpm build',
15
+ start: 'pnpm start',
16
+ inspect: 'pnpm inspect',
17
+ },
18
+ yarn: {
19
+ install: 'yarn',
20
+ dev: 'yarn dev',
21
+ build: 'yarn build',
22
+ start: 'yarn start',
23
+ inspect: 'yarn inspect',
24
+ },
7
25
  }[packageManager];
8
26
  return `# ${projectName}
9
27
 
@@ -29,6 +47,22 @@ ${commands.start}
29
47
 
30
48
  The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable.
31
49
 
50
+ ## Testing with MCP Inspector
51
+
52
+ This project includes [MCP Inspector](https://github.com/modelcontextprotocol/inspector) as a dev dependency for testing and debugging.
53
+
54
+ First, start the server in one terminal:
55
+
56
+ \`\`\`bash
57
+ ${commands.dev}
58
+ \`\`\`
59
+
60
+ Then, in another terminal, launch the inspector:
61
+
62
+ \`\`\`bash
63
+ ${commands.inspect}
64
+ \`\`\`
65
+
32
66
  ## API Endpoints
33
67
 
34
68
  - **POST /mcp** - Main MCP endpoint for JSON-RPC messages
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentailor/create-mcp-server",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Create a new MCP (Model Context Protocol) server project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,6 +37,7 @@
37
37
  "node": ">=20"
38
38
  },
39
39
  "dependencies": {
40
+ "commander": "^14.0.3",
40
41
  "prompts": "^2.4.2"
41
42
  },
42
43
  "devDependencies": {