jaegis-npm-mcp-server
Version:
NPM Package Management MCP Server with 10 production-ready tools including complete 5-step security pipeline
1,153 lines (1,141 loc) • 41.8 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// src/index.ts
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
var import_types = require("@modelcontextprotocol/sdk/types.js");
var dotenv = __toESM(require("dotenv"));
var fs2 = __toESM(require("fs"));
var path2 = __toESM(require("path"));
var os = __toESM(require("os"));
var semver = __toESM(require("semver"));
var import_child_process = require("child_process");
// src/security_scan.ts
var fs = __toESM(require("fs"));
var path = __toESM(require("path"));
var SENSITIVE_FILES = [
".env",
".env.local",
".env.production",
".env.development",
"id_rsa",
"id_dsa",
"id_ed25519",
"*.pem",
"*.key",
"*.p12",
"*.pfx",
"npm-debug.log",
"yarn-error.log",
".npmrc",
".pypirc",
"secrets.json",
"credentials.json"
];
var IGNORED_DIRS = [
"node_modules",
".git",
".svn",
".hg",
"dist",
"build",
"coverage",
"__pycache__",
".pytest_cache",
".mypy_cache",
"logs",
"cache",
"tmp",
"temp"
];
var SENSITIVE_PATTERNS = [
{ name: "AWS Access Key", regex: /AKIA[0-9A-Z]{16}/ },
{ name: "Generic Private Key", regex: /-----BEGIN PRIVATE KEY-----/ },
{ name: "Generic API Key", regex: /(api_key|apikey|secret|token)[\s]*[:=][\s]*['"][a-zA-Z0-9_\-]{20,}['"]/i },
{ name: "GitHub Token", regex: /gh[pousr]_[a-zA-Z0-9]{36}/ },
{ name: "NPM Token", regex: /npm_[a-zA-Z0-9]{36}/ },
{ name: "Slack Token", regex: /xox[baprs]-([0-9a-zA-Z]{10,48})/ }
];
async function scanDirectory(dirPath) {
const issues = [];
const warnings = [];
try {
const files = await getAllFiles(dirPath);
for (const file of files) {
const relativePath = path.relative(dirPath, file);
const fileName = path.basename(file);
if (SENSITIVE_FILES.some((pattern) => {
if (pattern.startsWith("*")) return fileName.endsWith(pattern.slice(1));
return fileName === pattern;
})) {
issues.push(`Sensitive file found: ${relativePath}`);
}
try {
const stats = fs.statSync(file);
if (stats.size < 1024 * 1024) {
const content = fs.readFileSync(file, "utf8");
if (!content.includes("\0")) {
for (const pattern of SENSITIVE_PATTERNS) {
if (pattern.regex.test(content)) {
issues.push(`Potential ${pattern.name} found in: ${relativePath}`);
}
}
}
}
} catch (err) {
}
}
} catch (error) {
issues.push(`Failed to scan directory: ${error instanceof Error ? error.message : String(error)}`);
}
return {
passed: issues.length === 0,
issues,
warnings
};
}
async function getAllFiles(dir) {
const files = [];
async function traverse(currentDir) {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (IGNORED_DIRS.includes(entry.name)) continue;
if (entry.isDirectory()) {
await traverse(fullPath);
} else {
files.push(fullPath);
}
}
}
await traverse(dir);
return files;
}
// src/index.ts
console.log = (...args) => {
try {
console.error(...args);
} catch {
}
};
(function loadEnvCascade() {
const homeEnv = path2.join(os.homedir() || "", ".mcp", ".env");
const localEnv = path2.resolve(process.cwd(), ".env");
for (const p of [homeEnv, localEnv]) {
try {
if (fs2.existsSync(p)) {
dotenv.config({ path: p, override: true });
}
} catch {
}
}
})();
var hasListTools = false;
var hasToolsJson = false;
var hasHelp = false;
var formatTable = false;
function sanitizeCredential(value) {
if (!value || value.length < 8) return "****";
return `${value.substring(0, 4)}****...****${value.substring(value.length - 4)}`;
}
function showHelp() {
console.error(`
NPM JAEGIS MCP Server v1.0.0 - NPM package management with 8 specialized tools
USAGE:
npm-mcp-server [OPTIONS]
OPTIONS:
--list-tools List all available tools with descriptions
--tools-json Output complete MCP tools schema as JSON
--format=table Use table format for tool listings (default: JSON)
--help, -h Show this help message
ENVIRONMENT VARIABLES:
NPM_TOKEN NPM access token (required)
Format: npm_*
Generate at: https://www.npmjs.com/settings/tokens
NPM_USERNAME NPM username (required)
NPM_EMAIL NPM email (required)
NPM_REGISTRY NPM registry URL (optional, default: https://registry.npmjs.org/)
DEFAULT_PACKAGE Default package name (optional, default: JAEGIS-web-os)
EXAMPLES:
# List all tools in table format
npm-mcp-server --list-tools --format=table
# Get tools schema for MCP client integration
npm-mcp-server --tools-json
# Start the MCP server (default)
npm-mcp-server
QUICK SETUP:
1. Generate NPM token: https://www.npmjs.com/settings/tokens
2. Set environment variables:
export NPM_TOKEN=npm_your_token_here
export NPM_USERNAME=your_username
export NPM_EMAIL=your_email@example.com
3. Run: npm-mcp-server
For detailed documentation, see: README.md
`);
}
function getToolsInfo() {
return [
{
name: "npm_package_info",
category: "Package Information",
description: "Get detailed information about an NPM package including metadata and dependencies",
requiredParams: ["package_name"],
optionalParams: ["version"],
inputSchema: { type: "object", properties: { package_name: { type: "string" } } }
},
{
name: "npm_search_packages",
category: "Package Discovery",
description: "Search for NPM packages using keywords and filters",
requiredParams: ["query"],
optionalParams: ["limit"],
inputSchema: { type: "object", properties: { query: { type: "string" } } }
},
{
name: "npm_publish_package",
category: "Package Publishing",
description: "Publish a package to NPM registry with authentication and specified configuration",
requiredParams: ["package_path"],
optionalParams: ["tag", "access", "additional_flags"],
inputSchema: { type: "object", properties: { package_path: { type: "string" } } }
},
{
name: "npm_unpublish_package",
category: "Package Management",
description: "Unpublish a package version from NPM registry (irreversible)",
requiredParams: ["package_name"],
optionalParams: ["version"],
inputSchema: { type: "object", properties: { package_name: { type: "string" } } }
},
{
name: "npm_deprecate_package",
category: "Package Management",
description: "Deprecate a package version with a custom message",
requiredParams: ["package_name", "version", "message"],
optionalParams: [],
inputSchema: { type: "object", properties: { package_name: { type: "string" }, version: { type: "string" }, message: { type: "string" } } }
},
{
name: "npm_list_versions",
category: "Package Information",
description: "List all available versions of a package with release information",
requiredParams: ["package_name"],
optionalParams: [],
inputSchema: { type: "object", properties: { package_name: { type: "string" } } }
},
{
name: "npm_download_stats",
category: "Package Analytics",
description: "Get download statistics and analytics for a package",
requiredParams: ["package_name"],
optionalParams: ["period"],
inputSchema: { type: "object", properties: { package_name: { type: "string" } } }
},
{
name: "npm_validate_package",
category: "Package Validation",
description: "Validate package.json and check for publishing readiness",
requiredParams: ["package_path"],
optionalParams: [],
inputSchema: { type: "object", properties: { package_path: { type: "string" } } }
},
{
name: "security_check",
category: "Security",
description: "Scan a directory for sensitive files and secrets",
requiredParams: ["path"],
optionalParams: [],
inputSchema: { type: "object", properties: { path: { type: "string" } } }
},
{
name: "health_check",
category: "Diagnostics",
description: "Return server health, version, uptime, and dependency status",
requiredParams: [],
optionalParams: [],
inputSchema: { type: "object", properties: {} }
}
];
}
function displayToolsTable(tools) {
console.error("\n\u{1F4E6} NPM JAEGIS MCP Server - Tool Inventory\n");
console.error("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
console.error("\u2502 Tool Name \u2502 Category \u2502 Required \u2502 Optional \u2502");
console.error("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
tools.forEach((tool) => {
const name = tool.name.padEnd(35);
const category = tool.category.padEnd(32);
const required = tool.requiredParams.length.toString().padEnd(8);
const optional = tool.optionalParams.length.toString().padEnd(8);
console.error(`\u2502 ${name} \u2502 ${category} \u2502 ${required} \u2502 ${optional} \u2502`);
});
console.error("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
const categories = tools.reduce((acc, tool) => {
acc[tool.category] = (acc[tool.category] || 0) + 1;
return acc;
}, {});
console.error("\n\u{1F4CB} Tool Summary by Category:");
Object.entries(categories).forEach(([category, count]) => {
console.error(` \u2022 ${category}: ${count} tools`);
});
console.error(`
\u{1F3AF} Total: ${tools.length} tools available
`);
}
function displayToolsJson(tools, includeSchema = false) {
const output = {
server: "npm-jaegis-mcp-server",
version: "1.0.0",
totalTools: tools.length,
categories: tools.reduce((acc, tool) => {
acc[tool.category] = (acc[tool.category] || 0) + 1;
return acc;
}, {}),
tools: tools.map((tool) => ({
name: tool.name,
category: tool.category,
description: tool.description,
parameters: {
required: tool.requiredParams,
optional: tool.optionalParams,
total: tool.requiredParams.length + tool.optionalParams.length
},
...includeSchema && { inputSchema: tool.inputSchema }
}))
};
console.error(JSON.stringify(output, null, 2));
}
if (hasHelp) {
showHelp();
process.exit(0);
}
if (hasListTools || hasToolsJson) {
const tools = getToolsInfo();
if (hasToolsJson) {
displayToolsJson(tools, true);
} else if (formatTable) {
displayToolsTable(tools);
} else {
displayToolsJson(tools, false);
}
process.exit(0);
}
var NPMJAEGISMCPServer = class {
server;
config;
constructor() {
this.config = this.loadConfig();
this.validateConfiguration();
this.server = new import_server.Server(
{
name: "npm-jaegis-mcp-server",
version: "1.0.0"
},
{
capabilities: {
tools: {}
}
}
);
this.setupToolHandlers();
this.setupErrorHandling();
}
loadConfig() {
const token = process.env.NPM_TOKEN || "";
const username = process.env.NPM_USERNAME || "";
const email = process.env.NPM_EMAIL || "";
return {
token,
username,
email,
registry: process.env.NPM_REGISTRY || "https://registry.npmjs.org/",
scope: process.env.NPM_SCOPE,
defaultPackage: process.env.DEFAULT_PACKAGE || "JAEGIS-web-os"
};
}
validateConfiguration() {
if (this.config.token && !this.config.token.startsWith("npm_")) {
console.warn("Warning: NPM token format may be invalid. Expected format: npm_*");
}
if (!this.config.username || !this.config.email || !this.config.token) {
console.warn("NPM credentials are not fully set; auth-required tools may fail until provided.");
}
console.error(`NPM MCP Server configured for user: ${sanitizeCredential(this.config.username || "unknown")}`);
console.error(`Registry: ${this.config.registry}`);
console.error(`Default package: ${this.config.defaultPackage}`);
}
setupToolHandlers() {
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "npm_package_info",
description: "Get detailed information about an NPM package",
inputSchema: {
type: "object",
properties: {
package_name: {
type: "string",
description: "Name of the NPM package"
},
version: {
type: "string",
description: "Specific version (optional, defaults to latest)"
}
},
required: ["package_name"]
}
},
{
name: "npm_search_packages",
description: "Search for NPM packages",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query"
},
limit: {
type: "number",
description: "Maximum number of results (default: 20)"
}
},
required: ["query"]
}
},
{
name: "npm_publish_package",
description: "Publish a package to NPM registry with authentication",
inputSchema: {
type: "object",
properties: {
package_path: {
type: "string",
description: "Path to package directory containing package.json"
},
tag: {
type: "string",
description: "Distribution tag (default: latest)"
},
access: {
type: "string",
enum: ["public", "restricted"],
description: "Package access level (default: public)"
},
additional_flags: {
type: "string",
description: 'Additional npm publish flags (optional, e.g., "--dry-run")'
}
},
required: ["package_path"]
}
},
{
name: "npm_unpublish_package",
description: "Unpublish a package version from NPM",
inputSchema: {
type: "object",
properties: {
package_name: {
type: "string",
description: "Name of the package to unpublish"
},
version: {
type: "string",
description: "Specific version to unpublish (optional, unpublishes entire package if not specified)"
}
},
required: ["package_name"]
}
},
{
name: "npm_deprecate_package",
description: "Deprecate a package version",
inputSchema: {
type: "object",
properties: {
package_name: {
type: "string",
description: "Name of the package"
},
version: {
type: "string",
description: "Version range to deprecate"
},
message: {
type: "string",
description: "Deprecation message"
}
},
required: ["package_name", "version", "message"]
}
},
{
name: "npm_list_versions",
description: "List all versions of a package",
inputSchema: {
type: "object",
properties: {
package_name: {
type: "string",
description: "Name of the package"
}
},
required: ["package_name"]
}
},
{
name: "npm_download_stats",
description: "Get download statistics for a package",
inputSchema: {
type: "object",
properties: {
package_name: {
type: "string",
description: "Name of the package"
},
period: {
type: "string",
enum: ["last-day", "last-week", "last-month", "last-year"],
description: "Time period for stats (default: last-month)"
}
},
required: ["package_name"]
}
},
{
name: "npm_validate_package",
description: "Validate package.json and check for publishing readiness",
inputSchema: {
type: "object",
properties: {
package_path: {
type: "string",
description: "Path to package directory"
}
},
required: ["package_path"]
}
},
{
name: "health_check",
description: "Return server health, version, uptime, and dependency status",
inputSchema: { type: "object", properties: {} }
}
]
};
});
this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "npm_package_info":
return await this.getPackageInfo(args);
case "npm_search_packages":
return await this.searchPackages(args);
case "npm_publish_package":
return await this.publishPackage(args);
case "npm_unpublish_package":
return await this.unpublishPackage(args);
case "npm_deprecate_package":
return await this.deprecatePackage(args);
case "npm_list_versions":
return await this.listVersions(args);
case "npm_download_stats":
return await this.getDownloadStats(args);
case "npm_validate_package":
return await this.validatePackage(args);
case "security_check":
return await this.securityCheck(args);
case "health_check": {
const version = (() => {
try {
return JSON.parse(fs2.readFileSync(path2.resolve(__dirname, "..", "package.json"), "utf8")).version;
} catch {
return "unknown";
}
})();
const uptime = process.uptime();
return { content: [{ type: "text", text: JSON.stringify({ status: "ok", version, uptime, success: true }) }] };
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
});
}
setupErrorHandling() {
this.server.onerror = (error) => {
console.error("[NPM MCP Server Error]", error);
};
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
// Tool implementations
async getPackageInfo(args) {
try {
const packageName = args.package_name;
if (!packageName) throw new Error("package_name is required");
const url = `${this.config.registry}${packageName}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Package not found: ${packageName}`);
}
const data = await response.json();
const version = args.version || "latest";
const versionData = version === "latest" ? data["dist-tags"]?.latest : version;
const packageData = data.versions?.[versionData] || data;
return {
content: [
{
type: "text",
text: JSON.stringify({
name: data.name,
version: packageData.version,
description: data.description,
author: data.author,
license: data.license,
homepage: data.homepage,
repository: data.repository,
keywords: data.keywords,
dependencies: packageData.dependencies,
devDependencies: packageData.devDependencies,
downloads: data.downloads,
lastPublished: data.time?.modified,
success: true
}, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
}
async searchPackages(args) {
try {
const query = args.query;
const limit = Math.min(args.limit || 20, 100);
if (!query) throw new Error("query is required");
const url = `${this.config.registry}-/v1/search?text=${encodeURIComponent(query)}&size=${limit}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error("Search failed");
}
const data = await response.json();
const results = data.objects?.map((obj) => ({
name: obj.package.name,
version: obj.package.version,
description: obj.package.description,
date: obj.package.date,
links: obj.package.links
})) || [];
return {
content: [
{
type: "text",
text: JSON.stringify({
query,
total: data.total,
results,
success: true
}, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
}
async publishPackage(args) {
try {
const packagePath = args.package_path;
if (!packagePath) throw new Error("package_path is required");
if (!this.config.token) throw new Error("NPM_TOKEN environment variable is required");
const packageJsonPath = path2.join(packagePath, "package.json");
if (!fs2.existsSync(packageJsonPath)) {
throw new Error(`package.json not found at ${packageJsonPath}`);
}
const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
const tag = args.tag || "latest";
const access = args.access || "public";
const additionalFlags = args.additional_flags || "";
const validationIssues = [];
if (!packageJson.name) validationIssues.push("Missing required field: name");
if (!packageJson.version) validationIssues.push("Missing required field: version");
if (!semver.valid(packageJson.version)) validationIssues.push("Invalid version format (must be semver)");
if (validationIssues.length > 0) {
throw new Error(`Package validation failed: ${validationIssues.join("; ")}`);
}
console.error("[Security] Step 1/5: Checking for version conflicts...");
const { checkVersionConflictAsync } = require(path2.join(__dirname, "../../../scripts/registry_manager.js"));
const versionConflict = await checkVersionConflictAsync(packageJson.name, packageJson.version, "npm");
if (versionConflict) {
throw new Error(`Version ${packageJson.version} already exists on NPM registry. Aborting to prevent conflicts.`);
}
console.error("[Security] \u2713 Version available");
console.error("[Security] Step 2/5: Checking README...");
const { smartReadmeUpdate } = require(path2.join(__dirname, "../../../scripts/professional_readme_generator.js"));
await smartReadmeUpdate(packagePath, {
name: packageJson.name,
version: packageJson.version,
description: packageJson.description,
author: packageJson.author,
license: packageJson.license,
repository: packageJson.repository?.url || packageJson.repository,
homepage: packageJson.homepage,
keywords: packageJson.keywords || [],
tools: [],
examples: []
}, "npm");
console.error("[Security] \u2713 README validated");
console.error("[Security] Step 3/5: Deep security scan...");
const scanResult = await scanDirectory(packagePath);
if (!scanResult.passed) {
throw new Error(`Security check failed: ${scanResult.issues.join("; ")}`);
}
console.error("[Security] \u2713 No sensitive data detected");
console.error("[Security] Step 4/5: Sanitizing build artifacts...");
const { sanitizeNpmPackage } = require(path2.join(__dirname, "../../../scripts/artifact_sanitizer.js"));
const sanitizationResult = await sanitizeNpmPackage(packagePath);
if (sanitizationResult.success && !sanitizationResult.clean) {
console.error(`[Security] \u26A0 Sanitized ${sanitizationResult.report.filesModified} files`);
}
console.error("[Security] \u2713 Artifacts clean");
console.error("[Security] Step 5/5: Triggering historical scan (async)...");
const { scanNpmPackageHistory, autoRemediateNpm } = require(path2.join(__dirname, "../../../scripts/npm_historical_scanner.js"));
setImmediate(async () => {
try {
console.error(`[Historical Scan] Scanning ${packageJson.name} history...`);
const historicalResults = await scanNpmPackageHistory(packageJson.name, { limit: 10 });
if (historicalResults.compromisedVersions.length > 0) {
console.error(`[Historical Scan] \u26A0 Found ${historicalResults.compromisedVersions.length} compromised versions`);
await autoRemediateNpm(packageJson.name, historicalResults);
} else {
console.error("[Historical Scan] \u2713 All historical versions clean");
}
} catch (e) {
console.error(`[Historical Scan] Error: ${e.message}`);
}
});
console.error("[Security] \u2713 Historical scan queued");
console.error("[Security] ==================== ALL CHECKS PASSED ====================");
const publishEnv = {
...process.env,
NPM_TOKEN: this.config.token,
NPM_CONFIG_REGISTRY: this.config.registry,
NPM_CONFIG__AUTH_TOKEN: this.config.token,
NPM_CONFIG_USERCONFIG: path2.join(os.homedir(), ".npmrc")
};
const publishArgs = ["publish", "--access", access, "--tag", tag];
if (additionalFlags) {
publishArgs.push(...additionalFlags.split(" "));
}
console.error(`[NPM Publish] Executing: npm ${publishArgs.join(" ")} in ${packagePath}`);
console.error(`[NPM Publish] Package: ${packageJson.name}@${packageJson.version}`);
const publishOutput = await new Promise((resolve2, reject) => {
const child = (0, import_child_process.spawn)("npm", publishArgs, {
cwd: packagePath,
env: publishEnv,
stdio: ["pipe", "pipe", "pipe"]
});
let output = "";
let errorOutput = "";
child.stdout.on("data", (data) => {
const str = data.toString();
output += str;
console.error(`[NPM] ${str}`);
const urlMatch = str.match(/https:\/\/www\.npmjs\.com\/auth\/cli\/[a-f0-9-]+/);
if (urlMatch) {
const url = urlMatch[0];
console.error(`[NPM] Auth URL detected: ${url}`);
try {
const startCmd = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
require("child_process").exec(`${startCmd} ${url}`);
} catch (e) {
console.error("Failed to open browser automatically");
}
if (child.stdin) {
child.stdin.write("\n");
}
}
});
child.stderr.on("data", (data) => {
const str = data.toString();
errorOutput += str;
console.error(`[NPM Error] ${str}`);
});
child.on("close", (code) => {
if (code === 0) {
resolve2(output);
} else {
reject(new Error(`npm publish failed with code ${code}: ${errorOutput}`));
}
});
child.on("error", (err) => {
reject(err);
});
});
console.error(`[NPM Publish] Success: ${packageJson.name}@${packageJson.version}`);
return {
content: [
{
type: "text",
text: JSON.stringify({
success: true,
message: `Successfully published ${packageJson.name}@${packageJson.version} to NPM registry`,
package: packageJson.name,
version: packageJson.version,
tag,
access,
registry: this.config.registry,
publishOutput: publishOutput.substring(0, 500),
// Limit output size
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}, null, 2)
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`[NPM Publish Error] ${errorMessage}`);
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
error: errorMessage,
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}, null, 2)
}
],
isError: true
};
}
}
async securityCheck(args) {
try {
const dirPath = args.path;
if (!dirPath) throw new Error("path is required");
const result = await scanDirectory(dirPath);
return {
content: [
{
type: "text",
text: JSON.stringify({
operation: "security_check",
path: dirPath,
...result,
success: true
}, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
}
async unpublishPackage(args) {
try {
const packageName = args.package_name;
if (!packageName) throw new Error("package_name is required");
if (!this.config.token) throw new Error("NPM_TOKEN environment variable is required");
return {
content: [
{
type: "text",
text: JSON.stringify({
message: `Unpublish request for ${packageName}${args.version ? `@${args.version}` : " (all versions)"}. This is a destructive operation.`,
package: packageName,
version: args.version || "all",
warning: "Unpublishing is irreversible. Use npm unpublish command to complete.",
success: true
}, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
}
async deprecatePackage(args) {
try {
const packageName = args.package_name;
const version = args.version;
const message = args.message;
if (!packageName || !version || !message) {
throw new Error("package_name, version, and message are all required");
}
if (!this.config.token) throw new Error("NPM_TOKEN environment variable is required");
return {
content: [
{
type: "text",
text: JSON.stringify({
message: `Deprecation request for ${packageName}@${version}`,
package: packageName,
version,
deprecationMessage: message,
warning: "Use npm deprecate command to complete this action.",
success: true
}, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
}
async listVersions(args) {
try {
const packageName = args.package_name;
if (!packageName) throw new Error("package_name is required");
const url = `${this.config.registry}${packageName}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Package not found: ${packageName}`);
}
const data = await response.json();
const versions = Object.keys(data.versions || {}).sort((a, b) => {
try {
return semver.compare(b, a);
} catch {
return b.localeCompare(a);
}
});
return {
content: [
{
type: "text",
text: JSON.stringify({
package: packageName,
totalVersions: versions.length,
versions,
latest: data["dist-tags"]?.latest,
success: true
}, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
}
async getDownloadStats(args) {
try {
const packageName = args.package_name;
const period = args.period || "last-month";
if (!packageName) throw new Error("package_name is required");
const url = `https://api.npmjs.org/downloads/point/${period}/${packageName}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch download stats for ${packageName}`);
}
const data = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify({
package: packageName,
period,
downloads: data.downloads,
start: data.start,
end: data.end,
success: true
}, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
}
async validatePackage(args) {
try {
const packagePath = args.package_path;
if (!packagePath) throw new Error("package_path is required");
const packageJsonPath = path2.join(packagePath, "package.json");
if (!fs2.existsSync(packageJsonPath)) {
throw new Error(`package.json not found at ${packageJsonPath}`);
}
const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
const issues = [];
if (!packageJson.name) issues.push("Missing required field: name");
if (!packageJson.version) issues.push("Missing required field: version");
if (!packageJson.description) issues.push("Missing recommended field: description");
if (!packageJson.author) issues.push("Missing recommended field: author");
if (!packageJson.license) issues.push("Missing recommended field: license");
if (!packageJson.repository) issues.push("Missing recommended field: repository");
if (packageJson.name && !/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(packageJson.name)) {
issues.push("Invalid package name format");
}
if (packageJson.version && !semver.valid(packageJson.version)) {
issues.push("Invalid version format (must be semver)");
}
return {
content: [
{
type: "text",
text: JSON.stringify({
package: packageJson.name,
version: packageJson.version,
isValid: issues.length === 0,
issues,
warnings: issues.length > 0 ? "Fix issues before publishing" : "Package is ready to publish",
success: true
}, null, 2)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
success: false
}, null, 2)
}
]
};
}
}
async run() {
const transport = new import_stdio.StdioServerTransport();
await this.server.connect(transport);
const tools = getToolsInfo();
const categories = tools.reduce((acc, tool) => {
acc[tool.category] = (acc[tool.category] || 0) + 1;
return acc;
}, {});
console.error("\u{1F4E6} NPM JAEGIS MCP Server (v1.0.0) running on stdio");
console.error(`\u{1F4CA} Total Tools: ${tools.length} | Categories: ${Object.keys(categories).length}`);
console.error(`\u{1F527} Configured for user: ${sanitizeCredential(this.config.username)}`);
console.error(`\u{1F310} Registry: ${this.config.registry}`);
console.error("\u2705 Server ready with NPM package management tools");
console.error("\u{1F4A1} Use --list-tools to see all available tools");
}
};
if (require.main === module) {
try {
const server = new NPMJAEGISMCPServer();
server.run().catch((error) => {
console.error("Failed to start NPM JAEGIS MCP Server:", error.message);
process.exit(1);
});
} catch (error) {
console.error("Failed to initialize NPM JAEGIS MCP Server:");
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
}
//# sourceMappingURL=index.cjs.map