@agentailor/create-mcp-server 0.2.0 → 0.3.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.
Files changed (41) hide show
  1. package/README.md +36 -6
  2. package/dist/index.js +64 -29
  3. package/dist/templates/common/env.example.d.ts +2 -4
  4. package/dist/templates/common/package.json.d.ts +2 -4
  5. package/dist/templates/common/package.json.js +34 -13
  6. package/dist/templates/common/templates.test.js +25 -1
  7. package/dist/templates/common/types.d.ts +29 -0
  8. package/dist/templates/fastmcp/index.d.ts +5 -0
  9. package/dist/templates/fastmcp/index.js +19 -0
  10. package/dist/templates/fastmcp/readme.js +82 -0
  11. package/dist/templates/fastmcp/server.js +66 -0
  12. package/dist/templates/fastmcp/templates.test.js +87 -0
  13. package/dist/templates/sdk/stateful/index.d.ts +6 -0
  14. package/dist/templates/{stateful-streamable-http → sdk/stateful}/index.js +38 -10
  15. package/dist/templates/{stateful-streamable-http → sdk/stateful}/readme.js +10 -4
  16. package/dist/templates/sdk/stateful/server.d.ts +1 -0
  17. package/dist/templates/sdk/stateful/server.js +2 -0
  18. package/dist/templates/sdk/stateful/templates.test.d.ts +1 -0
  19. package/dist/templates/{stateful-streamable-http → sdk/stateful}/templates.test.js +29 -2
  20. package/dist/templates/sdk/stateless/index.d.ts +5 -0
  21. package/dist/templates/{streamable-http → sdk/stateless}/index.js +19 -7
  22. package/dist/templates/sdk/stateless/readme.d.ts +2 -0
  23. package/dist/templates/{streamable-http → sdk/stateless}/readme.js +10 -6
  24. package/dist/templates/sdk/stateless/server.d.ts +1 -0
  25. package/dist/templates/sdk/stateless/templates.test.d.ts +1 -0
  26. package/dist/templates/{streamable-http → sdk/stateless}/templates.test.js +29 -2
  27. package/package.json +1 -1
  28. package/dist/templates/stateful-streamable-http/index.d.ts +0 -7
  29. package/dist/templates/stateful-streamable-http/server.d.ts +0 -1
  30. package/dist/templates/stateful-streamable-http/server.js +0 -2
  31. package/dist/templates/streamable-http/index.d.ts +0 -6
  32. /package/dist/templates/{stateful-streamable-http/auth.test.d.ts → common/types.js} +0 -0
  33. /package/dist/templates/{stateful-streamable-http → fastmcp}/readme.d.ts +0 -0
  34. /package/dist/templates/{streamable-http → fastmcp}/server.d.ts +0 -0
  35. /package/dist/templates/{stateful-streamable-http → fastmcp}/templates.test.d.ts +0 -0
  36. /package/dist/templates/{stateful-streamable-http → sdk/stateful}/auth.d.ts +0 -0
  37. /package/dist/templates/{stateful-streamable-http → sdk/stateful}/auth.js +0 -0
  38. /package/dist/templates/{streamable-http/templates.test.d.ts → sdk/stateful/auth.test.d.ts} +0 -0
  39. /package/dist/templates/{stateful-streamable-http → sdk/stateful}/auth.test.js +0 -0
  40. /package/dist/templates/{streamable-http → sdk/stateful}/readme.d.ts +0 -0
  41. /package/dist/templates/{streamable-http → sdk/stateless}/server.js +0 -0
package/README.md CHANGED
@@ -10,24 +10,54 @@ npx @agentailor/create-mcp-server
10
10
 
11
11
  ## Features
12
12
 
13
- - **Two templates** — stateless or stateful with session management
14
- - **Optional OAuth** — OIDC-compliant authentication ([setup guide](docs/oauth-setup.md))
13
+ - **Two frameworks** — Official MCP SDK or FastMCP
14
+ - **Two server modes** — stateless or stateful with session management
15
+ - **Optional OAuth** — OIDC-compliant authentication (SDK only) ([setup guide](docs/oauth-setup.md))
15
16
  - **Package manager choice** — npm, pnpm, or yarn
16
- - **TypeScript + Express.js** — ready to customize
17
+ - **TypeScript ready** — ready to customize
17
18
  - **MCP Inspector** — built-in debugging with `npm run inspect`
18
19
 
