mcp-ai-agent-guidelines
Version:
A comprehensive Model Context Protocol server providing advanced tools, resources, and prompts for implementing AI agent best practices
292 lines • 12.4 kB
JavaScript
/**
* Python Parsers (requirements.txt, pyproject.toml)
*/
import { BaseParser, PATTERNS } from "./base.js";
export class PythonRequirementsParser extends BaseParser {
static deprecatedPackages = {
fabric: {
reason: "Fabric 1.x is deprecated",
alternative: "Use fabric>=2.0 or invoke",
},
pycrypto: {
reason: "No longer maintained, security vulnerabilities",
alternative: "Use pycryptodome",
},
nose: { reason: "No longer maintained", alternative: "Use pytest" },
mock: {
reason: "Integrated into Python 3.3+ standard library",
alternative: "Use unittest.mock",
},
distribute: {
reason: "Merged into setuptools",
alternative: "Use setuptools",
},
"pylint-django": {
reason: "Consider newer alternatives",
alternative: "Use django-stubs with mypy",
},
urllib3: {
reason: "Versions < 1.26.5 have security vulnerabilities",
alternative: "Update to urllib3>=1.26.5",
},
};
parse(content) {
const packages = [];
const errors = [];
for (const rawLine of content.split("\n")) {
const line = rawLine.trim();
if (!line || line.startsWith("#") || line.startsWith("-"))
continue;
if (line.startsWith("-e") ||
line.includes("://") ||
line.startsWith("git+"))
continue;
const parsed = this.parseRequirementLine(line);
if (parsed)
packages.push(parsed);
}
return {
packages,
ecosystem: "python",
fileType: "requirements.txt",
errors: errors.length > 0 ? errors : undefined,
};
}
parseRequirementLine(line) {
const extrasMatch = line.match(/^([a-zA-Z0-9_-]+)\[([^\]]+)\]/);
let packageName;
let extras;
let remaining;
if (extrasMatch) {
packageName = extrasMatch[1];
extras = extrasMatch[2].split(",").map((e) => e.trim());
remaining = line.slice(extrasMatch[0].length);
}
else {
const nameMatch = line.match(/^([a-zA-Z0-9_-]+)/);
if (!nameMatch)
return null;
packageName = nameMatch[1];
remaining = line.slice(nameMatch[0].length);
}
const versionMatch = remaining.match(/([<>=!~]+\s*[\d.*]+(?:\s*,\s*[<>=!~]+\s*[\d.*]+)*)/);
const version = versionMatch ? versionMatch[1].trim() : "*";
return {
name: packageName.toLowerCase(),
version,
type: "dependencies",
ecosystem: "python",
extras,
};
}
canParse(content) {
if (content.includes("[package]") ||
content.includes("[dependencies]") ||
content.includes("[project]") ||
content.includes("module ") ||
(content.includes("source ") && content.includes("gem ")))
return false;
let validLines = 0, totalNonEmptyLines = 0, hasVersionConstraint = false;
for (const rawLine of content.split("\n")) {
const line = rawLine.trim();
if (!line || line.startsWith("#"))
continue;
totalNonEmptyLines++;
if (PATTERNS.PACKAGE_WITH_VERSION.test(line) ||
line.startsWith("-r") ||
line.startsWith("-e") ||
line.startsWith("-c") ||
line.startsWith("--")) {
validLines++;
if (/[<>=!~]+/.test(line))
hasVersionConstraint = true;
}
else if (PATTERNS.BARE_PACKAGE.test(line)) {
validLines++;
}
}
return (totalNonEmptyLines > 0 &&
validLines / totalNonEmptyLines >= 0.5 &&
(hasVersionConstraint || validLines === totalNonEmptyLines));
}
getEcosystem() {
return "python";
}
getFileTypes() {
return ["requirements.txt"];
}
analyzePackage(pkg, options, issues) {
if (options.checkOutdated) {
if (pkg.version === "*" || pkg.version === "") {
this.addIssue(issues, pkg, "Unpinned Version", "moderate", "No version constraint specified", "Pin to a specific version or use >= constraints");
}
if (pkg.version.match(/>=\s*0\./) && !pkg.version.includes(",")) {
this.addIssue(issues, pkg, "Version Constraint Issue", "info", "Very broad version constraint", "Consider adding an upper bound (e.g., >=1.0,<2.0)");
}
}
if (options.checkDeprecated) {
const deprecated = PythonRequirementsParser.deprecatedPackages[pkg.name];
if (deprecated) {
this.addIssue(issues, pkg, "Deprecated Package", "high", deprecated.reason, deprecated.alternative);
}
}
if (options.checkVulnerabilities)
this.checkKnownVulnerabilities(pkg, issues);
}
checkKnownVulnerabilities(pkg, issues) {
if (pkg.name === "django" &&
(pkg.version.match(/^[<>=~]*\s*[12]\./) ||
pkg.version.match(/^[<>=~]*\s*3\.[0-1]/))) {
this.addIssue(issues, pkg, "Known Vulnerabilities", "high", "Older Django versions have known security vulnerabilities", "Update to Django>=4.2 (LTS)");
}
if (pkg.name === "requests" &&
pkg.version.match(/^[<>=~]*\s*2\.[0-2][0-7]/)) {
this.addIssue(issues, pkg, "Known Vulnerabilities", "moderate", "Older requests versions have known vulnerabilities", "Update to requests>=2.28.0");
}
if (pkg.name === "pillow" && pkg.version.match(/^[<>=~]*\s*[0-8]\./)) {
this.addIssue(issues, pkg, "Known Vulnerabilities", "high", "Older Pillow versions have multiple security vulnerabilities", "Update to Pillow>=10.0.0");
}
if (pkg.name === "urllib3" &&
pkg.version.match(/^[<>=~]*\s*1\.(2[0-5]|[01])/)) {
this.addIssue(issues, pkg, "Known Vulnerabilities", "moderate", "urllib3 < 1.26.5 has security vulnerabilities", "Update to urllib3>=1.26.5");
}
}
addEcosystemRecommendations(recommendations) {
recommendations.push("Run 'pip-audit' or 'safety check' for vulnerability scanning");
recommendations.push("Run 'pip list --outdated' to check for updates");
recommendations.push("Consider using pip-compile for reproducible builds");
}
}
export class PythonPyprojectParser extends PythonRequirementsParser {
parse(content) {
const packages = [];
const errors = [];
let projectName;
let projectVersion;
try {
const projectMatch = content.match(/\[project\]([\s\S]*?)(?=\n\[|$)/);
if (projectMatch) {
const nameMatch = projectMatch[1].match(/name\s*=\s*["']([^"']+)["']/);
const versionMatch = projectMatch[1].match(/version\s*=\s*["']([^"']+)["']/);
if (nameMatch)
projectName = nameMatch[1];
if (versionMatch)
projectVersion = versionMatch[1];
const depsMatch = projectMatch[1].match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
if (depsMatch)
this.parseDependencyArray(depsMatch[1], "dependencies", packages);
const optDepsMatch = content.match(/\[project\.optional-dependencies\]([\s\S]*?)(?=\n\[|$)/);
if (optDepsMatch)
this.parseOptionalDependencies(optDepsMatch[1], packages);
}
const poetryDepsMatch = content.match(/\[tool\.poetry\.dependencies\]([\s\S]*?)(?=\n\[|$)/);
if (poetryDepsMatch)
this.parsePoetryDependencies(poetryDepsMatch[1], "dependencies", packages);
const poetryDevDepsMatch = content.match(/\[tool\.poetry\.dev-dependencies\]([\s\S]*?)(?=\n\[|$)/) ||
content.match(/\[tool\.poetry\.group\.dev\.dependencies\]([\s\S]*?)(?=\n\[|$)/);
if (poetryDevDepsMatch)
this.parsePoetryDependencies(poetryDevDepsMatch[1], "devDependencies", packages);
if (!projectName) {
const poetryProjectMatch = content.match(/\[tool\.poetry\]([\s\S]*?)(?=\n\[|$)/);
if (poetryProjectMatch) {
const nameMatch = poetryProjectMatch[1].match(/name\s*=\s*["']([^"']+)["']/);
const versionMatch = poetryProjectMatch[1].match(/version\s*=\s*["']([^"']+)["']/);
if (nameMatch)
projectName = nameMatch[1];
if (versionMatch)
projectVersion = versionMatch[1];
}
}
}
catch (error) {
errors.push(`Error parsing pyproject.toml: ${error instanceof Error ? error.message : "Unknown error"}`);
}
return {
packages,
projectName,
projectVersion,
ecosystem: "python",
fileType: "pyproject.toml",
errors: errors.length > 0 ? errors : undefined,
};
}
parseDependencyArray(content, type, packages) {
for (const match of content.matchAll(/["']([^"']+)["']/g)) {
const parsed = this.parsePep508Dependency(match[1]);
if (parsed)
packages.push({ ...parsed, type });
}
}
parseOptionalDependencies(content, packages) {
for (const match of content.matchAll(/(\w+)\s*=\s*\[([\s\S]*?)\]/g)) {
const groupName = match[1];
for (const dep of match[2].matchAll(/["']([^"']+)["']/g)) {
const parsed = this.parsePep508Dependency(dep[1]);
if (parsed)
packages.push({
...parsed,
type: "optionalDependencies",
extras: [groupName],
});
}
}
}
parsePoetryDependencies(content, type, packages) {
for (const line of content.split("\n")) {
const simpleMatch = line.match(/^([a-zA-Z0-9_-]+)\s*=\s*["']([^"']+)["']/);
if (simpleMatch && simpleMatch[1] !== "python") {
packages.push({
name: simpleMatch[1].toLowerCase(),
version: simpleMatch[2],
type,
ecosystem: "python",
});
continue;
}
const complexMatch = line.match(/^([a-zA-Z0-9_-]+)\s*=\s*\{[^}]*version\s*=\s*["']([^"']+)["']/);
if (complexMatch && complexMatch[1] !== "python") {
packages.push({
name: complexMatch[1].toLowerCase(),
version: complexMatch[2],
type,
ecosystem: "python",
});
}
}
}
parsePep508Dependency(spec) {
const match = spec.match(/^([a-zA-Z0-9_-]+)(\[[^\]]+\])?\s*([<>=!~][^;]*)?/);
if (!match)
return null;
const extras = match[2]
? match[2]
.slice(1, -1)
.split(",")
.map((e) => e.trim())
: undefined;
return {
name: match[1].toLowerCase(),
version: match[3]?.trim() || "*",
type: "dependencies",
ecosystem: "python",
extras,
};
}
canParse(content) {
return (content.includes("[project]") ||
content.includes("[tool.poetry") ||
content.includes("[build-system]"));
}
getFileTypes() {
return ["pyproject.toml"];
}
addEcosystemRecommendations(recommendations) {
recommendations.push("Run 'pip-audit' or 'safety check' for vulnerability scanning");
recommendations.push("Use 'poetry update' or 'pip-compile' to update dependencies");
recommendations.push("Consider using dependabot for automated updates");
}
}
// Aliases for backward compatibility
export const PyRequirementsParser = PythonRequirementsParser;
export const PyProjectParser = PythonPyprojectParser;
//# sourceMappingURL=python.js.map