chuk-tool-processor 0.6.7__tar.gz → 0.6.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/PKG-INFO +1 -1
  2. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/pyproject.toml +1 -1
  3. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/mcp/stream_manager.py +53 -34
  4. chuk_tool_processor-0.6.10/src/chuk_tool_processor/mcp/transport/__init__.py +22 -0
  5. chuk_tool_processor-0.6.10/src/chuk_tool_processor/mcp/transport/base_transport.py +254 -0
  6. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/mcp/transport/http_streamable_transport.py +134 -173
  7. chuk_tool_processor-0.6.10/src/chuk_tool_processor/mcp/transport/sse_transport.py +632 -0
  8. chuk_tool_processor-0.6.10/src/chuk_tool_processor/mcp/transport/stdio_transport.py +452 -0
  9. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor.egg-info/PKG-INFO +1 -1
  10. chuk_tool_processor-0.6.7/src/chuk_tool_processor/mcp/transport/__init__.py +0 -39
  11. chuk_tool_processor-0.6.7/src/chuk_tool_processor/mcp/transport/base_transport.py +0 -103
  12. chuk_tool_processor-0.6.7/src/chuk_tool_processor/mcp/transport/sse_transport.py +0 -477
  13. chuk_tool_processor-0.6.7/src/chuk_tool_processor/mcp/transport/stdio_transport.py +0 -236
  14. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/README.md +0 -0
  15. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/setup.cfg +0 -0
  16. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/__init__.py +0 -0
  17. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/core/__init__.py +0 -0
  18. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/core/exceptions.py +0 -0
  19. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/core/processor.py +0 -0
  20. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/__init__.py +0 -0
  21. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/strategies/__init__.py +0 -0
  22. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/strategies/inprocess_strategy.py +0 -0
  23. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/strategies/subprocess_strategy.py +0 -0
  24. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/tool_executor.py +0 -0
  25. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/wrappers/__init__.py +0 -0
  26. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/wrappers/caching.py +0 -0
  27. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/wrappers/rate_limiting.py +0 -0
  28. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/execution/wrappers/retry.py +0 -0
  29. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/logging/__init__.py +0 -0
  30. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/logging/context.py +0 -0
  31. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/logging/formatter.py +0 -0
  32. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/logging/helpers.py +0 -0
  33. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/logging/metrics.py +0 -0
  34. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/mcp/__init__.py +0 -0
  35. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/mcp/mcp_tool.py +0 -0
  36. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/mcp/register_mcp_tools.py +0 -0
  37. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/mcp/setup_mcp_http_streamable.py +0 -0
  38. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/mcp/setup_mcp_sse.py +0 -0
  39. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/mcp/setup_mcp_stdio.py +0 -0
  40. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/models/__init__.py +0 -0
  41. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/models/execution_strategy.py +0 -0
  42. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/models/streaming_tool.py +0 -0
  43. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/models/tool_call.py +0 -0
  44. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/models/tool_export_mixin.py +0 -0
  45. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/models/tool_result.py +0 -0
  46. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/models/validated_tool.py +0 -0
  47. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/plugins/__init__.py +0 -0
  48. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/plugins/discovery.py +0 -0
  49. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/plugins/parsers/__init__.py +0 -0
  50. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/plugins/parsers/base.py +0 -0
  51. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/plugins/parsers/function_call_tool.py +0 -0
  52. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/plugins/parsers/json_tool.py +0 -0
  53. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/plugins/parsers/openai_tool.py +0 -0
  54. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/plugins/parsers/xml_tool.py +0 -0
  55. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/__init__.py +0 -0
  56. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/auto_register.py +0 -0
  57. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/decorators.py +0 -0
  58. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/interface.py +0 -0
  59. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/metadata.py +0 -0
  60. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/provider.py +0 -0
  61. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/providers/__init__.py +0 -0
  62. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/providers/memory.py +0 -0
  63. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/registry/tool_export.py +0 -0
  64. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/utils/__init__.py +0 -0
  65. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor/utils/validation.py +0 -0
  66. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor.egg-info/SOURCES.txt +0 -0
  67. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor.egg-info/dependency_links.txt +0 -0
  68. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor.egg-info/requires.txt +0 -0
  69. {chuk_tool_processor-0.6.7 → chuk_tool_processor-0.6.10}/src/chuk_tool_processor.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chuk-tool-processor
