mcp-mesh 0.4.1__py3-none-any.whl → 0.5.0__py3-none-any.whl
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.
- _mcp_mesh/__init__.py +14 -3
- _mcp_mesh/engine/async_mcp_client.py +6 -19
- _mcp_mesh/engine/dependency_injector.py +161 -74
- _mcp_mesh/engine/full_mcp_proxy.py +25 -20
- _mcp_mesh/engine/mcp_client_proxy.py +5 -19
- _mcp_mesh/generated/.openapi-generator/FILES +2 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +2 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +1 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +305 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +1 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +10 -1
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +4 -4
- _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +108 -0
- _mcp_mesh/pipeline/__init__.py +2 -2
- _mcp_mesh/pipeline/api_heartbeat/__init__.py +16 -0
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +515 -0
- _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +117 -0
- _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +140 -0
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +247 -0
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +309 -0
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +332 -0
- _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +147 -0
- _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +97 -0
- _mcp_mesh/pipeline/api_startup/__init__.py +20 -0
- _mcp_mesh/pipeline/api_startup/api_pipeline.py +61 -0
- _mcp_mesh/pipeline/api_startup/api_server_setup.py +292 -0
- _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +302 -0
- _mcp_mesh/pipeline/api_startup/route_collection.py +56 -0
- _mcp_mesh/pipeline/api_startup/route_integration.py +318 -0
- _mcp_mesh/pipeline/{startup → mcp_startup}/fastmcpserver_discovery.py +4 -4
- _mcp_mesh/pipeline/{startup → mcp_startup}/heartbeat_loop.py +1 -1
- _mcp_mesh/pipeline/{startup → mcp_startup}/startup_orchestrator.py +170 -5
- _mcp_mesh/shared/config_resolver.py +0 -3
- _mcp_mesh/shared/logging_config.py +2 -1
- _mcp_mesh/shared/sse_parser.py +217 -0
- {mcp_mesh-0.4.1.dist-info → mcp_mesh-0.5.0.dist-info}/METADATA +1 -1
- {mcp_mesh-0.4.1.dist-info → mcp_mesh-0.5.0.dist-info}/RECORD +55 -37
- mesh/__init__.py +6 -2
- mesh/decorators.py +143 -1
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/__init__.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/dependency_resolution.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/fast_heartbeat_check.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/heartbeat_orchestrator.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/heartbeat_pipeline.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/heartbeat_send.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/lifespan_integration.py +0 -0
- /_mcp_mesh/pipeline/{heartbeat → mcp_heartbeat}/registry_connection.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/__init__.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/configuration.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/decorator_collection.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/fastapiserver_setup.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/heartbeat_preparation.py +0 -0
- /_mcp_mesh/pipeline/{startup → mcp_startup}/startup_pipeline.py +0 -0
- {mcp_mesh-0.4.1.dist-info → mcp_mesh-0.5.0.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.4.1.dist-info → mcp_mesh-0.5.0.dist-info}/licenses/LICENSE +0 -0
_mcp_mesh/__init__.py
CHANGED
|
@@ -31,7 +31,7 @@ from .engine.decorator_registry import (
|
|
|
31
31
|
get_decorator_stats,
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
__version__ = "0.
|
|
34
|
+
__version__ = "0.5.0"
|
|
35
35
|
|
|
36
36
|
# Store reference to runtime processor if initialized
|
|
37
37
|
_runtime_processor = None
|
|
@@ -48,7 +48,7 @@ def initialize_runtime():
|
|
|
48
48
|
# Legacy processor system has been replaced by pipeline architecture
|
|
49
49
|
|
|
50
50
|
# Use pipeline-based runtime
|
|
51
|
-
from .pipeline.
|
|
51
|
+
from .pipeline.mcp_startup import start_runtime
|
|
52
52
|
|
|
53
53
|
start_runtime()
|
|
54
54
|
|
|
@@ -60,7 +60,18 @@ def initialize_runtime():
|
|
|
60
60
|
|
|
61
61
|
# Auto-initialize runtime if enabled
|
|
62
62
|
if os.getenv("MCP_MESH_ENABLED", "true").lower() == "true":
|
|
63
|
-
|
|
63
|
+
# Use debounced initialization instead of immediate MCP startup
|
|
64
|
+
# This allows the system to determine MCP vs API pipeline based on decorators
|
|
65
|
+
try:
|
|
66
|
+
from .pipeline.mcp_startup import start_runtime
|
|
67
|
+
|
|
68
|
+
# Start the debounced runtime (sets up coordinator, no immediate pipeline execution)
|
|
69
|
+
start_runtime()
|
|
70
|
+
|
|
71
|
+
sys.stderr.write("MCP Mesh debounced runtime initialized\n")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
# Log but don't fail - allows graceful degradation
|
|
74
|
+
sys.stderr.write(f"MCP Mesh runtime initialization failed: {e}\n")
|
|
64
75
|
|
|
65
76
|
|
|
66
77
|
__all__ = [
|
|
@@ -6,6 +6,8 @@ import urllib.error
|
|
|
6
6
|
import urllib.request
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from ..shared.sse_parser import SSEParser
|
|
10
|
+
|
|
9
11
|
logger = logging.getLogger(__name__)
|
|
10
12
|
|
|
11
13
|
|
|
@@ -62,25 +64,10 @@ class AsyncMCPClient:
|
|
|
62
64
|
|
|
63
65
|
response_text = response.text
|
|
64
66
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
for line in response_text.split("\n"):
|
|
70
|
-
if line.startswith("data:"):
|
|
71
|
-
json_str = line[5:].strip() # Remove 'data:' prefix
|
|
72
|
-
try:
|
|
73
|
-
json_data = json.loads(json_str)
|
|
74
|
-
break
|
|
75
|
-
except json.JSONDecodeError:
|
|
76
|
-
continue
|
|
77
|
-
|
|
78
|
-
if json_data is None:
|
|
79
|
-
raise RuntimeError("Could not parse SSE response from FastMCP")
|
|
80
|
-
data = json_data
|
|
81
|
-
else:
|
|
82
|
-
# Plain JSON response
|
|
83
|
-
data = response.json()
|
|
67
|
+
# Use shared SSE parser
|
|
68
|
+
data = SSEParser.parse_sse_response(
|
|
69
|
+
response_text, f"AsyncMCPClient.{self.endpoint}"
|
|
70
|
+
)
|
|
84
71
|
|
|
85
72
|
# Check for JSON-RPC error
|
|
86
73
|
if "error" in data:
|
|
@@ -126,6 +126,9 @@ class DependencyInjector:
|
|
|
126
126
|
for func_id in self._dependency_mapping[name]:
|
|
127
127
|
if func_id in self._function_registry:
|
|
128
128
|
func = self._function_registry[func_id]
|
|
129
|
+
logger.debug(
|
|
130
|
+
f"🔄 UPDATING dependency '{name}' for {func_id} -> {func} at {hex(id(func))}"
|
|
131
|
+
)
|
|
129
132
|
if hasattr(func, "_mesh_update_dependency"):
|
|
130
133
|
func._mesh_update_dependency(name, instance)
|
|
131
134
|
|
|
@@ -279,99 +282,180 @@ class DependencyInjector:
|
|
|
279
282
|
# Capture logger in local scope to avoid NameError
|
|
280
283
|
wrapper_logger = logger
|
|
281
284
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
f"🔧
|
|
285
|
+
# If no mesh positions to inject, create minimal wrapper for tracking
|
|
286
|
+
if not mesh_positions:
|
|
287
|
+
logger.debug(
|
|
288
|
+
f"🔧 No injection positions for {func.__name__}, creating minimal wrapper for tracking"
|
|
286
289
|
)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
+
|
|
291
|
+
@functools.wraps(func)
|
|
292
|
+
def minimal_wrapper(*args, **kwargs):
|
|
293
|
+
return func(*args, **kwargs)
|
|
294
|
+
|
|
295
|
+
# Add minimal metadata for compatibility
|
|
296
|
+
minimal_wrapper._mesh_injected_deps = {}
|
|
297
|
+
minimal_wrapper._mesh_dependencies = dependencies
|
|
298
|
+
minimal_wrapper._mesh_positions = mesh_positions
|
|
299
|
+
minimal_wrapper._mesh_parameter_types = get_agent_parameter_types(func)
|
|
300
|
+
minimal_wrapper._mesh_original_func = func
|
|
301
|
+
|
|
302
|
+
def update_dependency(name: str, instance: Any | None) -> None:
|
|
303
|
+
"""No-op update for functions without injection positions."""
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
minimal_wrapper._mesh_update_dependency = update_dependency
|
|
307
|
+
|
|
308
|
+
# Register this wrapper for dependency updates (even though it won't use them)
|
|
309
|
+
logger.debug(
|
|
310
|
+
f"🔧 REGISTERING minimal wrapper: {func_id} -> {minimal_wrapper} at {hex(id(minimal_wrapper))}"
|
|
290
311
|
)
|
|
291
|
-
|
|
312
|
+
self._function_registry[func_id] = minimal_wrapper
|
|
313
|
+
|
|
314
|
+
return minimal_wrapper
|
|
292
315
|
|
|
293
|
-
|
|
294
|
-
|
|
316
|
+
# Determine if we need async wrapper
|
|
317
|
+
need_async_wrapper = inspect.iscoroutinefunction(func)
|
|
318
|
+
|
|
319
|
+
if need_async_wrapper:
|
|
320
|
+
|
|
321
|
+
@functools.wraps(func)
|
|
322
|
+
async def dependency_wrapper(*args, **kwargs):
|
|
295
323
|
wrapper_logger.debug(
|
|
296
|
-
"🔧 DEPENDENCY_WRAPPER:
|
|
324
|
+
f"🔧 DEPENDENCY_WRAPPER: Function {func.__name__} called"
|
|
297
325
|
)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
326
|
+
wrapper_logger.debug(
|
|
327
|
+
f"🔧 DEPENDENCY_WRAPPER: args={args}, kwargs={kwargs}"
|
|
328
|
+
)
|
|
329
|
+
wrapper_logger.debug(
|
|
330
|
+
f"🔧 DEPENDENCY_WRAPPER: mesh_positions={mesh_positions}"
|
|
331
|
+
)
|
|
332
|
+
wrapper_logger.debug(
|
|
333
|
+
f"🔧 DEPENDENCY_WRAPPER: dependencies={dependencies}"
|
|
302
334
|
)
|
|
303
335
|
|
|
304
|
-
|
|
305
|
-
sig = inspect.signature(func)
|
|
306
|
-
params = list(sig.parameters.keys())
|
|
307
|
-
final_kwargs = kwargs.copy()
|
|
336
|
+
# We know mesh_positions is not empty since we checked above
|
|
308
337
|
|
|
309
|
-
|
|
310
|
-
|
|
338
|
+
# Get function signature
|
|
339
|
+
sig = inspect.signature(func)
|
|
340
|
+
params = list(sig.parameters.keys())
|
|
341
|
+
final_kwargs = kwargs.copy()
|
|
311
342
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
for dep_index, param_position in enumerate(mesh_positions):
|
|
315
|
-
if dep_index < len(dependencies):
|
|
316
|
-
dep_name = dependencies[dep_index]
|
|
317
|
-
param_name = params[param_position]
|
|
343
|
+
wrapper_logger.debug(f"🔧 DEPENDENCY_WRAPPER: params={params}")
|
|
344
|
+
wrapper_logger.debug(f"🔧 DEPENDENCY_WRAPPER: original kwargs={kwargs}")
|
|
318
345
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
346
|
+
# Inject dependencies as kwargs
|
|
347
|
+
injected_count = 0
|
|
348
|
+
for dep_index, param_position in enumerate(mesh_positions):
|
|
349
|
+
if dep_index < len(dependencies):
|
|
350
|
+
dep_name = dependencies[dep_index]
|
|
351
|
+
param_name = params[param_position]
|
|
322
352
|
|
|
323
|
-
# Only inject if the parameter wasn't explicitly provided
|
|
324
|
-
if (
|
|
325
|
-
param_name not in final_kwargs
|
|
326
|
-
or final_kwargs.get(param_name) is None
|
|
327
|
-
):
|
|
328
|
-
# Get the dependency from wrapper's storage
|
|
329
|
-
dependency = dependency_wrapper._mesh_injected_deps.get(
|
|
330
|
-
dep_name
|
|
331
|
-
)
|
|
332
353
|
wrapper_logger.debug(
|
|
333
|
-
f"🔧 DEPENDENCY_WRAPPER:
|
|
354
|
+
f"🔧 DEPENDENCY_WRAPPER: Processing dep {dep_index}: {dep_name} -> {param_name}"
|
|
334
355
|
)
|
|
335
356
|
|
|
336
|
-
if
|
|
337
|
-
|
|
357
|
+
# Only inject if the parameter wasn't explicitly provided
|
|
358
|
+
if (
|
|
359
|
+
param_name not in final_kwargs
|
|
360
|
+
or final_kwargs.get(param_name) is None
|
|
361
|
+
):
|
|
362
|
+
# Get the dependency from wrapper's storage
|
|
363
|
+
dependency = dependency_wrapper._mesh_injected_deps.get(
|
|
364
|
+
dep_name
|
|
365
|
+
)
|
|
338
366
|
wrapper_logger.debug(
|
|
339
|
-
f"🔧 DEPENDENCY_WRAPPER: From
|
|
367
|
+
f"🔧 DEPENDENCY_WRAPPER: From wrapper storage: {dependency}"
|
|
340
368
|
)
|
|
341
369
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
else:
|
|
348
|
-
wrapper_logger.debug(
|
|
349
|
-
f"🔧 DEPENDENCY_WRAPPER: Skipping {param_name} - already provided"
|
|
350
|
-
)
|
|
370
|
+
if dependency is None:
|
|
371
|
+
dependency = self.get_dependency(dep_name)
|
|
372
|
+
wrapper_logger.debug(
|
|
373
|
+
f"🔧 DEPENDENCY_WRAPPER: From global storage: {dependency}"
|
|
374
|
+
)
|
|
351
375
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
result = ExecutionTracer.trace_function_execution(
|
|
362
|
-
func._mesh_original_func,
|
|
363
|
-
args,
|
|
364
|
-
final_kwargs,
|
|
365
|
-
dependencies,
|
|
366
|
-
mesh_positions,
|
|
367
|
-
injected_count,
|
|
368
|
-
wrapper_logger,
|
|
369
|
-
)
|
|
376
|
+
final_kwargs[param_name] = dependency
|
|
377
|
+
injected_count += 1
|
|
378
|
+
wrapper_logger.debug(
|
|
379
|
+
f"🔧 DEPENDENCY_WRAPPER: Injected {dep_name} as {param_name}"
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
wrapper_logger.debug(
|
|
383
|
+
f"🔧 DEPENDENCY_WRAPPER: Skipping {param_name} - already provided"
|
|
384
|
+
)
|
|
370
385
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
386
|
+
wrapper_logger.debug(
|
|
387
|
+
f"🔧 DEPENDENCY_WRAPPER: Injected {injected_count} dependencies"
|
|
388
|
+
)
|
|
389
|
+
wrapper_logger.debug(
|
|
390
|
+
f"🔧 DEPENDENCY_WRAPPER: final_kwargs={final_kwargs}"
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# ===== EXECUTE WITH DEPENDENCY INJECTION =====
|
|
394
|
+
# Call the original function with injected dependencies
|
|
395
|
+
original_func = func._mesh_original_func
|
|
396
|
+
|
|
397
|
+
# Check if the function is async and handle accordingly
|
|
398
|
+
if inspect.iscoroutinefunction(original_func):
|
|
399
|
+
# For async functions, await the result directly
|
|
400
|
+
result = await original_func(*args, **final_kwargs)
|
|
401
|
+
else:
|
|
402
|
+
# For sync functions, call directly
|
|
403
|
+
result = original_func(*args, **final_kwargs)
|
|
404
|
+
|
|
405
|
+
wrapper_logger.debug(
|
|
406
|
+
f"🔧 DEPENDENCY_WRAPPER: Original returned: {type(result)}"
|
|
407
|
+
)
|
|
408
|
+
return result
|
|
409
|
+
|
|
410
|
+
else:
|
|
411
|
+
# Create sync wrapper for sync functions without dependencies
|
|
412
|
+
@functools.wraps(func)
|
|
413
|
+
def dependency_wrapper(*args, **kwargs):
|
|
414
|
+
wrapper_logger.debug(
|
|
415
|
+
f"🔧 DEPENDENCY_WRAPPER: Function {func.__name__} called"
|
|
416
|
+
)
|
|
417
|
+
wrapper_logger.debug(
|
|
418
|
+
f"🔧 DEPENDENCY_WRAPPER: args={args}, kwargs={kwargs}"
|
|
419
|
+
)
|
|
420
|
+
wrapper_logger.debug(
|
|
421
|
+
f"🔧 DEPENDENCY_WRAPPER: mesh_positions={mesh_positions}"
|
|
422
|
+
)
|
|
423
|
+
wrapper_logger.debug(
|
|
424
|
+
f"🔧 DEPENDENCY_WRAPPER: dependencies={dependencies}"
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# We know mesh_positions is not empty since we checked above
|
|
428
|
+
|
|
429
|
+
# Handle dependency injection for sync functions
|
|
430
|
+
sig = inspect.signature(func)
|
|
431
|
+
params = list(sig.parameters.keys())
|
|
432
|
+
final_kwargs = kwargs.copy()
|
|
433
|
+
|
|
434
|
+
# Inject dependencies as kwargs
|
|
435
|
+
injected_count = 0
|
|
436
|
+
for dep_index, param_position in enumerate(mesh_positions):
|
|
437
|
+
if dep_index < len(dependencies):
|
|
438
|
+
dep_name = dependencies[dep_index]
|
|
439
|
+
param_name = params[param_position]
|
|
440
|
+
|
|
441
|
+
# Only inject if the parameter wasn't explicitly provided
|
|
442
|
+
if (
|
|
443
|
+
param_name not in final_kwargs
|
|
444
|
+
or final_kwargs.get(param_name) is None
|
|
445
|
+
):
|
|
446
|
+
# Get the dependency from wrapper's storage
|
|
447
|
+
dependency = dependency_wrapper._mesh_injected_deps.get(
|
|
448
|
+
dep_name
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
if dependency is None:
|
|
452
|
+
dependency = self.get_dependency(dep_name)
|
|
453
|
+
|
|
454
|
+
final_kwargs[param_name] = dependency
|
|
455
|
+
injected_count += 1
|
|
456
|
+
|
|
457
|
+
# Call the original function with injected dependencies
|
|
458
|
+
return func._mesh_original_func(*args, **final_kwargs)
|
|
375
459
|
|
|
376
460
|
# Store dependency state on wrapper
|
|
377
461
|
dependency_wrapper._mesh_injected_deps = {}
|
|
@@ -399,6 +483,9 @@ class DependencyInjector:
|
|
|
399
483
|
dependency_wrapper._mesh_original_func = func
|
|
400
484
|
|
|
401
485
|
# Register this wrapper for dependency updates
|
|
486
|
+
logger.debug(
|
|
487
|
+
f"🔧 REGISTERING in function_registry: {func_id} -> {dependency_wrapper} at {hex(id(dependency_wrapper))}"
|
|
488
|
+
)
|
|
402
489
|
self._function_registry[func_id] = dependency_wrapper
|
|
403
490
|
|
|
404
491
|
# Return the wrapper (which FastMCP will register)
|
|
@@ -7,6 +7,7 @@ import uuid
|
|
|
7
7
|
from collections.abc import AsyncIterator
|
|
8
8
|
from typing import Any, Optional
|
|
9
9
|
|
|
10
|
+
from ..shared.sse_parser import SSEStreamProcessor
|
|
10
11
|
from .async_mcp_client import AsyncMCPClient
|
|
11
12
|
from .mcp_client_proxy import MCPClientProxy
|
|
12
13
|
|
|
@@ -103,15 +104,18 @@ class FullMCPProxy(MCPClientProxy):
|
|
|
103
104
|
if response.status_code >= 400:
|
|
104
105
|
raise RuntimeError(f"HTTP error {response.status_code}")
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
# Use shared SSE stream processor
|
|
108
|
+
sse_processor = SSEStreamProcessor(f"FullMCPProxy.{name}")
|
|
109
|
+
|
|
110
|
+
async for chunk_bytes in response.aiter_bytes(8192):
|
|
111
|
+
chunks = sse_processor.process_chunk(chunk_bytes)
|
|
112
|
+
for chunk in chunks:
|
|
113
|
+
yield chunk
|
|
114
|
+
|
|
115
|
+
# Process any remaining data
|
|
116
|
+
final_chunks = sse_processor.finalize()
|
|
117
|
+
for chunk in final_chunks:
|
|
118
|
+
yield chunk
|
|
115
119
|
|
|
116
120
|
except ImportError:
|
|
117
121
|
# Fallback: if httpx not available, use sync call
|
|
@@ -588,19 +592,20 @@ class EnhancedFullMCPProxy(FullMCPProxy):
|
|
|
588
592
|
) as response:
|
|
589
593
|
response.raise_for_status()
|
|
590
594
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
buffer += chunk.decode("utf-8")
|
|
595
|
+
# Use shared SSE stream processor
|
|
596
|
+
sse_processor = SSEStreamProcessor(f"EnhancedFullMCPProxy.{name}")
|
|
594
597
|
|
|
595
|
-
|
|
596
|
-
|
|
598
|
+
async for chunk_bytes in response.aiter_bytes(
|
|
599
|
+
max(self.buffer_size, 8192)
|
|
600
|
+
):
|
|
601
|
+
chunks = sse_processor.process_chunk(chunk_bytes)
|
|
602
|
+
for chunk in chunks:
|
|
603
|
+
yield chunk
|
|
597
604
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
except json.JSONDecodeError:
|
|
603
|
-
continue
|
|
605
|
+
# Process any remaining data
|
|
606
|
+
final_chunks = sse_processor.finalize()
|
|
607
|
+
for chunk in final_chunks:
|
|
608
|
+
yield chunk
|
|
604
609
|
|
|
605
610
|
except httpx.TimeoutException:
|
|
606
611
|
raise Exception(f"Streaming timeout after {self.stream_timeout}s")
|
|
@@ -10,6 +10,7 @@ import uuid
|
|
|
10
10
|
from typing import Any, Optional
|
|
11
11
|
|
|
12
12
|
from ..shared.content_extractor import ContentExtractor
|
|
13
|
+
from ..shared.sse_parser import SSEParser
|
|
13
14
|
from .async_mcp_client import AsyncMCPClient
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -113,25 +114,10 @@ class MCPClientProxy:
|
|
|
113
114
|
with urllib.request.urlopen(req, timeout=30.0) as response:
|
|
114
115
|
response_data = response.read().decode("utf-8")
|
|
115
116
|
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
for line in response_data.split("\n"):
|
|
121
|
-
if line.startswith("data:"):
|
|
122
|
-
json_str = line[5:].strip() # Remove 'data:' prefix
|
|
123
|
-
try:
|
|
124
|
-
json_data = json.loads(json_str)
|
|
125
|
-
break
|
|
126
|
-
except json.JSONDecodeError:
|
|
127
|
-
continue
|
|
128
|
-
|
|
129
|
-
if json_data is None:
|
|
130
|
-
raise RuntimeError("Could not parse SSE response from FastMCP")
|
|
131
|
-
data = json_data
|
|
132
|
-
else:
|
|
133
|
-
# Plain JSON response
|
|
134
|
-
data = json.loads(response_data)
|
|
117
|
+
# Use shared SSE parser
|
|
118
|
+
data = SSEParser.parse_sse_response(
|
|
119
|
+
response_data, f"MCPClientProxy.{self.function_name}"
|
|
120
|
+
)
|
|
135
121
|
|
|
136
122
|
# Check for JSON-RPC error
|
|
137
123
|
if "error" in data:
|
|
@@ -2,6 +2,7 @@ mcp_mesh_registry_client/__init__.py
|
|
|
2
2
|
mcp_mesh_registry_client/api/__init__.py
|
|
3
3
|
mcp_mesh_registry_client/api/agents_api.py
|
|
4
4
|
mcp_mesh_registry_client/api/health_api.py
|
|
5
|
+
mcp_mesh_registry_client/api/tracing_api.py
|
|
5
6
|
mcp_mesh_registry_client/api_client.py
|
|
6
7
|
mcp_mesh_registry_client/api_response.py
|
|
7
8
|
mcp_mesh_registry_client/configuration.py
|
|
@@ -35,5 +36,6 @@ mcp_mesh_registry_client/models/registration_response.py
|
|
|
35
36
|
mcp_mesh_registry_client/models/rich_dependency.py
|
|
36
37
|
mcp_mesh_registry_client/models/root_response.py
|
|
37
38
|
mcp_mesh_registry_client/models/standardized_dependency.py
|
|
39
|
+
mcp_mesh_registry_client/models/trace_event.py
|
|
38
40
|
mcp_mesh_registry_client/py.typed
|
|
39
41
|
mcp_mesh_registry_client/rest.py
|
|
@@ -36,6 +36,7 @@ __version__ = "1.0.0"
|
|
|
36
36
|
# import apis into sdk package
|
|
37
37
|
from _mcp_mesh.generated.mcp_mesh_registry_client.api.agents_api import AgentsApi
|
|
38
38
|
from _mcp_mesh.generated.mcp_mesh_registry_client.api.health_api import HealthApi
|
|
39
|
+
from _mcp_mesh.generated.mcp_mesh_registry_client.api.tracing_api import TracingApi
|
|
39
40
|
|
|
40
41
|
# import ApiClient
|
|
41
42
|
from _mcp_mesh.generated.mcp_mesh_registry_client.api_response import ApiResponse
|
|
@@ -77,3 +78,4 @@ from _mcp_mesh.generated.mcp_mesh_registry_client.models.registration_response i
|
|
|
77
78
|
from _mcp_mesh.generated.mcp_mesh_registry_client.models.rich_dependency import RichDependency
|
|
78
79
|
from _mcp_mesh.generated.mcp_mesh_registry_client.models.root_response import RootResponse
|
|
79
80
|
from _mcp_mesh.generated.mcp_mesh_registry_client.models.standardized_dependency import StandardizedDependency
|
|
81
|
+
from _mcp_mesh.generated.mcp_mesh_registry_client.models.trace_event import TraceEvent
|
|
@@ -3,4 +3,5 @@
|
|
|
3
3
|
# import apis into api package
|
|
4
4
|
from _mcp_mesh.generated.mcp_mesh_registry_client.api.agents_api import AgentsApi
|
|
5
5
|
from _mcp_mesh.generated.mcp_mesh_registry_client.api.health_api import HealthApi
|
|
6
|
+
from _mcp_mesh.generated.mcp_mesh_registry_client.api.tracing_api import TracingApi
|
|
6
7
|
|