fast-agent-mcp 0.2.40__py3-none-any.whl → 0.2.42__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.
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/METADATA +2 -1
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/RECORD +45 -40
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/entry_points.txt +2 -2
- mcp_agent/agents/base_agent.py +111 -1
- mcp_agent/cli/__main__.py +29 -3
- mcp_agent/cli/commands/check_config.py +140 -81
- mcp_agent/cli/commands/go.py +151 -38
- mcp_agent/cli/commands/quickstart.py +6 -2
- mcp_agent/cli/commands/server_helpers.py +106 -0
- mcp_agent/cli/constants.py +25 -0
- mcp_agent/cli/main.py +1 -1
- mcp_agent/config.py +111 -44
- mcp_agent/core/agent_app.py +104 -15
- mcp_agent/core/agent_types.py +5 -1
- mcp_agent/core/direct_decorators.py +38 -0
- mcp_agent/core/direct_factory.py +18 -4
- mcp_agent/core/enhanced_prompt.py +173 -13
- mcp_agent/core/fastagent.py +4 -0
- mcp_agent/core/interactive_prompt.py +37 -37
- mcp_agent/core/usage_display.py +11 -1
- mcp_agent/core/validation.py +21 -2
- mcp_agent/human_input/elicitation_form.py +53 -21
- mcp_agent/llm/augmented_llm.py +28 -9
- mcp_agent/llm/augmented_llm_silent.py +48 -0
- mcp_agent/llm/model_database.py +20 -0
- mcp_agent/llm/model_factory.py +21 -0
- mcp_agent/llm/provider_key_manager.py +22 -8
- mcp_agent/llm/provider_types.py +20 -12
- mcp_agent/llm/providers/augmented_llm_anthropic.py +7 -2
- mcp_agent/llm/providers/augmented_llm_azure.py +7 -1
- mcp_agent/llm/providers/augmented_llm_bedrock.py +1787 -0
- mcp_agent/llm/providers/augmented_llm_google_native.py +4 -1
- mcp_agent/llm/providers/augmented_llm_openai.py +12 -3
- mcp_agent/llm/providers/augmented_llm_xai.py +38 -0
- mcp_agent/llm/usage_tracking.py +28 -3
- mcp_agent/logging/logger.py +7 -0
- mcp_agent/mcp/hf_auth.py +32 -4
- mcp_agent/mcp/mcp_agent_client_session.py +2 -0
- mcp_agent/mcp/mcp_aggregator.py +38 -44
- mcp_agent/mcp/sampling.py +15 -11
- mcp_agent/resources/examples/mcp/elicitations/forms_demo.py +0 -6
- mcp_agent/resources/examples/workflows/router.py +9 -0
- mcp_agent/ui/console_display.py +125 -13
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.40.dist-info → fast_agent_mcp-0.2.42.dist-info}/licenses/LICENSE +0 -0
mcp_agent/core/usage_display.py
CHANGED
|
@@ -36,6 +36,7 @@ def display_usage_report(
|
|
|
36
36
|
total_input = 0
|
|
37
37
|
total_output = 0
|
|
38
38
|
total_tokens = 0
|
|
39
|
+
total_tool_calls = 0
|
|
39
40
|
|
|
40
41
|
for agent_name, agent in agents.items():
|
|
41
42
|
if agent.usage_accumulator:
|
|
@@ -45,6 +46,7 @@ def display_usage_report(
|
|
|
45
46
|
output_tokens = summary["cumulative_output_tokens"]
|
|
46
47
|
billing_tokens = summary["cumulative_billing_tokens"]
|
|
47
48
|
turns = summary["turn_count"]
|
|
49
|
+
tool_calls = summary["cumulative_tool_calls"]
|
|
48
50
|
|
|
49
51
|
# Get context percentage for this agent
|
|
50
52
|
context_percentage = agent.usage_accumulator.context_usage_percentage
|
|
@@ -72,6 +74,7 @@ def display_usage_report(
|
|
|
72
74
|
"output": output_tokens,
|
|
73
75
|
"total": billing_tokens,
|
|
74
76
|
"turns": turns,
|
|
77
|
+
"tool_calls": tool_calls,
|
|
75
78
|
"context": context_percentage,
|
|
76
79
|
}
|
|
77
80
|
)
|
|
@@ -79,6 +82,7 @@ def display_usage_report(
|
|
|
79
82
|
total_input += input_tokens
|
|
80
83
|
total_output += output_tokens
|
|
81
84
|
total_tokens += billing_tokens
|
|
85
|
+
total_tool_calls += tool_calls
|
|
82
86
|
|
|
83
87
|
if not usage_data:
|
|
84
88
|
return
|
|
@@ -94,7 +98,7 @@ def display_usage_report(
|
|
|
94
98
|
|
|
95
99
|
# Print header with proper spacing
|
|
96
100
|
console.print(
|
|
97
|
-
f"[dim]{'Agent':<{agent_width}} {'Input':>9} {'Output':>9} {'Total':>9} {'Turns':>6} {'Context%':>9} {'Model':<25}[/dim]"
|
|
101
|
+
f"[dim]{'Agent':<{agent_width}} {'Input':>9} {'Output':>9} {'Total':>9} {'Turns':>6} {'Tools':>6} {'Context%':>9} {'Model':<25}[/dim]"
|
|
98
102
|
)
|
|
99
103
|
|
|
100
104
|
# Print agent rows - use styling based on subdued_colors flag
|
|
@@ -103,6 +107,7 @@ def display_usage_report(
|
|
|
103
107
|
output_str = f"{data['output']:,}"
|
|
104
108
|
total_str = f"{data['total']:,}"
|
|
105
109
|
turns_str = str(data["turns"])
|
|
110
|
+
tools_str = str(data["tool_calls"])
|
|
106
111
|
context_str = f"{data['context']:.1f}%" if data["context"] is not None else "-"
|
|
107
112
|
|
|
108
113
|
# Truncate agent name if needed
|
|
@@ -118,6 +123,7 @@ def display_usage_report(
|
|
|
118
123
|
f"{output_str:>9} "
|
|
119
124
|
f"[bold]{total_str:>9}[/bold] "
|
|
120
125
|
f"{turns_str:>6} "
|
|
126
|
+
f"{tools_str:>6} "
|
|
121
127
|
f"{context_str:>9} "
|
|
122
128
|
f"{data['model']:<25}[/dim]"
|
|
123
129
|
)
|
|
@@ -129,6 +135,7 @@ def display_usage_report(
|
|
|
129
135
|
f"{output_str:>9} "
|
|
130
136
|
f"[bold]{total_str:>9}[/bold] "
|
|
131
137
|
f"{turns_str:>6} "
|
|
138
|
+
f"{tools_str:>6} "
|
|
132
139
|
f"{context_str:>9} "
|
|
133
140
|
f"[dim]{data['model']:<25}[/dim]"
|
|
134
141
|
)
|
|
@@ -139,6 +146,7 @@ def display_usage_report(
|
|
|
139
146
|
total_input_str = f"{total_input:,}"
|
|
140
147
|
total_output_str = f"{total_output:,}"
|
|
141
148
|
total_tokens_str = f"{total_tokens:,}"
|
|
149
|
+
total_tools_str = str(total_tool_calls)
|
|
142
150
|
|
|
143
151
|
if subdued_colors:
|
|
144
152
|
# Original fastagent.py style with dim wrapper on bold
|
|
@@ -148,6 +156,7 @@ def display_usage_report(
|
|
|
148
156
|
f"{total_output_str:>9} "
|
|
149
157
|
f"[bold]{total_tokens_str:>9}[/bold] "
|
|
150
158
|
f"{'':<6} "
|
|
159
|
+
f"{total_tools_str:>6} "
|
|
151
160
|
f"{'':<9} "
|
|
152
161
|
f"{'':<25}[/bold dim]"
|
|
153
162
|
)
|
|
@@ -159,6 +168,7 @@ def display_usage_report(
|
|
|
159
168
|
f"[bold]{total_output_str:>9}[/bold] "
|
|
160
169
|
f"[bold]{total_tokens_str:>9}[/bold] "
|
|
161
170
|
f"{'':<6} "
|
|
171
|
+
f"[bold]{total_tools_str:>6}[/bold] "
|
|
162
172
|
f"{'':<9} "
|
|
163
173
|
f"{'':<25}"
|
|
164
174
|
)
|
mcp_agent/core/validation.py
CHANGED
|
@@ -52,7 +52,7 @@ def validate_workflow_references(agents: Dict[str, Dict[str, Any]]) -> None:
|
|
|
52
52
|
|
|
53
53
|
for name, agent_data in agents.items():
|
|
54
54
|
agent_type = agent_data["type"] # This is a string from config
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
# Note: Compare string values from config with the Enum's string value
|
|
57
57
|
if agent_type == AgentType.PARALLEL.value:
|
|
58
58
|
# Check fan_in exists
|
|
@@ -226,7 +226,7 @@ def get_dependencies_groups(
|
|
|
226
226
|
# Build the dependency graph
|
|
227
227
|
for name, agent_data in agents_dict.items():
|
|
228
228
|
agent_type = agent_data["type"] # This is a string from config
|
|
229
|
-
|
|
229
|
+
|
|
230
230
|
# Note: Compare string values from config with the Enum's string value
|
|
231
231
|
if agent_type == AgentType.PARALLEL.value:
|
|
232
232
|
# Parallel agents depend on their fan-out and fan-in agents
|
|
@@ -305,3 +305,22 @@ def get_dependencies_groups(
|
|
|
305
305
|
remaining -= current_level
|
|
306
306
|
|
|
307
307
|
return result
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def validate_provider_keys_post_creation(active_agents: Dict[str, Any]) -> None:
|
|
311
|
+
"""
|
|
312
|
+
Validate that API keys are available for all created agents with LLMs.
|
|
313
|
+
|
|
314
|
+
This runs after agent creation when we have actual agent instances.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
active_agents: Dictionary of created agent instances
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
ProviderKeyError: If any required API key is missing
|
|
321
|
+
"""
|
|
322
|
+
for agent_name, agent in active_agents.items():
|
|
323
|
+
llm = getattr(agent, "_llm", None)
|
|
324
|
+
if llm:
|
|
325
|
+
# This throws a ProviderKeyError if the key is not present
|
|
326
|
+
llm._api_key()
|
|
@@ -16,7 +16,6 @@ from prompt_toolkit.validation import ValidationError, Validator
|
|
|
16
16
|
from prompt_toolkit.widgets import (
|
|
17
17
|
Button,
|
|
18
18
|
Checkbox,
|
|
19
|
-
Dialog,
|
|
20
19
|
Frame,
|
|
21
20
|
Label,
|
|
22
21
|
RadioList,
|
|
@@ -187,13 +186,31 @@ class ElicitationForm:
|
|
|
187
186
|
height=len(self.message.split("\n")),
|
|
188
187
|
)
|
|
189
188
|
|
|
190
|
-
# Create
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
189
|
+
# Create sticky headers (outside scrollable area)
|
|
190
|
+
sticky_headers = HSplit(
|
|
191
|
+
[
|
|
192
|
+
Window(height=1), # Top padding
|
|
193
|
+
VSplit(
|
|
194
|
+
[
|
|
195
|
+
Window(width=2), # Left padding
|
|
196
|
+
fastagent_header, # Fast-agent info
|
|
197
|
+
Window(width=2), # Right padding
|
|
198
|
+
]
|
|
199
|
+
),
|
|
200
|
+
Window(height=1), # Spacing
|
|
201
|
+
VSplit(
|
|
202
|
+
[
|
|
203
|
+
Window(width=2), # Left padding
|
|
204
|
+
mcp_header, # MCP server message
|
|
205
|
+
Window(width=2), # Right padding
|
|
206
|
+
]
|
|
207
|
+
),
|
|
208
|
+
Window(height=1), # Spacing
|
|
209
|
+
]
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Create scrollable form fields (without headers)
|
|
213
|
+
form_fields = []
|
|
197
214
|
|
|
198
215
|
for field_name, field_def in self.properties.items():
|
|
199
216
|
field_widget = self._create_field(field_name, field_def)
|
|
@@ -228,18 +245,17 @@ class ElicitationForm:
|
|
|
228
245
|
]
|
|
229
246
|
)
|
|
230
247
|
|
|
231
|
-
# Main
|
|
248
|
+
# Main scrollable content (form fields and buttons only)
|
|
232
249
|
form_fields.extend([self.status_line, buttons])
|
|
233
|
-
|
|
250
|
+
scrollable_form_content = HSplit(form_fields)
|
|
234
251
|
|
|
235
|
-
# Add padding around content
|
|
236
|
-
|
|
252
|
+
# Add padding around scrollable content
|
|
253
|
+
padded_scrollable_content = HSplit(
|
|
237
254
|
[
|
|
238
|
-
Window(height=1), # Top padding
|
|
239
255
|
VSplit(
|
|
240
256
|
[
|
|
241
257
|
Window(width=2), # Left padding
|
|
242
|
-
|
|
258
|
+
scrollable_form_content,
|
|
243
259
|
Window(width=2), # Right padding
|
|
244
260
|
]
|
|
245
261
|
),
|
|
@@ -247,20 +263,36 @@ class ElicitationForm:
|
|
|
247
263
|
]
|
|
248
264
|
)
|
|
249
265
|
|
|
250
|
-
# Wrap
|
|
266
|
+
# Wrap only form fields in ScrollablePane (headers stay fixed)
|
|
251
267
|
scrollable_content = ScrollablePane(
|
|
252
|
-
content=
|
|
268
|
+
content=padded_scrollable_content,
|
|
253
269
|
show_scrollbar=False, # Only show when content exceeds available space
|
|
254
270
|
display_arrows=False, # Only show when content exceeds available space
|
|
255
271
|
keep_cursor_visible=True,
|
|
256
272
|
keep_focused_window_visible=True,
|
|
257
273
|
)
|
|
258
274
|
|
|
259
|
-
#
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
275
|
+
# Create title bar manually
|
|
276
|
+
title_bar = Window(
|
|
277
|
+
FormattedTextControl(FormattedText([("class:title", "Elicitation Request")])),
|
|
278
|
+
height=1,
|
|
279
|
+
style="class:dialog.title",
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Combine title, sticky headers, and scrollable content
|
|
283
|
+
full_content = HSplit(
|
|
284
|
+
[
|
|
285
|
+
title_bar,
|
|
286
|
+
Window(height=1), # Spacing after title
|
|
287
|
+
sticky_headers, # Headers stay fixed at top
|
|
288
|
+
scrollable_content, # Form fields can scroll
|
|
289
|
+
]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Create dialog frame manually to avoid Dialog's internal scrolling
|
|
293
|
+
dialog = Frame(
|
|
294
|
+
body=full_content,
|
|
295
|
+
style="class:dialog",
|
|
264
296
|
)
|
|
265
297
|
|
|
266
298
|
# Key bindings
|
mcp_agent/llm/augmented_llm.py
CHANGED
|
@@ -36,7 +36,7 @@ from mcp_agent.llm.sampling_format_converter import (
|
|
|
36
36
|
BasicFormatConverter,
|
|
37
37
|
ProviderFormatConverter,
|
|
38
38
|
)
|
|
39
|
-
from mcp_agent.llm.usage_tracking import UsageAccumulator
|
|
39
|
+
from mcp_agent.llm.usage_tracking import TurnUsage, UsageAccumulator
|
|
40
40
|
from mcp_agent.logging.logger import get_logger
|
|
41
41
|
from mcp_agent.mcp.helpers.content_helpers import get_text
|
|
42
42
|
from mcp_agent.mcp.interfaces import (
|
|
@@ -123,6 +123,7 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
123
123
|
] = BasicFormatConverter,
|
|
124
124
|
context: Optional["Context"] = None,
|
|
125
125
|
model: Optional[str] = None,
|
|
126
|
+
api_key: Optional[str] = None,
|
|
126
127
|
**kwargs: dict[str, Any],
|
|
127
128
|
) -> None:
|
|
128
129
|
"""
|
|
@@ -158,6 +159,9 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
158
159
|
# Initialize the display component
|
|
159
160
|
self.display = ConsoleDisplay(config=self.context.config)
|
|
160
161
|
|
|
162
|
+
# Tool call counter for current turn
|
|
163
|
+
self._current_turn_tool_calls = 0
|
|
164
|
+
|
|
161
165
|
# Initialize default parameters, passing model info
|
|
162
166
|
model_kwargs = kwargs.copy()
|
|
163
167
|
if model:
|
|
@@ -173,6 +177,8 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
173
177
|
self.type_converter = type_converter
|
|
174
178
|
self.verb = kwargs.get("verb")
|
|
175
179
|
|
|
180
|
+
self._init_api_key = api_key
|
|
181
|
+
|
|
176
182
|
# Initialize usage tracking
|
|
177
183
|
self.usage_accumulator = UsageAccumulator()
|
|
178
184
|
|
|
@@ -440,15 +446,25 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
440
446
|
|
|
441
447
|
def show_tool_result(self, result: CallToolResult) -> None:
|
|
442
448
|
"""Display a tool result in a formatted panel."""
|
|
443
|
-
self.display.show_tool_result(result)
|
|
449
|
+
self.display.show_tool_result(result, name=self.name)
|
|
444
450
|
|
|
445
451
|
def show_oai_tool_result(self, result: str) -> None:
|
|
446
452
|
"""Display a tool result in a formatted panel."""
|
|
447
|
-
self.display.show_oai_tool_result(result)
|
|
453
|
+
self.display.show_oai_tool_result(result, name=self.name)
|
|
448
454
|
|
|
449
455
|
def show_tool_call(self, available_tools, tool_name, tool_args) -> None:
|
|
450
456
|
"""Display a tool call in a formatted panel."""
|
|
451
|
-
self.
|
|
457
|
+
self._current_turn_tool_calls += 1
|
|
458
|
+
self.display.show_tool_call(available_tools, tool_name, tool_args, name=self.name)
|
|
459
|
+
|
|
460
|
+
def _reset_turn_tool_calls(self) -> None:
|
|
461
|
+
"""Reset tool call counter for new turn."""
|
|
462
|
+
self._current_turn_tool_calls = 0
|
|
463
|
+
|
|
464
|
+
def _finalize_turn_usage(self, turn_usage: "TurnUsage") -> None:
|
|
465
|
+
"""Set tool call count on TurnUsage and add to accumulator."""
|
|
466
|
+
turn_usage.set_tool_calls(self._current_turn_tool_calls)
|
|
467
|
+
self.usage_accumulator.add_turn(turn_usage)
|
|
452
468
|
|
|
453
469
|
async def show_assistant_message(
|
|
454
470
|
self,
|
|
@@ -556,12 +572,12 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
556
572
|
|
|
557
573
|
def _update_streaming_progress(self, content: str, model: str, estimated_tokens: int) -> int:
|
|
558
574
|
"""Update streaming progress with token estimation and formatting.
|
|
559
|
-
|
|
575
|
+
|
|
560
576
|
Args:
|
|
561
577
|
content: The text content from the streaming event
|
|
562
578
|
model: The model name
|
|
563
579
|
estimated_tokens: Current token count to update
|
|
564
|
-
|
|
580
|
+
|
|
565
581
|
Returns:
|
|
566
582
|
Updated estimated token count
|
|
567
583
|
"""
|
|
@@ -569,10 +585,10 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
569
585
|
text_length = len(content)
|
|
570
586
|
additional_tokens = max(1, text_length // 4)
|
|
571
587
|
new_total = estimated_tokens + additional_tokens
|
|
572
|
-
|
|
588
|
+
|
|
573
589
|
# Format token count for display
|
|
574
590
|
token_str = str(new_total).rjust(5)
|
|
575
|
-
|
|
591
|
+
|
|
576
592
|
# Emit progress event
|
|
577
593
|
data = {
|
|
578
594
|
"progress_action": ProgressAction.STREAMING,
|
|
@@ -582,7 +598,7 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
582
598
|
"details": token_str.strip(), # Token count goes in details for STREAMING action
|
|
583
599
|
}
|
|
584
600
|
self.logger.info("Streaming progress", data=data)
|
|
585
|
-
|
|
601
|
+
|
|
586
602
|
return new_total
|
|
587
603
|
|
|
588
604
|
def _log_chat_finished(self, model: Optional[str] = None) -> None:
|
|
@@ -692,6 +708,9 @@ class AugmentedLLM(ContextDependent, AugmentedLLMProtocol, Generic[MessageParamT
|
|
|
692
708
|
return self._message_history
|
|
693
709
|
|
|
694
710
|
def _api_key(self):
|
|
711
|
+
if self._init_api_key:
|
|
712
|
+
return self._init_api_key
|
|
713
|
+
|
|
695
714
|
from mcp_agent.llm.provider_key_manager import ProviderKeyManager
|
|
696
715
|
|
|
697
716
|
assert self.provider
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Silent LLM implementation that suppresses display output while maintaining functionality."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from mcp_agent.llm.augmented_llm_passthrough import PassthroughLLM
|
|
6
|
+
from mcp_agent.llm.provider_types import Provider
|
|
7
|
+
from mcp_agent.llm.usage_tracking import TurnUsage, UsageAccumulator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ZeroUsageAccumulator(UsageAccumulator):
|
|
11
|
+
"""Usage accumulator that always reports zero usage."""
|
|
12
|
+
|
|
13
|
+
def add_turn(self, turn: TurnUsage) -> None:
|
|
14
|
+
"""Override to do nothing - no usage accumulation."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SilentLLM(PassthroughLLM):
|
|
19
|
+
"""
|
|
20
|
+
A specialized LLM that processes messages like PassthroughLLM but suppresses all display output.
|
|
21
|
+
|
|
22
|
+
This is particularly useful for parallel agent workflows where the fan-in agent
|
|
23
|
+
should aggregate results without polluting the console with intermediate output.
|
|
24
|
+
Token counting is disabled - the model always reports zero usage.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, provider=Provider.FAST_AGENT, name: str = "Silent", **kwargs: dict[str, Any]
|
|
29
|
+
) -> None:
|
|
30
|
+
super().__init__(name=name, provider=provider, **kwargs)
|
|
31
|
+
# Override with zero usage accumulator - silent model reports no usage
|
|
32
|
+
self.usage_accumulator = ZeroUsageAccumulator()
|
|
33
|
+
|
|
34
|
+
def show_user_message(self, message: Any, **kwargs) -> None:
|
|
35
|
+
"""Override to suppress user message display."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
async def show_assistant_message(self, message: Any, **kwargs) -> None:
|
|
39
|
+
"""Override to suppress assistant message display."""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def show_tool_calls(self, tool_calls: Any, **kwargs) -> None:
|
|
43
|
+
"""Override to suppress tool call display."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
def show_tool_results(self, tool_results: Any, **kwargs) -> None:
|
|
47
|
+
"""Override to suppress tool result display."""
|
|
48
|
+
pass
|
mcp_agent/llm/model_database.py
CHANGED
|
@@ -47,6 +47,7 @@ class ModelDatabase:
|
|
|
47
47
|
"video/mp4",
|
|
48
48
|
]
|
|
49
49
|
QWEN_MULTIMODAL = ["text/plain", "image/jpeg", "image/png", "image/webp"]
|
|
50
|
+
XAI_VISION = ["text/plain", "image/jpeg", "image/png", "image/webp"]
|
|
50
51
|
TEXT_ONLY = ["text/plain"]
|
|
51
52
|
|
|
52
53
|
# Common parameter configurations
|
|
@@ -128,10 +129,22 @@ class ModelDatabase:
|
|
|
128
129
|
context_window=2097152, max_output_tokens=8192, tokenizes=GOOGLE_MULTIMODAL
|
|
129
130
|
)
|
|
130
131
|
|
|
132
|
+
# FIXME: xAI has not documented the max output tokens for Grok 4. Using Grok 3 as a placeholder. Will need to update when available (if ever)
|
|
133
|
+
GROK_4 = ModelParameters(
|
|
134
|
+
context_window=256000, max_output_tokens=16385, tokenizes=XAI_VISION
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Source for Grok 3 max output: https://www.reddit.com/r/grok/comments/1j7209p/exploring_grok_3_beta_output_capacity_a_simple/
|
|
138
|
+
# xAI does not document Grok 3 max output tokens, using the above source as a reference.
|
|
139
|
+
GROK_3 = ModelParameters(
|
|
140
|
+
context_window=131072, max_output_tokens=16385, tokenizes=TEXT_ONLY
|
|
141
|
+
)
|
|
142
|
+
|
|
131
143
|
# Model configuration database
|
|
132
144
|
MODELS: Dict[str, ModelParameters] = {
|
|
133
145
|
# internal models
|
|
134
146
|
"passthrough": FAST_AGENT_STANDARD,
|
|
147
|
+
"silent": FAST_AGENT_STANDARD,
|
|
135
148
|
"playback": FAST_AGENT_STANDARD,
|
|
136
149
|
"slow": FAST_AGENT_STANDARD,
|
|
137
150
|
# aliyun models
|
|
@@ -194,6 +207,13 @@ class ModelDatabase:
|
|
|
194
207
|
"gemini-2.5-pro-preview": GEMINI_2_5_PRO,
|
|
195
208
|
"gemini-2.5-flash-preview-05-20": GEMINI_FLASH,
|
|
196
209
|
"gemini-2.5-pro-preview-05-06": GEMINI_PRO,
|
|
210
|
+
# xAI Grok Models
|
|
211
|
+
"grok-4": GROK_4,
|
|
212
|
+
"grok-4-0709": GROK_4,
|
|
213
|
+
"grok-3": GROK_3,
|
|
214
|
+
"grok-3-mini": GROK_3,
|
|
215
|
+
"grok-3-fast": GROK_3,
|
|
216
|
+
"grok-3-mini-fast": GROK_3,
|
|
197
217
|
}
|
|
198
218
|
|
|
199
219
|
@classmethod
|
mcp_agent/llm/model_factory.py
CHANGED
|
@@ -8,11 +8,13 @@ from mcp_agent.core.exceptions import ModelConfigError
|
|
|
8
8
|
from mcp_agent.core.request_params import RequestParams
|
|
9
9
|
from mcp_agent.llm.augmented_llm_passthrough import PassthroughLLM
|
|
10
10
|
from mcp_agent.llm.augmented_llm_playback import PlaybackLLM
|
|
11
|
+
from mcp_agent.llm.augmented_llm_silent import SilentLLM
|
|
11
12
|
from mcp_agent.llm.augmented_llm_slow import SlowLLM
|
|
12
13
|
from mcp_agent.llm.provider_types import Provider
|
|
13
14
|
from mcp_agent.llm.providers.augmented_llm_aliyun import AliyunAugmentedLLM
|
|
14
15
|
from mcp_agent.llm.providers.augmented_llm_anthropic import AnthropicAugmentedLLM
|
|
15
16
|
from mcp_agent.llm.providers.augmented_llm_azure import AzureOpenAIAugmentedLLM
|
|
17
|
+
from mcp_agent.llm.providers.augmented_llm_bedrock import BedrockAugmentedLLM
|
|
16
18
|
from mcp_agent.llm.providers.augmented_llm_deepseek import DeepSeekAugmentedLLM
|
|
17
19
|
from mcp_agent.llm.providers.augmented_llm_generic import GenericAugmentedLLM
|
|
18
20
|
from mcp_agent.llm.providers.augmented_llm_google_native import GoogleNativeAugmentedLLM
|
|
@@ -20,6 +22,7 @@ from mcp_agent.llm.providers.augmented_llm_google_oai import GoogleOaiAugmentedL
|
|
|
20
22
|
from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM
|
|
21
23
|
from mcp_agent.llm.providers.augmented_llm_openrouter import OpenRouterAugmentedLLM
|
|
22
24
|
from mcp_agent.llm.providers.augmented_llm_tensorzero import TensorZeroAugmentedLLM
|
|
25
|
+
from mcp_agent.llm.providers.augmented_llm_xai import XAIAugmentedLLM
|
|
23
26
|
from mcp_agent.mcp.interfaces import AugmentedLLMProtocol
|
|
24
27
|
|
|
25
28
|
# from mcp_agent.workflows.llm.augmented_llm_deepseek import DeekSeekAugmentedLLM
|
|
@@ -31,6 +34,7 @@ LLMClass = Union[
|
|
|
31
34
|
Type[OpenAIAugmentedLLM],
|
|
32
35
|
Type[PassthroughLLM],
|
|
33
36
|
Type[PlaybackLLM],
|
|
37
|
+
Type[SilentLLM],
|
|
34
38
|
Type[SlowLLM],
|
|
35
39
|
Type[DeepSeekAugmentedLLM],
|
|
36
40
|
Type[OpenRouterAugmentedLLM],
|
|
@@ -38,6 +42,7 @@ LLMClass = Union[
|
|
|
38
42
|
Type[GoogleNativeAugmentedLLM],
|
|
39
43
|
Type[GenericAugmentedLLM],
|
|
40
44
|
Type[AzureOpenAIAugmentedLLM],
|
|
45
|
+
Type[BedrockAugmentedLLM],
|
|
41
46
|
]
|
|
42
47
|
|
|
43
48
|
|
|
@@ -75,6 +80,7 @@ class ModelFactory:
|
|
|
75
80
|
"""
|
|
76
81
|
DEFAULT_PROVIDERS = {
|
|
77
82
|
"passthrough": Provider.FAST_AGENT,
|
|
83
|
+
"silent": Provider.FAST_AGENT,
|
|
78
84
|
"playback": Provider.FAST_AGENT,
|
|
79
85
|
"slow": Provider.FAST_AGENT,
|
|
80
86
|
"gpt-4o": Provider.OPENAI,
|
|
@@ -106,10 +112,17 @@ class ModelFactory:
|
|
|
106
112
|
"gemini-2.0-flash": Provider.GOOGLE,
|
|
107
113
|
"gemini-2.5-flash-preview-05-20": Provider.GOOGLE,
|
|
108
114
|
"gemini-2.5-pro-preview-05-06": Provider.GOOGLE,
|
|
115
|
+
"grok-4": Provider.XAI,
|
|
116
|
+
"grok-4-0709": Provider.XAI,
|
|
117
|
+
"grok-3": Provider.XAI,
|
|
118
|
+
"grok-3-mini": Provider.XAI,
|
|
119
|
+
"grok-3-fast": Provider.XAI,
|
|
120
|
+
"grok-3-mini-fast": Provider.XAI,
|
|
109
121
|
"qwen-turbo": Provider.ALIYUN,
|
|
110
122
|
"qwen-plus": Provider.ALIYUN,
|
|
111
123
|
"qwen-max": Provider.ALIYUN,
|
|
112
124
|
"qwen-long": Provider.ALIYUN,
|
|
125
|
+
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
MODEL_ALIASES = {
|
|
@@ -140,16 +153,19 @@ class ModelFactory:
|
|
|
140
153
|
Provider.GENERIC: GenericAugmentedLLM,
|
|
141
154
|
Provider.GOOGLE_OAI: GoogleOaiAugmentedLLM,
|
|
142
155
|
Provider.GOOGLE: GoogleNativeAugmentedLLM,
|
|
156
|
+
Provider.XAI: XAIAugmentedLLM,
|
|
143
157
|
Provider.OPENROUTER: OpenRouterAugmentedLLM,
|
|
144
158
|
Provider.TENSORZERO: TensorZeroAugmentedLLM,
|
|
145
159
|
Provider.AZURE: AzureOpenAIAugmentedLLM,
|
|
146
160
|
Provider.ALIYUN: AliyunAugmentedLLM,
|
|
161
|
+
Provider.BEDROCK: BedrockAugmentedLLM,
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
# Mapping of special model names to their specific LLM classes
|
|
150
165
|
# This overrides the provider-based class selection
|
|
151
166
|
MODEL_SPECIFIC_CLASSES: Dict[str, LLMClass] = {
|
|
152
167
|
"playback": PlaybackLLM,
|
|
168
|
+
"silent": SilentLLM,
|
|
153
169
|
"slow": SlowLLM,
|
|
154
170
|
}
|
|
155
171
|
|
|
@@ -197,6 +213,11 @@ class ModelFactory:
|
|
|
197
213
|
# If provider still None, try to get from DEFAULT_PROVIDERS using the model_name_str
|
|
198
214
|
if provider is None:
|
|
199
215
|
provider = cls.DEFAULT_PROVIDERS.get(model_name_str)
|
|
216
|
+
|
|
217
|
+
# If still None, try pattern matching for Bedrock models
|
|
218
|
+
if provider is None and BedrockAugmentedLLM.matches_model_pattern(model_name_str):
|
|
219
|
+
provider = Provider.BEDROCK
|
|
220
|
+
|
|
200
221
|
if provider is None:
|
|
201
222
|
raise ModelConfigError(
|
|
202
223
|
f"Unknown model or provider for: {model_string}. Model name parsed as '{model_name_str}'"
|
|
@@ -11,12 +11,8 @@ from pydantic import BaseModel
|
|
|
11
11
|
from mcp_agent.core.exceptions import ProviderKeyError
|
|
12
12
|
|
|
13
13
|
PROVIDER_ENVIRONMENT_MAP: Dict[str, str] = {
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"deepseek": "DEEPSEEK_API_KEY",
|
|
17
|
-
"google": "GOOGLE_API_KEY",
|
|
18
|
-
"openrouter": "OPENROUTER_API_KEY",
|
|
19
|
-
"generic": "GENERIC_API_KEY",
|
|
14
|
+
# default behaviour in _get_env_key_name is to capitalize the
|
|
15
|
+
# provider name and suffix "_API_KEY" - so no specific mapping needed unless overriding
|
|
20
16
|
"huggingface": "HF_TOKEN",
|
|
21
17
|
}
|
|
22
18
|
API_KEY_HINT_TEXT = "<your-api-key-here>"
|
|
@@ -66,7 +62,14 @@ class ProviderKeyManager:
|
|
|
66
62
|
ProviderKeyError: If the API key is not found or is invalid
|
|
67
63
|
"""
|
|
68
64
|
|
|
65
|
+
from mcp_agent.llm.provider_types import Provider
|
|
66
|
+
|
|
69
67
|
provider_name = provider_name.lower()
|
|
68
|
+
|
|
69
|
+
# Fast-agent provider doesn't need external API keys
|
|
70
|
+
if provider_name == "fast-agent":
|
|
71
|
+
return ""
|
|
72
|
+
|
|
70
73
|
api_key = ProviderKeyManager.get_config_file_key(provider_name, config)
|
|
71
74
|
if not api_key:
|
|
72
75
|
api_key = ProviderKeyManager.get_env_var(provider_name)
|
|
@@ -75,9 +78,20 @@ class ProviderKeyManager:
|
|
|
75
78
|
api_key = "ollama" # Default for generic provider
|
|
76
79
|
|
|
77
80
|
if not api_key:
|
|
81
|
+
# Get proper display name for error message
|
|
82
|
+
try:
|
|
83
|
+
provider_enum = Provider(provider_name)
|
|
84
|
+
display_name = provider_enum.display_name
|
|
85
|
+
except ValueError:
|
|
86
|
+
# Invalid provider name
|
|
87
|
+
raise ProviderKeyError(
|
|
88
|
+
f"Invalid provider: {provider_name}",
|
|
89
|
+
f"'{provider_name}' is not a valid provider name.",
|
|
90
|
+
)
|
|
91
|
+
|
|
78
92
|
raise ProviderKeyError(
|
|
79
|
-
f"{
|
|
80
|
-
f"The {
|
|
93
|
+
f"{display_name} API key not configured",
|
|
94
|
+
f"The {display_name} API key is required but not set.\n"
|
|
81
95
|
f"Add it to your configuration file under {provider_name}.api_key "
|
|
82
96
|
f"or set the {ProviderKeyManager.get_env_key_name(provider_name)} environment variable.",
|
|
83
97
|
)
|
mcp_agent/llm/provider_types.py
CHANGED
|
@@ -8,15 +8,23 @@ from enum import Enum
|
|
|
8
8
|
class Provider(Enum):
|
|
9
9
|
"""Supported LLM providers"""
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
def __new__(cls, config_name, display_name=None):
|
|
12
|
+
obj = object.__new__(cls)
|
|
13
|
+
obj._value_ = config_name
|
|
14
|
+
obj.display_name = display_name or config_name.title()
|
|
15
|
+
return obj
|
|
16
|
+
|
|
17
|
+
ANTHROPIC = ("anthropic", "Anthropic")
|
|
18
|
+
DEEPSEEK = ("deepseek", "Deepseek")
|
|
19
|
+
FAST_AGENT = ("fast-agent", "FastAgent")
|
|
20
|
+
GENERIC = ("generic", "Generic")
|
|
21
|
+
GOOGLE_OAI = ("googleoai", "GoogleOAI") # For Google through OpenAI libraries
|
|
22
|
+
GOOGLE = ("google", "Google") # For Google GenAI native library
|
|
23
|
+
OPENAI = ("openai", "OpenAI")
|
|
24
|
+
OPENROUTER = ("openrouter", "OpenRouter")
|
|
25
|
+
TENSORZERO = ("tensorzero", "TensorZero") # For TensorZero Gateway
|
|
26
|
+
AZURE = ("azure", "Azure") # Azure OpenAI Service
|
|
27
|
+
ALIYUN = ("aliyun", "Aliyun") # Aliyun Bailian OpenAI Service
|
|
28
|
+
HUGGINGFACE = ("huggingface", "HuggingFace") # For HuggingFace MCP connections
|
|
29
|
+
XAI = ("xai", "XAI") # For xAI Grok models
|
|
30
|
+
BEDROCK = ("bedrock", "Bedrock")
|
|
@@ -112,7 +112,9 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
|
|
|
112
112
|
and event.delta.type == "text_delta"
|
|
113
113
|
):
|
|
114
114
|
# Use base class method for token estimation and progress emission
|
|
115
|
-
estimated_tokens = self._update_streaming_progress(
|
|
115
|
+
estimated_tokens = self._update_streaming_progress(
|
|
116
|
+
event.delta.text, model, estimated_tokens
|
|
117
|
+
)
|
|
116
118
|
|
|
117
119
|
# Also check for final message_delta events with actual usage info
|
|
118
120
|
elif (
|
|
@@ -285,7 +287,7 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
|
|
|
285
287
|
turn_usage = TurnUsage.from_anthropic(
|
|
286
288
|
response.usage, model or DEFAULT_ANTHROPIC_MODEL
|
|
287
289
|
)
|
|
288
|
-
self.
|
|
290
|
+
self._finalize_turn_usage(turn_usage)
|
|
289
291
|
# self._show_usage(response.usage, turn_usage)
|
|
290
292
|
except Exception as e:
|
|
291
293
|
self.logger.warning(f"Failed to track usage: {e}")
|
|
@@ -435,6 +437,9 @@ class AnthropicAugmentedLLM(AugmentedLLM[MessageParam, Message]):
|
|
|
435
437
|
Override this method to use a different LLM.
|
|
436
438
|
|
|
437
439
|
"""
|
|
440
|
+
# Reset tool call counter for new turn
|
|
441
|
+
self._reset_turn_tool_calls()
|
|
442
|
+
|
|
438
443
|
res = await self._anthropic_completion(
|
|
439
444
|
message_param=message_param,
|
|
440
445
|
request_params=request_params,
|