mcp-new 1.2.2 → 1.6.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 +33 -1
- package/dist/{chunk-3JG4FVS2.js → chunk-LJNMSDBU.js} +1157 -212
- package/dist/cli.js +1287 -18
- package/dist/index.d.ts +43 -10
- package/dist/index.js +22 -35
- package/package.json +4 -2
- package/templates/ci/circleci/config.yml.ejs +219 -0
- package/templates/ci/github/ci.yml.ejs +184 -0
- package/templates/ci/gitlab/.gitlab-ci.yml.ejs +233 -0
- package/templates/csharp/.env.example +6 -0
- package/templates/csharp/.gitignore.ejs +53 -0
- package/templates/csharp/McpServer.csproj.ejs +19 -0
- package/templates/csharp/README.md.ejs +136 -0
- package/templates/csharp/src/Program.cs.ejs +117 -0
- package/templates/elixir/.env.example +6 -0
- package/templates/elixir/.gitignore.ejs +33 -0
- package/templates/elixir/README.md.ejs +154 -0
- package/templates/elixir/config/config.exs.ejs +9 -0
- package/templates/elixir/config/dev.exs.ejs +3 -0
- package/templates/elixir/config/prod.exs.ejs +3 -0
- package/templates/elixir/lib/application.ex.ejs +19 -0
- package/templates/elixir/lib/cli.ex.ejs +17 -0
- package/templates/elixir/lib/server.ex.ejs +112 -0
- package/templates/elixir/mix.exs.ejs +32 -0
- package/templates/java/gradle/.env.example +6 -0
- package/templates/java/gradle/.gitignore.ejs +48 -0
- package/templates/java/gradle/README.md.ejs +132 -0
- package/templates/java/gradle/build.gradle.ejs +46 -0
- package/templates/java/gradle/settings.gradle.ejs +1 -0
- package/templates/java/gradle/src/main/java/com/example/mcp/McpServer.java.ejs +149 -0
- package/templates/java/gradle/src/main/resources/logback.xml +13 -0
- package/templates/java/maven/.env.example +6 -0
- package/templates/java/maven/.gitignore.ejs +53 -0
- package/templates/java/maven/README.md.ejs +131 -0
- package/templates/java/maven/pom.xml.ejs +86 -0
- package/templates/java/maven/src/main/java/com/example/mcp/McpServer.java.ejs +149 -0
- package/templates/java/maven/src/main/resources/logback.xml +13 -0
- package/templates/kotlin/gradle/.env.example +6 -0
- package/templates/kotlin/gradle/.gitignore.ejs +45 -0
- package/templates/kotlin/gradle/README.md.ejs +138 -0
- package/templates/kotlin/gradle/build.gradle.kts.ejs +48 -0
- package/templates/kotlin/gradle/settings.gradle.kts.ejs +1 -0
- package/templates/kotlin/gradle/src/main/kotlin/com/example/mcp/McpServer.kt.ejs +141 -0
- package/templates/kotlin/gradle/src/main/resources/logback.xml +13 -0
- package/templates/kotlin/maven/.env.example +6 -0
- package/templates/kotlin/maven/.gitignore.ejs +50 -0
- package/templates/kotlin/maven/README.md.ejs +96 -0
- package/templates/kotlin/maven/pom.xml.ejs +105 -0
- package/templates/kotlin/maven/src/main/kotlin/com/example/mcp/McpServer.kt.ejs +141 -0
- package/templates/kotlin/maven/src/main/resources/logback.xml +13 -0
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,32 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PRESETS,
|
|
4
4
|
addToolCommand,
|
|
5
|
+
clearPresetCache,
|
|
5
6
|
createCommand,
|
|
7
|
+
createInitialCommit,
|
|
6
8
|
createSpinner,
|
|
9
|
+
detectJavaBuildTool,
|
|
10
|
+
detectLanguageFromProject,
|
|
11
|
+
ensureDir,
|
|
12
|
+
exists,
|
|
13
|
+
generateCI,
|
|
14
|
+
generateFromWizard,
|
|
15
|
+
getCacheDir,
|
|
7
16
|
initCommand,
|
|
8
|
-
|
|
9
|
-
|
|
17
|
+
initGitRepository,
|
|
18
|
+
isGitInstalled,
|
|
19
|
+
isValidCIProvider,
|
|
20
|
+
listCachedPresets,
|
|
21
|
+
logger,
|
|
22
|
+
pluginRegistry,
|
|
23
|
+
promptCIProvider,
|
|
24
|
+
promptJavaBuildTool,
|
|
25
|
+
promptLanguage,
|
|
26
|
+
readDir,
|
|
27
|
+
readFile,
|
|
28
|
+
withSpinner,
|
|
29
|
+
writeFile
|
|
30
|
+
} from "./chunk-LJNMSDBU.js";
|
|
10
31
|
|
|
11
32
|
// src/cli.ts
|
|
12
33
|
import { Command } from "commander";
|
|
@@ -347,7 +368,7 @@ async function upgradeNodePackage(projectDir, info) {
|
|
|
347
368
|
await fs2.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
348
369
|
await execa("npm", ["install"], { cwd: projectDir });
|
|
349
370
|
}
|
|
350
|
-
async function checkPythonPackage(
|
|
371
|
+
async function checkPythonPackage(_projectDir) {
|
|
351
372
|
const packageName = "mcp";
|
|
352
373
|
let current = null;
|
|
353
374
|
let latest = null;
|
|
@@ -421,6 +442,1130 @@ async function upgradeRustPackage(projectDir, info) {
|
|
|
421
442
|
await execa("cargo", ["update", "-p", info.packageName], { cwd: projectDir });
|
|
422
443
|
}
|
|
423
444
|
|
|
445
|
+
// src/commands/monorepo.ts
|
|
446
|
+
import path3 from "path";
|
|
447
|
+
import inquirer from "inquirer";
|
|
448
|
+
async function monorepoInitCommand(workspaceName, options) {
|
|
449
|
+
try {
|
|
450
|
+
let name = workspaceName;
|
|
451
|
+
if (!name) {
|
|
452
|
+
const { inputName } = await inquirer.prompt([
|
|
453
|
+
{
|
|
454
|
+
type: "input",
|
|
455
|
+
name: "inputName",
|
|
456
|
+
message: "Workspace name:",
|
|
457
|
+
default: "mcp-workspace"
|
|
458
|
+
}
|
|
459
|
+
]);
|
|
460
|
+
name = inputName;
|
|
461
|
+
}
|
|
462
|
+
const outputDir = path3.resolve(process.cwd(), name);
|
|
463
|
+
if (await exists(outputDir)) {
|
|
464
|
+
const files = await readDir(outputDir);
|
|
465
|
+
if (files.length > 0 && !options.force) {
|
|
466
|
+
logger.error(
|
|
467
|
+
`Directory "${name}" already exists and is not empty. Use --force to override.`
|
|
468
|
+
);
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
logger.title(`Creating MCP Monorepo: ${name}`);
|
|
473
|
+
await withSpinner(
|
|
474
|
+
"Creating workspace structure...",
|
|
475
|
+
async () => {
|
|
476
|
+
await ensureDir(outputDir);
|
|
477
|
+
await ensureDir(path3.join(outputDir, "packages"));
|
|
478
|
+
await ensureDir(path3.join(outputDir, "shared"));
|
|
479
|
+
},
|
|
480
|
+
"Workspace structure created"
|
|
481
|
+
);
|
|
482
|
+
const workspaceConfig = {
|
|
483
|
+
name,
|
|
484
|
+
packages: []
|
|
485
|
+
};
|
|
486
|
+
await withSpinner(
|
|
487
|
+
"Creating configuration files...",
|
|
488
|
+
async () => {
|
|
489
|
+
await writeFile(
|
|
490
|
+
path3.join(outputDir, "mcp.workspace.json"),
|
|
491
|
+
JSON.stringify(workspaceConfig, null, 2)
|
|
492
|
+
);
|
|
493
|
+
const packageJson = {
|
|
494
|
+
name,
|
|
495
|
+
version: "1.0.0",
|
|
496
|
+
private: true,
|
|
497
|
+
workspaces: ["packages/*", "shared/*"],
|
|
498
|
+
scripts: {
|
|
499
|
+
build: "npm run build --workspaces",
|
|
500
|
+
dev: "npm run dev --workspaces --if-present",
|
|
501
|
+
test: "npm run test --workspaces --if-present"
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
await writeFile(path3.join(outputDir, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
505
|
+
const readme = `# ${name}
|
|
506
|
+
|
|
507
|
+
MCP Monorepo Workspace
|
|
508
|
+
|
|
509
|
+
## Structure
|
|
510
|
+
|
|
511
|
+
\`\`\`
|
|
512
|
+
${name}/
|
|
513
|
+
\u251C\u2500\u2500 packages/ # MCP servers
|
|
514
|
+
\u251C\u2500\u2500 shared/ # Shared utilities and types
|
|
515
|
+
\u251C\u2500\u2500 mcp.workspace.json # Workspace configuration
|
|
516
|
+
\u2514\u2500\u2500 package.json # Root package.json
|
|
517
|
+
\`\`\`
|
|
518
|
+
|
|
519
|
+
## Commands
|
|
520
|
+
|
|
521
|
+
\`\`\`bash
|
|
522
|
+
# Add a new MCP server
|
|
523
|
+
mcp-new monorepo add <server-name>
|
|
524
|
+
|
|
525
|
+
# Build all packages
|
|
526
|
+
npm run build
|
|
527
|
+
|
|
528
|
+
# Run all dev servers
|
|
529
|
+
npm run dev
|
|
530
|
+
\`\`\`
|
|
531
|
+
|
|
532
|
+
## Packages
|
|
533
|
+
|
|
534
|
+
${workspaceConfig.packages.length === 0 ? "_No packages yet. Run `mcp-new monorepo add <name>` to create one._" : workspaceConfig.packages.map((p) => `- ${p}`).join("\n")}
|
|
535
|
+
`;
|
|
536
|
+
await writeFile(path3.join(outputDir, "README.md"), readme);
|
|
537
|
+
const gitignore = `# Dependencies
|
|
538
|
+
node_modules/
|
|
539
|
+
|
|
540
|
+
# Build outputs
|
|
541
|
+
dist/
|
|
542
|
+
build/
|
|
543
|
+
*.tsbuildinfo
|
|
544
|
+
|
|
545
|
+
# Environment
|
|
546
|
+
.env
|
|
547
|
+
.env.local
|
|
548
|
+
.env.*.local
|
|
549
|
+
|
|
550
|
+
# IDE
|
|
551
|
+
.idea/
|
|
552
|
+
.vscode/
|
|
553
|
+
*.swp
|
|
554
|
+
*.swo
|
|
555
|
+
|
|
556
|
+
# OS
|
|
557
|
+
.DS_Store
|
|
558
|
+
Thumbs.db
|
|
559
|
+
|
|
560
|
+
# Logs
|
|
561
|
+
*.log
|
|
562
|
+
npm-debug.log*
|
|
563
|
+
`;
|
|
564
|
+
await writeFile(path3.join(outputDir, ".gitignore"), gitignore);
|
|
565
|
+
},
|
|
566
|
+
"Configuration files created"
|
|
567
|
+
);
|
|
568
|
+
const gitInstalled = await isGitInstalled();
|
|
569
|
+
if (gitInstalled) {
|
|
570
|
+
await withSpinner(
|
|
571
|
+
"Initializing git repository...",
|
|
572
|
+
async () => {
|
|
573
|
+
await initGitRepository(outputDir);
|
|
574
|
+
await createInitialCommit(outputDir);
|
|
575
|
+
},
|
|
576
|
+
"Git repository initialized"
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
logger.success(`Monorepo workspace "${name}" created successfully!`);
|
|
580
|
+
logger.box("Next steps:", ["", ` cd ${name}`, ` mcp-new monorepo add my-first-server`, ""]);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
if (error instanceof Error) {
|
|
583
|
+
logger.error(error.message);
|
|
584
|
+
} else {
|
|
585
|
+
logger.error("An unexpected error occurred");
|
|
586
|
+
}
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async function monorepoAddCommand(serverName, options) {
|
|
591
|
+
try {
|
|
592
|
+
const workspaceConfigPath = path3.resolve(process.cwd(), "mcp.workspace.json");
|
|
593
|
+
if (!await exists(workspaceConfigPath)) {
|
|
594
|
+
logger.error(
|
|
595
|
+
"Not in a monorepo workspace. Run `mcp-new monorepo init` first, or cd into an existing workspace."
|
|
596
|
+
);
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
let name = serverName || options.name;
|
|
600
|
+
if (!name) {
|
|
601
|
+
const { inputName } = await inquirer.prompt([
|
|
602
|
+
{
|
|
603
|
+
type: "input",
|
|
604
|
+
name: "inputName",
|
|
605
|
+
message: "Server name:",
|
|
606
|
+
default: "my-mcp-server"
|
|
607
|
+
}
|
|
608
|
+
]);
|
|
609
|
+
name = inputName;
|
|
610
|
+
}
|
|
611
|
+
let language;
|
|
612
|
+
const opts = options;
|
|
613
|
+
if (opts.typescript === true || opts.t === true) language = "typescript";
|
|
614
|
+
else if (opts.python === true || opts.p === true) language = "python";
|
|
615
|
+
else if (opts.go === true || opts.g === true) language = "go";
|
|
616
|
+
else if (opts.rust === true || opts.r === true) language = "rust";
|
|
617
|
+
else if (opts.java === true || opts.j === true) language = "java";
|
|
618
|
+
else if (opts.kotlin === true || opts.k === true) language = "kotlin";
|
|
619
|
+
else if (opts.csharp === true || opts.c === true) language = "csharp";
|
|
620
|
+
else if (opts.elixir === true || opts.e === true) language = "elixir";
|
|
621
|
+
if (!language) {
|
|
622
|
+
language = await promptLanguage();
|
|
623
|
+
}
|
|
624
|
+
let javaBuildTool;
|
|
625
|
+
if (language === "java" || language === "kotlin") {
|
|
626
|
+
javaBuildTool = options.maven ? "maven" : options.gradle ? "gradle" : await promptJavaBuildTool();
|
|
627
|
+
}
|
|
628
|
+
const outputDir = path3.join(process.cwd(), "packages", name);
|
|
629
|
+
if (await exists(outputDir)) {
|
|
630
|
+
logger.error(`Server "${name}" already exists in packages/`);
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
logger.title(`Adding MCP Server: ${name}`);
|
|
634
|
+
await generateFromWizard(
|
|
635
|
+
{
|
|
636
|
+
name,
|
|
637
|
+
description: "",
|
|
638
|
+
language,
|
|
639
|
+
transport: "stdio",
|
|
640
|
+
tools: [],
|
|
641
|
+
resources: [],
|
|
642
|
+
includeExampleTool: true,
|
|
643
|
+
skipInstall: options.skipInstall || false,
|
|
644
|
+
initGit: false,
|
|
645
|
+
// Don't init git for individual packages
|
|
646
|
+
javaBuildTool
|
|
647
|
+
},
|
|
648
|
+
outputDir
|
|
649
|
+
);
|
|
650
|
+
const workspaceConfigContent = await readFile(workspaceConfigPath);
|
|
651
|
+
const workspaceConfig = JSON.parse(workspaceConfigContent);
|
|
652
|
+
workspaceConfig.packages.push(name);
|
|
653
|
+
await writeFile(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2));
|
|
654
|
+
logger.success(`Server "${name}" added to packages/`);
|
|
655
|
+
logger.info(`Language: ${language}${javaBuildTool ? ` (${javaBuildTool})` : ""}`);
|
|
656
|
+
logger.box("Next steps:", [
|
|
657
|
+
"",
|
|
658
|
+
` cd packages/${name}`,
|
|
659
|
+
" # Start developing your MCP server",
|
|
660
|
+
""
|
|
661
|
+
]);
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (error instanceof Error) {
|
|
664
|
+
logger.error(error.message);
|
|
665
|
+
} else {
|
|
666
|
+
logger.error("An unexpected error occurred");
|
|
667
|
+
}
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async function monorepoListCommand() {
|
|
672
|
+
try {
|
|
673
|
+
const workspaceConfigPath = path3.resolve(process.cwd(), "mcp.workspace.json");
|
|
674
|
+
if (!await exists(workspaceConfigPath)) {
|
|
675
|
+
logger.error("Not in a monorepo workspace.");
|
|
676
|
+
process.exit(1);
|
|
677
|
+
}
|
|
678
|
+
const workspaceConfigContent = await readFile(workspaceConfigPath);
|
|
679
|
+
const workspaceConfig = JSON.parse(workspaceConfigContent);
|
|
680
|
+
logger.title(`Workspace: ${workspaceConfig.name}`);
|
|
681
|
+
if (workspaceConfig.packages.length === 0) {
|
|
682
|
+
logger.info("No packages yet. Run `mcp-new monorepo add <name>` to create one.");
|
|
683
|
+
} else {
|
|
684
|
+
logger.info("Packages:");
|
|
685
|
+
workspaceConfig.packages.forEach((pkg, index) => {
|
|
686
|
+
console.log(` ${index + 1}. ${pkg}`);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
} catch (error) {
|
|
690
|
+
if (error instanceof Error) {
|
|
691
|
+
logger.error(error.message);
|
|
692
|
+
} else {
|
|
693
|
+
logger.error("An unexpected error occurred");
|
|
694
|
+
}
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/commands/add-ci.ts
|
|
700
|
+
import path4 from "path";
|
|
701
|
+
async function addCICommand(provider, options = {}) {
|
|
702
|
+
const projectDir = process.cwd();
|
|
703
|
+
try {
|
|
704
|
+
const hasPackageJson = await exists(path4.join(projectDir, "package.json"));
|
|
705
|
+
const hasPyproject = await exists(path4.join(projectDir, "pyproject.toml"));
|
|
706
|
+
const hasGoMod = await exists(path4.join(projectDir, "go.mod"));
|
|
707
|
+
const hasCargoToml = await exists(path4.join(projectDir, "Cargo.toml"));
|
|
708
|
+
const hasPomXml = await exists(path4.join(projectDir, "pom.xml"));
|
|
709
|
+
const hasBuildGradle = await exists(path4.join(projectDir, "build.gradle"));
|
|
710
|
+
const hasBuildGradleKts = await exists(path4.join(projectDir, "build.gradle.kts"));
|
|
711
|
+
const hasMixExs = await exists(path4.join(projectDir, "mix.exs"));
|
|
712
|
+
const hasProjectFile = hasPackageJson || hasPyproject || hasGoMod || hasCargoToml || hasPomXml || hasBuildGradle || hasBuildGradleKts || hasMixExs;
|
|
713
|
+
if (!hasProjectFile) {
|
|
714
|
+
logger.error(
|
|
715
|
+
"No project configuration file found. Please run this command from a project root directory."
|
|
716
|
+
);
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
let ciProvider;
|
|
720
|
+
const providerArg = provider || options.provider;
|
|
721
|
+
if (providerArg) {
|
|
722
|
+
if (!isValidCIProvider(providerArg)) {
|
|
723
|
+
logger.error(`Invalid CI provider: ${providerArg}. Valid options: github, gitlab, circleci`);
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
726
|
+
ciProvider = providerArg;
|
|
727
|
+
} else {
|
|
728
|
+
ciProvider = await promptCIProvider();
|
|
729
|
+
}
|
|
730
|
+
let language = await detectLanguageFromProject(projectDir);
|
|
731
|
+
if (!language) {
|
|
732
|
+
logger.warning("Could not detect project language. Please select manually:");
|
|
733
|
+
language = await promptLanguage();
|
|
734
|
+
}
|
|
735
|
+
let javaBuildTool = await detectJavaBuildTool(projectDir);
|
|
736
|
+
if ((language === "java" || language === "kotlin") && !javaBuildTool) {
|
|
737
|
+
javaBuildTool = await promptJavaBuildTool();
|
|
738
|
+
}
|
|
739
|
+
await generateCI({
|
|
740
|
+
projectDir,
|
|
741
|
+
provider: ciProvider,
|
|
742
|
+
language,
|
|
743
|
+
javaBuildTool,
|
|
744
|
+
projectName: path4.basename(projectDir)
|
|
745
|
+
});
|
|
746
|
+
logger.success(`CI/CD configuration added successfully!`);
|
|
747
|
+
const providerInstructions = {
|
|
748
|
+
github: [
|
|
749
|
+
"Your GitHub Actions workflow has been created at .github/workflows/ci.yml",
|
|
750
|
+
"It will automatically run on push and pull requests to main branch",
|
|
751
|
+
"Commit and push to see it in action"
|
|
752
|
+
],
|
|
753
|
+
gitlab: [
|
|
754
|
+
"Your GitLab CI configuration has been created at .gitlab-ci.yml",
|
|
755
|
+
"It will automatically run on push to any branch",
|
|
756
|
+
"Push to your GitLab repository to see it in action"
|
|
757
|
+
],
|
|
758
|
+
circleci: [
|
|
759
|
+
"Your CircleCI configuration has been created at .circleci/config.yml",
|
|
760
|
+
"Connect your repository to CircleCI to enable the pipeline",
|
|
761
|
+
"Visit https://circleci.com to set up your project"
|
|
762
|
+
]
|
|
763
|
+
};
|
|
764
|
+
logger.nextSteps(providerInstructions[ciProvider]);
|
|
765
|
+
} catch (error) {
|
|
766
|
+
if (error instanceof Error) {
|
|
767
|
+
logger.error(error.message);
|
|
768
|
+
} else {
|
|
769
|
+
logger.error("An unexpected error occurred");
|
|
770
|
+
}
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/commands/docs.ts
|
|
776
|
+
import path8 from "path";
|
|
777
|
+
|
|
778
|
+
// src/docs-server/server.ts
|
|
779
|
+
import http from "http";
|
|
780
|
+
import path7 from "path";
|
|
781
|
+
|
|
782
|
+
// src/docs-server/markdown.ts
|
|
783
|
+
import { marked } from "marked";
|
|
784
|
+
marked.setOptions({
|
|
785
|
+
gfm: true,
|
|
786
|
+
breaks: true
|
|
787
|
+
});
|
|
788
|
+
function parseMarkdown(content) {
|
|
789
|
+
const html = marked.parse(content);
|
|
790
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
791
|
+
const title = titleMatch ? titleMatch[1] : "Untitled";
|
|
792
|
+
const headings = [];
|
|
793
|
+
const headingRegex = /^#{1,3}\s+(.+)$/gm;
|
|
794
|
+
let match;
|
|
795
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
796
|
+
headings.push(match[1]);
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
title,
|
|
800
|
+
content: html,
|
|
801
|
+
headings,
|
|
802
|
+
rawContent: content
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
function renderMarkdownToHTML(content, title) {
|
|
806
|
+
const parsed = parseMarkdown(content);
|
|
807
|
+
return `<!DOCTYPE html>
|
|
808
|
+
<html lang="en">
|
|
809
|
+
<head>
|
|
810
|
+
<meta charset="UTF-8">
|
|
811
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
812
|
+
<title>${title} - mcp-new Documentation</title>
|
|
813
|
+
<style>
|
|
814
|
+
* {
|
|
815
|
+
margin: 0;
|
|
816
|
+
padding: 0;
|
|
817
|
+
box-sizing: border-box;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
body {
|
|
821
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
822
|
+
line-height: 1.6;
|
|
823
|
+
color: #333;
|
|
824
|
+
background: #f5f5f5;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.container {
|
|
828
|
+
display: flex;
|
|
829
|
+
min-height: 100vh;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.sidebar {
|
|
833
|
+
width: 280px;
|
|
834
|
+
background: #1a1a2e;
|
|
835
|
+
color: #fff;
|
|
836
|
+
padding: 20px;
|
|
837
|
+
position: fixed;
|
|
838
|
+
height: 100vh;
|
|
839
|
+
overflow-y: auto;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.sidebar h1 {
|
|
843
|
+
font-size: 1.5rem;
|
|
844
|
+
margin-bottom: 20px;
|
|
845
|
+
color: #4fc3f7;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.sidebar nav ul {
|
|
849
|
+
list-style: none;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.sidebar nav li {
|
|
853
|
+
margin: 8px 0;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.sidebar nav a {
|
|
857
|
+
color: #b0bec5;
|
|
858
|
+
text-decoration: none;
|
|
859
|
+
display: block;
|
|
860
|
+
padding: 8px 12px;
|
|
861
|
+
border-radius: 4px;
|
|
862
|
+
transition: background 0.2s, color 0.2s;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.sidebar nav a:hover,
|
|
866
|
+
.sidebar nav a.active {
|
|
867
|
+
background: rgba(79, 195, 247, 0.1);
|
|
868
|
+
color: #4fc3f7;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.search-box {
|
|
872
|
+
margin-bottom: 20px;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.search-box input {
|
|
876
|
+
width: 100%;
|
|
877
|
+
padding: 10px 12px;
|
|
878
|
+
border: none;
|
|
879
|
+
border-radius: 4px;
|
|
880
|
+
background: rgba(255,255,255,0.1);
|
|
881
|
+
color: #fff;
|
|
882
|
+
font-size: 14px;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.search-box input::placeholder {
|
|
886
|
+
color: #78909c;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.main {
|
|
890
|
+
margin-left: 280px;
|
|
891
|
+
flex: 1;
|
|
892
|
+
padding: 40px;
|
|
893
|
+
max-width: 900px;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.content {
|
|
897
|
+
background: #fff;
|
|
898
|
+
padding: 40px;
|
|
899
|
+
border-radius: 8px;
|
|
900
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.content h1 {
|
|
904
|
+
font-size: 2.5rem;
|
|
905
|
+
margin-bottom: 20px;
|
|
906
|
+
color: #1a1a2e;
|
|
907
|
+
border-bottom: 2px solid #4fc3f7;
|
|
908
|
+
padding-bottom: 10px;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.content h2 {
|
|
912
|
+
font-size: 1.8rem;
|
|
913
|
+
margin-top: 40px;
|
|
914
|
+
margin-bottom: 16px;
|
|
915
|
+
color: #1a1a2e;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.content h3 {
|
|
919
|
+
font-size: 1.4rem;
|
|
920
|
+
margin-top: 30px;
|
|
921
|
+
margin-bottom: 12px;
|
|
922
|
+
color: #333;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.content p {
|
|
926
|
+
margin-bottom: 16px;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.content code {
|
|
930
|
+
background: #f0f0f0;
|
|
931
|
+
padding: 2px 6px;
|
|
932
|
+
border-radius: 3px;
|
|
933
|
+
font-family: 'Fira Code', 'Monaco', monospace;
|
|
934
|
+
font-size: 0.9em;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.content pre {
|
|
938
|
+
background: #1a1a2e;
|
|
939
|
+
color: #f0f0f0;
|
|
940
|
+
padding: 20px;
|
|
941
|
+
border-radius: 6px;
|
|
942
|
+
overflow-x: auto;
|
|
943
|
+
margin: 20px 0;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.content pre code {
|
|
947
|
+
background: none;
|
|
948
|
+
padding: 0;
|
|
949
|
+
color: inherit;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
.content ul, .content ol {
|
|
953
|
+
margin: 16px 0;
|
|
954
|
+
padding-left: 30px;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.content li {
|
|
958
|
+
margin: 8px 0;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.content a {
|
|
962
|
+
color: #4fc3f7;
|
|
963
|
+
text-decoration: none;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.content a:hover {
|
|
967
|
+
text-decoration: underline;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
.content blockquote {
|
|
971
|
+
border-left: 4px solid #4fc3f7;
|
|
972
|
+
padding-left: 20px;
|
|
973
|
+
margin: 20px 0;
|
|
974
|
+
color: #666;
|
|
975
|
+
font-style: italic;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.content table {
|
|
979
|
+
width: 100%;
|
|
980
|
+
border-collapse: collapse;
|
|
981
|
+
margin: 20px 0;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.content th, .content td {
|
|
985
|
+
border: 1px solid #ddd;
|
|
986
|
+
padding: 12px;
|
|
987
|
+
text-align: left;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.content th {
|
|
991
|
+
background: #f5f5f5;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.search-results {
|
|
995
|
+
display: none;
|
|
996
|
+
position: absolute;
|
|
997
|
+
top: 100%;
|
|
998
|
+
left: 0;
|
|
999
|
+
right: 0;
|
|
1000
|
+
background: #2a2a3e;
|
|
1001
|
+
border-radius: 4px;
|
|
1002
|
+
max-height: 300px;
|
|
1003
|
+
overflow-y: auto;
|
|
1004
|
+
margin-top: 4px;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.search-results.active {
|
|
1008
|
+
display: block;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.search-result-item {
|
|
1012
|
+
padding: 10px 12px;
|
|
1013
|
+
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
1014
|
+
cursor: pointer;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.search-result-item:hover {
|
|
1018
|
+
background: rgba(79, 195, 247, 0.1);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.search-result-item h4 {
|
|
1022
|
+
color: #fff;
|
|
1023
|
+
font-size: 14px;
|
|
1024
|
+
margin-bottom: 4px;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.search-result-item p {
|
|
1028
|
+
color: #78909c;
|
|
1029
|
+
font-size: 12px;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
@media (max-width: 768px) {
|
|
1033
|
+
.sidebar {
|
|
1034
|
+
display: none;
|
|
1035
|
+
}
|
|
1036
|
+
.main {
|
|
1037
|
+
margin-left: 0;
|
|
1038
|
+
padding: 20px;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
</style>
|
|
1042
|
+
</head>
|
|
1043
|
+
<body>
|
|
1044
|
+
<div class="container">
|
|
1045
|
+
<aside class="sidebar">
|
|
1046
|
+
<h1>mcp-new</h1>
|
|
1047
|
+
<div class="search-box" style="position: relative;">
|
|
1048
|
+
<input type="text" id="search" placeholder="Search docs..." autocomplete="off">
|
|
1049
|
+
<div id="search-results" class="search-results"></div>
|
|
1050
|
+
</div>
|
|
1051
|
+
<nav id="nav">
|
|
1052
|
+
<ul>
|
|
1053
|
+
${parsed.headings.map((h) => `<li><a href="#${slugify(h)}">${h}</a></li>`).join("\n ")}
|
|
1054
|
+
</ul>
|
|
1055
|
+
</nav>
|
|
1056
|
+
</aside>
|
|
1057
|
+
<main class="main">
|
|
1058
|
+
<article class="content">
|
|
1059
|
+
${parsed.content}
|
|
1060
|
+
</article>
|
|
1061
|
+
</main>
|
|
1062
|
+
</div>
|
|
1063
|
+
|
|
1064
|
+
<script>
|
|
1065
|
+
// SSE for hot reload
|
|
1066
|
+
const evtSource = new EventSource('/sse');
|
|
1067
|
+
evtSource.onmessage = function(event) {
|
|
1068
|
+
if (event.data === 'reload') {
|
|
1069
|
+
location.reload();
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
// Search functionality
|
|
1074
|
+
const searchInput = document.getElementById('search');
|
|
1075
|
+
const searchResults = document.getElementById('search-results');
|
|
1076
|
+
|
|
1077
|
+
searchInput.addEventListener('input', async (e) => {
|
|
1078
|
+
const query = e.target.value.trim();
|
|
1079
|
+
if (query.length < 2) {
|
|
1080
|
+
searchResults.classList.remove('active');
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
try {
|
|
1085
|
+
const res = await fetch('/api/search?q=' + encodeURIComponent(query));
|
|
1086
|
+
const results = await res.json();
|
|
1087
|
+
|
|
1088
|
+
if (results.length > 0) {
|
|
1089
|
+
searchResults.innerHTML = results.map(r =>
|
|
1090
|
+
'<div class="search-result-item" onclick="location.href=\\'' + r.path + '\\'"><h4>' + r.title + '</h4><p>' + r.snippet + '</p></div>'
|
|
1091
|
+
).join('');
|
|
1092
|
+
searchResults.classList.add('active');
|
|
1093
|
+
} else {
|
|
1094
|
+
searchResults.classList.remove('active');
|
|
1095
|
+
}
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
console.error('Search error:', err);
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
document.addEventListener('click', (e) => {
|
|
1102
|
+
if (!e.target.closest('.search-box')) {
|
|
1103
|
+
searchResults.classList.remove('active');
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
</script>
|
|
1107
|
+
</body>
|
|
1108
|
+
</html>`;
|
|
1109
|
+
}
|
|
1110
|
+
function slugify(text) {
|
|
1111
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/docs-server/search.ts
|
|
1115
|
+
import path5 from "path";
|
|
1116
|
+
var DocsSearchEngine = class {
|
|
1117
|
+
index = { documents: /* @__PURE__ */ new Map() };
|
|
1118
|
+
docsDir;
|
|
1119
|
+
constructor(docsDir) {
|
|
1120
|
+
this.docsDir = docsDir;
|
|
1121
|
+
}
|
|
1122
|
+
async buildIndex() {
|
|
1123
|
+
this.index.documents.clear();
|
|
1124
|
+
await this.indexDirectory(this.docsDir);
|
|
1125
|
+
}
|
|
1126
|
+
async indexDirectory(dir) {
|
|
1127
|
+
if (!await exists(dir)) {
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const entries = await readDir(dir);
|
|
1131
|
+
for (const entry of entries) {
|
|
1132
|
+
const fullPath = path5.join(dir, entry);
|
|
1133
|
+
try {
|
|
1134
|
+
const subEntries = await readDir(fullPath);
|
|
1135
|
+
await this.indexDirectory(fullPath);
|
|
1136
|
+
} catch {
|
|
1137
|
+
if (entry.endsWith(".md")) {
|
|
1138
|
+
await this.indexFile(fullPath);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async indexFile(filePath) {
|
|
1144
|
+
try {
|
|
1145
|
+
const content = await readFile(filePath);
|
|
1146
|
+
const parsed = parseMarkdown(content);
|
|
1147
|
+
const relativePath = "/" + path5.relative(this.docsDir, filePath).replace(/\.md$/, "");
|
|
1148
|
+
const words = /* @__PURE__ */ new Set();
|
|
1149
|
+
const textContent = parsed.rawContent.toLowerCase();
|
|
1150
|
+
const wordMatches = textContent.match(/\b[a-z0-9]+\b/g);
|
|
1151
|
+
if (wordMatches) {
|
|
1152
|
+
wordMatches.forEach((w) => words.add(w));
|
|
1153
|
+
}
|
|
1154
|
+
this.index.documents.set(relativePath, {
|
|
1155
|
+
path: relativePath,
|
|
1156
|
+
title: parsed.title,
|
|
1157
|
+
content: parsed.rawContent,
|
|
1158
|
+
words
|
|
1159
|
+
});
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
search(query, limit = 10) {
|
|
1164
|
+
const queryWords = query.toLowerCase().split(/\s+/).filter((w) => w.length > 1);
|
|
1165
|
+
if (queryWords.length === 0) {
|
|
1166
|
+
return [];
|
|
1167
|
+
}
|
|
1168
|
+
const results = [];
|
|
1169
|
+
for (const [docPath, doc] of this.index.documents) {
|
|
1170
|
+
let score = 0;
|
|
1171
|
+
const titleLower = doc.title.toLowerCase();
|
|
1172
|
+
for (const word of queryWords) {
|
|
1173
|
+
if (titleLower.includes(word)) {
|
|
1174
|
+
score += 10;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const contentLower = doc.content.toLowerCase();
|
|
1178
|
+
for (const word of queryWords) {
|
|
1179
|
+
if (doc.words.has(word)) {
|
|
1180
|
+
score += 1;
|
|
1181
|
+
}
|
|
1182
|
+
if (contentLower.includes(word)) {
|
|
1183
|
+
score += 2;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (score > 0) {
|
|
1187
|
+
const snippet = this.extractSnippet(doc.content, queryWords[0]);
|
|
1188
|
+
results.push({
|
|
1189
|
+
path: docPath,
|
|
1190
|
+
title: doc.title,
|
|
1191
|
+
snippet,
|
|
1192
|
+
score
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
results.sort((a, b) => b.score - a.score);
|
|
1197
|
+
return results.slice(0, limit);
|
|
1198
|
+
}
|
|
1199
|
+
extractSnippet(content, query) {
|
|
1200
|
+
const index = content.toLowerCase().indexOf(query.toLowerCase());
|
|
1201
|
+
if (index === -1) {
|
|
1202
|
+
return content.slice(0, 150) + "...";
|
|
1203
|
+
}
|
|
1204
|
+
const start = Math.max(0, index - 50);
|
|
1205
|
+
const end = Math.min(content.length, index + query.length + 100);
|
|
1206
|
+
let snippet = content.slice(start, end);
|
|
1207
|
+
if (start > 0) snippet = "..." + snippet;
|
|
1208
|
+
if (end < content.length) snippet = snippet + "...";
|
|
1209
|
+
return snippet.replace(/\n/g, " ").trim();
|
|
1210
|
+
}
|
|
1211
|
+
getDocuments() {
|
|
1212
|
+
return this.index.documents;
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
// src/docs-server/watcher.ts
|
|
1217
|
+
import fs3 from "fs";
|
|
1218
|
+
import path6 from "path";
|
|
1219
|
+
import { EventEmitter } from "events";
|
|
1220
|
+
var DocsWatcher = class extends EventEmitter {
|
|
1221
|
+
watchers = /* @__PURE__ */ new Map();
|
|
1222
|
+
debounceTimer = null;
|
|
1223
|
+
docsDir;
|
|
1224
|
+
constructor(docsDir) {
|
|
1225
|
+
super();
|
|
1226
|
+
this.docsDir = docsDir;
|
|
1227
|
+
}
|
|
1228
|
+
async start() {
|
|
1229
|
+
await this.watchDirectory(this.docsDir);
|
|
1230
|
+
}
|
|
1231
|
+
async watchDirectory(dir) {
|
|
1232
|
+
if (!await exists(dir)) {
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
const watcher = fs3.watch(dir, { recursive: false }, (eventType, filename) => {
|
|
1236
|
+
if (filename && filename.endsWith(".md")) {
|
|
1237
|
+
this.debouncedEmit();
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
this.watchers.set(dir, watcher);
|
|
1241
|
+
try {
|
|
1242
|
+
const entries = await readDir(dir);
|
|
1243
|
+
for (const entry of entries) {
|
|
1244
|
+
const fullPath = path6.join(dir, entry);
|
|
1245
|
+
try {
|
|
1246
|
+
const subEntries = await readDir(fullPath);
|
|
1247
|
+
await this.watchDirectory(fullPath);
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
} catch {
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
debouncedEmit() {
|
|
1255
|
+
if (this.debounceTimer) {
|
|
1256
|
+
clearTimeout(this.debounceTimer);
|
|
1257
|
+
}
|
|
1258
|
+
this.debounceTimer = setTimeout(() => {
|
|
1259
|
+
this.emit("change");
|
|
1260
|
+
}, 100);
|
|
1261
|
+
}
|
|
1262
|
+
stop() {
|
|
1263
|
+
for (const watcher of this.watchers.values()) {
|
|
1264
|
+
watcher.close();
|
|
1265
|
+
}
|
|
1266
|
+
this.watchers.clear();
|
|
1267
|
+
if (this.debounceTimer) {
|
|
1268
|
+
clearTimeout(this.debounceTimer);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1273
|
+
// src/docs-server/server.ts
|
|
1274
|
+
var DocsServer = class {
|
|
1275
|
+
server = null;
|
|
1276
|
+
searchEngine;
|
|
1277
|
+
watcher;
|
|
1278
|
+
sseClients = /* @__PURE__ */ new Set();
|
|
1279
|
+
options;
|
|
1280
|
+
constructor(options) {
|
|
1281
|
+
this.options = options;
|
|
1282
|
+
this.searchEngine = new DocsSearchEngine(options.docsDir);
|
|
1283
|
+
this.watcher = new DocsWatcher(options.docsDir);
|
|
1284
|
+
}
|
|
1285
|
+
async start() {
|
|
1286
|
+
await this.searchEngine.buildIndex();
|
|
1287
|
+
await this.watcher.start();
|
|
1288
|
+
this.watcher.on("change", async () => {
|
|
1289
|
+
await this.searchEngine.buildIndex();
|
|
1290
|
+
this.notifyClients();
|
|
1291
|
+
});
|
|
1292
|
+
this.server = http.createServer((req, res) => {
|
|
1293
|
+
this.handleRequest(req, res);
|
|
1294
|
+
});
|
|
1295
|
+
return new Promise((resolve) => {
|
|
1296
|
+
this.server.listen(this.options.port, () => {
|
|
1297
|
+
resolve();
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
stop() {
|
|
1302
|
+
if (this.server) {
|
|
1303
|
+
this.server.close();
|
|
1304
|
+
}
|
|
1305
|
+
this.watcher.stop();
|
|
1306
|
+
for (const client of this.sseClients) {
|
|
1307
|
+
client.end();
|
|
1308
|
+
}
|
|
1309
|
+
this.sseClients.clear();
|
|
1310
|
+
}
|
|
1311
|
+
async handleRequest(req, res) {
|
|
1312
|
+
const url = new URL(req.url || "/", `http://localhost:${this.options.port}`);
|
|
1313
|
+
const pathname = url.pathname;
|
|
1314
|
+
try {
|
|
1315
|
+
if (pathname === "/sse") {
|
|
1316
|
+
this.handleSSE(res);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (pathname === "/api/search") {
|
|
1320
|
+
const query = url.searchParams.get("q") || "";
|
|
1321
|
+
const results = this.searchEngine.search(query);
|
|
1322
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1323
|
+
res.end(JSON.stringify(results));
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
if (pathname.startsWith("/static/")) {
|
|
1327
|
+
await this.serveStatic(pathname, res);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
await this.serveMarkdown(pathname, res);
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
this.serveError(res, 500, "Internal Server Error");
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
handleSSE(res) {
|
|
1336
|
+
res.writeHead(200, {
|
|
1337
|
+
"Content-Type": "text/event-stream",
|
|
1338
|
+
"Cache-Control": "no-cache",
|
|
1339
|
+
Connection: "keep-alive"
|
|
1340
|
+
});
|
|
1341
|
+
this.sseClients.add(res);
|
|
1342
|
+
res.on("close", () => {
|
|
1343
|
+
this.sseClients.delete(res);
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
notifyClients() {
|
|
1347
|
+
for (const client of this.sseClients) {
|
|
1348
|
+
client.write("data: reload\n\n");
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async serveMarkdown(pathname, res) {
|
|
1352
|
+
let filePath = pathname === "/" ? "/index" : pathname;
|
|
1353
|
+
if (!filePath.endsWith(".md")) {
|
|
1354
|
+
filePath += ".md";
|
|
1355
|
+
}
|
|
1356
|
+
const fullPath = path7.join(this.options.docsDir, filePath);
|
|
1357
|
+
if (await exists(fullPath)) {
|
|
1358
|
+
const content = await readFile(fullPath);
|
|
1359
|
+
const title = path7.basename(filePath, ".md");
|
|
1360
|
+
const html = renderMarkdownToHTML(content, title);
|
|
1361
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1362
|
+
res.end(html);
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
const indexPath = path7.join(this.options.docsDir, pathname, "index.md");
|
|
1366
|
+
if (await exists(indexPath)) {
|
|
1367
|
+
const content = await readFile(indexPath);
|
|
1368
|
+
const title = path7.basename(pathname) || "Home";
|
|
1369
|
+
const html = renderMarkdownToHTML(content, title);
|
|
1370
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1371
|
+
res.end(html);
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const dirPath = path7.join(this.options.docsDir, pathname);
|
|
1375
|
+
if (await exists(dirPath)) {
|
|
1376
|
+
const indexHtml = await this.generateDirectoryIndex(pathname);
|
|
1377
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1378
|
+
res.end(indexHtml);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
this.serveError(res, 404, "Page Not Found");
|
|
1382
|
+
}
|
|
1383
|
+
async generateDirectoryIndex(pathname) {
|
|
1384
|
+
const docs = this.searchEngine.getDocuments();
|
|
1385
|
+
const prefix = pathname === "/" ? "" : pathname;
|
|
1386
|
+
const links = [];
|
|
1387
|
+
for (const [docPath, doc] of docs) {
|
|
1388
|
+
if (docPath.startsWith(prefix) || prefix === "") {
|
|
1389
|
+
links.push(`- [${doc.title}](${docPath})`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
const content = `# Documentation
|
|
1393
|
+
|
|
1394
|
+
${links.join("\n")}`;
|
|
1395
|
+
return renderMarkdownToHTML(content, "Documentation");
|
|
1396
|
+
}
|
|
1397
|
+
async serveStatic(pathname, res) {
|
|
1398
|
+
const ext = path7.extname(pathname);
|
|
1399
|
+
const contentTypes = {
|
|
1400
|
+
".css": "text/css",
|
|
1401
|
+
".js": "application/javascript",
|
|
1402
|
+
".png": "image/png",
|
|
1403
|
+
".svg": "image/svg+xml"
|
|
1404
|
+
};
|
|
1405
|
+
const contentType = contentTypes[ext] || "application/octet-stream";
|
|
1406
|
+
const fullPath = path7.join(this.options.docsDir, pathname);
|
|
1407
|
+
if (await exists(fullPath)) {
|
|
1408
|
+
const content = await readFile(fullPath);
|
|
1409
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
1410
|
+
res.end(content);
|
|
1411
|
+
} else {
|
|
1412
|
+
this.serveError(res, 404, "Not Found");
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
serveError(res, status, message) {
|
|
1416
|
+
const html = renderMarkdownToHTML(`# ${status}
|
|
1417
|
+
|
|
1418
|
+
${message}`, `${status} Error`);
|
|
1419
|
+
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
1420
|
+
res.end(html);
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
// src/commands/docs.ts
|
|
1425
|
+
async function docsCommand(options = {}) {
|
|
1426
|
+
const port = parseInt(options.port || "3000", 10);
|
|
1427
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
1428
|
+
logger.error("Invalid port number. Please provide a port between 1 and 65535.");
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
const possibleDocsDirs = [
|
|
1432
|
+
path8.join(process.cwd(), "docs"),
|
|
1433
|
+
path8.join(process.cwd(), "documentation"),
|
|
1434
|
+
path8.join(process.cwd(), "doc"),
|
|
1435
|
+
process.cwd()
|
|
1436
|
+
// Use current directory if it contains markdown files
|
|
1437
|
+
];
|
|
1438
|
+
let docsDir = null;
|
|
1439
|
+
for (const dir of possibleDocsDirs) {
|
|
1440
|
+
if (await exists(dir)) {
|
|
1441
|
+
const hasMarkdown = await hasMarkdownFiles(dir);
|
|
1442
|
+
if (hasMarkdown) {
|
|
1443
|
+
docsDir = dir;
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
if (!docsDir) {
|
|
1449
|
+
logger.error("No documentation directory found.");
|
|
1450
|
+
logger.info('Create a "docs" directory with .md files to use this command.');
|
|
1451
|
+
process.exit(1);
|
|
1452
|
+
}
|
|
1453
|
+
logger.title("mcp-new Documentation Server");
|
|
1454
|
+
logger.info(`Serving docs from: ${docsDir}`);
|
|
1455
|
+
const server = new DocsServer({
|
|
1456
|
+
port,
|
|
1457
|
+
docsDir
|
|
1458
|
+
});
|
|
1459
|
+
try {
|
|
1460
|
+
await server.start();
|
|
1461
|
+
logger.success(`Documentation server running at http://localhost:${port}`);
|
|
1462
|
+
logger.info("Hot reload enabled - changes will refresh automatically");
|
|
1463
|
+
logger.info("Press Ctrl+C to stop");
|
|
1464
|
+
process.on("SIGINT", () => {
|
|
1465
|
+
logger.info("\nShutting down...");
|
|
1466
|
+
server.stop();
|
|
1467
|
+
process.exit(0);
|
|
1468
|
+
});
|
|
1469
|
+
process.on("SIGTERM", () => {
|
|
1470
|
+
server.stop();
|
|
1471
|
+
process.exit(0);
|
|
1472
|
+
});
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
if (error instanceof Error) {
|
|
1475
|
+
if (error.message.includes("EADDRINUSE")) {
|
|
1476
|
+
logger.error(`Port ${port} is already in use. Try a different port with --port.`);
|
|
1477
|
+
} else {
|
|
1478
|
+
logger.error(error.message);
|
|
1479
|
+
}
|
|
1480
|
+
} else {
|
|
1481
|
+
logger.error("Failed to start documentation server");
|
|
1482
|
+
}
|
|
1483
|
+
process.exit(1);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
async function hasMarkdownFiles(dir) {
|
|
1487
|
+
try {
|
|
1488
|
+
const { readdir } = await import("fs/promises");
|
|
1489
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1490
|
+
for (const entry of entries) {
|
|
1491
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1492
|
+
return true;
|
|
1493
|
+
}
|
|
1494
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
1495
|
+
const subDir = path8.join(dir, entry.name);
|
|
1496
|
+
if (await hasMarkdownFiles(subDir)) {
|
|
1497
|
+
return true;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
} catch {
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/commands/preset-cache.ts
|
|
1508
|
+
async function presetCacheCommand(action) {
|
|
1509
|
+
try {
|
|
1510
|
+
if (!action) {
|
|
1511
|
+
action = "list";
|
|
1512
|
+
}
|
|
1513
|
+
switch (action) {
|
|
1514
|
+
case "clear":
|
|
1515
|
+
await handleClear();
|
|
1516
|
+
break;
|
|
1517
|
+
case "list":
|
|
1518
|
+
await handleList();
|
|
1519
|
+
break;
|
|
1520
|
+
case "path":
|
|
1521
|
+
handlePath();
|
|
1522
|
+
break;
|
|
1523
|
+
default:
|
|
1524
|
+
logger.error(`Unknown action: ${action}. Valid actions: clear, list, path`);
|
|
1525
|
+
process.exit(1);
|
|
1526
|
+
}
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
if (error instanceof Error) {
|
|
1529
|
+
logger.error(error.message);
|
|
1530
|
+
} else {
|
|
1531
|
+
logger.error("An unexpected error occurred");
|
|
1532
|
+
}
|
|
1533
|
+
process.exit(1);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
async function handleClear() {
|
|
1537
|
+
const count = await clearPresetCache();
|
|
1538
|
+
if (count > 0) {
|
|
1539
|
+
logger.success(`Cleared ${count} cached preset(s)`);
|
|
1540
|
+
} else {
|
|
1541
|
+
logger.info("No cached presets to clear");
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
async function handleList() {
|
|
1545
|
+
const presets = await listCachedPresets();
|
|
1546
|
+
if (presets.length === 0) {
|
|
1547
|
+
logger.info("No presets cached");
|
|
1548
|
+
logger.info(`Cache directory: ${getCacheDir()}`);
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
logger.title("Cached Presets");
|
|
1552
|
+
for (const preset of presets) {
|
|
1553
|
+
const age = Date.now() - preset.cachedAt;
|
|
1554
|
+
const hoursAgo = Math.round(age / (1e3 * 60 * 60));
|
|
1555
|
+
const ageStr = hoursAgo < 1 ? "less than an hour ago" : `${hoursAgo} hour(s) ago`;
|
|
1556
|
+
console.log(`
|
|
1557
|
+
${preset.manifest.name} (${preset.manifest.version})`);
|
|
1558
|
+
console.log(` Source: ${preset.source.type}:${preset.source.identifier}`);
|
|
1559
|
+
console.log(` Cached: ${ageStr}`);
|
|
1560
|
+
console.log(` Tools: ${preset.manifest.tools.map((t) => t.name).join(", ")}`);
|
|
1561
|
+
}
|
|
1562
|
+
console.log(`
|
|
1563
|
+
Cache directory: ${getCacheDir()}`);
|
|
1564
|
+
}
|
|
1565
|
+
function handlePath() {
|
|
1566
|
+
console.log(getCacheDir());
|
|
1567
|
+
}
|
|
1568
|
+
|
|
424
1569
|
// src/cli.ts
|
|
425
1570
|
var program = new Command();
|
|
426
1571
|
var logo = `
|
|
@@ -464,15 +1609,21 @@ ${chalk4.bold("Supported Languages:")}
|
|
|
464
1609
|
${chalk4.green("-p, --python")} Python with pip
|
|
465
1610
|
${chalk4.green("-g, --go")} Go with go modules
|
|
466
1611
|
${chalk4.green("-r, --rust")} Rust with cargo
|
|
1612
|
+
${chalk4.green("-j, --java")} Java with Maven/Gradle
|
|
1613
|
+
${chalk4.green("-k, --kotlin")} Kotlin with Maven/Gradle
|
|
1614
|
+
${chalk4.green("-c, --csharp")} C# with .NET
|
|
1615
|
+
${chalk4.green("-e, --elixir")} Elixir with Mix
|
|
467
1616
|
|
|
468
1617
|
${chalk4.bold("Learn More:")}
|
|
469
1618
|
|
|
470
1619
|
Documentation: ${chalk4.underline("https://github.com/d1maash/mcp-new")}
|
|
471
1620
|
MCP Spec: ${chalk4.underline("https://spec.modelcontextprotocol.io")}
|
|
472
1621
|
`;
|
|
473
|
-
program.name("mcp-new").description("CLI tool for generating MCP (Model Context Protocol) servers").version("1.
|
|
474
|
-
program.argument("[project-name]", "Name of the project to create").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("-g, --go", "Use Go template").option("-r, --rust", "Use Rust template").option("--skip-install", "Skip dependency installation").option("--from-openapi <path>", "Generate from OpenAPI/Swagger specification").option("--from-prompt", "Generate tools using AI from text description").option("--preset <name>", "Use a preset template (database, rest-api, filesystem)").option("-y, --yes", "Skip prompts and use defaults").action(createCommand);
|
|
475
|
-
program.command("init").description("Initialize MCP server in the current directory").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("-g, --go", "Use Go template").option("-r, --rust", "Use Rust template").option("--skip-install", "Skip dependency installation").option("-f, --force", "Initialize even if directory contains files").addHelpText(
|
|
1622
|
+
program.name("mcp-new").description("CLI tool for generating MCP (Model Context Protocol) servers").version("1.5.0").addHelpText("beforeAll", logo).addHelpText("after", examples);
|
|
1623
|
+
program.argument("[project-name]", "Name of the project to create").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("-g, --go", "Use Go template").option("-r, --rust", "Use Rust template").option("-j, --java", "Use Java template").option("-k, --kotlin", "Use Kotlin template").option("-c, --csharp", "Use C# (.NET) template").option("-e, --elixir", "Use Elixir template").option("--maven", "Use Maven build tool (for Java/Kotlin)").option("--gradle", "Use Gradle build tool (for Java/Kotlin)").option("--skip-install", "Skip dependency installation").option("--from-openapi <path>", "Generate from OpenAPI/Swagger specification").option("--from-prompt", "Generate tools using AI from text description").option("--preset <name>", "Use a preset template (database, rest-api, filesystem, or @org/package, github:user/repo)").option("--ci <provider>", "Add CI/CD configuration (github, gitlab, circleci)").option("-y, --yes", "Skip prompts and use defaults").action(createCommand);
|
|
1624
|
+
program.command("init").description("Initialize MCP server in the current directory").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("-g, --go", "Use Go template").option("-r, --rust", "Use Rust template").option("-j, --java", "Use Java template").option("-k, --kotlin", "Use Kotlin template").option("-c, --csharp", "Use C# (.NET) template").option("-e, --elixir", "Use Elixir template").option("--maven", "Use Maven build tool (for Java/Kotlin)").option("--gradle", "Use Gradle build tool (for Java/Kotlin)").option("--skip-install", "Skip dependency installation").option("-f, --force", "Initialize even if directory contains files").addHelpText(
|
|
1625
|
+
"after",
|
|
1626
|
+
`
|
|
476
1627
|
${chalk4.bold("Examples:")}
|
|
477
1628
|
|
|
478
1629
|
${chalk4.gray("# Initialize in current directory")}
|
|
@@ -483,8 +1634,11 @@ ${chalk4.bold("Examples:")}
|
|
|
483
1634
|
|
|
484
1635
|
${chalk4.gray("# Force initialize (overwrite existing files)")}
|
|
485
1636
|
${chalk4.cyan("$")} mcp-new init -f
|
|
486
|
-
`
|
|
487
|
-
|
|
1637
|
+
`
|
|
1638
|
+
).action(initCommand);
|
|
1639
|
+
program.command("add-tool").description("Add a new tool to an existing MCP server").option("-n, --name <name>", "Tool name (snake_case)").addHelpText(
|
|
1640
|
+
"after",
|
|
1641
|
+
`
|
|
488
1642
|
${chalk4.bold("Examples:")}
|
|
489
1643
|
|
|
490
1644
|
${chalk4.gray("# Add tool interactively")}
|
|
@@ -492,14 +1646,20 @@ ${chalk4.bold("Examples:")}
|
|
|
492
1646
|
|
|
493
1647
|
${chalk4.gray("# Add tool with name")}
|
|
494
1648
|
${chalk4.cyan("$")} mcp-new add-tool -n my_new_tool
|
|
495
|
-
`
|
|
496
|
-
|
|
1649
|
+
`
|
|
1650
|
+
).action(addToolCommand);
|
|
1651
|
+
program.command("list-presets").description("List all available preset templates").addHelpText(
|
|
1652
|
+
"after",
|
|
1653
|
+
`
|
|
497
1654
|
${chalk4.bold("Examples:")}
|
|
498
1655
|
|
|
499
1656
|
${chalk4.gray("# Show all presets with their tools")}
|
|
500
1657
|
${chalk4.cyan("$")} mcp-new list-presets
|
|
501
|
-
`
|
|
502
|
-
|
|
1658
|
+
`
|
|
1659
|
+
).action(listPresetsCommand);
|
|
1660
|
+
program.command("validate").description("Validate the current MCP server project").addHelpText(
|
|
1661
|
+
"after",
|
|
1662
|
+
`
|
|
503
1663
|
${chalk4.bold("Examples:")}
|
|
504
1664
|
|
|
505
1665
|
${chalk4.gray("# Validate current project")}
|
|
@@ -510,8 +1670,11 @@ ${chalk4.bold("Checks:")}
|
|
|
510
1670
|
\u2022 MCP SDK dependency presence and version
|
|
511
1671
|
\u2022 Entry point file existence
|
|
512
1672
|
\u2022 Basic project structure
|
|
513
|
-
`
|
|
514
|
-
|
|
1673
|
+
`
|
|
1674
|
+
).action(validateCommand);
|
|
1675
|
+
program.command("upgrade").description("Upgrade MCP SDK to the latest version").option("-c, --check", "Check for updates without installing").addHelpText(
|
|
1676
|
+
"after",
|
|
1677
|
+
`
|
|
515
1678
|
${chalk4.bold("Examples:")}
|
|
516
1679
|
|
|
517
1680
|
${chalk4.gray("# Upgrade MCP SDK to latest version")}
|
|
@@ -525,8 +1688,114 @@ ${chalk4.bold("Supported languages:")}
|
|
|
525
1688
|
\u2022 Python (pip)
|
|
526
1689
|
\u2022 Go (go modules)
|
|
527
1690
|
\u2022 Rust (cargo)
|
|
528
|
-
`
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
1691
|
+
`
|
|
1692
|
+
).action(upgradeCommand);
|
|
1693
|
+
program.command("preset-cache [action]").description("Manage external preset cache").addHelpText(
|
|
1694
|
+
"after",
|
|
1695
|
+
`
|
|
1696
|
+
${chalk4.bold("Actions:")}
|
|
1697
|
+
|
|
1698
|
+
${chalk4.green("list")} List cached presets (default)
|
|
1699
|
+
${chalk4.green("clear")} Clear all cached presets
|
|
1700
|
+
${chalk4.green("path")} Show cache directory path
|
|
1701
|
+
|
|
1702
|
+
${chalk4.bold("Examples:")}
|
|
1703
|
+
|
|
1704
|
+
${chalk4.gray("# List cached presets")}
|
|
1705
|
+
${chalk4.cyan("$")} mcp-new preset-cache list
|
|
1706
|
+
|
|
1707
|
+
${chalk4.gray("# Clear preset cache")}
|
|
1708
|
+
${chalk4.cyan("$")} mcp-new preset-cache clear
|
|
1709
|
+
|
|
1710
|
+
${chalk4.bold("External Presets:")}
|
|
1711
|
+
|
|
1712
|
+
Use external presets with the --preset flag:
|
|
1713
|
+
${chalk4.cyan("$")} mcp-new my-project --preset @company/custom-preset
|
|
1714
|
+
${chalk4.cyan("$")} mcp-new my-project --preset github:user/repo
|
|
1715
|
+
`
|
|
1716
|
+
).action(presetCacheCommand);
|
|
1717
|
+
program.command("docs").description("Start an interactive documentation server").option("-p, --port <port>", "Port to run the server on", "3000").addHelpText(
|
|
1718
|
+
"after",
|
|
1719
|
+
`
|
|
1720
|
+
${chalk4.bold("Examples:")}
|
|
1721
|
+
|
|
1722
|
+
${chalk4.gray("# Start docs server on default port (3000)")}
|
|
1723
|
+
${chalk4.cyan("$")} mcp-new docs
|
|
1724
|
+
|
|
1725
|
+
${chalk4.gray("# Start docs server on custom port")}
|
|
1726
|
+
${chalk4.cyan("$")} mcp-new docs --port 4000
|
|
1727
|
+
|
|
1728
|
+
${chalk4.bold("Features:")}
|
|
1729
|
+
${chalk4.green("Hot reload")} - Changes refresh automatically
|
|
1730
|
+
${chalk4.green("Search")} - Full-text search across all docs
|
|
1731
|
+
${chalk4.green("Markdown")} - Renders GitHub-flavored markdown
|
|
1732
|
+
`
|
|
1733
|
+
).action(docsCommand);
|
|
1734
|
+
program.command("add-ci [provider]").description("Add CI/CD configuration to an existing project").addHelpText(
|
|
1735
|
+
"after",
|
|
1736
|
+
`
|
|
1737
|
+
${chalk4.bold("Examples:")}
|
|
1738
|
+
|
|
1739
|
+
${chalk4.gray("# Add CI interactively")}
|
|
1740
|
+
${chalk4.cyan("$")} mcp-new add-ci
|
|
1741
|
+
|
|
1742
|
+
${chalk4.gray("# Add GitHub Actions")}
|
|
1743
|
+
${chalk4.cyan("$")} mcp-new add-ci github
|
|
1744
|
+
|
|
1745
|
+
${chalk4.gray("# Add GitLab CI")}
|
|
1746
|
+
${chalk4.cyan("$")} mcp-new add-ci gitlab
|
|
1747
|
+
|
|
1748
|
+
${chalk4.gray("# Add CircleCI")}
|
|
1749
|
+
${chalk4.cyan("$")} mcp-new add-ci circleci
|
|
1750
|
+
|
|
1751
|
+
${chalk4.bold("Supported providers:")}
|
|
1752
|
+
${chalk4.green("github")} GitHub Actions
|
|
1753
|
+
${chalk4.green("gitlab")} GitLab CI
|
|
1754
|
+
${chalk4.green("circleci")} CircleCI
|
|
1755
|
+
`
|
|
1756
|
+
).action(addCICommand);
|
|
1757
|
+
var monorepo = program.command("monorepo").description("Manage MCP monorepo workspaces");
|
|
1758
|
+
monorepo.command("init [workspace-name]").description("Initialize a new MCP monorepo workspace").option("-f, --force", "Initialize even if directory contains files").addHelpText(
|
|
1759
|
+
"after",
|
|
1760
|
+
`
|
|
1761
|
+
${chalk4.bold("Examples:")}
|
|
1762
|
+
|
|
1763
|
+
${chalk4.gray("# Create a new monorepo workspace")}
|
|
1764
|
+
${chalk4.cyan("$")} mcp-new monorepo init my-workspace
|
|
1765
|
+
|
|
1766
|
+
${chalk4.gray("# Create in current directory name")}
|
|
1767
|
+
${chalk4.cyan("$")} mcp-new monorepo init
|
|
1768
|
+
`
|
|
1769
|
+
).action((_workspaceName, _options, command) => {
|
|
1770
|
+
const opts = command.optsWithGlobals();
|
|
1771
|
+
const workspaceName = command.args[0];
|
|
1772
|
+
monorepoInitCommand(workspaceName, opts);
|
|
1773
|
+
});
|
|
1774
|
+
monorepo.command("add [server-name]").description("Add a new MCP server to the workspace").option("-n, --name <name>", "Server name").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("-g, --go", "Use Go template").option("-r, --rust", "Use Rust template").option("-j, --java", "Use Java template").option("-k, --kotlin", "Use Kotlin template").option("-c, --csharp", "Use C# (.NET) template").option("-e, --elixir", "Use Elixir template").option("--maven", "Use Maven build tool (for Java/Kotlin)").option("--gradle", "Use Gradle build tool (for Java/Kotlin)").option("--skip-install", "Skip dependency installation").addHelpText(
|
|
1775
|
+
"after",
|
|
1776
|
+
`
|
|
1777
|
+
${chalk4.bold("Examples:")}
|
|
1778
|
+
|
|
1779
|
+
${chalk4.gray("# Add a TypeScript server")}
|
|
1780
|
+
${chalk4.cyan("$")} mcp-new monorepo add my-server -t
|
|
1781
|
+
|
|
1782
|
+
${chalk4.gray("# Add a Java server with Gradle")}
|
|
1783
|
+
${chalk4.cyan("$")} mcp-new monorepo add api-server -j --gradle
|
|
1784
|
+
`
|
|
1785
|
+
).action((_serverName, _options, command) => {
|
|
1786
|
+
const opts = command.optsWithGlobals();
|
|
1787
|
+
const serverName = command.args[0];
|
|
1788
|
+
monorepoAddCommand(serverName, opts);
|
|
1789
|
+
});
|
|
1790
|
+
monorepo.command("list").description("List all packages in the workspace").action(monorepoListCommand);
|
|
1791
|
+
async function main() {
|
|
1792
|
+
await pluginRegistry.initialize();
|
|
1793
|
+
program.parse();
|
|
1794
|
+
if (process.argv.length === 2) {
|
|
1795
|
+
program.help();
|
|
1796
|
+
}
|
|
532
1797
|
}
|
|
1798
|
+
main().catch((error) => {
|
|
1799
|
+
console.error("Error:", error.message);
|
|
1800
|
+
process.exit(1);
|
|
1801
|
+
});
|