Skip to main content
Glama

NPM MCP Server

by Kpangaa
MIT License
0
main.ts18.7 kB
#!/usr/bin/env node /** * NPM MCP Server * * Un servidor MCP (Model Context Protocol) completo para interactuar con npm y sus servicios. * Este servidor permite a los modelos de lenguaje usar npm de forma segura y estructurada. * * @author Kpangaa * @version 1.0.0 * @license MIT * @repository https://github.com/Kpangaa/npm-mcp-server */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { exec } from "child_process"; import { promisify } from "util"; import * as fs from "fs/promises"; import * as path from "path"; const execAsync = promisify(exec); // Esquemas de validación con Zod const PackageInfoSchema = z.object({ name: z.string(), version: z.string().optional(), }); const SearchSchema = z.object({ query: z.string(), limit: z.number().optional().default(10), }); const ProjectSchema = z.object({ directory: z.string().optional(), }); const InstallSchema = z.object({ packages: z.array(z.string()), directory: z.string().optional(), dev: z.boolean().optional().default(false), global: z.boolean().optional().default(false), }); const ScriptSchema = z.object({ script: z.string(), directory: z.string().optional(), }); // Funciones de utilidad async function executeNpmCommand( command: string, cwd?: string ): Promise<{ stdout: string; stderr: string }> { try { const result = await execAsync(command, { cwd: cwd || process.cwd(), timeout: 30000, // 30 segundos timeout }); return result; } catch (error: any) { throw new Error(`Error ejecutando comando npm: ${error.message}`); } } async function readPackageJson(directory?: string): Promise<any> { const packagePath = path.join(directory || process.cwd(), "package.json"); try { const content = await fs.readFile(packagePath, "utf-8"); return JSON.parse(content); } catch (error) { throw new Error(`No se pudo leer package.json en ${packagePath}`); } } // Crear el servidor MCP const server = new Server( { name: "npm-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, resources: {}, prompts: {}, }, } ); // Registrar handlers para herramientas server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "npm_search", description: "Buscar paquetes en el registro de npm", inputSchema: { type: "object", properties: { query: { type: "string", description: "Término de búsqueda" }, limit: { type: "number", description: "Límite de resultados", default: 10, }, }, required: ["query"], }, }, { name: "npm_info", description: "Obtener información detallada de un paquete", inputSchema: { type: "object", properties: { name: { type: "string", description: "Nombre del paquete" }, version: { type: "string", description: "Versión específica (opcional)", }, }, required: ["name"], }, }, { name: "npm_install", description: "Instalar paquetes usando npm", inputSchema: { type: "object", properties: { packages: { type: "array", items: { type: "string" }, description: "Lista de paquetes a instalar", }, directory: { type: "string", description: "Directorio del proyecto (opcional)", }, dev: { type: "boolean", description: "Instalar como dependencia de desarrollo", default: false, }, global: { type: "boolean", description: "Instalación global", default: false, }, }, required: ["packages"], }, }, { name: "npm_uninstall", description: "Desinstalar paquetes usando npm", inputSchema: { type: "object", properties: { packages: { type: "array", items: { type: "string" }, description: "Lista de paquetes a desinstalar", }, directory: { type: "string", description: "Directorio del proyecto (opcional)", }, global: { type: "boolean", description: "Desinstalación global", default: false, }, }, required: ["packages"], }, }, { name: "npm_list", description: "Listar paquetes instalados", inputSchema: { type: "object", properties: { directory: { type: "string", description: "Directorio del proyecto (opcional)", }, }, }, }, { name: "npm_outdated", description: "Verificar paquetes desactualizados", inputSchema: { type: "object", properties: { directory: { type: "string", description: "Directorio del proyecto (opcional)", }, }, }, }, { name: "npm_update", description: "Actualizar paquetes a sus últimas versiones", inputSchema: { type: "object", properties: { directory: { type: "string", description: "Directorio del proyecto (opcional)", }, }, }, }, { name: "npm_run_script", description: "Ejecutar un script definido en package.json", inputSchema: { type: "object", properties: { script: { type: "string", description: "Nombre del script a ejecutar", }, directory: { type: "string", description: "Directorio del proyecto (opcional)", }, }, required: ["script"], }, }, { name: "npm_init", description: "Inicializar un nuevo proyecto npm", inputSchema: { type: "object", properties: { directory: { type: "string", description: "Directorio del proyecto (opcional)", }, }, }, }, { name: "npm_audit", description: "Auditar vulnerabilidades de seguridad", inputSchema: { type: "object", properties: { directory: { type: "string", description: "Directorio del proyecto (opcional)", }, }, }, }, { name: "npm_audit_fix", description: "Arreglar vulnerabilidades automáticamente", inputSchema: { type: "object", properties: { directory: { type: "string", description: "Directorio del proyecto (opcional)", }, }, }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "npm_search": { const { query, limit } = SearchSchema.parse(args); const command = `npm search ${query} --json --searchlimit=${limit}`; const { stdout } = await executeNpmCommand(command); const results = JSON.parse(stdout); return { content: [ { type: "text", text: `Resultados de búsqueda para "${query}":\n\n${JSON.stringify( results, null, 2 )}`, }, ], }; } case "npm_info": { const { name: packageName, version } = PackageInfoSchema.parse(args); const fullPackageName = version ? `${packageName}@${version}` : packageName; const command = `npm info ${fullPackageName} --json`; const { stdout } = await executeNpmCommand(command); const info = JSON.parse(stdout); return { content: [ { type: "text", text: `Información del paquete ${fullPackageName}:\n\n${JSON.stringify( info, null, 2 )}`, }, ], }; } case "npm_install": { const { packages, directory, dev, global } = InstallSchema.parse(args); let command = "npm install"; if (global) { command += " -g"; } else if (dev) { command += " --save-dev"; } command += ` ${packages.join(" ")}`; const { stdout, stderr } = await executeNpmCommand(command, directory); return { content: [ { type: "text", text: `Instalación completada:\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`, }, ], }; } case "npm_uninstall": { const { packages, directory, global } = InstallSchema.parse(args); let command = "npm uninstall"; if (global) { command += " -g"; } command += ` ${packages.join(" ")}`; const { stdout, stderr } = await executeNpmCommand(command, directory); return { content: [ { type: "text", text: `Desinstalación completada:\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`, }, ], }; } case "npm_list": { const { directory } = ProjectSchema.parse(args); const command = "npm list --json"; const { stdout } = await executeNpmCommand(command, directory); const packages = JSON.parse(stdout); return { content: [ { type: "text", text: `Paquetes instalados:\n\n${JSON.stringify( packages, null, 2 )}`, }, ], }; } case "npm_outdated": { const { directory } = ProjectSchema.parse(args); const command = "npm outdated --json"; try { const { stdout } = await executeNpmCommand(command, directory); if (stdout.trim()) { const outdated = JSON.parse(stdout); return { content: [ { type: "text", text: `Paquetes desactualizados:\n\n${JSON.stringify( outdated, null, 2 )}`, }, ], }; } else { return { content: [ { type: "text", text: "Todos los paquetes están actualizados.", }, ], }; } } catch (error) { return { content: [ { type: "text", text: "Todos los paquetes están actualizados.", }, ], }; } } case "npm_update": { const { directory } = ProjectSchema.parse(args); const command = "npm update"; const { stdout, stderr } = await executeNpmCommand(command, directory); return { content: [ { type: "text", text: `Actualización completada:\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`, }, ], }; } case "npm_run_script": { const { script, directory } = ScriptSchema.parse(args); const command = `npm run ${script}`; const { stdout, stderr } = await executeNpmCommand(command, directory); return { content: [ { type: "text", text: `Ejecución del script "${script}":\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`, }, ], }; } case "npm_init": { const { directory } = ProjectSchema.parse(args); const command = "npm init -y"; const { stdout, stderr } = await executeNpmCommand(command, directory); return { content: [ { type: "text", text: `Proyecto inicializado:\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`, }, ], }; } case "npm_audit": { const { directory } = ProjectSchema.parse(args); const command = "npm audit --json"; const { stdout } = await executeNpmCommand(command, directory); const audit = JSON.parse(stdout); return { content: [ { type: "text", text: `Auditoría de seguridad:\n\n${JSON.stringify( audit, null, 2 )}`, }, ], }; } case "npm_audit_fix": { const { directory } = ProjectSchema.parse(args); const command = "npm audit fix"; const { stdout, stderr } = await executeNpmCommand(command, directory); return { content: [ { type: "text", text: `Corrección de vulnerabilidades:\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`, }, ], }; } default: throw new Error(`Herramienta desconocida: ${name}`); } } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], isError: true, }; } }); // Registrar recursos server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: "npm://package.json", name: "Package.json", description: "Contenido del archivo package.json del proyecto actual", mimeType: "application/json", }, { uri: "npm://scripts", name: "NPM Scripts", description: "Scripts disponibles en package.json", mimeType: "application/json", }, ], }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; try { switch (uri) { case "npm://package.json": { const packageJson = await readPackageJson(); return { contents: [ { uri: "npm://package.json", mimeType: "application/json", text: JSON.stringify(packageJson, null, 2), }, ], }; } case "npm://scripts": { const packageJson = await readPackageJson(); const scripts = packageJson.scripts || {}; return { contents: [ { uri: "npm://scripts", mimeType: "application/json", text: JSON.stringify(scripts, null, 2), }, ], }; } default: throw new Error(`Recurso desconocido: ${uri}`); } } catch (error: any) { throw new Error(`Error leyendo recurso: ${error.message}`); } }); // Registrar prompts server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: [ { name: "npm_project_analysis", description: "Analizar el estado actual del proyecto npm", arguments: [ { name: "directory", description: "Directorio del proyecto (opcional)", required: false, }, ], }, ], }; }); server.setRequestHandler(GetPromptRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name !== "npm_project_analysis") { throw new Error(`Prompt desconocido: ${name}`); } const directory = args?.directory as string | undefined; try { const packageJson = await readPackageJson(directory); const { stdout: listOutput } = await executeNpmCommand( "npm list --json", directory ); let auditOutput = ""; try { const { stdout } = await executeNpmCommand("npm audit --json", directory); auditOutput = stdout; } catch (error) { auditOutput = "No se pudo realizar la auditoría"; } const analysis = `# Análisis del Proyecto NPM ## Información del Proyecto - **Nombre**: ${packageJson.name} - **Versión**: ${packageJson.version} - **Descripción**: ${packageJson.description || "No disponible"} ## Dependencias ${JSON.stringify(packageJson.dependencies || {}, null, 2)} ## Dependencias de Desarrollo ${JSON.stringify(packageJson.devDependencies || {}, null, 2)} ## Scripts Disponibles ${JSON.stringify(packageJson.scripts || {}, null, 2)} ## Estado de Instalación ${listOutput} ## Auditoría de Seguridad ${auditOutput}`; return { description: "Análisis completo del proyecto npm", messages: [ { role: "user", content: { type: "text", text: analysis, }, }, ], }; } catch (error: any) { return { description: "Error analizando proyecto", messages: [ { role: "user", content: { type: "text", text: `Error analizando el proyecto: ${error.message}`, }, }, ], }; } }); // Iniciar el servidor async function main() { const transport = new StdioServerTransport(); await server.connect(transport); // Esto solo se verá en stderr cuando se ejecute desde un cliente MCP console.error("Servidor MCP de npm iniciado correctamente"); } // Manejar errores y señales process.on("SIGINT", async () => { console.error("Cerrando servidor MCP de npm..."); await server.close(); process.exit(0); }); process.on("SIGTERM", async () => { console.error("Cerrando servidor MCP de npm..."); await server.close(); process.exit(0); }); // Ejecutar el servidor main().catch((error) => { console.error("Error iniciando el servidor:", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Kpangaa/npm-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server