1212
1313SESSIONS = defaultdict (dict )
1414
15+
1516def _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
2527def 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
6966def 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
10197def 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+
140133if __name__ == "__main__" :
141134 mcp .run ()
0 commit comments