diff --git a/.mcpbignore b/.mcpbignore new file mode 100644 index 0000000..4ee7ce1 --- /dev/null +++ b/.mcpbignore @@ -0,0 +1,14 @@ +.git +node_modules +dist +dist-old* +releases +data +tests +docs/screenshots +electron/assets +homebrew +.project +.unused +*.dmg +*.zip diff --git a/README.md b/README.md index 68b05c6..b9ebe97 100644 --- a/README.md +++ b/README.md @@ -555,6 +555,132 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on: - Creating new notification types - Running and writing tests +## Claude Desktop MCP Extension + +Argus ships with a built-in MCP server that integrates directly with Claude Desktop. Ask Claude questions about what AI agents are doing on your machine — in natural language. + +### Setup + +Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`): + +```json +{ + "mcpServers": { + "argus": { + "command": "node", + "args": ["/path/to/argus/src/mcp/server.js"] + } + } +} +``` + +Restart Claude Desktop. Argus tools appear automatically. + +### Available Tools + +| Tool | Description | +|------|-------------| +| `get_active_ai_processes` | List AI agents detected in the last N hours | +| `get_file_accesses` | Sensitive file access alerts (credentials, browser data) | +| `get_network_activity` | Network connections to AI endpoints | +| `get_sessions` | Session history with start/stop/duration | +| `get_daily_summary` | Daily stats for any date | +| `get_injection_alerts` | Prompt injection detections | +| `get_ai_usage` | Token usage and estimated API costs | +| `get_monitoring_status` | Overall Argus status and stats | + +All tools are **read-only** — they query the local SQLite database and never modify anything. + +### Example 1: Check what's running + +**Prompt:** "What AI agents ran on my machine today?" + +**Response:** +```json +[ + { "name": "claude", "app_label": "Claude Code (CLI)", "category": "AI Code Editor" }, + { "name": "Claude", "app_label": "Claude Desktop", "category": "LLM Desktop" }, + { "name": "ollama", "app_label": "Ollama", "category": "Local LLM" } +] +``` + +### Example 2: Check for credential access + +**Prompt:** "Did any AI app access my SSH keys or credentials?" + +**Response:** +```json +[ + { + "process_name": "Claude", + "app_label": "Claude Desktop", + "file_path": "/Users/you/.ssh/id_rsa", + "sensitivity": "credentials", + "is_alert": 1, + "timestamp": "2026-04-03T14:22:19.000Z" + }, + { + "process_name": "Claude", + "app_label": "Claude Desktop", + "file_path": "/Users/you/Library/Keychains/login.keychain-db", + "sensitivity": "credentials", + "is_alert": 1, + "timestamp": "2026-04-03T14:10:04.000Z" + } +] +``` + +### Example 3: Daily summary + +**Prompt:** "Give me a summary of AI activity for yesterday" + +**Response:** +```json +{ + "date": "2026-04-02", + "processCount": 4, + "fileAlertCount": 21, + "networkEventCount": 5265, + "topPorts": [443, 80, 9222], + "aiServicesHit": ["Anthropic", "OpenAI", "GitHub Copilot"] +} +``` + +## Privacy Policy + +Argus is a **local-only** privacy monitoring tool. This policy covers both the Argus application and its MCP extension for Claude Desktop. + +### Data Collection +- Argus monitors AI application activity (processes, file accesses, network connections) on your local machine +- All monitoring data is stored in a local SQLite database at `~/.argus/data.db` +- The MCP extension reads from this same local database + +### Data Transmission +- **No data is ever transmitted to any external server, cloud service, or third party** +- The MCP extension communicates only with Claude Desktop via local STDIO (standard input/output) +- There is no telemetry, analytics, crash reporting, or tracking of any kind +- The application makes zero outbound network connections + +### Data Storage +- All data remains on your local filesystem at `~/.argus/` +- Database file permissions are set to owner-only (mode 0600) +- You can inspect, export, or delete the database at any time +- Old events are automatically cleaned up after 7 days + +### Data Sharing +- Argus does not share data with anyone +- The MCP extension provides data only to Claude Desktop running on the same machine +- No third-party services, APIs, or SDKs are used for data processing + +### Your Control +- You can pause/resume monitoring at any time via the tray menu +- You can delete all data by removing `~/.argus/` +- All source code is open source and auditable at https://github.com/cortexark/argus +- Uninstall instructions: `argus uninstall && npm uninstall -g argus-monitor` + +### Contact +For privacy questions or concerns, open an issue at https://github.com/cortexark/argus/issues + ## Related Tools - **Little Snitch** — Network firewall (port-level monitoring) diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..5ef561d --- /dev/null +++ b/manifest.json @@ -0,0 +1,59 @@ +{ + "manifest_version": "0.3", + "name": "argus", + "version": "1.0.0", + "description": "Monitor what AI agents access on your machine — files, credentials, network connections, and browser data. Argus sees everything your AI apps do.", + "author": { + "name": "cortexark", + "url": "https://github.com/cortexark" + }, + "homepage": "https://github.com/cortexark/argus", + "documentation": "https://github.com/cortexark/argus#claude-desktop-mcp-extension", + "support": "https://github.com/cortexark/argus/issues", + "license": "MIT", + "server": { + "type": "node", + "entry_point": "src/mcp/server.js", + "mcp_config": { + "command": "node", + "args": ["${__dirname}/src/mcp/server.js"] + } + }, + "tools": [ + { + "name": "get_active_ai_processes", + "description": "List AI agents and LLM apps detected on this machine" + }, + { + "name": "get_file_accesses", + "description": "Show sensitive file accesses by AI agents (credentials, browser data, documents)" + }, + { + "name": "get_network_activity", + "description": "Show network connections made by AI agents" + }, + { + "name": "get_sessions", + "description": "Show AI agent session history (start, stop, duration)" + }, + { + "name": "get_daily_summary", + "description": "Get daily stats — process count, alerts, network events, AI services" + }, + { + "name": "get_injection_alerts", + "description": "Show prompt injection attempts detected in files" + }, + { + "name": "get_ai_usage", + "description": "Show AI tool token usage and estimated costs" + }, + { + "name": "get_monitoring_status", + "description": "Check monitoring status and overall stats" + } + ], + "privacy_policies": [ + "https://github.com/cortexark/argus#privacy-policy" + ] +} diff --git a/package.json b/package.json index d7c8f4e..68c2ebb 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ } }, "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", "better-sqlite3": "^12.8.0", "blessed": "^0.1.81", "chalk": "^5.3.0", @@ -78,7 +79,8 @@ "pino": "^10.3.1", "pino-pretty": "^13.1.3", "pino-roll": "^4.0.0", - "ps-list": "^8.1.1" + "ps-list": "^8.1.1", + "zod": "^4.3.6" }, "type": "module", "engines": { diff --git a/src/mcp/server.js b/src/mcp/server.js new file mode 100644 index 0000000..30783e9 --- /dev/null +++ b/src/mcp/server.js @@ -0,0 +1,325 @@ +#!/usr/bin/env node + +/** + * Argus MCP Server — Claude Desktop Extension + * + * Exposes Argus monitoring data as MCP tools so Claude can answer + * questions like "what AI agents are running?" or "show file accesses". + * + * Transport: STDIO (launched by Claude Desktop via claude_desktop_config.json) + * Database: reads the same SQLite DB that Argus writes to (~/.argus/data.db) + * + * Most tools are read-only — they query the local database. + * The kill_ai_process tool can terminate a running AI process. + * No data leaves the machine. + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; +import { execFileSync } from 'node:child_process'; +import { initializeDatabase } from '../db/schema.js'; +import { config } from '../lib/config.js'; +import { + getActiveProcesses, + getRecentAlerts, + getNetworkEvents, + getRecentSessions, + getOpenSessions, + getDailySummary, + getInjectionAlerts, + getRecentUsageSnapshots, + getApprovalDecisions, +} from '../db/store.js'; + +// --------------------------------------------------------------------------- +// Database +// --------------------------------------------------------------------------- + +const db = initializeDatabase(config.DB_PATH); + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function hoursAgoISO(hours) { + return new Date(Date.now() - hours * 3600_000).toISOString(); +} + +function formatRows(rows) { + if (!rows || rows.length === 0) return 'No results found.'; + return JSON.stringify(rows, null, 2); +} + +function todayDateStr() { + return new Date().toISOString().slice(0, 10); +} + +function safeTool(fn) { + return async (args) => { + try { + return await fn(args); + } catch (error) { + return { + content: [{ type: 'text', text: `Error: ${error.message}` }], + isError: true, + }; + } + }; +} + +// --------------------------------------------------------------------------- +// Read-only annotations shared by all tools +// --------------------------------------------------------------------------- + +const READ_ONLY = Object.freeze({ + readOnlyHint: true, + destructiveHint: false, + openWorldHint: false, +}); + +// --------------------------------------------------------------------------- +// MCP Server +// --------------------------------------------------------------------------- + +const server = new McpServer({ + name: 'argus', + version: '1.0.0', +}); + +// -- get_active_ai_processes ------------------------------------------------ + +server.registerTool( + 'get_active_ai_processes', + { + title: 'Active AI Processes', + description: 'List AI agents and LLM apps detected on this machine in the last N hours. Returns process name, app label, and category for each detected AI application.', + inputSchema: z.object({ + hours: z.number().min(1).max(168).default(24) + .describe('How far back to look (hours, max 168 = 7 days)'), + }), + annotations: READ_ONLY, + }, + safeTool(({ hours }) => ({ + content: [{ type: 'text', text: formatRows(getActiveProcesses(db, hoursAgoISO(hours))) }], + })), +); + +// -- get_file_accesses ------------------------------------------------------ + +server.registerTool( + 'get_file_accesses', + { + title: 'Sensitive File Accesses', + description: 'Show sensitive file accesses by AI agents — credentials (.ssh, .aws, Keychain), browser data (Chrome, Safari passwords), documents, and system files. Returns up to 100 alerts with timestamps, file paths, app names, and severity.', + inputSchema: z.object({ + hours: z.number().min(1).max(168).default(24) + .describe('How far back to look (hours)'), + }), + annotations: READ_ONLY, + }, + safeTool(({ hours }) => ({ + content: [{ type: 'text', text: formatRows(getRecentAlerts(db, hoursAgoISO(hours))) }], + })), +); + +// -- get_network_activity --------------------------------------------------- + +server.registerTool( + 'get_network_activity', + { + title: 'AI Network Activity', + description: 'Show network connections made by AI agents — remote hosts, ports, protocols, and identified AI services (Anthropic, OpenAI, Google, etc.). Returns up to 200 events with byte counts.', + inputSchema: z.object({ + hours: z.number().min(1).max(168).default(24) + .describe('How far back to look (hours)'), + }), + annotations: READ_ONLY, + }, + safeTool(({ hours }) => ({ + content: [{ type: 'text', text: formatRows(getNetworkEvents(db, hoursAgoISO(hours))) }], + })), +); + +// -- get_sessions ----------------------------------------------------------- + +server.registerTool( + 'get_sessions', + { + title: 'AI Session History', + description: 'Show AI agent session history — when each app started, stopped, and how long it ran. Use active_only=true to see only currently running sessions.', + inputSchema: z.object({ + active_only: z.boolean().default(false) + .describe('If true, only show currently running sessions'), + }), + annotations: READ_ONLY, + }, + safeTool(({ active_only }) => ({ + content: [{ type: 'text', text: formatRows(active_only ? getOpenSessions(db) : getRecentSessions(db)) }], + })), +); + +// -- get_daily_summary ------------------------------------------------------ + +server.registerTool( + 'get_daily_summary', + { + title: 'Daily Summary', + description: 'Get a summary for a specific day — unique AI process count, file access alert count, network event count, top ports used, and AI services contacted.', + inputSchema: z.object({ + date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).default(todayDateStr()) + .describe('Date in YYYY-MM-DD format (defaults to today)'), + }), + annotations: READ_ONLY, + }, + safeTool(({ date }) => ({ + content: [{ type: 'text', text: JSON.stringify(getDailySummary(db, date), null, 2) }], + })), +); + +// -- get_injection_alerts --------------------------------------------------- + +server.registerTool( + 'get_injection_alerts', + { + title: 'Prompt Injection Alerts', + description: 'Show prompt injection attempts detected in files accessed by AI agents. Returns severity, matched patterns, file path, and a snippet of the suspicious content.', + inputSchema: z.object({ + hours: z.number().min(1).max(168).default(24) + .describe('How far back to look (hours)'), + }), + annotations: READ_ONLY, + }, + safeTool(({ hours }) => ({ + content: [{ type: 'text', text: formatRows(getInjectionAlerts(db, hoursAgoISO(hours))) }], + })), +); + +// -- get_ai_usage ----------------------------------------------------------- + +server.registerTool( + 'get_ai_usage', + { + title: 'AI Token Usage', + description: 'Show AI tool token usage and estimated API costs for Claude, GPT, Codex, and other tools. Returns provider, model, token count, estimated cost in USD, and session count.', + inputSchema: z.object({}), + annotations: READ_ONLY, + }, + safeTool(() => ({ + content: [{ type: 'text', text: formatRows(getRecentUsageSnapshots(db)) }], + })), +); + +// -- get_monitoring_status -------------------------------------------------- + +server.registerTool( + 'get_monitoring_status', + { + title: 'Monitoring Status', + description: 'Check if Argus monitoring is active and get overall stats — today\'s summary, active session count, privacy mode, and database path.', + inputSchema: z.object({}), + annotations: READ_ONLY, + }, + safeTool(() => { + const today = new Date().toISOString().slice(0, 10); + const summary = getDailySummary(db, today); + const activeSessions = getOpenSessions(db); + const decisions = getApprovalDecisions(db); + + const status = { + monitoring_active: activeSessions.length > 0, + today_summary: summary, + active_sessions: activeSessions.length, + total_approval_decisions: decisions.size, + privacy_mode: config.PRIVACY_MODE, + db_path: config.DB_PATH, + }; + + return { + content: [{ type: 'text', text: JSON.stringify(status, null, 2) }], + }; + }), +); + +// -- kill_ai_process -------------------------------------------------------- + +server.registerTool( + 'kill_ai_process', + { + title: 'Kill AI Process', + description: 'Terminate a running AI agent process by PID. Use get_sessions with active_only=true first to find the PID. Sends SIGTERM for graceful shutdown, or SIGKILL if force=true.', + inputSchema: z.object({ + pid: z.number().int().positive() + .describe('Process ID to terminate (from get_sessions)'), + force: z.boolean().default(false) + .describe('If true, send SIGKILL instead of SIGTERM'), + }), + annotations: { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: false, + }, + }, + safeTool(({ pid, force }) => { + const sessions = getOpenSessions(db); + const match = sessions.find((s) => s.pid === pid); + if (!match) { + return { + content: [{ type: 'text', text: `Refused: PID ${pid} is not a known AI process tracked by Argus. Only monitored AI processes can be killed.` }], + }; + } + + const signal = force ? 'SIGKILL' : 'SIGTERM'; + try { + process.kill(pid, signal); + return { + content: [{ type: 'text', text: `Sent ${signal} to PID ${pid} (${match.app_label} — ${match.process_name}). Process should terminate shortly.` }], + }; + } catch (err) { + if (err.code === 'ESRCH') { + return { + content: [{ type: 'text', text: `PID ${pid} (${match.app_label}) is no longer running.` }], + }; + } + throw err; + } + }), +); + +// -- send_notification ------------------------------------------------------ + +server.registerTool( + 'send_notification', + { + title: 'Send Notification', + description: 'Send a macOS system notification from Argus. Useful for alerting the user about findings or actions taken.', + inputSchema: z.object({ + title: z.string().max(100) + .describe('Notification title'), + message: z.string().max(500) + .describe('Notification body text'), + }), + annotations: { + readOnlyHint: false, + destructiveHint: false, + openWorldHint: false, + }, + }, + safeTool(({ title, message }) => { + execFileSync('osascript', [ + '-e', + `display notification "${message}" with title "Argus — ${title}"`, + ]); + return { + content: [{ type: 'text', text: `Notification sent: "${title}" — ${message}` }], + }; + }), +); + +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- + +const transport = new StdioServerTransport(); +await server.connect(transport);