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
JavaScript
#!/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