Skip to content

Commit 5945321

Browse files
name = "/packetcopilot:analyze"
description = "Upload, convert, index, and analyze a PCAP using Gemini File Search." args = ["question", "path?=./capture.pcap"] prompt = """ SESSION=$(uuidgen) echo "🆕 Session: $SESSION" echo "📤 Uploading $path …" BASE64=$(base64 -w 0 "$path") mcp call convert_to_json --arguments "{\\"session_id\\": \\"$SESSION\\", \\"filename\\": \\"$(basename "$path")\\", \\"data_b64\\": \\"$BASE64\\"}" echo "📚 Indexing capture …" mcp call upload_and_index --arguments "{\\"session_id\\": \\"$SESSION\\"}" echo "🤖 Asking Gemini File Search …" mcp call analyze_pcap --arguments "{\\"session_id\\": \\"$SESSION\\", \\"question\\": \\"$question\\"}" """
1 parent aa35e7d commit 5945321

File tree

2 files changed

+22
-29
lines changed

2 files changed

+22
-29
lines changed

‎commands/packet_buddy/analyze.toml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ echo "🆕 Session: $SESSION"
77
88
echo "📤 Uploading $path …"
99
BASE64=$(base64 -w 0 "$path")
10-
mcp tools call convert_to_json --arguments "{\\"session_id\\": \\"$SESSION\\", \\"filename\\": \\"$(basename "$path")\\", \\"data_b64\\": \\"$BASE64\\"}"
10+
mcp call convert_to_json --arguments "{\\"session_id\\": \\"$SESSION\\", \\"filename\\": \\"$(basename "$path")\\", \\"data_b64\\": \\"$BASE64\\"}"
1111
1212
echo "📚 Indexing capture …"
13-
mcp tools call upload_and_index --arguments "{\\"session_id\\": \\"$SESSION\\"}"
13+
mcp call upload_and_index --arguments "{\\"session_id\\": \\"$SESSION\\"}"
1414
1515
echo "🤖 Asking Gemini File Search …"
16-
mcp tools call analyze_pcap --arguments "{\\"session_id\\": \\"$SESSION\\", \\"question\\": \\"$question\\"}"
16+
mcp call analyze_pcap --arguments "{\\"session_id\\": \\"$SESSION\\", \\"question\\": \\"$question\\"}"
1717
"""

‎servers/server.py‎

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,22 @@
1212

1313
SESSIONS = defaultdict(dict)
1414

15+
1516
def _session(session_id: str):
1617
s = SESSIONS[session_id]
1718
if "dir" not in s:
1819
s["dir"] = tempfile.mkdtemp(prefix=f"pcap_{session_id}_")
1920
return s
2021

22+
2123
# ──────────────────────────────────────────────
2224
# 1️⃣ Convert PCAP → JSON (robust)
2325
# ──────────────────────────────────────────────
2426
@mcp.tool
2527
def convert_to_json(session_id: str, filename: str = "", data_b64: str = "") -> str:
26-
"""
27-
Convert a .pcap to JSON using tshark. Accepts either a local file or base64 data.
28-
"""
28+
"""Convert a .pcap to JSON using tshark. Accepts either a local file or base64 data."""
2929
s = _session(session_id)
3030

31-
# prefer direct file if it exists
3231
if filename and os.path.exists(filename):
3332
pcap_path = filename
3433
else:
@@ -40,18 +39,15 @@ def convert_to_json(session_id: str, filename: str = "", data_b64: str = "") ->
4039

4140
json_path = os.path.join(s["dir"], os.path.basename(pcap_path) + ".json")
4241

43-
result = subprocess.run(
44-
["tshark", "-nlr", pcap_path, "-T", "json"],
45-
capture_output=True,
46-
text=True
47-
)
42+
result = subprocess.run(["tshark", "-nlr", pcap_path, "-T", "json"],
43+
capture_output=True, text=True)
4844

49-
# fallback: convert from pcapng → libpcap if needed
45+
# fallback: pcapng → libpcap
5046
if result.returncode != 0:
5147
if "pcapng" in result.stderr:
52-
fixed_path = pcap_path + ".fixed"
53-
subprocess.run(["editcap", "-F", "libpcap", pcap_path, fixed_path], check=True)
54-
result = subprocess.run(["tshark", "-nlr", fixed_path, "-T", "json"],
48+
fixed = pcap_path + ".fixed"
49+
subprocess.run(["editcap", "-F", "libpcap", pcap_path, fixed], check=True)
50+
result = subprocess.run(["tshark", "-nlr", fixed, "-T", "json"],
5551
capture_output=True, text=True)
5652
if result.returncode != 0:
5753
raise RuntimeError(f"tshark failed: {result.stderr}")
@@ -62,8 +58,9 @@ def convert_to_json(session_id: str, filename: str = "", data_b64: str = "") ->
6258
s["pcap_path"], s["json_path"] = pcap_path, json_path
6359
return json_path
6460

61+
6562
# ──────────────────────────────────────────────
66-
# 2️⃣ Upload JSON to Gemini File Search
63+
# 2️⃣ Upload JSON Gemini File Search
6764
# ──────────────────────────────────────────────
6865
@mcp.tool
6966
def upload_and_index(session_id: str) -> str:
@@ -73,32 +70,32 @@ def upload_and_index(session_id: str) -> str:
7370
if not json_path or not os.path.exists(json_path):
7471
raise ValueError("No JSON found. Run convert_to_json first.")
7572

76-
# Handles both string and object return types
73+
# Handle string / object return types
7774
store_obj = client.file_search_stores.create(
7875
config={"display_name": f"pcap_store_{session_id}"}
7976
)
8077
store_name = getattr(store_obj, "name", store_obj)
8178

82-
# Upload to store
8379
op = client.file_search_stores.upload_to_file_search_store(
8480
file_search_store_name=store_name,
8581
file=json_path,
8682
config={"display_name": os.path.basename(json_path)},
8783
)
8884

89-
# Wait for indexing
9085
while not getattr(op, "done", False):
9186
time.sleep(2)
9287
op = client.operations.get(op.name)
9388

9489
s["store_name"] = store_name
9590
return f"✅ Uploaded and indexed {json_path} to Gemini File Search store: {store_name}"
9691

92+
9793
# ──────────────────────────────────────────────
98-
# 3️⃣ Ask a grounded question
94+
# 3️⃣ Analyze → Gemini 2.5 Flash
9995
# ──────────────────────────────────────────────
10096
@mcp.tool
10197
def analyze_pcap(session_id: str, question: str) -> dict:
98+
"""Ask Gemini 2.5 Flash a grounded question on the indexed JSON."""
10299
s = _session(session_id)
103100
store_name = s.get("store_name")
104101
if not store_name:
@@ -117,15 +114,10 @@ def analyze_pcap(session_id: str, question: str) -> dict:
117114
)
118115

119116
grounding = getattr(resp.candidates[0], "grounding_metadata", None)
120-
sources = []
121-
if grounding and grounding.grounding_chunks:
122-
sources = [c.retrieved_context.title for c in grounding.grounding_chunks]
117+
sources = [c.retrieved_context.title for c in getattr(grounding, "grounding_chunks", [])]
118+
119+
return {"answer": resp.text, "sources": sources, "meta": {"store": store_name}}
123120

124-
return {
125-
"answer": resp.text,
126-
"sources": sources,
127-
"meta": {"store": store_name}
128-
}
129121

130122
# ──────────────────────────────────────────────
131123
# 4️⃣ Cleanup
@@ -137,5 +129,6 @@ def cleanup(session_id: str) -> str:
137129
shutil.rmtree(d, ignore_errors=True)
138130
return "ok"
139131

132+
140133
if __name__ == "__main__":
141134
mcp.run()

0 commit comments

Comments
 (0)