import { z } from "zod";
import fs from "fs/promises";
import path from "path";
// --- Tools ---
export const analyzePythonAstSchema = {
name: "analyze_python_ast",
description:
"Analyze Python code structure (classes, functions, imports) using static analysis.",
inputSchema: z.object({
code: z.string().describe("Python code to analyze"),
}),
};
export const checkPythonDepsSchema = {
name: "check_python_deps",
description:
"Check Python dependencies from requirements.txt or pyproject.toml",
inputSchema: z.object({
filePath: z.string().describe("Path to requirements.txt or pyproject.toml"),
}),
};
// --- Handlers ---
export async function analyzePythonAstHandler(args: { code: string }) {
const { code } = args;
// Simple regex-based analysis
const classes = [...code.matchAll(/^\s*class\s+([a-zA-Z0-9_]+)/gm)].map(
(m) => m[1],
);
const functions = [...code.matchAll(/^\s*def\s+([a-zA-Z0-9_]+)/gm)].map(
(m) => m[1],
);
const imports = [
...code.matchAll(/^\s*(?:from|import)\s+([a-zA-Z0-9_.]+)/gm),
].map((m) => m[1]);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
classes,
functions,
imports,
summary: `Found ${classes.length} classes, ${functions.length} functions, and ${imports.length} imports.`,
},
null,
2,
),
},
],
};
}
export async function checkPythonDepsHandler(args: { filePath: string }) {
try {
const content = await fs.readFile(args.filePath, "utf-8");
const isToml = args.filePath.endsWith(".toml");
let dependencies: string[] = [];
if (isToml) {
// Very basic TOML parsing for [tool.poetry.dependencies] or [project.dependencies]
const lines = content.split("\n");
let insideDeps = false;
for (const line of lines) {
if (
line.includes("[tool.poetry.dependencies]") ||
line.includes("[project.dependencies]")
) {
insideDeps = true;
continue;
}
if (line.startsWith("[") && insideDeps) {
insideDeps = false;
continue;
}
if (insideDeps && line.includes("=") && !line.startsWith("#")) {
dependencies.push(line.split("=")[0].trim());
}
}
} else {
// requirements.txt
dependencies = content
.split("\n")
.map((l) => l.split("#")[0].trim()) // remove comments
.filter((l) => l && !l.startsWith("-")); // remove flags
}
return {
content: [
{
type: "text",
text: JSON.stringify(
{
file: path.basename(args.filePath),
count: dependencies.length,
dependencies,
},
null,
2,
),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error reading file: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}