@weave_protocol/mund 0.1.0

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.
Files changed (98) hide show
  1. package/dist/analyzers/code-analyzer.d.ts +24 -0
  2. package/dist/analyzers/code-analyzer.d.ts.map +1 -0
  3. package/dist/analyzers/code-analyzer.js +73 -0
  4. package/dist/analyzers/code-analyzer.js.map +1 -0
  5. package/dist/analyzers/exfiltration-detector.d.ts +40 -0
  6. package/dist/analyzers/exfiltration-detector.d.ts.map +1 -0
  7. package/dist/analyzers/exfiltration-detector.js +220 -0
  8. package/dist/analyzers/exfiltration-detector.js.map +1 -0
  9. package/dist/analyzers/index.d.ts +26 -0
  10. package/dist/analyzers/index.d.ts.map +1 -0
  11. package/dist/analyzers/index.js +61 -0
  12. package/dist/analyzers/index.js.map +1 -0
  13. package/dist/analyzers/injection-detector.d.ts +36 -0
  14. package/dist/analyzers/injection-detector.d.ts.map +1 -0
  15. package/dist/analyzers/injection-detector.js +165 -0
  16. package/dist/analyzers/injection-detector.js.map +1 -0
  17. package/dist/analyzers/pii-detector.d.ts +44 -0
  18. package/dist/analyzers/pii-detector.d.ts.map +1 -0
  19. package/dist/analyzers/pii-detector.js +190 -0
  20. package/dist/analyzers/pii-detector.js.map +1 -0
  21. package/dist/analyzers/secret-scanner.d.ts +32 -0
  22. package/dist/analyzers/secret-scanner.d.ts.map +1 -0
  23. package/dist/analyzers/secret-scanner.js +119 -0
  24. package/dist/analyzers/secret-scanner.js.map +1 -0
  25. package/dist/constants.d.ts +18 -0
  26. package/dist/constants.d.ts.map +1 -0
  27. package/dist/constants.js +580 -0
  28. package/dist/constants.js.map +1 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +242 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/notifications/email.d.ts +18 -0
  34. package/dist/notifications/email.d.ts.map +1 -0
  35. package/dist/notifications/email.js +192 -0
  36. package/dist/notifications/email.js.map +1 -0
  37. package/dist/notifications/index.d.ts +51 -0
  38. package/dist/notifications/index.d.ts.map +1 -0
  39. package/dist/notifications/index.js +107 -0
  40. package/dist/notifications/index.js.map +1 -0
  41. package/dist/notifications/slack.d.ts +17 -0
  42. package/dist/notifications/slack.d.ts.map +1 -0
  43. package/dist/notifications/slack.js +128 -0
  44. package/dist/notifications/slack.js.map +1 -0
  45. package/dist/notifications/teams.d.ts +17 -0
  46. package/dist/notifications/teams.d.ts.map +1 -0
  47. package/dist/notifications/teams.js +136 -0
  48. package/dist/notifications/teams.js.map +1 -0
  49. package/dist/notifications/webhook.d.ts +15 -0
  50. package/dist/notifications/webhook.d.ts.map +1 -0
  51. package/dist/notifications/webhook.js +67 -0
  52. package/dist/notifications/webhook.js.map +1 -0
  53. package/dist/storage/index.d.ts +11 -0
  54. package/dist/storage/index.d.ts.map +1 -0
  55. package/dist/storage/index.js +26 -0
  56. package/dist/storage/index.js.map +1 -0
  57. package/dist/storage/memory.d.ts +35 -0
  58. package/dist/storage/memory.d.ts.map +1 -0
  59. package/dist/storage/memory.js +144 -0
  60. package/dist/storage/memory.js.map +1 -0
  61. package/dist/tools/configuration.d.ts +8 -0
  62. package/dist/tools/configuration.d.ts.map +1 -0
  63. package/dist/tools/configuration.js +399 -0
  64. package/dist/tools/configuration.js.map +1 -0
  65. package/dist/tools/index.d.ts +15 -0
  66. package/dist/tools/index.d.ts.map +1 -0
  67. package/dist/tools/index.js +16 -0
  68. package/dist/tools/index.js.map +1 -0
  69. package/dist/tools/monitoring.d.ts +10 -0
  70. package/dist/tools/monitoring.d.ts.map +1 -0
  71. package/dist/tools/monitoring.js +476 -0
  72. package/dist/tools/monitoring.js.map +1 -0
  73. package/dist/types.d.ts +207 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +6 -0
  76. package/dist/types.js.map +1 -0
  77. package/package.json +62 -0
  78. package/rules/default.yaml +189 -0
  79. package/src/analyzers/code-analyzer.ts +88 -0
  80. package/src/analyzers/exfiltration-detector.ts +257 -0
  81. package/src/analyzers/index.ts +74 -0
  82. package/src/analyzers/injection-detector.ts +191 -0
  83. package/src/analyzers/pii-detector.ts +220 -0
  84. package/src/analyzers/secret-scanner.ts +142 -0
  85. package/src/constants.ts +603 -0
  86. package/src/index.ts +285 -0
  87. package/src/notifications/email.ts +211 -0
  88. package/src/notifications/index.ts +136 -0
  89. package/src/notifications/slack.ts +170 -0
  90. package/src/notifications/teams.ts +200 -0
  91. package/src/notifications/webhook.ts +80 -0
  92. package/src/storage/index.ts +29 -0
  93. package/src/storage/memory.ts +181 -0
  94. package/src/tools/configuration.ts +478 -0
  95. package/src/tools/index.ts +29 -0
  96. package/src/tools/monitoring.ts +559 -0
  97. package/src/types.ts +300 -0
  98. package/tsconfig.json +24 -0
