@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 +39 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +60 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +145 -0
- package/dist/index.js +24 -183
- package/dist/interactive.d.ts +1 -0
- package/dist/interactive.js +103 -0
- package/dist/project-generator.d.ts +15 -0
- package/dist/project-generator.js +85 -0
- package/package.json +2 -1
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 {};
|
package/dist/cli.test.js
ADDED
|
@@ -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
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|