create-mcp-use-app 0.3.4 → 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/dist/index.js CHANGED
@@ -1,15 +1,42 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { execSync } from "child_process";
4
+ import chalk from "chalk";
5
+ import { Command } from "commander";
6
+ import inquirer from "inquirer";
7
+ import { spawn } from "child_process";
5
8
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
6
9
  import { dirname, join, resolve } from "path";
7
10
  import { fileURLToPath } from "url";
8
- import { createInterface } from "readline";
9
- import { Command } from "commander";
11
+ import ora from "ora";
10
12
  var __filename = fileURLToPath(import.meta.url);
11
13
  var __dirname = dirname(__filename);
14
+ function runPackageManager(packageManager, args, cwd) {
15
+ return new Promise((resolve2, reject) => {
16
+ const child = spawn(packageManager, args, {
17
+ cwd,
18
+ stdio: "inherit",
19
+ shell: false
20
+ // Disable shell to prevent command injection
21
+ });
22
+ child.on("close", (code) => {
23
+ if (code === 0) {
24
+ resolve2();
25
+ } else {
26
+ reject(new Error(`Process exited with code ${code}`));
27
+ }
28
+ });
29
+ child.on("error", (err) => {
30
+ reject(err);
31
+ });
32
+ });
33
+ }
12
34
  var program = new Command();
35
+ function renderLogo() {
36
+ console.log(chalk.cyan("\u259B\u259B\u258C\u259B\u2598\u259B\u258C\u2584\u2596\u258C\u258C\u259B\u2598\u2588\u258C"));
37
+ console.log(chalk.cyan("\u258C\u258C\u258C\u2599\u2596\u2599\u258C \u2599\u258C\u2584\u258C\u2599\u2596"));
38
+ console.log(chalk.cyan(" \u258C "));
39
+ }
13
40
  var packageJson = JSON.parse(
14
41
  readFileSync(join(__dirname, "../package.json"), "utf-8")
15
42
  );
@@ -49,8 +76,13 @@ function getCurrentPackageVersions() {
49
76
  );
50
77
  versions["@mcp-use/inspector"] = inspectorPackage.version;
51
78
  } catch (error) {
52
- console.warn("\u26A0\uFE0F Could not read workspace package versions, using defaults");
53
- console.warn(` Error: ${error}`);
79
+ if (process.env.NODE_ENV === "development") {
80
+ console.warn("\u26A0\uFE0F Could not read workspace package versions, using defaults");
81
+ console.warn(` Error: ${error instanceof Error ? error.message : String(error)}`);
82
+ if (error instanceof Error && error.stack) {
83
+ console.warn(` Stack: ${error.stack}`);
84
+ }
85
+ }
54
86
  }
55
87
  return versions;
56
88
  }
@@ -73,88 +105,122 @@ function processTemplateFile(filePath, versions, isDevelopment = false) {
73
105
  }
74
106
  return processedContent;
75
107
  }
