@agentailor/create-mcp-server 0.2.1 → 0.4.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 +38 -6
- package/dist/index.js +67 -29
- package/dist/templates/common/env.example.d.ts +2 -5
- package/dist/templates/common/package.json.d.ts +2 -5
- package/dist/templates/common/package.json.js +34 -13
- package/dist/templates/common/templates.test.js +25 -1
- package/dist/templates/common/types.d.ts +29 -0
- package/dist/templates/deployment/dockerfile.d.ts +2 -0
- package/dist/templates/deployment/dockerfile.js +64 -0
- package/dist/templates/deployment/dockerignore.d.ts +1 -0
- package/dist/templates/deployment/dockerignore.js +37 -0
- package/dist/templates/deployment/index.d.ts +2 -0
- package/dist/templates/deployment/index.js +2 -0
- package/dist/templates/deployment/templates.test.js +109 -0
- package/dist/templates/fastmcp/index.d.ts +5 -0
- package/dist/templates/fastmcp/index.js +19 -0
- package/dist/templates/fastmcp/readme.js +94 -0
- package/dist/templates/fastmcp/server.js +66 -0
- package/dist/templates/fastmcp/templates.test.js +87 -0
- package/dist/templates/sdk/stateful/auth.test.d.ts +1 -0
- package/dist/templates/sdk/stateful/index.d.ts +6 -0
- package/dist/templates/{stateful-streamable-http → sdk/stateful}/index.js +4 -1
- package/dist/templates/{stateful-streamable-http → sdk/stateful}/readme.js +16 -0
- package/dist/templates/sdk/stateful/server.d.ts +1 -0
- package/dist/templates/sdk/stateful/server.js +2 -0
- package/dist/templates/sdk/stateful/templates.test.d.ts +1 -0
- package/dist/templates/{stateful-streamable-http → sdk/stateful}/templates.test.js +1 -1
- package/dist/templates/sdk/stateless/index.d.ts +5 -0
- package/dist/templates/{streamable-http → sdk/stateless}/index.js +3 -0
- package/dist/templates/sdk/stateless/readme.d.ts +2 -0
- package/dist/templates/{streamable-http → sdk/stateless}/readme.js +14 -1
- package/dist/templates/sdk/stateless/server.d.ts +1 -0
- package/dist/templates/sdk/stateless/templates.test.d.ts +1 -0
- package/dist/templates/{streamable-http → sdk/stateless}/templates.test.js +1 -1
- package/package.json +1 -1
- package/dist/templates/stateful-streamable-http/index.d.ts +0 -8
- package/dist/templates/stateful-streamable-http/server.d.ts +0 -1
- package/dist/templates/stateful-streamable-http/server.js +0 -2
- package/dist/templates/streamable-http/index.d.ts +0 -7
- /package/dist/templates/{stateful-streamable-http/auth.test.d.ts → common/types.js} +0 -0
- /package/dist/templates/{stateful-streamable-http → deployment}/templates.test.d.ts +0 -0
- /package/dist/templates/{stateful-streamable-http → fastmcp}/readme.d.ts +0 -0
- /package/dist/templates/{streamable-http → fastmcp}/server.d.ts +0 -0
- /package/dist/templates/{streamable-http → fastmcp}/templates.test.d.ts +0 -0
- /package/dist/templates/{stateful-streamable-http → sdk/stateful}/auth.d.ts +0 -0
- /package/dist/templates/{stateful-streamable-http → sdk/stateful}/auth.js +0 -0
- /package/dist/templates/{stateful-streamable-http → sdk/stateful}/auth.test.js +0 -0
- /package/dist/templates/{streamable-http → sdk/stateful}/readme.d.ts +0 -0
- /package/dist/templates/{streamable-http → sdk/stateless}/server.js +0 -0
package/README.md
CHANGED
|
@@ -10,24 +10,55 @@ npx @agentailor/create-mcp-server
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **Two
|
|
14
|
-
- **
|
|
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
|
|
17
|
+
- **TypeScript ready** — ready to customize
|
|
18
|
+
- **Docker ready** — production Dockerfile included
|
|
17
19
|
- **MCP Inspector** — built-in debugging with `npm run inspect`
|
|
18
20
|
|
|
19
|
-
##
|
|
21
|
+
## Frameworks
|
|
22
|
+
|
|
23
|
+
| Framework | Description |
|
|
24
|
+
|-----------|-------------|
|
|
25
|
+
| **Official MCP SDK** (default) | Full control with Express.js, supports OAuth |
|
|
26
|
+
| **FastMCP** | Simpler API with less boilerplate |
|
|
27
|
+
|
|
28
|
+
### FastMCP
|
|
29
|
+
|
|
30
|
+
[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.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { FastMCP } from "fastmcp";
|
|
34
|
+
import { z } from "zod";
|
|
35
|
+
|
|
36
|
+
const server = new FastMCP({ name: "My Server", version: "1.0.0" });
|
|
37
|
+
|
|
38
|
+
server.addTool({
|
|
39
|
+
name: "add",
|
|
40
|
+
description: "Add two numbers",
|
|
41
|
+
parameters: z.object({ a: z.number(), b: z.number() }),
|
|
42
|
+
execute: async ({ a, b }) => String(a + b),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
server.start({ transportType: "httpStream", httpStream: { port: 3000 } });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Learn more: [FastMCP Documentation](https://github.com/punkpeye/fastmcp)
|
|
49
|
+
|
|
50
|
+
## Server Modes
|
|
20
51
|
|
|
21
52
|
| Feature | Stateless (default) | Stateful |
|
|
22
53
|
|---------|---------------------|----------|
|
|
23
54
|
| Session management | — | ✓ |
|
|
24
55
|
| SSE support | — | ✓ |
|
|
25
|
-
| OAuth option | — | ✓ |
|
|
56
|
+
| OAuth option (SDK only) | — | ✓ |
|
|
26
57
|
| Endpoints | POST /mcp | POST, GET, DELETE /mcp |
|
|
27
58
|
|
|
28
59
|
**Stateless**: Simple HTTP server — each request creates a new transport instance.
|
|
29
60
|
|
|
30
|
-
**Stateful**: Session-based server with transport reuse, Server-Sent Events for real-time updates, and optional OAuth authentication.
|
|
61
|
+
**Stateful**: Session-based server with transport reuse, Server-Sent Events for real-time updates, and optional OAuth authentication (SDK only).
|
|
31
62
|
|
|
32
63
|
## Generated Project
|
|
33
64
|
|
|
@@ -37,6 +68,7 @@ my-mcp-server/
|
|
|
37
68
|
│ ├── server.ts # MCP server (tools, prompts, resources)
|
|
38
69
|
│ ├── index.ts # Express app and transport setup
|
|
39
70
|
│ └── auth.ts # OAuth middleware (if enabled)
|
|
71
|
+
├── Dockerfile # Production-ready Docker build
|
|
40
72
|
├── package.json
|
|
41
73
|
├── tsconfig.json
|
|
42
74
|
├── .gitignore
|
package/dist/index.js
CHANGED
|
@@ -7,21 +7,28 @@ 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
|
|
11
|
-
import { getServerTemplate as
|
|
12
|
-
|
|
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 = {
|
|
13
15
|
stateless: {
|
|
14
|
-
getServerTemplate:
|
|
15
|
-
getIndexTemplate:
|
|
16
|
-
getReadmeTemplate:
|
|
16
|
+
getServerTemplate: getSdkStatelessServerTemplate,
|
|
17
|
+
getIndexTemplate: getSdkStatelessIndexTemplate,
|
|
18
|
+
getReadmeTemplate: getSdkStatelessReadmeTemplate,
|
|
17
19
|
},
|
|
18
20
|
stateful: {
|
|
19
|
-
getServerTemplate:
|
|
20
|
-
getIndexTemplate:
|
|
21
|
-
getReadmeTemplate:
|
|
22
|
-
getAuthTemplate:
|
|
21
|
+
getServerTemplate: getSdkStatefulServerTemplate,
|
|
22
|
+
getIndexTemplate: getSdkStatefulIndexTemplate,
|
|
23
|
+
getReadmeTemplate: getSdkStatefulReadmeTemplate,
|
|
24
|
+
getAuthTemplate: getSdkAuthTemplate,
|
|
23
25
|
},
|
|
24
26
|
};
|
|
27
|
+
const fastmcpTemplateFunctions = {
|
|
28
|
+
getServerTemplate: getFastMCPServerTemplate,
|
|
29
|
+
getIndexTemplate: getFastMCPIndexTemplate,
|
|
30
|
+
getReadmeTemplate: getFastMCPReadmeTemplate,
|
|
31
|
+
};
|
|
25
32
|
const packageManagerCommands = {
|
|
26
33
|
npm: { install: 'npm install', dev: 'npm run dev' },
|
|
27
34
|
pnpm: { install: 'pnpm install', dev: 'pnpm dev' },
|
|
@@ -64,12 +71,33 @@ async function main() {
|
|
|
64
71
|
initial: 0,
|
|
65
72
|
}, { onCancel });
|
|
66
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
|
|
67
95
|
const templateTypeResponse = await prompts({
|
|
68
96
|
type: 'select',
|
|
69
97
|
name: 'templateType',
|
|
70
|
-
message: '
|
|
98
|
+
message: 'Server mode:',
|
|
71
99
|
choices: [
|
|
72
|
-
{ title: 'Stateless', value: 'stateless', description: 'Simple
|
|
100
|
+
{ title: 'Stateless', value: 'stateless', description: 'Simple HTTP server' },
|
|
73
101
|
{
|
|
74
102
|
title: 'Stateful',
|
|
75
103
|
value: 'stateful',
|
|
@@ -79,9 +107,9 @@ async function main() {
|
|
|
79
107
|
initial: 0,
|
|
80
108
|
}, { onCancel });
|
|
81
109
|
const templateType = templateTypeResponse.templateType || 'stateless';
|
|
82
|
-
// OAuth prompt - only for stateful template
|
|
110
|
+
// OAuth prompt - only for SDK stateful template
|
|
83
111
|
let withOAuth = false;
|
|
84
|
-
if (templateType === 'stateful') {
|
|
112
|
+
if (framework === 'sdk' && templateType === 'stateful') {
|
|
85
113
|
const oauthResponse = await prompts({
|
|
86
114
|
type: 'confirm',
|
|
87
115
|
name: 'withOAuth',
|
|
@@ -98,27 +126,36 @@ async function main() {
|
|
|
98
126
|
initial: true,
|
|
99
127
|
}, { onCancel });
|
|
100
128
|
const withGitInit = gitInitResponse.withGitInit ?? false;
|
|
101
|
-
const templateOptions = {
|
|
102
|
-
|
|
129
|
+
const templateOptions = {
|
|
130
|
+
withOAuth,
|
|
131
|
+
packageManager,
|
|
132
|
+
framework,
|
|
133
|
+
stateless: templateType === 'stateless',
|
|
134
|
+
};
|
|
103
135
|
const projectPath = join(process.cwd(), projectName);
|
|
104
136
|
const srcPath = join(projectPath, 'src');
|
|
105
137
|
try {
|
|
106
138
|
// Create directories
|
|
107
139
|
await mkdir(srcPath, { recursive: true });
|
|
108
140
|
// Build list of files to write
|
|
109
|
-
const filesToWrite = [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
writeFile(join(projectPath, '
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
}
|
|
121
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()));
|
|
122
159
|
// Write all template files
|
|
123
160
|
await Promise.all(filesToWrite);
|
|
124
161
|
// Initialize git repository if requested
|
|
@@ -131,7 +168,8 @@ async function main() {
|
|
|
131
168
|
}
|
|
132
169
|
}
|
|
133
170
|
const commands = packageManagerCommands[packageManager];
|
|
134
|
-
|
|
171
|
+
const frameworkName = framework === 'fastmcp' ? 'FastMCP' : 'MCP SDK';
|
|
172
|
+
console.log(`\n✅ Created ${projectName} with ${frameworkName} at ${projectPath}`);
|
|
135
173
|
console.log(`\nNext steps:`);
|
|
136
174
|
console.log(` cd ${projectName}`);
|
|
137
175
|
console.log(` ${commands.install}`);
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
packageManager?: 'npm' | 'pnpm' | 'yarn';
|
|
4
|
-
}
|
|
5
|
-
export declare function getEnvExampleTemplate(options?: TemplateOptions): string;
|
|
1
|
+
import type { CommonTemplateOptions } from './types.js';
|
|
2
|
+
export declare function getEnvExampleTemplate(options?: CommonTemplateOptions): string;
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
packageManager?: 'npm' | 'pnpm' | 'yarn';
|
|
4
|
-
}
|
|
5
|
-
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
dependencies
|
|
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
|
|
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,64 @@
|
|
|
1
|
+
const packageManagerConfigs = {
|
|
2
|
+
npm: {
|
|
3
|
+
lockFile: 'package-lock.json',
|
|
4
|
+
install: 'npm ci',
|
|
5
|
+
installProd: 'npm ci --omit=dev',
|
|
6
|
+
build: 'npm run build',
|
|
7
|
+
},
|
|
8
|
+
pnpm: {
|
|
9
|
+
lockFile: 'pnpm-lock.yaml',
|
|
10
|
+
install: 'pnpm install --frozen-lockfile',
|
|
11
|
+
installProd: 'pnpm install --frozen-lockfile --prod',
|
|
12
|
+
build: 'pnpm run build',
|
|
13
|
+
setup: 'RUN corepack enable && corepack prepare pnpm@latest --activate',
|
|
14
|
+
},
|
|
15
|
+
yarn: {
|
|
16
|
+
lockFile: 'yarn.lock',
|
|
17
|
+
install: 'yarn install --frozen-lockfile',
|
|
18
|
+
installProd: 'yarn install --frozen-lockfile --production',
|
|
19
|
+
build: 'yarn build',
|
|
20
|
+
setup: 'RUN corepack enable',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export function getDockerfileTemplate(options) {
|
|
24
|
+
const packageManager = options?.packageManager ?? 'npm';
|
|
25
|
+
const config = packageManagerConfigs[packageManager];
|
|
26
|
+
const setupStep = config.setup ? `\n${config.setup}\n` : '';
|
|
27
|
+
return `# Multi-stage build for production
|
|
28
|
+
FROM node:20-alpine AS builder
|
|
29
|
+
|
|
30
|
+
WORKDIR /app
|
|
31
|
+
${setupStep}
|
|
32
|
+
# Copy package files
|
|
33
|
+
COPY package.json ${config.lockFile} ./
|
|
34
|
+
|
|
35
|
+
# Install all dependencies (including dev)
|
|
36
|
+
RUN ${config.install}
|
|
37
|
+
|
|
38
|
+
# Copy source code
|
|
39
|
+
COPY . .
|
|
40
|
+
|
|
41
|
+
# Build the application
|
|
42
|
+
RUN ${config.build}
|
|
43
|
+
|
|
44
|
+
# Production stage
|
|
45
|
+
FROM node:20-alpine AS production
|
|
46
|
+
|
|
47
|
+
WORKDIR /app
|
|
48
|
+
${setupStep}
|
|
49
|
+
# Copy package files
|
|
50
|
+
COPY package.json ${config.lockFile} ./
|
|
51
|
+
|
|
52
|
+
# Install production dependencies only
|
|
53
|
+
RUN ${config.installProd}
|
|
54
|
+
|
|
55
|
+
# Copy built application from builder stage
|
|
56
|
+
COPY --from=builder /app/dist ./dist
|
|
57
|
+
|
|
58
|
+
# Expose the port the app runs on
|
|
59
|
+
EXPOSE 3000
|
|
60
|
+
|
|
61
|
+
# Start the application
|
|
62
|
+
CMD ["node", "dist/index.js"]
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getDockerignoreTemplate(): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function getDockerignoreTemplate() {
|
|
2
|
+
return `# Dependencies
|
|
3
|
+
node_modules/
|
|
4
|
+
|
|
5
|
+
# Build output (rebuilt in container)
|
|
6
|
+
dist/
|
|
7
|
+
|
|
8
|
+
# Git
|
|
9
|
+
.git/
|
|
10
|
+
.gitignore
|
|
11
|
+
|
|
12
|
+
# Environment files (should be set in container)
|
|
13
|
+
.env
|
|
14
|
+
.env.*
|
|
15
|
+
|
|
16
|
+
# Documentation
|
|
17
|
+
*.md
|
|
18
|
+
|
|
19
|
+
# IDE
|
|
20
|
+
.vscode/
|
|
21
|
+
.idea/
|
|
22
|
+
|
|
23
|
+
# OS files
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
|
|
27
|
+
# Logs
|
|
28
|
+
*.log
|
|
29
|
+
npm-debug.log*
|
|
30
|
+
|
|
31
|
+
# Test files
|
|
32
|
+
*.test.ts
|
|
33
|
+
*.spec.ts
|
|
34
|
+
__tests__/
|
|
35
|
+
coverage/
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getDockerfileTemplate } from './dockerfile.js';
|
|
3
|
+
import { getDockerignoreTemplate } from './dockerignore.js';
|
|
4
|
+
describe('deployment templates', () => {
|
|
5
|
+
describe('getDockerfileTemplate', () => {
|
|
6
|
+
it('should use multi-stage build', () => {
|
|
7
|
+
const template = getDockerfileTemplate();
|
|
8
|
+
expect(template).toContain('AS builder');
|
|
9
|
+
expect(template).toContain('AS production');
|
|
10
|
+
});
|
|
11
|
+
it('should use Node 20 Alpine', () => {
|
|
12
|
+
const template = getDockerfileTemplate();
|
|
13
|
+
expect(template).toContain('FROM node:20-alpine');
|
|
14
|
+
});
|
|
15
|
+
it('should copy dist folder from builder', () => {
|
|
16
|
+
const template = getDockerfileTemplate();
|
|
17
|
+
expect(template).toContain('COPY --from=builder /app/dist ./dist');
|
|
18
|
+
});
|
|
19
|
+
it('should expose port 3000', () => {
|
|
20
|
+
const template = getDockerfileTemplate();
|
|
21
|
+
expect(template).toContain('EXPOSE 3000');
|
|
22
|
+
});
|
|
23
|
+
it('should run node dist/index.js', () => {
|
|
24
|
+
const template = getDockerfileTemplate();
|
|
25
|
+
expect(template).toContain('CMD ["node", "dist/index.js"]');
|
|
26
|
+
});
|
|
27
|
+
describe('npm (default)', () => {
|
|
28
|
+
it('should use npm ci for install', () => {
|
|
29
|
+
const template = getDockerfileTemplate();
|
|
30
|
+
expect(template).toContain('RUN npm ci');
|
|
31
|
+
});
|
|
32
|
+
it('should use npm ci --omit=dev for production', () => {
|
|
33
|
+
const template = getDockerfileTemplate();
|
|
34
|
+
expect(template).toContain('npm ci --omit=dev');
|
|
35
|
+
});
|
|
36
|
+
it('should copy package-lock.json', () => {
|
|
37
|
+
const template = getDockerfileTemplate();
|
|
38
|
+
expect(template).toContain('COPY package.json package-lock.json');
|
|
39
|
+
});
|
|
40
|
+
it('should use npm run build', () => {
|
|
41
|
+
const template = getDockerfileTemplate();
|
|
42
|
+
expect(template).toContain('RUN npm run build');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('pnpm', () => {
|
|
46
|
+
it('should use pnpm install --frozen-lockfile', () => {
|
|
47
|
+
const template = getDockerfileTemplate({ packageManager: 'pnpm' });
|
|
48
|
+
expect(template).toContain('RUN pnpm install --frozen-lockfile');
|
|
49
|
+
});
|
|
50
|
+
it('should use pnpm install --frozen-lockfile --prod for production', () => {
|
|
51
|
+
const template = getDockerfileTemplate({ packageManager: 'pnpm' });
|
|
52
|
+
expect(template).toContain('pnpm install --frozen-lockfile --prod');
|
|
53
|
+
});
|
|
54
|
+
it('should copy pnpm-lock.yaml', () => {
|
|
55
|
+
const template = getDockerfileTemplate({ packageManager: 'pnpm' });
|
|
56
|
+
expect(template).toContain('COPY package.json pnpm-lock.yaml');
|
|
57
|
+
});
|
|
58
|
+
it('should enable corepack for pnpm', () => {
|
|
59
|
+
const template = getDockerfileTemplate({ packageManager: 'pnpm' });
|
|
60
|
+
expect(template).toContain('corepack enable');
|
|
61
|
+
expect(template).toContain('corepack prepare pnpm');
|
|
62
|
+
});
|
|
63
|
+
it('should use pnpm run build', () => {
|
|
64
|
+
const template = getDockerfileTemplate({ packageManager: 'pnpm' });
|
|
65
|
+
expect(template).toContain('RUN pnpm run build');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('yarn', () => {
|
|
69
|
+
it('should use yarn install --frozen-lockfile', () => {
|
|
70
|
+
const template = getDockerfileTemplate({ packageManager: 'yarn' });
|
|
71
|
+
expect(template).toContain('RUN yarn install --frozen-lockfile');
|
|
72
|
+
});
|
|
73
|
+
it('should use yarn install --frozen-lockfile --production for production', () => {
|
|
74
|
+
const template = getDockerfileTemplate({ packageManager: 'yarn' });
|
|
75
|
+
expect(template).toContain('yarn install --frozen-lockfile --production');
|
|
76
|
+
});
|
|
77
|
+
it('should copy yarn.lock', () => {
|
|
78
|
+
const template = getDockerfileTemplate({ packageManager: 'yarn' });
|
|
79
|
+
expect(template).toContain('COPY package.json yarn.lock');
|
|
80
|
+
});
|
|
81
|
+
it('should enable corepack for yarn', () => {
|
|
82
|
+
const template = getDockerfileTemplate({ packageManager: 'yarn' });
|
|
83
|
+
expect(template).toContain('corepack enable');
|
|
84
|
+
});
|
|
85
|
+
it('should use yarn build', () => {
|
|
86
|
+
const template = getDockerfileTemplate({ packageManager: 'yarn' });
|
|
87
|
+
expect(template).toContain('RUN yarn build');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('getDockerignoreTemplate', () => {
|
|
92
|
+
it('should ignore node_modules', () => {
|
|
93
|
+
const template = getDockerignoreTemplate();
|
|
94
|
+
expect(template).toContain('node_modules/');
|
|
95
|
+
});
|
|
96
|
+
it('should ignore dist folder', () => {
|
|
97
|
+
const template = getDockerignoreTemplate();
|
|
98
|
+
expect(template).toContain('dist/');
|
|
99
|
+
});
|
|
100
|
+
it('should ignore .env files', () => {
|
|
101
|
+
const template = getDockerignoreTemplate();
|
|
102
|
+
expect(template).toContain('.env');
|
|
103
|
+
});
|
|
104
|
+
it('should ignore .git folder', () => {
|
|
105
|
+
const template = getDockerignoreTemplate();
|
|
106
|
+
expect(template).toContain('.git/');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -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,94 @@
|
|
|
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 Endpoints
|
|
38
|
+
|
|
39
|
+
- **POST /mcp** - Main MCP endpoint for JSON-RPC messages
|
|
40
|
+
- **GET /health** - Health check endpoint (returns 200 OK)
|
|
41
|
+
|
|
42
|
+
## Included Examples
|
|
43
|
+
|
|
44
|
+
This server comes with example implementations to help you get started:
|
|
45
|
+
|
|
46
|
+
### Prompts
|
|
47
|
+
|
|
48
|
+
- **greeting-template** - A simple greeting prompt that takes a name parameter
|
|
49
|
+
|
|
50
|
+
### Tools
|
|
51
|
+
|
|
52
|
+
- **start-notification-stream** - Sends periodic notifications for testing. Parameters:
|
|
53
|
+
- \`interval\`: Milliseconds between notifications (default: 100)
|
|
54
|
+
- \`count\`: Number of notifications to send (default: 10, use 0 for unlimited)
|
|
55
|
+
|
|
56
|
+
### Resources
|
|
57
|
+
|
|
58
|
+
- **greeting-resource** - A simple text resource at \`https://example.com/greetings/default\`
|
|
59
|
+
|
|
60
|
+
## Project Structure
|
|
61
|
+
|
|
62
|
+
\`\`\`
|
|
63
|
+
${projectName}/
|
|
64
|
+
├── src/
|
|
65
|
+
│ ├── server.ts # FastMCP server definition (tools, prompts, resources)
|
|
66
|
+
│ └── index.ts # Server startup configuration
|
|
67
|
+
├── Dockerfile # Multi-stage Docker build
|
|
68
|
+
├── package.json
|
|
69
|
+
├── tsconfig.json
|
|
70
|
+
└── README.md
|
|
71
|
+
\`\`\`
|
|
72
|
+
|
|
73
|
+
## Deployment
|
|
74
|
+
|
|
75
|
+
### Docker
|
|
76
|
+
|
|
77
|
+
Build and run the Docker container:
|
|
78
|
+
|
|
79
|
+
\`\`\`bash
|
|
80
|
+
docker build -t ${projectName} .
|
|
81
|
+
docker run -p 3000:3000 ${projectName}
|
|
82
|
+
\`\`\`
|
|
83
|
+
## Customization
|
|
84
|
+
|
|
85
|
+
- Add new tools, prompts, and resources in \`src/server.ts\`
|
|
86
|
+
- Modify transport configuration in \`src/index.ts\`
|
|
87
|
+
|
|
88
|
+
## Learn More
|
|
89
|
+
|
|
90
|
+
- [FastMCP](https://github.com/punkpeye/fastmcp) - The framework powering this server
|
|
91
|
+
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
|
92
|
+
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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';
|
|
@@ -19,7 +19,10 @@ import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js
|
|
|
19
19
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
20
20
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
21
21
|
import { getServer } from './server.js';`;
|
|
22
|
-
const appSetup = `const app = createMcpExpressApp()
|
|
22
|
+
const appSetup = `const app = createMcpExpressApp();
|
|
23
|
+
|
|
24
|
+
// Health check endpoint for container orchestration
|
|
25
|
+
app.get('/health', (_, res) => res.sendStatus(200));`;
|
|
23
26
|
const postRoute = withOAuth
|
|
24
27
|
? `app.post('/mcp', authMiddleware, async (req: Request, res: Response) => {`
|
|
25
28
|
: `app.post('/mcp', async (req: Request, res: Response) => {`;
|
|
@@ -63,6 +63,7 @@ ${projectName}/
|
|
|
63
63
|
│ ├── server.ts # MCP server definition (tools, prompts, resources)
|
|
64
64
|
│ ├── index.ts # Express app and stateful HTTP transport setup
|
|
65
65
|
│ └── auth.ts # OAuth configuration and middleware
|
|
66
|
+
├── Dockerfile # Multi-stage Docker build
|
|
66
67
|
├── package.json
|
|
67
68
|
├── tsconfig.json
|
|
68
69
|
└── README.md
|
|
@@ -72,10 +73,23 @@ ${projectName}/
|
|
|
72
73
|
├── src/
|
|
73
74
|
│ ├── server.ts # MCP server definition (tools, prompts, resources)
|
|
74
75
|
│ └── index.ts # Express app and stateful HTTP transport setup
|
|
76
|
+
├── Dockerfile # Multi-stage Docker build
|
|
75
77
|
├── package.json
|
|
76
78
|
├── tsconfig.json
|
|
77
79
|
└── README.md
|
|
78
80
|
\`\`\``;
|
|
81
|
+
const deploymentSection = `
|
|
82
|
+
## Deployment
|
|
83
|
+
|
|
84
|
+
### Docker
|
|
85
|
+
|
|
86
|
+
Build and run the Docker container:
|
|
87
|
+
|
|
88
|
+
\`\`\`bash
|
|
89
|
+
docker build -t ${projectName} .
|
|
90
|
+
docker run -p 3000:3000 ${projectName}
|
|
91
|
+
\`\`\`
|
|
92
|
+
`;
|
|
79
93
|
const customizationOAuthNote = withOAuth
|
|
80
94
|
? '\n- Configure OAuth scopes and token verification in `src/auth.ts`'
|
|
81
95
|
: '';
|
|
@@ -112,6 +126,7 @@ ${oauthSection}
|
|
|
112
126
|
- Requires \`mcp-session-id\` header${apiEndpointsOAuthNote}
|
|
113
127
|
- **DELETE /mcp** - Terminate a session
|
|
114
128
|
- Requires \`mcp-session-id\` header${apiEndpointsOAuthNote}
|
|
129
|
+
- **GET /health** - Health check endpoint (returns 200 OK)
|
|
115
130
|
|
|
116
131
|
## Session Management
|
|
117
132
|
|
|
@@ -144,6 +159,7 @@ This server comes with example implementations to help you get started:
|
|
|
144
159
|
## Project Structure
|
|
145
160
|
|
|
146
161
|
${projectStructure}
|
|
162
|
+
${deploymentSection}
|
|
147
163
|
|
|
148
164
|
## Customization
|
|
149
165
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
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
|
|
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', () => {
|
|
@@ -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';
|
|
@@ -8,6 +8,9 @@ import { getServer } from './server.js';
|
|
|
8
8
|
|
|
9
9
|
const app = createMcpExpressApp();
|
|
10
10
|
|
|
11
|
+
// Health check endpoint for container orchestration
|
|
12
|
+
app.get('/health', (_, res) => res.sendStatus(200));
|
|
13
|
+
|
|
11
14
|
app.post('/mcp', async (req: Request, res: Response) => {
|
|
12
15
|
const server = getServer();
|
|
13
16
|
try {
|
|
@@ -29,9 +29,10 @@ ${commands.start}
|
|
|
29
29
|
|
|
30
30
|
The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable.
|
|
31
31
|
|
|
32
|
-
## API
|
|
32
|
+
## API Endpoints
|
|
33
33
|
|
|
34
34
|
- **POST /mcp** - Main MCP endpoint for JSON-RPC messages
|
|
35
|
+
- **GET /health** - Health check endpoint (returns 200 OK)
|
|
35
36
|
|
|
36
37
|
## Included Examples
|
|
37
38
|
|
|
@@ -58,11 +59,23 @@ ${projectName}/
|
|
|
58
59
|
├── src/
|
|
59
60
|
│ ├── server.ts # MCP server definition (tools, prompts, resources)
|
|
60
61
|
│ └── index.ts # Express app and HTTP transport setup
|
|
62
|
+
├── Dockerfile # Multi-stage Docker build
|
|
61
63
|
├── package.json
|
|
62
64
|
├── tsconfig.json
|
|
63
65
|
└── README.md
|
|
64
66
|
\`\`\`
|
|
65
67
|
|
|
68
|
+
## Deployment
|
|
69
|
+
|
|
70
|
+
### Docker
|
|
71
|
+
|
|
72
|
+
Build and run the Docker container:
|
|
73
|
+
|
|
74
|
+
\`\`\`bash
|
|
75
|
+
docker build -t ${projectName} .
|
|
76
|
+
docker run -p 3000:3000 ${projectName}
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
66
79
|
## Customization
|
|
67
80
|
|
|
68
81
|
- Add new tools, prompts, and resources in \`src/server.ts\`
|
|
@@ -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('
|
|
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', () => {
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export interface TemplateOptions {
|
|
2
|
-
withOAuth?: boolean;
|
|
3
|
-
packageManager?: 'npm' | 'pnpm' | 'yarn';
|
|
4
|
-
}
|
|
5
|
-
export declare function getIndexTemplate(options?: TemplateOptions): string;
|
|
6
|
-
export { getServerTemplate } from './server.js';
|
|
7
|
-
export { getReadmeTemplate } from './readme.js';
|
|
8
|
-
export { getAuthTemplate } from './auth.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { getServerTemplate } from '../streamable-http/server.js';
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export interface TemplateOptions {
|
|
2
|
-
withOAuth?: boolean;
|
|
3
|
-
packageManager?: 'npm' | 'pnpm' | 'yarn';
|
|
4
|
-
}
|
|
5
|
-
export declare function getIndexTemplate(options?: TemplateOptions): string;
|
|
6
|
-
export { getServerTemplate } from './server.js';
|
|
7
|
-
export { getReadmeTemplate } from './readme.js';
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|