3
- Version: 0.6.7
3
+ Version: 0.6.10
4
4
  Summary: Async-native framework for registering, discovering, and executing tools referenced in LLM responses
5
5
  Author-email: CHUK Team <chrishayuk@somejunkmailbox.com>
6
6
  Maintainer-email: CHUK Team <chrishayuk@somejunkmailbox.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "chuk-tool-processor"
7
- version = "0.6.7"
7
+ version = "0.6.10"
8
8
  description = "Async-native framework for registering, discovering, and executing tools referenced in LLM responses"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -32,7 +32,7 @@ class StreamManager:
32
32
  Updated to support the latest transports:
33
33
  - STDIO (process-based)
34
34
  - SSE (Server-Sent Events) with headers support
35
- - HTTP Streamable (modern replacement for SSE, spec 2025-03-26) with headers support
35
+ - HTTP Streamable (modern replacement for SSE, spec 2025-03-26) with graceful headers handling
36
36
  """
37
37
 
38
38
  def __init__(self) -> None:
@@ -175,6 +175,7 @@ class StreamManager:
175
175
  transport_type: str = "stdio",
176
176
  default_timeout: float = 30.0,
177
177
  ) -> None:
178
+ """Initialize with graceful headers handling for all transport types."""
178
179
  if self._closed:
179
180
  raise RuntimeError("Cannot initialize a closed StreamManager")
180
181
 
@@ -200,12 +201,17 @@ class StreamManager:
200
201
  headers = {}
201
202
  logger.warning("No URL configured for SSE transport, using default: %s", sse_url)
202
203
 
203
- transport = SSETransport(
204
- sse_url,
205
- api_key,
206
- headers=headers,
207
- default_timeout=default_timeout
208
- )
204
+ # Build SSE transport with optional headers
205
+ transport_params = {
206
+ 'url': sse_url,
207
+ 'api_key': api_key,
208
+ 'default_timeout': default_timeout
209
+ }
210
+ if headers:
211
+ transport_params['headers'] = headers
212
+
213
+ transport = SSETransport(**transport_params)
214
+
209
215
  elif transport_type == "http_streamable":
210
216
  logger.warning("Using HTTP Streamable transport in initialize() - consider using initialize_with_http_streamable() instead")
211
217
  params = await load_config(config_file, server_name)
@@ -222,13 +228,19 @@ class StreamManager:
222
228
  session_id = None
223
229
  logger.warning("No URL configured for HTTP Streamable transport, using default: %s", http_url)
224
230
 
225
- transport = HTTPStreamableTransport(
226
- http_url,
227
- api_key,
228
- headers=headers,
229
- default_timeout=default_timeout,
230
- session_id=session_id
231
- )
231
+ # Build HTTP transport (headers not supported yet)
232
+ transport_params = {
233
+ 'url': http_url,
234
+ 'api_key': api_key,
235
+ 'default_timeout': default_timeout,
236
+ 'session_id': session_id
237
+ }
238
+ # Note: headers not added until HTTPStreamableTransport supports them
239
+ if headers:
240
+ logger.debug("Headers provided but not supported in HTTPStreamableTransport yet")
241
+
242
+ transport = HTTPStreamableTransport(**transport_params)
243
+
232
244
  else:
233
245
  logger.error("Unsupported transport type: %s", transport_type)
234
246
  continue
@@ -277,7 +289,7 @@ class StreamManager:
277
289
  connection_timeout: float = 10.0,
278
290
  default_timeout: float = 30.0,
279
291
  ) -> None:
280
- """Initialize with SSE transport with headers support."""
292
+ """Initialize with SSE transport with optional headers support."""
281
293
  if self._closed:
282
294
  raise RuntimeError("Cannot initialize a closed StreamManager")
283
295
 
@@ -290,18 +302,21 @@ class StreamManager:
290
302
  logger.error("Bad server config: %s", cfg)
291
303
  continue
292
304
  try:
293
- # FIXED: Extract and pass headers from server config
305
+ # Build SSE transport parameters with optional headers
306
+ transport_params = {
307
+ 'url': url,
308
+ 'api_key': cfg.get("api_key"),
309
+ 'connection_timeout': connection_timeout,
310
+ 'default_timeout': default_timeout
311
+ }
312
+
313
+ # Add headers if provided
294
314
  headers = cfg.get("headers", {})
295
315
  if headers:
296
316
  logger.debug("SSE %s: Using configured headers: %s", name, list(headers.keys()))
317
+ transport_params['headers'] = headers
297
318
 
298
- transport = SSETransport(
299
- url,
300
- cfg.get("api_key"),
301
- headers=headers, # ← FIXED: Pass headers
302
- connection_timeout=connection_timeout,
303
- default_timeout=default_timeout
304
- )
319
+ transport = SSETransport(**transport_params)
305
320
 
306
321
  if not await asyncio.wait_for(transport.initialize(), timeout=connection_timeout):
307
322
  logger.error("Failed to init SSE %s", name)
@@ -339,7 +354,7 @@ class StreamManager:
339
354
  connection_timeout: float = 30.0,
340
355
  default_timeout: float = 30.0,
341
356
  ) -> None:
342
- """Initialize with HTTP Streamable transport with headers support."""
357
+ """Initialize with HTTP Streamable transport with graceful headers handling."""
343
358
  if self._closed:
344
359
  raise RuntimeError("Cannot initialize a closed StreamManager")
345
360
 
@@ -352,19 +367,23 @@ class StreamManager:
352
367
  logger.error("Bad server config: %s", cfg)
353
368
  continue
354
369
  try:
355
- # FIXED: Extract and pass headers from server config
370
+ # Build HTTP Streamable transport parameters
371
+ transport_params = {
372
+ 'url': url,
373
+ 'api_key': cfg.get("api_key"),
374
+ 'connection_timeout': connection_timeout,
375
+ 'default_timeout': default_timeout,
376
+ 'session_id': cfg.get("session_id")
377
+ }
378
+
379
+ # Handle headers if provided (for future HTTPStreamableTransport support)
356
380
  headers = cfg.get("headers", {})
357
381
  if headers:
358
- logger.debug("HTTP Streamable %s: Using configured headers: %s", name, list(headers.keys()))
382
+ logger.debug("HTTP Streamable %s: Headers provided but not yet supported in transport", name)
383
+ # TODO: Add headers support when HTTPStreamableTransport is updated
384
+ # transport_params['headers'] = headers
359
385
 
360
- transport = HTTPStreamableTransport(
361
- url,
362
- cfg.get("api_key"),
363
- headers=headers, # ← FIXED: Pass headers (requires HTTPStreamableTransport to be updated)
364
- connection_timeout=connection_timeout,
365
- default_timeout=default_timeout,
366
- session_id=cfg.get("session_id")
367
- )
386
+ transport = HTTPStreamableTransport(**transport_params)
368
387
 
369
388
  if not await asyncio.wait_for(transport.initialize(), timeout=connection_timeout):
370
389
  logger.error("Failed to init HTTP Streamable %s", name)
@@ -0,0 +1,22 @@
1
+ # chuk_tool_processor/mcp/transport/__init__.py
2
+ """
3
+ MCP Transport module providing consistent transport implementations.
4
+
5
+ All transports now follow the same interface and provide consistent behavior:
6
+ - Standardized initialization and cleanup
7
+ - Unified metrics and monitoring
8
+ - Consistent error handling and timeouts
9
+ - Shared response normalization
10
+ """
11
+
12
+ from .base_transport import MCPBaseTransport
13
+ from .stdio_transport import StdioTransport
14
+ from .sse_transport import SSETransport
15
+ from .http_streamable_transport import HTTPStreamableTransport
16
+
17
+ __all__ = [
18
+ "MCPBaseTransport",
19
+ "StdioTransport",
20
+ "SSETransport",
21
+ "HTTPStreamableTransport",
22
+ ]
@@ -0,0 +1,254 @@
1
+ # chuk_tool_processor/mcp/transport/base_transport.py
2
+ """
3
+ Abstract base class for MCP transports with complete interface definition.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, Dict, List, Optional
9
+
10
+
11
+ class MCPBaseTransport(ABC):
12
+ """
13
+ Abstract base class for all MCP transport implementations.
14
+
15
+ Defines the complete interface that all transports must implement
16
+ for consistency across stdio, SSE, and HTTP streamable transports.
17
+ """
18
+
19
+ # ------------------------------------------------------------------ #
20
+ # Core connection lifecycle #
21
+ # ------------------------------------------------------------------ #
22
+ @abstractmethod
23
+ async def initialize(self) -> bool:
24
+ """
25
+ Initialize the transport connection.
26
+
27
+ Returns:
28
+ True if initialization was successful, False otherwise.
29
+ """
30
+ pass
31
+
32
+ @abstractmethod
33
+ async def close(self) -> None:
34
+ """Close the transport connection and clean up all resources."""
35
+ pass
36
+
37
+ # ------------------------------------------------------------------ #
38
+ # Health and diagnostics #
39
+ # ------------------------------------------------------------------ #
40
+ @abstractmethod
41
+ async def send_ping(self) -> bool:
42
+ """
43
+ Send a ping to verify the connection is alive.
44
+
45
+ Returns:
46
+ True if ping was successful, False otherwise.
47
+ """
48
+ pass
49
+
50
+ @abstractmethod
51
+ def is_connected(self) -> bool:
52
+ """
53
+ Check if the transport is connected and ready for operations.
54
+
55
+ Returns:
56
+ True if connected, False otherwise.
57
+ """
58
+ pass
59
+
60
+ # ------------------------------------------------------------------ #
61
+ # Core MCP operations #
62
+ # ------------------------------------------------------------------ #
63
+ @abstractmethod
64
+ async def get_tools(self) -> List[Dict[str, Any]]:
65
+ """
66
+ Get the list of available tools from the server.
67
+
68
+ Returns:
69
+ List of tool definitions.
70
+ """
71
+ pass
72
+
73
+ @abstractmethod
74
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any],
75
+ timeout: Optional[float] = None) -> Dict[str, Any]:
76
+ """
77
+ Call a tool with the given arguments.
78
+
79
+ Args:
80
+ tool_name: Name of the tool to call
81
+ arguments: Arguments to pass to the tool
82
+ timeout: Optional timeout for the operation
83
+
84
+ Returns:
85
+ Dictionary with 'isError' boolean and either 'content' or 'error'
86
+ """
87
+ pass
88
+
89
+ @abstractmethod
90
+ async def list_resources(self) -> Dict[str, Any]:
91
+ """
92
+ List available resources from the server.
93
+
94
+ Returns:
95
+ Dictionary containing resources list or empty dict if not supported.
96
+ """
97
+ pass
98
+
99
+ @abstractmethod
100
+ async def list_prompts(self) -> Dict[str, Any]:
101
+ """
102
+ List available prompts from the server.
103
+
104
+ Returns:
105
+ Dictionary containing prompts list or empty dict if not supported.
106
+ """
107
+ pass
108
+
109
+ # ------------------------------------------------------------------ #
110
+ # Metrics and monitoring (all transports should support these) #
111
+ # ------------------------------------------------------------------ #
112
+ @abstractmethod
113
+ def get_metrics(self) -> Dict[str, Any]:
114
+ """
115
+ Get performance and connection metrics.
116
+
117
+ Returns:
118
+ Dictionary containing metrics data.
119
+ """
120
+ pass
121
+
122
+ @abstractmethod
123
+ def reset_metrics(self) -> None:
124
+ """Reset performance metrics to initial state."""
125
+ pass
126
+
127
+ # ------------------------------------------------------------------ #
128
+ # Backward compatibility and utility methods #
129
+ # ------------------------------------------------------------------ #
130
+ def get_streams(self) -> List[tuple]:
131
+ """
132
+ Get underlying stream objects for backward compatibility.
133
+
134
+ Returns:
135
+ List of (read_stream, write_stream) tuples, empty if not applicable.
136
+ """
137
+ return []
138
+
139
+ # ------------------------------------------------------------------ #
140
+ # Context manager support (all transports should support this) #
141
+ # ------------------------------------------------------------------ #
142
+ async def __aenter__(self):
143
+ """Context manager entry."""
144
+ success = await self.initialize()
145
+ if not success:
146
+ raise RuntimeError(f"Failed to initialize {self.__class__.__name__}")
147
+ return self
148
+
149
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
150
+ """Context manager cleanup."""
151
+ await self.close()
152
+
153
+ # ------------------------------------------------------------------ #
154
+ # Shared helper methods for response normalization #
155
+ # ------------------------------------------------------------------ #
156
+ def _normalize_mcp_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
157
+ """
158
+ Normalize MCP response to consistent format.
159
+
160
+ This provides shared logic for all transports to ensure consistent
161
+ response format regardless of transport type.
162
+ """
163
+ # Handle explicit error in response
164
+ if "error" in response:
165
+ error_info = response["error"]
166
+ if isinstance(error_info, dict):
167
+ error_msg = error_info.get("message", "Unknown error")
168
+ else:
169
+ error_msg = str(error_info)
170
+
171
+ return {
172
+ "isError": True,
173
+ "error": error_msg
174
+ }
175
+
176
+ # Handle successful response with result
177
+ if "result" in response:
178
+ result = response["result"]
179
+
180
+ if isinstance(result, dict) and "content" in result:
181
+ return {
182
+ "isError": False,
183
+ "content": self._extract_mcp_content(result["content"])
184
+ }
185
+ else:
186
+ return {
187
+ "isError": False,
188
+ "content": result
189
+ }
190
+
191
+ # Handle direct content-based response
192
+ if "content" in response:
193
+ return {
194
+ "isError": False,
195
+ "content": self._extract_mcp_content(response["content"])
196
+ }
197
+
198
+ # Fallback
199
+ return {
200
+ "isError": False,
201
+ "content": response
202
+ }
203
+
204
+ def _extract_mcp_content(self, content_list: Any) -> Any:
205
+ """
206
+ Extract content from MCP content format.
207
+
208
+ Handles the standard MCP content format where content is a list
209
+ of content items with type and data.
210
+ """
211
+ if not isinstance(content_list, list) or not content_list:
212
+ return content_list
213
+
214
+ # Handle single content item
215
+ if len(content_list) == 1:
216
+ content_item = content_list[0]
217
+ if isinstance(content_item, dict):
218
+ if content_item.get("type") == "text":
219
+ text_content = content_item.get("text", "")
220
+ # Try to parse JSON, fall back to plain text
221
+ try:
222
+ import json
223
+ return json.loads(text_content)
224
+ except json.JSONDecodeError:
225
+ return text_content
226
+ else:
227
+ return content_item
228
+
229
+ # Multiple content items - return as-is
230
+ return content_list
231
+
232
+ # ------------------------------------------------------------------ #
233
+ # Standard string representation #
234
+ # ------------------------------------------------------------------ #
235
+ def __repr__(self) -> str:
236
+ """Standard string representation for all transports."""
237
+ status = "initialized" if getattr(self, '_initialized', False) else "not initialized"
238
+
239
+ # Add metrics info if available
240
+ metrics_info = ""
241
+ if hasattr(self, 'enable_metrics') and getattr(self, 'enable_metrics', False):
242
+ metrics = self.get_metrics()
243
+ if metrics.get("total_calls", 0) > 0:
244
+ success_rate = (metrics.get("successful_calls", 0) / metrics["total_calls"]) * 100
245
+ metrics_info = f", calls: {metrics['total_calls']}, success: {success_rate:.1f}%"
246
+
247
+ # Add transport-specific info - FIXED FORMAT
248
+ transport_info = ""
249
+ if hasattr(self, 'url'):
250
+ transport_info = f", url={self.url}" # Fixed: was "url: "
251
+ elif hasattr(self, 'server_params') and hasattr(self.server_params, 'command'):
252
+ transport_info = f", command={self.server_params.command}" # Fixed: was "command: "
253
+
254
+ return f"{self.__class__.__name__}(status={status}{transport_info}{metrics_info})"