76
- program.name("create-mcp-use-app").description("Create a new MCP server project").version(packageJson.version).argument("[project-name]", "Name of the MCP server project").option("-t, --template <template>", "Template to use", "ui").option("--no-install", "Skip installing dependencies").option("--dev", "Use workspace dependencies for development").action(async (projectName, options) => {
108
+ program.name("create-mcp-use-app").description("Create a new MCP server project").version(packageJson.version).argument("[project-name]", "Name of the MCP server project").option("-t, --template <template>", "Template to use", "simple").option("--no-install", "Skip installing dependencies").option("--dev", "Use workspace dependencies for development").action(async (projectName, options) => {
77
109
  try {
110
+ let selectedTemplate = options.template;
78
111
  if (!projectName) {
79
- console.log("\u{1F3AF} Welcome to create-mcp-use-app!");
80
112
  console.log("");
81
- const promptedName = await promptForProjectName();
82
- if (!promptedName) {
83
- console.log("\u274C Project creation cancelled.");
84
- process.exit(0);
85
- }
86
- projectName = promptedName;
113
+ renderLogo();
114
+ console.log("");
115
+ console.log(chalk.bold("Welcome to create-mcp-use-app!"));
116
+ console.log("");
117
+ projectName = await promptForProjectName();
118
+ console.log("");
119
+ selectedTemplate = await promptForTemplate();
120
+ }
121
+ const sanitizedProjectName = projectName.trim();
122
+ if (!sanitizedProjectName) {
123
+ console.error(chalk.red("\u274C Project name cannot be empty"));
124
+ process.exit(1);
87
125
  }
88
- console.log(`\u{1F680} Creating MCP server "${projectName}"...`);
89
- const projectPath = resolve(process.cwd(), projectName);
126
+ if (sanitizedProjectName.includes("..") || sanitizedProjectName.includes("/") || sanitizedProjectName.includes("\\")) {
127
+ console.error(chalk.red('\u274C Project name cannot contain path separators or ".."'));
128
+ console.error(chalk.yellow(' Use simple names like "my-mcp-server"'));
129
+ process.exit(1);
130
+ }
131
+ const protectedNames = ["node_modules", ".git", ".env", "package.json", "src", "dist"];
132
+ if (protectedNames.includes(sanitizedProjectName.toLowerCase())) {
133
+ console.error(chalk.red(`\u274C Cannot use protected name "${sanitizedProjectName}"`));
134
+ console.error(chalk.yellow(" Please choose a different project name"));
135
+ process.exit(1);
136
+ }
137
+ console.log(chalk.cyan(`\u{1F680} Creating MCP server "${sanitizedProjectName}"...`));
138
+ const projectPath = resolve(process.cwd(), sanitizedProjectName);
90
139
  if (existsSync(projectPath)) {
91
- console.error(`\u274C Directory "${projectName}" already exists!`);
140
+ console.error(chalk.red(`\u274C Directory "${sanitizedProjectName}" already exists!`));
141
+ console.error(chalk.yellow(" Please choose a different name or remove the existing directory"));
92
142
  process.exit(1);
93
143
  }
94
144
  mkdirSync(projectPath, { recursive: true });
145
+ const validatedTemplate = validateTemplateName(selectedTemplate);
95
146
  const versions = getCurrentPackageVersions();
96
- await copyTemplate(projectPath, options.template, versions, options.dev);
97
- updatePackageJson(projectPath, projectName);
147
+ await copyTemplate(projectPath, validatedTemplate, versions, options.dev);
148
+ updatePackageJson(projectPath, sanitizedProjectName);
98
149
  if (options.install) {
99
- console.log("\u{1F4E6} Installing dependencies...");
150
+ const spinner = ora("Installing packages...").start();
100
151
  try {
101
- execSync("pnpm install", { cwd: projectPath, stdio: "inherit" });
152
+ await runPackageManager("pnpm", ["install"], projectPath);
153
+ spinner.succeed("Packages installed successfully");
102
154
  } catch {
103
- console.log("\u26A0\uFE0F pnpm not found, trying npm...");
155
+ spinner.text = "pnpm not found, trying npm...";
104
156
  try {
105
- execSync("npm install", { cwd: projectPath, stdio: "inherit" });
106
- } catch {
107
- console.log('\u26A0\uFE0F npm install failed, please run "npm install" manually');
157
+ await runPackageManager("npm", ["install"], projectPath);
158
+ spinner.succeed("Packages installed successfully");
159
+ } catch (error) {
160
+ spinner.fail("Package installation failed");
161
+ console.log('\u26A0\uFE0F Please run "npm install" or "pnpm install" manually');
108
162
  }
109
163
  }
110
164
  }
111
- console.log("\u2705 MCP server created successfully!");
165
+ console.log("");
166
+ console.log(chalk.green("\u2705 MCP server created successfully!"));
112
167
  if (options.dev) {
113
- console.log("\u{1F527} Development mode: Using workspace dependencies");
168
+ console.log(chalk.yellow("\u{1F527} Development mode: Using workspace dependencies"));
114
169
  }
115
170
  console.log("");
116
- console.log("\u{1F4C1} Project structure:");
117
- console.log(` ${projectName}/`);
118
- if (options.template === "ui") {
119
- console.log(" \u251C\u2500\u2500 src/");
120
- console.log(" \u2502 \u2514\u2500\u2500 server.ts");
171
+ console.log(chalk.bold("\u{1F4C1} Project structure:"));
172
+ console.log(` ${sanitizedProjectName}/`);
173
+ console.log(" \u251C\u2500\u2500 src/");
174
+ console.log(" \u2502 \u2514\u2500\u2500 server.ts");
175
+ if (validatedTemplate === "ui") {
121
176
  console.log(" \u251C\u2500\u2500 resources/");
122
177
  console.log(" \u2502 \u251C\u2500\u2500 data-visualization.tsx");
123
178
  console.log(" \u2502 \u251C\u2500\u2500 kanban-board.tsx");
124
179
  console.log(" \u2502 \u2514\u2500\u2500 todo-list.tsx");
125
- console.log(" \u251C\u2500\u2500 index.ts");
126
- console.log(" \u251C\u2500\u2500 package.json");
127
- console.log(" \u251C\u2500\u2500 tsconfig.json");
128
- console.log(" \u2514\u2500\u2500 README.md");
129
- } else {
130
- console.log(" \u251C\u2500\u2500 src/");
131
- console.log(" \u2502 \u2514\u2500\u2500 server.ts");
132
- console.log(" \u251C\u2500\u2500 package.json");
133
- console.log(" \u251C\u2500\u2500 tsconfig.json");
134
- console.log(" \u2514\u2500\u2500 README.md");
135
180
  }
181
+ console.log(" \u251C\u2500\u2500 index.ts");
182
+ console.log(" \u251C\u2500\u2500 package.json");
183
+ console.log(" \u251C\u2500\u2500 tsconfig.json");
184
+ console.log(" \u2514\u2500\u2500 README.md");
136
185
  console.log("");
137
- console.log("\u{1F680} To get started:");
138
- console.log(` cd ${projectName}`);
186
+ console.log(chalk.bold("\u{1F680} To get started:"));
187
+ console.log(chalk.cyan(` cd ${sanitizedProjectName}`));
139
188
  if (!options.install) {
140
- console.log(" npm install");
189
+ console.log(chalk.cyan(" npm install"));
141
190
  }
142
- console.log(" npm run dev");
191
+ console.log(chalk.cyan(" npm run dev"));
143
192
  console.log("");
144
193
  if (options.dev) {
145
- console.log("\u{1F4A1} Development mode: Your project uses workspace dependencies");
146
- console.log(" Make sure you're in the mcp-use workspace root for development");
194
+ console.log(chalk.yellow("\u{1F4A1} Development mode: Your project uses workspace dependencies"));
195
+ console.log(chalk.yellow(" Make sure you're in the mcp-use workspace root for development"));
196
+ console.log("");
147
197
  }
148
- console.log("\u{1F4DA} Learn more: https://docs.mcp-use.io");
198
+ console.log(chalk.blue("\u{1F4DA} Learn more: https://docs.mcp-use.com"));
199
+ console.log(chalk.gray("\u{1F4AC} For feedback and bug reporting visit:"));
200
+ console.log(chalk.gray(" https://github.com/mcp-use/mcp-use or https://mcp-use.com"));
149
201
  } catch (error) {
150
202
  console.error("\u274C Error creating MCP server:", error);
151
203
  process.exit(1);
152
204
  }
153
205
  });
206
+ function validateTemplateName(template) {
207
+ const sanitized = template.trim();
208
+ if (sanitized.includes("..") || sanitized.includes("/") || sanitized.includes("\\")) {
209
+ console.error(chalk.red("\u274C Invalid template name"));
210
+ console.error(chalk.yellow(" Template name cannot contain path separators"));
211
+ process.exit(1);
212
+ }
213
+ if (!/^[a-zA-Z0-9_-]+$/.test(sanitized)) {
214
+ console.error(chalk.red("\u274C Invalid template name"));
215
+ console.error(chalk.yellow(" Template name can only contain letters, numbers, hyphens, and underscores"));
216
+ process.exit(1);
217
+ }
218
+ return sanitized;
219
+ }
154
220
  async function copyTemplate(projectPath, template, versions, isDevelopment = false) {
155
221
  const templatePath = join(__dirname, "templates", template);
156
222
  if (!existsSync(templatePath)) {
157
- console.error(`\u274C Template "${template}" not found!`);
223
+ console.error(chalk.red(`\u274C Template "${template}" not found!`));
158
224
  const templatesDir = join(__dirname, "templates");
159
225
  if (existsSync(templatesDir)) {
160
226
  const availableTemplates = readdirSync(templatesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).sort();
@@ -193,35 +259,49 @@ function updatePackageJson(projectPath, projectName) {
193
259
  packageJsonContent.description = `MCP server: ${projectName}`;
194
260
  writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent, null, 2));
195
261
  }
196
- function promptForProjectName() {
197
- return new Promise((resolvePromise) => {
198
- const rl = createInterface({
199
- input: process.stdin,
200
- output: process.stdout
201
- });
202
- const askForName = () => {
203
- rl.question("What is your project name? ", (answer) => {
204
- const trimmed = answer.trim();
262
+ async function promptForProjectName() {
263
+ const { projectName } = await inquirer.prompt([
264
+ {
265
+ type: "input",
266
+ name: "projectName",
267
+ message: "What is your project name?",
268
+ validate: (input) => {
269
+ const trimmed = input.trim();
205
270
  if (!trimmed) {
206
- console.log("\u274C Project name is required");
207
- askForName();
208
- return;
271
+ return "Project name is required";
209
272
  }
210
273
  if (!/^[a-zA-Z0-9-_]+$/.test(trimmed)) {
211
- console.log("\u274C Project name can only contain letters, numbers, hyphens, and underscores");
212
- askForName();
213
- return;
274
+ return "Project name can only contain letters, numbers, hyphens, and underscores";
214
275
  }
215
276
  if (existsSync(join(process.cwd(), trimmed))) {
216
- console.log(`\u274C Directory "${trimmed}" already exists! Please choose a different name.`);
217
- askForName();
218
- return;
277
+ return `Directory "${trimmed}" already exists! Please choose a different name.`;
219
278
  }
220
- rl.close();
221
- resolvePromise(trimmed);
222
- });
223
- };
224
- askForName();
225
- });
279
+ return true;
280
+ }
281
+ }
282
+ ]);
283
+ return projectName;
284
+ }
285
+ async function promptForTemplate() {
286
+ const templatesDir = join(__dirname, "templates");
287
+ const availableTemplates = existsSync(templatesDir) ? readdirSync(templatesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).sort() : ["simple", "ui", "uiresource"];
288
+ const templateDescriptions = {
289
+ "simple": "Simple MCP server with a basic calculator tool (add numbers)",
290
+ "ui": "MCP Server with mcp-ui resources returned from tools",
291
+ "uiresource": "MCP Server with mcp-ui resources"
292
+ };
293
+ const { template } = await inquirer.prompt([
294
+ {
295
+ type: "list",
296
+ name: "template",
297
+ message: "Select a template:",
298
+ default: "simple",
299
+ choices: availableTemplates.map((template2) => ({
300
+ name: `${template2} - ${templateDescriptions[template2] || "MCP server template"}`,
301
+ value: template2
302
+ }))
303
+ }
304
+ ]);
305
+ return template;
226
306
  }
227
307
  program.parse();