Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 75 additions & 66 deletions src/cli/project/create.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
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?: string, options?: { http?: boolean, cors?: boolean, port?: number, install?: boolean, example?: boolean }) {
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?: string,
options?: { http?: boolean; cors?: boolean; port?: number; install?: boolean; example?: boolean }
) {
let projectName: string;
// Default install and example to true if not specified
const shouldInstall = options?.install !== false;
Expand All @@ -14,18 +17,18 @@ export async function createProject(name?: string, options?: { http?: boolean, c
if (!name) {
const response = await prompts([
{
type: "text",
name: "projectName",
message: "What is the name of your MCP server project?",
type: 'text',
name: 'projectName',
message: 'What is the name of your MCP server project?',
validate: (value: string) =>
/^[a-z0-9-]+$/.test(value)
? true
: "Project name can only contain lowercase letters, numbers, and hyphens",
: 'Project name can only contain lowercase letters, numbers, and hyphens',
},
]);

if (!response.projectName) {
console.log("Project creation cancelled");
console.log('Project creation cancelled');
process.exit(1);
}

Expand All @@ -35,81 +38,88 @@ export async function createProject(name?: string, options?: { http?: boolean, c
}

if (!projectName) {
throw new Error("Project name is required");
throw new Error('Project name is required');
}

const projectDir = join(process.cwd(), projectName);
const srcDir = join(projectDir, "src");
const toolsDir = join(srcDir, "tools");
const srcDir = join(projectDir, 'src');
const toolsDir = join(srcDir, 'tools');

try {
console.log("Creating project structure...");
console.log('Creating project structure...');
await mkdir(projectDir);
await mkdir(srcDir);
await mkdir(toolsDir);

const packageJson = {
name: projectName,
version: "0.0.1",
version: '0.0.1',
description: `${projectName} MCP server`,
type: "module",
type: 'module',
bin: {
[projectName]: "./dist/index.js",
[projectName]: './dist/index.js',
},
files: ["dist"],
files: ['dist'],
scripts: {
build: "tsc && mcp-build",
watch: "tsc --watch",
start: "node dist/index.js"
build: 'tsc && mcp-build',
watch: 'tsc --watch',
start: 'node dist/index.js',
},
dependencies: {
"mcp-framework": "^0.2.2"
'mcp-framework': '^0.2.2',
},
devDependencies: {
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
'@types/node': '^20.11.24',
typescript: '^5.3.3',
},
engines: {
"node": ">=18.19.0"
}
node: '>=18.19.0',
},
};

const tsconfig = {
compilerOptions: {
target: "ESNext",
module: "ESNext",
moduleResolution: "node",
outDir: "./dist",
rootDir: "./src",
target: 'ESNext',
module: 'ESNext',
moduleResolution: 'node',
outDir: './dist',
rootDir: './src',
strict: true,
esModuleInterop: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
},
include: ["src/**/*"],
exclude: ["node_modules"],
include: ['src/**/*'],
exclude: ['node_modules'],
};

let indexTs = "";

const gitignore = `node_modules
dist
.env
.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}});
Expand Down Expand Up @@ -148,66 +158,65 @@ class ExampleTool extends MCPTool<ExampleInput> {

export default ExampleTool;`;

// Prepare the files to write
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, '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),
];

// Conditionally add the example tool
if (shouldCreateExample) {
filesToWrite.push(writeFile(join(toolsDir, "ExampleTool.ts"), exampleToolTs));
filesToWrite.push(writeFile(join(toolsDir, 'ExampleTool.ts'), exampleToolTs));
}

console.log("Creating project files...");
console.log('Creating project files...');
await Promise.all(filesToWrite);

process.chdir(projectDir);

console.log("Initializing git repository...");
const gitInit = spawnSync("git", ["init"], {
stdio: "inherit",
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");
throw new Error('Failed to initialize git repository');
}

if (shouldInstall) {
console.log("Installing dependencies...");
const npmInstall = spawnSync("npm", ["install"], {
stdio: "inherit",
shell: true
console.log('Installing dependencies...');
const npmInstall = spawnSync('npm', ['install'], {
stdio: 'inherit',
shell: true,
});

if (npmInstall.status !== 0) {
throw new Error("Failed to install dependencies");
throw new Error('Failed to install dependencies');
}

console.log("Building project...");
console.log('Building project...');
const tscBuild = await execa('npx', ['tsc'], {
cwd: projectDir,
stdio: "inherit",
stdio: 'inherit',
});

if (tscBuild.exitCode !== 0) {
throw new Error("Failed to build TypeScript");
throw new Error('Failed to build TypeScript');
}

const mcpBuild = await execa('npx', ['mcp-build'], {
cwd: projectDir,
stdio: "inherit",
stdio: 'inherit',
env: {
...process.env,
MCP_SKIP_VALIDATION: "true"
}
MCP_SKIP_VALIDATION: 'true',
},
});

if (mcpBuild.exitCode !== 0) {
throw new Error("Failed to run mcp-build");
throw new Error('Failed to run mcp-build');
}

console.log(`
Expand All @@ -231,7 +240,7 @@ You can now:
`);
}
} catch (error) {
console.error("Error creating project:", error);
console.error('Error creating project:', error);
process.exit(1);
}
}