elastic / elastic/example-mcp-app-security
[Epic] Attack Discovery generation — workflow/skill parity with the Kibana UI
- Dominant language
- TypeScript
- Stars
- 24
- Forks
- 16
- Avg merge
- 1d 41m
- Merged PRs (30d)
- 2
Description
## Objective
When the MCP app generates an Attack Discovery, route it through the **AD 2.0 workflow/skill pipeline** — identical to a default UI "Generate" — instead of the legacy LangGraph path it uses today. Running Attack Discovery from the MCP app should behave the same way as running it from the Security UI, including using the skill-based generation pipeline.
Related Kibana work: AD 2.0 "Workflows Integration" PR stack (branch `attack-discovery-workflows-integration/10-scheduling`).
## Background / current behavior
Today the MCP app triggers generation via the **legacy** route:
- `AttackDiscoveryClient.generate()` → `POST /api/attack_discovery/_generate` (`elastic_assistant` plugin, LangGraph path).
- This **bypasses** the new AD 2.0 workflow pipeline and its skill usage entirely.
The Kibana UI, when the feature flag `securitySolution.attackDiscoveryWorkflowsEnabled` is on:
- Posts to `POST /internal/attack_discovery/_generate` (`discoveries` plugin) → `executeGenerationWorkflow()` → the 3-phase managed-workflow pipeline (alert retrieval → generation → validation), with `workflow_config.skill_enabled: true`.
## Why this is a small change
- The MCP Kibana client already sends the headers internal routes require (`kbn-xsrf`, `x-elastic-internal-origin: Kibana`) — see `src/elastic/kibana-client/create-kibana-client.ts`.
- **Status polling is unchanged.** Both the legacy and workflow paths write to the same Kibana event log, and `GET /api/attack_discovery/generations` returns workflow runs too. So `AttackDiscoveryService.getGenerations()` and the app's progress banner keep working with no changes.
- The only gap is the **start** call: endpoint + request-body shape.
## Design decisions
- **Route strategy:** Prefer the internal workflow route; on `404`/`403` (flag off, or the API key lacks workflow privileges) automatically fall back to the legacy route.
- **Config surface (Option A):** No new tool inputs. The `generate-attack-discovery` tool keeps its current schema (`connectorName`, `size`, `start`, `end`, `filter`). Internally we attach a fixed, UI-matching `workflow_config`.
### Internal (workflow) request body
\`\`\`json
{
"alerts_index_pattern": ".alerts-security.alerts-default",
"api_config": { "connector_id": "...", "action_type_id": "...", "model": "..." },
"size": 50,
"start": "now-7d",
"end": "now",
"source": "interactive",
"workflow_config": {
"alert_retrieval_mode": "custom_query",
"alert_retrieval_workflow_ids": [],
"alert_retrieval_workflows_enabled": false,
"default_retrieval_enabled": false,
"skill_enabled": true,
"validation_workflow_id": "default"
}
}
\`\`\`
Note: the internal body is snake_case and does **not** take `anonymizationFields` / `replacements` / `subAction` — those belong only to the legacy fallback body.
## Scope
**In scope**
- `AttackDiscoveryClient`: add `generateViaWorkflow()` (internal route) + snake_case body type; keep legacy `generate()`.
- `AttackDiscoveryService.generateAttackDiscovery()`: build the UI-matching workflow body and try the workflow route, falling back to legacy on `404`/`403`; return `{ execution_uuid }` unchanged.
- Unit tests for both paths (workflow happy path + fallback).
- Live end-to-end verification against local Kibana.
**Out of scope**
- Exposing `workflow_config` knobs as tool inputs (Option B).
- Changes to status polling / progress banner.
- Any Kibana-side changes; any UI changes beyond the MCP app.
## Done criteria
- MCP `generate-attack-discovery` triggers a workflow-based (`skill_enabled`) generation identical to a default UI Generate when the flag is on.
- Falls back cleanly to legacy when the workflow route is unavailable (`404`/`403`).
- Unit tests cover both paths.
- Verified end-to-end against local Kibana on `attack-discovery-workflows-integration/10-scheduling`.
## Risks / notes
- The MCP API key must carry `workflowsManagement:read`+`execute` and `ATTACK_DISCOVERY_API_ACTION_ALL`+`ALERTS_API_READ`, else the internal route returns `403` and we fall back to legacy (acceptable, but worth surfacing in the returned status message / logs).
## Work breakdown
### Wave 1 — foundation
- [ ] **Route AD generation through internal workflow route with legacy fallback**
- `src/elastic/client/attackDiscoveryClient.ts`: add `generateViaWorkflow(body)` → `POST /internal/attack_discovery/_generate`; keep legacy `generate(body)`; add snake_case body type mirroring Kibana's `PostGenerateRequestBody`.
- `src/elastic/service/attackDiscoveryService.ts`: build the internal body with the fixed `workflow_config` defaults + `source: "interactive"`; try workflow route, catch `404`/`403`, fall back to legacy body. Return `{ execution_uuid }` unchanged.
- `src/tools/attack-discovery.ts`: (optional) report which path ran (`workflow` vs `legacy`) in the status message; no schema change.
### Wave 2 — parallel (depend on Wave 1)
- [ ] **Unit tests: internal workflow route + legacy fallback** — extend `src/elastic/service/attackDiscoveryService.test.ts` and `src/tools/attack-discovery.test.ts` to assert the internal route is called with the correct snake_case body + `workflow_config` defaults, and that `404`/`403` triggers the legacy fallback.
- [ ] **Live end-to-end verification against local Kibana (flag ON)** — trigger generation from the MCP app; confirm `POST /internal/attack_discovery/_generate` is hit and a workflow/skill pipeline runs (event-log step events); confirm the progress banner shows in-progress → succeeded and results refresh; confirm legacy fallback when the flag is off / privileges are missing.
Contributor guide
Research direction
Start with src/elastic/client/attackDiscoveryClient.ts and src/elastic/service/attackDiscoveryService.ts, then read the existing tests in src/elastic/service/attackDiscoveryService.test.ts and src/tools/attack-discovery.test.ts. Run the unit tests before making changes, then verify the internal workflow request, 404/403 legacy fallback, unchanged execution_uuid response, and live behavior against the specified local Kibana branch.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend, testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100