This document walks one data path end to end: a captured session in Fiddler becomes tool evidence that Gemini or DeepSeek can reason over.
The header tool is used as the concrete example. Every other tool is the same pattern with a different REST path. There are currently 10 MCP tools on 5ire-bridge.
- Fiddler converts captured sessions to JSON and POSTs them to the local Flask bridge.
- enhanced-bridge.py stores JSON in memory and exposes REST endpoints such as
/api/sessions/headers/<id>. - 5ire-bridge.py exposes MCP tools such as
fiddler_mcp__session_headersthat call those REST endpoints. - gemini-fiddler-client.py binds MCP schemas on the active provider (Gemini FunctionDeclarations or DeepSeek OpenAI tools), runs the native chat loop, executes tools through
call_tool, and returns results to the model. - gemini_native_tools.py, llm_prompts.py, llm_tool_schema.py, and llm_providers/ are helper libraries. They are not processes you start.
The model never executes Python and never talks to Fiddler directly. The client is the only component that calls tools.
Fiddler CustomRules
-> enhanced-bridge POST /live-session
-> 5ire-bridge MCP tools/list + tools/call
-> gemini-fiddler-client + llm_providers (Gemini or DeepSeek)
-> LLM native tool loop
Fiddler keeps sessions in memory. To make them accessible outside the UI we push each completed session to the local HTTP server. CustomRules does this after every response via McpTryPost.
// CustomRules.js
static function McpTryPost(oSession: Session): void {
try {
// Skip tunnels or sessions with no HTTP response
if ((oSession.oResponse == null) || (oSession.responseCode == 0)) return;
var json: String = McpBuildSimpleJson(oSession);
McpHttpPost(json);
} catch (e) {
FiddlerApplication.Log.LogString("MCP error: " + e.Message);
}
}Key idea: McpBuildSimpleJson condenses the session (request line, headers, body, EKFiddle flags) and McpHttpPost sends it to http://127.0.0.1:8081/live-session.
The staging HTTP server buffers JSON in a ring buffer and exposes REST endpoints. For headers:
# enhanced-bridge.py
@self.app.route('/api/sessions/headers/<session_id>', methods=['GET'])
def get_session_headers(session_id):
"""Get headers for specific session"""
try:
with self.session_lock:
for session in reversed(self.live_sessions):
if str(session.get('id', '')) == str(session_id):
return jsonify({
"success": True,
"session_id": session_id,
"request_headers": session.get('requestHeaders', {}),
"response_headers": session.get('responseHeaders', {}),
"found": True
})
return jsonify({
"success": False,
"error": f"Session {session_id} not found",
"found": False
}), 404Key idea: everything stays reachable through plain HTTP so curl, MCP, or a browser can fetch it like a normal API.
The MCP bridge translates model tool invocations into REST calls.
# 5ire-bridge.py (FiddlerBridgeClient helper)
def get_session_headers(self, *, session_id: str) -> Dict[str, Any]:
try:
data = self.request("GET", f"/api/sessions/headers/{session_id}")
except BridgeConnectionError:
return {
"success": False,
"error": "Cannot connect to real-time bridge",
"bridge_status": "Disconnected",
}
...That helper is wired to the tool definition the LLM sees:
# 5ire-bridge.py (MCP tool)
@mcp.tool()
def fiddler_mcp__session_headers(
session_id: Annotated[str, Field(description="Session ID from live_sessions or sessions_search.")],
) -> Dict[str, Any]:
"""Fetch ONLY the HTTP headers (NOT the body content) for a captured session."""
return client.get_session_headers(session_id=session_id)Key idea: the tool name fiddler_mcp__session_headers is what appears in tools/list. Calling it hits the REST endpoint above.
Current MCP tools:
fiddler_mcp__live_sessionsfiddler_mcp__sessions_searchfiddler_mcp__session_headersfiddler_mcp__session_bodyfiddler_mcp__compare_sessionsfiddler_mcp__live_statsfiddler_mcp__sessions_timelinefiddler_mcp__sessions_clearfiddler_mcp__ekfiddle_sessionsfiddler_mcp__ekfiddle_threats
gemini-fiddler-client.py bootstraps the runtime:
- Checks / installs packages from
requirements-gemini.txt(includesopenaifor DeepSeek) - Verifies companion scripts exist, including
gemini_native_tools.pyandllm_prompts.py - Starts
enhanced-bridge.pyif port 8081 is unhealthy - Starts
5ire-bridge.pyas an MCP child over stdin/stdout - Calls MCP
tools/listand binds tools on the active provider (Gemini default)
llm_prompts.py— INVESTIGATE CAPTURE + EKFiddle HARD MODE system instruction shared by both backendsllm_tool_schema.py— MCP JSON Schema normalization; OpenAI tools array conversion for DeepSeekgemini_native_tools.py— Gemini FunctionDeclaration / FunctionResponse helpersllm_providers/gemini_provider.py/llm_providers/deepseek_provider.py— provider adapters used by_chat_native
# Native path: GEMINI_NATIVE_TOOLS=1 (provider-driven)
calls = provider.extract_tool_calls(response)
for call in calls:
result = self.call_tool(call["name"], call["args"]) # execution gate
provider.append_tool_results(conversation, executed, nudge)Flow for one analyst question:
- Client sends user text plus bound tools to the active LLM
- Gemini may return
function_callparts; DeepSeek may returntool_calls - Client executes each call sequentially through
call_tool - Client appends model turn + tool results to the provider conversation
- The model continues with more tools or returns the final answer
- If the tool budget is exhausted, client forces a text-only synthesis turn (
tool_choice=none)
/investigate builds a shared playbook prompt then enters the same chat loop, so both providers get investigation + EKFiddle parity.
The model chooses tool names and parameters. Python validates them before MCP:
- rename common hallucinated tool names
- reject unknown tools
- sanitize args: map
id→session_id,filter→host_pattern, strip leading* - drop unknown keys or return a structured error with a correction hint
- block re-fetch of an already analyzed
session_bodyin the same query - optionally auto-fetch a body after a narrow host-filtered search
If sanitize rejects the call, that error is returned as the tool result. The model sees it on the next turn and can retry with better args.
Native schemas reduce invented keys. They do not eliminate bad values. That is why call_tool stays in front of every execution.
With GEMINI_NATIVE_TOOLS=0, the Gemini client scrapes tool JSON from free text instead of using function_call parts. Prefer the native path. DeepSeek uses native tools only.
| Component | Role |
|---|---|
| Fiddler + CustomRules.js | Capture and publish sessions |
| enhanced-bridge.py | In-memory HTTP API for session data |
| 5ire-bridge.py | MCP tool surface over that API |
| gemini-fiddler-client.py | Chat UI, bootstrap, agent loop, call_tool gate |
| llm_providers/* | Gemini / DeepSeek native tool adapters |
| Gemini or DeepSeek API | Chooses tools and writes the analyst answer |
Whatever the bridge returns (headers, bodies, stats, EKFiddle triage) becomes evidence for the next model turn. Different tools simply supply different JSON through the same gate.