19
- ## Templates
20
+ ## Frameworks
21
+
22
+ | Framework | Description |
23
+ |-----------|-------------|
24
+ | **Official MCP SDK** (default) | Full control with Express.js, supports OAuth |
25
+ | **FastMCP** | Simpler API with less boilerplate |
26
+
27
+ ### FastMCP
28
+
29
+ [FastMCP](https://github.com/punkpeye/fastmcp) is a TypeScript framework built on top of the official MCP SDK that provides a simpler, more intuitive API for building MCP servers.
30
+
31
+ ```typescript
32
+ import { FastMCP } from "fastmcp";
33
+ import { z } from "zod";
34
+
35
+ const server = new FastMCP({ name: "My Server", version: "1.0.0" });
36
+
37
+ server.addTool({
38
+ name: "add",
39
+ description: "Add two numbers",
40
+ parameters: z.object({ a: z.number(), b: z.number() }),
41
+ execute: async ({ a, b }) => String(a + b),
42
+ });
43
+
44
+ server.start({ transportType: "httpStream", httpStream: { port: 3000 } });
45
+ ```
46
+
47
+ Learn more: [FastMCP Documentation](https://github.com/punkpeye/fastmcp)
48
+
49
+ ## Server Modes
20
50
 
21
51
  | Feature | Stateless (default) | Stateful |
22
52
  |---------|---------------------|----------|
23
53
  | Session management | — | ✓ |
24
54
  | SSE support | — | ✓ |
25
- | OAuth option | — | ✓ |
55
+ | OAuth option (SDK only) | — | ✓ |
26
56
  | Endpoints | POST /mcp | POST, GET, DELETE /mcp |
27
57
 
28
58
  **Stateless**: Simple HTTP server — each request creates a new transport instance.
29
59
 
30
- **Stateful**: Session-based server with transport reuse, Server-Sent Events for real-time updates, and optional OAuth authentication.
60
+ **Stateful**: Session-based server with transport reuse, Server-Sent Events for real-time updates, and optional OAuth authentication (SDK only).
31
61
 
32
62
  ## Generated Project
33
63
 
package/dist/index.js CHANGED
@@ -7,21 +7,27 @@ import { getPackageJsonTemplate } from './templates/common/package.json.js';
7
7
  import { getTsconfigTemplate } from './templates/common/tsconfig.json.js';
8
8
  import { getGitignoreTemplate } from './templates/common/gitignore.js';
9
9
  import { getEnvExampleTemplate } from './templates/common/env.example.js';
10
- import { getServerTemplate as getStatelessServerTemplate, getIndexTemplate as getStatelessIndexTemplate, getReadmeTemplate as getStatelessReadmeTemplate, } from './templates/streamable-http/index.js';
11
- import { getServerTemplate as getStatefulServerTemplate, getIndexTemplate as getStatefulIndexTemplate, getReadmeTemplate as getStatefulReadmeTemplate, getAuthTemplate as getStatefulAuthTemplate, } from './templates/stateful-streamable-http/index.js';
12
- const templateFunctions = {
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
+ const sdkTemplateFunctions = {
13
14
  stateless: {
14
- getServerTemplate: getStatelessServerTemplate,
15
- getIndexTemplate: getStatelessIndexTemplate,
16
- getReadmeTemplate: getStatelessReadmeTemplate,
15
+ getServerTemplate: getSdkStatelessServerTemplate,
16
+ getIndexTemplate: getSdkStatelessIndexTemplate,
17
+ getReadmeTemplate: getSdkStatelessReadmeTemplate,
17
18
  },
18
19
  stateful: {
19
- getServerTemplate: getStatefulServerTemplate,
20
- getIndexTemplate: getStatefulIndexTemplate,
21
- getReadmeTemplate: getStatefulReadmeTemplate,
22
- getAuthTemplate: getStatefulAuthTemplate,
20
+ getServerTemplate: getSdkStatefulServerTemplate,
21
+ getIndexTemplate: getSdkStatefulIndexTemplate,
22
+ getReadmeTemplate: getSdkStatefulReadmeTemplate,
23
+ getAuthTemplate: getSdkAuthTemplate,
23
24
  },
24
25
  };
26
+ const fastmcpTemplateFunctions = {
27
+ getServerTemplate: getFastMCPServerTemplate,
28
+ getIndexTemplate: getFastMCPIndexTemplate,
29
+ getReadmeTemplate: getFastMCPReadmeTemplate,
30
+ };
25
31
  const packageManagerCommands = {
26
32
  npm: { install: 'npm install', dev: 'npm run dev' },
27
33
  pnpm: { install: 'pnpm install', dev: 'pnpm dev' },
@@ -64,12 +70,33 @@ async function main() {
64
70
  initial: 0,
65
71
  }, { onCancel });
66
72
  const packageManager = packageManagerResponse.packageManager || 'npm';
73
+ // Framework selection
74
+ const frameworkResponse = await prompts({
75
+ type: 'select',
76
+ name: 'framework',
77
+ message: 'Framework:',
78
+ choices: [
79
+ {
80
+ title: 'Official MCP SDK',
81
+ value: 'sdk',
82
+ description: 'Full control with Express.js',
83
+ },
84
+ {
85
+ title: 'FastMCP',
86
+ value: 'fastmcp',
87
+ description: 'Simpler API, less boilerplate',
88
+ },
89
+ ],
90
+ initial: 0,
91
+ }, { onCancel });
92
+ const framework = frameworkResponse.framework || 'sdk';
93
+ // Server mode selection
67
94
  const templateTypeResponse = await prompts({
68
95
  type: 'select',
69
96
  name: 'templateType',
70
- message: 'Template type:',
97
+ message: 'Server mode:',
71
98
  choices: [
72
- { title: 'Stateless', value: 'stateless', description: 'Simple stateless HTTP server' },
99
+ { title: 'Stateless', value: 'stateless', description: 'Simple HTTP server' },
73
100
  {
74
101
  title: 'Stateful',
75
102
  value: 'stateful',
@@ -79,9 +106,9 @@ async function main() {
79
106
  initial: 0,
80
107
  }, { onCancel });
81
108
  const templateType = templateTypeResponse.templateType || 'stateless';
82
- // OAuth prompt - only for stateful template
109
+ // OAuth prompt - only for SDK stateful template
83
110
  let withOAuth = false;
84
- if (templateType === 'stateful') {
111
+ if (framework === 'sdk' && templateType === 'stateful') {
85
112
  const oauthResponse = await prompts({
86
113
  type: 'confirm',
87
114
  name: 'withOAuth',
@@ -98,27 +125,34 @@ async function main() {
98
125
  initial: true,
99
126
  }, { onCancel });
100
127
  const withGitInit = gitInitResponse.withGitInit ?? false;
101
- const templateOptions = { withOAuth };
102
- const templates = templateFunctions[templateType];
128
+ const templateOptions = {
129
+ withOAuth,
130
+ packageManager,
131
+ framework,
132
+ stateless: templateType === 'stateless',
133
+ };
103
134
  const projectPath = join(process.cwd(), projectName);
104
135
  const srcPath = join(projectPath, 'src');
105
136
  try {
106
137
  // Create directories
107
138
  await mkdir(srcPath, { recursive: true });
108
139
  // Build list of files to write
109
- const filesToWrite = [
110
- writeFile(join(srcPath, 'server.ts'), templates.getServerTemplate(projectName)),
111
- writeFile(join(srcPath, 'index.ts'), templates.getIndexTemplate(templateOptions)),
112
- writeFile(join(projectPath, 'package.json'), getPackageJsonTemplate(projectName, templateOptions)),
113
- writeFile(join(projectPath, 'tsconfig.json'), getTsconfigTemplate()),
114
- writeFile(join(projectPath, '.gitignore'), getGitignoreTemplate()),
115
- writeFile(join(projectPath, '.env.example'), getEnvExampleTemplate(templateOptions)),
116
- writeFile(join(projectPath, 'README.md'), templates.getReadmeTemplate(projectName, templateOptions)),
117
- ];
118
- // Conditionally add auth.ts for OAuth-enabled stateful template
119
- if (withOAuth && templates.getAuthTemplate) {
120
- filesToWrite.push(writeFile(join(srcPath, 'auth.ts'), templates.getAuthTemplate()));
140
+ const filesToWrite = [];
141
+ if (framework === 'fastmcp') {
142
+ // FastMCP templates
143
+ 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)));
144
+ }
145
+ else {
146
+ // SDK templates
147
+ const templates = sdkTemplateFunctions[templateType];
148
+ 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)));
149
+ // Conditionally add auth.ts for OAuth-enabled stateful template
150
+ if (withOAuth && templates.getAuthTemplate) {
151
+ filesToWrite.push(writeFile(join(srcPath, 'auth.ts'), templates.getAuthTemplate()));
152
+ }
121
153
  }
154
+ // Common files for all templates
155
+ 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)));
122
156
  // Write all template files
123
157
  await Promise.all(filesToWrite);
124
158
  // Initialize git repository if requested
@@ -131,7 +165,8 @@ async function main() {
131
165
  }
132
166
  }
133
167
  const commands = packageManagerCommands[packageManager];
134
- console.log(`\n✅ Created ${projectName} at ${projectPath}`);
168
+ const frameworkName = framework === 'fastmcp' ? 'FastMCP' : 'MCP SDK';
169
+ console.log(`\n✅ Created ${projectName} with ${frameworkName} at ${projectPath}`);
135
170
  console.log(`\nNext steps:`);
136
171
  console.log(` cd ${projectName}`);
137
172
  console.log(` ${commands.install}`);
@@ -1,4 +1,2 @@
1
- export interface TemplateOptions {
2
- withOAuth?: boolean;
3
- }
4
- export declare function getEnvExampleTemplate(options?: TemplateOptions): string;
1
+ import type { CommonTemplateOptions } from './types.js';
2
+ export declare function getEnvExampleTemplate(options?: CommonTemplateOptions): string;
@@ -1,4 +1,2 @@
1
- export interface TemplateOptions {
2
- withOAuth?: boolean;
3
- }
4
- export declare function getPackageJsonTemplate(projectName: string, options?: TemplateOptions): string;
1
+ import type { CommonTemplateOptions } from './types.js';
2
+ export declare function getPackageJsonTemplate(projectName: string, options?: CommonTemplateOptions): string;
@@ -1,13 +1,39 @@
1
1
  export function getPackageJsonTemplate(projectName, options) {
2
2
  const withOAuth = options?.withOAuth ?? false;
3
- const dependencies = {
4
- '@modelcontextprotocol/sdk': '^1.25.1',
5
- express: '^5.2.1',
6
- zod: '^4.3.5',
3
+ const framework = options?.framework ?? 'sdk';
4
+ let dependencies;
5
+ let devDependencies;
6
+ const commonDevDependencies = {
7
+ typescript: '^5.9.3',
8
+ '@modelcontextprotocol/inspector': '^0.18.0',
7
9
  };
8
- if (withOAuth) {
9
- dependencies['dotenv'] = '^17.2.3';
10
- dependencies['jose'] = '^6.1.3';
10
+ const zodDependency = { zod: '^4.3.5' };
11
+ if (framework === 'fastmcp') {
12
+ // FastMCP dependencies - simpler setup
13
+ dependencies = {
14
+ fastmcp: '^3.26.8',
15
+ ...zodDependency,
16
+ };
17
+ devDependencies = {
18
+ ...commonDevDependencies,
19
+ };
20
+ }
21
+ else {
22
+ // Official SDK dependencies
23
+ dependencies = {
24
+ '@modelcontextprotocol/sdk': '^1.25.1',
25
+ express: '^5.2.1',
26
+ ...zodDependency,
27
+ };
28
+ if (withOAuth) {
29
+ dependencies['dotenv'] = '^17.2.3';
30
+ dependencies['jose'] = '^6.1.3';
31
+ }
32
+ devDependencies = {
33
+ '@types/express': '^5.0.6',
34
+ '@types/node': '^25.0.3',
35
+ ...commonDevDependencies,
36
+ };
11
37
  }
12
38
  const packageJson = {
13
39
  name: projectName,
@@ -21,12 +47,7 @@ export function getPackageJsonTemplate(projectName, options) {
21
47
  inspect: 'mcp-inspector http://localhost:3000/mcp',
22
48
  },
23
49
  dependencies,
24
- devDependencies: {
25
- '@types/express': '^5.0.6',
26
- '@types/node': '^25.0.3',
27
- typescript: '^5.9.3',
28
- '@modelcontextprotocol/inspector': '^0.18.0',
29
- },
50
+ devDependencies,
30
51
  engines: {
31
52
  node: '>=20',
32
53
  },
@@ -11,10 +11,34 @@ describe('common templates', () => {
11
11
  const pkg = JSON.parse(template);
12
12
  expect(pkg.name).toBe(projectName);
13
13
  });
14
- it('should use correct SDK package', () => {
14
+ it('should use SDK package by default', () => {
15
15
  const template = getPackageJsonTemplate(projectName);
16
16
  const pkg = JSON.parse(template);
17
17
  expect(pkg.dependencies['@modelcontextprotocol/sdk']).toBeDefined();
18
+ expect(pkg.dependencies['express']).toBeDefined();
19
+ });
20
+ it('should use FastMCP package when framework is fastmcp', () => {
21
+ const template = getPackageJsonTemplate(projectName, { framework: 'fastmcp' });
22
+ const pkg = JSON.parse(template);
23
+ expect(pkg.dependencies['fastmcp']).toBeDefined();
24
+ expect(pkg.dependencies['@modelcontextprotocol/sdk']).toBeUndefined();
25
+ expect(pkg.dependencies['express']).toBeUndefined();
26
+ });
27
+ it('should include OAuth dependencies when withOAuth is true for SDK', () => {
28
+ const template = getPackageJsonTemplate(projectName, { framework: 'sdk', withOAuth: true });
29
+ const pkg = JSON.parse(template);
30
+ expect(pkg.dependencies['dotenv']).toBeDefined();
31
+ expect(pkg.dependencies['jose']).toBeDefined();
32
+ });
33
+ it('should not include @types/express for FastMCP', () => {
34
+ const template = getPackageJsonTemplate(projectName, { framework: 'fastmcp' });
35
+ const pkg = JSON.parse(template);
36
+ expect(pkg.devDependencies['@types/express']).toBeUndefined();
37
+ });
38
+ it('should include @types/express for SDK', () => {
39
+ const template = getPackageJsonTemplate(projectName, { framework: 'sdk' });
40
+ const pkg = JSON.parse(template);
41
+ expect(pkg.devDependencies['@types/express']).toBeDefined();
18
42
  });
19
43
  it('should include required scripts', () => {
20
44
  const template = getPackageJsonTemplate(projectName);
@@ -0,0 +1,29 @@
1
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn';
2
+ export type Framework = 'sdk' | 'fastmcp';
3
+ /**
4
+ * Base template options shared across all templates
5
+ */
6
+ export interface BaseTemplateOptions {
7
+ packageManager?: PackageManager;
8
+ }
9
+ /**
10
+ * Template options for SDK templates (stateless and stateful)
11
+ */
12
+ export interface SdkTemplateOptions extends BaseTemplateOptions {
13
+ withOAuth?: boolean;
14
+ }
15
+ /**
16
+ * Template options for FastMCP templates
17
+ */
18
+ export interface FastMCPTemplateOptions extends BaseTemplateOptions {
19
+ stateless?: boolean;
20
+ }
21
+ /**
22
+ * Template options for common templates (package.json, env.example)
23
+ * Includes all options since these are used across all frameworks
24
+ */
25
+ export interface CommonTemplateOptions extends BaseTemplateOptions {
26
+ withOAuth?: boolean;
27
+ framework?: Framework;
28
+ stateless?: boolean;
29
+ }
@@ -0,0 +1,5 @@
1
+ export type { FastMCPTemplateOptions as TemplateOptions } from '../common/types.js';
2
+ import type { FastMCPTemplateOptions } from '../common/types.js';
3
+ export declare function getIndexTemplate(options?: FastMCPTemplateOptions): string;
4
+ export { getServerTemplate } from './server.js';
5
+ export { getReadmeTemplate } from './readme.js';
@@ -0,0 +1,19 @@
1
+ export function getIndexTemplate(options) {
2
+ const stateless = options?.stateless ?? false;
3
+ const statelessConfig = stateless ? '\n stateless: true,' : '';
4
+ return `import { server } from './server.js';
5
+
6
+ const PORT = Number(process.env.PORT) || 3000;
7
+
8
+ server.start({
9
+ transportType: 'httpStream',
10
+ httpStream: {
11
+ port: PORT,${statelessConfig}
12
+ },
13
+ });
14
+
15
+ console.log(\`MCP Server listening on port \${PORT}\`);
16
+ `;
17
+ }
18
+ export { getServerTemplate } from './server.js';
19
+ export { getReadmeTemplate } from './readme.js';
@@ -0,0 +1,82 @@
1
+ export function getReadmeTemplate(projectName, options) {
2
+ const packageManager = options?.packageManager ?? 'npm';
3
+ const stateless = options?.stateless ?? false;
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' },
8
+ };
9
+ const cmd = commands[packageManager];
10
+ const modeDescription = stateless
11
+ ? 'A stateless streamable HTTP MCP server built with FastMCP.'
12
+ : 'A stateful streamable HTTP MCP server built with FastMCP.';
13
+ return `# ${projectName}
14
+
15
+ ${modeDescription}
16
+
17
+ ## About
18
+
19
+ This project was created with [@agentailor/create-mcp-server](https://www.npmjs.com/package/@agentailor/create-mcp-server) using [FastMCP](https://github.com/punkpeye/fastmcp).
20
+
21
+ ## Getting Started
22
+
23
+ \`\`\`bash
24
+ # Install dependencies
25
+ ${cmd.install}
26
+
27
+ # Build and run in development
28
+ ${cmd.dev}
29
+
30
+ # Or build and start separately
31
+ ${cmd.build}
32
+ ${cmd.start}
33
+ \`\`\`
34
+
35
+ The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable.
36
+
37
+ ## API Endpoint
38
+
39
+ - **POST /mcp** - Main MCP endpoint for JSON-RPC messages
40
+
41
+ ## Included Examples
42
+
43
+ This server comes with example implementations to help you get started:
44
+
45
+ ### Prompts
46
+
47
+ - **greeting-template** - A simple greeting prompt that takes a name parameter
48
+
49
+ ### Tools
50
+
51
+ - **start-notification-stream** - Sends periodic notifications for testing. Parameters:
52
+ - \`interval\`: Milliseconds between notifications (default: 100)
53
+ - \`count\`: Number of notifications to send (default: 10, use 0 for unlimited)
54
+
55
+ ### Resources
56
+
57
+ - **greeting-resource** - A simple text resource at \`https://example.com/greetings/default\`
58
+
59
+ ## Project Structure
60
+
61
+ \`\`\`
62
+ ${projectName}/
63
+ ├── src/
64
+ │ ├── server.ts # FastMCP server definition (tools, prompts, resources)
65
+ │ └── index.ts # Server startup configuration
66
+ ├── package.json
67
+ ├── tsconfig.json
68
+ └── README.md
69
+ \`\`\`
70
+
71
+ ## Customization
72
+
73
+ - Add new tools, prompts, and resources in \`src/server.ts\`
74
+ - Modify transport configuration in \`src/index.ts\`
75
+
76
+ ## Learn More
77
+
78
+ - [FastMCP](https://github.com/punkpeye/fastmcp) - The framework powering this server
79
+ - [Model Context Protocol](https://modelcontextprotocol.io/)
80
+ - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
81
+ `;
82
+ }
@@ -0,0 +1,66 @@
1
+ export function getServerTemplate(projectName) {
2
+ return `import { FastMCP } from 'fastmcp';
3
+ import { z } from 'zod';
4
+
5
+ const server = new FastMCP({
6
+ name: '${projectName}',
7
+ version: '1.0.0',
8
+ });
9
+
10
+ // Register a simple prompt
11
+ server.addPrompt({
12
+ name: 'greeting-template',
13
+ description: 'A simple greeting prompt template',
14
+ arguments: [
15
+ {
16
+ name: 'name',
17
+ description: 'Name to include in greeting',
18
+ required: true,
19
+ },
20
+ ],
21
+ load: async ({ name }) => {
22
+ return \`Please greet \${name} in a friendly manner.\`;
23
+ },
24
+ });
25
+
26
+ // Register a tool for testing resumability
27
+ server.addTool({
28
+ name: 'start-notification-stream',
29
+ description: 'Starts sending periodic notifications for testing resumability',
30
+ parameters: z.object({
31
+ interval: z
32
+ .number()
33
+ .describe('Interval in milliseconds between notifications')
34
+ .default(100),
35
+ count: z.number().describe('Number of notifications to send (0 for 100)').default(10),
36
+ }),
37
+ execute: async ({ interval, count }) => {
38
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
39
+ let counter = 0;
40
+
41
+ while (count === 0 || counter < count) {
42
+ counter++;
43
+ console.log(\`Periodic notification #\${counter} at \${new Date().toISOString()}\`);
44
+ await sleep(interval);
45
+ }
46
+
47
+ return \`Sent \${counter} periodic notifications every \${interval}ms\`;
48
+ },
49
+ });
50
+
51
+ // Create a simple resource at a fixed URI
52
+ server.addResource({
53
+ uri: 'https://example.com/greetings/default',
54
+ name: 'greeting-resource',
55
+ description: 'A simple greeting resource',
56
+ mimeType: 'text/plain',
57
+ load: async () => {
58
+ return {
59
+ text: 'Hello, world!',
60
+ };
61
+ },
62
+ });
63
+
64
+ export { server };
65
+ `;
66
+ }
@@ -0,0 +1,87 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getServerTemplate, getIndexTemplate, getReadmeTemplate } from './index.js';
3
+ describe('fastmcp templates', () => {
4
+ const projectName = 'test-project';
5
+ describe('getServerTemplate', () => {
6
+ it('should include project name in server config', () => {
7
+ const template = getServerTemplate(projectName);
8
+ expect(template).toContain(`name: '${projectName}'`);
9
+ });
10
+ it('should import FastMCP', () => {
11
+ const template = getServerTemplate(projectName);
12
+ expect(template).toContain("from 'fastmcp'");
13
+ expect(template).toContain('FastMCP');
14
+ });
15
+ it('should include example prompt', () => {
16
+ const template = getServerTemplate(projectName);
17
+ expect(template).toContain('greeting-template');
18
+ expect(template).toContain('addPrompt');
19
+ });
20
+ it('should include example tool', () => {
21
+ const template = getServerTemplate(projectName);
22
+ expect(template).toContain('start-notification-stream');
23
+ expect(template).toContain('addTool');
24
+ });
25
+ it('should include example resource', () => {
26
+ const template = getServerTemplate(projectName);
27
+ expect(template).toContain('greeting-resource');
28
+ expect(template).toContain('addResource');
29
+ });
30
+ it('should use zod for parameter validation', () => {
31
+ const template = getServerTemplate(projectName);
32
+ expect(template).toContain("from 'zod'");
33
+ expect(template).toContain('z.object');
34
+ });
35
+ });
36
+ describe('getIndexTemplate', () => {
37
+ it('should import server from server.ts', () => {
38
+ const template = getIndexTemplate();
39
+ expect(template).toContain("from './server.js'");
40
+ });
41
+ it('should use httpStream transport', () => {
42
+ const template = getIndexTemplate();
43
+ expect(template).toContain("transportType: 'httpStream'");
44
+ });
45
+ it('should use PORT from environment variable', () => {
46
+ const template = getIndexTemplate();
47
+ expect(template).toContain('process.env.PORT');
48
+ });
49
+ it('should NOT include stateless option by default', () => {
50
+ const template = getIndexTemplate();
51
+ expect(template).not.toContain('stateless: true');
52
+ });
53
+ it('should include stateless option when specified', () => {
54
+ const template = getIndexTemplate({ stateless: true });
55
+ expect(template).toContain('stateless: true');
56
+ });
57
+ });
58
+ describe('getReadmeTemplate', () => {
59
+ it('should include project name', () => {
60
+ const template = getReadmeTemplate(projectName);
61
+ expect(template).toContain(`# ${projectName}`);
62
+ });
63
+ it('should mention FastMCP', () => {
64
+ const template = getReadmeTemplate(projectName);
65
+ expect(template).toContain('FastMCP');
66
+ expect(template).toContain('github.com/punkpeye/fastmcp');
67
+ });
68
+ it('should include npm commands by default', () => {
69
+ const template = getReadmeTemplate(projectName);
70
+ expect(template).toContain('npm install');
71
+ expect(template).toContain('npm run dev');
72
+ });
73
+ it('should use pnpm commands when specified', () => {
74
+ const template = getReadmeTemplate(projectName, { packageManager: 'pnpm' });
75
+ expect(template).toContain('pnpm install');
76
+ expect(template).toContain('pnpm dev');
77
+ });
78
+ it('should describe stateful mode by default', () => {
79
+ const template = getReadmeTemplate(projectName);
80
+ expect(template).toContain('stateful');
81
+ });
82
+ it('should describe stateless mode when specified', () => {
83
+ const template = getReadmeTemplate(projectName, { stateless: true });
84
+ expect(template).toContain('stateless');
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,6 @@
1
+ export type { SdkTemplateOptions as TemplateOptions } from '../../common/types.js';
2
+ import type { SdkTemplateOptions } from '../../common/types.js';
3
+ export declare function getIndexTemplate(options?: SdkTemplateOptions): string;
4
+ export { getServerTemplate } from './server.js';
5
+ export { getReadmeTemplate } from './readme.js';
6
+ export { getAuthTemplate } from './auth.js';
@@ -29,10 +29,6 @@ import { getServer } from './server.js';`;
29
29
  const deleteRoute = withOAuth
30
30
  ? `app.delete('/mcp', authMiddleware, async (req: Request, res: Response) => {`
31
31
  : `app.delete('/mcp', async (req: Request, res: Response) => {`;
32
- const startupLog = withOAuth
33
- ? `console.log(\`MCP Stateful HTTP Server listening on port \${PORT}\`);
34
- console.log(\`OAuth metadata available at \${getOAuthMetadataUrl()}\`);`
35
- : `console.log(\`MCP Stateful HTTP Server listening on port \${PORT}\`);`;
36
32
  return `${imports}
37
33
 
38
34
  ${appSetup}
@@ -144,6 +140,24 @@ ${withOAuth
144
140
  ? `// Start the server
145
141
  const PORT = process.env.PORT || 3000;
146
142
 
143
+ function startServer(port: number | string): void {
144
+ const server = app.listen(port, () => {
145
+ console.log(\`MCP Stateful HTTP Server listening on port \${port}\`);
146
+ console.log(\`OAuth metadata available at \${getOAuthMetadataUrl()}\`);
147
+ });
148
+
149
+ server.on('error', (error: NodeJS.ErrnoException) => {
150
+ if (error.code === 'EADDRINUSE') {
151
+ const randomPort = Math.floor(Math.random() * (65535 - 49152) + 49152);
152
+ console.log(\`Port \${port} is in use, trying port \${randomPort}...\`);
153
+ startServer(randomPort);
154
+ } else {
155
+ console.error('Failed to start server:', error);
156
+ process.exit(1);
157
+ }
158
+ });
159
+ }
160
+
147
161
  async function main() {
148
162
  // Validate OAuth configuration and fetch OIDC discovery document
149
163
  await validateOAuthConfig();
@@ -151,9 +165,7 @@ async function main() {
151
165
  // Setup OAuth metadata routes (must be after validateOAuthConfig)
152
166
  setupAuthMetadataRouter(app);
153
167
 
154
- app.listen(PORT, () => {
155
- ${startupLog}
156
- });
168
+ startServer(PORT);
157
169
  }
158
170
 
159
171
  main().catch((error) => {
@@ -162,9 +174,25 @@ main().catch((error) => {
162
174
  });`
163
175
  : `// Start the server
164
176
  const PORT = process.env.PORT || 3000;
165
- app.listen(PORT, () => {
166
- ${startupLog}
167
- });`}
177
+
178
+ function startServer(port: number | string): void {
179
+ const server = app.listen(port, () => {
180
+ console.log(\`MCP Stateful HTTP Server listening on port \${port}\`);
181
+ });
182
+
183
+ server.on('error', (error: NodeJS.ErrnoException) => {
184
+ if (error.code === 'EADDRINUSE') {
185
+ const randomPort = Math.floor(Math.random() * (65535 - 49152) + 49152);
186
+ console.log(\`Port \${port} is in use, trying port \${randomPort}...\`);
187
+ startServer(randomPort);
188
+ } else {
189
+ console.error('Failed to start server:', error);
190
+ process.exit(1);
191
+ }
192
+ });
193
+ }
194
+
195
+ startServer(PORT);`}
168
196
 
169
197
  // Handle server shutdown
170
198
  process.on('SIGINT', async () => {
@@ -1,5 +1,11 @@
1
1
  export function getReadmeTemplate(projectName, options) {
2
2
  const withOAuth = options?.withOAuth ?? false;
3
+ const packageManager = options?.packageManager ?? 'npm';
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' },
8
+ }[packageManager];
3
9
  const description = withOAuth
4
10
  ? 'A stateful streamable HTTP MCP (Model Context Protocol) server with session management and OAuth authentication.'
5
11
  : 'A stateful streamable HTTP MCP (Model Context Protocol) server with session management.';
@@ -85,14 +91,14 @@ This project was created with [@agentailor/create-mcp-server](https://www.npmjs.
85
91
 
86
92
  \`\`\`bash
87
93
  # Install dependencies
88
- npm install
94
+ ${commands.install}
89
95
 
90
96
  # Build and run in development
91
- npm run dev
97
+ ${commands.dev}
92
98
 
93
99
  # Or build and start separately
94
- npm run build
95
- npm start
100
+ ${commands.build}
101
+ ${commands.start}
96
102
  \`\`\`
97
103
 
98
104
  The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable.
@@ -0,0 +1 @@
1
+ export { getServerTemplate } from '../stateless/server.js';
@@ -0,0 +1,2 @@
1
+ // Re-export from stateless template - server logic is identical
2
+ export { getServerTemplate } from '../stateless/server.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { getServerTemplate, getIndexTemplate, getReadmeTemplate } from './index.js';
3
- describe('stateful-streamable-http templates', () => {
3
+ describe('sdk/stateful templates', () => {
4
4
  const projectName = 'test-project';
5
5
  describe('getServerTemplate', () => {
6
6
  it('should include project name in server config', () => {
@@ -83,10 +83,12 @@ describe('stateful-streamable-http templates', () => {
83
83
  const template = getReadmeTemplate(projectName);
84
84
  expect(template).toContain(`# ${projectName}`);
85
85
  });
86
- it('should include getting started instructions', () => {
86
+ it('should include getting started instructions with npm by default', () => {
87
87
  const template = getReadmeTemplate(projectName);
88
88
  expect(template).toContain('npm install');
89
89
  expect(template).toContain('npm run dev');
90
+ expect(template).toContain('npm run build');
91
+ expect(template).toContain('npm start');
90
92
  });
91
93
  it('should document the /mcp endpoint', () => {
92
94
  const template = getReadmeTemplate(projectName);
@@ -108,6 +110,31 @@ describe('stateful-streamable-http templates', () => {
108
110
  expect(template).toContain('DELETE /mcp');
109
111
  });
110
112
  });
113
+ describe('getReadmeTemplate with package manager', () => {
114
+ it('should use npm commands when packageManager is npm', () => {
115
+ const template = getReadmeTemplate(projectName, { packageManager: 'npm' });
116
+ expect(template).toContain('npm install');
117
+ expect(template).toContain('npm run dev');
118
+ expect(template).toContain('npm run build');
119
+ expect(template).toContain('npm start');
120
+ });
121
+ it('should use pnpm commands when packageManager is pnpm', () => {
122
+ const template = getReadmeTemplate(projectName, { packageManager: 'pnpm' });
123
+ expect(template).toContain('pnpm install');
124
+ expect(template).toContain('pnpm dev');
125
+ expect(template).toContain('pnpm build');
126
+ expect(template).toContain('pnpm start');
127
+ expect(template).not.toContain('npm run');
128
+ });
129
+ it('should use yarn commands when packageManager is yarn', () => {
130
+ const template = getReadmeTemplate(projectName, { packageManager: 'yarn' });
131
+ expect(template).toContain('yarn\n');
132
+ expect(template).toContain('yarn dev');
133
+ expect(template).toContain('yarn build');
134
+ expect(template).toContain('yarn start');
135
+ expect(template).not.toContain('npm run');
136
+ });
137
+ });
111
138
  describe('getIndexTemplate with OAuth', () => {
112
139
  it('should include auth imports when OAuth enabled', () => {
113
140
  const template = getIndexTemplate({ withOAuth: true });
@@ -0,0 +1,5 @@
1
+ export type { SdkTemplateOptions as TemplateOptions } from '../../common/types.js';
2
+ import type { SdkTemplateOptions } from '../../common/types.js';
3
+ export declare function getIndexTemplate(options?: SdkTemplateOptions): string;
4
+ export { getServerTemplate } from './server.js';
5
+ export { getReadmeTemplate } from './readme.js';
@@ -66,13 +66,25 @@ app.delete('/mcp', async (req: Request, res: Response) => {
66
66
 
67
67
  // Start the server
68
68
  const PORT = process.env.PORT || 3000;
69
- app.listen(PORT, (error) => {
70
- if (error) {
71
- console.error('Failed to start server:', error);
72
- process.exit(1);
73
- }
74
- console.log(\`MCP Streamable HTTP Server listening on port \${PORT}\`);
75
- });
69
+
70
+ function startServer(port: number | string): void {
71
+ const server = app.listen(port, () => {
72
+ console.log(\`MCP Streamable HTTP Server listening on port \${port}\`);
73
+ });
74
+
75
+ server.on('error', (error: NodeJS.ErrnoException) => {
76
+ if (error.code === 'EADDRINUSE') {
77
+ const randomPort = Math.floor(Math.random() * (65535 - 49152) + 49152);
78
+ console.log(\`Port \${port} is in use, trying port \${randomPort}...\`);
79
+ startServer(randomPort);
80
+ } else {
81
+ console.error('Failed to start server:', error);
82
+ process.exit(1);
83
+ }
84
+ });
85
+ }
86
+
87
+ startServer(PORT);
76
88
 
77
89
  // Handle server shutdown
78
90
  process.on('SIGINT', async () => {
@@ -0,0 +1,2 @@
1
+ import type { TemplateOptions } from './index.js';
2
+ export declare function getReadmeTemplate(projectName: string, options?: TemplateOptions): string;
@@ -1,6 +1,10 @@
1
- // Options parameter added for type consistency with stateful template (OAuth not supported in stateless)
2
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3
1
  export function getReadmeTemplate(projectName, options) {
2
+ const packageManager = options?.packageManager ?? 'npm';
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' },
7
+ }[packageManager];
4
8
  return `# ${projectName}
5
9
 
6
10
  A stateless streamable HTTP MCP (Model Context Protocol) server.
@@ -13,14 +17,14 @@ This project was created with [@agentailor/create-mcp-server](https://www.npmjs.
13
17
 
14
18
  \`\`\`bash
15
19
  # Install dependencies
16
- npm install
20
+ ${commands.install}
17
21
 
18
22
  # Build and run in development
19
- npm run dev
23
+ ${commands.dev}
20
24
 
21
25
  # Or build and start separately
22
- npm run build
23
- npm start
26
+ ${commands.build}
27
+ ${commands.start}
24
28
  \`\`\`
25
29
 
26
30
  The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable.
@@ -0,0 +1 @@
1
+ export declare function getServerTemplate(projectName: string): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { getServerTemplate, getIndexTemplate, getReadmeTemplate } from './index.js';
3
- describe('streamable-http templates', () => {
3
+ describe('sdk/stateless templates', () => {
4
4
  const projectName = 'test-project';
5
5
  describe('getServerTemplate', () => {
6
6
  it('should include project name in server config', () => {
@@ -63,10 +63,12 @@ describe('streamable-http templates', () => {
63
63
  const template = getReadmeTemplate(projectName);
64
64
  expect(template).toContain(`# ${projectName}`);
65
65
  });
66
- it('should include getting started instructions', () => {
66
+ it('should include getting started instructions with npm by default', () => {
67
67
  const template = getReadmeTemplate(projectName);
68
68
  expect(template).toContain('npm install');
69
69
  expect(template).toContain('npm run dev');
70
+ expect(template).toContain('npm run build');
71
+ expect(template).toContain('npm start');
70
72
  });
71
73
  it('should document the /mcp endpoint', () => {
72
74
  const template = getReadmeTemplate(projectName);
@@ -77,4 +79,29 @@ describe('streamable-http templates', () => {
77
79
  expect(template).toContain('stateless');
78
80
  });
79
81
  });
82
+ describe('getReadmeTemplate with package manager', () => {
83
+ it('should use npm commands when packageManager is npm', () => {
84
+ const template = getReadmeTemplate(projectName, { packageManager: 'npm' });
85
+ expect(template).toContain('npm install');
86
+ expect(template).toContain('npm run dev');
87
+ expect(template).toContain('npm run build');
88
+ expect(template).toContain('npm start');
89
+ });
90
+ it('should use pnpm commands when packageManager is pnpm', () => {
91
+ const template = getReadmeTemplate(projectName, { packageManager: 'pnpm' });
92
+ expect(template).toContain('pnpm install');
93
+ expect(template).toContain('pnpm dev');
94
+ expect(template).toContain('pnpm build');
95
+ expect(template).toContain('pnpm start');
96
+ expect(template).not.toContain('npm run');
97
+ });
98
+ it('should use yarn commands when packageManager is yarn', () => {
99
+ const template = getReadmeTemplate(projectName, { packageManager: 'yarn' });
100
+ expect(template).toContain('yarn\n');
101
+ expect(template).toContain('yarn dev');
102
+ expect(template).toContain('yarn build');
103
+ expect(template).toContain('yarn start');
104
+ expect(template).not.toContain('npm run');
105
+ });
106
+ });
80
107
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentailor/create-mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Create a new MCP (Model Context Protocol) server project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +0,0 @@
1
- export interface TemplateOptions {
2
- withOAuth?: boolean;
3
- }
4
- export declare function getIndexTemplate(options?: TemplateOptions): string;
5
- export { getServerTemplate } from './server.js';
6
- export { getReadmeTemplate } from './readme.js';
7
- export { getAuthTemplate } from './auth.js';
@@ -1 +0,0 @@
1
- export { getServerTemplate } from '../streamable-http/server.js';
@@ -1,2 +0,0 @@
1
- // Re-export from stateless template - server logic is identical
2
- export { getServerTemplate } from '../streamable-http/server.js';
@@ -1,6 +0,0 @@
1
- export interface TemplateOptions {
2
- withOAuth?: boolean;
3
- }
4
- export declare function getIndexTemplate(options?: TemplateOptions): string;
5
- export { getServerTemplate } from './server.js';
6
- export { getReadmeTemplate } from './readme.js';