fast-agent-mcp 0.1.12__py3-none-any.whl → 0.1.13__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.
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.1.13.dist-info}/METADATA +1 -1
- fast_agent_mcp-0.1.13.dist-info/RECORD +164 -0
- mcp_agent/agents/agent.py +37 -79
- mcp_agent/app.py +16 -22
- mcp_agent/cli/commands/bootstrap.py +22 -52
- mcp_agent/cli/commands/config.py +4 -4
- mcp_agent/cli/commands/setup.py +11 -26
- mcp_agent/cli/main.py +6 -9
- mcp_agent/cli/terminal.py +2 -2
- mcp_agent/config.py +1 -5
- mcp_agent/context.py +13 -24
- mcp_agent/context_dependent.py +3 -7
- mcp_agent/core/agent_app.py +45 -121
- mcp_agent/core/agent_utils.py +3 -5
- mcp_agent/core/decorators.py +5 -12
- mcp_agent/core/enhanced_prompt.py +25 -52
- mcp_agent/core/exceptions.py +8 -8
- mcp_agent/core/factory.py +29 -70
- mcp_agent/core/fastagent.py +48 -88
- mcp_agent/core/mcp_content.py +8 -16
- mcp_agent/core/prompt.py +8 -15
- mcp_agent/core/proxies.py +34 -25
- mcp_agent/core/request_params.py +6 -3
- mcp_agent/core/types.py +4 -6
- mcp_agent/core/validation.py +4 -3
- mcp_agent/executor/decorator_registry.py +11 -23
- mcp_agent/executor/executor.py +8 -17
- mcp_agent/executor/task_registry.py +2 -4
- mcp_agent/executor/temporal.py +28 -74
- mcp_agent/executor/workflow.py +3 -5
- mcp_agent/executor/workflow_signal.py +17 -29
- mcp_agent/human_input/handler.py +4 -9
- mcp_agent/human_input/types.py +2 -3
- mcp_agent/logging/events.py +1 -5
- mcp_agent/logging/json_serializer.py +7 -6
- mcp_agent/logging/listeners.py +20 -23
- mcp_agent/logging/logger.py +15 -17
- mcp_agent/logging/rich_progress.py +10 -8
- mcp_agent/logging/tracing.py +4 -6
- mcp_agent/logging/transport.py +22 -22
- mcp_agent/mcp/gen_client.py +4 -12
- mcp_agent/mcp/interfaces.py +71 -86
- mcp_agent/mcp/mcp_agent_client_session.py +11 -19
- mcp_agent/mcp/mcp_agent_server.py +8 -10
- mcp_agent/mcp/mcp_aggregator.py +45 -117
- mcp_agent/mcp/mcp_connection_manager.py +16 -37
- mcp_agent/mcp/prompt_message_multipart.py +12 -18
- mcp_agent/mcp/prompt_serialization.py +13 -38
- mcp_agent/mcp/prompts/prompt_load.py +99 -0
- mcp_agent/mcp/prompts/prompt_server.py +21 -128
- mcp_agent/mcp/prompts/prompt_template.py +20 -42
- mcp_agent/mcp/resource_utils.py +8 -17
- mcp_agent/mcp/sampling.py +5 -14
- mcp_agent/mcp/stdio.py +11 -8
- mcp_agent/mcp_server/agent_server.py +10 -17
- mcp_agent/mcp_server_registry.py +13 -35
- mcp_agent/resources/examples/data-analysis/analysis-campaign.py +1 -1
- mcp_agent/resources/examples/data-analysis/analysis.py +1 -1
- mcp_agent/resources/examples/data-analysis/slides.py +110 -0
- mcp_agent/resources/examples/internal/agent.py +2 -1
- mcp_agent/resources/examples/internal/job.py +2 -1
- mcp_agent/resources/examples/internal/prompt_category.py +1 -1
- mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
- mcp_agent/resources/examples/internal/sizer.py +2 -1
- mcp_agent/resources/examples/internal/social.py +2 -1
- mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/prompting/agent.py +2 -1
- mcp_agent/resources/examples/prompting/image_server.py +5 -11
- mcp_agent/resources/examples/researcher/researcher-eval.py +1 -1
- mcp_agent/resources/examples/researcher/researcher-imp.py +3 -4
- mcp_agent/resources/examples/researcher/researcher.py +2 -1
- mcp_agent/resources/examples/workflows/agent_build.py +2 -1
- mcp_agent/resources/examples/workflows/chaining.py +2 -1
- mcp_agent/resources/examples/workflows/evaluator.py +2 -1
- mcp_agent/resources/examples/workflows/human_input.py +2 -1
- mcp_agent/resources/examples/workflows/orchestrator.py +2 -1
- mcp_agent/resources/examples/workflows/parallel.py +2 -1
- mcp_agent/resources/examples/workflows/router.py +2 -1
- mcp_agent/resources/examples/workflows/sse.py +1 -1
- mcp_agent/telemetry/usage_tracking.py +2 -1
- mcp_agent/ui/console_display.py +15 -39
- mcp_agent/workflows/embedding/embedding_base.py +1 -4
- mcp_agent/workflows/embedding/embedding_cohere.py +2 -2
- mcp_agent/workflows/embedding/embedding_openai.py +4 -13
- mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +23 -57
- mcp_agent/workflows/intent_classifier/intent_classifier_base.py +5 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +7 -11
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +4 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +4 -8
- mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +11 -22
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +3 -3
- mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +4 -6
- mcp_agent/workflows/llm/anthropic_utils.py +8 -29
- mcp_agent/workflows/llm/augmented_llm.py +69 -247
- mcp_agent/workflows/llm/augmented_llm_anthropic.py +39 -73
- mcp_agent/workflows/llm/augmented_llm_openai.py +42 -97
- mcp_agent/workflows/llm/augmented_llm_passthrough.py +13 -20
- mcp_agent/workflows/llm/augmented_llm_playback.py +8 -6
- mcp_agent/workflows/llm/memory.py +103 -0
- mcp_agent/workflows/llm/model_factory.py +8 -20
- mcp_agent/workflows/llm/openai_utils.py +1 -1
- mcp_agent/workflows/llm/prompt_utils.py +1 -3
- mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +47 -89
- mcp_agent/workflows/llm/providers/multipart_converter_openai.py +20 -55
- mcp_agent/workflows/llm/providers/openai_multipart.py +19 -61
- mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +10 -12
- mcp_agent/workflows/llm/providers/sampling_converter_openai.py +7 -11
- mcp_agent/workflows/llm/sampling_converter.py +4 -11
- mcp_agent/workflows/llm/sampling_format_converter.py +12 -12
- mcp_agent/workflows/orchestrator/orchestrator.py +24 -67
- mcp_agent/workflows/orchestrator/orchestrator_models.py +14 -40
- mcp_agent/workflows/parallel/fan_in.py +17 -47
- mcp_agent/workflows/parallel/fan_out.py +6 -12
- mcp_agent/workflows/parallel/parallel_llm.py +9 -26
- mcp_agent/workflows/router/router_base.py +19 -49
- mcp_agent/workflows/router/router_embedding.py +11 -25
- mcp_agent/workflows/router/router_embedding_cohere.py +2 -2
- mcp_agent/workflows/router/router_embedding_openai.py +2 -2
- mcp_agent/workflows/router/router_llm.py +12 -28
- mcp_agent/workflows/swarm/swarm.py +20 -48
- mcp_agent/workflows/swarm/swarm_anthropic.py +2 -2
- mcp_agent/workflows/swarm/swarm_openai.py +2 -2
- fast_agent_mcp-0.1.12.dist-info/RECORD +0 -161
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.1.13.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.1.12.dist-info → fast_agent_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
import uuid
|
3
|
-
from abc import
|
3
|
+
from abc import ABC, abstractmethod
|
4
4
|
from typing import Any, Callable, Dict, Generic, List, Protocol, TypeVar
|
5
5
|
|
6
6
|
from pydantic import BaseModel, ConfigDict
|
@@ -71,14 +71,14 @@ class PendingSignal(BaseModel):
|
|
71
71
|
class BaseSignalHandler(ABC, Generic[SignalValueT]):
|
72
72
|
"""Base class implementing common signal handling functionality."""
|
73
73
|
|
74
|
-
def __init__(self):
|
74
|
+
def __init__(self) -> None:
|
75
75
|
# Map signal_name -> list of PendingSignal objects
|
76
76
|
self._pending_signals: Dict[str, List[PendingSignal]] = {}
|
77
77
|
# Map signal_name -> list of (unique_name, handler) tuples
|
78
78
|
self._handlers: Dict[str, List[tuple[str, Callable]]] = {}
|
79
79
|
self._lock = asyncio.Lock()
|
80
80
|
|
81
|
-
async def cleanup(self, signal_name: str | None = None):
|
81
|
+
async def cleanup(self, signal_name: str | None = None) -> None:
|
82
82
|
"""Clean up handlers and registrations for a signal or all signals."""
|
83
83
|
async with self._lock:
|
84
84
|
if signal_name:
|
@@ -90,7 +90,7 @@ class BaseSignalHandler(ABC, Generic[SignalValueT]):
|
|
90
90
|
self._handlers.clear()
|
91
91
|
self._pending_signals.clear()
|
92
92
|
|
93
|
-
def validate_signal(self, signal: Signal[SignalValueT]):
|
93
|
+
def validate_signal(self, signal: Signal[SignalValueT]) -> None:
|
94
94
|
"""Validate signal properties."""
|
95
95
|
if not signal.name:
|
96
96
|
raise ValueError("Signal name is required")
|
@@ -102,7 +102,7 @@ class BaseSignalHandler(ABC, Generic[SignalValueT]):
|
|
102
102
|
def decorator(func: Callable) -> Callable:
|
103
103
|
unique_name = f"{signal_name}_{uuid.uuid4()}"
|
104
104
|
|
105
|
-
async def wrapped(value: SignalValueT):
|
105
|
+
async def wrapped(value: SignalValueT) -> None:
|
106
106
|
try:
|
107
107
|
if asyncio.iscoroutinefunction(func):
|
108
108
|
await func(value)
|
@@ -133,7 +133,7 @@ class BaseSignalHandler(ABC, Generic[SignalValueT]):
|
|
133
133
|
class ConsoleSignalHandler(SignalHandler[str]):
|
134
134
|
"""Simple console-based signal handling (blocks on input)."""
|
135
135
|
|
136
|
-
def __init__(self):
|
136
|
+
def __init__(self) -> None:
|
137
137
|
self._pending_signals: Dict[str, List[PendingSignal]] = {}
|
138
138
|
self._handlers: Dict[str, List[Callable]] = {}
|
139
139
|
|
@@ -147,9 +147,7 @@ class ConsoleSignalHandler(SignalHandler[str]):
|
|
147
147
|
loop = asyncio.get_event_loop()
|
148
148
|
if timeout_seconds is not None:
|
149
149
|
try:
|
150
|
-
value = await asyncio.wait_for(
|
151
|
-
loop.run_in_executor(None, input, "Enter value: "), timeout_seconds
|
152
|
-
)
|
150
|
+
value = await asyncio.wait_for(loop.run_in_executor(None, input, "Enter value: "), timeout_seconds)
|
153
151
|
except asyncio.TimeoutError:
|
154
152
|
print("\nTimeout waiting for input")
|
155
153
|
raise
|
@@ -163,7 +161,7 @@ class ConsoleSignalHandler(SignalHandler[str]):
|
|
163
161
|
|
164
162
|
def on_signal(self, signal_name):
|
165
163
|
def decorator(func):
|
166
|
-
async def wrapped(value: SignalValueT):
|
164
|
+
async def wrapped(value: SignalValueT) -> None:
|
167
165
|
if asyncio.iscoroutinefunction(func):
|
168
166
|
await func(value)
|
169
167
|
else:
|
@@ -174,13 +172,11 @@ class ConsoleSignalHandler(SignalHandler[str]):
|
|
174
172
|
|
175
173
|
return decorator
|
176
174
|
|
177
|
-
async def signal(self, signal):
|
175
|
+
async def signal(self, signal) -> None:
|
178
176
|
print(f"[SIGNAL SENT: {signal.name}] Value: {signal.payload}")
|
179
177
|
|
180
178
|
handlers = self._handlers.get(signal.name, [])
|
181
|
-
await asyncio.gather(
|
182
|
-
*(handler(signal) for handler in handlers), return_exceptions=True
|
183
|
-
)
|
179
|
+
await asyncio.gather(*(handler(signal) for handler in handlers), return_exceptions=True)
|
184
180
|
|
185
181
|
# Notify any waiting coroutines
|
186
182
|
if signal.name in self._pending_signals:
|
@@ -194,9 +190,7 @@ class AsyncioSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
194
190
|
Asyncio-based signal handling using an internal dictionary of asyncio Events.
|
195
191
|
"""
|
196
192
|
|
197
|
-
async def wait_for_signal(
|
198
|
-
self, signal, timeout_seconds: int | None = None
|
199
|
-
) -> SignalValueT:
|
193
|
+
async def wait_for_signal(self, signal, timeout_seconds: int | None = None) -> SignalValueT:
|
200
194
|
event = asyncio.Event()
|
201
195
|
unique_name = str(uuid.uuid4())
|
202
196
|
|
@@ -226,17 +220,13 @@ class AsyncioSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
226
220
|
async with self._lock:
|
227
221
|
# Remove from pending signals
|
228
222
|
if signal.name in self._pending_signals:
|
229
|
-
self._pending_signals[signal.name] = [
|
230
|
-
ps
|
231
|
-
for ps in self._pending_signals[signal.name]
|
232
|
-
if ps.registration.unique_name != unique_name
|
233
|
-
]
|
223
|
+
self._pending_signals[signal.name] = [ps for ps in self._pending_signals[signal.name] if ps.registration.unique_name != unique_name]
|
234
224
|
if not self._pending_signals[signal.name]:
|
235
225
|
del self._pending_signals[signal.name]
|
236
226
|
|
237
227
|
def on_signal(self, signal_name):
|
238
228
|
def decorator(func):
|
239
|
-
async def wrapped(value: SignalValueT):
|
229
|
+
async def wrapped(value: SignalValueT) -> None:
|
240
230
|
if asyncio.iscoroutinefunction(func):
|
241
231
|
await func(value)
|
242
232
|
else:
|
@@ -247,7 +237,7 @@ class AsyncioSignalHandler(BaseSignalHandler[SignalValueT]):
|
|
247
237
|
|
248
238
|
return decorator
|
249
239
|
|
250
|
-
async def signal(self, signal):
|
240
|
+
async def signal(self, signal) -> None:
|
251
241
|
async with self._lock:
|
252
242
|
# Notify any waiting coroutines
|
253
243
|
if signal.name in self._pending_signals:
|
@@ -272,11 +262,11 @@ class LocalSignalStore:
|
|
272
262
|
and triggers them when a signal is emitted.
|
273
263
|
"""
|
274
264
|
|
275
|
-
def __init__(self):
|
265
|
+
def __init__(self) -> None:
|
276
266
|
# For each signal_name, store a list of futures that are waiting for it
|
277
267
|
self._waiters: Dict[str, List[asyncio.Future]] = {}
|
278
268
|
|
279
|
-
async def emit(self, signal_name: str, payload: Any):
|
269
|
+
async def emit(self, signal_name: str, payload: Any) -> None:
|
280
270
|
# If we have waiting futures, set their result
|
281
271
|
if signal_name in self._waiters:
|
282
272
|
for future in self._waiters[signal_name]:
|
@@ -284,9 +274,7 @@ class LocalSignalStore:
|
|
284
274
|
future.set_result(payload)
|
285
275
|
self._waiters[signal_name].clear()
|
286
276
|
|
287
|
-
async def wait_for(
|
288
|
-
self, signal_name: str, timeout_seconds: int | None = None
|
289
|
-
) -> Any:
|
277
|
+
async def wait_for(self, signal_name: str, timeout_seconds: int | None = None) -> Any:
|
290
278
|
loop = asyncio.get_running_loop()
|
291
279
|
future = loop.create_future()
|
292
280
|
|
mcp_agent/human_input/handler.py
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
import asyncio
|
2
|
+
|
2
3
|
from rich.panel import Panel
|
3
4
|
|
4
5
|
from mcp_agent.console import console
|
6
|
+
from mcp_agent.core.enhanced_prompt import get_enhanced_input, handle_special_commands
|
5
7
|
from mcp_agent.human_input.types import (
|
6
8
|
HumanInputRequest,
|
7
9
|
HumanInputResponse,
|
8
10
|
)
|
9
11
|
from mcp_agent.progress_display import progress_display
|
10
|
-
from mcp_agent.core.enhanced_prompt import get_enhanced_input, handle_special_commands
|
11
12
|
|
12
13
|
|
13
14
|
async def console_input_callback(request: HumanInputRequest) -> HumanInputResponse:
|
@@ -29,11 +30,7 @@ async def console_input_callback(request: HumanInputRequest) -> HumanInputRespon
|
|
29
30
|
)
|
30
31
|
|
31
32
|
# Extract agent name from metadata dictionary
|
32
|
-
agent_name = (
|
33
|
-
request.metadata.get("agent_name", "Unknown Agent")
|
34
|
-
if request.metadata
|
35
|
-
else "Unknown Agent"
|
36
|
-
)
|
33
|
+
agent_name = request.metadata.get("agent_name", "Unknown Agent") if request.metadata else "Unknown Agent"
|
37
34
|
|
38
35
|
# Use the context manager to pause the progress display while getting input
|
39
36
|
with progress_display.paused():
|
@@ -70,9 +67,7 @@ async def console_input_callback(request: HumanInputRequest) -> HumanInputRespon
|
|
70
67
|
if isinstance(command_result, dict) and "list_prompts" in command_result:
|
71
68
|
from rich import print as rich_print
|
72
69
|
|
73
|
-
rich_print(
|
74
|
-
"[yellow]Prompt listing not available in human input context[/yellow]"
|
75
|
-
)
|
70
|
+
rich_print("[yellow]Prompt listing not available in human input context[/yellow]")
|
76
71
|
|
77
72
|
except KeyboardInterrupt:
|
78
73
|
console.print("\n[yellow]Input interrupted[/yellow]")
|
mcp_agent/human_input/types.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from typing import Any, AsyncIterator, Protocol
|
2
|
+
|
2
3
|
from pydantic import BaseModel
|
3
4
|
|
4
5
|
HUMAN_INPUT_SIGNAL_NAME = "__human_input__"
|
@@ -42,9 +43,7 @@ class HumanInputResponse(BaseModel):
|
|
42
43
|
class HumanInputCallback(Protocol):
|
43
44
|
"""Protocol for callbacks that handle human input requests."""
|
44
45
|
|
45
|
-
async def __call__(
|
46
|
-
self, request: HumanInputRequest
|
47
|
-
) -> AsyncIterator[HumanInputResponse]:
|
46
|
+
async def __call__(self, request: HumanInputRequest) -> AsyncIterator[HumanInputResponse]:
|
48
47
|
"""
|
49
48
|
Handle a human input request.
|
50
49
|
|
mcp_agent/logging/events.py
CHANGED
@@ -4,7 +4,6 @@ Events and event filters for the logger module for the MCP Agent
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
import random
|
7
|
-
|
8
7
|
from datetime import datetime
|
9
8
|
from typing import (
|
10
9
|
Any,
|
@@ -15,7 +14,6 @@ from typing import (
|
|
15
14
|
|
16
15
|
from pydantic import BaseModel, ConfigDict, Field
|
17
16
|
|
18
|
-
|
19
17
|
EventType = Literal["debug", "info", "warning", "error", "progress"]
|
20
18
|
"""Broad categories for events (severity or role)."""
|
21
19
|
|
@@ -86,9 +84,7 @@ class EventFilter(BaseModel):
|
|
86
84
|
return False
|
87
85
|
|
88
86
|
# 3) Filter by namespace prefix
|
89
|
-
if self.namespaces and not any(
|
90
|
-
event.namespace.startswith(ns) for ns in self.namespaces
|
91
|
-
):
|
87
|
+
if self.namespaces and not any(event.namespace.startswith(ns) for ns in self.namespaces):
|
92
88
|
return False
|
93
89
|
|
94
90
|
# 4) Minimum severity
|
@@ -1,13 +1,14 @@
|
|
1
|
+
import dataclasses
|
2
|
+
import inspect
|
1
3
|
import os
|
2
4
|
import warnings
|
3
|
-
from
|
4
|
-
from datetime import datetime, date
|
5
|
+
from datetime import date, datetime
|
5
6
|
from decimal import Decimal
|
7
|
+
from enum import Enum
|
6
8
|
from pathlib import Path
|
9
|
+
from typing import Any, Dict, Iterable, Set
|
7
10
|
from uuid import UUID
|
8
|
-
|
9
|
-
import dataclasses
|
10
|
-
import inspect
|
11
|
+
|
11
12
|
import httpx
|
12
13
|
|
13
14
|
from mcp_agent.logging import logger
|
@@ -34,7 +35,7 @@ class JSONSerializer:
|
|
34
35
|
"refresh_token",
|
35
36
|
}
|
36
37
|
|
37
|
-
def __init__(self):
|
38
|
+
def __init__(self) -> None:
|
38
39
|
# Set of already processed objects to prevent infinite recursion
|
39
40
|
self._processed_objects: Set[int] = set()
|
40
41
|
# Check if secrets should be logged in full
|
mcp_agent/logging/listeners.py
CHANGED
@@ -5,12 +5,11 @@ Listeners for the logger module of MCP Agent.
|
|
5
5
|
import asyncio
|
6
6
|
import logging
|
7
7
|
import time
|
8
|
-
|
9
8
|
from abc import ABC, abstractmethod
|
10
9
|
from typing import Dict, List
|
11
10
|
|
12
|
-
from mcp_agent.logging.events import Event, EventFilter, EventType
|
13
11
|
from mcp_agent.event_progress import convert_log_event
|
12
|
+
from mcp_agent.logging.events import Event, EventFilter, EventType
|
14
13
|
|
15
14
|
|
16
15
|
class EventListener(ABC):
|
@@ -27,11 +26,11 @@ class LifecycleAwareListener(EventListener):
|
|
27
26
|
The event bus calls these at bus start/stop time.
|
28
27
|
"""
|
29
28
|
|
30
|
-
async def start(self):
|
29
|
+
async def start(self) -> None:
|
31
30
|
"""Start an event listener, usually when the event bus is set up."""
|
32
31
|
pass
|
33
32
|
|
34
|
-
async def stop(self):
|
33
|
+
async def stop(self) -> None:
|
35
34
|
"""Stop an event listener, usually when the event bus is shutting down."""
|
36
35
|
pass
|
37
36
|
|
@@ -42,7 +41,7 @@ class FilteredListener(LifecycleAwareListener):
|
|
42
41
|
Subclasses override _handle_matched_event().
|
43
42
|
"""
|
44
43
|
|
45
|
-
def __init__(self, event_filter: EventFilter | None = None):
|
44
|
+
def __init__(self, event_filter: EventFilter | None = None) -> None:
|
46
45
|
"""
|
47
46
|
Initialize the listener.
|
48
47
|
Args:
|
@@ -50,11 +49,11 @@ class FilteredListener(LifecycleAwareListener):
|
|
50
49
|
"""
|
51
50
|
self.filter = event_filter
|
52
51
|
|
53
|
-
async def handle_event(self, event):
|
52
|
+
async def handle_event(self, event) -> None:
|
54
53
|
if not self.filter or self.filter.matches(event):
|
55
54
|
await self.handle_matched_event(event)
|
56
55
|
|
57
|
-
async def handle_matched_event(self, event: Event):
|
56
|
+
async def handle_matched_event(self, event: Event) -> None:
|
58
57
|
"""Process an event that matches the filter."""
|
59
58
|
pass
|
60
59
|
|
@@ -68,7 +67,7 @@ class LoggingListener(FilteredListener):
|
|
68
67
|
self,
|
69
68
|
event_filter: EventFilter | None = None,
|
70
69
|
logger: logging.Logger | None = None,
|
71
|
-
):
|
70
|
+
) -> None:
|
72
71
|
"""
|
73
72
|
Initialize the listener.
|
74
73
|
Args:
|
@@ -77,7 +76,7 @@ class LoggingListener(FilteredListener):
|
|
77
76
|
super().__init__(event_filter=event_filter)
|
78
77
|
self.logger = logger or logging.getLogger("mcp_agent")
|
79
78
|
|
80
|
-
async def handle_matched_event(self, event):
|
79
|
+
async def handle_matched_event(self, event) -> None:
|
81
80
|
level_map: Dict[EventType, int] = {
|
82
81
|
"debug": logging.DEBUG,
|
83
82
|
"info": logging.INFO,
|
@@ -113,7 +112,7 @@ class ProgressListener(LifecycleAwareListener):
|
|
113
112
|
FilteredListener, we get events before any filtering occurs.
|
114
113
|
"""
|
115
114
|
|
116
|
-
def __init__(self, display=None):
|
115
|
+
def __init__(self, display=None) -> None:
|
117
116
|
"""Initialize the progress listener.
|
118
117
|
Args:
|
119
118
|
display: Optional display handler. If None, the shared progress_display will be used.
|
@@ -122,15 +121,15 @@ class ProgressListener(LifecycleAwareListener):
|
|
122
121
|
|
123
122
|
self.display = display or progress_display
|
124
123
|
|
125
|
-
async def start(self):
|
124
|
+
async def start(self) -> None:
|
126
125
|
"""Start the progress display."""
|
127
126
|
self.display.start()
|
128
127
|
|
129
|
-
async def stop(self):
|
128
|
+
async def stop(self) -> None:
|
130
129
|
"""Stop the progress display."""
|
131
130
|
self.display.stop()
|
132
131
|
|
133
|
-
async def handle_event(self, event: Event):
|
132
|
+
async def handle_event(self, event: Event) -> None:
|
134
133
|
"""Process an incoming event and display progress if relevant."""
|
135
134
|
|
136
135
|
if event.data:
|
@@ -150,7 +149,7 @@ class BatchingListener(FilteredListener):
|
|
150
149
|
event_filter: EventFilter | None = None,
|
151
150
|
batch_size: int = 5,
|
152
151
|
flush_interval: float = 2.0,
|
153
|
-
):
|
152
|
+
) -> None:
|
154
153
|
"""
|
155
154
|
Initialize the listener.
|
156
155
|
Args:
|
@@ -165,12 +164,12 @@ class BatchingListener(FilteredListener):
|
|
165
164
|
self._flush_task: asyncio.Task | None = None # Task for periodic flush loop
|
166
165
|
self._stop_event = None # Event to signal flush task to stop
|
167
166
|
|
168
|
-
async def start(self, loop=None):
|
167
|
+
async def start(self, loop=None) -> None:
|
169
168
|
"""Spawn a periodic flush loop."""
|
170
169
|
self._stop_event = asyncio.Event()
|
171
170
|
self._flush_task = asyncio.create_task(self._periodic_flush())
|
172
171
|
|
173
|
-
async def stop(self):
|
172
|
+
async def stop(self) -> None:
|
174
173
|
"""Stop flush loop and flush any remaining events."""
|
175
174
|
if self._stop_event:
|
176
175
|
self._stop_event.set()
|
@@ -181,13 +180,11 @@ class BatchingListener(FilteredListener):
|
|
181
180
|
self._flush_task = None
|
182
181
|
await self.flush()
|
183
182
|
|
184
|
-
async def _periodic_flush(self):
|
183
|
+
async def _periodic_flush(self) -> None:
|
185
184
|
try:
|
186
185
|
while not self._stop_event.is_set():
|
187
186
|
try:
|
188
|
-
await asyncio.wait_for(
|
189
|
-
self._stop_event.wait(), timeout=self.flush_interval
|
190
|
-
)
|
187
|
+
await asyncio.wait_for(self._stop_event.wait(), timeout=self.flush_interval)
|
191
188
|
except asyncio.TimeoutError:
|
192
189
|
await self.flush()
|
193
190
|
# except asyncio.CancelledError:
|
@@ -195,12 +192,12 @@ class BatchingListener(FilteredListener):
|
|
195
192
|
finally:
|
196
193
|
await self.flush() # Final flush
|
197
194
|
|
198
|
-
async def handle_matched_event(self, event):
|
195
|
+
async def handle_matched_event(self, event) -> None:
|
199
196
|
self.batch.append(event)
|
200
197
|
if len(self.batch) >= self.batch_size:
|
201
198
|
await self.flush()
|
202
199
|
|
203
|
-
async def flush(self):
|
200
|
+
async def flush(self) -> None:
|
204
201
|
"""Flush the current batch of events."""
|
205
202
|
if not self.batch:
|
206
203
|
return
|
@@ -209,5 +206,5 @@ class BatchingListener(FilteredListener):
|
|
209
206
|
self.last_flush = time.time()
|
210
207
|
await self._process_batch(to_process)
|
211
208
|
|
212
|
-
async def _process_batch(self, events: List[Event]):
|
209
|
+
async def _process_batch(self, events: List[Event]) -> None:
|
213
210
|
pass
|
mcp_agent/logging/logger.py
CHANGED
@@ -10,10 +10,8 @@ Logger module for the MCP Agent, which provides:
|
|
10
10
|
import asyncio
|
11
11
|
import threading
|
12
12
|
import time
|
13
|
-
|
14
|
-
from typing import Any, Dict
|
15
|
-
|
16
13
|
from contextlib import asynccontextmanager, contextmanager
|
14
|
+
from typing import Any, Dict
|
17
15
|
|
18
16
|
from mcp_agent.logging.events import Event, EventContext, EventFilter, EventType
|
19
17
|
from mcp_agent.logging.listeners import (
|
@@ -31,7 +29,7 @@ class Logger:
|
|
31
29
|
- `name` can be a custom domain-specific event name, e.g. "ORDER_PLACED".
|
32
30
|
"""
|
33
31
|
|
34
|
-
def __init__(self, namespace: str):
|
32
|
+
def __init__(self, namespace: str) -> None:
|
35
33
|
self.namespace = namespace
|
36
34
|
self.event_bus = AsyncEventBus.get()
|
37
35
|
|
@@ -45,7 +43,7 @@ class Logger:
|
|
45
43
|
asyncio.set_event_loop(loop)
|
46
44
|
return loop
|
47
45
|
|
48
|
-
def _emit_event(self, event: Event):
|
46
|
+
def _emit_event(self, event: Event) -> None:
|
49
47
|
"""Emit an event by running it in the event loop."""
|
50
48
|
loop = self._ensure_event_loop()
|
51
49
|
if loop.is_running():
|
@@ -62,7 +60,7 @@ class Logger:
|
|
62
60
|
message: str,
|
63
61
|
context: EventContext | None,
|
64
62
|
data: dict,
|
65
|
-
):
|
63
|
+
) -> None:
|
66
64
|
"""Create and emit an event."""
|
67
65
|
evt = Event(
|
68
66
|
type=etype,
|
@@ -78,9 +76,9 @@ class Logger:
|
|
78
76
|
self,
|
79
77
|
message: str,
|
80
78
|
name: str | None = None,
|
81
|
-
context: EventContext = None,
|
79
|
+
context: EventContext | None = None,
|
82
80
|
**data,
|
83
|
-
):
|
81
|
+
) -> None:
|
84
82
|
"""Log a debug message."""
|
85
83
|
self.event("debug", name, message, context, data)
|
86
84
|
|
@@ -88,9 +86,9 @@ class Logger:
|
|
88
86
|
self,
|
89
87
|
message: str,
|
90
88
|
name: str | None = None,
|
91
|
-
context: EventContext = None,
|
89
|
+
context: EventContext | None = None,
|
92
90
|
**data,
|
93
|
-
):
|
91
|
+
) -> None:
|
94
92
|
"""Log an info message."""
|
95
93
|
self.event("info", name, message, context, data)
|
96
94
|
|
@@ -98,9 +96,9 @@ class Logger:
|
|
98
96
|
self,
|
99
97
|
message: str,
|
100
98
|
name: str | None = None,
|
101
|
-
context: EventContext = None,
|
99
|
+
context: EventContext | None = None,
|
102
100
|
**data,
|
103
|
-
):
|
101
|
+
) -> None:
|
104
102
|
"""Log a warning message."""
|
105
103
|
self.event("warning", name, message, context, data)
|
106
104
|
|
@@ -108,9 +106,9 @@ class Logger:
|
|
108
106
|
self,
|
109
107
|
message: str,
|
110
108
|
name: str | None = None,
|
111
|
-
context: EventContext = None,
|
109
|
+
context: EventContext | None = None,
|
112
110
|
**data,
|
113
|
-
):
|
111
|
+
) -> None:
|
114
112
|
"""Log an error message."""
|
115
113
|
self.event("error", name, message, context, data)
|
116
114
|
|
@@ -118,10 +116,10 @@ class Logger:
|
|
118
116
|
self,
|
119
117
|
message: str,
|
120
118
|
name: str | None = None,
|
121
|
-
percentage: float = None,
|
122
|
-
context: EventContext = None,
|
119
|
+
percentage: float | None = None,
|
120
|
+
context: EventContext | None = None,
|
123
121
|
**data,
|
124
|
-
):
|
122
|
+
) -> None:
|
125
123
|
"""Log a progress message."""
|
126
124
|
merged_data = dict(percentage=percentage, **data)
|
127
125
|
self.event("progress", name, message, context, merged_data)
|
@@ -1,18 +1,20 @@
|
|
1
1
|
"""Rich-based progress display for MCP Agent."""
|
2
2
|
|
3
3
|
import time
|
4
|
+
from contextlib import contextmanager
|
4
5
|
from typing import Optional
|
6
|
+
|
5
7
|
from rich.console import Console
|
6
|
-
from mcp_agent.console import console as default_console
|
7
|
-
from mcp_agent.event_progress import ProgressEvent, ProgressAction
|
8
8
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
9
|
-
|
9
|
+
|
10
|
+
from mcp_agent.console import console as default_console
|
11
|
+
from mcp_agent.event_progress import ProgressAction, ProgressEvent
|
10
12
|
|
11
13
|
|
12
14
|
class RichProgressDisplay:
|
13
15
|
"""Rich-based display for progress events."""
|
14
16
|
|
15
|
-
def __init__(self, console: Optional[Console] = None):
|
17
|
+
def __init__(self, console: Optional[Console] = None) -> None:
|
16
18
|
"""Initialize the progress display."""
|
17
19
|
self.console = console or default_console
|
18
20
|
self._taskmap = {}
|
@@ -29,16 +31,16 @@ class RichProgressDisplay:
|
|
29
31
|
)
|
30
32
|
self._paused = False
|
31
33
|
|
32
|
-
def start(self):
|
34
|
+
def start(self) -> None:
|
33
35
|
"""start"""
|
34
36
|
|
35
37
|
self._progress.start()
|
36
38
|
|
37
|
-
def stop(self):
|
39
|
+
def stop(self) -> None:
|
38
40
|
"""stop"""
|
39
41
|
self._progress.stop()
|
40
42
|
|
41
|
-
def pause(self):
|
43
|
+
def pause(self) -> None:
|
42
44
|
"""Pause the progress display."""
|
43
45
|
if not self._paused:
|
44
46
|
self._paused = True
|
@@ -47,7 +49,7 @@ class RichProgressDisplay:
|
|
47
49
|
task.visible = False
|
48
50
|
self._progress.stop()
|
49
51
|
|
50
|
-
def resume(self):
|
52
|
+
def resume(self) -> None:
|
51
53
|
"""Resume the progress display."""
|
52
54
|
if self._paused:
|
53
55
|
for task in self._progress.tasks:
|
mcp_agent/logging/tracing.py
CHANGED
@@ -5,16 +5,14 @@ for the Logger module for MCP Agent
|
|
5
5
|
|
6
6
|
import asyncio
|
7
7
|
import functools
|
8
|
-
from typing import
|
8
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple
|
9
9
|
|
10
10
|
from opentelemetry import trace
|
11
11
|
from opentelemetry.context import Context as OtelContext
|
12
12
|
from opentelemetry.propagate import extract as otel_extract
|
13
|
-
from opentelemetry.trace import set_span_in_context
|
13
|
+
from opentelemetry.trace import SpanKind, Status, StatusCode, set_span_in_context
|
14
14
|
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
15
15
|
|
16
|
-
from opentelemetry.trace import SpanKind, Status, StatusCode
|
17
|
-
|
18
16
|
from mcp_agent.context_dependent import ContextDependent
|
19
17
|
|
20
18
|
if TYPE_CHECKING:
|
@@ -27,7 +25,7 @@ class TelemetryManager(ContextDependent):
|
|
27
25
|
Decorator usage: @telemetry.traced("SomeSpanName")
|
28
26
|
"""
|
29
27
|
|
30
|
-
def __init__(self, context: Optional["Context"] = None, **kwargs):
|
28
|
+
def __init__(self, context: Optional["Context"] = None, **kwargs) -> None:
|
31
29
|
# If needed, configure resources, exporters, etc.
|
32
30
|
# E.g.: from opentelemetry.sdk.trace import TracerProvider
|
33
31
|
# trace.set_tracer_provider(TracerProvider(...))
|
@@ -88,7 +86,7 @@ class TelemetryManager(ContextDependent):
|
|
88
86
|
|
89
87
|
return decorator
|
90
88
|
|
91
|
-
def _record_args(self, span, args, kwargs):
|
89
|
+
def _record_args(self, span, args, kwargs) -> None:
|
92
90
|
"""Optionally record primitive args as span attributes."""
|
93
91
|
for i, arg in enumerate(args):
|
94
92
|
if isinstance(arg, (str, int, float, bool)):
|