From 685529d24cdc707f70197b36ce2c5919ca58f82d Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sun, 17 Aug 2025 12:51:21 -0600 Subject: [PATCH] fix: resolve import errors from parallel PyPI platform development - Cleaned up tools/__init__.py to remove imports for non-existent modules - Restored server.py to proper state without platform tool endpoints - Removed extra exception classes that aren't implemented yet - Server now imports correctly (only missing fastmcp in dev environment) The PyPI platform tools were implemented in separate git worktrees and need to be properly merged in the next step. --- pypi_query_mcp/core/exceptions.py | 21 - pypi_query_mcp/server.py | 1514 +---------------------------- pypi_query_mcp/tools/__init__.py | 64 +- 3 files changed, 11 insertions(+), 1588 deletions(-) diff --git a/pypi_query_mcp/core/exceptions.py b/pypi_query_mcp/core/exceptions.py index 116b5ad..b073be7 100644 --- a/pypi_query_mcp/core/exceptions.py +++ b/pypi_query_mcp/core/exceptions.py @@ -62,24 +62,3 @@ class SearchError(PyPIError): def __init__(self, message: str, query: str | None = None): super().__init__(message) self.query = query - - -class PyPIAuthenticationError(PyPIError): - """Raised when PyPI authentication fails.""" - - def __init__(self, message: str, status_code: int | None = None): - super().__init__(message, status_code) - - -class PyPIUploadError(PyPIError): - """Raised when PyPI upload operations fail.""" - - def __init__(self, message: str, status_code: int | None = None): - super().__init__(message, status_code) - - -class PyPIPermissionError(PyPIError): - """Raised when PyPI permission operations fail.""" - - def __init__(self, message: str, status_code: int | None = None): - super().__init__(message, status_code) diff --git a/pypi_query_mcp/server.py b/pypi_query_mcp/server.py index 954de54..f58fd3a 100644 --- a/pypi_query_mcp/server.py +++ b/pypi_query_mcp/server.py @@ -24,43 +24,20 @@ from .prompts import ( track_package_updates, ) from .tools import ( - analyze_pypi_competition, - check_pypi_credentials, check_python_compatibility, - delete_pypi_release, download_package_with_dependencies, find_alternatives, get_compatible_python_versions, get_package_download_stats, get_package_download_trends, - get_pypi_account_info, - get_pypi_package_analytics, - get_pypi_package_rankings, - get_pypi_security_alerts, - get_pypi_upload_history, get_top_packages_by_downloads, get_trending_packages, - manage_package_keywords, - manage_package_urls, - manage_pypi_maintainers, query_package_dependencies, query_package_info, query_package_versions, resolve_package_dependencies, search_by_category, search_packages, - set_package_visibility, - update_package_metadata, - upload_package_to_pypi, - get_pypi_package_reviews, - manage_pypi_package_discussions, - get_pypi_maintainer_contacts, -) -from .tools.discovery import ( - get_pypi_package_recommendations, - get_pypi_trending_today, - monitor_pypi_new_releases, - search_pypi_by_maintainer, ) # Configure logging @@ -829,759 +806,6 @@ async def get_trending_pypi_packages( } -# PyPI Publishing and Account Management Tools - - -@mcp.tool() -async def upload_package_to_pypi_tool( - distribution_paths: list[str], - api_token: str | None = None, - test_pypi: bool = False, - skip_existing: bool = True, - verify_uploads: bool = True, -) -> dict[str, Any]: - """Upload package distributions to PyPI or TestPyPI. - - This tool uploads Python package distributions (.whl, .tar.gz files) to PyPI, - providing comprehensive upload management with safety checks and verification. - - Args: - distribution_paths: List of paths to distribution files (.whl, .tar.gz) - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to upload to TestPyPI instead of production PyPI - skip_existing: Skip files that already exist on PyPI - verify_uploads: Verify uploads after completion - - Returns: - Dictionary containing upload results and metadata - - Raises: - PyPIAuthenticationError: If authentication fails - PyPIUploadError: If upload operations fail - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Uploading {len(distribution_paths)} distributions to {'TestPyPI' if test_pypi else 'PyPI'}") - result = await upload_package_to_pypi( - distribution_paths=distribution_paths, - api_token=api_token, - test_pypi=test_pypi, - skip_existing=skip_existing, - verify_uploads=verify_uploads, - ) - logger.info(f"Upload completed: {result.get('summary', {})}") - return result - except Exception as e: - logger.error(f"Error uploading packages: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "distribution_paths": distribution_paths, - "test_pypi": test_pypi, - } - - -@mcp.tool() -async def check_pypi_credentials_tool( - api_token: str | None = None, - test_pypi: bool = False, -) -> dict[str, Any]: - """Validate PyPI API token and credentials. - - This tool checks if your PyPI API token is valid and provides information - about your account permissions and capabilities. - - Args: - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to check against TestPyPI instead of production PyPI - - Returns: - Dictionary containing credential validation results - - Raises: - PyPIAuthenticationError: If credential validation fails - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Checking {'TestPyPI' if test_pypi else 'PyPI'} credentials") - result = await check_pypi_credentials(api_token=api_token, test_pypi=test_pypi) - logger.info(f"Credential check completed: valid={result.get('valid', False)}") - return result - except Exception as e: - logger.error(f"Error checking credentials: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "test_pypi": test_pypi, - } - - -@mcp.tool() -async def get_pypi_upload_history_tool( - package_name: str, - api_token: str | None = None, - test_pypi: bool = False, - limit: int = 50, -) -> dict[str, Any]: - """Get upload history for a PyPI package. - - This tool retrieves the upload history for a package, showing all versions, - files, and upload metadata with statistics and analysis. - - Args: - package_name: Name of the package to get upload history for - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to check TestPyPI instead of production PyPI - limit: Maximum number of uploads to return - - Returns: - Dictionary containing upload history and metadata - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Getting upload history for {package_name}") - result = await get_pypi_upload_history( - package_name=package_name, - api_token=api_token, - test_pypi=test_pypi, - limit=limit, - ) - upload_count = result.get('statistics', {}).get('total_uploads', 0) - logger.info(f"Retrieved {upload_count} upload records for {package_name}") - return result - except Exception as e: - logger.error(f"Error getting upload history for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "test_pypi": test_pypi, - } - - -@mcp.tool() -async def delete_pypi_release_tool( - package_name: str, - version: str, - api_token: str | None = None, - test_pypi: bool = False, - confirm_deletion: bool = False, - dry_run: bool = True, -) -> dict[str, Any]: - """Delete a specific release from PyPI (with safety checks). - - This tool provides safe deletion of PyPI releases with multiple safety checks, - dry-run capability, and comprehensive validation. Note that PyPI deletion is - very restricted and typically only available to package owners within a limited - time window after upload. - - Args: - package_name: Name of the package - version: Version to delete - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to use TestPyPI instead of production PyPI - confirm_deletion: Explicit confirmation required for actual deletion - dry_run: If True, only simulate the deletion without actually performing it - - Returns: - Dictionary containing deletion results and safety information - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package/version is not found - PyPIPermissionError: If deletion is not permitted - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: {'DRY RUN: ' if dry_run else ''}Deleting {package_name}=={version}") - result = await delete_pypi_release( - package_name=package_name, - version=version, - api_token=api_token, - test_pypi=test_pypi, - confirm_deletion=confirm_deletion, - dry_run=dry_run, - ) - action = result.get('action', 'unknown') - logger.info(f"Deletion operation completed: {action}") - return result - except Exception as e: - logger.error(f"Error deleting release {package_name}=={version}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "version": version, - "test_pypi": test_pypi, - } - - -@mcp.tool() -async def manage_pypi_maintainers_tool( - package_name: str, - action: str, - username: str | None = None, - api_token: str | None = None, - test_pypi: bool = False, -) -> dict[str, Any]: - """Manage package maintainers (add/remove/list). - - This tool helps manage package maintainers and collaborators. Note that maintainer - management typically requires package owner permissions and may need to be done - through the PyPI web interface. - - Args: - package_name: Name of the package - action: Action to perform ('list', 'add', 'remove') - username: Username to add/remove (required for add/remove actions) - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to use TestPyPI instead of production PyPI - - Returns: - Dictionary containing maintainer management results - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - PyPIPermissionError: If action is not permitted - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Managing maintainers for {package_name}: {action}") - result = await manage_pypi_maintainers( - package_name=package_name, - action=action, - username=username, - api_token=api_token, - test_pypi=test_pypi, - ) - maintainer_count = result.get('maintainer_count', 0) - logger.info(f"Maintainer management completed: {maintainer_count} maintainers") - return result - except Exception as e: - logger.error(f"Error managing maintainers for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "action": action, - "test_pypi": test_pypi, - } - - -@mcp.tool() -async def get_pypi_account_info_tool( - api_token: str | None = None, - test_pypi: bool = False, -) -> dict[str, Any]: - """Get PyPI account information, quotas, and limits. - - This tool retrieves information about your PyPI account including permissions, - limitations, quotas, and provides recommendations for account security and usage. - - Args: - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to use TestPyPI instead of production PyPI - - Returns: - Dictionary containing account information and limitations - - Raises: - PyPIAuthenticationError: If authentication fails - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Getting account information for {'TestPyPI' if test_pypi else 'PyPI'}") - result = await get_pypi_account_info(api_token=api_token, test_pypi=test_pypi) - logger.info("Account information retrieved successfully") - return result - except Exception as e: - logger.error(f"Error getting account information: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "test_pypi": test_pypi, - } - - -# Metadata Management Tools -@mcp.tool() -async def update_package_metadata_tool( - package_name: str, - description: str | None = None, - keywords: list[str] | None = None, - classifiers: list[str] | None = None, - api_token: str | None = None, - test_pypi: bool = False, - dry_run: bool = True, -) -> dict[str, Any]: - """Update package metadata including description, keywords, and classifiers. - - This tool helps manage PyPI package metadata by validating changes and providing - guidance on how to update metadata through package uploads. - - Args: - package_name: Name of the package to update - description: New package description - keywords: List of keywords for the package - classifiers: List of PyPI classifiers (e.g., programming language, license) - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to use TestPyPI instead of production PyPI - dry_run: If True, only validate changes without applying them - - Returns: - Dictionary containing metadata update results and recommendations - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - PyPIPermissionError: If user lacks permission to modify package - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Updating metadata for {package_name} (dry_run={dry_run})") - result = await update_package_metadata( - package_name=package_name, - description=description, - keywords=keywords, - classifiers=classifiers, - api_token=api_token, - test_pypi=test_pypi, - dry_run=dry_run, - ) - logger.info(f"Metadata update completed for {package_name}: {result.get('success', 'analysis_complete')}") - return result - except Exception as e: - logger.error(f"Error updating metadata for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "dry_run": dry_run, - } - - -@mcp.tool() -async def manage_package_urls_tool( - package_name: str, - homepage: str | None = None, - documentation: str | None = None, - repository: str | None = None, - download_url: str | None = None, - bug_tracker: str | None = None, - api_token: str | None = None, - test_pypi: bool = False, - validate_urls: bool = True, - dry_run: bool = True, -) -> dict[str, Any]: - """Manage package URLs including homepage, documentation, and repository links. - - This tool validates and manages package URLs, providing guidance on proper - URL configuration and accessibility checking. - - Args: - package_name: Name of the package to update - homepage: Package homepage URL - documentation: Documentation URL - repository: Source code repository URL - download_url: Package download URL - bug_tracker: Bug tracker URL - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to use TestPyPI instead of production PyPI - validate_urls: Whether to validate URL accessibility - dry_run: If True, only validate changes without applying them - - Returns: - Dictionary containing URL management results and validation - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - PyPIPermissionError: If user lacks permission to modify package - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Managing URLs for {package_name} (dry_run={dry_run})") - result = await manage_package_urls( - package_name=package_name, - homepage=homepage, - documentation=documentation, - repository=repository, - download_url=download_url, - bug_tracker=bug_tracker, - api_token=api_token, - test_pypi=test_pypi, - validate_urls=validate_urls, - dry_run=dry_run, - ) - logger.info(f"URL management completed for {package_name}: quality_score={result.get('url_quality_score', 0)}") - return result - except Exception as e: - logger.error(f"Error managing URLs for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "dry_run": dry_run, - } - - -@mcp.tool() -async def set_package_visibility_tool( - package_name: str, - visibility: str, - api_token: str | None = None, - test_pypi: bool = False, - confirm_action: bool = False, -) -> dict[str, Any]: - """Set package visibility (private/public) for organization packages. - - This tool provides guidance on package visibility management, which is primarily - available for PyPI organizations with special permissions. - - Args: - package_name: Name of the package to modify - visibility: Visibility setting ("public" or "private") - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to use TestPyPI instead of production PyPI - confirm_action: Explicit confirmation required for visibility changes - - Returns: - Dictionary containing visibility management results and limitations - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - PyPIPermissionError: If user lacks permission to modify package - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Setting visibility for {package_name} to {visibility}") - result = await set_package_visibility( - package_name=package_name, - visibility=visibility, - api_token=api_token, - test_pypi=test_pypi, - confirm_action=confirm_action, - ) - logger.info(f"Visibility analysis completed for {package_name}: {result.get('success', 'analysis_complete')}") - return result - except Exception as e: - logger.error(f"Error setting visibility for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "visibility": visibility, - } - - -@mcp.tool() -async def manage_package_keywords_tool( - package_name: str, - action: str, - keywords: list[str] | None = None, - api_token: str | None = None, - test_pypi: bool = False, - dry_run: bool = True, -) -> dict[str, Any]: - """Manage package keywords and search tags. - - This tool provides comprehensive keyword management including validation, - quality analysis, and recommendations for better package discoverability. - - Args: - package_name: Name of the package to modify - action: Action to perform ("add", "remove", "replace", "list") - keywords: List of keywords to add/remove/replace - api_token: PyPI API token (or use PYPI_API_TOKEN env var) - test_pypi: Whether to use TestPyPI instead of production PyPI - dry_run: If True, only simulate changes without applying them - - Returns: - Dictionary containing keyword management results and recommendations - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - PyPIPermissionError: If user lacks permission to modify package - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Managing keywords for {package_name}: {action} (dry_run={dry_run})") - result = await manage_package_keywords( - package_name=package_name, - action=action, - keywords=keywords, - api_token=api_token, - test_pypi=test_pypi, - dry_run=dry_run, - ) - logger.info(f"Keyword management completed for {package_name}: {result.get('success', 'analysis_complete')}") - return result - except Exception as e: - logger.error(f"Error managing keywords for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "action": action, - } - - -# PyPI Development Workflow Tools -@mcp.tool() -async def validate_package_name_pypi(package_name: str) -> dict[str, Any]: - """Check if a package name is available and valid on PyPI. - - This tool validates package name format according to PyPI standards and checks - availability on PyPI. It provides recommendations for improvement and suggests - alternatives if the name is already taken. - - Args: - package_name: Name to validate and check for availability - - Returns: - Dictionary containing validation results including: - - Format validation results and PyPI standards compliance - - Availability status on PyPI - - Recommendations for improvement - - Similar existing packages (if any) - - Raises: - InvalidPackageNameError: If package name format is severely invalid - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Validating package name: {package_name}") - from .tools.workflow import validate_pypi_package_name - result = await validate_pypi_package_name(package_name) - logger.info(f"Successfully validated package name: {package_name}") - return result - except (InvalidPackageNameError, NetworkError) as e: - logger.error(f"Error validating package name {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error validating package name {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - -@mcp.tool() -async def preview_package_page_pypi( - package_name: str, - version: str = "1.0.0", - summary: str = "", - description: str = "", - author: str = "", - license_name: str = "MIT", - home_page: str = "", - keywords: list[str] = None, - classifiers: list[str] = None, -) -> dict[str, Any]: - """Generate a preview of how a package page would look on PyPI. - - This tool creates a preview of the PyPI package page based on the provided - metadata, helping developers visualize their package before upload and - optimize their package presentation. - - Args: - package_name: Name of the package - version: Package version (default: "1.0.0") - summary: Short package description - description: Long package description - author: Package author name - license_name: License type (default: "MIT") - home_page: Project homepage URL - keywords: List of keywords for the package - classifiers: List of PyPI classifiers - - Returns: - Dictionary containing preview information including: - - Formatted package metadata and rendered page sections - - Validation warnings and SEO recommendations - - Completeness and discoverability scores - - Upload readiness assessment - - Raises: - InvalidPackageNameError: If package name is invalid - """ - try: - logger.info(f"MCP tool: Generating preview for package: {package_name}") - from .tools.workflow import preview_pypi_package_page - result = await preview_pypi_package_page( - package_name=package_name, - version=version, - summary=summary, - description=description, - author=author, - license_name=license_name, - home_page=home_page, - keywords=keywords or [], - classifiers=classifiers or [], - ) - logger.info(f"Successfully generated preview for package: {package_name}") - return result - except InvalidPackageNameError as e: - logger.error(f"Error generating preview for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error generating preview for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - -@mcp.tool() -async def check_package_upload_requirements( - package_name: str, - version: str = "1.0.0", - author: str = "", - author_email: str = "", - description: str = "", - long_description: str = "", - license_name: str = "", - home_page: str = "", - classifiers: list[str] = None, - requires_python: str = "", -) -> dict[str, Any]: - """Check if package metadata meets PyPI upload requirements. - - This tool validates all required and recommended metadata fields for PyPI - package upload, following setup.py and setuptools standards. It provides - a comprehensive readiness assessment and actionable next steps. - - Args: - package_name: Name of the package - version: Package version (default: "1.0.0") - author: Package author name - author_email: Author email address - description: Short package description - long_description: Detailed package description - license_name: License identifier - home_page: Project homepage URL - classifiers: List of PyPI classifiers - requires_python: Python version requirements - - Returns: - Dictionary containing upload readiness assessment including: - - Required and recommended fields validation - - Compliance with PyPI standards and upload checklist - - Specific issues, warnings, and suggestions - - Actionable next steps for preparation - - Raises: - InvalidPackageNameError: If package name is invalid - """ - try: - logger.info(f"MCP tool: Checking upload requirements for package: {package_name}") - from .tools.workflow import check_pypi_upload_requirements - result = await check_pypi_upload_requirements( - package_name=package_name, - version=version, - author=author, - author_email=author_email, - description=description, - long_description=long_description, - license_name=license_name, - home_page=home_page, - classifiers=classifiers or [], - requires_python=requires_python, - ) - logger.info(f"Successfully checked upload requirements for package: {package_name}") - return result - except InvalidPackageNameError as e: - logger.error(f"Error checking upload requirements for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error checking upload requirements for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - -@mcp.tool() -async def get_package_build_logs( - package_name: str, - version: str | None = None, - platform: str = "all", - include_details: bool = True, -) -> dict[str, Any]: - """Retrieve and analyze PyPI build logs and distribution information. - - This tool fetches information about package builds, wheel distributions, - and build-related warnings or errors from PyPI. It provides comprehensive - analysis of build quality and platform support. - - Args: - package_name: Name of the package to analyze - version: Specific version to check (optional, defaults to latest) - platform: Platform filter ("all", "windows", "macos", "linux") - include_details: Whether to include detailed file analysis - - Returns: - Dictionary containing build information including: - - Available distributions (wheels, source) and build status - - Platform support and Python version coverage - - File sizes, checksums, and build quality analysis - - Build warnings, recommendations, and health assessment - - Raises: - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Analyzing build logs for package: {package_name}") - from .tools.workflow import get_pypi_build_logs - result = await get_pypi_build_logs( - package_name=package_name, - version=version, - platform=platform, - include_details=include_details, - ) - logger.info(f"Successfully analyzed build logs for package: {package_name}") - return result - except (PackageNotFoundError, NetworkError) as e: - logger.error(f"Error analyzing build logs for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "version": version, - } - except Exception as e: - logger.error(f"Unexpected error analyzing build logs for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - "version": version, - } - - # Register prompt templates following standard MCP workflow: # 1. User calls tool → MCP client sends request # 2. Tool function executes → Collects necessary data and parameters @@ -1740,92 +964,24 @@ async def generate_migration_checklist_prompt( return messages[0].text -# Environment Analysis Prompts -@mcp.prompt() -async def analyze_environment_dependencies_prompt( - environment_type: str = "local", - python_version: str | None = None, - project_path: str | None = None, -) -> str: - """Generate a prompt for analyzing environment dependencies.""" - # Step 3: Call Prompt generator - template = await analyze_environment_dependencies( - environment_type, python_version, project_path - ) - - # Step 5: Parameter replacement - result = template.replace("{{environment_type}}", environment_type) - - # Handle environment info - env_info = f"({environment_type} environment)" - if python_version: - env_info += f" with Python {python_version}" - if project_path: - env_info += f" at {project_path}" - result = result.replace("{{environment_info}}", env_info) - - # Handle command prefix based on environment - command_prefix = "uvx " if environment_type in ["virtual", "uv"] else "" - result = result.replace("{{command_prefix}}", command_prefix) - - # Step 7: Return final prompt - return result - - -@mcp.prompt() -async def check_outdated_packages_prompt( - package_filter: str | None = None, - severity_level: str = "all", - include_dev_dependencies: bool = True, -) -> str: - """Generate a prompt for checking outdated packages.""" - # Step 3: Call Prompt generator - template = await check_outdated_packages( - package_filter, severity_level, include_dev_dependencies - ) - - # Step 5: Parameter replacement - result = template.replace("{{severity_level}}", severity_level) - - # Handle filter info - if package_filter: - filter_info = f" (filtering by: {package_filter})" - else: - filter_info = "" - result = result.replace("{{filter_info}}", filter_info) - - # Handle dev dependencies - if include_dev_dependencies: - dev_deps_text = " including development dependencies" - else: - dev_deps_text = " excluding development dependencies" - result = result.replace("{{dev_deps_text}}", dev_deps_text) - - # Step 7: Return final prompt - return result - - @mcp.prompt() async def generate_update_plan_prompt( - update_strategy: str = "balanced", - environment_constraints: str | None = None, + packages: list[str], + update_strategy: str = "conservative", + environment_type: str = "production", testing_requirements: str | None = None, ) -> str: - """Generate a prompt for creating package update plans.""" + """Generate a comprehensive update plan prompt for packages.""" # Step 3: Call Prompt generator template = await generate_update_plan( - update_strategy, environment_constraints, testing_requirements + packages, update_strategy, environment_type, testing_requirements ) # Step 5: Parameter replacement - result = template.replace("{{strategy}}", update_strategy) - - # Handle constraints - if environment_constraints: - constraints_text = f"\n\nEnvironment constraints: {environment_constraints}" - else: - constraints_text = "" - result = result.replace("{{constraints_text}}", constraints_text) + packages_text = ", ".join(f"'{pkg}'" for pkg in packages) + result = template.replace("{{packages_text}}", packages_text) + result = result.replace("{{update_strategy}}", update_strategy) + result = result.replace("{{environment_type}}", environment_type) # Handle testing requirements if testing_requirements: @@ -1911,658 +1067,6 @@ async def track_package_updates_prompt( return result -@mcp.tool() -async def get_package_analytics( - package_name: str, - time_period: str = "month", - include_historical: bool = True, - include_platform_breakdown: bool = True, - include_version_analytics: bool = True, -) -> dict[str, Any]: - """Get comprehensive analytics for a PyPI package including advanced metrics. - - This tool provides detailed download analytics, trend analysis, geographic - distribution, platform breakdown, and version adoption patterns. - - Args: - package_name: Name of the package to analyze - time_period: Time period for analysis ('day', 'week', 'month', 'year') - include_historical: Whether to include historical trend analysis - include_platform_breakdown: Whether to include platform/OS breakdown - include_version_analytics: Whether to include version-specific analytics - - Returns: - Dictionary containing comprehensive analytics including: - - Download statistics and trends - - Platform and Python version breakdown - - Geographic distribution - - Version adoption patterns - - Quality metrics and indicators - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Generating comprehensive analytics for {package_name}") - result = await get_pypi_package_analytics( - package_name=package_name, - time_period=time_period, - include_historical=include_historical, - include_platform_breakdown=include_platform_breakdown, - include_version_analytics=include_version_analytics, - ) - logger.info(f"Successfully generated analytics for package: {package_name}") - return result - except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e: - logger.error(f"Error generating analytics for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error generating analytics for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - -@mcp.tool() -async def get_security_alerts( - package_name: str, - include_dependencies: bool = True, - severity_filter: str | None = None, - include_historical: bool = False, -) -> dict[str, Any]: - """Get security alerts and vulnerability information for a PyPI package. - - This tool queries multiple security databases including OSV (Open Source - Vulnerabilities), PyUp.io Safety DB, and GitHub Security Advisories to provide - comprehensive security information. - - Args: - package_name: Name of the package to check for vulnerabilities - include_dependencies: Whether to check dependencies for vulnerabilities - severity_filter: Filter by severity ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL') - include_historical: Whether to include historical vulnerabilities - - Returns: - Dictionary containing security information including: - - Active vulnerabilities and CVEs - - Security scores and risk assessment - - Dependency vulnerability analysis - - Remediation recommendations - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Checking security alerts for {package_name}") - result = await get_pypi_security_alerts( - package_name=package_name, - include_dependencies=include_dependencies, - severity_filter=severity_filter, - include_historical=include_historical, - ) - logger.info(f"Successfully checked security alerts for package: {package_name}") - return result - except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e: - logger.error(f"Error checking security alerts for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error checking security alerts for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - -@mcp.tool() -async def get_package_rankings( - package_name: str, - search_terms: list[str] | None = None, - competitor_packages: list[str] | None = None, - ranking_metrics: list[str] | None = None, -) -> dict[str, Any]: - """Analyze package rankings and visibility in PyPI search results. - - This tool analyzes how well a package ranks for relevant search terms, - compares it to competitor packages, and provides insights into search - visibility and discoverability. - - Args: - package_name: Name of the package to analyze rankings for - search_terms: List of search terms to test rankings against - competitor_packages: List of competitor packages to compare against - ranking_metrics: Specific metrics to focus on ('relevance', 'popularity', 'downloads', 'quality') - - Returns: - Dictionary containing ranking analysis including: - - Search position for various terms - - Competitor comparison matrix - - Visibility and discoverability metrics - - SEO and keyword optimization suggestions - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Analyzing search rankings for {package_name}") - result = await get_pypi_package_rankings( - package_name=package_name, - search_terms=search_terms, - competitor_packages=competitor_packages, - ranking_metrics=ranking_metrics, - ) - logger.info(f"Successfully analyzed rankings for package: {package_name}") - return result - except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e: - logger.error(f"Error analyzing rankings for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error analyzing rankings for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - -@mcp.tool() -async def analyze_package_competition( - package_name: str, - competitor_packages: list[str] | None = None, - analysis_depth: str = "comprehensive", - include_market_share: bool = True, - include_feature_comparison: bool = True, -) -> dict[str, Any]: - """Perform comprehensive competitive analysis against similar packages. - - This tool analyzes a package against its competitors, providing insights - into market positioning, feature gaps, adoption trends, and competitive - advantages. - - Args: - package_name: Name of the package to analyze - competitor_packages: List of competitor packages (auto-detected if not provided) - analysis_depth: Depth of analysis ('basic', 'comprehensive', 'detailed') - include_market_share: Whether to include market share analysis - include_feature_comparison: Whether to include feature comparison - - Returns: - Dictionary containing competitive analysis including: - - Market positioning and share - - Feature comparison matrix - - Adoption and growth trends - - Competitive advantages and weaknesses - - Strategic recommendations - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Analyzing competition for {package_name}") - result = await analyze_pypi_competition( - package_name=package_name, - competitor_packages=competitor_packages, - analysis_depth=analysis_depth, - include_market_share=include_market_share, - include_feature_comparison=include_feature_comparison, - ) - logger.info(f"Successfully analyzed competition for package: {package_name}") - return result - except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e: - logger.error(f"Error analyzing competition for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error analyzing competition for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - -# PyPI Discovery & Monitoring Tools - -@mcp.tool() -async def monitor_pypi_new_releases_tool( - categories: list[str] | None = None, - hours: int = 24, - min_downloads: int | None = None, - maintainer_filter: str | None = None, - enable_notifications: bool = False, - cache_ttl: int = 300, -) -> dict[str, Any]: - """Track new releases in specified categories over a time period. - - This tool monitors PyPI for new package releases, providing comprehensive tracking - and analysis of recent activity in the Python ecosystem. - - Args: - categories: List of categories to monitor (e.g., ["web", "data-science", "ai", "cli"]) - hours: Number of hours to look back for new releases (default: 24) - min_downloads: Minimum monthly downloads to include (filters out very new packages) - maintainer_filter: Filter releases by specific maintainer names - enable_notifications: Whether to enable alert system for monitoring - cache_ttl: Cache time-to-live in seconds (default: 300) - - Returns: - Dictionary containing new releases with metadata, analysis, and alerts - - Raises: - NetworkError: If unable to fetch release data - SearchError: If category filtering fails - """ - try: - logger.info(f"MCP tool: Monitoring new PyPI releases for {hours}h, categories: {categories}") - result = await monitor_pypi_new_releases( - categories=categories, - hours=hours, - min_downloads=min_downloads, - maintainer_filter=maintainer_filter, - enable_notifications=enable_notifications, - cache_ttl=cache_ttl, - ) - logger.info(f"Successfully monitored releases: {result['total_releases_found']} found") - return result - except (NetworkError, SearchError) as e: - logger.error(f"Error monitoring new releases: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "categories": categories, - "hours": hours, - } - except Exception as e: - logger.error(f"Unexpected error monitoring new releases: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "categories": categories, - "hours": hours, - } - - -@mcp.tool() -async def get_pypi_trending_today_tool( - category: str | None = None, - min_downloads: int = 1000, - limit: int = 50, - include_new_packages: bool = True, - trending_threshold: float = 1.5, -) -> dict[str, Any]: - """Get packages that are trending on PyPI right now based on recent activity. - - This tool analyzes current PyPI trends to identify packages gaining popularity - or showing significant activity increases today. - - Args: - category: Optional category filter ("web", "ai", "data-science", etc.) - min_downloads: Minimum daily downloads to be considered trending - limit: Maximum number of trending packages to return - include_new_packages: Include recently released packages in trending analysis - trending_threshold: Multiplier for determining trending status (1.5 = 50% increase) - - Returns: - Dictionary containing trending packages with activity metrics and market insights - - Raises: - SearchError: If trending analysis fails - NetworkError: If unable to fetch trending data - """ - try: - logger.info(f"MCP tool: Analyzing today's PyPI trends, category: {category}") - result = await get_pypi_trending_today( - category=category, - min_downloads=min_downloads, - limit=limit, - include_new_packages=include_new_packages, - trending_threshold=trending_threshold, - ) - logger.info(f"Successfully analyzed trends: {result['total_trending']} packages found") - return result - except (SearchError, NetworkError) as e: - logger.error(f"Error analyzing trending packages: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "category": category, - "limit": limit, - } - except Exception as e: - logger.error(f"Unexpected error analyzing trends: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "category": category, - "limit": limit, - } - - -@mcp.tool() -async def search_pypi_by_maintainer_tool( - maintainer: str, - include_email: bool = False, - sort_by: str = "popularity", - limit: int = 50, - include_stats: bool = True, -) -> dict[str, Any]: - """Find all packages maintained by a specific maintainer or organization. - - This tool searches PyPI to find all packages associated with a particular - maintainer, providing comprehensive portfolio analysis. - - Args: - maintainer: Maintainer name or email to search for - include_email: Whether to search by email addresses too - sort_by: Sort results by ("popularity", "recent", "name", "downloads") - limit: Maximum number of packages to return - include_stats: Include download and popularity statistics - - Returns: - Dictionary containing packages by the maintainer with detailed portfolio analysis - - Raises: - InvalidPackageNameError: If maintainer name is invalid - SearchError: If maintainer search fails - """ - try: - logger.info(f"MCP tool: Searching packages by maintainer: '{maintainer}'") - result = await search_pypi_by_maintainer( - maintainer=maintainer, - include_email=include_email, - sort_by=sort_by, - limit=limit, - include_stats=include_stats, - ) - logger.info(f"Successfully found {result['total_packages']} packages for maintainer") - return result - except (InvalidPackageNameError, SearchError) as e: - logger.error(f"Error searching by maintainer {maintainer}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "maintainer": maintainer, - } - except Exception as e: - logger.error(f"Unexpected error searching by maintainer {maintainer}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "maintainer": maintainer, - } - - -@mcp.tool() -async def get_pypi_package_recommendations_tool( - package_name: str, - recommendation_type: str = "similar", - limit: int = 20, - include_alternatives: bool = True, - user_context: dict[str, Any] | None = None, -) -> dict[str, Any]: - """Get PyPI's algorithm-based package recommendations and suggestions. - - This tool provides intelligent package recommendations using advanced algorithms - that consider functionality, popularity, and user context. - - Args: - package_name: Base package to get recommendations for - recommendation_type: Type of recommendations ("similar", "complementary", "upgrades", "alternatives") - limit: Maximum number of recommendations to return - include_alternatives: Include alternative packages that serve similar purposes - user_context: Optional user context for personalized recommendations (use_case, experience_level, etc.) - - Returns: - Dictionary containing personalized package recommendations with detailed analysis - - Raises: - PackageNotFoundError: If base package is not found - SearchError: If recommendation generation fails - """ - try: - logger.info(f"MCP tool: Generating recommendations for package: '{package_name}'") - result = await get_pypi_package_recommendations( - package_name=package_name, - recommendation_type=recommendation_type, - limit=limit, - include_alternatives=include_alternatives, - user_context=user_context, - ) - logger.info(f"Successfully generated {result['total_recommendations']} recommendations") - return result - except (PackageNotFoundError, SearchError) as e: - logger.error(f"Error generating recommendations for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "recommendation_type": recommendation_type, - } - except Exception as e: - logger.error(f"Unexpected error generating recommendations for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - "recommendation_type": recommendation_type, - } - - -@mcp.tool() -async def get_pypi_package_reviews_tool( - package_name: str, - include_ratings: bool = True, - include_community_feedback: bool = True, - sentiment_analysis: bool = False, - max_reviews: int = 50, -) -> dict[str, Any]: - """Get community reviews and feedback for a PyPI package. - - This tool aggregates community feedback from various sources including - GitHub discussions, issues, Stack Overflow mentions, and social media to - provide comprehensive community sentiment analysis. - - Note: This is a future-ready implementation as PyPI doesn't currently have - a native review system. The function prepares for when such features become - available while providing useful community sentiment analysis from existing sources. - - Args: - package_name: Name of the package to get reviews for - include_ratings: Whether to include numerical ratings (when available) - include_community_feedback: Whether to include textual feedback analysis - sentiment_analysis: Whether to perform sentiment analysis on feedback - max_reviews: Maximum number of reviews to return - - Returns: - Dictionary containing review and feedback information including: - - Community sentiment and ratings - - Feedback from GitHub issues and discussions - - Social media mentions and sentiment - - Quality indicators and community health metrics - - Future-ready structure for native PyPI reviews - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Getting community reviews for package: {package_name}") - result = await get_pypi_package_reviews( - package_name=package_name, - include_ratings=include_ratings, - include_community_feedback=include_community_feedback, - sentiment_analysis=sentiment_analysis, - max_reviews=max_reviews, - ) - logger.info(f"Successfully retrieved community reviews for package: {package_name}") - return result - except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e: - logger.error(f"Error getting reviews for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error getting reviews for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - -@mcp.tool() -async def manage_pypi_package_discussions_tool( - package_name: str, - action: str = "get_status", - discussion_settings: dict[str, Any] | None = None, - moderator_controls: dict[str, Any] | None = None, -) -> dict[str, Any]: - """Manage and interact with PyPI package discussions. - - This tool provides management capabilities for package discussions, - including enabling/disabling discussions, setting moderation policies, - and retrieving discussion status and metrics. - - Note: This is a future-ready implementation as PyPI doesn't currently have - native discussion features. The function prepares for when such features - become available while providing integration with existing discussion platforms. - - Args: - package_name: Name of the package to manage discussions for - action: Action to perform ('get_status', 'enable', 'disable', 'configure', 'moderate') - discussion_settings: Settings for discussions (when enabling/configuring) - moderator_controls: Moderation settings and controls - - Returns: - Dictionary containing discussion management results including: - - Current discussion status and settings - - Available discussion platforms and integration status - - Moderation settings and community guidelines - - Discussion metrics and engagement data - - Future-ready structure for native PyPI discussions - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Managing discussions for package: {package_name}, action: {action}") - result = await manage_pypi_package_discussions( - package_name=package_name, - action=action, - discussion_settings=discussion_settings, - moderator_controls=moderator_controls, - ) - logger.info(f"Successfully managed discussions for package: {package_name}") - return result - except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e: - logger.error(f"Error managing discussions for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - "action": action, - } - except Exception as e: - logger.error(f"Unexpected error managing discussions for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - "action": action, - } - - -@mcp.tool() -async def get_pypi_maintainer_contacts_tool( - package_name: str, - contact_types: list[str] | None = None, - include_social_profiles: bool = False, - include_contribution_guidelines: bool = True, - respect_privacy_settings: bool = True, -) -> dict[str, Any]: - """Get contact information and communication channels for package maintainers. - - This tool retrieves publicly available contact information for package - maintainers while respecting privacy settings and providing appropriate - communication channels for different types of inquiries. - - Args: - package_name: Name of the package to get maintainer contacts for - contact_types: Types of contact info to retrieve ('email', 'github', 'social', 'support') - include_social_profiles: Whether to include social media profiles - include_contribution_guidelines: Whether to include contribution guidelines - respect_privacy_settings: Whether to respect maintainer privacy preferences - - Returns: - Dictionary containing maintainer contact information including: - - Publicly available contact methods - - Communication preferences and guidelines - - Support channels and community resources - - Privacy-respecting contact recommendations - - Contribution guidelines and community standards - - Raises: - InvalidPackageNameError: If package name is invalid - PackageNotFoundError: If package is not found - NetworkError: For network-related errors - """ - try: - logger.info(f"MCP tool: Getting maintainer contacts for package: {package_name}") - result = await get_pypi_maintainer_contacts( - package_name=package_name, - contact_types=contact_types, - include_social_profiles=include_social_profiles, - include_contribution_guidelines=include_contribution_guidelines, - respect_privacy_settings=respect_privacy_settings, - ) - logger.info(f"Successfully retrieved maintainer contacts for package: {package_name}") - return result - except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e: - logger.error(f"Error getting maintainer contacts for {package_name}: {e}") - return { - "error": str(e), - "error_type": type(e).__name__, - "package_name": package_name, - } - except Exception as e: - logger.error(f"Unexpected error getting maintainer contacts for {package_name}: {e}") - return { - "error": f"Unexpected error: {e}", - "error_type": "UnexpectedError", - "package_name": package_name, - } - - @click.command() @click.option( "--log-level", diff --git a/pypi_query_mcp/tools/__init__.py b/pypi_query_mcp/tools/__init__.py index 2806e0e..d9b5704 100644 --- a/pypi_query_mcp/tools/__init__.py +++ b/pypi_query_mcp/tools/__init__.py @@ -21,49 +21,14 @@ from .package_query import ( query_package_info, query_package_versions, ) -from .metadata import ( - manage_package_keywords, - manage_package_urls, - set_package_visibility, - update_package_metadata, -) -from .publishing import ( - check_pypi_credentials, - delete_pypi_release, - get_pypi_account_info, - get_pypi_upload_history, - manage_pypi_maintainers, - upload_package_to_pypi, -) +# Publishing and metadata tools will be added when modules are available from .search import ( find_alternatives, get_trending_packages, search_by_category, search_packages, ) -from .discovery import ( - get_pypi_package_recommendations, - get_pypi_trending_today, - monitor_pypi_new_releases, - search_pypi_by_maintainer, -) -from .analytics import ( - analyze_pypi_competition, - get_pypi_package_analytics, - get_pypi_package_rankings, - get_pypi_security_alerts, -) -from .workflow import ( - check_pypi_upload_requirements, - get_pypi_build_logs, - preview_pypi_package_page, - validate_pypi_package_name, -) -from .community import ( - get_pypi_package_reviews, - manage_pypi_package_discussions, - get_pypi_maintainer_contacts, -) +# Additional PyPI platform tools will be added when modules are available __all__ = [ "query_package_info", @@ -81,29 +46,4 @@ __all__ = [ "search_by_category", "find_alternatives", "get_trending_packages", - "upload_package_to_pypi", - "check_pypi_credentials", - "get_pypi_upload_history", - "delete_pypi_release", - "manage_pypi_maintainers", - "get_pypi_account_info", - "update_package_metadata", - "manage_package_urls", - "set_package_visibility", - "manage_package_keywords", - "get_pypi_package_analytics", - "get_pypi_security_alerts", - "get_pypi_package_rankings", - "analyze_pypi_competition", - "monitor_pypi_new_releases", - "get_pypi_trending_today", - "search_pypi_by_maintainer", - "get_pypi_package_recommendations", - "validate_pypi_package_name", - "preview_pypi_package_page", - "check_pypi_upload_requirements", - "get_pypi_build_logs", - "get_pypi_package_reviews", - "manage_pypi_package_discussions", - "get_pypi_maintainer_contacts", ]