UNPKG

jaegis-pypi-mcp-server

Version:

PyPI Package Management MCP Server for JAEGIS-Web-OS project with 8 specialized tools

646 lines (637 loc) • 26.7 kB
#!/usr/bin/env node "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 }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const dotenv = __importStar(require("dotenv")); const node_fetch_1 = __importDefault(require("node-fetch")); // Load environment variables dotenv.config(); // Command line argument parsing const args = process.argv.slice(2); const hasListTools = args.includes('--list-tools'); const hasToolsJson = args.includes('--tools-json'); const hasHelp = args.includes('--help') || args.includes('-h'); const formatTable = args.includes('--format=table'); // Security: Credential sanitization function sanitizeCredential(value) { if (!value || value.length < 8) return '****'; return `${value.substring(0, 4)}****...****${value.substring(value.length - 4)}`; } function sanitizeEnvVars(env) { const sensitiveKeys = ['TOKEN', 'KEY', 'SECRET', 'PASSWORD', 'API_']; const sanitized = { ...env }; Object.keys(sanitized).forEach(key => { if (sensitiveKeys.some(sensitive => key.toUpperCase().includes(sensitive))) { sanitized[key] = sanitizeCredential(sanitized[key]); } }); return sanitized; } function showHelp() { console.log(` PyPI JAEGIS MCP Server v1.0.0 - PyPI package management with 8 specialized tools USAGE: pypi-mcp-server [OPTIONS] OPTIONS: --list-tools List all available tools with descriptions --tools-json Output complete MCP tools schema as JSON --format=table Use table format for tool listings (default: JSON) --help, -h Show this help message ENVIRONMENT VARIABLES: PYPI_TOKEN PyPI API token (required) Format: pypi-* Generate at: https://pypi.org/manage/account/token/ PYPI_USERNAME PyPI username (required, use __token__ for token auth) PYPI_INDEX_URL PyPI index URL (optional, default: https://upload.pypi.org/legacy/) DEFAULT_PROJECT Default project name (optional, default: JAEGIS-Web-OS) EXAMPLES: # List all tools in table format pypi-mcp-server --list-tools --format=table # Get tools schema for MCP client integration pypi-mcp-server --tools-json # Start the MCP server (default) pypi-mcp-server QUICK SETUP: 1. Generate PyPI token: https://pypi.org/manage/account/token/ 2. Set environment variables: export PYPI_TOKEN=pypi-your_token_here export PYPI_USERNAME=__token__ 3. Run: pypi-mcp-server For detailed documentation, see: README.md `); } function getToolsInfo() { return [ { name: 'pypi_package_info', category: 'Package Information', description: 'Get detailed information about a PyPI package including metadata and dependencies', requiredParams: ['package_name'], optionalParams: ['version'], inputSchema: { type: 'object', properties: { package_name: { type: 'string' } } } }, { name: 'pypi_search_packages', category: 'Package Discovery', description: 'Search for PyPI packages using keywords and filters', requiredParams: ['query'], optionalParams: ['limit'], inputSchema: { type: 'object', properties: { query: { type: 'string' } } } }, { name: 'pypi_upload_package', category: 'Package Publishing', description: 'Upload a package to PyPI registry with specified configuration', requiredParams: ['package_path'], optionalParams: ['repository'], inputSchema: { type: 'object', properties: { package_path: { type: 'string' } } } }, { name: 'pypi_list_versions', category: 'Package Information', description: 'List all available versions of a package with release information', requiredParams: ['package_name'], optionalParams: [], inputSchema: { type: 'object', properties: { package_name: { type: 'string' } } } }, { name: 'pypi_download_stats', category: 'Package Analytics', description: 'Get download statistics and analytics for a package', requiredParams: ['package_name'], optionalParams: ['period'], inputSchema: { type: 'object', properties: { package_name: { type: 'string' } } } }, { name: 'pypi_validate_package', category: 'Package Validation', description: 'Validate package metadata and check for publishing readiness', requiredParams: ['package_path'], optionalParams: [], inputSchema: { type: 'object', properties: { package_path: { type: 'string' } } } }, { name: 'pypi_build_package', category: 'Package Building', description: 'Build distribution files for a Python package (sdist, wheel)', requiredParams: ['package_path'], optionalParams: ['build_type'], inputSchema: { type: 'object', properties: { package_path: { type: 'string' } } } }, { name: 'pypi_check_name_availability', category: 'Package Discovery', description: 'Check if a package name is available on PyPI registry', requiredParams: ['package_name'], optionalParams: [], inputSchema: { type: 'object', properties: { package_name: { type: 'string' } } } } ]; } function displayToolsTable(tools) { console.log('\nšŸ PyPI JAEGIS MCP Server - Tool Inventory\n'); console.log('ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”'); console.log('│ Tool Name │ Category │ Required │ Optional │'); console.log('ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤'); tools.forEach(tool => { const name = tool.name.padEnd(35); const category = tool.category.padEnd(32); const required = tool.requiredParams.length.toString().padEnd(8); const optional = tool.optionalParams.length.toString().padEnd(8); console.log(`│ ${name} │ ${category} │ ${required} │ ${optional} │`); }); console.log('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜'); // Summary by category const categories = tools.reduce((acc, tool) => { acc[tool.category] = (acc[tool.category] || 0) + 1; return acc; }, {}); console.log('\nšŸ“‹ Tool Summary by Category:'); Object.entries(categories).forEach(([category, count]) => { console.log(` • ${category}: ${count} tools`); }); console.log(`\nšŸŽÆ Total: ${tools.length} tools available\n`); } function displayToolsJson(tools, includeSchema = false) { const output = { server: 'pypi-jaegis-mcp-server', version: '1.0.0', totalTools: tools.length, categories: tools.reduce((acc, tool) => { acc[tool.category] = (acc[tool.category] || 0) + 1; return acc; }, {}), tools: tools.map(tool => ({ name: tool.name, category: tool.category, description: tool.description, parameters: { required: tool.requiredParams, optional: tool.optionalParams, total: tool.requiredParams.length + tool.optionalParams.length }, ...(includeSchema && { inputSchema: tool.inputSchema }) })) }; console.log(JSON.stringify(output, null, 2)); } // Handle command line arguments before server initialization if (hasHelp) { showHelp(); process.exit(0); } if (hasListTools || hasToolsJson) { const tools = getToolsInfo(); if (hasToolsJson) { displayToolsJson(tools, true); } else if (formatTable) { displayToolsTable(tools); } else { displayToolsJson(tools, false); } process.exit(0); } class PyPIJAEGISMCPServer { server; config; constructor() { this.config = this.loadConfig(); this.validateConfiguration(); this.server = new index_js_1.Server({ name: 'pypi-jaegis-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, }); this.setupToolHandlers(); this.setupErrorHandling(); } loadConfig() { const token = process.env.PYPI_TOKEN; const username = process.env.PYPI_USERNAME || '__token__'; if (!token) { throw new Error(`PyPI token is required. Please set PYPI_TOKEN environment variable. Generate a token at: https://pypi.org/manage/account/token/`); } return { token, username, indexUrl: process.env.PYPI_INDEX_URL || 'https://upload.pypi.org/legacy/', repositoryUrl: process.env.PYPI_REPOSITORY_URL || 'https://pypi.org/simple/', defaultProject: process.env.DEFAULT_PROJECT || 'JAEGIS-Web-OS', testMode: process.env.NODE_ENV === 'test' }; } validateConfiguration() { if (!this.config.token.startsWith('pypi-')) { console.warn('Warning: PyPI token format may be invalid. Expected format: pypi-*'); } if (this.config.username !== '__token__') { console.warn('Warning: For token authentication, username should be "__token__"'); } console.log(`PyPI MCP Server configured for project: ${this.config.defaultProject}`); console.log(`Index URL: ${this.config.indexUrl}`); console.log(`Repository URL: ${this.config.repositoryUrl}`); } setupToolHandlers() { this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { return { tools: [ { name: 'pypi_package_info', description: 'Get detailed information about a PyPI package', inputSchema: { type: 'object', properties: { package_name: { type: 'string', description: 'Name of the PyPI package' }, version: { type: 'string', description: 'Specific version (optional, defaults to latest)' } }, required: ['package_name'] } }, { name: 'pypi_search_packages', description: 'Search for PyPI packages', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', description: 'Maximum number of results (default: 20)' } }, required: ['query'] } }, { name: 'pypi_upload_package', description: 'Upload a package to PyPI', inputSchema: { type: 'object', properties: { package_path: { type: 'string', description: 'Path to the distribution file (.tar.gz or .whl)' }, repository: { type: 'string', enum: ['pypi', 'testpypi'], description: 'Target repository (default: pypi)' } }, required: ['package_path'] } }, { name: 'pypi_list_versions', description: 'List all versions of a package', inputSchema: { type: 'object', properties: { package_name: { type: 'string', description: 'Name of the package' } }, required: ['package_name'] } }, { name: 'pypi_download_stats', description: 'Get download statistics for a package', inputSchema: { type: 'object', properties: { package_name: { type: 'string', description: 'Name of the package' }, period: { type: 'string', enum: ['recent', 'last-day', 'last-week', 'last-month'], description: 'Time period for stats (default: last-month)' } }, required: ['package_name'] } }, { name: 'pypi_validate_package', description: 'Validate package metadata and check for publishing readiness', inputSchema: { type: 'object', properties: { package_path: { type: 'string', description: 'Path to package directory containing setup.py or pyproject.toml' } }, required: ['package_path'] } }, { name: 'pypi_build_package', description: 'Build distribution files for a Python package', inputSchema: { type: 'object', properties: { package_path: { type: 'string', description: 'Path to package directory' }, build_type: { type: 'string', enum: ['sdist', 'wheel', 'both'], description: 'Type of distribution to build (default: both)' } }, required: ['package_path'] } }, { name: 'pypi_check_name_availability', description: 'Check if a package name is available on PyPI', inputSchema: { type: 'object', properties: { package_name: { type: 'string', description: 'Name to check for availability' } }, required: ['package_name'] } } ] }; }); this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'pypi_package_info': return await this.getPackageInfo(args); case 'pypi_search_packages': return await this.searchPackages(args); case 'pypi_upload_package': return await this.uploadPackage(args); case 'pypi_list_versions': return await this.listVersions(args); case 'pypi_download_stats': return await this.getDownloadStats(args); case 'pypi_validate_package': return await this.validatePackage(args); case 'pypi_build_package': return await this.buildPackage(args); case 'pypi_check_name_availability': return await this.checkNameAvailability(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: error instanceof Error ? error.message : String(error), success: false }, null, 2) } ] }; } }); } setupErrorHandling() { this.server.onerror = (error) => { console.error('[PyPI MCP Server Error]', error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } // Tool implementations will be added in the next part async getPackageInfo(args) { const { package_name, version } = args; try { const url = version ? `https://pypi.org/pypi/${package_name}/${version}/json` : `https://pypi.org/pypi/${package_name}/json`; const response = await (0, node_fetch_1.default)(url); if (!response.ok) { throw new Error(`Package not found: ${package_name}`); } const data = await response.json(); const packageInfo = { name: data.info.name, version: data.info.version, summary: data.info.summary, description: data.info.description, author: data.info.author, authorEmail: data.info.author_email, license: data.info.license, homepage: data.info.home_page, projectUrls: data.info.project_urls, classifiers: data.info.classifiers, keywords: data.info.keywords?.split(',').map((k) => k.trim()), requiresPython: data.info.requires_python, uploadTime: data.urls?.[0]?.upload_time }; return { content: [ { type: 'text', text: JSON.stringify({ operation: 'pypi_package_info', package: packageInfo, success: true }, null, 2) } ] }; } catch (error) { throw new Error(`Failed to get package info: ${error instanceof Error ? error.message : String(error)}`); } } async searchPackages(args) { // Implementation placeholder return { content: [ { type: 'text', text: 'Search packages functionality will be implemented' } ] }; } async uploadPackage(args) { // Implementation placeholder return { content: [ { type: 'text', text: 'Upload package functionality will be implemented' } ] }; } async listVersions(args) { // Implementation placeholder return { content: [ { type: 'text', text: 'List versions functionality will be implemented' } ] }; } async getDownloadStats(args) { // Implementation placeholder return { content: [ { type: 'text', text: 'Download stats functionality will be implemented' } ] }; } async validatePackage(args) { // Implementation placeholder return { content: [ { type: 'text', text: 'Validate package functionality will be implemented' } ] }; } async buildPackage(args) { // Implementation placeholder return { content: [ { type: 'text', text: 'Build package functionality will be implemented' } ] }; } async checkNameAvailability(args) { const { package_name } = args; try { const response = await (0, node_fetch_1.default)(`https://pypi.org/pypi/${package_name}/json`); const available = !response.ok; return { content: [ { type: 'text', text: JSON.stringify({ operation: 'pypi_check_name_availability', package_name, available, message: available ? `Package name "${package_name}" is available` : `Package name "${package_name}" is already taken`, success: true }, null, 2) } ] }; } catch (error) { throw new Error(`Failed to check name availability: ${error instanceof Error ? error.message : String(error)}`); } } async run() { const transport = new stdio_js_1.StdioServerTransport(); await this.server.connect(transport); // Display startup summary const tools = getToolsInfo(); const categories = tools.reduce((acc, tool) => { acc[tool.category] = (acc[tool.category] || 0) + 1; return acc; }, {}); console.error('šŸ PyPI JAEGIS MCP Server (v1.0.0) running on stdio'); console.error(`šŸ“Š Total Tools: ${tools.length} | Categories: ${Object.keys(categories).length}`); console.error(`šŸ”§ Configured for project: ${this.config.defaultProject}`); console.error(`🌐 Index URL: ${this.config.indexUrl}`); console.error('āœ… Server ready with PyPI package management tools'); console.error('šŸ’” Use --list-tools to see all available tools'); } } // Run the server if (require.main === module) { try { const server = new PyPIJAEGISMCPServer(); server.run().catch((error) => { console.error('Failed to start PyPI JAEGIS MCP Server:', error.message); process.exit(1); }); } catch (error) { console.error('Failed to initialize PyPI JAEGIS MCP Server:'); console.error(error instanceof Error ? error.message : String(error)); process.exit(1); } } //# sourceMappingURL=index.js.map