@raghulm/aegis-mcp 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Raghul M
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -6,22 +6,12 @@
6
6
  [![Python version](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
7
7
  [![MCP Protocol](https://img.shields.io/badge/MCP-Server-green.svg)](https://modelcontextprotocol.io/)
8
8
  [![Docker](https://img.shields.io/badge/docker-ready-blue.svg)](https://www.docker.com/)
9
- [![npm version](https://img.shields.io/npm/v/@raghulm/aegis-mcp.svg)](https://www.npmjs.com/package/@raghulm/aegis-mcp)
10
- [![npm downloads](https://img.shields.io/npm/dt/@raghulm/aegis-mcp.svg)](https://www.npmjs.com/package/@raghulm/aegis-mcp)
11
9
  </div>
12
10
 
13
11
  ---
14
12
 
15
13
  **Aegis MCP Server** empowers AI assistants (like Claude, Cursor, and GitHub Copilot) to perform cloud architecture administration, security scanning, and network analyses directly from their execution environments. It wraps powerful underlying tools and SDKs into secure, audited MCP tool sets.
16
14
 
17
- ### Quick Start
18
-
19
- ```bash
20
- npx @raghulm/aegis-mcp
21
- ```
22
-
23
- That's it! The installer will check Python, clone the repo, set up dependencies, and print your MCP config.
24
-
25
15
  ---
26
16
 
27
17
  ## 📸 Demo in Action
@@ -144,39 +134,14 @@ MEDIUM: 7
144
134
 
145
135
  ## 🚀 Getting Started
146
136
 
147
- ### One-Command Install (NPX)
137
+ ### Prerequisites
138
+
139
+ - **Python 3.12+**
140
+ - **Node.js 18+** (only if you want to run via npm/npx)
141
+ - **Semgrep** — `pip install semgrep` (for SAST scanning)
142
+ - Optional: AWS CLI / `boto3`, `kubectl`, Trivy (for their respective tools)
148
143
 
149
- ```bash
150
- npx @raghulm/aegis-mcp
151
- ```
152
-
153
- That's it. Aegis will:
154
- 1. ✅ Check your Python version (3.12+ required)
155
- 2. 📥 Clone and install the server into `~/.aegis-mcp`
156
- 3. 🖨️ Print your ready-to-paste MCP config
157
- 4. 🚀 Start the server
158
-
159
- **Additional commands:**
160
-
161
- ```bash
162
- npx @raghulm/aegis-mcp --install # Force reinstall / update
163
- npx @raghulm/aegis-mcp --config # Print MCP config JSON only
164
- npx @raghulm/aegis-mcp --help # Show all options
165
- ```
166
-
167
- > 💡 **Tip:** After running `npx @raghulm/aegis-mcp`, copy the printed MCP config into your AI agent's config file (Claude Desktop, Cursor, Windsurf, etc.)
168
-
169
- ---
170
-
171
- ### Prerequisites
172
-
173
- - **Node.js 16+** (for NPX install)
174
- - **Python 3.12+**
175
- - **Git**
176
- - **Semgrep** — `pip install semgrep` (for SAST scanning)
177
- - Optional: AWS CLI / `boto3`, `kubectl`, Trivy (for their respective tools)
178
-
179
- ### Manual Installation
144
+ ### Installation
180
145
 
181
146
  ```bash
182
147
  git clone https://github.com/raghulvj01/aegis-mcp.git
@@ -192,10 +157,20 @@ source .venv/bin/activate
192
157
  .venv\Scripts\activate
193
158
 
194
159
  # Install dependencies
195
- pip install -r requirements.txt
196
- ```
197
-
198
- ---
160
+ pip install -r requirements.txt
161
+ ```
162
+
163
+ ### Install via npm (Public Package)
164
+
165
+ ```bash
166
+ npm install -g @raghulm/aegis-mcp
167
+ # or run without installing globally:
168
+ npx -y @raghulm/aegis-mcp
169
+ ```
170
+
171
+ On first run, the npm wrapper creates a local Python virtual environment and installs dependencies from `requirements.txt` automatically.
172
+
173
+ ---
199
174
 
200
175
  ## 🤖 Usage with AI Agents
201
176
 
@@ -203,16 +178,16 @@ pip install -r requirements.txt
203
178
 
204
179
  Add to your MCP config (e.g., `mcp_config.json`):
205
180
 
206
- ```json
207
- {
208
- "mcpServers": {
209
- "aegis": {
210
- "command": "c:\\path\\to\\aegis-mcp\\.venv\\Scripts\\python.exe",
211
- "args": ["c:\\path\\to\\aegis-mcp\\run_stdio.py"]
212
- }
213
- }
214
- }
215
- ```
181
+ ```json
182
+ {
183
+ "mcpServers": {
184
+ "aegis": {
185
+ "command": "npx",
186
+ "args": ["-y", "@raghulm/aegis-mcp"]
187
+ }
188
+ }
189
+ }
190
+ ```
216
191
 
217
192
  > ✅ **All 12 tools work**, including Semgrep SAST.
218
193
 
@@ -222,16 +197,16 @@ Add to `claude_desktop_config.json`:
222
197
  - **Windows**: `%LOCALAPPDATA%\Packages\Claude_...\LocalCache\Roaming\Claude\`
223
198
  - **Mac**: `~/Library/Application Support/Claude/`
224
199
 
225
- ```json
226
- {
227
- "mcpServers": {
228
- "aegis": {
229
- "command": "c:\\path\\to\\aegis-mcp\\.venv\\Scripts\\python.exe",
230
- "args": ["c:\\path\\to\\aegis-mcp\\run_stdio.py"]
231
- }
232
- }
233
- }
234
- ```
200
+ ```json
201
+ {
202
+ "mcpServers": {
203
+ "aegis": {
204
+ "command": "npx",
205
+ "args": ["-y", "@raghulm/aegis-mcp"]
206
+ }
207
+ }
208
+ }
209
+ ```
235
210
 
236
211
  > ⚠️ **11 of 12 tools work.** Semgrep SAST does not work due to Windows pipe limitations.
237
212
 
@@ -326,17 +301,34 @@ The `@audit_tool_call` decorator emits structured JSON logs for every invocation
326
301
 
327
302
  ---
328
303
 
329
- ## 🛣️ Roadmap
330
-
331
- - [ ] Terraform security scanner
332
- - [ ] IAM policy risk detection
333
- - [ ] Kubernetes misconfiguration scanner (Basic `k8s_security_audit` implemented!)
334
- - [ ] GitHub Actions security audit
335
- - [ ] Cloud cost analysis tools
336
-
337
- ---
338
-
339
- ## 🤝 Contributing
304
+ ## 🛣️ Roadmap
305
+
306
+ - [ ] Terraform security scanner
307
+ - [ ] IAM policy risk detection
308
+ - [ ] Kubernetes misconfiguration scanner (Basic `k8s_security_audit` implemented!)
309
+ - [ ] GitHub Actions security audit
310
+ - [ ] Cloud cost analysis tools
311
+
312
+ ---
313
+
314
+ ## 📦 Publish to npm
315
+
316
+ ```bash
317
+ # 1) Login to npm
318
+ npm login
319
+
320
+ # 2) Verify package contents
321
+ npm run pack:check
322
+
323
+ # 3) Publish publicly
324
+ npm publish --access public
325
+ ```
326
+
327
+ For scoped packages like `@raghulm/aegis-mcp`, keep `--access public` in the publish command.
328
+
329
+ ---
330
+
331
+ ## 🤝 Contributing
340
332
 
341
333
  1. Fork the project
342
334
  2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
File without changes
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import wraps
4
+ from time import perf_counter
5
+ from typing import Any, Callable
6
+
7
+ from server.logging import get_logger
8
+
9
+ logger = get_logger("mcp.aegis.audit")
10
+
11
+
12
+
13
+ def audit_tool_call(tool_name: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
14
+ """Decorator that emits structured audit records for every tool invocation."""
15
+
16
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
17
+ @wraps(func)
18
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
19
+ started = perf_counter()
20
+ logger.info(
21
+ "tool_call_started",
22
+ extra={
23
+ "extra_payload": {
24
+ "event": "tool_call_started",
25
+ "tool": tool_name,
26
+ "args": str(args),
27
+ "kwargs": kwargs,
28
+ }
29
+ },
30
+ )
31
+ try:
32
+ result = func(*args, **kwargs)
33
+ duration_ms = int((perf_counter() - started) * 1000)
34
+ logger.info(
35
+ "tool_call_succeeded",
36
+ extra={
37
+ "extra_payload": {
38
+ "event": "tool_call_succeeded",
39
+ "tool": tool_name,
40
+ "duration_ms": duration_ms,
41
+ }
42
+ },
43
+ )
44
+ return result
45
+ except Exception as exc: # noqa: BLE001
46
+ duration_ms = int((perf_counter() - started) * 1000)
47
+ logger.error(
48
+ "tool_call_failed",
49
+ extra={
50
+ "extra_payload": {
51
+ "event": "tool_call_failed",
52
+ "tool": tool_name,
53
+ "duration_ms": duration_ms,
54
+ "error": str(exc),
55
+ }
56
+ },
57
+ )
58
+ raise
59
+
60
+ return wrapper
61
+
62
+ return decorator
package/bin/aegis-mcp.js CHANGED
@@ -1,164 +1,44 @@
1
- #!/usr/bin/env node
2
-
3
- const { execSync, spawn } = require("child_process");
4
- const fs = require("fs");
5
- const path = require("path");
6
- const os = require("os");
7
-
8
- const REPO_URL = "https://github.com/raghulvj01/aegis-mcp.git";
9
- const INSTALL_DIR = path.join(os.homedir(), ".aegis-mcp");
10
- const VERSION = require("../package.json").version;
11
-
12
- const colors = {
13
- reset: "\x1b[0m",
14
- cyan: "\x1b[36m",
15
- green: "\x1b[32m",
16
- yellow: "\x1b[33m",
17
- red: "\x1b[31m",
18
- bold: "\x1b[1m",
19
- };
20
-
21
- function log(msg, color = "reset") {
22
- console.error(`${colors[color]}${msg}${colors.reset}`);
23
- }
24
-
25
- function banner() {
26
- console.error(`
27
- ${colors.cyan}${colors.bold}
28
- 🛡️ Aegis MCP Server v${VERSION}
29
- Open-source DevSecOps MCP for AI Agents
30
- ${colors.reset}`);
31
- }
32
-
33
- function checkPython() {
34
- const candidates = ["python3", "python"];
35
- for (const cmd of candidates) {
36
- try {
37
- const version = execSync(`${cmd} --version 2>&1`, { encoding: "utf8" }).trim();
38
- const match = version.match(/Python (\d+)\.(\d+)/);
39
- if (match && (parseInt(match[1]) > 3 || (parseInt(match[1]) === 3 && parseInt(match[2]) >= 12))) {
40
- log(`✅ Found ${version}`, "green");
41
- return cmd;
42
- } else if (match) {
43
- log(`⚠️ Found ${version} but Python 3.12+ is required.`, "yellow");
44
- }
45
- } catch {}
46
- }
47
- log("❌ Python 3.12+ not found. Please install it from https://python.org", "red");
48
- process.exit(1);
49
- }
50
-
51
- function isInstalled() {
52
- return (
53
- fs.existsSync(INSTALL_DIR) &&
54
- fs.existsSync(path.join(INSTALL_DIR, "run_stdio.py"))
55
- );
56
- }
57
-
58
- function install(pythonCmd) {
59
- log("📦 Installing Aegis MCP...", "cyan");
60
-
61
- if (fs.existsSync(INSTALL_DIR)) {
62
- log("🔄 Updating existing installation...", "yellow");
63
- execSync("git pull", { cwd: INSTALL_DIR, stdio: "inherit" });
64
- } else {
65
- log(`📥 Cloning from ${REPO_URL}...`, "cyan");
66
- execSync(`git clone ${REPO_URL} "${INSTALL_DIR}"`, { stdio: "inherit" });
67
- }
68
-
69
- log("📦 Creating virtual environment...", "cyan");
70
- execSync(`${pythonCmd} -m venv .venv`, { cwd: INSTALL_DIR, stdio: "inherit" });
71
-
72
- const pip = process.platform === "win32"
73
- ? path.join(INSTALL_DIR, ".venv", "Scripts", "pip.exe")
74
- : path.join(INSTALL_DIR, ".venv", "bin", "pip");
75
-
76
- log("📦 Installing Python dependencies...", "cyan");
77
- execSync(`"${pip}" install -r requirements.txt`, { cwd: INSTALL_DIR, stdio: "inherit" });
78
-
79
- log("✅ Aegis MCP installed successfully!", "green");
80
- }
81
-
82
- function run() {
83
- const pythonExe = process.platform === "win32"
84
- ? path.join(INSTALL_DIR, ".venv", "Scripts", "python.exe")
85
- : path.join(INSTALL_DIR, ".venv", "bin", "python");
86
-
87
- const scriptPath = path.join(INSTALL_DIR, "run_stdio.py");
88
-
89
- log("🚀 Starting Aegis MCP Server...\n", "green");
90
-
91
- const child = spawn(pythonExe, [scriptPath], {
92
- stdio: "inherit",
93
- env: { ...process.env, MCP_AUTH_DISABLED: "true" },
94
- });
95
-
96
- child.on("error", (err) => {
97
- log(`\n❌ Failed to start Aegis MCP: ${err.message}`, "red");
98
- process.exit(1);
99
- });
100
-
101
- child.on("exit", (code) => {
102
- process.exit(code || 0);
103
- });
104
-
105
- process.on("SIGINT", () => child.kill("SIGINT"));
106
- process.on("SIGTERM", () => child.kill("SIGTERM"));
107
- }
108
-
109
- function printMCPConfig() {
110
- const pythonExe = process.platform === "win32"
111
- ? path.join(INSTALL_DIR, ".venv", "Scripts", "python.exe")
112
- : path.join(INSTALL_DIR, ".venv", "bin", "python");
113
-
114
- const scriptPath = path.join(INSTALL_DIR, "run_stdio.py");
115
-
116
- log("\n📋 Add this to your MCP config (claude_desktop_config.json / mcp_config.json):\n", "cyan");
117
- console.error(JSON.stringify({
118
- mcpServers: {
119
- aegis: {
120
- command: pythonExe,
121
- args: [scriptPath],
122
- },
123
- },
124
- }, null, 2));
125
- console.error();
126
- }
127
-
128
- // ── Main ──────────────────────────────────────────────────────────────────────
129
-
130
- const args = process.argv.slice(2);
131
-
132
- banner();
133
-
134
- if (args.includes("--help") || args.includes("-h")) {
135
- console.error(`Usage: npx aegis-mcp [options]
136
-
137
- Options:
138
- (no args) Install (if needed) and start the MCP server
139
- --install Force reinstall
140
- --config Print MCP config JSON and exit
141
- --version, -v Show version
142
- --help, -h Show this help
143
- `);
144
- process.exit(0);
145
- }
146
-
147
- if (args.includes("--version") || args.includes("-v")) {
148
- console.log(VERSION);
149
- process.exit(0);
150
- }
151
-
152
- const pythonCmd = checkPython();
153
-
154
- if (args.includes("--install") || !isInstalled()) {
155
- install(pythonCmd);
156
- }
157
-
158
- if (args.includes("--config")) {
159
- printMCPConfig();
160
- process.exit(0);
161
- }
162
-
163
- printMCPConfig();
164
- run();
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const path = require("path");
5
+ const { spawn } = require("child_process");
6
+ const { ensurePythonEnvironment } = require("./prepare-python-env");
7
+
8
+ function main() {
9
+ let runtime;
10
+ try {
11
+ runtime = ensurePythonEnvironment();
12
+ } catch (error) {
13
+ console.error(`[aegis-mcp] ${error.message}`);
14
+ process.exit(1);
15
+ }
16
+
17
+ const runScript = path.join(runtime.projectRoot, "run_stdio.py");
18
+ const env = {
19
+ ...process.env,
20
+ MCP_AUTH_DISABLED: process.env.MCP_AUTH_DISABLED || "true",
21
+ PATH: `${runtime.scriptsDir}${path.delimiter}${process.env.PATH || ""}`
22
+ };
23
+
24
+ const child = spawn(runtime.pythonInVenv, [runScript, ...process.argv.slice(2)], {
25
+ cwd: runtime.projectRoot,
26
+ env,
27
+ stdio: "inherit"
28
+ });
29
+
30
+ child.on("error", (error) => {
31
+ console.error(`[aegis-mcp] Failed to start MCP server: ${error.message}`);
32
+ process.exit(1);
33
+ });
34
+
35
+ child.on("exit", (code, signal) => {
36
+ if (signal) {
37
+ process.kill(process.pid, signal);
38
+ return;
39
+ }
40
+ process.exit(code ?? 0);
41
+ });
42
+ }
43
+
44
+ main();
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const crypto = require("crypto");
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const { spawnSync } = require("child_process");
8
+
9
+ const PROJECT_ROOT = path.resolve(__dirname, "..");
10
+ const REQUIREMENTS_FILE = path.join(PROJECT_ROOT, "requirements.txt");
11
+ const VENV_DIR = path.join(PROJECT_ROOT, ".venv");
12
+ const REQUIREMENTS_STAMP = path.join(VENV_DIR, ".requirements.sha256");
13
+ const IS_WINDOWS = process.platform === "win32";
14
+
15
+ function run(command, args, options = {}) {
16
+ return spawnSync(command, args, {
17
+ cwd: PROJECT_ROOT,
18
+ stdio: "inherit",
19
+ ...options
20
+ });
21
+ }
22
+
23
+ function candidatePythonCommands() {
24
+ const override = process.env.AEGIS_PYTHON;
25
+ if (override && override.trim()) {
26
+ const pieces = override.trim().split(/\s+/);
27
+ return [{ command: pieces[0], prefixArgs: pieces.slice(1), label: override.trim() }];
28
+ }
29
+
30
+ if (IS_WINDOWS) {
31
+ return [
32
+ { command: "py", prefixArgs: ["-3"], label: "py -3" },
33
+ { command: "python", prefixArgs: [], label: "python" },
34
+ { command: "python3", prefixArgs: [], label: "python3" }
35
+ ];
36
+ }
37
+
38
+ return [
39
+ { command: "python3", prefixArgs: [], label: "python3" },
40
+ { command: "python", prefixArgs: [], label: "python" }
41
+ ];
42
+ }
43
+
44
+ function findWorkingPython() {
45
+ for (const candidate of candidatePythonCommands()) {
46
+ const versionCheck = run(candidate.command, [...candidate.prefixArgs, "--version"], {
47
+ stdio: "pipe",
48
+ encoding: "utf8"
49
+ });
50
+ if (versionCheck.status === 0) {
51
+ return candidate;
52
+ }
53
+ }
54
+
55
+ throw new Error(
56
+ "Python 3.12+ was not found. Install Python and make sure it is on PATH, or set AEGIS_PYTHON."
57
+ );
58
+ }
59
+
60
+ function venvPythonPath() {
61
+ if (IS_WINDOWS) {
62
+ return path.join(VENV_DIR, "Scripts", "python.exe");
63
+ }
64
+ return path.join(VENV_DIR, "bin", "python");
65
+ }
66
+
67
+ function venvScriptsPath() {
68
+ if (IS_WINDOWS) {
69
+ return path.join(VENV_DIR, "Scripts");
70
+ }
71
+ return path.join(VENV_DIR, "bin");
72
+ }
73
+
74
+ function requirementsHash() {
75
+ const content = fs.readFileSync(REQUIREMENTS_FILE);
76
+ return crypto.createHash("sha256").update(content).digest("hex");
77
+ }
78
+
79
+ function ensureVirtualEnvironment(python) {
80
+ const pythonInVenv = venvPythonPath();
81
+ if (fs.existsSync(pythonInVenv)) {
82
+ return;
83
+ }
84
+
85
+ console.error("[aegis-mcp] Creating Python virtual environment...");
86
+ const created = run(python.command, [...python.prefixArgs, "-m", "venv", VENV_DIR]);
87
+ if (created.status !== 0) {
88
+ throw new Error("Failed to create Python virtual environment.");
89
+ }
90
+ }
91
+
92
+ function installDependencies(pythonInVenv) {
93
+ console.error("[aegis-mcp] Installing Python dependencies from requirements.txt...");
94
+
95
+ const pipUpgrade = run(pythonInVenv, ["-m", "pip", "install", "--upgrade", "pip"]);
96
+ if (pipUpgrade.status !== 0) {
97
+ throw new Error("Failed to upgrade pip in virtual environment.");
98
+ }
99
+
100
+ const pipInstall = run(pythonInVenv, ["-m", "pip", "install", "-r", REQUIREMENTS_FILE]);
101
+ if (pipInstall.status !== 0) {
102
+ throw new Error("Failed to install Python dependencies.");
103
+ }
104
+ }
105
+
106
+ function ensurePythonEnvironment() {
107
+ if (!fs.existsSync(REQUIREMENTS_FILE)) {
108
+ throw new Error("requirements.txt was not found in package root.");
109
+ }
110
+
111
+ const python = findWorkingPython();
112
+ ensureVirtualEnvironment(python);
113
+
114
+ const pythonInVenv = venvPythonPath();
115
+ const expectedHash = requirementsHash();
116
+ const currentHash = fs.existsSync(REQUIREMENTS_STAMP)
117
+ ? fs.readFileSync(REQUIREMENTS_STAMP, "utf8").trim()
118
+ : "";
119
+
120
+ if (currentHash !== expectedHash) {
121
+ installDependencies(pythonInVenv);
122
+ fs.writeFileSync(REQUIREMENTS_STAMP, `${expectedHash}\n`, "utf8");
123
+ }
124
+
125
+ return {
126
+ projectRoot: PROJECT_ROOT,
127
+ pythonInVenv,
128
+ scriptsDir: venvScriptsPath()
129
+ };
130
+ }
131
+
132
+ module.exports = {
133
+ ensurePythonEnvironment
134
+ };
135
+
136
+ if (require.main === module) {
137
+ try {
138
+ ensurePythonEnvironment();
139
+ } catch (error) {
140
+ console.error(`[aegis-mcp] ${error.message}`);
141
+ process.exit(1);
142
+ }
143
+ }