@smithery/cli 0.0.3
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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/auto-update.js +33 -0
- package/dist/commands/get.js +25 -0
- package/dist/commands/install.js +70 -0
- package/dist/commands/installed.js +31 -0
- package/dist/commands/list.js +45 -0
- package/dist/commands/uninstall.js +47 -0
- package/dist/extractors/modelcontextprotocol-extractor.js +209 -0
- package/dist/helpers/index.js +94 -0
- package/dist/index.js +44 -0
- package/dist/install.js +79 -0
- package/dist/types/index.js +1 -0
- package/dist/types/package.js +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/config-manager.js +113 -0
- package/dist/utils/config.js +73 -0
- package/dist/utils/display.js +38 -0
- package/dist/utils/package-actions.js +52 -0
- package/dist/utils/package-management.js +197 -0
- package/dist/utils/package-resolver.js +143 -0
- package/dist/utils/runtime-utils.js +37 -0
- package/dist/utils/ui.js +46 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Michael Latman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# smithery/get
|
|
2
|
+
|
|
3
|
+
A lightweight registry and package manager for Model Context Protocol (MCP) servers, designed to be client-agnostic.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
This guide will help you get started with developing for @smithery/cli.
|
|
8
|
+
|
|
9
|
+
### Getting Started
|
|
10
|
+
|
|
11
|
+
#### Initial Setup
|
|
12
|
+
|
|
13
|
+
1. Clone the repository:
|
|
14
|
+
```bash
|
|
15
|
+
git clone git@github.com:smithery-ai/cli.git
|
|
16
|
+
cd smithery-get
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. Install dependencies:
|
|
20
|
+
```bash
|
|
21
|
+
npm install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
3. Build the project:
|
|
25
|
+
```bash
|
|
26
|
+
npm run build
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Testing Locally
|
|
30
|
+
|
|
31
|
+
To test the CLI tool locally:
|
|
32
|
+
```bash
|
|
33
|
+
npx .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Common Commands
|
|
37
|
+
|
|
38
|
+
Note: yet to fix 'npx . list' to list all packages
|
|
39
|
+
|
|
40
|
+
Test specific functionality:
|
|
41
|
+
```bash
|
|
42
|
+
# Get details about a specific package
|
|
43
|
+
npx . get <package-id>
|
|
44
|
+
|
|
45
|
+
# Install a package
|
|
46
|
+
npx . install <package-name>
|
|
47
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
async function getCurrentVersion() {
|
|
8
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
9
|
+
return packageJson.version;
|
|
10
|
+
}
|
|
11
|
+
async function getLatestVersion() {
|
|
12
|
+
const { stdout } = await execAsync("npm show @smithery/cli version");
|
|
13
|
+
return stdout.trim();
|
|
14
|
+
}
|
|
15
|
+
export async function updatePackage() {
|
|
16
|
+
try {
|
|
17
|
+
const currentVersion = await getCurrentVersion();
|
|
18
|
+
const latestVersion = await getLatestVersion();
|
|
19
|
+
if (currentVersion !== latestVersion) {
|
|
20
|
+
console.log(chalk.yellow(`\nA new version of @smithery/cli is available: ${latestVersion} (current: ${currentVersion})`));
|
|
21
|
+
console.log(chalk.cyan("Installing update..."));
|
|
22
|
+
// Use npx to ensure we get the latest version
|
|
23
|
+
await execAsync("npx --yes @smithery/cli@latest");
|
|
24
|
+
console.log(chalk.green("✓ Update complete\n"));
|
|
25
|
+
// Exit after update to ensure the new version is used
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// Log update check failure but continue with execution
|
|
31
|
+
console.log(chalk.yellow("\nFailed to check for updates. Continuing with current version."));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { displayPackageDetailsWithActions } from "../utils/display.js";
|
|
3
|
+
import { resolvePackage } from "../utils/package-resolver.js";
|
|
4
|
+
import { handlePackageAction } from "../utils/package-actions.js";
|
|
5
|
+
export async function get(packageId) {
|
|
6
|
+
try {
|
|
7
|
+
const pkg = await resolvePackage(packageId);
|
|
8
|
+
if (!pkg) {
|
|
9
|
+
console.log(chalk.yellow(`No package found with ID: ${packageId}`));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const action = await displayPackageDetailsWithActions(pkg);
|
|
13
|
+
await handlePackageAction(pkg, action, {});
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error(chalk.red("Error loading package:"));
|
|
17
|
+
if (error instanceof Error && error.message.includes("fetch")) {
|
|
18
|
+
console.error(chalk.red("Failed to connect to the package registry. Please check your internet connection."));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
22
|
+
}
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { installPackage as installPkg } from "../utils/package-management.js";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { resolvePackage } from "../utils/package-resolver.js";
|
|
5
|
+
async function promptForRuntime() {
|
|
6
|
+
const { runtime } = await inquirer.prompt([
|
|
7
|
+
{
|
|
8
|
+
type: "list",
|
|
9
|
+
name: "runtime",
|
|
10
|
+
message: "What runtime does this package use?",
|
|
11
|
+
choices: [
|
|
12
|
+
{ name: "Node.js", value: "node" },
|
|
13
|
+
{ name: "Python", value: "python" },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
]);
|
|
17
|
+
return runtime;
|
|
18
|
+
}
|
|
19
|
+
function createUnknownPackage(id, runtime) {
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
name: id,
|
|
23
|
+
description: "Unverified package",
|
|
24
|
+
// runtime,
|
|
25
|
+
vendor: "",
|
|
26
|
+
sourceUrl: "",
|
|
27
|
+
homepage: "",
|
|
28
|
+
license: "",
|
|
29
|
+
isInstalled: false,
|
|
30
|
+
isVerified: false,
|
|
31
|
+
connections: [
|
|
32
|
+
{
|
|
33
|
+
stdio: {
|
|
34
|
+
command: runtime === "node" ? "npx" : "python",
|
|
35
|
+
args: runtime === "node" ? ["-y", id] : ["-m", id],
|
|
36
|
+
env: {},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function installPackage(pkg) {
|
|
43
|
+
return installPkg(pkg);
|
|
44
|
+
}
|
|
45
|
+
export async function install(packageId) {
|
|
46
|
+
const pkg = await resolvePackage(packageId);
|
|
47
|
+
if (!pkg) {
|
|
48
|
+
console.warn(chalk.yellow(`Package ${packageId} not found in the curated list.`));
|
|
49
|
+
const { proceedWithInstall } = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: "confirm",
|
|
52
|
+
name: "proceedWithInstall",
|
|
53
|
+
message: `Would you like to try installing ${packageId} anyway? This package hasn't been verified.`,
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
if (proceedWithInstall) {
|
|
58
|
+
console.log(chalk.cyan(`Proceeding with installation of ${packageId}...`));
|
|
59
|
+
const runtime = await promptForRuntime();
|
|
60
|
+
const unknownPkg = createUnknownPackage(packageId, runtime);
|
|
61
|
+
await installPkg(unknownPkg);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log("Installation cancelled.");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await installPkg(pkg);
|
|
70
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { displayPackageDetailsWithActions } from "../utils/display.js";
|
|
4
|
+
import { resolvePackages } from "../utils/package-resolver.js";
|
|
5
|
+
import AutocompletePrompt from "inquirer-autocomplete-prompt";
|
|
6
|
+
import { createPackagePrompt, printPackageListHeader } from "../utils/ui.js";
|
|
7
|
+
import { handlePackageAction } from "../utils/package-actions.js";
|
|
8
|
+
inquirer.registerPrompt("autocomplete", AutocompletePrompt);
|
|
9
|
+
export async function listInstalledPackages() {
|
|
10
|
+
const allPackages = await resolvePackages();
|
|
11
|
+
const installedPackages = allPackages.filter((pkg) => pkg.isInstalled);
|
|
12
|
+
if (installedPackages.length === 0) {
|
|
13
|
+
console.log(chalk.yellow("\nNo MCP servers are currently installed."));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
printPackageListHeader(installedPackages.length, "installed");
|
|
17
|
+
const prompt = createPackagePrompt(installedPackages, {
|
|
18
|
+
message: "Search and select a package:",
|
|
19
|
+
});
|
|
20
|
+
const answer = await inquirer.prompt([
|
|
21
|
+
prompt,
|
|
22
|
+
]);
|
|
23
|
+
if (!answer.selectedPackage) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const action = await displayPackageDetailsWithActions(answer.selectedPackage);
|
|
27
|
+
await handlePackageAction(answer.selectedPackage, action, {
|
|
28
|
+
onUninstall: () => listInstalledPackages(),
|
|
29
|
+
onBack: listInstalledPackages,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import { displayPackageDetailsWithActions } from "../utils/display.js";
|
|
4
|
+
import { resolvePackages } from "../utils/package-resolver.js";
|
|
5
|
+
import AutocompletePrompt from "inquirer-autocomplete-prompt";
|
|
6
|
+
import { createPackagePrompt, printPackageListHeader } from "../utils/ui.js";
|
|
7
|
+
import { handlePackageAction } from "../utils/package-actions.js";
|
|
8
|
+
// Register the autocomplete prompt
|
|
9
|
+
inquirer.registerPrompt("autocomplete", AutocompletePrompt);
|
|
10
|
+
export async function list() {
|
|
11
|
+
try {
|
|
12
|
+
const packages = await resolvePackages();
|
|
13
|
+
// Add validation check for packages
|
|
14
|
+
if (!Array.isArray(packages)) {
|
|
15
|
+
throw new Error("Invalid package list returned");
|
|
16
|
+
}
|
|
17
|
+
// Add validation to ensure each package has the required properties
|
|
18
|
+
if (packages.some((pkg) => !pkg || typeof pkg.id !== "string")) {
|
|
19
|
+
throw new Error("Invalid package format: each package must have an id property");
|
|
20
|
+
}
|
|
21
|
+
printPackageListHeader(packages.length);
|
|
22
|
+
// Only proceed if we have packages to display
|
|
23
|
+
if (packages.length === 0) {
|
|
24
|
+
console.log(chalk.yellow("No packages found."));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const prompt = createPackagePrompt(packages, { showInstallStatus: true });
|
|
28
|
+
const answer = await inquirer.prompt([
|
|
29
|
+
prompt,
|
|
30
|
+
]);
|
|
31
|
+
// Validate the selected package
|
|
32
|
+
if (!answer?.selectedPackage) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const action = await displayPackageDetailsWithActions(answer.selectedPackage);
|
|
36
|
+
await handlePackageAction(answer.selectedPackage, action, {
|
|
37
|
+
onBack: list,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error(chalk.red("Error loading package list:"));
|
|
42
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import { resolvePackage } from "../utils/package-resolver.js";
|
|
4
|
+
import { uninstallPackage } from "../utils/package-management.js";
|
|
5
|
+
export async function uninstall(packageName) {
|
|
6
|
+
console.error("!");
|
|
7
|
+
try {
|
|
8
|
+
// If no package name provided, show error
|
|
9
|
+
if (!packageName) {
|
|
10
|
+
console.error(chalk.red("Error: Package name is required"));
|
|
11
|
+
console.log("Usage: @smithery/cli uninstall <package-name>");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
// Resolve the package
|
|
15
|
+
const pkg = await resolvePackage(packageName);
|
|
16
|
+
if (!pkg) {
|
|
17
|
+
console.log(chalk.yellow(`Package ${packageName} not found.`));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!pkg.isInstalled) {
|
|
21
|
+
console.log(chalk.yellow(`Package ${packageName} is not installed.`));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Confirm uninstallation
|
|
25
|
+
const { confirmUninstall } = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: "confirm",
|
|
28
|
+
name: "confirmUninstall",
|
|
29
|
+
message: `Are you sure you want to uninstall ${packageName}?`,
|
|
30
|
+
default: false,
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
if (!confirmUninstall) {
|
|
34
|
+
console.log("Uninstallation cancelled.");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Perform uninstallation
|
|
38
|
+
await uninstallPackage(packageName);
|
|
39
|
+
console.log(chalk.green(`\nSuccessfully uninstalled ${packageName}`));
|
|
40
|
+
console.log(chalk.yellow("\nNote: Please restart Claude for the changes to take effect."));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(chalk.red("Failed to uninstall package:"));
|
|
44
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
import * as TOML from '@iarna/toml';
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const REPOS = [
|
|
12
|
+
{
|
|
13
|
+
url: 'https://github.com/modelcontextprotocol/servers.git',
|
|
14
|
+
branch: 'main',
|
|
15
|
+
packagePath: 'src',
|
|
16
|
+
runtime: 'mixed'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
url: 'https://github.com/mcp-get/community-servers.git',
|
|
20
|
+
branch: 'main',
|
|
21
|
+
packagePath: 'src',
|
|
22
|
+
runtime: 'mixed'
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
async function cloneRepo(config, tempDir) {
|
|
26
|
+
const repoDir = path.join(tempDir, path.basename(config.url, '.git'));
|
|
27
|
+
console.log(`Cloning ${config.url} into ${repoDir}...`);
|
|
28
|
+
await execAsync(`git clone --depth 1 --branch ${config.branch} ${config.url} ${repoDir}`, { cwd: tempDir });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
async function extractNodePackage(packageJsonPath, repoUrl, subPath) {
|
|
32
|
+
try {
|
|
33
|
+
const packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
34
|
+
const repoPath = `${repoUrl}/blob/main/${subPath}`;
|
|
35
|
+
return {
|
|
36
|
+
name: packageData.name || '',
|
|
37
|
+
description: packageData.description || '',
|
|
38
|
+
vendor: packageData.author || '',
|
|
39
|
+
sourceUrl: repoPath,
|
|
40
|
+
homepage: packageData.homepage || '',
|
|
41
|
+
license: packageData.license || '',
|
|
42
|
+
runtime: 'node'
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error(`Error extracting Node package from ${packageJsonPath}:`, error);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function extractPythonPackage(pyprojectPath, repoUrl, subPath) {
|
|
51
|
+
try {
|
|
52
|
+
const pyprojectContent = fs.readFileSync(pyprojectPath, 'utf8');
|
|
53
|
+
const pyproject = TOML.parse(pyprojectContent);
|
|
54
|
+
if (!pyproject.project) {
|
|
55
|
+
console.error(`No [project] section found in ${pyprojectPath}`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const { project } = pyproject;
|
|
59
|
+
// Set vendor to Anthropic
|
|
60
|
+
const vendor = 'Anthropic, PBC (https://anthropic.com)';
|
|
61
|
+
// Set license to MIT
|
|
62
|
+
const license = 'MIT';
|
|
63
|
+
return {
|
|
64
|
+
name: project.name || '',
|
|
65
|
+
description: project.description || '',
|
|
66
|
+
vendor,
|
|
67
|
+
sourceUrl: `${repoUrl}/blob/main/${subPath}`,
|
|
68
|
+
homepage: project.homepage || project.repository || repoUrl,
|
|
69
|
+
license,
|
|
70
|
+
runtime: 'python'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error(`Error extracting Python package from ${pyprojectPath}:`, error);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function extractPackageInfo() {
|
|
79
|
+
const tempDir = path.join(__dirname, '../../temp');
|
|
80
|
+
const outputPath = path.join(__dirname, '../../packages/package-list.json');
|
|
81
|
+
const commitMsgPath = path.join(__dirname, '../../temp/commit-msg.txt');
|
|
82
|
+
console.log("Starting package extraction...");
|
|
83
|
+
try {
|
|
84
|
+
// Load existing packages
|
|
85
|
+
let existingPackages = [];
|
|
86
|
+
if (fs.existsSync(outputPath)) {
|
|
87
|
+
existingPackages = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
|
|
88
|
+
}
|
|
89
|
+
// Create temp directory if it doesn't exist
|
|
90
|
+
if (!fs.existsSync(tempDir)) {
|
|
91
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
// Initialize commit message
|
|
94
|
+
let commitMsg = "chore(packages): update MCP package list\n\nChanges:\n";
|
|
95
|
+
let changes = [];
|
|
96
|
+
// Process each repository
|
|
97
|
+
const newPackages = [];
|
|
98
|
+
for (const repo of REPOS) {
|
|
99
|
+
await cloneRepo(repo, tempDir);
|
|
100
|
+
const repoDir = path.join(tempDir, path.basename(repo.url, '.git'));
|
|
101
|
+
const packagePath = path.join(repoDir, repo.packagePath);
|
|
102
|
+
if (!fs.existsSync(packagePath))
|
|
103
|
+
continue;
|
|
104
|
+
const dirs = fs.readdirSync(packagePath);
|
|
105
|
+
for (const dir of dirs) {
|
|
106
|
+
const fullPath = path.join(packagePath, dir);
|
|
107
|
+
if (!fs.statSync(fullPath).isDirectory())
|
|
108
|
+
continue;
|
|
109
|
+
// Try to extract as Node.js package
|
|
110
|
+
const packageJsonPath = path.join(fullPath, 'package.json');
|
|
111
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
112
|
+
const nodePackage = await extractNodePackage(packageJsonPath, repo.url.replace('.git', ''), path.join(repo.packagePath, dir));
|
|
113
|
+
if (nodePackage) {
|
|
114
|
+
newPackages.push(nodePackage);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Try to extract as Python package
|
|
119
|
+
const pyprojectPath = path.join(fullPath, 'pyproject.toml');
|
|
120
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
121
|
+
const pythonPackage = await extractPythonPackage(pyprojectPath, repo.url.replace('.git', ''), path.join(repo.packagePath, dir));
|
|
122
|
+
if (pythonPackage) {
|
|
123
|
+
newPackages.push(pythonPackage);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Merge existing and new packages
|
|
129
|
+
const mergedPackages = [...existingPackages];
|
|
130
|
+
let hasChanges = false;
|
|
131
|
+
for (const newPkg of newPackages) {
|
|
132
|
+
const existingIndex = mergedPackages.findIndex(pkg => pkg.name === newPkg.name);
|
|
133
|
+
if (existingIndex >= 0) {
|
|
134
|
+
// Update existing package if there are changes
|
|
135
|
+
if (JSON.stringify(mergedPackages[existingIndex]) !== JSON.stringify(newPkg)) {
|
|
136
|
+
const oldPkg = mergedPackages[existingIndex];
|
|
137
|
+
mergedPackages[existingIndex] = newPkg;
|
|
138
|
+
hasChanges = true;
|
|
139
|
+
console.log(`Updated package: ${newPkg.name}`);
|
|
140
|
+
// Add detailed change information
|
|
141
|
+
const changeDetails = [];
|
|
142
|
+
if (oldPkg.description !== newPkg.description)
|
|
143
|
+
changeDetails.push('description');
|
|
144
|
+
if (oldPkg.vendor !== newPkg.vendor)
|
|
145
|
+
changeDetails.push('vendor');
|
|
146
|
+
if (oldPkg.license !== newPkg.license)
|
|
147
|
+
changeDetails.push('license');
|
|
148
|
+
if (oldPkg.homepage !== newPkg.homepage)
|
|
149
|
+
changeDetails.push('homepage');
|
|
150
|
+
if (oldPkg.sourceUrl !== newPkg.sourceUrl)
|
|
151
|
+
changeDetails.push('sourceUrl');
|
|
152
|
+
if (oldPkg.runtime !== newPkg.runtime)
|
|
153
|
+
changeDetails.push('runtime');
|
|
154
|
+
changes.push(`- Updated ${newPkg.name} (changed: ${changeDetails.join(', ')})`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Add new package
|
|
159
|
+
mergedPackages.push(newPkg);
|
|
160
|
+
hasChanges = true;
|
|
161
|
+
console.log(`Added new package: ${newPkg.name} (${newPkg.runtime})`);
|
|
162
|
+
changes.push(`- Added new package: ${newPkg.name} (${newPkg.runtime})`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Write updated packages to file if there are changes
|
|
166
|
+
if (hasChanges) {
|
|
167
|
+
fs.writeFileSync(outputPath, JSON.stringify(mergedPackages, null, 2));
|
|
168
|
+
console.log('Package list updated successfully');
|
|
169
|
+
// Write commit message with total number of changes
|
|
170
|
+
if (changes.length === 0) {
|
|
171
|
+
changes.push('- Initial package list creation');
|
|
172
|
+
}
|
|
173
|
+
commitMsg = `chore(packages): update MCP package list (${changes.length} packages)\n\nChanges:\n`;
|
|
174
|
+
commitMsg += changes.join('\n');
|
|
175
|
+
fs.writeFileSync(commitMsgPath, commitMsg);
|
|
176
|
+
console.log('Commit message generated');
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.log('No changes detected in package list');
|
|
180
|
+
}
|
|
181
|
+
// Cleanup (but keep commit-msg.txt if it exists)
|
|
182
|
+
const filesToKeep = new Set(['commit-msg.txt']);
|
|
183
|
+
for (const file of fs.readdirSync(tempDir)) {
|
|
184
|
+
if (!filesToKeep.has(file)) {
|
|
185
|
+
const filePath = path.join(tempDir, file);
|
|
186
|
+
fs.rmSync(filePath, { recursive: true, force: true });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
console.log('Temporary files cleaned up');
|
|
190
|
+
return mergedPackages;
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
console.error('Error:', error);
|
|
194
|
+
// Cleanup on error
|
|
195
|
+
if (fs.existsSync(tempDir)) {
|
|
196
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Run the function if this file is executed directly
|
|
202
|
+
if (import.meta.url === `file://${__filename}`) {
|
|
203
|
+
extractPackageInfo()
|
|
204
|
+
.then(() => console.log('Package extraction completed'))
|
|
205
|
+
.catch(error => {
|
|
206
|
+
console.error('Failed to extract packages:', error);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export const packageHelpers = {
|
|
2
|
+
"@modelcontextprotocol/server-brave-search": {
|
|
3
|
+
requiredEnvVars: {
|
|
4
|
+
BRAVE_API_KEY: {
|
|
5
|
+
description: "API key for Brave Search",
|
|
6
|
+
required: true,
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
"@modelcontextprotocol/server-github": {
|
|
11
|
+
requiredEnvVars: {
|
|
12
|
+
GITHUB_PERSONAL_ACCESS_TOKEN: {
|
|
13
|
+
description: "Personal access token for GitHub API access",
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
"@modelcontextprotocol/server-gitlab": {
|
|
19
|
+
requiredEnvVars: {
|
|
20
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: {
|
|
21
|
+
description: "Personal access token for GitLab API access",
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
GITLAB_API_URL: {
|
|
25
|
+
description: "GitLab API URL (optional, for self-hosted instances)",
|
|
26
|
+
required: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
"@modelcontextprotocol/server-google-maps": {
|
|
31
|
+
requiredEnvVars: {
|
|
32
|
+
GOOGLE_MAPS_API_KEY: {
|
|
33
|
+
description: "API key for Google Maps services",
|
|
34
|
+
required: true,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
"@modelcontextprotocol/server-slack": {
|
|
39
|
+
requiredEnvVars: {
|
|
40
|
+
SLACK_BOT_TOKEN: {
|
|
41
|
+
description: "Slack Bot User OAuth Token (starts with xoxb-)",
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
SLACK_TEAM_ID: {
|
|
45
|
+
description: "Slack Team/Workspace ID",
|
|
46
|
+
required: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
"@raygun.io/mcp-server-raygun": {
|
|
51
|
+
requiredEnvVars: {
|
|
52
|
+
RAYGUN_PAT_TOKEN: {
|
|
53
|
+
description: "Personal access token for Raygun API access",
|
|
54
|
+
required: true,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
"@kagi/mcp-server-kagi": {
|
|
59
|
+
requiredEnvVars: {
|
|
60
|
+
KAGI_API_KEY: {
|
|
61
|
+
description: "API key for Kagi Search",
|
|
62
|
+
required: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
"@exa/mcp-server": {
|
|
67
|
+
requiredEnvVars: {
|
|
68
|
+
EXA_API_KEY: {
|
|
69
|
+
description: "API key for Exa AI Search",
|
|
70
|
+
required: true,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
"@search1api/mcp-server": {
|
|
75
|
+
requiredEnvVars: {
|
|
76
|
+
SEARCH1API_KEY: {
|
|
77
|
+
description: "API key for Search1API",
|
|
78
|
+
required: true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
"mcp-tinybird": {
|
|
83
|
+
requiredEnvVars: {
|
|
84
|
+
TB_API_URL: {
|
|
85
|
+
description: "API URL for Tinybird",
|
|
86
|
+
required: true,
|
|
87
|
+
},
|
|
88
|
+
TB_ADMIN_TOKEN: {
|
|
89
|
+
description: "Admin token for Tinybird",
|
|
90
|
+
required: true,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { list } from "./commands/list.js";
|
|
3
|
+
import { install } from "./commands/install.js";
|
|
4
|
+
import { uninstall } from "./commands/uninstall.js";
|
|
5
|
+
import { listInstalledPackages } from "./commands/installed.js";
|
|
6
|
+
import { get } from "./commands/get.js";
|
|
7
|
+
const command = process.argv[2];
|
|
8
|
+
const packageName = process.argv[3];
|
|
9
|
+
async function main() {
|
|
10
|
+
switch (command) {
|
|
11
|
+
case "list":
|
|
12
|
+
await list();
|
|
13
|
+
break;
|
|
14
|
+
case "install":
|
|
15
|
+
if (!packageName) {
|
|
16
|
+
console.error("Please provide a package name to install");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
await install(packageName);
|
|
20
|
+
break;
|
|
21
|
+
case "uninstall":
|
|
22
|
+
await uninstall(packageName);
|
|
23
|
+
break;
|
|
24
|
+
case "installed":
|
|
25
|
+
await listInstalledPackages();
|
|
26
|
+
break;
|
|
27
|
+
case "get":
|
|
28
|
+
if (!packageName) {
|
|
29
|
+
console.error("Please provide a package ID to get details");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
await get(packageName);
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
console.log("Available commands:");
|
|
36
|
+
console.log(" list List all available packages");
|
|
37
|
+
console.log(" install <package> Install a package");
|
|
38
|
+
console.log(" uninstall [package] Uninstall a package");
|
|
39
|
+
console.log(" installed List installed packages");
|
|
40
|
+
console.log(" get <package> Get details for a specific package");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
main();
|