| 215 | return |
| 216 | |
| 217 | def invoke( |
| 218 | self, name: str, arguments: Dict[str, Any], is_catch_exception=True |
| 219 | ) -> Dict[str, Any]: |
| 220 | # Replay mode: return recorded tool outputs deterministically |
| 221 | if self._replay_events is not None: |
| 222 | try: |
| 223 | ev = self._replay_next() |
| 224 | if ev.get("name") != name: |
| 225 | raise ValueError( |
| 226 | f"trace mismatch: expected tool {name}, got {ev.get('name')}" |
| 227 | ) |
| 228 | recorded_args = ev.get("arguments") |
| 229 | if isinstance(recorded_args, dict) and recorded_args != ( |
| 230 | arguments or {} |
| 231 | ): |
| 232 | raise ValueError("trace mismatch: arguments differ") |
| 233 | self._maybe_apply_side_effects_in_replay(name, arguments or {}) |
| 234 | result = ev.get("result") |
| 235 | if isinstance(result, dict): |
| 236 | return result |
| 237 | # Back-compat: allow non-dict results |
| 238 | return {"result": result} |
| 239 | except Exception as e: |
| 240 | if is_catch_exception: |
| 241 | return {"error": f"Tool replay failed: {e}"} |
| 242 | raise |
| 243 | |
| 244 | async def _invoke(): |
| 245 | if not self._client: |
| 246 | raise RuntimeError("MCP client not connected") |
| 247 | result = await self._client.call_tool(name, arguments) |
| 248 | return result.data if hasattr(result, "data") else {"content": result} |
| 249 | |
| 250 | try: |
| 251 | # Use lock to ensure thread-safe access to the event loop |
| 252 | with self._loop_lock: |
| 253 | if self._loop is None: |
| 254 | raise RuntimeError("MCP client not connected") |
| 255 | out = self._loop.run_until_complete(_invoke()) |
| 256 | self._update_session_id_from_transport() |
| 257 | # Trace tool call + output (append-only) |
| 258 | if self._trace_path: |
| 259 | with self._trace_lock: |
| 260 | self._trace_seq += 1 |
| 261 | _append_jsonl( |
| 262 | self._trace_path, |
| 263 | { |
| 264 | "type": "tool", |
| 265 | "seq": self._trace_seq, |
| 266 | "ts": time.time(), |
| 267 | "session_id": self.get_session_id(), |
| 268 | "name": name, |
| 269 | "arguments": arguments or {}, |
| 270 | "result": out, |
| 271 | }, |
| 272 | ) |
| 273 | return out |
| 274 | except Exception as e: |