create-mcp-use-app 0.9.4 → 0.10.0-canary.1
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 +28 -0
- package/dist/index.js +162 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,11 @@ npx create-mcp-use-app my-project
|
|
|
112
112
|
npx create-mcp-use-app my-project --template apps-sdk
|
|
113
113
|
npx create-mcp-use-app my-project --template mcp-ui
|
|
114
114
|
|
|
115
|
+
# Use a GitHub repository as a template
|
|
116
|
+
npx create-mcp-use-app my-project --template owner/repo
|
|
117
|
+
npx create-mcp-use-app my-project --template https://github.com/owner/repo
|
|
118
|
+
npx create-mcp-use-app my-project --template owner/repo#branch-name
|
|
119
|
+
|
|
115
120
|
# Use a specific package manager
|
|
116
121
|
npx create-mcp-use-app my-project --npm
|
|
117
122
|
npx create-mcp-use-app my-project --yarn
|
|
@@ -159,6 +164,29 @@ The mcp-ui template includes:
|
|
|
159
164
|
|
|
160
165
|
Best for building MCP servers with rich interactive UI components.
|
|
161
166
|
|
|
167
|
+
### GitHub Repository Templates
|
|
168
|
+
|
|
169
|
+
You can use any GitHub repository as a template by providing the repository URL:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Short format (owner/repo)
|
|
173
|
+
npx create-mcp-use-app my-project --template owner/repo
|
|
174
|
+
|
|
175
|
+
# Full URL format
|
|
176
|
+
npx create-mcp-use-app my-project --template https://github.com/owner/repo
|
|
177
|
+
|
|
178
|
+
# With specific branch
|
|
179
|
+
npx create-mcp-use-app my-project --template owner/repo#branch-name
|
|
180
|
+
npx create-mcp-use-app my-project --template https://github.com/owner/repo#branch-name
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The repository will be cloned and its contents will be used to initialize your project. This is useful for:
|
|
184
|
+
- Using community templates
|
|
185
|
+
- Sharing custom templates within your organization
|
|
186
|
+
- Creating projects from existing repositories
|
|
187
|
+
|
|
188
|
+
**Note:** Git must be installed and available in your PATH to use GitHub repository templates.
|
|
189
|
+
|
|
162
190
|
---
|
|
163
191
|
|
|
164
192
|
## 🏗️ What Gets Installed
|
package/dist/index.js
CHANGED
|
@@ -3,24 +3,25 @@
|
|
|
3
3
|
// src/index.tsx
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import { render } from "ink";
|
|
6
|
+
import { Box, render, Text } from "ink";
|
|
7
7
|
import SelectInput from "ink-select-input";
|
|
8
8
|
import TextInput from "ink-text-input";
|
|
9
|
-
import { execSync, spawn } from "child_process";
|
|
9
|
+
import { execSync, spawn, spawnSync } from "child_process";
|
|
10
10
|
import {
|
|
11
11
|
copyFileSync,
|
|
12
12
|
existsSync,
|
|
13
13
|
mkdirSync,
|
|
14
|
+
mkdtempSync,
|
|
14
15
|
readdirSync,
|
|
15
16
|
readFileSync,
|
|
16
17
|
rmSync,
|
|
17
18
|
writeFileSync
|
|
18
19
|
} from "fs";
|
|
20
|
+
import { tmpdir } from "os";
|
|
19
21
|
import { dirname, join, resolve } from "path";
|
|
20
22
|
import { fileURLToPath } from "url";
|
|
21
23
|
import ora from "ora";
|
|
22
24
|
import React, { useState } from "react";
|
|
23
|
-
import { Box, Text } from "ink";
|
|
24
25
|
var __filename = fileURLToPath(import.meta.url);
|
|
25
26
|
var __dirname = dirname(__filename);
|
|
26
27
|
function runPackageManager(packageManager, args, cwd) {
|
|
@@ -279,8 +280,7 @@ function listTemplates() {
|
|
|
279
280
|
}
|
|
280
281
|
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(
|
|
281
282
|
"-t, --template <template>",
|
|
282
|
-
"Template to use (starter, mcp-ui, apps-sdk)"
|
|
283
|
-
"starter"
|
|
283
|
+
"Template to use (starter, mcp-ui, apps-sdk) or GitHub repo URL (owner/repo or https://github.com/owner/repo)"
|
|
284
284
|
).option("--list-templates", "List all available templates").option("--install", "Install dependencies after creating project").option("--no-git", "Skip initializing a git repository").option("--dev", "Use workspace dependencies for development").option("--canary", "Use canary versions of packages").option("--yarn", "Use yarn as package manager").option("--npm", "Use npm as package manager").option("--pnpm", "Use pnpm as package manager").action(
|
|
285
285
|
async (projectName, options) => {
|
|
286
286
|
try {
|
|
@@ -304,7 +304,12 @@ program.name("create-mcp-use-app").description("Create a new MCP server project"
|
|
|
304
304
|
console.log("");
|
|
305
305
|
projectName = await promptForProjectName();
|
|
306
306
|
console.log("");
|
|
307
|
-
|
|
307
|
+
if (!options.template) {
|
|
308
|
+
selectedTemplate = await promptForTemplate();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!selectedTemplate) {
|
|
312
|
+
selectedTemplate = "starter";
|
|
308
313
|
}
|
|
309
314
|
const sanitizedProjectName = projectName.trim();
|
|
310
315
|
if (!sanitizedProjectName) {
|
|
@@ -479,7 +484,7 @@ program.name("create-mcp-use-app").description("Create a new MCP server project"
|
|
|
479
484
|
);
|
|
480
485
|
}
|
|
481
486
|
}
|
|
482
|
-
if (options.git) {
|
|
487
|
+
if (options.git && !isGitHubRepoUrl(validatedTemplate)) {
|
|
483
488
|
tryGitInit(projectPath);
|
|
484
489
|
}
|
|
485
490
|
console.log("");
|
|
@@ -554,8 +559,127 @@ program.name("create-mcp-use-app").description("Create a new MCP server project"
|
|
|
554
559
|
}
|
|
555
560
|
}
|
|
556
561
|
);
|
|
562
|
+
function parseGitHubRepoUrl(url) {
|
|
563
|
+
const trimmed = url.trim();
|
|
564
|
+
let match = trimmed.match(
|
|
565
|
+
/^https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/#]+?)(?:\.git)?(?:\/tree\/([^/]+))?(?:#(.+))?$/
|
|
566
|
+
);
|
|
567
|
+
if (match) {
|
|
568
|
+
const result = {
|
|
569
|
+
owner: match[1],
|
|
570
|
+
repo: match[2],
|
|
571
|
+
branch: match[3] || match[4] || void 0
|
|
572
|
+
};
|
|
573
|
+
return validateGitHubRepoInfo(result) ? result : null;
|
|
574
|
+
}
|
|
575
|
+
match = trimmed.match(
|
|
576
|
+
/^(?:www\.)?github\.com\/([^/]+)\/([^/#]+?)(?:\.git)?(?:#(.+))?$/
|
|
577
|
+
);
|
|
578
|
+
if (match) {
|
|
579
|
+
const result = {
|
|
580
|
+
owner: match[1],
|
|
581
|
+
repo: match[2],
|
|
582
|
+
branch: match[3] || void 0
|
|
583
|
+
};
|
|
584
|
+
return validateGitHubRepoInfo(result) ? result : null;
|
|
585
|
+
}
|
|
586
|
+
match = trimmed.match(/^([^/#]+)\/([^/#]+?)(?:#(.+))?$/);
|
|
587
|
+
if (match) {
|
|
588
|
+
const result = {
|
|
589
|
+
owner: match[1],
|
|
590
|
+
repo: match[2],
|
|
591
|
+
branch: match[3] || void 0
|
|
592
|
+
};
|
|
593
|
+
return validateGitHubRepoInfo(result) ? result : null;
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
function validateGitHubRepoInfo(info) {
|
|
598
|
+
const validIdentifier = /^[a-zA-Z0-9_-]+$/;
|
|
599
|
+
const validBranch = /^[a-zA-Z0-9_./-]+$/;
|
|
600
|
+
if (!validIdentifier.test(info.owner) || !validIdentifier.test(info.repo)) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
if (info.branch && !validBranch.test(info.branch)) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
function isGitHubRepoUrl(template) {
|
|
609
|
+
return parseGitHubRepoUrl(template) !== null;
|
|
610
|
+
}
|
|
611
|
+
async function cloneGitHubRepo(repoInfo, tempDir) {
|
|
612
|
+
const versionCheck = spawnSync("git", ["--version"], {
|
|
613
|
+
stdio: "ignore",
|
|
614
|
+
shell: false
|
|
615
|
+
});
|
|
616
|
+
if (versionCheck.status !== 0 || versionCheck.error) {
|
|
617
|
+
console.error(
|
|
618
|
+
chalk.red("\u274C Git is not installed or not available in PATH")
|
|
619
|
+
);
|
|
620
|
+
console.error(
|
|
621
|
+
chalk.yellow(" Please install Git to use GitHub repository templates")
|
|
622
|
+
);
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
const repoUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}.git`;
|
|
626
|
+
const branch = repoInfo.branch || "main";
|
|
627
|
+
const spinner = ora(`Cloning repository from GitHub...`).start();
|
|
628
|
+
const cloneWithBranch = (branchName) => {
|
|
629
|
+
const result = spawnSync(
|
|
630
|
+
"git",
|
|
631
|
+
["clone", "--depth", "1", "--branch", branchName, repoUrl, tempDir],
|
|
632
|
+
{
|
|
633
|
+
stdio: "pipe",
|
|
634
|
+
shell: false
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
if (result.status !== 0) {
|
|
638
|
+
const errorMessage2 = result.stderr?.toString() || result.stdout?.toString() || "Unknown error";
|
|
639
|
+
return {
|
|
640
|
+
success: false,
|
|
641
|
+
error: new Error(errorMessage2)
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
return { success: true };
|
|
645
|
+
};
|
|
646
|
+
const cloneResult = cloneWithBranch(branch);
|
|
647
|
+
if (cloneResult.success) {
|
|
648
|
+
spinner.succeed("Repository cloned successfully");
|
|
649
|
+
return tempDir;
|
|
650
|
+
}
|
|
651
|
+
if (!repoInfo.branch) {
|
|
652
|
+
spinner.text = `Branch "${branch}" not found, trying "main"...`;
|
|
653
|
+
const mainResult = cloneWithBranch("main");
|
|
654
|
+
if (mainResult.success) {
|
|
655
|
+
spinner.succeed("Repository cloned successfully (using main branch)");
|
|
656
|
+
return tempDir;
|
|
657
|
+
}
|
|
658
|
+
spinner.text = `Branch "main" not found, trying "master"...`;
|
|
659
|
+
const masterResult = cloneWithBranch("master");
|
|
660
|
+
if (masterResult.success) {
|
|
661
|
+
spinner.succeed("Repository cloned successfully (using master branch)");
|
|
662
|
+
return tempDir;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
spinner.fail("Failed to clone repository");
|
|
666
|
+
console.error(chalk.red(`\u274C Error cloning repository: ${repoUrl}`));
|
|
667
|
+
if (repoInfo.branch) {
|
|
668
|
+
console.error(chalk.yellow(` Branch "${repoInfo.branch}" may not exist`));
|
|
669
|
+
}
|
|
670
|
+
const errorMessage = cloneResult.error?.message || "Unknown error";
|
|
671
|
+
if (errorMessage.includes("not found")) {
|
|
672
|
+
console.error(chalk.yellow(` Repository may not exist or is private`));
|
|
673
|
+
} else {
|
|
674
|
+
console.error(chalk.yellow(` ${errorMessage}`));
|
|
675
|
+
}
|
|
676
|
+
throw cloneResult.error || new Error("Failed to clone repository");
|
|
677
|
+
}
|
|
557
678
|
function validateTemplateName(template) {
|
|
558
679
|
const sanitized = template.trim();
|
|
680
|
+
if (isGitHubRepoUrl(sanitized)) {
|
|
681
|
+
return sanitized;
|
|
682
|
+
}
|
|
559
683
|
if (sanitized.includes("..") || sanitized.includes("/") || sanitized.includes("\\")) {
|
|
560
684
|
console.error(chalk.red("\u274C Invalid template name"));
|
|
561
685
|
console.error(
|
|
@@ -575,6 +699,31 @@ function validateTemplateName(template) {
|
|
|
575
699
|
return sanitized;
|
|
576
700
|
}
|
|
577
701
|
async function copyTemplate(projectPath, template, versions, isDevelopment = false, useCanary = false) {
|
|
702
|
+
const repoInfo = parseGitHubRepoUrl(template);
|
|
703
|
+
if (repoInfo) {
|
|
704
|
+
const tempDir = mkdtempSync(join(tmpdir(), "create-mcp-use-app-"));
|
|
705
|
+
try {
|
|
706
|
+
await cloneGitHubRepo(repoInfo, tempDir);
|
|
707
|
+
copyDirectoryWithProcessing(
|
|
708
|
+
tempDir,
|
|
709
|
+
projectPath,
|
|
710
|
+
versions,
|
|
711
|
+
isDevelopment,
|
|
712
|
+
useCanary
|
|
713
|
+
);
|
|
714
|
+
} finally {
|
|
715
|
+
try {
|
|
716
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
717
|
+
} catch (error) {
|
|
718
|
+
if (process.env.NODE_ENV === "development") {
|
|
719
|
+
console.warn(
|
|
720
|
+
`Warning: Failed to clean up temp directory: ${tempDir}`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
578
727
|
const templatePath = join(__dirname, "templates", template);
|
|
579
728
|
if (!existsSync(templatePath)) {
|
|
580
729
|
console.error(chalk.red(`\u274C Template "${template}" not found!`));
|
|
@@ -596,6 +745,9 @@ async function copyTemplate(projectPath, template, versions, isDevelopment = fal
|
|
|
596
745
|
console.log(
|
|
597
746
|
'\u{1F4A1} Tip: Use "apps-sdk" template for a MCP server with OpenAI Apps SDK integration'
|
|
598
747
|
);
|
|
748
|
+
console.log(
|
|
749
|
+
'\u{1F4A1} Tip: Use a GitHub repo URL like "owner/repo" or "https://github.com/owner/repo" to use a custom template'
|
|
750
|
+
);
|
|
599
751
|
process.exit(1);
|
|
600
752
|
}
|
|
601
753
|
copyDirectoryWithProcessing(
|
|
@@ -609,6 +761,9 @@ async function copyTemplate(projectPath, template, versions, isDevelopment = fal
|
|
|
609
761
|
function copyDirectoryWithProcessing(src, dest, versions, isDevelopment, useCanary = false) {
|
|
610
762
|
const entries = readdirSync(src, { withFileTypes: true });
|
|
611
763
|
for (const entry of entries) {
|
|
764
|
+
if (entry.name === ".git") {
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
612
767
|
const srcPath = join(src, entry.name);
|
|
613
768
|
const destPath = join(dest, entry.name);
|
|
614
769
|
if (entry.isDirectory()) {
|