fast-agent-mcp 0.1.3__py3-none-any.whl → 0.1.5__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 (28) hide show
  1. {fast_agent_mcp-0.1.3.dist-info → fast_agent_mcp-0.1.5.dist-info}/METADATA +5 -1
  2. {fast_agent_mcp-0.1.3.dist-info → fast_agent_mcp-0.1.5.dist-info}/RECORD +28 -17
  3. mcp_agent/agents/agent.py +46 -0
  4. mcp_agent/core/agent_app.py +373 -9
  5. mcp_agent/core/decorators.py +455 -0
  6. mcp_agent/core/enhanced_prompt.py +70 -4
  7. mcp_agent/core/factory.py +501 -0
  8. mcp_agent/core/fastagent.py +140 -1059
  9. mcp_agent/core/proxies.py +83 -47
  10. mcp_agent/core/validation.py +221 -0
  11. mcp_agent/human_input/handler.py +5 -2
  12. mcp_agent/mcp/mcp_aggregator.py +537 -47
  13. mcp_agent/mcp/mcp_connection_manager.py +13 -2
  14. mcp_agent/mcp_server/__init__.py +4 -0
  15. mcp_agent/mcp_server/agent_server.py +121 -0
  16. mcp_agent/resources/examples/internal/fastagent.config.yaml +52 -0
  17. mcp_agent/resources/examples/internal/prompt_category.py +21 -0
  18. mcp_agent/resources/examples/internal/prompt_sizing.py +53 -0
  19. mcp_agent/resources/examples/internal/sizer.py +24 -0
  20. mcp_agent/resources/examples/researcher/fastagent.config.yaml +14 -1
  21. mcp_agent/resources/examples/workflows/sse.py +23 -0
  22. mcp_agent/ui/console_display.py +278 -0
  23. mcp_agent/workflows/llm/augmented_llm.py +245 -179
  24. mcp_agent/workflows/llm/augmented_llm_anthropic.py +49 -3
  25. mcp_agent/workflows/llm/augmented_llm_openai.py +52 -4
  26. {fast_agent_mcp-0.1.3.dist-info → fast_agent_mcp-0.1.5.dist-info}/WHEEL +0 -0
  27. {fast_agent_mcp-0.1.3.dist-info → fast_agent_mcp-0.1.5.dist-info}/entry_points.txt +0 -0
  28. {fast_agent_mcp-0.1.3.dist-info → fast_agent_mcp-0.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -3,46 +3,27 @@ Decorator-based interface for MCP Agent applications.
3
3
  Provides a simplified way to create and manage agents using decorators.
4
4
  """
5
5
 
6
+ import asyncio
6
7
  from typing import (
7
- List,
8
8
  Optional,
9
9
  Dict,
10
- Callable,
11
10
  TypeVar,
12
11
  Any,
13
- Literal,
14
12
  )
15
13
  import yaml
16
14
  import argparse
17
15
  from contextlib import asynccontextmanager
16
+ from functools import partial
18
17
 
19
18
  from mcp_agent.app import MCPApp
20
- from mcp_agent.agents.agent import Agent, AgentConfig
21
19
  from mcp_agent.context_dependent import ContextDependent
22
20
  from mcp_agent.config import Settings
23
- from mcp_agent.event_progress import ProgressAction
24
- from mcp_agent.workflows.evaluator_optimizer.evaluator_optimizer import (
25
- EvaluatorOptimizerLLM,
26
- QualityRating,
27
- )
28
- from mcp_agent.workflows.llm.augmented_llm import AugmentedLLM, RequestParams
29
- from mcp_agent.workflows.llm.model_factory import ModelFactory
30
- from mcp_agent.workflows.orchestrator.orchestrator import Orchestrator
31
- from mcp_agent.workflows.parallel.parallel_llm import ParallelLLM
32
- from mcp_agent.workflows.router.router_llm import LLMRouter
33
21
 
34
22
  from mcp_agent.core.agent_app import AgentApp
35
23
  from mcp_agent.core.agent_types import AgentType
36
- from mcp_agent.core.agent_utils import unwrap_proxy, get_agent_instances, log_agent_load
37
24
  from mcp_agent.core.error_handling import handle_error
38
- from mcp_agent.core.proxies import (
39
- BaseAgentProxy,
40
- LLMAgentProxy,
41
- WorkflowProxy,
42
- RouterProxy,
43
- ChainProxy,
44
- )
45
- from mcp_agent.core.types import AgentOrWorkflow, ProxyDict
25
+ from mcp_agent.core.proxies import LLMAgentProxy
26
+ from mcp_agent.core.types import ProxyDict
46
27
  from mcp_agent.core.exceptions import (
47
28
  AgentConfigError,
48
29
  CircularDependencyError,
@@ -52,12 +33,34 @@ from mcp_agent.core.exceptions import (
52
33
  ProviderKeyError,
53
34
  ServerInitializationError,
54
35
  )
36
+ from mcp_agent.core.decorators import (
37
+ _create_decorator,
38
+ agent,
39
+ orchestrator,
40
+ parallel,
41
+ evaluator_optimizer,
42
+ router,
43
+ chain,
44
+ passthrough,
45
+ )
46
+ from mcp_agent.core.validation import (
47
+ validate_server_references,
48
+ validate_workflow_references,
49
+ )
50
+ from mcp_agent.core.factory import (
51
+ get_model_factory,
52
+ create_basic_agents,
53
+ create_agents_in_dependency_order,
54
+ create_agents_by_type,
55
+ )
55
56
 
56
57
  # TODO -- reinstate once Windows&Python 3.13 platform issues are fixed
57
58
  # import readline # noqa: F401
58
59
 
59
60
  from rich import print
60
61
 
62
+ from mcp_agent.mcp_server import AgentMCPServer
63
+
61
64
  T = TypeVar("T") # For the wrapper classes
62
65
 
63
66
 
@@ -113,64 +116,17 @@ class FastAgent(ContextDependent):
113
116
  )
114
117
  self.agents: Dict[str, Dict[str, Any]] = {}
115
118
 
116
- def _create_proxy(
117
- self, name: str, instance: AgentOrWorkflow, agent_type: str
118
- ) -> BaseAgentProxy:
119
- """Create appropriate proxy type based on agent type and validate instance type
119
+ # Bind decorator methods to this instance
120
+ self._create_decorator = _create_decorator.__get__(self)
121
+ self.agent = agent.__get__(self)
122
+ self.orchestrator = orchestrator.__get__(self)
123
+ self.parallel = parallel.__get__(self)
124
+ self.evaluator_optimizer = evaluator_optimizer.__get__(self)
125
+ self.router = router.__get__(self)
126
+ self.chain = chain.__get__(self)
127
+ self.passthrough = passthrough.__get__(self)
120
128
 
121
- Args:
122
- name: Name of the agent/workflow
123
- instance: The agent or workflow instance
124
- agent_type: Type from AgentType enum values
125
-
126
- Returns:
127
- Appropriate proxy type wrapping the instance
128
-
129
- Raises:
130
- TypeError: If instance type doesn't match expected type for agent_type
131
- """
132
- if agent_type not in [
133
- AgentType.PARALLEL.value,
134
- AgentType.EVALUATOR_OPTIMIZER.value,
135
- AgentType.CHAIN.value,
136
- ]:
137
- log_agent_load(self.app, name)
138
- if agent_type == AgentType.BASIC.value:
139
- if not isinstance(instance, Agent):
140
- raise TypeError(
141
- f"Expected Agent instance for {name}, got {type(instance)}"
142
- )
143
- return LLMAgentProxy(self.app, name, instance)
144
- elif agent_type == AgentType.ORCHESTRATOR.value:
145
- if not isinstance(instance, Orchestrator):
146
- raise TypeError(
147
- f"Expected Orchestrator instance for {name}, got {type(instance)}"
148
- )
149
- return WorkflowProxy(self.app, name, instance)
150
- elif agent_type == AgentType.PARALLEL.value:
151
- if not isinstance(instance, ParallelLLM):
152
- raise TypeError(
153
- f"Expected ParallelLLM instance for {name}, got {type(instance)}"
154
- )
155
- return WorkflowProxy(self.app, name, instance)
156
- elif agent_type == AgentType.EVALUATOR_OPTIMIZER.value:
157
- if not isinstance(instance, EvaluatorOptimizerLLM):
158
- raise TypeError(
159
- f"Expected EvaluatorOptimizerLLM instance for {name}, got {type(instance)}"
160
- )
161
- return WorkflowProxy(self.app, name, instance)
162
- elif agent_type == AgentType.ROUTER.value:
163
- if not isinstance(instance, LLMRouter):
164
- raise TypeError(
165
- f"Expected LLMRouter instance for {name}, got {type(instance)}"
166
- )
167
- return RouterProxy(self.app, name, instance)
168
- elif agent_type == AgentType.CHAIN.value:
169
- # Chain proxy is directly returned from _create_agents_by_type
170
- # No need for type checking as it's already a ChainProxy
171
- return instance
172
- else:
173
- raise ValueError(f"Unknown agent type: {agent_type}")
129
+ # _create_proxy moved to factory.py
174
130
 
175
131
  @property
176
132
  def context(self):
@@ -183,125 +139,12 @@ class FastAgent(ContextDependent):
183
139
  with open(self.config_path) as f:
184
140
  self.config = yaml.safe_load(f) or {}
185
141
 
186
- def _validate_server_references(self) -> None:
187
- """
188
- Validate that all server references in agent configurations exist in config.
189
- Raises ServerConfigError if any referenced servers are not defined.
190
- """
191
- if not self.context.config.mcp or not self.context.config.mcp.servers:
192
- available_servers = set()
193
- else:
194
- available_servers = set(self.context.config.mcp.servers.keys())
195
-
196
- # Check each agent's server references
197
- for name, agent_data in self.agents.items():
198
- config = agent_data["config"]
199
- if config.servers:
200
- missing = [s for s in config.servers if s not in available_servers]
201
- if missing:
202
- raise ServerConfigError(
203
- f"Missing server configuration for agent '{name}'",
204
- f"The following servers are referenced but not defined in config: {', '.join(missing)}",
205
- )
206
-
207
- def _validate_workflow_references(self) -> None:
208
- """
209
- Validate that all workflow references point to valid agents/workflows.
210
- Also validates that referenced agents have required configuration.
211
- Raises AgentConfigError if any validation fails.
212
- """
213
- available_components = set(self.agents.keys())
214
-
215
- for name, agent_data in self.agents.items():
216
- agent_type = agent_data["type"]
217
-
218
- if agent_type == AgentType.PARALLEL.value:
219
- # Check fan_in exists
220
- fan_in = agent_data["fan_in"]
221
- if fan_in not in available_components:
222
- raise AgentConfigError(
223
- f"Parallel workflow '{name}' references non-existent fan_in component: {fan_in}"
224
- )
225
-
226
- # Check fan_out agents exist
227
- fan_out = agent_data["fan_out"]
228
- missing = [a for a in fan_out if a not in available_components]
229
- if missing:
230
- raise AgentConfigError(
231
- f"Parallel workflow '{name}' references non-existent fan_out components: {', '.join(missing)}"
232
- )
233
-
234
- elif agent_type == AgentType.ORCHESTRATOR.value:
235
- # Check all child agents exist and are properly configured
236
- child_agents = agent_data["child_agents"]
237
- missing = [a for a in child_agents if a not in available_components]
238
- if missing:
239
- raise AgentConfigError(
240
- f"Orchestrator '{name}' references non-existent agents: {', '.join(missing)}"
241
- )
242
-
243
- # Validate child agents have required LLM configuration
244
- for agent_name in child_agents:
245
- child_data = self.agents[agent_name]
246
- if child_data["type"] == AgentType.BASIC.value:
247
- # For basic agents, we'll validate LLM config during creation
248
- continue
249
- # Check if it's a workflow type or has LLM capability
250
- # Workflows like EvaluatorOptimizer and Parallel are valid for orchestrator
251
- func = child_data["func"]
252
- workflow_types = [
253
- AgentType.EVALUATOR_OPTIMIZER.value,
254
- AgentType.PARALLEL.value,
255
- AgentType.ROUTER.value,
256
- AgentType.CHAIN.value,
257
- ]
258
-
259
- if not (
260
- isinstance(func, AugmentedLLM)
261
- or child_data["type"] in workflow_types
262
- or (hasattr(func, "_llm") and func._llm is not None)
263
- ):
264
- raise AgentConfigError(
265
- f"Agent '{agent_name}' used by orchestrator '{name}' lacks LLM capability",
266
- "All agents used by orchestrators must be LLM-capable (either an AugmentedLLM or have an _llm property)",
267
- )
268
-
269
- elif agent_type == AgentType.ROUTER.value:
270
- # Check all referenced agents exist
271
- router_agents = agent_data["agents"]
272
- missing = [a for a in router_agents if a not in available_components]
273
- if missing:
274
- raise AgentConfigError(
275
- f"Router '{name}' references non-existent agents: {', '.join(missing)}"
276
- )
277
-
278
- elif agent_type == AgentType.EVALUATOR_OPTIMIZER.value:
279
- # Check both evaluator and optimizer exist
280
- evaluator = agent_data["evaluator"]
281
- generator = agent_data["generator"]
282
- missing = []
283
- if evaluator not in available_components:
284
- missing.append(f"evaluator: {evaluator}")
285
- if generator not in available_components:
286
- missing.append(f"generator: {generator}")
287
- if missing:
288
- raise AgentConfigError(
289
- f"Evaluator-Optimizer '{name}' references non-existent components: {', '.join(missing)}"
290
- )
291
-
292
- elif agent_type == AgentType.CHAIN.value:
293
- # Check that all agents in the sequence exist
294
- sequence = agent_data.get("sequence", agent_data.get("agents", []))
295
- missing = [a for a in sequence if a not in available_components]
296
- if missing:
297
- raise AgentConfigError(
298
- f"Chain '{name}' references non-existent agents: {', '.join(missing)}"
299
- )
142
+ # Validation methods moved to validation.py
300
143
 
301
144
  def _get_model_factory(
302
145
  self,
303
146
  model: Optional[str] = None,
304
- request_params: Optional[RequestParams] = None,
147
+ request_params: Optional[Any] = None,
305
148
  ) -> Any:
306
149
  """
307
150
  Get model factory using specified or default model.
@@ -314,462 +157,13 @@ class FastAgent(ContextDependent):
314
157
  Returns:
315
158
  ModelFactory instance for the specified or default model
316
159
  """
317
-
318
- # Config has lowest precedence
319
- model_spec = self.context.config.default_model
320
-
321
- # Command line override has next precedence
322
- if self.args.model:
323
- model_spec = self.args.model
324
-
325
- # Model from decorator has highest precedence
326
- if model:
327
- model_spec = model
328
-
329
- # Update or create request_params with the final model choice
330
- if request_params:
331
- request_params = request_params.model_copy(update={"model": model_spec})
332
- else:
333
- request_params = RequestParams(model=model_spec)
334
-
335
- # Let model factory handle the model string parsing and setup
336
- return ModelFactory.create_factory(model_spec, request_params=request_params)
337
-
338
- def _create_decorator(
339
- self,
340
- agent_type: AgentType,
341
- default_name: str = None,
342
- default_instruction: str = None,
343
- default_servers: List[str] = None,
344
- default_use_history: bool = True,
345
- wrapper_needed: bool = False,
346
- **extra_defaults,
347
- ) -> Callable:
348
- """
349
- Factory method for creating agent decorators with common behavior.
350
-
351
- Args:
352
- agent_type: Type of agent/workflow to create
353
- default_name: Default name to use if not provided
354
- default_instruction: Default instruction to use if not provided
355
- default_servers: Default servers list to use if not provided
356
- default_use_history: Default history setting
357
- wrapper_needed: Whether to wrap the decorated function
358
- **extra_defaults: Additional agent/workflow-specific parameters
359
- """
360
-
361
- def decorator_wrapper(**kwargs):
362
- # Apply defaults for common parameters
363
- name = kwargs.get("name", default_name or f"{agent_type.name.title()}")
364
- instruction = kwargs.get("instruction", default_instruction or "")
365
- servers = kwargs.get("servers", default_servers or [])
366
- model = kwargs.get("model", None)
367
- use_history = kwargs.get("use_history", default_use_history)
368
- request_params = kwargs.get("request_params", None)
369
- human_input = kwargs.get("human_input", False)
370
-
371
- # Create base request params
372
- def decorator(func: Callable) -> Callable:
373
- # Create base request params
374
- if (
375
- request_params is not None
376
- or model is not None
377
- or use_history != default_use_history
378
- ):
379
- max_tokens = 4096 if agent_type == AgentType.BASIC else None
380
- params_dict = {"use_history": use_history, "model": model}
381
- if max_tokens:
382
- params_dict["maxTokens"] = max_tokens
383
- if request_params:
384
- params_dict.update(request_params)
385
- base_params = RequestParams(**params_dict)
386
- else:
387
- base_params = RequestParams(use_history=use_history)
388
-
389
- # Create agent configuration
390
- config = AgentConfig(
391
- name=name,
392
- instruction=instruction,
393
- servers=servers,
394
- model=model,
395
- use_history=use_history,
396
- default_request_params=base_params,
397
- human_input=human_input,
398
- )
399
-
400
- # Build agent/workflow specific data
401
- agent_data = {
402
- "config": config,
403
- "type": agent_type.value,
404
- "func": func,
405
- }
406
-
407
- # Add extra parameters specific to this agent type
408
- for key, value in kwargs.items():
409
- if key not in [
410
- "name",
411
- "instruction",
412
- "servers",
413
- "model",
414
- "use_history",
415
- "request_params",
416
- "human_input",
417
- ]:
418
- agent_data[key] = value
419
-
420
- # Store the configuration under the agent name
421
- self.agents[name] = agent_data
422
-
423
- # Either wrap or return the original function
424
- if wrapper_needed:
425
-
426
- async def wrapper(*args, **kwargs):
427
- return await func(*args, **kwargs)
428
-
429
- return wrapper
430
- return func
431
-
432
- return decorator
433
-
434
- return decorator_wrapper
435
-
436
- def agent(
437
- self,
438
- name: str = "Agent",
439
- instruction_or_kwarg: str = None,
440
- *,
441
- instruction: str = "You are a helpful agent.",
442
- servers: List[str] = [],
443
- model: str | None = None,
444
- use_history: bool = True,
445
- request_params: Optional[Dict] = None,
446
- human_input: bool = False,
447
- ) -> Callable:
448
- """
449
- Decorator to create and register an agent with configuration.
450
-
451
- Args:
452
- name: Name of the agent
453
- instruction_or_kwarg: Optional positional parameter for instruction
454
- instruction: Base instruction for the agent (keyword arg)
455
- servers: List of server names the agent should connect to
456
- model: Model specification string (highest precedence)
457
- use_history: Whether to maintain conversation history
458
- request_params: Additional request parameters for the LLM
459
- human_input: Whether to enable human input capabilities
460
-
461
- The instruction can be provided either as a second positional argument
462
- or as a keyword argument. Positional argument takes precedence when both are provided.
463
-
464
- Usage:
465
- @fast.agent("agent_name", "Your instruction here") # Using positional arg
466
- @fast.agent("agent_name", instruction="Your instruction here") # Using keyword arg
467
- """
468
- # Use positional argument if provided, otherwise use keyword argument
469
- final_instruction = (
470
- instruction_or_kwarg if instruction_or_kwarg is not None else instruction
471
- )
472
-
473
- decorator = self._create_decorator(
474
- AgentType.BASIC,
475
- default_name="Agent",
476
- default_instruction="You are a helpful agent.",
477
- default_use_history=True,
478
- )(
479
- name=name,
480
- instruction=final_instruction,
481
- servers=servers,
482
- model=model,
483
- use_history=use_history,
484
- request_params=request_params,
485
- human_input=human_input,
486
- )
487
- return decorator
488
-
489
- def orchestrator(
490
- self,
491
- name: str = "Orchestrator",
492
- *,
493
- instruction: str | None = None,
494
- agents: List[str],
495
- model: str | None = None,
496
- use_history: bool = False,
497
- request_params: Optional[Dict] = None,
498
- human_input: bool = False,
499
- plan_type: Literal["full", "iterative"] = "full",
500
- max_iterations: int = 30, # Add the max_iterations parameter with default value
501
- ) -> Callable:
502
- """
503
- Decorator to create and register an orchestrator.
504
-
505
- Args:
506
- name: Name of the orchestrator
507
- instruction: Base instruction for the orchestrator
508
- agents: List of agent names this orchestrator can use
509
- model: Model specification string (highest precedence)
510
- use_history: Whether to maintain conversation history (forced false)
511
- request_params: Additional request parameters for the LLM
512
- human_input: Whether to enable human input capabilities
513
- plan_type: Planning approach - "full" generates entire plan first, "iterative" plans one step at a time
514
- max_iterations: Maximum number of planning iterations (default: 10)
515
- """
516
- default_instruction = """
517
- You are an expert planner. Given an objective task and a list of MCP servers (which are collections of tools)
518
- or Agents (which are collections of servers), your job is to break down the objective into a series of steps,
519
- which can be performed by LLMs with access to the servers or agents.
520
- """
521
-
522
- # Handle request_params update with max_iterations
523
- if request_params is None:
524
- request_params = {"max_iterations": max_iterations}
525
- elif isinstance(request_params, dict):
526
- if "max_iterations" not in request_params:
527
- request_params["max_iterations"] = max_iterations
528
-
529
- decorator = self._create_decorator(
530
- AgentType.ORCHESTRATOR,
531
- default_name="Orchestrator",
532
- default_instruction=default_instruction,
533
- default_servers=[],
534
- default_use_history=False,
535
- )(
536
- name=name,
537
- instruction=instruction,
538
- child_agents=agents,
160
+ # Wrap the factory function to use our context and CLI model
161
+ return get_model_factory(
162
+ self.context,
539
163
  model=model,
540
- use_history=use_history,
541
164
  request_params=request_params,
542
- human_input=human_input,
543
- plan_type=plan_type,
165
+ cli_model=self.args.model if hasattr(self, "args") else None,
544
166
  )
545
- return decorator
546
-
547
- def parallel(
548
- self,
549
- name: str,
550
- fan_out: List[str],
551
- fan_in: Optional[str] = None,
552
- instruction: str = "",
553
- model: str | None = None,
554
- use_history: bool = True,
555
- request_params: Optional[Dict] = None,
556
- include_request: bool = True,
557
- ) -> Callable:
558
- """
559
- Decorator to create and register a parallel executing agent.
560
-
561
- Args:
562
- name: Name of the parallel executing agent
563
- fan_out: List of parallel execution agents
564
- fan_in: Optional name of collecting agent. If not provided, a passthrough agent
565
- will be created automatically with the name "{name}_fan_in"
566
- instruction: Optional instruction for the parallel agent
567
- model: Model specification string
568
- use_history: Whether to maintain conversation history
569
- request_params: Additional request parameters for the LLM
570
- include_request: Whether to include the original request in the fan-in message
571
- """
572
- # If fan_in is not provided, create a passthrough agent with a derived name
573
- if fan_in is None:
574
- passthrough_name = f"{name}_fan_in"
575
-
576
- # Register the passthrough agent directly in self.agents
577
- self.agents[passthrough_name] = {
578
- "config": AgentConfig(
579
- name=passthrough_name,
580
- instruction=f"Passthrough fan-in for {name}",
581
- servers=[],
582
- use_history=use_history,
583
- ),
584
- "type": AgentType.BASIC.value, # Using BASIC type since we're just attaching a PassthroughLLM
585
- "func": lambda x: x, # Simple passthrough function (never actually called)
586
- }
587
-
588
- # Use this passthrough as the fan-in
589
- fan_in = passthrough_name
590
-
591
- decorator = self._create_decorator(
592
- AgentType.PARALLEL,
593
- default_instruction="",
594
- default_servers=[],
595
- default_use_history=True,
596
- )(
597
- name=name,
598
- fan_in=fan_in,
599
- fan_out=fan_out,
600
- instruction=instruction,
601
- model=model,
602
- use_history=use_history,
603
- request_params=request_params,
604
- include_request=include_request,
605
- )
606
- return decorator
607
-
608
- def evaluator_optimizer(
609
- self,
610
- name: str,
611
- generator: str,
612
- evaluator: str,
613
- min_rating: str = "GOOD",
614
- max_refinements: int = 3,
615
- use_history: bool = True,
616
- request_params: Optional[Dict] = None,
617
- instruction: Optional[str] = None,
618
- ) -> Callable:
619
- """
620
- Decorator to create and register an evaluator-optimizer workflow.
621
-
622
- Args:
623
- name: Name of the workflow
624
- generator: Name of the generator agent
625
- evaluator: Name of the evaluator agent
626
- min_rating: Minimum acceptable quality rating (EXCELLENT, GOOD, FAIR, POOR)
627
- max_refinements: Maximum number of refinement iterations
628
- use_history: Whether to maintain conversation history
629
- request_params: Additional request parameters for the LLM
630
- instruction: Optional instruction for the workflow (if not provided, uses generator's instruction)
631
- """
632
- decorator = self._create_decorator(
633
- AgentType.EVALUATOR_OPTIMIZER,
634
- default_instruction="", # We'll get instruction from generator or override
635
- default_servers=[],
636
- default_use_history=True,
637
- wrapper_needed=True,
638
- )(
639
- name=name,
640
- generator=generator,
641
- evaluator=evaluator,
642
- min_rating=min_rating,
643
- max_refinements=max_refinements,
644
- use_history=use_history,
645
- request_params=request_params,
646
- instruction=instruction, # Pass through any custom instruction
647
- )
648
- return decorator
649
-
650
- def router(
651
- self,
652
- name: str,
653
- agents: List[str],
654
- # servers: List[str] = [],
655
- model: Optional[str] = None,
656
- use_history: bool = True,
657
- request_params: Optional[Dict] = None,
658
- human_input: bool = False,
659
- ) -> Callable:
660
- """
661
- Decorator to create and register a router.
662
-
663
- Args:
664
- name: Name of the router
665
- agents: List of agent names this router can delegate to
666
- servers: List of server names the router can use directly (currently not supported)
667
- model: Model specification string
668
- use_history: Whether to maintain conversation history
669
- request_params: Additional request parameters for the LLM
670
- human_input: Whether to enable human input capabilities
671
- """
672
- decorator = self._create_decorator(
673
- AgentType.ROUTER,
674
- default_instruction="",
675
- default_servers=[],
676
- default_use_history=False,
677
- wrapper_needed=True,
678
- )(
679
- name=name,
680
- agents=agents,
681
- model=model,
682
- use_history=use_history,
683
- request_params=request_params,
684
- human_input=human_input,
685
- )
686
- return decorator
687
-
688
- def chain(
689
- self,
690
- name: str = "Chain",
691
- *,
692
- sequence: List[str] = None,
693
- agents: List[str] = None, # Alias for sequence
694
- instruction: str = None,
695
- model: str | None = None,
696
- use_history: bool = True,
697
- request_params: Optional[Dict] = None,
698
- continue_with_final: bool = True,
699
- cumulative: bool = False,
700
- ) -> Callable:
701
- """
702
- Decorator to create and register a chain of agents.
703
-
704
- Args:
705
- name: Name of the chain
706
- sequence: List of agent names in order of execution (preferred name)
707
- agents: Alias for sequence (backwards compatibility)
708
- instruction: Optional custom instruction for the chain (if none provided, will autogenerate based on sequence)
709
- model: Model specification string (not used directly in chain)
710
- use_history: Whether to maintain conversation history
711
- request_params: Additional request parameters
712
- continue_with_final: When using prompt(), whether to continue with the final agent after processing chain (default: True)
713
- cumulative: When True, each agent receives all previous agent responses concatenated (default: False)
714
- When False, each agent only gets the output of the previous agent (default behavior)
715
- """
716
- # Support both parameter names
717
- agent_sequence = sequence or agents
718
- if agent_sequence is None:
719
- raise ValueError("Either 'sequence' or 'agents' parameter must be provided")
720
-
721
- # Auto-generate instruction if not provided
722
- if instruction is None:
723
- # Generate an appropriate instruction based on mode
724
- if cumulative:
725
- instruction = f"Cumulative chain of agents: {', '.join(agent_sequence)}"
726
- else:
727
- instruction = f"Chain of agents: {', '.join(agent_sequence)}"
728
-
729
- decorator = self._create_decorator(
730
- AgentType.CHAIN,
731
- default_name="Chain",
732
- default_instruction=instruction,
733
- default_use_history=True,
734
- wrapper_needed=True,
735
- )(
736
- name=name,
737
- sequence=agent_sequence,
738
- instruction=instruction,
739
- model=model,
740
- use_history=use_history,
741
- request_params=request_params,
742
- continue_with_final=continue_with_final,
743
- cumulative=cumulative,
744
- )
745
- return decorator
746
-
747
- def passthrough(
748
- self, name: str = "Passthrough", use_history: bool = True, **kwargs
749
- ) -> Callable:
750
- """
751
- Decorator to create and register a passthrough agent.
752
- A passthrough agent simply returns any input message without modification.
753
-
754
- This is useful for parallel workflows where no fan-in aggregation is needed
755
- (the fan-in agent can be a passthrough that simply returns the combined outputs).
756
-
757
- Args:
758
- name: Name of the passthrough agent
759
- use_history: Whether to maintain conversation history
760
- **kwargs: Additional parameters (ignored, for compatibility)
761
- """
762
- decorator = self._create_decorator(
763
- AgentType.BASIC, # Using BASIC agent type since we'll use a regular agent with PassthroughLLM
764
- default_name="Passthrough",
765
- default_instruction="Passthrough agent that returns input without modification",
766
- default_use_history=use_history,
767
- wrapper_needed=True,
768
- )(
769
- name=name,
770
- use_history=use_history,
771
- )
772
- return decorator
773
167
 
774
168
  async def _create_agents_by_type(
775
169
  self,
@@ -790,261 +184,17 @@ class FastAgent(ContextDependent):
790
184
  Returns:
791
185
  Dictionary of initialized agents wrapped in appropriate proxies
792
186
  """
793
- if active_agents is None:
794
- active_agents = {}
795
-
796
- # Create a dictionary to store the initialized agents
797
- result_agents = {}
798
-
799
- # Get all agents of the specified type
800
- for name, agent_data in self.agents.items():
801
- if agent_data["type"] == agent_type.value:
802
- # Get common configuration
803
- config = agent_data["config"]
804
-
805
- # Type-specific initialization
806
- if agent_type == AgentType.BASIC:
807
- # Get the agent name for special handling
808
- agent_name = agent_data["config"].name
809
-
810
- # Check if this is an agent that should use the PassthroughLLM
811
- if agent_name.endswith("_fan_in") or agent_name.startswith(
812
- "passthrough"
813
- ):
814
- # Import here to avoid circular imports
815
- from mcp_agent.workflows.llm.augmented_llm import PassthroughLLM
816
-
817
- # Create basic agent with configuration
818
- agent = Agent(config=config, context=agent_app.context)
819
-
820
- # Set up a PassthroughLLM directly
821
- async with agent:
822
- agent._llm = PassthroughLLM(
823
- name=f"{config.name}_llm",
824
- context=agent_app.context,
825
- agent=agent,
826
- default_request_params=config.default_request_params,
827
- )
828
-
829
- # Store the agent
830
- instance = agent
831
- else:
832
- # Standard basic agent with LLM
833
- agent = Agent(config=config, context=agent_app.context)
834
-
835
- # Set up LLM with proper configuration
836
- async with agent:
837
- llm_factory = self._get_model_factory(
838
- model=config.model,
839
- request_params=config.default_request_params,
840
- )
841
- agent._llm = await agent.attach_llm(llm_factory)
842
-
843
- # Store the agent
844
- instance = agent
845
-
846
- elif agent_type == AgentType.ORCHESTRATOR:
847
- # Get base params configured with model settings
848
- base_params = (
849
- config.default_request_params.model_copy()
850
- if config.default_request_params
851
- else RequestParams()
852
- )
853
- base_params.use_history = False # Force no history for orchestrator
854
-
855
- # Get the child agents - need to unwrap proxies and validate LLM config
856
- child_agents = []
857
- for agent_name in agent_data["child_agents"]:
858
- proxy = active_agents[agent_name]
859
- instance = self._unwrap_proxy(proxy)
860
- # Validate basic agents have LLM
861
- if isinstance(instance, Agent):
862
- if not hasattr(instance, "_llm") or not instance._llm:
863
- raise AgentConfigError(
864
- f"Agent '{agent_name}' used by orchestrator '{name}' missing LLM configuration",
865
- "All agents must be fully configured with LLMs before being used in an orchestrator",
866
- )
867
- child_agents.append(instance)
868
-
869
- # Create a properly configured planner agent
870
- planner_config = AgentConfig(
871
- name=f"{name}", # Use orchestrator name as prefix
872
- instruction=config.instruction
873
- or """
874
- You are an expert planner. Given an objective task and a list of MCP servers (which are collections of tools)
875
- or Agents (which are collections of servers), your job is to break down the objective into a series of steps,
876
- which can be performed by LLMs with access to the servers or agents.
877
- """,
878
- servers=[], # Planner doesn't need server access
879
- model=config.model,
880
- default_request_params=base_params,
881
- )
882
- planner_agent = Agent(
883
- config=planner_config, context=agent_app.context
884
- )
885
- planner_factory = self._get_model_factory(
886
- model=config.model,
887
- request_params=config.default_request_params,
888
- )
889
-
890
- async with planner_agent:
891
- planner = await planner_agent.attach_llm(planner_factory)
892
-
893
- # Create the orchestrator with pre-configured planner
894
- instance = Orchestrator(
895
- name=config.name,
896
- planner=planner, # Pass pre-configured planner
897
- available_agents=child_agents,
898
- context=agent_app.context,
899
- request_params=planner.default_request_params, # Base params already include model settings
900
- plan_type=agent_data.get(
901
- "plan_type", "full"
902
- ), # Get plan_type from agent_data
903
- verb=ProgressAction.PLANNING,
904
- )
905
-
906
- elif agent_type == AgentType.EVALUATOR_OPTIMIZER:
907
- # Get the referenced agents - unwrap from proxies
908
- generator = self._unwrap_proxy(
909
- active_agents[agent_data["generator"]]
910
- )
911
- evaluator = self._unwrap_proxy(
912
- active_agents[agent_data["evaluator"]]
913
- )
914
-
915
- if not generator or not evaluator:
916
- raise ValueError(
917
- f"Missing agents for workflow {name}: "
918
- f"generator={agent_data['generator']}, "
919
- f"evaluator={agent_data['evaluator']}"
920
- )
921
-
922
- # Get model from generator if it's an Agent, or from config otherwise
923
- optimizer_model = None
924
- if isinstance(generator, Agent):
925
- optimizer_model = generator.config.model
926
- elif hasattr(generator, '_sequence') and hasattr(generator, '_agent_proxies'):
927
- # For ChainProxy, use the config model directly
928
- optimizer_model = config.model
929
-
930
- instance = EvaluatorOptimizerLLM(
931
- name=config.name, # Pass name from config
932
- generator=generator,
933
- evaluator=evaluator,
934
- min_rating=QualityRating[agent_data["min_rating"]],
935
- max_refinements=agent_data["max_refinements"],
936
- llm_factory=self._get_model_factory(model=optimizer_model),
937
- context=agent_app.context,
938
- instruction=config.instruction, # Pass any custom instruction
939
- )
940
-
941
- elif agent_type == AgentType.ROUTER:
942
- # Get the router's agents - unwrap proxies
943
- router_agents = self._get_agent_instances(
944
- agent_data["agents"], active_agents
945
- )
946
-
947
- # Create the router with proper configuration
948
- llm_factory = self._get_model_factory(
949
- model=config.model,
950
- request_params=config.default_request_params,
951
- )
952
-
953
- instance = LLMRouter(
954
- name=config.name,
955
- llm_factory=llm_factory,
956
- agents=router_agents,
957
- server_names=config.servers,
958
- context=agent_app.context,
959
- default_request_params=config.default_request_params,
960
- verb=ProgressAction.ROUTING, # Set verb for progress display
961
- )
962
-
963
- elif agent_type == AgentType.CHAIN:
964
- # Get the sequence from either parameter
965
- sequence = agent_data.get("sequence", agent_data.get("agents", []))
966
-
967
- # Auto-generate instruction if not provided or if it's just the default
968
- default_instruction = f"Chain of agents: {', '.join(sequence)}"
969
-
970
- # If user provided a custom instruction, use that
971
- # Otherwise, generate a description based on the sequence and their servers
972
- if config.instruction == default_instruction:
973
- # Get all agent names in the sequence
974
- agent_names = []
975
- all_servers = set()
976
-
977
- # Collect information about the agents and their servers
978
- for agent_name in sequence:
979
- if agent_name in active_agents:
980
- agent_proxy = active_agents[agent_name]
981
- if hasattr(agent_proxy, "_agent"):
982
- # For LLMAgentProxy
983
- agent_instance = agent_proxy._agent
984
- agent_names.append(agent_name)
985
- if hasattr(agent_instance, "server_names"):
986
- all_servers.update(agent_instance.server_names)
987
- elif hasattr(agent_proxy, "_workflow"):
988
- # For WorkflowProxy
989
- agent_names.append(agent_name)
990
-
991
- # Generate a better description
992
- if agent_names:
993
- server_part = (
994
- f" with access to servers: {', '.join(sorted(all_servers))}"
995
- if all_servers
996
- else ""
997
- )
998
- config.instruction = f"Sequence of agents: {', '.join(agent_names)}{server_part}."
999
-
1000
- # Create a ChainProxy without needing a new instance
1001
- # Just pass the agent proxies and sequence
1002
- instance = ChainProxy(self.app, name, sequence, active_agents)
1003
- # Set continue_with_final behavior from configuration
1004
- instance._continue_with_final = agent_data.get(
1005
- "continue_with_final", True
1006
- )
1007
- # Set cumulative behavior from configuration
1008
- instance._cumulative = agent_data.get(
1009
- "cumulative", False
1010
- )
1011
-
1012
- # We removed the AgentType.PASSTHROUGH case
1013
- # Passthrough agents are now created as BASIC agents with a special LLM
1014
-
1015
- elif agent_type == AgentType.PARALLEL:
1016
- # Get fan-out agents (could be basic agents or other parallels)
1017
- fan_out_agents = self._get_agent_instances(
1018
- agent_data["fan_out"], active_agents
1019
- )
1020
-
1021
- # Get fan-in agent - unwrap proxy
1022
- fan_in_agent = self._unwrap_proxy(
1023
- active_agents[agent_data["fan_in"]]
1024
- )
1025
-
1026
- # Create the parallel workflow
1027
- llm_factory = self._get_model_factory(config.model)
1028
- instance = ParallelLLM(
1029
- name=config.name,
1030
- instruction=config.instruction,
1031
- fan_out_agents=fan_out_agents,
1032
- fan_in_agent=fan_in_agent,
1033
- context=agent_app.context,
1034
- llm_factory=llm_factory,
1035
- default_request_params=config.default_request_params,
1036
- include_request=agent_data.get("include_request", True),
1037
- )
1038
-
1039
- else:
1040
- raise ValueError(f"Unsupported agent type: {agent_type}")
1041
-
1042
- # Create the appropriate proxy and store in results
1043
- result_agents[name] = self._create_proxy(
1044
- name, instance, agent_type.value
1045
- )
187
+ # Create a model factory function that we can pass to the factory module
188
+ model_factory_func = partial(self._get_model_factory)
1046
189
 
1047
- return result_agents
190
+ return await create_agents_by_type(
191
+ agent_app,
192
+ self.agents,
193
+ agent_type,
194
+ active_agents,
195
+ model_factory_func=model_factory_func,
196
+ **kwargs,
197
+ )
1048
198
 
1049
199
  async def _create_basic_agents(self, agent_app: MCPApp) -> ProxyDict:
1050
200
  """
@@ -1056,7 +206,8 @@ class FastAgent(ContextDependent):
1056
206
  Returns:
1057
207
  Dictionary of initialized basic agents wrapped in appropriate proxies
1058
208
  """
1059
- return await self._create_agents_by_type(agent_app, AgentType.BASIC)
209
+ model_factory_func = partial(self._get_model_factory)
210
+ return await create_basic_agents(agent_app, self.agents, model_factory_func)
1060
211
 
1061
212
  async def _create_orchestrators(
1062
213
  self, agent_app: MCPApp, active_agents: ProxyDict
@@ -1092,83 +243,27 @@ class FastAgent(ContextDependent):
1092
243
  agent_app, AgentType.EVALUATOR_OPTIMIZER, active_agents
1093
244
  )
1094
245
 
1095
- def _get_dependencies(
1096
- self, name: str, visited: set, path: set, agent_type: AgentType = None
1097
- ) -> List[str]:
1098
- """
1099
- Get dependencies for an agent in topological order.
1100
- Works for both Parallel and Chain workflows.
1101
-
1102
- Args:
1103
- name: Name of the agent
1104
- visited: Set of already visited agents
1105
- path: Current path for cycle detection
1106
- agent_type: Optional type filter (e.g., only check Parallel or Chain)
1107
-
1108
- Returns:
1109
- List of agent names in dependency order
1110
-
1111
- Raises:
1112
- ValueError: If circular dependency detected
1113
- """
1114
- if name in path:
1115
- path_str = " -> ".join(path)
1116
- raise CircularDependencyError(f"Path: {path_str} -> {name}")
1117
-
1118
- if name in visited:
1119
- return []
1120
-
1121
- if name not in self.agents:
1122
- return []
1123
-
1124
- config = self.agents[name]
1125
-
1126
- # Skip if not the requested type (when filtering)
1127
- if agent_type and config["type"] != agent_type.value:
1128
- return []
1129
-
1130
- path.add(name)
1131
- deps = []
1132
-
1133
- # Handle dependencies based on agent type
1134
- if config["type"] == AgentType.PARALLEL.value:
1135
- # Get dependencies from fan-out agents
1136
- for fan_out in config["fan_out"]:
1137
- deps.extend(self._get_dependencies(fan_out, visited, path, agent_type))
1138
- elif config["type"] == AgentType.CHAIN.value:
1139
- # Get dependencies from sequence agents
1140
- sequence = config.get("sequence", config.get("agents", []))
1141
- for agent_name in sequence:
1142
- deps.extend(
1143
- self._get_dependencies(agent_name, visited, path, agent_type)
1144
- )
1145
-
1146
- # Add this agent after its dependencies
1147
- deps.append(name)
1148
- visited.add(name)
1149
- path.remove(name)
1150
-
1151
- return deps
1152
-
1153
- def _get_parallel_dependencies(
1154
- self, name: str, visited: set, path: set
1155
- ) -> List[str]:
246
+ async def _create_parallel_agents(
247
+ self, agent_app: MCPApp, active_agents: ProxyDict
248
+ ) -> ProxyDict:
1156
249
  """
1157
- Get dependencies for a parallel agent in topological order.
1158
- Legacy function that calls the more general _get_dependencies.
250
+ Create parallel execution agents in dependency order.
1159
251
 
1160
252
  Args:
1161
- name: Name of the parallel agent
1162
- visited: Set of already visited agents
1163
- path: Current path for cycle detection
253
+ agent_app: The main application instance
254
+ active_agents: Dictionary of already created agents/proxies
1164
255
 
1165
256
  Returns:
1166
- List of agent names in dependency order
1167
-
1168
- Raises:
1169
- ValueError: If circular dependency detected
257
+ Dictionary of initialized parallel agents
1170
258
  """
1171
- return self._get_dependencies(name, visited, path, AgentType.PARALLEL)
259
+ model_factory_func = partial(self._get_model_factory)
260
+ return await create_agents_in_dependency_order(
261
+ agent_app,
262
+ self.agents,
263
+ active_agents,
264
+ AgentType.PARALLEL,
265
+ model_factory_func,
266
+ )
1172
267
 
1173
268
  async def _create_agents_in_dependency_order(
1174
269
  self, agent_app: MCPApp, active_agents: ProxyDict, agent_type: AgentType
@@ -1185,59 +280,9 @@ class FastAgent(ContextDependent):
1185
280
  Returns:
1186
281
  Dictionary of initialized agents
1187
282
  """
1188
- result_agents = {}
1189
- visited = set()
1190
-
1191
- # Get all agents of the specified type
1192
- agent_names = [
1193
- name
1194
- for name, agent_data in self.agents.items()
1195
- if agent_data["type"] == agent_type.value
1196
- ]
1197
-
1198
- # Create agents in dependency order
1199
- for name in agent_names:
1200
- # Get ordered dependencies if not already processed
1201
- if name not in visited:
1202
- try:
1203
- ordered_agents = self._get_dependencies(
1204
- name, visited, set(), agent_type
1205
- )
1206
- except ValueError as e:
1207
- raise ValueError(
1208
- f"Error creating {agent_type.name.lower()} agent {name}: {str(e)}"
1209
- )
1210
-
1211
- # Create each agent in order
1212
- for agent_name in ordered_agents:
1213
- if agent_name not in result_agents:
1214
- # Create one agent at a time using the generic method
1215
- agent_result = await self._create_agents_by_type(
1216
- agent_app,
1217
- agent_type,
1218
- active_agents,
1219
- agent_name=agent_name,
1220
- )
1221
- if agent_name in agent_result:
1222
- result_agents[agent_name] = agent_result[agent_name]
1223
-
1224
- return result_agents
1225
-
1226
- async def _create_parallel_agents(
1227
- self, agent_app: MCPApp, active_agents: ProxyDict
1228
- ) -> ProxyDict:
1229
- """
1230
- Create parallel execution agents in dependency order.
1231
-
1232
- Args:
1233
- agent_app: The main application instance
1234
- active_agents: Dictionary of already created agents/proxies
1235
-
1236
- Returns:
1237
- Dictionary of initialized parallel agents
1238
- """
1239
- return await self._create_agents_in_dependency_order(
1240
- agent_app, active_agents, AgentType.PARALLEL
283
+ model_factory_func = partial(self._get_model_factory)
284
+ return await create_agents_in_dependency_order(
285
+ agent_app, self.agents, active_agents, agent_type, model_factory_func
1241
286
  )
1242
287
 
1243
288
  async def _create_routers(
@@ -1257,33 +302,6 @@ class FastAgent(ContextDependent):
1257
302
  agent_app, AgentType.ROUTER, active_agents
1258
303
  )
1259
304
 
1260
- def _unwrap_proxy(self, proxy: BaseAgentProxy) -> AgentOrWorkflow:
1261
- """
1262
- Unwrap a proxy to get the underlying agent or workflow instance.
1263
-
1264
- Args:
1265
- proxy: The proxy object to unwrap
1266
-
1267
- Returns:
1268
- The underlying Agent or workflow instance
1269
- """
1270
- return unwrap_proxy(proxy)
1271
-
1272
- def _get_agent_instances(
1273
- self, agent_names: List[str], active_agents: ProxyDict
1274
- ) -> List[AgentOrWorkflow]:
1275
- """
1276
- Get list of actual agent/workflow instances from a list of names.
1277
-
1278
- Args:
1279
- agent_names: List of agent names to look up
1280
- active_agents: Dictionary of active agent proxies
1281
-
1282
- Returns:
1283
- List of unwrapped agent/workflow instances
1284
- """
1285
- return get_agent_instances(agent_names, active_agents)
1286
-
1287
305
  @asynccontextmanager
1288
306
  async def run(self):
1289
307
  """
@@ -1315,8 +333,8 @@ class FastAgent(ContextDependent):
1315
333
  progress_display.stop() # This will stop and hide the display
1316
334
 
1317
335
  # Pre-flight validation
1318
- self._validate_server_references()
1319
- self._validate_workflow_references()
336
+ validate_server_references(self.context, self.agents)
337
+ validate_workflow_references(self.agents)
1320
338
 
1321
339
  # Create all types of agents in dependency order
1322
340
  # First create basic agents
@@ -1337,7 +355,7 @@ class FastAgent(ContextDependent):
1337
355
  agent_app, active_agents, AgentType.CHAIN
1338
356
  )
1339
357
  active_agents.update(chains)
1340
-
358
+
1341
359
  # Now create evaluator-optimizers AFTER chains are available
1342
360
  evaluator_optimizers = await self._create_evaluator_optimizers(
1343
361
  agent_app, active_agents
@@ -1471,5 +489,68 @@ class FastAgent(ContextDependent):
1471
489
  handle_error(e, error_type, suggestion)
1472
490
 
1473
491
  def _log_agent_load(self, agent_name: str) -> None:
1474
- # Using the imported function
1475
- log_agent_load(self.app, agent_name)
492
+ # This function is no longer needed - agent loading is now handled in factory.py
493
+ pass
494
+
495
+ def create_mcp_server(
496
+ self,
497
+ agent_app_instance: AgentApp,
498
+ server_name: str = None,
499
+ server_description: str = None,
500
+ ) -> AgentMCPServer:
501
+ """
502
+ Create an MCP server that exposes the agents as tools.
503
+
504
+ Args:
505
+ agent_app_instance: The AgentApp instance with initialized agents
506
+ server_name: Optional custom name for the MCP server
507
+ server_description: Optional description for the MCP server
508
+
509
+ Returns:
510
+ An AgentMCPServer instance ready to be run
511
+ """
512
+ return AgentMCPServer(
513
+ agent_app=agent_app_instance,
514
+ server_name=server_name or f"{self.name}-MCP-Server",
515
+ server_description=server_description,
516
+ )
517
+
518
+ async def run_with_mcp_server(
519
+ self,
520
+ transport: str = "sse",
521
+ host: str = "0.0.0.0",
522
+ port: int = 8000,
523
+ server_name: str = None,
524
+ server_description: str = None,
525
+ ):
526
+ """
527
+ Run the FastAgent application and expose agents through an MCP server.
528
+
529
+ Args:
530
+ transport: Transport protocol to use ("stdio" or "sse")
531
+ host: Host address for the server when using SSE
532
+ port: Port for the server when using SSE
533
+ server_name: Optional custom name for the MCP server
534
+ server_description: Optional description for the MCP server
535
+ """
536
+ async with self.run() as agent_app:
537
+ # Create the MCP server
538
+ mcp_server = self.create_mcp_server(
539
+ agent_app_instance=agent_app,
540
+ server_name=server_name,
541
+ server_description=server_description,
542
+ )
543
+
544
+ # Run the MCP server in a separate task
545
+ server_task = asyncio.create_task(
546
+ mcp_server.run_async(transport=transport, host=host, port=port)
547
+ )
548
+
549
+ try:
550
+ # Wait for the server task to complete (or be cancelled)
551
+ await server_task
552
+ except asyncio.CancelledError:
553
+ # Propagate cancellation
554
+ server_task.cancel()
555
+ await asyncio.gather(server_task, return_exceptions=True)
556
+ raise