fix: support to define MPC connection timeout on CrewBase instance (#3465)

* fix: support to define MPC connection timeout on CrewBase instance

* fix: resolve linter issues

* chore: ignore specific rule N802 on CrewBase class

* fix: ignore untyped import
This commit is contained in:
Lucas Gomide
2025-09-10 10:58:46 -03:00
committed by GitHub
parent 1dc4f2e897
commit 260b49c10a
5 changed files with 188 additions and 34 deletions

View File

@@ -142,7 +142,7 @@ with MCPServerAdapter(server_params, "tool_name", connect_timeout=60) as mcp_too
## Using with CrewBase ## Using with CrewBase
To use MCPServer tools within a CrewBase class, use the `mcp_tools` method. Server configurations should be provided via the mcp_server_params attribute. You can pass either a single configuration or a list of multiple server configurations. To use MCPServer tools within a CrewBase class, use the `get_mcp_tools` method. Server configurations should be provided via the `mcp_server_params` attribute. You can pass either a single configuration or a list of multiple server configurations.
```python ```python
@CrewBase @CrewBase
@@ -175,6 +175,34 @@ class CrewWithMCP:
# ... rest of your crew setup ... # ... rest of your crew setup ...
``` ```
### Connection Timeout Configuration
You can configure the connection timeout for MCP servers by setting the `mcp_connect_timeout` class attribute. If no timeout is specified, it defaults to 30 seconds.
```python
@CrewBase
class CrewWithMCP:
mcp_server_params = [...]
mcp_connect_timeout = 60 # 60 seconds timeout for all MCP connections
@agent
def your_agent(self):
return Agent(config=self.agents_config["your_agent"], tools=self.get_mcp_tools())
```
```python
@CrewBase
class CrewWithDefaultTimeout:
mcp_server_params = [...]
# No mcp_connect_timeout specified - uses default 30 seconds
@agent
def your_agent(self):
return Agent(config=self.agents_config["your_agent"], tools=self.get_mcp_tools())
```
### Filtering Tools
You can filter which tools are available to your agent by passing a list of tool names to the `get_mcp_tools` method. You can filter which tools are available to your agent by passing a list of tool names to the `get_mcp_tools` method.
```python ```python
@@ -186,6 +214,22 @@ def another_agent(self):
) )
``` ```
The timeout configuration applies to all MCP tool calls within the crew:
```python
@CrewBase
class CrewWithCustomTimeout:
mcp_server_params = [...]
mcp_connect_timeout = 90 # 90 seconds timeout for all MCP connections
@agent
def filtered_agent(self):
return Agent(
config=self.agents_config["your_agent"],
tools=self.get_mcp_tools("tool_1", "tool_2") # specific tools with custom timeout
)
```
## Explore MCP Integrations ## Explore MCP Integrations
<CardGroup cols={2}> <CardGroup cols={2}>

View File

