mcp-framework
Version:
Framework for building Model Context Protocol (MCP) servers in Typescript
220 lines (208 loc) • 6.67 kB
JavaScript
import { spawnSync } from 'child_process';
import { mkdir, writeFile } from 'fs/promises';
import { join } from 'path';
import prompts from 'prompts';
import { generateReadme } from '../templates/readme.js';
import { execa } from 'execa';
export async function createProject(name, options) {
let projectName;
// Default install and example to true if not specified
const shouldInstall = options?.install !== false;
const shouldCreateExample = options?.example !== false;
if (!name) {
const response = await prompts([
{
type: 'text',
name: 'projectName',
message: 'What is the name of your MCP server project?',
validate: (value) => /^[a-z0-9-]+$/.test(value)
? true
: 'Project name can only contain lowercase letters, numbers, and hyphens',
},
]);
if (!response.projectName) {
console.log('Project creation cancelled');
process.exit(1);
}
projectName = response.projectName;
}
else {
projectName = name;
}
if (!projectName) {
throw new Error('Project name is required');
}
const projectDir = join(process.cwd(), projectName);
const srcDir = join(projectDir, 'src');
const toolsDir = join(srcDir, 'tools');
try {
console.log('Creating project structure...');
await mkdir(projectDir);
await mkdir(srcDir);
await mkdir(toolsDir);
const packageJson = {
name: projectName,
version: '0.0.1',
description: `${projectName} MCP server`,
type: 'module',
bin: {
[projectName]: './dist/index.js',
},
files: ['dist'],
scripts: {
build: 'tsc && mcp-build',
watch: 'tsc --watch',
start: 'node dist/index.js',
},
dependencies: {
'mcp-framework': '^0.2.2',
},
devDependencies: {
'@types/node': '^20.11.24',
typescript: '^5.3.3',
},
engines: {
node: '>=18.19.0',
},
};
const tsconfig = {
compilerOptions: {
target: 'ESNext',
module: 'ESNext',
moduleResolution: 'node',
outDir: './dist',
rootDir: './src',
strict: true,
esModuleInterop: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
},
include: ['src/**/*'],
exclude: ['node_modules'],
};
const gitignore = `node_modules
dist
.env
logs
.DS_Store
.idea
.vscode
`;
let indexTs = '';
if (options?.http) {
const port = options.port || 8080;
let transportConfig = `\n transport: {
type: "http-stream",
options: {
port: ${port}`;
if (options.cors) {
transportConfig += `,
cors: {
allowOrigin: "*"
}`;
}
transportConfig += `
}
}`;
indexTs = `import { MCPServer } from "mcp-framework";
const server = new MCPServer({${transportConfig}});
server.start();`;
}
else {
indexTs = `import { MCPServer } from "mcp-framework";
const server = new MCPServer();
server.start();`;
}
const exampleToolTs = `import { MCPTool } from "mcp-framework";
import { z } from "zod";
interface ExampleInput {
message: string;
}
class ExampleTool extends MCPTool<ExampleInput> {
name = "example_tool";
description = "An example tool that processes messages";
schema = {
message: {
type: z.string(),
description: "Message to process",
},
};
async execute(input: ExampleInput) {
return \`Processed: \${input.message}\`;
}
}
export default ExampleTool;`;
const filesToWrite = [
writeFile(join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2)),
writeFile(join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2)),
writeFile(join(projectDir, 'README.md'), generateReadme(projectName)),
writeFile(join(srcDir, 'index.ts'), indexTs),
writeFile(join(projectDir, '.gitignore'), gitignore),
];
if (shouldCreateExample) {
filesToWrite.push(writeFile(join(toolsDir, 'ExampleTool.ts'), exampleToolTs));
}
console.log('Creating project files...');
await Promise.all(filesToWrite);
process.chdir(projectDir);
console.log('Initializing git repository...');
const gitInit = spawnSync('git', ['init'], {
stdio: 'inherit',
shell: true,
});
if (gitInit.status !== 0) {
throw new Error('Failed to initialize git repository');
}
if (shouldInstall) {
console.log('Installing dependencies...');
const npmInstall = spawnSync('npm', ['install'], {
stdio: 'inherit',
shell: true,
});
if (npmInstall.status !== 0) {
throw new Error('Failed to install dependencies');
}
console.log('Building project...');
const tscBuild = await execa('npx', ['tsc'], {
cwd: projectDir,
stdio: 'inherit',
});
if (tscBuild.exitCode !== 0) {
throw new Error('Failed to build TypeScript');
}
const mcpBuild = await execa('npx', ['mcp-build'], {
cwd: projectDir,
stdio: 'inherit',
env: {
...process.env,
MCP_SKIP_VALIDATION: 'true',
},
});
if (mcpBuild.exitCode !== 0) {
throw new Error('Failed to run mcp-build');
}
console.log(`
Project ${projectName} created and built successfully!
You can now:
1. cd ${projectName}
2. Add more tools using:
mcp add tool <n>
`);
}
else {
console.log(`
Project ${projectName} created successfully (without dependencies)!
You can now:
1. cd ${projectName}
2. Run 'npm install' to install dependencies
3. Run 'npm run build' to build the project
4. Add more tools using:
mcp add tool <n>
`);
}
}
catch (error) {
console.error('Error creating project:', error);
process.exit(1);
}
}