feat: add comprehensive security, license, health, and requirements analysis tools

- 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.
This commit is contained in:
Ryan Malloy 2025-09-06 10:28:57 -06:00
parent 48f1027c3e
commit 43f36b60fb
12 changed files with 4688 additions and 53 deletions

View file

@ -70,6 +70,18 @@ from .tools import (
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
@ -1929,6 +1941,390 @@ async def get_pypi_maintainer_contacts_tool(
}
@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