@aurracloud/mcp-cli
Version:
A command-line tool to install, manage, and setup MCP (Model Context Protocol) servers with Docker support
380 lines • 15 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.publishCommand = publishCommand;
const node_fetch_1 = __importDefault(require("node-fetch"));
const logger_1 = require("../utils/logger");
const auth_1 = require("../utils/auth");
const parser_1 = require("../lib/parser");
const python_1 = require("../python");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/**
* Detects project type and reads configuration
*/
function detectAndReadProject(directory) {
const packageJsonPath = path.join(directory, 'package.json');
const pyprojectPath = path.join(directory, 'pyproject.toml');
// Try Node.js project first
if (fs.existsSync(packageJsonPath)) {
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (packageJson.mcpServer) {
const { config } = (0, parser_1.parseMcpServerConfig)(packageJson);
if (config) {
return {
type: 'node',
config,
projectData: packageJson
};
}
}
}
catch (error) {
logger_1.logger.debug(`Failed to parse package.json: ${error}`);
}
}
// Try Python project
if (fs.existsSync(pyprojectPath)) {
try {
const pyprojectData = (0, python_1.readPyProjectToml)(pyprojectPath);
if (pyprojectData?.tool?.['mcp-server'] && pyprojectData.project) {
// Convert pyproject.toml to PackageJsonWithMcp format for parser
const packageJsonFormat = {
name: pyprojectData.project.name,
version: pyprojectData.project.version,
description: pyprojectData.project.description,
author: pyprojectData.project.authors?.[0]?.name,
license: typeof pyprojectData.project.license === 'object' && 'text' in pyprojectData.project.license
? pyprojectData.project.license.text
: undefined,
repository: pyprojectData.project.urls?.repository,
homepage: pyprojectData.project.urls?.homepage,
keywords: pyprojectData.project.keywords,
mcpServer: pyprojectData.tool['mcp-server']
};
const { config } = (0, parser_1.parseMcpServerConfig)(packageJsonFormat);
if (config) {
return {
type: 'python',
config,
projectData: pyprojectData
};
}
}
}
catch (error) {
logger_1.logger.debug(`Failed to parse pyproject.toml: ${error}`);
}
}
return null;
}
/**
* Converts MCP config to registry item format
*/
function convertToRegistryItem(projectInfo, options) {
const { config, type, projectData } = projectInfo;
// Generate slug from name if not provided
const slug = options.slug || config.name.toLowerCase()
.replace(/[@\/]/g, '-')
.replace(/[^a-z0-9-]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
// Determine category
let category = 'tools';
if (options.category) {
category = options.category;
}
else if (config.metadata.category) {
category = config.metadata.category;
}
else {
// Infer from keywords or name
const allKeywords = [...(config.keywords || []), ...(config.metadata.keywords || [])];
const keywordString = allKeywords.join(' ').toLowerCase();
if (keywordString.includes('agent') || keywordString.includes('ai-agent')) {
category = 'agents';
}
else if (keywordString.includes('prompt') || keywordString.includes('template')) {
category = 'prompts';
}
else if (keywordString.includes('data') || keywordString.includes('feed') || keywordString.includes('api')) {
category = 'data-feeds';
}
}
// Determine type based on project type and connection
let itemType = type === 'python' ? 'python' : 'npm';
// Check if it's a GitHub-only project (no package published)
if (config.repository && !hasPublishedPackage(projectInfo)) {
itemType = 'github';
}
// Generate source URL
let sourceUrl = '';
if (itemType === 'npm') {
sourceUrl = `https://www.npmjs.com/package/${config.name}`;
}
else if (itemType === 'python') {
sourceUrl = `https://pypi.org/project/${config.name}/`;
}
else if (itemType === 'github' && config.repository) {
sourceUrl = config.repository;
}
// Generate tags from keywords and metadata
const tags = Array.from(new Set([
...(config.keywords || []),
...(config.metadata.keywords || []),
'mcp',
type === 'python' ? 'python' : 'nodejs'
])).slice(0, 10); // Limit to 10 tags
// Read README if available
let readme = '';
const readmePaths = ['README.md', 'README.rst', 'README.txt', 'readme.md'];
for (const readmePath of readmePaths) {
const fullPath = path.join(process.cwd(), readmePath);
if (fs.existsSync(fullPath)) {
try {
readme = fs.readFileSync(fullPath, 'utf8');
break;
}
catch (error) {
// Continue to next README file
}
}
}
return {
slug,
name: config.name,
description: config.description,
category,
type: itemType,
source_url: sourceUrl,
repository_url: config.repository,
package_name: config.name,
version: config.version,
author: config.author,
license: config.license,
tags,
metadata: {
description: config.description,
readme: readme || undefined,
examples: generateExamplesFromCapabilities(config)
}
};
}
/**
* Checks if the project has a published package
*/
function hasPublishedPackage(projectInfo) {
if (projectInfo.type === 'node') {
const packageJson = projectInfo.projectData;
// Check if it looks like it's published to npm (has proper name, not private)
return !!(packageJson.name && !packageJson.private);
}
else {
const pyproject = projectInfo.projectData;
// Python projects with proper project section are likely published
return !!(pyproject.project?.name && pyproject.project?.version);
}
}
/**
* Generates example usage based on MCP capabilities
*/
function generateExamplesFromCapabilities(config) {
const examples = [];
if (config.capabilities.tools) {
examples.push({
role: "user",
content: `What tools are available in the ${config.name} MCP server?`
});
}
if (config.capabilities.resources) {
examples.push({
role: "user",
content: `Can you list the resources provided by ${config.name}?`
});
}
if (config.capabilities.prompts) {
examples.push({
role: "user",
content: `Show me the available prompts from ${config.name}`
});
}
// Add a generic example if no specific capabilities
if (examples.length === 0) {
examples.push({
role: "user",
content: `How do I use the ${config.name} MCP server?`
});
}
return examples;
}
/**
* Publish MCP server to registry
*/
async function publishToRegistry(registryItem, registry) {
try {
const headers = (0, auth_1.getAuthHeaders)(registry);
logger_1.logger.verbose('Sending publish request to registry...');
logger_1.logger.debug(`Registry: ${registry}/api/registry`);
const response = await (0, node_fetch_1.default)(`${registry}/api/registry`, {
method: 'POST',
headers,
body: JSON.stringify(registryItem),
});
const result = await response.json();
if (!response.ok) {
return {
success: false,
error: result.error || `HTTP ${response.status}`,
details: result.details
};
}
return result;
}
catch (error) {
if (error.message.includes('Not authenticated')) {
return {
success: false,
error: 'Authentication required',
details: 'Please run "mcp login" first to authenticate with the registry.'
};
}
return {
success: false,
error: 'Network error',
details: error.message
};
}
}
/**
* Command to publish an MCP server to the registry
*/
async function publishCommand(options) {
const registry = options.registry || 'https://aurracloud.com';
const directory = options.directory || process.cwd();
// Check authentication
if (!(0, auth_1.isAuthenticated)(registry)) {
logger_1.logger.error('You must be logged in to publish MCP servers.');
logger_1.logger.info(`Run "mcp login" to authenticate with ${registry}`);
process.exit(1);
}
logger_1.logger.info('Publishing MCP server to registry...');
logger_1.logger.info(`Registry: ${registry}`);
logger_1.logger.verbose(`Directory: ${directory}`);
try {
// Detect and read project configuration
logger_1.logger.verbose('Detecting project type and reading MCP configuration...');
const projectInfo = detectAndReadProject(directory);
if (!projectInfo) {
logger_1.logger.error('No MCP server configuration found.');
logger_1.logger.info('');
logger_1.logger.info('Make sure you have one of the following:');
logger_1.logger.info(' • package.json with "mcpServer" configuration (Node.js)');
logger_1.logger.info(' • pyproject.toml with [tool.mcp-server] configuration (Python)');
logger_1.logger.info('');
logger_1.logger.info('For examples, see:');
logger_1.logger.info(' • https://github.com/modelcontextprotocol/servers');
logger_1.logger.info(' • MCP documentation at https://modelcontextprotocol.io');
process.exit(1);
}
logger_1.logger.verbose(`Detected ${projectInfo.type} project: ${projectInfo.config.name}`);
logger_1.logger.verbose(`Version: ${projectInfo.config.version}`);
logger_1.logger.verbose(`Description: ${projectInfo.config.description}`);
// Convert to registry item format
logger_1.logger.verbose('Converting MCP configuration to registry format...');
const registryItem = convertToRegistryItem(projectInfo, {
slug: options.slug,
category: options.category
});
logger_1.logger.verbose(`Generated slug: ${registryItem.slug}`);
logger_1.logger.verbose(`Category: ${registryItem.category}`);
logger_1.logger.verbose(`Type: ${registryItem.type}`);
logger_1.logger.verbose(`Source URL: ${registryItem.source_url}`);
// Publish to registry
logger_1.logger.info('Submitting to registry...');
const result = await publishToRegistry(registryItem, registry);
if (options.json) {
console.log(JSON.stringify(result, null, 2));
return;
}
if (result.success) {
logger_1.logger.success('✅ MCP server published successfully!');
logger_1.logger.info(`Name: ${registryItem.name}`);
logger_1.logger.info(`Slug: ${registryItem.slug}`);
logger_1.logger.info(`Category: ${registryItem.category}`);
logger_1.logger.info(`Type: ${registryItem.type}`);
logger_1.logger.info('Your submission will be reviewed and verified by administrators.');
if (result.data?.author) {
logger_1.logger.info(`Author: ${result.data.author}`);
}
}
else {
logger_1.logger.error('Failed to publish MCP server');
logger_1.logger.error(`Error: ${result.error}`);
if (result.details) {
if (Array.isArray(result.details)) {
// Validation errors
logger_1.logger.info('Validation errors:');
result.details.forEach((error) => {
logger_1.logger.info(` - ${error.path?.join('.')}: ${error.message}`);
});
}
else if (typeof result.details === 'string') {
logger_1.logger.info(`Details: ${result.details}`);
}
}
process.exit(1);
}
}
catch (error) {
if (options.json) {
console.log(JSON.stringify({
success: false,
error: error.message
}, null, 2));
return;
}
logger_1.logger.error('Failed to publish MCP server');
logger_1.logger.error(error.message);
if (error.message.includes('No MCP server configuration found')) {
logger_1.logger.info('Make sure your project has the proper MCP server configuration.');
}
process.exit(1);
}
}
//# sourceMappingURL=publish.js.map