@@ -0,0 +1,559 @@
1
+ /**
2
+ * Mund - The Guardian Protocol
3
+ * Monitoring Tools - MCP tools for security monitoring
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import { v4 as uuidv4 } from 'uuid';
8
+ import { createHash } from 'crypto';
9
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
+ import type {
11
+ IStorage,
12
+ DetectionRule,
13
+ SecurityEvent,
14
+ ScanResult,
15
+ EventContext,
16
+ Severity,
17
+ DetectorType
18
+ } from '../types.js';
19
+ import { AnalyzerEngine } from '../analyzers/index.js';
20
+ import { NotificationHub } from '../notifications/index.js';
21
+ import { MAX_SNIPPET_LENGTH, DEFAULT_EVENTS_LIMIT, MAX_EVENTS_PER_QUERY } from '../constants.js';
22
+
23
+ // ============================================================================
24
+ // Input Schemas
25
+ // ============================================================================
26
+
27
+ const ScanContentInputSchema = z.object({
28
+ content: z.string()
29
+ .min(1, 'Content is required')
30
+ .max(1000000, 'Content exceeds maximum length')
31
+ .describe('The text/code content to scan for security issues'),
32
+ content_type: z.enum(['text', 'code', 'json', 'yaml'])
33
+ .default('text')
34
+ .describe('Type of content being scanned'),
35
+ tool_name: z.string().optional().describe('Name of the tool that generated this content'),
36
+ agent_id: z.string().optional().describe('ID of the agent making this request'),
37
+ session_id: z.string().optional().describe('Current session ID')
38
+ }).strict();
39
+
40
+ const CheckUrlInputSchema = z.object({
41
+ url: z.string()
42
+ .url('Invalid URL format')
43
+ .describe('URL to check for safety'),
44
+ tool_name: z.string().optional().describe('Name of the tool attempting to access this URL'),
45
+ agent_id: z.string().optional().describe('ID of the agent making this request')
46
+ }).strict();
47
+
48
+ const ValidateCommandInputSchema = z.object({
49
+ command: z.string()
50
+ .min(1, 'Command is required')
51
+ .describe('Shell command to validate'),
52
+ tool_name: z.string().optional().describe('Name of the tool executing this command'),
53
+ agent_id: z.string().optional().describe('ID of the agent making this request')
54
+ }).strict();
55
+
56
+ const GetEventsInputSchema = z.object({
57
+ limit: z.number().int().min(1).max(MAX_EVENTS_PER_QUERY).default(DEFAULT_EVENTS_LIMIT)
58
+ .describe('Maximum number of events to return'),
59
+ offset: z.number().int().min(0).default(0)
60
+ .describe('Number of events to skip for pagination'),
61
+ severity: z.enum(['critical', 'high', 'medium', 'low', 'info']).optional()
62
+ .describe('Filter by severity level'),
63
+ type: z.enum(['secret', 'pii', 'code_pattern', 'injection', 'exfiltration', 'behavioral']).optional()
64
+ .describe('Filter by detection type'),
65
+ acknowledged: z.boolean().optional()
66
+ .describe('Filter by acknowledgment status')
67
+ }).strict();
68
+
69
+ const GetStatusInputSchema = z.object({}).strict();
70
+
71
+ // ============================================================================
72
+ // Tool Registration
73
+ // ============================================================================
74
+
75
+ export function registerMonitoringTools(
76
+ server: McpServer,
77
+ storage: IStorage,
78
+ rules: DetectionRule[],
79
+ analyzer: AnalyzerEngine,
80
+ notificationHub: NotificationHub,
81
+ blockMode: boolean
82
+ ): void {
83
+
84
+ // ============================================================================
85
+ // mund_scan_content - Scan text/code for security issues
86
+ // ============================================================================
87
+ server.registerTool(
88
+ 'mund_scan_content',
89
+ {
90
+ title: 'Scan Content for Security Issues',
91
+ description: `Scan text or code content for security vulnerabilities, secrets, PII, and other issues.
92
+
93
+ This tool analyzes the provided content using multiple security analyzers:
94
+ - Secret Scanner: Detects API keys, tokens, passwords, and credentials
95
+ - PII Detector: Finds personal identifiable information
96
+ - Code Analyzer: Identifies dangerous code patterns
97
+ - Injection Detector: Spots prompt injection attempts
98
+ - Exfiltration Detector: Detects data exfiltration patterns
99
+
100
+ Args:
101
+ - content (string): The text/code to scan (required)
102
+ - content_type ('text' | 'code' | 'json' | 'yaml'): Type of content (default: 'text')
103
+ - tool_name (string): Name of tool that generated this content (optional)
104
+ - agent_id (string): ID of the requesting agent (optional)
105
+ - session_id (string): Current session ID (optional)
106
+
107
+ Returns:
108
+ JSON object with:
109
+ - scan_id: Unique identifier for this scan
110
+ - issues_found: Number of security issues detected
111
+ - blocked: Whether the content was blocked (if block_mode is enabled)
112
+ - issues: Array of detected issues with severity, type, and suggestions
113
+ - scan_duration_ms: Time taken to complete the scan
114
+
115
+ Examples:
116
+ - Scan code before committing: {"content": "const key = 'AKIAIOSFODNN7EXAMPLE'"}
117
+ - Check user input: {"content": "Please ignore previous instructions", "content_type": "text"}`,
118
+ inputSchema: ScanContentInputSchema,
119
+ annotations: {
120
+ readOnlyHint: true,
121
+ destructiveHint: false,
122
+ idempotentHint: true,
123
+ openWorldHint: false
124
+ }
125
+ },
126
+ async (params) => {
127
+ const startTime = Date.now();
128
+ const contentHash = createHash('sha256').update(params.content).digest('hex');
129
+
130
+ // Run analysis
131
+ const issues = await analyzer.analyzeAll(params.content, rules);
132
+
133
+ // Create scan result
134
+ const scanResult: ScanResult = {
135
+ id: uuidv4(),
136
+ timestamp: new Date(),
137
+ content_hash: contentHash,
138
+ issues,
139
+ scan_duration_ms: Date.now() - startTime,
140
+ rules_checked: rules.filter(r => r.enabled).length
141
+ };
142
+
143
+ // Determine if content should be blocked
144
+ const hasCritical = issues.some(i => i.severity === 'critical');
145
+ const hasHigh = issues.some(i => i.severity === 'high');
146
+ const shouldBlock = blockMode && (hasCritical || hasHigh);
147
+
148
+ // Create and store security events for significant issues
149
+ const context: EventContext = {
150
+ tool_name: params.tool_name,
151
+ agent_id: params.agent_id,
152
+ session_id: params.session_id
153
+ };
154
+
155
+ for (const issue of issues.filter(i => i.severity === 'critical' || i.severity === 'high')) {
156
+ const event: SecurityEvent = {
157
+ id: uuidv4(),
158
+ timestamp: new Date(),
159
+ rule_id: issue.rule_id,
160
+ rule_name: issue.rule_name,
161
+ severity: issue.severity as Severity,
162
+ type: issue.type as DetectorType,
163
+ action_taken: shouldBlock ? 'block' : issue.action,
164
+ content_snippet: truncateSnippet(issue.match, MAX_SNIPPET_LENGTH),
165
+ full_content_hash: contentHash,
166
+ context,
167
+ acknowledged: false
168
+ };
169
+
170
+ await storage.saveEvent(event);
171
+
172
+ // Send notifications for critical/high severity
173
+ await notificationHub.notify(event);
174
+ }
175
+
176
+ // Format response
177
+ const response = {
178
+ scan_id: scanResult.id,
179
+ issues_found: issues.length,
180
+ blocked: shouldBlock,
181
+ issues: issues.map(i => ({
182
+ rule_id: i.rule_id,
183
+ rule_name: i.rule_name,
184
+ severity: i.severity,
185
+ type: i.type,
186
+ match: i.match,
187
+ location: i.location,
188
+ suggestion: i.suggestion
189
+ })),
190
+ scan_duration_ms: scanResult.scan_duration_ms,
191
+ rules_checked: scanResult.rules_checked
192
+ };
193
+
194
+ return {
195
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
196
+ structuredContent: response
197
+ };
198
+ }
199
+ );
200
+
201
+ // ============================================================================
202
+ // mund_check_url - Validate URL safety
203
+ // ============================================================================
204
+ server.registerTool(
205
+ 'mund_check_url',
206
+ {
207
+ title: 'Check URL Safety',
208
+ description: `Check if a URL is safe to access.
209
+
210
+ Validates URLs against known dangerous patterns including:
211
+ - Data exfiltration services (webhook.site, requestbin, etc.)
212
+ - IP-based URLs (often used to bypass domain filtering)
213
+ - Suspicious TLDs (.tk, .ml, etc.)
214
+ - Data URLs (can contain arbitrary content)
215
+
216
+ Args:
217
+ - url (string): The URL to check (required)
218
+ - tool_name (string): Name of tool attempting access (optional)
219
+ - agent_id (string): ID of requesting agent (optional)
220
+
221
+ Returns:
222
+ JSON object with:
223
+ - url: The checked URL
224
+ - safe: Boolean indicating if URL is safe
225
+ - risk_level: 'safe' | 'suspicious' | 'dangerous'
226
+ - warnings: Array of warning messages if any
227
+ - blocked: Whether access was blocked`,
228
+ inputSchema: CheckUrlInputSchema,
229
+ annotations: {
230
+ readOnlyHint: true,
231
+ destructiveHint: false,
232
+ idempotentHint: true,
233
+ openWorldHint: false
234
+ }
235
+ },
236
+ async (params) => {
237
+ const warnings: string[] = [];
238
+ let riskLevel: 'safe' | 'suspicious' | 'dangerous' = 'safe';
239
+
240
+ // Check against dangerous URL patterns
241
+ const { DANGEROUS_URL_PATTERNS } = await import('../constants.js');
242
+
243
+ for (const pattern of DANGEROUS_URL_PATTERNS) {
244
+ if (pattern.test(params.url)) {
245
+ riskLevel = 'dangerous';
246
+ warnings.push('URL matches known dangerous pattern');
247
+ break;
248
+ }
249
+ }
250
+
251
+ // Check for IP-based URLs
252
+ if (/^https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.test(params.url)) {
253
+ if (riskLevel === 'safe') riskLevel = 'suspicious';
254
+ warnings.push('IP-based URL detected');
255
+ }
256
+
257
+ // Check for data URLs
258
+ if (params.url.startsWith('data:')) {
259
+ riskLevel = 'dangerous';
260
+ warnings.push('Data URL can contain arbitrary content');
261
+ }
262
+
263
+ // Check for suspicious ports
264
+ try {
265
+ const urlObj = new URL(params.url);
266
+ const port = urlObj.port;
267
+ if (port && !['80', '443', '8080', '8443'].includes(port)) {
268
+ if (riskLevel === 'safe') riskLevel = 'suspicious';
269
+ warnings.push(`Unusual port: ${port}`);
270
+ }
271
+ } catch {
272
+ riskLevel = 'dangerous';
273
+ warnings.push('Invalid URL format');
274
+ }
275
+
276
+ const isSafe = riskLevel === 'safe';
277
+ const shouldBlock = blockMode && riskLevel === 'dangerous';
278
+
279
+ // Log event if dangerous
280
+ if (riskLevel === 'dangerous') {
281
+ const event: SecurityEvent = {
282
+ id: uuidv4(),
283
+ timestamp: new Date(),
284
+ rule_id: 'url_check',
285
+ rule_name: 'Dangerous URL Detected',
286
+ severity: 'high',
287
+ type: 'exfiltration',
288
+ action_taken: shouldBlock ? 'block' : 'alert',
289
+ content_snippet: truncateSnippet(params.url, MAX_SNIPPET_LENGTH),
290
+ full_content_hash: createHash('sha256').update(params.url).digest('hex'),
291
+ context: { tool_name: params.tool_name, agent_id: params.agent_id },
292
+ acknowledged: false
293
+ };
294
+
295
+ await storage.saveEvent(event);
296
+ await notificationHub.notify(event);
297
+ }
298
+
299
+ const response = {
300
+ url: params.url,
301
+ safe: isSafe,
302
+ risk_level: riskLevel,
303
+ warnings,
304
+ blocked: shouldBlock
305
+ };
306
+
307
+ return {
308
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
309
+ structuredContent: response
310
+ };
311
+ }
312
+ );
313
+
314
+ // ============================================================================
315
+ // mund_validate_command - Check shell command safety
316
+ // ============================================================================
317
+ server.registerTool(
318
+ 'mund_validate_command',
319
+ {
320
+ title: 'Validate Shell Command',
321
+ description: `Validate a shell command for safety before execution.
322
+
323
+ Checks for dangerous patterns including:
324
+ - Destructive commands (rm -rf, format, etc.)
325
+ - Privilege escalation (sudo, chmod 777, etc.)
326
+ - Network exfiltration (curl to suspicious URLs, etc.)
327
+ - Code injection (eval, exec with variables, etc.)
328
+
329
+ Args:
330
+ - command (string): Shell command to validate (required)
331
+ - tool_name (string): Name of tool executing command (optional)
332
+ - agent_id (string): ID of requesting agent (optional)
333
+
334
+ Returns:
335
+ JSON object with:
336
+ - command: The validated command
337
+ - safe: Boolean indicating if command is safe
338
+ - risk_level: 'safe' | 'suspicious' | 'dangerous'
339
+ - warnings: Array of warning messages
340
+ - blocked: Whether execution was blocked`,
341
+ inputSchema: ValidateCommandInputSchema,
342
+ annotations: {
343
+ readOnlyHint: true,
344
+ destructiveHint: false,
345
+ idempotentHint: true,
346
+ openWorldHint: false
347
+ }
348
+ },
349
+ async (params) => {
350
+ const warnings: string[] = [];
351
+ let riskLevel: 'safe' | 'suspicious' | 'dangerous' = 'safe';
352
+
353
+ // Dangerous command patterns
354
+ const dangerousPatterns = [
355
+ { pattern: /rm\s+-[rf]{1,2}\s+[\/~]/, message: 'Recursive deletion of root/home directory' },
356
+ { pattern: /mkfs\./, message: 'Filesystem format command' },
357
+ { pattern: /dd\s+if=.*of=\/dev\//, message: 'Direct disk write' },
358
+ { pattern: />\s*\/dev\/sd[a-z]/, message: 'Direct device write' },
359
+ { pattern: /chmod\s+777/, message: 'Overly permissive chmod' },
360
+ { pattern: /curl.*\|\s*(ba)?sh/, message: 'Piping curl to shell' },
361
+ { pattern: /wget.*\|\s*(ba)?sh/, message: 'Piping wget to shell' },
362
+ { pattern: /eval\s+"?\$/, message: 'Eval with variable' },
363
+ { pattern: /:(){ :|:& };:/, message: 'Fork bomb detected' },
364
+ ];
365
+
366
+ for (const { pattern, message } of dangerousPatterns) {
367
+ if (pattern.test(params.command)) {
368
+ riskLevel = 'dangerous';
369
+ warnings.push(message);
370
+ }
371
+ }
372
+
373
+ // Suspicious patterns
374
+ const suspiciousPatterns = [
375
+ { pattern: /sudo\s/, message: 'Sudo usage' },
376
+ { pattern: />\s*\/etc\//, message: 'Writing to /etc' },
377
+ { pattern: /curl|wget/, message: 'Network download command' },
378
+ { pattern: /nc\s+-/, message: 'Netcat usage' },
379
+ { pattern: /base64\s+-d/, message: 'Base64 decoding' },
380
+ ];
381
+
382
+ for (const { pattern, message } of suspiciousPatterns) {
383
+ if (pattern.test(params.command) && riskLevel === 'safe') {
384
+ riskLevel = 'suspicious';
385
+ warnings.push(message);
386
+ }
387
+ }
388
+
389
+ const isSafe = riskLevel === 'safe';
390
+ const shouldBlock = blockMode && riskLevel === 'dangerous';
391
+
392
+ // Log event if dangerous
393
+ if (riskLevel === 'dangerous') {
394
+ const event: SecurityEvent = {
395
+ id: uuidv4(),
396
+ timestamp: new Date(),
397
+ rule_id: 'command_check',
398
+ rule_name: 'Dangerous Command Detected',
399
+ severity: 'high',
400
+ type: 'code_pattern',
401
+ action_taken: shouldBlock ? 'block' : 'alert',
402
+ content_snippet: truncateSnippet(params.command, MAX_SNIPPET_LENGTH),
403
+ full_content_hash: createHash('sha256').update(params.command).digest('hex'),
404
+ context: { tool_name: params.tool_name, agent_id: params.agent_id },
405
+ acknowledged: false
406
+ };
407
+
408
+ await storage.saveEvent(event);
409
+ await notificationHub.notify(event);
410
+ }
411
+
412
+ const response = {
413
+ command: params.command,
414
+ safe: isSafe,
415
+ risk_level: riskLevel,
416
+ warnings,
417
+ blocked: shouldBlock
418
+ };
419
+
420
+ return {
421
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
422
+ structuredContent: response
423
+ };
424
+ }
425
+ );
426
+
427
+ // ============================================================================
428
+ // mund_get_events - Retrieve recent security events
429
+ // ============================================================================
430
+ server.registerTool(
431
+ 'mund_get_events',
432
+ {
433
+ title: 'Get Security Events',
434
+ description: `Retrieve recent security events from the Mund monitoring system.
435
+
436
+ Supports filtering and pagination for efficient event retrieval.
437
+
438
+ Args:
439
+ - limit (number): Maximum events to return, 1-1000 (default: 50)
440
+ - offset (number): Skip this many events for pagination (default: 0)
441
+ - severity ('critical' | 'high' | 'medium' | 'low' | 'info'): Filter by severity
442
+ - type: Filter by detection type
443
+ - acknowledged (boolean): Filter by acknowledgment status
444
+
445
+ Returns:
446
+ JSON object with:
447
+ - total: Total matching events
448
+ - count: Events in this response
449
+ - offset: Current offset
450
+ - has_more: Whether more events exist
451
+ - events: Array of security events`,
452
+ inputSchema: GetEventsInputSchema,
453
+ annotations: {
454
+ readOnlyHint: true,
455
+ destructiveHint: false,
456
+ idempotentHint: true,
457
+ openWorldHint: false
458
+ }
459
+ },
460
+ async (params) => {
461
+ const query = {
462
+ limit: params.limit,
463
+ offset: params.offset,
464
+ severity: params.severity as Severity | undefined,
465
+ type: params.type as DetectorType | undefined,
466
+ acknowledged: params.acknowledged
467
+ };
468
+
469
+ const [events, total] = await Promise.all([
470
+ storage.getEvents(query),
471
+ storage.countEvents(query)
472
+ ]);
473
+
474
+ const response = {
475
+ total,
476
+ count: events.length,
477
+ offset: params.offset,
478
+ has_more: total > params.offset + events.length,
479
+ events: events.map(e => ({
480
+ id: e.id,
481
+ timestamp: e.timestamp.toISOString(),
482
+ rule_id: e.rule_id,
483
+ rule_name: e.rule_name,
484
+ severity: e.severity,
485
+ type: e.type,
486
+ action_taken: e.action_taken,
487
+ content_snippet: e.content_snippet,
488
+ context: e.context,
489
+ acknowledged: e.acknowledged,
490
+ acknowledged_by: e.acknowledged_by,
491
+ acknowledged_at: e.acknowledged_at?.toISOString()
492
+ }))
493
+ };
494
+
495
+ return {
496
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
497
+ structuredContent: response
498
+ };
499
+ }
500
+ );
501
+
502
+ // ============================================================================
503
+ // mund_get_status - Get current monitoring status
504
+ // ============================================================================
505
+ server.registerTool(
506
+ 'mund_get_status',
507
+ {
508
+ title: 'Get Monitoring Status',
509
+ description: `Get the current status of the Mund monitoring system.
510
+
511
+ Returns information about:
512
+ - Active analyzers and their status
513
+ - Number of rules loaded
514
+ - Configured notification channels
515
+ - Block mode status
516
+ - Recent event statistics`,
517
+ inputSchema: GetStatusInputSchema,
518
+ annotations: {
519
+ readOnlyHint: true,
520
+ destructiveHint: false,
521
+ idempotentHint: true,
522
+ openWorldHint: false
523
+ }
524
+ },
525
+ async () => {
526
+ const eventCounts = {
527
+ total: await storage.countEvents({}),
528
+ unacknowledged: await storage.countEvents({ acknowledged: false }),
529
+ critical: await storage.countEvents({ severity: 'critical' as Severity }),
530
+ high: await storage.countEvents({ severity: 'high' as Severity })
531
+ };
532
+
533
+ const response = {
534
+ status: 'active',
535
+ version: '0.1.0',
536
+ block_mode: blockMode,
537
+ analyzers: analyzer.getAnalyzerNames(),
538
+ rules_loaded: rules.filter(r => r.enabled).length,
539
+ notifiers: notificationHub.getNotifierNames(),
540
+ event_counts: eventCounts,
541
+ timestamp: new Date().toISOString()
542
+ };
543
+
544
+ return {
545
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
546
+ structuredContent: response
547
+ };
548
+ }
549
+ );
550
+ }
551
+
552
+ // ============================================================================
553
+ // Helper Functions
554
+ // ============================================================================
555
+
556
+ function truncateSnippet(text: string, maxLength: number): string {
557
+ if (text.length <= maxLength) return text;
558
+ return text.substring(0, maxLength - 3) + '...';
559
+ }