Your CTI Reports Are Useless Without Structure: How LLMs + MCP Transform Unstructured Threat Intel into STIX Knowledge Graphs + Video

Listen to this Post

Featured Image

Introduction

Most cyber threat intelligence (CTI) reports are buried in unstructured text—PDFs, blogs, and Slack messages—rendering them nearly impossible to operationalize at scale. By combining Large Language Models (LLMs) with the Model Context Protocol (MCP) and STIX 2.1, you can automatically extract, structure, and graph threat data into machine‑ready knowledge bases that expose hidden patterns across thousands of reports.

Learning Objectives

  • Learn how to convert raw, unstructured CTI reports into validated STIX 2.1 bundles using LLM prompts and API pipelines.
  • Implement an MCP server to serve STIX knowledge graphs as consumable endpoints for AI agents and SOAR platforms.
  • Build cross‑report correlation using STIX constellations and ATT&CK heatmaps, uncovering adversary TTPs missed by single‑report analysis.

You Should Know

  1. Environment Setup: STIX Toolkit, MCP SDK, and LLM Access
    Before generating structured intel, prepare your environment with the necessary libraries and authentication keys. These steps work on both Linux (Ubuntu/Debian) and Windows (WSL2 or native PowerShell).

Linux / macOS:

 Install Python 3.10+ and virtual environment
sudo apt update && sudo apt install python3 python3-pip python3-venv -y
python3 -m venv cti_env
source cti_env/bin/activate

Install core libraries
pip install stix2 pandas openai anthropic mcp numpy networkx
pip install neo4j  for graph database integration

Windows (PowerShell as Admin):

 Install Python from official site first, then:
python -m venv cti_env
.\cti_env\Scripts\Activate
pip install stix2 pandas openai anthropic mcp numpy networkx neo4j

Set your LLM API keys as environment variables (both OS):

export OPENAI_API_KEY="sk-..."  Linux/macOS
set OPENAI_API_KEY="sk-..."  Windows CMD
$env:OPENAI_API_KEY="sk-..."  Windows PowerShell

MCP Server Note: The Model Context Protocol (MCP) requires an MCP server endpoint. Install the reference implementation:

  pip install mcp-sdk
   Later we'll configure the server with a STIX data source
  
  1. From Unstructured Text to STIX 2.1 Bundles Using LLMs
    This step takes a raw CTI report (e.g., a blog about a new phishing campaign) and outputs a valid STIX 2.1 bundle containing indicators, campaigns, and threat actors.

Python script (`raw_to_stix.py`):

import openai
import json
from stix2 import Bundle, Indicator, Campaign, ThreatActor, parse

def unstructured_to_stix(report_text, llm_model="gpt-4"):
prompt = f"""
Convert the following threat report into a STIX 2.1 JSON bundle.
Extract indicators (IPv4, domains, hashes), campaigns, and threat actors.
Output only valid JSON, no extra text.
Report: {report_text}
"""
response = openai.ChatCompletion.create(
model=llm_model,
messages=[{"role": "user", "content": prompt}],
temperature=0.0
)
stix_json = json.loads(response.choices[bash].message.content)
 Validate with stix2 library
bundle = parse(stix_json, allow_custom=True)
return bundle

if <strong>name</strong> == "<strong>main</strong>":
sample_report = "A new APT group 'DarkRain' is using ransomware with sha256 hash 5e8... and C2 domain evil.c2."
bundle = unstructured_to_stix(sample_report)
with open("output_stix_bundle.json", "w") as f:
f.write(str(bundle))
print(bundle.serialize(pretty=True))

How to use: Save the script, replace the sample report with your own text, and run python raw_to_stix.py. The output is a fully structured STIX bundle ready for ingestion into a threat intelligence platform (TIP).

  1. Building a Knowledge Graph: Load STIX into Neo4j
    Single STIX bundles have limited value. Load them into a graph database (Neo4j) to see relationships across reports—for example, linking the same C2 server to multiple campaigns.