@@ -7,8 +7,8 @@ mode: "wide"
## 개요 ## 개요
[Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP)는 AI 에이전트가 MCP 서버로 알려진 외부 서비스와 통신함으로써 LLM에 컨텍스트를 제공할 수 있도록 표준화된 방식을 제공합니다. [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP)는 AI 에이전트가 MCP 서버로 알려진 외부 서비스와 통신함으로써 LLM에 컨텍스트를 제공할 수 있도록 표준화된 방식을 제공합니다.
`crewai-tools` 라이브러리는 CrewAI의 기능을 확장하여, 이러한 MCP 서버에서 제공하는 툴을 에이전트에 원활하게 통합할 수 있도록 해줍니다. `crewai-tools` 라이브러리는 CrewAI의 기능을 확장하여, 이러한 MCP 서버에서 제공하는 툴을 에이전트에 원활하게 통합할 수 있도록 해줍니다.
이를 통해 여러분의 crew는 방대한 기능 에코시스템에 접근할 수 있습니다. 이를 통해 여러분의 crew는 방대한 기능 에코시스템에 접근할 수 있습니다.
현재 다음과 같은 전송 메커니즘을 지원합니다: 현재 다음과 같은 전송 메커니즘을 지원합니다:
@@ -142,7 +142,7 @@ with MCPServerAdapter(server_params, "tool_name", connect_timeout=60) as mcp_too
## CrewBase와 함께 사용하기 ## CrewBase와 함께 사용하기
CrewBase 클래스 내에서 MCPServer 도구를 사용하려면 `mcp_tools` 메서드를 사용하세요. 서버 구성은 mcp_server_params 속성을 통해 제공되어야 합니다. 단일 구성 또는 여러 서버 구성을 리스트 형태로 전달할 수 있습니다. CrewBase 클래스 내에서 MCPServer 도구를 사용하려면 `get_mcp_tools` 메서드를 사용하세요. 서버 구성은 `mcp_server_params` 속성을 통해 제공되어야 합니다. 단일 구성 또는 여러 서버 구성을 리스트 형태로 전달할 수 있습니다.
```python ```python
@CrewBase @CrewBase
@@ -175,6 +175,34 @@ class CrewWithMCP:
# ... 나머지 crew 설정 ... # ... 나머지 crew 설정 ...
``` ```
### 연결 타임아웃 구성
`mcp_connect_timeout` 클래스 속성을 설정하여 MCP 서버의 연결 타임아웃을 구성할 수 있습니다. 타임아웃을 지정하지 않으면 기본값으로 30초가 사용됩니다.
```python
@CrewBase
class CrewWithMCP:
mcp_server_params = [...]
mcp_connect_timeout = 60 # 모든 MCP 연결에 60초 타임아웃
@agent
def your_agent(self):
return Agent(config=self.agents_config["your_agent"], tools=self.get_mcp_tools())
```
```python
@CrewBase
class CrewWithDefaultTimeout:
mcp_server_params = [...]
# mcp_connect_timeout 지정하지 않음 - 기본 30초 사용
@agent
def your_agent(self):
return Agent(config=self.agents_config["your_agent"], tools=self.get_mcp_tools())
```
### 도구 필터링
`get_mcp_tools` 메서드에 도구 이름의 리스트를 전달하여, 에이전트에 제공되는 도구를 필터링할 수 있습니다. `get_mcp_tools` 메서드에 도구 이름의 리스트를 전달하여, 에이전트에 제공되는 도구를 필터링할 수 있습니다.
```python ```python
@@ -186,6 +214,22 @@ def another_agent(self):
) )
``` ```
타임아웃 구성은 crew 내의 모든 MCP 도구 호출에 적용됩니다:
```python
@CrewBase
class CrewWithCustomTimeout:
mcp_server_params = [...]
mcp_connect_timeout = 90 # 모든 MCP 연결에 90초 타임아웃
@agent
def filtered_agent(self):
return Agent(
config=self.agents_config["your_agent"],
tools=self.get_mcp_tools("tool_1", "tool_2") # 사용자 지정 타임아웃으로 특정 도구
)
```
## MCP 통합 탐색 ## MCP 통합 탐색
<CardGroup cols={2}> <CardGroup cols={2}>
@@ -261,4 +305,4 @@ SSE 전송은 적절하게 보안되지 않은 경우 DNS 리바인딩 공격에
### 제한 사항 ### 제한 사항
* **지원되는 프리미티브**: 현재 `MCPServerAdapter`는 주로 MCP `tools`를 어댑팅하는 기능을 지원합니다. 다른 MCP 프리미티브(예: `prompts` 또는 `resources`)는 현재 이 어댑터를 통해 CrewAI 컴포넌트로 직접 통합되어 있지 않습니다. * **지원되는 프리미티브**: 현재 `MCPServerAdapter`는 주로 MCP `tools`를 어댑팅하는 기능을 지원합니다. 다른 MCP 프리미티브(예: `prompts` 또는 `resources`)는 현재 이 어댑터를 통해 CrewAI 컴포넌트로 직접 통합되어 있지 않습니다.
* **출력 처리**: 어댑터는 일반적으로 MCP tool의 주요 텍스트 출력(예: `.content[0].text`)을 처리합니다. 복잡하거나 멀티모달 출력의 경우 이 패턴에 맞지 않으면 별도의 커스텀 처리가 필요할 수 있습니다. * **출력 처리**: 어댑터는 일반적으로 MCP tool의 주요 텍스트 출력(예: `.content[0].text`)을 처리합니다. 복잡하거나 멀티모달 출력의 경우 이 패턴에 맞지 않으면 별도의 커스텀 처리가 필요할 수 있습니다.

View File

