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,8 +1,11 @@
1
- from typing import Any, List, Optional, Type, Union
2
1
  import json # Import at the module level
2
+ from typing import Any, List, Optional, Type, Union
3
+
3
4
  from mcp import GetPromptResult
4
5
  from mcp.types import PromptMessage
5
6
  from pydantic_core import from_json
7
+
8
+ from mcp_agent.logging.logger import get_logger
6
9
  from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
7
10
  from mcp_agent.workflows.llm.augmented_llm import (
8
11
  AugmentedLLM,
@@ -11,7 +14,6 @@ from mcp_agent.workflows.llm.augmented_llm import (
11
14
  ModelT,
12
15
  RequestParams,
13
16
  )
14
- from mcp_agent.logging.logger import get_logger
15
17
 
16
18
 
17
19
  class PassthroughLLM(AugmentedLLM):
@@ -23,7 +25,7 @@ class PassthroughLLM(AugmentedLLM):
23
25
  parallel workflow where no fan-in aggregation is needed.
24
26
  """
25
27
 
26
- def __init__(self, name: str = "Passthrough", context=None, **kwargs):
28
+ def __init__(self, name: str = "Passthrough", context=None, **kwargs) -> None:
27
29
  super().__init__(name=name, context=context, **kwargs)
28
30
  self.provider = "fast-agent"
29
31
  # Initialize logger - keep it simple without name reference
@@ -61,6 +63,9 @@ class PassthroughLLM(AugmentedLLM):
61
63
 
62
64
  return str(message)
63
65
 
66
+ async def initialize(self) -> None:
67
+ pass
68
+
64
69
  async def _call_tool_and_return_result(self, command: str) -> str:
65
70
  """
66
71
  Call a tool based on the command and return its result as a string.
@@ -94,9 +99,7 @@ class PassthroughLLM(AugmentedLLM):
94
99
  """
95
100
  parts = command.split(" ", 2)
96
101
  if len(parts) < 2:
97
- raise ValueError(
98
- "Invalid format. Expected '***CALL_TOOL <tool_name> [arguments_json]'"
99
- )
102
+ raise ValueError("Invalid format. Expected '***CALL_TOOL <tool_name> [arguments_json]'")
100
103
 
101
104
  tool_name = parts[1].strip()
102
105
  arguments = None
@@ -158,15 +161,9 @@ class PassthroughLLM(AugmentedLLM):
158
161
  elif isinstance(message, str):
159
162
  return response_model.model_validate(from_json(message, allow_partial=True))
160
163
 
161
- async def generate_prompt(
162
- self, prompt: "PromptMessageMultipart", request_params: RequestParams | None
163
- ) -> str:
164
+ async def generate_prompt(self, prompt: "PromptMessageMultipart", request_params: RequestParams | None) -> str:
164
165
  # Check if this prompt contains a tool call command
165
- if (
166
- prompt.content
167
- and prompt.content[0].text
168
- and prompt.content[0].text.startswith("***CALL_TOOL ")
169
- ):
166
+ if prompt.content and prompt.content[0].text and prompt.content[0].text.startswith("***CALL_TOOL "):
170
167
  return await self._call_tool_and_return_result(prompt.content[0].text)
171
168
 
172
169
  # Process all parts of the PromptMessageMultipart
@@ -181,21 +178,40 @@ class PassthroughLLM(AugmentedLLM):
181
178
  # Join all parts and process with generate_str
182
179
  return await self.generate_str("\n".join(parts_text), request_params)
183
180
 
184
- async def apply_prompt_template(
185
- self, prompt_result: GetPromptResult, prompt_name: str
181
+ async def apply_prompt(
182
+ self,
183
+ multipart_messages: List["PromptMessageMultipart"],
184
+ request_params: Optional[RequestParams] = None,
186
185
  ) -> str:
186
+ """
187
+ Apply a list of PromptMessageMultipart messages directly to the LLM.
188
+ In PassthroughLLM, this returns a concatenated string of all message content.
189
+
190
+ Args:
191
+ multipart_messages: List of PromptMessageMultipart objects
192
+ request_params: Optional parameters to configure the LLM request
193
+
194
+ Returns:
195
+ String representation of all message content concatenated together
196
+ """
197
+ # Generate and concatenate result from all messages
198
+ result = ""
199
+ for prompt in multipart_messages:
200
+ result += await self.generate_prompt(prompt, request_params) + "\n"
201
+
202
+ return result
203
+
204
+ async def apply_prompt_template(self, prompt_result: GetPromptResult, prompt_name: str) -> str:
187
205
  """
188
206
  Apply a prompt template by adding it to the conversation history.
189
- If the last message in the prompt is from a user, automatically
190
- generate an assistant response.
207
+ For PassthroughLLM, this returns all content concatenated together.
191
208
 
192
209
  Args:
193
210
  prompt_result: The GetPromptResult containing prompt messages
194
211
  prompt_name: The name of the prompt being applied
195
212
 
196
213
  Returns:
197
- String representation of the assistant's response if generated,
198
- or the last assistant message in the prompt
214
+ String representation of all message content concatenated together
199
215
  """
200
216
  prompt_messages: List[PromptMessage] = prompt_result.messages
201
217
 
@@ -210,3 +226,9 @@ class PassthroughLLM(AugmentedLLM):
210
226
  arguments=arguments,
211
227
  )
212
228
  self._messages = prompt_messages
229
+
230
+ # Convert prompt messages to multipart format
231
+ multipart_messages = PromptMessageMultipart.to_multipart(prompt_messages)
232
+
233
+ # Use apply_prompt to handle the multipart messages
234
+ return await self.apply_prompt(multipart_messages)
@@ -1,9 +1,13 @@
1
- from typing import List, Optional, Union
1
+ from typing import TYPE_CHECKING, List, Optional, Union
2
+
2
3
  from mcp import GetPromptResult
3
- from mcp.types import PromptMessage
4
+
4
5
  from mcp_agent.workflows.llm.augmented_llm import MessageParamT, RequestParams
5
6
  from mcp_agent.workflows.llm.augmented_llm_passthrough import PassthroughLLM
6
7
 
8
+ if TYPE_CHECKING:
9
+ from mcp.types import PromptMessage
10
+
7
11
 
8
12
  # TODO -- support tool calling
9
13
  class PlaybackLLM(PassthroughLLM):
@@ -19,7 +23,7 @@ class PlaybackLLM(PassthroughLLM):
19
23
  been played back, it returns a message indicating that messages are exhausted.
20
24
  """
21
25
 
22
- def __init__(self, name: str = "Playback", **kwargs):
26
+ def __init__(self, name: str = "Playback", **kwargs) -> None:
23
27
  super().__init__(name=name, **kwargs)
24
28
  self._messages: List[PromptMessage] = []
25
29
  self._current_index = 0
@@ -70,9 +74,7 @@ class PlaybackLLM(PassthroughLLM):
70
74
  # If we get here, we've run out of assistant messages
71
75
  return f"MESSAGES EXHAUSTED (list size {len(self._messages)})"
72
76
 
73
- async def apply_prompt_template(
74
- self, prompt_result: GetPromptResult, prompt_name: str
75
- ) -> str:
77
+ async def apply_prompt_template(self, prompt_result: GetPromptResult, prompt_name: str) -> str:
76
78
  """
77
79
  Apply a prompt template by adding its messages to the playback queue.
78
80
 
@@ -0,0 +1,103 @@
1
+ from typing import Generic, List, Protocol, TypeVar
2
+
3
+ # Define our own type variable for implementation use
4
+ MessageParamT = TypeVar("MessageParamT")
5
+
6
+
7
+ class Memory(Protocol, Generic[MessageParamT]):
8
+ """
9
+ Simple memory management for storing past interactions in-memory.
10
+ """
11
+
12
+ # TODO: saqadri - add checkpointing and other advanced memory capabilities
13
+
14
+ def __init__(self) -> None: ...
15
+
16
+ def extend(self, messages: List[MessageParamT], is_prompt: bool = False) -> None: ...
17
+
18
+ def set(self, messages: List[MessageParamT], is_prompt: bool = False) -> None: ...
19
+
20
+ def append(self, message: MessageParamT, is_prompt: bool = False) -> None: ...
21
+
22
+ def get(self, include_history: bool = True) -> List[MessageParamT]: ...
23
+
24
+ def clear(self, clear_prompts: bool = False) -> None: ...
25
+
26
+
27
+ class SimpleMemory(Memory, Generic[MessageParamT]):
28
+ """
29
+ Simple memory management for storing past interactions in-memory.
30
+
31
+ Maintains both prompt messages (which are always included) and
32
+ generated conversation history (which is included based on use_history setting).
33
+ """
34
+
35
+ def __init__(self) -> None:
36
+ self.history: List[MessageParamT] = []
37
+ self.prompt_messages: List[MessageParamT] = [] # Always included
38
+
39
+ def extend(self, messages: List[MessageParamT], is_prompt: bool = False) -> None:
40
+ """
41
+ Add multiple messages to history.
42
+
43
+ Args:
44
+ messages: Messages to add
45
+ is_prompt: If True, add to prompt_messages instead of regular history
46
+ """
47
+ if is_prompt:
48
+ self.prompt_messages.extend(messages)
49
+ else:
50
+ self.history.extend(messages)
51
+
52
+ def set(self, messages: List[MessageParamT], is_prompt: bool = False) -> None:
53
+ """
54
+ Replace messages in history.
55
+
56
+ Args:
57
+ messages: Messages to set
58
+ is_prompt: If True, replace prompt_messages instead of regular history
59
+ """
60
+ if is_prompt:
61
+ self.prompt_messages = messages.copy()
62
+ else:
63
+ self.history = messages.copy()
64
+
65
+ def append(self, message: MessageParamT, is_prompt: bool = False) -> None:
66
+ """
67
+ Add a single message to history.
68
+
69
+ Args:
70
+ message: Message to add
71
+ is_prompt: If True, add to prompt_messages instead of regular history
72
+ """
73
+ if is_prompt:
74
+ self.prompt_messages.append(message)
75
+ else:
76
+ self.history.append(message)
77
+
78
+ def get(self, include_history: bool = True) -> List[MessageParamT]:
79
+ """
80
+ Get all messages in memory.
81
+
82
+ Args:
83
+ include_history: If True, include regular history messages
84
+ If False, only return prompt messages
85
+
86
+ Returns:
87
+ Combined list of prompt messages and optionally history messages
88
+ """
89
+ if include_history:
90
+ return self.prompt_messages + self.history
91
+ else:
92
+ return self.prompt_messages.copy()
93
+
94
+ def clear(self, clear_prompts: bool = False) -> None:
95
+ """
96
+ Clear history and optionally prompt messages.
97
+
98
+ Args:
99
+ clear_prompts: If True, also clear prompt messages
100
+ """
101
+ self.history = []
102
+ if clear_prompts:
103
+ self.prompt_messages = []
@@ -1,16 +1,16 @@
1
1
  from dataclasses import dataclass
2
2
  from enum import Enum, auto
3
- from typing import Optional, Type, Dict, Union, Callable
3
+ from typing import Callable, Dict, Optional, Type, Union
4
4
 
5
5
  from mcp_agent.agents.agent import Agent
6
6
  from mcp_agent.core.exceptions import ModelConfigError
7
+ from mcp_agent.core.request_params import RequestParams
8
+ from mcp_agent.mcp.interfaces import AugmentedLLMProtocol
7
9
  from mcp_agent.workflows.llm.augmented_llm_anthropic import AnthropicAugmentedLLM
8
10
  from mcp_agent.workflows.llm.augmented_llm_openai import OpenAIAugmentedLLM
9
- from mcp_agent.workflows.llm.augmented_llm import RequestParams
10
11
  from mcp_agent.workflows.llm.augmented_llm_passthrough import PassthroughLLM
11
12
  from mcp_agent.workflows.llm.augmented_llm_playback import PlaybackLLM
12
13
 
13
-
14
14
  # Type alias for LLM classes
15
15
  LLMClass = Union[
16
16
  Type[AnthropicAugmentedLLM],
@@ -145,14 +145,10 @@ class ModelFactory:
145
145
  if provider is None:
146
146
  raise ModelConfigError(f"Unknown model: {model_name}")
147
147
 
148
- return ModelConfig(
149
- provider=provider, model_name=model_name, reasoning_effort=reasoning_effort
150
- )
148
+ return ModelConfig(provider=provider, model_name=model_name, reasoning_effort=reasoning_effort)
151
149
 
152
150
  @classmethod
153
- def create_factory(
154
- cls, model_string: str, request_params: Optional[RequestParams] = None
155
- ) -> Callable[..., LLMClass]:
151
+ def create_factory(cls, model_string: str, request_params: Optional[RequestParams] = None) -> Callable[..., AugmentedLLMProtocol]:
156
152
  """
157
153
  Creates a factory function that follows the attach_llm protocol.
158
154
 
@@ -173,23 +169,15 @@ class ModelFactory:
173
169
  # Create a factory function matching the attach_llm protocol
174
170
  def factory(agent: Agent, **kwargs) -> LLMClass:
175
171
  # Create merged params with parsed model name
176
- factory_params = (
177
- request_params.model_copy() if request_params else RequestParams()
178
- )
179
- factory_params.model = (
180
- config.model_name
181
- ) # Use the parsed model name, not the alias
172
+ factory_params = request_params.model_copy() if request_params else RequestParams()
173
+ factory_params.model = config.model_name # Use the parsed model name, not the alias
182
174
 
183
175
  # Merge with any provided default_request_params
184
176
  if "default_request_params" in kwargs and kwargs["default_request_params"]:
185
177
  params_dict = factory_params.model_dump()
186
- params_dict.update(
187
- kwargs["default_request_params"].model_dump(exclude_unset=True)
188
- )
178
+ params_dict.update(kwargs["default_request_params"].model_dump(exclude_unset=True))
189
179
  factory_params = RequestParams(**params_dict)
190
- factory_params.model = (
191
- config.model_name
192
- ) # Ensure parsed model name isn't overwritten
180
+ factory_params.model = config.model_name # Ensure parsed model name isn't overwritten
193
181
 
194
182
  # Forward all keyword arguments to LLM constructor
195
183
  llm_args = {
@@ -5,7 +5,7 @@ This file provides backward compatibility with the existing API while
5
5
  delegating to the proper implementations in the providers/ directory.
6
6
  """
7
7
 
8
- from typing import Dict, Any, Union
8
+ from typing import Any, Dict, Union
9
9
 
10
10
  from openai.types.chat import (
11
11
  ChatCompletionMessage,
@@ -5,8 +5,11 @@ XML formatting utilities for consistent prompt engineering across components.
5
5
  from typing import Dict, List, Optional, Union
6
6
 
7
7
 
8
- def format_xml_tag(tag_name: str, content: Optional[str] = None,
9
- attributes: Optional[Dict[str, str]] = None) -> str:
8
+ def format_xml_tag(
9
+ tag_name: str,
10
+ content: Optional[str] = None,
11
+ attributes: Optional[Dict[str, str]] = None,
12
+ ) -> str:
10
13
  """
11
14
  Format an XML tag with optional content and attributes.
12
15
  Uses self-closing tag when content is None or empty.
@@ -23,56 +26,62 @@ def format_xml_tag(tag_name: str, content: Optional[str] = None,
23
26
  attrs_str = ""
24
27
  if attributes:
25
28
  attrs_str = " " + " ".join(f'{k}="{v}"' for k, v in attributes.items())
26
-
29
+
27
30
  # Use self-closing tag if no content
28
31
  if content is None or content == "":
29
32
  return f"<{tag_name}{attrs_str} />"
30
-
33
+
31
34
  # Full tag with content
32
35
  return f"<{tag_name}{attrs_str}>{content}</{tag_name}>"
33
36
 
34
37
 
35
- def format_fastagent_tag(tag_type: str, content: Optional[str] = None,
36
- attributes: Optional[Dict[str, str]] = None) -> str:
38
+ def format_fastagent_tag(
39
+ tag_type: str,
40
+ content: Optional[str] = None,
41
+ attributes: Optional[Dict[str, str]] = None,
42
+ ) -> str:
37
43
  """
38
44
  Format a fastagent-namespaced XML tag with consistent formatting.
39
-
45
+
40
46
  Args:
41
47
  tag_type: Type of fastagent tag (without namespace prefix)
42
48
  content: Content to include inside the tag
43
49
  attributes: Dictionary of attribute name-value pairs
44
-
50
+
45
51
  Returns:
46
52
  Formatted fastagent XML tag as string
47
53
  """
48
54
  return format_xml_tag(f"fastagent:{tag_type}", content, attributes)
49
55
 
50
56
 
51
- def format_server_info(server_name: str, description: Optional[str] = None,
52
- tools: Optional[List[Dict[str, str]]] = None) -> str:
57
+ def format_server_info(
58
+ server_name: str,
59
+ description: Optional[str] = None,
60
+ tools: Optional[List[Dict[str, str]]] = None,
61
+ ) -> str:
53
62
  """
54
63
  Format server information consistently across router and orchestrator modules.
55
-
64
+
56
65
  Args:
57
66
  server_name: Name of the server
58
67
  description: Optional server description
59
68
  tools: Optional list of tool dictionaries with 'name' and 'description' keys
60
-
69
+
61
70
  Returns:
62
71
  Formatted server XML as string
63
72
  """
64
73
  # Use self-closing tag if no description or tools
65
74
  if not description and not tools:
66
75
  return format_fastagent_tag("server", None, {"name": server_name})
67
-
76
+
68
77
  # Start building components
69
78
  components = []
70
-
79
+
71
80
  # Add description if present
72
81
  if description:
73
82
  desc_tag = format_fastagent_tag("description", description)
74
83
  components.append(desc_tag)
75
-
84
+
76
85
  # Add tools section if tools exist
77
86
  if tools and len(tools) > 0:
78
87
  tool_tags = []
@@ -81,41 +90,44 @@ def format_server_info(server_name: str, description: Optional[str] = None,
81
90
  tool_desc = tool.get("description", "")
82
91
  tool_tag = format_fastagent_tag("tool", tool_desc, {"name": tool_name})
83
92
  tool_tags.append(tool_tag)
84
-
93
+
85
94
  tools_content = "\n".join(tool_tags)
86
95
  tools_tag = format_fastagent_tag("tools", f"\n{tools_content}\n")
87
96
  components.append(tools_tag)
88
-
97
+
89
98
  # Combine all components
90
99
  server_content = "\n".join(components)
91
100
  return format_fastagent_tag("server", f"\n{server_content}\n", {"name": server_name})
92
101
 
93
102
 
94
- def format_agent_info(agent_name: str, description: Optional[str] = None,
95
- servers: Optional[List[Dict[str, Union[str, List[Dict[str, str]]]]]] = None) -> str:
103
+ def format_agent_info(
104
+ agent_name: str,
105
+ description: Optional[str] = None,
106
+ servers: Optional[List[Dict[str, Union[str, List[Dict[str, str]]]]]] = None,
107
+ ) -> str:
96
108
  """
97
109
  Format agent information consistently across router and orchestrator modules.
98
-
110
+
99
111
  Args:
100
112
  agent_name: Name of the agent
101
113
  description: Optional agent description/instruction
102
114
  servers: Optional list of server dictionaries with 'name', 'description', and 'tools' keys
103
-
115
+
104
116
  Returns:
105
117
  Formatted agent XML as string
106
118
  """
107
119
  # Start building components
108
120
  components = []
109
-
121
+
110
122
  # Add description if present
111
123
  if description:
112
124
  desc_tag = format_fastagent_tag("description", description)
113
125
  components.append(desc_tag)
114
-
126
+
115
127
  # If no description or servers, use self-closing tag
116
128
  if not description and not servers:
117
129
  return format_fastagent_tag("agent", None, {"name": agent_name})
118
-
130
+
119
131
  # If has servers, format them
120
132
  if servers and len(servers) > 0:
121
133
  server_tags = []
@@ -125,13 +137,13 @@ def format_agent_info(agent_name: str, description: Optional[str] = None,
125
137
  server_tools = server.get("tools", [])
126
138
  server_tag = format_server_info(server_name, server_desc, server_tools)
127
139
  server_tags.append(server_tag)
128
-
140
+
129
141
  # Only add servers section if we have servers
130
142
  if server_tags:
131
143
  servers_content = "\n".join(server_tags)
132
144
  servers_tag = format_fastagent_tag("servers", f"\n{servers_content}\n")
133
145
  components.append(servers_tag)
134
-
146
+
135
147
  # Combine all components
136
148
  agent_content = "\n".join(components)
137
- return format_fastagent_tag("agent", f"\n{agent_content}\n", {"name": agent_name})
149
+ return format_fastagent_tag("agent", f"\n{agent_content}\n", {"name": agent_name})