UNPKG

@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
"use strict"; 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