@@ -118,7 +118,7 @@ with MCPServerAdapter(server_params, connect_timeout=60) as mcp_tools:
## Usando com CrewBase ## Usando com CrewBase
Para usar ferramentas de servidores MCP dentro de uma classe CrewBase, utilize o método `mcp_tools`. As configurações dos servidores devem ser fornecidas via o atributo mcp_server_params. Você pode passar uma configuração única ou uma lista com múltiplas configurações. Para usar ferramentas de servidores MCP dentro de uma classe CrewBase, utilize o método `get_mcp_tools`. As configurações dos servidores devem ser fornecidas via o atributo `mcp_server_params`. Você pode passar uma configuração única ou uma lista com múltiplas configurações.
```python ```python
@CrewBase @CrewBase
@@ -146,10 +146,65 @@ class CrewWithMCP:
@agent @agent
def your_agent(self): def your_agent(self):
return Agent(config=self.agents_config["your_agent"], tools=self.get_mcp_tools()) # você também pode filtrar quais ferramentas estarão disponíveis return Agent(config=self.agents_config["your_agent"], tools=self.get_mcp_tools()) # obter todas as ferramentas disponíveis
# ... restante da configuração do seu crew ... # ... restante da configuração do seu crew ...
``` ```
### Configuração de Timeout de Conexão
Você pode configurar o timeout de conexão para servidores MCP definindo o atributo de classe `mcp_connect_timeout`. Se nenhum timeout for especificado, o padrão é 30 segundos.
```python
@CrewBase
class CrewWithMCP:
mcp_server_params = [...]
mcp_connect_timeout = 60 # timeout de 60 segundos para todas as conexões MCP
@agent
def your_agent(self):
return Agent(config=self.agents_config["your_agent"], tools=self.get_mcp_tools())
```
```python
@CrewBase
class CrewWithDefaultTimeout:
mcp_server_params = [...]
# Nenhum mcp_connect_timeout especificado - usa padrão de 30 segundos
@agent
def your_agent(self):
return Agent(config=self.agents_config["your_agent"], tools=self.get_mcp_tools())
```
### Filtragem de Ferramentas
Você pode filtrar quais ferramentas estão disponíveis para seu agente passando uma lista de nomes de ferramentas para o método `get_mcp_tools`.
```python
@agent
def another_agent(self):
return Agent(
config=self.agents_config["your_agent"],
tools=self.get_mcp_tools("tool_1", "tool_2") # obter ferramentas específicas
)
```
A configuração de timeout se aplica a todas as chamadas de ferramentas MCP dentro do crew:
```python
@CrewBase
class CrewWithCustomTimeout:
mcp_server_params = [...]
mcp_connect_timeout = 90 # timeout de 90 segundos para todas as conexões MCP
@agent
def filtered_agent(self):
return Agent(
config=self.agents_config["your_agent"],
tools=self.get_mcp_tools("tool_1", "tool_2") # ferramentas específicas com timeout personalizado
)
```
## Explore Integrações MCP ## Explore Integrações MCP
<CardGroup cols={2}> <CardGroup cols={2}>

View File

