From 494ed7e671bbfe407e46f7882faa4a511b59bd63 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:42:08 -0700 Subject: [PATCH] liteagent supports apps and mcps (#3794) * liteagent supports apps and mcps * generated cassettes for these --- lib/crewai/src/crewai/agent.py | 9 + lib/crewai/tests/agents/test_lite_agent.py | 78 ++++++ .../test_agent_kickoff_with_mcp_tools.yaml | 244 ++++++++++++++++++ ...est_agent_kickoff_with_platform_tools.yaml | 126 +++++++++ 4 files changed, 457 insertions(+) create mode 100644 lib/crewai/tests/cassettes/test_agent_kickoff_with_mcp_tools.yaml create mode 100644 lib/crewai/tests/cassettes/test_agent_kickoff_with_platform_tools.yaml diff --git a/lib/crewai/src/crewai/agent.py b/lib/crewai/src/crewai/agent.py index 1b1383169..75593a1d4 100644 --- a/lib/crewai/src/crewai/agent.py +++ b/lib/crewai/src/crewai/agent.py @@ -1186,6 +1186,15 @@ class Agent(BaseAgent): Returns: LiteAgentOutput: The result of the agent execution. """ + if self.apps: + platform_tools = self.get_platform_tools(self.apps) + if platform_tools: + self.tools.extend(platform_tools) + if self.mcps: + mcps = self.get_mcp_tools(self.mcps) + if mcps: + self.tools.extend(mcps) + lite_agent = LiteAgent( id=self.id, role=self.role, diff --git a/lib/crewai/tests/agents/test_lite_agent.py b/lib/crewai/tests/agents/test_lite_agent.py index 57e141cf9..5d59b52cf 100644 --- a/lib/crewai/tests/agents/test_lite_agent.py +++ b/lib/crewai/tests/agents/test_lite_agent.py @@ -591,3 +591,81 @@ def test_lite_agent_with_invalid_llm(): llm="invalid-model", ) assert "Expected LLM instance of type BaseLLM" in str(exc_info.value) + + +@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"}) +@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get") +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_agent_kickoff_with_platform_tools(mock_get): + """Test that Agent.kickoff() properly integrates platform tools with LiteAgent""" + mock_response = Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = { + "actions": { + "github": [ + { + "name": "create_issue", + "description": "Create a GitHub issue", + "parameters": { + "type": "object", + "properties": { + "title": {"type": "string", "description": "Issue title"}, + "body": {"type": "string", "description": "Issue body"}, + }, + "required": ["title"], + }, + } + ] + } + } + mock_get.return_value = mock_response + + agent = Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + llm=LLM(model="gpt-3.5-turbo"), + apps=["github"], + verbose=True + ) + + result = agent.kickoff("Create a GitHub issue") + + assert isinstance(result, LiteAgentOutput) + assert result.raw is not None + + +@patch.dict("os.environ", {"EXA_API_KEY": "test_exa_key"}) +@patch("crewai.agent.Agent._get_external_mcp_tools") +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_agent_kickoff_with_mcp_tools(mock_get_mcp_tools): + """Test that Agent.kickoff() properly integrates MCP tools with LiteAgent""" + # Setup mock MCP tools - create a proper BaseTool instance + class MockMCPTool(BaseTool): + name: str = "exa_search" + description: str = "Search the web using Exa" + + def _run(self, query: str) -> str: + return f"Mock search results for: {query}" + + mock_get_mcp_tools.return_value = [MockMCPTool()] + + # Create agent with MCP servers + agent = Agent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + llm=LLM(model="gpt-3.5-turbo"), + mcps=["https://mcp.exa.ai/mcp?api_key=test_exa_key&profile=research"], + verbose=True + ) + + # Execute kickoff + result = agent.kickoff("Search for information about AI") + + # Verify the result is a LiteAgentOutput + assert isinstance(result, LiteAgentOutput) + assert result.raw is not None + + # Verify MCP tools were retrieved + mock_get_mcp_tools.assert_called_once_with("https://mcp.exa.ai/mcp?api_key=test_exa_key&profile=research") diff --git a/lib/crewai/tests/cassettes/test_agent_kickoff_with_mcp_tools.yaml b/lib/crewai/tests/cassettes/test_agent_kickoff_with_mcp_tools.yaml new file mode 100644 index 000000000..a5edc7b75 --- /dev/null +++ b/lib/crewai/tests/cassettes/test_agent_kickoff_with_mcp_tools.yaml @@ -0,0 +1,244 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour + personal goal is: Test goal\n\nYou ONLY have access to the following tools, + and should NEVER make up tools that are not listed here:\n\nTool Name: exa_search\nTool + Arguments: {''query'': {''description'': None, ''type'': ''str''}}\nTool Description: + Search the web using Exa\n\nIMPORTANT: Use the following format in your response:\n\n```\nThought: + you should always think about what to do\nAction: the action to take, only one + name of [exa_search], just the name, exactly as it''s written.\nAction Input: + the input to the action, just a simple JSON object, enclosed in curly braces, + using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce + all necessary information is gathered, return the following format:\n\n```\nThought: + I now know the final answer\nFinal Answer: the final answer to the original + input question\n```"}, {"role": "user", "content": "Search for information about + AI"}], "model": "gpt-3.5-turbo", "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1038' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFNNb9swDL3nVxA6J0GTLgnmW7pLDGzrPi+dC0ORaVurLHoSVaQI8t8H + OR92twzYxYD4+MjHR3o/AhC6EAkIVUtWTWsm776/320+fbzbPLfy893br8vi6WGePnywNW3uxTgy + aPsTFZ9ZU0VNa5A12SOsHErGWHW2Ws5uFzfz1awDGirQRFrV8uR2uphwcFua3MzmixOzJq3QiwR+ + jAAA9t03arQF7kQCN+NzpEHvZYUiuSQBCEcmRoT0XnuWlsW4BxVZRtvJ/lZTqGpOIAVfUzAFBI/A + NQLuZO5ROlUDExlggtOzJAfaluQaGUcFuaXAsE6nmV2rGEkG5HMMUtsGTmCfiV8B3UsmEsjEOs3E + IbP3W4/uWR65X9AHwx4cmmhebLxOoXTUXNM1zexwNIdl8DJaa4MxA0BaS9x16Ex9PCGHi42GqtbR + 1v9BFaW22te5Q+nJRss8Uys69DACeOzWFV5tQLSOmpZzpifs2s1ns2M90V9Ij75ZnkAmlmbAWqzG + V+rlBbLUxg8WLpRUNRY9tb8OGQpNA2A0mPpvNddqHyfXtvqf8j2gFLaMRd46LLR6PXGf5jD+QP9K + u7jcCRbxSLTCnDW6uIkCSxnM8bSFf/GMTV5qW6Frne7uO25ydBj9BgAA//8DAChlpSTeAwAA + headers: + CF-RAY: + - 993d6b3e6b64ffb8-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 24 Oct 2025 23:57:52 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=cXZeAPPk9o5VuaArJFruIKai9Oj2X9ResvQgx_qCwdg-1761350272-1.0.1.1-42v7QDan6OIFJYT2vOisNB0AeLg3KsbAiCGsrrsPgH1N13l8o_Vy6HvQCVCIRAqPaHCcvybK8xTxrHKqZgLBRH4XM7.l5IYkFLhgl8IIUA0; + path=/; expires=Sat, 25-Oct-25 00:27:52 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=wGtD6dA8GfZzwvY_uzLiXlAVzOIOJPtIPQYQRS_19oo-1761350272656-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '718' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '791' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999774' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_a2e42e9d98bc4c3db1a4de14cf1a94ec + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour + personal goal is: Test goal\n\nYou ONLY have access to the following tools, + and should NEVER make up tools that are not listed here:\n\nTool Name: exa_search\nTool + Arguments: {''query'': {''description'': None, ''type'': ''str''}}\nTool Description: + Search the web using Exa\n\nIMPORTANT: Use the following format in your response:\n\n```\nThought: + you should always think about what to do\nAction: the action to take, only one + name of [exa_search], just the name, exactly as it''s written.\nAction Input: + the input to the action, just a simple JSON object, enclosed in curly braces, + using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce + all necessary information is gathered, return the following format:\n\n```\nThought: + I now know the final answer\nFinal Answer: the final answer to the original + input question\n```"}, {"role": "user", "content": "Search for information about + AI"}, {"role": "assistant", "content": "Thought: I should use the exa_search + tool to search for information about AI.\nAction: exa_search\nAction Input: + {\"query\": \"AI\"}\nObservation: Mock search results for: AI"}], "model": "gpt-3.5-turbo", + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1250' + content-type: + - application/json + cookie: + - __cf_bm=cXZeAPPk9o5VuaArJFruIKai9Oj2X9ResvQgx_qCwdg-1761350272-1.0.1.1-42v7QDan6OIFJYT2vOisNB0AeLg3KsbAiCGsrrsPgH1N13l8o_Vy6HvQCVCIRAqPaHCcvybK8xTxrHKqZgLBRH4XM7.l5IYkFLhgl8IIUA0; + _cfuvid=wGtD6dA8GfZzwvY_uzLiXlAVzOIOJPtIPQYQRS_19oo-1761350272656-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFNNaxsxEL3vrxh06cU2/sBJs5diCi0phULr0EMaFlma3VWs1ajSbG0T + /N+L1o5306bQi0B6743evJGeMgBhtMhBqFqyarwdv7/7vP9kb+jjt8dV/Ln/otdrh3ezjdx9DUaM + koI2j6j4WTVR1HiLbMidYBVQMqaqs+ur2WI5nV8vOqAhjTbJKs/jxWQ55jZsaDydzZdnZU1GYRQ5 + 3GcAAE/dmjw6jXuRw3T0fNJgjLJCkV9IACKQTSdCxmgiS8di1IOKHKPrbK9raquac7gFRzvYpoVr + hNI4aUG6uMPww33odqtul6iKWqvdG040DRKiR2VKo86CCXxPBDhQC9ZsERoEJogog6qhpADSHbg2 + rgK0ESGgTTElzur23dBpwLKNMiXlWmsHgHSOWKaku4wezsjxkoqlygfaxD+kojTOxLoIKCO5lEBk + 8qJDjxnAQ5d++yJQ4QM1ngumLXbXzZdXp3qiH3iPLhZnkImlHaje3oxeqVdoZGlsHMxPKKlq1L20 + H7ZstaEBkA26/tvNa7VPnRtX/U/5HlAKPaMufEBt1MuOe1rA9B/+Rbuk3BkWEcMvo7BggyFNQmMp + W3t6qSIeImNTlMZVGHww3XNNk8yO2W8AAAD//wMA7uEpt60DAAA= + headers: + CF-RAY: + - 993d6b44dc97ffb8-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 24 Oct 2025 23:57:53 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '446' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '655' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999732' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_9ce6b4f80d9546eba4ce23b5fac77153 + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/test_agent_kickoff_with_platform_tools.yaml b/lib/crewai/tests/cassettes/test_agent_kickoff_with_platform_tools.yaml new file mode 100644 index 000000000..1174d562b --- /dev/null +++ b/lib/crewai/tests/cassettes/test_agent_kickoff_with_platform_tools.yaml @@ -0,0 +1,126 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour + personal goal is: Test goal\n\nYou ONLY have access to the following tools, + and should NEVER make up tools that are not listed here:\n\nTool Name: create_issue\nTool + Arguments: {''title'': {''description'': ''Issue title'', ''type'': ''str''}, + ''body'': {''description'': ''Issue body'', ''type'': ''Union[str, NoneType]''}}\nTool + Description: Create a GitHub issue\nDetailed Parameter Structure:\nObject with + properties:\n - title: Issue title (required)\n - body: Issue body (optional)\n\nIMPORTANT: + Use the following format in your response:\n\n```\nThought: you should always + think about what to do\nAction: the action to take, only one name of [create_issue], + just the name, exactly as it''s written.\nAction Input: the input to the action, + just a simple JSON object, enclosed in curly braces, using \" to wrap keys and + values.\nObservation: the result of the action\n```\n\nOnce all necessary information + is gathered, return the following format:\n\n```\nThought: I now know the final + answer\nFinal Answer: the final answer to the original input question\n```"}, + {"role": "user", "content": "Create a GitHub issue"}], "model": "gpt-3.5-turbo", + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1233' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFNNbxMxEL3vrxj5nET5aGjIBUGoIMAFCRASqiLHns0O9Xose7ZtqPLf + 0XrTbApF4rKHefOe37yZfSgAFFm1BGUqLaYObrj6+un+45er9ZvF/PW3tZirz+OXvy6+0/jDav1W + DVoGb3+ikUfWyHAdHAqx72ATUQu2qpPLF5PZfDy9vMhAzRZdS9sFGc5G86E0ccvD8WQ6PzIrJoNJ + LeFHAQDwkL+tR2/xXi1hPHis1JiS3qFanpoAVGTXVpROiZJoL2rQg4a9oM+213BHzoFHtFBzREgB + DZVkgHzJsdbtMCAM3Sig4R3J+2YLlFKDI1hx4yzsuYHgUCeEEPmWLHZiFkWTS5AaU4FOIBWCkDgE + 7S1s2e6By1zNclnnLis6usH+2Vfn7iOWTdJter5x7gzQ3rNkwzm36yNyOCXleBcib9MfVFWSp1Rt + IurEvk0lCQeV0UMBcJ030jwJWYXIdZCN8A3m56bzeaen+iPo0dnsCAqLdmesxWLwjN7mGNzZTpXR + pkLbU/sD0I0lPgOKs6n/dvOcdjc5+d3/yPeAMRgE7SZEtGSeTty3RWz/kX+1nVLOhlXCeEsGN0IY + 201YLHXjuutVaZ8E601JfocxRMon3G6yOBS/AQAA//8DABKn8+vBAwAA + headers: + CF-RAY: + - 993d6b4be9862379-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 24 Oct 2025 23:57:54 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=WY9bgemMDI_hUYISAPlQ2a.DBGeZfM6AjVEa3SKNg1c-1761350274-1.0.1.1-K3Qm2cl6IlDAgmocoKZ8IMUTmue6Q81hH9stECprUq_SM8LF8rR9d1sHktvRCN3.jEM.twEuFFYDNpBnN8NBRJFZcea1yvpm8Uo0G_UhyDs; + path=/; expires=Sat, 25-Oct-25 00:27:54 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=JklLS4i3hBGELpS9cz1KMpTbj72hCwP41LyXDSxWIv8-1761350274521-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '487' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '526' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999727' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_1708dc0928c64882aaa5bc2c168c140f + status: + code: 200 + message: OK +version: 1