diff --git a/app/en/get-started/agent-frameworks/crewai/_meta.tsx b/app/en/get-started/agent-frameworks/crewai/_meta.tsx
index 67edee61a..349f4327a 100644
--- a/app/en/get-started/agent-frameworks/crewai/_meta.tsx
+++ b/app/en/get-started/agent-frameworks/crewai/_meta.tsx
@@ -9,10 +9,7 @@ const meta: MetaRecord = {
},
},
"use-arcade-tools": {
- title: "Using Arcade tools",
- },
- "custom-auth-flow": {
- title: "Custom auth flow",
+ title: "Setup Arcade tools with CrewAI",
},
};
diff --git a/app/en/get-started/agent-frameworks/crewai/custom-auth-flow/page.mdx b/app/en/get-started/agent-frameworks/crewai/custom-auth-flow/page.mdx
deleted file mode 100644
index 6a2cc67fa..000000000
--- a/app/en/get-started/agent-frameworks/crewai/custom-auth-flow/page.mdx
+++ /dev/null
@@ -1,234 +0,0 @@
----
-title: "Custom Auth Flow with CrewAI"
-description: "Learn how to create a custom auth flow with CrewAI"
----
-
-import { Steps, Callout } from "nextra/components";
-import ToggleContent from "@/app/_components/toggle-content";
-
-## Custom Auth Flow with CrewAI
-
-In this guide, we will explore how to create a custom auth flow that will be performed before executing Arcade tools within your CrewAI agent team.
-
-The `ArcadeToolManager`'s built-in authorization and tool execution flows work well for many typical use cases. However, some scenarios call for a tailored approach. By implementing a custom auth flow, you gain flexibility in handling tool authorization. If your use case calls for a unique interface, additional approval steps, or specialized error handling, then this guide is for you.
-
-
-
-### Prerequisites
-
-- [Obtain an Arcade API key](/get-started/setup/api-keys)
-
-### Set up your environment
-
-Install the required package, and ensure your environment variables are set with your Arcade and OpenAI API keys:
-
-```bash
-pip install crewai-arcade
-```
-
-### Configure API keys
-
-Provide your Arcade and OpenAI API keys. You can store them in environment variables like so:
-
-```bash
-export ARCADE_API_KEY="your_arcade_api_key"
-export OPENAI_API_KEY="your_openai_api_key"
-```
-
-### Define your custom auth flow
-
-The custom auth flow defined in the following code snippet is a function that will be called whenever CrewAI needs to call a tool.
-
-```python
-from typing import Any
-
-from crewai_arcade import ArcadeToolManager
-
-USER_ID = "{arcade_user_id}"
-
-def custom_auth_flow(
- manager: ArcadeToolManager, tool_name: str, **tool_input: dict[str, Any]
-) -> Any:
- """Custom auth flow for the ArcadeToolManager
-
- This function is called when CrewAI needs to call a tool that requires authorization.
- Authorization is handled before executing the tool.
- This function overrides the ArcadeToolManager's default auth flow performed by ArcadeToolManager.authorize_tool
- """
- # Get authorization status
- auth_response = manager.authorize(tool_name, USER_ID)
-
- # If the user is not authorized for the tool,
- # then we need to handle the authorization before executing the tool
- if not manager.is_authorized(auth_response.id):
- print(f"Authorization required for tool: '{tool_name}' with inputs:")
- for input_name, input_value in tool_input.items():
- print(f" {input_name}: {input_value}")
- # Handle authorization
- print(f"\nTo authorize, visit: {auth_response.url}")
- # Block until the user has completed the authorization
- auth_response = manager.wait_for_auth(auth_response)
-
- # Ensure authorization completed successfully
- if not manager.is_authorized(auth_response.id):
- raise ValueError(f"Authorization failed for {tool_name}. URL: {auth_response.url}")
- else:
- print(f"Authorization already granted for tool: '{tool_name}' with inputs:")
- for input_name, input_value in tool_input.items():
- print(f" {input_name}: {input_value}")
-
-
-def tool_manager_callback(tool_manager: ArcadeToolManager, tool_name: str, **tool_input: dict[str, Any]) -> Any:
- """Tool executor callback with custom auth flow for the ArcadeToolManager
-
- ArcadeToolManager's default executor handles authorization and tool execution.
- This function overrides the default executor to handle authorization in a custom way and then executes the tool.
- """
- custom_auth_flow(tool_manager, tool_name, **tool_input)
- return tool_manager.execute_tool(USER_ID, tool_name, **tool_input)
-```
-
-### Get Arcade tools
-
-You can now provide the tool manager callback to the `ArcadeToolManager` upon initialization:
-
-```python
-# Provide the tool manager callback to the ArcadeToolManager
-manager = ArcadeToolManager(executor=tool_manager_callback)
-
-# Retrieve the provided tools and/or MCP Servers as CrewAI StructuredTools.
-tools = manager.get_tools(tools=["Gmail.ListEmails"], toolkits=["Slack"])
-```
-
-### Use tools in your CrewAI agent team
-
-Create a Crew that uses your tools with the custom auth flow. When the tool is called, your tool manager callback will be called to handle the authorization and then the tool will be executed.
-
-```python
-from crewai import Agent, Crew, Task
-from crewai.llm import LLM
-
-crew_agent = Agent(
- role="Main Agent",
- backstory="You are a helpful assistant",
- goal="Help the user with their requests",
- tools=tools,
- allow_delegation=False,
- verbose=True,
- llm=LLM(model="gpt-4o"),
-)
-
-task = Task(
- description="Get the 5 most recent emails from the user's inbox and summarize them and recommend a response for each.",
- expected_output="A bulleted list with a one sentence summary of each email and a recommended response to the email.",
- agent=crew_agent,
- tools=crew_agent.tools,
-)
-
-crew = Crew(
- agents=[crew_agent],
- tasks=[task],
- verbose=True,
- memory=True,
-)
-
-result = crew.kickoff()
-
-print("\n\n\n ------------ Result ------------ \n\n\n")
-print(result)
-```
-
-
-
-
-
-```python
-from typing import Any
-
-from crewai import Agent, Crew, Task
-from crewai.llm import LLM
-from crewai_arcade import ArcadeToolManager
-
-USER_ID = "{arcade_user_id}"
-
-def custom_auth_flow(
- manager: ArcadeToolManager, tool_name: str, **tool_input: dict[str, Any]
-) -> Any:
- """Custom auth flow for the ArcadeToolManager
-
- This function is called when CrewAI needs to call a tool that requires authorization.
- Authorization is handled before executing the tool.
- This function overrides the ArcadeToolManager's default auth flow performed by ArcadeToolManager.authorize_tool
- """
- # Get authorization status
- auth_response = manager.authorize(tool_name, USER_ID)
-
- # If the user is not authorized for the tool,
- # then we need to handle the authorization before executing the tool
- if not manager.is_authorized(auth_response.id):
- print(f"Authorization required for tool: '{tool_name}' with inputs:")
- for input_name, input_value in tool_input.items():
- print(f" {input_name}: {input_value}")
- # Handle authorization
- print(f"\nTo authorize, visit: {auth_response.url}")
- # Block until the user has completed the authorization
- auth_response = manager.wait_for_auth(auth_response)
-
- # Ensure authorization completed successfully
- if not manager.is_authorized(auth_response.id):
- raise ValueError(f"Authorization failed for {tool_name}. URL: {auth_response.url}")
- else:
- print(f"Authorization already granted for tool: '{tool_name}' with inputs:")
- for input_name, input_value in tool_input.items():
- print(f" {input_name}: {input_value}")
-
-
-def tool_manager_callback(tool_manager: ArcadeToolManager, tool_name: str, **tool_input: dict[str, Any]) -> Any:
- """Tool executor callback with custom auth flow for the ArcadeToolManager
-
- ArcadeToolManager's default executor handles authorization and tool execution.
- This function overrides the default executor to handle authorization in a custom way and then executes the tool.
- """
- custom_auth_flow(tool_manager, tool_name, **tool_input)
- return tool_manager.execute_tool(USER_ID, tool_name, **tool_input)
-
-
-manager = ArcadeToolManager(executor=tool_manager_callback)
-
-tools = manager.get_tools(tools=["Gmail.ListEmails"], toolkits=["Slack"])
-
-crew_agent = Agent(
- role="Main Agent",
- backstory="You are a helpful assistant",
- goal="Help the user with their requests",
- tools=tools,
- allow_delegation=False,
- verbose=True,
- llm=LLM(model="gpt-4o"),
-)
-
-task = Task(
- description="Get the 5 most recent emails from the user's inbox and summarize them and recommend a response for each.",
- expected_output="A bulleted list with a one sentence summary of each email and a recommended response to the email.",
- agent=crew_agent,
- tools=crew_agent.tools,
-)
-
-crew = Crew(
- agents=[crew_agent],
- tasks=[task],
- verbose=True,
- memory=True,
-)
-
-result = crew.kickoff()
-
-print("\n\n\n ------------ Result ------------ \n\n\n")
-print(result)
-```
-
-
-
-## Next steps
-
-Now you're ready to integrate Arcade tools with a custom auth flow into your own CrewAI agent team.
diff --git a/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx
index acf27603a..4ee83289f 100644
--- a/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx
+++ b/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx
@@ -1,138 +1,337 @@
---
-title: "Use Arcade tools with CrewAI"
-description: "Integrate Arcade tools into your CrewAI applications"
+title: "Setup Arcade tools with CrewAI"
+description: "Learn how to use Arcade tools in CrewAI applications"
---
-import { Steps } from "nextra/components";
-import ToggleContent from "@/app/_components/toggle-content";
+import { Steps, Callout } from "nextra/components";
-## Use CrewAI with Arcade
+# Use Arcade tools with CrewAI
-This guide explains how to integrate Arcade tools into your CrewAI application. Follow the step-by-step instructions below. If a tool requires authorization, an authorization URL will appear in the console, waiting for your approval. This process ensures that only the tools you choose to authorize execute.
+[CrewAI](https://www.crewai.com/) is an agentic framework optimized for building task-oriented multi-agent systems. This guide explains how to integrate Arcade tools into your CrewAI applications.
-To tailor the tool authorization flow to meet your application's specific needs, check out the [Custom Auth Flow with CrewAI](/get-started/agent-frameworks/crewai/custom-auth-flow) guide.
+
+
-
+You will build a CrewAI agent that uses Arcade tools to help users with Gmail and Slack.
+
+
-### Prerequisites
+
+-
- [Obtain an Arcade API key](/get-started/setup/api-keys)
+- The [`uv` package manager](https://docs.astral.sh/uv/)
+
+
+
+
+
+- How to retrieve Arcade tools and convert them to CrewAI format
+- How to build a CrewAI agent with Arcade tools
+- How to implement "just in time" (JIT) tool authorization using Arcade's client
+
+
+
-### Set up your environment
+## The agent architecture you will build in this guide
-Install the required package, and ensure your environment variables are set with your Arcade and OpenAI API keys:
+CrewAI provides a [Crew](https://docs.crewai.com/reference/crew) class that implements a multi-agent system. It provides an interface for you to define the agents, tasks, and memory. In this guide, you will manually keep track of the agent's history and state, and use the `kickoff` method to invoke the agent in an agentic loop.
+
+## Integrate Arcade tools into a CrewAI agent
+
+
+
+### Create a new project
+
+Create a new directory for your project and initialize a new virtual environment:
```bash
-pip install crewai-arcade
+mkdir crewai-arcade-example
+cd crewai-arcade-example
+uv init
+uv venv
+source .venv/bin/activate
```
-### Configure API keys
-
-Provide your Arcade and OpenAI API keys. You can store them in environment variables like so:
+Install the necessary packages:
```bash
-export ARCADE_API_KEY="your_arcade_api_key"
-export OPENAI_API_KEY="your_openai_api_key"
+uv add 'crewai[tools]' arcadepy
```
-### Get Arcade tools
+Create a new file called `.env` and add the following environment variables:
-Use the `ArcadeToolManager` to initialize, add, and get Arcade tools:
+```env filename=".env"
+# Arcade API key
+ARCADE_API_KEY=YOUR_ARCADE_API_KEY
+# Arcade user ID (this is the email address you used to login to Arcade)
+ARCADE_USER_ID={arcade_user_id}
+# OpenAI API key
+OPENAI_API_KEY=YOUR_OPENAI_API_KEY
+```
+
+### Import the necessary packages
-```python
-from crewai_arcade import ArcadeToolManager
+Create a new file called `main.py` and add the following code:
-manager = ArcadeToolManager(default_user_id="{arcade_user_id}")
+```python filename="main.py"
+from typing import Any
+from arcadepy import Arcade
+from arcadepy.types import ToolDefinition
+from crewai.tools import BaseTool
+from crewai import Agent
+from crewai.events.event_listener import EventListener
+from pydantic import BaseModel, Field, create_model
+from dotenv import load_dotenv
+import os
-"""
-Retrieves the provided tools and/or MCP Servers as CrewAI StructuredTools.
-"""
-tools = manager.get_tools(tools=["Gmail.ListEmails"], toolkits=["Slack"])
```
-### Use tools in your CrewAI agent team
-
-Create a Crew that uses your tools. When the tool is called, you will be prompted to go visit an authorization page to authorize the tool before it executes.
-
-```python
-from crewai import Agent, Crew, Task
-from crewai.llm import LLM
-
-crew_agent = Agent(
- role="Main Agent",
- backstory="You are a helpful assistant",
- goal="Help the user with their requests",
- tools=tools,
- allow_delegation=False,
- verbose=True,
- llm=LLM(model="gpt-4o"),
-)
-
-task = Task(
- description="Get the 5 most recent emails from the user's inbox and summarize them and recommend a response for each.",
- expected_output="A bulleted list with a one sentence summary of each email and a recommended response to the email.",
- agent=crew_agent,
- tools=crew_agent.tools,
-)
-
-crew = Crew(
- agents=[crew_agent],
- tasks=[task],
- verbose=True,
- memory=True,
-)
-
-result = crew.kickoff()
-
-print("\n\n\n ------------ Result ------------ \n\n\n")
-print(result)
+This includes many imports, here's a breakdown:
+
+- Arcade imports:
+ - `Arcade`: The Arcade client, used to interact with the Arcade API.
+ - `ToolDefinition`: The tool definition type, used to define the input and output of a tool.
+- CrewAI imports:
+ - `BaseTool`: The base tool class, used to create custom CrewAI tools.
+ - `Agent`: The CrewAI agent class, used to create an agent.
+ - `EventListener`: The event listener class, used to suppress CrewAI's rich panel output.
+- Other imports:
+ - `pydantic` imports: Used for data validation and model creation when converting Arcade tools to LangChain tools.
+ - `typing.Any`: A type hint for the any type.
+ - `load_dotenv`: Loads the environment variables from the `.env` file.
+ - `os`: The operating system module, used to interact with the operating system.
+
+### Configure the agent
+
+The rest of the code uses these variables to customize the agent and manage the tools. Feel free to configure them to your liking. Here, the `EventListener` class is used to suppress CrewAI's rich panel output, which is useful for debugging but verbose for an interactive session like the one you're building.
+
+```python filename="main.py"
+# Load environment variables from the .env file
+load_dotenv()
+
+# The Arcade User ID identifies who is authorizing each service.
+ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
+# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
+MCP_SERVERS = ["Slack"]
+# This determines individual tools. Useful to pick specific tools when you don't need all of them.
+TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
+# This determines the maximum number of tool definitions Arcade will return per MCP server
+TOOL_LIMIT = 30
+# This determines which LLM model will be used inside the agent
+MODEL = "openai/gpt-5-mini"
+# The individual objective that guides the agent's decision-making
+AGENT_GOAL = "Help the user with all their requests"
+# Provides context and personality to the agent, enriching interactions
+AGENT_BACKSTORY = "You are a helpful assistant that can assist with Gmail and Slack."
+# This defines the Agent's role. A short description of its function and expertise
+AGENT_NAME = "Communication Manager"
+
+# Suppress CrewAI's rich panel output
+EventListener().formatter.verbose = False
```
-
+### Write a utility function to transform Arcade tool definitions into Pydantic models
+
+In this utility function, you transform an Arcade tool definition into a Pydantic model. Later, you will transform these models to construct tools in the format expected by CrewAI. The `_build_args_model` function extracts the tools' parameters, name, and description, and maps them to a Pydantic model.
+
+```python filename="main.py"
+TYPE_MAP: dict[str, type] = {
+ "string": str,
+ "number": float,
+ "integer": int,
+ "boolean": bool,
+ "array": list,
+ "json": dict,
+}
+
+
+def _python_type(val_type: str) -> type:
+ t = TYPE_MAP.get(val_type)
+ if t is None:
+ raise ValueError(f"Unsupported Arcade value type: {val_type}")
+ return t
+
+
+def _build_args_model(tool_def: ToolDefinition) -> type[BaseModel]:
+ fields: dict[str, Any] = {}
+ for param in tool_def.input.parameters or []:
+ param_type = _python_type(param.value_schema.val_type)
+ if param_type is list and param.value_schema.inner_val_type:
+ inner = _python_type(param.value_schema.inner_val_type)
+ param_type = list[inner] # type: ignore[valid-type]
+ default = ... if param.required else None
+ fields[param.name] = (
+ param_type,
+ Field(default=default, description=param.description or ""),
+ )
+ return create_model(f"{tool_def.name}Input", **fields)
+```
+
+### Write a custom class that extends the CrewAI BaseTool class
+
+Here, you define the `ArcadeTool` class that extends the CrewAI `BaseTool` class to add the following capability:
+
+- Authorize the tool with the Arcade client with the `_auth_tool` helper function
+- Execute the tool with the Arcade client with the `_run` method
+
+
+ This class captures the authorization flow outside of the agent's context,
+ which is a good practice for security and context engineering. By handling
+ everything in the `ArcadeTool` class, you remove the risk of the LLM replacing
+ the authorization URL or leaking it, and you keep the context free from any
+ authorization-related traces, which reduces the risk of hallucinations, and
+ reduces context bloat.
+
+
+```python filename="main.py"
+class ArcadeTool(BaseTool):
+ """A CrewAI tool backed by an Arcade tool definition."""
+
+ name: str
+ description: str
+ args_schema: type[BaseModel]
+
+ # Internal fields (not exposed to the agent)
+ arcade_tool_name: str = ""
+ user_id: str = ""
+ _client: Arcade | None = None
+
+ def _auth_tool(self):
+ auth = self._client.tools.authorize(
+ tool_name=self.arcade_tool_name,
+ user_id=self.user_id,
+ )
+ if auth.status != "completed":
+ print(f"Authorization required. Visit: {auth.url}")
+ self._client.auth.wait_for_completion(auth)
-
+ def _run(self, **kwargs: Any) -> str:
+ if self._client is None:
+ self._client = Arcade()
-```python
-from crewai import Agent, Crew, Task
-from crewai.llm import LLM
-from crewai_arcade import ArcadeToolManager
+ self._auth_tool()
-manager = ArcadeToolManager(default_user_id="{arcade_user_id}")
+ print(f"Calling {self.arcade_tool_name}...")
-tools = manager.get_tools(tools=["Gmail.ListEmails"])
+ result = self._client.tools.execute(
+ tool_name=self.arcade_tool_name,
+ input=kwargs,
+ user_id=self.user_id,
+ )
+ if not result.success:
+ return f"Tool error: {result.output.error.message}"
-crew_agent = Agent(
- role="Main Agent",
- backstory="You are a helpful assistant",
- goal="Help the user with their requests",
- tools=tools,
- allow_delegation=False,
- verbose=True,
- llm=LLM(model="gpt-4o"),
-)
+ print(f"Call to {self.arcade_tool_name} successful, the agent will now process the result...")
+ return result.output.value
+```
+
+### Retrieve Arcade tools and transform them into CrewAI tools
+
+Here you get the Arcade tools you want the agent to utilize, and transform them into CrewAI tools. The first step is to initialize the Arcade client, and get the tools you want to work with.
+
+Here's a breakdown of what it does for clarity:
+
+- retrieve tools from all configured MCP servers (defined in the `MCP_SERVERS` variable)
+- retrieve individual tools (defined in the `TOOLS` variable)
+- transform the Arcade tools to CrewAI tools with the `ArcadeTool` class you defined earlier
+
+```python filename="main.py"
+def get_arcade_tools(
+ client: Arcade,
+ *,
+ tools: list[str] | None = None,
+ mcp_servers: list[str] | None = None,
+ user_id: str = "",
+) -> list[ArcadeTool]:
+ if not tools and not mcp_servers:
+ raise ValueError("Provide at least one tool name or toolkit name")
+
+ definitions: list[ToolDefinition] = []
+
+ if tools:
+ for name in tools:
+ definitions.append(client.tools.get(name=name))
+
+ if mcp_servers:
+ for tk in mcp_servers:
+ page = client.tools.list(toolkit=tk)
+ definitions.extend(page.items)
+
+ result: list[ArcadeTool] = []
+ for defn in definitions:
+ sanitized_name = defn.qualified_name.replace(".", "_")
+ t = ArcadeTool(
+ client=client,
+ name=sanitized_name,
+ description=defn.description,
+ args_schema=_build_args_model(defn),
+ arcade_tool_name=defn.qualified_name,
+ user_id=user_id,
+ )
+ result.append(t)
+
+ return result
+```
+
+### Create the main function
+
+The main function is where you:
+
+- Get the Arcade tools from the configured MCP servers
+- Create an agent with the Arcade tools
+- Initialize the conversation
+- Run the loop
+
+```python filename="main.py"
+def main():
+ client = Arcade()
+
+ arcade_tools = get_arcade_tools(
+ client,
+ tools=TOOLS,
+ mcp_servers=MCP_SERVERS,
+ user_id=ARCADE_USER_ID,
+ )
+
+ agent = Agent(
+ role=AGENT_NAME,
+ goal=AGENT_GOAL,
+ backstory=AGENT_BACKSTORY,
+ tools=arcade_tools,
+ )
+
+ history = []
+ print("Agent ready. Type 'exit' to quit.\n")
+
+ while True:
+ user_input = input("> ")
+ if user_input.strip().lower() in ("exit", "quit"):
+ break
+
+ history.append({"role": "user", "content": user_input})
+ result = agent.kickoff(history)
+ history.append({"role": "assistant", "content": result.raw})
+ print(f"\n{result.raw}\n")
-task = Task(
- description="Get the 5 most recent emails from the user's inbox and summarize them and recommend a response for each.",
- expected_output="A bulleted list with a one sentence summary of each email and a recommended response to the email.",
- agent=crew_agent,
- tools=crew_agent.tools,
-)
-crew = Crew(
- agents=[crew_agent],
- tasks=[task],
- verbose=True,
- memory=True,
-)
+if __name__ == "__main__":
+ main()
+```
-result = crew.kickoff()
+### Run the agent
-print("\n\n\n ------------ Result ------------ \n\n\n")
-print(result)
+```bash
+uv run main.py
```
-
+You should see the agent responding to your prompts like any model, as well as handling any tool calls and authorization requests. Here are some example prompts you can try:
+
+- "Send me an email with a random haiku about OpenAI Agents"
+- "Summarize my latest 3 emails"
+
+
## Tips for selecting tools
@@ -146,3 +345,192 @@ Now that you have integrated Arcade tools into your CrewAI agent team, you can:
- Experiment with different toolkits, such as "Math" or "Search."
- Customize the agent's prompts for specific tasks.
- Customize the tool authorization and execution flow to meet your application's requirements.
+
+## Example code
+
+
+**main.py** (full file)
+
+```python filename="main.py"
+from typing import Any
+from arcadepy import Arcade
+from arcadepy.types import ToolDefinition
+from crewai.tools import BaseTool
+from crewai import Agent
+from crewai.events.event_listener import EventListener
+from pydantic import BaseModel, Field, create_model
+from dotenv import load_dotenv
+import os
+
+# Load environment variables from the .env file
+load_dotenv()
+
+# The Arcade User ID identifies who is authorizing each service.
+ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
+# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
+MCP_SERVERS = ["Slack"]
+# This determines individual tools. Useful to pick specific tools when you don't need all of them.
+TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
+# This determines the maximum number of tool definitions Arcade will return per MCP server
+TOOL_LIMIT = 30
+# This determines which LLM model will be used inside the agent
+MODEL = "openai/gpt-5-mini"
+# The individual objective that guides the agent's decision-making
+AGENT_GOAL = "Help the user with all their requests"
+# Provides context and personality to the agent, enriching interactions
+AGENT_BACKSTORY = "You are a helpful assistant that can assist with Gmail and Slack."
+# This defines the Agent's role. A short description of its function and expertise
+AGENT_NAME = "Communication Manager"
+
+# Suppress CrewAI's rich panel output
+EventListener().formatter.verbose = False
+
+TYPE_MAP: dict[str, type] = {
+ "string": str,
+ "number": float,
+ "integer": int,
+ "boolean": bool,
+ "array": list,
+ "json": dict,
+}
+
+
+def _python_type(val_type: str) -> type:
+ t = TYPE_MAP.get(val_type)
+ if t is None:
+ raise ValueError(f"Unsupported Arcade value type: {val_type}")
+ return t
+
+
+def _build_args_model(tool_def: ToolDefinition) -> type[BaseModel]:
+ fields: dict[str, Any] = {}
+ for param in tool_def.input.parameters or []:
+ param_type = _python_type(param.value_schema.val_type)
+ if param_type is list and param.value_schema.inner_val_type:
+ inner = _python_type(param.value_schema.inner_val_type)
+ param_type = list[inner] # type: ignore[valid-type]
+ default = ... if param.required else None
+ fields[param.name] = (
+ param_type,
+ Field(default=default, description=param.description or ""),
+ )
+ return create_model(f"{tool_def.name}Input", **fields)
+
+
+class ArcadeTool(BaseTool):
+ """A CrewAI tool backed by an Arcade tool definition."""
+
+ name: str
+ description: str
+ args_schema: type[BaseModel]
+
+ # Internal fields (not exposed to the agent)
+ arcade_tool_name: str = ""
+ user_id: str = ""
+ _client: Arcade | None = None
+
+ def _auth_tool(self):
+ auth = self._client.tools.authorize(
+ tool_name=self.arcade_tool_name,
+ user_id=self.user_id,
+ )
+ if auth.status != "completed":
+ print(f"Authorization required. Visit: {auth.url}")
+ self._client.auth.wait_for_completion(auth)
+
+ def _run(self, **kwargs: Any) -> str:
+ if self._client is None:
+ self._client = Arcade()
+
+ self._auth_tool()
+
+ print(f"Calling {self.arcade_tool_name}...")
+
+ result = self._client.tools.execute(
+ tool_name=self.arcade_tool_name,
+ input=kwargs,
+ user_id=self.user_id,
+ )
+
+ if not result.success:
+ return f"Tool error: {result.output.error.message}"
+
+ print(f"Call to {self.arcade_tool_name} successful, the agent will now process the result...")
+ return result.output.value
+
+
+def get_arcade_tools(
+ client: Arcade,
+ *,
+ tools: list[str] | None = None,
+ mcp_servers: list[str] | None = None,
+ user_id: str = "",
+) -> list[ArcadeTool]:
+ if not tools and not mcp_servers:
+ raise ValueError("Provide at least one tool name or toolkit name")
+
+ definitions: list[ToolDefinition] = []
+
+ if tools:
+ for name in tools:
+ definitions.append(client.tools.get(name=name))
+
+ if mcp_servers:
+ for tk in mcp_servers:
+ page = client.tools.list(toolkit=tk)
+ definitions.extend(page.items)
+
+ result: list[ArcadeTool] = []
+ for defn in definitions:
+ sanitized_name = defn.qualified_name.replace(".", "_")
+ t = ArcadeTool(
+ client=client,
+ name=sanitized_name,
+ description=defn.description,
+ args_schema=_build_args_model(defn),
+ arcade_tool_name=defn.qualified_name,
+ user_id=user_id,
+ )
+ result.append(t)
+
+ return result
+
+
+def main():
+ client = Arcade()
+
+ arcade_tools = get_arcade_tools(
+ client,
+ tools=TOOLS,
+ mcp_servers=MCP_SERVERS,
+ user_id=ARCADE_USER_ID,
+ )
+
+ agent = Agent(
+ role=AGENT_NAME,
+ goal=AGENT_GOAL,
+ backstory=AGENT_BACKSTORY,
+ tools=arcade_tools,
+ )
+
+ history = []
+ print("Agent ready. Type 'exit' to quit.\n")
+
+ while True:
+ user_input = input("> ")
+ if user_input.strip().lower() in ("exit", "quit"):
+ break
+
+ history.append({"role": "user", "content": user_input})
+ result = agent.kickoff(history)
+ history.append({"role": "assistant", "content": result.raw})
+ print(f"\n{result.raw}\n")
+
+
+if __name__ == "__main__":
+ main()
+
+
+```
+
+
diff --git a/app/en/get-started/agent-frameworks/google-adk/setup-python/page.mdx b/app/en/get-started/agent-frameworks/google-adk/setup-python/page.mdx
index b6ff08ac7..0302a0560 100644
--- a/app/en/get-started/agent-frameworks/google-adk/setup-python/page.mdx
+++ b/app/en/get-started/agent-frameworks/google-adk/setup-python/page.mdx
@@ -316,7 +316,7 @@ class ArcadeTool(FunctionTool):
Here you get the Arcade tools you want the agent to utilize, and transform them into Google ADK tools. The first step is to initialize the Arcade client, and get the tools you want to work with.
-This helper function is long, here's a breakdown of what it does for clarity:
+Here's a breakdown of what it does for clarity:
- retrieve tools from all configured MCP servers (defined in the `MCP_SERVERS` variable)
- retrieve individual tools (defined in the `TOOLS` variable)
@@ -743,4 +743,4 @@ if __name__ == '__main__':
asyncio.run(main())
```
-
\ No newline at end of file
+
diff --git a/app/en/get-started/agent-frameworks/openai-agents/setup-python/page.mdx b/app/en/get-started/agent-frameworks/openai-agents/setup-python/page.mdx
index 58e3d68f0..903b74371 100644
--- a/app/en/get-started/agent-frameworks/openai-agents/setup-python/page.mdx
+++ b/app/en/get-started/agent-frameworks/openai-agents/setup-python/page.mdx
@@ -1,18 +1,20 @@
---
-title: "Setup Arcade with OpenAI Agents (Python)"
-description: "Build a CLI agent with Arcade tools using the OpenAI Agents SDK for Python"
+title: "Setup Arcade with OpenAI Agents SDK"
+description: "Learn how to use Arcade tools in OpenAI Agents applications"
---
-import { Steps, Callout } from "nextra/components";
+import { Steps, Tabs, Callout } from "nextra/components";
-# Setup Arcade with OpenAI Agents (Python)
+# Setup Arcade with OpenAI Agents SDK
-The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) is a Python library for building AI agents. It provides an interface for defining agents with tools, system prompts, and model configurations.
+Learn how to integrate Arcade tools using OpenAI Agents primitives.
+
+The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) is a popular Python library for building AI agents. It builds on top of the OpenAI API, and provides an interface for building agents.
-Build a CLI agent that uses Arcade tools to help users with Gmail and Slack
+You will implement a CLI agent that can use Arcade tools to help the user with their requests. The harness handles tools that require authorization automatically, so users don't need to worry about it.
@@ -26,106 +28,293 @@ Build a CLI agent that uses Arcade tools to help users with Gmail and Slack
-- How to retrieve Arcade tools and convert them to OpenAI Agents format
-- How to build an OpenAI Agents agent with Arcade tools
-- How to handle "just in time" (JIT) tool authorization
+- How to retrieve Arcade tools and transform them into OpenAI Agents tools
+- How to build an OpenAI Agents agent
+- How to integrate Arcade tools into the OpenAI Agents flow
+- How to implement "just in time" (JIT) tool authorization using Arcade's client
-## How Arcade integrates with OpenAI Agents
-
-The OpenAI Agents SDK provides an [Agent](https://openai.github.io/openai-agents-python/ref/agent/#agents.agent.Agent) class that implements a ReAct agent. It accepts tools in the [FunctionTool](https://openai.github.io/openai-agents-python/ref/tool/#agents.tool.FunctionTool) format. The `agents-arcade` package provides a `get_arcade_tools` function that retrieves Arcade tools and converts them to this format automatically.
+## The agent architecture you will build in this guide
-When a tool requires authorization (like Gmail or GitHub), the agent raises an `AuthorizationError` with a URL for the user to visit. After the user authorizes, the agent can retry the operation.
+The OpenAI Agents SDK provides an [Agent](https://openai.github.io/openai-agents-python/ref/agent/#agents.agent.Agent) class that implements a ReAct agent. It provides an interface for you to define the system prompt, the model, the tools, and possible sub-agents for handoffs. In this guide, you will manually keep track of the agent's history and state, and use the `run` method to invoke the agent in an agentic loop.
-## Build the agent
+## Integrate Arcade tools into an OpenAI Agents agent
### Create a new project
-Create a new directory and set up a virtual environment:
+Create a new directory for your project and initialize a new virtual environment:
```bash
mkdir openai-agents-arcade-example
cd openai-agents-arcade-example
+uv init
uv venv
source .venv/bin/activate
```
-Install the required packages:
+Install the necessary packages:
```bash
-uv pip install openai-agents arcadepy agents-arcade python-dotenv
+uv add openai-agents arcadepy
```
-Create a `.env` file with your API keys:
+Create a new file called `.env` and add the following environment variables:
```env filename=".env"
-# Arcade API key from https://app.arcade.dev
+# Arcade API key
ARCADE_API_KEY=YOUR_ARCADE_API_KEY
-# Your Arcade user ID (the email you used to sign up)
+# Arcade user ID (this is the email address you used to login to Arcade)
ARCADE_USER_ID={arcade_user_id}
# OpenAI API key
OPENAI_API_KEY=YOUR_OPENAI_API_KEY
```
-### Write the agent code
+### Import the necessary packages
-Create `main.py` with the following imports:
+Create a new file called `main.py` and add the following code:
```python filename="main.py"
from agents import Agent, Runner, TResponseInputItem
+from agents.run_context import RunContextWrapper
+from agents.tool import FunctionTool
+from agents.exceptions import AgentsException
from arcadepy import AsyncArcade
-from agents_arcade import get_arcade_tools
-from agents_arcade.errors import AuthorizationError
+from arcadepy.types.execute_tool_response import ExecuteToolResponse
from dotenv import load_dotenv
+from functools import partial
+from typing import Any
import os
import asyncio
-
-# Load environment variables
-load_dotenv()
+import json
```
+This includes many imports, here's a breakdown:
+
+- Arcade imports:
+ - `AsyncArcade`: The Arcade client, used to interact with the Arcade API.
+ - `ExecuteToolResponse`: The response type for the execute tool response.
+- OpenAI Agents imports:
+ - `Agent`: The OpenAI Agents agent, used to define an agent.
+ - `Runner`: The OpenAI Agents runner, which runs the agent in an agentic loop.
+ - `TResponseInputItem`: The response input item type, determines the type of message in the conversation history.
+ - `RunContextWrapper`: Wraps the run context, providing information such as the user ID, the tool name, tool arguments, and other contextual information different parts of the agent may need.
+ - `FunctionTool`: OpenAI Agents tool definition format.
+ - `AgentsException`: The OpenAI Agents exception, used to handle errors in the agentic loop.
+- Other imports:
+ - `load_dotenv`: Loads the environment variables from the `.env` file.
+ - `functools.partial`: Partially applies a function to a given set of arguments.
+ - `typing.Any`: A type hint for the any type.
+ - `os`: The operating system module, used to interact with the operating system.
+ - `asyncio`: The asynchronous I/O module, used to interact with the asynchronous I/O.
+ - `json`: The JSON module, used to interact with JSON data.
+
### Configure the agent
-Define which tools and MCP servers your agent can access:
+These variables customize the agent and manage the tools in the rest of the code. Feel free to configure them to your liking.
```python filename="main.py"
-# Configuration
+# Load environment variables
+load_dotenv()
+
+# The Arcade User ID identifies who is authorizing each service.
ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
-# MCP servers provide groups of related tools
+# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
MCP_SERVERS = ["Slack"]
-# Individual tools can be specified by name
+# This determines individual tools. Useful to pick specific tools when you don't need all of them.
TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
-# System prompt for the agent
+# This determines the maximum number of tool definitions Arcade will return per MCP server
+TOOL_LIMIT = 30
+# This prompt defines the behavior of the agent.
SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."
-# Model to use
+# This determines which LLM model will be used inside the agent
MODEL = "gpt-4o-mini"
```
+### Write a custom error and utility functions to help with tool calls
+
+Here, you define `ToolError` to handle errors from the Arcade tools. It wraps the `AgentsException` and provides an informative error message that the agentic loop can handle in case anything goes wrong.
+
+You also define `convert_output_to_json` to convert the output of the Arcade tools to a JSON string. This is useful because the output of the Arcade tools is not always a JSON object, and the OpenAI Agents SDK expects a JSON string.
+
+```python filename="main.py"
+# Arcade to OpenAI agent exception classes
+class ToolError(AgentsException):
+ def __init__(self, result: ExecuteToolResponse | str):
+ self.result = None
+ if isinstance(result, str):
+ self.message = result
+ else:
+ self.message = result.output.error.message
+ self.result = result
+
+ def __str__(self):
+ if self.result:
+ return f"Tool {self.result.tool_name} failed with error: {self.message}"
+ else:
+ return self.message
+
+
+def convert_output_to_json(output: Any) -> str:
+ if isinstance(output, dict) or isinstance(output, list):
+ return json.dumps(output)
+ else:
+ return str(output)
+```
+
+### Write a helper function to authorize Arcade tools
+
+This helper function implements "just in time" (JIT) tool authorization using Arcade's client. When the agent tries to execute a tool that requires authorization, the `result` object's `status` will be `"pending"`, and you can use the `authorize` method to get an authorization URL. You then wait for the user to complete the authorization and retry the tool call. If the user has already authorized the tool, the `status` will be `"completed"`, and the OAuth dance skips silently, which improves the user experience.
+
-Browse the [MCP server catalog](/resources/integrations) to see all available MCP servers and tools.
+ This function captures the authorization flow outside of the agent's context,
+ which is a good practice for security and context engineering. By handling
+ everything in the harness, you remove the risk of the LLM replacing the
+ authorization URL or leaking it, and you keep the context free from any
+ authorization-related traces, which reduces the risk of hallucinations.
+```python filename="main.py"
+async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str):
+ if not context.context.get("user_id"):
+ raise ToolError("No user ID and authorization required for tool")
+
+ result = await client.tools.authorize(
+ tool_name=tool_name,
+ user_id=context.context.get("user_id"),
+ )
+
+ if result.status != "completed":
+ print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}")
+
+ await client.auth.wait_for_completion(result)
+```
+
+### Write a helper function to execute Arcade tools
+
+This helper function shows how the OpenAI Agents framework invokes the Arcade tools. It handles the authorization flow, and then calls the tool using the `execute` method. It handles the conversion of the arguments from JSON to a dictionary (expected by Arcade) and the conversion of the output from the Arcade tool to a JSON string (expected by the OpenAI Agents framework). Here is where you call the helper functions defined earlier to authorize the tool and convert the output to a JSON string.
+
+```python filename="main.py"
+async def invoke_arcade_tool(
+ context: RunContextWrapper,
+ tool_args: str,
+ tool_name: str,
+ client: AsyncArcade,
+):
+ args = json.loads(tool_args)
+ await authorize_tool(client, context, tool_name)
+
+ print(f"Invoking tool {tool_name} with args: {args}")
+ result = await client.tools.execute(
+ tool_name=tool_name,
+ input=args,
+ user_id=context.context.get("user_id"),
+ )
+ if not result.success:
+ raise ToolError(result)
+
+ print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...")
+
+ return convert_output_to_json(result.output.value)
+```
+
+### Retrieve Arcade tools and transform them into LangChain tools
+
+Here you get the Arcade tools you want the agent to use, and transform them into OpenAI Agents tools. The first step is to initialize the Arcade client, and get the tools you want. Since OpenAI is itself an inference provider, the Arcade API provides a convenient endpoint to get the tools in the OpenAI format, which is also the format expected by the OpenAI Agents framework.
+
+This helper function is long, here's a breakdown of what it does for clarity:
+
+- retrieve tools from all configured MCP servers (defined in the `MCP_SERVERS` variable)
+- retrieve individual tools (defined in the `TOOLS` variable)
+- get the Arcade tools to OpenAI-formatted tools
+- create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client.
+
+```python filename="main.py"
+async def get_arcade_tools(
+ client: AsyncArcade | None = None,
+ tools: list[str] | None = None,
+ mcp_servers: list[str] | None = None,
+) -> list[FunctionTool]:
+
+ if not client:
+ client = AsyncArcade()
+
+ # if no tools or MCP servers are provided, raise an error
+ if not tools and not mcp_servers:
+ raise ValueError(
+ "No tools or MCP servers provided to retrieve tool definitions")
+
+ # Use the Arcade Client to get OpenAI-formatted tool definitions
+ tool_formats = []
+
+ # Retrieve individual tools if specified
+ if tools:
+ # OpenAI-formatted tool definition
+ tasks = [client.tools.formatted.get(name=tool_id, format="openai")
+ for tool_id in tools]
+ responses = await asyncio.gather(*tasks)
+ for response in responses:
+ tool_formats.append(response)
+
+ # Retrieve tools from specified toolkits
+ if mcp_servers:
+ # Create a task for each toolkit to fetche the formatted tool definition concurrently.
+ tasks = [client.tools.formatted.list(toolkit=tk, format="openai")
+ for tk in mcp_servers]
+ responses = await asyncio.gather(*tasks)
+
+ # Combine the tool definitions from each response.
+ for response in responses:
+ # Here the code assumes the returned response has an "items" attribute
+ # containing a list of ToolDefinition objects.
+ tool_formats.extend(response.items)
+
+
+ # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client.
+ tool_functions = []
+ for tool in tool_formats:
+ tool_name = tool["function"]["name"]
+ tool_description = tool["function"]["description"]
+ tool_params = tool["function"]["parameters"]
+ tool_function = FunctionTool(
+ name=tool_name,
+ description=tool_description,
+ params_json_schema=tool_params,
+ on_invoke_tool=partial(
+ invoke_arcade_tool,
+ tool_name=tool_name,
+ client=client,
+ ),
+ strict_json_schema=False,
+ )
+ tool_functions.append(tool_function)
+
+ return tool_functions
+```
+
### Create the main function
-The main function initializes the Arcade client, retrieves tools, creates an agent, and runs a conversation loop:
+The main function is where you:
+
+- Get the tools from the configured MCP servers
+- Create an agent with the configured tools
+- Initialize the conversation
+- Run the loop
+
+The loop is a while loop that captures the user input, appends it to the conversation history, and then runs the agent. The agent's response is then appended to the conversation history, and the loop continues.
+
+When a user interrupts the loop, the interruption handles via the helper function you wrote earlier.
```python filename="main.py"
async def main():
- # Initialize the Arcade client
- client = AsyncArcade()
-
- # Get tools from MCP servers and individual tool names
- tools = await get_arcade_tools(
- client,
- toolkits=MCP_SERVERS,
- tools=TOOLS
- )
+ # Get tools from the configured MCP servers
+ tools = await get_arcade_tools(mcp_servers=MCP_SERVERS,
+ tools=TOOLS)
- # Create the agent with Arcade tools
+ # Create an agent with the configured tools
agent = Agent(
name="Inbox Assistant",
instructions=SYSTEM_PROMPT,
@@ -133,15 +322,14 @@ async def main():
tools=tools,
)
- # Conversation loop
+ # initialize the conversation
history: list[TResponseInputItem] = []
+ # run the loop
while True:
prompt = input("You: ")
if prompt.lower() == "exit":
break
-
history.append({"role": "user", "content": prompt})
-
try:
result = await Runner.run(
starting_agent=agent,
@@ -150,84 +338,199 @@ async def main():
)
history = result.to_input_list()
print(f"Assistant: {result.final_output}")
- except AuthorizationError as e:
- # Display the authorization URL to the user
- print(f"Authorization required. Please visit: {e}")
- print("After authorizing, run your request again.")
+ except ToolError as e:
+ # Something went wrong with the tool call, print the error message and exit the loop
+ print(e.message)
+ break
+# Run the main function as the entry point of the script
if __name__ == "__main__":
asyncio.run(main())
```
-### Handle authorization
-
-When a tool requires OAuth authorization (like Gmail or GitHub), the `AuthorizationError` contains a URL where the user can grant access:
-
-```python
-except AuthorizationError as e:
- print(f"Please visit this URL to authorize: {e}")
-```
-
-After the user completes authorization, Arcade remembers it for that `user_id`. Future requests with the same `user_id` won't require re-authorization.
-
-
-The `user_id` should be a unique, consistent identifier for each user in your application (like their email or database ID). Arcade uses this to track which users have authorized which tools.
-
-
### Run the agent
```bash
uv run main.py
```
-Try prompts like:
+You should see the agent responding to your prompts like any model, as well as handling any tool calls and authorization requests. Here are some example prompts you can try:
-- "What are my latest emails?"
-- "Send a Slack message to #general saying hello"
-- "Summarize my last 3 emails"
+- "Send me an email with a random haiku about OpenAI Agents"
+- "Summarize my latest 3 emails"
## Key takeaways
-- **`get_arcade_tools`** retrieves Arcade tools and converts them to OpenAI Agents `FunctionTool` format
-- **`AuthorizationError`** occurs when a tool needs OAuth authorization, containing the URL for the user to visit
-- **`user_id`** tracks authorization per user - use a consistent ID for each user in your application
-- You can mix MCP servers (for groups of tools) with individual tool names
+- You can integrate Arcade tools into any agentic framework like OpenAI Agents, all you need is to transform the Arcade tools into OpenAI Agents tools and handle the authorization flow.
+- Context isolation: By handling the authorization flow outside of the agent's context, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations.
+
+## Next steps
+
+1. Try adding additional tools to the agent or modifying the tools in the catalog for a different use case by modifying the `MCP_SERVERS` and `TOOLS` variables.
+2. Try implementing a fully deterministic flow before the agentic loop, you can use this deterministic phase to prepare the context for the agent, adding things like the current date, time, or any other information that is relevant to the task at hand.
+
+## Example code
-## Complete code
+The team provides example code for you to reference:
```python filename="main.py"
from agents import Agent, Runner, TResponseInputItem
+from agents.run_context import RunContextWrapper
+from agents.tool import FunctionTool
+from agents.exceptions import AgentsException
from arcadepy import AsyncArcade
-from agents_arcade import get_arcade_tools
-from agents_arcade.errors import AuthorizationError
+from arcadepy.types.execute_tool_response import ExecuteToolResponse
from dotenv import load_dotenv
+from functools import partial
+from typing import Any
import os
import asyncio
+import json
# Load environment variables
load_dotenv()
-# Configuration
+# The Arcade User ID identifies who is authorizing each service.
ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
+# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
MCP_SERVERS = ["Slack"]
+# This determines individual tools. Useful to pick specific tools when you don't need all of them.
TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
+# This determines the maximum number of tool definitions Arcade will return per MCP server
+TOOL_LIMIT = 30
+# This prompt defines the behavior of the agent.
SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."
+# This determines which LLM model will be used inside the agent
MODEL = "gpt-4o-mini"
-async def main():
- # Initialize the Arcade client
- client = AsyncArcade()
-
- # Get tools from MCP servers and individual tool names
- tools = await get_arcade_tools(
- client,
- toolkits=MCP_SERVERS,
- tools=TOOLS
+# Arcade to OpenAI agent exception classes
+class ToolError(AgentsException):
+ def __init__(self, result: ExecuteToolResponse | str):
+ self.result = None
+ if isinstance(result, str):
+ self.message = result
+ else:
+ self.message = result.output.error.message
+ self.result = result
+
+ def __str__(self):
+ if self.result:
+ return f"Tool {self.result.tool_name} failed with error: {self.message}"
+ else:
+ return self.message
+
+
+def convert_output_to_json(output: Any) -> str:
+ if isinstance(output, dict) or isinstance(output, list):
+ return json.dumps(output)
+ else:
+ return str(output)
+
+async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str):
+ if not context.context.get("user_id"):
+ raise ToolError("No user ID and authorization required for tool")
+
+ result = await client.tools.authorize(
+ tool_name=tool_name,
+ user_id=context.context.get("user_id"),
+ )
+
+ if result.status != "completed":
+ print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}")
+
+ await client.auth.wait_for_completion(result)
+
+async def invoke_arcade_tool(
+ context: RunContextWrapper,
+ tool_args: str,
+ tool_name: str,
+ client: AsyncArcade,
+):
+ args = json.loads(tool_args)
+ await authorize_tool(client, context, tool_name)
+
+ print(f"Invoking tool {tool_name} with args: {args}")
+ result = await client.tools.execute(
+ tool_name=tool_name,
+ input=args,
+ user_id=context.context.get("user_id"),
)
+ if not result.success:
+ raise ToolError(result)
+
+ print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...")
+
+ return convert_output_to_json(result.output.value)
+
+async def get_arcade_tools(
+ client: AsyncArcade | None = None,
+ tools: list[str] | None = None,
+ mcp_servers: list[str] | None = None,
+) -> list[FunctionTool]:
+
+ if not client:
+ client = AsyncArcade()
+
+ # if no tools or MCP servers are provided, raise an error
+ if not tools and not mcp_servers:
+ raise ValueError(
+ "No tools or MCP servers provided to retrieve tool definitions")
+
+ # Use the Arcade Client to get OpenAI-formatted tool definitions
+ tool_formats = []
+
+ # Retrieve individual tools if specified
+ if tools:
+ # OpenAI-formatted tool definition
+ tasks = [client.tools.formatted.get(name=tool_id, format="openai")
+ for tool_id in tools]
+ responses = await asyncio.gather(*tasks)
+ for response in responses:
+ tool_formats.append(response)
+
+ # Retrieve tools from specified toolkits
+ if mcp_servers:
+ # Create a task for each toolkit to fetche the formatted tool definition concurrently.
+ tasks = [client.tools.formatted.list(toolkit=tk, format="openai")
+ for tk in mcp_servers]
+ responses = await asyncio.gather(*tasks)
+
+ # Combine the tool definitions from each response.
+ for response in responses:
+ # Here the code assumes the returned response has an "items" attribute
+ # containing a list of ToolDefinition objects.
+ tool_formats.extend(response.items)
+
+
+ # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client.
+ tool_functions = []
+ for tool in tool_formats:
+ tool_name = tool["function"]["name"]
+ tool_description = tool["function"]["description"]
+ tool_params = tool["function"]["parameters"]
+ tool_function = FunctionTool(
+ name=tool_name,
+ description=tool_description,
+ params_json_schema=tool_params,
+ on_invoke_tool=partial(
+ invoke_arcade_tool,
+ tool_name=tool_name,
+ client=client,
+ ),
+ strict_json_schema=False,
+ )
+ tool_functions.append(tool_function)
+
+ return tool_functions
- # Create the agent with Arcade tools
+async def main():
+ # Get tools from the configured MCP servers
+ tools = await get_arcade_tools(mcp_servers=MCP_SERVERS,
+ tools=TOOLS)
+
+ # Create an agent with the configured tools
agent = Agent(
name="Inbox Assistant",
instructions=SYSTEM_PROMPT,
@@ -235,15 +538,14 @@ async def main():
tools=tools,
)
- # Conversation loop
+ # initialize the conversation
history: list[TResponseInputItem] = []
+ # run the loop
while True:
prompt = input("You: ")
if prompt.lower() == "exit":
break
-
history.append({"role": "user", "content": prompt})
-
try:
result = await Runner.run(
starting_agent=agent,
@@ -252,16 +554,11 @@ async def main():
)
history = result.to_input_list()
print(f"Assistant: {result.final_output}")
- except AuthorizationError as e:
- print(f"Authorization required. Please visit: {e}")
- print("After authorizing, run your request again.")
+ except ToolError as e:
+ # Something went wrong with the tool call, print the error message and exit the loop
+ print(e.message)
+ break
+# Run the main function as the entry point of the script
if __name__ == "__main__":
asyncio.run(main())
-```
-
-## Next steps
-
-- Add more tools by modifying `MCP_SERVERS` and `TOOLS`
-- Build a web interface instead of CLI using frameworks like FastAPI or Flask
-- Explore [creating custom tools](/guides/create-tools/tool-basics/build-mcp-server) with the Arcade Tool SDK
diff --git a/app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx b/app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx
deleted file mode 100644
index 38bd1a1a4..000000000
--- a/app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx
+++ /dev/null
@@ -1,564 +0,0 @@
----
-title: "Setup Arcade with OpenAI Agents SDK"
-description: "Learn how to use Arcade tools in OpenAI Agents applications"
----
-
-import { Steps, Tabs, Callout } from "nextra/components";
-
-# Setup Arcade with OpenAI Agents SDK
-
-Learn how to integrate Arcade tools using OpenAI Agents primitives.
-
-The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) is a popular Python library for building AI agents. It builds on top of the OpenAI API, and provides an interface for building agents.
-
-
-
-
-You will implement a CLI agent that can use Arcade tools to help the user with their requests. The harness handles tools that require authorization automatically, so users don't need to worry about it.
-
-
-
-
-
--
-- [Obtain an Arcade API key](/get-started/setup/api-keys)
-- The [`uv` package manager](https://docs.astral.sh/uv/)
-
-
-
-
-
-- How to retrieve Arcade tools and transform them into OpenAI Agents tools
-- How to build an OpenAI Agents agent
-- How to integrate Arcade tools into the OpenAI Agents flow
-- How to implement "just in time" (JIT) tool authorization using Arcade's client
-
-
-
-
-## The agent architecture you will build in this guide
-
-The OpenAI Agents SDK provides an [Agent](https://openai.github.io/openai-agents-python/ref/agent/#agents.agent.Agent) class that implements a ReAct agent. It provides an interface for you to define the system prompt, the model, the tools, and possible sub-agents for handoffs. In this guide, you will manually keep track of the agent's history and state, and use the `run` method to invoke the agent in an agentic loop.
-
-## Integrate Arcade tools into an OpenAI Agents agent
-
-
-
-### Create a new project
-
-Create a new directory for your project and initialize a new virtual environment:
-
-```bash
-mkdir openai-agents-arcade-example
-cd openai-agents-arcade-example
-uv init
-uv venv
-source .venv/bin/activate
-```
-
-Install the necessary packages:
-
-```bash
-uv add openai-agents arcadepy
-```
-
-Create a new file called `.env` and add the following environment variables:
-
-```env filename=".env"
-# Arcade API key
-ARCADE_API_KEY=YOUR_ARCADE_API_KEY
-# Arcade user ID (this is the email address you used to login to Arcade)
-ARCADE_USER_ID={arcade_user_id}
-# OpenAI API key
-OPENAI_API_KEY=YOUR_OPENAI_API_KEY
-```
-
-### Import the necessary packages
-
-Create a new file called `main.py` and add the following code:
-
-```python filename="main.py"
-from agents import Agent, Runner, TResponseInputItem
-from agents.run_context import RunContextWrapper
-from agents.tool import FunctionTool
-from agents.exceptions import AgentsException
-from arcadepy import AsyncArcade
-from arcadepy.types.execute_tool_response import ExecuteToolResponse
-from dotenv import load_dotenv
-from functools import partial
-from typing import Any
-import os
-import asyncio
-import json
-```
-
-This includes many imports, here's a breakdown:
-
-- Arcade imports:
- - `AsyncArcade`: The Arcade client, used to interact with the Arcade API.
- - `ExecuteToolResponse`: The response type for the execute tool response.
-- OpenAI Agents imports:
- - `Agent`: The OpenAI Agents agent, used to define an agent.
- - `Runner`: The OpenAI Agents runner, which runs the agent in an agentic loop.
- - `TResponseInputItem`: The response input item type, determines the type of message in the conversation history.
- - `RunContextWrapper`: Wraps the run context, providing information such as the user ID, the tool name, tool arguments, and other contextual information different parts of the agent may need.
- - `FunctionTool`: OpenAI Agents tool definition format.
- - `AgentsException`: The OpenAI Agents exception, used to handle errors in the agentic loop.
-- Other imports:
- - `load_dotenv`: Loads the environment variables from the `.env` file.
- - `functools.partial`: Partially applies a function to a given set of arguments.
- - `typing.Any`: A type hint for the any type.
- - `os`: The operating system module, used to interact with the operating system.
- - `asyncio`: The asynchronous I/O module, used to interact with the asynchronous I/O.
- - `json`: The JSON module, used to interact with JSON data.
-
-### Configure the agent
-
-These variables customize the agent and manage the tools in the rest of the code. Feel free to configure them to your liking.
-
-```python filename="main.py"
-# Load environment variables
-load_dotenv()
-
-# The Arcade User ID identifies who is authorizing each service.
-ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
-# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
-MCP_SERVERS = ["Slack"]
-# This determines individual tools. Useful to pick specific tools when you don't need all of them.
-TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
-# This determines the maximum number of tool definitions Arcade will return per MCP server
-TOOL_LIMIT = 30
-# This prompt defines the behavior of the agent.
-SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."
-# This determines which LLM model will be used inside the agent
-MODEL = "gpt-4o-mini"
-```
-
-### Write a custom error and utility functions to help with tool calls
-
-Here, you define `ToolError` to handle errors from the Arcade tools. It wraps the `AgentsException` and provides an informative error message that the agentic loop can handle in case anything goes wrong.
-
-You also define `convert_output_to_json` to convert the output of the Arcade tools to a JSON string. This is useful because the output of the Arcade tools is not always a JSON object, and the OpenAI Agents SDK expects a JSON string.
-
-```python filename="main.py"
-# Arcade to OpenAI agent exception classes
-class ToolError(AgentsException):
- def __init__(self, result: ExecuteToolResponse | str):
- self.result = None
- if isinstance(result, str):
- self.message = result
- else:
- self.message = result.output.error.message
- self.result = result
-
- def __str__(self):
- if self.result:
- return f"Tool {self.result.tool_name} failed with error: {self.message}"
- else:
- return self.message
-
-
-def convert_output_to_json(output: Any) -> str:
- if isinstance(output, dict) or isinstance(output, list):
- return json.dumps(output)
- else:
- return str(output)
-```
-
-### Write a helper function to authorize Arcade tools
-
-This helper function implements "just in time" (JIT) tool authorization using Arcade's client. When the agent tries to execute a tool that requires authorization, the `result` object's `status` will be `"pending"`, and you can use the `authorize` method to get an authorization URL. You then wait for the user to complete the authorization and retry the tool call. If the user has already authorized the tool, the `status` will be `"completed"`, and the OAuth dance skips silently, which improves the user experience.
-
-
- This function captures the authorization flow outside of the agent's context,
- which is a good practice for security and context engineering. By handling
- everything in the harness, you remove the risk of the LLM replacing the
- authorization URL or leaking it, and you keep the context free from any
- authorization-related traces, which reduces the risk of hallucinations.
-
-
-```python filename="main.py"
-async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str):
- if not context.context.get("user_id"):
- raise ToolError("No user ID and authorization required for tool")
-
- result = await client.tools.authorize(
- tool_name=tool_name,
- user_id=context.context.get("user_id"),
- )
-
- if result.status != "completed":
- print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}")
-
- await client.auth.wait_for_completion(result)
-```
-
-### Write a helper function to execute Arcade tools
-
-This helper function shows how the OpenAI Agents framework invokes the Arcade tools. It handles the authorization flow, and then calls the tool using the `execute` method. It handles the conversion of the arguments from JSON to a dictionary (expected by Arcade) and the conversion of the output from the Arcade tool to a JSON string (expected by the OpenAI Agents framework). Here is where you call the helper functions defined earlier to authorize the tool and convert the output to a JSON string.
-
-```python filename="main.py"
-async def invoke_arcade_tool(
- context: RunContextWrapper,
- tool_args: str,
- tool_name: str,
- client: AsyncArcade,
-):
- args = json.loads(tool_args)
- await authorize_tool(client, context, tool_name)
-
- print(f"Invoking tool {tool_name} with args: {args}")
- result = await client.tools.execute(
- tool_name=tool_name,
- input=args,
- user_id=context.context.get("user_id"),
- )
- if not result.success:
- raise ToolError(result)
-
- print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...")
-
- return convert_output_to_json(result.output.value)
-```
-
-### Retrieve Arcade tools and transform them into LangChain tools
-
-Here you get the Arcade tools you want the agent to use, and transform them into OpenAI Agents tools. The first step is to initialize the Arcade client, and get the tools you want. Since OpenAI is itself an inference provider, the Arcade API provides a convenient endpoint to get the tools in the OpenAI format, which is also the format expected by the OpenAI Agents framework.
-
-This helper function is long, here's a breakdown of what it does for clarity:
-
-- retrieve tools from all configured MCP servers (defined in the `MCP_SERVERS` variable)
-- retrieve individual tools (defined in the `TOOLS` variable)
-- get the Arcade tools to OpenAI-formatted tools
-- create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client.
-
-```python filename="main.py"
-async def get_arcade_tools(
- client: AsyncArcade | None = None,
- tools: list[str] | None = None,
- mcp_servers: list[str] | None = None,
-) -> list[FunctionTool]:
-
- if not client:
- client = AsyncArcade()
-
- # if no tools or MCP servers are provided, raise an error
- if not tools and not mcp_servers:
- raise ValueError(
- "No tools or MCP servers provided to retrieve tool definitions")
-
- # Use the Arcade Client to get OpenAI-formatted tool definitions
- tool_formats = []
-
- # Retrieve individual tools if specified
- if tools:
- # OpenAI-formatted tool definition
- tasks = [client.tools.formatted.get(name=tool_id, format="openai")
- for tool_id in tools]
- responses = await asyncio.gather(*tasks)
- for response in responses:
- tool_formats.append(response)
-
- # Retrieve tools from specified toolkits
- if mcp_servers:
- # Create a task for each toolkit to fetche the formatted tool definition concurrently.
- tasks = [client.tools.formatted.list(toolkit=tk, format="openai")
- for tk in mcp_servers]
- responses = await asyncio.gather(*tasks)
-
- # Combine the tool definitions from each response.
- for response in responses:
- # Here the code assumes the returned response has an "items" attribute
- # containing a list of ToolDefinition objects.
- tool_formats.extend(response.items)
-
-
- # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client.
- tool_functions = []
- for tool in tool_formats:
- tool_name = tool["function"]["name"]
- tool_description = tool["function"]["description"]
- tool_params = tool["function"]["parameters"]
- tool_function = FunctionTool(
- name=tool_name,
- description=tool_description,
- params_json_schema=tool_params,
- on_invoke_tool=partial(
- invoke_arcade_tool,
- tool_name=tool_name,
- client=client,
- ),
- strict_json_schema=False,
- )
- tool_functions.append(tool_function)
-
- return tool_functions
-```
-
-### Create the main function
-
-The main function is where you:
-
-- Get the tools from the configured MCP servers
-- Create an agent with the configured tools
-- Initialize the conversation
-- Run the loop
-
-The loop is a while loop that captures the user input, appends it to the conversation history, and then runs the agent. The agent's response is then appended to the conversation history, and the loop continues.
-
-When a user interrupts the loop, the interruption handles via the helper function you wrote earlier.
-
-```python filename="main.py"
-async def main():
- # Get tools from the configured MCP servers
- tools = await get_arcade_tools(mcp_servers=MCP_SERVERS,
- tools=TOOLS)
-
- # Create an agent with the configured tools
- agent = Agent(
- name="Inbox Assistant",
- instructions=SYSTEM_PROMPT,
- model=MODEL,
- tools=tools,
- )
-
- # initialize the conversation
- history: list[TResponseInputItem] = []
- # run the loop
- while True:
- prompt = input("You: ")
- if prompt.lower() == "exit":
- break
- history.append({"role": "user", "content": prompt})
- try:
- result = await Runner.run(
- starting_agent=agent,
- input=history,
- context={"user_id": ARCADE_USER_ID},
- )
- history = result.to_input_list()
- print(f"Assistant: {result.final_output}")
- except ToolError as e:
- # Something went wrong with the tool call, print the error message and exit the loop
- print(e.message)
- break
-
-# Run the main function as the entry point of the script
-if __name__ == "__main__":
- asyncio.run(main())
-```
-
-### Run the agent
-
-```bash
-uv run main.py
-```
-
-You should see the agent responding to your prompts like any model, as well as handling any tool calls and authorization requests. Here are some example prompts you can try:
-
-- "Send me an email with a random haiku about OpenAI Agents"
-- "Summarize my latest 3 emails"
-
-
-
-## Key takeaways
-
-- You can integrate Arcade tools into any agentic framework like OpenAI Agents, all you need is to transform the Arcade tools into OpenAI Agents tools and handle the authorization flow.
-- Context isolation: By handling the authorization flow outside of the agent's context, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations.
-
-## Next steps
-
-1. Try adding additional tools to the agent or modifying the tools in the catalog for a different use case by modifying the `MCP_SERVERS` and `TOOLS` variables.
-2. Try implementing a fully deterministic flow before the agentic loop, you can use this deterministic phase to prepare the context for the agent, adding things like the current date, time, or any other information that is relevant to the task at hand.
-
-## Example code
-
-The team provides example code for you to reference:
-
-```python filename="main.py"
-from agents import Agent, Runner, TResponseInputItem
-from agents.run_context import RunContextWrapper
-from agents.tool import FunctionTool
-from agents.exceptions import AgentsException
-from arcadepy import AsyncArcade
-from arcadepy.types.execute_tool_response import ExecuteToolResponse
-from dotenv import load_dotenv
-from functools import partial
-from typing import Any
-import os
-import asyncio
-import json
-
-# Load environment variables
-load_dotenv()
-
-# The Arcade User ID identifies who is authorizing each service.
-ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
-# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
-MCP_SERVERS = ["Slack"]
-# This determines individual tools. Useful to pick specific tools when you don't need all of them.
-TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
-# This determines the maximum number of tool definitions Arcade will return per MCP server
-TOOL_LIMIT = 30
-# This prompt defines the behavior of the agent.
-SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."
-# This determines which LLM model will be used inside the agent
-MODEL = "gpt-4o-mini"
-
-# Arcade to OpenAI agent exception classes
-class ToolError(AgentsException):
- def __init__(self, result: ExecuteToolResponse | str):
- self.result = None
- if isinstance(result, str):
- self.message = result
- else:
- self.message = result.output.error.message
- self.result = result
-
- def __str__(self):
- if self.result:
- return f"Tool {self.result.tool_name} failed with error: {self.message}"
- else:
- return self.message
-
-
-def convert_output_to_json(output: Any) -> str:
- if isinstance(output, dict) or isinstance(output, list):
- return json.dumps(output)
- else:
- return str(output)
-
-async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str):
- if not context.context.get("user_id"):
- raise ToolError("No user ID and authorization required for tool")
-
- result = await client.tools.authorize(
- tool_name=tool_name,
- user_id=context.context.get("user_id"),
- )
-
- if result.status != "completed":
- print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}")
-
- await client.auth.wait_for_completion(result)
-
-async def invoke_arcade_tool(
- context: RunContextWrapper,
- tool_args: str,
- tool_name: str,
- client: AsyncArcade,
-):
- args = json.loads(tool_args)
- await authorize_tool(client, context, tool_name)
-
- print(f"Invoking tool {tool_name} with args: {args}")
- result = await client.tools.execute(
- tool_name=tool_name,
- input=args,
- user_id=context.context.get("user_id"),
- )
- if not result.success:
- raise ToolError(result)
-
- print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...")
-
- return convert_output_to_json(result.output.value)
-
-async def get_arcade_tools(
- client: AsyncArcade | None = None,
- tools: list[str] | None = None,
- mcp_servers: list[str] | None = None,
-) -> list[FunctionTool]:
-
- if not client:
- client = AsyncArcade()
-
- # if no tools or MCP servers are provided, raise an error
- if not tools and not mcp_servers:
- raise ValueError(
- "No tools or MCP servers provided to retrieve tool definitions")
-
- # Use the Arcade Client to get OpenAI-formatted tool definitions
- tool_formats = []
-
- # Retrieve individual tools if specified
- if tools:
- # OpenAI-formatted tool definition
- tasks = [client.tools.formatted.get(name=tool_id, format="openai")
- for tool_id in tools]
- responses = await asyncio.gather(*tasks)
- for response in responses:
- tool_formats.append(response)
-
- # Retrieve tools from specified toolkits
- if mcp_servers:
- # Create a task for each toolkit to fetche the formatted tool definition concurrently.
- tasks = [client.tools.formatted.list(toolkit=tk, format="openai")
- for tk in mcp_servers]
- responses = await asyncio.gather(*tasks)
-
- # Combine the tool definitions from each response.
- for response in responses:
- # Here the code assumes the returned response has an "items" attribute
- # containing a list of ToolDefinition objects.
- tool_formats.extend(response.items)
-
-
- # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client.
- tool_functions = []
- for tool in tool_formats:
- tool_name = tool["function"]["name"]
- tool_description = tool["function"]["description"]
- tool_params = tool["function"]["parameters"]
- tool_function = FunctionTool(
- name=tool_name,
- description=tool_description,
- params_json_schema=tool_params,
- on_invoke_tool=partial(
- invoke_arcade_tool,
- tool_name=tool_name,
- client=client,
- ),
- strict_json_schema=False,
- )
- tool_functions.append(tool_function)
-
- return tool_functions
-
-async def main():
- # Get tools from the configured MCP servers
- tools = await get_arcade_tools(mcp_servers=MCP_SERVERS,
- tools=TOOLS)
-
- # Create an agent with the configured tools
- agent = Agent(
- name="Inbox Assistant",
- instructions=SYSTEM_PROMPT,
- model=MODEL,
- tools=tools,
- )
-
- # initialize the conversation
- history: list[TResponseInputItem] = []
- # run the loop
- while True:
- prompt = input("You: ")
- if prompt.lower() == "exit":
- break
- history.append({"role": "user", "content": prompt})
- try:
- result = await Runner.run(
- starting_agent=agent,
- input=history,
- context={"user_id": ARCADE_USER_ID},
- )
- history = result.to_input_list()
- print(f"Assistant: {result.final_output}")
- except ToolError as e:
- # Something went wrong with the tool call, print the error message and exit the loop
- print(e.message)
- break
-
-# Run the main function as the entry point of the script
-if __name__ == "__main__":
- asyncio.run(main())
\ No newline at end of file
diff --git a/next.config.ts b/next.config.ts
index 87682462a..56ff53687 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -23,6 +23,14 @@ const nextConfig: NextConfig = withLlmsTxt({
withNextra({
async redirects() {
return [
+ // CrewAI custom auth flow redirect to use-arcade-tools
+ {
+ source:
+ "/:locale/get-started/agent-frameworks/crewai/custom-auth-flow",
+ destination:
+ "/:locale/get-started/agent-frameworks/crewai/use-arcade-tools",
+ permanent: true,
+ },
// "others" category removed — toolkits moved to proper categories
{
source: "/:locale/resources/integrations/others/:path*",
@@ -516,7 +524,7 @@ const nextConfig: NextConfig = withLlmsTxt({
{
source: "/:locale/home/crewai/custom-auth-flow",
destination:
- "/:locale/get-started/agent-frameworks/crewai/custom-auth-flow",
+ "/:locale/get-started/agent-frameworks/crewai/use-arcade-tools",
permanent: true,
},
{
diff --git a/package.json b/package.json
index 3751f359b..f472fbfb7 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,6 @@
"@mdx-js/react": "^3.1.1",
"@next/third-parties": "16.0.1",
"@ory/client": "1.22.7",
- "@tailwindcss/typography": "^0.5.19",
"@theguild/remark-mermaid": "0.3.0",
"@uidotdev/usehooks": "2.4.1",
"chalk": "^5.6.2",
@@ -76,6 +75,7 @@
"@biomejs/biome": "2.3.2",
"@octokit/rest": "^22.0.1",
"@tailwindcss/postcss": "4.1.16",
+ "@tailwindcss/typography": "^0.5.19",
"@types/mdast": "4.0.4",
"@types/mdx": "2.0.13",
"@types/node": "24.9.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 51d389698..32e3e0ebd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,9 +26,6 @@ importers:
'@ory/client':
specifier: 1.22.7
version: 1.22.7
- '@tailwindcss/typography':
- specifier: ^0.5.19
- version: 0.5.19(tailwindcss@4.1.16)
'@theguild/remark-mermaid':
specifier: 0.3.0
version: 0.3.0(react@19.2.3)
@@ -108,6 +105,9 @@ importers:
'@tailwindcss/postcss':
specifier: 4.1.16
version: 4.1.16
+ '@tailwindcss/typography':
+ specifier: ^0.5.19
+ version: 0.5.19(tailwindcss@4.1.16)
'@types/mdast':
specifier: 4.0.4
version: 4.0.4
diff --git a/public/_markdown/en/get-started/agent-frameworks/crewai/custom-auth-flow.md b/public/_markdown/en/get-started/agent-frameworks/crewai/custom-auth-flow.md
deleted file mode 100644
index 0b5684add..000000000
--- a/public/_markdown/en/get-started/agent-frameworks/crewai/custom-auth-flow.md
+++ /dev/null
@@ -1,147 +0,0 @@
----
-title: "Custom Auth Flow with CrewAI"
-description: "Learn how to create a custom auth flow with CrewAI"
----
-[Agent Frameworks](/en/get-started/agent-frameworks.md)
-[CrewAI](/en/get-started/agent-frameworks/crewai/use-arcade-tools.md)
-Custom auth flow
-
-## Custom Auth Flow with CrewAI
-
-In this guide, we will explore how to create a custom auth flow that will be performed before executing Arcade tools within your CrewAI team.
-
-The `ArcadeToolManager`’s built-in authorization and execution flows work well for many typical use cases. However, some scenarios call for a tailored approach. By implementing a custom auth flow, you gain flexibility in handling tool authorization. If your use case calls for a unique interface, additional approval steps, or specialized error handling, then this guide is for you.
-
-### Prerequisites
-
-- [Obtain an Arcade API key](/get-started/setup/api-keys.md)
-
-
-### Set up your environment
-
-Install the required package, and ensure your environment variables are set with your Arcade and OpenAI :
-
-```bash
-pip install crewai-arcade
-```
-
-### Configure API keys
-
-Provide your Arcade and OpenAI . You can store them in environment variables like so:
-
-```bash
-export ARCADE_API_KEY="your_arcade_api_key"
-export OPENAI_API_KEY="your_openai_api_key"
-```
-
-### Define your custom auth flow
-
-The custom auth flow defined in the following code snippet is a function that will be called whenever CrewAI needs to call a .
-
-```python
-from typing import Any
-
-from crewai_arcade import ArcadeToolManager
-
-USER_ID = "{arcade_user_id}"
-
-def custom_auth_flow(
- manager: ArcadeToolManager, tool_name: str, **tool_input: dict[str, Any]
-) -> Any:
- """Custom auth flow for the ArcadeToolManager
-
- This function is called when CrewAI needs to call a tool that requires authorization.
- Authorization is handled before executing the tool.
- This function overrides the ArcadeToolManager's default auth flow performed by ArcadeToolManager.authorize_tool
- """
- # Get authorization status
- auth_response = manager.authorize(tool_name, USER_ID)
-
- # If the user is not authorized for the tool,
- # then we need to handle the authorization before executing the tool
- if not manager.is_authorized(auth_response.id):
- print(f"Authorization required for tool: '{tool_name}' with inputs:")
- for input_name, input_value in tool_input.items():
- print(f" {input_name}: {input_value}")
- # Handle authorization
- print(f"\nTo authorize, visit: {auth_response.url}")
- # Block until the user has completed the authorization
- auth_response = manager.wait_for_auth(auth_response)
-
- # Ensure authorization completed successfully
- if not manager.is_authorized(auth_response.id):
- raise ValueError(f"Authorization failed for {tool_name}. URL: {auth_response.url}")
- else:
- print(f"Authorization already granted for tool: '{tool_name}' with inputs:")
- for input_name, input_value in tool_input.items():
- print(f" {input_name}: {input_value}")
-
-
-def tool_manager_callback(tool_manager: ArcadeToolManager, tool_name: str, **tool_input: dict[str, Any]) -> Any:
- """Tool executor callback with custom auth flow for the ArcadeToolManager
-
- ArcadeToolManager's default executor handles authorization and tool execution.
- This function overrides the default executor to handle authorization in a custom way and then executes the tool.
- """
- custom_auth_flow(tool_manager, tool_name, **tool_input)
- return tool_manager.execute_tool(USER_ID, tool_name, **tool_input)
-```
-
-### Get Arcade tools
-
-You can now provide the manager callback to the `ArcadeToolManager` upon initialization:
-
-```python
-# Provide the tool manager callback to the ArcadeToolManager
-manager = ArcadeToolManager(executor=tool_manager_callback)
-
-# Retrieve the provided tools and/or MCP Servers as CrewAI StructuredTools.
-tools = manager.get_tools(tools=["Gmail.ListEmails"], toolkits=["Slack"])
-```
-
-### Use tools in your CrewAI agent team
-
-Create a Crew that uses your tools with the custom auth flow. When the is called, your tool manager callback will be called to handle the authorization and then the tool will be executed.
-
-```python
-from crewai import Agent, Crew, Task
-from crewai.llm import LLM
-
-crew_agent = Agent(
- role="Main Agent",
- backstory="You are a helpful assistant",
- goal="Help the user with their requests",
- tools=tools,
- allow_delegation=False,
- verbose=True,
- llm=LLM(model="gpt-4o"),
-)
-
-task = Task(
- description="Get the 5 most recent emails from the user's inbox and summarize them and recommend a response for each.",
- expected_output="A bulleted list with a one sentence summary of each email and a recommended response to the email.",
- agent=crew_agent,
- tools=crew_agent.tools,
-)
-
-crew = Crew(
- agents=[crew_agent],
- tasks=[task],
- verbose=True,
- memory=True,
-)
-
-result = crew.kickoff()
-
-print("\n\n\n ------------ Result ------------ \n\n\n")
-print(result)
-```
-
-## Next steps
-
-Now you’re ready to integrate Arcade tools with a custom auth flow into your own CrewAI team.
-
-Last updated on January 30, 2026
-
-[Using Arcade tools](/en/get-started/agent-frameworks/crewai/use-arcade-tools.md)
-[Overview](/en/get-started/agent-frameworks/google-adk/overview.md)
diff --git a/public/_markdown/en/get-started/agent-frameworks/crewai/use-arcade-tools.md b/public/_markdown/en/get-started/agent-frameworks/crewai/use-arcade-tools.md
index ec50ae951..96f3b54ff 100644
--- a/public/_markdown/en/get-started/agent-frameworks/crewai/use-arcade-tools.md
+++ b/public/_markdown/en/get-started/agent-frameworks/crewai/use-arcade-tools.md
@@ -1,91 +1,329 @@
---
-title: "Use Arcade tools with CrewAI"
-description: "Integrate Arcade tools into your CrewAI applications"
+title: "Setup Arcade tools with CrewAI"
+description: "Learn how to use Arcade tools in CrewAI applications"
---
[Agent Frameworks](/en/get-started/agent-frameworks.md)
-CrewAIUsing Arcade tools
+CrewAISetup Arcade tools with CrewAI
-## Use CrewAI with Arcade
+# Use Arcade tools with CrewAI
-This guide explains how to integrate Arcade tools into your CrewAI application. Follow the step-by-step instructions below. If a requires authorization, an authorization URL will appear in the console, waiting for your approval. This process ensures that only the tools you choose to authorize execute.
+[CrewAI](https://www.crewai.com/) is an agentic framework optimized for building task-oriented multi- systems. This guide explains how to integrate Arcade into your CrewAI applications.
-To tailor the authorization flow to meet your application’s specific needs, check out the [Custom Auth Flow with CrewAI](/get-started/agent-frameworks/crewai/custom-auth-flow.md) guide.
+## Outcomes
+
+You will build a CrewAI that uses Arcade to help with Gmail and Slack.
+
+### You will Learn
+
+- How to retrieve Arcade and convert them to CrewAI format
+- How to build a CrewAI with Arcade
+- How to implement “just in time” (JIT) authorization using Arcade’s client
### Prerequisites
+- [Arcade account](https://app.arcade.dev/register)
+
- [Obtain an Arcade API key](/get-started/setup/api-keys.md)
+- The [`uv` package manager](https://docs.astral.sh/uv/)
+
+
+## The agent architecture you will build in this guide
+
+CrewAI provides a [Crew](https://docs.crewai.com/reference/crew) class that implements a multi- system. It provides an interface for you to define the agents, tasks, and memory. In this guide, you will manually keep track of the agent’s history and state, and use the `kickoff` method to invoke the agent in an agentic loop.
-### Set up your environment
+## Integrate Arcade tools into a CrewAI agent
-Install the required package, and ensure your environment variables are set with your Arcade and OpenAI :
+### Create a new project
+
+Create a new directory for your and initialize a new virtual environment:
```bash
-pip install crewai-arcade
+mkdir crewai-arcade-example
+cd crewai-arcade-example
+uv init
+uv venv
+source .venv/bin/activate
```
-### Configure API keys
+Install the necessary packages:
+
+```bash
+uv add 'crewai[tools]' arcadepy
+```
-Provide your Arcade and OpenAI . You can store them in environment variables like so:
+Create a new file called `.env` and add the following environment variables:
```bash
-export ARCADE_API_KEY="your_arcade_api_key"
-export OPENAI_API_KEY="your_openai_api_key"
+# .env
+# Arcade API key
+ARCADE_API_KEY=YOUR_ARCADE_API_KEY
+# Arcade user ID (this is the email address you used to login to Arcade)
+ARCADE_USER_ID={arcade_user_id}
+# OpenAI API key
+OPENAI_API_KEY=YOUR_OPENAI_API_KEY
+```
+
+### Import the necessary packages
+
+Create a new file called `main.py` and add the following code:
+
+```python
+# main.py
+from typing import Any
+from arcadepy import Arcade
+from arcadepy.types import ToolDefinition
+from crewai.tools import BaseTool
+from crewai import Agent
+from crewai.events.event_listener import EventListener
+from pydantic import BaseModel, Field, create_model
+from dotenv import load_dotenv
+import os
+
+```
+
+This includes many imports, here’s a breakdown:
+
+- Arcade imports:
+ - `Arcade`: The , used to interact with the .
+ - `ToolDefinition`: The definition type, used to define the input and output of a tool.
+- CrewAI imports:
+ - `BaseTool`: The base class, used to create custom CrewAI tools.
+ - `Agent`: The CrewAI class, used to create an agent.
+ - `EventListener`: The event listener class, used to suppress CrewAI’s rich panel output.
+- Other imports:
+ - `pydantic` imports: Used for data validation and model creation when converting Arcade to LangChain tools.
+ - `typing.Any`: A type hint for the any type.
+ - `load_dotenv`: Loads the environment variables from the `.env` file.
+ - `os`: The operating system module, used to interact with the operating system.
+
+### Configure the agent
+
+The rest of the code uses these variables to customize the and manage the . Feel free to configure them to your liking. Here, the `EventListener` class is used to suppress CrewAI’s rich panel output, which is useful for debugging but verbose for an interactive session like the one you’re building.
+
+```python
+# main.py
+# Load environment variables from the .env file
+load_dotenv()
+
+# The Arcade User ID identifies who is authorizing each service.
+ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
+# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
+MCP_SERVERS = ["Slack"]
+# This determines individual tools. Useful to pick specific tools when you don't need all of them.
+TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
+# This determines the maximum number of tool definitions Arcade will return per MCP server
+TOOL_LIMIT = 30
+# This determines which LLM model will be used inside the agent
+MODEL = "openai/gpt-5-mini"
+# The individual objective that guides the agent's decision-making
+AGENT_GOAL = "Help the user with all their requests"
+# Provides context and personality to the agent, enriching interactions
+AGENT_BACKSTORY = "You are a helpful assistant that can assist with Gmail and Slack."
+# This defines the Agent's role. A short description of its function and expertise
+AGENT_NAME = "Communication Manager"
+
+# Suppress CrewAI's rich panel output
+EventListener().formatter.verbose = False
+```
+
+### Write a utility function to transform Arcade tool definitions into Pydantic models
+
+In this utility function, you transform an Arcade definition into a Pydantic model. Later, you will transform these models to construct tools in the format expected by CrewAI. The `_build_args_model` function extracts the tools’ parameters, name, and description, and maps them to a Pydantic model.
+
+```python
+# main.py
+TYPE_MAP: dict[str, type] = {
+ "string": str,
+ "number": float,
+ "integer": int,
+ "boolean": bool,
+ "array": list,
+ "json": dict,
+}
+
+
+def _python_type(val_type: str) -> type:
+ t = TYPE_MAP.get(val_type)
+ if t is None:
+ raise ValueError(f"Unsupported Arcade value type: {val_type}")
+ return t
+
+
+def _build_args_model(tool_def: ToolDefinition) -> type[BaseModel]:
+ fields: dict[str, Any] = {}
+ for param in tool_def.input.parameters or []:
+ param_type = _python_type(param.value_schema.val_type)
+ if param_type is list and param.value_schema.inner_val_type:
+ inner = _python_type(param.value_schema.inner_val_type)
+ param_type = list[inner] # type: ignore[valid-type]
+ default = ... if param.required else None
+ fields[param.name] = (
+ param_type,
+ Field(default=default, description=param.description or ""),
+ )
+ return create_model(f"{tool_def.name}Input", **fields)
```
-### Get Arcade tools
+### Write a custom class that extends the CrewAI BaseTool class
+
+Here, you define the `ArcadeTool` class that extends the CrewAI `BaseTool` class to add the following capability:
+
+- Authorize the tool with the with the `_auth_tool` helper function
+- Execute the tool with the with the `_run` method
-Use the `ArcadeToolManager` to initialize, add, and get Arcade :
+This class captures the authorization flow outside of the agent’s , which is a good practice for security and context engineering. By handling everything in the `ArcadeTool` class, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations, and reduces context bloat.
```python
-from crewai_arcade import ArcadeToolManager
+# main.py
+class ArcadeTool(BaseTool):
+ """A CrewAI tool backed by an Arcade tool definition."""
+
+ name: str
+ description: str
+ args_schema: type[BaseModel]
+
+ # Internal fields (not exposed to the agent)
+ arcade_tool_name: str = ""
+ user_id: str = ""
+ _client: Arcade | None = None
+
+ def _auth_tool(self):
+ auth = self._client.tools.authorize(
+ tool_name=self.arcade_tool_name,
+ user_id=self.user_id,
+ )
+ if auth.status != "completed":
+ print(f"Authorization required. Visit: {auth.url}")
+ self._client.auth.wait_for_completion(auth)
+
+ def _run(self, **kwargs: Any) -> str:
+ if self._client is None:
+ self._client = Arcade()
+
+ self._auth_tool()
+
+ print(f"Calling {self.arcade_tool_name}...")
+
+ result = self._client.tools.execute(
+ tool_name=self.arcade_tool_name,
+ input=kwargs,
+ user_id=self.user_id,
+ )
+
+ if not result.success:
+ return f"Tool error: {result.output.error.message}"
+
+ print(f"Call to {self.arcade_tool_name} successful, the agent will now process the result...")
+ return result.output.value
+```
+
+### Retrieve Arcade tools and transform them into CrewAI tools
+
+Here you get the Arcade tools you want the agent to utilize, and transform them into CrewAI tools. The first step is to initialize the , and get the you want to work with.
-manager = ArcadeToolManager(default_user_id="{arcade_user_id}")
+Here’s a breakdown of what it does for clarity:
-"""
-Retrieves the provided tools and/or MCP Servers as CrewAI StructuredTools.
-"""
-tools = manager.get_tools(tools=["Gmail.ListEmails"], toolkits=["Slack"])
+- retrieve tools from all configured servers (defined in the `MCP_SERVERS` variable)
+- retrieve individual (defined in the `TOOLS` variable)
+- transform the Arcade to CrewAI tools with the `ArcadeTool` class you defined earlier
+
+```python
+# main.py
+def get_arcade_tools(
+ client: Arcade,
+ *,
+ tools: list[str] | None = None,
+ mcp_servers: list[str] | None = None,
+ user_id: str = "",
+) -> list[ArcadeTool]:
+ if not tools and not mcp_servers:
+ raise ValueError("Provide at least one tool name or toolkit name")
+
+ definitions: list[ToolDefinition] = []
+
+ if tools:
+ for name in tools:
+ definitions.append(client.tools.get(name=name))
+
+ if mcp_servers:
+ for tk in mcp_servers:
+ page = client.tools.list(toolkit=tk)
+ definitions.extend(page.items)
+
+ result: list[ArcadeTool] = []
+ for defn in definitions:
+ sanitized_name = defn.qualified_name.replace(".", "_")
+ t = ArcadeTool(
+ client=client,
+ name=sanitized_name,
+ description=defn.description,
+ args_schema=_build_args_model(defn),
+ arcade_tool_name=defn.qualified_name,
+ user_id=user_id,
+ )
+ result.append(t)
+
+ return result
```
-### Use tools in your CrewAI agent team
+### Create the main function
-Create a Crew that uses your tools. When the is called, you will be prompted to go visit an authorization page to authorize the tool before it executes.
+The main function is where you:
+
+- Get the Arcade tools from the configured servers
+- Create an with the Arcade
+- Initialize the conversation
+- Run the loop
```python
-from crewai import Agent, Crew, Task
-from crewai.llm import LLM
-
-crew_agent = Agent(
- role="Main Agent",
- backstory="You are a helpful assistant",
- goal="Help the user with their requests",
- tools=tools,
- allow_delegation=False,
- verbose=True,
- llm=LLM(model="gpt-4o"),
-)
-
-task = Task(
- description="Get the 5 most recent emails from the user's inbox and summarize them and recommend a response for each.",
- expected_output="A bulleted list with a one sentence summary of each email and a recommended response to the email.",
- agent=crew_agent,
- tools=crew_agent.tools,
-)
-
-crew = Crew(
- agents=[crew_agent],
- tasks=[task],
- verbose=True,
- memory=True,
-)
-
-result = crew.kickoff()
-
-print("\n\n\n ------------ Result ------------ \n\n\n")
-print(result)
+# main.py
+def main():
+ client = Arcade()
+
+ arcade_tools = get_arcade_tools(
+ client,
+ tools=TOOLS,
+ mcp_servers=MCP_SERVERS,
+ user_id=ARCADE_USER_ID,
+ )
+
+ agent = Agent(
+ role=AGENT_NAME,
+ goal=AGENT_GOAL,
+ backstory=AGENT_BACKSTORY,
+ tools=arcade_tools,
+ )
+
+ history = []
+ print("Agent ready. Type 'exit' to quit.\n")
+
+ while True:
+ user_input = input("> ")
+ if user_input.strip().lower() in ("exit", "quit"):
+ break
+
+ history.append({"role": "user", "content": user_input})
+ result = agent.kickoff(history)
+ history.append({"role": "assistant", "content": result.raw})
+ print(f"\n{result.raw}\n")
+
+
+if __name__ == "__main__":
+ main()
+```
+
+### Run the agent
+
+```bash
+uv run main.py
```
+You should see the responding to your prompts like any model, as well as handling any calls and authorization requests. Here are some example prompts you can try:
+
+- “Send me an email with a random haiku about OpenAI ”
+- “Summarize my latest 3 emails”
+
## Tips for selecting tools
- **Relevance**: Pick only the you need. Avoid using all tools at once.
@@ -99,7 +337,196 @@ Now that you have integrated Arcade tools into your CrewAI team, you can:
- Customize the ’s prompts for specific tasks.
- Customize the authorization and execution flow to meet your application’s requirements.
-Last updated on January 30, 2026
+## Example code
+
+### **main.py** (full file)
+
+```python
+# main.py
+from typing import Any
+from arcadepy import Arcade
+from arcadepy.types import ToolDefinition
+from crewai.tools import BaseTool
+from crewai import Agent
+from crewai.events.event_listener import EventListener
+from pydantic import BaseModel, Field, create_model
+from dotenv import load_dotenv
+import os
+
+# Load environment variables from the .env file
+load_dotenv()
+
+# The Arcade User ID identifies who is authorizing each service.
+ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
+# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
+MCP_SERVERS = ["Slack"]
+# This determines individual tools. Useful to pick specific tools when you don't need all of them.
+TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
+# This determines the maximum number of tool definitions Arcade will return per MCP server
+TOOL_LIMIT = 30
+# This determines which LLM model will be used inside the agent
+MODEL = "openai/gpt-5-mini"
+# The individual objective that guides the agent's decision-making
+AGENT_GOAL = "Help the user with all their requests"
+# Provides context and personality to the agent, enriching interactions
+AGENT_BACKSTORY = "You are a helpful assistant that can assist with Gmail and Slack."
+# This defines the Agent's role. A short description of its function and expertise
+AGENT_NAME = "Communication Manager"
+
+# Suppress CrewAI's rich panel output
+EventListener().formatter.verbose = False
+
+TYPE_MAP: dict[str, type] = {
+ "string": str,
+ "number": float,
+ "integer": int,
+ "boolean": bool,
+ "array": list,
+ "json": dict,
+}
+
+
+def _python_type(val_type: str) -> type:
+ t = TYPE_MAP.get(val_type)
+ if t is None:
+ raise ValueError(f"Unsupported Arcade value type: {val_type}")
+ return t
+
+
+def _build_args_model(tool_def: ToolDefinition) -> type[BaseModel]:
+ fields: dict[str, Any] = {}
+ for param in tool_def.input.parameters or []:
+ param_type = _python_type(param.value_schema.val_type)
+ if param_type is list and param.value_schema.inner_val_type:
+ inner = _python_type(param.value_schema.inner_val_type)
+ param_type = list[inner] # type: ignore[valid-type]
+ default = ... if param.required else None
+ fields[param.name] = (
+ param_type,
+ Field(default=default, description=param.description or ""),
+ )
+ return create_model(f"{tool_def.name}Input", **fields)
+
+
+class ArcadeTool(BaseTool):
+ """A CrewAI tool backed by an Arcade tool definition."""
+
+ name: str
+ description: str
+ args_schema: type[BaseModel]
+
+ # Internal fields (not exposed to the agent)
+ arcade_tool_name: str = ""
+ user_id: str = ""
+ _client: Arcade | None = None
+
+ def _auth_tool(self):
+ auth = self._client.tools.authorize(
+ tool_name=self.arcade_tool_name,
+ user_id=self.user_id,
+ )
+ if auth.status != "completed":
+ print(f"Authorization required. Visit: {auth.url}")
+ self._client.auth.wait_for_completion(auth)
+
+ def _run(self, **kwargs: Any) -> str:
+ if self._client is None:
+ self._client = Arcade()
+
+ self._auth_tool()
+
+ print(f"Calling {self.arcade_tool_name}...")
+
+ result = self._client.tools.execute(
+ tool_name=self.arcade_tool_name,
+ input=kwargs,
+ user_id=self.user_id,
+ )
+
+ if not result.success:
+ return f"Tool error: {result.output.error.message}"
+
+ print(f"Call to {self.arcade_tool_name} successful, the agent will now process the result...")
+ return result.output.value
+
+
+def get_arcade_tools(
+ client: Arcade,
+ *,
+ tools: list[str] | None = None,
+ mcp_servers: list[str] | None = None,
+ user_id: str = "",
+) -> list[ArcadeTool]:
+ if not tools and not mcp_servers:
+ raise ValueError("Provide at least one tool name or toolkit name")
+
+ definitions: list[ToolDefinition] = []
+
+ if tools:
+ for name in tools:
+ definitions.append(client.tools.get(name=name))
+
+ if mcp_servers:
+ for tk in mcp_servers:
+ page = client.tools.list(toolkit=tk)
+ definitions.extend(page.items)
+
+ result: list[ArcadeTool] = []
+ for defn in definitions:
+ sanitized_name = defn.qualified_name.replace(".", "_")
+ t = ArcadeTool(
+ client=client,
+ name=sanitized_name,
+ description=defn.description,
+ args_schema=_build_args_model(defn),
+ arcade_tool_name=defn.qualified_name,
+ user_id=user_id,
+ )
+ result.append(t)
+
+ return result
+
+
+def main():
+ client = Arcade()
+
+ arcade_tools = get_arcade_tools(
+ client,
+ tools=TOOLS,
+ mcp_servers=MCP_SERVERS,
+ user_id=ARCADE_USER_ID,
+ )
+
+ agent = Agent(
+ role=AGENT_NAME,
+ goal=AGENT_GOAL,
+ backstory=AGENT_BACKSTORY,
+ tools=arcade_tools,
+ )
+
+ history = []
+ print("Agent ready. Type 'exit' to quit.\n")
+
+ while True:
+ user_input = input("> ")
+ if user_input.strip().lower() in ("exit", "quit"):
+ break
+
+ history.append({"role": "user", "content": user_input})
+ result = agent.kickoff(history)
+ history.append({"role": "assistant", "content": result.raw})
+ print(f"\n{result.raw}\n")
+
+
+if __name__ == "__main__":
+ main()
+
+
+```
+
+
+
+Last updated on February 10, 2026
[Setup Arcade with your LLM (Python)](/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md)
-[Custom auth flow](/en/get-started/agent-frameworks/crewai/custom-auth-flow.md)
+[Overview](/en/get-started/agent-frameworks/google-adk/overview.md)
diff --git a/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-python.md b/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-python.md
index a37c8aef0..d75ce1ad4 100644
--- a/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-python.md
+++ b/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-python.md
@@ -307,7 +307,7 @@ class ArcadeTool(FunctionTool):
Here you get the Arcade tools you want the agent to utilize, and transform them into Google ADK tools. The first step is to initialize the , and get the you want to work with.
-This helper function is long, here’s a breakdown of what it does for clarity:
+Here’s a breakdown of what it does for clarity:
- retrieve tools from all configured servers (defined in the `MCP_SERVERS` variable)
- retrieve individual (defined in the `TOOLS` variable)
diff --git a/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-python.md b/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-python.md
index 99e95c7cc..57a2a0fcb 100644
--- a/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-python.md
+++ b/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-python.md
@@ -1,24 +1,27 @@
---
-title: "Setup Arcade with OpenAI Agents (Python)"
-description: "Build a CLI agent with Arcade tools using the OpenAI Agents SDK for Python"
+title: "Setup Arcade with OpenAI Agents SDK"
+description: "Learn how to use Arcade tools in OpenAI Agents applications"
---
[Agent Frameworks](/en/get-started/agent-frameworks.md)
[OpenAI Agents](/en/get-started/agent-frameworks/openai-agents/overview.md)
Setup (Python)
-# Setup Arcade with OpenAI Agents (Python)
+# Setup Arcade with OpenAI Agents SDK
-The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) is a Python library for building AI . It provides an interface for defining agents with , system prompts, and model configurations.
+Learn how to integrate Arcade tools using OpenAI primitives.
+
+The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) is a popular Python library for building AI . It builds on top of the OpenAI API, and provides an interface for building agents.
## Outcomes
-Build a CLI that uses Arcade to help with Gmail and Slack
+You will implement a CLI agent that can use Arcade tools to help the user with their requests. The handles that require authorization automatically, so don’t need to worry about it.
### You will Learn
-- How to retrieve Arcade tools and convert them to OpenAI format
-- How to build an OpenAI Agents with Arcade
-- How to handle “just in time” (JIT) authorization
+- How to retrieve Arcade tools and transform them into OpenAI
+- How to build an OpenAI Agents
+- How to integrate Arcade tools into the OpenAI flow
+- How to implement “just in time” (JIT) authorization using Arcade’s client
### Prerequisites
@@ -29,99 +32,285 @@ Build a CLI that uses Arcade to help with Gmail and Slack
- The [`uv` package manager](https://docs.astral.sh/uv/)
-## How Arcade integrates with OpenAI Agents
-
-The OpenAI SDK provides an [Agent](https://openai.github.io/openai-agents-python/ref/agent/#agents.agent.Agent) class that implements a . It accepts in the [FunctionTool](https://openai.github.io/openai-agents-python/ref/tool/#agents.tool.FunctionTool) format. The `agents-arcade` package provides a `get_arcade_tools` function that retrieves Arcade tools and converts them to this format automatically.
+## The agent architecture you will build in this guide
-When a tool requires authorization (like Gmail or GitHub), the raises an `AuthorizationError` with a URL for the to visit. After the user authorizes, the agent can retry the operation.
+The OpenAI SDK provides an [Agent](https://openai.github.io/openai-agents-python/ref/agent/#agents.agent.Agent) class that implements a . It provides an interface for you to define the system prompt, the model, the , and possible sub-agents for handoffs. In this guide, you will manually keep track of the agent’s history and state, and use the `run` method to invoke the agent in an agentic loop.
-## Build the agent
+## Integrate Arcade tools into an OpenAI Agents agent
### Create a new project
-Create a new directory and set up a virtual environment:
+Create a new directory for your and initialize a new virtual environment:
```bash
mkdir openai-agents-arcade-example
cd openai-agents-arcade-example
+uv init
uv venv
source .venv/bin/activate
```
-Install the required packages:
+Install the necessary packages:
```bash
-uv pip install openai-agents arcadepy agents-arcade python-dotenv
+uv add openai-agents arcadepy
```
-Create a `.env` file with your :
+Create a new file called `.env` and add the following environment variables:
```bash
# .env
-# Arcade API key from https://app.arcade.dev
+# Arcade API key
ARCADE_API_KEY=YOUR_ARCADE_API_KEY
-# Your Arcade user ID (the email you used to sign up)
+# Arcade user ID (this is the email address you used to login to Arcade)
ARCADE_USER_ID={arcade_user_id}
# OpenAI API key
OPENAI_API_KEY=YOUR_OPENAI_API_KEY
```
-### Write the agent code
+### Import the necessary packages
-Create `main.py` with the following imports:
+Create a new file called `main.py` and add the following code:
-```python
+```json
# main.py
from agents import Agent, Runner, TResponseInputItem
+from agents.run_context import RunContextWrapper
+from agents.tool import FunctionTool
+from agents.exceptions import AgentsException
from arcadepy import AsyncArcade
-from agents_arcade import get_arcade_tools
-from agents_arcade.errors import AuthorizationError
+from arcadepy.types.execute_tool_response import ExecuteToolResponse
from dotenv import load_dotenv
+from functools import partial
+from typing import Any
import os
import asyncio
-
-# Load environment variables
-load_dotenv()
+import json
```
+This includes many imports, here’s a breakdown:
+
+- Arcade imports:
+ - `AsyncArcade`: The , used to interact with the .
+ - `ExecuteToolResponse`: The response type for the execute response.
+- OpenAI imports:
+ - `Agent`: The OpenAI Agents , used to define an agent.
+ - `Runner`: The OpenAI Agents runner, which runs the in an agentic loop.
+ - `TResponseInputItem`: The response input item type, determines the type of message in the conversation history.
+ - `RunContextWrapper`: Wraps the run , providing information such as the user ID, the tool name, tool arguments, and other contextual information different parts of the may need.
+ - `FunctionTool`: OpenAI definition format.
+ - `AgentsException`: The OpenAI exception, used to handle errors in the agentic loop.
+- Other imports:
+ - `load_dotenv`: Loads the environment variables from the `.env` file.
+ - `functools.partial`: Partially applies a function to a given set of arguments.
+ - `typing.Any`: A type hint for the any type.
+ - `os`: The operating system module, used to interact with the operating system.
+ - `asyncio`: The asynchronous I/O module, used to interact with the asynchronous I/O.
+ - `json`: The JSON module, used to interact with JSON data.
+
### Configure the agent
-Define which tools and servers your can access:
+These variables customize the and manage the in the rest of the code. Feel free to configure them to your liking.
```python
# main.py
-# Configuration
+# Load environment variables
+load_dotenv()
+
+# The Arcade User ID identifies who is authorizing each service.
ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
-# MCP servers provide groups of related tools
+# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
MCP_SERVERS = ["Slack"]
-# Individual tools can be specified by name
+# This determines individual tools. Useful to pick specific tools when you don't need all of them.
TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
-# System prompt for the agent
+# This determines the maximum number of tool definitions Arcade will return per MCP server
+TOOL_LIMIT = 30
+# This prompt defines the behavior of the agent.
SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."
-# Model to use
+# This determines which LLM model will be used inside the agent
MODEL = "gpt-4o-mini"
```
-Browse the [MCP server catalog](/resources/integrations.md) to see all available servers and .
+### Write a custom error and utility functions to help with tool calls
+
+Here, you define `ToolError` to handle errors from the Arcade . It wraps the `AgentsException` and provides an informative error message that the agentic loop can handle in case anything goes wrong.
+
+You also define `convert_output_to_json` to convert the output of the Arcade tools to a JSON string. This is useful because the output of the Arcade tools is not always a JSON object, and the OpenAI SDK expects a JSON string.
+
+```python
+# main.py
+# Arcade to OpenAI agent exception classes
+class ToolError(AgentsException):
+ def __init__(self, result: ExecuteToolResponse | str):
+ self.result = None
+ if isinstance(result, str):
+ self.message = result
+ else:
+ self.message = result.output.error.message
+ self.result = result
+
+ def __str__(self):
+ if self.result:
+ return f"Tool {self.result.tool_name} failed with error: {self.message}"
+ else:
+ return self.message
+
+
+def convert_output_to_json(output: Any) -> str:
+ if isinstance(output, dict) or isinstance(output, list):
+ return json.dumps(output)
+ else:
+ return str(output)
+```
+
+### Write a helper function to authorize Arcade tools
+
+This helper function implements “just in time” (JIT) tool authorization using Arcade’s client. When the tries to execute a that requires authorization, the `result` object’s `status` will be `"pending"`, and you can use the `authorize` method to get an authorization URL. You then wait for the to complete the authorization and retry the tool call. If the user has already authorized the tool, the `status` will be `"completed"`, and the OAuth dance skips silently, which improves the user experience.
+
+This function captures the authorization flow outside of the agent’s context, which is a good practice for security and context engineering. By handling everything in the , you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the free from any authorization-related traces, which reduces the risk of hallucinations.
+
+```python
+# main.py
+async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str):
+ if not context.context.get("user_id"):
+ raise ToolError("No user ID and authorization required for tool")
+
+ result = await client.tools.authorize(
+ tool_name=tool_name,
+ user_id=context.context.get("user_id"),
+ )
+
+ if result.status != "completed":
+ print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}")
+
+ await client.auth.wait_for_completion(result)
+```
+
+### Write a helper function to execute Arcade tools
+
+This helper function shows how the OpenAI framework invokes the Arcade tools. It handles the authorization flow, and then calls the using the `execute` method. It handles the conversion of the arguments from JSON to a dictionary (expected by Arcade) and the conversion of the output from the Arcade tool to a JSON string (expected by the OpenAI Agents framework). Here is where you call the helper functions defined earlier to authorize the tool and convert the output to a JSON string.
+
+```python
+# main.py
+async def invoke_arcade_tool(
+ context: RunContextWrapper,
+ tool_args: str,
+ tool_name: str,
+ client: AsyncArcade,
+):
+ args = json.loads(tool_args)
+ await authorize_tool(client, context, tool_name)
+
+ print(f"Invoking tool {tool_name} with args: {args}")
+ result = await client.tools.execute(
+ tool_name=tool_name,
+ input=args,
+ user_id=context.context.get("user_id"),
+ )
+ if not result.success:
+ raise ToolError(result)
+
+ print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...")
+
+ return convert_output_to_json(result.output.value)
+```
+
+### Retrieve Arcade tools and transform them into LangChain tools
+
+Here you get the Arcade tools you want the agent to use, and transform them into OpenAI Agents tools. The first step is to initialize the , and get the tools you want. Since OpenAI is itself an inference provider, the provides a convenient endpoint to get the tools in the OpenAI format, which is also the format expected by the OpenAI framework.
+
+This helper function is long, here’s a breakdown of what it does for clarity:
+
+- retrieve tools from all configured servers (defined in the `MCP_SERVERS` variable)
+- retrieve individual (defined in the `TOOLS` variable)
+- get the Arcade to OpenAI-formatted tools
+- create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the .
+
+```python
+# main.py
+async def get_arcade_tools(
+ client: AsyncArcade | None = None,
+ tools: list[str] | None = None,
+ mcp_servers: list[str] | None = None,
+) -> list[FunctionTool]:
+
+ if not client:
+ client = AsyncArcade()
+
+ # if no tools or MCP servers are provided, raise an error
+ if not tools and not mcp_servers:
+ raise ValueError(
+ "No tools or MCP servers provided to retrieve tool definitions")
+
+ # Use the Arcade Client to get OpenAI-formatted tool definitions
+ tool_formats = []
+
+ # Retrieve individual tools if specified
+ if tools:
+ # OpenAI-formatted tool definition
+ tasks = [client.tools.formatted.get(name=tool_id, format="openai")
+ for tool_id in tools]
+ responses = await asyncio.gather(*tasks)
+ for response in responses:
+ tool_formats.append(response)
+
+ # Retrieve tools from specified toolkits
+ if mcp_servers:
+ # Create a task for each toolkit to fetche the formatted tool definition concurrently.
+ tasks = [client.tools.formatted.list(toolkit=tk, format="openai")
+ for tk in mcp_servers]
+ responses = await asyncio.gather(*tasks)
+
+ # Combine the tool definitions from each response.
+ for response in responses:
+ # Here the code assumes the returned response has an "items" attribute
+ # containing a list of ToolDefinition objects.
+ tool_formats.extend(response.items)
+
+
+ # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client.
+ tool_functions = []
+ for tool in tool_formats:
+ tool_name = tool["function"]["name"]
+ tool_description = tool["function"]["description"]
+ tool_params = tool["function"]["parameters"]
+ tool_function = FunctionTool(
+ name=tool_name,
+ description=tool_description,
+ params_json_schema=tool_params,
+ on_invoke_tool=partial(
+ invoke_arcade_tool,
+ tool_name=tool_name,
+ client=client,
+ ),
+ strict_json_schema=False,
+ )
+ tool_functions.append(tool_function)
+
+ return tool_functions
+```
### Create the main function
-The main function initializes the , retrieves tools, creates an , and runs a conversation loop:
+The main function is where you:
+
+- Get the tools from the configured servers
+- Create an with the configured
+- Initialize the conversation
+- Run the loop
+
+The loop is a while loop that captures the user input, appends it to the conversation history, and then runs the . The agent’s response is then appended to the conversation history, and the loop continues.
+
+When a interrupts the loop, the interruption handles via the helper function you wrote earlier.
```python
# main.py
async def main():
- # Initialize the Arcade client
- client = AsyncArcade()
-
- # Get tools from MCP servers and individual tool names
- tools = await get_arcade_tools(
- client,
- toolkits=MCP_SERVERS,
- tools=TOOLS
- )
+ # Get tools from the configured MCP servers
+ tools = await get_arcade_tools(mcp_servers=MCP_SERVERS,
+ tools=TOOLS)
- # Create the agent with Arcade tools
+ # Create an agent with the configured tools
agent = Agent(
name="Inbox Assistant",
instructions=SYSTEM_PROMPT,
@@ -129,15 +318,14 @@ async def main():
tools=tools,
)
- # Conversation loop
+ # initialize the conversation
history: list[TResponseInputItem] = []
+ # run the loop
while True:
prompt = input("You: ")
if prompt.lower() == "exit":
break
-
history.append({"role": "user", "content": prompt})
-
try:
result = await Runner.run(
starting_agent=agent,
@@ -146,81 +334,198 @@ async def main():
)
history = result.to_input_list()
print(f"Assistant: {result.final_output}")
- except AuthorizationError as e:
- # Display the authorization URL to the user
- print(f"Authorization required. Please visit: {e}")
- print("After authorizing, run your request again.")
+ except ToolError as e:
+ # Something went wrong with the tool call, print the error message and exit the loop
+ print(e.message)
+ break
+# Run the main function as the entry point of the script
if __name__ == "__main__":
asyncio.run(main())
```
-### Handle authorization
-
-When a requires OAuth authorization (like Gmail or GitHub), the `AuthorizationError` contains a URL where the can grant access:
-
-```python
-except AuthorizationError as e:
- print(f"Please visit this URL to authorize: {e}")
-```
-
-After the completes authorization, Arcade remembers it for that `user_id`. Future requests with the same `user_id` won’t require re-authorization.
-
-The `user_id` should be a unique, consistent identifier for each user in your application (like their email or database ID). Arcade uses this to track which users have authorized which .
-
### Run the agent
```bash
uv run main.py
```
-Try prompts like:
+You should see the responding to your prompts like any model, as well as handling any calls and authorization requests. Here are some example prompts you can try:
-- “What are my latest emails?”
-- “Send a Slack message to #general saying hello”
-- “Summarize my last 3 emails”
+- “Send me an email with a random haiku about OpenAI ”
+- “Summarize my latest 3 emails”
## Key takeaways
-- **`get_arcade_tools`** retrieves Arcade tools and converts them to OpenAI `FunctionTool` format
-- **`AuthorizationError`** occurs when a needs OAuth authorization, containing the URL for the to visit
-- **`user_id`** tracks authorization per - use a consistent ID for each user in your application
-- You can mix servers (for groups of tools) with individual names
+- You can integrate Arcade tools into any agentic framework like OpenAI , all you need is to transform the Arcade into OpenAI Agents tools and handle the authorization flow.
+- isolation: By handling the authorization flow outside of the ’s context, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations.
-## Complete code
+## Next steps
-```python
+1. Try adding additional tools to the or modifying the in the catalog for a different use case by modifying the `MCP_SERVERS` and `TOOLS` variables.
+2. Try implementing a fully deterministic flow before the agentic loop, you can use this deterministic phase to prepare the for the , adding things like the current date, time, or any other information that is relevant to the task at hand.
+
+## Example code
+
+The team provides example code for you to reference:
+
+```json
# main.py
from agents import Agent, Runner, TResponseInputItem
+from agents.run_context import RunContextWrapper
+from agents.tool import FunctionTool
+from agents.exceptions import AgentsException
from arcadepy import AsyncArcade
-from agents_arcade import get_arcade_tools
-from agents_arcade.errors import AuthorizationError
+from arcadepy.types.execute_tool_response import ExecuteToolResponse
from dotenv import load_dotenv
+from functools import partial
+from typing import Any
import os
import asyncio
+import json
# Load environment variables
load_dotenv()
-# Configuration
+# The Arcade User ID identifies who is authorizing each service.
ARCADE_USER_ID = os.getenv("ARCADE_USER_ID")
+# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used.
MCP_SERVERS = ["Slack"]
+# This determines individual tools. Useful to pick specific tools when you don't need all of them.
TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]
+# This determines the maximum number of tool definitions Arcade will return per MCP server
+TOOL_LIMIT = 30
+# This prompt defines the behavior of the agent.
SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."
+# This determines which LLM model will be used inside the agent
MODEL = "gpt-4o-mini"
-async def main():
- # Initialize the Arcade client
- client = AsyncArcade()
-
- # Get tools from MCP servers and individual tool names
- tools = await get_arcade_tools(
- client,
- toolkits=MCP_SERVERS,
- tools=TOOLS
+# Arcade to OpenAI agent exception classes
+class ToolError(AgentsException):
+ def __init__(self, result: ExecuteToolResponse | str):
+ self.result = None
+ if isinstance(result, str):
+ self.message = result
+ else:
+ self.message = result.output.error.message
+ self.result = result
+
+ def __str__(self):
+ if self.result:
+ return f"Tool {self.result.tool_name} failed with error: {self.message}"
+ else:
+ return self.message
+
+
+def convert_output_to_json(output: Any) -> str:
+ if isinstance(output, dict) or isinstance(output, list):
+ return json.dumps(output)
+ else:
+ return str(output)
+
+async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str):
+ if not context.context.get("user_id"):
+ raise ToolError("No user ID and authorization required for tool")
+
+ result = await client.tools.authorize(
+ tool_name=tool_name,
+ user_id=context.context.get("user_id"),
+ )
+
+ if result.status != "completed":
+ print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}")
+
+ await client.auth.wait_for_completion(result)
+
+async def invoke_arcade_tool(
+ context: RunContextWrapper,
+ tool_args: str,
+ tool_name: str,
+ client: AsyncArcade,
+):
+ args = json.loads(tool_args)
+ await authorize_tool(client, context, tool_name)
+
+ print(f"Invoking tool {tool_name} with args: {args}")
+ result = await client.tools.execute(
+ tool_name=tool_name,
+ input=args,
+ user_id=context.context.get("user_id"),
)
+ if not result.success:
+ raise ToolError(result)
+
+ print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...")
+
+ return convert_output_to_json(result.output.value)
+
+async def get_arcade_tools(
+ client: AsyncArcade | None = None,
+ tools: list[str] | None = None,
+ mcp_servers: list[str] | None = None,
+) -> list[FunctionTool]:
+
+ if not client:
+ client = AsyncArcade()
+
+ # if no tools or MCP servers are provided, raise an error
+ if not tools and not mcp_servers:
+ raise ValueError(
+ "No tools or MCP servers provided to retrieve tool definitions")
+
+ # Use the Arcade Client to get OpenAI-formatted tool definitions
+ tool_formats = []
+
+ # Retrieve individual tools if specified
+ if tools:
+ # OpenAI-formatted tool definition
+ tasks = [client.tools.formatted.get(name=tool_id, format="openai")
+ for tool_id in tools]
+ responses = await asyncio.gather(*tasks)
+ for response in responses:
+ tool_formats.append(response)
+
+ # Retrieve tools from specified toolkits
+ if mcp_servers:
+ # Create a task for each toolkit to fetche the formatted tool definition concurrently.
+ tasks = [client.tools.formatted.list(toolkit=tk, format="openai")
+ for tk in mcp_servers]
+ responses = await asyncio.gather(*tasks)
+
+ # Combine the tool definitions from each response.
+ for response in responses:
+ # Here the code assumes the returned response has an "items" attribute
+ # containing a list of ToolDefinition objects.
+ tool_formats.extend(response.items)
+
+
+ # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client.
+ tool_functions = []
+ for tool in tool_formats:
+ tool_name = tool["function"]["name"]
+ tool_description = tool["function"]["description"]
+ tool_params = tool["function"]["parameters"]
+ tool_function = FunctionTool(
+ name=tool_name,
+ description=tool_description,
+ params_json_schema=tool_params,
+ on_invoke_tool=partial(
+ invoke_arcade_tool,
+ tool_name=tool_name,
+ client=client,
+ ),
+ strict_json_schema=False,
+ )
+ tool_functions.append(tool_function)
+
+ return tool_functions
+
+async def main():
+ # Get tools from the configured MCP servers
+ tools = await get_arcade_tools(mcp_servers=MCP_SERVERS,
+ tools=TOOLS)
- # Create the agent with Arcade tools
+ # Create an agent with the configured tools
agent = Agent(
name="Inbox Assistant",
instructions=SYSTEM_PROMPT,
@@ -228,15 +533,14 @@ async def main():
tools=tools,
)
- # Conversation loop
+ # initialize the conversation
history: list[TResponseInputItem] = []
+ # run the loop
while True:
prompt = input("You: ")
if prompt.lower() == "exit":
break
-
history.append({"role": "user", "content": prompt})
-
try:
result = await Runner.run(
starting_agent=agent,
@@ -245,22 +549,17 @@ async def main():
)
history = result.to_input_list()
print(f"Assistant: {result.final_output}")
- except AuthorizationError as e:
- print(f"Authorization required. Please visit: {e}")
- print("After authorizing, run your request again.")
+ except ToolError as e:
+ # Something went wrong with the tool call, print the error message and exit the loop
+ print(e.message)
+ break
+# Run the main function as the entry point of the script
if __name__ == "__main__":
asyncio.run(main())
```
-## Next steps
-
-- Add more by modifying `MCP_SERVERS` and `TOOLS`
-- Build a web interface instead of CLI using frameworks like FastAPI or Flask
-- Explore [creating custom tools](/guides/create-tools/tool-basics/build-mcp-server.md)
- with the Arcade SDK
-
-Last updated on January 30, 2026
+Last updated on February 10, 2026
[Overview](/en/get-started/agent-frameworks/openai-agents/overview.md)
[Setup (TypeScript)](/en/get-started/agent-frameworks/openai-agents/setup-typescript.md)
diff --git a/public/_markdown/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents.md b/public/_markdown/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents.md
deleted file mode 100644
index e5c6d6a3e..000000000
--- a/public/_markdown/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: "Arcade with OpenAI Agents"
-description: "Integrate Arcade tools with the OpenAI Agents SDK"
----
-[Agent Frameworks](/en/get-started/agent-frameworks.md)
-OpenAI AgentsOverview
-
-# Arcade with OpenAI Agents
-
-The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) provides a framework for building AI . Arcade integrates with both the Python and JavaScript versions, giving your agents access to Gmail, GitHub, Slack, and 100+ other .
-
-## Get started
-
-Choose your language to set up Arcade with OpenAI :
-
-- **[Python setup](/get-started/agent-frameworks/openai-agents/setup-python.md)
- ** - Build a CLI with Arcade using the `agents-arcade` package
-- **[TypeScript setup](/get-started/agent-frameworks/openai-agents/setup-typescript.md)
- ** - Build an with Arcade using `@arcadeai/arcadejs`
-
-## What you can build
-
-With Arcade and OpenAI , your agents can:
-
-- Read and send emails via Gmail
-- Post messages to Slack channels
-- Create GitHub issues and pull requests
-- Search the web and extract content
-- Access 100+ other integrations
-
-Browse the [full MCP server catalog](/resources/integrations.md) to see all available .
-
-Last updated on January 30, 2026
-
-[Mastra](/en/get-started/agent-frameworks/mastra.md)
-[Setup (Python)](/en/get-started/agent-frameworks/openai-agents/setup-python.md)
diff --git a/public/llms.txt b/public/llms.txt
index 96d8a4ac3..4fa711d00 100644
--- a/public/llms.txt
+++ b/public/llms.txt
@@ -1,4 +1,4 @@
-
+
# Arcade
@@ -206,7 +206,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w
- [PagerdutyApi](https://docs.arcade.dev/en/resources/integrations/development/pagerduty-api.md): The PagerDutyApi documentation provides a comprehensive overview of tools that enable users to manage incidents, services, and integrations within the PagerDuty platform using the API. It outlines various functionalities, such as assigning tags, retrieving metrics, and managing add-ons, allowing
- [Postgres](https://docs.arcade.dev/en/resources/integrations/databases/postgres.md): This documentation page provides users with a comprehensive guide to the Arcade Postgres MCP Server, which enables agents to interact with PostgreSQL databases in a read-only manner. Users can learn how to discover database schemas, explore table structures, and execute safe SELECT queries
- [PosthogApi](https://docs.arcade.dev/en/resources/integrations/development/posthog-api.md): The PosthogApi documentation provides users with tools and guidance for managing and analyzing data within the PostHog platform via its API. It details the necessary configuration, including authentication secrets, and outlines various available tools for retrieving metrics, managing exports, and handling
-- [Provide the tool manager callback to the ArcadeToolManager](https://docs.arcade.dev/en/get-started/agent-frameworks/crewai/custom-auth-flow.md): This documentation page provides a comprehensive guide on creating a custom authorization flow for the ArcadeToolManager within a CrewAI agent team. It outlines the necessary prerequisites, setup steps, and code examples to help users implement tailored authorization processes for executing Arcade tools. By
- [Providing useful tool errors](https://docs.arcade.dev/en/guides/create-tools/error-handling/useful-tool-errors.md): This documentation page teaches developers how to effectively handle errors when building tools with Arcade MCP, emphasizing the automatic error adaptation feature that reduces boilerplate code. It outlines the error handling philosophy, explains the use of error adapters, and provides guidance on when to raise
- [Pylon](https://docs.arcade.dev/en/resources/integrations/customer-support/pylon.md): The Pylon documentation provides agents with the necessary tools and API functionalities to manage issues, contacts, users, and teams within the Pylon MCP Server. Users can learn how to list and search issues, assign ownership, and interact with user and team data
- [Reddit](https://docs.arcade.dev/en/resources/integrations/social-communication/reddit.md): This documentation page provides users with a comprehensive guide to the Arcade Reddit MCP Server, which enables the development of agents and AI applications that can interact with Reddit. It details various tools available for actions such as submitting posts, commenting, retrieving content, and checking
@@ -230,7 +229,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w
- [Setup Arcade with LangChain](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain.md): This documentation page guides users on how to integrate Arcade tools into LangChain agents, enabling them to leverage Arcade's capabilities within the LangChain framework. Users will learn to set up their environment, create a LangChain agent, and manage tool authorization and execution
- [Setup Arcade with OpenAI Agents (Python)](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/setup-python.md): This documentation page guides users through setting up a Command Line Interface (CLI) agent using the OpenAI Agents SDK for Python in conjunction with Arcade tools. It covers the process of retrieving Arcade tools, building the agent, managing authorization, and provides code examples
- [Setup Arcade with OpenAI Agents (TypeScript)](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/setup-typescript.md): This documentation page guides users through the process of setting up Arcade with OpenAI Agents using TypeScript, enabling them to build AI agents that can interact with applications like Gmail and Slack. Users will learn how to convert Arcade tools to the OpenAI format,
-- [Setup Arcade with OpenAI Agents SDK](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents.md): This documentation page provides a comprehensive guide on integrating Arcade tools with the OpenAI Agents SDK to build AI agents. Users will learn how to set up a project, implement a command-line interface (CLI) agent, and manage tool authorization seamlessly. By following
- [Sharepoint](https://docs.arcade.dev/en/resources/integrations/productivity/sharepoint.md): This documentation page provides a comprehensive guide for using the SharePoint MCP Server, enabling users to efficiently interact with SharePoint sites and their contents through various tools. Users can learn to retrieve lists, items, pages, and metadata, as well as search for
- [Slack](https://docs.arcade.dev/en/resources/integrations/social-communication/slack.md): This documentation page provides users with tools and functionalities to integrate and interact with the Slack platform, enabling efficient management of conversations and user information. It outlines various capabilities, such as retrieving user details, sending messages, and accessing conversation metadata, all aimed at enhancing
- [SlackApi](https://docs.arcade.dev/en/resources/integrations/social-communication/slack-api.md): The SlackApi documentation provides a comprehensive guide for administrators and developers to effectively manage and automate various aspects of Slack workspaces, including user management, messaging, channel operations, and file sharing. It outlines key functionalities such as creating teams, managing user profiles,