Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a6776ca
Fix startup errors when MCP servers are not available
jondecker76 Jul 16, 2025
281086d
Update direct_decorators.py
jondecker76 Jul 16, 2025
a589d04
Make logging of agent availability not appear to be an error
jondecker76 Jul 16, 2025
0a8b431
Fix status display output
jondecker76 Jul 16, 2025
6c3e9af
Update fastagent.py
jondecker76 Jul 16, 2025
5dc5a68
Handle the Exception Group
jondecker76 Jul 16, 2025
4f959a7
Error handling improvements
jondecker76 Jul 16, 2025
c3269a0
Try a synchronous connection so that the error can be caught easier
jondecker76 Jul 16, 2025
95c7544
More exception handling
jondecker76 Jul 16, 2025
7aba3a7
Error handling fixes, progress display improvements
jondecker76 Jul 16, 2025
e8e58d4
More fixes for startup without MCP
jondecker76 Jul 16, 2025
d21c44a
Add debug logging to trace down remaining issues
jondecker76 Jul 16, 2025
78103e9
Clean up logging, improve server detection
jondecker76 Jul 16, 2025
597d522
Update fastagent.py
jondecker76 Jul 16, 2025
2ac4cd5
Server polling fix
jondecker76 Jul 16, 2025
cd91ced
Update fastagent.py
jondecker76 Jul 16, 2025
b5de3bc
Clean up progress display
jondecker76 Jul 16, 2025
2f00f72
Fix logging issues
jondecker76 Jul 16, 2025
ff2c4d6
Add configurable polling interval
jondecker76 Jul 16, 2025
945bf45
Final features and cleanup
jondecker76 Jul 16, 2025
83714d2
fix linting errors
jondecker76 Jul 16, 2025
046304d
Update fastagent.py
jondecker76 Jul 16, 2025
1cf40d4
Update direct_factory.py
jondecker76 Jul 16, 2025
747291b
Fix import
jondecker76 Jul 16, 2025
5944e08
Merge branch 'main' into pr/jdecker76/291
evalstate Jul 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/mcp_agent/core/agent_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def __getattr__(self, name: str) -> Agent:
if name in self._agents:
return self._agents[name]
raise AttributeError(f"Agent '{name}' not found")

def add_agent(self, name: str, agent: Agent) -> None:
"""Add a new agent to the app."""
self._agents[name] = agent

async def __call__(
self,
Expand Down
10 changes: 10 additions & 0 deletions src/mcp_agent/core/direct_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ def _decorator_impl(
default: Whether to mark this as the default agent
**extra_kwargs: Additional agent/workflow-specific parameters
"""
# Check for duplicate agent registration
if name in self.agents:
# We can't use the logger here as it's not configured yet, so print a warning
print(f"Warning: Agent '{name}' is already registered. Skipping duplicate definition.")

# Return a no-op decorator to prevent re-registration
def no_op_decorator(func: AgentCallable[P, R]) -> AgentCallable[P, R]:
return func

return no_op_decorator

def decorator(func: AgentCallable[P, R]) -> DecoratedAgentProtocol[P, R]:
is_async = inspect.iscoroutinefunction(func)
Expand Down
207 changes: 94 additions & 113 deletions src/mcp_agent/core/direct_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Implements type-safe factories with improved error handling.
"""

import re
from typing import Any, Callable, Dict, Optional, Protocol, TypeVar

from mcp_agent.agents.agent import Agent, AgentConfig
Expand All @@ -15,7 +16,7 @@
from mcp_agent.agents.workflow.router_agent import RouterAgent
from mcp_agent.app import MCPApp
from mcp_agent.core.agent_types import AgentType
from mcp_agent.core.exceptions import AgentConfigError
from mcp_agent.core.exceptions import AgentConfigError, ServerInitializationError
from mcp_agent.core.validation import get_dependencies_groups
from mcp_agent.event_progress import ProgressAction
from mcp_agent.llm.augmented_llm import RequestParams
Expand Down Expand Up @@ -125,13 +126,6 @@ def model_factory_func(model=None, request_params=None):

# Get all agents of the specified type
for name, agent_data in agents_dict.items():
logger.info(
f"Loaded {name}",
data={
"progress_action": ProgressAction.LOADED,
"agent_name": name,
},
)

# Compare type string from config with Enum value
if agent_data["type"] == agent_type.value:
Expand All @@ -156,6 +150,16 @@ def model_factory_func(model=None, request_params=None):
api_key=config.api_key
)
result_agents[name] = agent

# Log successful agent creation
logger.info(
f"Loaded {name}",
data={
"progress_action": ProgressAction.LOADED,
"agent_name": name,
"target": name,
},
)

elif agent_type == AgentType.CUSTOM:
# Get the class to instantiate
Expand All @@ -175,6 +179,16 @@ def model_factory_func(model=None, request_params=None):
api_key=config.api_key
)
result_agents[name] = agent

# Log successful agent creation
logger.info(
f"Loaded {name}",
data={
"progress_action": ProgressAction.LOADED,
"agent_name": name,
"target": name,
},
)