@@ -1,12 +1,14 @@
import inspect import inspect
import logging import logging
from collections.abc import Callable
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Dict, TypeVar, cast, List from typing import Any, TypeVar, cast
from crewai.tools import BaseTool
import yaml import yaml
from dotenv import load_dotenv from dotenv import load_dotenv
from crewai.tools import BaseTool
load_dotenv() load_dotenv()
T = TypeVar("T", bound=type) T = TypeVar("T", bound=type)
@@ -14,7 +16,7 @@ T = TypeVar("T", bound=type)
"""Base decorator for creating crew classes with configuration and function management.""" """Base decorator for creating crew classes with configuration and function management."""
def CrewBase(cls: T) -> T: def CrewBase(cls: T) -> T: # noqa: N802
"""Wraps a class with crew functionality and configuration management.""" """Wraps a class with crew functionality and configuration management."""
class WrappedClass(cls): # type: ignore class WrappedClass(cls): # type: ignore
@@ -29,6 +31,7 @@ def CrewBase(cls: T) -> T:
original_tasks_config_path = getattr(cls, "tasks_config", "config/tasks.yaml") original_tasks_config_path = getattr(cls, "tasks_config", "config/tasks.yaml")
mcp_server_params: Any = getattr(cls, "mcp_server_params", None) mcp_server_params: Any = getattr(cls, "mcp_server_params", None)
mcp_connect_timeout: int = getattr(cls, "mcp_connect_timeout", 30)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -86,15 +89,18 @@ def CrewBase(cls: T) -> T:
import types import types
return types.MethodType(_close_mcp_server, self) return types.MethodType(_close_mcp_server, self)
def get_mcp_tools(self, *tool_names: list[str]) -> List[BaseTool]: def get_mcp_tools(self, *tool_names: list[str]) -> list[BaseTool]:
if not self.mcp_server_params: if not self.mcp_server_params:
return [] return []
from crewai_tools import MCPServerAdapter from crewai_tools import MCPServerAdapter # type: ignore[import-untyped]
adapter = getattr(self, '_mcp_server_adapter', None) adapter = getattr(self, '_mcp_server_adapter', None)
if not adapter: if not adapter:
self._mcp_server_adapter = MCPServerAdapter(self.mcp_server_params) self._mcp_server_adapter = MCPServerAdapter(
self.mcp_server_params,
connect_timeout=self.mcp_connect_timeout
)
return self._mcp_server_adapter.tools.filter_by_names(tool_names or None) return self._mcp_server_adapter.tools.filter_by_names(tool_names or None)
@@ -154,8 +160,8 @@ def CrewBase(cls: T) -> T:
} }
def _filter_functions( def _filter_functions(
self, functions: Dict[str, Callable], attribute: str self, functions: dict[str, Callable], attribute: str
) -> Dict[str, Callable]: ) -> dict[str, Callable]:
return { return {
name: func name: func
for name, func in functions.items() for name, func in functions.items()
@@ -184,11 +190,11 @@ def CrewBase(cls: T) -> T:
def _map_agent_variables( def _map_agent_variables(
self, self,
agent_name: str, agent_name: str,
agent_info: Dict[str, Any], agent_info: dict[str, Any],
llms: Dict[str, Callable], llms: dict[str, Callable],
tool_functions: Dict[str, Callable], tool_functions: dict[str, Callable],
cache_handler_functions: Dict[str, Callable], cache_handler_functions: dict[str, Callable],
callbacks: Dict[str, Callable], callbacks: dict[str, Callable],
) -> None: ) -> None:
if llm := agent_info.get("llm"): if llm := agent_info.get("llm"):
try: try:
@@ -245,13 +251,13 @@ def CrewBase(cls: T) -> T:
def _map_task_variables( def _map_task_variables(
self, self,
task_name: str, task_name: str,
task_info: Dict[str, Any], task_info: dict[str, Any],
agents: Dict[str, Callable], agents: dict[str, Callable],
tasks: Dict[str, Callable], tasks: dict[str, Callable],
output_json_functions: Dict[str, Callable], output_json_functions: dict[str, Callable],
tool_functions: Dict[str, Callable], tool_functions: dict[str, Callable],
callback_functions: Dict[str, Callable], callback_functions: dict[str, Callable],
output_pydantic_functions: Dict[str, Callable], output_pydantic_functions: dict[str, Callable],
) -> None: ) -> None:
if context_list := task_info.get("context"): if context_list := task_info.get("context"):
self.tasks_config[task_name]["context"] = [ self.tasks_config[task_name]["context"] = [

View File

@@ -1,5 +1,6 @@
from typing import List from typing import Any, ClassVar
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import pytest import pytest
from crewai.agent import Agent from crewai.agent import Agent
@@ -44,8 +45,8 @@ class InternalCrew:
agents_config = "config/agents.yaml" agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml" tasks_config = "config/tasks.yaml"
agents: List[BaseAgent] agents: list[BaseAgent]
tasks: List[Task] tasks: list[Task]
@llm @llm
def local_llm(self): def local_llm(self):
@@ -89,7 +90,8 @@ class InternalCrew:
@CrewBase @CrewBase
class InternalCrewWithMCP(InternalCrew): class InternalCrewWithMCP(InternalCrew):
mcp_server_params = {"host": "localhost", "port": 8000} mcp_server_params: ClassVar[dict[str, Any]] = {"host": "localhost", "port": 8000}
mcp_connect_timeout = 120
@agent @agent
def reporting_analyst(self): def reporting_analyst(self):
@@ -200,8 +202,8 @@ def test_before_kickoff_with_none_input():
def test_multiple_before_after_kickoff(): def test_multiple_before_after_kickoff():
@CrewBase @CrewBase
class MultipleHooksCrew: class MultipleHooksCrew:
agents: List[BaseAgent] agents: list[BaseAgent]
tasks: List[Task] tasks: list[Task]
agents_config = "config/agents.yaml" agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml" tasks_config = "config/tasks.yaml"
@@ -284,4 +286,7 @@ def test_internal_crew_with_mcp():
assert crew.reporting_analyst().tools == [simple_tool, another_simple_tool] assert crew.reporting_analyst().tools == [simple_tool, another_simple_tool]
assert crew.researcher().tools == [simple_tool] assert crew.researcher().tools == [simple_tool]
adapter_mock.assert_called_once_with({"host": "localhost", "port": 8000}) adapter_mock.assert_called_once_with(
{"host": "localhost", "port": 8000},
connect_timeout=120
)