Start Neo4j using Docker (Linux/Windows with Docker Desktop):

docker run -p 7474:7474 -p 7687:7687 -e NEO4J_AUTH=neo4j/password neo4j:latest

Python script to load STIX into Neo4j:

from neo4j import GraphDatabase
import json
from stix2 import Bundle

uri = "bolt://localhost:7687"
driver = GraphDatabase.driver(uri, auth=("neo4j", "password"))

def load_stix_bundle(stix_file, driver):
with open(stix_file) as f:
bundle = Bundle(json.loads(f.read()))
with driver.session() as session:
for obj in bundle.objects:
session.run(
"MERGE (n:STIXObject {id: $id, type: $type}) SET n += $props",
id=obj.id, type=obj.type, props=obj.serialize()
)
 Create relationships (e.g., indicator relates to campaign)
if obj.type == "indicator" and hasattr(obj, "kills_chain_phases"):
for phase in obj.kills_chain_phases:
session.run("MATCH (i:STIXObject {id: $iid}) "
"MERGE (c:Campaign {name: $phase}) "
"MERGE (i)-[:INDICATES]->(c)",
iid=obj.id, phase=phase["phase_name"])
print("Graph populated.")

load_stix_bundle("output_stix_bundle.json", driver)
driver.close()

Cypher query to find cross‑report patterns:

MATCH (i:STIXObject {type:"indicator"})-[r:INDICATES]->(c:Campaign)
RETURN i.id, c.name, count(r) as frequency ORDER BY frequency DESC
  1. Implementing MCP as the Interface Layer for AI Agents
    MCP turns your STIX knowledge graph into a consumable API that any AI agent (like a security chatbot) can query in real time.

Create an MCP server (`stix_mcp_server.py`):

from mcp import Server, Tool
import json
from neo4j import GraphDatabase

Initialize MCP server
app = Server("stix-kg-server")
neo4j_driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))

@app.tool()
async def get_indicators_for_actor(actor_name: str) -> str:
with neo4j_driver.session() as session:
result = session.run(
"MATCH (a:ThreatActor {name: $name})-[:USES]->(i:Indicator) RETURN i.pattern",
name=actor_name
)
indicators = [record["i.pattern"] for record in result]
return json.dumps(indicators)

@app.tool()
async def enrich_ioc(ioc_value: str) -> str:
 Query LLM or other sources for context
return f"Intel for {ioc_value} found in 3 campaigns."

if <strong>name</strong> == "<strong>main</strong>":
app.run(host="0.0.0.0", port=5000)

Run the server:

python stix_mcp_server.py

Now any AI agent (using MCP client libraries) can call `get_indicators_for_actor(“DarkRain”)` and receive machine‑readable STIX data.

5. Creating a STIX Constellation: Cross‑Report Pattern Analysis

A STIX constellation merges dozens of individual STIX bundles into a unified graph, revealing TTP overlaps invisible in isolation. Use the `stix2` library to merge bundles and then apply community detection (e.g., with NetworkX).

Bash script to merge multiple STIX files:

mkdir merged
for f in reports/.json; do
jq '.objects[]' $f >> merged/all_objects.json
done
 Then use Python to deduplicate and create a single Bundle

Python merge and generate ATT&CK heatmap:

import json, collections
from stix2 import Bundle

object_map = {}
for file in glob.glob("reports/.json"):
with open(file) as f:
bundle = json.load(f)
for obj in bundle["objects"]:
object_map[obj["id"]] = obj

merged_bundle = Bundle(list(object_map.values()))
with open("constellation_bundle.json", "w") as f:
f.write(merged_bundle.serialize())

