@agentailor/create-mcp-server 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,10 +4,49 @@ Scaffold production-ready MCP servers in seconds.
4
4
 
5
5
  ## Quick Start
6
6
 
7
+ **Interactive mode** (guided prompts):
8
+
7
9
  ```bash
8
10
  npx @agentailor/create-mcp-server
9
11
  ```
10
12
 
13
+ **CLI mode** (all options via arguments):
14
+
15
+ ```bash
16
+ npx @agentailor/create-mcp-server --name=my-server
17
+ ```
18
+
19
+ ## CLI Options
20
+
21
+ | Option | Short | Default | Description |
22
+ |--------|-------|---------|-------------|
23
+ | `--name` | `-n` | — | Project name (required in CLI mode) |
24
+ | `--package-manager` | `-p` | `npm` | Package manager: npm, pnpm, yarn |
25
+ | `--framework` | `-f` | `sdk` | Framework: sdk, fastmcp |
26
+ | `--template` | `-t` | `stateless` | Server mode: stateless, stateful |
27
+ | `--oauth` | — | `false` | Enable OAuth (sdk+stateful only) |
28
+ | `--no-git` | — | `false` | Skip git initialization |
29
+ | `--help` | `-h` | — | Show help |
30
+ | `--version` | `-V` | — | Show version |
31
+
32
+ **Examples:**
33
+
34
+ ```bash
35
+ # Minimal - uses all defaults
36
+ npx @agentailor/create-mcp-server --name=my-server
37
+
38
+ # Full options
39
+ npx @agentailor/create-mcp-server \
40
+ --name=my-auth-server \
41
+ --package-manager=pnpm \
42
+ --framework=sdk \
43
+ --template=stateful \
44
+ --oauth
45
+
46
+ # Short flags
47
+ npx @agentailor/create-mcp-server -n my-server -p yarn -f fastmcp
48
+ ```
49
+
11
50
  ## Features
12
51
 
13
52
  - **Two frameworks** — Official MCP SDK or FastMCP
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
+ }
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.0",
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": {