From 29604a02febdf703ed51576d87f2e6a786171ce1 Mon Sep 17 00:00:00 2001 From: Mateo Torres Date: Mon, 19 Jan 2026 11:42:36 -0300 Subject: [PATCH 1/6] resolve conflict --- .../agent-frameworks/langchain/_meta.tsx | 3 +- .../langchain/use-arcade-tools/page.mdx | 243 ------------ .../use-arcade-with-langchain/page.mdx | 3 + .../langchain/user-auth-interrupts/page.mdx | 373 ------------------ next.config.ts | 15 +- 5 files changed, 18 insertions(+), 619 deletions(-) delete mode 100644 app/en/guides/agent-frameworks/langchain/use-arcade-tools/page.mdx delete mode 100644 app/en/guides/agent-frameworks/langchain/user-auth-interrupts/page.mdx diff --git a/app/en/guides/agent-frameworks/langchain/_meta.tsx b/app/en/guides/agent-frameworks/langchain/_meta.tsx index a2708a562..070d90407 100644 --- a/app/en/guides/agent-frameworks/langchain/_meta.tsx +++ b/app/en/guides/agent-frameworks/langchain/_meta.tsx @@ -1,5 +1,4 @@ export default { - "use-arcade-tools": "Using Arcade tools", - "user-auth-interrupts": "User authorization", "auth-langchain-tools": "Authorizing existing tools", + "use-arcade-with-langchain": "Setup Arcade with LangChain", }; diff --git a/app/en/guides/agent-frameworks/langchain/use-arcade-tools/page.mdx b/app/en/guides/agent-frameworks/langchain/use-arcade-tools/page.mdx deleted file mode 100644 index a16ccd5b4..000000000 --- a/app/en/guides/agent-frameworks/langchain/use-arcade-tools/page.mdx +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: "Use Arcade tools with LangGraph" -description: "Integrate Arcade tools into your LangGraph applications" ---- - -import { Steps, Tabs, Callout } from "nextra/components"; - -## Use LangGraph with Arcade - -In this guide, let's explore how to integrate Arcade tools into your LangGraph application. Follow the step-by-step instructions below. For complete working examples, see our [Python](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain/langgraph_arcade_minimal.py) and [JavaScript](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain-ts/langgraph-arcade-minimal.ts) examples. - - - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys) - -### Set up your environment - -Install the required packages, and ensure your environment variables are set with your Arcade and OpenAI API keys: - - - -```bash -pip install langchain-arcade langchain-openai langgraph -``` - - -```bash -npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph -``` - - - - -### Configure API keys - -Provide your Arcade and OpenAI API keys. You can store them in environment variables or directly in your code: - -> Need an Arcade API key? Visit the [Get an API key](/get-started/setup/api-keys) page to create one. - - - -```python -import os - -arcade_api_key = os.environ.get("ARCADE_API_KEY", "YOUR_ARCADE_API_KEY") -openai_api_key = os.environ.get("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY") -``` - - -```bash -ARCADE_API_KEY= -OPENAI_API_KEY= -``` - - - -### Create and manage Arcade tools - - - -Use the ArcadeToolManager to retrieve specific tools or entire MCP Servers: - -```python -from langchain_arcade import ArcadeToolManager - -manager = ArcadeToolManager(api_key=arcade_api_key) - -# Fetch the "ScrapeUrl" tool from the "Firecrawl" MCP Server -tools = manager.get_tools(tools=["Firecrawl.ScrapeUrl"]) -print(manager.tools) - -# Get all tools from the "Gmail" MCP Server -tools = manager.get_tools(toolkits=["Gmail"]) -print(manager.tools) -``` - - -Arcade offers methods to convert tools into Zod schemas, which is essential since LangGraph defines tools using Zod. The `toZod` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with LangGraph. Learn more about Arcade's Zod integration options [here](/guides/tool-calling/custom-apps/get-tool-definitions#get-zod-tool-definitions). -```javascript -import { Arcade } from "@arcadeai/arcadejs"; -import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib"; -import { tool } from "@langchain/core/tools"; - -// Initialize the Arcade client -const arcade = new Arcade(); - -// Get the Arcade tools, you can customize the MCP Server (e.g. "github", "notion", "gmail", etc.) -const googleToolkit = await arcade.tools.list({ toolkit: "gmail", limit: 30 }); -const arcadeTools = toZod({ - tools: googleToolkit.items, - client: arcade, - userId: "", // Replace this with your application's user ID (e.g. email address, UUID, etc.) -}); -// Convert Arcade tools to LangGraph tools -const tools = arcadeTools.map(({ name, description, execute, parameters }) => - tool(execute, { - name, - description, - schema: parameters, - }), -); -console.log(tools); -``` - - - - -### Set up the language model and memory - -Create an AI model and bind your tools. Use MemorySaver for checkpointing: - - - -```python -from langchain_openai import ChatOpenAI -from langgraph.checkpoint.memory import MemorySaver - -model = ChatOpenAI(model="gpt-4o", api_key=openai_api_key) -bound_model = model.bind_tools(tools) - -memory = MemorySaver() -``` - - -```javascript -import { ChatOpenAI } from "@langchain/openai"; -import { MemorySaver } from "@langchain/langgraph"; - -const model = new ChatOpenAI({ model: "gpt-4o", apiKey: process.env.OPENAI_API_KEY }); -const boundModel = model.bindTools(tools); -const memory = new MemorySaver(); -``` - - - -### Create a ReAct-style agent - -Use the prebuilt ReAct agent from LangGraph to handle your Arcade tools: - - -```python -from langgraph.prebuilt import create_react_agent - -graph = create_react_agent(model=bound_model, tools=tools, checkpointer=memory) -``` - - -```javascript -import { createReactAgent } from "@langchain/langgraph/prebuilt"; - -const graph = createReactAgent({ llm: boundModel, tools, checkpointer: memory }); -``` - - - -### Provide configuration and user query - -Supply a basic config dictionary and a user query. Notice that user_id is required for tool authorization: - - -```python -config = { - "configurable": { - "thread_id": "1", - "user_id": "{arcade_user_id}" - } -} -user_input = { - "messages": [ - ("user", "List any new and important emails in my inbox.") - ] -} -``` - - -```javascript -const config = { - configurable: { - thread_id: "1", - user_id: "{arcade_user_id}", - }, - streamMode: "values" as const, -}; -const user_input = { - messages: [ - { - role: "user", - content: "List any new and important emails in my inbox.", - }, - ], -}; -``` - - - -### Stream the response - -Stream the assistant's output. If the tool requires authorization, the agent will ask the user to authorize the tool. - - - -```python -from langgraph.errors import NodeInterrupt - -try: - for chunk in graph.stream(user_input, config, stream_mode="values"): - chunk["messages"][-1].pretty_print() -except NodeInterrupt as exc: - print(f"\nNodeInterrupt occurred: {exc}") - print("Please authorize the tool or update the request, then re-run.") -``` - - -```javascript -try { - const stream = await graph.stream(user_input, config); - for await (const chunk of stream) { - console.log(chunk.messages[chunk.messages.length - 1]); - } -} catch (error) { - console.error("Error streaming response:", error); -} -``` - - - - -## Tips for selecting tools - -- **Relevance**: Pick only the tools you need. Avoid using all tools at once. -- **Avoid conflicts**: Be mindful of duplicate or overlapping functionality. - -## Next steps - -Now that you have integrated Arcade tools into your LangGraph agent, you can: - -- Experiment with different MCP Servers, such as "Math" or "Search." -- Customize the agent's prompts for specific tasks. -- Try out other language models and compare their performance. - -Enjoy exploring Arcade and building powerful AI-enabled Python applications! diff --git a/app/en/guides/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx b/app/en/guides/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx index 16f73cb26..fee9dc3d1 100644 --- a/app/en/guides/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx +++ b/app/en/guides/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx @@ -461,6 +461,8 @@ You should see the agent responding to your prompts like any model, as well as h ## Example code +
+**main.ts** (full file) ```ts filename="main.ts" "use strict"; import { Arcade } from "@arcadeai/arcadejs"; @@ -737,3 +739,4 @@ async function main() { // Run the main function main().catch((err) => console.error(err)); ``` +
diff --git a/app/en/guides/agent-frameworks/langchain/user-auth-interrupts/page.mdx b/app/en/guides/agent-frameworks/langchain/user-auth-interrupts/page.mdx deleted file mode 100644 index d52678880..000000000 --- a/app/en/guides/agent-frameworks/langchain/user-auth-interrupts/page.mdx +++ /dev/null @@ -1,373 +0,0 @@ ---- -title: "Using Arcade User Auth" -description: "Build a custom LangGraph that handles tool authorization with Arcade" ---- - -import { Steps, Tabs, Callout } from "nextra/components"; - -## User Authorization in LangGraph - -In this guide, you will create a LangGraph workflow that requires user authorization before running certain Arcade tools. When a tool needs authorization, the graph displays an authorization URL and waits for the user's approval. This ensures that only the tools you explicitly authorize are available to the language model. For complete working examples, see our [Python](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain/langgraph_with_user_auth.py) and [JavaScript](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain-ts/langgraph-with-user-auth.ts) examples. - - - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys) - -### Install the required packages - -Set up your environment with the following installations: - - - -```bash -pip install langchain-arcade langchain-openai langgraph -``` - - -```bash -npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph -``` - - - -### Configure your Arcade environment - -Make sure you have set your Arcade API key (and any other relevant keys) in the environment, or assign them directly in the code: - -> Need an Arcade API key? Visit the [Get an API key](/get-started/setup/api-keys) page to create one. - - - -```python -import os - -# Import necessary classes and modules -from langchain_arcade import ArcadeToolManager -from langchain_openai import ChatOpenAI -from langgraph.checkpoint.memory import MemorySaver -from langgraph.graph import END, START, MessagesState, StateGraph -from langgraph.prebuilt import ToolNode -from langchain_core.runnables import RunnableConfig - -arcade_api_key = os.environ["ARCADE_API_KEY"] - -# Initialize the tool manager and fetch tools compatible with langgraph -tool_manager = ArcadeToolManager(api_key=arcade_api_key) -tools = tool_manager.get_tools(toolkits=["Gmail"]) -tool_node = ToolNode(tools) - -# Create a language model instance and bind it with the tools -model = ChatOpenAI(model="gpt-4o") -model_with_tools = model.bind_tools(tools) -``` - -Here are the main code elements: - -- arcade_api_key is your Arcade key. -- tool_manager fetches your Arcade tools, for example the "Gmail" MCP Server. -- tool_node encapsulates these tools for usage in LangGraph. -- model_with_tools binds your tools to the "gpt-4o" language model, enabling tool calls. - - - -```javascript -import { pathToFileURL } from "node:url"; -import { Arcade } from "@arcadeai/arcadejs"; -import { toZod } from "@arcadeai/arcadejs/lib"; -import type { AIMessage } from "@langchain/core/messages"; -import { tool } from "@langchain/core/tools"; -import { MessagesAnnotation, StateGraph } from "@langchain/langgraph"; -import { ToolNode } from "@langchain/langgraph/prebuilt"; -import { ChatOpenAI } from "@langchain/openai"; - -// Initialize Arcade with API key from environment -const arcade = new Arcade(); - -// Replace with your application's user ID (e.g. email address, UUID, etc.) -const USER_ID = "{arcade_user_id}"; - -// Initialize tools from Gmail MCP Server -const googleToolkit = await arcade.tools.list({ toolkit: "gmail", limit: 30 }); -const arcadeTools = toZod({ - tools: googleToolkit.items, - client: arcade, - userId: USER_ID, -}); - -// Convert Arcade tools to LangGraph tools -const tools = arcadeTools.map(({ name, description, execute, parameters }) => - tool(execute, { - name, - description, - schema: parameters, - }), -); - -// Initialize the prebuilt tool node -const toolNode = new ToolNode(tools); - -// Create a language model instance and bind it with the tools -const model = new ChatOpenAI({ - model: "gpt-4o", - apiKey: process.env.OPENAI_API_KEY, -}); -const modelWithTools = model.bindTools(tools); -``` - -Here are the main code elements: - -- arcade.tools.list fetches your Arcade tools, for example the "Gmail" MCP Server. -- toZod converts Arcade tools to Zod schemas, which are required by LangGraph. -- ToolNode encapsulates these tools for usage in LangGraph. -- modelWithTools binds your tools to the "gpt-4o" language model, enabling tool calls. - - - - -### Define the workflow steps - -You will create three primary functions to handle AI interaction, tool authorization, and flow control. - - -```python -# Function to invoke the model and get a response -def call_agent(state: MessagesState): - messages = state["messages"] - response = model_with_tools.invoke(messages) - # Return the updated message history - return {"messages": [response]} - - -# Function to determine the next step in the workflow based on the last message -def should_continue(state: MessagesState): - if state["messages"][-1].tool_calls: - for tool_call in state["messages"][-1].tool_calls: - if tool_manager.requires_auth(tool_call["name"]): - return "authorization" - return "tools" # Proceed to tool execution if no authorization is needed - return END # End the workflow if no tool calls are present - - -# Function to handle authorization for tools that require it -def authorize(state: MessagesState, config: RunnableConfig | None = None): - if config is None: - raise ValueError("Config is required for authorization") - - user_id = config["configurable"].get("user_id") - for tool_call in state["messages"][-1].tool_calls: - tool_name = tool_call["name"] - if not tool_manager.requires_auth(tool_name): - continue - auth_response = tool_manager.authorize(tool_name, user_id) - if auth_response.status != "completed": - # Prompt the user to visit the authorization URL - print(f"Visit the following URL to authorize: {auth_response.url}") - - # Wait for the user to complete the authorization - # and then check the authorization status again - tool_manager.wait_for_auth(auth_response.id) - if not tool_manager.is_authorized(auth_response.id): - # This stops execution if authorization fails - raise ValueError("Authorization failed.") - - return {"messages": []} -``` -Explanations for these functions: - -- call_agent: Invokes the language model using the latest conversation state. -- should_continue: Checks the last AI message for any tool calls. If a tool requires authorization, the flow transitions to authorization. Otherwise, it goes straight to tool execution or ends if no tools are called. -- authorize: Prompts the user to authorize any required tools, blocking until authorization is completed successfully or fails. - - -```javascript -// Function to check if a tool requires authorization -async function requiresAuth(toolName: string): Promise<{ - needsAuth: boolean; - id: string; - authUrl: string; -}> { - const authResponse = await arcade.tools.authorize({ - tool_name: toolName, - user_id: USER_ID, - }); - return { - needsAuth: authResponse.status === "pending", - id: authResponse.id ?? "", - authUrl: authResponse.url ?? "", - }; -} - -// Function to invoke the model and get a response -async function callAgent( - state: typeof MessagesAnnotation.State, -): Promise { - const messages = state.messages; - const response = await modelWithTools.invoke(messages); - return { messages: [response] }; -} - -// Function to determine the next step in the workflow based on the last message -async function shouldContinue( - state: typeof MessagesAnnotation.State, -): Promise { - const lastMessage = state.messages[state.messages.length - 1] as AIMessage; - if (lastMessage.tool_calls?.length) { - for (const toolCall of lastMessage.tool_calls) { - const { needsAuth } = await requiresAuth(toolCall.name); - if (needsAuth) { - return "authorization"; - } - } - return "tools"; // Proceed to tool execution if no authorization is needed - } - return "__end__"; // End the workflow if no tool calls are present -} - -// Function to handle authorization for tools that require it -async function authorize( - state: typeof MessagesAnnotation.State, -): Promise { - const lastMessage = state.messages[state.messages.length - 1] as AIMessage; - for (const toolCall of lastMessage.tool_calls || []) { - const toolName = toolCall.name; - const { needsAuth, id, authUrl } = await requiresAuth(toolName); - if (needsAuth) { - // Prompt the user to visit the authorization URL - console.log(`Visit the following URL to authorize: ${authUrl}`); - - // Wait for the user to complete the authorization - const response = await arcade.auth.waitForCompletion(id); - if (response.status !== "completed") { - throw new Error("Authorization failed"); - } - } - } - - return { messages: [] }; -} -``` - -Explanations for these functions: - -- requiresAuth: Checks if a tool requires authorization. -- callAgent: Invokes the language model using the latest conversation state. -- shouldContinue: Checks the last AI message for any tool calls. If a tool requires authorization, the flow transitions to authorization. Otherwise, it goes straight to tool execution or ends if no tools are called. -- authorize: Prompts the user to authorize any required tools, blocking until authorization is completed successfully or fails. - - - - -### Build and compile your LangGraph workflow - -Use StateGraph to assemble the nodes and edges, then compile the graph with a MemorySaver. - - - -```python -if __name__ == "__main__": - # Build the workflow graph using StateGraph - workflow = StateGraph(MessagesState) - - # Add nodes (steps) to the graph - workflow.add_node("agent", call_agent) - workflow.add_node("tools", tool_node) - workflow.add_node("authorization", authorize) - - # Define the edges and control flow between nodes - workflow.add_edge(START, "agent") - workflow.add_conditional_edges("agent", should_continue, ["authorization", "tools", END]) - workflow.add_edge("authorization", "tools") - workflow.add_edge("tools", "agent") - - # Set up memory for checkpointing the state - memory = MemorySaver() - - # Compile the graph with the checkpointer - graph = workflow.compile(checkpointer=memory) -``` - - -```javascript -// Build the workflow graph -const workflow = new StateGraph(MessagesAnnotation) - .addNode("agent", callAgent) - .addNode("tools", toolNode) - .addNode("authorization", authorize) - .addEdge("__start__", "agent") - .addConditionalEdges("agent", shouldContinue, [ - "authorization", - "tools", - "__end__", - ]) - .addEdge("authorization", "tools") - .addEdge("tools", "agent"); - -// Compile the graph -const graph = workflow.compile(); -``` - - - -### Provide inputs and run the graph - -Finally, define user-supplied messages, authorization config, and stream the outputs. The graph will pause for any required tool authorization. - - - -```python -# Define the input messages from the user -inputs = { - "messages": [ - { - "role": "user", - "content": "Check and see if I have any emails in my inbox", - } - ], -} - -# Configuration with thread and user IDs for authorization purposes -config = {"configurable": {"thread_id": "4", "user_id": "{arcade_user_id}"}} - -# Run the graph and stream the outputs -for chunk in graph.stream(inputs, config=config, stream_mode="values"): - # Pretty-print the last message in the chunk - chunk["messages"][-1].pretty_print() -``` - - -```javascript -const inputs = { - messages: [ - { - role: "user", - content: "Check and see if I have any important emails in my inbox", - }, - ], -}; -// Run the graph and stream the outputs -const stream = await graph.stream(inputs, { streamMode: "values" }); -for await (const chunk of stream) { - // Print the last message in the chunk - console.log(chunk.messages[chunk.messages.length - 1].content); -} -``` - - - -In this example: - -- The user prompts the agent to check emails. -- The message triggers a potential need for the "Gmail" MCP Server. -- If authorization is required, the code prints a URL and waits until you permit the tool call. - - - -## Next steps - -- Experiment with more Arcade MCP Servers for expanded capabilities. -- Explore advanced authorization logic, such as multi-user or role-based checks. -- Integrate additional nodes to handle more complex flows or multi-step tasks in your LangGraph. - -By combining Arcade's authorization features with stateful management in LangGraph, you can build AI-driven workflows that respect user permissions at every step. Have fun exploring Arcade! diff --git a/next.config.ts b/next.config.ts index 4cd7690c0..9ec891629 100644 --- a/next.config.ts +++ b/next.config.ts @@ -27,13 +27,26 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/home/langchain/use-arcade-tools", destination: - "/:locale/guides/agent-frameworks/langchain/use-arcade-tools", + "/:locale/guides/agent-frameworks/langchain/use-arcade-with-langchain", + permanent: true, + }, + { + source: "/:locale/guides/agent-frameworks/langchain/use-arcade-tools", + destination: + "/:locale/guides/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { source: "/:locale/home/langchain/user-auth-interrupts", destination: + "/:locale/guides/agent-frameworks/langchain/use-arcade-with-langchain", + permanent: true, + }, + { + source: "/:locale/guides/agent-frameworks/langchain/user-auth-interrupts", + destination: + "/:locale/guides/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { From daf2890d0f1069c82e81c9a8ddd9f2c85343d74a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 19 Jan 2026 14:44:32 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=A4=96=20Regenerate=20LLMs.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/llms.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/llms.txt b/public/llms.txt index f7a3fec12..57e9528ca 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,4 +1,4 @@ - + # Arcade @@ -119,7 +119,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Environment Variables](https://docs.arcade.dev/en/resources/integrations/social-communication/slack/environment-variables.md): This documentation page provides guidance on configuring environment variables related to Slack API interactions, specifically `SLACK_MAX_CONCURRENT_REQUESTS`, `MAX_PAGINATION_SIZE_LIMIT`, and `MAX_PAGINATION_TIMEOUT_SECONDS`. Users will learn how to adjust these settings - [Evaluate Tools](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools.md): The "Evaluate Tools" documentation page provides guidance on systematically testing and enhancing tools using Arcade's evaluation framework. It helps users validate the performance of their tools after initial development and offers techniques for iterative improvements to ensure reliability in production. - [ExaApi](https://docs.arcade.dev/en/resources/integrations/search/exa-api.md): The ExaApi documentation provides users with a comprehensive guide to utilizing the Exa.ai Search API, enabling them to conduct searches, manage websets, and handle research requests effectively. It outlines various tools available within the API, detailing their functionalities such as -- [Fetch the "ScrapeUrl" tool from the "Firecrawl" MCP Server](https://docs.arcade.dev/en/guides/agent-frameworks/langchain/use-arcade-tools.md): This documentation page provides a comprehensive guide for integrating Arcade tools into LangGraph applications, detailing prerequisites, environment setup, API key configuration, and tool management. Users will learn how to create and manage AI models, configure agents, and stream responses while utilizing code - [Figma](https://docs.arcade.dev/en/resources/integrations/development/figma.md): This documentation page provides users with a comprehensive guide to the Figma MCP Server, enabling interaction with Figma's design files, components, and collaboration features through the Figma REST API. Users can learn to access file structures, manage components, add comments - [FigmaApi](https://docs.arcade.dev/en/resources/integrations/productivity/figma-api.md): The FigmaApi documentation provides a comprehensive guide for developers to utilize tools that enable interaction with the Figma API, facilitating efficient management of design assets and collaboration on projects. Users can learn how to perform various actions such as retrieving Figma files, managing - [Firecrawl](https://docs.arcade.dev/en/resources/integrations/development/firecrawl.md): The Firecrawl documentation provides users with a comprehensive guide to utilizing the Arcade Firecrawl MCP Server, enabling them to build agents and AI applications for scraping, crawling, and mapping websites. It outlines available tools, including functionalities for scraping URLs, crawling websites, @@ -164,7 +163,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [HubspotUsersApi](https://docs.arcade.dev/en/resources/integrations/sales/hubspot-users-api.md): The HubspotUsersApi documentation provides users with tools to efficiently manage users and teams within a HubSpot account, including functionalities for retrieving user lists, creating and updating user accounts, and removing users. It offers detailed descriptions of available API tools, along with - [Imgflip](https://docs.arcade.dev/en/resources/integrations/entertainment/imgflip.md): The Imgflip documentation provides users with tools to create and manage memes using the Imgflip API, enabling the development of agents and AI applications. Users can search for meme templates, retrieve popular memes, and create custom memes by adding text to existing templates. - [Imgflip](https://docs.arcade.dev/en/resources/integrations/entertainment/spotify/imgflip.md): The Imgflip documentation page provides users with tools to create and manage memes using the Imgflip API, allowing them to search for meme templates, retrieve popular templates, and create custom memes. It outlines the available features, including a premium search option and customizable -- [Import necessary classes and modules](https://docs.arcade.dev/en/guides/agent-frameworks/langchain/user-auth-interrupts.md): This documentation page guides users in creating a LangGraph workflow that integrates user authorization for specific Arcade tools, ensuring that only authorized tools are accessible to the language model. It provides step-by-step instructions on setting up the necessary environment, installing required packages, and - [In Custom Applications](https://docs.arcade.dev/en/guides/tool-calling/custom-apps.md): This documentation page provides guidance on integrating Arcade tools into custom applications, focusing on user authentication, authorization status checking, and managing tool definitions. It serves as a resource for developers building tool-calling interfaces to ensure proper implementation and functionality. - [Initialize the Arcade client](https://docs.arcade.dev/en/guides/agent-frameworks/google-adk/use-arcade-tools.md): This documentation page provides a comprehensive guide for integrating Arcade tools into Google ADK applications, outlining the necessary prerequisites, setup procedures, and configuration steps. Users will learn how to manage and authorize Arcade tools, create agents, and run them effectively within their applications - [IntercomApi](https://docs.arcade.dev/en/resources/integrations/customer-support/intercom-api.md): The IntercomApi documentation provides a comprehensive guide to tools that enable users to interact with the Intercom platform using OAuth2 authentication. It details various functionalities, such as managing admin information, creating and updating articles, and handling company data, allowing users to From b3f2dfed79ffabab31922e8a783c5e5ef51f01e6 Mon Sep 17 00:00:00 2001 From: Mateo Torres Date: Wed, 21 Jan 2026 11:22:00 -0300 Subject: [PATCH 3/6] merge main into this branch --- .github/workflows/check-redirects.yml | 134 ++++ .husky/pre-commit | 58 ++ agents/changelog/agents/changelog.ts | 3 +- agents/changelog/index.ts | 4 +- app/_components/tool-footer.tsx | 4 +- .../agent-frameworks/_meta.tsx | 3 + .../agent-frameworks/crewai/_meta.tsx | 0 .../crewai/custom-auth-flow/page.mdx | 0 .../crewai/use-arcade-tools/page.mdx | 4 +- .../agent-frameworks/google-adk/_meta.tsx | 0 .../google-adk/overview/page.mdx | 4 +- .../google-adk/use-arcade-tools/page.mdx | 0 .../agent-frameworks/langchain/_meta.tsx | 0 .../langchain/auth-langchain-tools/page.mdx | 0 .../langchain/use-arcade-tools/page.mdx | 243 +++++++ .../use-arcade-with-langchain/page.mdx | 0 .../langchain/user-auth-interrupts/page.mdx | 373 ++++++++++ .../agent-frameworks/mastra/_meta.tsx | 0 .../agent-frameworks/mastra/overview/page.mdx | 4 +- .../mastra/use-arcade-tools/page.mdx | 2 +- .../mastra/user-auth-interrupts/page.mdx | 0 .../agent-frameworks/openai-agents/_meta.tsx | 1 + .../openai-agents/overview/page.mdx | 4 +- .../openai-agents/use-arcade-tools/page.mdx | 0 .../use-arcade-with-openai-agents/page.mdx | 560 +++++++++++++++ .../user-auth-interrupts/page.mdx | 0 .../agent-frameworks/page.mdx | 2 - .../page.mdx | 471 +++++++++++++ .../agent-frameworks/vercelai/page.mdx | 0 .../mcp-clients/_meta.tsx | 0 .../mcp-clients/claude-desktop/page.mdx | 0 .../mcp-clients/copilot-studio/page.mdx | 0 .../mcp-clients/cursor/page.mdx | 0 .../mcp-clients/mcp-client-grid.tsx | 0 .../mcp-clients/page.mdx | 0 .../mcp-clients/visual-studio-code/page.mdx | 0 .../quickstarts/call-tool-agent/page.mdx | 56 +- .../quickstarts/call-tool-client/page.mdx | 47 +- app/en/guides/_meta.tsx | 3 - .../guides/create-tools/mcp-gateways/page.mdx | 6 +- .../deployment-hosting/on-prem/page.mdx | 6 +- app/en/guides/tool-calling/_meta.tsx | 3 - app/en/home/landing-page.tsx | 4 +- app/en/references/changelog/page.mdx | 14 +- .../integrations/productivity/gmail/page.mdx | 2 +- next.config.ts | 89 ++- package.json | 5 +- public/llms.txt | 44 +- scripts/check-meta-keys.ts | 446 ++++++++++++ scripts/check-redirects.ts | 652 ++++++++++++++++++ scripts/update-internal-links.ts | 281 ++++++++ scripts/vale-editorial.ts | 28 +- 52 files changed, 3418 insertions(+), 142 deletions(-) create mode 100644 .github/workflows/check-redirects.yml rename app/en/{guides => get-started}/agent-frameworks/_meta.tsx (80%) rename app/en/{guides => get-started}/agent-frameworks/crewai/_meta.tsx (100%) rename app/en/{guides => get-started}/agent-frameworks/crewai/custom-auth-flow/page.mdx (100%) rename app/en/{guides => get-started}/agent-frameworks/crewai/use-arcade-tools/page.mdx (90%) rename app/en/{guides => get-started}/agent-frameworks/google-adk/_meta.tsx (100%) rename app/en/{guides => get-started}/agent-frameworks/google-adk/overview/page.mdx (96%) rename app/en/{guides => get-started}/agent-frameworks/google-adk/use-arcade-tools/page.mdx (100%) rename app/en/{guides => get-started}/agent-frameworks/langchain/_meta.tsx (100%) rename app/en/{guides => get-started}/agent-frameworks/langchain/auth-langchain-tools/page.mdx (100%) create mode 100644 app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx rename app/en/{guides => get-started}/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx (100%) create mode 100644 app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx rename app/en/{guides => get-started}/agent-frameworks/mastra/_meta.tsx (100%) rename app/en/{guides => get-started}/agent-frameworks/mastra/overview/page.mdx (87%) rename app/en/{guides => get-started}/agent-frameworks/mastra/use-arcade-tools/page.mdx (91%) rename app/en/{guides => get-started}/agent-frameworks/mastra/user-auth-interrupts/page.mdx (100%) rename app/en/{guides => get-started}/agent-frameworks/openai-agents/_meta.tsx (66%) rename app/en/{guides => get-started}/agent-frameworks/openai-agents/overview/page.mdx (95%) rename app/en/{guides => get-started}/agent-frameworks/openai-agents/use-arcade-tools/page.mdx (100%) create mode 100644 app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx rename app/en/{guides => get-started}/agent-frameworks/openai-agents/user-auth-interrupts/page.mdx (100%) rename app/en/{guides => get-started}/agent-frameworks/page.mdx (99%) create mode 100644 app/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python/page.mdx rename app/en/{guides => get-started}/agent-frameworks/vercelai/page.mdx (100%) rename app/en/{guides/tool-calling => get-started}/mcp-clients/_meta.tsx (100%) rename app/en/{guides/tool-calling => get-started}/mcp-clients/claude-desktop/page.mdx (100%) rename app/en/{guides/tool-calling => get-started}/mcp-clients/copilot-studio/page.mdx (100%) rename app/en/{guides/tool-calling => get-started}/mcp-clients/cursor/page.mdx (100%) rename app/en/{guides/tool-calling => get-started}/mcp-clients/mcp-client-grid.tsx (100%) rename app/en/{guides/tool-calling => get-started}/mcp-clients/page.mdx (100%) rename app/en/{guides/tool-calling => get-started}/mcp-clients/visual-studio-code/page.mdx (100%) create mode 100644 scripts/check-meta-keys.ts create mode 100644 scripts/check-redirects.ts create mode 100644 scripts/update-internal-links.ts diff --git a/.github/workflows/check-redirects.yml b/.github/workflows/check-redirects.yml new file mode 100644 index 000000000..1482842be --- /dev/null +++ b/.github/workflows/check-redirects.yml @@ -0,0 +1,134 @@ +name: Check Redirects for Deleted Pages + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - "app/**/*.md" + - "app/**/*.mdx" + - "next.config.ts" + +permissions: + contents: read + pull-requests: write + +jobs: + check-redirects: + name: Verify Deleted Pages Have Redirects + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history needed for branch comparison + + - name: Fetch base branch + run: git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install dependencies + run: npm install -g pnpm && pnpm install + + - name: Check for missing redirects + id: check + run: | + set -o pipefail + pnpm check-redirects ${{ github.base_ref }} 2>&1 | tee redirect-check-output.txt + continue-on-error: true + + - name: Comment on PR if redirects are missing + if: steps.check.outcome == 'failure' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const output = fs.readFileSync('redirect-check-output.txt', 'utf8'); + + // Extract the missing redirects and suggestions from output + const body = `## 🔗 Missing Redirects Detected + + This PR deletes markdown files that don't have corresponding redirects in \`next.config.ts\`. + + When you delete a page, you must add a redirect to prevent broken links for users who have bookmarked the old URL. + +
+ 📋 View Details + + \`\`\` + ${output} + \`\`\` + +
+ + ### How to fix + + 1. Open \`next.config.ts\` + 2. Find the \`redirects()\` function + 3. Add redirect entries for each deleted file (see suggestions above) + 4. Push the changes + + --- + *This check ensures we maintain URL stability for our documentation.*`; + + // Check if we already commented + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('Missing Redirects Detected') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: body + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body + }); + } + + - name: Remove outdated comment if check passes + if: steps.check.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('Missing Redirects Detected') + ); + + if (botComment) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + }); + } + + - name: Fail if redirects are missing + if: steps.check.outcome == 'failure' + run: | + echo "❌ Missing redirects for deleted pages. See PR comment for details." + exit 1 diff --git a/.husky/pre-commit b/.husky/pre-commit index 8a2bd82e0..1e192ed30 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -29,6 +29,64 @@ if [ -n "$STAGED_DOCS" ]; then fi fi +# --- Check Meta Keys (when _meta.tsx files are changed) --- +STAGED_META=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '_meta\.tsx$' || true) + +if [ -n "$STAGED_META" ]; then + echo "🔍 Checking _meta.tsx keys..." + + if ! pnpm check-meta --staged-only; then + echo "" + echo "❌ Commit blocked: _meta.tsx keys must match sibling directories or files." + exit 1 + fi +fi + +# --- Check Redirects (when markdown pages are deleted or renamed) --- +# Detect deleted pages (D status) and renamed pages (R status - old path needs redirect) +DELETED_PAGES=$(git diff --cached --name-status | grep -E "^D.*page\.(md|mdx)$" | cut -f2 || true) +RENAMED_PAGES=$(git diff --cached --name-status | grep -E "^R.*page\.(md|mdx)$" | cut -f2 || true) + +if [ -n "$DELETED_PAGES" ] || [ -n "$RENAMED_PAGES" ]; then + echo "🔗 Detected deleted/renamed page(s), checking for redirects..." + + # Run the TypeScript redirect checker with auto-fix (only checks staged changes) + # This will add redirect entries to next.config.ts if missing + if ! pnpm check-redirects --auto-fix --staged-only 2>&1; then + # Stage next.config.ts if it was modified + if git diff --name-only next.config.ts 2>/dev/null | grep -q "next.config.ts"; then + git add next.config.ts + echo "" + echo "📝 Redirect entries added to next.config.ts and staged." + fi + echo "" + # Check if there are placeholders vs other errors + if grep -q "REPLACE_WITH_NEW_PATH" next.config.ts 2>/dev/null; then + echo "❌ Commit blocked: Please update the placeholder destinations in next.config.ts" + echo " Search for 'REPLACE_WITH_NEW_PATH' and provide actual redirect paths." + else + echo "❌ Commit blocked: Please fix the redirect issues shown above." + fi + exit 1 + fi +fi + +# --- Update Internal Links (when redirects are added) --- +# If next.config.ts is staged, update any internal links pointing to redirected paths +if git diff --cached --name-only | grep -q "next.config.ts"; then + echo "🔗 Updating internal links for new redirects..." + + # Run the TypeScript update script + if pnpm update-links 2>/dev/null; then + # Stage any files that were modified + UPDATED_FILES=$(git diff --name-only -- 'app/**/*.mdx' 'app/**/*.tsx' 'app/**/*.md' 2>/dev/null || true) + if [ -n "$UPDATED_FILES" ]; then + echo "$UPDATED_FILES" | xargs git add + echo "✅ Internal links updated and staged" + fi + fi +fi + # --- Lint Staged (formatting) --- pnpm dlx lint-staged diff --git a/agents/changelog/agents/changelog.ts b/agents/changelog/agents/changelog.ts index 9939b6531..9518304e8 100644 --- a/agents/changelog/agents/changelog.ts +++ b/agents/changelog/agents/changelog.ts @@ -53,7 +53,7 @@ When updating the changelog, follow these rules: privateRepositories: string[], ) { this.logger.startSpan( - `Generating changelog from changes in ${repositories.join(", ")}...`, + `Generating changelog from changes in ${repositories.join(", ")} and ${privateRepositories.join(", ")}...`, ); const result = await this.run( @@ -61,6 +61,7 @@ When updating the changelog, follow these rules: Today is ${new Date().toISOString().split("T")[0]}. The full path to the changelog.md that you will be appending to is \`${changelogPath}\`. The Github repositories to load commits from are: ${repositories.join(", ")} + The Github private repositories to load commits from are: ${privateRepositories.join(", ")} When appending to the changelog, do not include links for the private repositories, which are: ${privateRepositories.join(", ")} `, ); diff --git a/agents/changelog/index.ts b/agents/changelog/index.ts index 042b3b8cb..b84901642 100644 --- a/agents/changelog/index.ts +++ b/agents/changelog/index.ts @@ -65,12 +65,12 @@ program .argument( "[repos]", "A comma separated list of repositories to load the changelogs from", - "ArcadeAI/docs,ArcadeAI/arcade-mcp,ArcadeAI/Cloud,ArcadeAI/Engine,ArcadeAI/dashboard,ArcadeAI/toolkits", + "ArcadeAI/docs,ArcadeAI/arcade-mcp", ) .argument( "[private_repos]", "A comma separated list of private repositories to load the changelogs from", - "ArcadeAI/Cloud,ArcadeAI/Engine,ArcadeAI/dashboard,ArcadeAI/toolkits", + "ArcadeAI/monorepo", ) .action( async ( diff --git a/app/_components/tool-footer.tsx b/app/_components/tool-footer.tsx index 749498a0e..bbac5ae9f 100644 --- a/app/_components/tool-footer.tsx +++ b/app/_components/tool-footer.tsx @@ -15,7 +15,7 @@ const ToolFooter: React.FC = ({ pipPackageName }) => (
@@ -24,7 +24,7 @@ const ToolFooter: React.FC = ({ pipPackageName }) => ( description={ "Arcade tools can be self-hosted on your own infrastructure. Learn more about self-hosting." } - href="/home/hosting-overview" + href="/guides/deployment-hosting" icon={Puzzle} title="Self Host Arcade tools" /> diff --git a/app/en/guides/agent-frameworks/_meta.tsx b/app/en/get-started/agent-frameworks/_meta.tsx similarity index 80% rename from app/en/guides/agent-frameworks/_meta.tsx rename to app/en/get-started/agent-frameworks/_meta.tsx index 294221288..3ebbaae3a 100644 --- a/app/en/guides/agent-frameworks/_meta.tsx +++ b/app/en/get-started/agent-frameworks/_meta.tsx @@ -4,6 +4,9 @@ export const meta: MetaRecord = { index: { title: "Overview", }, + "setup-arcade-with-your-llm-python": { + title: "Setup Arcade with your LLM (Python)", + }, crewai: { title: "CrewAI", }, diff --git a/app/en/guides/agent-frameworks/crewai/_meta.tsx b/app/en/get-started/agent-frameworks/crewai/_meta.tsx similarity index 100% rename from app/en/guides/agent-frameworks/crewai/_meta.tsx rename to app/en/get-started/agent-frameworks/crewai/_meta.tsx diff --git a/app/en/guides/agent-frameworks/crewai/custom-auth-flow/page.mdx b/app/en/get-started/agent-frameworks/crewai/custom-auth-flow/page.mdx similarity index 100% rename from app/en/guides/agent-frameworks/crewai/custom-auth-flow/page.mdx rename to app/en/get-started/agent-frameworks/crewai/custom-auth-flow/page.mdx diff --git a/app/en/guides/agent-frameworks/crewai/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx similarity index 90% rename from app/en/guides/agent-frameworks/crewai/use-arcade-tools/page.mdx rename to app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx index 3b975f314..acf27603a 100644 --- a/app/en/guides/agent-frameworks/crewai/use-arcade-tools/page.mdx +++ b/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx @@ -8,9 +8,9 @@ import ToggleContent from "@/app/_components/toggle-content"; ## Use CrewAI with Arcade -In this guide, we will explore 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 are executed. +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. -To tailor the tool authorization flow to meet your application's specific needs, check out the [Custom Auth Flow with CrewAI](/guides/agent-frameworks/crewai/custom-auth-flow) guide. +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. diff --git a/app/en/guides/agent-frameworks/google-adk/_meta.tsx b/app/en/get-started/agent-frameworks/google-adk/_meta.tsx similarity index 100% rename from app/en/guides/agent-frameworks/google-adk/_meta.tsx rename to app/en/get-started/agent-frameworks/google-adk/_meta.tsx diff --git a/app/en/guides/agent-frameworks/google-adk/overview/page.mdx b/app/en/get-started/agent-frameworks/google-adk/overview/page.mdx similarity index 96% rename from app/en/guides/agent-frameworks/google-adk/overview/page.mdx rename to app/en/get-started/agent-frameworks/google-adk/overview/page.mdx index 6f97f8457..963e91232 100644 --- a/app/en/guides/agent-frameworks/google-adk/overview/page.mdx +++ b/app/en/get-started/agent-frameworks/google-adk/overview/page.mdx @@ -127,7 +127,7 @@ for tool in google_tools: Ready to start building with Arcade and OpenAI Agents? Check out these guides: -- [Using Arcade tools](/guides/agent-frameworks/google-adk/use-arcade-tools) - Learn the basics of using Arcade tools with Google ADK +- [Using Arcade tools](/get-started/agent-frameworks/google-adk/use-arcade-tools) - Learn the basics of using Arcade tools with Google ADK - [Creating custom tools](/guides/create-tools/tool-basics/build-mcp-server) - Build your own tools with the Arcade Tool SDK -Enjoy exploring Arcade and building powerful AI-enabled applications! +Enjoy exploring Arcade and building powerful AI-enabled applications. diff --git a/app/en/guides/agent-frameworks/google-adk/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/google-adk/use-arcade-tools/page.mdx similarity index 100% rename from app/en/guides/agent-frameworks/google-adk/use-arcade-tools/page.mdx rename to app/en/get-started/agent-frameworks/google-adk/use-arcade-tools/page.mdx diff --git a/app/en/guides/agent-frameworks/langchain/_meta.tsx b/app/en/get-started/agent-frameworks/langchain/_meta.tsx similarity index 100% rename from app/en/guides/agent-frameworks/langchain/_meta.tsx rename to app/en/get-started/agent-frameworks/langchain/_meta.tsx diff --git a/app/en/guides/agent-frameworks/langchain/auth-langchain-tools/page.mdx b/app/en/get-started/agent-frameworks/langchain/auth-langchain-tools/page.mdx similarity index 100% rename from app/en/guides/agent-frameworks/langchain/auth-langchain-tools/page.mdx rename to app/en/get-started/agent-frameworks/langchain/auth-langchain-tools/page.mdx diff --git a/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx new file mode 100644 index 000000000..a16ccd5b4 --- /dev/null +++ b/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx @@ -0,0 +1,243 @@ +--- +title: "Use Arcade tools with LangGraph" +description: "Integrate Arcade tools into your LangGraph applications" +--- + +import { Steps, Tabs, Callout } from "nextra/components"; + +## Use LangGraph with Arcade + +In this guide, let's explore how to integrate Arcade tools into your LangGraph application. Follow the step-by-step instructions below. For complete working examples, see our [Python](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain/langgraph_arcade_minimal.py) and [JavaScript](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain-ts/langgraph-arcade-minimal.ts) examples. + + + +### Prerequisites + +- [Obtain an Arcade API key](/get-started/setup/api-keys) + +### Set up your environment + +Install the required packages, and ensure your environment variables are set with your Arcade and OpenAI API keys: + + + +```bash +pip install langchain-arcade langchain-openai langgraph +``` + + +```bash +npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph +``` + + + + +### Configure API keys + +Provide your Arcade and OpenAI API keys. You can store them in environment variables or directly in your code: + +> Need an Arcade API key? Visit the [Get an API key](/get-started/setup/api-keys) page to create one. + + + +```python +import os + +arcade_api_key = os.environ.get("ARCADE_API_KEY", "YOUR_ARCADE_API_KEY") +openai_api_key = os.environ.get("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY") +``` + + +```bash +ARCADE_API_KEY= +OPENAI_API_KEY= +``` + + + +### Create and manage Arcade tools + + + +Use the ArcadeToolManager to retrieve specific tools or entire MCP Servers: + +```python +from langchain_arcade import ArcadeToolManager + +manager = ArcadeToolManager(api_key=arcade_api_key) + +# Fetch the "ScrapeUrl" tool from the "Firecrawl" MCP Server +tools = manager.get_tools(tools=["Firecrawl.ScrapeUrl"]) +print(manager.tools) + +# Get all tools from the "Gmail" MCP Server +tools = manager.get_tools(toolkits=["Gmail"]) +print(manager.tools) +``` + + +Arcade offers methods to convert tools into Zod schemas, which is essential since LangGraph defines tools using Zod. The `toZod` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with LangGraph. Learn more about Arcade's Zod integration options [here](/guides/tool-calling/custom-apps/get-tool-definitions#get-zod-tool-definitions). +```javascript +import { Arcade } from "@arcadeai/arcadejs"; +import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib"; +import { tool } from "@langchain/core/tools"; + +// Initialize the Arcade client +const arcade = new Arcade(); + +// Get the Arcade tools, you can customize the MCP Server (e.g. "github", "notion", "gmail", etc.) +const googleToolkit = await arcade.tools.list({ toolkit: "gmail", limit: 30 }); +const arcadeTools = toZod({ + tools: googleToolkit.items, + client: arcade, + userId: "", // Replace this with your application's user ID (e.g. email address, UUID, etc.) +}); +// Convert Arcade tools to LangGraph tools +const tools = arcadeTools.map(({ name, description, execute, parameters }) => + tool(execute, { + name, + description, + schema: parameters, + }), +); +console.log(tools); +``` + + + + +### Set up the language model and memory + +Create an AI model and bind your tools. Use MemorySaver for checkpointing: + + + +```python +from langchain_openai import ChatOpenAI +from langgraph.checkpoint.memory import MemorySaver + +model = ChatOpenAI(model="gpt-4o", api_key=openai_api_key) +bound_model = model.bind_tools(tools) + +memory = MemorySaver() +``` + + +```javascript +import { ChatOpenAI } from "@langchain/openai"; +import { MemorySaver } from "@langchain/langgraph"; + +const model = new ChatOpenAI({ model: "gpt-4o", apiKey: process.env.OPENAI_API_KEY }); +const boundModel = model.bindTools(tools); +const memory = new MemorySaver(); +``` + + + +### Create a ReAct-style agent + +Use the prebuilt ReAct agent from LangGraph to handle your Arcade tools: + + +```python +from langgraph.prebuilt import create_react_agent + +graph = create_react_agent(model=bound_model, tools=tools, checkpointer=memory) +``` + + +```javascript +import { createReactAgent } from "@langchain/langgraph/prebuilt"; + +const graph = createReactAgent({ llm: boundModel, tools, checkpointer: memory }); +``` + + + +### Provide configuration and user query + +Supply a basic config dictionary and a user query. Notice that user_id is required for tool authorization: + + +```python +config = { + "configurable": { + "thread_id": "1", + "user_id": "{arcade_user_id}" + } +} +user_input = { + "messages": [ + ("user", "List any new and important emails in my inbox.") + ] +} +``` + + +```javascript +const config = { + configurable: { + thread_id: "1", + user_id: "{arcade_user_id}", + }, + streamMode: "values" as const, +}; +const user_input = { + messages: [ + { + role: "user", + content: "List any new and important emails in my inbox.", + }, + ], +}; +``` + + + +### Stream the response + +Stream the assistant's output. If the tool requires authorization, the agent will ask the user to authorize the tool. + + + +```python +from langgraph.errors import NodeInterrupt + +try: + for chunk in graph.stream(user_input, config, stream_mode="values"): + chunk["messages"][-1].pretty_print() +except NodeInterrupt as exc: + print(f"\nNodeInterrupt occurred: {exc}") + print("Please authorize the tool or update the request, then re-run.") +``` + + +```javascript +try { + const stream = await graph.stream(user_input, config); + for await (const chunk of stream) { + console.log(chunk.messages[chunk.messages.length - 1]); + } +} catch (error) { + console.error("Error streaming response:", error); +} +``` + + + + +## Tips for selecting tools + +- **Relevance**: Pick only the tools you need. Avoid using all tools at once. +- **Avoid conflicts**: Be mindful of duplicate or overlapping functionality. + +## Next steps + +Now that you have integrated Arcade tools into your LangGraph agent, you can: + +- Experiment with different MCP Servers, such as "Math" or "Search." +- Customize the agent's prompts for specific tasks. +- Try out other language models and compare their performance. + +Enjoy exploring Arcade and building powerful AI-enabled Python applications! diff --git a/app/en/guides/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx b/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx similarity index 100% rename from app/en/guides/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx rename to app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain/page.mdx diff --git a/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx b/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx new file mode 100644 index 000000000..d52678880 --- /dev/null +++ b/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx @@ -0,0 +1,373 @@ +--- +title: "Using Arcade User Auth" +description: "Build a custom LangGraph that handles tool authorization with Arcade" +--- + +import { Steps, Tabs, Callout } from "nextra/components"; + +## User Authorization in LangGraph + +In this guide, you will create a LangGraph workflow that requires user authorization before running certain Arcade tools. When a tool needs authorization, the graph displays an authorization URL and waits for the user's approval. This ensures that only the tools you explicitly authorize are available to the language model. For complete working examples, see our [Python](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain/langgraph_with_user_auth.py) and [JavaScript](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain-ts/langgraph-with-user-auth.ts) examples. + + + +### Prerequisites + +- [Obtain an Arcade API key](/get-started/setup/api-keys) + +### Install the required packages + +Set up your environment with the following installations: + + + +```bash +pip install langchain-arcade langchain-openai langgraph +``` + + +```bash +npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph +``` + + + +### Configure your Arcade environment + +Make sure you have set your Arcade API key (and any other relevant keys) in the environment, or assign them directly in the code: + +> Need an Arcade API key? Visit the [Get an API key](/get-started/setup/api-keys) page to create one. + + + +```python +import os + +# Import necessary classes and modules +from langchain_arcade import ArcadeToolManager +from langchain_openai import ChatOpenAI +from langgraph.checkpoint.memory import MemorySaver +from langgraph.graph import END, START, MessagesState, StateGraph +from langgraph.prebuilt import ToolNode +from langchain_core.runnables import RunnableConfig + +arcade_api_key = os.environ["ARCADE_API_KEY"] + +# Initialize the tool manager and fetch tools compatible with langgraph +tool_manager = ArcadeToolManager(api_key=arcade_api_key) +tools = tool_manager.get_tools(toolkits=["Gmail"]) +tool_node = ToolNode(tools) + +# Create a language model instance and bind it with the tools +model = ChatOpenAI(model="gpt-4o") +model_with_tools = model.bind_tools(tools) +``` + +Here are the main code elements: + +- arcade_api_key is your Arcade key. +- tool_manager fetches your Arcade tools, for example the "Gmail" MCP Server. +- tool_node encapsulates these tools for usage in LangGraph. +- model_with_tools binds your tools to the "gpt-4o" language model, enabling tool calls. + + + +```javascript +import { pathToFileURL } from "node:url"; +import { Arcade } from "@arcadeai/arcadejs"; +import { toZod } from "@arcadeai/arcadejs/lib"; +import type { AIMessage } from "@langchain/core/messages"; +import { tool } from "@langchain/core/tools"; +import { MessagesAnnotation, StateGraph } from "@langchain/langgraph"; +import { ToolNode } from "@langchain/langgraph/prebuilt"; +import { ChatOpenAI } from "@langchain/openai"; + +// Initialize Arcade with API key from environment +const arcade = new Arcade(); + +// Replace with your application's user ID (e.g. email address, UUID, etc.) +const USER_ID = "{arcade_user_id}"; + +// Initialize tools from Gmail MCP Server +const googleToolkit = await arcade.tools.list({ toolkit: "gmail", limit: 30 }); +const arcadeTools = toZod({ + tools: googleToolkit.items, + client: arcade, + userId: USER_ID, +}); + +// Convert Arcade tools to LangGraph tools +const tools = arcadeTools.map(({ name, description, execute, parameters }) => + tool(execute, { + name, + description, + schema: parameters, + }), +); + +// Initialize the prebuilt tool node +const toolNode = new ToolNode(tools); + +// Create a language model instance and bind it with the tools +const model = new ChatOpenAI({ + model: "gpt-4o", + apiKey: process.env.OPENAI_API_KEY, +}); +const modelWithTools = model.bindTools(tools); +``` + +Here are the main code elements: + +- arcade.tools.list fetches your Arcade tools, for example the "Gmail" MCP Server. +- toZod converts Arcade tools to Zod schemas, which are required by LangGraph. +- ToolNode encapsulates these tools for usage in LangGraph. +- modelWithTools binds your tools to the "gpt-4o" language model, enabling tool calls. + + + + +### Define the workflow steps + +You will create three primary functions to handle AI interaction, tool authorization, and flow control. + + +```python +# Function to invoke the model and get a response +def call_agent(state: MessagesState): + messages = state["messages"] + response = model_with_tools.invoke(messages) + # Return the updated message history + return {"messages": [response]} + + +# Function to determine the next step in the workflow based on the last message +def should_continue(state: MessagesState): + if state["messages"][-1].tool_calls: + for tool_call in state["messages"][-1].tool_calls: + if tool_manager.requires_auth(tool_call["name"]): + return "authorization" + return "tools" # Proceed to tool execution if no authorization is needed + return END # End the workflow if no tool calls are present + + +# Function to handle authorization for tools that require it +def authorize(state: MessagesState, config: RunnableConfig | None = None): + if config is None: + raise ValueError("Config is required for authorization") + + user_id = config["configurable"].get("user_id") + for tool_call in state["messages"][-1].tool_calls: + tool_name = tool_call["name"] + if not tool_manager.requires_auth(tool_name): + continue + auth_response = tool_manager.authorize(tool_name, user_id) + if auth_response.status != "completed": + # Prompt the user to visit the authorization URL + print(f"Visit the following URL to authorize: {auth_response.url}") + + # Wait for the user to complete the authorization + # and then check the authorization status again + tool_manager.wait_for_auth(auth_response.id) + if not tool_manager.is_authorized(auth_response.id): + # This stops execution if authorization fails + raise ValueError("Authorization failed.") + + return {"messages": []} +``` +Explanations for these functions: + +- call_agent: Invokes the language model using the latest conversation state. +- should_continue: Checks the last AI message for any tool calls. If a tool requires authorization, the flow transitions to authorization. Otherwise, it goes straight to tool execution or ends if no tools are called. +- authorize: Prompts the user to authorize any required tools, blocking until authorization is completed successfully or fails. + + +```javascript +// Function to check if a tool requires authorization +async function requiresAuth(toolName: string): Promise<{ + needsAuth: boolean; + id: string; + authUrl: string; +}> { + const authResponse = await arcade.tools.authorize({ + tool_name: toolName, + user_id: USER_ID, + }); + return { + needsAuth: authResponse.status === "pending", + id: authResponse.id ?? "", + authUrl: authResponse.url ?? "", + }; +} + +// Function to invoke the model and get a response +async function callAgent( + state: typeof MessagesAnnotation.State, +): Promise { + const messages = state.messages; + const response = await modelWithTools.invoke(messages); + return { messages: [response] }; +} + +// Function to determine the next step in the workflow based on the last message +async function shouldContinue( + state: typeof MessagesAnnotation.State, +): Promise { + const lastMessage = state.messages[state.messages.length - 1] as AIMessage; + if (lastMessage.tool_calls?.length) { + for (const toolCall of lastMessage.tool_calls) { + const { needsAuth } = await requiresAuth(toolCall.name); + if (needsAuth) { + return "authorization"; + } + } + return "tools"; // Proceed to tool execution if no authorization is needed + } + return "__end__"; // End the workflow if no tool calls are present +} + +// Function to handle authorization for tools that require it +async function authorize( + state: typeof MessagesAnnotation.State, +): Promise { + const lastMessage = state.messages[state.messages.length - 1] as AIMessage; + for (const toolCall of lastMessage.tool_calls || []) { + const toolName = toolCall.name; + const { needsAuth, id, authUrl } = await requiresAuth(toolName); + if (needsAuth) { + // Prompt the user to visit the authorization URL + console.log(`Visit the following URL to authorize: ${authUrl}`); + + // Wait for the user to complete the authorization + const response = await arcade.auth.waitForCompletion(id); + if (response.status !== "completed") { + throw new Error("Authorization failed"); + } + } + } + + return { messages: [] }; +} +``` + +Explanations for these functions: + +- requiresAuth: Checks if a tool requires authorization. +- callAgent: Invokes the language model using the latest conversation state. +- shouldContinue: Checks the last AI message for any tool calls. If a tool requires authorization, the flow transitions to authorization. Otherwise, it goes straight to tool execution or ends if no tools are called. +- authorize: Prompts the user to authorize any required tools, blocking until authorization is completed successfully or fails. + + + + +### Build and compile your LangGraph workflow + +Use StateGraph to assemble the nodes and edges, then compile the graph with a MemorySaver. + + + +```python +if __name__ == "__main__": + # Build the workflow graph using StateGraph + workflow = StateGraph(MessagesState) + + # Add nodes (steps) to the graph + workflow.add_node("agent", call_agent) + workflow.add_node("tools", tool_node) + workflow.add_node("authorization", authorize) + + # Define the edges and control flow between nodes + workflow.add_edge(START, "agent") + workflow.add_conditional_edges("agent", should_continue, ["authorization", "tools", END]) + workflow.add_edge("authorization", "tools") + workflow.add_edge("tools", "agent") + + # Set up memory for checkpointing the state + memory = MemorySaver() + + # Compile the graph with the checkpointer + graph = workflow.compile(checkpointer=memory) +``` + + +```javascript +// Build the workflow graph +const workflow = new StateGraph(MessagesAnnotation) + .addNode("agent", callAgent) + .addNode("tools", toolNode) + .addNode("authorization", authorize) + .addEdge("__start__", "agent") + .addConditionalEdges("agent", shouldContinue, [ + "authorization", + "tools", + "__end__", + ]) + .addEdge("authorization", "tools") + .addEdge("tools", "agent"); + +// Compile the graph +const graph = workflow.compile(); +``` + + + +### Provide inputs and run the graph + +Finally, define user-supplied messages, authorization config, and stream the outputs. The graph will pause for any required tool authorization. + + + +```python +# Define the input messages from the user +inputs = { + "messages": [ + { + "role": "user", + "content": "Check and see if I have any emails in my inbox", + } + ], +} + +# Configuration with thread and user IDs for authorization purposes +config = {"configurable": {"thread_id": "4", "user_id": "{arcade_user_id}"}} + +# Run the graph and stream the outputs +for chunk in graph.stream(inputs, config=config, stream_mode="values"): + # Pretty-print the last message in the chunk + chunk["messages"][-1].pretty_print() +``` + + +```javascript +const inputs = { + messages: [ + { + role: "user", + content: "Check and see if I have any important emails in my inbox", + }, + ], +}; +// Run the graph and stream the outputs +const stream = await graph.stream(inputs, { streamMode: "values" }); +for await (const chunk of stream) { + // Print the last message in the chunk + console.log(chunk.messages[chunk.messages.length - 1].content); +} +``` + + + +In this example: + +- The user prompts the agent to check emails. +- The message triggers a potential need for the "Gmail" MCP Server. +- If authorization is required, the code prints a URL and waits until you permit the tool call. + + + +## Next steps + +- Experiment with more Arcade MCP Servers for expanded capabilities. +- Explore advanced authorization logic, such as multi-user or role-based checks. +- Integrate additional nodes to handle more complex flows or multi-step tasks in your LangGraph. + +By combining Arcade's authorization features with stateful management in LangGraph, you can build AI-driven workflows that respect user permissions at every step. Have fun exploring Arcade! diff --git a/app/en/guides/agent-frameworks/mastra/_meta.tsx b/app/en/get-started/agent-frameworks/mastra/_meta.tsx similarity index 100% rename from app/en/guides/agent-frameworks/mastra/_meta.tsx rename to app/en/get-started/agent-frameworks/mastra/_meta.tsx diff --git a/app/en/guides/agent-frameworks/mastra/overview/page.mdx b/app/en/get-started/agent-frameworks/mastra/overview/page.mdx similarity index 87% rename from app/en/guides/agent-frameworks/mastra/overview/page.mdx rename to app/en/get-started/agent-frameworks/mastra/overview/page.mdx index 12ab8b334..53dd45c2a 100644 --- a/app/en/guides/agent-frameworks/mastra/overview/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/overview/page.mdx @@ -29,5 +29,5 @@ The integration works through three key mechanisms: ### Next Steps -- Learn how to [use Arcade tools](/guides/agent-frameworks/mastra/use-arcade-tools) in a Mastra agent -- Implement [user authentication handling](/guides/agent-frameworks/mastra/user-auth-interrupts) for tools in multi-user applications +- Learn how to [use Arcade tools](/get-started/agent-frameworks/mastra/use-arcade-tools) in a Mastra agent +- Implement [user authentication handling](/get-started/agent-frameworks/mastra/user-auth-interrupts) for tools in multi-user applications diff --git a/app/en/guides/agent-frameworks/mastra/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/mastra/use-arcade-tools/page.mdx similarity index 91% rename from app/en/guides/agent-frameworks/mastra/use-arcade-tools/page.mdx rename to app/en/get-started/agent-frameworks/mastra/use-arcade-tools/page.mdx index da44a2469..ff85034af 100644 --- a/app/en/guides/agent-frameworks/mastra/use-arcade-tools/page.mdx +++ b/app/en/get-started/agent-frameworks/mastra/use-arcade-tools/page.mdx @@ -158,4 +158,4 @@ for await (const chunk of stream.textStream) { -When running your agent for the first time with tools that require user consent (like Google or Github), the agent will return an authorization reponse (e.g., `{ authorization_required: true, url: '...', message: '...' }`). Your agent's instructions should guide it to present this URL to the user. After the user visits this URL and grants permissions, the tool can be used successfully. See the [Managing user authorization](/guides/agent-frameworks/mastra/user-auth-interrupts) guide for more details on handling authentication flows. +When running your agent for the first time with tools that require user consent (like Google or Github), the agent will return an authorization reponse (for example, `{ authorization_required: true, url: '...', message: '...' }`). Your agent's instructions should guide it to present this URL to the user. After the user visits this URL and grants permissions, the tool can be used successfully. See the [Managing user authorization](/get-started/agent-frameworks/mastra/user-auth-interrupts) guide for more details on handling authentication flows. diff --git a/app/en/guides/agent-frameworks/mastra/user-auth-interrupts/page.mdx b/app/en/get-started/agent-frameworks/mastra/user-auth-interrupts/page.mdx similarity index 100% rename from app/en/guides/agent-frameworks/mastra/user-auth-interrupts/page.mdx rename to app/en/get-started/agent-frameworks/mastra/user-auth-interrupts/page.mdx diff --git a/app/en/guides/agent-frameworks/openai-agents/_meta.tsx b/app/en/get-started/agent-frameworks/openai-agents/_meta.tsx similarity index 66% rename from app/en/guides/agent-frameworks/openai-agents/_meta.tsx rename to app/en/get-started/agent-frameworks/openai-agents/_meta.tsx index cdb53979a..734c1f463 100644 --- a/app/en/guides/agent-frameworks/openai-agents/_meta.tsx +++ b/app/en/get-started/agent-frameworks/openai-agents/_meta.tsx @@ -1,5 +1,6 @@ export default { overview: "Overview", + "use-arcade-with-openai-agents": "Setup Arcade with OpenAI Agents SDK", "use-arcade-tools": "Using Arcade tools", "user-auth-interrupts": "Managing user authorization", }; diff --git a/app/en/guides/agent-frameworks/openai-agents/overview/page.mdx b/app/en/get-started/agent-frameworks/openai-agents/overview/page.mdx similarity index 95% rename from app/en/guides/agent-frameworks/openai-agents/overview/page.mdx rename to app/en/get-started/agent-frameworks/openai-agents/overview/page.mdx index 73e6b5384..c992e9209 100644 --- a/app/en/guides/agent-frameworks/openai-agents/overview/page.mdx +++ b/app/en/get-started/agent-frameworks/openai-agents/overview/page.mdx @@ -177,8 +177,8 @@ For a full list of available MCP Servers, visit the [Arcade MCP Servers](/resour Ready to start building with Arcade and OpenAI Agents? Check out these guides: -- [Using Arcade tools](/guides/agent-frameworks/openai-agents/use-arcade-tools) - Learn the basics of using Arcade tools with OpenAI Agents -- [Managing user authorization](/guides/agent-frameworks/openai-agents/user-auth-interrupts) - Handle tool authorization efficiently +- [Using Arcade tools](/get-started/agent-frameworks/openai-agents/use-arcade-tools) - Learn the basics of using Arcade tools with OpenAI Agents +- [Managing user authorization](/get-started/agent-frameworks/openai-agents/user-auth-interrupts) - Handle tool authorization efficiently - [Creating custom tools](/guides/create-tools/tool-basics/build-mcp-server) - Build your own tools with the Arcade Tool SDK Enjoy exploring Arcade and building powerful AI-enabled applications! diff --git a/app/en/guides/agent-frameworks/openai-agents/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/openai-agents/use-arcade-tools/page.mdx similarity index 100% rename from app/en/guides/agent-frameworks/openai-agents/use-arcade-tools/page.mdx rename to app/en/get-started/agent-frameworks/openai-agents/use-arcade-tools/page.mdx 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 new file mode 100644 index 000000000..37d09a785 --- /dev/null +++ b/app/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents/page.mdx @@ -0,0 +1,560 @@ +--- +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 + +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. + + + + +Learn how to integrate Arcade tools using OpenAI Agents primitives. You will implement a CLI agent that can user 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 venv +source .venv/bin/activate +``` + +Install the necessary packages: + +```bash +uv pip install 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 several 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, used to run 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 are used in the rest of the code to customize the agent and manage the tools. 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 can be handled in the agentic loop 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 is how you implement "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 is skipped 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 is 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 to use. 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. + +The loop is interrupted when the agent's response contains a tool call, and the tool call is handled by 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 + +- Arcade tools can be integrated 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, 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 + +```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()) +``` diff --git a/app/en/guides/agent-frameworks/openai-agents/user-auth-interrupts/page.mdx b/app/en/get-started/agent-frameworks/openai-agents/user-auth-interrupts/page.mdx similarity index 100% rename from app/en/guides/agent-frameworks/openai-agents/user-auth-interrupts/page.mdx rename to app/en/get-started/agent-frameworks/openai-agents/user-auth-interrupts/page.mdx diff --git a/app/en/guides/agent-frameworks/page.mdx b/app/en/get-started/agent-frameworks/page.mdx similarity index 99% rename from app/en/guides/agent-frameworks/page.mdx rename to app/en/get-started/agent-frameworks/page.mdx index 12716a8a2..879262657 100644 --- a/app/en/guides/agent-frameworks/page.mdx +++ b/app/en/get-started/agent-frameworks/page.mdx @@ -1,4 +1,3 @@ -```mdx import { PlatformCard } from "@/app/_components/platform-card"; import { Tabs } from "nextra/components"; @@ -72,4 +71,3 @@ These guides are for developers building AI applications who need to connect Arc
-``` diff --git a/app/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python/page.mdx b/app/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python/page.mdx new file mode 100644 index 000000000..7739ac310 --- /dev/null +++ b/app/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python/page.mdx @@ -0,0 +1,471 @@ +--- +title: "Connect Arcade to LLM (Python)" +description: "Learn how to connect Arcade to your LLM in Python" +--- + +import { Steps, Tabs, Callout } from "nextra/components"; +import { SignupLink } from "@/app/_components/analytics"; + +# Connect Arcade to your LLM + +Arcade tools work alongside an LLM. To make that work, you need a small piece of glue code called a "harness." The harness orchestrates the back-and-forth between the user, the model, and the tools. In this guide, you'll build one so you can wire Arcade into your LLM-powered app. + + + + +Integrate Arcade's tool-calling capabilities into an application that uses an LLM in Python. + + + + + +- An Arcade account +- An [Arcade API key](/get-started/setup/api-keys) +- An [OpenRouter API key](https://openrouter.ai/docs/guides/overview/auth/provisioning-api-keys) +- The [`uv` package manager](https://docs.astral.sh/uv/getting-started/installation/) + + + + + +- Setup an agentic loop +- Add Arcade tools to your agentic loop +- Implement a multi-turn conversation loop + + + + + + +### Create a new project and install the dependencies + +In your terminal, run the following command to create a new `uv` project + +```bash +mkdir arcade-llm-example +cd arcade-llm-example +uv init +``` + +Create a new virtual environment and activate it: + +```bash +uv venv +source .venv/bin/activate +``` + +Install the dependencies: + +```bash +uv add arcadepy openai python-dotenv +``` + +Your directory should now look like this: + +```bash +arcade-llm-example/ +├── .git/ +├── .gitignore +├── python-version +├── .venv/ +├── main.py +├── pyproject.toml +├── main.py +├── README.md +└── uv.lock +``` + +### Instantiate and use the clients + +Create a new file called `.env` and add your Arcade API key, as well as your OpenAI API key: + +```text filename=".env" +ARCADE_API_KEY=YOUR_ARCADE_API_KEY +ARCADE_USER_ID=YOUR_ARCADE_USER_ID +OPENROUTER_API_KEY=YOUR_OPENROUTER_API_KEY +OPENROUTER_MODEL=YOUR_OPENROUTER_MODEL +``` + + + The `ARCADE_USER_ID` is the email address you used to sign up for Arcade. When + your app is ready for production, you can set this dynamically based on your + app's auth system. Learn more about how to achieve secure auth in production + [here](/guides/user-facing-agents/secure-auth-production). + + + + In this example, you're using OpenRouter to access the model, as it makes it + straightforward to use any model from multiple providers with a single API. + +OpenRouter is compliant with the OpenAI API specification, so you can use it +with any OpenAI-compatible library. + +If you don't know which model to use, try one of these: + +- `anthropic/claude-haiku-4.5` +- `deepseek/deepseek-v3.2` +- `google/gemini-3-flash-preview` +- `google/gemini-2.5-flash-lite` +- `openai/gpt-4o-mini` + + + +Open the `main.py` file in your editor of choice, and replace the contents with the following: + +```python filename="main.py" +from arcadepy import Arcade +from openai import OpenAI +from dotenv import load_dotenv +import json +import os + +load_dotenv() + +arcade_client = Arcade() +arcade_user_id = os.getenv("ARCADE_USER_ID") +llm_client = OpenAI( + api_key=os.getenv("OPENROUTER_API_KEY"), + base_url="https://openrouter.ai/api/v1" +) + +``` + +### Select and retrieve the tools from Arcade + +In this example, you're implementing a multi-tool agent that can retrieve and send emails, as well as send messages to Slack. While a harness can expose a broad catalog of tools to the LLM, it's best to limit that set to what's relevant for the task to keep the model efficient. + +```python filename="main.py" +# Define the tools for the agent to use +tool_catalog = [ + "Gmail.ListEmails", + "Gmail.SendEmail", + "Slack.SendMessage", + "Slack.WhoAmI" +] + +# Get the tool definitions from the Arcade API +tool_definitions = [] +for tool in tool_catalog: + tool_definitions.append(arcade_client.tools.formatted.get(name=tool, format="openai")) +``` + +### Write a helper function that handles tool authorization and execution + +The model can use any tool you give it, and some tools require permission before they work. When this happens, you can either involve the model in the permission step or handle it behind the scenes and continue as if the tool were already authorized. In this guide, authorization happens outside the model so it can act as if the tool is already available. It's like ordering a coffee: after you place your order, the barista handles payment behind the counter instead of explaining every step of card verification and receipts. The customer (and the model) gets the result without having to think about any of the intermediate steps. + +```python filename="main.py" +# Helper function to authorize and run any tool +def authorize_and_run_tool(tool_name: str, input: str): + # Start the authorization process + auth_response = arcade_client.tools.authorize( + tool_name=tool_name, + user_id=arcade_user_id, + ) + + # If the authorization is not completed, print the authorization URL and wait for the user to authorize the app. + # Tools that do not require authorization will have the status "completed" already. + if auth_response.status != "completed": + print(f"Click this link to authorize {tool_name}: {auth_response.url}. The process will continue once you have authorized the app.") + arcade_client.auth.wait_for_completion(auth_response.id) + + # Parse the input + input_json = json.loads(input) + + # Run the tool + result = arcade_client.tools.execute( + tool_name=tool_name, + input=input_json, + user_id=arcade_user_id, + ) + + # Return the tool output to the caller as a JSON string + return json.dumps(result.output.value) +``` + +This helper function adapts to any tool in the catalog and will make sure that the authorization requirements are met before executing the tool. For more complex agentic patterns, this is generally the best place to handle interruptions that may require user interaction, such as when the tool requires a user to approve a request, or to provide additional context. + +### Write a helper function that handles the LLM's invocation + +There are many orchestration patterns that can be used to handle the LLM invocation. A common pattern is a ReAct architecture, where the user prompt will result in a loop of messages between the LLM and the tools, until the LLM provides a final response (no tool calls). This is the pattern we will implement in this example. + +To avoid the risk of infinite loops, limit the number of turns (in this case, a maximum of 5). This is a parameter that you can tune to your needs. Set it to a value that is high enough to allow the LLM to complete its task but low enough to prevent infinite loops. + +```python filename="main.py" +def invoke_llm( + history: list[dict], + model: str = "google/gemini-2.5-flash", + max_turns: int = 5, + tools: list[dict] = None, + tool_choice: str = "auto", +) -> list[dict]: + """ + Multi-turn LLM invocation that processes the conversation until + the assistant provides a final response (no tool calls). + + Returns the updated conversation history. + """ + turns = 0 + + while turns < max_turns: + turns += 1 + + response = llm_client.chat.completions.create( + model=model, + messages=history, + tools=tools, + tool_choice=tool_choice, + ) + + assistant_message = response.choices[0].message + + if assistant_message.tool_calls: + for tool_call in assistant_message.tool_calls: + tool_name = tool_call.function.name + tool_args = tool_call.function.arguments + print(f"🛠️ Harness: Calling {tool_name} with input {tool_args}") + tool_result = authorize_and_run_tool(tool_name, tool_args) + print(f"🛠️ Harness: Tool call {tool_name} completed") + history.append({ + "role": "tool", + "tool_call_id": tool_call.id, + "content": tool_result, + }) + + continue + + else: + history.append({ + "role": "assistant", + "content": assistant_message.content, + }) + + break + + return history +``` + +These two helper functions form the core of your agentic loop. Notice that authorization is handled outside the agentic context, and the tool execution is passed back to the LLM in every case. Depending on your needs, you may want to handle tool orchestration within the harness and pass only the final result of multiple tool calls to the LLM. + +### Write the main agentic loop + +Now that you've written the helper functions, write an agentic loop that interacts with the user. The core pieces of this loop are: + +1. Initialize the conversation history with the system prompt +2. Get the user input and add it to the conversation history +3. Invoke the LLM with the conversation history, tools, and tool choice +4. Repeat from step 2 until the user decides to stop the conversation + +```python filename="main.py" +def chat(): + """Interactive multi-turn chat session.""" + print("Chat started. Type 'quit' or 'exit' to end the session.\n") + + # Initialize the conversation history with the system prompt + history: list[dict] = [ + {"role": "system", "content": "You are a helpful assistant."} + ] + + while True: + try: + user_input = input("😎 You: ").strip() + except (EOFError, KeyboardInterrupt): + print("\nGoodbye!") + break + + if not user_input: + continue + + if user_input.lower() in ("quit", "exit"): + print("Goodbye!") + break + + # Add user message to history + history.append({"role": "user", "content": user_input}) + + # Get LLM response + history = invoke_llm( + history, tools=tool_definitions) + + # Print the latest assistant response + assistant_response = history[-1]["content"] + print(f"\n🤖 Assistant: {assistant_response}\n") + + +if __name__ == "__main__": + chat() +``` + +### Run the code + +It's time to run the code and see it in action. Run the following command to start the chat: + +```bash +uv run main.py +``` + +With the selection of tools above, you should be able to get the agent to effectively complete the following prompts: + +- "Please send a message to the #general channel on Slack greeting everyone with a haiku about agents." +- "Please write a poem about multi-tool orchestration and send it to the #general channel on Slack, also send it to me in an email." +- "Please summarize my latest 5 emails, then send me a DM on Slack with the summary." + + + +## Next Steps + +- Learn more about using Arcade with a [framework](/get-started/agent-frameworks) or [MCP client](/get-started/mcp-clients). +- Learn more about how to [build your own MCP Servers](/guides/create-tools/tool-basics/build-mcp-server). + +## Example code + +
+**main.py** (full file) +```python filename="main.py" +from arcadepy import Arcade +from dotenv import load_dotenv +from openai import OpenAI +import json +import os + +load_dotenv() + +arcade_client = Arcade() +arcade_user_id = os.getenv("ARCADE_USER_ID") +llm_client = OpenAI( + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv("OPENROUTER_API_KEY"), +) + +# Define the tools to use in the agent +tool_catalog = [ + "Gmail.ListEmails", + "Gmail.SendEmail", + "Slack.SendMessage", + "Slack.WhoAmI" +] + +# Get the tool definitions from the Arcade API to expose them to the LLM +tool_definitions = [] +for tool in tool_catalog: + tool_definitions.append(arcade_client.tools.formatted.get(name=tool, format="openai")) + + +# Helper function to authorize and run any tool +def authorize_and_run_tool(tool_name: str, input: str): + # Start the authorization process + auth_response = arcade_client.tools.authorize( + tool_name=tool_name, + user_id=arcade_user_id, + ) + + # If the authorization is not completed, print the authorization URL and wait for the user to authorize the app. + # Tools that do not require authorization will have the status "completed" already. + if auth_response.status != "completed": + print(f"Click this link to authorize {tool_name}: {auth_response.url}. The process will continue once you have authorized the app.") + arcade_client.auth.wait_for_completion(auth_response.id) + + # Parse the input + input_json = json.loads(input) + + # Run the tool + result = arcade_client.tools.execute( + tool_name=tool_name, + input=input_json, + user_id=arcade_user_id, + ) + + # Return the tool output to the caller as a JSON string + return json.dumps(result.output.value) + + +def invoke_llm( + history: list[dict], + model: str = "google/gemini-2.5-flash", + max_turns: int = 5, + tools: list[dict] = None, + tool_choice: str = "auto", +) -> list[dict]: + """ + Multi-turn LLM invocation that processes the conversation until + the assistant provides a final response (no tool calls). + + Returns the updated conversation history. + """ + turns = 0 + + while turns < max_turns: + turns += 1 + + response = llm_client.chat.completions.create( + model=model, + messages=history, + tools=tools, + tool_choice=tool_choice, + ) + + assistant_message = response.choices[0].message + + if assistant_message.tool_calls: + for tool_call in assistant_message.tool_calls: + tool_name = tool_call.function.name + tool_args = tool_call.function.arguments + print(f"🛠️ Harness: Calling {tool_name} with input {tool_args}") + tool_result = authorize_and_run_tool(tool_name, tool_args) + print(f"🛠️ Harness: Tool call {tool_name} completed") + history.append({ + "role": "tool", + "tool_call_id": tool_call.id, + "content": tool_result, + }) + + continue + + else: + history.append({ + "role": "assistant", + "content": assistant_message.content, + }) + + break + + return history + + +def chat(): + """Interactive multi-turn chat session.""" + print("Chat started. Type 'quit' or 'exit' to end the session.\n") + + history: list[dict] = [ + {"role": "system", "content": "You are a helpful assistant."} + ] + + while True: + try: + user_input = input("😎 You: ").strip() + except (EOFError, KeyboardInterrupt): + print("\nGoodbye!") + break + + if not user_input: + continue + + if user_input.lower() in ("quit", "exit"): + print("Goodbye!") + break + + # Add user message to history + history.append({"role": "user", "content": user_input}) + + # Get LLM response + history = invoke_llm( + history, tools=tool_definitions) + + # Print the latest assistant response + assistant_response = history[-1]["content"] + print(f"\n🤖 Assistant: {assistant_response}\n") + + +if __name__ == "__main__": + chat() +``` +
diff --git a/app/en/guides/agent-frameworks/vercelai/page.mdx b/app/en/get-started/agent-frameworks/vercelai/page.mdx similarity index 100% rename from app/en/guides/agent-frameworks/vercelai/page.mdx rename to app/en/get-started/agent-frameworks/vercelai/page.mdx diff --git a/app/en/guides/tool-calling/mcp-clients/_meta.tsx b/app/en/get-started/mcp-clients/_meta.tsx similarity index 100% rename from app/en/guides/tool-calling/mcp-clients/_meta.tsx rename to app/en/get-started/mcp-clients/_meta.tsx diff --git a/app/en/guides/tool-calling/mcp-clients/claude-desktop/page.mdx b/app/en/get-started/mcp-clients/claude-desktop/page.mdx similarity index 100% rename from app/en/guides/tool-calling/mcp-clients/claude-desktop/page.mdx rename to app/en/get-started/mcp-clients/claude-desktop/page.mdx diff --git a/app/en/guides/tool-calling/mcp-clients/copilot-studio/page.mdx b/app/en/get-started/mcp-clients/copilot-studio/page.mdx similarity index 100% rename from app/en/guides/tool-calling/mcp-clients/copilot-studio/page.mdx rename to app/en/get-started/mcp-clients/copilot-studio/page.mdx diff --git a/app/en/guides/tool-calling/mcp-clients/cursor/page.mdx b/app/en/get-started/mcp-clients/cursor/page.mdx similarity index 100% rename from app/en/guides/tool-calling/mcp-clients/cursor/page.mdx rename to app/en/get-started/mcp-clients/cursor/page.mdx diff --git a/app/en/guides/tool-calling/mcp-clients/mcp-client-grid.tsx b/app/en/get-started/mcp-clients/mcp-client-grid.tsx similarity index 100% rename from app/en/guides/tool-calling/mcp-clients/mcp-client-grid.tsx rename to app/en/get-started/mcp-clients/mcp-client-grid.tsx diff --git a/app/en/guides/tool-calling/mcp-clients/page.mdx b/app/en/get-started/mcp-clients/page.mdx similarity index 100% rename from app/en/guides/tool-calling/mcp-clients/page.mdx rename to app/en/get-started/mcp-clients/page.mdx diff --git a/app/en/guides/tool-calling/mcp-clients/visual-studio-code/page.mdx b/app/en/get-started/mcp-clients/visual-studio-code/page.mdx similarity index 100% rename from app/en/guides/tool-calling/mcp-clients/visual-studio-code/page.mdx rename to app/en/get-started/mcp-clients/visual-studio-code/page.mdx diff --git a/app/en/get-started/quickstarts/call-tool-agent/page.mdx b/app/en/get-started/quickstarts/call-tool-agent/page.mdx index a4cbbc17c..c9ffd61d6 100644 --- a/app/en/get-started/quickstarts/call-tool-agent/page.mdx +++ b/app/en/get-started/quickstarts/call-tool-agent/page.mdx @@ -40,16 +40,33 @@ Install and use the Arcade client to call Arcade Hosted Tools. + ### Install the Arcade client - In your terminal, run the following command to install the Python client package `arcadepy`: + In your terminal, run the following command to create a new `uv` project: -```bash -uv pip install arcadepy -``` + ```bash + mkdir arcade-quickstart + cd arcade-quickstart + uv init + ``` + + Then, run the following command to create and activate a new virtual environment, isolating the project dependencies from your system: + + + ```bash + uv venv + source .venv/bin/activate + ``` + + Then, run the following command to install the Python client package `arcadepy`: + + ```bash + uv add arcadepy + ``` @@ -70,9 +87,9 @@ bun install @arcadeai/arcadejs -Create a new script called `example.py`: +Open the `main.py` file and replace the content with the following: -```python filename="example.py" +```python filename="main.py" from arcadepy import Arcade # You can also set the `ARCADE_API_KEY` environment variable instead of passing it as a parameter. @@ -114,7 +131,7 @@ This helper function will check if a tool requires authorization and if so, it w -```python filename="example.py" +```python filename="main.py" # Helper function to authorize and run any tool def authorize_and_run_tool(tool_name, input, user_id): # Start the authorization process @@ -126,7 +143,7 @@ def authorize_and_run_tool(tool_name, input, user_id): # If the authorization is not completed, print the authorization URL and wait for the user to authorize the app. # Tools that do not require authorization will have the status "completed" already. if auth_response.status != "completed": - print(f"Click this link to authorize {tool_name}: {auth_response.url}. The process will continue once you have authorized the app.") + print(f"Click this link to authorize {tool_name}:\n{auth_response.url}.\nThe process will continue once you have authorized the app.") client.auth.wait_for_completion(auth_response.id) # Run the tool @@ -157,7 +174,7 @@ async function authorize_and_run_tool({ // If the authorization is not completed, print the authorization URL and wait for the user to authorize the app. Tools that do not require authorization will have the status "completed" already. if (authResponse.status !== "completed") { console.log( - `Click this link to authorize ${tool_name}: \`${authResponse.url}\`. The process will continue once you have authorized the app.` + `Click this link to authorize ${tool_name}:\n${authResponse.url}.\nThe process will continue once you have authorized the app.` ); // Wait for the user to authorize the app await client.auth.waitForCompletion(authResponse.id); @@ -188,7 +205,7 @@ In this example workflow, we: -```python filename="example.py" +```python filename="main.py" # This tool does not require authorization, so it will return the results # without prompting the user to authorize the tool call. response_search = authorize_and_run_tool( @@ -311,10 +328,11 @@ console.log(respose_send_email.output?.value); ```bash - uv run example.py + uv run main.py ``` + ```text - Success! Check your email at mateo@arcade.dev + Success! Check your email at {arcade_user_id} You just chained 3 tools together: 1. Searched Google News for stories about MCP URL mode elicitation @@ -333,7 +351,7 @@ console.log(respose_send_email.output?.value); ``` ```text - Success! Check your email at mateo@arcade.dev + Success! Check your email at {arcade_user_id} You just chained 3 tools together: 1. Searched Google News for stories about MCP URL mode elicitation @@ -356,7 +374,7 @@ console.log(respose_send_email.output?.value); ## Next Steps -In this simple example, we call the tool methods directly. In your real applications and agents, you'll likely be letting the LLM decide which tools to call. Learn more about using Arcade with Frameworks in the [Frameworks](/guides/agent-frameworks) section, or [how to build your own tools](/guides/create-tools/tool-basics/build-mcp-server). +In this simple example, we call the tool methods directly. In your real applications and agents, you'll likely be letting the LLM decide which tools to call. Learn more about using Arcade with Frameworks in the [Frameworks](/get-started/agent-frameworks) section, or [how to build your own tools](/guides/create-tools/tool-basics/build-mcp-server). ## Example Code @@ -364,6 +382,8 @@ In this simple example, we call the tool methods directly. In your real applicat +
+**main.py** (full file) ```python filename="example.py" from arcadepy import Arcade @@ -386,7 +406,7 @@ def authorize_and_run_tool(tool_name, input, user_id): # If the authorization is not completed, print the authorization URL and wait for the user to authorize the app. # Tools that do not require authorization will have the status "completed" already. if auth_response.status != "completed": - print(f"Click this link to authorize {tool_name}: {auth_response.url}. The process will continue once you have authorized the app.") + print(f"Click this link to authorize {tool_name}:\n{auth_response.url}.\nThe process will continue once you have authorized the app.") client.auth.wait_for_completion(auth_response.id) # Run the tool @@ -443,10 +463,13 @@ response_send_email = authorize_and_run_tool( print(f"Success! Check your email at {user_id}\n\nYou just chained 3 tools together:\n 1. Searched Google News for stories about MCP URL mode elicitation\n 2. Created a Google Doc with the results\n 3. Sent yourself an email with the document link\n\nEmail metadata:") print(response_send_email.output.value) ``` +
+
+**example.ts** (full file) ```typescript filename="example.ts" import Arcade from "@arcadeai/arcadejs"; @@ -481,7 +504,7 @@ async function authorize_and_run_tool({ // If the authorization is not completed, print the authorization URL and wait for the user to authorize the app. Tools that do not require authorization will have the status "completed" already. if (authResponse.status !== "completed") { console.log( - `Click this link to authorize ${tool_name}: \`${authResponse.url}\`. The process will continue once you have authorized the app.` + `Click this link to authorize ${tool_name}:\n${authResponse.url}.\nThe process will continue once you have authorized the app.` ); // Wait for the user to authorize the app await client.auth.waitForCompletion(authResponse.id); @@ -550,6 +573,7 @@ console.log( ); console.log(respose_send_email.output?.value); ``` +
diff --git a/app/en/get-started/quickstarts/call-tool-client/page.mdx b/app/en/get-started/quickstarts/call-tool-client/page.mdx index 449ebea24..67d2b2490 100644 --- a/app/en/get-started/quickstarts/call-tool-client/page.mdx +++ b/app/en/get-started/quickstarts/call-tool-client/page.mdx @@ -6,6 +6,7 @@ description: "Learn how to call a tool in your IDE/MCP Client" import { Steps, Tabs, Callout } from "nextra/components"; import { SignupLink } from "@/app/_components/analytics"; import Image from "next/image"; +import { MCPClientGrid } from "@/app/en/get-started/mcp-clients/mcp-client-grid"; export const IMAGE_SCALE_FACTOR = 2; export const CREATE_MCP_GATEWAY_DARK_WIDTH = 921; @@ -72,7 +73,10 @@ Give your MCP gateway: - A name - A description -- A slug (optional: recommended for shareability, but auto-generates if left blank) +- A slug (this is recommended so it's easy to remember and share, but will be generated if left blank) +- Select the Authentication mode for the MCP Gateway + - **Arcade Auth**: To access the MCP Gateway, you'll need to authenticate with your Arcade account in an OAuth flow on a browser. For security, the token is only valid for a short time and your MCP client will need to refresh it periodically. + - **Arcade Headers**: To access the MCP Gateway, you'll need to authenticate with your Arcade account by passing an Arcade API key in the `Authorization` header and the user ID in the `Arcade-User-ID` header. Use this authentication mode for MCP clients that don't support browser authentication or token refresh. ### Select the tools you want to include in the gateway @@ -128,40 +132,9 @@ Get the URL of your MCP Gateway by clicking the "Copy URL" button in the MCP Gat height={MCP_GATEWAY_URL_DARK_HEIGHT / IMAGE_SCALE_FACTOR} /> - - - - 1. Open the command palette (Mac: Cmd + Shift + p / Windows: Ctrl + Shift + p) and select **Open MCP Settings** - 1. Click on the **New MCP Server** button - - Cursor will open the MCP settings file, and you can add a new entry to the `mcpServers` object: - - ```json - { - "mcpServers": { - "mcp-arcade": { - "url": "https://api.arcade.dev/mcp/", - "headers": { - "Authorization": "Bearer {arcade_api_key}", - "Arcade-User-ID": "{arcade_user_id}" - } - } - } - } - ``` - - - - - 1. Open the command palette (Mac: Cmd + Shift + p / Windows: Ctrl + Shift + p) and select **MCP: Add Server...** - 1. Choose **HTTP** - 1. Paste the URL of your MCP Gateway. - 1. Give your MCP server a name, like `mcp-arcade` - 1. Visual Studio Code will update your `mcp.json` file. - 1. Either in the `mcp.json` file or in the "Extensions" > "MCP Servers - Installed" pane, click the "Start" button next to your MCP server and follow the prompts to authenticate. - - - +Select the MCP client you want to use to read the instructions to connect to the MCP Gateway: + + ### Try it out @@ -176,6 +149,6 @@ As you interact with the agent, it will call the tools from the MCP Gateway. You - Learn more about [MCP Gateways](/guides/create-tools/mcp-gateways). - Learn how to use MCP Gateways with: - - [Cursor](/guides/tool-calling/mcp-clients/cursor) - - [Visual Studio Code](/guides/tool-calling/mcp-clients/visual-studio-code) + - [Cursor](/get-started/mcp-clients/cursor) + - [Visual Studio Code](/get-started/mcp-clients/visual-studio-code) - Build your own MCP servers with [arcade-mcp](/get-started/quickstarts/mcp-server-quickstart). diff --git a/app/en/guides/_meta.tsx b/app/en/guides/_meta.tsx index bf2e1073b..3c8f69763 100644 --- a/app/en/guides/_meta.tsx +++ b/app/en/guides/_meta.tsx @@ -7,9 +7,6 @@ export const meta: MetaRecord = { "create-tools": { title: "Create tools", }, - "agent-frameworks": { - title: "Agent frameworks", - }, "user-facing-agents": { title: "User-facing agents", }, diff --git a/app/en/guides/create-tools/mcp-gateways/page.mdx b/app/en/guides/create-tools/mcp-gateways/page.mdx index c0d2288da..303c9befe 100644 --- a/app/en/guides/create-tools/mcp-gateways/page.mdx +++ b/app/en/guides/create-tools/mcp-gateways/page.mdx @@ -51,6 +51,6 @@ The options available when configuring an MCP Gateway are: Any MCP client that supports the Streamable HTTP transport can use an Arcade MCP Gateway. To use an Arcade MCP Gateway, you can use the `https://api.arcade.dev/mcp/` URL in your MCP client. Learn how to use MCP Gateways with: -- [Cursor](/guides/tool-calling/mcp-clients/cursor) -- [Claude Desktop](/guides/tool-calling/mcp-clients/claude-desktop) -- [Visual Studio Code](/guides/tool-calling/mcp-clients/visual-studio-code) +- [Cursor](/get-started/mcp-clients/cursor) +- [Claude Desktop](/get-started/mcp-clients/claude-desktop) +- [Visual Studio Code](/get-started/mcp-clients/visual-studio-code) diff --git a/app/en/guides/deployment-hosting/on-prem/page.mdx b/app/en/guides/deployment-hosting/on-prem/page.mdx index db008fbbd..1d46bbc71 100644 --- a/app/en/guides/deployment-hosting/on-prem/page.mdx +++ b/app/en/guides/deployment-hosting/on-prem/page.mdx @@ -284,9 +284,9 @@ You can now test your MCP Server by making requests using the Playground, or an 1. Use an app that supports MCP clients, like AI assistants and IDEs: - - [Visual Studio Code](/guides/tool-calling/mcp-clients/visual-studio-code) - - [Claude Desktop](/guides/tool-calling/mcp-clients/claude-desktop) - - [Cursor](/guides/tool-calling/mcp-clients/cursor) + - [Visual Studio Code](/get-started/mcp-clients/visual-studio-code) + - [Claude Desktop](/get-started/mcp-clients/claude-desktop) + - [Cursor](/get-started/mcp-clients/cursor) 1. Enable your MCP Server from the list of available MCP Servers 1. Verify that the response is correct and you see request logs in your MCP Server diff --git a/app/en/guides/tool-calling/_meta.tsx b/app/en/guides/tool-calling/_meta.tsx index bfef9282b..4afb60e1a 100644 --- a/app/en/guides/tool-calling/_meta.tsx +++ b/app/en/guides/tool-calling/_meta.tsx @@ -7,9 +7,6 @@ export const meta: MetaRecord = { "call-third-party-apis": { title: "Call third-party APIs", }, - "mcp-clients": { - title: "Connect to MCP clients", - }, "custom-apps": { title: "In custom applications", }, diff --git a/app/en/home/landing-page.tsx b/app/en/home/landing-page.tsx index c98712df5..a6cca53bb 100644 --- a/app/en/home/landing-page.tsx +++ b/app/en/home/landing-page.tsx @@ -345,7 +345,7 @@ export function LandingPage() {
@@ -544,7 +556,7 @@ Self-hosed Arcade developers cannot be grandfathered into the old (insecure) beh **Frameworks** -- `[feature - 🚀]` Support for OpenAI Agent SDK in Typescript ([docs](/guides/agent-frameworks/openai-agents/overview) and [example](https://github.com/ArcadeAI/arcade-ai/tree/main/examples/openai-agents-ts)) +- `[feature - 🚀]` Support for OpenAI Agent SDK in Typescript ([docs](/get-started/agent-frameworks/openai-agents/overview) and [example](https://github.com/ArcadeAI/arcade-ai/tree/main/examples/openai-agents-ts)) **Toolkits** diff --git a/app/en/resources/integrations/productivity/gmail/page.mdx b/app/en/resources/integrations/productivity/gmail/page.mdx index f20dc6303..5b7b3d9e2 100644 --- a/app/en/resources/integrations/productivity/gmail/page.mdx +++ b/app/en/resources/integrations/productivity/gmail/page.mdx @@ -272,7 +272,7 @@ Delete a draft email using the Gmail API. The `TrashEmail` tool is currently only available on a self-hosted instance of the Arcade Engine. To learn more about self-hosting, see the [self-hosting - documentation](http://localhost:3000/en/home/deployment/engine-configuration). + documentation](http://localhost:3000/en/guides/deployment-hosting/configure-engine).
diff --git a/next.config.ts b/next.config.ts index 9ec891629..9084e5925 100644 --- a/next.config.ts +++ b/next.config.ts @@ -23,42 +23,50 @@ const nextConfig: NextConfig = withLlmsTxt({ withNextra({ async redirects() { return [ + // Moved from guides to get-started + { + source: + "/:locale/guides/agent-frameworks/setup-arcade-with-your-llm-python", + destination: + "/:locale/get-started/agent-frameworks/setup-arcade-with-your-llm-python", + permanent: true, + }, // Old /home/* paths to new structure { source: "/:locale/home/langchain/use-arcade-tools", destination: - "/:locale/guides/agent-frameworks/langchain/use-arcade-with-langchain", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { source: "/:locale/guides/agent-frameworks/langchain/use-arcade-tools", destination: - "/:locale/guides/agent-frameworks/langchain/use-arcade-with-langchain", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { source: "/:locale/home/langchain/user-auth-interrupts", destination: - "/:locale/guides/agent-frameworks/langchain/use-arcade-with-langchain", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { source: "/:locale/guides/agent-frameworks/langchain/user-auth-interrupts", destination: - "/:locale/guides/agent-frameworks/langchain/use-arcade-with-langchain", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { source: "/:locale/home/oai-agents/user-auth-interrupts", destination: - "/:locale/guides/agent-frameworks/openai-agents/user-auth-interrupts", + "/:locale/get-started/agent-frameworks/openai-agents/user-auth-interrupts", permanent: true, }, { source: "/:locale/home/mastra/user-auth-interrupts", destination: - "/:locale/guides/agent-frameworks/mastra/user-auth-interrupts", + "/:locale/get-started/agent-frameworks/mastra/user-auth-interrupts", permanent: true, }, { @@ -73,7 +81,7 @@ const nextConfig: NextConfig = withLlmsTxt({ }, { source: "/:locale/home/agent-frameworks-overview", - destination: "/:locale/guides/agent-frameworks", + destination: "/:locale/get-started/agent-frameworks", permanent: true, }, { @@ -89,7 +97,7 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/guides/agent-frameworks/vercelai/using-arcade-tools", - destination: "/:locale/guides/agent-frameworks/vercelai", + destination: "/:locale/get-started/agent-frameworks/vercelai", permanent: true, }, { @@ -207,13 +215,13 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/home/crewai/custom-auth-flow", destination: - "/:locale/guides/agent-frameworks/crewai/custom-auth-flow", + "/:locale/get-started/agent-frameworks/crewai/custom-auth-flow", permanent: true, }, { source: "/:locale/home/crewai/use-arcade-tools", destination: - "/:locale/guides/agent-frameworks/crewai/use-arcade-tools", + "/:locale/get-started/agent-frameworks/crewai/use-arcade-tools", permanent: true, }, { @@ -267,7 +275,7 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/home/google-adk/use-arcade-tools", destination: - "/:locale/guides/agent-frameworks/google-adk/use-arcade-tools", + "/:locale/get-started/agent-frameworks/google-adk/use-arcade-tools", permanent: true, }, { @@ -278,30 +286,28 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/home/langchain/auth-langchain-tools", destination: - "/:locale/guides/agent-frameworks/langchain/auth-langchain-tools", + "/:locale/get-started/agent-frameworks/langchain/auth-langchain-tools", permanent: true, }, { source: "/:locale/home/mastra/use-arcade-tools", destination: - "/:locale/guides/agent-frameworks/mastra/use-arcade-tools", + "/:locale/get-started/agent-frameworks/mastra/use-arcade-tools", permanent: true, }, { source: "/:locale/home/mcp-clients/claude-desktop", - destination: - "/:locale/guides/tool-calling/mcp-clients/claude-desktop", + destination: "/:locale/get-started/mcp-clients/claude-desktop", permanent: true, }, { source: "/:locale/home/mcp-clients/cursor", - destination: "/:locale/guides/tool-calling/mcp-clients/cursor", + destination: "/:locale/get-started/mcp-clients/cursor", permanent: true, }, { source: "/:locale/home/mcp-clients/visual-studio-code", - destination: - "/:locale/guides/tool-calling/mcp-clients/visual-studio-code", + destination: "/:locale/get-started/mcp-clients/visual-studio-code", permanent: true, }, { @@ -317,7 +323,7 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/home/oai-agents/use-arcade-tools", destination: - "/:locale/guides/agent-frameworks/openai-agents/use-arcade-tools", + "/:locale/get-started/agent-frameworks/openai-agents/use-arcade-tools", permanent: true, }, { @@ -364,8 +370,7 @@ const nextConfig: NextConfig = withLlmsTxt({ }, { source: "/:locale/home/vercelai/using-arcade-tools", - destination: - "/:locale/guides/agent-frameworks/vercelai/using-arcade-tools", + destination: "/:locale/get-started/agent-frameworks/vercelai", permanent: true, }, // MCP servers to integrations @@ -422,7 +427,7 @@ const nextConfig: NextConfig = withLlmsTxt({ }, { source: "/:locale/guides/tool-calling/mcp-client/:client", - destination: "/:locale/guides/tool-calling/mcp-clients/:client", + destination: "/:locale/get-started/mcp-clients/:client", permanent: true, }, { @@ -452,55 +457,55 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/guides/agent-frameworks/crewai/python", destination: - "/:locale/guides/agent-frameworks/crewai/use-arcade-tools", + "/:locale/get-started/agent-frameworks/crewai/use-arcade-tools", permanent: true, }, { source: "/:locale/guides/agent-frameworks/langchain/python", destination: - "/:locale/guides/agent-frameworks/langchain/use-arcade-tools", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-tools", permanent: true, }, { source: "/:locale/guides/agent-frameworks/langchain/tools", destination: - "/:locale/guides/agent-frameworks/langchain/auth-langchain-tools", + "/:locale/get-started/agent-frameworks/langchain/auth-langchain-tools", permanent: true, }, { source: "/:locale/guides/agent-frameworks/mastra/typescript", destination: - "/:locale/guides/agent-frameworks/mastra/use-arcade-tools", + "/:locale/get-started/agent-frameworks/mastra/use-arcade-tools", permanent: true, }, { source: "/:locale/guides/agent-frameworks/google-adk/python", destination: - "/:locale/guides/agent-frameworks/google-adk/use-arcade-tools", + "/:locale/get-started/agent-frameworks/google-adk/use-arcade-tools", permanent: true, }, { source: "/:locale/guides/agent-frameworks/openai/python", destination: - "/:locale/guides/agent-frameworks/openai-agents/use-arcade-tools", + "/:locale/get-started/agent-frameworks/openai-agents/use-arcade-tools", permanent: true, }, { source: "/:locale/guides/agent-frameworks/vercel-ai/typescript", - destination: "/:locale/guides/agent-frameworks/vercelai", + destination: "/:locale/get-started/agent-frameworks/vercelai", permanent: true, }, // Old resource paths { source: "/:locale/resources/mastra/user-auth-interrupts", destination: - "/:locale/guides/agent-frameworks/mastra/user-auth-interrupts", + "/:locale/get-started/agent-frameworks/mastra/user-auth-interrupts", permanent: true, }, { source: "/:locale/resources/oai-agents/overview", destination: - "/:locale/guides/agent-frameworks/openai-agents/overview", + "/:locale/get-started/agent-frameworks/openai-agents/overview", permanent: true, }, { @@ -508,6 +513,28 @@ const nextConfig: NextConfig = withLlmsTxt({ destination: "/:locale/guides/create-tools/:path*", permanent: true, }, + // Agent frameworks moved from guides to get-started + { + source: "/:locale/guides/agent-frameworks", + destination: "/:locale/get-started/agent-frameworks", + permanent: true, + }, + { + source: "/:locale/guides/agent-frameworks/:path*", + destination: "/:locale/get-started/agent-frameworks/:path*", + permanent: true, + }, + // MCP clients moved from guides/tool-calling to get-started + { + source: "/:locale/guides/tool-calling/mcp-clients", + destination: "/:locale/get-started/mcp-clients", + permanent: true, + }, + { + source: "/:locale/guides/tool-calling/mcp-clients/:path*", + destination: "/:locale/get-started/mcp-clients/:path*", + permanent: true, + }, ]; }, headers: async () => [ diff --git a/package.json b/package.json index ba7953195..dc2f529e2 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,10 @@ "vale:fix": "pnpm dlx tsx scripts/vale-fix.ts", "vale:review": "pnpm dlx tsx scripts/vale-style-review.ts", "vale:editorial": "pnpm dlx tsx scripts/vale-editorial.ts", - "vale:sync": "vale sync" + "vale:sync": "vale sync", + "check-redirects": "pnpm dlx tsx scripts/check-redirects.ts", + "update-links": "pnpm dlx tsx scripts/update-internal-links.ts", + "check-meta": "pnpm dlx tsx scripts/check-meta-keys.ts" }, "repository": { "type": "git", diff --git a/public/llms.txt b/public/llms.txt index 57e9528ca..67d3dc127 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,4 +1,4 @@ - + # Arcade @@ -70,9 +70,9 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Arcade for Slack](https://docs.arcade.dev/en/resources/integrations/social-communication/slack/install.md): The documentation page for Arcade for Slack provides users with guidance on integrating Arcade's AI tools into their Slack workspace to enhance team efficiency. It outlines the installation process, functionalities such as sending messages, finding information, and generating content, while also advising on the - [Arcade for Zoom](https://docs.arcade.dev/en/resources/integrations/social-communication/zoom/install.md): The "Arcade for Zoom" documentation page provides users with guidance on integrating Arcade's AI tools with their Zoom accounts to enhance meeting management and information retrieval. It outlines the functionalities available, such as listing upcoming meetings and retrieving invitation details, while also addressing - [Arcade Glossary](https://docs.arcade.dev/en/resources/glossary.md): The Arcade Glossary documentation provides definitions and explanations of key terms and concepts related to the Arcade platform, including agents, tools, and MCP servers. It aims to help users understand the components necessary for building, testing, and deploying applications that utilize large language -- [Arcade with Agent Frameworks and MCP Clients](https://docs.arcade.dev/en/guides/agent-frameworks.md): This documentation page provides guidance on integrating Arcade with various agent frameworks and MCP clients, enabling users to enhance their AI applications with tool-calling capabilities. It features a list of supported agent frameworks, including LangChain, CrewAI, and OpenAI Agents, -- [Arcade with Google ADK](https://docs.arcade.dev/en/guides/agent-frameworks/google-adk/overview.md): This documentation page provides a comprehensive guide on integrating the `google-adk-arcade` package with Arcade, enabling users to enhance their AI agents with various tools such as Google Mail and GitHub. It covers installation, key features, basic usage examples -- [Arcade with OpenAI Agents](https://docs.arcade.dev/en/guides/agent-frameworks/openai-agents/overview.md): This documentation page provides a comprehensive guide for integrating Arcade with the OpenAI Agents library, enabling users to enhance their AI agents with various tools such as Gmail, LinkedIn, and GitHub. It covers installation, key features, basic usage examples, and +- [Arcade with Agent Frameworks and MCP Clients](https://docs.arcade.dev/en/get-started/agent-frameworks.md): This documentation page provides developers with guidance on integrating Arcade with various agent frameworks and MCP clients to enhance their AI applications. It includes authentication procedures, tool loading, and execution instructions, along with code examples and configuration steps for quick implementation. Users can explore specific +- [Arcade with Google ADK](https://docs.arcade.dev/en/get-started/agent-frameworks/google-adk/overview.md): This documentation page provides a comprehensive guide for integrating the `google-adk-arcade` package with the Google ADK library, enabling users to enhance their AI agents with various Arcade tools such as Google Mail and GitHub. It covers installation, key +- [Arcade with OpenAI Agents](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/overview.md): This documentation page provides a comprehensive guide for integrating Arcade with the OpenAI Agents library, enabling users to enhance their AI agents with various tools like Gmail, LinkedIn, and GitHub. It covers installation, key features, basic usage examples, and handling - [ArcadeEngineApi](https://docs.arcade.dev/en/resources/integrations/development/arcade-engine-api.md): The ArcadeEngineApi documentation provides users with a comprehensive guide to the tools available for interacting with the Arcade Engine API, focusing on managing authentication providers, secrets, and worker configurations. It outlines various actions users and LLMs can perform, such as retrieving - [Asana](https://docs.arcade.dev/en/resources/integrations/productivity/asana.md): This documentation page provides users with a comprehensive guide to the Arcade Asana MCP Server, enabling them to build agents and AI applications that interact with Asana tasks, projects, and workspaces. Users can learn how to manage teams, create and update tasks - [Asana Reference](https://docs.arcade.dev/en/resources/integrations/productivity/asana/reference.md): The Asana Reference documentation provides a comprehensive list of enumerations related to tag colors, task sorting options, and sort orders used in the Asana MCP Server. Users can utilize this reference to understand and implement the various color codes and sorting parameters effectively in @@ -82,7 +82,7 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [BoxApi](https://docs.arcade.dev/en/resources/integrations/productivity/box-api.md): The BoxApi documentation provides users with tools to manage and automate various aspects of Box content, including file management, metadata handling, collaboration, document generation, and enterprise operations. It outlines capabilities for interacting with Box's API, enabling users to build applications or - [Brightdata](https://docs.arcade.dev/en/resources/integrations/development/brightdata.md): The Brightdata documentation page provides users with tools and guidance for scraping, searching, and extracting structured data from various websites at scale without being blocked. It outlines specific functionalities such as scraping web pages in Markdown format, performing advanced searches across major search engines, - [Build a Tool](https://docs.arcade.dev/en/guides/create-tools/tool-basics.md): This documentation page provides a comprehensive guide on building custom tools using Arcade's MCP Server framework, enabling users to enhance AI agents with new functionalities. It serves as a starting point for those looking to create their first tool or expand existing capabilities. Users will learn -- [Build an AI Chatbot with Arcade and Vercel AI SDK](https://docs.arcade.dev/en/guides/agent-frameworks/vercelai.md): This documentation page provides a step-by-step guide for building a browser-based AI chatbot using the Vercel AI SDK and Arcade tools, enabling users to interact with Gmail and Slack through a conversational interface. Users will learn how to set up a Next.js +- [Build an AI Chatbot with Arcade and Vercel AI SDK](https://docs.arcade.dev/en/get-started/agent-frameworks/vercelai.md): This documentation page provides a step-by-step guide for building a browser-based AI chatbot using the Vercel AI SDK and Arcade tools to integrate with Gmail and Slack. Users will learn how to set up a Next.js project, handle authentication, and implement - [Build MCP Server QuickStart](https://docs.arcade.dev/en/get-started/quickstarts/mcp-server-quickstart.md): The "Build MCP Server QuickStart" documentation provides a step-by-step guide for users to create and run a custom MCP Server using the Arcade MCP framework. It covers prerequisites, installation instructions, server setup, and how to connect and authorize tools, enabling - [CalendlyApi](https://docs.arcade.dev/en/resources/integrations/productivity/calendly-api.md): The CalendlyApi documentation provides tools and resources for developers to integrate and manage scheduling and event-related tasks within the Calendly platform using OAuth2 authentication. Users can learn how to create, retrieve, and update event types, manage invitees, and handle - [Call a tool in your IDE/MCP Client](https://docs.arcade.dev/en/get-started/quickstarts/call-tool-client.md): This documentation page guides users on how to create and utilize an MCP Gateway in their IDE or MCP Client to streamline the process of calling tools from multiple MCP servers. It covers the steps to set up the gateway, select relevant tools, and connect it to @@ -99,9 +99,10 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Comparative evaluations](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools/comparative-evaluations.md): The "Comparative Evaluations" documentation page guides users in testing and comparing the performance of AI models across different tool implementations using isolated tool registries, known as tracks. It outlines how to set up comparative evaluations, register tools, create test cases, - [Compare MCP Server Types](https://docs.arcade.dev/en/guides/create-tools/tool-basics/compare-server-types.md): This documentation page provides a comparative overview of different MCP server types offered by Arcade, detailing their functionalities based on transport method and deployment options. Users can learn about the capabilities of each server type, including the availability of tools with or without authentication and secrets. - [Confluence](https://docs.arcade.dev/en/resources/integrations/productivity/confluence.md): This documentation page provides a comprehensive overview of the Arcade Confluence MCP Server, which enables users to build agents and AI applications that interact with Confluence. It details various tools available for managing pages, spaces, and attachments, as well as searching for content -- [Connect to MCP Clients](https://docs.arcade.dev/en/guides/tool-calling/mcp-clients.md): This documentation page provides guidance on connecting Arcade MCP servers to various MCP-compatible clients and development environments, enabling users to enhance their agent workflows. +- [Connect Arcade to your LLM](https://docs.arcade.dev/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md): This documentation page provides a step-by-step guide for integrating Arcade with a Large Language Model (LLM) using Python. It outlines the necessary prerequisites, including API keys and package installations, and teaches users how to create a harness that facilitates communication between the +- [Connect to MCP Clients](https://docs.arcade.dev/en/get-started/mcp-clients.md): This documentation page provides guidance on connecting MCP servers to various MCP-compatible clients and development environments, enabling users to enhance their agent workflows. - [Contact Us](https://docs.arcade.dev/en/resources/contact-us.md): This documentation page provides users with information on how to connect with the Arcade team for support through various channels. It aims to facilitate communication and assistance for users and their agents. -- [Create a new Mastra project](https://docs.arcade.dev/en/guides/agent-frameworks/mastra/use-arcade-tools.md): This documentation page provides a step-by-step guide for integrating Arcade tools into a new Mastra project, enabling users to leverage these tools within their Mastra applications. It covers prerequisites, project setup, installation of the Arcade client, API key configuration, and +- [Create a new Mastra project](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra/use-arcade-tools.md): This documentation page provides a comprehensive guide for users to create a new Mastra project and integrate Arcade tools into their applications. It covers prerequisites, project setup, installation of the Arcade client, configuration of API keys, and interaction methods with the Mastra agent - [Create an evaluation suite](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools/create-evaluation-suite.md): This documentation page provides a comprehensive guide on creating an evaluation suite to test AI models' tool usage through Arcade. Users will learn how to set up prerequisites, define evaluation files and suites, run evaluations, and interpret results, ensuring accurate tool selection and parameter - [Create an MCP tool with secrets](https://docs.arcade.dev/en/guides/create-tools/tool-basics/create-tool-secrets.md): This documentation page guides users on how to create custom MCP tools that securely handle sensitive information, or "secrets," using the Arcade platform. It covers the process of reading secrets from various sources, such as environment variables and the Arcade Dashboard, and provides - [Creating an MCP Server with Arcade](https://docs.arcade.dev/en/guides/create-tools/tool-basics/build-mcp-server.md): This documentation page provides a comprehensive guide for users to create, test, deploy, and publish a custom MCP Server using the Arcade framework. It details the installation of necessary tools, the scaffolding of a server project, and the setup of environment configurations, @@ -119,6 +120,7 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Environment Variables](https://docs.arcade.dev/en/resources/integrations/social-communication/slack/environment-variables.md): This documentation page provides guidance on configuring environment variables related to Slack API interactions, specifically `SLACK_MAX_CONCURRENT_REQUESTS`, `MAX_PAGINATION_SIZE_LIMIT`, and `MAX_PAGINATION_TIMEOUT_SECONDS`. Users will learn how to adjust these settings - [Evaluate Tools](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools.md): The "Evaluate Tools" documentation page provides guidance on systematically testing and enhancing tools using Arcade's evaluation framework. It helps users validate the performance of their tools after initial development and offers techniques for iterative improvements to ensure reliability in production. - [ExaApi](https://docs.arcade.dev/en/resources/integrations/search/exa-api.md): The ExaApi documentation provides users with a comprehensive guide to utilizing the Exa.ai Search API, enabling them to conduct searches, manage websets, and handle research requests effectively. It outlines various tools available within the API, detailing their functionalities such as +- [Fetch the "ScrapeUrl" tool from the "Firecrawl" MCP Server](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/use-arcade-tools.md): This documentation page provides a comprehensive guide on integrating Arcade tools into LangGraph applications, detailing prerequisites, environment setup, API key configuration, and tool management. Users will learn how to create and manage AI models, configure agents, and stream responses while leveraging specific - [Figma](https://docs.arcade.dev/en/resources/integrations/development/figma.md): This documentation page provides users with a comprehensive guide to the Figma MCP Server, enabling interaction with Figma's design files, components, and collaboration features through the Figma REST API. Users can learn to access file structures, manage components, add comments - [FigmaApi](https://docs.arcade.dev/en/resources/integrations/productivity/figma-api.md): The FigmaApi documentation provides a comprehensive guide for developers to utilize tools that enable interaction with the Figma API, facilitating efficient management of design assets and collaboration on projects. Users can learn how to perform various actions such as retrieving Figma files, managing - [Firecrawl](https://docs.arcade.dev/en/resources/integrations/development/firecrawl.md): The Firecrawl documentation provides users with a comprehensive guide to utilizing the Arcade Firecrawl MCP Server, enabling them to build agents and AI applications for scraping, crawling, and mapping websites. It outlines available tools, including functionalities for scraping URLs, crawling websites, @@ -163,8 +165,9 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [HubspotUsersApi](https://docs.arcade.dev/en/resources/integrations/sales/hubspot-users-api.md): The HubspotUsersApi documentation provides users with tools to efficiently manage users and teams within a HubSpot account, including functionalities for retrieving user lists, creating and updating user accounts, and removing users. It offers detailed descriptions of available API tools, along with - [Imgflip](https://docs.arcade.dev/en/resources/integrations/entertainment/imgflip.md): The Imgflip documentation provides users with tools to create and manage memes using the Imgflip API, enabling the development of agents and AI applications. Users can search for meme templates, retrieve popular memes, and create custom memes by adding text to existing templates. - [Imgflip](https://docs.arcade.dev/en/resources/integrations/entertainment/spotify/imgflip.md): The Imgflip documentation page provides users with tools to create and manage memes using the Imgflip API, allowing them to search for meme templates, retrieve popular templates, and create custom memes. It outlines the available features, including a premium search option and customizable +- [Import necessary classes and modules](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/user-auth-interrupts.md): This documentation page guides users in creating a LangGraph workflow that incorporates user authorization for specific Arcade tools, ensuring that only authorized tools are accessible to the language model. It provides step-by-step instructions on setting up the environment, defining workflow functions, and compiling - [In Custom Applications](https://docs.arcade.dev/en/guides/tool-calling/custom-apps.md): This documentation page provides guidance on integrating Arcade tools into custom applications, focusing on user authentication, authorization status checking, and managing tool definitions. It serves as a resource for developers building tool-calling interfaces to ensure proper implementation and functionality. -- [Initialize the Arcade client](https://docs.arcade.dev/en/guides/agent-frameworks/google-adk/use-arcade-tools.md): This documentation page provides a comprehensive guide for integrating Arcade tools into Google ADK applications, outlining the necessary prerequisites, setup procedures, and configuration steps. Users will learn how to manage and authorize Arcade tools, create agents, and run them effectively within their applications +- [Initialize the Arcade client](https://docs.arcade.dev/en/get-started/agent-frameworks/google-adk/use-arcade-tools.md): This documentation page provides a comprehensive guide for integrating Arcade tools into Google ADK applications, detailing the necessary prerequisites, environment setup, and configuration steps. Users will learn how to create and manage Arcade tools, authorize them for agents, and run these agents with - [IntercomApi](https://docs.arcade.dev/en/resources/integrations/customer-support/intercom-api.md): The IntercomApi documentation provides a comprehensive guide to tools that enable users to interact with the Intercom platform using OAuth2 authentication. It details various functionalities, such as managing admin information, creating and updating articles, and handling company data, allowing users to - [Jira](https://docs.arcade.dev/en/resources/integrations/productivity/jira.md): This documentation page provides a comprehensive overview of the Jira MCP Server, which enables users and AI applications to efficiently manage Jira issues and projects. It outlines various functionalities such as creating, updating, and searching for issues, managing labels and attachments, and transitioning issues - [Jira Environment Variables](https://docs.arcade.dev/en/resources/integrations/productivity/jira/environment-variables.md): This documentation page provides essential information on configuring Jira environment variables to optimize API interactions, specifically focusing on controlling the maximum concurrent requests, API request timeout, and cache size for improved performance during tool execution. Users will learn how to set numeric string values for these @@ -183,23 +186,23 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Notion](https://docs.arcade.dev/en/resources/integrations/productivity/notion.md): This documentation page provides users with a comprehensive guide to the Arcade Notion MCP Server, which enables the creation and management of agents and AI applications that interact with Notion. Users can learn how to utilize various tools to retrieve page content, create new pages - [Obsidian](https://docs.arcade.dev/en/resources/integrations/productivity/obsidian.md): This documentation page provides an overview of the Arcade Obsidian Toolkit, a community-contributed MCP Sever verified by the Arcade team. It guides users to learn more about the toolkit by directing them to the associated GitHub repository. - [On-premise MCP Servers](https://docs.arcade.dev/en/guides/deployment-hosting/on-prem.md): This documentation page provides guidance on deploying on-premises MCP servers within a hybrid architecture, enabling users to utilize Arcade's cloud infrastructure while maintaining control over their private resources and data security. Users will learn how to set up their MCP servers, create secure tunnels -- [Or set it directly when initializing the client](https://docs.arcade.dev/en/guides/agent-frameworks/openai-agents/use-arcade-tools.md): This documentation page provides a comprehensive guide on integrating Arcade tools into OpenAI Agents applications, detailing the necessary prerequisites, environment setup, and configuration steps. Users will learn how to create and manage Arcade tools, set up agents, handle authentication, and run their +- [Or set it directly when initializing the client](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/use-arcade-tools.md): This documentation page provides a comprehensive guide on integrating Arcade tools into OpenAI Agents applications, detailing the necessary prerequisites, environment setup, and configuration steps. Users will learn how to create and manage Arcade tools, set up agents with these tools, handle authentication, - [Organize your MCP server and tools](https://docs.arcade.dev/en/guides/create-tools/tool-basics/organize-mcp-tools.md): This documentation page provides best practices for organizing your MCP server and tools, including how to define and import tools from separate files and other packages. Users will learn to maintain a clean project structure by keeping tools in a dedicated directory and using decorators effectively. Additionally - [Outlook Calendar](https://docs.arcade.dev/en/resources/integrations/productivity/outlook-calendar.md): The Outlook Calendar documentation page provides users with tools to create, list, and retrieve events in their Outlook Calendar using the Outlook API. It includes detailed descriptions of available tools, such as creating events and fetching event details, along with example code snippets for implementation - [Outlook Mail](https://docs.arcade.dev/en/resources/integrations/productivity/outlook-mail.md): The Outlook Mail documentation page provides users with tools to interact with the Outlook API, enabling them to read, write, and send emails efficiently. It details various functionalities, such as creating drafts, sending emails, and listing messages, along with code examples for - [OutlookMail Reference](https://docs.arcade.dev/en/resources/integrations/productivity/outlook-mail/reference.md): The OutlookMail Reference documentation provides a comprehensive list of enumerations, folder names, email filter properties, and filter operators used in the OutlookMail MCP Server. It helps users understand and utilize these elements effectively when working with OutlookMail tools. This reference is - [page](https://docs.arcade.dev/en/resources/examples.md): This documentation page showcases a collection of example applications that utilize Arcade's tools and MCP servers, providing users with practical implementations and templates for various workflows and agent functionalities. Users can explore detailed descriptions and links to GitHub repositories for each app, enabling them to - [page](https://docs.arcade.dev/en/resources/integrations.md): This documentation page provides a comprehensive registry of all MCP Servers within the Arcade ecosystem, helping users to easily identify and access the available servers. -- [page](https://docs.arcade.dev/en/guides/agent-frameworks/crewai/use-arcade-tools.md): This documentation page provides a comprehensive guide for integrating Arcade tools into CrewAI applications, detailing the necessary prerequisites, environment setup, and configuration steps. Users will learn how to authorize and utilize these tools within their CrewAI agent teams, ensuring tailored functionality for their -- [page](https://docs.arcade.dev/en/guides/agent-frameworks/langchain/auth-langchain-tools.md): This documentation page provides a step-by-step guide on how to authorize existing LangChain tools, such as the `GmailToolkit`, using the Arcade platform. It outlines the necessary prerequisites, installation of required packages, and detailed instructions for the authorization process in -- [page](https://docs.arcade.dev/en/guides/agent-frameworks/mastra/overview.md): This documentation page guides users on integrating Arcade's tool ecosystem with Mastra applications, enabling enhanced functionality for AI agents. It outlines how to access various pre-built tools, simplifies tool management, and details the processes for tool discovery, schema conversion, and execution -- [page](https://docs.arcade.dev/en/guides/agent-frameworks/mastra/user-auth-interrupts.md): This documentation page provides guidance on managing user-specific authorization for Arcade tools within Mastra applications, focusing on dynamic tool loading and per-user authentication flows. It outlines the steps to set up agents and API endpoints, ensuring each user has a secure and tailored experience +- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/crewai/use-arcade-tools.md): This documentation page provides a comprehensive guide on integrating Arcade tools into CrewAI applications, detailing the necessary prerequisites, setup, and configuration steps. Users will learn how to manage tool authorization and effectively utilize these tools within their CrewAI agent teams. Additionally, it +- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md): This documentation page provides a step-by-step guide on how to authorize existing LangChain tools, such as the `GmailToolkit`, using the Arcade platform. It outlines the prerequisites, installation of necessary packages, and detailed instructions for the authorization process in both +- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra/overview.md): This documentation page provides guidance on integrating Arcade's tool ecosystem with Mastra applications, enabling users to enhance their Mastra agents with access to a variety of pre-built tools and simplified tool management. It outlines the key mechanisms for integration, including tool discovery, +- [page](https://docs.arcade.dev/en/get-started/agent-frameworks/mastra/user-auth-interrupts.md): This documentation page provides guidance on managing user-specific authorization for Arcade tools within Mastra applications, focusing on dynamic tool loading and per-user authentication flows. It outlines the necessary steps to configure agents, create API endpoints, and handle tool authorization effectively, ensuring a - [page](https://docs.arcade.dev/en/resources/integrations/productivity/dropbox/reference.md): This documentation page defines the various item categories used in Dropbox, including types such as image, document, PDF, spreadsheet, presentation, audio, video, folder, and paper. It helps users understand the classification of files within the Dropbox system. - [PagerDuty](https://docs.arcade.dev/en/resources/integrations/customer-support/pagerduty.md): This documentation page provides guidance on using the PagerDuty MCP Server, which enables agents to access and manage incidents, on-call information, services, and teams through read-only API tools. It includes details on OAuth configuration, available tools with descriptions, and code - [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/guides/agent-frameworks/crewai/custom-auth-flow.md): This documentation page provides a step-by-step guide on creating a custom authorization flow for executing Arcade tools within a CrewAI agent team. It outlines the prerequisites, environment setup, and configuration needed to implement a tailored approach to tool authorization, allowing for unique interfaces +- [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 @@ -214,8 +217,9 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Security](https://docs.arcade.dev/en/guides/security.md): This documentation page provides users with best practices for ensuring the security of MCP servers and Arcade tools, particularly when handling sensitive data or operating in production environments. It also highlights the importance of security in tool development and outlines the resources available for implementing robust security measures - [Security Research Program](https://docs.arcade.dev/en/guides/security/security-research-program.md): The Security Research Program documentation page outlines how users can report security vulnerabilities in Arcade's tools and services, emphasizing the importance of community involvement in enhancing security. It details the types of vulnerabilities sought, the reporting process, and guidelines for responsible disclosure. Additionally, - [Server-Level vs Tool-Level Authorization](https://docs.arcade.dev/en/learn/server-level-vs-tool-level-auth.md): This documentation page explains the differences between server-level authorization (Resource Server auth) and tool-level authorization in Arcade MCP servers, highlighting their roles in securing access to the server and third-party APIs. It provides guidance on when to implement each type of authorization, -- [Set your API key](https://docs.arcade.dev/en/guides/agent-frameworks/openai-agents/user-auth-interrupts.md): This documentation page provides guidance on managing user authorization for Arcade tools within OpenAI Agents applications. It covers obtaining an API key, configuring the environment, handling authorization errors, and ensuring persistence of user authorization. Users will learn how to effectively implement and manage the -- [Setup Arcade with LangChain](https://docs.arcade.dev/en/guides/agent-frameworks/langchain/use-arcade-with-langchain.md): This documentation page provides a comprehensive guide on integrating Arcade tools within LangChain agents, enabling users to build and manage AI agents effectively. It covers the necessary prerequisites, key concepts like agents and interrupts, and detailed steps for setting up a project, importing required +- [Set your API key](https://docs.arcade.dev/en/get-started/agent-frameworks/openai-agents/user-auth-interrupts.md): This documentation page provides a comprehensive guide on managing user authorization for Arcade tools within OpenAI Agents applications. It outlines the steps to obtain an API key, configure the environment, handle authorization errors, and implement a complete authorization flow. Users will learn how to +- [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 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 applications to 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, sending @@ -234,10 +238,10 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Twitch auth provider](https://docs.arcade.dev/en/resources/integrations/entertainment/twitch.md): The Twitch auth provider documentation page guides users in creating a custom authentication provider to access the Twitch API using their own OAuth 2.0 credentials. It outlines the steps to configure Twitch authentication within the Arcade platform, including creating a Twitch application and integrating it - [Types of Tools](https://docs.arcade.dev/en/guides/create-tools/improve/types-of-tools.md): This documentation page explains the two types of tools offered by Arcade: Optimized tools and Starter tools. It highlights the differences in design and functionality, emphasizing that Optimized tools are tailored for AI-powered chat interfaces to improve performance, while Starter tools provide more - [Understanding `Context` and tools](https://docs.arcade.dev/en/guides/create-tools/tool-basics/runtime-data-access.md): This documentation page explains the `Context` class used in Arcade tools, detailing how to access runtime capabilities and tool-specific data securely. Users will learn how to utilize the `Context` object to retrieve OAuth tokens, secrets, user information, and to log -- [Use Arcade in Cursor](https://docs.arcade.dev/en/guides/tool-calling/mcp-clients/cursor.md): This documentation page provides a step-by-step guide for users to connect Cursor to an Arcade MCP Gateway, enabling the use of Arcade tools within Cursor. It outlines the prerequisites needed for setup, including creating an Arcade account and obtaining an API key, as well -- [Use Arcade in Microsoft Copilot Studio](https://docs.arcade.dev/en/guides/tool-calling/mcp-clients/copilot-studio.md): This documentation page guides users on how to connect Microsoft Copilot Studio to an Arcade MCP Gateway, enabling the integration of Arcade tools within their agents. It outlines the prerequisites, step-by-step instructions for creating or opening an agent, adding an MCP tool, -- [Use Arcade in Visual Studio Code](https://docs.arcade.dev/en/guides/tool-calling/mcp-clients/visual-studio-code.md): This documentation page provides a step-by-step guide for connecting Visual Studio Code to an Arcade MCP Gateway, enabling users to integrate and utilize Arcade tools within the IDE. It outlines the prerequisites for setup, including creating an Arcade account and obtaining an API key, -- [Use Arcade with Claude Desktop](https://docs.arcade.dev/en/guides/tool-calling/mcp-clients/claude-desktop.md): This documentation page is intended to guide users on how to utilize Arcade with Claude Desktop. However, it currently indicates that the content is forthcoming and does not provide any detailed instructions or information at this time. +- [Use Arcade in Claude Desktop](https://docs.arcade.dev/en/get-started/mcp-clients/claude-desktop.md): This documentation page provides a step-by-step guide for users to connect Claude Desktop to an Arcade MCP Gateway, enabling them to utilize custom connectors. It outlines the prerequisites for setup, including creating an Arcade account and obtaining an API key, as well as detailed +- [Use Arcade in Cursor](https://docs.arcade.dev/en/get-started/mcp-clients/cursor.md): This documentation page provides a step-by-step guide for connecting Cursor to an Arcade MCP Gateway, enabling users to utilize Arcade tools within the Cursor environment. It outlines the prerequisites for setup, including creating an Arcade account and obtaining an API key, as well as +- [Use Arcade in Microsoft Copilot Studio](https://docs.arcade.dev/en/get-started/mcp-clients/copilot-studio.md): This documentation page guides users on how to connect Microsoft Copilot Studio to an Arcade MCP Gateway, enabling the integration of Arcade tools into their agents. It outlines the prerequisites, step-by-step instructions for creating or opening an agent, adding an MCP tool, +- [Use Arcade in Visual Studio Code](https://docs.arcade.dev/en/get-started/mcp-clients/visual-studio-code.md): This documentation page provides a step-by-step guide for connecting Visual Studio Code to an Arcade MCP Gateway, enabling users to set up and run an MCP server within the IDE. It outlines prerequisites, setup instructions, and authentication processes to ensure successful integration. By - [VercelApi](https://docs.arcade.dev/en/resources/integrations/development/vercel-api.md): The VercelApi documentation provides a comprehensive guide for users to manage their Vercel projects, domains, and integrations through various API tools. It outlines available functionalities such as creating and managing access groups, handling deployments, and managing DNS records, enabling - [Walmart Search](https://docs.arcade.dev/en/resources/integrations/search/walmart.md): The Walmart Search documentation provides tools for developers to integrate product search and details retrieval from Walmart into their applications. It outlines how to use the `Walmart.SearchProducts` and `Walmart.GetProductDetails` tools, including parameters for customizing searches and retrieving - [WeaviateApi](https://docs.arcade.dev/en/resources/integrations/databases/weaviate-api.md): The WeaviateApi documentation provides users with essential tools and instructions for managing and interacting with the Weaviate vector search engine via its API. It covers authentication requirements, including obtaining API keys, and offers a comprehensive list of available API endpoints for various diff --git a/scripts/check-meta-keys.ts b/scripts/check-meta-keys.ts new file mode 100644 index 000000000..c71c8a67f --- /dev/null +++ b/scripts/check-meta-keys.ts @@ -0,0 +1,446 @@ +#!/usr/bin/env npx tsx + +/** + * Validate that _meta.tsx keys match existing filesystem entries + * + * Usage: + * pnpm check-meta [--staged-only] + * + * Features: + * - Scans all _meta.tsx files in app/en/ + * - Validates that each key corresponds to a sibling directory or page file + * - Skips special keys like "*", "index", "---", and external links + * - --staged-only: Only check staged _meta.tsx files (for pre-commit hook) + */ + +import { execSync } from "node:child_process"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { dirname } from "node:path"; +import fg from "fast-glob"; + +// Colors for terminal output +const colors = { + red: (s: string) => `\x1b[0;31m${s}\x1b[0m`, + green: (s: string) => `\x1b[0;32m${s}\x1b[0m`, + yellow: (s: string) => `\x1b[1;33m${s}\x1b[0m`, + blue: (s: string) => `\x1b[0;34m${s}\x1b[0m`, + dim: (s: string) => `\x1b[2m${s}\x1b[0m`, +}; + +// Parse command line arguments +const args = process.argv.slice(2); +const stagedOnly = args.includes("--staged-only"); + +// Special keys that don't need filesystem entries +const SPECIAL_KEYS = new Set(["*", "index", "---"]); + +// Nested property names that should never be treated as page keys +const NESTED_PROPERTIES = new Set([ + "theme", + "title", + "display", + "href", + "newWindow", + "breadcrumb", + "toc", + "layout", + "copyPage", + "type", +]); + +// Top-level regex patterns +const META_OBJECT_REGEX = + /(?:const|export\s+const)\s+meta\s*(?::\s*\w+)?\s*=\s*\{/; +const KEY_CHAR_REGEX = /[a-zA-Z0-9_-]/; +const MDX_EXTENSION_REGEX = /\.mdx?$/; + +// Constants +const LOOK_BEHIND_LENGTH = 15; + +type MetaError = { + file: string; + key: string; + message: string; +}; + +type ParserState = { + keys: string[]; + depth: number; + index: number; + inString: boolean; + stringChar: string; + currentKey: string; + collectingKey: boolean; +}; + +/** + * Get list of staged _meta.tsx files + */ +function getStagedMetaFiles(): string[] { + try { + const output = execSync( + "git diff --cached --name-only --diff-filter=ACMR", + { + encoding: "utf-8", + } + ); + return output + .split("\n") + .filter((f) => f.endsWith("_meta.tsx") && f.startsWith("app/")); + } catch { + return []; + } +} + +/** + * Get all _meta.tsx files in app/en/ + */ +function getAllMetaFiles(): string[] { + return fg.sync("app/en/**/_meta.tsx", { onlyFiles: true }); +} + +/** + * Simple key extractor - finds keys at depth 1 of the meta object + */ +function extractMetaKeys(content: string): string[] { + const match = META_OBJECT_REGEX.exec(content); + if (!match) { + return []; + } + + const startIndex = (match.index ?? 0) + match[0].length; + const state: ParserState = { + keys: [], + depth: 1, + index: startIndex, + inString: false, + stringChar: "", + currentKey: "", + collectingKey: false, + }; + + while (state.index < content.length && state.depth > 0) { + parseNextChar(content, state); + } + + return state.keys; +} + +/** + * Parse the next character in the content + */ +function parseNextChar(content: string, state: ParserState): void { + const char = content[state.index]; + const prevChar = state.index > 0 ? content[state.index - 1] : ""; + + if (handleStringBoundary(char, prevChar, state)) { + return; + } + + if (state.inString) { + handleStringContent(char, state); + return; + } + + if (char === "`") { + state.index = skipTemplateLiteral(content, state.index); + return; + } + + if (handleBraceDepth(char, state)) { + return; + } + + handleKeyParsing(content, char, state); + state.index += 1; +} + +/** + * Handle string boundary (quote characters) + */ +function handleStringBoundary( + char: string, + prevChar: string, + state: ParserState +): boolean { + if ((char !== '"' && char !== "'") || prevChar === "\\") { + return false; + } + + if (!state.inString) { + state.inString = true; + state.stringChar = char; + if (state.depth === 1) { + state.collectingKey = true; + state.currentKey = ""; + } + } else if (char === state.stringChar) { + state.inString = false; + state.collectingKey = false; + } + state.index += 1; + return true; +} + +/** + * Handle content inside a string + */ +function handleStringContent(char: string, state: ParserState): void { + if (state.collectingKey) { + state.currentKey += char; + } + state.index += 1; +} + +/** + * Handle brace depth tracking + */ +function handleBraceDepth(char: string, state: ParserState): boolean { + if (char === "{") { + state.depth += 1; + state.index += 1; + return true; + } + + if (char === "}") { + state.depth -= 1; + state.index += 1; + return true; + } + + return false; +} + +/** + * Handle key parsing logic + */ +function handleKeyParsing( + content: string, + char: string, + state: ParserState +): void { + // Start collecting unquoted key + if (state.depth === 1 && KEY_CHAR_REGEX.test(char) && !state.collectingKey) { + const start = Math.max(0, state.index - LOOK_BEHIND_LENGTH); + const lookBehind = content.slice(start, state.index).trim(); + if (isKeyStart(lookBehind)) { + state.collectingKey = true; + state.currentKey = char; + } + return; + } + + // Continue collecting unquoted key + if (state.collectingKey && KEY_CHAR_REGEX.test(char)) { + state.currentKey += char; + return; + } + + // Key ends at colon + if (char === ":" && state.depth === 1 && state.currentKey) { + addKeyIfValid(state.keys, state.currentKey); + state.currentKey = ""; + state.collectingKey = false; + } + + // Reset on comma + if (char === "," && state.depth === 1) { + state.currentKey = ""; + state.collectingKey = false; + } +} + +/** + * Skip over a template literal starting at index i + */ +function skipTemplateLiteral(content: string, startIndex: number): number { + let i = startIndex + 1; + while (i < content.length && content[i] !== "`") { + if (content[i] === "\\" && i + 1 < content.length) { + i += 1; + } + i += 1; + } + return i + 1; +} + +/** + * Check if the lookBehind indicates start of a new key + */ +function isKeyStart(lookBehind: string): boolean { + return ( + lookBehind.endsWith("{") || + lookBehind.endsWith(",") || + lookBehind.endsWith("\n") + ); +} + +/** + * Add key to list if it's valid (not special or nested property) + */ +function addKeyIfValid(keys: string[], key: string): void { + if (!(SPECIAL_KEYS.has(key) || NESTED_PROPERTIES.has(key))) { + keys.push(key); + } +} + +/** + * Check if a key has an href property (making it an external link) + */ +function keyHasHref(content: string, key: string): boolean { + const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const pattern = new RegExp( + `["']?${escapedKey}["']?\\s*:\\s*\\{[^}]*href\\s*:`, + "s" + ); + return pattern.test(content); +} + +/** + * Check if a key is a separator (type: "separator" or starts with --) + */ +function keyIsSeparator(content: string, key: string): boolean { + if (key.startsWith("--")) { + return true; + } + + const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const pattern = new RegExp( + `["']?${escapedKey}["']?\\s*:\\s*\\{[^}]*type\\s*:\\s*["']separator["']`, + "s" + ); + return pattern.test(content); +} + +/** + * Get valid sibling names for a _meta.tsx file + */ +function getValidSiblings(metaFilePath: string): Set { + const dir = dirname(metaFilePath); + const siblings = new Set(); + + try { + const entries = readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + if (entry.name === "_meta.tsx") { + continue; + } + + if (entry.isDirectory()) { + siblings.add(entry.name); + continue; + } + + const isMarkdown = + entry.name.endsWith(".mdx") || entry.name.endsWith(".md"); + const isPage = entry.name === "page.mdx" || entry.name === "page.md"; + + if (isMarkdown && !isPage) { + siblings.add(entry.name.replace(MDX_EXTENSION_REGEX, "")); + } + } + } catch { + // Directory doesn't exist or can't be read + } + + return siblings; +} + +/** + * Validate a single _meta.tsx file + */ +function validateMetaFile(filePath: string): MetaError[] { + const errors: MetaError[] = []; + + if (!existsSync(filePath)) { + return errors; + } + + const content = readFileSync(filePath, "utf-8"); + const keys = extractMetaKeys(content); + const validSiblings = getValidSiblings(filePath); + + for (const key of keys) { + if (keyHasHref(content, key) || keyIsSeparator(content, key)) { + continue; + } + + if (!validSiblings.has(key)) { + const suggestions = findSimilarSiblings(key, validSiblings); + let message = `Key "${key}" does not match any sibling directory or file`; + if (suggestions.length > 0) { + message += `. Did you mean: ${suggestions.map((s) => `"${s}"`).join(", ")}?`; + } + errors.push({ file: filePath, key, message }); + } + } + + return errors; +} + +/** + * Find siblings with similar names (e.g., copilot-studio vs copilot_studio) + */ +function findSimilarSiblings( + key: string, + validSiblings: Set +): string[] { + const normalizedKey = key.replace(/[-_]/g, ""); + return [...validSiblings].filter((s) => { + const normalizedSibling = s.replace(/[-_]/g, ""); + return normalizedKey === normalizedSibling; + }); +} + +/** + * Main function + */ +function main(): void { + console.log(colors.blue("🔍 Checking _meta.tsx keys...\n")); + + const metaFiles = stagedOnly ? getStagedMetaFiles() : getAllMetaFiles(); + + if (metaFiles.length === 0) { + if (stagedOnly) { + console.log(colors.dim("No staged _meta.tsx files to check")); + } else { + console.log(colors.yellow("No _meta.tsx files found")); + } + process.exit(0); + } + + console.log( + colors.dim(`Checking ${metaFiles.length} _meta.tsx file(s)...\n`) + ); + + const allErrors: MetaError[] = []; + + for (const file of metaFiles) { + const errors = validateMetaFile(file); + allErrors.push(...errors); + } + + if (allErrors.length === 0) { + console.log(colors.green("✅ All _meta.tsx keys are valid\n")); + process.exit(0); + } + + console.log(colors.red(`❌ Found ${allErrors.length} invalid key(s):\n`)); + + for (const error of allErrors) { + console.log(colors.yellow(` ${error.file}:`)); + console.log(` ${error.message}\n`); + } + + console.log( + colors.dim( + "Meta keys must match sibling directories or page files (without extension)." + ) + ); + console.log(colors.dim("Special keys like *, index, and --- are allowed.\n")); + + process.exit(1); +} + +main(); diff --git a/scripts/check-redirects.ts b/scripts/check-redirects.ts new file mode 100644 index 000000000..093003ea2 --- /dev/null +++ b/scripts/check-redirects.ts @@ -0,0 +1,652 @@ +#!/usr/bin/env npx tsx + +/** + * Check that deleted/renamed markdown files have corresponding redirects in next.config.ts + * + * Usage: + * pnpm check-redirects [--auto-fix] [--staged-only] [base_branch] + * + * Features: + * - Detects deleted AND renamed markdown files without redirects + * - Auto-fix mode: automatically inserts redirect entries into next.config.ts + * - Validates existing redirects for circular references and invalid destinations + * - Collapses redirect chains automatically + * - --staged-only: Only check staged changes (for pre-commit hook) + */ + +import { execSync } from "node:child_process"; +import { existsSync, readFileSync, writeFileSync } from "node:fs"; + +// Colors for terminal output +const colors = { + red: (s: string) => `\x1b[0;31m${s}\x1b[0m`, + green: (s: string) => `\x1b[0;32m${s}\x1b[0m`, + yellow: (s: string) => `\x1b[1;33m${s}\x1b[0m`, + blue: (s: string) => `\x1b[0;34m${s}\x1b[0m`, +}; + +// Parse command line arguments +const args = process.argv.slice(2); +const autoFix = args.includes("--auto-fix"); +const stagedOnly = args.includes("--staged-only"); +const baseBranch = args.find((arg) => !arg.startsWith("--")) || "main"; + +const CONFIG_FILE = "next.config.ts"; + +// Magic number constant for "return [" offset +const RETURN_BRACKET_LENGTH = 8; + +// Top-level regex patterns for performance +const APP_LOCALE_PREFIX_REGEX = /^app\/[a-z]{2}\//; +const PAGE_FILE_SUFFIX_REGEX = /\/?page\.mdx?$/; +const LOCALE_PREFIX_REGEX = /^\/:locale\/?/; +const PAGE_FILE_MATCH_REGEX = /page\.mdx?$/; +const LOCALE_PATH_PREFIX_REGEX = /^\/:locale\//; +const WILDCARD_PATH_REGEX = /\/:path\*.*$/; +const MDX_EXTENSION_REGEX = /\.mdx$/; +const SPECIAL_REGEX_CHARS_REGEX = /[.*+?^${}()|[\]\\]/g; +const REDIRECT_REGEX = + /\{\s*source:\s*["']([^"']+)["']\s*,\s*destination:\s*["']([^"']+)["']/g; +const REVERSED_REDIRECT_REGEX = + /\{\s*destination:\s*["']([^"']+)["']\s*,\s*source:\s*["']([^"']+)["']/g; + +type Redirect = { + source: string; + destination: string; + permanent?: boolean; +}; + +type RedirectChain = { + source: string; + oldDest: string; + newDest: string; +}; + +/** + * Convert file path to URL path + * e.g., app/en/guides/foo/page.mdx -> /:locale/guides/foo + */ +function fileToUrl(filePath: string): string { + const urlPath = filePath + .replace(APP_LOCALE_PREFIX_REGEX, "") + .replace(PAGE_FILE_SUFFIX_REGEX, ""); + + return urlPath ? `/:locale/${urlPath}` : "/:locale"; +} + +/** + * Convert URL path to file path + * e.g., /:locale/guides/foo -> app/en/guides/foo/page.mdx + */ +function urlToFile(urlPath: string): string { + const pathWithoutLocale = urlPath.replace(LOCALE_PREFIX_REGEX, ""); + return pathWithoutLocale + ? `app/en/${pathWithoutLocale}/page.mdx` + : "app/en/page.mdx"; +} + +/** + * Check if a page exists on disk + */ +function pageExists(urlPath: string): boolean { + if (urlPath.includes(":path*") || urlPath.includes(":path")) { + return true; + } + + const filePath = urlToFile(urlPath); + if (existsSync(filePath)) { + return true; + } + + const mdPath = filePath.replace(MDX_EXTENSION_REGEX, ".md"); + if (existsSync(mdPath)) { + return true; + } + + return false; +} + +/** + * Execute regex and collect all matches (avoids assignment in expression) + */ +function collectRegexMatches( + regex: RegExp, + content: string, + sourceIndex: number, + destIndex: number +): Array<{ source: string; destination: string }> { + const results: Array<{ source: string; destination: string }> = []; + regex.lastIndex = 0; + + let match = regex.exec(content); + while (match !== null) { + results.push({ + source: match[sourceIndex], + destination: match[destIndex], + }); + match = regex.exec(content); + } + + return results; +} + +/** + * Parse redirects from next.config.ts + */ +function parseRedirects(content: string): Redirect[] { + const results: Redirect[] = []; + + // Collect standard format: { source: "...", destination: "..." } + const standardMatches = collectRegexMatches(REDIRECT_REGEX, content, 1, 2); + for (const m of standardMatches) { + results.push(m); + } + + // Collect reversed format: { destination: "...", source: "..." } + const reversedMatches = collectRegexMatches( + REVERSED_REDIRECT_REGEX, + content, + 2, + 1 + ); + for (const m of reversedMatches) { + results.push(m); + } + + return results; +} + +/** + * Parse git diff output for deleted and renamed files + */ +function parseGitDiffOutput( + output: string, + deletedFiles: string[], + renamedFromFiles: string[] +): void { + for (const line of output.split("\n")) { + if (!line) { + continue; + } + const parts = line.split("\t"); + const status = parts[0]; + const filePath = parts[1]; + + if (status === "D" && filePath && PAGE_FILE_MATCH_REGEX.test(filePath)) { + deletedFiles.push(filePath); + } else if ( + status?.startsWith("R") && + filePath && + PAGE_FILE_MATCH_REGEX.test(filePath) + ) { + renamedFromFiles.push(filePath); + } + } +} + +/** + * Ensure base branch exists locally + */ +function ensureBranchExists(branch: string): void { + try { + execSync(`git rev-parse --verify ${branch}`, { encoding: "utf-8" }); + } catch { + console.log(`Fetching ${branch} branch...`); + try { + execSync(`git fetch origin ${branch}:${branch}`, { encoding: "utf-8" }); + } catch { + console.error( + colors.red(`ERROR: Could not fetch base branch '${branch}'`) + ); + process.exit(1); + } + } +} + +/** + * Get deleted and renamed files by comparing branches + * @param branch - Base branch to compare against + * @param checkStagedOnly - If true, only check staged changes (for pre-commit hook) + */ +function getDeletedAndRenamedFiles( + branch: string, + checkStagedOnly: boolean +): { + deleted: string[]; + renamedFrom: string[]; +} { + const deletedFiles: string[] = []; + const renamedFromFiles: string[] = []; + + if (checkStagedOnly) { + // Only check staged changes (for pre-commit hook) + try { + const stagedChanges = execSync("git diff --cached --name-status", { + encoding: "utf-8", + }); + parseGitDiffOutput(stagedChanges, deletedFiles, renamedFromFiles); + } catch { + // Ignore errors from diff + } + } else { + // Check committed changes against base branch + ensureBranchExists(branch); + + try { + const committedChanges = execSync( + `git diff --name-status ${branch}...HEAD`, + { + encoding: "utf-8", + } + ); + parseGitDiffOutput(committedChanges, deletedFiles, renamedFromFiles); + } catch { + // Ignore errors from diff + } + + // Also check uncommitted changes (both staged and unstaged) + try { + const uncommittedChanges = execSync("git diff --name-status HEAD", { + encoding: "utf-8", + }); + parseGitDiffOutput(uncommittedChanges, deletedFiles, renamedFromFiles); + } catch { + // Ignore errors from diff + } + } + + return { + deleted: [...new Set(deletedFiles)], + renamedFrom: [...new Set(renamedFromFiles)], + }; +} + +/** + * Check if a wildcard redirect covers a path + */ +function checkWildcardMatch(path: string, redirectList: Redirect[]): boolean { + const pathWithoutLocale = path.replace(LOCALE_PATH_PREFIX_REGEX, ""); + + for (const redirect of redirectList) { + if (redirect.source.includes(":path*")) { + const prefix = redirect.source + .replace(WILDCARD_PATH_REGEX, "") + .replace(LOCALE_PATH_PREFIX_REGEX, ""); + + if ( + pathWithoutLocale.startsWith(`${prefix}/`) || + pathWithoutLocale === prefix + ) { + return true; + } + } + } + + return false; +} + +/** + * Find the final destination in a redirect chain (follows all hops) + * Returns null if the path doesn't redirect anywhere + */ +function findFinalRedirectDestination( + path: string, + redirectList: Redirect[] +): string | null { + const visited = new Set(); + let current = path; + + while (true) { + // Prevent infinite loops from circular redirects + if (visited.has(current)) { + return null; + } + visited.add(current); + + const redirect = redirectList.find((r) => r.source === current); + if (!redirect) { + // No redirect found - if we've moved at all, return current destination + return current === path ? null : current; + } + + current = redirect.destination; + } +} + +/** + * Insert redirect entries into next.config.ts + */ +function insertRedirects(entries: string[]): void { + const content = readFileSync(CONFIG_FILE, "utf-8"); + + const insertPoint = content.indexOf("return ["); + if (insertPoint === -1) { + console.error(colors.red("ERROR: Could not find 'return [' in config")); + process.exit(1); + } + + const beforeReturn = content.substring( + 0, + insertPoint + RETURN_BRACKET_LENGTH + ); + const afterReturn = content.substring(insertPoint + RETURN_BRACKET_LENGTH); + + const newContent = `${beforeReturn}\n // Auto-added redirects for deleted pages\n${entries.join("\n")}${afterReturn}`; + + writeFileSync(CONFIG_FILE, newContent); +} + +/** + * Update a redirect destination in the config + */ +function updateRedirectDestination( + oldDest: string, + newDest: string, + content: string +): string { + const escaped = oldDest.replace(SPECIAL_REGEX_CHARS_REGEX, "\\$&"); + const regex = new RegExp(`destination:\\s*["']${escaped}["']`, "g"); + // Using replacer function to avoid special replacement pattern interpretation + // (e.g., $1, $2, $& in newDest would be incorrectly expanded if using string) + return content.replace(regex, () => `destination: "${newDest}"`); +} + +// ============================================================ +// Main script +// ============================================================ + +console.log("Checking for deleted markdown files without redirects..."); +console.log(`Comparing current branch to: ${baseBranch}`); +console.log(""); + +const configContent = readFileSync(CONFIG_FILE, "utf-8"); +const redirects = parseRedirects(configContent); + +let exitCode = 0; +const invalidRedirects: string[] = []; +const chains: RedirectChain[] = []; + +// ============================================================ +// PART 1: Validate existing redirects +// ============================================================ +console.log(colors.blue(`Validating existing redirects in ${CONFIG_FILE}...`)); +console.log(""); + +for (const redirect of redirects) { + const { source, destination } = redirect; + + if ( + destination.includes("REPLACE_WITH") || + destination.includes("TODO") || + destination.includes("FIXME") + ) { + console.log(colors.red(`✗ Invalid redirect: ${source}`)); + console.log( + colors.yellow(` Destination contains placeholder text: ${destination}`) + ); + invalidRedirects.push(`${source} -> ${destination} (placeholder text)`); + exitCode = 1; + } else if (source === destination) { + console.log(colors.red(`✗ Circular redirect: ${source}`)); + console.log(colors.yellow(" Source and destination are the same!")); + invalidRedirects.push(`${source} -> ${destination} (circular)`); + exitCode = 1; + } else if (!destination.includes(":path")) { + const destWithoutLocale = destination.replace(LOCALE_PATH_PREFIX_REGEX, ""); + if (!(destWithoutLocale.includes(":") || pageExists(destination))) { + const finalDest = findFinalRedirectDestination(destination, redirects); + if (finalDest) { + console.log(colors.yellow(`⚠ Redirect chain detected: ${source}`)); + console.log(colors.blue(` ${source} → ${destination} → ${finalDest}`)); + chains.push({ source, oldDest: destination, newDest: finalDest }); + } else { + console.log(colors.red(`✗ Invalid redirect: ${source}`)); + console.log( + colors.yellow(` Destination does not exist: ${destination}`) + ); + invalidRedirects.push( + `${source} -> ${destination} (destination not found)` + ); + exitCode = 1; + } + } + } +} + +if (invalidRedirects.length === 0 && chains.length === 0) { + console.log(colors.green("✓ All existing redirects are valid.")); +} +console.log(""); + +// ============================================================ +// PART 1b: Auto-fix redirect chains +// ============================================================ +if (chains.length > 0) { + if (autoFix) { + console.log( + colors.blue(`Collapsing ${chains.length} redirect chain(s)...`) + ); + console.log(""); + + let updatedConfig = configContent; + for (const chain of chains) { + console.log(`${colors.green(" ✓")} ${chain.source}`); + console.log(` was: ${chain.oldDest}`); + console.log(` now: ${chain.newDest}`); + + updatedConfig = updateRedirectDestination( + chain.oldDest, + chain.newDest, + updatedConfig + ); + } + + writeFileSync(CONFIG_FILE, updatedConfig); + console.log(""); + console.log(colors.green(`✓ Redirect chains collapsed in ${CONFIG_FILE}`)); + console.log(""); + } else { + console.log( + colors.yellow( + `Found ${chains.length} redirect chain(s) that need collapsing.` + ) + ); + console.log("Run with --auto-fix to collapse them automatically."); + console.log(""); + exitCode = 1; + } +} + +// ============================================================ +// PART 2: Check for deleted/renamed files without redirects +// ============================================================ +const { deleted, renamedFrom } = getDeletedAndRenamedFiles( + baseBranch, + stagedOnly +); +const allDeletedOrRenamed = [...deleted, ...renamedFrom].filter((f) => + f.startsWith("app/") +); + +if (allDeletedOrRenamed.length === 0) { + console.log(colors.green("✓ No deleted or renamed markdown files found.")); + process.exit(exitCode); +} + +console.log("Found deleted/renamed markdown files:"); +for (const file of allDeletedOrRenamed) { + console.log(` ${file}`); +} +console.log(""); + +const missingRedirects: string[] = []; +const suggestedEntries: string[] = []; + +const latestRedirects = parseRedirects(readFileSync(CONFIG_FILE, "utf-8")); + +for (const file of allDeletedOrRenamed) { + const urlPath = fileToUrl(file); + + const hasExactRedirect = latestRedirects.some((r) => r.source === urlPath); + const hasWildcardRedirect = checkWildcardMatch(urlPath, latestRedirects); + + if (hasExactRedirect) { + console.log(colors.green(`✓ Redirect exists for: ${urlPath}`)); + } else if (hasWildcardRedirect) { + console.log( + colors.green(`✓ Redirect exists for: ${urlPath} (via wildcard)`) + ); + } else { + console.log(colors.red(`✗ Missing redirect for: ${urlPath}`)); + missingRedirects.push(urlPath); + + suggestedEntries.push(` { + source: "${urlPath}", + destination: "/:locale/REPLACE_WITH_NEW_PATH", + permanent: true, + },`); + + exitCode = 1; + } +} + +console.log(""); + +// ============================================================ +// PART 3: Report results and optionally auto-fix +// ============================================================ +if (missingRedirects.length > 0) { + if (autoFix) { + console.log( + colors.blue( + "══════════════════════════════════════════════════════════════" + ) + ); + console.log( + colors.blue( + `Auto-fixing: Adding ${missingRedirects.length} redirect(s) to ${CONFIG_FILE}` + ) + ); + console.log( + colors.blue( + "══════════════════════════════════════════════════════════════" + ) + ); + console.log(""); + + insertRedirects(suggestedEntries); + + console.log(colors.green(`✓ Added redirect entries to ${CONFIG_FILE}`)); + console.log(""); + console.log( + colors.red( + "══════════════════════════════════════════════════════════════" + ) + ); + console.log( + colors.red("ACTION REQUIRED: Update placeholder destinations!") + ); + console.log( + colors.red( + "══════════════════════════════════════════════════════════════" + ) + ); + console.log(""); + console.log("Redirect entries were added with placeholder destinations."); + console.log( + "You MUST update 'REPLACE_WITH_NEW_PATH' with actual paths before committing." + ); + console.log(""); + console.log(colors.yellow("Redirects needing destinations:")); + for (const p of missingRedirects) { + console.log(` - ${p} -> /:locale/REPLACE_WITH_NEW_PATH`); + } + console.log(""); + console.log( + `Open ${CONFIG_FILE} and search for 'REPLACE_WITH_NEW_PATH' to find them.` + ); + console.log(""); + + exitCode = 1; + } else { + console.log( + colors.red( + "══════════════════════════════════════════════════════════════" + ) + ); + console.log( + colors.red( + `ERROR: Found ${missingRedirects.length} deleted file(s) without redirects!` + ) + ); + console.log( + colors.red( + "══════════════════════════════════════════════════════════════" + ) + ); + console.log(""); + console.log( + "When you delete a markdown file, you must add a redirect in next.config.ts" + ); + console.log( + "to prevent broken links for users who have bookmarked the old URL." + ); + console.log(""); + console.log(colors.yellow("Missing redirects for:")); + for (const p of missingRedirects) { + console.log(` - ${p}`); + } + console.log(""); + console.log( + colors.yellow( + "Add the following to the redirects array in next.config.ts:" + ) + ); + console.log(""); + for (const entry of suggestedEntries) { + console.log(entry); + } + console.log(""); + } +} + +if (invalidRedirects.length > 0) { + console.log(""); + console.log( + colors.red("══════════════════════════════════════════════════════════════") + ); + console.log( + colors.red( + `ERROR: Found ${invalidRedirects.length} invalid redirect(s) in config!` + ) + ); + console.log( + colors.red("══════════════════════════════════════════════════════════════") + ); + console.log(""); + for (const invalid of invalidRedirects) { + console.log(` - ${invalid}`); + } + console.log(""); + console.log(colors.yellow("How to fix:")); + console.log(" 1. Open next.config.ts"); + console.log(" 2. Find the redirect(s) listed above"); + console.log(" 3. Update the destination to a valid page path"); + console.log(" (Check that the path exists under app/en/)"); +} + +if (exitCode === 0) { + console.log( + colors.green( + "══════════════════════════════════════════════════════════════" + ) + ); + console.log(colors.green("SUCCESS: All redirects are valid!")); + console.log( + colors.green( + "══════════════════════════════════════════════════════════════" + ) + ); +} + +process.exit(exitCode); diff --git a/scripts/update-internal-links.ts b/scripts/update-internal-links.ts new file mode 100644 index 000000000..da10d93c9 --- /dev/null +++ b/scripts/update-internal-links.ts @@ -0,0 +1,281 @@ +#!/usr/bin/env npx tsx + +/** + * Update internal links to use redirect destinations instead of old paths + * + * Usage: + * pnpm update-links [--dry-run] + * + * This script reads redirects from next.config.ts and updates any internal links + * in MDX/TSX files that point to redirected paths. + */ + +import { readFileSync, writeFileSync } from "node:fs"; +import fg from "fast-glob"; + +// Colors for terminal output +const colors = { + red: (s: string) => `\x1b[0;31m${s}\x1b[0m`, + green: (s: string) => `\x1b[0;32m${s}\x1b[0m`, + yellow: (s: string) => `\x1b[1;33m${s}\x1b[0m`, + blue: (s: string) => `\x1b[0;34m${s}\x1b[0m`, +}; + +// Parse command line arguments +const dryRun = process.argv.includes("--dry-run"); + +const CONFIG_FILE = "next.config.ts"; + +// Top-level regex patterns +const LOCALE_PREFIX_REGEX = /^\/:locale/; +const SPECIAL_REGEX_CHARS_REGEX = /[.*+?^${}()|[\]\\]/g; +const REDIRECT_REGEX = + /\{\s*source:\s*["']([^"']+)["']\s*,\s*destination:\s*["']([^"']+)["']/g; +const REVERSED_REDIRECT_REGEX = + /\{\s*destination:\s*["']([^"']+)["']\s*,\s*source:\s*["']([^"']+)["']/g; + +type Redirect = { + source: string; + destination: string; +}; + +/** + * Execute regex and collect all matches (avoids assignment in expression) + */ +function collectRegexMatches( + regex: RegExp, + content: string, + sourceIndex: number, + destIndex: number +): Array<{ source: string; destination: string }> { + const results: Array<{ source: string; destination: string }> = []; + regex.lastIndex = 0; + + let match = regex.exec(content); + while (match !== null) { + results.push({ + source: match[sourceIndex], + destination: match[destIndex], + }); + match = regex.exec(content); + } + + return results; +} + +/** + * Parse redirects from next.config.ts + */ +function parseRedirects(content: string): Redirect[] { + const results: Redirect[] = []; + + // Collect standard format: { source: "...", destination: "..." } + const standardMatches = collectRegexMatches(REDIRECT_REGEX, content, 1, 2); + for (const m of standardMatches) { + results.push(m); + } + + // Collect reversed format: { destination: "...", source: "..." } + const reversedMatches = collectRegexMatches( + REVERSED_REDIRECT_REGEX, + content, + 2, + 1 + ); + for (const m of reversedMatches) { + results.push(m); + } + + return results; +} + +/** + * Filter redirects to only those that can be auto-updated + */ +function getUpdatableRedirects(redirectList: Redirect[]): Redirect[] { + return redirectList.filter((r) => { + if (r.source.includes(":path*") || r.destination.includes(":path*")) { + return false; + } + + const sourceWithoutLocale = r.source.replace(LOCALE_PREFIX_REGEX, ""); + const destWithoutLocale = r.destination.replace(LOCALE_PREFIX_REGEX, ""); + + if (sourceWithoutLocale.includes(":") || destWithoutLocale.includes(":")) { + return false; + } + + if ( + r.destination.includes("REPLACE_WITH") || + r.destination.includes("TODO") + ) { + return false; + } + + return true; + }); +} + +/** + * Convert redirect to link patterns (without :locale prefix) + */ +function redirectToLinkPatterns(redirect: Redirect): { + oldPath: string; + newPath: string; +} { + return { + oldPath: redirect.source.replace(LOCALE_PREFIX_REGEX, ""), + newPath: redirect.destination.replace(LOCALE_PREFIX_REGEX, ""), + }; +} + +/** + * Escape special regex characters + */ +function escapeRegex(str: string): string { + return str.replace(SPECIAL_REGEX_CHARS_REGEX, "\\$&"); +} + +/** + * Update links in file content + */ +function updateLinksInContent( + content: string, + oldPath: string, + newPath: string +): string { + let updated = content; + + // Markdown links: [text](/old-path) -> [text](/new-path) + // Using replacer function to avoid special replacement pattern interpretation + // (e.g., $1, $2, $& in newPath would be incorrectly expanded if using string) + const markdownLinkRegex = new RegExp( + `(\\]\\()${escapeRegex(oldPath)}([)"#\\s])`, + "g" + ); + updated = updated.replace( + markdownLinkRegex, + (_, p1, p2) => `${p1}${newPath}${p2}` + ); + + // JSX href with double quotes: href="/old-path" + const jsxDoubleQuoteRegex = new RegExp( + `(href=")${escapeRegex(oldPath)}(["#])`, + "g" + ); + updated = updated.replace( + jsxDoubleQuoteRegex, + (_, p1, p2) => `${p1}${newPath}${p2}` + ); + + // JSX href with single quotes: href='/old-path' + const jsxSingleQuoteRegex = new RegExp( + `(href=')${escapeRegex(oldPath)}(['#])`, + "g" + ); + updated = updated.replace( + jsxSingleQuoteRegex, + (_, p1, p2) => `${p1}${newPath}${p2}` + ); + + // Paths in backticks (code blocks showing links) + const backtickRegex = new RegExp(`(\`)${escapeRegex(oldPath)}(\`|[#])`, "g"); + updated = updated.replace( + backtickRegex, + (_, p1, p2) => `${p1}${newPath}${p2}` + ); + + return updated; +} + +// ============================================================ +// Main script +// ============================================================ + +if (dryRun) { + console.log( + colors.blue("Running in dry-run mode - no files will be modified") + ); + console.log(""); +} + +console.log(colors.blue(`Parsing redirects from ${CONFIG_FILE}...`)); + +const configContent = readFileSync(CONFIG_FILE, "utf-8"); +const allRedirects = parseRedirects(configContent); +const redirects = getUpdatableRedirects(allRedirects); + +console.log( + `Found ${colors.green(String(redirects.length))} non-wildcard redirects to check` +); +console.log(""); + +if (redirects.length === 0) { + console.log(colors.green("No redirects to process.")); + process.exit(0); +} + +console.log(colors.blue("Scanning files for internal links to update...")); +console.log(""); + +// Find all MDX, MD, and TSX files in app directory +const files = fg.sync(["app/**/*.mdx", "app/**/*.md", "app/**/*.tsx"], { + ignore: ["**/node_modules/**"], +}); + +let updatedCount = 0; + +for (const file of files) { + const originalContent = readFileSync(file, "utf-8"); + let updatedContent = originalContent; + + for (const redirect of redirects) { + const { oldPath, newPath } = redirectToLinkPatterns(redirect); + + if (updatedContent.includes(oldPath)) { + updatedContent = updateLinksInContent(updatedContent, oldPath, newPath); + } + } + + if (updatedContent !== originalContent) { + if (dryRun) { + console.log(`${colors.yellow("Would update:")} ${file}`); + + const lines = originalContent.split("\n"); + const newLines = updatedContent.split("\n"); + for (let i = 0; i < lines.length; i += 1) { + if (lines[i] !== newLines[i]) { + console.log(colors.red(` - ${lines[i].trim()}`)); + console.log(colors.green(` + ${newLines[i].trim()}`)); + } + } + console.log(""); + } else { + writeFileSync(file, updatedContent); + console.log(`${colors.green("Updated:")} ${file}`); + } + updatedCount += 1; + } +} + +console.log(""); +console.log( + colors.blue("========================================================") +); + +if (dryRun) { + console.log( + colors.yellow(`Dry run complete: ${updatedCount} file(s) would be updated`) + ); + console.log("Run without --dry-run to apply changes."); +} else if (updatedCount > 0) { + console.log( + colors.green(`Updated ${updatedCount} file(s) with new link paths`) + ); +} else { + console.log(colors.green("All internal links are up to date!")); +} + +console.log( + colors.blue("========================================================") +); diff --git a/scripts/vale-editorial.ts b/scripts/vale-editorial.ts index f2daa1f89..46a5d799c 100644 --- a/scripts/vale-editorial.ts +++ b/scripts/vale-editorial.ts @@ -46,6 +46,8 @@ const MAX_AI_TOKENS = 8192; const OWNER = "ArcadeAI"; const REPO = "docs"; const EDITORIAL_COMMENT_REGEX = //; +const CODE_FENCE_OPEN_REGEX = /^```(?:mdx?|markdown)?\n/; +const CODE_FENCE_CLOSE_REGEX = /\n```$/; const HTTP_UNPROCESSABLE_ENTITY = 422; const MEGABYTE = KILOBYTE * KILOBYTE; const MAX_BUFFER_MB = 10; @@ -84,6 +86,15 @@ type ValeIssue = { type ValeOutput = Record; +// Strip markdown code fences from the beginning and end of LLM responses +// LLMs often wrap their output in ```mdx or ```markdown blocks +function stripCodeFences(content: string): string { + let result = content.trim(); + result = result.replace(CODE_FENCE_OPEN_REGEX, ""); + result = result.replace(CODE_FENCE_CLOSE_REGEX, ""); + return result; +} + // Run Vale on content and return issues function runValeOnContent(filename: string, content: string): ValeIssue[] { // Create temp directory and file with correct extension @@ -155,7 +166,9 @@ async function fixValeIssues( messages: [{ role: "user", content: prompt }], }); const textBlock = response.content.find((b) => b.type === "text"); - return textBlock?.type === "text" ? textBlock.text : content; + return textBlock?.type === "text" + ? stripCodeFences(textBlock.text) + : content; } const response = await ai.client.chat.completions.create({ @@ -163,7 +176,8 @@ async function fixValeIssues( max_tokens: MAX_AI_TOKENS, messages: [{ role: "user", content: prompt }], }); - return response.choices[0]?.message?.content ?? content; + const result = response.choices[0]?.message?.content; + return result ? stripCodeFences(result) : content; } catch (error) { console.error("Error fixing Vale issues:", error); return content; @@ -266,7 +280,7 @@ async function getEditorialFromAnthropic( return null; } - const revisedContent = textBlock.text.trim(); + const revisedContent = stripCodeFences(textBlock.text); if (revisedContent === "NO_CHANGES_NEEDED") { return null; @@ -298,8 +312,12 @@ async function getEditorialFromOpenAI( messages: [{ role: "user", content: prompt }], }); - const revisedContent = response.choices[0]?.message?.content?.trim(); - if (!revisedContent || revisedContent === "NO_CHANGES_NEEDED") { + const rawContent = response.choices[0]?.message?.content; + if (!rawContent) { + return null; + } + const revisedContent = stripCodeFences(rawContent); + if (revisedContent === "NO_CHANGES_NEEDED") { return null; } From c39a20763c673dbbb8a217b6bf1936e40cf961b1 Mon Sep 17 00:00:00 2001 From: Mateo Torres Date: Wed, 21 Jan 2026 12:47:20 -0300 Subject: [PATCH 4/6] redirects --- .../langchain/use-arcade-tools/page.mdx | 243 ------------ .../langchain/user-auth-interrupts/page.mdx | 373 ------------------ 2 files changed, 616 deletions(-) delete mode 100644 app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx delete mode 100644 app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx diff --git a/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx deleted file mode 100644 index a16ccd5b4..000000000 --- a/app/en/get-started/agent-frameworks/langchain/use-arcade-tools/page.mdx +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: "Use Arcade tools with LangGraph" -description: "Integrate Arcade tools into your LangGraph applications" ---- - -import { Steps, Tabs, Callout } from "nextra/components"; - -## Use LangGraph with Arcade - -In this guide, let's explore how to integrate Arcade tools into your LangGraph application. Follow the step-by-step instructions below. For complete working examples, see our [Python](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain/langgraph_arcade_minimal.py) and [JavaScript](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain-ts/langgraph-arcade-minimal.ts) examples. - - - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys) - -### Set up your environment - -Install the required packages, and ensure your environment variables are set with your Arcade and OpenAI API keys: - - - -```bash -pip install langchain-arcade langchain-openai langgraph -``` - - -```bash -npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph -``` - - - - -### Configure API keys - -Provide your Arcade and OpenAI API keys. You can store them in environment variables or directly in your code: - -> Need an Arcade API key? Visit the [Get an API key](/get-started/setup/api-keys) page to create one. - - - -```python -import os - -arcade_api_key = os.environ.get("ARCADE_API_KEY", "YOUR_ARCADE_API_KEY") -openai_api_key = os.environ.get("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY") -``` - - -```bash -ARCADE_API_KEY= -OPENAI_API_KEY= -``` - - - -### Create and manage Arcade tools - - - -Use the ArcadeToolManager to retrieve specific tools or entire MCP Servers: - -```python -from langchain_arcade import ArcadeToolManager - -manager = ArcadeToolManager(api_key=arcade_api_key) - -# Fetch the "ScrapeUrl" tool from the "Firecrawl" MCP Server -tools = manager.get_tools(tools=["Firecrawl.ScrapeUrl"]) -print(manager.tools) - -# Get all tools from the "Gmail" MCP Server -tools = manager.get_tools(toolkits=["Gmail"]) -print(manager.tools) -``` - - -Arcade offers methods to convert tools into Zod schemas, which is essential since LangGraph defines tools using Zod. The `toZod` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with LangGraph. Learn more about Arcade's Zod integration options [here](/guides/tool-calling/custom-apps/get-tool-definitions#get-zod-tool-definitions). -```javascript -import { Arcade } from "@arcadeai/arcadejs"; -import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib"; -import { tool } from "@langchain/core/tools"; - -// Initialize the Arcade client -const arcade = new Arcade(); - -// Get the Arcade tools, you can customize the MCP Server (e.g. "github", "notion", "gmail", etc.) -const googleToolkit = await arcade.tools.list({ toolkit: "gmail", limit: 30 }); -const arcadeTools = toZod({ - tools: googleToolkit.items, - client: arcade, - userId: "", // Replace this with your application's user ID (e.g. email address, UUID, etc.) -}); -// Convert Arcade tools to LangGraph tools -const tools = arcadeTools.map(({ name, description, execute, parameters }) => - tool(execute, { - name, - description, - schema: parameters, - }), -); -console.log(tools); -``` - - - - -### Set up the language model and memory - -Create an AI model and bind your tools. Use MemorySaver for checkpointing: - - - -```python -from langchain_openai import ChatOpenAI -from langgraph.checkpoint.memory import MemorySaver - -model = ChatOpenAI(model="gpt-4o", api_key=openai_api_key) -bound_model = model.bind_tools(tools) - -memory = MemorySaver() -``` - - -```javascript -import { ChatOpenAI } from "@langchain/openai"; -import { MemorySaver } from "@langchain/langgraph"; - -const model = new ChatOpenAI({ model: "gpt-4o", apiKey: process.env.OPENAI_API_KEY }); -const boundModel = model.bindTools(tools); -const memory = new MemorySaver(); -``` - - - -### Create a ReAct-style agent - -Use the prebuilt ReAct agent from LangGraph to handle your Arcade tools: - - -```python -from langgraph.prebuilt import create_react_agent - -graph = create_react_agent(model=bound_model, tools=tools, checkpointer=memory) -``` - - -```javascript -import { createReactAgent } from "@langchain/langgraph/prebuilt"; - -const graph = createReactAgent({ llm: boundModel, tools, checkpointer: memory }); -``` - - - -### Provide configuration and user query - -Supply a basic config dictionary and a user query. Notice that user_id is required for tool authorization: - - -```python -config = { - "configurable": { - "thread_id": "1", - "user_id": "{arcade_user_id}" - } -} -user_input = { - "messages": [ - ("user", "List any new and important emails in my inbox.") - ] -} -``` - - -```javascript -const config = { - configurable: { - thread_id: "1", - user_id: "{arcade_user_id}", - }, - streamMode: "values" as const, -}; -const user_input = { - messages: [ - { - role: "user", - content: "List any new and important emails in my inbox.", - }, - ], -}; -``` - - - -### Stream the response - -Stream the assistant's output. If the tool requires authorization, the agent will ask the user to authorize the tool. - - - -```python -from langgraph.errors import NodeInterrupt - -try: - for chunk in graph.stream(user_input, config, stream_mode="values"): - chunk["messages"][-1].pretty_print() -except NodeInterrupt as exc: - print(f"\nNodeInterrupt occurred: {exc}") - print("Please authorize the tool or update the request, then re-run.") -``` - - -```javascript -try { - const stream = await graph.stream(user_input, config); - for await (const chunk of stream) { - console.log(chunk.messages[chunk.messages.length - 1]); - } -} catch (error) { - console.error("Error streaming response:", error); -} -``` - - - - -## Tips for selecting tools - -- **Relevance**: Pick only the tools you need. Avoid using all tools at once. -- **Avoid conflicts**: Be mindful of duplicate or overlapping functionality. - -## Next steps - -Now that you have integrated Arcade tools into your LangGraph agent, you can: - -- Experiment with different MCP Servers, such as "Math" or "Search." -- Customize the agent's prompts for specific tasks. -- Try out other language models and compare their performance. - -Enjoy exploring Arcade and building powerful AI-enabled Python applications! diff --git a/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx b/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx deleted file mode 100644 index d52678880..000000000 --- a/app/en/get-started/agent-frameworks/langchain/user-auth-interrupts/page.mdx +++ /dev/null @@ -1,373 +0,0 @@ ---- -title: "Using Arcade User Auth" -description: "Build a custom LangGraph that handles tool authorization with Arcade" ---- - -import { Steps, Tabs, Callout } from "nextra/components"; - -## User Authorization in LangGraph - -In this guide, you will create a LangGraph workflow that requires user authorization before running certain Arcade tools. When a tool needs authorization, the graph displays an authorization URL and waits for the user's approval. This ensures that only the tools you explicitly authorize are available to the language model. For complete working examples, see our [Python](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain/langgraph_with_user_auth.py) and [JavaScript](https://github.com/ArcadeAI/arcade-ai/blob/main/examples/langchain-ts/langgraph-with-user-auth.ts) examples. - - - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys) - -### Install the required packages - -Set up your environment with the following installations: - - - -```bash -pip install langchain-arcade langchain-openai langgraph -``` - - -```bash -npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph -``` - - - -### Configure your Arcade environment - -Make sure you have set your Arcade API key (and any other relevant keys) in the environment, or assign them directly in the code: - -> Need an Arcade API key? Visit the [Get an API key](/get-started/setup/api-keys) page to create one. - - - -```python -import os - -# Import necessary classes and modules -from langchain_arcade import ArcadeToolManager -from langchain_openai import ChatOpenAI -from langgraph.checkpoint.memory import MemorySaver -from langgraph.graph import END, START, MessagesState, StateGraph -from langgraph.prebuilt import ToolNode -from langchain_core.runnables import RunnableConfig - -arcade_api_key = os.environ["ARCADE_API_KEY"] - -# Initialize the tool manager and fetch tools compatible with langgraph -tool_manager = ArcadeToolManager(api_key=arcade_api_key) -tools = tool_manager.get_tools(toolkits=["Gmail"]) -tool_node = ToolNode(tools) - -# Create a language model instance and bind it with the tools -model = ChatOpenAI(model="gpt-4o") -model_with_tools = model.bind_tools(tools) -``` - -Here are the main code elements: - -- arcade_api_key is your Arcade key. -- tool_manager fetches your Arcade tools, for example the "Gmail" MCP Server. -- tool_node encapsulates these tools for usage in LangGraph. -- model_with_tools binds your tools to the "gpt-4o" language model, enabling tool calls. - - - -```javascript -import { pathToFileURL } from "node:url"; -import { Arcade } from "@arcadeai/arcadejs"; -import { toZod } from "@arcadeai/arcadejs/lib"; -import type { AIMessage } from "@langchain/core/messages"; -import { tool } from "@langchain/core/tools"; -import { MessagesAnnotation, StateGraph } from "@langchain/langgraph"; -import { ToolNode } from "@langchain/langgraph/prebuilt"; -import { ChatOpenAI } from "@langchain/openai"; - -// Initialize Arcade with API key from environment -const arcade = new Arcade(); - -// Replace with your application's user ID (e.g. email address, UUID, etc.) -const USER_ID = "{arcade_user_id}"; - -// Initialize tools from Gmail MCP Server -const googleToolkit = await arcade.tools.list({ toolkit: "gmail", limit: 30 }); -const arcadeTools = toZod({ - tools: googleToolkit.items, - client: arcade, - userId: USER_ID, -}); - -// Convert Arcade tools to LangGraph tools -const tools = arcadeTools.map(({ name, description, execute, parameters }) => - tool(execute, { - name, - description, - schema: parameters, - }), -); - -// Initialize the prebuilt tool node -const toolNode = new ToolNode(tools); - -// Create a language model instance and bind it with the tools -const model = new ChatOpenAI({ - model: "gpt-4o", - apiKey: process.env.OPENAI_API_KEY, -}); -const modelWithTools = model.bindTools(tools); -``` - -Here are the main code elements: - -- arcade.tools.list fetches your Arcade tools, for example the "Gmail" MCP Server. -- toZod converts Arcade tools to Zod schemas, which are required by LangGraph. -- ToolNode encapsulates these tools for usage in LangGraph. -- modelWithTools binds your tools to the "gpt-4o" language model, enabling tool calls. - - - - -### Define the workflow steps - -You will create three primary functions to handle AI interaction, tool authorization, and flow control. - - -```python -# Function to invoke the model and get a response -def call_agent(state: MessagesState): - messages = state["messages"] - response = model_with_tools.invoke(messages) - # Return the updated message history - return {"messages": [response]} - - -# Function to determine the next step in the workflow based on the last message -def should_continue(state: MessagesState): - if state["messages"][-1].tool_calls: - for tool_call in state["messages"][-1].tool_calls: - if tool_manager.requires_auth(tool_call["name"]): - return "authorization" - return "tools" # Proceed to tool execution if no authorization is needed - return END # End the workflow if no tool calls are present - - -# Function to handle authorization for tools that require it -def authorize(state: MessagesState, config: RunnableConfig | None = None): - if config is None: - raise ValueError("Config is required for authorization") - - user_id = config["configurable"].get("user_id") - for tool_call in state["messages"][-1].tool_calls: - tool_name = tool_call["name"] - if not tool_manager.requires_auth(tool_name): - continue - auth_response = tool_manager.authorize(tool_name, user_id) - if auth_response.status != "completed": - # Prompt the user to visit the authorization URL - print(f"Visit the following URL to authorize: {auth_response.url}") - - # Wait for the user to complete the authorization - # and then check the authorization status again - tool_manager.wait_for_auth(auth_response.id) - if not tool_manager.is_authorized(auth_response.id): - # This stops execution if authorization fails - raise ValueError("Authorization failed.") - - return {"messages": []} -``` -Explanations for these functions: - -- call_agent: Invokes the language model using the latest conversation state. -- should_continue: Checks the last AI message for any tool calls. If a tool requires authorization, the flow transitions to authorization. Otherwise, it goes straight to tool execution or ends if no tools are called. -- authorize: Prompts the user to authorize any required tools, blocking until authorization is completed successfully or fails. - - -```javascript -// Function to check if a tool requires authorization -async function requiresAuth(toolName: string): Promise<{ - needsAuth: boolean; - id: string; - authUrl: string; -}> { - const authResponse = await arcade.tools.authorize({ - tool_name: toolName, - user_id: USER_ID, - }); - return { - needsAuth: authResponse.status === "pending", - id: authResponse.id ?? "", - authUrl: authResponse.url ?? "", - }; -} - -// Function to invoke the model and get a response -async function callAgent( - state: typeof MessagesAnnotation.State, -): Promise { - const messages = state.messages; - const response = await modelWithTools.invoke(messages); - return { messages: [response] }; -} - -// Function to determine the next step in the workflow based on the last message -async function shouldContinue( - state: typeof MessagesAnnotation.State, -): Promise { - const lastMessage = state.messages[state.messages.length - 1] as AIMessage; - if (lastMessage.tool_calls?.length) { - for (const toolCall of lastMessage.tool_calls) { - const { needsAuth } = await requiresAuth(toolCall.name); - if (needsAuth) { - return "authorization"; - } - } - return "tools"; // Proceed to tool execution if no authorization is needed - } - return "__end__"; // End the workflow if no tool calls are present -} - -// Function to handle authorization for tools that require it -async function authorize( - state: typeof MessagesAnnotation.State, -): Promise { - const lastMessage = state.messages[state.messages.length - 1] as AIMessage; - for (const toolCall of lastMessage.tool_calls || []) { - const toolName = toolCall.name; - const { needsAuth, id, authUrl } = await requiresAuth(toolName); - if (needsAuth) { - // Prompt the user to visit the authorization URL - console.log(`Visit the following URL to authorize: ${authUrl}`); - - // Wait for the user to complete the authorization - const response = await arcade.auth.waitForCompletion(id); - if (response.status !== "completed") { - throw new Error("Authorization failed"); - } - } - } - - return { messages: [] }; -} -``` - -Explanations for these functions: - -- requiresAuth: Checks if a tool requires authorization. -- callAgent: Invokes the language model using the latest conversation state. -- shouldContinue: Checks the last AI message for any tool calls. If a tool requires authorization, the flow transitions to authorization. Otherwise, it goes straight to tool execution or ends if no tools are called. -- authorize: Prompts the user to authorize any required tools, blocking until authorization is completed successfully or fails. - - - - -### Build and compile your LangGraph workflow - -Use StateGraph to assemble the nodes and edges, then compile the graph with a MemorySaver. - - - -```python -if __name__ == "__main__": - # Build the workflow graph using StateGraph - workflow = StateGraph(MessagesState) - - # Add nodes (steps) to the graph - workflow.add_node("agent", call_agent) - workflow.add_node("tools", tool_node) - workflow.add_node("authorization", authorize) - - # Define the edges and control flow between nodes - workflow.add_edge(START, "agent") - workflow.add_conditional_edges("agent", should_continue, ["authorization", "tools", END]) - workflow.add_edge("authorization", "tools") - workflow.add_edge("tools", "agent") - - # Set up memory for checkpointing the state - memory = MemorySaver() - - # Compile the graph with the checkpointer - graph = workflow.compile(checkpointer=memory) -``` - - -```javascript -// Build the workflow graph -const workflow = new StateGraph(MessagesAnnotation) - .addNode("agent", callAgent) - .addNode("tools", toolNode) - .addNode("authorization", authorize) - .addEdge("__start__", "agent") - .addConditionalEdges("agent", shouldContinue, [ - "authorization", - "tools", - "__end__", - ]) - .addEdge("authorization", "tools") - .addEdge("tools", "agent"); - -// Compile the graph -const graph = workflow.compile(); -``` - - - -### Provide inputs and run the graph - -Finally, define user-supplied messages, authorization config, and stream the outputs. The graph will pause for any required tool authorization. - - - -```python -# Define the input messages from the user -inputs = { - "messages": [ - { - "role": "user", - "content": "Check and see if I have any emails in my inbox", - } - ], -} - -# Configuration with thread and user IDs for authorization purposes -config = {"configurable": {"thread_id": "4", "user_id": "{arcade_user_id}"}} - -# Run the graph and stream the outputs -for chunk in graph.stream(inputs, config=config, stream_mode="values"): - # Pretty-print the last message in the chunk - chunk["messages"][-1].pretty_print() -``` - - -```javascript -const inputs = { - messages: [ - { - role: "user", - content: "Check and see if I have any important emails in my inbox", - }, - ], -}; -// Run the graph and stream the outputs -const stream = await graph.stream(inputs, { streamMode: "values" }); -for await (const chunk of stream) { - // Print the last message in the chunk - console.log(chunk.messages[chunk.messages.length - 1].content); -} -``` - - - -In this example: - -- The user prompts the agent to check emails. -- The message triggers a potential need for the "Gmail" MCP Server. -- If authorization is required, the code prints a URL and waits until you permit the tool call. - - - -## Next steps - -- Experiment with more Arcade MCP Servers for expanded capabilities. -- Explore advanced authorization logic, such as multi-user or role-based checks. -- Integrate additional nodes to handle more complex flows or multi-step tasks in your LangGraph. - -By combining Arcade's authorization features with stateful management in LangGraph, you can build AI-driven workflows that respect user permissions at every step. Have fun exploring Arcade! From 752ad719e18fd0523182d5e9a745275fbdf8e26b Mon Sep 17 00:00:00 2001 From: Mateo Torres Date: Wed, 21 Jan 2026 12:50:29 -0300 Subject: [PATCH 5/6] redirect? --- next.config.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/next.config.ts b/next.config.ts index 9084e5925..ad865469f 100644 --- a/next.config.ts +++ b/next.config.ts @@ -23,6 +23,21 @@ const nextConfig: NextConfig = withLlmsTxt({ withNextra({ async redirects() { return [ + // Removed LangChain old stuff + { + source: + "/:locale/get-started/agent-frameworks/langchain/use-arcade-tools", + destination: + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", + permanent: true, + }, + { + source: + "/:locale/get-started/agent-frameworks/langchain/user-auth-interrupts", + destination: + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", + permanent: true, + }, // Moved from guides to get-started { source: @@ -463,7 +478,7 @@ const nextConfig: NextConfig = withLlmsTxt({ { source: "/:locale/guides/agent-frameworks/langchain/python", destination: - "/:locale/get-started/agent-frameworks/langchain/use-arcade-tools", + "/:locale/get-started/agent-frameworks/langchain/use-arcade-with-langchain", permanent: true, }, { From c38d930bb23bb9b70d80d50c0b2165dd350b8a71 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Jan 2026 15:51:12 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=A4=96=20Regenerate=20LLMs.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/llms.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/llms.txt b/public/llms.txt index 67d3dc127..b9466f87b 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,4 +1,4 @@ - + # Arcade @@ -120,7 +120,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Environment Variables](https://docs.arcade.dev/en/resources/integrations/social-communication/slack/environment-variables.md): This documentation page provides guidance on configuring environment variables related to Slack API interactions, specifically `SLACK_MAX_CONCURRENT_REQUESTS`, `MAX_PAGINATION_SIZE_LIMIT`, and `MAX_PAGINATION_TIMEOUT_SECONDS`. Users will learn how to adjust these settings - [Evaluate Tools](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools.md): The "Evaluate Tools" documentation page provides guidance on systematically testing and enhancing tools using Arcade's evaluation framework. It helps users validate the performance of their tools after initial development and offers techniques for iterative improvements to ensure reliability in production. - [ExaApi](https://docs.arcade.dev/en/resources/integrations/search/exa-api.md): The ExaApi documentation provides users with a comprehensive guide to utilizing the Exa.ai Search API, enabling them to conduct searches, manage websets, and handle research requests effectively. It outlines various tools available within the API, detailing their functionalities such as -- [Fetch the "ScrapeUrl" tool from the "Firecrawl" MCP Server](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/use-arcade-tools.md): This documentation page provides a comprehensive guide on integrating Arcade tools into LangGraph applications, detailing prerequisites, environment setup, API key configuration, and tool management. Users will learn how to create and manage AI models, configure agents, and stream responses while leveraging specific - [Figma](https://docs.arcade.dev/en/resources/integrations/development/figma.md): This documentation page provides users with a comprehensive guide to the Figma MCP Server, enabling interaction with Figma's design files, components, and collaboration features through the Figma REST API. Users can learn to access file structures, manage components, add comments - [FigmaApi](https://docs.arcade.dev/en/resources/integrations/productivity/figma-api.md): The FigmaApi documentation provides a comprehensive guide for developers to utilize tools that enable interaction with the Figma API, facilitating efficient management of design assets and collaboration on projects. Users can learn how to perform various actions such as retrieving Figma files, managing - [Firecrawl](https://docs.arcade.dev/en/resources/integrations/development/firecrawl.md): The Firecrawl documentation provides users with a comprehensive guide to utilizing the Arcade Firecrawl MCP Server, enabling them to build agents and AI applications for scraping, crawling, and mapping websites. It outlines available tools, including functionalities for scraping URLs, crawling websites, @@ -165,7 +164,6 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [HubspotUsersApi](https://docs.arcade.dev/en/resources/integrations/sales/hubspot-users-api.md): The HubspotUsersApi documentation provides users with tools to efficiently manage users and teams within a HubSpot account, including functionalities for retrieving user lists, creating and updating user accounts, and removing users. It offers detailed descriptions of available API tools, along with - [Imgflip](https://docs.arcade.dev/en/resources/integrations/entertainment/imgflip.md): The Imgflip documentation provides users with tools to create and manage memes using the Imgflip API, enabling the development of agents and AI applications. Users can search for meme templates, retrieve popular memes, and create custom memes by adding text to existing templates. - [Imgflip](https://docs.arcade.dev/en/resources/integrations/entertainment/spotify/imgflip.md): The Imgflip documentation page provides users with tools to create and manage memes using the Imgflip API, allowing them to search for meme templates, retrieve popular templates, and create custom memes. It outlines the available features, including a premium search option and customizable -- [Import necessary classes and modules](https://docs.arcade.dev/en/get-started/agent-frameworks/langchain/user-auth-interrupts.md): This documentation page guides users in creating a LangGraph workflow that incorporates user authorization for specific Arcade tools, ensuring that only authorized tools are accessible to the language model. It provides step-by-step instructions on setting up the environment, defining workflow functions, and compiling - [In Custom Applications](https://docs.arcade.dev/en/guides/tool-calling/custom-apps.md): This documentation page provides guidance on integrating Arcade tools into custom applications, focusing on user authentication, authorization status checking, and managing tool definitions. It serves as a resource for developers building tool-calling interfaces to ensure proper implementation and functionality. - [Initialize the Arcade client](https://docs.arcade.dev/en/get-started/agent-frameworks/google-adk/use-arcade-tools.md): This documentation page provides a comprehensive guide for integrating Arcade tools into Google ADK applications, detailing the necessary prerequisites, environment setup, and configuration steps. Users will learn how to create and manage Arcade tools, authorize them for agents, and run these agents with - [IntercomApi](https://docs.arcade.dev/en/resources/integrations/customer-support/intercom-api.md): The IntercomApi documentation provides a comprehensive guide to tools that enable users to interact with the Intercom platform using OAuth2 authentication. It details various functionalities, such as managing admin information, creating and updating articles, and handling company data, allowing users to