elif agent_type == AgentType.ORCHESTRATOR:
# Get base params configured with model settings
Expand Down Expand Up @@ -370,111 +384,78 @@ async def create_agents_in_dependency_order(

# Create agent proxies for each group in dependency order
for group in dependencies:
# Create basic agents first
# Note: We compare string values from config with the Enum's string value
if AgentType.BASIC.value in [agents_dict[name]["type"] for name in group]:
basic_agents = await create_agents_by_type(
app_instance,
{
name: agents_dict[name]
for name in group
if agents_dict[name]["type"] == AgentType.BASIC.value
},
AgentType.BASIC,
active_agents,
model_factory_func,
)
active_agents.update(basic_agents)

# Create custom agents first
if AgentType.CUSTOM.value in [agents_dict[name]["type"] for name in group]:
basic_agents = await create_agents_by_type(
app_instance,
{
name: agents_dict[name]
for name in group
if agents_dict[name]["type"] == AgentType.CUSTOM.value
},
AgentType.CUSTOM,
active_agents,
model_factory_func,
)
active_agents.update(basic_agents)

# Create parallel agents
if AgentType.PARALLEL.value in [agents_dict[name]["type"] for name in group]:
parallel_agents = await create_agents_by_type(
app_instance,
{
name: agents_dict[name]
for name in group
if agents_dict[name]["type"] == AgentType.PARALLEL.value
},
AgentType.PARALLEL,
active_agents,
model_factory_func,
)
active_agents.update(parallel_agents)

# Create router agents
if AgentType.ROUTER.value in [agents_dict[name]["type"] for name in group]:
router_agents = await create_agents_by_type(
app_instance,
{
name: agents_dict[name]
for name in group
if agents_dict[name]["type"] == AgentType.ROUTER.value
},
AgentType.ROUTER,
active_agents,
model_factory_func,
)
active_agents.update(router_agents)

# Create chain agents
if AgentType.CHAIN.value in [agents_dict[name]["type"] for name in group]:
chain_agents = await create_agents_by_type(
app_instance,
{
name: agents_dict[name]
for name in group
if agents_dict[name]["type"] == AgentType.CHAIN.value
},
AgentType.CHAIN,
active_agents,
model_factory_func,
)
active_agents.update(chain_agents)

# Create evaluator-optimizer agents
if AgentType.EVALUATOR_OPTIMIZER.value in [agents_dict[name]["type"] for name in group]:
evaluator_agents = await create_agents_by_type(
app_instance,
{
name: agents_dict[name]
for name in group
if agents_dict[name]["type"] == AgentType.EVALUATOR_OPTIMIZER.value
},
AgentType.EVALUATOR_OPTIMIZER,
active_agents,
model_factory_func,
)
active_agents.update(evaluator_agents)

# Create orchestrator agents last since they might depend on other agents
if AgentType.ORCHESTRATOR.value in [agents_dict[name]["type"] for name in group]:
orchestrator_agents = await create_agents_by_type(
app_instance,
{
name: agents_dict[name]
for name in group
if agents_dict[name]["type"] == AgentType.ORCHESTRATOR.value
},
AgentType.ORCHESTRATOR,
active_agents,
model_factory_func,
)
active_agents.update(orchestrator_agents)
for agent_name in group:
agent_config = agents_dict[agent_name]

# Check if any required servers for this agent are unavailable
# Only do this check if polling is enabled (mcp_polling_interval > 0)
if (hasattr(app_instance.fast_agent, 'mcp_polling_interval') and
app_instance.fast_agent.mcp_polling_interval is not None and
app_instance.fast_agent.mcp_polling_interval > 0):

agent_config_obj = agent_config.get("config")
required_servers = agent_config_obj.servers if agent_config_obj else []
if any(server in app_instance.fast_agent.unavailable_servers for server in required_servers):
logger.warning(
f"Skipping agent '{agent_name}' because it depends on an unavailable server."
)
app_instance.fast_agent.deactivated_agents[agent_name] = agent_config
continue

try:
agent_type = AgentType(agent_config["type"])
# Create agents of a specific type for the current group
created_agents = await create_agents_by_type(
app_instance,
{agent_name: agent_config},
agent_type,
active_agents,
model_factory_func,
)
active_agents.update(created_agents)

except ServerInitializationError as e:
# Only handle graceful deactivation if polling is enabled
if (hasattr(app_instance.fast_agent, 'mcp_polling_interval') and
app_instance.fast_agent.mcp_polling_interval is not None and
app_instance.fast_agent.mcp_polling_interval > 0):

# The original error (e.g., ConnectError) is the cause
server_name = "unknown"

# We need to find the server name from the original exception text
# as ServerInitializationError doesn't carry it directly.
match = re.search(r"MCP Server: '([^']*)'", str(e))
if match:
server_name = match.group(1)

app_instance.fast_agent.unavailable_servers.add(server_name)
app_instance.fast_agent.deactivated_agents[agent_name] = agent_config

logger.debug(f"MCP server '{server_name}' is not available. Agent '{agent_name}' will be deactivated.")

logger.info(
f"Agent '{agent_name}' deactivated",
data={
"progress_action": ProgressAction.DEACTIVATED,
"agent_name": agent_name,
},
)
else:
# If polling is not enabled, let the error propagate normally (old behavior)
raise

except Exception as e:
# Only handle graceful deactivation if polling is enabled
if (hasattr(app_instance.fast_agent, 'mcp_polling_interval') and
app_instance.fast_agent.mcp_polling_interval is not None and
app_instance.fast_agent.mcp_polling_interval > 0):

logger.error(f"Failed to create agent '{agent_name}': {e}")
app_instance.fast_agent.deactivated_agents[agent_name] = agent_config
else:
# If polling is not enabled, let the error propagate normally (old behavior)
raise

return active_agents

Expand Down
Loading
Loading