fast-agent-mcp 0.1.11__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.
Files changed (131) hide show
  1. {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/METADATA +1 -1
  2. fast_agent_mcp-0.1.13.dist-info/RECORD +164 -0
  3. mcp_agent/agents/agent.py +37 -102
  4. mcp_agent/app.py +16 -27
  5. mcp_agent/cli/commands/bootstrap.py +22 -52
  6. mcp_agent/cli/commands/config.py +4 -4
  7. mcp_agent/cli/commands/setup.py +11 -26
  8. mcp_agent/cli/main.py +6 -9
  9. mcp_agent/cli/terminal.py +2 -2
  10. mcp_agent/config.py +1 -5
  11. mcp_agent/context.py +13 -26
  12. mcp_agent/context_dependent.py +3 -7
  13. mcp_agent/core/agent_app.py +46 -122
  14. mcp_agent/core/agent_types.py +29 -2
  15. mcp_agent/core/agent_utils.py +3 -5
  16. mcp_agent/core/decorators.py +6 -14
  17. mcp_agent/core/enhanced_prompt.py +25 -52
  18. mcp_agent/core/error_handling.py +1 -1
  19. mcp_agent/core/exceptions.py +8 -8
  20. mcp_agent/core/factory.py +30 -72
  21. mcp_agent/core/fastagent.py +48 -88
  22. mcp_agent/core/mcp_content.py +10 -19
  23. mcp_agent/core/prompt.py +8 -15
  24. mcp_agent/core/proxies.py +34 -25
  25. mcp_agent/core/request_params.py +46 -0
  26. mcp_agent/core/types.py +6 -6
  27. mcp_agent/core/validation.py +16 -16
  28. mcp_agent/executor/decorator_registry.py +11 -23
  29. mcp_agent/executor/executor.py +8 -17
  30. mcp_agent/executor/task_registry.py +2 -4
  31. mcp_agent/executor/temporal.py +28 -74
  32. mcp_agent/executor/workflow.py +3 -5
  33. mcp_agent/executor/workflow_signal.py +17 -29
  34. mcp_agent/human_input/handler.py +4 -9
  35. mcp_agent/human_input/types.py +2 -3
  36. mcp_agent/logging/events.py +1 -5
  37. mcp_agent/logging/json_serializer.py +7 -6
  38. mcp_agent/logging/listeners.py +20 -23
  39. mcp_agent/logging/logger.py +15 -17
  40. mcp_agent/logging/rich_progress.py +10 -8
  41. mcp_agent/logging/tracing.py +4 -6
  42. mcp_agent/logging/transport.py +24 -24
  43. mcp_agent/mcp/gen_client.py +4 -12
  44. mcp_agent/mcp/interfaces.py +107 -88
  45. mcp_agent/mcp/mcp_agent_client_session.py +11 -19
  46. mcp_agent/mcp/mcp_agent_server.py +8 -10
  47. mcp_agent/mcp/mcp_aggregator.py +49 -122
  48. mcp_agent/mcp/mcp_connection_manager.py +16 -37
  49. mcp_agent/mcp/prompt_message_multipart.py +12 -18
  50. mcp_agent/mcp/prompt_serialization.py +13 -38
  51. mcp_agent/mcp/prompts/prompt_load.py +99 -0
  52. mcp_agent/mcp/prompts/prompt_server.py +21 -128
  53. mcp_agent/mcp/prompts/prompt_template.py +20 -42
  54. mcp_agent/mcp/resource_utils.py +8 -17
  55. mcp_agent/mcp/sampling.py +62 -64
  56. mcp_agent/mcp/stdio.py +11 -8
  57. mcp_agent/mcp_server/__init__.py +1 -1
  58. mcp_agent/mcp_server/agent_server.py +10 -17
  59. mcp_agent/mcp_server_registry.py +13 -35
  60. mcp_agent/resources/examples/data-analysis/analysis-campaign.py +1 -1
  61. mcp_agent/resources/examples/data-analysis/analysis.py +1 -1
  62. mcp_agent/resources/examples/data-analysis/slides.py +110 -0
  63. mcp_agent/resources/examples/internal/agent.py +2 -1
  64. mcp_agent/resources/examples/internal/job.py +2 -1
  65. mcp_agent/resources/examples/internal/prompt_category.py +1 -1
  66. mcp_agent/resources/examples/internal/prompt_sizing.py +3 -5
  67. mcp_agent/resources/examples/internal/sizer.py +2 -1
  68. mcp_agent/resources/examples/internal/social.py +2 -1
  69. mcp_agent/resources/examples/mcp_researcher/researcher-eval.py +1 -1
  70. mcp_agent/resources/examples/prompting/__init__.py +1 -1
  71. mcp_agent/resources/examples/prompting/agent.py +2 -1
  72. mcp_agent/resources/examples/prompting/image_server.py +5 -11
  73. mcp_agent/resources/examples/researcher/researcher-eval.py +1 -1
  74. mcp_agent/resources/examples/researcher/researcher-imp.py +3 -4
  75. mcp_agent/resources/examples/researcher/researcher.py +2 -1
  76. mcp_agent/resources/examples/workflows/agent_build.py +2 -1
  77. mcp_agent/resources/examples/workflows/chaining.py +2 -1
  78. mcp_agent/resources/examples/workflows/evaluator.py +2 -1
  79. mcp_agent/resources/examples/workflows/human_input.py +2 -1
  80. mcp_agent/resources/examples/workflows/orchestrator.py +2 -1
  81. mcp_agent/resources/examples/workflows/parallel.py +2 -1
  82. mcp_agent/resources/examples/workflows/router.py +2 -1
  83. mcp_agent/resources/examples/workflows/sse.py +1 -1
  84. mcp_agent/telemetry/usage_tracking.py +2 -1
  85. mcp_agent/ui/console_display.py +17 -41
  86. mcp_agent/workflows/embedding/embedding_base.py +1 -4
  87. mcp_agent/workflows/embedding/embedding_cohere.py +2 -2
  88. mcp_agent/workflows/embedding/embedding_openai.py +4 -13
  89. mcp_agent/workflows/evaluator_optimizer/evaluator_optimizer.py +23 -57
  90. mcp_agent/workflows/intent_classifier/intent_classifier_base.py +5 -8
  91. mcp_agent/workflows/intent_classifier/intent_classifier_embedding.py +7 -11
  92. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_cohere.py +4 -8
  93. mcp_agent/workflows/intent_classifier/intent_classifier_embedding_openai.py +4 -8
  94. mcp_agent/workflows/intent_classifier/intent_classifier_llm.py +11 -22
  95. mcp_agent/workflows/intent_classifier/intent_classifier_llm_anthropic.py +3 -3
  96. mcp_agent/workflows/intent_classifier/intent_classifier_llm_openai.py +4 -6
  97. mcp_agent/workflows/llm/anthropic_utils.py +8 -29
  98. mcp_agent/workflows/llm/augmented_llm.py +94 -332
  99. mcp_agent/workflows/llm/augmented_llm_anthropic.py +43 -76
  100. mcp_agent/workflows/llm/augmented_llm_openai.py +46 -100
  101. mcp_agent/workflows/llm/augmented_llm_passthrough.py +42 -20
  102. mcp_agent/workflows/llm/augmented_llm_playback.py +8 -6
  103. mcp_agent/workflows/llm/memory.py +103 -0
  104. mcp_agent/workflows/llm/model_factory.py +9 -21
  105. mcp_agent/workflows/llm/openai_utils.py +1 -1
  106. mcp_agent/workflows/llm/prompt_utils.py +39 -27
  107. mcp_agent/workflows/llm/providers/multipart_converter_anthropic.py +246 -184
  108. mcp_agent/workflows/llm/providers/multipart_converter_openai.py +212 -202
  109. mcp_agent/workflows/llm/providers/openai_multipart.py +19 -61
  110. mcp_agent/workflows/llm/providers/sampling_converter_anthropic.py +11 -212
  111. mcp_agent/workflows/llm/providers/sampling_converter_openai.py +13 -215
  112. mcp_agent/workflows/llm/sampling_converter.py +117 -0
  113. mcp_agent/workflows/llm/sampling_format_converter.py +12 -29
  114. mcp_agent/workflows/orchestrator/orchestrator.py +24 -67
  115. mcp_agent/workflows/orchestrator/orchestrator_models.py +14 -40
  116. mcp_agent/workflows/parallel/fan_in.py +17 -47
  117. mcp_agent/workflows/parallel/fan_out.py +6 -12
  118. mcp_agent/workflows/parallel/parallel_llm.py +9 -26
  119. mcp_agent/workflows/router/router_base.py +29 -59
  120. mcp_agent/workflows/router/router_embedding.py +11 -25
  121. mcp_agent/workflows/router/router_embedding_cohere.py +2 -2
  122. mcp_agent/workflows/router/router_embedding_openai.py +2 -2
  123. mcp_agent/workflows/router/router_llm.py +12 -28
  124. mcp_agent/workflows/swarm/swarm.py +20 -48
  125. mcp_agent/workflows/swarm/swarm_anthropic.py +2 -2
  126. mcp_agent/workflows/swarm/swarm_openai.py +2 -2
  127. fast_agent_mcp-0.1.11.dist-info/RECORD +0 -160
  128. mcp_agent/workflows/llm/llm_selector.py +0 -345
  129. {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/WHEEL +0 -0
  130. {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
  131. {fast_agent_mcp-0.1.11.dist-info → fast_agent_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,345 +0,0 @@
1
- import json
2
- from difflib import SequenceMatcher
3
- from importlib import resources
4
- from typing import Dict, List
5
-
6
- from numpy import average
7
- from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
8
-
9
- from mcp.types import ModelHint, ModelPreferences
10
-
11
-
12
- class ModelBenchmarks(BaseModel):
13
- """
14
- Performance benchmarks for comparing different models.
15
- """
16
-
17
- __pydantic_extra__: dict[str, float] = Field(
18
- init=False
19
- ) # Enforces that extra fields are floats
20
-
21
- quality_score: float | None = None
22
- """A blended quality score for the model."""
23
-
24
- mmlu_score: float | None = None
25
- gsm8k_score: float | None = None
26
- bbh_score: float | None = None
27
-
28
- model_config = ConfigDict(extra="allow")
29
-
30
-
31
- class ModelLatency(BaseModel):
32
- """
33
- Latency benchmarks for comparing different models.
34
- """
35
-
36
- time_to_first_token_ms: float = Field(gt=0)
37
- """
38
- Median Time to first token in milliseconds.
39
- """
40
-
41
- tokens_per_second: float = Field(gt=0)
42
- """
43
- Median output tokens per second.
44
- """
45
-
46
-
47
- class ModelCost(BaseModel):
48
- """
49
- Cost benchmarks for comparing different models.
50
- """
51
-
52
- blended_cost_per_1m: float | None = None
53
- """
54
- Blended cost mixing input/output cost per 1M tokens.
55
- """
56
-
57
- input_cost_per_1m: float | None = None
58
- """
59
- Cost per 1M input tokens.
60
- """
61
-
62
- output_cost_per_1m: float | None = None
63
- """
64
- Cost per 1M output tokens.
65
- """
66
-
67
-
68
- class ModelMetrics(BaseModel):
69
- """
70
- Model metrics for comparing different models.
71
- """
72
-
73
- cost: ModelCost
74
- speed: ModelLatency
75
- intelligence: ModelBenchmarks
76
-
77
-
78
- class ModelInfo(BaseModel):
79
- """
80
- LLM metadata, including performance benchmarks.
81
- """
82
-
83
- name: str
84
- description: str | None = None
85
- provider: str
86
- metrics: ModelMetrics
87
-
88
-
89
- class ModelSelector:
90
- """
91
- A heuristic-based selector to choose the best model from a list of models.
92
-
93
- Because LLMs can vary along multiple dimensions, choosing the "best" model is
94
- rarely straightforward. Different models excel in different areas—some are
95
- faster but less capable, others are more capable but more expensive, and so
96
- on.
97
-
98
- MCP's ModelPreferences interface allows servers to express their priorities across multiple
99
- dimensions to help clients make an appropriate selection for their use case.
100
- """
101
-
102
- def __init__(
103
- self,
104
- models: List[ModelInfo] = None,
105
- benchmark_weights: Dict[str, float] | None = None,
106
- ):
107
- if not models:
108
- self.models = load_default_models()
109
- else:
110
- self.models = models
111
-
112
- if benchmark_weights:
113
- self.benchmark_weights = benchmark_weights
114
- else:
115
- # Defaults for how much to value each benchmark metric (must add to 1)
116
- self.benchmark_weights = {"mmlu": 0.4, "gsm8k": 0.3, "bbh": 0.3}
117
-
118
- if abs(sum(self.benchmark_weights.values()) - 1.0) > 1e-6:
119
- raise ValueError("Benchmark weights must sum to 1.0")
120
-
121
- self.max_values = self._calculate_max_scores(self.models)
122
- self.models_by_provider = self._models_by_provider(self.models)
123
-
124
- def select_best_model(
125
- self, model_preferences: ModelPreferences, provider: str | None = None
126
- ) -> ModelInfo:
127
- """
128
- Select the best model from a given list of models based on the given model preferences.
129
- """
130
-
131
- models: List[ModelInfo] = []
132
- if provider:
133
- models = self.models_by_provider[provider]
134
- else:
135
- models = self.models
136
-
137
- if not models:
138
- raise ValueError(f"No models available for selection. Provider={provider}")
139
-
140
- candidate_models = models
141
- # First check the model hints
142
- if model_preferences.hints:
143
- candidate_models = []
144
- for model in models:
145
- for hint in model_preferences.hints:
146
- if self._check_model_hint(model, hint):
147
- candidate_models.append(model)
148
-
149
- if not candidate_models:
150
- # If no hints match, we'll use all models and let the benchmark weights decide
151
- candidate_models = models
152
-
153
- scores = []
154
-
155
- # Next, we'll use the benchmark weights to decide the best model
156
- for model in candidate_models:
157
- cost_score = self._calculate_cost_score(
158
- model, model_preferences, max_cost=self.max_values["max_cost"]
159
- )
160
- speed_score = self._calculate_speed_score(
161
- model,
162
- max_tokens_per_second=self.max_values["max_tokens_per_second"],
163
- max_time_to_first_token_ms=self.max_values[
164
- "max_time_to_first_token_ms"
165
- ],
166
- )
167
- intelligence_score = self._calculate_intelligence_score(
168
- model, self.max_values
169
- )
170
-
171
- model_score = (
172
- (model_preferences.costPriority or 0) * cost_score
173
- + (model_preferences.speedPriority or 0) * speed_score
174
- + (model_preferences.intelligencePriority or 0) * intelligence_score
175
- )
176
- scores.append((model_score, model))
177
-
178
- return max(scores, key=lambda x: x[0])[1]
179
-
180
- def _models_by_provider(
181
- self, models: List[ModelInfo]
182
- ) -> Dict[str, List[ModelInfo]]:
183
- """
184
- Group models by provider.
185
- """
186
- provider_models: Dict[str, List[ModelInfo]] = {}
187
- for model in models:
188
- if model.provider not in provider_models:
189
- provider_models[model.provider] = []
190
- provider_models[model.provider].append(model)
191
- return provider_models
192
-
193
- def _check_model_hint(self, model: ModelInfo, hint: ModelHint) -> bool:
194
- """
195
- Check if a model matches a specific hint.
196
- """
197
-
198
- name_match = True
199
- if hint.name:
200
- name_match = _fuzzy_match(hint.name, model.name)
201
-
202
- provider_match = True
203
- provider: str | None = getattr(hint, "provider", None)
204
- if provider:
205
- provider_match = _fuzzy_match(provider, model.provider)
206
-
207
- # This can be extended to check for more hints
208
- return name_match and provider_match
209
-
210
- def _calculate_total_cost(self, model: ModelInfo, io_ratio: float = 3.0) -> float:
211
- """
212
- Calculate a single cost metric of a model based on input/output token costs,
213
- and a ratio of input to output tokens.
214
-
215
- Args:
216
- model: The model to calculate the cost for.
217
- io_ratio: The estimated ratio of input to output tokens. Defaults to 3.0.
218
- """
219
-
220
- if model.metrics.cost.blended_cost_per_1m is not None:
221
- return model.metrics.cost.blended_cost_per_1m
222
-
223
- input_cost = model.metrics.cost.input_cost_per_1m
224
- output_cost = model.metrics.cost.output_cost_per_1m
225
-
226
- total_cost = (input_cost * io_ratio + output_cost) / (1 + io_ratio)
227
- return total_cost
228
-
229
- def _calculate_cost_score(
230
- self,
231
- model: ModelInfo,
232
- model_preferences: ModelPreferences,
233
- max_cost: float,
234
- ) -> float:
235
- """Normalized 0->1 cost score for a model."""
236
- total_cost = self._calculate_total_cost(model, model_preferences)
237
- return 1 - (total_cost / max_cost)
238
-
239
- def _calculate_intelligence_score(
240
- self, model: ModelInfo, max_values: Dict[str, float]
241
- ) -> float:
242
- """
243
- Return a normalized 0->1 intelligence score for a model based on its benchmark metrics.
244
- """
245
- scores = []
246
- weights = []
247
-
248
- benchmark_dict: Dict[str, float] = model.metrics.intelligence.model_dump()
249
- use_weights = True
250
- for bench, score in benchmark_dict.items():
251
- key = f"max_{bench}"
252
- if score is not None and key in max_values:
253
- scores.append(score / max_values[key])
254
- if bench in self.benchmark_weights:
255
- weights.append(self.benchmark_weights[bench])
256
- else:
257
- # If a benchmark doesn't have a weight, don't use weights at all, we'll just average the scores
258
- use_weights = False
259
-
260
- if not scores:
261
- return 0
262
- elif use_weights:
263
- return average(scores, weights=weights)
264
- else:
265
- return average(scores)
266
-
267
- def _calculate_speed_score(
268
- self,
269
- model: ModelInfo,
270
- max_tokens_per_second: float,
271
- max_time_to_first_token_ms: float,
272
- ) -> float:
273
- """Normalized 0->1 cost score for a model."""
274
-
275
- time_to_first_token_score = 1 - (
276
- model.metrics.speed.time_to_first_token_ms / max_time_to_first_token_ms
277
- )
278
-
279
- tokens_per_second_score = (
280
- model.metrics.speed.tokens_per_second / max_tokens_per_second
281
- )
282
-
283
- latency_score = average(
284
- [time_to_first_token_score, tokens_per_second_score], weights=[0.4, 0.6]
285
- )
286
- return latency_score
287
-
288
- def _calculate_max_scores(self, models: List[ModelInfo]) -> Dict[str, float]:
289
- """
290
- Of all the models, calculate the maximum value for each benchmark metric.
291
- """
292
- max_dict: Dict[str, float] = {}
293
-
294
- max_dict["max_cost"] = max(self._calculate_total_cost(m) for m in models)
295
- max_dict["max_tokens_per_second"] = max(
296
- max(m.metrics.speed.tokens_per_second for m in models), 1e-6
297
- )
298
- max_dict["max_time_to_first_token_ms"] = max(
299
- max(m.metrics.speed.time_to_first_token_ms for m in models), 1e-6
300
- )
301
-
302
- # Find the maximum value for each model performance benchmark
303
- for model in models:
304
- benchmark_dict: Dict[str, float] = model.metrics.intelligence.model_dump()
305
- for bench, score in benchmark_dict.items():
306
- if score is None:
307
- continue
308
-
309
- key = f"max_{bench}"
310
- if key in max_dict:
311
- max_dict[key] = max(max_dict[key], score)
312
- else:
313
- max_dict[key] = score
314
-
315
- return max_dict
316
-
317
-
318
- def load_default_models() -> List[ModelInfo]:
319
- """
320
- We use ArtificialAnalysis benchmarks for determining the best model.
321
- """
322
- with (
323
- resources.files("mcp_agent.data")
324
- .joinpath("artificial_analysis_llm_benchmarks.json")
325
- .open() as file
326
- ):
327
- data = json.load(file) # Array of ModelInfo objects
328
- adapter = TypeAdapter(List[ModelInfo])
329
- return adapter.validate_python(data)
330
-
331
-
332
- def _fuzzy_match(str1: str, str2: str, threshold: float = 0.8) -> bool:
333
- """
334
- Fuzzy match two strings
335
-
336
- Args:
337
- str1: First string to compare
338
- str2: Second string to compare
339
- threshold: Minimum similarity ratio to consider a match (0.0 to 1.0)
340
-
341
- Returns:
342
- bool: True if strings match above threshold, False otherwise
343
- """
344
- sequence_ratio = SequenceMatcher(None, str1.lower(), str2.lower()).ratio()
345
- return sequence_ratio >= threshold