From 76bb39aedb6e6d1f85931cab7df50f913e25cd16 Mon Sep 17 00:00:00 2001 From: mission-agi Date: Wed, 8 Apr 2026 00:31:07 -0700 Subject: [PATCH] feat: add Claude Desktop MCP extension with 10 monitoring tools STDIO-based MCP server that exposes Argus monitoring data to Claude Desktop as local tools. Includes process detection, file access alerts, network activity, session history, usage tracking, injection detection, notifications, and process termination. Adds manifest.json for Anthropic Connectors Directory submission, privacy policy, and 3 usage examples in README. --- .mcpbignore | 14 ++ README.md | 126 ++++++++++++++++++ manifest.json | 59 +++++++++ package.json | 4 +- src/mcp/server.js | 325 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 .mcpbignore create mode 100644 manifest.json create mode 100644 src/mcp/server.js 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);