fast-agent-mcp 0.0.7__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.

Potentially problematic release.


This version of fast-agent-mcp might be problematic. Click here for more details.

Files changed (100) hide show
  1. fast_agent_mcp-0.0.7.dist-info/METADATA +322 -0
  2. fast_agent_mcp-0.0.7.dist-info/RECORD +100 -0
  3. fast_agent_mcp-0.0.7.dist-info/WHEEL +4 -0
  4. fast_agent_mcp-0.0.7.dist-info/entry_points.txt +5 -0
  5. fast_agent_mcp-0.0.7.dist-info/licenses/LICENSE +201 -0
  6. mcp_agent/__init__.py +0 -0
  7. mcp_agent/agents/__init__.py +0 -0
  8. mcp_agent/agents/agent.py +277 -0
  9. mcp_agent/app.py +303 -0
  10. mcp_agent/cli/__init__.py +0 -0
  11. mcp_agent/cli/__main__.py +4 -0
  12. mcp_agent/cli/commands/bootstrap.py +221 -0
  13. mcp_agent/cli/commands/config.py +11 -0
  14. mcp_agent/cli/commands/setup.py +229 -0
  15. mcp_agent/cli/main.py +68 -0
  16. mcp_agent/cli/terminal.py +24 -0
  17. mcp_agent/config.py +334 -0
  18. mcp_agent/console.py +28 -0
  19. mcp_agent/context.py +251 -0
  20. mcp_agent/context_dependent.py +48 -0
  21. mcp_agent/core/fastagent.py +1013 -0
  22. mcp_agent/eval/__init__.py +0 -0
  23. mcp_agent/event_progress.py +88 -0
  24. mcp_agent/executor/__init__.py +0 -0
  25. mcp_agent/executor/decorator_registry.py +120 -0
  26. mcp_agent/executor/executor.py +293 -0
  27. mcp_agent/executor/task_registry.py +34 -0
  28. mcp_agent/executor/temporal.py +405 -0
  29. mcp_agent/executor/workflow.py +197 -0
  30. mcp_agent/executor/workflow_signal.py +325 -0
  31. mcp_agent/human_input/__init__.py +0 -0
  32. mcp_agent/human_input/handler.py +49 -0
  33. mcp_agent/human_input/types.py +58 -0
  34. mcp_agent/logging/__init__.py +0 -0
  35. mcp_agent/logging/events.py +123 -0
  36. mcp_agent/logging/json_serializer.py +163 -0
  37. mcp_agent/logging/listeners.py +216 -0
  38. mcp_agent/logging/logger.py +365 -0
  39. mcp_agent/logging/rich_progress.py +120 -0
  40. mcp_agent/logging/tracing.py +140 -0
  41. mcp_agent/logging/transport.py +461 -0
  42. mcp_agent/mcp/__init__.py +0 -0
  43. mcp_agent/mcp/gen_client.py +85 -0
  44. mcp_agent/mcp/mcp_activity.py +18 -0
  45. mcp_agent/mcp/mcp_agent_client_session.py +242 -0
  46. mcp_agent/mcp/mcp_agent_server.py +56 -0
  47. mcp_agent/mcp/mcp_aggregator.py +394 -0
  48. mcp_agent/mcp/mcp_connection_manager.py +330 -0
  49. mcp_agent/mcp/stdio.py +104 -0
  50. mcp_agent/mcp_server_registry.py +275 -0
  51. mcp_agent/progress_display.py +10 -0
  52. mcp_agent/resources/examples/decorator/main.py +26 -0
  53. mcp_agent/resources/examples/decorator/optimizer.py +78 -0
  54. mcp_agent/resources/examples/decorator/orchestrator.py +68 -0
  55. mcp_agent/resources/examples/decorator/parallel.py +81 -0
  56. mcp_agent/resources/examples/decorator/router.py +56 -0
  57. mcp_agent/resources/examples/decorator/tiny.py +22 -0
  58. mcp_agent/resources/examples/mcp_researcher/main-evalopt.py +53 -0
  59. mcp_agent/resources/examples/mcp_researcher/main.py +38 -0
  60. mcp_agent/telemetry/__init__.py +0 -0
  61. mcp_agent/telemetry/usage_tracking.py +18 -0
  62. mcp_agent/workflows/__init__.py +0 -0
  63. mcp_agent/workflows/embedding/__init__.py +0 -0
  64. mcp_agent/workflows/embedding/embedding_base.py +61 -0
  65. mcp_agent/workflows/embedding/embedding_cohere.py +49 -0
  66. mcp_agent/workflows/embedding/embedding_openai.py +46 -0
  67. mcp_agent/workflows/evaluator_optimizer/__init__.py +0 -0
  68. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +359 -0
  69. mcp_agent/workflows/intent_classifier/__init__.py +0 -0
  70. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +120 -0
  71. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +134 -0
  72. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +45 -0
  73. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +45 -0
  74. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +161 -0
  75. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +60 -0
  76. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +60 -0
  77. mcp_agent/workflows/llm/__init__.py +0 -0
  78. mcp_agent/workflows/llm/augmented_llm.py +645 -0
  79. mcp_agent/workflows/llm/augmented_llm_anthropic.py +539 -0
  80. mcp_agent/workflows/llm/augmented_llm_openai.py +615 -0
  81. mcp_agent/workflows/llm/llm_selector.py +345 -0
  82. mcp_agent/workflows/llm/model_factory.py +175 -0
  83. mcp_agent/workflows/orchestrator/__init__.py +0 -0
  84. mcp_agent/workflows/orchestrator/orchestrator.py +407 -0
  85. mcp_agent/workflows/orchestrator/orchestrator_models.py +154 -0
  86. mcp_agent/workflows/orchestrator/orchestrator_prompts.py +113 -0
  87. mcp_agent/workflows/parallel/__init__.py +0 -0
  88. mcp_agent/workflows/parallel/fan_in.py +350 -0
  89. mcp_agent/workflows/parallel/fan_out.py +187 -0
  90. mcp_agent/workflows/parallel/parallel_llm.py +141 -0
  91. mcp_agent/workflows/router/__init__.py +0 -0
  92. mcp_agent/workflows/router/router_base.py +276 -0
  93. mcp_agent/workflows/router/router_embedding.py +240 -0
  94. mcp_agent/workflows/router/router_embedding_cohere.py +59 -0
  95. mcp_agent/workflows/router/router_embedding_openai.py +59 -0
  96. mcp_agent/workflows/router/router_llm.py +301 -0
  97. mcp_agent/workflows/swarm/__init__.py +0 -0
  98. mcp_agent/workflows/swarm/swarm.py +320 -0
  99. mcp_agent/workflows/swarm/swarm_anthropic.py +42 -0
  100. mcp_agent/workflows/swarm/swarm_openai.py +41 -0