Generate attack heatmap (count techniques per tactic)
tactic_counter = collections.Counter()
for obj in merged_bundle.objects:
if obj.type == "attack-pattern" and hasattr(obj, "kill_chain_phases"):
for phase in obj.kill_chain_phases:
tactic_counter[phase["phase_name"]] += 1
print("ATT&CK Heatmap:", tactic_counter.most_common())

6. Validating LLM‑Generated STIX Against Ground Truth Datasets

Academic validation ensures your LLM isn’t hallucinating IOCs. Use a public ground truth dataset (e.g., the `STIX‑validation` corpus) and calculate precision/recall.

Validation script:

from sklearn.metrics import precision_recall_fscore_support

def compare_stix(ground_truth_bundle, generated_bundle):
gt_indicators = {ind.pattern for ind in ground_truth_bundle.objects if ind.type == "indicator"}
gen_indicators = {ind.pattern for ind in generated_bundle.objects if ind.type == "indicator"}
y_true = [1 if i in gt_indicators else 0 for i in gen_indicators.union(gt_indicators)]
y_pred = [1 if i in gen_indicators else 0 for i in gen_indicators.union(gt_indicators)]
precision, recall, f1, _ = precision_recall_fscore_support(y_true, y_pred, average="binary")
print(f"Precision: {precision:.2f}, Recall: {recall:.2f}, F1: {f1:.2f}")

Command to download a sample ground truth:

wget https://raw.githubusercontent.com/oasis-open/cti-stix-common-objects/main/objects/indicator/indicator--sample.json
  1. Operationalizing the TI Mindmap HUB as a Continuous Research Engine
    The TI Mindmap HUB continuously ingests RSS feeds, Twitter lists, and email reports, runs the LLM extraction pipeline, updates the MCP‑backed graph, and pushes delta updates to SIEMs.

Production deployment with cron (Linux) or Task Scheduler (Windows):

 Linux crontab – run every hour
0     /home/cti_env/bin/python /opt/ti_hub/ingest_feeds.py

Example `ingest_feeds.py` skeleton:

import feedparser
from raw_to_stix import unstructured_to_stix

feeds = ["https://feeds.feedburner.com/Threatpost", "https://feeds.riskiq.com/labs"]
for url in feeds:
feed = feedparser.parse(url)
for entry in feed.entries:
bundle = unstructured_to_stix(entry.summary)
 Append to annual STIX constellation
with open("master_constellation.json", "a") as f:
f.write(str(bundle) + "\n")

What Undercode Say

  • Structure is survival: Without STIX, your CTI reports remain noise; LLMs + MCP turn noise into searchable, actionable intelligence.
  • AI agents need MCP: The Model Context Protocol bridges the gap between human‑readable intel and autonomous security tools, enabling real‑time queries and automated enrichment.
  • Cross‑report correlations are the real win: Single reports miss 80% of TTP overlaps; a STIX constellation exposes adversary tradecraft across years of data.

The shift from PDF‑based intel to LLM‑generated knowledge graphs is not incremental—it’s foundational. By adopting STIX 2.1, MCP servers, and cheap LLM extraction, even small teams can build threat intelligence capabilities that once required billion‑dollar budgets. The future of CTI is graph‑based, machine‑first, and validated by rigorous testing against ground truth.

Prediction

Within 24 months, most SOCs will replace manual intel parsing with LLM‑driven STIX pipelines. MCP will become the de facto standard for AI‑agent threat intel consumption, leading to automated cross‑organization sharing via STIX constellations. Attackers will face not just signature‑detection but whole‑graph behavioral profiling—shifting the advantage from obscurity to structure. The biggest challenge won’t be technical but cultural: convincing analysts to trust LLM‑generated bundles without human validation. The organizations that succeed will automate validation and focus human effort on strategic hunting, not copy‑pasting IOCs from blogs.

▶️ Related Video (70% Match):

🎯Let’s Practice For Free:

IT/Security Reporter URL:

Reported By: Antonioformato Bsidesluxembourg2026 – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow UndercodeTesting & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin | 🦋BlueSky