Some checks are pending
Bump version / Bump version and create changelog with commitizen (push) Waiting to run
Tests / test (macos-latest, 3.10) (push) Waiting to run
Tests / test (macos-latest, 3.11) (push) Waiting to run
Tests / test (macos-latest, 3.12) (push) Waiting to run
Tests / test (ubuntu-latest, 3.10) (push) Waiting to run
Tests / test (ubuntu-latest, 3.11) (push) Waiting to run
Tests / test (ubuntu-latest, 3.12) (push) Waiting to run
Tests / test (windows-latest, 3.10) (push) Waiting to run
Tests / test (windows-latest, 3.11) (push) Waiting to run
Tests / test (windows-latest, 3.12) (push) Waiting to run
Tests / security (push) Waiting to run
- Add security vulnerability scanning with OSV and GitHub advisories integration - Add license compatibility analysis with SPDX normalization and risk assessment - Add package health scoring across 7 categories with GitHub metrics integration - Add requirements file analysis supporting multiple formats (requirements.txt, pyproject.toml, etc.) - Fix search functionality MCP wrapper and error handling - Fix Python compatibility checking parameter order issue - Fix package recommendations NoneType handling - Add 8 new MCP tool endpoints for enhanced analysis capabilities This brings the total to 37 comprehensive MCP tools across 8 categories for complete PyPI package analysis and management.
2610 lines
97 KiB
Python
2610 lines
97 KiB
Python
"""FastMCP server for PyPI package queries."""
|
|
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
import click
|
|
from fastmcp import FastMCP
|
|
|
|
from .core.exceptions import InvalidPackageNameError, NetworkError, PackageNotFoundError, SearchError
|
|
from .prompts import (
|
|
analyze_daily_trends,
|
|
analyze_environment_dependencies,
|
|
analyze_package_quality,
|
|
audit_security_risks,
|
|
check_outdated_packages,
|
|
compare_packages,
|
|
find_trending_packages,
|
|
generate_migration_checklist,
|
|
generate_update_plan,
|
|
plan_package_migration,
|
|
plan_version_upgrade,
|
|
resolve_dependency_conflicts,
|
|
suggest_alternatives,
|
|
track_package_updates,
|
|
)
|
|
from .tools import (
|
|
check_python_compatibility,
|
|
download_package_with_dependencies,
|
|
find_alternatives,
|
|
get_compatible_python_versions,
|
|
get_package_download_stats,
|
|
get_package_download_trends,
|
|
get_top_packages_by_downloads,
|
|
get_trending_packages,
|
|
query_package_dependencies,
|
|
query_package_info,
|
|
query_package_versions,
|
|
resolve_package_dependencies,
|
|
search_by_category,
|
|
search_packages,
|
|
# Publishing tools
|
|
upload_package_to_pypi,
|
|
check_pypi_credentials,
|
|
get_pypi_upload_history,
|
|
delete_pypi_release,
|
|
manage_pypi_maintainers,
|
|
get_pypi_account_info,
|
|
# Metadata tools
|
|
update_package_metadata,
|
|
manage_package_urls,
|
|
set_package_visibility,
|
|
manage_package_keywords,
|
|
# Analytics tools
|
|
get_pypi_package_analytics,
|
|
get_pypi_security_alerts,
|
|
get_pypi_package_rankings,
|
|
analyze_pypi_competition,
|
|
# Discovery tools
|
|
monitor_pypi_new_releases,
|
|
get_pypi_trending_today,
|
|
search_pypi_by_maintainer,
|
|
get_pypi_package_recommendations,
|
|
# Workflow tools
|
|
validate_pypi_package_name,
|
|
preview_pypi_package_page,
|
|
check_pypi_upload_requirements,
|
|
get_pypi_build_logs,
|
|
# Community tools
|
|
get_pypi_package_reviews,
|
|
manage_pypi_package_discussions,
|
|
get_pypi_maintainer_contacts,
|
|
# Security tools
|
|
bulk_scan_package_security,
|
|
scan_pypi_package_security,
|
|
# License tools
|
|
analyze_pypi_package_license,
|
|
check_bulk_license_compliance,
|
|
# Health tools
|
|
assess_package_health_score,
|
|
compare_packages_health_scores,
|
|
# Requirements tools
|
|
analyze_requirements_file_tool,
|
|
compare_multiple_requirements_files,
|
|
)
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Create FastMCP application
|
|
mcp = FastMCP("PyPI Query MCP Server")
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_package_info(package_name: str) -> dict[str, Any]:
|
|
"""Query comprehensive information about a PyPI package.
|
|
|
|
This tool retrieves detailed information about a Python package from PyPI,
|
|
including metadata, description, author information, dependencies, and more.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to query (e.g., 'requests', 'django')
|
|
|
|
Returns:
|
|
Dictionary containing comprehensive package information including:
|
|
- Basic metadata (name, version, summary, description)
|
|
- Author and maintainer information
|
|
- License and project URLs
|
|
- Python version requirements
|
|
- Dependencies and classifiers
|
|
- Version history summary
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Querying package info for {package_name}")
|
|
result = await query_package_info(package_name)
|
|
logger.info(f"Successfully retrieved info for package: {package_name}")
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error querying package {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 querying package {package_name}: {e}")
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_package_versions(package_name: str) -> dict[str, Any]:
|
|
"""Get version information for a PyPI package.
|
|
|
|
This tool retrieves comprehensive version information for a Python package,
|
|
including all available versions, release details, and distribution formats.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to query (e.g., 'requests', 'numpy')
|
|
|
|
Returns:
|
|
Dictionary containing version information including:
|
|
- Latest version and total version count
|
|
- List of all available versions (sorted)
|
|
- Recent versions with release details
|
|
- Distribution format information (wheel, source)
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Querying versions for {package_name}")
|
|
result = await query_package_versions(package_name)
|
|
logger.info(f"Successfully retrieved versions for package: {package_name}")
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error querying versions 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 querying versions for {package_name}: {e}")
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_package_dependencies(
|
|
package_name: str,
|
|
version: str | None = None,
|
|
include_transitive: bool = False,
|
|
max_depth: int = 5,
|
|
python_version: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Get dependency information for a PyPI package.
|
|
|
|
This tool retrieves comprehensive dependency information for a Python package,
|
|
including runtime dependencies, development dependencies, and optional dependencies.
|
|
When include_transitive=True, provides complete dependency tree analysis.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to query (e.g., 'django', 'flask')
|
|
version: Specific version to query (optional, defaults to latest version)
|
|
include_transitive: Whether to include transitive dependencies (default: False)
|
|
max_depth: Maximum recursion depth for transitive dependencies (default: 5)
|
|
python_version: Target Python version for dependency filtering (optional)
|
|
|
|
Returns:
|
|
Dictionary containing dependency information including:
|
|
- Runtime dependencies and development dependencies
|
|
- Optional dependency groups
|
|
- Python version requirements
|
|
- Dependency counts and summary statistics
|
|
- Transitive dependency tree (if include_transitive=True)
|
|
- Circular dependency detection
|
|
- Performance impact analysis
|
|
- Complexity scoring
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(
|
|
f"MCP tool: Querying dependencies for {package_name}"
|
|
+ (f" version {version}" if version else " (latest)")
|
|
+ (
|
|
f" with transitive dependencies (max depth: {max_depth})"
|
|
if include_transitive
|
|
else " (direct only)"
|
|
)
|
|
)
|
|
result = await query_package_dependencies(
|
|
package_name, version, include_transitive, max_depth, python_version
|
|
)
|
|
logger.info(f"Successfully retrieved dependencies for package: {package_name}")
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error querying dependencies for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
"version": version,
|
|
"include_transitive": include_transitive,
|
|
"max_depth": max_depth,
|
|
"python_version": python_version,
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error querying dependencies for {package_name}: {e}")
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
"version": version,
|
|
"include_transitive": include_transitive,
|
|
"max_depth": max_depth,
|
|
"python_version": python_version,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def check_package_python_compatibility(
|
|
package_name: str, target_python_version: str, use_cache: bool = True
|
|
) -> dict[str, Any]:
|
|
"""Check if a package is compatible with a specific Python version.
|
|
|
|
This tool analyzes a package's Python version requirements and determines
|
|
if it's compatible with your target Python version.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to check (e.g., 'django', 'requests')
|
|
target_python_version: Target Python version to check (e.g., '3.9', '3.10.5', '3.11')
|
|
use_cache: Whether to use cached package data (default: True)
|
|
|
|
Returns:
|
|
Dictionary containing detailed compatibility information including:
|
|
- Compatibility status (True/False)
|
|
- Source of compatibility information (requires_python or classifiers)
|
|
- Detailed analysis and suggestions
|
|
- Package version requirements
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(
|
|
f"MCP tool: Checking Python {target_python_version} compatibility for {package_name}"
|
|
)
|
|
result = await check_python_compatibility(
|
|
package_name, target_python_version, use_cache
|
|
)
|
|
logger.info(f"Compatibility check completed for {package_name}")
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error checking compatibility for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
"target_python_version": target_python_version,
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error checking compatibility for {package_name}: {e}")
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
"target_python_version": target_python_version,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_package_compatible_python_versions(
|
|
package_name: str, python_versions: list[str] | None = None, use_cache: bool = True
|
|
) -> dict[str, Any]:
|
|
"""Get all Python versions compatible with a package.
|
|
|
|
This tool analyzes a package and returns which Python versions are
|
|
compatible with it, along with recommendations.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to analyze (e.g., 'numpy', 'pandas')
|
|
python_versions: List of Python versions to check (optional, defaults to common versions)
|
|
use_cache: Whether to use cached package data (default: True)
|
|
|
|
Returns:
|
|
Dictionary containing compatibility information including:
|
|
- List of compatible Python versions
|
|
- List of incompatible versions with reasons
|
|
- Compatibility rate and recommendations
|
|
- Package version requirements
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Getting compatible Python versions for {package_name}")
|
|
result = await get_compatible_python_versions(
|
|
package_name, python_versions, use_cache
|
|
)
|
|
logger.info(f"Compatible versions analysis completed for {package_name}")
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error getting compatible versions 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 compatible versions for {package_name}: {e}"
|
|
)
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def resolve_dependencies(
|
|
package_name: str,
|
|
python_version: str | None = None,
|
|
include_extras: list[str] | None = None,
|
|
include_dev: bool = False,
|
|
max_depth: int = 5,
|
|
) -> dict[str, Any]:
|
|
"""Resolve all dependencies for a PyPI package recursively.
|
|
|
|
This tool performs comprehensive dependency resolution for a Python package,
|
|
analyzing the complete dependency tree including transitive dependencies.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to analyze (e.g., 'pyside2', 'django')
|
|
python_version: Target Python version for dependency filtering (e.g., '3.10', '3.11')
|
|
include_extras: List of extra dependency groups to include. These are optional
|
|
dependency groups defined by the package (e.g., ['socks'] for requests,
|
|
['argon2', 'bcrypt'] for django, ['test', 'doc'] for setuptools). Check the
|
|
package's PyPI page or use the provides_extra field to see available extras.
|
|
include_dev: Whether to include development dependencies (default: False)
|
|
max_depth: Maximum recursion depth for dependency resolution (default: 5)
|
|
|
|
Returns:
|
|
Dictionary containing comprehensive dependency analysis including:
|
|
- Complete dependency tree with all transitive dependencies
|
|
- Dependency categorization (runtime, development, extras)
|
|
- Package metadata for each dependency
|
|
- Summary statistics and analysis
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(
|
|
f"MCP tool: Resolving dependencies for {package_name} "
|
|
f"(Python {python_version}, extras: {include_extras})"
|
|
)
|
|
result = await resolve_package_dependencies(
|
|
package_name=package_name,
|
|
python_version=python_version,
|
|
include_extras=include_extras,
|
|
include_dev=include_dev,
|
|
max_depth=max_depth,
|
|
)
|
|
logger.info(f"Successfully resolved dependencies for package: {package_name}")
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error resolving dependencies for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
"python_version": python_version,
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error resolving dependencies for {package_name}: {e}")
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
"python_version": python_version,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def download_package(
|
|
package_name: str,
|
|
download_dir: str = "./downloads",
|
|
python_version: str | None = None,
|
|
include_extras: list[str] | None = None,
|
|
include_dev: bool = False,
|
|
prefer_wheel: bool = True,
|
|
verify_checksums: bool = True,
|
|
max_depth: int = 5,
|
|
) -> dict[str, Any]:
|
|
"""Download a PyPI package and all its dependencies to local directory.
|
|
|
|
This tool downloads a Python package and all its dependencies, providing
|
|
comprehensive package collection for offline installation or analysis.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to download (e.g., 'pyside2', 'requests')
|
|
download_dir: Local directory to download packages to (default: './downloads')
|
|
python_version: Target Python version for compatibility (e.g., '3.10', '3.11')
|
|
include_extras: List of extra dependency groups to include. These are optional
|
|
dependency groups defined by the package (e.g., ['socks'] for requests,
|
|
['argon2', 'bcrypt'] for django). Check the package's PyPI page to see available extras.
|
|
include_dev: Whether to include development dependencies (default: False)
|
|
prefer_wheel: Whether to prefer wheel files over source distributions (default: True)
|
|
verify_checksums: Whether to verify downloaded file checksums (default: True)
|
|
max_depth: Maximum dependency resolution depth (default: 5)
|
|
|
|
Returns:
|
|
Dictionary containing download results including:
|
|
- Download statistics and file information
|
|
- Dependency resolution results
|
|
- File verification results
|
|
- Success/failure summary for each package
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(
|
|
f"MCP tool: Downloading {package_name} and dependencies to {download_dir} "
|
|
f"(Python {python_version})"
|
|
)
|
|
result = await download_package_with_dependencies(
|
|
package_name=package_name,
|
|
download_dir=download_dir,
|
|
python_version=python_version,
|
|
include_extras=include_extras,
|
|
include_dev=include_dev,
|
|
prefer_wheel=prefer_wheel,
|
|
verify_checksums=verify_checksums,
|
|
max_depth=max_depth,
|
|
)
|
|
logger.info(f"Successfully downloaded {package_name} and dependencies")
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error downloading {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
"download_dir": download_dir,
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error downloading {package_name}: {e}")
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
"download_dir": download_dir,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_download_statistics(
|
|
package_name: str, period: str = "month", use_cache: bool = True
|
|
) -> dict[str, Any]:
|
|
"""Get download statistics for a PyPI package.
|
|
|
|
This tool retrieves comprehensive download statistics for a Python package,
|
|
including recent download counts, trends, and analysis.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to analyze (e.g., 'requests', 'numpy')
|
|
period: Time period for recent downloads ('day', 'week', 'month', default: 'month')
|
|
use_cache: Whether to use cached data for faster responses (default: True)
|
|
|
|
Returns:
|
|
Dictionary containing download statistics including:
|
|
- Recent download counts (last day/week/month)
|
|
- Package metadata and repository information
|
|
- Download trends and growth analysis
|
|
- Data source and timestamp information
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(
|
|
f"MCP tool: Getting download statistics for {package_name} (period: {period})"
|
|
)
|
|
result = await get_package_download_stats(package_name, period, use_cache)
|
|
logger.info(
|
|
f"Successfully retrieved download statistics for package: {package_name}"
|
|
)
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error getting download statistics for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
"period": period,
|
|
}
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Unexpected error getting download statistics for {package_name}: {e}"
|
|
)
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
"period": period,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_download_trends(
|
|
package_name: str, include_mirrors: bool = False, use_cache: bool = True
|
|
) -> dict[str, Any]:
|
|
"""Get download trends and time series for a PyPI package.
|
|
|
|
This tool retrieves detailed download trends and time series data for a Python package,
|
|
providing insights into download patterns over the last 180 days.
|
|
|
|
Args:
|
|
package_name: The name of the PyPI package to analyze (e.g., 'django', 'flask')
|
|
include_mirrors: Whether to include mirror downloads in analysis (default: False)
|
|
use_cache: Whether to use cached data for faster responses (default: True)
|
|
|
|
Returns:
|
|
Dictionary containing download trends including:
|
|
- Time series data for the last 180 days
|
|
- Trend analysis (increasing/decreasing/stable)
|
|
- Peak download periods and statistics
|
|
- Average daily downloads and growth indicators
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(
|
|
f"MCP tool: Getting download trends for {package_name} "
|
|
f"(include_mirrors: {include_mirrors})"
|
|
)
|
|
result = await get_package_download_trends(
|
|
package_name, include_mirrors, use_cache
|
|
)
|
|
logger.info(
|
|
f"Successfully retrieved download trends for package: {package_name}"
|
|
)
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError) as e:
|
|
logger.error(f"Error getting download trends for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
"include_mirrors": include_mirrors,
|
|
}
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Unexpected error getting download trends for {package_name}: {e}"
|
|
)
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"package_name": package_name,
|
|
"include_mirrors": include_mirrors,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_top_downloaded_packages(
|
|
period: str = "month", limit: int = 20
|
|
) -> dict[str, Any]:
|
|
"""Get the most downloaded PyPI packages.
|
|
|
|
This tool retrieves a list of the most popular Python packages by download count,
|
|
helping you discover trending and widely-used packages in the Python ecosystem.
|
|
|
|
Args:
|
|
period: Time period for download ranking ('day', 'week', 'month', default: 'month')
|
|
limit: Maximum number of packages to return (default: 20, max: 50)
|
|
|
|
Returns:
|
|
Dictionary containing top packages information including:
|
|
- Ranked list of packages with download counts
|
|
- Package metadata and repository links
|
|
- Period and ranking information
|
|
- Data source and limitations
|
|
|
|
Note:
|
|
Due to API limitations, this tool provides results based on known popular packages.
|
|
For comprehensive data analysis, consider using Google BigQuery with PyPI datasets.
|
|
"""
|
|
try:
|
|
# Limit the maximum number of packages to prevent excessive API calls
|
|
actual_limit = min(limit, 50)
|
|
|
|
logger.info(
|
|
f"MCP tool: Getting top {actual_limit} packages for period: {period}"
|
|
)
|
|
result = await get_top_packages_by_downloads(period, actual_limit)
|
|
logger.info("Successfully retrieved top packages list")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error getting top packages: {e}")
|
|
return {
|
|
"error": f"Unexpected error: {e}",
|
|
"error_type": "UnexpectedError",
|
|
"period": period,
|
|
"limit": limit,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def search_pypi_packages(
|
|
query: str,
|
|
limit: int = 20,
|
|
python_versions: list[str] | None = None,
|
|
licenses: list[str] | None = None,
|
|
categories: list[str] | None = None,
|
|
min_downloads: int | None = None,
|
|
maintenance_status: str | None = None,
|
|
has_wheels: bool | None = None,
|
|
sort_by: str = "relevance",
|
|
sort_desc: bool = True,
|
|
semantic_search: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""Search PyPI packages with advanced filtering and sorting.
|
|
|
|
This tool provides comprehensive search functionality for PyPI packages with
|
|
advanced filtering options, multiple sorting criteria, and semantic search capabilities.
|
|
|
|
Args:
|
|
query: Search query string (required)
|
|
limit: Maximum number of results to return (default: 20, max: 100)
|
|
python_versions: Filter by Python versions (e.g., ["3.9", "3.10", "3.11"])
|
|
licenses: Filter by license types (e.g., ["mit", "apache", "bsd", "gpl"])
|
|
categories: Filter by categories (e.g., ["web", "data-science", "testing"])
|
|
min_downloads: Minimum monthly downloads threshold
|
|
maintenance_status: Filter by maintenance status ("active", "maintained", "stale", "abandoned")
|
|
has_wheels: Filter packages that have wheel distributions (true/false)
|
|
sort_by: Sort field ("relevance", "popularity", "recency", "quality", "name", "downloads")
|
|
sort_desc: Sort in descending order (default: true)
|
|
semantic_search: Use semantic search on package descriptions (default: false)
|
|
|
|
Returns:
|
|
Dictionary containing search results with packages, metadata, and filtering info
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If search query is empty or invalid
|
|
SearchError: If search operation fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP search_pypi_packages called with query='{query}', limit={limit}")
|
|
logger.info(f"MCP search_pypi_packages parameters: python_versions={python_versions}, licenses={licenses}, categories={categories}")
|
|
logger.info(f"MCP search_pypi_packages parameters: min_downloads={min_downloads}, maintenance_status={maintenance_status}, has_wheels={has_wheels}")
|
|
logger.info(f"MCP search_pypi_packages parameters: sort_by={sort_by}, sort_desc={sort_desc}, semantic_search={semantic_search}")
|
|
|
|
result = await search_packages(
|
|
query=query,
|
|
limit=limit,
|
|
python_versions=python_versions,
|
|
licenses=licenses,
|
|
categories=categories,
|
|
min_downloads=min_downloads,
|
|
maintenance_status=maintenance_status,
|
|
has_wheels=has_wheels,
|
|
sort_by=sort_by,
|
|
sort_desc=sort_desc,
|
|
semantic_search=semantic_search,
|
|
)
|
|
logger.info(f"MCP search_pypi_packages raw result keys: {list(result.keys())}")
|
|
logger.info(f"MCP search_pypi_packages raw result total_found: {result.get('total_found')}")
|
|
logger.info(f"MCP search_pypi_packages raw result packages count: {len(result.get('packages', []))}")
|
|
logger.info(f"MCP search_pypi_packages returning: {len(result.get('packages', []))} packages")
|
|
return result
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError, SearchError) as e:
|
|
logger.error(f"Known error searching packages for '{query}': {e}")
|
|
return {
|
|
"error": f"Search failed: {e}",
|
|
"error_type": type(e).__name__,
|
|
"query": query,
|
|
"limit": limit,
|
|
"packages": [],
|
|
"total_found": 0,
|
|
"returned_count": 0,
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error searching packages for '{query}': {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return {
|
|
"error": f"Search failed: {e}",
|
|
"error_type": "SearchError",
|
|
"query": query,
|
|
"limit": limit,
|
|
"packages": [],
|
|
"total_found": 0,
|
|
"returned_count": 0,
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def search_packages_by_category(
|
|
category: str,
|
|
limit: int = 20,
|
|
sort_by: str = "popularity",
|
|
python_version: str | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Search packages by category with popularity sorting.
|
|
|
|
This tool searches for packages in specific categories, making it easy to discover
|
|
relevant packages for particular use cases or domains.
|
|
|
|
Args:
|
|
category: Category to search ("web", "data-science", "database", "testing", "cli",
|
|
"security", "networking", "dev-tools", "cloud", "gui")
|
|
limit: Maximum number of results to return (default: 20)
|
|
sort_by: Sort field (default: "popularity")
|
|
python_version: Filter by Python version compatibility (e.g., "3.10")
|
|
|
|
Returns:
|
|
Dictionary containing categorized search results
|
|
|
|
Raises:
|
|
SearchError: If category search fails
|
|
"""
|
|
try:
|
|
return await search_by_category(
|
|
category=category,
|
|
limit=limit,
|
|
sort_by=sort_by,
|
|
python_version=python_version,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error searching category '{category}': {e}")
|
|
return {
|
|
"error": f"Category search failed: {e}",
|
|
"error_type": "SearchError",
|
|
"category": category,
|
|
"limit": limit,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def find_package_alternatives(
|
|
package_name: str,
|
|
limit: int = 10,
|
|
include_similar: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Find alternative packages to a given package.
|
|
|
|
This tool analyzes a package's functionality and finds similar or alternative
|
|
packages that could serve the same purpose, useful for evaluating options
|
|
or finding replacements.
|
|
|
|
Args:
|
|
package_name: Name of the package to find alternatives for
|
|
limit: Maximum number of alternatives to return (default: 10)
|
|
include_similar: Include packages with similar functionality (default: true)
|
|
|
|
Returns:
|
|
Dictionary containing alternative packages with analysis and recommendations
|
|
|
|
Raises:
|
|
PackageNotFoundError: If the target package is not found
|
|
SearchError: If alternatives search fails
|
|
"""
|
|
try:
|
|
return await find_alternatives(
|
|
package_name=package_name,
|
|
limit=limit,
|
|
include_similar=include_similar,
|
|
)
|
|
except (InvalidPackageNameError, PackageNotFoundError, NetworkError):
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error finding alternatives for '{package_name}': {e}")
|
|
return {
|
|
"error": f"Alternatives search failed: {e}",
|
|
"error_type": "SearchError",
|
|
"package_name": package_name,
|
|
"limit": limit,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_trending_pypi_packages(
|
|
category: str | None = None,
|
|
time_period: str = "week",
|
|
limit: int = 20,
|
|
) -> dict[str, Any]:
|
|
"""Get trending packages based on recent download activity.
|
|
|
|
This tool identifies packages that are gaining popularity or have high
|
|
recent download activity, useful for discovering emerging trends in the
|
|
Python ecosystem.
|
|
|
|
Args:
|
|
category: Optional category filter ("web", "data-science", "database", etc.)
|
|
time_period: Time period for trending analysis ("day", "week", "month")
|
|
limit: Maximum number of packages to return (default: 20)
|
|
|
|
Returns:
|
|
Dictionary containing trending packages with analysis and metrics
|
|
|
|
Raises:
|
|
SearchError: If trending analysis fails
|
|
"""
|
|
try:
|
|
return await get_trending_packages(
|
|
category=category,
|
|
time_period=time_period,
|
|
limit=limit,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error getting trending packages (category: {category}): {e}")
|
|
return {
|
|
"error": f"Trending analysis failed: {e}",
|
|
"error_type": "SearchError",
|
|
"category": category,
|
|
"time_period": time_period,
|
|
"limit": limit,
|
|
}
|
|
|
|
|
|
# Publishing Tools MCP Endpoints
|
|
|
|
@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 distribution files (.whl, .tar.gz) to PyPI
|
|
or TestPyPI, providing comprehensive upload management 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, statistics, and verification info
|
|
|
|
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 with {result.get('successful_uploads', 0)} successful uploads")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error uploading package: {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 validates PyPI API tokens and checks authentication status,
|
|
helping ensure proper credentials before performing upload operations.
|
|
|
|
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 and status
|
|
|
|
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, test_pypi)
|
|
logger.info(f"Credential check completed: {'valid' if result.get('valid') else 'invalid'}")
|
|
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 comprehensive upload history for a package,
|
|
including file information, upload times, and statistics.
|
|
|
|
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, api_token, test_pypi, limit)
|
|
logger.info(f"Retrieved {len(result.get('upload_history', []))} upload records")
|
|
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 comprehensive
|
|
safety checks and dry-run capability. PyPI deletion is very restricted.
|
|
|
|
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, version, api_token, test_pypi, confirm_deletion, dry_run
|
|
)
|
|
logger.info(f"Deletion {'simulation' if dry_run else 'attempt'} completed")
|
|
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 provides maintainer management functionality for PyPI packages,
|
|
including listing current maintainers and guidance for adding/removing.
|
|
|
|
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, action, username, api_token, test_pypi)
|
|
logger.info(f"Maintainer management completed for {package_name}")
|
|
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 comprehensive account information including
|
|
limitations, features, and useful links for PyPI account management.
|
|
|
|
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, 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 Endpoints
|
|
|
|
@mcp.tool()
|
|
async def update_package_metadata_tool(
|
|
package_name: str,
|
|
metadata_updates: dict[str, Any],
|
|
api_token: str | None = None,
|
|
test_pypi: bool = False,
|
|
validate_changes: bool = True,
|
|
dry_run: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""Update PyPI package metadata and configuration.
|
|
|
|
This tool updates package metadata including description, keywords,
|
|
classifiers, and other package information on PyPI.
|
|
|
|
Args:
|
|
package_name: Name of the package to update
|
|
metadata_updates: Dictionary of metadata fields to update
|
|
api_token: PyPI API token (or use PYPI_API_TOKEN env var)
|
|
test_pypi: Whether to use TestPyPI instead of production PyPI
|
|
validate_changes: Whether to validate metadata before applying
|
|
dry_run: If True, only validate without applying changes
|
|
|
|
Returns:
|
|
Dictionary containing update results and validation info
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is invalid
|
|
PackageNotFoundError: If package is not found
|
|
PyPIPermissionError: If update is not permitted
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Updating metadata for {package_name}")
|
|
result = await update_package_metadata(
|
|
package_name, metadata_updates, api_token, test_pypi, validate_changes, dry_run
|
|
)
|
|
logger.info(f"Metadata update {'simulated' if dry_run else 'completed'} for {package_name}")
|
|
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,
|
|
"test_pypi": test_pypi,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def manage_package_urls_tool(
|
|
package_name: str,
|
|
action: str,
|
|
url_type: str | None = None,
|
|
url_value: str | None = None,
|
|
api_token: str | None = None,
|
|
test_pypi: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""Manage package URLs (homepage, documentation, repository, etc.).
|
|
|
|
This tool manages package URL configurations including adding, updating,
|
|
removing, and listing various URL types for a PyPI package.
|
|
|
|
Args:
|
|
package_name: Name of the package
|
|
action: Action to perform ('list', 'add', 'update', 'remove')
|
|
url_type: Type of URL ('homepage', 'documentation', 'repository', 'bug_tracker', etc.)
|
|
url_value: URL value (required for add/update 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 URL 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 URLs for {package_name}: {action}")
|
|
result = await manage_package_urls(
|
|
package_name, action, url_type, url_value, api_token, test_pypi
|
|
)
|
|
logger.info(f"URL management completed for {package_name}")
|
|
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,
|
|
"action": action,
|
|
"test_pypi": test_pypi,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def set_package_visibility_tool(
|
|
package_name: str,
|
|
visibility: str,
|
|
api_token: str | None = None,
|
|
test_pypi: bool = False,
|
|
confirmation_required: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Set package visibility and access controls.
|
|
|
|
This tool manages package visibility settings including public/private
|
|
status and access controls for PyPI packages.
|
|
|
|
Args:
|
|
package_name: Name of the package
|
|
visibility: Visibility setting ('public', 'private', 'unlisted')
|
|
api_token: PyPI API token (or use PYPI_API_TOKEN env var)
|
|
test_pypi: Whether to use TestPyPI instead of production PyPI
|
|
confirmation_required: Whether to require explicit confirmation
|
|
|
|
Returns:
|
|
Dictionary containing visibility change 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: Setting visibility for {package_name} to {visibility}")
|
|
result = await set_package_visibility(
|
|
package_name, visibility, api_token, test_pypi, confirmation_required
|
|
)
|
|
logger.info(f"Visibility update completed for {package_name}")
|
|
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,
|
|
"test_pypi": test_pypi,
|
|
}
|
|
|
|
|
|
@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,
|
|
) -> dict[str, Any]:
|
|
"""Manage package keywords and tags.
|
|
|
|
This tool manages package keywords and tags for better discoverability,
|
|
including adding, removing, and updating keyword sets.
|
|
|
|
Args:
|
|
package_name: Name of the package
|
|
action: Action to perform ('list', 'add', 'remove', 'replace')
|
|
keywords: List of keywords (required for add/remove/replace 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 keyword 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 keywords for {package_name}: {action}")
|
|
result = await manage_package_keywords(
|
|
package_name, action, keywords, api_token, test_pypi
|
|
)
|
|
logger.info(f"Keyword management completed for {package_name}")
|
|
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,
|
|
"test_pypi": test_pypi,
|
|
}
|
|
|
|
|
|
# Analytics Tools MCP Endpoints
|
|
|
|
@mcp.tool()
|
|
async def get_pypi_package_analytics_tool(
|
|
package_name: str,
|
|
time_period: str = "month",
|
|
include_historical: bool = True,
|
|
analytics_scope: str = "comprehensive",
|
|
) -> dict[str, Any]:
|
|
"""Get comprehensive analytics for a PyPI package.
|
|
|
|
This tool provides detailed analytics including download statistics,
|
|
version distribution, platform analytics, and quality metrics.
|
|
|
|
Args:
|
|
package_name: Name of the package to analyze
|
|
time_period: Time period for analytics ('day', 'week', 'month', 'year')
|
|
include_historical: Whether to include historical trend data
|
|
analytics_scope: Scope of analytics ('basic', 'comprehensive', 'detailed')
|
|
|
|
Returns:
|
|
Dictionary containing comprehensive package analytics
|
|
|
|
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 analytics for {package_name}")
|
|
result = await get_pypi_package_analytics(
|
|
package_name, time_period, include_historical, analytics_scope
|
|
)
|
|
logger.info(f"Analytics retrieved for {package_name}")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error getting analytics for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
"time_period": time_period,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_pypi_security_alerts_tool(
|
|
package_name: str,
|
|
include_dependencies: bool = True,
|
|
severity_filter: str | None = None,
|
|
alert_sources: list[str] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Get security alerts and vulnerability information for a package.
|
|
|
|
This tool provides comprehensive security analysis including known
|
|
vulnerabilities, security advisories, and dependency risk assessment.
|
|
|
|
Args:
|
|
package_name: Name of the package to analyze
|
|
include_dependencies: Whether to include dependency vulnerabilities
|
|
severity_filter: Filter by severity ('low', 'medium', 'high', 'critical')
|
|
alert_sources: List of alert sources to check
|
|
|
|
Returns:
|
|
Dictionary containing security alerts and analysis
|
|
|
|
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 security alerts for {package_name}")
|
|
result = await get_pypi_security_alerts(
|
|
package_name, include_dependencies, severity_filter, alert_sources
|
|
)
|
|
logger.info(f"Security analysis completed for {package_name}")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error getting security alerts for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_pypi_package_rankings_tool(
|
|
package_name: str,
|
|
ranking_metrics: list[str] | None = None,
|
|
search_terms: list[str] | None = None,
|
|
include_competitors: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Get package ranking and discoverability analysis.
|
|
|
|
This tool analyzes package rankings in search results, popularity
|
|
metrics, and competitive positioning within the Python ecosystem.
|
|
|
|
Args:
|
|
package_name: Name of the package to analyze
|
|
ranking_metrics: List of metrics to analyze ('downloads', 'stars', 'search_rank')
|
|
search_terms: Search terms to check rankings for
|
|
include_competitors: Whether to include competitor analysis
|
|
|
|
Returns:
|
|
Dictionary containing ranking analysis and insights
|
|
|
|
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 rankings for {package_name}")
|
|
result = await get_pypi_package_rankings(
|
|
package_name, ranking_metrics, search_terms, include_competitors
|
|
)
|
|
logger.info(f"Ranking analysis completed for {package_name}")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error getting rankings for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def analyze_pypi_competition_tool(
|
|
package_name: str,
|
|
analysis_depth: str = "comprehensive",
|
|
competitor_limit: int = 5,
|
|
include_market_analysis: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Analyze competitive landscape for a PyPI package.
|
|
|
|
This tool provides comprehensive competitive analysis including
|
|
competitor identification, feature comparison, and market positioning.
|
|
|
|
Args:
|
|
package_name: Name of the package to analyze
|
|
analysis_depth: Depth of analysis ('basic', 'comprehensive', 'detailed')
|
|
competitor_limit: Maximum number of competitors to analyze
|
|
include_market_analysis: Whether to include market share analysis
|
|
|
|
Returns:
|
|
Dictionary containing competitive analysis and insights
|
|
|
|
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, analysis_depth, competitor_limit, include_market_analysis
|
|
)
|
|
logger.info(f"Competition analysis completed for {package_name}")
|
|
return result
|
|
except Exception 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,
|
|
}
|
|
|
|
|
|
# Discovery Tools MCP Endpoints
|
|
|
|
@mcp.tool()
|
|
async def monitor_pypi_new_releases_tool(
|
|
time_window: str = "24h",
|
|
category_filter: str | None = None,
|
|
min_downloads: int | None = None,
|
|
include_prereleases: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""Monitor recent PyPI package releases and updates.
|
|
|
|
This tool tracks new packages and version releases on PyPI,
|
|
providing insights into the latest developments in the Python ecosystem.
|
|
|
|
Args:
|
|
time_window: Time window to monitor ('1h', '6h', '24h', '7d')
|
|
category_filter: Filter by package category
|
|
min_downloads: Minimum download threshold for inclusion
|
|
include_prereleases: Whether to include pre-release versions
|
|
|
|
Returns:
|
|
Dictionary containing recent releases and analysis
|
|
|
|
Raises:
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Monitoring new releases (window: {time_window})")
|
|
result = await monitor_pypi_new_releases(
|
|
time_window, category_filter, min_downloads, include_prereleases
|
|
)
|
|
logger.info(f"Found {len(result.get('new_releases', []))} new releases")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error monitoring new releases: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"time_window": time_window,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_pypi_trending_today_tool(
|
|
category: str | None = None,
|
|
limit: int = 20,
|
|
trending_metric: str = "downloads",
|
|
) -> dict[str, Any]:
|
|
"""Get trending PyPI packages for today.
|
|
|
|
This tool identifies packages that are trending today based on
|
|
various metrics like download spikes, new releases, or community activity.
|
|
|
|
Args:
|
|
category: Filter by package category
|
|
limit: Maximum number of trending packages to return
|
|
trending_metric: Metric to base trending on ('downloads', 'stars', 'releases')
|
|
|
|
Returns:
|
|
Dictionary containing trending packages and analysis
|
|
|
|
Raises:
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Getting trending packages for today")
|
|
result = await get_pypi_trending_today(category, limit, trending_metric)
|
|
logger.info(f"Found {len(result.get('trending_packages', []))} trending packages")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error getting trending packages: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"category": category,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def search_pypi_by_maintainer_tool(
|
|
maintainer_name: str,
|
|
search_scope: str = "all",
|
|
include_statistics: bool = True,
|
|
sort_by: str = "popularity",
|
|
) -> dict[str, Any]:
|
|
"""Search PyPI packages by maintainer or author.
|
|
|
|
This tool finds all packages maintained by a specific person or organization,
|
|
providing insights into their contribution to the Python ecosystem.
|
|
|
|
Args:
|
|
maintainer_name: Name of the maintainer to search for
|
|
search_scope: Scope of search ('author', 'maintainer', 'all')
|
|
include_statistics: Whether to include maintainer statistics
|
|
sort_by: Sort packages by ('popularity', 'recency', 'name')
|
|
|
|
Returns:
|
|
Dictionary containing maintainer packages and statistics
|
|
|
|
Raises:
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Searching packages by maintainer: {maintainer_name}")
|
|
result = await search_pypi_by_maintainer(
|
|
maintainer_name, search_scope, include_statistics, sort_by
|
|
)
|
|
logger.info(f"Found {len(result.get('packages', []))} packages")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error searching by maintainer {maintainer_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"maintainer_name": maintainer_name,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_pypi_package_recommendations_tool(
|
|
package_name: str,
|
|
recommendation_type: str = "similar",
|
|
limit: int = 10,
|
|
include_reasoning: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Get personalized package recommendations based on a given package.
|
|
|
|
This tool provides intelligent package recommendations including
|
|
similar packages, complementary tools, and upgrade suggestions.
|
|
|
|
Args:
|
|
package_name: Base package for recommendations
|
|
recommendation_type: Type of recommendations ('similar', 'complementary', 'upgrades')
|
|
limit: Maximum number of recommendations to return
|
|
include_reasoning: Whether to include reasoning for recommendations
|
|
|
|
Returns:
|
|
Dictionary containing package recommendations and analysis
|
|
|
|
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 recommendations for {package_name}")
|
|
result = await get_pypi_package_recommendations(
|
|
package_name, recommendation_type, limit, include_reasoning
|
|
)
|
|
logger.info(f"Generated {len(result.get('recommendations', []))} recommendations")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error getting recommendations for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
}
|
|
|
|
|
|
# Workflow Tools MCP Endpoints
|
|
|
|
@mcp.tool()
|
|
async def validate_pypi_package_name_tool(package_name: str) -> dict[str, Any]:
|
|
"""Validate PyPI package name according to PEP 508 and PyPI requirements.
|
|
|
|
This tool validates package names against PyPI naming conventions,
|
|
checks availability, and provides suggestions for improvements.
|
|
|
|
Args:
|
|
package_name: Package name to validate
|
|
|
|
Returns:
|
|
Dictionary containing validation results and suggestions
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name format is invalid
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Validating package name: {package_name}")
|
|
result = await validate_pypi_package_name(package_name)
|
|
logger.info(f"Package name validation completed: {'valid' if result.get('valid') else 'invalid'}")
|
|
return result
|
|
except Exception 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,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def preview_pypi_package_page_tool(
|
|
package_name: str,
|
|
version: str | None = None,
|
|
include_rendered_content: bool = True,
|
|
check_metadata_completeness: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Preview how a package page will look on PyPI.
|
|
|
|
This tool generates a preview of the PyPI package page including
|
|
rendered README, metadata display, and completeness analysis.
|
|
|
|
Args:
|
|
package_name: Name of the package to preview
|
|
version: Specific version to preview (optional, defaults to latest)
|
|
include_rendered_content: Whether to include rendered README content
|
|
check_metadata_completeness: Whether to analyze metadata completeness
|
|
|
|
Returns:
|
|
Dictionary containing page preview and analysis
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is invalid
|
|
PackageNotFoundError: If package is not found
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Previewing package page for {package_name}")
|
|
result = await preview_pypi_package_page(
|
|
package_name, version, include_rendered_content, check_metadata_completeness
|
|
)
|
|
logger.info(f"Package page preview generated for {package_name}")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error previewing package page for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def check_pypi_upload_requirements_tool(
|
|
package_path: str,
|
|
check_completeness: bool = True,
|
|
validate_metadata: bool = True,
|
|
check_security: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Check if a package meets PyPI upload requirements.
|
|
|
|
This tool validates package structure, metadata, and requirements
|
|
before upload to ensure successful PyPI submission.
|
|
|
|
Args:
|
|
package_path: Path to the package directory or distribution file
|
|
check_completeness: Whether to check metadata completeness
|
|
validate_metadata: Whether to validate metadata format
|
|
check_security: Whether to perform security checks
|
|
|
|
Returns:
|
|
Dictionary containing requirement check results and recommendations
|
|
|
|
Raises:
|
|
FileNotFoundError: If package path doesn't exist
|
|
NetworkError: For network-related errors
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Checking upload requirements for {package_path}")
|
|
result = await check_pypi_upload_requirements(
|
|
package_path, check_completeness, validate_metadata, check_security
|
|
)
|
|
logger.info(f"Upload requirements check completed")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error checking upload requirements for {package_path}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_path": package_path,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_pypi_build_logs_tool(
|
|
package_name: str,
|
|
version: str | None = None,
|
|
build_type: str = "all",
|
|
include_analysis: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Get PyPI package build logs and analysis.
|
|
|
|
This tool retrieves build logs for PyPI packages and provides
|
|
analysis of build issues, warnings, and optimization opportunities.
|
|
|
|
Args:
|
|
package_name: Name of the package
|
|
version: Specific version (optional, defaults to latest)
|
|
build_type: Type of builds to include ('wheel', 'sdist', 'all')
|
|
include_analysis: Whether to include build analysis
|
|
|
|
Returns:
|
|
Dictionary containing build logs and analysis
|
|
|
|
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 build logs for {package_name}")
|
|
result = await get_pypi_build_logs(package_name, version, build_type, include_analysis)
|
|
logger.info(f"Build logs retrieved for {package_name}")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error getting build logs for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_name": package_name,
|
|
}
|
|
|
|
|
|
# Community Tools MCP Endpoints
|
|
|
|
@mcp.tool()
|
|
async def get_pypi_package_reviews_tool(
|
|
package_name: str,
|
|
include_sentiment_analysis: bool = True,
|
|
review_sources: list[str] | None = None,
|
|
time_period: str = "all",
|
|
) -> dict[str, Any]:
|
|
"""Get community reviews and ratings for a PyPI package.
|
|
|
|
This tool aggregates community feedback, reviews, and sentiment
|
|
analysis from various sources to provide comprehensive package insights.
|
|
|
|
Args:
|
|
package_name: Name of the package to get reviews for
|
|
include_sentiment_analysis: Whether to include sentiment analysis
|
|
review_sources: List of sources to check ('github', 'stackoverflow', 'reddit')
|
|
time_period: Time period for reviews ('week', 'month', 'year', 'all')
|
|
|
|
Returns:
|
|
Dictionary containing reviews, ratings, and sentiment analysis
|
|
|
|
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 reviews for {package_name}")
|
|
result = await get_pypi_package_reviews(
|
|
package_name, include_sentiment_analysis, review_sources, time_period
|
|
)
|
|
logger.info(f"Reviews retrieved for {package_name}")
|
|
return result
|
|
except Exception 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,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def manage_pypi_package_discussions_tool(
|
|
package_name: str,
|
|
action: str,
|
|
discussion_settings: dict[str, Any] | None = None,
|
|
moderator_controls: dict[str, Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Manage package discussions and community features.
|
|
|
|
This tool manages community discussion features for PyPI packages
|
|
including enabling discussions, moderation, and configuration.
|
|
|
|
Args:
|
|
package_name: Name of the package
|
|
action: Action to perform ('status', 'enable', 'disable', 'configure', 'moderate')
|
|
discussion_settings: Settings for discussion configuration
|
|
moderator_controls: Moderation controls and settings
|
|
|
|
Returns:
|
|
Dictionary containing discussion 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 discussions for {package_name}: {action}")
|
|
result = await manage_pypi_package_discussions(
|
|
package_name, action, discussion_settings, moderator_controls
|
|
)
|
|
logger.info(f"Discussion management completed for {package_name}")
|
|
return result
|
|
except Exception 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,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_pypi_maintainer_contacts_tool(
|
|
package_name: str,
|
|
contact_types: list[str] | None = None,
|
|
include_social_profiles: bool = False,
|
|
respect_privacy: bool = True,
|
|
) -> dict[str, Any]:
|
|
"""Get maintainer contact information and communication channels.
|
|
|
|
This tool finds maintainer contact information and preferred
|
|
communication channels while respecting privacy preferences.
|
|
|
|
Args:
|
|
package_name: Name of the package
|
|
contact_types: Types of contacts to find ('email', 'github', 'twitter')
|
|
include_social_profiles: Whether to include social media profiles
|
|
respect_privacy: Whether to respect privacy settings and preferences
|
|
|
|
Returns:
|
|
Dictionary containing maintainer contact information
|
|
|
|
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_name}")
|
|
result = await get_pypi_maintainer_contacts(
|
|
package_name, contact_types, include_social_profiles, respect_privacy
|
|
)
|
|
logger.info(f"Maintainer contacts retrieved for {package_name}")
|
|
return result
|
|
except Exception 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,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def scan_pypi_package_security_tool(
|
|
package_name: str,
|
|
version: str | None = None,
|
|
include_dependencies: bool = True,
|
|
severity_filter: str | None = None
|
|
) -> dict[str, Any]:
|
|
"""Scan a PyPI package for security vulnerabilities.
|
|
|
|
This tool performs comprehensive security vulnerability scanning of PyPI packages,
|
|
checking against multiple vulnerability databases including OSV (Open Source Vulnerabilities),
|
|
GitHub Security Advisories, and analyzing package metadata for security indicators.
|
|
|
|
Args:
|
|
package_name: Name of the package to scan for vulnerabilities
|
|
version: Specific version to scan (optional, defaults to latest version)
|
|
include_dependencies: Whether to scan package dependencies for vulnerabilities
|
|
severity_filter: Filter results by severity level (low, medium, high, critical)
|
|
|
|
Returns:
|
|
Dictionary containing comprehensive security scan results including:
|
|
- Total vulnerability count and severity breakdown
|
|
- Direct package vulnerabilities vs dependency vulnerabilities
|
|
- Risk score and level assessment (minimal, low, medium, high, critical)
|
|
- Detailed vulnerability information with IDs, descriptions, and references
|
|
- Package metadata security analysis
|
|
- Actionable security recommendations
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
SearchError: If security scanning fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Scanning security vulnerabilities for {package_name}")
|
|
result = await scan_pypi_package_security(
|
|
package_name, version, include_dependencies, severity_filter
|
|
)
|
|
logger.info(f"Security scan completed for {package_name} - found {result.get('security_summary', {}).get('total_vulnerabilities', 0)} vulnerabilities")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error scanning security for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package": package_name,
|
|
"version": version,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def bulk_scan_package_security_tool(
|
|
package_names: list[str],
|
|
include_dependencies: bool = False,
|
|
severity_threshold: str = "medium"
|
|
) -> dict[str, Any]:
|
|
"""Perform bulk security scanning of multiple PyPI packages.
|
|
|
|
This tool scans multiple packages simultaneously for security vulnerabilities,
|
|
providing a consolidated report with summary statistics and prioritized
|
|
recommendations for addressing security issues across your package ecosystem.
|
|
|
|
Args:
|
|
package_names: List of package names to scan for vulnerabilities
|
|
include_dependencies: Whether to include dependency vulnerability scanning
|
|
severity_threshold: Minimum severity level to report (low, medium, high, critical)
|
|
|
|
Returns:
|
|
Dictionary containing bulk scan results including:
|
|
- Summary statistics (total packages, packages with vulnerabilities, high-risk packages)
|
|
- Detailed scan results for each package
|
|
- Prioritized recommendations for security remediation
|
|
- Scan timestamp and completion status
|
|
|
|
Raises:
|
|
ValueError: If package_names list is empty
|
|
NetworkError: For network-related errors during scanning
|
|
SearchError: If bulk scanning fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Starting bulk security scan of {len(package_names)} packages")
|
|
result = await bulk_scan_package_security(
|
|
package_names, include_dependencies, severity_threshold
|
|
)
|
|
logger.info(f"Bulk security scan completed - {result.get('summary', {}).get('packages_with_vulnerabilities', 0)} packages have vulnerabilities")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error in bulk security scan: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_names": package_names,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def analyze_pypi_package_license_tool(
|
|
package_name: str,
|
|
version: str | None = None,
|
|
include_dependencies: bool = True
|
|
) -> dict[str, Any]:
|
|
"""Analyze license compatibility for a PyPI package.
|
|
|
|
This tool provides comprehensive license analysis including license identification,
|
|
dependency license scanning, compatibility checking, and risk assessment to help
|
|
ensure your project complies with open source license requirements.
|
|
|
|
Args:
|
|
package_name: Name of the package to analyze for license compatibility
|
|
version: Specific version to analyze (optional, defaults to latest version)
|
|
include_dependencies: Whether to analyze dependency licenses for compatibility
|
|
|
|
Returns:
|
|
Dictionary containing comprehensive license analysis including:
|
|
- License identification and normalization (SPDX format)
|
|
- License categorization (permissive, copyleft, proprietary, etc.)
|
|
- Dependency license analysis and compatibility matrix
|
|
- Risk assessment with score and risk level (minimal, low, medium, high, critical)
|
|
- Compatibility analysis highlighting conflicts and review-required combinations
|
|
- Actionable recommendations for license compliance
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
SearchError: If license analysis fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Analyzing license compatibility for {package_name}")
|
|
result = await analyze_pypi_package_license(
|
|
package_name, version, include_dependencies
|
|
)
|
|
logger.info(f"License analysis completed for {package_name} - {result.get('analysis_summary', {}).get('license_conflicts', 0)} conflicts found")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error analyzing license for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package": package_name,
|
|
"version": version,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def check_bulk_license_compliance_tool(
|
|
package_names: list[str],
|
|
target_license: str | None = None
|
|
) -> dict[str, Any]:
|
|
"""Check license compliance for multiple PyPI packages.
|
|
|
|
This tool performs bulk license compliance checking across multiple packages,
|
|
providing a consolidated report to help ensure your entire package ecosystem
|
|
complies with license requirements and identifying potential legal risks.
|
|
|
|
Args:
|
|
package_names: List of package names to check for license compliance
|
|
target_license: Target license for compatibility checking (optional)
|
|
|
|
Returns:
|
|
Dictionary containing bulk compliance analysis including:
|
|
- Summary statistics (total packages, compliant/non-compliant counts)
|
|
- Detailed license analysis for each package
|
|
- High-risk packages requiring immediate attention
|
|
- Unknown license packages needing investigation
|
|
- Prioritized recommendations for compliance remediation
|
|
|
|
Raises:
|
|
ValueError: If package_names list is empty
|
|
NetworkError: For network-related errors during analysis
|
|
SearchError: If bulk compliance checking fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Starting bulk license compliance check for {len(package_names)} packages")
|
|
result = await check_bulk_license_compliance(
|
|
package_names, target_license
|
|
)
|
|
logger.info(f"Bulk license compliance completed - {result.get('summary', {}).get('non_compliant_packages', 0)} non-compliant packages found")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error in bulk license compliance check: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_names": package_names,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def assess_package_health_score_tool(
|
|
package_name: str,
|
|
version: str | None = None,
|
|
include_github_metrics: bool = True
|
|
) -> dict[str, Any]:
|
|
"""Assess comprehensive health and quality of a PyPI package.
|
|
|
|
This tool evaluates package health across multiple dimensions including maintenance,
|
|
popularity, documentation, testing, security practices, compatibility, and metadata
|
|
completeness to provide an overall health score and actionable recommendations.
|
|
|
|
Args:
|
|
package_name: Name of the package to assess for health and quality
|
|
version: Specific version to assess (optional, defaults to latest version)
|
|
include_github_metrics: Whether to fetch GitHub repository metrics for analysis
|
|
|
|
Returns:
|
|
Dictionary containing comprehensive health assessment including:
|
|
- Overall health score (0-100) and level (excellent/good/fair/poor/critical)
|
|
- Category-specific scores (maintenance, popularity, documentation, testing, etc.)
|
|
- Detailed assessment breakdown with indicators and issues for each category
|
|
- GitHub repository metrics (stars, forks, activity) if available
|
|
- Actionable recommendations for health improvements
|
|
- Strengths, weaknesses, and improvement priorities analysis
|
|
|
|
Raises:
|
|
InvalidPackageNameError: If package name is empty or invalid
|
|
PackageNotFoundError: If package is not found on PyPI
|
|
NetworkError: For network-related errors
|
|
SearchError: If health assessment fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Assessing health for {package_name}")
|
|
result = await assess_package_health_score(
|
|
package_name, version, include_github_metrics
|
|
)
|
|
overall_score = result.get("overall_health", {}).get("score", 0)
|
|
health_level = result.get("overall_health", {}).get("level", "unknown")
|
|
logger.info(f"Health assessment completed for {package_name} - score: {overall_score:.1f}/100 ({health_level})")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error assessing health for {package_name}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package": package_name,
|
|
"version": version,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def compare_packages_health_scores_tool(
|
|
package_names: list[str],
|
|
include_github_metrics: bool = False
|
|
) -> dict[str, Any]:
|
|
"""Compare health scores across multiple PyPI packages.
|
|
|
|
This tool performs comparative health analysis across multiple packages,
|
|
providing rankings, insights, and recommendations to help evaluate
|
|
package ecosystem quality and identify the best options.
|
|
|
|
Args:
|
|
package_names: List of package names to compare for health and quality
|
|
include_github_metrics: Whether to include GitHub metrics in the comparison
|
|
|
|
Returns:
|
|
Dictionary containing comparative health analysis including:
|
|
- Detailed health results for each package
|
|
- Health score rankings with best/worst package identification
|
|
- Comparison insights (average scores, score ranges, rankings)
|
|
- Recommendations for package selection and improvements
|
|
- Statistical analysis of health across the package set
|
|
|
|
Raises:
|
|
ValueError: If package_names list is empty
|
|
NetworkError: For network-related errors during analysis
|
|
SearchError: If health comparison fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Starting health comparison for {len(package_names)} packages")
|
|
result = await compare_packages_health_scores(
|
|
package_names, include_github_metrics
|
|
)
|
|
comparison_insights = result.get("comparison_insights", {})
|
|
best_package = comparison_insights.get("best_package", {})
|
|
packages_compared = result.get("packages_compared", 0)
|
|
logger.info(f"Health comparison completed for {packages_compared} packages - best: {best_package.get('name', 'unknown')} ({best_package.get('score', 0):.1f}/100)")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error in health comparison: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"package_names": package_names,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def analyze_requirements_file_tool_mcp(
|
|
file_path: str,
|
|
check_updates: bool = True,
|
|
security_scan: bool = True,
|
|
compatibility_check: bool = True
|
|
) -> dict[str, Any]:
|
|
"""Analyze project requirements file for dependencies, security, and compatibility.
|
|
|
|
This tool provides comprehensive analysis of Python project requirements files
|
|
including dependency parsing, version checking, security vulnerability scanning,
|
|
Python compatibility assessment, and actionable recommendations for improvements.
|
|
|
|
Args:
|
|
file_path: Path to the requirements file (requirements.txt, pyproject.toml, setup.py, etc.)
|
|
check_updates: Whether to check for available package updates
|
|
security_scan: Whether to perform security vulnerability scanning on dependencies
|
|
compatibility_check: Whether to check Python version compatibility for all dependencies
|
|
|
|
Returns:
|
|
Dictionary containing comprehensive requirements analysis including:
|
|
- File information and detected format (requirements.txt, pyproject.toml, etc.)
|
|
- Parsed dependencies with version specifiers and extras
|
|
- Dependency health analysis with specification issues and recommendations
|
|
- Package update analysis showing outdated packages and latest versions
|
|
- Security vulnerability scan results for all dependencies
|
|
- Python version compatibility assessment
|
|
- Overall risk level and actionable improvement recommendations
|
|
|
|
Raises:
|
|
FileNotFoundError: If the requirements file is not found
|
|
NetworkError: For network-related errors during analysis
|
|
SearchError: If requirements analysis fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Analyzing requirements file {file_path}")
|
|
result = await analyze_requirements_file_tool(
|
|
file_path, check_updates, security_scan, compatibility_check
|
|
)
|
|
summary = result.get("analysis_summary", {})
|
|
total_deps = summary.get("total_dependencies", 0)
|
|
risk_level = summary.get("overall_risk_level", "unknown")
|
|
logger.info(f"Requirements analysis completed for {file_path} - {total_deps} dependencies, risk level: {risk_level}")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error analyzing requirements file {file_path}: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"file_path": file_path,
|
|
}
|
|
|
|
|
|
@mcp.tool()
|
|
async def compare_multiple_requirements_files_mcp(
|
|
file_paths: list[str]
|
|
) -> dict[str, Any]:
|
|
"""Compare multiple requirements files to identify differences and conflicts.
|
|
|
|
This tool analyzes multiple requirements files simultaneously to identify
|
|
version conflicts, unique dependencies, and inconsistencies across different
|
|
project configurations or environments.
|
|
|
|
Args:
|
|
file_paths: List of paths to requirements files to compare and analyze
|
|
|
|
Returns:
|
|
Dictionary containing comparative requirements analysis including:
|
|
- Detailed analysis results for each individual file
|
|
- Common packages shared across all files
|
|
- Conflicting package versions between files with specific version details
|
|
- Packages unique to specific files
|
|
- Recommendations for resolving conflicts and standardizing requirements
|
|
- Statistics on package overlap and conflict rates
|
|
|
|
Raises:
|
|
ValueError: If file_paths list is empty
|
|
NetworkError: For network-related errors during analysis
|
|
SearchError: If requirements comparison fails
|
|
"""
|
|
try:
|
|
logger.info(f"MCP tool: Comparing {len(file_paths)} requirements files")
|
|
result = await compare_multiple_requirements_files(file_paths)
|
|
comparison_results = result.get("comparison_results", {})
|
|
conflicts = len(comparison_results.get("conflicting_packages", []))
|
|
total_packages = comparison_results.get("total_unique_packages", 0)
|
|
logger.info(f"Requirements comparison completed - {total_packages} unique packages, {conflicts} conflicts found")
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Error comparing requirements files: {e}")
|
|
return {
|
|
"error": str(e),
|
|
"error_type": type(e).__name__,
|
|
"file_paths": file_paths,
|
|
}
|
|
|
|
|
|
# Register prompt templates following standard MCP workflow:
|
|
# 1. User calls tool → MCP client sends request
|
|
# 2. Tool function executes → Collects necessary data and parameters
|
|
# 3. Call Prompt generator → Pass parameters to corresponding generator
|
|
# 4. Load template → Get template with {{parameter}} placeholders
|
|
# 5. Parameter replacement → Replace {{parameter_name}} with actual values
|
|
# 6. Environment variable customization → Apply user's custom prompt words
|
|
# 7. Return final prompt → As tool's response back to AI
|
|
|
|
|
|
@mcp.prompt()
|
|
async def analyze_package_quality_prompt(
|
|
package_name: str, version: str | None = None
|
|
) -> str:
|
|
"""Generate a comprehensive quality analysis prompt for a PyPI package."""
|
|
# Step 3: Call Prompt generator
|
|
template = await analyze_package_quality(package_name, version)
|
|
|
|
# Step 5: Parameter replacement - replace {{parameter_name}} with actual values
|
|
result = template.replace("{{package_name}}", package_name)
|
|
|
|
# Handle version parameter
|
|
if version:
|
|
version_text = f"version {version}"
|
|
else:
|
|
version_text = ""
|
|
result = result.replace("{{version_text}}", version_text)
|
|
|
|
# Step 7: Return final prompt
|
|
return result
|
|
|
|
|
|
@mcp.prompt()
|
|
async def compare_packages_prompt(
|
|
packages: list[str], use_case: str, criteria: list[str] | None = None
|
|
) -> str:
|
|
"""Generate a detailed comparison prompt for multiple PyPI packages."""
|
|
# Step 3: Call Prompt generator
|
|
template = await compare_packages(packages, use_case, criteria)
|
|
|
|
# Step 5: Parameter replacement
|
|
packages_text = ", ".join(f"'{pkg}'" for pkg in packages)
|
|
result = template.replace("{{packages_text}}", packages_text)
|
|
result = result.replace("{{use_case}}", use_case)
|
|
|
|
# Handle criteria parameter
|
|
if criteria:
|
|
criteria_text = (
|
|
f"\n\nFocus particularly on these criteria: {', '.join(criteria)}"
|
|
)
|
|
else:
|
|
criteria_text = ""
|
|
result = result.replace("{{criteria_text}}", criteria_text)
|
|
|
|
# Step 7: Return final prompt
|
|
return result
|
|
|
|
|
|
@mcp.prompt()
|
|
async def suggest_alternatives_prompt(
|
|
package_name: str, reason: str, requirements: str | None = None
|
|
) -> str:
|
|
"""Generate a prompt for finding package alternatives."""
|
|
# Step 3: Call Prompt generator
|
|
template = await suggest_alternatives(package_name, reason, requirements)
|
|
|
|
# Step 5: Parameter replacement
|
|
result = template.replace("{{package_name}}", package_name)
|
|
|
|
# Handle reason parameter with context mapping
|
|
reason_context = {
|
|
"deprecated": "the package is deprecated or no longer maintained",
|
|
"security": "security vulnerabilities or concerns",
|
|
"performance": "performance issues or requirements",
|
|
"licensing": "licensing conflicts or restrictions",
|
|
"maintenance": "poor maintenance or lack of updates",
|
|
"features": "missing features or functionality gaps",
|
|
}
|
|
reason_text = reason_context.get(reason, reason)
|
|
result = result.replace("{{reason_text}}", reason_text)
|
|
|
|
# Handle requirements parameter
|
|
if requirements:
|
|
requirements_text = f"\n\nSpecific requirements: {requirements}"
|
|
else:
|
|
requirements_text = ""
|
|
result = result.replace("{{requirements_text}}", requirements_text)
|
|
|
|
# Step 7: Return final prompt
|
|
return result
|
|
|
|
|
|
@mcp.prompt()
|
|
async def resolve_dependency_conflicts_prompt(
|
|
conflicts: list[str],
|
|
python_version: str | None = None,
|
|
project_context: str | None = None,
|
|
) -> str:
|
|
"""Generate a prompt for resolving dependency conflicts."""
|
|
messages = await resolve_dependency_conflicts(
|
|
conflicts, python_version, project_context
|
|
)
|
|
return messages[0].text
|
|
|
|
|
|
@mcp.prompt()
|
|
async def plan_version_upgrade_prompt(
|
|
package_name: str,
|
|
current_version: str,
|
|
target_version: str | None = None,
|
|
project_size: str | None = None,
|
|
) -> str:
|
|
"""Generate a prompt for planning package version upgrades."""
|
|
messages = await plan_version_upgrade(
|
|
package_name, current_version, target_version, project_size
|
|
)
|
|
return messages[0].text
|
|
|
|
|
|
@mcp.prompt()
|
|
async def audit_security_risks_prompt(
|
|
packages: list[str],
|
|
environment: str | None = None,
|
|
compliance_requirements: str | None = None,
|
|
) -> str:
|
|
"""Generate a prompt for security risk auditing of packages."""
|
|
messages = await audit_security_risks(
|
|
packages, environment, compliance_requirements
|
|
)
|
|
return messages[0].text
|
|
|
|
|
|
@mcp.prompt()
|
|
async def plan_package_migration_prompt(
|
|
from_package: str,
|
|
to_package: str,
|
|
codebase_size: str = "medium",
|
|
timeline: str | None = None,
|
|
team_size: int | None = None,
|
|
) -> str:
|
|
"""Generate a comprehensive package migration plan prompt."""
|
|
messages = await plan_package_migration(
|
|
from_package, to_package, codebase_size, timeline, team_size
|
|
)
|
|
return messages[0].text
|
|
|
|
|
|
@mcp.prompt()
|
|
async def generate_migration_checklist_prompt(
|
|
migration_type: str, packages_involved: list[str], environment: str = "all"
|
|
) -> str:
|
|
"""Generate a detailed migration checklist prompt."""
|
|
messages = await generate_migration_checklist(
|
|
migration_type, packages_involved, environment
|
|
)
|
|
return messages[0].text
|
|
|
|
|
|
@mcp.prompt()
|
|
async def generate_update_plan_prompt(
|
|
packages: list[str],
|
|
update_strategy: str = "conservative",
|
|
environment_type: str = "production",
|
|
testing_requirements: str | None = None,
|
|
) -> str:
|
|
"""Generate a comprehensive update plan prompt for packages."""
|
|
# Step 3: Call Prompt generator
|
|
template = await generate_update_plan(
|
|
packages, update_strategy, environment_type, testing_requirements
|
|
)
|
|
|
|
# Step 5: Parameter replacement
|
|
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:
|
|
testing_text = f"\n\nTesting requirements: {testing_requirements}"
|
|
else:
|
|
testing_text = ""
|
|
result = result.replace("{{testing_text}}", testing_text)
|
|
|
|
# Step 7: Return final prompt
|
|
return result
|
|
|
|
|
|
# Trending Analysis Prompts
|
|
@mcp.prompt()
|
|
async def analyze_daily_trends_prompt(
|
|
date: str = "today", category: str | None = None, limit: int = 20
|
|
) -> str:
|
|
"""Generate a prompt for analyzing daily PyPI trends."""
|
|
# Step 3: Call Prompt generator
|
|
template = await analyze_daily_trends(date, category, limit)
|
|
|
|
# Step 5: Parameter replacement
|
|
result = template.replace("{{date}}", date)
|
|
result = result.replace("{{limit}}", str(limit))
|
|
|
|
# Handle category filter
|
|
if category:
|
|
category_filter = f" focusing on {category} packages"
|
|
else:
|
|
category_filter = ""
|
|
result = result.replace("{{category_filter}}", category_filter)
|
|
|
|
# Step 7: Return final prompt
|
|
return result
|
|
|
|
|
|
@mcp.prompt()
|
|
async def find_trending_packages_prompt(
|
|
time_period: str = "weekly", trend_type: str = "rising", domain: str | None = None
|
|
) -> str:
|
|
"""Generate a prompt for finding trending packages."""
|
|
# Step 3: Call Prompt generator
|
|
template = await find_trending_packages(time_period, trend_type, domain)
|
|
|
|
# Step 5: Parameter replacement
|
|
result = template.replace("{{time_period}}", time_period)
|
|
result = result.replace("{{trend_type}}", trend_type)
|
|
|
|
# Handle domain filter
|
|
if domain:
|
|
domain_filter = f" in the {domain} domain"
|
|
else:
|
|
domain_filter = ""
|
|
result = result.replace("{{domain_filter}}", domain_filter)
|
|
|
|
# Step 7: Return final prompt
|
|
return result
|
|
|
|
|
|
@mcp.prompt()
|
|
async def track_package_updates_prompt(
|
|
time_range: str = "today", update_type: str = "all", popular_only: bool = False
|
|
) -> str:
|
|
"""Generate a prompt for tracking recent package updates."""
|
|
# Step 3: Call Prompt generator
|
|
template = await track_package_updates(time_range, update_type, popular_only)
|
|
|
|
# Step 5: Parameter replacement
|
|
result = template.replace("{{time_range}}", time_range)
|
|
result = result.replace("{{update_type}}", update_type)
|
|
|
|
# Handle popularity filter
|
|
if popular_only:
|
|
popularity_filter = " (popular packages only)"
|
|
popularity_description = "Popular packages with >1M downloads"
|
|
else:
|
|
popularity_filter = ""
|
|
popularity_description = "All packages in the ecosystem"
|
|
result = result.replace("{{popularity_filter}}", popularity_filter)
|
|
result = result.replace("{{popularity_description}}", popularity_description)
|
|
|
|
# Step 7: Return final prompt
|
|
return result
|
|
|
|
|
|
@click.command()
|
|
@click.option(
|
|
"--log-level",
|
|
default="INFO",
|
|
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"]),
|
|
help="Logging level",
|
|
)
|
|
def main(log_level: str) -> None:
|
|
"""Start the PyPI Query MCP Server."""
|
|
# Set logging level
|
|
logging.getLogger().setLevel(getattr(logging, log_level))
|
|
|
|
logger.info("Starting PyPI Query MCP Server")
|
|
logger.info(f"Log level set to: {log_level}")
|
|
|
|
# Run the FastMCP server (uses STDIO transport by default)
|
|
mcp.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|