tracecat-mcp-community 1.0.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.
@@ -0,0 +1,547 @@
1
+ import { z } from "zod";
2
+ // Templates from official Tracecat documentation and community sources.
3
+ // To add a template: push a new Template object to this array.
4
+ const TEMPLATES = [
5
+ {
6
+ id: "crowdsec_false_positive",
7
+ title: "CrowdSec False Positive Manager",
8
+ description: "Check IP reputation via CrowdSec CTI API and conditionally unblock benign IPs. Source: aukfood.fr community blog.",
9
+ category: "threat_response",
10
+ actions: 3,
11
+ yaml: `# CrowdSec False Positive Manager
12
+ # Source: https://www.aukfood.fr/faux-positifs-sous-controle-grace-a-tracecat-et-crowdsec/
13
+ # Trigger: webhook with { "ip_address": "x.x.x.x" }
14
+ # Secrets required: crowdsec (keys: API_TOKEN)
15
+
16
+ definition:
17
+ title: CrowdSec False Positive Manager
18
+ description: Check IP reputation via CrowdSec CTI and conditionally unblock benign IPs
19
+
20
+ triggers:
21
+ - type: webhook
22
+ ref: incoming_ip
23
+
24
+ actions:
25
+ - ref: lookup_ip_reputation
26
+ action: core.http_request
27
+ args:
28
+ url: "https://cti.api.crowdsec.net/v2/smoke/\${{ TRIGGER.data.ip_address }}"
29
+ method: GET
30
+ headers:
31
+ x-api-key: \${{ SECRETS.crowdsec.API_TOKEN }}
32
+ Accept: application/json
33
+
34
+ - ref: evaluate_reputation
35
+ action: core.transform.reshape
36
+ args:
37
+ value:
38
+ ip: \${{ TRIGGER.data.ip_address }}
39
+ is_benign: \${{ ACTIONS.lookup_ip_reputation.result.reputation == "benign" }}
40
+ reputation: \${{ ACTIONS.lookup_ip_reputation.result.reputation }}
41
+ total_reports: \${{ ACTIONS.lookup_ip_reputation.result.ip_range_score }}
42
+ background_noise_score: \${{ ACTIONS.lookup_ip_reputation.result.background_noise_score }}
43
+ depends_on: [lookup_ip_reputation]
44
+
45
+ - ref: unblock_benign_ip
46
+ action: core.http_request
47
+ args:
48
+ url: "https://admin.api.crowdsec.net/v1/decisions"
49
+ method: DELETE
50
+ headers:
51
+ x-api-key: \${{ SECRETS.crowdsec.API_TOKEN }}
52
+ Content-Type: application/json
53
+ params:
54
+ ip: \${{ TRIGGER.data.ip_address }}
55
+ run_if: \${{ ACTIONS.evaluate_reputation.result.is_benign }}
56
+ depends_on: [evaluate_reputation]`,
57
+ },
58
+ {
59
+ id: "wazuh_alert_to_case",
60
+ title: "Wazuh Alert-to-Case Pipeline",
61
+ description: "Receive Wazuh alerts via webhook, enrich with agent info, evaluate severity, and create a Tracecat case. Source: aukfood.fr community blog.",
62
+ category: "alert_management",
63
+ actions: 5,
64
+ yaml: `# Wazuh Alert-to-Case Pipeline
65
+ # Source: https://www.aukfood.fr/tracecat-le-soar-open-source-qui-change-la-donne/
66
+ # Trigger: webhook from Wazuh (alert JSON)
67
+ # Secrets required: wazuh (keys: API_KEY, SERVER_URL)
68
+
69
+ definition:
70
+ title: Wazuh Alert-to-Case Pipeline
71
+ description: Ingest Wazuh alerts, enrich with agent info, and create investigation cases
72
+
73
+ triggers:
74
+ - type: webhook
75
+ ref: wazuh_alert
76
+
77
+ actions:
78
+ - ref: parse_alert
79
+ action: core.transform.reshape
80
+ args:
81
+ value:
82
+ alert_id: \${{ TRIGGER.data.id }}
83
+ rule_id: \${{ TRIGGER.data.rule.id }}
84
+ rule_level: \${{ TRIGGER.data.rule.level }}
85
+ rule_description: \${{ TRIGGER.data.rule.description }}
86
+ agent_id: \${{ TRIGGER.data.agent.id }}
87
+ agent_name: \${{ TRIGGER.data.agent.name }}
88
+ timestamp: \${{ TRIGGER.data.timestamp }}
89
+ src_ip: \${{ TRIGGER.data.data.srcip }}
90
+
91
+ - ref: get_agent_info
92
+ action: core.http_request
93
+ args:
94
+ url: "\${{ SECRETS.wazuh.SERVER_URL }}/agents/\${{ ACTIONS.parse_alert.result.agent_id }}"
95
+ method: GET
96
+ headers:
97
+ Authorization: "Bearer \${{ SECRETS.wazuh.API_KEY }}"
98
+ verify_ssl: false
99
+ depends_on: [parse_alert]
100
+
101
+ - ref: evaluate_severity
102
+ action: core.transform.reshape
103
+ args:
104
+ value:
105
+ priority: \${{ FN.conditional(ACTIONS.parse_alert.result.rule_level > 12, "critical", FN.conditional(ACTIONS.parse_alert.result.rule_level > 8, "high", FN.conditional(ACTIONS.parse_alert.result.rule_level > 4, "medium", "low"))) }}
106
+ malice: \${{ FN.conditional(ACTIONS.parse_alert.result.rule_level > 10, "malicious", "unknown") }}
107
+ depends_on: [parse_alert]
108
+
109
+ - ref: create_investigation_case
110
+ action: core.cases.create_case
111
+ args:
112
+ case_title: "[Wazuh] \${{ ACTIONS.parse_alert.result.rule_description }}"
113
+ priority: \${{ ACTIONS.evaluate_severity.result.priority }}
114
+ malice: \${{ ACTIONS.evaluate_severity.result.malice }}
115
+ status: new
116
+ payload:
117
+ alert_id: \${{ ACTIONS.parse_alert.result.alert_id }}
118
+ rule_id: \${{ ACTIONS.parse_alert.result.rule_id }}
119
+ agent: \${{ ACTIONS.get_agent_info.result.data.name }}
120
+ src_ip: \${{ ACTIONS.parse_alert.result.src_ip }}
121
+ depends_on: [evaluate_severity, get_agent_info]
122
+
123
+ - ref: add_context_comment
124
+ action: core.cases.create_comment
125
+ args:
126
+ case_id: \${{ ACTIONS.create_investigation_case.result.id }}
127
+ content: "Auto-created from Wazuh alert. Agent: \${{ ACTIONS.get_agent_info.result.data.name }} (OS: \${{ ACTIONS.get_agent_info.result.data.os.platform }}). Rule level: \${{ ACTIONS.parse_alert.result.rule_level }}"
128
+ depends_on: [create_investigation_case]`,
129
+ },
130
+ {
131
+ id: "crowdstrike_forensics",
132
+ title: "CrowdStrike Evidence Collection",
133
+ description: "Collect forensic evidence from CrowdStrike Falcon: detections, device info, IOCs, and network connections. Source: stickyricebytes.com community blog.",
134
+ category: "forensics",
135
+ actions: 6,
136
+ yaml: `# CrowdStrike Evidence Collection
137
+ # Source: https://stickyricebytes.com/2025/02/11/crowdstrike-falcon-forensics-with-tracecat/
138
+ # Trigger: webhook with { "host_id": "device-id", "detection_id": "ldt:xxx" }
139
+ # Secrets required: crowdstrike (keys: CLIENT_ID, CLIENT_SECRET, BASE_URL)
140
+
141
+ definition:
142
+ title: CrowdStrike Evidence Collection
143
+ description: Gather forensic evidence from CrowdStrike Falcon for incident investigation
144
+
145
+ triggers:
146
+ - type: webhook
147
+ ref: forensic_request
148
+
149
+ actions:
150
+ - ref: authenticate
151
+ action: core.http_request
152
+ args:
153
+ url: \${{ SECRETS.crowdstrike.BASE_URL }}/oauth2/token
154
+ method: POST
155
+ headers:
156
+ Content-Type: application/x-www-form-urlencoded
157
+ payload:
158
+ client_id: \${{ SECRETS.crowdstrike.CLIENT_ID }}
159
+ client_secret: \${{ SECRETS.crowdstrike.CLIENT_SECRET }}
160
+
161
+ - ref: get_detection_details
162
+ action: core.http_request
163
+ args:
164
+ url: \${{ SECRETS.crowdstrike.BASE_URL }}/detects/entities/summaries/GET/v1
165
+ method: POST
166
+ headers:
167
+ Authorization: "Bearer \${{ ACTIONS.authenticate.result.access_token }}"
168
+ Content-Type: application/json
169
+ payload:
170
+ ids:
171
+ - \${{ TRIGGER.data.detection_id }}
172
+ depends_on: [authenticate]
173
+
174
+ - ref: get_device_info
175
+ action: core.http_request
176
+ args:
177
+ url: \${{ SECRETS.crowdstrike.BASE_URL }}/devices/entities/devices/v2
178
+ method: GET
179
+ headers:
180
+ Authorization: "Bearer \${{ ACTIONS.authenticate.result.access_token }}"
181
+ params:
182
+ ids: \${{ TRIGGER.data.host_id }}
183
+ depends_on: [authenticate]
184
+
185
+ - ref: get_iocs
186
+ action: core.http_request
187
+ args:
188
+ url: \${{ SECRETS.crowdstrike.BASE_URL }}/indicators/queries/iocs/v1
189
+ method: GET
190
+ headers:
191
+ Authorization: "Bearer \${{ ACTIONS.authenticate.result.access_token }}"
192
+ params:
193
+ filter: "device.device_id:'\${{ TRIGGER.data.host_id }}'"
194
+ limit: "50"
195
+ depends_on: [authenticate]
196
+
197
+ - ref: get_network_connections
198
+ action: core.http_request
199
+ args:
200
+ url: \${{ SECRETS.crowdstrike.BASE_URL }}/detects/entities/processes/v1
201
+ method: GET
202
+ headers:
203
+ Authorization: "Bearer \${{ ACTIONS.authenticate.result.access_token }}"
204
+ params:
205
+ ids: \${{ ACTIONS.get_detection_details.result.resources[0].behaviors[0].process_id }}
206
+ depends_on: [get_detection_details]
207
+
208
+ - ref: compile_evidence
209
+ action: core.transform.reshape
210
+ args:
211
+ value:
212
+ case_summary:
213
+ detection: \${{ ACTIONS.get_detection_details.result.resources[0] }}
214
+ device:
215
+ hostname: \${{ ACTIONS.get_device_info.result.resources[0].hostname }}
216
+ os: \${{ ACTIONS.get_device_info.result.resources[0].os_version }}
217
+ last_seen: \${{ ACTIONS.get_device_info.result.resources[0].last_seen }}
218
+ external_ip: \${{ ACTIONS.get_device_info.result.resources[0].external_ip }}
219
+ iocs: \${{ ACTIONS.get_iocs.result.resources }}
220
+ network: \${{ ACTIONS.get_network_connections.result }}
221
+ collected_at: \${{ FN.now() }}
222
+ depends_on: [get_detection_details, get_device_info, get_iocs, get_network_connections]`,
223
+ },
224
+ {
225
+ id: "virustotal_url_enrichment",
226
+ title: "VirusTotal URL Enrichment",
227
+ description: "Submit a URL to VirusTotal for scanning, retrieve analysis results, and evaluate verdict. Source: Tracecat official docs + Discord community.",
228
+ category: "enrichment",
229
+ actions: 3,
230
+ yaml: `# VirusTotal URL Enrichment
231
+ # Source: Tracecat official documentation + Discord community patterns
232
+ # Trigger: webhook with { "url": "https://suspicious-site.com" }
233
+ # Secrets required: virustotal (keys: API_KEY)
234
+ # Note: VT URL lookup requires base64url encoding of the URL
235
+
236
+ definition:
237
+ title: VirusTotal URL Enrichment
238
+ description: Scan a URL with VirusTotal and return analysis verdict
239
+
240
+ triggers:
241
+ - type: webhook
242
+ ref: url_to_scan
243
+
244
+ actions:
245
+ - ref: encode_and_lookup
246
+ action: core.http_request
247
+ args:
248
+ url: "https://www.virustotal.com/api/v3/urls/\${{ FN.strip(FN.to_base64url(TRIGGER.data.url)) }}"
249
+ method: GET
250
+ headers:
251
+ x-apikey: \${{ SECRETS.virustotal.API_KEY }}
252
+ Accept: application/json
253
+
254
+ - ref: extract_verdict
255
+ action: core.transform.reshape
256
+ args:
257
+ value:
258
+ url: \${{ TRIGGER.data.url }}
259
+ malicious: \${{ ACTIONS.encode_and_lookup.result.data.attributes.last_analysis_stats.malicious }}
260
+ suspicious: \${{ ACTIONS.encode_and_lookup.result.data.attributes.last_analysis_stats.suspicious }}
261
+ harmless: \${{ ACTIONS.encode_and_lookup.result.data.attributes.last_analysis_stats.harmless }}
262
+ total: \${{ ACTIONS.encode_and_lookup.result.data.attributes.last_analysis_stats.malicious + ACTIONS.encode_and_lookup.result.data.attributes.last_analysis_stats.suspicious + ACTIONS.encode_and_lookup.result.data.attributes.last_analysis_stats.harmless }}
263
+ categories: \${{ ACTIONS.encode_and_lookup.result.data.attributes.categories }}
264
+ is_malicious: \${{ ACTIONS.encode_and_lookup.result.data.attributes.last_analysis_stats.malicious > 3 }}
265
+ depends_on: [encode_and_lookup]
266
+
267
+ - ref: create_alert_case
268
+ action: core.cases.create_case
269
+ args:
270
+ case_title: "[VT URL] Malicious URL detected: \${{ TRIGGER.data.url }}"
271
+ priority: high
272
+ malice: malicious
273
+ status: new
274
+ payload:
275
+ url: \${{ TRIGGER.data.url }}
276
+ vt_malicious: \${{ ACTIONS.extract_verdict.result.malicious }}
277
+ vt_suspicious: \${{ ACTIONS.extract_verdict.result.suspicious }}
278
+ run_if: \${{ ACTIONS.extract_verdict.result.is_malicious }}
279
+ depends_on: [extract_verdict]`,
280
+ },
281
+ {
282
+ id: "crowdsec_ip_blocker",
283
+ title: "CrowdSec IP Blocker",
284
+ description: "Add a malicious IP to CrowdSec blocklist with a timed ban decision. Source: Discord community contributors.",
285
+ category: "threat_response",
286
+ actions: 2,
287
+ yaml: `# CrowdSec IP Blocker
288
+ # Source: Tracecat Discord community contributors
289
+ # Trigger: webhook with { "ip_address": "x.x.x.x", "reason": "...", "duration": "24h" }
290
+ # Secrets required: crowdsec (keys: API_TOKEN)
291
+
292
+ definition:
293
+ title: CrowdSec IP Blocker
294
+ description: Add a malicious IP to CrowdSec blocklist with a timed ban
295
+
296
+ triggers:
297
+ - type: webhook
298
+ ref: block_request
299
+
300
+ actions:
301
+ - ref: add_decision
302
+ action: core.http_request
303
+ args:
304
+ url: "https://admin.api.crowdsec.net/v1/decisions"
305
+ method: POST
306
+ headers:
307
+ x-api-key: \${{ SECRETS.crowdsec.API_TOKEN }}
308
+ Content-Type: application/json
309
+ payload:
310
+ - scope: ip
311
+ value: \${{ TRIGGER.data.ip_address }}
312
+ type: ban
313
+ duration: \${{ TRIGGER.data.duration }}
314
+ origin: tracecat
315
+ scenario: "manual block via SOAR"
316
+
317
+ - ref: confirm_block
318
+ action: core.transform.reshape
319
+ args:
320
+ value:
321
+ status: blocked
322
+ ip: \${{ TRIGGER.data.ip_address }}
323
+ reason: \${{ TRIGGER.data.reason }}
324
+ duration: \${{ TRIGGER.data.duration }}
325
+ decision_id: \${{ ACTIONS.add_decision.result }}
326
+ depends_on: [add_decision]`,
327
+ },
328
+ {
329
+ id: "phishing_response",
330
+ title: "Phishing Email Response",
331
+ description: "Analyze a reported phishing email: extract IOCs, check URLs with VirusTotal, evaluate verdict, create case, and notify SOC. Source: Tracecat official website and documentation.",
332
+ category: "incident_response",
333
+ actions: 6,
334
+ yaml: `# Phishing Email Response
335
+ # Source: Tracecat official website (https://tracecat.com) and documentation
336
+ # Trigger: webhook with { "from": "...", "subject": "...", "body": "...", "urls": [...], "reporter": "..." }
337
+ # Secrets required: virustotal (keys: API_KEY), slack (keys: BOT_TOKEN)
338
+
339
+ definition:
340
+ title: Phishing Email Response
341
+ description: Analyze reported phishing emails, check URLs, verdict, create case and notify
342
+
343
+ triggers:
344
+ - type: webhook
345
+ ref: reported_email
346
+
347
+ actions:
348
+ - ref: parse_email
349
+ action: core.transform.reshape
350
+ args:
351
+ value:
352
+ sender: \${{ TRIGGER.data.from }}
353
+ subject: \${{ TRIGGER.data.subject }}
354
+ urls: \${{ TRIGGER.data.urls }}
355
+ reporter: \${{ TRIGGER.data.reporter }}
356
+ received_at: \${{ FN.now() }}
357
+
358
+ - ref: check_urls_vt
359
+ action: core.http_request
360
+ args:
361
+ url: "https://www.virustotal.com/api/v3/urls/\${{ FN.strip(FN.to_base64url(var.url)) }}"
362
+ method: GET
363
+ headers:
364
+ x-apikey: \${{ SECRETS.virustotal.API_KEY }}
365
+ Accept: application/json
366
+ control_flow:
367
+ for_each: \${{ ACTIONS.parse_email.result.urls }}
368
+ start_delay: 1
369
+ retry_policy:
370
+ max_attempts: 3
371
+ timeout: 30
372
+ depends_on: [parse_email]
373
+
374
+ - ref: evaluate_verdict
375
+ action: core.script.run_python
376
+ args:
377
+ script: |
378
+ import json
379
+
380
+ def main(vt_results_json, sender):
381
+ vt_results = json.loads(vt_results_json)
382
+ total_malicious = 0
383
+ malicious_urls = []
384
+ for r in vt_results:
385
+ if isinstance(r, dict) and "data" in r:
386
+ stats = r["data"].get("attributes", {}).get("last_analysis_stats", {})
387
+ mal = stats.get("malicious", 0)
388
+ total_malicious += mal
389
+ if mal > 0:
390
+ malicious_urls.append(r["data"]["id"])
391
+ is_phishing = total_malicious > 2
392
+ return {
393
+ "is_phishing": is_phishing,
394
+ "total_malicious_detections": total_malicious,
395
+ "malicious_urls": malicious_urls,
396
+ "confidence": "high" if total_malicious > 5 else "medium" if total_malicious > 0 else "low"
397
+ }
398
+ inputs:
399
+ vt_results_json: \${{ FN.serialize_json(ACTIONS.check_urls_vt.result) }}
400
+ sender: \${{ ACTIONS.parse_email.result.sender }}
401
+ depends_on: [check_urls_vt]
402
+
403
+ - ref: create_phishing_case
404
+ action: core.cases.create_case
405
+ args:
406
+ case_title: "[PHISH] \${{ ACTIONS.parse_email.result.subject }}"
407
+ priority: \${{ FN.conditional(ACTIONS.evaluate_verdict.result.is_phishing, "high", "medium") }}
408
+ malice: \${{ FN.conditional(ACTIONS.evaluate_verdict.result.is_phishing, "malicious", "unknown") }}
409
+ status: new
410
+ payload:
411
+ sender: \${{ ACTIONS.parse_email.result.sender }}
412
+ subject: \${{ ACTIONS.parse_email.result.subject }}
413
+ reporter: \${{ ACTIONS.parse_email.result.reporter }}
414
+ malicious_urls: \${{ ACTIONS.evaluate_verdict.result.malicious_urls }}
415
+ confidence: \${{ ACTIONS.evaluate_verdict.result.confidence }}
416
+ depends_on: [evaluate_verdict]
417
+
418
+ - ref: add_evidence_comment
419
+ action: core.cases.create_comment
420
+ args:
421
+ case_id: \${{ ACTIONS.create_phishing_case.result.id }}
422
+ content: "VT Analysis: \${{ ACTIONS.evaluate_verdict.result.total_malicious_detections }} malicious detections across \${{ FN.length(ACTIONS.parse_email.result.urls) }} URLs. Confidence: \${{ ACTIONS.evaluate_verdict.result.confidence }}. Malicious URLs: \${{ FN.serialize_json(ACTIONS.evaluate_verdict.result.malicious_urls) }}"
423
+ depends_on: [create_phishing_case]
424
+
425
+ - ref: notify_soc
426
+ action: core.http_request
427
+ args:
428
+ url: https://slack.com/api/chat.postMessage
429
+ method: POST
430
+ headers:
431
+ Authorization: "Bearer \${{ SECRETS.slack.BOT_TOKEN }}"
432
+ Content-Type: application/json
433
+ payload:
434
+ channel: "#soc-alerts"
435
+ blocks:
436
+ - type: header
437
+ text:
438
+ type: plain_text
439
+ text: "Phishing Report: \${{ ACTIONS.parse_email.result.subject }}"
440
+ - type: section
441
+ fields:
442
+ - type: mrkdwn
443
+ text: "*Sender:*\\n\${{ ACTIONS.parse_email.result.sender }}"
444
+ - type: mrkdwn
445
+ text: "*Verdict:*\\n\${{ FN.conditional(ACTIONS.evaluate_verdict.result.is_phishing, 'PHISHING', 'CLEAN') }}"
446
+ - type: mrkdwn
447
+ text: "*Confidence:*\\n\${{ ACTIONS.evaluate_verdict.result.confidence }}"
448
+ - type: mrkdwn
449
+ text: "*Reporter:*\\n\${{ ACTIONS.parse_email.result.reporter }}"
450
+ depends_on: [create_phishing_case]`,
451
+ },
452
+ {
453
+ id: "siem_alert_triage",
454
+ title: "Generic SIEM Alert Triage",
455
+ description: "Receive SIEM alerts, check if source IP is public, enrich with VirusTotal, score severity, and create a case. Source: Tracecat Discord + community patterns.",
456
+ category: "alert_management",
457
+ actions: 4,
458
+ yaml: `# Generic SIEM Alert Triage
459
+ # Source: Tracecat Discord community + common SOAR patterns
460
+ # Trigger: webhook with { "alert_name": "...", "src_ip": "...", "severity": "...", "raw_log": "..." }
461
+ # Secrets required: virustotal (keys: API_KEY)
462
+
463
+ definition:
464
+ title: Generic SIEM Alert Triage
465
+ description: Triage SIEM alerts with IP enrichment and automated case creation
466
+
467
+ triggers:
468
+ - type: webhook
469
+ ref: siem_alert
470
+
471
+ actions:
472
+ - ref: check_ip_type
473
+ action: core.transform.reshape
474
+ args:
475
+ value:
476
+ alert_name: \${{ TRIGGER.data.alert_name }}
477
+ src_ip: \${{ TRIGGER.data.src_ip }}
478
+ severity: \${{ TRIGGER.data.severity }}
479
+ is_public: \${{ FN.ipv4_is_public(TRIGGER.data.src_ip) }}
480
+
481
+ - ref: enrich_with_vt
482
+ action: core.http_request
483
+ args:
484
+ url: "https://www.virustotal.com/api/v3/ip_addresses/\${{ ACTIONS.check_ip_type.result.src_ip }}"
485
+ method: GET
486
+ headers:
487
+ x-apikey: \${{ SECRETS.virustotal.API_KEY }}
488
+ Accept: application/json
489
+ run_if: \${{ ACTIONS.check_ip_type.result.is_public }}
490
+ depends_on: [check_ip_type]
491
+
492
+ - ref: score_alert
493
+ action: core.transform.reshape
494
+ args:
495
+ value:
496
+ alert_name: \${{ ACTIONS.check_ip_type.result.alert_name }}
497
+ src_ip: \${{ ACTIONS.check_ip_type.result.src_ip }}
498
+ is_public: \${{ ACTIONS.check_ip_type.result.is_public }}
499
+ vt_malicious: \${{ FN.conditional(ACTIONS.check_ip_type.result.is_public, ACTIONS.enrich_with_vt.result.data.attributes.last_analysis_stats.malicious, 0) }}
500
+ priority: \${{ FN.conditional(TRIGGER.data.severity == "critical", "critical", FN.conditional(TRIGGER.data.severity == "high", "high", FN.conditional(TRIGGER.data.severity == "medium", "medium", "low"))) }}
501
+ malice: \${{ FN.conditional(ACTIONS.check_ip_type.result.is_public && ACTIONS.enrich_with_vt.result.data.attributes.last_analysis_stats.malicious > 5, "malicious", "unknown") }}
502
+ depends_on: [enrich_with_vt]
503
+
504
+ - ref: create_triage_case
505
+ action: core.cases.create_case
506
+ args:
507
+ case_title: "[SIEM] \${{ ACTIONS.score_alert.result.alert_name }}"
508
+ priority: \${{ ACTIONS.score_alert.result.priority }}
509
+ malice: \${{ ACTIONS.score_alert.result.malice }}
510
+ status: new
511
+ payload:
512
+ src_ip: \${{ ACTIONS.score_alert.result.src_ip }}
513
+ is_public_ip: \${{ ACTIONS.score_alert.result.is_public }}
514
+ vt_malicious: \${{ ACTIONS.score_alert.result.vt_malicious }}
515
+ original_severity: \${{ TRIGGER.data.severity }}
516
+ raw_log: \${{ TRIGGER.data.raw_log }}
517
+ depends_on: [score_alert]`,
518
+ },
519
+ ];
520
+ export function registerTemplateTools(server, _client) {
521
+ server.tool("tracecat_list_templates", "List available SOAR workflow templates. Returns template ID, title, description, category, and action count for each template.", {}, async () => {
522
+ if (TEMPLATES.length === 0) {
523
+ return {
524
+ content: [{ type: "text", text: "No templates available yet. Templates from official Tracecat documentation and community sources will be added here." }],
525
+ };
526
+ }
527
+ const list = TEMPLATES.map(({ id, title, description, category, actions }) => ({
528
+ id, title, description, category, actions,
529
+ }));
530
+ return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
531
+ });
532
+ server.tool("tracecat_get_template", "Get the full YAML template for a SOAR workflow pattern. Use tracecat_list_templates to see available template IDs.", {
533
+ template_id: z.string().describe("Template ID from tracecat_list_templates"),
534
+ }, async ({ template_id }) => {
535
+ const template = TEMPLATES.find((t) => t.id === template_id);
536
+ if (!template) {
537
+ const available = TEMPLATES.map((t) => t.id).join(", ") || "(none)";
538
+ return {
539
+ content: [{ type: "text", text: `Unknown template: ${template_id}. Available templates: ${available}` }],
540
+ isError: true,
541
+ };
542
+ }
543
+ return {
544
+ content: [{ type: "text", text: `# Template: ${template.title}\n\n${template.description}\n\n${template.yaml}` }],
545
+ };
546
+ });
547
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { TracecatClient } from "../client.js";
3
+ export declare function registerWebhookTools(server: McpServer, client: TracecatClient): void;
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export function registerWebhookTools(server, client) {
3
+ server.tool("tracecat_create_webhook_key", "Generate or rotate a webhook API key for a workflow", {
4
+ workflow_id: z.string().describe("Workflow ID"),
5
+ }, async ({ workflow_id }) => {
6
+ const result = await client.post(`/workflows/${workflow_id}/webhook/api-key`);
7
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
8
+ });
9
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { TracecatClient } from "../client.js";
3
+ export declare function registerWorkflowTools(server: McpServer, client: TracecatClient): void;