@@ -0,0 +1,163 @@
1
+ import os
2
+ import warnings
3
+ from typing import Any, Dict, Iterable, Set
4
+ from datetime import datetime, date
5
+ from decimal import Decimal
6
+ from pathlib import Path
7
+ from uuid import UUID
8
+ from enum import Enum
9
+ import dataclasses
10
+ import inspect
11
+ import httpx
12
+
13
+ from mcp_agent.logging import logger
14
+
15
+
16
+ class JSONSerializer:
17
+ """
18
+ A robust JSON serializer that handles various Python objects by attempting
19
+ different serialization strategies recursively.
20
+ """
21
+
22
+ MAX_DEPTH = 99 # Maximum recursion depth
23
+
24
+ # Fields that are likely to contain sensitive information
25
+ SENSITIVE_FIELDS = {
26
+ "api_key",
27
+ "secret",
28
+ "password",
29
+ "token",
30
+ "auth",
31
+ "private_key",
32
+ "client_secret",
33
+ "access_token",
34
+ "refresh_token",
35
+ }
36
+
37
+ def __init__(self):
38
+ # Set of already processed objects to prevent infinite recursion
39
+ self._processed_objects: Set[int] = set()
40
+ # Check if secrets should be logged in full
41
+ self._log_secrets = os.getenv("LOG_SECRETS", "").upper() == "TRUE"
42
+
43
+ def _redact_sensitive_value(self, value: str) -> str:
44
+ """Redact sensitive values to show only first 10 chars."""
45
+ if not value or not isinstance(value, str):
46
+ return value
47
+ if self._log_secrets:
48
+ return value
49
+ if len(value) <= 10:
50
+ return value + "....."
51
+ return value[:10] + "....."
52
+
53
+ def serialize(self, obj: Any) -> Any:
54
+ """Main entry point for serialization."""
55
+ # Reset processed objects for new serialization
56
+ self._processed_objects.clear()
57
+ return self._serialize_object(obj, depth=0)
58
+
59
+ def _is_sensitive_key(self, key: str) -> bool:
60
+ """Check if a key likely contains sensitive information."""
61
+ key = str(key).lower()
62
+ return any(sensitive in key for sensitive in self.SENSITIVE_FIELDS)
63
+
64
+ def _serialize_object(self, obj: Any, depth: int = 0) -> Any:
65
+ """Recursively serialize an object using various strategies."""
66
+ # Handle None
67
+ if obj is None:
68
+ return None
69
+
70
+ if depth == 0:
71
+ self._parent_obj = obj
72
+ # Check depth
73
+ if depth > self.MAX_DEPTH:
74
+ warnings.warn(
75
+ f"Maximum recursion depth ({self.MAX_DEPTH}) exceeded while serializing object of type {type(obj).__name__} parent: {type(self._parent_obj).__name__}"
76
+ )
77
+ return str(obj)
78
+
79
+ # Prevent infinite recursion
80
+ obj_id = id(obj)
81
+ if obj_id in self._processed_objects:
82
+ return str(obj)
83
+ self._processed_objects.add(obj_id)
84
+
85
+ # Try different serialization strategies in order
86
+ try:
87
+ if isinstance(obj, httpx.Response):
88
+ return f"<httpx.Response [{obj.status_code}] {obj.url}>"
89
+
90
+ if isinstance(obj, logger.Logger):
91
+ return "<logging: logger>"
92
+
93
+ # Basic JSON-serializable types
94
+ if isinstance(obj, (str, int, float, bool)):
95
+ return obj
96
+
97
+ # Handle common built-in types
98
+ if isinstance(obj, (datetime, date)):
99
+ return obj.isoformat()
100
+ if isinstance(obj, (Decimal, UUID)):
101
+ return str(obj)
102
+ if isinstance(obj, Path):
103
+ return str(obj)
104
+ if isinstance(obj, Enum):
105
+ return obj.value
106
+
107
+ # Handle callables
108
+ if callable(obj):
109
+ return f"<callable: {obj.__name__}>"
110
+
111
+ # Handle Pydantic models
112
+ if hasattr(obj, "model_dump"): # Pydantic v2
113
+ return self._serialize_object(obj.model_dump())
114
+ if hasattr(obj, "dict"): # Pydantic v1
115
+ return self._serialize_object(obj.dict())
116
+
117
+ # Handle dataclasses
118
+ if dataclasses.is_dataclass(obj):
119
+ return self._serialize_object(dataclasses.asdict(obj))
120
+
121
+ # Handle objects with custom serialization method
122
+ if hasattr(obj, "to_json"):
123
+ return self._serialize_object(obj.to_json())
124
+ if hasattr(obj, "to_dict"):
125
+ return self._serialize_object(obj.to_dict())
126
+
127
+ # Handle dictionaries with sensitive data redaction
128
+ if isinstance(obj, Dict):
129
+ return {
130
+ str(key): self._redact_sensitive_value(value)
131
+ if self._is_sensitive_key(key)
132
+ else self._serialize_object(value, depth + 1)
133
+ for key, value in obj.items()
134
+ }
135
+
136
+ # Handle iterables (lists, tuples, sets)
137
+ if isinstance(obj, Iterable) and not isinstance(obj, (str, bytes)):
138
+ return [self._serialize_object(item, depth + 1) for item in obj]
139
+
140
+ # Handle objects with __dict__
141
+ if hasattr(obj, "__dict__"):
142
+ return self._serialize_object(obj.__dict__, depth + 1)
143
+
144
+ # Handle objects with attributes
145
+ if inspect.getmembers(obj):
146
+ return {
147
+ name: self._redact_sensitive_value(value)
148
+ if self._is_sensitive_key(name)
149
+ else self._serialize_object(value, depth + 1)
150
+ for name, value in inspect.getmembers(obj)
151
+ if not name.startswith("_") and not inspect.ismethod(value)
152
+ }
153
+
154
+ # Fallback: convert to string
155
+ return str(obj)
156
+
157
+ except Exception as e:
158
+ # If all serialization attempts fail, return string representation
159
+ return f"<unserializable: {type(obj).__name__}, error: {str(e)}>"
160
+
161
+ def __call__(self, obj: Any) -> Any:
162
+ """Make the serializer callable."""
163
+ return self.serialize(obj)
@@ -0,0 +1,216 @@
1
+ """
2
+ Listeners for the logger module of MCP Agent.
3
+ """
4
+
5
+ import asyncio
6
+ import logging
7
+ import time
8
+
9
+ from abc import ABC, abstractmethod
10
+ from typing import Dict, List
11
+
12
+ from mcp_agent.logging.events import Event, EventFilter, EventType
13
+ from mcp_agent.event_progress import convert_log_event
14
+
15
+
16
+ class EventListener(ABC):
17
+ """Base async listener that processes events."""
18
+
19
+ @abstractmethod
20
+ async def handle_event(self, event: Event):
21
+ """Process an incoming event."""
22
+
23
+
24
+ class LifecycleAwareListener(EventListener):
25
+ """
26
+ Optionally override start()/stop() for setup/teardown.
27
+ The event bus calls these at bus start/stop time.
28
+ """
29
+
30
+ async def start(self):
31
+ """Start an event listener, usually when the event bus is set up."""
32
+ pass
33
+
34
+ async def stop(self):
35
+ """Stop an event listener, usually when the event bus is shutting down."""
36
+ pass
37
+
38
+
39
+ class FilteredListener(LifecycleAwareListener):
40
+ """
41
+ Only processes events that pass the given filter.
42
+ Subclasses override _handle_matched_event().
43
+ """
44
+
45
+ def __init__(self, event_filter: EventFilter | None = None):
46
+ """
47
+ Initialize the listener.
48
+ Args:
49
+ filter: Event filter to apply to incoming events.
50
+ """
51
+ self.filter = event_filter
52
+
53
+ async def handle_event(self, event):
54
+ if not self.filter or self.filter.matches(event):
55
+ await self.handle_matched_event(event)
56
+
57
+ async def handle_matched_event(self, event: Event):
58
+ """Process an event that matches the filter."""
59
+ pass
60
+
61
+
62
+ class LoggingListener(FilteredListener):
63
+ """
64
+ Routes events to Python's logging facility with appropriate severity level.
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ event_filter: EventFilter | None = None,
70
+ logger: logging.Logger | None = None,
71
+ ):
72
+ """
73
+ Initialize the listener.
74
+ Args:
75
+ logger: Logger to use for event processing. Defaults to 'mcp_agent'.
76
+ """
77
+ super().__init__(event_filter=event_filter)
78
+ self.logger = logger or logging.getLogger("mcp_agent")
79
+
80
+ async def handle_matched_event(self, event):
81
+ level_map: Dict[EventType, int] = {
82
+ "debug": logging.DEBUG,
83
+ "info": logging.INFO,
84
+ "warning": logging.WARNING,
85
+ "error": logging.ERROR,
86
+ }
87
+ level = level_map.get(event.type, logging.INFO)
88
+
89
+ # Check if this is a server stderr message and format accordingly
90
+ if event.name == "mcpserver.stderr":
91
+ message = f"MCP Server: {event.message}"
92
+ else:
93
+ message = event.message
94
+
95
+ self.logger.log(
96
+ level,
97
+ "[%s] %s",
98
+ event.namespace,
99
+ message,
100
+ extra={
101
+ "event_data": event.data,
102
+ "span_id": event.span_id,
103
+ "trace_id": event.trace_id,
104
+ "event_name": event.name,
105
+ },
106
+ )
107
+
108
+
109
+ class ProgressListener(LifecycleAwareListener):
110
+ """
111
+ Listens for all events pre-filtering and converts them to progress events
112
+ for display. By inheriting directly from LifecycleAwareListener instead of
113
+ FilteredListener, we get events before any filtering occurs.
114
+ """
115
+
116
+ def __init__(self, display=None):
117
+ """Initialize the progress listener.
118
+ Args:
119
+ display: Optional display handler. If None, the shared progress_display will be used.
120
+ """
121
+ from mcp_agent.progress_display import progress_display
122
+
123
+ self.display = display or progress_display
124
+
125
+ async def start(self):
126
+ """Start the progress display."""
127
+ self.display.start()
128
+
129
+ async def stop(self):
130
+ """Stop the progress display."""
131
+ self.display.stop()
132
+
133
+ async def handle_event(self, event: Event):
134
+ """Process an incoming event and display progress if relevant."""
135
+
136
+ if event.data:
137
+ progress_event = convert_log_event(event)
138
+ if progress_event:
139
+ self.display.update(progress_event)
140
+
141
+
142
+ class BatchingListener(FilteredListener):
143
+ """
144
+ Accumulates events in memory, flushes them in batches.
145
+ Here we just print the batch size, but you might store or forward them.
146
+ """
147
+
148
+ def __init__(
149
+ self,
150
+ event_filter: EventFilter | None = None,
151
+ batch_size: int = 5,
152
+ flush_interval: float = 2.0,
153
+ ):
154
+ """
155
+ Initialize the listener.
156
+ Args:
157
+ batch_size: Number of events to accumulate before flushing.
158
+ flush_interval: Time in seconds to wait before flushing events.
159
+ """
160
+ super().__init__(event_filter=event_filter)
161
+ self.batch_size = batch_size
162
+ self.flush_interval = flush_interval
163
+ self.batch: List[Event] = []
164
+ self.last_flush: float = time.time() # Time of last flush
165
+ self._flush_task: asyncio.Task | None = None # Task for periodic flush loop
166
+ self._stop_event = None # Event to signal flush task to stop
167
+
168
+ async def start(self, loop=None):
169
+ """Spawn a periodic flush loop."""
170
+ self._stop_event = asyncio.Event()
171
+ self._flush_task = asyncio.create_task(self._periodic_flush())
172
+
173
+ async def stop(self):
174
+ """Stop flush loop and flush any remaining events."""
175
+ if self._stop_event:
176
+ self._stop_event.set()
177
+
178
+ if self._flush_task and not self._flush_task.done():
179
+ self._flush_task.cancel()
180
+ try:
181
+ await self._flush_task
182
+ except asyncio.CancelledError:
183
+ pass
184
+ self._flush_task = None
185
+ await self.flush()
186
+
187
+ async def _periodic_flush(self):
188
+ try:
189
+ while not self._stop_event.is_set():
190
+ try:
191
+ await asyncio.wait_for(
192
+ self._stop_event.wait(), timeout=self.flush_interval
193
+ )
194
+ except asyncio.TimeoutError:
195
+ await self.flush()
196
+ except asyncio.CancelledError:
197
+ pass
198
+ finally:
199
+ await self.flush() # Final flush
200
+
201
+ async def handle_matched_event(self, event):
202
+ self.batch.append(event)
203
+ if len(self.batch) >= self.batch_size:
204
+ await self.flush()
205
+
206
+ async def flush(self):
207
+ """Flush the current batch of events."""
208
+ if not self.batch:
209
+ return
210
+ to_process = self.batch[:]
211
+ self.batch.clear()
212
+ self.last_flush = time.time()
213
+ await self._process_batch(to_process)
214
+
215
+ async def _process_batch(self, events: List[Event]):
216
+ pass