diff --git a/.github/workflows/generate-markdown.yml b/.github/workflows/generate-markdown.yml deleted file mode 100644 index 245997998..000000000 --- a/.github/workflows/generate-markdown.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Generate Clean Markdown - -on: - workflow_dispatch: - pull_request: - types: [opened, synchronize, reopened] - paths: - - "app/**/*.mdx" - - "app/**/_meta.tsx" - - "scripts/generate-clean-markdown.ts" - -permissions: - contents: write - pull-requests: write - -jobs: - generate-markdown: - name: Generate Clean Markdown - runs-on: ubuntu-latest - - permissions: - contents: write - pull-requests: write - - steps: - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: "22.x" - - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.ref || github.ref }} - token: ${{ secrets.DOCS_PUBLISHABLE_GH_TOKEN }} - # Fetch full git history so Nextra can get accurate timestamps - fetch-depth: 0 - - - name: Install pnpm - run: npm install -g pnpm - - - name: Install dependencies - run: pnpm install - - - name: Build Next.js - run: pnpm build - env: - # Skip postbuild to avoid circular dependency - SKIP_POSTBUILD: "true" - - - name: Generate clean markdown - run: pnpm generate:markdown - - - name: Check for changes - id: check-changes - run: | - if [ -n "$(git status --porcelain public/_markdown/)" ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - else - echo "has_changes=false" >> $GITHUB_OUTPUT - fi - - - name: Commit changes to PR - if: steps.check-changes.outputs.has_changes == 'true' && github.event_name == 'pull_request' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add public/_markdown/ - git commit -m "Regenerate clean markdown files" - git push - - - name: Create Pull Request (for manual runs) - if: steps.check-changes.outputs.has_changes == 'true' && github.event_name != 'pull_request' - id: cpr - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.DOCS_PUBLISHABLE_GH_TOKEN }} - commit-message: Regenerate clean markdown files - branch: auto-update-clean-markdown - delete-branch: true - title: "Regenerate clean markdown files" - reviewers: > - evantahler - torresmateo - - - name: Enable Pull Request Automerge - if: steps.check-changes.outputs.has_changes == 'true' && github.event_name != 'pull_request' - run: gh pr merge --squash --auto ${{ steps.cpr.outputs.pull-request-number }} - env: - GH_TOKEN: ${{ secrets.DOCS_PUBLISHABLE_GH_TOKEN }} diff --git a/.github/workflows/llmstxt.yml b/.github/workflows/llmstxt.yml index 55fc3091e..ad23ffbb0 100644 --- a/.github/workflows/llmstxt.yml +++ b/.github/workflows/llmstxt.yml @@ -2,22 +2,15 @@ name: Generate LLMs.txt on: workflow_dispatch: - pull_request: - types: [opened, synchronize, reopened] permissions: - contents: write - pull-requests: write + contents: read jobs: llmstxt: name: Generate LLMSTXT runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - name: Use Node.js uses: actions/setup-node@v4 @@ -27,7 +20,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.ref || github.ref }} + ref: ${{ github.ref }} token: ${{ secrets.DOCS_PUBLISHABLE_GH_TOKEN }} - name: Install dependencies @@ -40,41 +33,3 @@ jobs: run: pnpm llmstxt env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - - - name: Check for changes - id: check-changes - run: | - if [ -n "$(git status --porcelain)" ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - else - echo "has_changes=false" >> $GITHUB_OUTPUT - fi - - - name: Commit changes to PR - if: steps.check-changes.outputs.has_changes == 'true' && github.event_name == 'pull_request' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add public/llms.txt - git commit -m "🤖 Regenerate LLMs.txt" - git push - - - name: Create Pull Request (for scheduled/manual runs) - if: steps.check-changes.outputs.has_changes == 'true' && github.event_name != 'pull_request' - id: cpr - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.DOCS_PUBLISHABLE_GH_TOKEN }} - commit-message: Regenerate LLMs.txt and related files - branch: auto-update-llms-txt - delete-branch: true - title: "🤖 Regenerate LLMs.txt" - reviewers: > - evantahler - torresmateo - - - name: Enable Pull Request Automerge - if: steps.check-changes.outputs.has_changes == 'true' && github.event_name != 'pull_request' - run: gh pr merge --squash --auto ${{ steps.cpr.outputs.pull-request-number }} - env: - GH_TOKEN: ${{ secrets.DOCS_PUBLISHABLE_GH_TOKEN }} diff --git a/CLAUDE.md b/CLAUDE.md index b09bc8fbe..d0d643b17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,6 +15,7 @@ pnpm vale:check # Check docs against style rules ``` Run a single test: + ```bash pnpm vitest run tests/broken-link-check.test.ts ``` @@ -26,7 +27,7 @@ pnpm vitest run tests/broken-link-check.test.ts - **`app/_lib/`** — Data-fetching utilities (toolkit catalog, slug generation, static params). - **`app/api/`** — API routes (markdown export, toolkit-data, glossary). - **`toolkit-docs-generator/`** — Generates MCP toolkit documentation from server metadata JSON files in `toolkit-docs-generator/data/toolkits/`. -- **`scripts/`** — Build/CI scripts (clean markdown export, Vale style fixes, redirect checking, pagefind indexing, i18n sync). +- **`scripts/`** — Build/CI scripts (Vale style fixes, redirect checking, pagefind indexing, i18n sync). - **`tests/`** — Vitest tests (broken links, internal link validation, sitemap, smoke tests). - **`lib/`** — Next.js utilities (glossary remark plugin, llmstxt plugin). - **`next.config.ts`** — Contains ~138 redirect rules. diff --git a/app/_components/copy-page-override.tsx b/app/_components/copy-page-override.tsx index f89dc528a..c0a431437 100644 --- a/app/_components/copy-page-override.tsx +++ b/app/_components/copy-page-override.tsx @@ -10,14 +10,14 @@ const DROPDOWN_IDENTIFIER = "Markdown for LLMs"; /** * This component overrides the default nextra-theme-docs "Copy page" button behavior - * to fetch clean markdown from our API instead of copying raw MDX source. + * to fetch markdown from our API instead of copying raw MDX source. */ export function CopyPageOverride() { const pathname = usePathname(); const fetchAndCopyMarkdown = useCallback(async (): Promise => { try { - const markdownUrl = `/api/markdown${pathname}.md`; + const markdownUrl = `/api/markdown${pathname}`; const response = await fetch(markdownUrl); if (!response.ok) { diff --git a/app/_components/toolkit-docs/components/page-actions.tsx b/app/_components/toolkit-docs/components/page-actions.tsx index 9738927f3..821d3c47a 100644 --- a/app/_components/toolkit-docs/components/page-actions.tsx +++ b/app/_components/toolkit-docs/components/page-actions.tsx @@ -60,7 +60,7 @@ function CopyPageButton() { setLoading(true); try { - const response = await fetch(`/api/markdown${pathname}.md`); + const response = await fetch(`/api/markdown${pathname}`); if (!response.ok) { throw new Error("Failed to fetch markdown"); } diff --git a/app/api/markdown/[[...slug]]/route.ts b/app/api/markdown/[[...slug]]/route.ts index a31f7965a..5df52ff79 100644 --- a/app/api/markdown/[[...slug]]/route.ts +++ b/app/api/markdown/[[...slug]]/route.ts @@ -10,9 +10,6 @@ const MD_EXTENSION_REGEX = /\.md$/; const TOOLKIT_MARKDOWN_ROOT = join(process.cwd(), "public", "toolkit-markdown"); const APP_ROOT = join(process.cwd(), "app"); -// Directory containing pre-generated clean markdown files -const CLEAN_MARKDOWN_DIR = join(process.cwd(), "public", "_markdown"); - /** * Validates that a resolved path is within the allowed directory. * Prevents path traversal attacks (e.g., ../../../etc/passwd). @@ -108,39 +105,6 @@ type ToolkitMarkdownTarget = { toolkitId: string; }; -/** - * Try to serve clean pre-generated markdown. - * Returns NextResponse if found, null otherwise. - */ -async function tryServeCleanMarkdown( - request: NextRequest, - sanitizedPath: string -): Promise { - const cleanMarkdownPath = join(CLEAN_MARKDOWN_DIR, `${sanitizedPath}.md`); - - try { - await access(cleanMarkdownPath); - if (!isPathWithinDirectory(cleanMarkdownPath, CLEAN_MARKDOWN_DIR)) { - return null; - } - - const content = await readFile(cleanMarkdownPath, "utf-8"); - await trackMarkdownRequest(request, sanitizedPath); - - return new NextResponse(content, { - status: 200, - headers: { - "Content-Type": "text/markdown; charset=utf-8", - "Content-Disposition": "inline", - "Cache-Control": "public, max-age=3600", - Vary: "Accept, User-Agent", - }, - }); - } catch { - return null; - } -} - /** * Check if a path matches the toolkit documentation pattern. * Handles both actual toolkit IDs and the [toolkitId] dynamic route pattern. @@ -248,13 +212,7 @@ export async function GET( filePath = join(APP_ROOT, `${sanitizedPath}/page.mdx`); } } else { - // Try clean markdown first (preferred) - const cleanResponse = await tryServeCleanMarkdown(request, sanitizedPath); - if (cleanResponse) { - return cleanResponse; - } - - // Fallback: raw MDX file + // Serve raw MDX file. filePath = join(APP_ROOT, `${sanitizedPath}/page.mdx`); } diff --git a/app/en/get-started/setup/connect-arcade-docs/page.mdx b/app/en/get-started/setup/connect-arcade-docs/page.mdx index a083b538a..d92ab6453 100644 --- a/app/en/get-started/setup/connect-arcade-docs/page.mdx +++ b/app/en/get-started/setup/connect-arcade-docs/page.mdx @@ -5,7 +5,7 @@ description: "Learn how to speed up your development with agents in your IDEs" # Agentic Development -Every page on the Arcade docs site renders as clean markdown. When an AI agent or coding assistant visits any docs URL, the site automatically returns `Content-Type: text/markdown` instead of HTML if: +Every page on the Arcade docs site can be served as markdown. When an AI agent or coding assistant visits any docs URL, the site automatically returns `Content-Type: text/markdown` instead of HTML if: - The request `User-Agent` header matches a known AI agent (Claude, ChatGPT, Cursor, etc.) - The request includes the `Accept: text/markdown` header diff --git a/app/layout.tsx b/app/layout.tsx index 2d8aa10e4..69e6bdec8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -22,15 +22,16 @@ import { } from "nextra-theme-docs"; const REGEX_LOCALE = /^\/([a-z]{2}(?:-[A-Z]{2})?)(?:\/|$)/; +const ENABLE_MARKDOWN_ALTERNATE = false; function getMarkdownAlternatePath(pathname: string): string { // Handle root paths if (pathname === "/" || pathname === "") { - return "/index.md"; + return "/"; } - // Remove trailing slash if present, then add .md extension + // Remove trailing slash if present. const cleanPath = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname; - return `${cleanPath}.md`; + return cleanPath; } export async function generateMetadata() { @@ -77,11 +78,13 @@ export async function generateMetadata() { appleWebApp: { title: "Arcade Documentation", }, - alternates: { - types: { - "text/markdown": getMarkdownAlternatePath(pathname), - }, - }, + alternates: ENABLE_MARKDOWN_ALTERNATE + ? { + types: { + "text/markdown": getMarkdownAlternatePath(pathname), + }, + } + : undefined, other: { "apple-mobile-web-app-title": "Arcade Documentation", "twitter:url": "https://docs.arcade.dev", diff --git a/middleware.ts b/middleware.ts index 7cd936828..e8d303f34 100644 --- a/middleware.ts +++ b/middleware.ts @@ -101,15 +101,15 @@ function getLocaleFromPathname(pathname: string, request: NextRequest): string { function buildMarkdownPath(pathname: string, locale: string): string { if (pathname === "/" || pathname === "") { - return `/${locale}/home.md`; + return `/${locale}/home`; } if (pathnameIsMissingLocale(pathname)) { - return `/${locale}${pathname}.md`; + return `/${locale}${pathname}`; } if (SUPPORTED_LOCALES.some((loc) => pathname === `/${loc}`)) { - return `${pathname}/home.md`; + return `${pathname}/home`; } - return `${pathname}.md`; + return pathname; } function isToolkitDetailPath(pathname: string): boolean { diff --git a/package.json b/package.json index f796fcd9b..118ae6747 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ "format": "pnpm exec ultracite fix", "prepare": "husky install", "toolkit-markdown": "pnpm dlx tsx toolkit-docs-generator/scripts/generate-toolkit-markdown.ts", - "postbuild": "if [ \"$SKIP_POSTBUILD\" != \"true\" ]; then pnpm run generate:markdown && pnpm run custompagefind; fi", - "generate:markdown": "pnpm dlx tsx scripts/generate-clean-markdown.ts", + "postbuild": "if [ \"$SKIP_POSTBUILD\" != \"true\" ]; then pnpm run custompagefind; fi", "translate": "pnpm dlx tsx scripts/i18n-sync/index.ts && pnpm format", "llmstxt": "pnpm dlx tsx scripts/generate-llmstxt.ts", "custompagefind": "pnpm dlx tsx scripts/pagefind.ts", diff --git a/public/_markdown/en/get-started/about-arcade.md b/public/_markdown/en/get-started/about-arcade.md deleted file mode 100644 index 9dbe470df..000000000 --- a/public/_markdown/en/get-started/about-arcade.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: "How Arcade helps with Agent Authorization" -description: "Learn how Arcade helps with auth and tool calling" ---- -About Arcade - -# About Arcade - -Applications that use models to perform tasks (_agentic applications_) commonly require access to sensitive data and services. Authentication complexities often hinder AI from performing tasks that require \-specific information, like what emails were recently received or what’s coming up on a calendar. - -To retrieve this information, agentic applications need to be able to authenticate and authorize access to external services like Gmail or Google Calendar. - -Authenticating to retrieve information, however, is not the only challenge. Agentic applications also need to authenticate to **act** on behalf of - like sending an email or updating a calendar. - -Without auth, AI are severely limited in what they can do. - -## How Arcade solves this - -Arcade provides an authorization system that handles OAuth 2.0, , and user tokens needed by AI to access external services through . This means AI agents can now act on behalf of securely and privately. - -![Arcade architecture overview](/images/overview-light.png) - -With Arcade, developers can now create that can _act as the end of their application_ to perform tasks like: - -- Creating a new Zoom meeting -- Sending or reading email -- Answering questions about files in Google Drive. - -## Auth permissions and scopes - -Each tool in Arcade’s servers has a set of required permissions - or, more commonly referred to in OAuth 2.0, **scopes**. For example, the [`Gmail.SendEmail`](/resources/integrations/productivity/gmail.md#gmailsendemail) requires the [`https://www.googleapis.com/auth/gmail.send`](https://developers.google.com/identity/protocols/oauth2/scopes#gmail) scope. - -A scope is what the user has authorized someone else (in this case, the AI agent) to do on their behalf. In any OAuth 2.0-compatible service, each kind of action requires a different set of permissions. This gives the user fine-grained control over what data third-party services can access and what actions they can take in their . - -When an agent calls a tool, if the user has not granted the required permissions, will automatically prompt the user to authorize the and coordinate the OAuth 2.0 flow with the service provider. - -## How to implement OAuth 2.0-authorized tool calling - -To learn how Arcade authorizes actions () through OAuth 2.0 and how to implement auth flow, check out [Authorized Tool Calling](/guides/tool-calling/custom-apps/auth-tool-calling.md). - -## Tools that don’t require authorization - -Some , like [`GoogleSearch.Search`](/resources/integrations/search/google_search.md#googlesearchsearch), allow AI to retrieve information or perform actions without needing \-specific authorization. - -### Python - -```python -from arcadepy import Arcade - -client = Arcade(api_key="arcade_api_key") # or set the ARCADE_API_KEY env var - -# Use the GoogleSearch.Searchtool to perform a web search - -response = await client.tools.execute( -tool_name="GoogleSearch.Search", -input={"query": "Latest AI advancements"}, -) -print(response.output.value) - -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the ARCADE_API_KEY env variable - -// Use the GoogleSearch.Search tool to perform a web search -const response = await client.tools.execute({ - tool_name: "GoogleSearch.Search", - input: { query: "Latest AI advancements" }, -}); -console.log(response.output.value); -``` - -Last updated on January 30, 2026 - -[Docs Home](/en/home.md) -[Get an API key](/en/get-started/setup/api-keys.md) diff --git a/public/_markdown/en/get-started/agent-frameworks.md b/public/_markdown/en/get-started/agent-frameworks.md deleted file mode 100644 index c1155461c..000000000 --- a/public/_markdown/en/get-started/agent-frameworks.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: "Arcade with Agent Frameworks and MCP Clients" -description: "Arcade - AI platform for developers" ---- -Agent FrameworksOverview - -# Arcade with Agent Frameworks and MCP Clients - -Arcade integrates with agent frameworks and clients to add \-calling capabilities to your AI applications. - -These guides are for developers building AI applications who need to connect Arcade tools to their agent frameworks or clients. You’ll learn how to authenticate with Arcade, load , and execute them within your chosen framework. Each guide provides code examples and configuration steps to get you started quickly with the most popular frameworks and programming languages. - -## Agent Frameworks - -[![Vanilla Python logo](/images/icons/python.svg) Vanilla Python MCP Client](/guides/agent-frameworks/setup-arcade-with-your-llm-python.md) -[![LangChain logo](/images/icons/langchain.svg) LangChain Agent Framework](/guides/agent-frameworks/langchain/use-arcade-tools.md) -[![CrewAI logo](https://avatars.githubusercontent.com/u/170677839?s=200&v=4) CrewAI Agent Framework](/guides/agent-frameworks/crewai/use-arcade-tools.md) -[![OpenAI Agents logo](https://avatars.githubusercontent.com/u/14957082?s=200&v=4) OpenAI Agents Agent Framework](/guides/agent-frameworks/openai-agents/overview.md) -[![Google ADK logo](https://avatars.githubusercontent.com/u/1342004?s=200&v=4) Google ADK Agent Framework](/guides/agent-frameworks/google-adk/overview.md) - -[![LangChain logo](/images/icons/langchain.svg) LangChain Agent Framework](/guides/agent-frameworks/langchain/use-arcade-tools.md) -[![Google ADK logo](https://avatars.githubusercontent.com/u/1342004?s=200&v=4) Google ADK Agent Framework](/guides/agent-frameworks/google-adk/overview.md) -[![Mastra logo](/images/icons/mastra.svg) Mastra Agent Framework](/guides/agent-frameworks/mastra/overview.md) -[![Vercel AI logo](/images/icons/vercel.svg) Vercel AI Agent Framework](/guides/agent-frameworks/vercelai.md) - -Last updated on January 30, 2026 - -[Build an MCP server for custom tools](/en/get-started/quickstarts/mcp-server-quickstart.md) -[Setup Arcade with your LLM (Python)](/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/crewai/use-arcade-tools.md b/public/_markdown/en/get-started/agent-frameworks/crewai/use-arcade-tools.md deleted file mode 100644 index 7942c05d0..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/crewai/use-arcade-tools.md +++ /dev/null @@ -1,543 +0,0 @@ ---- -title: "Setup Arcade tools with CrewAI" -description: "Learn how to use Arcade tools in CrewAI applications" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -CrewAISetup Arcade tools with CrewAI - -# Use Arcade tools with CrewAI - -[CrewAI](https://www.crewai.com/)  is an agentic framework optimized for building task-oriented multi- systems. This guide explains how to integrate Arcade into your CrewAI applications. - -## Outcomes - -You will build a CrewAI that uses Arcade to help with Gmail and Slack. - -### You will Learn - -- How to retrieve Arcade and convert them to CrewAI format -- How to build a CrewAI with Arcade -- How to implement “just in time” (JIT) authorization using Arcade’s client - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Obtain an Arcade API key](/get-started/setup/api-keys.md) - -- The [`uv` package manager](https://docs.astral.sh/uv/) - - -## The agent architecture you will build in this guide - -CrewAI provides a [Crew](https://docs.crewai.com/reference/crew)  class that implements a multi- system. It provides an interface for you to define the agents, tasks, and memory. In this guide, you will manually keep track of the agent’s history and state, and use the `kickoff` method to invoke the agent in an agentic loop. - -## Integrate Arcade tools into a CrewAI agent - -### Create a new project - -Create a new directory for your and initialize a new virtual environment: - -```bash -mkdir crewai-arcade-example -cd crewai-arcade-example -uv init -uv venv -``` - -### Bash/Zsh (macOS/Linux) - -```bash -source .venv/bin/activate -``` - -### PowerShell (Windows) - -```powershell -. ".venv\Scripts\Activate.ps1" -``` - -Install the necessary packages: - -```powershell -uv add 'crewai[tools]' arcadepy -``` - -Create a new file called `.env` and add the following environment variables: - -```powershell -# .env -# Arcade API key -ARCADE_API_KEY=YOUR_ARCADE_API_KEY -# Arcade user ID (this is the email address you used to login to Arcade) -ARCADE_USER_ID={arcade_user_id} -# OpenAI API key -OPENAI_API_KEY=YOUR_OPENAI_API_KEY -``` - -### Import the necessary packages - -Create a new file called `main.py` and add the following code: - -```python -# main.py -from typing import Any -from arcadepy import Arcade -from arcadepy.types import ToolDefinition -from crewai.tools import BaseTool -from crewai import Agent -from crewai.events.event_listener import EventListener -from pydantic import BaseModel, Field, create_model -from dotenv import load_dotenv -import os - -``` - -This includes many imports, here’s a breakdown: - -- Arcade imports: - - `Arcade`: The , used to interact with the . - - `ToolDefinition`: The definition type, used to define the input and output of a tool. -- CrewAI imports: - - `BaseTool`: The base class, used to create custom CrewAI tools. - - `Agent`: The CrewAI class, used to create an agent. - - `EventListener`: The event listener class, used to suppress CrewAI’s rich panel output. -- Other imports: - - `pydantic` imports: Used for data validation and model creation when converting Arcade to LangChain tools. - - `typing.Any`: A type hint for the any type. - - `load_dotenv`: Loads the environment variables from the `.env` file. - - `os`: The operating system module, used to interact with the operating system. - -### Configure the agent - -The rest of the code uses these variables to customize the and manage the . Feel free to configure them to your liking. Here, the `EventListener` class is used to suppress CrewAI’s rich panel output, which is useful for debugging but verbose for an interactive session like the one you’re building. - -```python -# main.py -# Load environment variables from the .env file -load_dotenv() - -# The Arcade User ID identifies who is authorizing each service. -ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") -# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. -MCP_SERVERS = ["Slack"] -# This determines individual tools. Useful to pick specific tools when you don't need all of them. -TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] -# This determines the maximum number of tool definitions Arcade will return per MCP server -TOOL_LIMIT = 30 -# This determines which LLM model will be used inside the agent -MODEL = "openai/gpt-5-mini" -# The individual objective that guides the agent's decision-making -AGENT_GOAL = "Help the user with all their requests" -# Provides context and personality to the agent, enriching interactions -AGENT_BACKSTORY = "You are a helpful assistant that can assist with Gmail and Slack." -# This defines the Agent's role. A short description of its function and expertise -AGENT_NAME = "Communication Manager" - -# Suppress CrewAI's rich panel output -EventListener().formatter.verbose = False -``` - -### Write a utility function to transform Arcade tool definitions into Pydantic models - -In this utility function, you transform an Arcade definition into a Pydantic model. Later, you will transform these models to construct tools in the format expected by CrewAI. The `_build_args_model` function extracts the tools’ parameters, name, and description, and maps them to a Pydantic model. - -```python -# main.py -TYPE_MAP: dict[str, type] = { - "string": str, - "number": float, - "integer": int, - "boolean": bool, - "array": list, - "json": dict, -} - - -def _python_type(val_type: str) -> type: - t = TYPE_MAP.get(val_type) - if t is None: - raise ValueError(f"Unsupported Arcade value type: {val_type}") - return t - - -def _build_args_model(tool_def: ToolDefinition) -> type[BaseModel]: - fields: dict[str, Any] = {} - for param in tool_def.input.parameters or []: - param_type = _python_type(param.value_schema.val_type) - if param_type is list and param.value_schema.inner_val_type: - inner = _python_type(param.value_schema.inner_val_type) - param_type = list[inner] # type: ignore[valid-type] - default = ... if param.required else None - fields[param.name] = ( - param_type, - Field(default=default, description=param.description or ""), - ) - return create_model(f"{tool_def.name}Input", **fields) -``` - -### Write a custom class that extends the CrewAI BaseTool class - -Here, you define the `ArcadeTool` class that extends the CrewAI `BaseTool` class to add the following capability: - -- Authorize the tool with the with the `_auth_tool` helper function -- Execute the tool with the with the `_run` method - -This class captures the authorization flow outside of the agent’s , which is a good practice for security and context engineering. By handling everything in the `ArcadeTool` class, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations, and reduces context bloat. - -```python -# main.py -class ArcadeTool(BaseTool): - """A CrewAI tool backed by an Arcade tool definition.""" - - name: str - description: str - args_schema: type[BaseModel] - - # Internal fields (not exposed to the agent) - arcade_tool_name: str = "" - user_id: str = "" - _client: Arcade | None = None - - def _auth_tool(self): - auth = self._client.tools.authorize( - tool_name=self.arcade_tool_name, - user_id=self.user_id, - ) - if auth.status != "completed": - print(f"Authorization required. Visit: {auth.url}") - self._client.auth.wait_for_completion(auth) - - def _run(self, **kwargs: Any) -> str: - if self._client is None: - self._client = Arcade() - - self._auth_tool() - - print(f"Calling {self.arcade_tool_name}...") - - result = self._client.tools.execute( - tool_name=self.arcade_tool_name, - input=kwargs, - user_id=self.user_id, - ) - - if not result.success: - return f"Tool error: {result.output.error.message}" - - print(f"Call to {self.arcade_tool_name} successful, the agent will now process the result...") - return result.output.value -``` - -### Retrieve Arcade tools and transform them into CrewAI tools - -Here you get the Arcade tools you want the agent to utilize, and transform them into CrewAI tools. The first step is to initialize the , and get the you want to work with. - -Here’s a breakdown of what it does for clarity: - -- retrieve tools from all configured servers (defined in the `MCP_SERVERS` variable) -- retrieve individual (defined in the `TOOLS` variable) -- transform the Arcade to CrewAI tools with the `ArcadeTool` class you defined earlier - -```python -# main.py -def get_arcade_tools( - client: Arcade, - *, - tools: list[str] | None = None, - mcp_servers: list[str] | None = None, - user_id: str = "", -) -> list[ArcadeTool]: - if not tools and not mcp_servers: - raise ValueError("Provide at least one tool name or toolkit name") - - definitions: list[ToolDefinition] = [] - - if tools: - for name in tools: - definitions.append(client.tools.get(name=name)) - - if mcp_servers: - for tk in mcp_servers: - page = client.tools.list(toolkit=tk) - definitions.extend(page.items) - - result: list[ArcadeTool] = [] - for defn in definitions: - sanitized_name = defn.qualified_name.replace(".", "_") - t = ArcadeTool( - client=client, - name=sanitized_name, - description=defn.description, - args_schema=_build_args_model(defn), - arcade_tool_name=defn.qualified_name, - user_id=user_id, - ) - result.append(t) - - return result -``` - -### Create the main function - -The main function is where you: - -- Get the Arcade tools from the configured servers -- Create an with the Arcade -- Initialize the conversation -- Run the loop - -```python -# main.py -def main(): - client = Arcade() - - arcade_tools = get_arcade_tools( - client, - tools=TOOLS, - mcp_servers=MCP_SERVERS, - user_id=ARCADE_USER_ID, - ) - - agent = Agent( - role=AGENT_NAME, - goal=AGENT_GOAL, - backstory=AGENT_BACKSTORY, - tools=arcade_tools, - ) - - history = [] - print("Agent ready. Type 'exit' to quit.\n") - - while True: - user_input = input("> ") - if user_input.strip().lower() in ("exit", "quit"): - break - - history.append({"role": "user", "content": user_input}) - result = agent.kickoff(history) - history.append({"role": "assistant", "content": result.raw}) - print(f"\n{result.raw}\n") - - -if __name__ == "__main__": - main() -``` - -### Run the agent - -```bash -uv run main.py -``` - -You should see the responding to your prompts like any model, as well as handling any calls and authorization requests. Here are some example prompts you can try: - -- “Send me an email with a random haiku about OpenAI ” -- “Summarize my latest 3 emails” - -## Tips for selecting tools - -- **Relevance**: Pick only the you need. Avoid using all tools at once. -- **Avoid conflicts**: Be mindful of duplicate or overlapping functionality. - -## Next steps - -Now that you have integrated Arcade tools into your CrewAI team, you can: - -- Experiment with different toolkits, such as “Math” or “Search.” -- Customize the ’s prompts for specific tasks. -- Customize the authorization and execution flow to meet your application’s requirements. - -## Example code - -### **main.py** (full file) - -```python -# main.py -from typing import Any -from arcadepy import Arcade -from arcadepy.types import ToolDefinition -from crewai.tools import BaseTool -from crewai import Agent -from crewai.events.event_listener import EventListener -from pydantic import BaseModel, Field, create_model -from dotenv import load_dotenv -import os - -# Load environment variables from the .env file -load_dotenv() - -# The Arcade User ID identifies who is authorizing each service. -ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") -# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. -MCP_SERVERS = ["Slack"] -# This determines individual tools. Useful to pick specific tools when you don't need all of them. -TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] -# This determines the maximum number of tool definitions Arcade will return per MCP server -TOOL_LIMIT = 30 -# This determines which LLM model will be used inside the agent -MODEL = "openai/gpt-5-mini" -# The individual objective that guides the agent's decision-making -AGENT_GOAL = "Help the user with all their requests" -# Provides context and personality to the agent, enriching interactions -AGENT_BACKSTORY = "You are a helpful assistant that can assist with Gmail and Slack." -# This defines the Agent's role. A short description of its function and expertise -AGENT_NAME = "Communication Manager" - -# Suppress CrewAI's rich panel output -EventListener().formatter.verbose = False - -TYPE_MAP: dict[str, type] = { - "string": str, - "number": float, - "integer": int, - "boolean": bool, - "array": list, - "json": dict, -} - - -def _python_type(val_type: str) -> type: - t = TYPE_MAP.get(val_type) - if t is None: - raise ValueError(f"Unsupported Arcade value type: {val_type}") - return t - - -def _build_args_model(tool_def: ToolDefinition) -> type[BaseModel]: - fields: dict[str, Any] = {} - for param in tool_def.input.parameters or []: - param_type = _python_type(param.value_schema.val_type) - if param_type is list and param.value_schema.inner_val_type: - inner = _python_type(param.value_schema.inner_val_type) - param_type = list[inner] # type: ignore[valid-type] - default = ... if param.required else None - fields[param.name] = ( - param_type, - Field(default=default, description=param.description or ""), - ) - return create_model(f"{tool_def.name}Input", **fields) - - -class ArcadeTool(BaseTool): - """A CrewAI tool backed by an Arcade tool definition.""" - - name: str - description: str - args_schema: type[BaseModel] - - # Internal fields (not exposed to the agent) - arcade_tool_name: str = "" - user_id: str = "" - _client: Arcade | None = None - - def _auth_tool(self): - auth = self._client.tools.authorize( - tool_name=self.arcade_tool_name, - user_id=self.user_id, - ) - if auth.status != "completed": - print(f"Authorization required. Visit: {auth.url}") - self._client.auth.wait_for_completion(auth) - - def _run(self, **kwargs: Any) -> str: - if self._client is None: - self._client = Arcade() - - self._auth_tool() - - print(f"Calling {self.arcade_tool_name}...") - - result = self._client.tools.execute( - tool_name=self.arcade_tool_name, - input=kwargs, - user_id=self.user_id, - ) - - if not result.success: - return f"Tool error: {result.output.error.message}" - - print(f"Call to {self.arcade_tool_name} successful, the agent will now process the result...") - return result.output.value - - -def get_arcade_tools( - client: Arcade, - *, - tools: list[str] | None = None, - mcp_servers: list[str] | None = None, - user_id: str = "", -) -> list[ArcadeTool]: - if not tools and not mcp_servers: - raise ValueError("Provide at least one tool name or toolkit name") - - definitions: list[ToolDefinition] = [] - - if tools: - for name in tools: - definitions.append(client.tools.get(name=name)) - - if mcp_servers: - for tk in mcp_servers: - page = client.tools.list(toolkit=tk) - definitions.extend(page.items) - - result: list[ArcadeTool] = [] - for defn in definitions: - sanitized_name = defn.qualified_name.replace(".", "_") - t = ArcadeTool( - client=client, - name=sanitized_name, - description=defn.description, - args_schema=_build_args_model(defn), - arcade_tool_name=defn.qualified_name, - user_id=user_id, - ) - result.append(t) - - return result - - -def main(): - client = Arcade() - - arcade_tools = get_arcade_tools( - client, - tools=TOOLS, - mcp_servers=MCP_SERVERS, - user_id=ARCADE_USER_ID, - ) - - agent = Agent( - role=AGENT_NAME, - goal=AGENT_GOAL, - backstory=AGENT_BACKSTORY, - tools=arcade_tools, - ) - - history = [] - print("Agent ready. Type 'exit' to quit.\n") - - while True: - user_input = input("> ") - if user_input.strip().lower() in ("exit", "quit"): - break - - history.append({"role": "user", "content": user_input}) - result = agent.kickoff(history) - history.append({"role": "assistant", "content": result.raw}) - print(f"\n{result.raw}\n") - - -if __name__ == "__main__": - main() - - -``` - - - -Last updated on February 10, 2026 - -[Setup Arcade with your LLM (Python)](/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md) -[Overview](/en/get-started/agent-frameworks/google-adk/overview.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/google-adk/overview.md b/public/_markdown/en/get-started/agent-frameworks/google-adk/overview.md deleted file mode 100644 index e41d5551f..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/google-adk/overview.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: "Arcade with Google ADK" -description: "Integrate Arcade tools with Google ADK agents" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -Google ADKOverview - -# Arcade with Google ADK - -[Google ADK](https://github.com/google/adk-python/)  is a modular framework for building and deploying AI . It’s optimized for Gemini and the Google ecosystem. Arcade integrates with both the Python and TypeScript versions, giving your agents access to Gmail, GitHub, Slack, and 100+ other . - -## Get started - -Choose your language to set up Arcade with Google ADK: - -- **[Python setup](/get-started/agent-frameworks/google-adk/setup-python.md) - ** - Build an with Arcade using the `google-adk-arcade` package -- **[TypeScript setup](/get-started/agent-frameworks/google-adk/setup-typescript.md) - ** - Build an with Arcade using `@arcadeai/arcadejs` - -## What you can build - -With Arcade and Google ADK, your can: - -- Read and send emails via Gmail -- Post messages to Slack channels -- Create GitHub issues and pull requests -- Search the web and extract content -- Access 100+ other integrations - -Browse the [full MCP server catalog](/resources/integrations.md) to see all available . - -Last updated on January 30, 2026 - -[Custom auth flow](/en/get-started/agent-frameworks/crewai/custom-auth-flow.md) -[Setup (Python)](/en/get-started/agent-frameworks/google-adk/setup-python.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-python.md b/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-python.md deleted file mode 100644 index 7dc759aa9..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-python.md +++ /dev/null @@ -1,751 +0,0 @@ ---- -title: "Setup Arcade with Google ADK (Python)" -description: "Build an agent with Arcade tools using Google ADK" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[Google ADK](/en/get-started/agent-frameworks/google-adk/overview.md) -Setup (Python) - -# Setup Arcade with Google ADK (Python) - -Google ADK is a modular framework for building and deploying AI . It optimizes for Gemini and the Google Ecosystem, but supports any model. - -## Outcomes - -Learn how to integrate Arcade using Google ADK primitives - -### You will Learn - -- How to retrieve Arcade and transform them into Google ADK tools -- How to build a Google ADK -- How to integrate Arcade into the agentic flow -- How to manage Arcade tool authorization for Google ADK - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- The [`uv` package manager](https://docs.astral.sh/uv/) - - -## The agent architecture you will build in this guide - -In this guide, you will build an that can use Arcade to help the with their requests. It will follow the ReAct pattern, where the agent thinks about what to do, plans the steps, and then executes the steps, calling tools as needed. - -### Create a new project - -Create a new directory for your and initialize a new virtual environment: - -```bash -mkdir google-adk-arcade-example -cd google-adk-arcade-example -uv init -uv venv -``` - -### Bash/Zsh (macOS/Linux) - -```bash -source .venv/bin/activate -``` - -### PowerShell (Windows) - -```powershell -. ".venv\Scripts\Activate.ps1" -``` - -Install the necessary packages: - -```powershell -uv add arcadepy google-adk -``` - -### Configure API keys - -Provide your Arcade and Google . You can store it in environment variables or directly in your code: - -> Need an key? Visit the [Get an API key](/get-started/setup/api-keys.md) page to create one. - -Create a new file called `.env` and add the following environment variables: - -```bash -# .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} -# Google API key -GOOGLE_API_KEY=YOUR_GOOGLE_API_KEY -# Google GenAI use VertexAI -GOOGLE_GENAI_USE_VERTEXAI=FALSE -``` - -### Import the necessary packages - -Create a new file called `main.py` and add the following code: - -```python -# main.py -from arcadepy import AsyncArcade -from arcadepy.types import ToolDefinition -from arcadepy.types.execute_tool_response import ExecuteToolResponse -from google.adk import Agent, Runner -from google.adk.artifacts import InMemoryArtifactService -from google.adk.sessions import InMemorySessionService, Session -from google.adk.tools import ToolContext, FunctionTool -from google.adk.tools._automatic_function_calling_util import ( - _map_pydantic_type_to_property_schema -) -from google.genai import types -from pydantic import BaseModel, Field, create_model -from typing import Any -from typing_extensions import override -from dotenv import load_dotenv -import logging -import os -``` - -This includes multiple imports, here’s a breakdown: - -- Arcade imports: - - `AsyncArcade`: The , used to interact with the . - - `ToolDefinition`: The definition type, used to define the input and output of a tool. - - `ExecuteToolResponse`: The response type for the execute response. -- Google ADK imports: - - `Agent`: The Google ADK class, used to define an agent. - - `Runner`: The Google ADK runner, used to manage and run the agentic loop. - - `InMemoryArtifactService`: The in-memory artifact service, used to store and retrieve artifacts, such as the ’s state. - - `InMemorySessionService`: The in-memory session service, used to store and retrieve sessions, such as the conversation history. - - `Session`: The session type, used to define a session. - - `ToolContext`: The , used to provide contextual information, such as the ID. - - `FunctionTool`: The Google ADK function class, used to define a function tool. - - `_map_pydantic_type_to_property_schema`: A utility function that maps Pydantic types to Google ADK schemas. -- Google GenAI imports: - - `types`: The Google GenAI types, used to define the types for the Google GenAI API. -- Pydantic imports: - - `BaseModel`: The Pydantic base model, used to define a base model. - - `Field`: The Pydantic field, used to define a field. - - `create_model`: A Pydantic function used to create a model from a dictionary of fields. - - `typing` imports: Used to provide type hints for the code. - - `dotenv`: Used to load environment variables from a `.env` file. -- Other imports: - - `logging`: The logging module, used to log messages to the console. - - `os`: Used to retrieve loaded environment variables. - -### Configure the agent - -These variables set the configuration for the rest of the code to customize the and manage the . Feel free to configure them to your liking. Set the `google_genai.types` logging level to `ERROR` to avoid a lot of noise in the console. Load the environment variables from the `.env` file using `load_dotenv()`. - -```python -# main.py -logging.getLogger("google_genai.types").setLevel(logging.ERROR) - -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 prompt defines the behavior of the agent. -MODEL = "gemini-2.5-flash" -# This determines which LLM model will be used inside the agent -SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack." -# This determines the name of the agent. -AGENT_NAME = "AwesomeAgent" -``` - -### Write a utility function to transform Arcade tool definitions into Pydantic models - -In this utility function, you transform an Arcade definition into a Pydantic model. Later, you will transform these models to construct tools in the format expected by Google ADK. The `tool_definition_to_pydantic_model` function extracts the tools’ parameters, name, and description, and maps them to a Pydantic model. - -```python -# main.py -# Mapping of Arcade value types to Python types -TYPE_MAPPING = { - "string": str, - "number": float, - "integer": int, - "boolean": bool, - "array": list, - "json": dict, -} - - -def get_python_type(val_type: str) -> Any: - _type = TYPE_MAPPING.get(val_type) - if _type is None: - raise ValueError(f"Invalid value type: {val_type}") - return _type - - -def tool_definition_to_pydantic_model(tool_def: ToolDefinition) -> type[BaseModel]: - try: - fields: dict[str, Any] = {} - for param in tool_def.input.parameters or []: - param_type = get_python_type(param.value_schema.val_type) - if param_type == list and param.value_schema.inner_val_type: # noqa: E721 - inner_type: type[Any] = get_python_type(param.value_schema.inner_val_type) - param_type = list[inner_type] # type: ignore[valid-type] - param_description = param.description or "No description provided." - default = ... if param.required else None - fields[param.name] = ( - param_type, - Field(default=default, description=param_description), - ) - return create_model(f"{tool_def.name}Args", **fields) - except ValueError as e: - raise ValueError( - f"Error converting {tool_def.name} parameters into pydantic model: {e}" - ) -``` - -### Write a custom class that extends the Google ADK FunctionTool class - -Here, you define the `ArcadeTool` class that extends the Google ADK `FunctionTool` class to add the following capability: - -- Authorize the tool with the with the `_authorize_tool` helper function -- Execute the tool with the with the `_async_invoke_arcade_tool` helper function -- Map the Pydantic model to the Google ADK schema with the `_map_pydantic_type_to_property_schema` utility function - -You also define a `ToolError` class to handle errors from the Arcade . It wraps the `ExecuteToolResponse` and provides an informative error message that the system can handle in the agentic loop in case anything goes wrong. - -This class captures the authorization flow outside of the agent’s , which is a good practice for security and context engineering. By handling everything in the `ArcadeTool` class, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations, and reduces context bloat. - -```python -# main.py -class ToolError(ValueError): - def __init__(self, result: ExecuteToolResponse): - self.result = result - - @property - def message(self): - return self.result.output.error.message - - def __str__(self): - return f"Tool {self.result.tool_name} failed with error: {self.message}" - - -async def _authorize_tool(client: AsyncArcade, tool_context: ToolContext, tool_name: str): - if not tool_context.state.get("user_id"): - raise ValueError("No user ID and authorization required for tool") - - result = await client.tools.authorize( - tool_name=tool_name, - user_id=tool_context.state.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 _async_invoke_arcade_tool( - tool_context: ToolContext, - tool_args: dict, - tool_name: str, - client: AsyncArcade, -) -> dict: - await _authorize_tool(client, tool_context, tool_name) - - print(f"Executing tool: {tool_name} with args: {tool_args}") - - result = await client.tools.execute( - tool_name=tool_name, - input=tool_args, - user_id=tool_context.state.get("user_id"), - ) - - if not result.success: - raise ToolError(result) - - print(f"{tool_name} called successfully, processing result...") - return result.output.value - - -class ArcadeTool(FunctionTool): - def __init__(self, - name: str, - description: str, - schema: BaseModel, - client: AsyncArcade): - - # define callable - async def func(tool_context: ToolContext, - **kwargs: Any) -> dict: - return await _async_invoke_arcade_tool( - tool_context=tool_context, - tool_args=kwargs, - tool_name=name, - client=client - ) - func.__name__ = name.lower() - func.__doc__ = description - - super().__init__(func) - schema = schema.model_json_schema() - _map_pydantic_type_to_property_schema(schema) - self.schema = schema - self.name = name.replace(".", "_") - self.description = description - self.client = client - - @override - async def run_async(self, *, args: dict[str, Any], - tool_context: ToolContext) -> Any: - return await _async_invoke_arcade_tool( - tool_context=tool_context, - tool_args=args, - tool_name=self.name, - client=self.client, - ) - - @override - def _get_declaration(self) -> types.FunctionDeclaration: - return types.FunctionDeclaration( - parameters=types.Schema( - type='OBJECT', - properties=self.schema["properties"], - ), - description=self.description, - name=self.name, - ) -``` - -### Retrieve Arcade tools and transform them into Google ADK tools - -Here you get the Arcade tools you want the agent to utilize, and transform them into Google ADK tools. The first step is to initialize the , and get the you want to work with. - -Here’s a breakdown of what it does for clarity: - -- retrieve tools from all configured servers (defined in the `MCP_SERVERS` variable) -- retrieve individual (defined in the `TOOLS` variable) -- transform the Arcade to Google ADK tools with the `ArcadeTool` class you defined earlier - -```python -# main.py -async def get_arcade_tools( - client: AsyncArcade | None = None, - tools: list[str] | None = None, - mcp_servers: list[str] | None = None, - **kwargs: dict[str, Any], -) -> list[ArcadeTool]: - if not client: - client = AsyncArcade() - - if not tools and not mcp_servers: - raise ValueError("No tools or toolkits provided to retrieve tool definitions") - - tool_formats: list[ToolDefinition] = [] - # Retrieve individual tools if specified - if tools: - tasks = [client.tools.get(name=tool_id) - 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: - tasks = [client.tools.list(toolkit=mcp_server) - for mcp_server in mcp_servers] - responses = await asyncio.gather(*tasks) - - # Combine the tool definitions from each response. - for response in responses: - tool_formats.extend(response.items) - - tool_functions = [] - for tool in tool_formats: - sanitized_name = tool.qualified_name.replace(".", "_") - tool_function = ArcadeTool( - name=sanitized_name, - description=tool.description, - schema=tool_definition_to_pydantic_model(tool), - client=client, - ) - tool_functions.append(tool_function) - - return tool_functions -``` - -### Create the main function - -The main function is where you: - -- Initialize the session and artifact services -- Get the Arcade tools from the configured servers -- Create an with the Arcade -- Initialize the conversation -- Run the loop - -Google ADK provides a `Runner` class that manages the agentic loop, and will employ the session and artifact services you created earlier to store the conversation history and state. Therefore, you don’t need to manually store the conversation history or agent state, and you can just pass the latest message to the runner. - -```python -# main.py -async def main(): - - session_service = InMemorySessionService() - artifact_service = InMemoryArtifactService() - client = AsyncArcade() - - arcade_tools = await get_arcade_tools(client, - tools=TOOLS, - mcp_servers=MCP_SERVERS) - - agent = Agent( - model=MODEL, - name=AGENT_NAME, - instruction=SYSTEM_PROMPT, - tools=arcade_tools, - ) - - session = await session_service.create_session( - app_name=AGENT_NAME, user_id=ARCADE_USER_ID, state={ - "user_id": ARCADE_USER_ID, - } - ) - - runner = Runner( - app_name=AGENT_NAME, - agent=agent, - artifact_service=artifact_service, - session_service=session_service, - ) - - async def run_prompt(session: Session, new_message: str): - content = types.Content( - role='user', parts=[types.Part.from_text(text=new_message)] - ) - async for event in runner.run_async( - user_id=ARCADE_USER_ID, - session_id=session.id, - new_message=content, - ): - if event.content.parts and event.content.parts[0].text: - print(f'** {event.author}: {event.content.parts[0].text}') - - while True: - user_input = input("User: ") - if user_input.lower() == "exit": - print("Goodbye!") - break - await run_prompt(session, user_input) - - -if __name__ == '__main__': - import asyncio - asyncio.run(main()) -``` - -### Run the agent - -```bash -uv run main.py -``` - -You should see the responding to your prompts like any model, as well as handling any calls and authorization requests. Here are some example prompts you can try: - -- “Send me an email with a random haiku about Google ADK” -- “Summarize my latest 3 emails” -- “summarize my latest 3 emails, then send me that summary on a Slack DM” - -## Tips for selecting tools - -- **Relevance**: Pick only the you need. Avoid utilizing all tools at once. -- ** identification**: Always provide a unique and consistent `user_id` for each user. Apply your internal or database user ID, not something entered by the user. - -## Next steps - -Now that you have integrated Arcade into your Google ADK application, you can: - -- Experiment with different servers, such as “Github” or “LinkedIn” -- Customize the ’s instructions for specific tasks -- Try out multi- systems with different Arcade -- Build your own custom tools with the Arcade SDK - -## Example code - -### **main.py** (full file) - -```python -# main.py -from arcadepy import AsyncArcade -from arcadepy.types import ToolDefinition -from arcadepy.types.execute_tool_response import ExecuteToolResponse -from google.adk import Agent, Runner -from google.adk.artifacts import InMemoryArtifactService -from google.adk.sessions import InMemorySessionService, Session -from google.adk.tools import ToolContext, FunctionTool -from google.adk.tools._automatic_function_calling_util import ( - _map_pydantic_type_to_property_schema -) -from google.genai import types -from pydantic import BaseModel, Field, create_model -from typing import Any -from typing_extensions import override -from dotenv import load_dotenv -import logging -import os - -logging.getLogger("google_genai.types").setLevel(logging.ERROR) - -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 prompt defines the behavior of the agent. -MODEL = "gemini-2.5-flash" -# This determines which LLM model will be used inside the agent -SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack." -# This determines the name of the agent. -AGENT_NAME = "AwesomeAgent" - - -# Mapping of Arcade value types to Python types -TYPE_MAPPING = { - "string": str, - "number": float, - "integer": int, - "boolean": bool, - "array": list, - "json": dict, -} - - -def get_python_type(val_type: str) -> Any: - _type = TYPE_MAPPING.get(val_type) - if _type is None: - raise ValueError(f"Invalid value type: {val_type}") - return _type - - -def tool_definition_to_pydantic_model(tool_def: ToolDefinition) -> type[BaseModel]: - try: - fields: dict[str, Any] = {} - for param in tool_def.input.parameters or []: - param_type = get_python_type(param.value_schema.val_type) - if param_type == list and param.value_schema.inner_val_type: # noqa: E721 - inner_type: type[Any] = get_python_type(param.value_schema.inner_val_type) - param_type = list[inner_type] # type: ignore[valid-type] - param_description = param.description or "No description provided." - default = ... if param.required else None - fields[param.name] = ( - param_type, - Field(default=default, description=param_description), - ) - return create_model(f"{tool_def.name}Args", **fields) - except ValueError as e: - raise ValueError( - f"Error converting {tool_def.name} parameters into pydantic model: {e}" - ) - - -class ToolError(ValueError): - def __init__(self, result: ExecuteToolResponse): - self.result = result - - @property - def message(self): - return self.result.output.error.message - - def __str__(self): - return f"Tool {self.result.tool_name} failed with error: {self.message}" - - -async def _authorize_tool(client: AsyncArcade, tool_context: ToolContext, tool_name: str): - if not tool_context.state.get("user_id"): - raise ValueError("No user ID and authorization required for tool") - - result = await client.tools.authorize( - tool_name=tool_name, - user_id=tool_context.state.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 _async_invoke_arcade_tool( - tool_context: ToolContext, - tool_args: dict, - tool_name: str, - client: AsyncArcade, -) -> dict: - await _authorize_tool(client, tool_context, tool_name) - - print(f"Executing tool: {tool_name} with args: {tool_args}") - - result = await client.tools.execute( - tool_name=tool_name, - input=tool_args, - user_id=tool_context.state.get("user_id"), - ) - - if not result.success: - raise ToolError(result) - - print(f"{tool_name} called successfully, processing result...") - return result.output.value - - -class ArcadeTool(FunctionTool): - def __init__(self, - name: str, - description: str, - schema: BaseModel, - client: AsyncArcade): - - # define callable - async def func(tool_context: ToolContext, - **kwargs: Any) -> dict: - return await _async_invoke_arcade_tool( - tool_context=tool_context, - tool_args=kwargs, - tool_name=name, - client=client - ) - func.__name__ = name.lower() - func.__doc__ = description - - super().__init__(func) - schema = schema.model_json_schema() - _map_pydantic_type_to_property_schema(schema) - self.schema = schema - self.name = name.replace(".", "_") - self.description = description - self.client = client - - @override - async def run_async(self, *, args: dict[str, Any], - tool_context: ToolContext) -> Any: - return await _async_invoke_arcade_tool( - tool_context=tool_context, - tool_args=args, - tool_name=self.name, - client=self.client, - ) - - @override - def _get_declaration(self) -> types.FunctionDeclaration: - return types.FunctionDeclaration( - parameters=types.Schema( - type='OBJECT', - properties=self.schema["properties"], - ), - description=self.description, - name=self.name, - ) - - -async def get_arcade_tools( - client: AsyncArcade | None = None, - tools: list[str] | None = None, - mcp_servers: list[str] | None = None, - **kwargs: dict[str, Any], -) -> list[ArcadeTool]: - if not client: - client = AsyncArcade() - - if not tools and not mcp_servers: - raise ValueError("No tools or toolkits provided to retrieve tool definitions") - - tool_formats: list[ToolDefinition] = [] - # Retrieve individual tools if specified - if tools: - tasks = [client.tools.get(name=tool_id) - 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: - tasks = [client.tools.list(toolkit=mcp_server) - for mcp_server in mcp_servers] - responses = await asyncio.gather(*tasks) - - # Combine the tool definitions from each response. - for response in responses: - tool_formats.extend(response.items) - - tool_functions = [] - for tool in tool_formats: - sanitized_name = tool.qualified_name.replace(".", "_") - tool_function = ArcadeTool( - name=sanitized_name, - description=tool.description, - schema=tool_definition_to_pydantic_model(tool), - client=client, - ) - tool_functions.append(tool_function) - - return tool_functions - - -async def main(): - - session_service = InMemorySessionService() - artifact_service = InMemoryArtifactService() - client = AsyncArcade() - - arcade_tools = await get_arcade_tools(client, - tools=TOOLS, - mcp_servers=MCP_SERVERS) - - agent = Agent( - model=MODEL, - name=AGENT_NAME, - instruction=SYSTEM_PROMPT, - tools=arcade_tools, - ) - - session = await session_service.create_session( - app_name=AGENT_NAME, user_id=ARCADE_USER_ID, state={ - "user_id": ARCADE_USER_ID, - } - ) - - runner = Runner( - app_name=AGENT_NAME, - agent=agent, - artifact_service=artifact_service, - session_service=session_service, - ) - - async def run_prompt(session: Session, new_message: str): - content = types.Content( - role='user', parts=[types.Part.from_text(text=new_message)] - ) - async for event in runner.run_async( - user_id=ARCADE_USER_ID, - session_id=session.id, - new_message=content, - ): - if event.content.parts and event.content.parts[0].text: - print(f'** {event.author}: {event.content.parts[0].text}') - - while True: - user_input = input("User: ") - if user_input.lower() == "exit": - print("Goodbye!") - break - await run_prompt(session, user_input) - - -if __name__ == '__main__': - import asyncio - asyncio.run(main()) -``` - -Last updated on February 10, 2026 - -[Overview](/en/get-started/agent-frameworks/google-adk/overview.md) -[Setup (TypeScript)](/en/get-started/agent-frameworks/google-adk/setup-typescript.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-typescript.md b/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-typescript.md deleted file mode 100644 index bc2f27c39..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/google-adk/setup-typescript.md +++ /dev/null @@ -1,482 +0,0 @@ ---- -title: "Setup Arcade with Google ADK (TypeScript)" -description: "Build an agent with Arcade tools using Google ADK for TypeScript" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[Google ADK](/en/get-started/agent-frameworks/google-adk/overview.md) -Setup (TypeScript) - -# Setup Arcade with Google ADK (TypeScript) - -[Google ADK for TypeScript](https://github.com/google/adk-js)  provides a framework for building AI in TypeScript. Arcade’s `@arcadeai/arcadejs` library provides the integration, allowing your agents to access Gmail, GitHub, Slack, and 100+ other services. - -## Outcomes - -Build an that uses Arcade to help with Gmail and Slack - -### You will Learn - -- How to retrieve Arcade and convert them to Google ADK format -- How to build a Google ADK with Arcade -- How to handle authorization - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Obtain an Arcade API key](/get-started/setup/api-keys.md) - -- [Node.js](https://nodejs.org/) -   18+ (includes npm) or [Bun](https://bun.sh/) -   -- A [Gemini API key](https://aistudio.google.com/apikey) -   - -## How Arcade integrates with Google ADK - -Google ADK uses `FunctionTool` for defining callable . Arcade’s `@arcadeai/arcadejs` library provides `toZod` to convert tool definitions to Zod schemas, which Google ADK’s `FunctionTool` accepts. The `Runner` class manages the ’s conversation loop, while `InMemorySessionService` handles session state. - -## Build the agent - -### Create a new project - -Create a new directory and install dependencies: - -### npm - -```bash -mkdir google-adk-arcade-ts -cd google-adk-arcade-ts -npm init -y -npm install @google/adk @arcadeai/arcadejs dotenv -``` - -### bun - -```bash -mkdir google-adk-arcade-ts -cd google-adk-arcade-ts -bun init -y -bun add @google/adk @arcadeai/arcadejs dotenv -``` - -Create a `.env` file with your : - -```bash -# .env -# Arcade API key from https://app.arcade.dev/api-keys -ARCADE_API_KEY=YOUR_ARCADE_API_KEY -# Your Arcade user ID (the email you used to sign up) -ARCADE_USER_ID={arcade_user_id} -# Google Gemini API key (get one at https://aistudio.google.com/apikey) -GEMINI_API_KEY=YOUR_GEMINI_API_KEY -``` - -### Create the agent file - -Create `index.ts` with the following imports and configuration: - -```typescript -// index.ts -import { - LlmAgent, - FunctionTool, - Runner, - InMemorySessionService, - setLogLevel, - LogLevel, -} from "@google/adk"; -import Arcade from "@arcadeai/arcadejs"; -import { toZod } from "@arcadeai/arcadejs/lib/zod/zod"; -import "dotenv/config"; -import readline from "node:readline/promises"; - -// Suppress verbose ADK logs (options: DEBUG, INFO, WARNING, ERROR) -setLogLevel(LogLevel.ERROR); - -// Configuration -const ARCADE_USER_ID = process.env.ARCADE_USER_ID || "default-user"; -const MCP_SERVERS = ["Slack"]; -const INDIVIDUAL_TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]; -const SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."; -const MODEL = "gemini-2.0-flash"; -const APP_NAME = "inbox_assistant"; -``` - -### Convert Arcade tools to Google ADK format - -Use Arcade’s `toZod` to convert definitions to Zod schemas, then wrap them in Google ADK’s `FunctionTool`: - -```typescript -// index.ts -async function getArcadeTools(client: Arcade, userId: string): Promise { - // Fetch tools from MCP servers - const mcpServerTools = await Promise.all( - MCP_SERVERS.map(async (serverName) => { - const response = await client.tools.list({ - toolkit: serverName, - limit: 30, - }); - return response.items; - }) - ); - - // Fetch individual tools by name - const individualToolDefs = await Promise.all( - INDIVIDUAL_TOOLS.map((toolName) => client.tools.get(toolName)) - ); - - // Combine and deduplicate - const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; - const uniqueTools = Array.from( - new Map(allTools.map((t) => [t.qualified_name, t])).values() - ); - - // Convert Arcade tools to Zod format (with proper schemas) - const zodTools = toZod({ - tools: uniqueTools, - client, - userId, - executeFactory: ({ toolDefinition, client, userId }) => { - const toolName = toolDefinition.qualified_name; - return async (args: unknown) => { - // Handle authorization - const authResult = await client.tools.authorize({ - tool_name: toolName, - user_id: userId, - }); - - if (authResult.status !== "completed") { - console.log(`\nAuthorization required for ${toolName}`); - console.log(`Please visit: ${authResult.url}\n`); - await client.auth.waitForCompletion(authResult); - console.log("Authorization complete!\n"); - } - - // Execute the tool - const result = await client.tools.execute({ - tool_name: toolName, - input: args as Record, - user_id: userId, - }); - - if (!result.success) { - throw new Error(`Tool execution failed: ${result.output?.error?.message}`); - } - - return result.output?.value; - }; - }, - }); - - // Convert to Google ADK FunctionTool format - return zodTools.map((tool) => - new FunctionTool({ - name: tool.name, - description: tool.description, - parameters: tool.parameters, - execute: tool.execute, - }) - ); -} -``` - -**What’s happening here:** - -- `toZod` converts Arcade definitions to Zod schemas with proper parameter types -- `executeFactory` creates the execution function that handles authorization and calls -- Each Zod wraps in a Google ADK `FunctionTool` - -### Create and run the agent - -```typescript -// index.ts -async function main() { - const client = new Arcade(); - const sessionService = new InMemorySessionService(); - - // Get Arcade tools - const arcadeTools = await getArcadeTools(client, ARCADE_USER_ID); - - // Create the agent - const agent = new LlmAgent({ - name: APP_NAME, - description: "An assistant that helps with Gmail and Slack", - model: MODEL, - instruction: SYSTEM_PROMPT, - tools: arcadeTools, - }); - - // Create a session - const session = await sessionService.createSession({ - appName: APP_NAME, - userId: ARCADE_USER_ID, - state: { user_id: ARCADE_USER_ID }, - }); - - // Create the runner - const runner = new Runner({ - appName: APP_NAME, - agent, - sessionService, - }); - - // Set up interactive chat - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - console.log("Hello! I'm your Google ADK Agent with Arcade Tools."); - console.log("Try asking me to summarize your recent emails or send a Slack message!"); - console.log("Type 'exit' to quit.\n"); - - while (true) { - const input = await rl.question("> "); - if (input.toLowerCase() === "exit") { - break; - } - - try { - const events = runner.runAsync({ - userId: ARCADE_USER_ID, - sessionId: session.id, - newMessage: { role: "user", parts: [{ text: input }] }, - }); - - for await (const event of events) { - if (event.content?.parts?.[0]?.text) { - console.log(`\n${event.author}: ${event.content.parts[0].text}\n`); - } - } - } catch (error) { - console.error("Error:", error); - } - } - - console.log("Goodbye!"); - rl.close(); - process.exit(0); -} - -main().catch(console.error); -``` - -### Run the agent - -### npm - -```bash -npx tsx index.ts -``` - -### bun - -```bash -bun run index.ts -``` - -Google ADK for TypeScript is still changing. Check the [official documentation](https://google.github.io/adk-docs)  and [samples repository](https://github.com/google/adk-samples)  for the latest API updates. - -## Key takeaways - -- **`toZod`** from `@arcadeai/arcadejs` converts Arcade to Zod schemas with proper parameter types -- **`FunctionTool`** wraps the Zod for Google ADK -- **`Runner`** manages the ’s conversation loop with `runAsync()` -- **`InMemorySessionService`** handles session state between messages -- **Authorization** occurs in the execute factory. The auth URL appears when needed -- **`userId`** tracks authorization per - use a consistent ID for each user in your application - -## Example code - -### **index.ts** (full file) - -```typescript -// index.ts -import { - LlmAgent, - FunctionTool, - Runner, - InMemorySessionService, - setLogLevel, - LogLevel, -} from "@google/adk"; -import Arcade from "@arcadeai/arcadejs"; -import { toZod } from "@arcadeai/arcadejs/lib/zod/zod"; -import "dotenv/config"; -import readline from "node:readline/promises"; - -// Suppress verbose ADK logs (options: DEBUG, INFO, WARNING, ERROR) -setLogLevel(LogLevel.ERROR); - -// Configuration -const ARCADE_USER_ID = process.env.ARCADE_USER_ID || "default-user"; -const MCP_SERVERS = ["Slack"]; -const INDIVIDUAL_TOOLS = [ - "Gmail_ListEmails", - "Gmail_SendEmail", - "Gmail_WhoAmI", -]; -const SYSTEM_PROMPT = - "You are a helpful assistant that can assist with Gmail and Slack."; -const MODEL = "gemini-2.0-flash"; -const APP_NAME = "inbox_assistant"; - -async function getArcadeTools( - client: Arcade, - userId: string -): Promise { - // Fetch tools from MCP servers - const mcpServerTools = await Promise.all( - MCP_SERVERS.map(async (serverName) => { - const response = await client.tools.list({ - toolkit: serverName, - limit: 30, - }); - return response.items; - }) - ); - - // Fetch individual tools by name - const individualToolDefs = await Promise.all( - INDIVIDUAL_TOOLS.map((toolName) => client.tools.get(toolName)) - ); - - // Combine and deduplicate - const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; - const uniqueTools = Array.from( - new Map(allTools.map((t) => [t.qualified_name, t])).values() - ); - - // Convert Arcade tools to Zod format (with proper schemas) - const zodTools = toZod({ - tools: uniqueTools, - client, - userId, - executeFactory: ({ toolDefinition, client, userId }) => { - const toolName = toolDefinition.qualified_name; - return async (args: unknown) => { - // Handle authorization - const authResult = await client.tools.authorize({ - tool_name: toolName, - user_id: userId, - }); - - if (authResult.status !== "completed") { - console.log(`\nAuthorization required for ${toolName}`); - console.log(`Please visit: ${authResult.url}\n`); - await client.auth.waitForCompletion(authResult); - console.log("Authorization complete!\n"); - } - - // Execute the tool - const result = await client.tools.execute({ - tool_name: toolName, - input: args as Record, - user_id: userId, - }); - - if (!result.success) { - throw new Error( - `Tool execution failed: ${result.output?.error?.message}` - ); - } - - return result.output?.value; - }; - }, - }); - - // Convert to Google ADK FunctionTool format - return zodTools.map((tool) => - new FunctionTool({ - name: tool.name, - description: tool.description, - parameters: tool.parameters, - execute: tool.execute, - }) - ); -} - -async function main() { - const client = new Arcade(); - const sessionService = new InMemorySessionService(); - - // Get Arcade tools - const arcadeTools = await getArcadeTools(client, ARCADE_USER_ID); - - // Create the agent - const agent = new LlmAgent({ - name: APP_NAME, - description: "An assistant that helps with Gmail and Slack", - model: MODEL, - instruction: SYSTEM_PROMPT, - tools: arcadeTools, - }); - - // Create a session - const session = await sessionService.createSession({ - appName: APP_NAME, - userId: ARCADE_USER_ID, - state: { user_id: ARCADE_USER_ID }, - }); - - // Create the runner - const runner = new Runner({ - appName: APP_NAME, - agent, - sessionService, - }); - - // Set up interactive chat - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - console.log("Hello! I'm your Google ADK Agent with Arcade Tools."); - console.log("Try asking me to summarize your recent emails or send a Slack message!"); - console.log("Type 'exit' to quit.\n"); - - while (true) { - const input = await rl.question("> "); - if (input.toLowerCase() === "exit") { - break; - } - - try { - const events = runner.runAsync({ - userId: ARCADE_USER_ID, - sessionId: session.id, - newMessage: { role: "user", parts: [{ text: input }] }, - }); - - for await (const event of events) { - if (event.content?.parts?.[0]?.text) { - console.log(`\n${event.author}: ${event.content.parts[0].text}\n`); - } - } - } catch (error) { - console.error("Error:", error); - } - } - - console.log("Goodbye!"); - rl.close(); - process.exit(0); -} - -main().catch(console.error); -``` - -## Next steps - -- Add more by modifying `MCP_SERVERS` and `INDIVIDUAL_TOOLS` -- Build multi- systems with different Arcade -- Explore [creating custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) - with the Arcade SDK - -Last updated on February 10, 2026 - -[Setup (Python)](/en/get-started/agent-frameworks/google-adk/setup-python.md) -[Overview](/en/get-started/agent-frameworks/langchain/overview.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/google-adk/use-arcade-tools.md b/public/_markdown/en/get-started/agent-frameworks/google-adk/use-arcade-tools.md deleted file mode 100644 index a5dd9e544..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/google-adk/use-arcade-tools.md +++ /dev/null @@ -1,226 +0,0 @@ ---- -title: "Use Arcade with Google ADK" -description: "Integrate Arcade tools into your Google ADK applications" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[Google ADK](/en/get-started/agent-frameworks/google-adk/overview.md) -Using Arcade tools - -## Use Arcade with Google ADK - -In this guide, let’s explore how to integrate Arcade into your Google ADK application. Follow the step-by-step instructions below. - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys.md) - - -### Set up your environment - -Install the required packages, and ensure your environment variables are set with your key: - -```bash -pip install google-adk-arcade -``` - -### Configure API keys - -Provide your Arcade and Google . You can store it in environment variables or directly in your code: - -> Need an key? Visit the [Get an API key](/get-started/setup/api-keys.md) page to create one. - -```bash -export ARCADE_API_KEY='YOUR_ARCADE_API_KEY' -export GOOGLE_API_KEY='YOUR_GOOGLE_API_KEY' -export GOOGLE_GENAI_USE_VERTEXAI=FALSE -``` - -### Create and manage Arcade tools - -Use the `get_arcade_tools` function to retrieve tools from specific Servers: - -```python -from arcadepy import AsyncArcade -from google_adk_arcade.tools import get_arcade_tools - -# Initialize the Arcade client -client = AsyncArcade() - -# Get all tools from the "Gmail" MCP Server -tools = await get_arcade_tools(client, toolkits=["gmail"]) - -# You can request multiple MCP Servers at once -tools = await get_arcade_tools(client, toolkits=["gmail", "github", "linkedin"]) - -# You can request specific tools -tools = await get_arcade_tools(client, tools=["Gmail_ListEmails", "Slack_ListUsers", "Slack_SendDmToUser"]) -``` - -### Authorize the tools - -Enable the tools for your : - -```python -# authorize the tools -for tool in google_tools: - result = await client.tools.authorize( - tool_name=tool.name, - user_id=user_id - ) - if result.status != "completed": - print(f"Click this link to authorize {tool.name}:\n{result.url}") - await client.auth.wait_for_completion(result) -``` - -### Set up the agent with Arcade tools - -Create an and provide it with the Arcade : - -```python -# import the Google ADK requirements -from google.adk import Agent, Runner -from google.adk.artifacts import InMemoryArtifactService -from google.adk.sessions import InMemorySessionService -from google.genai import types - -# create a new session and artifact services -session_service = InMemorySessionService() -artifact_service = InMemoryArtifactService() - -# Create an agent with Gmail tools -google_agent = Agent( - model="gemini-2.0-flash", - name="google_tool_agent", - instruction="I can use Gmail tools to manage an inbox!", - description="An agent equipped with tools to read Gmail emails." - tools=tools, -) -``` - -### Run the agent with user context - -Run the , providing a user\_id for authorization: - -```python -# create a new session and pass the user id into the context -session = await session_service.create_session( - app_name=app_name, user_id=user_id, state={ - "user_id": user_id, - } -) - -# create a new runner with the agent and services -runner = Runner( - app_name=app_name, - agent=google_agent, - artifact_service=artifact_service, - session_service=session_service, -) - -# pass a prompt to the agent and stream the response -user_input = "summarize my latest 3 emails" -content = types.Content( - role='user', parts=[types.Part.from_text(text=user_input)] -) -for event in runner.run( - user_id=user_id, - session_id=session.id, - new_message=content, -): - if event.content.parts and event.content.parts[0].text: - print(f'** {event.author}: {event.content.parts[0].text}') -``` - -### Complete example - -Here’s a complete example putting everything together: - -```python -import asyncio - -from arcadepy import AsyncArcade -from google.adk import Agent, Runner -from google.adk.artifacts import InMemoryArtifactService -from google.adk.sessions import InMemorySessionService -from google.genai import types - -from google_adk_arcade.tools import get_arcade_tools - - -async def main(): - app_name = 'my_app' - user_id = 'mateo@arcade.dev' - session_service = InMemorySessionService() - artifact_service = InMemoryArtifactService() - client = AsyncArcade() - - google_tools = await get_arcade_tools(client, tools=["Gmail.ListEmails"]) - - # authorize the tools - for tool in google_tools: - result = await client.tools.authorize( - tool_name=tool.name, - user_id=user_id - ) - if result.status != "completed": - print(f"Click this link to authorize {tool.name}:\n{result.url}") - await client.auth.wait_for_completion(result) - - google_agent = Agent( - model="gemini-2.0-flash", - name="google_tool_agent", - instruction="I can use Gmail tools to manage an inbox!", - description="An agent equipped with tools to read Gmail emails. " - "Make sure to call gmail_listemails to read and summarize" - " emails", - tools=google_tools, - ) - session = await session_service.create_session( - app_name=app_name, user_id=user_id, state={ - "user_id": user_id, - } - ) - runner = Runner( - app_name=app_name, - agent=google_agent, - artifact_service=artifact_service, - session_service=session_service, - ) - - user_input = "summarize my latest 3 emails" - content = types.Content( - role='user', parts=[types.Part.from_text(text=user_input)] - ) - for event in runner.run( - user_id=user_id, - session_id=session.id, - new_message=content, - ): - if event.content.parts and event.content.parts[0].text: - print(f'** {event.author}: {event.content.parts[0].text}') - -if __name__ == '__main__': - asyncio.run(main()) - -``` - -## Tips for selecting tools - -- **Relevance**: Pick only the you need. Avoid using all tools at once. -- ** identification**: Always provide a unique and consistent `user_id` for each user. Use your internal or database user ID, not something entered by the user. - -## Next steps - -Now that you have integrated Arcade into your Google ADK application, you can: - -- Experiment with different Servers, such as “Github” or “LinkedIn” -- Customize the ’s instructions for specific tasks -- Try out multi- systems using different Arcade -- Build your own custom tools with the Arcade SDK - -Enjoy exploring Arcade and building powerful AI-enabled Python applications! - -Last updated on February 10, 2026 - -[Overview](/en/get-started/agent-frameworks/google-adk/overview.md) -[Setup Arcade with LangChain (Python)](/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md b/public/_markdown/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md deleted file mode 100644 index a6a622d2a..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md +++ /dev/null @@ -1,219 +0,0 @@ ---- -title: "Authorize Existing Tools" -description: "Use Arcade to authorize existing tools" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[LangChain](/en/get-started/agent-frameworks/langchain/overview.md) -Authorizing Existing Tools - -## Authorize Existing Tools - -In this guide, we’ll show you how to authorize LangChain like the `GmailToolkit` using Arcade. You may already have tools you want to use, and this guide will show you how to authorize them. Arcade handles retrieving, authorizing, and managing tokens so you don’t have to. - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys.md) - - -### Install the required packages - -Instead of the `langchain_arcade` package, you only need the `arcadepy` package to authorize existing since Arcade tools are not being used. - -### Python - -```bash -pip install langchain-openai langgraph arcadepy langchain-google-community -``` - -### JavaScript - -```bash -npm install @arcadeai/arcadejs @langchain/openai @langchain/core @langchain/langgraph @langchain/community -``` - -### Import the necessary packages - -### Python - -```python -import os -from arcadepy import Arcade -from google.oauth2.credentials import Credentials -from langchain_google_community import GmailToolkit -from langchain_google_community.gmail.utils import build_resource_service -from langchain_openai import ChatOpenAI -from langgraph.prebuilt import create_react_agent -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; -import { - GmailCreateDraft, - GmailGetMessage, - GmailGetThread, - GmailSearch, - GmailSendMessage, -} from "@langchain/community/tools/gmail"; -import type { StructuredTool } from "@langchain/core/tools"; -import { createReactAgent } from "@langchain/langgraph/prebuilt"; -import { ChatOpenAI } from "@langchain/openai"; -``` - -### Initialize the Arcade client - -### Python - -```python -api_key = os.getenv("ARCADE_API_KEY") -client = Arcade(api_key=api_key) -``` - -### JavaScript - -```javascript -const arcade = new Arcade(); -``` - -### Start the authorization process for Gmail - -### Python - -```python -user_id = "{arcade_user_id}" -auth_response = client.auth.start( - user_id=user_id, - provider="google", - scopes=["https://www.googleapis.com/auth/gmail.readonly"], -) -``` - -### JavaScript - -```javascript -const USER_ID = "{arcade_user_id}"; -const authResponse = await arcade.auth.start(USER_ID, "google", { - scopes: ["https://www.googleapis.com/auth/gmail.readonly"], -}); - -``` - -### Prompt the user to authorize - -### Python - -```python -if auth_response.status != "completed": - print("Please authorize the application in your browser:") - print(auth_response.url) -``` - -The `auth_response.status` will be `"completed"` if the has already authorized the application, so they won’t be prompted to authorize again. - -### JavaScript - -```javascript -if (authResponse.status !== "completed") { - console.log("Please authorize the application in your browser:"); - console.log(authResponse.url); -} -``` - -The `authResponse.status` will be `"completed"` if the has already authorized the application, so they won’t be prompted to authorize again. - -### Wait for authorization completion - -### Python - -```python -auth_response = client.auth.wait_for_completion(auth_response) -``` - -The `wait_for_completion` method will do nothing if the has already authorized the application. - -### JavaScript - -```javascript -const completedAuth = await arcade.auth.waitForCompletion(authResponse); -``` - -The `waitForCompletion` method will do nothing if the has already authorized the application. - -### Use the token to initialize the Gmail MCP Server - -### Python - -```python -creds = Credentials(auth_response.context.token) -api_resource = build_resource_service(credentials=creds) -toolkit = GmailToolkit(api_resource=api_resource) -tools = toolkit.get_tools() -``` - -### JavaScript - -```javascript -const gmailParam = { - credentials: { - accessToken: completedAuth.context.token, - }, -}; -const tools: StructuredTool[] = [ - new GmailCreateDraft(gmailParam), - new GmailGetMessage(gmailParam), - new GmailGetThread(gmailParam), - new GmailSearch(gmailParam), - new GmailSendMessage(gmailParam), -]; -``` - -### Initialize the agent - -### Python - -```python -model = ChatOpenAI(model="gpt-4o") -agent_executor = create_react_agent(model, tools) -``` - -### JavaScript - -```javascript -const llm = new ChatOpenAI({ model: "gpt-4o" }); -const agent_executor = createReactAgent({ llm, tools }); -``` - -### Execute the agent - -### Python - -```python -example_query = "Read my latest emails and summarize them." - -events = agent_executor.stream( - {"messages": [("user", example_query)]}, - stream_mode="values", -) -for event in events: - event["messages"][-1].pretty_print() -``` - -### JavaScript - -```javascript -const exampleQuery = "Read my latest emails and summarize them."; -const events = await agent_executor.stream({ messages: [ { role: "user", content: exampleQuery } ] }, { streamMode: "values" } ); -for await (const event of events) { - console.log(event.messages[event.messages.length - 1]); -} -``` - -### Next Steps - -Now you’re ready to explore more LangChain tools with Arcade. Try integrating additional Servers and crafting more complex queries to enhance your AI workflows. - -Last updated on February 10, 2026 - -[Setup (TypeScript)](/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-ts.md) -[Mastra](/en/get-started/agent-frameworks/mastra.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/langchain/overview.md b/public/_markdown/en/get-started/agent-frameworks/langchain/overview.md deleted file mode 100644 index b335cdd13..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/langchain/overview.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "Arcade with LangChain" -description: "Integrate Arcade tools with LangChain agents" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -LangChainOverview - -# Arcade with LangChain - -[LangChain](https://www.langchain.com/)  is a popular framework for building AI agents that abstracts much of the complexity of development. Arcade integrates with both the Python and JavaScript versions, giving your agents access to Gmail, GitHub, Slack, and 100+ other . - -## Get started - -Choose your language to set up Arcade with LangChain: - -- **[Python setup](/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py.md) - ** - Build an with Arcade using `langchain-arcade` -- **[TypeScript setup](/get-started/agent-frameworks/langchain/use-arcade-with-langchain-ts.md) - ** - Build an with Arcade using `@arcadeai/arcadejs` - -## What you can build - -With Arcade and LangChain, your can: - -- Read and send emails via Gmail -- Post messages to Slack channels -- Create GitHub issues and pull requests -- Search the web and extract content -- Access 100+ other integrations - -Browse the [full MCP server catalog](/resources/integrations.md) to see all available . - -## Advanced topics - -- **[Authorizing existing tools](/get-started/agent-frameworks/langchain/auth-langchain-tools.md) - ** - Add Arcade authorization to your existing LangChain - -Last updated on February 10, 2026 - -[Setup (TypeScript)](/en/get-started/agent-frameworks/google-adk/setup-typescript.md) -[Setup (Python)](/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py.md b/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py.md deleted file mode 100644 index ab48ddda7..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py.md +++ /dev/null @@ -1,877 +0,0 @@ ---- -title: "Setup Arcade with LangChain" -description: "Learn how to use Arcade tools in LangChain agents" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[LangChain](/en/get-started/agent-frameworks/langchain/overview.md) -Setup (Python) - -# Setup Arcade with LangChain - -LangChain is a popular agentic framework that abstracts a lot of the complexity of building AI agents. It is built on top of LangGraph, a lower level orchestration framework that offers more control over the inner flow of the . - -## Outcomes - -Learn how to integrate Arcade using LangChain primitives - -### You will Learn - -- How to retrieve Arcade and transform them into LangChain tools -- How to build a LangChain -- How to integrate Arcade into the agentic flow -- How to manage Arcade authorization using LangChain interrupts - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- The [`uv` package manager](https://docs.astral.sh/uv/) - - -## LangChain primitives you will use in this guide - -LangChain provides multiple abstractions for building AI , and it’s useful to internalize how some of these primitives work, so you can understand and extend the different agentic patterns LangChain supports. - -- : Most agentic frameworks, including LangChain, provide an abstraction for a . -- [_Interrupts_](https://docs.langchain.com/oss/python/langgraph/interrupts) - : Interrupts in LangChain are a way to control the flow of the agentic loop when something needs to be done outside of the normal ReAct flow. For example, if a tool requires authorization, you can interrupt the and ask the user to authorize the before continuing. -- [_Checkpointers_](https://docs.langchain.com/oss/python/langgraph/persistence) - : Checkpointers are how LangChain implements persistence. A checkpointer stores the ’s state in a “checkpoint” that you can resume later. You save those checkpoints to a _thread_, which you can access after the agent’s execution, making it simple for long-running agents and for handling interruptions and more sophisticated flows such as branching, time travel, and more. - -## Integrate Arcade tools into a LangChain agent - -### Create a new project - -```bash -mkdir langchain-arcade-example -cd langchain-arcade-example -uv init -uv venv -``` - -### Bash/Zsh (macOS/Linux) - -```bash -source .venv/bin/activate -``` - -### PowerShell (Windows) - -```powershell -. ".venv\Scripts\Activate.ps1" -``` - -```powershell -uv add arcadepy langchain langchain-openai python-dotenv -``` - -Create a new file called `.env` and add the following : - -```powershell -# .env -ARCADE_API_KEY=YOUR_ARCADE_API_KEY -OPENAI_API_KEY=YOUR_OPENAI_API_KEY -``` - -### Import the necessary packages - -Create a new file called `main.py` and add the following code: - -```python -# main.py -import asyncio -import os -from typing import Dict, Any, List - -from dotenv import load_dotenv -from arcadepy import AsyncArcade -from arcadepy.types import ToolDefinition -from langchain.agents import create_agent -from langchain_core.messages import AIMessage, ToolMessage -from langchain_core.tools import StructuredTool -from langchain_core.runnables import RunnableConfig -from langchain_openai import ChatOpenAI -from langgraph.checkpoint.memory import MemorySaver -from langgraph.types import Command, interrupt -from pydantic import BaseModel, Field, create_model -``` - -This is quite a number of imports, let’s break them down: - -- Arcade imports: - - `AsyncArcade`: The , interacts with the . - - `ToolDefinition`: The tool definition type, defines the shape of the tools that the can use. -- LangChain imports: - - `create_agent`: Creates a LangChain using the ReAct pattern. - - `AIMessage`: The message type for the ’s response. - - `ToolMessage`: The message type for the ’s calls. - - `StructuredTool`: LangChain’s tool definition type, defines the shape of the tools that the can use. - - `RunnableConfig`: The configuration type for the ’s run, includes the thread ID and other configuration options. - - `ChatOpenAI`: The LLM to use for the ’s response, specific to OpenAI models. - - `MemorySaver`: Stores the ’s state, and checkpointing and interrupts require it. - - `Command`: Communicates the user’s decisions to the ’s interrupts. - - `interrupt`: Interrupts the ReAct flow and asks the for input. -- Other imports: - - `load_dotenv`: Loads the environment variables from the `.env` file. - - `os`: The operating system module, used to interact with the operating system. - - `typing` imports: Used for type hints, which are not required but recommended for type safety. - - `pydantic` imports: Used for data validation and model creation when converting Arcade to LangChain tools. - -### Configure the agent - -The rest of the code uses these variables to customize the and manage the . Feel free to configure them to your liking. - -```python -# main.py -# Load environment variables from the .env file -load_dotenv() - -# Configure your own values to customize your agent -# 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 use Gmail tools. Your main task is to help the user with anything they may need." -# This determines which LLM will be used inside the agent -MODEL = "gpt-5-nano" -``` - -### Write a helper function to convert Arcade tools to LangChain tools - -Here you convert the Arcade to LangChain tools. You use the `arcade_schema_to_pydantic` function to convert the Arcade tool definition to a Pydantic model, and then use the moddel to define a `StructuredTool` and create a LangChain tool. - -The `arcade_to_langchain` function wraps the and dynamically creates a `tool_function` that executes the and handles the authorization flow using the `interrupt` function. Once the tool is authorized, the `tool_function` will use the Arcade client to execute the tool with the provided arguments, and handle any errors that may occur. - -```python -# main.py -TYPE_MAPPING = { - "string": str, - "number": float, - "integer": int, - "boolean": bool, - "array": list, - "json": dict, -} - -def get_python_type(val_type: str) -> Any: - _type = TYPE_MAPPING.get(val_type) - if _type is None: - raise ValueError(f"Invalid value type: {val_type}") - return _type - -def arcade_schema_to_pydantic(tool_def: ToolDefinition) -> type[BaseModel]: - try: - fields: dict[str, Any] = {} - for param in tool_def.input.parameters or []: - param_type = get_python_type(param.value_schema.val_type) - if param_type is list and param.value_schema.inner_val_type: - inner_type: type[Any] = get_python_type(param.value_schema.inner_val_type) - param_type = list[inner_type] - param_description = param.description or "No description provided." - default = ... if param.required else None - fields[param.name] = ( - param_type, - Field(default=default, description=param_description), - ) - return create_model(f"{tool_def.name}Args", **fields) - except ValueError as e: - raise ValueError( - f"Error converting {tool_def.name} parameters into pydantic model for langchain: {e}" - ) - -async def arcade_to_langchain( - arcade_client: AsyncArcade, - arcade_tool: ToolDefinition, -) -> StructuredTool: - # Convert Arcade schema to Pydantic model - args_schema = arcade_schema_to_pydantic(arcade_tool) - - # Create the executor function with interrupt handling - async def tool_function(config: RunnableConfig, **kwargs: Any) -> Any: - user_id = config.get("configurable", {}).get("user_id") if config else None - if not user_id: - raise ValueError("User ID is required to execute Arcade tools") - - auth_response = await arcade_client.tools.authorize( - tool_name=arcade_tool.qualified_name, - user_id=user_id - ) - - if auth_response.status != "completed": - # Interrupt the agent to handle authorization - interrupt_result = interrupt({ - "type": "authorization_required", - "tool_name": arcade_tool.qualified_name, - "auth_response": { - "id": auth_response.id, - "url": auth_response.url, - } - }) - - # Resume the flow with the authorization decision - authorized = interrupt_result.get("authorized") - if not authorized: - raise RuntimeError( - f"Authorization was not completed for tool {arcade_tool.name}" - ) - - # Filter out None values to avoid passing unset optional parameters - filtered_kwargs = {k: v for k, v in kwargs.items() if v is not None} - - response = await arcade_client.tools.execute( - tool_name=arcade_tool.qualified_name, - input=filtered_kwargs, - user_id=user_id, - ) - - if response.output and response.output.value: - return response.output.value - - error_details = { - "error": "Unknown error occurred", - "tool": arcade_tool.qualified_name, - } - - if response.output is not None and response.output.error is not None: - error = response.output.error - error_message = str(error.message) if hasattr(error, "message") else "Unknown error" - error_details["error"] = error_message - - # Add all non-None optional error fields to the details - for field in ["additional_prompt_content", "can_retry", "developer_message", "retry_after_ms"]: - if (value := getattr(error, field, None)) is not None: - error_details[field] = str(value) - - return error_details - - # Create and return the LangChain StructuredTool - return StructuredTool.from_function( - coroutine=tool_function, - name=arcade_tool.qualified_name.replace(".", "_"), - description=arcade_tool.description, - args_schema=args_schema - ) -``` - -### Write a helper function to get Arcade tools in LangChain format - -In this helper function you use the to retrieved the you configured at the beginning of the `main.py` file. You will use a dictionary to store the tools and avoid possible duplicates that may occur if you retrieve the same tool in the `TOOLS` and `MCP_SERVERS` variables. After retrieving all the tools, you will call the `arcade_to_langchain` function to convert the Arcade tools to LangChain tools. - -```python -# main.py -async def get_arcade_tools( - arcade_client: AsyncArcade | None = None, - mcp_servers: List[str] | None = None, - tools: List[str] | None = None, -) -> List[StructuredTool]: - - if not arcade_client: - arcade_client = AsyncArcade(api_key=os.getenv("ARCADE_API_KEY")) - - # 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") - - # Collect tool definitions, using qualified name as key to avoid duplicates - tool_definitions: dict[str, ToolDefinition] = {} - - # Retrieve individual tools if specified - if tools: - tasks = [arcade_client.tools.get(name=tool_name) for tool_name in tools] - responses = await asyncio.gather(*tasks) - for response in responses: - tool_definitions[response.fully_qualified_name] = response - - # Retrieve tools from specified toolkits - if mcp_servers: - tasks = [arcade_client.tools.list(toolkit=mcp_server) for mcp_server in mcp_servers] - responses = await asyncio.gather(*tasks) - - # Combine the tool definitions from each response. - for response in responses: - for tool in response.items: - tool_definitions[tool.fully_qualified_name] = tool - - tasks = [arcade_to_langchain(arcade_client, tool_definition) for tool_definition in tool_definitions.values()] - langchain_tools = await asyncio.gather(*tasks) - return langchain_tools -``` - -### Write the interrupt handler - -In LangChain, each interrupt needs to be “resolved” for the flow to continue. In response to an interrupt, you need to return a decision object with the information needed to resolve the interrupt. In this case, the decision is whether the authorization was successful, in which case the tool call will be retried, or if the authorization failed, the flow will be interrupted with an error, and the will decide what to do next. - -This helper function receives an interrupt and returns a decision object. Decision objects can be of any serializable type (convertible to JSON). In this case, you return a dictionary with a boolean flag indicating if the authorization was successful. - -This function captures the authorization flow outside of the agent’s context, which is a good practice for security and context engineering. By handling everything in the , you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the free from any authorization-related traces, which reduces the risk of hallucinations. - -```python -# main.py -async def handle_authorization_interrupt( - interrupt_value: Dict[str, Any], - arcade_client: AsyncArcade -) -> Dict[str, bool]: - # Extract authorization context - auth_response = interrupt_value.get("auth_response", {}) - auth_id = auth_response.get("id") - auth_url = auth_response.get("url") - tool_name = interrupt_value.get("tool_name") - - if not auth_id or not auth_url: - print("❌ Authorization interrupt missing required context") - return {"authorized": False} - - # Display authorization URL to user - print(f"\n{'='*70}") - print(f"🔐 Authorization Required for {tool_name}") - print("\nPlease visit the following URL to authorize:") - print(f"\n {auth_url}\n") - print("Waiting for authorization to complete...") - print(f"{'='*70}\n") - - try: - status_response = await arcade_client.auth.wait_for_completion(auth_id) - - if status_response.status == "completed": - print("✅ Authorization completed successfully!\n") - return {"authorized": True} - else: - print(f"❌ Authorization failed with status: {status_response.status}\n") - return {"authorized": False} - - except Exception as e: - print(f"❌ Error during authorization: {str(e)}\n") - return {"authorized": False} -``` - -### Write the invoke helper - -This last helper function handles the streaming of the ’s response, and captures the interrupts. When an interrupt is detected, it is added to the `interrupts` array, and the flow is interrupted. If there are no interrupts, it will just stream the agent’s to your console. - -```python -# main.py -async def stream_agent_response(agent, input_data, config) -> List[Any]: - interrupts = [] - - async for chunk in agent.astream(input_data, config, stream_mode="updates"): - # Check and collect interrupts - if "__interrupt__" in chunk: - interrupts.extend(chunk["__interrupt__"]) - - # Display agent actions - for node_name, node_output in chunk.items(): - if node_name == "__interrupt__": - continue - - if "messages" in node_output: - for msg in node_output["messages"]: - # Tool calls from the AI - if isinstance(msg, AIMessage) and msg.tool_calls: - for tool_call in msg.tool_calls: - print(f"🔧 Calling tool: {tool_call['name']}") - - # Tool response - just acknowledge it, don't dump the content - elif isinstance(msg, ToolMessage): - print(f" ✓ {msg.name} completed, processing output...") - - # Final AI response text - elif isinstance(msg, AIMessage) and msg.content: - print(f"\n🤖 Assistant:\n{msg.content}") - - return interrupts -``` - -### Write the main function - -Finally, write the main function that will create the , initialize the conversation, and handle the input. - -Here the `config` object is used to configure the `thread_id`, which tells the to store the state of the conversation into that specific thread. In the main function you will also initialize the checkpointer, and handle route the interrupts to the handles you wrote earlier. Notice how a single turn of the agentic loop may have multiple interrupts, and you need to handle them all before continuing to the next turn. - -```python -# main.py -async def main(): - # Initialize Arcade client - arcade = AsyncArcade() - - # Get tools - all_tools = await get_arcade_tools(arcade_client=arcade, - mcp_servers=MCP_SERVERS, tools=TOOLS) - - # Initialize LLM - model = ChatOpenAI( - model=MODEL, - api_key=os.getenv("OPENAI_API_KEY") - ) - - # Create agent with memory checkpointer - memory = MemorySaver() - agent = create_agent( - system_prompt=SYSTEM_PROMPT, - model=model, - tools=all_tools, - checkpointer=memory - ) - - print(f"\n🤖 Agent created with {len(all_tools)} tools") - print("Type 'quit' or 'exit' to end the conversation.\n") - print("="*70) - - # Configuration for agent execution - config = { - "configurable": { - "thread_id": "conversation_thread", - "user_id": ARCADE_USER_ID - } - } - - # Interactive conversation loop - while True: - # Get user input - try: - user_message = input("\n💬 You: ").strip() - except (EOFError, KeyboardInterrupt): - print("\n\n👋 Goodbye!") - break - - # Check for exit commands - if not user_message: - continue - if user_message.lower() in ("quit", "exit", "q"): - print("\n👋 Goodbye!") - break - - print("="*70) - - # Start with user message - current_input = {"messages": [{"role": "user", "content": user_message}]} - - # Agent execution loop with interrupt handling - while True: - print("\n🔄 Running agent...\n") - - interrupts = await stream_agent_response(agent, current_input, config) - - # Handle interrupts if any occurred - if interrupts: - print(f"\n⚠️ Detected {len(interrupts)} interrupt(s)\n") - - # Process each interrupt - for interrupt_obj in interrupts: - interrupt_type = interrupt_obj.value.get("type") - - if interrupt_type == "authorization_required": - # Handle authorization interrupt - decision = await handle_authorization_interrupt( - interrupt_obj.value, - arcade - ) - - # Resume agent with authorization decision - current_input = Command(resume=decision) - break # Continue to next iteration - else: - print(f"❌ Unknown interrupt type: {interrupt_type}") - break - else: - # All interrupts processed without break - break - else: - # No interrupts - agent completed successfully - print("\n✅ Response complete!") - break - - print("\n" + "="*70) - - -if __name__ == "__main__": - asyncio.run(main()) -``` - -### Run the agent - -```bash -uv run main.py -``` - -You should see the responding to your prompts like any model, as well as handling any calls and authorization requests. Here are some example prompts you can try: - -- “Send me an email with a random haiku about LangChain ” -- “Summarize my latest 3 emails” - -## Key takeaways - -- Arcade can be integrated into any agentic framework like LangChain, all you need is to transform the Arcade tools into LangChain tools and handle the authorization flow. -- isolation: By handling the authorization flow outside of the ’s context, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations. -- You can leverage the interrupts mechanism to handle human intervention in the ’s flow, useful for authorization flows, policy enforcement, or anything else that requires input from the . - -## Next Steps - -1. Try adding additional tools to the or modifying the in the catalog for a different use case by modifying the `MCP_SERVERS` and `TOOLS` variables. -2. Try refactoring the `handle_authorization_interrupt` function to handle more complex flows, such as human-in-the-loop. -3. Try implementing a fully deterministic flow before the agentic loop, use this deterministic phase to prepare the for the , adding things like the current date, time, or any other information that is relevant to the task at hand. - -## Example code - -### **main.py** (full file) - -```python -# main.py -import asyncio -import os -from typing import Dict, Any, List - -from dotenv import load_dotenv -from arcadepy import AsyncArcade -from arcadepy.types import ToolDefinition -from langchain.agents import create_agent -from langchain_core.messages import AIMessage, ToolMessage -from langchain_core.tools import StructuredTool -from langchain_core.runnables import RunnableConfig -from langchain_openai import ChatOpenAI -from langgraph.checkpoint.memory import MemorySaver -from langgraph.types import Command, interrupt -from pydantic import BaseModel, Field, create_model - -# Load environment variables from the .env file -load_dotenv() - -# Configure your own values to customize your agent -# 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 use Gmail tools. Your main task is to help the user with anything they may need." -# This determines which LLM will be used inside the agent -MODEL = "gpt-4o-mini" - -TYPE_MAPPING = { - "string": str, - "number": float, - "integer": int, - "boolean": bool, - "array": list, - "json": dict, -} - -def get_python_type(val_type: str) -> Any: - _type = TYPE_MAPPING.get(val_type) - if _type is None: - raise ValueError(f"Invalid value type: {val_type}") - return _type - -def arcade_schema_to_pydantic(tool_def: ToolDefinition) -> type[BaseModel]: - try: - fields: dict[str, Any] = {} - for param in tool_def.input.parameters or []: - param_type = get_python_type(param.value_schema.val_type) - if param_type is list and param.value_schema.inner_val_type: - inner_type: type[Any] = get_python_type(param.value_schema.inner_val_type) - param_type = list[inner_type] - param_description = param.description or "No description provided." - default = ... if param.required else None - fields[param.name] = ( - param_type, - Field(default=default, description=param_description), - ) - return create_model(f"{tool_def.name}Args", **fields) - except ValueError as e: - raise ValueError( - f"Error converting {tool_def.name} parameters into pydantic model for langchain: {e}" - ) - - -async def arcade_to_langchain( - arcade_client: AsyncArcade, - arcade_tool: ToolDefinition, -) -> StructuredTool: - # Convert Arcade schema to Pydantic model - args_schema = arcade_schema_to_pydantic(arcade_tool) - - # Create the executor function with interrupt handling - async def tool_function(config: RunnableConfig, **kwargs: Any) -> Any: - user_id = config.get("configurable", {}).get("user_id") if config else None - if not user_id: - raise ValueError("User ID is required to execute Arcade tools") - - auth_response = await arcade_client.tools.authorize( - tool_name=arcade_tool.qualified_name, - user_id=user_id - ) - - if auth_response.status != "completed": - # Interrupt the agent to handle authorization - interrupt_result = interrupt({ - "type": "authorization_required", - "tool_name": arcade_tool.qualified_name, - "auth_response": { - "id": auth_response.id, - "url": auth_response.url, - } - }) - - # Resume the flow with the authorization decision - authorized = interrupt_result.get("authorized") - if not authorized: - raise RuntimeError( - f"Authorization was not completed for tool {arcade_tool.name}" - ) - - # Filter out None values to avoid passing unset optional parameters - filtered_kwargs = {k: v for k, v in kwargs.items() if v is not None} - - response = await arcade_client.tools.execute( - tool_name=arcade_tool.qualified_name, - input=filtered_kwargs, - user_id=user_id, - ) - - if response.output and response.output.value: - return response.output.value - - error_details = { - "error": "Unknown error occurred", - "tool": arcade_tool.qualified_name, - } - - if response.output is not None and response.output.error is not None: - error = response.output.error - error_message = str(error.message) if hasattr(error, "message") else "Unknown error" - error_details["error"] = error_message - - # Add all non-None optional error fields to the details - for field in ["additional_prompt_content", "can_retry", "developer_message", "retry_after_ms"]: - if (value := getattr(error, field, None)) is not None: - error_details[field] = str(value) - - return error_details - - # Create and return the LangChain StructuredTool - return StructuredTool.from_function( - coroutine=tool_function, - name=arcade_tool.qualified_name.replace(".", "_"), - description=arcade_tool.description, - args_schema=args_schema - ) - - -async def get_arcade_tools( - arcade_client: AsyncArcade | None = None, - mcp_servers: List[str] | None = None, - tools: List[str] | None = None, - tool_limit: int = TOOL_LIMIT, -) -> List[StructuredTool]: - - if not arcade_client: - arcade_client = AsyncArcade(api_key=os.getenv("ARCADE_API_KEY")) - - # 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") - - # Collect tool definitions, using qualified name as key to avoid duplicates - tool_definitions: dict[str, ToolDefinition] = {} - - # Retrieve individual tools if specified - if tools: - tasks = [arcade_client.tools.get(name=tool_name) for tool_name in tools] - responses = await asyncio.gather(*tasks) - for response in responses: - tool_definitions[response.fully_qualified_name] = response - - # Retrieve tools from specified toolkits - if mcp_servers: - tasks = [arcade_client.tools.list(toolkit=mcp_server, limit=tool_limit) for mcp_server in mcp_servers] - responses = await asyncio.gather(*tasks) - - # Combine the tool definitions from each response. - for response in responses: - for tool in response.items: - tool_definitions[tool.fully_qualified_name] = tool - - tasks = [arcade_to_langchain(arcade_client, tool_definition) for tool_definition in tool_definitions.values()] - langchain_tools = await asyncio.gather(*tasks) - return langchain_tools - - -async def handle_authorization_interrupt( - interrupt_value: Dict[str, Any], - arcade_client: AsyncArcade -) -> Dict[str, bool]: - # Extract authorization context - auth_response = interrupt_value.get("auth_response", {}) - auth_id = auth_response.get("id") - auth_url = auth_response.get("url") - tool_name = interrupt_value.get("tool_name") - - if not auth_id or not auth_url: - print("❌ Authorization interrupt missing required context") - return {"authorized": False} - - # Display authorization URL to user - print(f"\n{'='*70}") - print(f"🔐 Authorization Required for {tool_name}") - print("\nPlease visit the following URL to authorize:") - print(f"\n {auth_url}\n") - print("Waiting for authorization to complete...") - print(f"{'='*70}\n") - - try: - status_response = await arcade_client.auth.wait_for_completion(auth_id) - - if status_response.status == "completed": - print("✅ Authorization completed successfully!\n") - return {"authorized": True} - else: - print(f"❌ Authorization failed with status: {status_response.status}\n") - return {"authorized": False} - - except Exception as e: - print(f"❌ Error during authorization: {str(e)}\n") - return {"authorized": False} - - -async def stream_agent_response(agent, input_data, config) -> List[Any]: - interrupts = [] - - async for chunk in agent.astream(input_data, config, stream_mode="updates"): - # Check and collect interrupts - if "__interrupt__" in chunk: - interrupts.extend(chunk["__interrupt__"]) - - # Display agent actions - for node_name, node_output in chunk.items(): - if node_name == "__interrupt__": - continue - - if "messages" in node_output: - for msg in node_output["messages"]: - # Tool calls from the AI - if isinstance(msg, AIMessage) and msg.tool_calls: - for tool_call in msg.tool_calls: - print(f"🔧 Calling tool: {tool_call['name']}") - - # Tool response - just acknowledge it, don't dump the content - elif isinstance(msg, ToolMessage): - print(f" ✓ {msg.name} completed, processing output...") - - # Final AI response text - elif isinstance(msg, AIMessage) and msg.content: - print(f"\n🤖 Assistant:\n{msg.content}") - - return interrupts - - -async def main(): - # Initialize Arcade client - arcade = AsyncArcade() - - # Get tools - all_tools = await get_arcade_tools(arcade_client=arcade, - mcp_servers=MCP_SERVERS, tools=TOOLS) - - # Initialize LLM - model = ChatOpenAI( - model=MODEL, - api_key=os.getenv("OPENAI_API_KEY") - ) - - # Create agent with memory checkpointer - memory = MemorySaver() - agent = create_agent( - system_prompt=SYSTEM_PROMPT, - model=model, - tools=all_tools, - checkpointer=memory - ) - - print(f"\n🤖 Agent created with {len(all_tools)} tools") - print("Type 'quit' or 'exit' to end the conversation.\n") - print("="*70) - - # Configuration for agent execution - config = { - "configurable": { - "thread_id": "conversation_thread", - "user_id": ARCADE_USER_ID - } - } - - # Interactive conversation loop - while True: - # Get user input - try: - user_message = input("\n💬 You: ").strip() - except (EOFError, KeyboardInterrupt): - print("\n\n👋 Goodbye!") - break - - # Check for exit commands - if not user_message: - continue - if user_message.lower() in ("quit", "exit", "q"): - print("\n👋 Goodbye!") - break - - print("="*70) - - # Start with user message - current_input = {"messages": [{"role": "user", "content": user_message}]} - - # Agent execution loop with interrupt handling - while True: - print("\n🔄 Running agent...\n") - - interrupts = await stream_agent_response(agent, current_input, config) - - # Handle interrupts if any occurred - if interrupts: - print(f"\n⚠️ Detected {len(interrupts)} interrupt(s)\n") - - # Process each interrupt - for interrupt_obj in interrupts: - interrupt_type = interrupt_obj.value.get("type") - - if interrupt_type == "authorization_required": - # Handle authorization interrupt - decision = await handle_authorization_interrupt( - interrupt_obj.value, - arcade - ) - - # Resume agent with authorization decision - current_input = Command(resume=decision) - break # Continue to next iteration - else: - print(f"❌ Unknown interrupt type: {interrupt_type}") - break - else: - # All interrupts processed without break - break - else: - # No interrupts - agent completed successfully - print("\n✅ Response complete!") - break - - print("\n" + "="*70) - - -if __name__ == "__main__": - asyncio.run(main()) -``` - -Last updated on February 10, 2026 - -[Overview](/en/get-started/agent-frameworks/langchain/overview.md) -[Setup (TypeScript)](/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-ts.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-ts.md b/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-ts.md deleted file mode 100644 index c8a1adcca..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-ts.md +++ /dev/null @@ -1,750 +0,0 @@ ---- -title: "Setup Arcade with LangChain" -description: "Learn how to use Arcade tools in LangChain agents" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[LangChain](/en/get-started/agent-frameworks/langchain/overview.md) -Setup (TypeScript) - -# Setup Arcade with LangChain - -Learn how to integrate Arcade tools using LangChain primitives to build AI . - -LangChain is a popular agentic framework that abstracts a lot of the complexity of building AI agents. LangGraph, a lower level orchestration framework, builds it and offers more control over the inner flow of the . - -## Outcomes - -Learn how to integrate Arcade using LangChain primitives - -### You will Learn - -- How to retrieve Arcade and transform them into LangChain tools -- How to build a LangChain -- How to integrate Arcade into the agentic flow -- How to manage Arcade authorization using LangChain interrupts - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- The [`bun` runtime](https://bun.com/) - - -## LangChain primitives you will use in this guide - -LangChain provides multiple abstractions for building AI , and it’s useful to internalize how some of these primitives work, so you can understand and extend the different agentic patterns LangChain supports. - -- : Most agentic frameworks, including LangChain, provide an abstraction for a . -- [_Interrupts_](https://docs.langchain.com/oss/javascript/langgraph/interrupts) - : Interrupts in LangChain are a way to control the flow of the agentic loop when something needs to occur outside of the normal ReAct flow. For example, if a tool requires authorization, you can interrupt the and ask the user to authorize the before continuing. -- [_Checkpointers_](https://docs.langchain.com/oss/javascript/langgraph/persistence) - : Checkpointers are how LangChain implements persistence. A checkpointer stores the ’s state in a “checkpoint” that you can resume later. Those checkpoints are saved to a _thread_, which you can access after the agent’s execution, making it simple for long-running agents and for handling interruptions and more sophisticated flows such as branching, time travel, and more. - -## Integrate Arcade tools into a LangChain agent - -### Create a new project - -```bash -mkdir langchain-arcade-example -cd langchain-arcade-example -bun install @arcadeai/arcadejs langchain @langchain/openai @langchain/core @langchain/langgraph -``` - -Create a new file called `.env` and add the following : - -```bash -# .env -ARCADE_API_KEY=YOUR_ARCADE_API_KEY -OPENAI_API_KEY=YOUR_OPENAI_API_KEY -``` - -### Import the necessary packages - -Create a new file called `main.ts` and add the following code: - -```typescript -// main.ts -"use strict"; -import { Arcade } from "@arcadeai/arcadejs"; -import { - type ToolExecuteFunctionFactoryInput, - executeZodTool, - isAuthorizationRequiredError, - toZod, -} from "@arcadeai/arcadejs/lib/index"; -import { type ToolExecuteFunction } from "@arcadeai/arcadejs/lib/zod/types"; -import { createAgent, tool } from "langchain"; -import { - Command, - interrupt, - MemorySaver, - type Interrupt, -} from "@langchain/langgraph"; -import chalk from "chalk"; -import readline from "node:readline/promises"; -``` - -This is a number of imports, examine them: - -- Arcade imports: - - `Arcade`: This is the , used to interact with the . - - `type ToolExecuteFunctionFactoryInput`: Encodes the input to execute Arcade . - - `isAuthorizationRequiredError`: Checks if a requires authorization. - - `toZod`: Converts an Arcade definition into a [Zod](https://zod.dev) -   schema (Zod provides type safety and validation at runtime). - - `executeZodTool`: Executes an Zod-converted . -- LangChain imports: - - `createAgent`: Creates a LangChain using the ReAct pattern. - - `tool`: Turns an Arcade definition into a LangChain tool. - - `interrupt`: Interrupts the ReAct flow and asks the for input. - - `Command`: Communicates the user’s decisions to the ’s interrupts. - - `MemorySaver`: Stores the ’s state, and is required for checkpointing and interrupts. -- Other imports: - - `chalk`: This is a library to colorize the console output. - - `readline`: This is a library to read input from the console. - -### Configure the agent - -These variables are used in the rest of the code to customize the and manage the . Feel free to configure them to your liking. - -```typescript -// main.ts -// configure your own values to customize your agent - -// The Arcade User ID identifies who is authorizing each service. -const arcadeUserID = "{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. -const MCPServers = ["Slack"]; -// This determines individual tools. Useful to pick specific tools when you don't need all of them. -const individualTools = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]; -// This determines the maximum number of tool definitions Arcade will return per MCP server -const toolLimit = 30; -// This prompt defines the behavior of the agent. -const systemPrompt = - "You are a helpful assistant that can use Gmail tools. Your main task is to help the user with anything they may need."; -// This determines which LLM the agent uses -const agentModel = "gpt-4o-mini"; -// This allows LangChain to retain the context of the session -const threadID = "1"; -``` - -### Write a helper function to execute Arcade tools - -This is a wrapper around the `executeZodTool` function. When it fails, you interrupt the flow and send the authorization request for the to handle. If the user authorizes the , the harness will reply with an `{authorized: true}` object, and the system will retry the tool call without interrupting the flow. - -```typescript -// main.ts -function executeOrInterruptTool({ - zodToolSchema, - toolDefinition, - client, - userId, -}: ToolExecuteFunctionFactoryInput): ToolExecuteFunction { - const { name: toolName } = zodToolSchema; - - return async (input: unknown) => { - try { - // Try to execute the tool - const result = await executeZodTool({ - zodToolSchema, - toolDefinition, - client, - userId, - })(input); - return result; - } catch (error) { - // If the tool requires authorization, interrupt the flow and ask the user to authorize the tool - if (error instanceof Error && isAuthorizationRequiredError(error)) { - const response = await client.tools.authorize({ - tool_name: toolName, - user_id: userId, - }); - - // Interrupt the flow here, and pass everything the handler needs to get the user's authorization - const interrupt_response = interrupt({ - authorization_required: true, - authorization_response: response, - tool_name: toolName, - url: response.url ?? "", - }); - - // If the user authorized the tool, retry the tool call without interrupting the flow - if (interrupt_response.authorized) { - const result = await executeZodTool({ - zodToolSchema, - toolDefinition, - client, - userId, - })(input); - return result; - } else { - // If the user didn't authorize the tool, the system handles errors through LangChain - throw new Error( - `Authorization required for tool call ${toolName}, but user didn't authorize.`, - ); - } - } - throw error; - } - }; -} -``` - -### 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 LangChain tools. The first step is to initialize the , and get the you want to use. Then, use the `toZod` function to convert the Arcade tools into a Zod schema, and pass it to the `executeOrInterruptTool` function to create a LangChain tool. - -This helper function is long, here’s a breakdown of what it does for clarity: - -- retrieve tools from all configured servers (defined in the `MCPServers` variable) -- retrieve individual (defined in the `individualTools` variable) -- combine the tools from the servers and the individual -- convert the Arcade to Zod tools -- convert the Zod to LangChain tools - -You then call the `getTools` function to get the tools you want the to use. - -```typescript -// main.ts -// Initialize the Arcade client -const arcade = new Arcade(); - -export type GetToolsProps = { - arcade: Arcade; - mcpServers?: string[]; - individualTools?: string[]; - userId: string; - limit?: number; -}; - -export async function getTools({ - arcade, - mcpServers = [], - individualTools = [], - userId, - limit = 30, -}: GetToolsProps) { - if (mcpServers.length === 0 && individualTools.length === 0) { - throw new Error("At least one tool or toolkit must be provided"); - } - - // Get up to `limit` tools from each configured MCP server - const from_mcpServers = await Promise.all( - mcpServers.map(async (mcpServerName) => { - const definitions = await arcade.tools.list({ - toolkit: mcpServerName, - limit: limit, - }); - return definitions.items; - }), - ); - - // Get the individual tools - const from_individualTools = await Promise.all( - individualTools.map(async (toolName) => { - return await arcade.tools.get(toolName); - }), - ); - - // Combine the tools from the MCP servers and the individual tools - const all_tools = [...from_mcpServers.flat(), ...from_individualTools]; - const unique_tools = Array.from( - new Map(all_tools.map((tool) => [tool.qualified_name, tool])).values(), - ); - - // Convert the Arcade tools to Zod tools - const arcadeTools = toZod({ - tools: unique_tools, - client: arcade, - executeFactory: executeOrInterruptTool, - userId: userId, - }); - - // Convert Arcade tools to LangGraph tools - const langchainTools = arcadeTools.map( - ({ name, description, execute, parameters }) => - (tool as Function)(execute, { - name, - description, - schema: parameters, - }), - ); - - return langchainTools; -} - -const tools = await getTools({ - arcade, - mcpServers: MCPServers, - individualTools: individualTools, - userId: arcadeUserID, - limit: toolLimit, -}); -``` - -### Write the interrupt handler - -In LangChain, each interrupt needs to be “resolved” for the flow to continue. In response to an interrupt, you need to return a decision object with the information needed to resolve the interrupt. In this case, the decision is whether the authorization was successful, in which case the system will retry the tool call, or if the authorization failed, the flow will be interrupted with an error, and the will decide what to do next. - -This helper function receives an interrupt and returns a decision object. Decision objects can be of any serializable type (convertible to JSON). In this case, you return an object with a boolean flag indicating if the authorization was successful. - -This function captures the authorization flow outside of the agent’s context, which is a good practice for security and context engineering. By handling everything in the , you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the free from any authorization-related traces, which reduces the risk of hallucinations, and reduces context bloat. - -```typescript -// main.ts -async function handleAuthInterrupt( - interrupt: Interrupt, -): Promise<{ authorized: boolean }> { - const value = interrupt.value; - const authorization_required = value.authorization_required; - if (authorization_required) { - const tool_name = value.tool_name; - const authorization_response = value.authorization_response; - console.log("⚙️: Authorization required for tool call", tool_name); - console.log( - "⚙️: Please authorize in your browser", - authorization_response.url, - ); - console.log("⚙️: Waiting for you to complete authorization..."); - try { - await arcade.auth.waitForCompletion(authorization_response.id); - console.log("⚙️: Authorization granted. Resuming execution..."); - return { authorized: true }; - } catch (error) { - console.error("⚙️: Error waiting for authorization to complete:", error); - return { authorized: false }; - } - } - return { authorized: false }; -} -``` - -### Create the agent - -Here you create the using the `createAgent` function. You pass the system prompt, the model, the tools, and the checkpointer. When the agent runs, it will automatically use the helper function you wrote earlier to handle calls and authorization requests. - -```typescript -// main.ts -const agent = createAgent({ - systemPrompt: systemPrompt, - model: agentModel, - tools: tools, - checkpointer: new MemorySaver(), -}); -``` - -### Write the invoke helper - -This last helper function handles the streaming of the ’s response, and captures the interrupts. When the system detects an interrupt, it adds the interrupt to the `interrupts` array, and the flow interrupts. If there are no interrupts, it will just stream the agent’s to your console. - -```typescript -// main.ts -async function streamAgent( - agent: any, - input: any, - config: any, -): Promise { - const stream = await agent.stream(input, { - ...config, - streamMode: "updates", - }); - const interrupts: Interrupt[] = []; - - for await (const chunk of stream) { - if (chunk.__interrupt__) { - interrupts.push(...(chunk.__interrupt__ as Interrupt[])); - continue; - } - for (const update of Object.values(chunk)) { - for (const msg of (update as any)?.messages ?? []) { - console.log("🤖: ", msg.toFormattedString()); - } - } - } - - return interrupts; -} -``` - -### Write the main function - -Finally, write the main function that will call the and handle the input. - -Here the `config` object configures the `thread_id`, which tells the to store the state of the conversation into that specific thread. Like any typical agent loop, you: - -1. Capture the input -2. Stream the ’s response -3. Handle any authorization interrupts -4. Resume the after authorization -5. Handle any errors -6. Exit the loop if the wants to quit - -```typescript -// main.ts -async function main() { - const config = { configurable: { thread_id: threadID } }; - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - console.log(chalk.green("Welcome to the chatbot Type 'exit' to quit.")); - while (true) { - const input = await rl.question("> "); - if (input.toLowerCase() === "exit") { - break; - } - rl.pause(); - - try { - let agentInput: any = { - messages: [{ role: "user", content: input }], - }; - - // Loop until no more interrupts - while (true) { - const interrupts = await streamAgent(agent, agentInput, config); - - if (interrupts.length === 0) { - break; // No more interrupts, we're done - } - - // Handle all interrupts - const decisions: any[] = []; - for (const interrupt of interrupts) { - decisions.push(await handleAuthInterrupt(interrupt)); - } - - // Resume with decisions, then loop to check for more interrupts - // Pass single decision directly, or array for multiple interrupts - agentInput = new Command({ - resume: decisions.length === 1 ? decisions[0] : decisions, - }); - } - } catch (error) { - console.error(error); - } - - rl.resume(); - } - console.log(chalk.red("👋 Bye...")); - process.exit(0); -} - -// Run the main function -main().catch((err) => console.error(err)); -``` - -### Run the agent - -```bash -bun run main.ts -``` - -You should see the responding to your prompts like any model, as well as handling any calls and authorization requests. Here are some example prompts you can try: - -- “Send me an email with a random haiku about LangChain ” -- “Summarize my latest 3 emails” - -## Key takeaways - -- You can integrate Arcade into any agentic framework like LangChain, all you need is to transform the Arcade tools into LangChain tools and handle the authorization flow. -- isolation: By handling the authorization flow outside of the ’s context, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations. -- You can leverage the interrupts mechanism to handle human intervention in the ’s flow, useful for authorization flows, policy enforcement, or anything else that requires input from the . - -## Next Steps - -1. Try adding additional tools to the or modifying the in the catalog for a different use case by modifying the `MCPServers` and `individualTools` variables. -2. Try refactoring the `handleAuthInterrupt` function to handle more complex flows, such as human-in-the-loop. -3. Try implementing a fully deterministic flow before the agentic loop, use this deterministic phase to prepare the for the , adding things like the current date, time, or any other information that is relevant to the task at hand. - -## Example code - -### **main.ts** (full file) - -```typescript -// main.ts -"use strict"; -import { Arcade } from "@arcadeai/arcadejs"; -import { - type ToolExecuteFunctionFactoryInput, - executeZodTool, - isAuthorizationRequiredError, - toZod, -} from "@arcadeai/arcadejs/lib/index"; -import { type ToolExecuteFunction } from "@arcadeai/arcadejs/lib/zod/types"; -import { createAgent, tool } from "langchain"; -import { - Command, - interrupt, - MemorySaver, - type Interrupt, -} from "@langchain/langgraph"; -import chalk from "chalk"; -import readline from "node:readline/promises"; - -// configure your own values to customize your agent - -// The Arcade User ID identifies who is authorizing each service. -const arcadeUserID = "{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. -const MCPServers = ["Slack"]; -// This determines individual tools. Useful to pick specific tools when you don't need all of them. -const individualTools = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]; -// This determines the maximum number of tool definitions Arcade will return -const toolLimit = 30; -// This prompt defines the behavior of the agent. -const systemPrompt = -"You are a helpful assistant that can use Gmail tools. Your main task is to help the user with anything they may need."; -// This determines which LLM the agent uses -const agentModel = "gpt-4o-mini"; -// This allows LangChain to retain the context of the session -const threadID = "1"; - -function executeOrInterruptTool({ -zodToolSchema, -toolDefinition, -client, -userId, -}: ToolExecuteFunctionFactoryInput): ToolExecuteFunction { -const { name: toolName } = zodToolSchema; - -return async (input: unknown) => { -try { -// Try to execute the tool -const result = await executeZodTool({ -zodToolSchema, -toolDefinition, -client, -userId, -})(input); -return result; -} catch (error) { -// If the tool requires authorization, interrupt the flow and ask the user to authorize the tool -if (error instanceof Error && isAuthorizationRequiredError(error)) { -const response = await client.tools.authorize({ -tool_name: toolName, -user_id: userId, -}); - - // Interrupt the flow here, and pass everything the handler needs to get the user's authorization - const interrupt_response = interrupt({ - authorization_required: true, - authorization_response: response, - tool_name: toolName, - url: response.url ?? "", - }); - - // If the user authorized the tool, retry the tool call without interrupting the flow - if (interrupt_response.authorized) { - const result = await executeZodTool({ - zodToolSchema, - toolDefinition, - client, - userId, - })(input); - return result; - } else { - // If the user didn't authorize the tool, the system handles errors through LangChain - throw new Error( - `Authorization required for tool call ${toolName}, but user didn't authorize.` - ); - } - } - throw error; - } - -}; -} - -// Initialize the Arcade client -const arcade = new Arcade(); - -export type GetToolsProps = { - arcade: Arcade; - mcpServers?: string[]; - individualTools?: string[]; - userId: string; - limit?: number; -}; - -export async function getTools({ - arcade, - mcpServers = [], - individualTools = [], - userId, - limit = 30, -}: GetToolsProps) { - if (mcpServers.length === 0 && individualTools.length === 0) { - throw new Error("At least one tool or toolkit must be provided"); - } - -const from_mcpServers = await Promise.all( -mcpServers.map(async (mcpServerName) => { -const definitions = await arcade.tools.list({ -toolkit: mcpServerName, -limit: limit, -}); -return definitions.items; -}) -); - -const from_individualTools = await Promise.all( -individualTools.map(async (toolName) => { -return await arcade.tools.get(toolName); -}) -); - -const all_tools = [...from_mcpServers.flat(), ...from_individualTools]; -const unique_tools = Array.from( -new Map(all_tools.map((tool) => [tool.qualified_name, tool])).values() -); - -const arcadeTools = toZod({ -tools: unique_tools, -client: arcade, -executeFactory: executeOrInterruptTool, -userId: userId, -}); - -// Convert Arcade tools to LangGraph tools -const langchainTools = arcadeTools.map( -({ name, description, execute, parameters }) => -(tool as Function)(execute, { -name, -description, -schema: parameters, -}) -); - -return langchainTools; -} - -const tools = await getTools({ -arcade, -mcpServers: MCPServers, -individualTools: individualTools, -userId: arcadeUserID, -limit: toolLimit, -}); - -async function handleAuthInterrupt( -interrupt: Interrupt -): Promise<{ authorized: boolean }> { -const value = interrupt.value; -const authorization_required = value.authorization_required; -if (authorization_required) { -const tool_name = value.tool_name; -const authorization_response = value.authorization_response; -console.log("⚙️: Authorization required for tool call", tool_name); -console.log( -"⚙️: Please authorize in your browser", -authorization_response.url -); -console.log("⚙️: Waiting for you to complete authorization..."); -try { -await arcade.auth.waitForCompletion(authorization_response.id); -console.log("⚙️: Authorization granted. Resuming execution..."); -return { authorized: true }; -} catch (error) { -console.error("⚙️: Error waiting for authorization to complete:", error); -return { authorized: false }; -} -} -return { authorized: false }; -} - -const agent = createAgent({ -systemPrompt: systemPrompt, -model: agentModel, -tools: tools, -checkpointer: new MemorySaver(), -}); - -async function streamAgent( -agent: any, -input: any, -config: any -): Promise { -const stream = await agent.stream(input, { -...config, -streamMode: "updates", -}); -const interrupts: Interrupt[] = []; - -for await (const chunk of stream) { -if (chunk.**interrupt**) { -interrupts.push(...(chunk.**interrupt** as Interrupt[])); -continue; -} -for (const update of Object.values(chunk)) { -for (const msg of (update as any)?.messages ?? []) { -console.log("🤖: ", msg.toFormattedString()); -} -} -} - -return interrupts; -} - -async function main() { -const config = { configurable: { thread_id: threadID } }; -const rl = readline.createInterface({ -input: process.stdin, -output: process.stdout, -}); - -console.log(chalk.green("Welcome to the chatbot Type 'exit' to quit.")); -while (true) { -const input = await rl.question("> "); -if (input.toLowerCase() === "exit") { -break; -} -rl.pause(); - - try { - let agentInput: any = { - messages: [{ role: "user", content: input }], - }; - - // Loop until no more interrupts - while (true) { - const interrupts = await streamAgent(agent, agentInput, config); - - if (interrupts.length === 0) { - break; // No more interrupts, we're done - } - - // Handle all interrupts - const decisions: any[] = []; - for (const interrupt of interrupts) { - decisions.push(await handleAuthInterrupt(interrupt)); - } - - // Resume with decisions, then loop to check for more interrupts - // Pass single decision directly, or array for multiple interrupts - agentInput = new Command({ - resume: decisions.length === 1 ? decisions[0] : decisions, - }); - } - } catch (error) { - console.error(error); - } - - rl.resume(); - -} -console.log(chalk.red("👋 Bye...")); -process.exit(0); -} - -// Run the main function -main().catch((err) => console.error(err)); - -``` - - -Last updated on January 30, 2026 - -[Setup (Python)](/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py.md) -[Authorizing Existing Tools](/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain.md b/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain.md deleted file mode 100644 index e60a7dda3..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain.md +++ /dev/null @@ -1,749 +0,0 @@ ---- -title: "Setup Arcade with LangChain" -description: "Learn how to use Arcade tools in LangChain agents" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -LangChainSetup Arcade with LangChain - -# Setup Arcade with LangChain - -LangChain is a popular agentic framework that abstracts a lot of the complexity of building AI agents. It is built on top of LangGraph, a lower level orchestration framework that offers more control over the inner flow of the . - -## Outcomes - -Learn how to integrate Arcade using LangChain primitives - -### You will Learn - -- How to retrieve Arcade and transform them into LangChain tools -- How to build a LangChain -- How to integrate Arcade into the agentic flow -- How to manage Arcade authorization using LangChain interrupts - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- The [`bun` runtime](https://bun.com/) - - -## LangChain primitives you will use in this guide - -LangChain provides multiple abstractions for building AI , and it’s very useful to internalize how some of these primitives work, so you can understand and extend the different agentic patterns LangChain supports. - -- : Most agentic frameworks, including LangChain, provide an abstraction for a . -- [_Interrupts_](https://docs.langchain.com/oss/javascript/langgraph/interrupts) - : Interrupts in LangChain are a way to control the flow of the agentic loop when something needs to be done outside of the normal ReAct flow. For example, if a tool requires authorization, you can interrupt the and ask the user to authorize the before continuing. -- [_Checkpointers_](https://docs.langchain.com/oss/javascript/langgraph/persistence) - : Checkpointers are how LangChain implements persistence. A checkpointer stores the ’s state in a “checkpoint” that can be resumed later. Those checkpoints are saved to a _thread_, which can be accessed after the agent’s execution, making it very simlpe for long-running agents and for handling interruptions and more sophisticated flows such as branching, time travel, and more. - -## Integrate Arcade tools into a LangChain agent - -### Create a new project - -```bash -mkdir langchain-arcade-example -cd langchain-arcade-example -bun install @arcadeai/arcadejs langchain @langchain/openai @langchain/core @langchain/langgraph -``` - -Create a new file called `.env` and add the following : - -```bash -# .env -ARCADE_API_KEY=YOUR_ARCADE_API_KEY -OPENAI_API_KEY=YOUR_OPENAI_API_KEY -``` - -### Import the necessary packages - -Create a new file called `main.ts` and add the following code: - -```typescript -// main.ts -"use strict"; -import { Arcade } from "@arcadeai/arcadejs"; -import { - type ToolExecuteFunctionFactoryInput, - executeZodTool, - isAuthorizationRequiredError, - toZod, -} from "@arcadeai/arcadejs/lib"; -import { type ToolExecuteFunction } from "@arcadeai/arcadejs/lib/zod/types"; -import { createAgent, tool } from "langchain"; -import { - Command, - interrupt, - MemorySaver, - type Interrupt, -} from "@langchain/langgraph"; -import chalk from "chalk"; -import readline from "node:readline/promises"; -``` - -This is quite a number of imports, let’s break them down: - -- Arcade imports: - - `Arcade`: This is the , used to interact with the . - - `type ToolExecuteFunctionFactoryInput`: Encodes the input to execute Arcade . - - `isAuthorizationRequiredError`: Checks if a requires authorization. - - `toZod`: Converts an Arcade definition into a [Zod](https://zod.dev) -   schema (Zod provides type safety and validation at runtime). - - `executeZodTool`: Executes an Zod-converted . -- LangChain imports: - - `createAgent`: Creates a LangChain using the ReAct pattern. - - `tool`: Turns an Arcade definition into a LangChain tool. - - `interrupt`: Interrupts the ReAct flow and asks the for input. - - `Command`: Communicates the user’s decisions to the ’s interrupts. - - `MemorySaver`: Stores the ’s state, and is required for checkpointing and interrupts. -- Other imports: - - `chalk`: This is a library to colorize the console output. - - `readline`: This is a library to read input from the console. - -### Configure the agent - -These variables are used in the rest of the code to customize the and manage the . Feel free to configure them to your liking. - -```typescript -// main.ts -// configure your own values to customize your agent - -// The Arcade User ID identifies who is authorizing each service. -const arcadeUserID = "{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. -const MCPServers = ["Slack"]; -// This determines individual tools. Useful to pick specific tools when you don't need all of them. -const individualTools = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]; -// This determines the maximum number of tool definitions Arcade will return per MCP server -const toolLimit = 30; -// This prompt defines the behavior of the agent. -const systemPrompt = - "You are a helpful assistant that can use Gmail tools. Your main task is to help the user with anything they may need."; -// This determines which LLM will be used inside the agent -const agentModel = "gpt-4o-mini"; -// This allows LangChain to retain the context of the session -const threadID = "1"; -``` - -### Write a helper function to execute Arcade tools - -This is a wrapper around the `executeZodTool` function. When it fails, you interrupt the flow and send the authorization request for the to handle. If the user authorizes the , the harness will reply with an `{authorized: true}` object, and the tool call will be retried without interrupting the flow. - -```typescript -// main.ts -function executeOrInterruptTool({ - zodToolSchema, - toolDefinition, - client, - userId, -}: ToolExecuteFunctionFactoryInput): ToolExecuteFunction { - const { name: toolName } = zodToolSchema; - - return async (input: unknown) => { - try { - // Try to execute the tool - const result = await executeZodTool({ - zodToolSchema, - toolDefinition, - client, - userId, - })(input); - return result; - } catch (error) { - // If the tool requires authorization, interrupt the flow and ask the user to authorize the tool - if (error instanceof Error && isAuthorizationRequiredError(error)) { - const response = await client.tools.authorize({ - tool_name: toolName, - user_id: userId, - }); - - // Interrupt the flow here, and pass everything the handler needs to get the user's authorization - const interrupt_response = interrupt({ - authorization_required: true, - authorization_response: response, - tool_name: toolName, - url: response.url ?? "", - }); - - // If the user authorized the tool, retry the tool call without interrupting the flow - if (interrupt_response.authorized) { - const result = await executeZodTool({ - zodToolSchema, - toolDefinition, - client, - userId, - })(input); - return result; - } else { - // If the user didn't authorize the tool, throw an error, which will be handled by LangChain - throw new Error( - `Authorization required for tool call ${toolName}, but user didn't authorize.`, - ); - } - } - throw error; - } - }; -} -``` - -### 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 LangChain tools. The first step is to initialize the , and get the you want to use. Then, use the `toZod` function to convert the Arcade tools into a Zod schema, and pass it to the `executeOrInterruptTool` function to create a LangChain tool. - -This helper function is fairly long, here’s a breakdown of what it does for clarity: - -- retrieve tools from all configured servers (defined in the `MCPServers` variable) -- retrieve individual (defined in the `individualTools` variable) -- combine the tools from the servers and the individual -- convert the Arcade to Zod tools -- convert the Zod to LangChain tools - -You then call the `getTools` function to get the tools you want the to use. - -```typescript -// main.ts -// Initialize the Arcade client -const arcade = new Arcade(); - -export type GetToolsProps = { - arcade: Arcade; - mcpServers?: string[]; - individualTools?: string[]; - userId: string; - limit?: number; -}; - -export async function getTools({ - arcade, - mcpServers = [], - individualTools = [], - userId, - limit = 30, -}: GetToolsProps) { - if (mcpServers.length === 0 && individualTools.length === 0) { - throw new Error("At least one tool or toolkit must be provided"); - } - - // Get up to `limit` tools from each configured MCP server - const from_mcpServers = await Promise.all( - mcpServers.map(async (mcpServerName) => { - const definitions = await arcade.tools.list({ - toolkit: mcpServerName, - limit: limit, - }); - return definitions.items; - }), - ); - - // Get the individual tools - const from_individualTools = await Promise.all( - individualTools.map(async (toolName) => { - return await arcade.tools.get(toolName); - }), - ); - - // Combine the tools from the MCP servers and the individual tools - const all_tools = [...from_mcpServers.flat(), ...from_individualTools]; - const unique_tools = Array.from( - new Map(all_tools.map((tool) => [tool.qualified_name, tool])).values(), - ); - - // Convert the Arcade tools to Zod tools - const arcadeTools = toZod({ - tools: unique_tools, - client: arcade, - executeFactory: executeOrInterruptTool, - userId: userId, - }); - - // Convert Arcade tools to LangGraph tools - const langchainTools = arcadeTools.map( - ({ name, description, execute, parameters }) => - (tool as Function)(execute, { - name, - description, - schema: parameters, - }), - ); - - return langchainTools; -} - -const tools = await getTools({ - arcade, - mcpServers: MCPServers, - individualTools: individualTools, - userId: arcadeUserID, - limit: toolLimit, -}); -``` - -### Write the interrupt handler - -In LangChain, each interrupt needs to be “resolved” for the flow to continue. In response to an interrupt, you need to return a decision object with the information needed to resolve the interrupt. In this case, the decision is whether the authorization was successful, in which case the tool call will be retried, or if the authorization failed, the flow will be interrupted with an error, and the will decide what to do next. - -This helper function receives an interrupt and returns a decision object. Decision objects can be of any serializable type (convertible to JSON). In this case, you return an object with a boolean flag indicating if the authorization was successful. - -This function captures the authorization flow outside of the agent’s context, which is a good practice for security and context engineering. By handling everything in the , you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the free from any authorization-related traces, which reduces the risk of hallucinations, and reduces context bloat. - -```typescript -// main.ts -async function handleAuthInterrupt( - interrupt: Interrupt, -): Promise<{ authorized: boolean }> { - const value = interrupt.value; - const authorization_required = value.authorization_required; - if (authorization_required) { - const tool_name = value.tool_name; - const authorization_response = value.authorization_response; - console.log("⚙️: Authorization required for tool call", tool_name); - console.log( - "⚙️: Please authorize in your browser", - authorization_response.url, - ); - console.log("⚙️: Waiting for you to complete authorization..."); - try { - await arcade.auth.waitForCompletion(authorization_response.id); - console.log("⚙️: Authorization granted. Resuming execution..."); - return { authorized: true }; - } catch (error) { - console.error("⚙️: Error waiting for authorization to complete:", error); - return { authorized: false }; - } - } - return { authorized: false }; -} -``` - -### Create the agent - -Here you create the using the `createAgent` function. You pass the system prompt, the model, the tools, and the checkpointer. When the agent runs, it will automatically use the helper function you wrote earlier to handle calls and authorization requests. - -```typescript -// main.ts -const agent = createAgent({ - systemPrompt: systemPrompt, - model: agentModel, - tools: tools, - checkpointer: new MemorySaver(), -}); -``` - -### Write the invoke helper - -This last helper function handles the streaming of the ’s response, and captures the interrupts. When an interrupt is detected, it is added to the `interrupts` array, and the flow is interrupted. If there are no interrupts, it will just stream the agent’s to your console. - -```typescript -// main.ts -async function streamAgent( - agent: any, - input: any, - config: any, -): Promise { - const stream = await agent.stream(input, { - ...config, - streamMode: "updates", - }); - const interrupts: Interrupt[] = []; - - for await (const chunk of stream) { - if (chunk.__interrupt__) { - interrupts.push(...(chunk.__interrupt__ as Interrupt[])); - continue; - } - for (const update of Object.values(chunk)) { - for (const msg of (update as any)?.messages ?? []) { - console.log("🤖: ", msg.toFormattedString()); - } - } - } - - return interrupts; -} -``` - -### Write the main function - -Finally, write the main function that will call the and handle the input. - -Here the `config` object is used to configure the `thread_id`, which tells the to store the state of the conversation into that specific thread. Like any typical agent loop, you: - -1. Capture the input -2. Stream the ’s response -3. Handle any authorization interrupts -4. Resume the after authorization -5. Handle any errors -6. Exit the loop if the wants to quit - -```typescript -// main.ts -async function main() { - const config = { configurable: { thread_id: threadID } }; - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - console.log(chalk.green("Welcome to the chatbot! Type 'exit' to quit.")); - while (true) { - const input = await rl.question("> "); - if (input.toLowerCase() === "exit") { - break; - } - rl.pause(); - - try { - let agentInput: any = { - messages: [{ role: "user", content: input }], - }; - - // Loop until no more interrupts - while (true) { - const interrupts = await streamAgent(agent, agentInput, config); - - if (interrupts.length === 0) { - break; // No more interrupts, we're done - } - - // Handle all interrupts - const decisions: any[] = []; - for (const interrupt of interrupts) { - decisions.push(await handleAuthInterrupt(interrupt, rl)); - } - - // Resume with decisions, then loop to check for more interrupts - // Pass single decision directly, or array for multiple interrupts - agentInput = new Command({ - resume: decisions.length === 1 ? decisions[0] : decisions, - }); - } - } catch (error) { - console.error(error); - } - - rl.resume(); - } - console.log(chalk.red("👋 Bye...")); - process.exit(0); -} - -// Run the main function -main().catch((err) => console.error(err)); -``` - -### Run the agent - -```bash -bun run main.ts -``` - -You should see the responding to your prompts like any model, as well as handling any calls and authorization requests. Here are some example prompts you can try: - -- “Send me an email with a random haiku about LangChain ” -- “Summarize my latest 3 emails” - -## Key takeaways - -- Arcade can be integrated into any agentic framework like LangChain, all you need is to transform the Arcade tools into LangChain tools and handle the authorization flow. -- isolation: By handling the authorization flow outside of the ’s context, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations. -- You can leverage the interrupts mechanism to handle human intervention in the ’s flow, useful for authorization flows, policy enforcement, or anything else that requires input from the . - -## Next Steps - -1. Try adding additional tools to the or modifying the in the catalog for a different use case by modifying the `MCPServers` and `individualTools` variables. -2. Try refactoring the `handleAuthInterrupt` function to handle more complex flows, such as human-in-the-loop. -3. Try implementing a fully deterministic flow before the agentic loop, use this deterministic phase to prepare the for the , adding things like the current date, time, or any other information that is relevant to the task at hand. - -## Example code - -### **main.ts** (full file) - -```typescript -// main.ts -"use strict"; -import { Arcade } from "@arcadeai/arcadejs"; -import { - type ToolExecuteFunctionFactoryInput, - executeZodTool, - isAuthorizationRequiredError, - toZod, -} from "@arcadeai/arcadejs/lib"; -import { type ToolExecuteFunction } from "@arcadeai/arcadejs/lib/zod/types"; -import { createAgent, tool } from "langchain"; -import { - Command, - interrupt, - MemorySaver, - type Interrupt, -} from "@langchain/langgraph"; -import chalk from "chalk"; -import readline from "node:readline/promises"; - -// configure your own values to customize your agent - -// The Arcade User ID identifies who is authorizing each service. -const arcadeUserID = "{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. -const MCPServers = ["Slack"]; -// This determines individual tools. Useful to pick specific tools when you don't need all of them. -const individualTools = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]; -// This determines the maximum number of tool definitions Arcade will return -const toolLimit = 30; -// This prompt defines the behavior of the agent. -const systemPrompt = -"You are a helpful assistant that can use Gmail tools. Your main task is to help the user with anything they may need."; -// This determines which LLM will be used inside the agent -const agentModel = "gpt-4o-mini"; -// This allows LangChain to retain the context of the session -const threadID = "1"; - -function executeOrInterruptTool({ -zodToolSchema, -toolDefinition, -client, -userId, -}: ToolExecuteFunctionFactoryInput): ToolExecuteFunction { -const { name: toolName } = zodToolSchema; - -return async (input: unknown) => { -try { -// Try to execute the tool -const result = await executeZodTool({ -zodToolSchema, -toolDefinition, -client, -userId, -})(input); -return result; -} catch (error) { -// If the tool requires authorization, we interrupt the flow and ask the user to authorize the tool -if (error instanceof Error && isAuthorizationRequiredError(error)) { -const response = await client.tools.authorize({ -tool_name: toolName, -user_id: userId, -}); - - // We interrupt the flow here, and pass everything the handler needs to get the user's authorization - const interrupt_response = interrupt({ - authorization_required: true, - authorization_response: response, - tool_name: toolName, - url: response.url ?? "", - }); - - // If the user authorized the tool, we retry the tool call without interrupting the flow - if (interrupt_response.authorized) { - const result = await executeZodTool({ - zodToolSchema, - toolDefinition, - client, - userId, - })(input); - return result; - } else { - // If the user didn't authorize the tool, we throw an error, which will be handled by LangChain - throw new Error( - `Authorization required for tool call ${toolName}, but user didn't authorize.` - ); - } - } - throw error; - } - -}; -} - -// Initialize the Arcade client -const arcade = new Arcade(); - -export type GetToolsProps = { - arcade: Arcade; - mcpServers?: string[]; - individualTools?: string[]; - userId: string; - limit?: number; -}; - -export async function getTools({ - arcade, - mcpServers = [], - individualTools = [], - userId, - limit = 30, -}: GetToolsProps) { - if (mcpServers.length === 0 && individualTools.length === 0) { - throw new Error("At least one tool or toolkit must be provided"); - } - -const from_mcpServers = await Promise.all( -mcpServers.map(async (mcpServerName) => { -const definitions = await arcade.tools.list({ -toolkit: mcpServerName, -limit: limit, -}); -return definitions.items; -}) -); - -const from_individualTools = await Promise.all( -individualTools.map(async (toolName) => { -return await arcade.tools.get(toolName); -}) -); - -const all_tools = [...from_mcpServers.flat(), ...from_individualTools]; -const unique_tools = Array.from( -new Map(all_tools.map((tool) => [tool.qualified_name, tool])).values() -); - -const arcadeTools = toZod({ -tools: unique_tools, -client: arcade, -executeFactory: executeOrInterruptTool, -userId: userId, -}); - -// Convert Arcade tools to LangGraph tools -const langchainTools = arcadeTools.map( -({ name, description, execute, parameters }) => -(tool as Function)(execute, { -name, -description, -schema: parameters, -}) -); - -return langchainTools; -} - -const tools = await getTools({ -arcade, -mcpServers: MCPServers, -individualTools: individualTools, -userId: arcadeUserID, -limit: toolLimit, -}); - -async function handleAuthInterrupt( -interrupt: Interrupt -): Promise<{ authorized: boolean }> { -const value = interrupt.value; -const authorization_required = value.authorization_required; -if (authorization_required) { -const tool_name = value.tool_name; -const authorization_response = value.authorization_response; -console.log("⚙️: Authorization required for tool call", tool_name); -console.log( -"⚙️: Please authorize in your browser", -authorization_response.url -); -console.log("⚙️: Waiting for you to complete authorization..."); -try { -await arcade.auth.waitForCompletion(authorization_response.id); -console.log("⚙️: Authorization granted. Resuming execution..."); -return { authorized: true }; -} catch (error) { -console.error("⚙️: Error waiting for authorization to complete:", error); -return { authorized: false }; -} -} -return { authorized: false }; -} - -const agent = createAgent({ -systemPrompt: systemPrompt, -model: agentModel, -tools: tools, -checkpointer: new MemorySaver(), -}); - -async function streamAgent( -agent: any, -input: any, -config: any -): Promise { -const stream = await agent.stream(input, { -...config, -streamMode: "updates", -}); -const interrupts: Interrupt[] = []; - -for await (const chunk of stream) { -if (chunk.**interrupt**) { -interrupts.push(...(chunk.**interrupt** as Interrupt[])); -continue; -} -for (const update of Object.values(chunk)) { -for (const msg of (update as any)?.messages ?? []) { -console.log("🤖: ", msg.toFormattedString()); -} -} -} - -return interrupts; -} - -async function main() { -const config = { configurable: { thread_id: threadID } }; -const rl = readline.createInterface({ -input: process.stdin, -output: process.stdout, -}); - -console.log(chalk.green("Welcome to the chatbot! Type 'exit' to quit.")); -while (true) { -const input = await rl.question("> "); -if (input.toLowerCase() === "exit") { -break; -} -rl.pause(); - - try { - let agentInput: any = { - messages: [{ role: "user", content: input }], - }; - - // Loop until no more interrupts - while (true) { - const interrupts = await streamAgent(agent, agentInput, config); - - if (interrupts.length === 0) { - break; // No more interrupts, we're done - } - - // Handle all interrupts - const decisions: any[] = []; - for (const interrupt of interrupts) { - decisions.push(await handleAuthInterrupt(interrupt, rl)); - } - - // Resume with decisions, then loop to check for more interrupts - // Pass single decision directly, or array for multiple interrupts - agentInput = new Command({ - resume: decisions.length === 1 ? decisions[0] : decisions, - }); - } - } catch (error) { - console.error(error); - } - - rl.resume(); - -} -console.log(chalk.red("👋 Bye...")); -process.exit(0); -} - -// Run the main function -main().catch((err) => console.error(err)); - -``` - - -PLAINTEXT - -Last updated on February 10, 2026 - -[Setup Arcade with Google ADK (Python)](/en/get-started/agent-frameworks/google-adk/use-arcade-tools.md) -[Authorizing existing tools](/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/mastra.md b/public/_markdown/en/get-started/agent-frameworks/mastra.md deleted file mode 100644 index cbc089f05..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/mastra.md +++ /dev/null @@ -1,966 +0,0 @@ ---- -title: "Build an AI Agent and Workflow with Arcade and Mastra" -description: "Create a TypeScript agent that uses Arcade tools to access Gmail and Slack" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -Mastra - -[Mastra](https://mastra.ai/docs)  is an open-source, TypeScript framework for building AI applications. It provides agents with memory, calling, workflows, and RAG capabilities. This guide uses **Mastra v1.x**. - -In this guide, you’ll build an agent lets you read emails, send messages, and interact with Gmail and Slack using Arcade’s in a conversational interface with built-in authentication. You will also build a workflow that summarizes emails and sends them to Slack. - -## Outcomes - -A Mastra and workflow that integrates Arcade for Gmail and Slack. - -### You will Learn - -- How to retrieve Arcade and convert them to Mastra format -- How to create an with calling capabilities -- How to create a workflow with multiple steps -- How to handle Arcade’s authorization flow in your application -- How to test your and workflow with Mastra Studio - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Node.js 18+](https://nodejs.org/) -   -- An [OpenAI API key](https://platform.openai.com/api-keys) -   (or another supported model provider) - -## Mastra concepts - -Before diving into the code, here are the key Mastra concepts you’ll use: - -- [Mastra Studio](https://mastra.ai/docs/getting-started/studio) -  : An interactive development environment for building and testing locally. -- [Zod schemas](https://zod.dev) -  : Mastra uses Zod for type-safe definitions. -- [Memory](https://mastra.ai/docs/memory/overview) -  : Persists conversation history across sessions using storage backends like LibSQL. -- [Processors](https://mastra.ai/docs/memory/processors) -  : Transform messages before they reach the LLM. This tutorial uses: - - `ToolCallFilter`: Removes tool calls and results from memory to prevent large API responses from bloating . - - `TokenLimiterProcessor`: Limits input tokens to stay within model limits. - -## Build an agent - -### Create a new Mastra project - -```bash -npx create-mastra@latest arcade-agent -``` - -Select your preferred model provider when prompted (we recommend OpenAI). Enter your when asked. - -Then navigate to the project directory and install the : - -### npm - -```bash -cd arcade-agent -npm install @arcadeai/arcadejs @ai-sdk/openai zod@3 -``` - -### pnpm - -```bash -cd arcade-agent -pnpm add @arcadeai/arcadejs @ai-sdk/openai zod@3 -``` - -### yarn - -```bash -cd arcade-agent -yarn add @arcadeai/arcadejs @ai-sdk/openai zod@3 -``` - -### bun - -```bash -cd arcade-agent -bun add @arcadeai/arcadejs @ai-sdk/openai zod@3 -``` - -These commands explicitly install `zod@3` because the Arcade SDK’s `toZodToolSet` currently requires Zod 3.x. Zod 4 has a different internal API that isn’t yet supported. - -### Set up environment variables - -Add your key to **.env**: - -```bash -# .env -ARCADE_API_KEY={arcade_api_key} -ARCADE_USER_ID={arcade_user_id} -``` - -The `ARCADE_USER_ID` is your app’s internal identifier for the (often the email you signed up with, a UUID, etc.). Arcade uses this to track authorizations per user. - -### Create the tool configuration - -Create **src/mastra//arcade.ts** to handle Arcade tool fetching and conversion. - -**Handling large outputs:** Tools like `Gmail.ListEmails` can return 200KB+ of email content. When the passes this data back to the LLM in the agentic loop, it may exceed token limits, resulting in rate limit errors. The code below includes output truncation to prevent this. - -src/mastra/tools/arcade.ts - -```typescript -import { Arcade } from "@arcadeai/arcadejs"; -import { - toZodToolSet, - executeOrAuthorizeZodTool, -} from "@arcadeai/arcadejs/lib/index"; - -const config = { - // Get all tools from these MCP servers - mcpServers: ["Slack"], - // Add specific individual tools - individualTools: ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"], -}; - -// Maximum characters for any string field in tool output -// Keeps responses small while preserving structure (subjects, senders, snippets) -const MAX_STRING_CHARS = 300; - -/** - * Recursively truncates all large strings in objects/arrays. - * This prevents token overflow when tool results pass back to the LLM. - */ -function truncateDeep(obj: unknown): unknown { - if (obj === null || obj === undefined) return obj; - - if (typeof obj === "string") { - if (obj.length > MAX_STRING_CHARS) { - return obj.slice(0, MAX_STRING_CHARS) + "..."; - } - return obj; - } - - if (Array.isArray(obj)) { - return obj.map(truncateDeep); - } - - if (typeof obj === "object") { - const result: Record = {}; - for (const [key, value] of Object.entries(obj as Record)) { - result[key] = truncateDeep(value); - } - return result; - } - - return obj; -} - -export async function getArcadeTools(userId: string) { - const arcade = new Arcade(); - - // Fetch tools from MCP servers - const mcpTools = await Promise.all( - config.mcpServers.map(async (server) => { - const response = await arcade.tools.list({ toolkit: server }); - return response.items; - }) - ); - - // Fetch individual tools - const individualTools = await Promise.all( - config.individualTools.map((toolName) => arcade.tools.get(toolName)) - ); - - // Combine all tools - const allTools = [...mcpTools.flat(), ...individualTools]; - - // Convert to Zod format for Mastra compatibility - const zodTools = toZodToolSet({ - tools: allTools, - client: arcade, - userId, - executeFactory: executeOrAuthorizeZodTool, - }); - - // Wrap tools with truncation and add 'id' property for Mastra Studio - type ToolType = (typeof zodTools)[string] & { id: string }; - const mastraTools: Record = {}; - - for (const [toolName, tool] of Object.entries(zodTools)) { - const originalExecute = tool.execute; - mastraTools[toolName] = { - ...tool, - id: toolName, - execute: async (input: unknown) => { - const result = await originalExecute(input); - return truncateDeep(result) as Awaited>; - }, - } as ToolType; - } - - return mastraTools; -} -``` - -#### What this code does - -##### Tool fetching - -- `mcpServers`: Fetches _all_ tools from an server. Use this when you want everything a service offers (for example, `"Slack"` gives you `Slack_SendMessage`, `Slack_ListChannels`, `Slack_ListUsers`, etc.) -- `individualTools`: Fetches specific by name. Use this to cherry-pick only what you need (for example, `"Gmail_ListEmails"` without `Gmail_DeleteEmail` or other tools you don’t want exposed) - -You might select your individually for a few reasons: - -- **Security** You may not want to expose all the a service offers, for instance `Gmail_DeleteEmail` is not necessary and could even be dangerous to expose to an designed to summarize emails. -- **Cost** Each ’s schema consumes tokens. Loading all Gmail tools (~20 tools) uses more tokens than loading just the 3 you need. This matters for rate limits and cost. - -Browse the [complete MCP server catalog](/resources/integrations.md) to see available servers and their . - -##### Arcade SDK functions - -- `arcade.tools.list({ toolkit })`: Fetches all tools from an server -- `arcade.tools.get(toolName)`: Fetches a single by its full name -- `toZodToolSet`: Converts Arcade to [Zod](https://zod.dev) -   schemas that Mastra requires -- `executeOrAuthorizeZodTool`: Handles execution and returns authorization URLs when needed - -### Output handling - -- `truncateDeep`: Recursively limits all strings to 300 characters to prevent token overflow when results are passed back to the LLM - -### Create the agent - -Create **src/mastra//arcade.ts**: - -src/mastra/agents/arcade.ts - -```typescript -import { Agent } from "@mastra/core/agent"; -import { TokenLimiterProcessor, ToolCallFilter } from "@mastra/core/processors"; -import { Memory } from "@mastra/memory"; -import { LibSQLStore } from "@mastra/libsql"; -import { openai } from "@ai-sdk/openai"; -import { getArcadeTools } from "../tools/arcade"; - -const userId = process.env.ARCADE_USER_ID || "default-user"; - -// Fetch Arcade tools at startup -const arcadeTools = await getArcadeTools(userId); - -// Configure memory with conversation history -const memory = new Memory({ - storage: new LibSQLStore({ - id: "arcade-agent-memory", - url: "file:memory.db", - }), - options: { - lastMessages: 10, - }, -}); - -export const arcadeAgent = new Agent({ - id: "arcade-agent", - name: "arcadeAgent", - instructions: `You are a helpful assistant that can access Gmail and Slack. -Always use the available tools to fulfill user requests. - -For Gmail: -- Use Gmail_ListEmails to fetch recent emails -- Use Gmail_SendEmail to send emails -- Use Gmail_WhoAmI to get the user's email address -- To find sent emails, use the query parameter with "in:sent" -- To find received emails, use "in:inbox" or no query -- When composing emails, use plain text (no markdown) - -For Slack: -- Use Slack_SendMessage to send messages to channels or users -- Use Slack_ListChannels to see available channels - -After completing any action, always confirm what you did with specific details. - -IMPORTANT: When a tool returns an authorization response with a URL, tell the user to visit that URL to grant access. After they authorize, they can retry their request.`, - model: openai("gpt-4o"), - tools: arcadeTools, - memory, - // Filter out tool results from memory (they can be large) and limit tokens - inputProcessors: [new ToolCallFilter(), new TokenLimiterProcessor({ limit: 50000 })], -}); -``` - -### Register the agent - -Replace the contents of **src/mastra/index.ts** with the following to register your : - -src/mastra/index.ts - -```typescript -import { Mastra } from "@mastra/core"; -import { arcadeAgent } from "./agents/arcade"; - -export const mastra = new Mastra({ - agents: { - arcadeAgent, - }, -}); -``` - -### Test with Mastra Studio - -Start the development server: - -### npm - -```bash -npm run dev -``` - -### pnpm - -```bash -pnpm dev -``` - -### yarn - -```bash -yarn dev -``` - -### bun - -```bash -bun dev -``` - -Open [http://localhost:4111](http://localhost:4111)  to access Mastra Studio. Select **arcadeAgent** from the list and try prompts like: - -- “Summarize my last 3 emails” -- “Send a Slack DM to myself saying hello” -- “What’s my Gmail address?” - -On first use, the agent will return an authorization URL. Visit the URL to connect your Gmail or Slack , then retry your request. Arcade remembers this authorization for future requests. - -![Mastra Studio agent interaction](/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmastra-studio-agent-interaction.f5d656a9.png&w=3840&q=75) - -## Build a workflow - - are great for open-ended conversations, but sometimes you want a **deterministic process** that runs the same way every time. Mastra workflows let you chain steps together, with each step’s output feeding into the next. - -This workflow does the following: - -1. Fetches emails from Gmail -2. Summarizes them with an LLM -3. Sends the digest as a direct message to the on Slack - -This also demonstrates how workflows: - -- handle **large data** the full email content stays internal to the workflow, and only the compact summary gets sent to Slack. -- handle **authorization errors** -- **pass auth URLs** through multiple workflow steps - -### Create the workflow - -Create **src/mastra/workflows/email-digest.ts**: - -src/mastra/workflows/email-digest.ts - -```typescript -import { createStep, createWorkflow } from "@mastra/core/workflows"; -import { z } from "zod"; -import { Arcade } from "@arcadeai/arcadejs"; - -const defaultUserId = process.env.ARCADE_USER_ID || "default-user"; - -// Step 1: Fetch emails from Gmail -const fetchEmails = createStep({ - id: "fetch-emails", - inputSchema: z.object({ - userId: z.string().optional(), - maxEmails: z.number().optional(), - }), - outputSchema: z.object({ - emails: z.array(z.object({ - subject: z.string(), - from: z.string(), - snippet: z.string(), - })), - userId: z.string(), - authRequired: z.boolean().optional(), - authUrl: z.string().optional(), - }), - execute: async ({ inputData }) => { - const arcade = new Arcade(); - const userId = inputData?.userId || defaultUserId; - - try { - const result = await arcade.tools.execute({ - tool_name: "Gmail_ListEmails", - user_id: userId, - input: { n_emails: inputData!.maxEmails ?? 5 }, - }); - - const response = result as { output?: { value?: { emails?: any[] } } }; - const emails = (response.output?.value?.emails || []).map((e: any) => ({ - subject: String(e.subject || "(No subject)"), - from: String(e.sender || e.from || "Unknown"), - snippet: String(e.snippet || "").slice(0, 200), - })); - - return { emails, userId }; - } catch (error: any) { - // Handle authorization required error - if (error.status === 403 || error.message?.includes("authorization")) { - const authResponse = await arcade.auth.start({ - user_id: userId, - provider: "google", - scopes: ["https://www.googleapis.com/auth/gmail.readonly"], - }); - return { - emails: [], - userId, - authRequired: true, - authUrl: authResponse.url, - }; - } - throw error; - } - }, -}); - -// Step 2: Summarize with LLM -const summarizeEmails = createStep({ - id: "summarize-emails", - inputSchema: z.object({ - emails: z.array(z.object({ - subject: z.string(), - from: z.string(), - snippet: z.string(), - })), - userId: z.string(), - authRequired: z.boolean().optional(), - authUrl: z.string().optional(), - }), - outputSchema: z.object({ - summary: z.string(), - userId: z.string(), - authRequired: z.boolean().optional(), - authUrl: z.string().optional(), - }), - execute: async ({ inputData, mastra }) => { - const { emails, userId, authRequired, authUrl } = inputData!; - - // Pass through auth requirement - if (authRequired) { - return { summary: "", userId, authRequired, authUrl }; - } - - if (emails.length === 0) { - return { summary: "No new emails.", userId }; - } - - const agent = mastra?.getAgent("arcadeAgent"); - const emailList = emails.map((e, i) => - `${i + 1}. From: ${e.from}\n Subject: ${e.subject}\n Preview: ${e.snippet}` - ).join("\n\n"); - - const response = await agent!.generate( - `Summarize these emails in 2-3 bullet points:\n\n${emailList}` - ); - - return { summary: response.text, userId }; - }, -}); - -// Step 3: Send DM to Slack user -const sendToSlack = createStep({ - id: "send-to-slack", - inputSchema: z.object({ - summary: z.string(), - userId: z.string(), - authRequired: z.boolean().optional(), - authUrl: z.string().optional(), - }), - outputSchema: z.object({ - success: z.boolean(), - message: z.string(), - authUrl: z.string().optional(), - }), - execute: async ({ inputData }) => { - const { summary, userId, authRequired, authUrl } = inputData!; - - // Return auth URL if authorization is needed - if (authRequired) { - return { - success: false, - message: `Authorization required. Please visit this URL to grant access: ${authUrl}`, - authUrl, - }; - } - - const arcade = new Arcade(); - - try { - // Get the user's Slack identity - const whoAmI = await arcade.tools.execute({ - tool_name: "Slack_WhoAmI", - user_id: userId, - input: {}, - }); - - const slackUserId = (whoAmI as any)?.output?.value?.user_id; - - // Send DM to the user - await arcade.tools.execute({ - tool_name: "Slack_SendMessage", - user_id: userId, - input: { - message: `📬 *Email Digest*\n\n${summary}`, - user_ids: [slackUserId], - }, - }); - - return { success: true, message: "Digest sent as DM" }; - } catch (error: any) { - // Handle Slack authorization required - if (error.status === 403 || error.message?.includes("authorization")) { - const slackAuth = await arcade.auth.start({ - user_id: userId, - provider: "slack", - scopes: ["chat:write", "users:read"], - }); - return { - success: false, - message: `Slack authorization required. Please visit: ${slackAuth.url}`, - authUrl: slackAuth.url, - }; - } - throw error; - } - }, -}); - -// Chain the steps together -const emailDigestWorkflow = createWorkflow({ - id: "email-digest", - inputSchema: z.object({ - userId: z.string().default(defaultUserId), - maxEmails: z.number().default(5), - }), - outputSchema: z.object({ - success: z.boolean(), - message: z.string(), - authUrl: z.string().optional(), - }), -}) - .then(fetchEmails) - .then(summarizeEmails) - .then(sendToSlack); - -emailDigestWorkflow.commit(); - -export { emailDigestWorkflow }; -``` - -### Register the workflow - -Update **src/mastra/index.ts**: - -src/mastra/index.ts - -```typescript -import { Mastra } from "@mastra/core"; -import { arcadeAgent } from "./agents/arcade"; -import { emailDigestWorkflow } from "./workflows/email-digest"; - -export const mastra = new Mastra({ - agents: { - arcadeAgent, - }, - workflows: { - emailDigestWorkflow, - }, -}); -``` - -### Test the workflow - -1. Restart the dev server and open Mastra Studio. In the sidebar, open **Workflows**. Select **email-digest**. -2. In the right sidebar, select “run” to run the workflow. -3. If authorization is required, the workflow returns an auth URL. Visit the URL, complete authorization, then run the workflow again. -4. Check your Slack DMs for the digest. - -![Mastra Studio workflow run](/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fmastra-studio-workflow-run.353a7e5d.png&w=3840&q=75) - -## Key takeaways - -- **Arcade work seamlessly with Mastra**: Use `toZodToolSet` to convert Arcade tools to the Zod schema format Mastra expects. -- ** vs Workflow**: The agent handles open-ended requests (“help me with my emails”). The workflow handles repeatable processes (“every morning, summarize and send to Slack”). Use both together for powerful automation. -- **Truncate large outputs**: Tools like Gmail can return 200KB+ of data. Wrap execution with truncation to prevent token overflow in the agentic loop. -- **Authorization is automatic**: The `executeOrAuthorizeZodTool` factory handles auth flows. When a needs authorization, it returns a URL for the to visit. -- **Workflows need explicit auth handling**: Unlike , workflows don’t have built-in auth handling. Catch 403 errors, call `arcade.auth.start()`, and pass the auth URL through your workflow steps. - -## Next steps - -- **Add more **: Browse the [tool catalog](/resources/integrations.md) - and add tools for GitHub, Notion, Linear, and more. -- **Schedule your workflow**: Use a cron job or [Mastra’s scheduling](https://mastra.ai/docs/workflows/scheduling) -   to run your email digest every morning. -- **Deploy to production**: Follow Mastra’s [deployment guides](https://mastra.ai/docs/deployment/overview) -   to deploy your and workflows. - -**Building a multi- app?** This tutorial uses a single `ARCADE_USER_ID` for simplicity. For production apps where each user needs their own OAuth tokens, see [Secure auth for production](/guides/user-facing-agents/secure-auth-production.md) to learn how to dynamically pass user IDs and handle per-user authorization. - -## Complete code - -### **src/mastra/tools/arcade.ts** (full file) - -src/mastra/tools/arcade.ts - -```typescript -import { Arcade } from "@arcadeai/arcadejs"; -import { - toZodToolSet, - executeOrAuthorizeZodTool, -} from "@arcadeai/arcadejs/lib/index"; - -const config = { - mcpServers: ["Slack"], - individualTools: ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"], -}; - -const MAX_STRING_CHARS = 300; - -function truncateDeep(obj: unknown): unknown { - if (obj === null || obj === undefined) return obj; - - if (typeof obj === "string") { - if (obj.length > MAX_STRING_CHARS) { - return obj.slice(0, MAX_STRING_CHARS) + "..."; - } - return obj; - } - - if (Array.isArray(obj)) { - return obj.map(truncateDeep); - } - - if (typeof obj === "object") { - const result: Record = {}; - for (const [key, value] of Object.entries(obj as Record)) { - result[key] = truncateDeep(value); - } - return result; - } - - return obj; -} - -export async function getArcadeTools(userId: string) { - const arcade = new Arcade(); - - const mcpTools = await Promise.all( - config.mcpServers.map(async (server) => { - const response = await arcade.tools.list({ toolkit: server }); - return response.items; - }) - ); - - const individualTools = await Promise.all( - config.individualTools.map((toolName) => arcade.tools.get(toolName)) - ); - - const allTools = [...mcpTools.flat(), ...individualTools]; - - const zodTools = toZodToolSet({ - tools: allTools, - client: arcade, - userId, - executeFactory: executeOrAuthorizeZodTool, - }); - - type ToolType = (typeof zodTools)[string] & { id: string }; - const mastraTools: Record = {}; - - for (const [toolName, tool] of Object.entries(zodTools)) { - const originalExecute = tool.execute; - mastraTools[toolName] = { - ...tool, - id: toolName, - execute: async (input: unknown) => { - const result = await originalExecute(input); - return truncateDeep(result) as Awaited>; - }, - } as ToolType; - } - - return mastraTools; -} -``` - -### **src/mastra/agents/arcade.ts** (full file) - -src/mastra/agents/arcade.ts - -```typescript -import { Agent } from "@mastra/core/agent"; -import { TokenLimiterProcessor, ToolCallFilter } from "@mastra/core/processors"; -import { Memory } from "@mastra/memory"; -import { LibSQLStore } from "@mastra/libsql"; -import { openai } from "@ai-sdk/openai"; -import { getArcadeTools } from "../tools/arcade"; - -const userId = process.env.ARCADE_USER_ID || "default-user"; -const arcadeTools = await getArcadeTools(userId); - -const memory = new Memory({ - storage: new LibSQLStore({ - id: "arcade-agent-memory", - url: "file:memory.db", - }), - options: { - lastMessages: 10, - }, -}); - -export const arcadeAgent = new Agent({ - id: "arcade-agent", - name: "arcadeAgent", - instructions: `You are a helpful assistant that can access Gmail and Slack. -Always use the available tools to fulfill user requests. - -For Gmail: -- Use Gmail_ListEmails to fetch recent emails -- Use Gmail_SendEmail to send emails -- Use Gmail_WhoAmI to get the user's email address -- To find sent emails, use the query parameter with "in:sent" -- To find received emails, use "in:inbox" or no query -- When composing emails, use plain text (no markdown) - -For Slack: -- Use Slack_SendMessage to send messages to channels or users -- Use Slack_ListChannels to see available channels - -After completing any action, always confirm what you did with specific details. - -IMPORTANT: When a tool returns an authorization response with a URL, tell the user to visit that URL to grant access. After they authorize, they can retry their request.`, - model: openai("gpt-4o"), - tools: arcadeTools, - memory, - // Filter out tool results from memory (they can be large) and limit tokens - inputProcessors: [new ToolCallFilter(), new TokenLimiterProcessor({ limit: 50000 })], -}); -``` - -### **src/mastra/index.ts** (full file) - -src/mastra/index.ts - -```typescript -import { Mastra } from "@mastra/core"; -import { arcadeAgent } from "./agents/arcade"; -import { emailDigestWorkflow } from "./workflows/email-digest"; - -export const mastra = new Mastra({ - agents: { - arcadeAgent, - }, - workflows: { - emailDigestWorkflow, - }, -}); -``` - -### **src/mastra/workflows/email-digest.ts** (full file) - -src/mastra/workflows/email-digest.ts - -```typescript -import { createStep, createWorkflow } from "@mastra/core/workflows"; -import { z } from "zod"; -import { Arcade } from "@arcadeai/arcadejs"; - -const defaultUserId = process.env.ARCADE_USER_ID || "default-user"; - -const fetchEmails = createStep({ - id: "fetch-emails", - inputSchema: z.object({ - userId: z.string().optional(), - maxEmails: z.number().optional(), - }), - outputSchema: z.object({ - emails: z.array(z.object({ - subject: z.string(), - from: z.string(), - snippet: z.string(), - })), - userId: z.string(), - authRequired: z.boolean().optional(), - authUrl: z.string().optional(), - }), - execute: async ({ inputData }) => { - const arcade = new Arcade(); - const userId = inputData?.userId || defaultUserId; - - try { - const result = await arcade.tools.execute({ - tool_name: "Gmail_ListEmails", - user_id: userId, - input: { n_emails: inputData!.maxEmails ?? 5 }, - }); - - const response = result as { output?: { value?: { emails?: any[] } } }; - const emails = (response.output?.value?.emails || []).map((e: any) => ({ - subject: String(e.subject || "(No subject)"), - from: String(e.sender || e.from || "Unknown"), - snippet: String(e.snippet || "").slice(0, 200), - })); - - return { emails, userId }; - } catch (error: any) { - if (error.status === 403 || error.message?.includes("authorization")) { - const authResponse = await arcade.auth.start({ - user_id: userId, - provider: "google", - scopes: ["https://www.googleapis.com/auth/gmail.readonly"], - }); - return { - emails: [], - userId, - authRequired: true, - authUrl: authResponse.url, - }; - } - throw error; - } - }, -}); - -const summarizeEmails = createStep({ - id: "summarize-emails", - inputSchema: z.object({ - emails: z.array(z.object({ - subject: z.string(), - from: z.string(), - snippet: z.string(), - })), - userId: z.string(), - authRequired: z.boolean().optional(), - authUrl: z.string().optional(), - }), - outputSchema: z.object({ - summary: z.string(), - userId: z.string(), - authRequired: z.boolean().optional(), - authUrl: z.string().optional(), - }), - execute: async ({ inputData, mastra }) => { - const { emails, userId, authRequired, authUrl } = inputData!; - - if (authRequired) { - return { summary: "", userId, authRequired, authUrl }; - } - - if (emails.length === 0) { - return { summary: "No new emails.", userId }; - } - - const agent = mastra?.getAgent("arcadeAgent"); - const emailList = emails.map((e, i) => - `${i + 1}. From: ${e.from}\n Subject: ${e.subject}\n Preview: ${e.snippet}` - ).join("\n\n"); - - const response = await agent!.generate( - `Summarize these emails in 2-3 bullet points:\n\n${emailList}` - ); - - return { summary: response.text, userId }; - }, -}); - -const sendToSlack = createStep({ - id: "send-to-slack", - inputSchema: z.object({ - summary: z.string(), - userId: z.string(), - authRequired: z.boolean().optional(), - authUrl: z.string().optional(), - }), - outputSchema: z.object({ - success: z.boolean(), - message: z.string(), - authUrl: z.string().optional(), - }), - execute: async ({ inputData }) => { - const { summary, userId, authRequired, authUrl } = inputData!; - - if (authRequired) { - return { - success: false, - message: `Authorization required. Please visit this URL to grant access: ${authUrl}`, - authUrl, - }; - } - - const arcade = new Arcade(); - - try { - const whoAmI = await arcade.tools.execute({ - tool_name: "Slack_WhoAmI", - user_id: userId, - input: {}, - }); - - const slackUserId = (whoAmI as any)?.output?.value?.user_id; - - await arcade.tools.execute({ - tool_name: "Slack_SendMessage", - user_id: userId, - input: { - message: `📬 *Email Digest*\n\n${summary}`, - user_ids: [slackUserId], - }, - }); - - return { success: true, message: "Digest sent as DM" }; - } catch (error: any) { - if (error.status === 403 || error.message?.includes("authorization")) { - const slackAuth = await arcade.auth.start({ - user_id: userId, - provider: "slack", - scopes: ["chat:write", "users:read"], - }); - return { - success: false, - message: `Slack authorization required. Please visit: ${slackAuth.url}`, - authUrl: slackAuth.url, - }; - } - throw error; - } - }, -}); - -const emailDigestWorkflow = createWorkflow({ - id: "email-digest", - inputSchema: z.object({ - userId: z.string().default(defaultUserId), - maxEmails: z.number().default(5), - }), - outputSchema: z.object({ - success: z.boolean(), - message: z.string(), - authUrl: z.string().optional(), - }), -}) - .then(fetchEmails) - .then(summarizeEmails) - .then(sendToSlack); - -emailDigestWorkflow.commit(); - -export { emailDigestWorkflow }; -``` - -Last updated on January 30, 2026 - -[Authorizing Existing Tools](/en/get-started/agent-frameworks/langchain/auth-langchain-tools.md) -[Overview](/en/get-started/agent-frameworks/openai-agents/overview.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/openai-agents/overview.md b/public/_markdown/en/get-started/agent-frameworks/openai-agents/overview.md deleted file mode 100644 index e5c6d6a3e..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/openai-agents/overview.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: "Arcade with OpenAI Agents" -description: "Integrate Arcade tools with the OpenAI Agents SDK" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -OpenAI AgentsOverview - -# Arcade with OpenAI Agents - -The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/)  provides a framework for building AI . Arcade integrates with both the Python and JavaScript versions, giving your agents access to Gmail, GitHub, Slack, and 100+ other . - -## Get started - -Choose your language to set up Arcade with OpenAI : - -- **[Python setup](/get-started/agent-frameworks/openai-agents/setup-python.md) - ** - Build a CLI with Arcade using the `agents-arcade` package -- **[TypeScript setup](/get-started/agent-frameworks/openai-agents/setup-typescript.md) - ** - Build an with Arcade using `@arcadeai/arcadejs` - -## What you can build - -With Arcade and OpenAI , your agents can: - -- Read and send emails via Gmail -- Post messages to Slack channels -- Create GitHub issues and pull requests -- Search the web and extract content -- Access 100+ other integrations - -Browse the [full MCP server catalog](/resources/integrations.md) to see all available . - -Last updated on January 30, 2026 - -[Mastra](/en/get-started/agent-frameworks/mastra.md) -[Setup (Python)](/en/get-started/agent-frameworks/openai-agents/setup-python.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-python.md b/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-python.md deleted file mode 100644 index caf17eb79..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-python.md +++ /dev/null @@ -1,576 +0,0 @@ ---- -title: "Setup Arcade with OpenAI Agents SDK" -description: "Learn how to use Arcade tools in OpenAI Agents applications" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[OpenAI Agents](/en/get-started/agent-frameworks/openai-agents/overview.md) -Setup (Python) - -# Setup Arcade with OpenAI Agents SDK - -Learn how to integrate Arcade tools using OpenAI primitives. - -The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/)  is a popular Python library for building AI . It builds on top of the OpenAI API, and provides an interface for building agents. - -## Outcomes - -You will implement a CLI agent that can use Arcade tools to help the user with their requests. The handles that require authorization automatically, so don’t need to worry about it. - -### You will Learn - -- How to retrieve Arcade tools and transform them into OpenAI -- How to build an OpenAI Agents -- How to integrate Arcade tools into the OpenAI flow -- How to implement “just in time” (JIT) authorization using Arcade’s client - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Obtain an Arcade API key](/get-started/setup/api-keys.md) - -- The [`uv` package manager](https://docs.astral.sh/uv/) - - -## The agent architecture you will build in this guide - -The OpenAI SDK provides an [Agent](https://openai.github.io/openai-agents-python/ref/agent/#agents.agent.Agent)  class that implements a . It provides an interface for you to define the system prompt, the model, the , and possible sub-agents for handoffs. In this guide, you will manually keep track of the agent’s history and state, and use the `run` method to invoke the agent in an agentic loop. - -## Integrate Arcade tools into an OpenAI Agents agent - -### Create a new project - -Create a new directory for your and initialize a new virtual environment: - -```bash -mkdir openai-agents-arcade-example -cd openai-agents-arcade-example -uv init -uv venv -``` - -### Bash/Zsh (macOS/Linux) - -```bash -source .venv/bin/activate -``` - -### PowerShell (Windows) - -```powershell -. ".venv\Scripts\Activate.ps1" -``` - -Install the necessary packages: - -```powershell -uv add openai-agents arcadepy -``` - -Create a new file called `.env` and add the following environment variables: - -```powershell -# .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: - -```json -# main.py -from agents import Agent, Runner, TResponseInputItem -from agents.run_context import RunContextWrapper -from agents.tool import FunctionTool -from agents.exceptions import AgentsException -from arcadepy import AsyncArcade -from arcadepy.types.execute_tool_response import ExecuteToolResponse -from dotenv import load_dotenv -from functools import partial -from typing import Any -import os -import asyncio -import json -``` - -This includes many imports, here’s a breakdown: - -- Arcade imports: - - `AsyncArcade`: The , used to interact with the . - - `ExecuteToolResponse`: The response type for the execute response. -- OpenAI imports: - - `Agent`: The OpenAI Agents , used to define an agent. - - `Runner`: The OpenAI Agents runner, which runs the in an agentic loop. - - `TResponseInputItem`: The response input item type, determines the type of message in the conversation history. - - `RunContextWrapper`: Wraps the run , providing information such as the user ID, the tool name, tool arguments, and other contextual information different parts of the may need. - - `FunctionTool`: OpenAI definition format. - - `AgentsException`: The OpenAI exception, used to handle errors in the agentic loop. -- Other imports: - - `load_dotenv`: Loads the environment variables from the `.env` file. - - `functools.partial`: Partially applies a function to a given set of arguments. - - `typing.Any`: A type hint for the any type. - - `os`: The operating system module, used to interact with the operating system. - - `asyncio`: The asynchronous I/O module, used to interact with the asynchronous I/O. - - `json`: The JSON module, used to interact with JSON data. - -### Configure the agent - -These variables customize the and manage the in the rest of the code. Feel free to configure them to your liking. - -```python -# 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 . It wraps the `AgentsException` and provides an informative error message that the agentic loop can handle in case anything goes wrong. - -You also define `convert_output_to_json` to convert the output of the Arcade tools to a JSON string. This is useful because the output of the Arcade tools is not always a JSON object, and the OpenAI SDK expects a JSON string. - -```python -# main.py -# Arcade to OpenAI agent exception classes -class ToolError(AgentsException): - def __init__(self, result: ExecuteToolResponse | str): - self.result = None - if isinstance(result, str): - self.message = result - else: - self.message = result.output.error.message - self.result = result - - def __str__(self): - if self.result: - return f"Tool {self.result.tool_name} failed with error: {self.message}" - else: - return self.message - - -def convert_output_to_json(output: Any) -> str: - if isinstance(output, dict) or isinstance(output, list): - return json.dumps(output) - else: - return str(output) -``` - -### Write a helper function to authorize Arcade tools - -This helper function implements “just in time” (JIT) tool authorization using Arcade’s client. When the tries to execute a that requires authorization, the `result` object’s `status` will be `"pending"`, and you can use the `authorize` method to get an authorization URL. You then wait for the to complete the authorization and retry the tool call. If the user has already authorized the tool, the `status` will be `"completed"`, and the OAuth dance skips silently, which improves the user experience. - -This function captures the authorization flow outside of the agent’s context, which is a good practice for security and context engineering. By handling everything in the , you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the free from any authorization-related traces, which reduces the risk of hallucinations. - -```python -# main.py -async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str): - if not context.context.get("user_id"): - raise ToolError("No user ID and authorization required for tool") - - result = await client.tools.authorize( - tool_name=tool_name, - user_id=context.context.get("user_id"), - ) - - if result.status != "completed": - print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}") - - await client.auth.wait_for_completion(result) -``` - -### Write a helper function to execute Arcade tools - -This helper function shows how the OpenAI framework invokes the Arcade tools. It handles the authorization flow, and then calls the using the `execute` method. It handles the conversion of the arguments from JSON to a dictionary (expected by Arcade) and the conversion of the output from the Arcade tool to a JSON string (expected by the OpenAI Agents framework). Here is where you call the helper functions defined earlier to authorize the tool and convert the output to a JSON string. - -```python -# main.py -async def invoke_arcade_tool( - context: RunContextWrapper, - tool_args: str, - tool_name: str, - client: AsyncArcade, -): - args = json.loads(tool_args) - await authorize_tool(client, context, tool_name) - - print(f"Invoking tool {tool_name} with args: {args}") - result = await client.tools.execute( - tool_name=tool_name, - input=args, - user_id=context.context.get("user_id"), - ) - if not result.success: - raise ToolError(result) - - print(f"Tool {tool_name} called successfully, {MODEL} will now process the result...") - - return convert_output_to_json(result.output.value) -``` - -### Retrieve Arcade tools and transform them into LangChain tools - -Here you get the Arcade tools you want the agent to use, and transform them into OpenAI Agents tools. The first step is to initialize the , and get the tools you want. Since OpenAI is itself an inference provider, the provides a convenient endpoint to get the tools in the OpenAI format, which is also the format expected by the OpenAI framework. - -This helper function is long, here’s a breakdown of what it does for clarity: - -- retrieve tools from all configured servers (defined in the `MCP_SERVERS` variable) -- retrieve individual (defined in the `TOOLS` variable) -- get the Arcade to OpenAI-formatted tools -- create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the . - -```python -# main.py -async def get_arcade_tools( - client: AsyncArcade | None = None, - tools: list[str] | None = None, - mcp_servers: list[str] | None = None, -) -> list[FunctionTool]: - - if not client: - client = AsyncArcade() - - # if no tools or MCP servers are provided, raise an error - if not tools and not mcp_servers: - raise ValueError( - "No tools or MCP servers provided to retrieve tool definitions") - - # Use the Arcade Client to get OpenAI-formatted tool definitions - tool_formats = [] - - # Retrieve individual tools if specified - if tools: - # OpenAI-formatted tool definition - tasks = [client.tools.formatted.get(name=tool_id, format="openai") - for tool_id in tools] - responses = await asyncio.gather(*tasks) - for response in responses: - tool_formats.append(response) - - # Retrieve tools from specified toolkits - if mcp_servers: - # Create a task for each toolkit to fetche the formatted tool definition concurrently. - tasks = [client.tools.formatted.list(toolkit=tk, format="openai") - for tk in mcp_servers] - responses = await asyncio.gather(*tasks) - - # Combine the tool definitions from each response. - for response in responses: - # Here the code assumes the returned response has an "items" attribute - # containing a list of ToolDefinition objects. - tool_formats.extend(response.items) - - - # Create a list of FunctionTool objects, mapping each tool to a partial function that invokes the tool via the Arcade client. - tool_functions = [] - for tool in tool_formats: - tool_name = tool["function"]["name"] - tool_description = tool["function"]["description"] - tool_params = tool["function"]["parameters"] - tool_function = FunctionTool( - name=tool_name, - description=tool_description, - params_json_schema=tool_params, - on_invoke_tool=partial( - invoke_arcade_tool, - tool_name=tool_name, - client=client, - ), - strict_json_schema=False, - ) - tool_functions.append(tool_function) - - return tool_functions -``` - -### Create the main function - -The main function is where you: - -- Get the tools from the configured servers -- Create an with the configured -- Initialize the conversation -- Run the loop - -The loop is a while loop that captures the user input, appends it to the conversation history, and then runs the . The agent’s response is then appended to the conversation history, and the loop continues. - -When a interrupts the loop, the interruption handles via the helper function you wrote earlier. - -```python -# main.py -async def main(): - # 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 responding to your prompts like any model, as well as handling any calls and authorization requests. Here are some example prompts you can try: - -- “Send me an email with a random haiku about OpenAI ” -- “Summarize my latest 3 emails” - -## Key takeaways - -- You can integrate Arcade tools into any agentic framework like OpenAI , all you need is to transform the Arcade into OpenAI Agents tools and handle the authorization flow. -- isolation: By handling the authorization flow outside of the ’s context, you remove the risk of the LLM replacing the authorization URL or leaking it, and you keep the context free from any authorization-related traces, which reduces the risk of hallucinations. - -## Next steps - -1. Try adding additional tools to the or modifying the in the catalog for a different use case by modifying the `MCP_SERVERS` and `TOOLS` variables. -2. Try implementing a fully deterministic flow before the agentic loop, you can use this deterministic phase to prepare the for the , adding things like the current date, time, or any other information that is relevant to the task at hand. - -## Example code - -The team provides example code for you to reference: - -```json -# main.py -from agents import Agent, Runner, TResponseInputItem -from agents.run_context import RunContextWrapper -from agents.tool import FunctionTool -from agents.exceptions import AgentsException -from arcadepy import AsyncArcade -from 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()) -``` - -Last updated on February 10, 2026 - -[Overview](/en/get-started/agent-frameworks/openai-agents/overview.md) -[Setup (TypeScript)](/en/get-started/agent-frameworks/openai-agents/setup-typescript.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-typescript.md b/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-typescript.md deleted file mode 100644 index 4e551a461..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/openai-agents/setup-typescript.md +++ /dev/null @@ -1,388 +0,0 @@ ---- -title: "Setup Arcade with OpenAI Agents (TypeScript)" -description: "Build an agent with Arcade tools using the OpenAI Agents SDK for JavaScript/TypeScript" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[OpenAI Agents](/en/get-started/agent-frameworks/openai-agents/overview.md) -Setup (TypeScript) - -# Setup Arcade with OpenAI Agents (TypeScript) - -The [OpenAI Agents SDK for JavaScript](https://openai.github.io/openai-agents-js/)  provides a framework for building AI in TypeScript and JavaScript. Arcade’s `@arcadeai/arcadejs` library converts Arcade to the format OpenAI Agents expects. - -## Outcomes - -Build an that uses Arcade to help with Gmail and Slack - -### You will Learn - -- How to retrieve Arcade tools and convert them to OpenAI format -- How to use the factory pattern for execution -- How to handle authorization with `executeOrAuthorizeZodTool` - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Obtain an Arcade API key](/get-started/setup/api-keys.md) - -- [Node.js](https://nodejs.org/) -   18+ (includes npm) or [Bun](https://bun.sh/) -   - -## How Arcade integrates with OpenAI Agents - -The OpenAI JS SDK uses [Zod](https://zod.dev)  schemas for definitions. Arcade’s `toZod()` function converts Arcade tool definitions to Zod schemas, and the `executeFactory` parameter determines how tools execute. - -TypeScript uses a **factory pattern** to bind to functions. `executeFactory` receives (`client`, `userId`, `tool definition`) and returns an execute function with that context “baked in”. - -## Build the agent - -### Create a new project - -Create a new directory and install dependencies: - -### npm - -```bash -mkdir openai-agents-arcade-ts -cd openai-agents-arcade-ts -npm init -y -npm install @openai/agents @arcadeai/arcadejs dotenv -``` - -### bun - -```bash -mkdir openai-agents-arcade-ts -cd openai-agents-arcade-ts -bun init -y -bun add @openai/agents @arcadeai/arcadejs dotenv -``` - -Create a `.env` file with your : - -```bash -# .env -# Arcade API key from https://app.arcade.dev/api-keys -ARCADE_API_KEY=YOUR_ARCADE_API_KEY -# Your Arcade user ID (the email you used to sign up) -ARCADE_USER_ID={arcade_user_id} -# OpenAI API key -OPENAI_API_KEY=YOUR_OPENAI_API_KEY -``` - -### Create agent file - -Create `index.ts` (or `index.mjs` for plain JavaScript): - -```typescript -// index.ts -import Arcade from "@arcadeai/arcadejs"; -import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib/index"; -import { Agent, run, tool } from "@openai/agents"; -import "dotenv/config"; -import readline from "node:readline/promises"; - -// Configuration -const ARCADE_USER_ID = process.env.ARCADE_USER_ID || "default-user"; -const MCP_SERVERS = ["Slack"]; -const INDIVIDUAL_TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]; -const SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."; -const MODEL = "gpt-4o-mini"; -``` - -### Retrieve and convert Arcade tools - -Use `toZod()` to convert Arcade to Zod schemas, then `tool()` to convert them to OpenAI format: - -```typescript -// index.ts -async function getArcadeTools(client: Arcade, userId: string) { - // Fetch tools from MCP servers - const mcpServerTools = await Promise.all( - MCP_SERVERS.map(async (serverName) => { - const response = await client.tools.list({ - toolkit: serverName, - limit: 30, - }); - return response.items; - }) - ); - - // Fetch individual tools by name - const individualToolDefs = await Promise.all( - INDIVIDUAL_TOOLS.map((toolName) => client.tools.get(toolName)) - ); - - // Combine and deduplicate - const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; - const uniqueTools = Array.from( - new Map(allTools.map((t) => [t.qualified_name, t])).values() - ); - - // Convert to Zod format with the execute factory - // This is the TypeScript equivalent of Python's functools.partial - - // the factory receives context and returns an execute function - const zodTools = toZod({ - tools: uniqueTools, - client, - userId, - executeFactory: executeOrAuthorizeZodTool, - }); - - // Convert Zod tools to OpenAI Agents format - return zodTools.map(tool); -} -``` - -**Understanding the factory pattern**: In TypeScript, `executeFactory` binds context to a function. It receives the (`client`, `userId`, `tool definition`) and returns an execute function with that context already bound. - -### Create and run the agent - -```typescript -// index.ts -async function main() { - // Initialize Arcade client - const client = new Arcade(); - - // Get tools converted to OpenAI Agents format - const tools = await getArcadeTools(client, ARCADE_USER_ID); - - // Create the agent - const agent = new Agent({ - name: "Inbox Assistant", - instructions: SYSTEM_PROMPT, - model: MODEL, - tools, - }); - - // Set up interactive chat - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - console.log("Hello! I'm your helpful OpenAI Agent! I use Arcade Tools to access your Gmail and Slack. Try asking me to summarize your recent emails or DM you on Slack!\n\nType 'exit' to quit.\n"); - - // Track conversation history for multi-turn context - let conversationHistory: any[] = []; - - while (true) { - const input = await rl.question("> "); - if (input.toLowerCase() === "exit") { - break; - } - - try { - // Pass conversation history for context, add new user message - const result = await run( - agent, - conversationHistory.concat({ role: "user", content: input }) - ); - // Update history with full conversation - conversationHistory = result.history; - console.log(`\n${result.finalOutput}\n`); - } catch (error) { - console.error("Error:", error); - } - } - - console.log("Goodbye!"); - rl.close(); - process.exit(0); -} - -main().catch(console.error); -``` - -### Handle authorization - -The `executeOrAuthorizeZodTool` factory automatically handles authorization. When a tool needs OAuth authorization (like Gmail), instead of throwing an error, it returns a response with the authorization URL. The ’s output will include something like the following: - -```bash -Please authorize access: https://accounts.google.com/... -``` - -After the visits the URL and authorizes, running the same request again will succeed. - -For more control over authorization flow, you can create a custom execute factory: - -```typescript -// index.ts -import { isAuthorizationRequiredError, executeZodTool } from "@arcadeai/arcadejs/lib/index"; - -// Custom factory that waits for authorization -function executeWithWait({ zodToolSchema, toolDefinition, client, userId }) { - return async (input) => { - try { - return await executeZodTool({ zodToolSchema, toolDefinition, client, userId })(input); - } catch (error) { - if (error instanceof Error && isAuthorizationRequiredError(error)) { - const response = await client.tools.authorize({ - tool_name: zodToolSchema.name, - user_id: userId, - }); - - if (response.status !== "completed") { - console.log(`Please authorize: ${response.url}`); - // Wait for the user to complete authorization - await client.auth.waitForCompletion(response); - } - - // Retry after authorization - return await executeZodTool({ zodToolSchema, toolDefinition, client, userId })(input); - } - throw error; - } - }; -} - -// Use with toZod -const zodTools = toZod({ - tools: uniqueTools, - client, - userId, - executeFactory: executeWithWait, -}); -``` - -### Run the agent - -### npm - -Use `npx` (included with npm) to run the TypeScript file: - -```bash -npx tsx index.ts -``` - -### bun - -```bash -bun run index.ts -``` - -## Key takeaways - -- **`toZod()`** converts Arcade tools to Zod schemas that OpenAI can use -- **`executeFactory`** is the TypeScript equivalent of Python’s `functools.partial` - it binds to execute functions -- **`executeOrAuthorizeZodTool`** automatically returns authorization URLs when need OAuth -- **`isAuthorizationRequiredError`** lets you detect and handle authorization errors in custom factories -- The **`userId`** parameter tracks authorization per - use a consistent ID for each user -- **`result.history`** contains the full conversation history - pass it back to `run()` for multi-turn - -## Complete code - -```typescript -// index.ts -import Arcade from "@arcadeai/arcadejs"; -import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib/index"; -import { Agent, run, tool } from "@openai/agents"; -import "dotenv/config"; -import readline from "node:readline/promises"; - -// Configuration -const ARCADE_USER_ID = process.env.ARCADE_USER_ID || "default-user"; -const MCP_SERVERS = ["Slack"]; -const INDIVIDUAL_TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"]; -const SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack."; -const MODEL = "gpt-4o-mini"; - -async function getArcadeTools(client: Arcade, userId: string) { - // Fetch tools from MCP servers - const mcpServerTools = await Promise.all( - MCP_SERVERS.map(async (serverName) => { - const response = await client.tools.list({ - toolkit: serverName, - limit: 30, - }); - return response.items; - }) - ); - - // Fetch individual tools by name - const individualToolDefs = await Promise.all( - INDIVIDUAL_TOOLS.map((toolName) => client.tools.get(toolName)) - ); - - // Combine and deduplicate - const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; - const uniqueTools = Array.from( - new Map(allTools.map((t) => [t.qualified_name, t])).values() - ); - - // Convert to Zod format with the execute factory - const zodTools = toZod({ - tools: uniqueTools, - client, - userId, - executeFactory: executeOrAuthorizeZodTool, - }); - - // Convert Zod tools to OpenAI Agents format - return zodTools.map(tool); -} - -async function main() { - const client = new Arcade(); - const tools = await getArcadeTools(client, ARCADE_USER_ID); - - const agent = new Agent({ - name: "Inbox Assistant", - instructions: SYSTEM_PROMPT, - model: MODEL, - tools, - }); - - // Set up interactive chat - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - console.log("Hello! I'm your helpful OpenAI Agent! I use Arcade Tools to access your Gmail and Slack. Try asking me to summarize your recent emails or DM you on Slack!\n\nType 'exit' to quit.\n"); - - // Track conversation history for multi-turn context - let conversationHistory: any[] = []; - - while (true) { - const input = await rl.question("> "); - if (input.toLowerCase() === "exit") { - break; - } - - try { - const result = await run( - agent, - conversationHistory.concat({ role: "user", content: input }) - ); - conversationHistory = result.history; - console.log(`\n${result.finalOutput}\n`); - } catch (error) { - console.error("Error:", error); - } - } - - console.log("Goodbye!"); - rl.close(); - process.exit(0); -} - -main().catch(console.error); -``` - -## Next steps - -- Add more by modifying `MCP_SERVERS` and `INDIVIDUAL_TOOLS` -- Build a web interface using frameworks like Next.js or Express -- See the [Vercel AI SDK tutorial](/get-started/agent-frameworks/vercelai.md) - or [TanStack AI tutorial](/get-started/agent-frameworks/tanstack-ai.md) - for complete web chatbot examples -- Explore [creating custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) - with the Arcade SDK - -Last updated on February 10, 2026 - -[Setup (Python)](/en/get-started/agent-frameworks/openai-agents/setup-python.md) -[TanStack AI](/en/get-started/agent-frameworks/tanstack-ai.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/openai-agents/use-arcade-tools.md b/public/_markdown/en/get-started/agent-frameworks/openai-agents/use-arcade-tools.md deleted file mode 100644 index abe8cc199..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/openai-agents/use-arcade-tools.md +++ /dev/null @@ -1,300 +0,0 @@ ---- -title: "Use Arcade with OpenAI Agents" -description: "Integrate Arcade tools into your OpenAI Agents applications" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[OpenAI Agents](/en/get-started/agent-frameworks/openai-agents/overview.md) -Using Arcade tools - -## Use Arcade with OpenAI Agents - -In this guide, let’s explore how to integrate Arcade tools into your OpenAI application. Follow the step-by-step instructions below. - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys.md) - - -### Set up your environment - -Install the required packages, and ensure your environment variables are set with your key: - -### Python - -```bash -pip install agents-arcade arcadepy -``` - -### JavaScript - -```bash -npm install @openai/agents @arcadeai/arcadejs -``` - -### Configure API keys - -Provide your key. You can store it in environment variables or directly in your code: - -> Need an key? Visit the [Get an API key](/get-started/setup/api-keys.md) page to create one. - -### Python - -```python -import os - -os.environ["ARCADE_API_KEY"] = "YOUR_ARCADE_API_KEY" -# Or set it directly when initializing the client -``` - -### JavaScript - -```bash -# In your .env file -ARCADE_API_KEY=YOUR_ARCADE_API_KEY -``` - -### Create and manage Arcade tools - -### Python - -Use the `get_arcade_tools` function to retrieve tools from specific Servers: - -```python -from arcadepy import AsyncArcade -from agents_arcade import get_arcade_tools - -# Initialize the Arcade client -client = AsyncArcade() - -# Get all tools from the "Gmail" MCP Server -tools = await get_arcade_tools(client, toolkits=["gmail"]) - -# You can request multiple MCP Servers at once -tools = await get_arcade_tools(client, toolkits=["gmail", "github", "linkedin"]) - -# You can request specific tools -tools = await get_arcade_tools(client, tools=["Gmail_ListEmails", "Slack_ListUsers", "Slack_SendDmToUser"]) -``` - -### JavaScript - -```javascript -import Arcade from '@arcadeai/arcadejs'; -import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib"; -import { tool } from '@openai/agents'; - -const client = new Arcade(); - -// Option 1: Get tools from a single MCP Server -const googleTools = await client.tools.list({ toolkit: "gmail", limit: 30 }); -const toolsFromGoogle = googleTools.items; - -// Option 2: Get tools from multiple MCP Servers -const [google, github, linkedin] = await Promise.all([ - client.tools.list({ toolkit: "gmail", limit: 30 }), - client.tools.list({ toolkit: "github", limit: 30 }), - client.tools.list({ toolkit: "linkedin", limit: 30 }) -]); -const toolsFromMultiple = [...google.items, ...github.items, ...linkedin.items]; - -// Option 3: Get specific tools by name -const specificTools = await Promise.all([ - client.tools.get("Gmail_ListEmails"), - client.tools.get("Slack_ListUsers"), - client.tools.get("Slack_SendDmToUser"), -]); - -// Convert any of the above to OpenAI Agents format -const convertToAgents = (arcadeTools) => { - return toZod({ - tools: arcadeTools, - client, - userId: "", // Replace this with your application's user ID (e.g. email address, UUID, etc.) - executeFactory: executeOrAuthorizeZodTool, - }).map(tool); -}; - -// Use with any of the options above -const tools = convertToAgents(toolsFromGoogle); // or toolsFromMultiple or specificTools -``` - -### Set up the agent with Arcade tools - -Create an and provide it with the Arcade : - -### Python - -```python -from agents import Agent, Runner - -# Create an agent with Gmail tools -google_agent = Agent( - name="Gmail agent", - instructions="You are a helpful assistant that can assist with Google API calls.", - model="gpt-4o-mini", - tools=tools, -) -``` - -### JavaScript - -```javascript -import { Agent, Runner, tool } from '@openai/agents'; - -// Create an agent with Arcade tools -const googleAgent = new Agent({ - name: "Gmail agent", - instructions: "You are a helpful assistant that can assist with Google API calls.", - model: "gpt-4o-mini", - tools -}); -``` - -### Run the agent - -### Python - -Run the , providing a user\_id for authorization: - -```python -try: - result = await Runner.run( - starting_agent=google_agent, - input="What are my latest emails?", - context={"user_id": "{arcade_user_id}"}, - ) - print("Final output:\n\n", result.final_output) -except AuthorizationError as e: - print("Please Login to Google:", e) -``` - -### JavaScript - -```javascript -const result = await run(googleAgent, "What are my latest emails?"); -``` - -### Handle authentication - -### Python - -If a requires authorization, an `AuthorizationError` will be raised with an authorization URL: - -```python -from agents_arcade.errors import AuthorizationError - -try: - # Run agent code from earlier examples - # ... -except AuthorizationError as e: - print(f"Please visit this URL to authorize: {e}") - # The URL contained in the error will take the user to the authorization page -``` - -### JavaScript - -If a tool requires authorization, the will show a message like this: - -```bash -[Authorize Gmail Access](https://accounts.google.com/o/oauth2/v2/auth?access_type=offline...) -Once you have authorized access, I can retrieve your latest emails. -``` - -### Complete example - -Here’s a complete example putting everything together: - -### Python - -```python -from agents import Agent, Runner -from arcadepy import AsyncArcade - -from agents_arcade import get_arcade_tools -from agents_arcade.errors import AuthorizationError - - -async def main(): - client = AsyncArcade() - tools = await get_arcade_tools(client, toolkits=["gmail"]) - - google_agent = Agent( - name="Google agent", - instructions="You are a helpful assistant that can assist with Google API calls.", - model="gpt-4o-mini", - tools=tools, - ) - - try: - result = await Runner.run( - starting_agent=google_agent, - input="What are my latest emails?", - context={"user_id": "{arcade_user_id}"}, - ) - print("Final output:\n\n", result.final_output) - except AuthorizationError as e: - print("Please Login to Google:", e) - - -if __name__ == "__main__": - import asyncio - - asyncio.run(main()) -``` - -### JavaScript - -Check out the complete working example in our [GitHub repository](https://github.com/ArcadeAI/arcade-ai/tree/main/examples/openai-agents-ts/src/index.ts) . - -```javascript -import Arcade from '@arcadeai/arcadejs'; -import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib"; -import { Agent, run, tool } from '@openai/agents'; - -// 1) Initialize Arcade client -const client = new Arcade(); - -// 2) Fetch Gmail MCP Server from Arcade and prepare tools for OpenAI Agents -const googleToolkit = await client.tools.list({ toolkit: "gmail", limit: 30 }); -const tools = toZod({ - tools: googleToolkit.items, - client, - userId: "", // Replace this with your application's user ID (e.g. email address, UUID, etc.) - executeFactory: executeOrAuthorizeZodTool, -}).map(tool); - -// 3) Create a new agent with the Gmail MCP Server -const googleAgent = new Agent({ - name: "Gmail agent", - instructions: "You are a helpful assistant that can assist with Google API calls.", - model: "gpt-4o-mini", - tools -}); - -// 4) Run the agent -const result = await run(googleAgent, "What are my latest emails?"); - -// 5) Print the result -console.log(result.finalOutput); -``` - -## Tips for selecting tools - -- **Relevance**: Pick only the you need. Avoid using all tools at once. -- ** identification**: Always provide a unique and consistent `user_id` for each user. Use your internal or database user ID, not something entered by the user. - -## Next steps - -Now that you have integrated Arcade tools into your OpenAI application, you can: - -- Experiment with different Servers, such as “Github” or “LinkedIn” -- Customize the ’s instructions for specific tasks -- Try out multi- systems using different Arcade -- Build your own custom tools with the Arcade SDK - -Enjoy exploring Arcade and building powerful AI-enabled Python applications! - -Last updated on February 10, 2026 - -[Setup Arcade with OpenAI Agents SDK](/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents.md) -[Managing user authorization](/en/get-started/agent-frameworks/openai-agents/user-auth-interrupts.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/openai-agents/user-auth-interrupts.md b/public/_markdown/en/get-started/agent-frameworks/openai-agents/user-auth-interrupts.md deleted file mode 100644 index b37c98df3..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/openai-agents/user-auth-interrupts.md +++ /dev/null @@ -1,403 +0,0 @@ ---- -title: "Managing user authorization" -description: "Handle tool authorization with Arcade and OpenAI Agents" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -[OpenAI Agents](/en/get-started/agent-frameworks/openai-agents/overview.md) -Managing user authorization - -## User authorization with OpenAI Agents - -In this guide, you will learn how to handle user authorization for Arcade tools in your OpenAI Agents application. When a tool requires authorization, the will raise an `AuthorizationError` with a URL for the to visit and grant permissions. - -### Prerequisites - -- [Obtain an Arcade API key](/get-started/setup/api-keys.md) - - -### Install the required packages - -Set up your environment with the following installations: - -### Python - -```bash -pip install agents-arcade arcadepy -``` - -### JavaScript - -```bash -npm install @openai/agents @arcadeai/agents-arcade -``` - -### Configure your Arcade environment - -Make sure you have set your key in the environment, or assign it directly in the code: - -> Need an key? Visit the [Get an API key](/get-started/setup/api-keys.md) page to create one. - -### Python - -```python -import os -from arcadepy import AsyncArcade -from agents import Agent, Runner -from agents_arcade import get_arcade_tools -from agents_arcade.errors import AuthorizationError - -# Set your API key -os.environ["ARCADE_API_KEY"] = "YOUR_ARCADE_API_KEY" - -# Initialize the Arcade client -client = AsyncArcade() -``` - -### JavaScript - -Add your to your environment variables: - -```bash -# In your .env file -ARCADE_API_KEY=YOUR_ARCADE_API_KEY -``` - -```javascript -import Arcade from '@arcadeai/arcadejs'; -import { isAuthorizationRequiredError, toZod } from "@arcadeai/arcadejs/lib"; -import { Agent, run, tool } from '@openai/agents'; - -const client = new Arcade(); -``` - -### Fetch Arcade tools - -Get the tools you need for your . In this example, we’ll use GitHub : - -### Python - -```python -# Get GitHub tools for this example -tools = await get_arcade_tools(client, toolkits=["github"]) - -# Create an agent with GitHub tools -github_agent = Agent( - name="GitHub agent", - instructions="You are a helpful assistant that can assist with GitHub API calls.", - model="gpt-4o-mini", - tools=tools, -) -``` - -### JavaScript - -```javascript -const githubToolkit = await client.tools.list({ toolkit: "github", limit: 30 }); -const arcadeTools = toZod({ - tools: githubToolkit.items, - client, - userId: "", // Replace this with your application's user ID (e.g. email address, UUID, etc.) -}) -``` - -### Handle authorization errors - -### Python - -When a user needs to authorize access to a tool, the will raise an `AuthorizationError`. You can handle it like this: - -```python -try: - result = await Runner.run( - starting_agent=github_agent, - input="Star the arcadeai/arcade-ai repo", - # Pass a unique user_id for authentication - context={"user_id": "{arcade_user_id}"}, - ) - print("Final output:\n\n", result.final_output) -except AuthorizationError as e: - # Display the authorization URL to the user - print(f"Please Login to GitHub: {e}") - # The error contains the authorization URL that the user should visit -``` - -### JavaScript - -Choose how to handle authorization errors based on your needs: - -**Default behavior (throws errors):** - -```javascript -const arcadeTools = toZod({ - tools: githubToolkit.items, - client, - userId: "", // Replace with your user ID -}); -``` - -Use this when you want to handle authorization flow yourself with custom logic. - -**Auto-handle authorization (recommended):** - -```javascript -import { executeOrAuthorizeZodTool } from "@arcadeai/arcadejs/lib"; - -const arcadeTools = toZod({ - tools: githubToolkit.items, - client, - userId: "", // Replace with your user ID - executeFactory: executeOrAuthorizeZodTool, -}); -``` - -This automatically returns authorization URLs instead of throwing errors. - -**Custom error handling:** - -```javascript -import { isAuthorizationRequiredError } from "@arcadeai/arcadejs/lib"; - -const tools = arcadeTools.map((arcadeTool) => { - return tool({ - ...arcadeTool, - errorFunction: async (_, error) => { - if (error instanceof Error && isAuthorizationRequiredError(error)) { - const response = await client.tools.authorize({ - tool_name: arcadeTool.name, - user_id: "", - }); - return `Please login to Google: ${response.url}`; - } - return "Error executing tool" - } - }) -}); -``` - -### Wait for authorization completion - -You can also wait for the to complete the authorization before continuing: - -### Python - -```python -from arcadepy import AsyncArcade -import asyncio - -client = AsyncArcade() - -async def handle_auth_flow(auth_id): - # Display a message to the user - print("Please visit the authorization URL in your browser") - - - # Wait for the user to authenticate - await client.auth.wait_for_completion(auth_id) - - # Check if authorization was successful - if await is_authorized(auth_id): - print("Authorization successful! You can now use the tool.") - return True - else: - print("Authorization failed or timed out.") - return False - -# In your main function -try: - # Run agent code - # ... -except AuthorizationError as e: - auth_id = e.auth_id - if await handle_auth_flow(auth_id): - # Try running the agent again - result = await Runner.run( - starting_agent=github_agent, - input="Star the arcadeai/arcade-ai repo", - context={"user_id": "{arcade_user_id}"}, - ) - print("Final output:\n\n", result.final_output) -``` - -### JavaScript - -To wait for authorization completion, follow this approach: - -1. Throw the error to the -2. Catch and handle the error while waiting for completion - -```javascript -const tools = arcadeTools.map((arcadeTool) => { - return tool({ - ...arcadeTool, - errorFunction: (_, error) => { throw error } // Throw the error to the agent for handling - }) -}); - -while (true) { - try { - const result = await run(googleAgent, "What are my latest emails?"); - console.log(result.finalOutput); - } catch (error) { - // Catch the authorization error and wait for completion - if (error instanceof Error && isAuthorizationRequiredError(error)) { - const response = await client.tools.authorize({ - tool_name: "Gmail_ListEmails", - user_id: "", - }); - if (response.status !== "completed") { - console.log(`Please complete the authorization challenge in your browser: ${response.url}`); - } - - // Wait for the authorization to complete - await client.auth.waitForCompletion(response); - console.log("Authorization completed, retrying..."); - } - } - } -``` - -### Complete example - -Here’s a full example that demonstrates the authorization flow with waiting for authentication: - -### Python - -```python -from arcadepy.auth import wait_for_authorization_completion - -import time - -from agents import Agent, Runner -from arcadepy import AsyncArcade - -from agents_arcade import get_arcade_tools -from agents_arcade.errors import AuthorizationError - - -async def main(): - client = AsyncArcade() - # Use the "github" MCP Server for this example - tools = await get_arcade_tools(client, toolkits=["github"]) - - github_agent = Agent( - name="GitHub agent", - instructions="You are a helpful assistant that can assist with GitHub API calls.", - model="gpt-4o-mini", - tools=tools, - ) - - user_id = "{arcade_user_id}" # Make sure to use a unique user ID - - while True: - try: - result = await Runner.run( - starting_agent=github_agent, - input="Star the arcadeai/arcade-ai repo", - # Pass the user_id for auth - context={"user_id": user_id}, - ) - print("Final output:\n\n", result.final_output) - break # Exit the loop if successful - - except AuthorizationError as e: - auth_url = str(e) - print(f"{auth_url}. Please authenticate to continue.") - - # Wait for the user to authenticate - await client.auth.wait_for_completion(e.result.id) - - -if __name__ == "__main__": - import asyncio - - asyncio.run(main()) -``` - -### JavaScript - -Check out the complete working example in our [GitHub repository](https://github.com/ArcadeAI/arcade-ai/tree/main/examples/openai-agents-ts/src/waitForCompletion.ts) . - -```javascript -import Arcade from '@arcadeai/arcadejs'; -import { isAuthorizationRequiredError, toZod } from "@arcadeai/arcadejs/lib"; -import { Agent, run, tool } from '@openai/agents'; - -async function main() { - // 1) Initialize Arcade client - const client = new Arcade(); - - // 2) Fetch Gmail MCP Server from Arcade and prepare tools for OpenAI Agents - const googleToolkit = await client.tools.list({ toolkit: "gmail", limit: 30 }); - const tools = toZod({ - tools: googleToolkit.items, - client, - userId: "", // Replace this with your application's user ID (e.g. email address, UUID, etc.) - }).map(tool); - - // 3) Create a new agent with the Gmail MCP Server - const googleAgent = new Agent({ - name: "Gmail agent", - instructions: "You are a helpful assistant that can assist with Google API calls.", - model: "gpt-4o-mini", - tools - }); - - // 4) Run the agent, if authorization is required, wait for it to complete and retry - while (true) { - try { - const result = await run(googleAgent, "What are my latest emails?"); - console.log(result.finalOutput); - break; // Exit the loop if the result is successful - } catch (error) { - if (error instanceof Error && isAuthorizationRequiredError(error)) { - const response = await client.tools.authorize({ - tool_name: "Gmail_ListEmails", - user_id: "", - }); - if (response.status !== "completed") { - console.log(`Please complete the authorization challenge in your browser: ${response.url}`); - } - - // Wait for the authorization to complete - await client.auth.waitForCompletion(response); - console.log("Authorization completed, retrying..."); - } - } - } -} - -main(); -``` - -This example handles the authentication flow by: - -1. Attempting to run the -2. Catching any AuthorizationError -3. Open the authentication URL in a browser -4. Waiting for the to complete authentication -5. Retrying the operation after a wait period - -## Authentication persistence - -Once a user authorizes with an auth provider, Arcade will remember the authorization for that specific user\_id and Server. You don’t need to re-authorize each time you run the . - -Key points to remember: - -- Always use a consistent and unique `user_id` for each -- Store the `user_id` securely in your application -- Different Servers require separate authorization flows -- Authorization tokens are managed by Arcade, not your application - -## Next steps - -- Build a interface to handle authorization flows smoothly -- Explore other Arcade Servers like Google, LinkedIn, or X -- Create multi-step workflows with multiple and authorizations -- Learn to build your own custom tools with the Arcade SDK - -By handling Arcade’s authorization flow correctly, you can build AI-driven applications that securely integrate with various services while respecting permissions. Have fun exploring Arcade! - -Last updated on February 10, 2026 - -[Using Arcade tools](/en/get-started/agent-frameworks/openai-agents/use-arcade-tools.md) -[Vercel AI SDK](/en/get-started/agent-frameworks/vercelai.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md b/public/_markdown/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md deleted file mode 100644 index fc5159778..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md +++ /dev/null @@ -1,479 +0,0 @@ ---- -title: "Connect Arcade to LLM (Python)" -description: "Learn how to connect Arcade to your LLM in Python" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -Setup Arcade with your LLM (Python) - -# 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 “.” The harness orchestrates the back-and-forth between the user, the model, and the . In this guide, you’ll build one so you can wire Arcade into your LLM-powered app. - -## Outcomes - -Integrate Arcade’s \-calling capabilities into an application that uses an LLM in Python. - -### You will Learn - -- Setup an agentic loop -- Add Arcade to your agentic loop -- Implement a multi-turn conversation loop - -### Prerequisites - -- An [Arcade](https://app.arcade.dev/register) - -- An [Arcade API key](/get-started/setup/api-keys.md) - -- 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/) - - -### Create a new project and install the dependencies - -In your terminal, run the following command to create a new `uv` - -```bash -mkdir arcade-llm-example -cd arcade-llm-example -uv init -``` - -Create a new virtual environment and activate it: - -```bash -uv venv -``` - -### Bash/Zsh (macOS/Linux) - -```bash -source .venv/bin/activate -``` - -### PowerShell (Windows) - -```powershell -. ".venv\Scripts\Activate.ps1" -``` - -Install the dependencies: - -```powershell -uv add arcadepy openai python-dotenv -``` - -Your directory should now look like this: - -```powershell -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 key, as well as your OpenAI : - -TEXT - -``` -# .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.md). - -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: - -```json -# 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 can expose a broad catalog of to the LLM, it’s best to limit that set to what’s relevant for the task to keep the model efficient. - -```python -# 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 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 -# 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 . - -### 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 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 -# 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 execution is passed back to the LLM in every case. Depending on your needs, you may want to handle tool orchestration within the 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 . The core pieces of this loop are: - -1. Initialize the conversation history with the system prompt -2. Get the input and add it to the conversation history -3. Invoke the LLM with the conversation history, tools, and choice -4. Repeat from step 2 until the decides to stop the conversation - -```python -# 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 to effectively complete the following prompts: - -- “Please send a message to the #general channel on Slack greeting everyone with a haiku about .” -- “Please write a poem about multi- 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.md) - or [MCP client](/get-started/mcp-clients.md) - . -- Learn more about how to [build your own MCP Servers](/guides/create-tools/tool-basics/build-mcp-server.md) - . - -## Example code - -### **main.py** (full file) - -```json -# 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() -``` - -Last updated on February 10, 2026 - -[Overview](/en/get-started/agent-frameworks.md) -[Setup Arcade tools with CrewAI](/en/get-started/agent-frameworks/crewai/use-arcade-tools.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/tanstack-ai.md b/public/_markdown/en/get-started/agent-frameworks/tanstack-ai.md deleted file mode 100644 index cf26bb30f..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/tanstack-ai.md +++ /dev/null @@ -1,1080 +0,0 @@ ---- -title: "Build an AI Chatbot with Arcade and TanStack AI" -description: "Create a browser-based chatbot that uses Arcade tools to access Gmail and Slack" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -TanStack AI - -# Build an AI Chatbot with Arcade and TanStack AI - -[TanStack AI](https://tanstack.com/ai/latest/docs)  is a type-safe, provider-agnostic SDK for building AI applications in JavaScript and TypeScript. It provides streaming responses, calling, and framework-agnostic primitives for React, Solid, and vanilla JavaScript. Provider adapters let you switch between OpenAI, Anthropic, Google Gemini, and Ollama without rewriting your code. - -In this guide, you’ll build a browser-based chatbot using [TanStack Start](https://tanstack.com/start)  that uses Arcade’s Gmail and Slack . Your can read emails, send messages, and interact with Slack through a conversational interface with built-in authentication. - -## Outcomes - -Build a TanStack Start chatbot that integrates Arcade with TanStack AI - -### You will Learn - -- How to retrieve Arcade and convert them to TanStack AI format -- How to build a streaming chatbot with server functions -- How to handle Arcade’s authorization flow in a web app -- How to combine tools from different Arcade servers - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Node.js 18+](https://nodejs.org/) -   -- An [OpenAI API key](https://platform.openai.com/api-keys) -   - -## TanStack concepts - -Before diving into the code, here are the key TanStack concepts you’ll use: - -- [chat](https://tanstack.com/ai/latest/docs/reference/functions/chat) -   The server-side function that streams AI responses with support for calling. Returns an `AsyncIterable` stream for real-time responses. -- [useChat](https://tanstack.com/ai/latest/docs/api/ai-react) -   A React hook that manages chat state, handles streaming via Server-Sent Events, and renders results automatically. -- [Server routes](https://tanstack.com/start/latest/docs/framework/react/guide/server-routes) -   TanStack Start’s server routes run exclusively on the server using the `server.handlers` property, keeping secure while handling streaming responses. -- [toolDefinition](https://tanstack.com/ai/latest/docs/guides/tools) -   Defines with type-safe parameters. Tools can run on server, client, or both. - -## Build the chatbot - -### Create a new TanStack Start project - -### pnpm - -```bash -pnpm create @tanstack/start@latest arcade-chatbot -cd arcade-chatbot -``` - -### npm - -```bash -npx @tanstack/create-start@latest arcade-chatbot -cd arcade-chatbot -``` - -### yarn - -```bash -yarn create @tanstack/start arcade-chatbot -cd arcade-chatbot -``` - -### bun - -```bash -bunx @tanstack/create-start@latest arcade-chatbot -cd arcade-chatbot -``` - -Follow the prompts to configure your . Select the defaults or customize as needed. - -Install the required dependencies: - -### pnpm - -```bash -pnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @arcadeai/arcadejs react-markdown -pnpm add -D @tailwindcss/typography -``` - -### npm - -```bash -npm install @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @arcadeai/arcadejs react-markdown -npm install -D @tailwindcss/typography -``` - -### yarn - -```bash -yarn add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @arcadeai/arcadejs react-markdown -yarn add -D @tailwindcss/typography -``` - -### bun - -```bash -bun add @tanstack/ai @tanstack/ai-react @tanstack/ai-openai @arcadeai/arcadejs react-markdown -bun add -D @tailwindcss/typography -``` - -Then add the typography plugin to your **src/styles.css**: - -src/styles.css - -```css -@import "tailwindcss"; -@plugin "@tailwindcss/typography"; -``` - -### Set up environment variables - -Create a **.env** file with your : - -```bash -# .env -ARCADE_API_KEY={arcade_api_key} -ARCADE_USER_ID={arcade_user_id} -OPENAI_API_KEY=your_openai_api_key -``` - -The `ARCADE_USER_ID` is your app’s internal identifier for the (often the email you signed up with, a UUID, etc.). Arcade uses this to track authorizations per user. - -### Create the chat API route - -Create the directory and file **src/routes/api/chat.ts**. This server route handles chat requests and streams AI responses: - -src/routes/api/chat.ts - -```json -import { createFileRoute } from "@tanstack/react-router"; -import { toServerSentEventsResponse, chat, toolDefinition } from "@tanstack/ai"; -import type { JSONSchema } from "@tanstack/ai"; -import { openaiText } from "@tanstack/ai-openai"; -import { Arcade } from "@arcadeai/arcadejs"; - -const config = { - // Get all tools from these MCP servers - mcpServers: ["Slack"], - // Add specific individual tools - individualTools: [ - "Gmail.ListEmails", - "Gmail.SendEmail", - "Gmail.WhoAmI", - ], - // Maximum tools to fetch per MCP server - toolLimit: 30, - // System prompt defining the assistant's behavior - systemPrompt: `You are a helpful assistant that can access Gmail and Slack. -Always use the available tools to fulfill user requests. Do not tell users to authorize manually - just call the tool and the system will handle authorization if needed. - -For Gmail: -- To find sent emails, use the query parameter with "in:sent" -- To find received emails, use "in:inbox" or no query - -For Slack: -- Use Slack.ListConversations to see channels and DMs -- Use Slack.ListMessages to read messages from a channel or DM -- Use Slack.SendDmToUser to send a direct message -- Use Slack.SendMessageToChannel to post in a channel - -After completing any action, always confirm what you did with specific details. - -IMPORTANT: When calling tools, if an argument is optional, do not set it. Never pass null for optional parameters.`, -}; - -// Empty JSON Schema for tools with no parameters -const emptySchema: JSONSchema = { type: "object", properties: {} }; - -// Strip null values from tool inputs -function stripNullValues(obj: Record): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(obj)) { - if (value !== null && value !== undefined) { - result[key] = value; - } - } - return result; -} - -// Maximum characters for any string field in tool output -const MAX_STRING_CHARS = 300; - -/** - * Recursively truncates all large strings in objects/arrays. - * This prevents token overflow when tool results pass back to the LLM. - */ -function truncateDeep(obj: unknown): unknown { - if (obj === null || obj === undefined) return obj; - - if (typeof obj === "string") { - if (obj.length > MAX_STRING_CHARS) { - return obj.slice(0, MAX_STRING_CHARS) + "..."; - } - return obj; - } - - if (Array.isArray(obj)) { - return obj.map(truncateDeep); - } - - if (typeof obj === "object") { - const result: Record = {}; - for (const [key, value] of Object.entries(obj as Record)) { - result[key] = truncateDeep(value); - } - return result; - } - - return obj; -} - -// Maximum number of recent messages to keep in context -const MAX_MESSAGES = 10; - -/** - * Truncates message content and limits message history to prevent context overflow. - */ -function prepareMessages(messages: unknown[]): unknown[] { - const recentMessages = messages.slice(-MAX_MESSAGES); - return recentMessages.map((msg) => truncateDeep(msg)); -} - -async function getArcadeTools(userId: string) { - const arcade = new Arcade(); - - // Fetch tools from MCP servers - const mcpServerTools = await Promise.all( - config.mcpServers.map(async (serverName) => { - const response = await arcade.tools.list({ - toolkit: serverName, - limit: config.toolLimit, - }); - return response.items; - }) - ); - - // Fetch individual tools - const individualToolDefs = await Promise.all( - config.individualTools.map((toolName) => arcade.tools.get(toolName)) - ); - - // Combine and deduplicate - const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; - const uniqueTools = Array.from( - new Map(allTools.map((tool) => [tool.qualified_name, tool])).values() - ); - - // Convert to TanStack AI tool format - return uniqueTools.map((tool) => { - // Use Arcade's JSON Schema directly - TanStack AI accepts it natively - const params = tool.input?.parameters as JSONSchema | undefined; - const hasValidSchema = params && params.type && params.type !== "None"; - const inputSchema = hasValidSchema ? params : emptySchema; - - return toolDefinition({ - name: tool.qualified_name.replace(".", "_"), - description: tool.description || "", - inputSchema, - }).server(async (input: unknown) => { - const typedInput = input as Record; - const cleanedInput = stripNullValues(typedInput); - - try { - // Check authorization status first - const authResponse = await arcade.tools.authorize({ - tool_name: tool.qualified_name, - user_id: userId, - }); - - if (authResponse.status !== "completed") { - return { - authorization_required: true, - authorization_response: { - url: authResponse.url, - }, - tool_name: tool.qualified_name, - }; - } - - // Execute the tool - const result = await arcade.tools.execute({ - tool_name: tool.qualified_name, - user_id: userId, - input: cleanedInput, - }); - - // Truncate large strings to prevent context window overflow - const output = result.output?.value ?? result; - return truncateDeep(output); - } catch (error) { - console.error(`Tool execution error for ${tool.qualified_name}:`, error); - throw error; - } - }); - }); -} - -// Server route with POST handler for chat streaming -export const Route = createFileRoute("/api/chat")({ - server: { - handlers: { - POST: async ({ request }) => { - const body = await request.json(); - const userId = process.env.ARCADE_USER_ID || "default-user"; - const tools = await getArcadeTools(userId); - - // Prepare messages: limit history and truncate large content - const preparedMessages = prepareMessages(body.messages || []); - - const stream = chat({ - adapter: openaiText("gpt-4o"), - systemPrompts: [config.systemPrompt], - messages: preparedMessages, - tools, - }); - - return toServerSentEventsResponse(stream); - }, - }, - }, -}); -``` - -You can mix servers (which give you all their ) with individual tools. Browse the [complete MCP server catalog](/resources/integrations.md) to see what’s available. - -**Handling large outputs:** Tools like `Gmail.ListEmails` can return 200KB+ of email content. When the passes this data back to the LLM in the agentic loop, it may exceed token limits. The code above includes `truncateDeep` to limit all strings to 300 characters and `prepareMessages` to keep only the last 10 messages. - -### Create the auth status server function - -Create **src/functions/auth.ts** to check authorization status: - -src/functions/auth.ts - -```typescript -import { createServerFn } from "@tanstack/react-start"; -import { Arcade } from "@arcadeai/arcadejs"; - -export const checkAuthStatus = createServerFn({ method: "POST" }) - .inputValidator((data: { toolName: string }) => data) - .handler(async ({ data }) => { - if (!data.toolName) { - throw new Error("toolName required"); - } - - const arcade = new Arcade(); - const userId = process.env.ARCADE_USER_ID || "default-user"; - - const authResponse = await arcade.tools.authorize({ - tool_name: data.toolName, - user_id: userId, - }); - - return { status: authResponse.status }; - }); -``` - -This server function allows the frontend to poll for authorization completion, creating a seamless experience where the chatbot automatically retries after the authorizes. - -### Build the chat route - -Update **src/routes/index.tsx** with the chat interface. TanStack AI’s `useChat` hook manages messages, streaming, and loading states: - -TSX - -src/routes/index.tsx - -``` -import { createFileRoute } from "@tanstack/react-router"; -import { useChat, fetchServerSentEvents } from "@tanstack/ai-react"; -import { useState, useRef, useEffect } from "react"; -import ReactMarkdown from "react-markdown"; -import { checkAuthStatus } from "../functions/auth"; - -// Component that handles Arcade's OAuth authorization flow -function AuthPendingUI({ - authUrl, - toolName, - onAuthComplete, -}: { - authUrl: string; - toolName: string; - onAuthComplete: () => void; -}) { - const [status, setStatus] = useState<"initial" | "waiting" | "completed">( - "initial" - ); - const pollingRef = useRef(null); - const hasCompletedRef = useRef(false); - const onAuthCompleteRef = useRef(onAuthComplete); - - useEffect(() => { - onAuthCompleteRef.current = onAuthComplete; - }, [onAuthComplete]); - - // Poll for auth completion after user clicks authorize - useEffect(() => { - if (status !== "waiting" || !toolName || hasCompletedRef.current) return; - - const pollStatus = async () => { - try { - const result = await checkAuthStatus({ data: { toolName } }); - - if (result.status === "completed" && !hasCompletedRef.current) { - hasCompletedRef.current = true; - if (pollingRef.current) clearInterval(pollingRef.current); - setStatus("completed"); - setTimeout(() => onAuthCompleteRef.current(), 1500); - } - } catch (error) { - console.error("Polling error:", error); - } - }; - - pollingRef.current = setInterval(pollStatus, 2000); - return () => { - if (pollingRef.current) clearInterval(pollingRef.current); - }; - }, [status, toolName]); - - const displayName = toolName.split("_")[0] || toolName; - - const handleAuthClick = () => { - if (!authUrl) return; - window.open(authUrl, "_blank"); - setStatus("waiting"); - }; - - return ( -
- {status === "completed" ? ( -

- {displayName} authorized successfully -

- ) : !authUrl ? ( -

Authorization URL not available

- ) : ( -
- Authorize access to {displayName}? - -
- )} -
- ); -} - -function Chat() { - const [input, setInput] = useState(""); - const inputRef = useRef(null); - - const { messages, sendMessage, reload, isLoading } = useChat({ - connection: fetchServerSentEvents("/api/chat"), - }); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (input.trim() && !isLoading) { - sendMessage(input); - setInput(""); - } - }; - - // Refocus input after response completes - useEffect(() => { - if (!isLoading && inputRef.current) { - inputRef.current.focus(); - } - }, [isLoading]); - - return ( -
-

Arcade + TanStack AI Chat

- - {/* Messages */} -
- {messages - .filter((message) => { - // Filter out empty assistant messages (tool calls without text) - if (message.role === "assistant") { - const textParts = (message.parts || []).filter( - (p: { type: string }) => p.type === "text" - ); - const textContent = textParts - .map((p: any) => p.content || "") - .join("") - .trim(); - const toolResultParts = (message.parts || []).filter( - (p: { type: string }) => p.type === "tool-result" - ); - const authRequired = toolResultParts.find( - (result: any) => result.output?.authorization_required - ); - return textContent.length > 0 || authRequired; - } - return true; - }) - .map((message) => { - const textParts = (message.parts || []).filter( - (p: { type: string }) => p.type === "text" - ); - const toolResultParts = (message.parts || []).filter( - (p: { type: string }) => p.type === "tool-result" - ); - const textContent = textParts - .map((p: { content?: string }) => p.content || "") - .join(""); - const authRequired = toolResultParts.find( - (result: { output?: { authorization_required?: boolean } }) => - result.output?.authorization_required - ); - - return ( -
-
- {message.role === "assistant" ? "Assistant" : "You"} -
- - {authRequired ? ( - reload()} - /> - ) : ( -
- {textContent} -
- )} -
- ); - })} - - {isLoading && - (() => { - // Only show loading if there's no assistant message with content - const lastMessage = messages[messages.length - 1]; - const hasAssistantContent = - lastMessage?.role === "assistant" && - (lastMessage.parts || []) - .filter((p: { type: string }) => p.type === "text") - .some((p: any) => (p.content || "").trim().length > 0); - - if (hasAssistantContent) return null; - - return ( -
-
- Assistant -
-
Thinking...
-
- ); - })()} -
- - {/* Input form */} -
- setInput(e.target.value)} - placeholder="Ask about your emails or Slack..." - disabled={isLoading} - className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - -
-
- ); -} - -export const Route = createFileRoute("/")({ - component: Chat, -}); -``` - -The `AuthPendingUI` component polls for OAuth completion using the `checkAuthStatus` server function and calls `onAuthComplete` when the user finishes authorizing, triggering a reload to retry the call. - -### Run the chatbot - -### pnpm - -```bash -pnpm dev -``` - -### npm - -```bash -npm run dev -``` - -### yarn - -```bash -yarn dev -``` - -### bun - -```bash -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000)  and try prompts like: - -- “Summarize my last 3 emails” -- “Send a Slack DM to myself saying hello” -- “Email me a summary of this slack channel’s activity since yesterday…” - -On first use, you’ll see an authorization button. Click it to connect your Gmail or Slack (Arcade remembers this for future requests). - -## Key takeaways - -- **Full TanStack stack**: TanStack Start + TanStack Router + TanStack AI work together seamlessly with server routes and file-based routing. -- **Server routes keep secrets safe**: The `server.handlers` property ensures your never reach the client while handling streaming responses. -- **Authorization is automatic**: Check `authorization_required` in results and display the authorization UI. Poll for completion to retry automatically. -- **Truncate large outputs**: Tools like Gmail can return 200KB+ of data. Wrap execution with truncation to prevent token overflow in the agentic loop. -- **Provider flexibility**: Switch between OpenAI, Anthropic, Gemini, or Ollama by changing the adapter. No code rewrites needed. - -## Next steps - -1. **Add more **: Browse the [MCP server catalog](/resources/integrations.md) - and add tools for GitHub, Notion, Linear, and more. -2. **Try different providers**: Swap `@tanstack/ai-openai` for `@tanstack/ai-anthropic` or `@tanstack/ai-gemini` to use different AI models. -3. **Add authentication**: In production, get `userId` from your auth system instead of environment variables. See [Security](/guides/security.md) - for best practices. -4. **Deploy your chatbot**: TanStack Start supports deployment to Vercel, Netlify, Cloudflare, and Node.js servers. - -## Complete code - -### **src/routes/api/chat.ts** (full file) - -src/routes/api/chat.ts - -```json -import { createFileRoute } from "@tanstack/react-router"; -import { toServerSentEventsResponse, chat, toolDefinition } from "@tanstack/ai"; -import type { JSONSchema } from "@tanstack/ai"; -import { openaiText } from "@tanstack/ai-openai"; -import { Arcade } from "@arcadeai/arcadejs"; - -const config = { - mcpServers: ["Slack"], - individualTools: [ - "Gmail.ListEmails", - "Gmail.SendEmail", - "Gmail.WhoAmI", - ], - toolLimit: 30, - systemPrompt: `You are a helpful assistant that can access Gmail and Slack. -Always use the available tools to fulfill user requests. Do not tell users to authorize manually - just call the tool and the system will handle authorization if needed. - -For Gmail: -- To find sent emails, use the query parameter with "in:sent" -- To find received emails, use "in:inbox" or no query - -For Slack: -- Use Slack.ListConversations to see channels and DMs -- Use Slack.ListMessages to read messages from a channel or DM -- Use Slack.SendDmToUser to send a direct message -- Use Slack.SendMessageToChannel to post in a channel - -After completing any action, always confirm what you did with specific details. - -IMPORTANT: When calling tools, if an argument is optional, do not set it. Never pass null for optional parameters.`, -}; - -// Empty JSON Schema for tools with no parameters -const emptySchema: JSONSchema = { type: "object", properties: {} }; - -function stripNullValues(obj: Record): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(obj)) { - if (value !== null && value !== undefined) { - result[key] = value; - } - } - return result; -} - -const MAX_STRING_CHARS = 300; - -function truncateDeep(obj: unknown): unknown { - if (obj === null || obj === undefined) return obj; - - if (typeof obj === "string") { - if (obj.length > MAX_STRING_CHARS) { - return obj.slice(0, MAX_STRING_CHARS) + "..."; - } - return obj; - } - - if (Array.isArray(obj)) { - return obj.map(truncateDeep); - } - - if (typeof obj === "object") { - const result: Record = {}; - for (const [key, value] of Object.entries(obj as Record)) { - result[key] = truncateDeep(value); - } - return result; - } - - return obj; -} - -const MAX_MESSAGES = 10; - -function prepareMessages(messages: unknown[]): unknown[] { - const recentMessages = messages.slice(-MAX_MESSAGES); - return recentMessages.map((msg) => truncateDeep(msg)); -} - -async function getArcadeTools(userId: string) { - const arcade = new Arcade(); - - const mcpServerTools = await Promise.all( - config.mcpServers.map(async (serverName) => { - const response = await arcade.tools.list({ - toolkit: serverName, - limit: config.toolLimit, - }); - return response.items; - }) - ); - - const individualToolDefs = await Promise.all( - config.individualTools.map((toolName) => arcade.tools.get(toolName)) - ); - - const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; - const uniqueTools = Array.from( - new Map(allTools.map((tool) => [tool.qualified_name, tool])).values() - ); - - return uniqueTools.map((tool) => { - // Use Arcade's JSON Schema directly - TanStack AI accepts it natively - const params = tool.input?.parameters as JSONSchema | undefined; - const hasValidSchema = params && params.type && params.type !== "None"; - const inputSchema = hasValidSchema ? params : emptySchema; - - return toolDefinition({ - name: tool.qualified_name.replace(".", "_"), - description: tool.description || "", - inputSchema, - }).server(async (input: unknown) => { - const typedInput = input as Record; - const cleanedInput = stripNullValues(typedInput); - - try { - const authResponse = await arcade.tools.authorize({ - tool_name: tool.qualified_name, - user_id: userId, - }); - - if (authResponse.status !== "completed") { - return { - authorization_required: true, - authorization_response: { - url: authResponse.url, - }, - tool_name: tool.qualified_name, - }; - } - - const result = await arcade.tools.execute({ - tool_name: tool.qualified_name, - user_id: userId, - input: cleanedInput, - }); - - const output = result.output?.value ?? result; - return truncateDeep(output); - } catch (error) { - console.error(`Tool execution error for ${tool.qualified_name}:`, error); - throw error; - } - }); - }); -} - -export const Route = createFileRoute("/api/chat")({ - server: { - handlers: { - POST: async ({ request }) => { - const body = await request.json(); - const userId = process.env.ARCADE_USER_ID || "default-user"; - const tools = await getArcadeTools(userId); - - const preparedMessages = prepareMessages(body.messages || []); - - const stream = chat({ - adapter: openaiText("gpt-4o"), - systemPrompts: [config.systemPrompt], - messages: preparedMessages, - tools, - }); - - return toServerSentEventsResponse(stream); - }, - }, - }, -}); -``` - -### **src/functions/auth.ts** (full file) - -src/functions/auth.ts - -```typescript -import { createServerFn } from "@tanstack/react-start"; -import { Arcade } from "@arcadeai/arcadejs"; - -export const checkAuthStatus = createServerFn({ method: "POST" }) - .inputValidator((data: { toolName: string }) => data) - .handler(async ({ data }) => { - if (!data.toolName) { - throw new Error("toolName required"); - } - - const arcade = new Arcade(); - const userId = process.env.ARCADE_USER_ID || "default-user"; - - const authResponse = await arcade.tools.authorize({ - tool_name: data.toolName, - user_id: userId, - }); - - return { status: authResponse.status }; - }); -``` - -### **src/routes/index.tsx** (full file) - -TSX - -src/routes/index.tsx - -``` -import { createFileRoute } from "@tanstack/react-router"; -import { useChat, fetchServerSentEvents } from "@tanstack/ai-react"; -import { useState, useRef, useEffect } from "react"; -import ReactMarkdown from "react-markdown"; -import { checkAuthStatus } from "../functions/auth"; - -function AuthPendingUI({ - authUrl, - toolName, - onAuthComplete, -}: { - authUrl: string; - toolName: string; - onAuthComplete: () => void; -}) { - const [status, setStatus] = useState<"initial" | "waiting" | "completed">( - "initial" - ); - const pollingRef = useRef(null); - const hasCompletedRef = useRef(false); - const onAuthCompleteRef = useRef(onAuthComplete); - - useEffect(() => { - onAuthCompleteRef.current = onAuthComplete; - }, [onAuthComplete]); - - useEffect(() => { - if (status !== "waiting" || !toolName || hasCompletedRef.current) return; - - const pollStatus = async () => { - try { - const result = await checkAuthStatus({ data: { toolName } }); - - if (result.status === "completed" && !hasCompletedRef.current) { - hasCompletedRef.current = true; - if (pollingRef.current) clearInterval(pollingRef.current); - setStatus("completed"); - setTimeout(() => onAuthCompleteRef.current(), 1500); - } - } catch (error) { - console.error("Polling error:", error); - } - }; - - pollingRef.current = setInterval(pollStatus, 2000); - return () => { - if (pollingRef.current) clearInterval(pollingRef.current); - }; - }, [status, toolName]); - - const displayName = toolName.split("_")[0] || toolName; - - const handleAuthClick = () => { - if (!authUrl) return; - window.open(authUrl, "_blank"); - setStatus("waiting"); - }; - - return ( -
- {status === "completed" ? ( -

- {displayName} authorized successfully -

- ) : !authUrl ? ( -

Authorization URL not available

- ) : ( -
- Authorize access to {displayName}? - -
- )} -
- ); -} - -function Chat() { - const [input, setInput] = useState(""); - const inputRef = useRef(null); - - const { messages, sendMessage, reload, isLoading } = useChat({ - connection: fetchServerSentEvents("/api/chat"), - }); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (input.trim() && !isLoading) { - sendMessage(input); - setInput(""); - } - }; - - useEffect(() => { - if (!isLoading && inputRef.current) { - inputRef.current.focus(); - } - }, [isLoading]); - - return ( -
-

Arcade + TanStack AI Chat

- -
- {messages.map((message) => { - // Extract text content and tool results from parts - const textParts = (message.parts || []).filter( - (p: { type: string }) => p.type === "text" - ); - const toolResultParts = (message.parts || []).filter( - (p: { type: string }) => p.type === "tool-result" - ); - const textContent = textParts - .map((p: { content?: string }) => p.content || "") - .join(""); - const authRequired = toolResultParts.find( - (result: { output?: { authorization_required?: boolean } }) => - result.output?.authorization_required - ); - - return ( -
-
- {message.role === "assistant" ? "Assistant" : "You"} -
- - {authRequired ? ( - reload()} - /> - ) : ( -
{textContent}
- )} -
- ); - })} - - {isLoading && ( -
-
- Assistant -
-
Thinking...
-
- )} -
- -
- setInput(e.target.value)} - placeholder="Ask about your emails or Slack..." - disabled={isLoading} - className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - -
-
- ); -} - -export const Route = createFileRoute("/")({ - component: Chat, -}); -``` - -Last updated on February 10, 2026 - -[Setup (TypeScript)](/en/get-started/agent-frameworks/openai-agents/setup-typescript.md) -[Vercel AI SDK](/en/get-started/agent-frameworks/vercelai.md) diff --git a/public/_markdown/en/get-started/agent-frameworks/vercelai.md b/public/_markdown/en/get-started/agent-frameworks/vercelai.md deleted file mode 100644 index 2006b56f7..000000000 --- a/public/_markdown/en/get-started/agent-frameworks/vercelai.md +++ /dev/null @@ -1,949 +0,0 @@ ---- -title: "Build an AI Chatbot with Arcade and Vercel AI SDK" -description: "Create a browser-based chatbot that uses Arcade tools to access Gmail and Slack" ---- -[Agent Frameworks](/en/get-started/agent-frameworks.md) -Vercel AI SDK - -# Build an AI Chatbot with Arcade and Vercel AI SDK - -The [Vercel AI SDK](https://sdk.vercel.ai/)  is a TypeScript toolkit for building AI-powered applications. It provides streaming responses, framework-agnostic support for React, Next.js, Vue, and more, plus AI provider switching. This guide uses **Vercel AI SDK v6**. - -In this guide, you’ll build a browser-based chatbot that uses Arcade’s Gmail and Slack . Your can read emails, send messages, and interact with Slack through a conversational interface with built-in authentication. - -## Outcomes - -Build a Next.js chatbot that integrates Arcade with the Vercel AI SDK - -### You will Learn - -- How to retrieve Arcade and convert them to Vercel AI SDK format -- How to build a streaming chatbot with calling -- How to handle Arcade’s authorization flow in a web app -- How to combine tools from different Arcade servers - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Node.js 18+](https://nodejs.org/) -   -- An [OpenAI API key](https://platform.openai.com/api-keys) -   - -## Vercel AI SDK concepts - -Before diving into the code, here are the key Vercel AI SDK concepts you’ll use: - -- [streamText](https://sdk.vercel.ai/docs/reference/ai-sdk-core/stream-text) -   Streams AI responses with support for calling. Perfect for chat interfaces where you want responses to appear progressively. -- [useChat](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat) -   A React hook that manages chat state, handles streaming, and renders results. It connects your frontend to your API route automatically. -- [Tools](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling) -   Functions the AI can call to perform actions. Vercel AI SDK uses [Zod](https://zod.dev) -   schemas for type-safe definitions. Arcade’s `toZodToolSet` handles this conversion for you. - -## Build the chatbot - -### Create a new Next.js project - -```bash -npx create-next-app@latest arcade-chatbot -cd arcade-chatbot -``` - -Use the default settings for the . - -Install the required dependencies and [AI Elements](https://ai-sdk.dev/elements)  (pre-built React components for chat interfaces): - -### npm - -```bash -npm install ai @ai-sdk/openai @ai-sdk/react @arcadeai/arcadejs zod -npx ai-elements@latest -``` - -### pnpm - -```bash -pnpm add ai @ai-sdk/openai @ai-sdk/react @arcadeai/arcadejs zod -pnpm dlx ai-elements@latest -``` - -### yarn - -```bash -yarn add ai @ai-sdk/openai @ai-sdk/react @arcadeai/arcadejs zod -yarn dlx ai-elements@latest -``` - -### bun - -```bash -bun add ai @ai-sdk/openai @ai-sdk/react @arcadeai/arcadejs zod -bunx ai-elements@latest -``` - -Follow the AI Elements prompts to complete the installation. - -AI Elements installs components directly into your `components/ui/` directory, giving you full control to customize them later. - -### Set up environment variables - -Create a **.env.local** file with your : - -.env.local - -```bash -ARCADE_API_KEY={arcade_api_key} -ARCADE_USER_ID={arcade_user_id} -OPENAI_API_KEY=your_openai_api_key -``` - -The `ARCADE_USER_ID` is your app’s internal identifier for the (often the email you signed up with, a UUID, etc.). Arcade uses this to track authorizations per user. - -### Create the API route - -Create **app/api/chat/route.ts**. Start with the imports: - -app/api/chat/route.ts - -```typescript -import { openai } from "@ai-sdk/openai"; -import { streamText, convertToModelMessages, stepCountIs } from "ai"; -import { Arcade } from "@arcadeai/arcadejs"; -import { - toZodToolSet, - executeOrAuthorizeZodTool, -} from "@arcadeai/arcadejs/lib/index"; -``` - -**What these imports do:** - -- `streamText`: Streams AI responses with calling support -- `convertToModelMessages`: Converts chat messages to the format the AI model expects -- `stepCountIs`: Controls how many \-calling steps the AI can take -- `Arcade`: The for fetching and executing -- `toZodToolSet`: Converts Arcade to [Zod](https://zod.dev) -   schemas (required by Vercel AI SDK) -- `executeOrAuthorizeZodTool`: Handles execution and returns authorization URLs when needed - -### Configure which tools to use - -Define which servers and individual your chatbot can access: - -app/api/chat/route.ts - -```typescript -const config = { - // Get all tools from these MCP servers - mcpServers: ["Slack"], - // Add specific individual tools - individualTools: ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"], - // Maximum tools to fetch per MCP server - toolLimit: 30, - // System prompt defining the assistant's behavior - systemPrompt: `You are a helpful assistant that can access Gmail and Slack. -Always use the available tools to fulfill user requests. Do not tell users to authorize manually - just call the tool and the system will handle authorization if needed. - -For Gmail: -- To find sent emails, use the query parameter with "in:sent" -- To find received emails, use "in:inbox" or no query - -After completing any action (sending emails, Slack messages, etc.), always confirm what you did with specific details. - -IMPORTANT: When calling tools, if an argument is optional, do not set it. Never pass null for optional parameters.`, -}; -``` - -You can mix servers (which give you all their ) with individual tools. Browse the [complete MCP server catalog](/resources/integrations.md) to see what’s available. - -### Write the tool fetching logic - -This function retrieves from Arcade and converts them to Vercel AI SDK format. The `toVercelTools` adapter converts Arcade’s tool format to match what the Vercel AI SDK expects, and `stripNullValues` prevents issues with optional parameters: - -app/api/chat/route.ts - -```typescript -// Strip null and undefined values from tool inputs -// Some LLMs send null for optional params, which can cause tool failures -function stripNullValues( - obj: Record -): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(obj)) { - if (value !== null && value !== undefined) { - result[key] = value; - } - } - return result; -} - -// Adapter to convert Arcade tools to Vercel AI SDK v6 format -function toVercelTools(arcadeTools: Record): Record { - const vercelTools: Record = {}; - - for (const [name, tool] of Object.entries(arcadeTools)) { - const t = tool as { description: string; parameters: unknown; execute: Function; }; - vercelTools[name] = { - description: t.description, - inputSchema: t.parameters, // AI SDK v6 uses inputSchema, not parameters - execute: async (input: Record) => { - const cleanedInput = stripNullValues(input); - return t.execute(cleanedInput); - }, - }; - } - - return vercelTools; -} - -async function getArcadeTools(userId: string) { - const arcade = new Arcade(); - - // Fetch tools from MCP servers - const mcpServerTools = await Promise.all( - config.mcpServers.map(async (serverName) => { - const response = await arcade.tools.list({ - toolkit: serverName, - limit: config.toolLimit, - }); - return response.items; - }), - ); - - // Fetch individual tools - const individualToolDefs = await Promise.all( - config.individualTools.map((toolName) => arcade.tools.get(toolName)) - ); - - // Combine and deduplicate - const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; - const uniqueTools = Array.from( - new Map(allTools.map((tool) => [tool.qualified_name, tool])).values() - ); - - // Convert to Arcade's Zod format, then adapt for Vercel AI SDK - const arcadeTools = toZodToolSet({ - tools: uniqueTools, - client: arcade, - userId, - executeFactory: executeOrAuthorizeZodTool, - }); - - return toVercelTools(arcadeTools); -} -``` - -The `executeOrAuthorizeZodTool` factory is key here. It automatically handles authorization. When a needs the to authorize access (like connecting their Gmail), it returns an object with `authorization_required: true` and the URL they need to visit. - -### Create the POST handler - -Handle incoming chat requests by streaming AI responses with : - -app/api/chat/route.ts - -```json -export async function POST(req: Request) { - try { - const { messages } = await req.json(); - const userId = process.env.ARCADE_USER_ID || "default-user"; - - const tools = await getArcadeTools(userId); - - const result = streamText({ - model: openai("gpt-4o-mini"), - system: config.systemPrompt, - messages: await convertToModelMessages(messages), - tools, - stopWhen: stepCountIs(5), - }); - - return result.toUIMessageStreamResponse(); - } catch (error) { - console.error("Chat API error:", error); - return Response.json( - { error: "Failed to process chat request" }, - { status: 500 } - ); - } -} -``` - -The `stopWhen: stepCountIs(5)` allows the AI to make multiple calls in a single response (useful when it needs to chain actions together). - -### Create the auth status endpoint - -To detect when a completes OAuth authorization, create **app/api/auth/status/route.ts**: - -app/api/auth/status/route.ts - -```json -import { Arcade } from "@arcadeai/arcadejs"; - -export async function POST(req: Request) { - const { toolName } = await req.json(); - - if (!toolName) { - return Response.json({ error: "toolName required" }, { status: 400 }); - } - - const arcade = new Arcade(); - const userId = process.env.ARCADE_USER_ID || "default-user"; - - try { - const authResponse = await arcade.tools.authorize({ - tool_name: toolName, - user_id: userId, - }); - return Response.json({ status: authResponse.status }); - } catch (error) { - console.error("Auth status check error:", error); - return Response.json( - { status: "error", error: String(error) }, - { status: 500 } - ); - } -} -``` - -This endpoint allows the frontend to poll for authorization completion, creating a seamless experience where the chatbot automatically retries after the authorizes. - -### Build the chat interface - -AI Elements provides pre-built components for conversations, messages, and input. All you need to add is custom handling for Arcade’s OAuth flow. Replace the contents of **app/page.tsx** with the following code: - -TSX - -app/page.tsx - -```json -"use client"; - -import { useChat } from "@ai-sdk/react"; -import { useState, useRef, useEffect } from "react"; - -// AI Elements components for chat UI -import { - Conversation, - ConversationContent, -} from "@/components/ai-elements/conversation"; -import { - Message, - MessageContent, - MessageResponse, -} from "@/components/ai-elements/message"; -import { - PromptInput, - PromptInputTextarea, - PromptInputFooter, - PromptInputSubmit, -} from "@/components/ai-elements/prompt-input"; -import { Spinner } from "@/components/ui/spinner"; - -// Component that handles Arcade's OAuth authorization flow -function AuthPendingUI({ - authUrl, - toolName, - onAuthComplete, -}: { - authUrl: string; - toolName: string; - onAuthComplete: () => void; -}) { - const [status, setStatus] = useState<"initial" | "waiting" | "completed">("initial"); - const pollingRef = useRef(null); - const timeoutRef = useRef(null); - const hasCompletedRef = useRef(false); - const onAuthCompleteRef = useRef(onAuthComplete); - - // Keep callback ref updated - useEffect(() => { - onAuthCompleteRef.current = onAuthComplete; - }, [onAuthComplete]); - - // Poll /api/auth/status every 2 seconds after user clicks authorize - useEffect(() => { - if (status !== "waiting" || !toolName || hasCompletedRef.current) return; - - const pollStatus = async () => { - try { - const res = await fetch("/api/auth/status", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ toolName }), - }); - const data = await res.json(); - - if (data.status === "completed" && !hasCompletedRef.current) { - hasCompletedRef.current = true; - if (pollingRef.current) clearInterval(pollingRef.current); - setStatus("completed"); - timeoutRef.current = setTimeout(() => onAuthCompleteRef.current(), 1500); - } - } catch (error) { - console.error("Polling error:", error); - } - }; - - pollingRef.current = setInterval(pollStatus, 2000); - return () => { - if (pollingRef.current) clearInterval(pollingRef.current); - }; - }, [status, toolName]); - - const displayName = toolName.split("_")[0] || toolName; - - const handleAuthClick = () => { - if (!authUrl) return; // Don't open empty URLs - window.open(authUrl, "_blank"); - setStatus("waiting"); - }; - - return ( -
- {status === "completed" ? ( -

✓ {displayName} authorized

- ) : !authUrl ? ( -

Authorization URL not available

- ) : ( - <> - Give Arcade Chat access to {displayName}?{" "} - - - )} -
- ); -} -``` - -The `AuthPendingUI` component polls for OAuth completion and calls `onAuthComplete` when the finishes authorizing. - -#### Create the Chat component - -The `Conversation` component handles auto-scrolling, `Message` handles role-based styling, and [`MessageResponse` renders markdown automatically](https://ai-sdk.dev/elements/components/message#features). The implementation checks for Arcade’s `authorization_required` flag in results: - -TSX - -app/page.tsx - -``` -export default function Chat() { - const { messages, sendMessage, regenerate, status } = useChat(); - const isLoading = status === "submitted" || status === "streaming"; - const inputRef = useRef(null); - - // Refocus input after response completes - useEffect(() => { - if (!isLoading && inputRef.current) { - inputRef.current.focus(); - } - }, [isLoading]); - - return ( -
- {/* Conversation handles auto-scrolling */} - - - {messages.map((message) => { - // Check if any tool part requires authorization - const authPart = message.parts?.find((part) => { - if (part.type.startsWith("tool-")) { - const toolPart = part as { state?: string; output?: unknown }; - if (toolPart.state === "output-available") { - const result = toolPart.output as Record; - return result?.authorization_required; - } - } - return false; - }); - - // Get text content from message parts - const textContent = message.parts - ?.filter((part) => part.type === "text") - .map((part) => part.text) - .join(""); - - // Skip empty messages without auth prompts - if (!textContent && !authPart && !(message.role === "assistant" && isLoading)) { - return null; - } - - return ( - - - {/* Show loader while assistant is thinking */} - {message.role === "assistant" && !textContent && !authPart && isLoading ? ( - - ) : authPart ? ( - // Show auth UI when Arcade needs authorization - (() => { - const toolPart = authPart as { toolName?: string; output?: unknown }; - const result = toolPart.output as Record; - const authResponse = result?.authorization_response as { url?: string }; - // In Vercel AI SDK v6, toolName is a property on the part, not derived from type - const toolName = toolPart.toolName || ""; - return ( - regenerate()} - /> - ); - })() - ) : ( - {textContent} - )} - - - ); - })} - - - - {/* PromptInput handles the form with auto-resize textarea */} -
- { - if (text.trim()) { - sendMessage({ text }); - } - }} - > - - -
{/* Spacer */} - - - -
-
- ); -} -``` - -The full `page.tsx` file is available in the [Complete code](#complete-code) section below. - -### Run the chatbot - -### npm - -```bash -npm run dev -``` - -### pnpm - -```bash -pnpm dev -``` - -### yarn - -```bash -yarn dev -``` - -### bun - -```bash -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000)  and try prompts like: - -- “Summarize my last 3 emails” -- “Send a Slack DM to myself saying hello” -- “Email me a summary of this slack channel’s activity since yesterday…” - -On first use, you’ll see an authorization button. Click it to connect your Gmail or Slack (Arcade remembers this for future requests). - -## Key takeaways - -- **Arcade work seamlessly with Vercel AI SDK**: Use `toZodToolSet` with the `toVercelTools` adapter to convert Arcade tools to the format Vercel AI SDK expects. -- **Authorization is automatic**: The `executeOrAuthorizeZodTool` factory handles auth flows. Check for `authorization_required` in results and display the authorization UI. -- **Handle null parameters**: LLMs sometimes send `null` for optional parameters. The `stripNullValues` wrapper prevents failures. -- **Mix servers and individual **: Combine entire with specific tools to give your exactly the capabilities it needs. - -## Next steps - -1. **Add more **: Browse the [MCP server catalog](/resources/integrations.md) - and add tools for GitHub, Notion, Linear, and more. -2. **Add authentication**: In production, get `userId` from your auth system instead of environment variables. See [Security](/guides/security.md) - for best practices. -3. **Deploy to Vercel**: Push your chatbot to GitHub and [deploy to Vercel](https://vercel.com/docs/deployments/overview) -   with one click. Add your environment variables in the Vercel dashboard. - -## Complete code - -### **app/api/chat/route.ts** (full file) - -app/api/chat/route.ts - -```json -import { openai } from "@ai-sdk/openai"; -import { streamText, convertToModelMessages, stepCountIs } from "ai"; -import { Arcade } from "@arcadeai/arcadejs"; -import { - toZodToolSet, - executeOrAuthorizeZodTool, -} from "@arcadeai/arcadejs/lib/index"; - -const config = { - mcpServers: ["Slack"], - individualTools: ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"], - toolLimit: 30, - systemPrompt: `You are a helpful assistant that can access Gmail and Slack. -Always use the available tools to fulfill user requests. Do not tell users to authorize manually - just call the tool and the system will handle authorization if needed. - -For Gmail: -- To find sent emails, use the query parameter with "in:sent" -- To find received emails, use "in:inbox" or no query - -After completing any action (sending emails, Slack messages, etc.), always confirm what you did with specific details. - -IMPORTANT: When calling tools, if an argument is optional, do not set it. Never pass null for optional parameters.`, -}; - -function stripNullValues( - obj: Record -): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(obj)) { - if (value !== null && value !== undefined) { - result[key] = value; - } - } - return result; -} - -function toVercelTools(arcadeTools: Record): Record { - const vercelTools: Record = {}; - - for (const [name, tool] of Object.entries(arcadeTools)) { - const t = tool as { description: string; parameters: unknown; execute: Function }; - vercelTools[name] = { - description: t.description, - inputSchema: t.parameters, // AI SDK v6 uses inputSchema, not parameters - execute: async (input: Record) => { - const cleanedInput = stripNullValues(input); - return t.execute(cleanedInput); - }, - }; - } - - return vercelTools; -} - -async function getArcadeTools(userId: string) { - const arcade = new Arcade(); - - const mcpServerTools = await Promise.all( - config.mcpServers.map(async (serverName) => { - const response = await arcade.tools.list({ - toolkit: serverName, - limit: config.toolLimit, - }); - return response.items; - }), - ); - - const individualToolDefs = await Promise.all( - config.individualTools.map((toolName) => arcade.tools.get(toolName)) - ); - - const allTools = [...mcpServerTools.flat(), ...individualToolDefs]; - const uniqueTools = Array.from( - new Map(allTools.map((tool) => [tool.qualified_name, tool])).values() - ); - - const arcadeTools = toZodToolSet({ - tools: uniqueTools, - client: arcade, - userId, - executeFactory: executeOrAuthorizeZodTool, - }); - - return toVercelTools(arcadeTools); -} - -export async function POST(req: Request) { - try { - const { messages } = await req.json(); - const userId = process.env.ARCADE_USER_ID || "default-user"; - - const tools = await getArcadeTools(userId); - - const result = streamText({ - model: openai("gpt-4o-mini"), - system: config.systemPrompt, - messages: await convertToModelMessages(messages), - tools, - stopWhen: stepCountIs(5), - }); - - return result.toUIMessageStreamResponse(); - } catch (error) { - console.error("Chat API error:", error); - return Response.json( - { error: "Failed to process chat request" }, - { status: 500 } - ); - } -} -``` - -### **app/api/auth/status/route.ts** (full file) - -app/api/auth/status/route.ts - -```json -import { Arcade } from "@arcadeai/arcadejs"; - -export async function POST(req: Request) { - const { toolName } = await req.json(); - - if (!toolName) { - return Response.json({ error: "toolName required" }, { status: 400 }); - } - - const arcade = new Arcade(); - const userId = process.env.ARCADE_USER_ID || "default-user"; - - try { - const authResponse = await arcade.tools.authorize({ - tool_name: toolName, - user_id: userId, - }); - return Response.json({ status: authResponse.status }); - } catch (error) { - console.error("Auth status check error:", error); - return Response.json( - { status: "error", error: String(error) }, - { status: 500 } - ); - } -} -``` - -### **app/page.tsx** (full file) - -TSX - -app/page.tsx - -```json -"use client"; - -import { useChat } from "@ai-sdk/react"; -import { useState, useRef, useEffect } from "react"; - -// AI Elements components for chat UI -import { - Conversation, - ConversationContent, -} from "@/components/ai-elements/conversation"; -import { - Message, - MessageContent, - MessageResponse, -} from "@/components/ai-elements/message"; -import { - PromptInput, - PromptInputTextarea, - PromptInputFooter, - PromptInputSubmit, -} from "@/components/ai-elements/prompt-input"; -import { Spinner } from "@/components/ui/spinner"; - -// Component that handles Arcade's OAuth authorization flow -function AuthPendingUI({ - authUrl, - toolName, - onAuthComplete, -}: { - authUrl: string; - toolName: string; - onAuthComplete: () => void; -}) { - const [status, setStatus] = useState<"initial" | "waiting" | "completed">("initial"); - const pollingRef = useRef(null); - const timeoutRef = useRef(null); - const hasCompletedRef = useRef(false); - const onAuthCompleteRef = useRef(onAuthComplete); - - // Keep callback ref updated - useEffect(() => { - onAuthCompleteRef.current = onAuthComplete; - }, [onAuthComplete]); - - // Poll for auth completion after user clicks authorize - useEffect(() => { - if (status !== "waiting" || !toolName || hasCompletedRef.current) return; - - const pollStatus = async () => { - try { - const res = await fetch("/api/auth/status", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ toolName }), - }); - const data = await res.json(); - - if (data.status === "completed" && !hasCompletedRef.current) { - hasCompletedRef.current = true; - if (pollingRef.current) clearInterval(pollingRef.current); - setStatus("completed"); - timeoutRef.current = setTimeout(() => onAuthCompleteRef.current(), 1500); - } - } catch (error) { - console.error("Polling error:", error); - } - }; - - pollingRef.current = setInterval(pollStatus, 2000); - return () => { - if (pollingRef.current) clearInterval(pollingRef.current); - }; - }, [status, toolName]); - - const displayName = toolName.split("_")[0] || toolName; - - const handleAuthClick = () => { - if (!authUrl) return; // Don't open empty URLs - window.open(authUrl, "_blank"); - setStatus("waiting"); - }; - - return ( -
- {status === "completed" ? ( -

✓ {displayName} authorized

- ) : !authUrl ? ( -

Authorization URL not available

- ) : ( - <> - Give Arcade Chat access to {displayName}?{" "} - - - )} -
- ); -} - -export default function Chat() { - const { messages, sendMessage, regenerate, status } = useChat(); - const isLoading = status === "submitted" || status === "streaming"; - const inputRef = useRef(null); - - // Refocus input after response completes - useEffect(() => { - if (!isLoading && inputRef.current) { - inputRef.current.focus(); - } - }, [isLoading]); - - return ( -
- {/* Conversation handles auto-scrolling */} - - - {messages.map((message) => { - // Check if any tool part requires authorization - const authPart = message.parts?.find((part) => { - if (part.type.startsWith("tool-")) { - const toolPart = part as { state?: string; output?: unknown }; - if (toolPart.state === "output-available") { - const result = toolPart.output as Record; - return result?.authorization_required; - } - } - return false; - }); - - // Get text content from message parts - const textContent = message.parts - ?.filter((part) => part.type === "text") - .map((part) => part.text) - .join(""); - - // Skip empty messages without auth prompts - if (!textContent && !authPart && !(message.role === "assistant" && isLoading)) { - return null; - } - - return ( - - - {/* Show loader while assistant is thinking */} - {message.role === "assistant" && !textContent && !authPart && isLoading ? ( - - ) : authPart ? ( - // Show auth UI when Arcade needs authorization - (() => { - const toolPart = authPart as { toolName?: string; output?: unknown }; - const result = toolPart.output as Record; - const authResponse = result?.authorization_response as { url?: string }; - // In Vercel AI SDK v6, toolName is a property on the part, not derived from type - const toolName = toolPart.toolName || ""; - return ( - regenerate()} - /> - ); - })() - ) : ( - {textContent} - )} - - - ); - })} - - - - {/* PromptInput handles the form with auto-resize textarea */} -
- { - if (text.trim()) { - sendMessage({ text }); - } - }} - > - - -
{/* Spacer */} - - - -
-
- ); -} -``` - -Last updated on January 30, 2026 - -[Setup Arcade with OpenAI Agents SDK](/en/get-started/agent-frameworks/openai-agents/use-arcade-with-openai-agents.md) -[Overview](/en/get-started/mcp-clients.md) diff --git a/public/_markdown/en/get-started/mcp-clients.md b/public/_markdown/en/get-started/mcp-clients.md deleted file mode 100644 index 0ce2493ae..000000000 --- a/public/_markdown/en/get-started/mcp-clients.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "Connect to MCP Clients" -description: "Learn how to connect your MCP servers to various MCP-compatible clients and development environments" ---- -MCP ClientsOverview - -# Connect to MCP Clients - -You can connect [Arcade MCP servers](/resources/integrations.md) to \-compatible clients and development environments to unlock powerful flows for your . - -[![Cursor logo](/images/icons/cursor.png) Cursor AI-powered code editor with built-in MCP support](/guides/tool-calling/mcp-clients/cursor.md) -[![Claude Desktop logo](/images/icons/claude.png) Claude Desktop Anthropic's desktop app for Claude with MCP integration](/guides/tool-calling/mcp-clients/claude-desktop.md) -[![Visual Studio Code logo](/images/icons/vscode.svg) Visual Studio Code Microsoft's code editor with MCP extensions](/guides/tool-calling/mcp-clients/visual-studio-code.md) -[![Microsoft Copilot Studio logo](/images/icons/microsoft-copilot-studio.png) Microsoft Copilot Studio Microsoft's AI agent platform with MCP integration](/guides/tool-calling/mcp-clients/copilot-studio.md) - -Last updated on January 30, 2026 - -[Vercel AI SDK](/en/get-started/agent-frameworks/vercelai.md) -[Cursor](/en/get-started/mcp-clients/cursor.md) diff --git a/public/_markdown/en/get-started/mcp-clients/claude-desktop.md b/public/_markdown/en/get-started/mcp-clients/claude-desktop.md deleted file mode 100644 index 2797541b5..000000000 --- a/public/_markdown/en/get-started/mcp-clients/claude-desktop.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "Use Arcade in Claude Desktop" -description: "Arcade - AI platform for developers" ---- -[MCP Clients](/en/get-started/mcp-clients.md) -Claude Desktop - -# Use Arcade in Claude Desktop - -## Outcomes - -Connect Claude Desktop to an Arcade Gateway. - -### Prerequisites - -1. Create an [Arcade](https://app.arcade.dev/register) - -2. Get an [Arcade API key](/get-started/setup/api-keys.md) - -3. Create an [Arcade MCP Gateway](/guides/mcp-gateways.md) - and select the you want to use - -For Claude Desktop, you need to set the `Authorization` field to `Arcade Auth` in the dashboard. If you are using the `Arcade Headers` auth mode, you won’t be able to use it with Claude Desktop. - -### Go to your Claude Desktop setting page - -On the bottom left corner of Claude Desktop, click on your avatar to open the settings menu, then click on the “Settings” button. - -![Step 1: Click on the settings icon on Claude Desktop](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-1-light.jpg&w=640&q=75)![Step 1: Click on the settings icon on Claude Desktop](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-1.jpg&w=640&q=75) - -### Add a Custom Connector - -On the settings page, click on the “Connectors” tab, and then on the “Add custom Connector” button. - -![Step 2: Add a custom connector](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-2-light.jpg&w=3840&q=75)![Step 2: Add a custom connector](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-2.jpg&w=3840&q=75) - -A modal dialog will open asking you for a name and a URL. Enter a name for your connector, and the URL of your Gateway. Then, click on the “Add” button. - -![Step 3: Enter a name and URL for your connector](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-3-light.jpg&w=1200&q=75)![Step 3: Enter a name and URL for your connector](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-3.jpg&w=1200&q=75) - -### authenticate with your Arcade account - -You will see a new connector added to your list of connectors. Click on the “Connect” button to authenticate with your Arcade . - -![Step 4: Authenticate with your Arcade account](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-4-light.jpg&w=1920&q=75)![Step 4: Authenticate with your Arcade account](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-4.jpg&w=1920&q=75) - -Your browser will open a new tab to authenticate with your Arcade . Check that the URL matches the one in the modal dialog, and then click on the “Allow” button. - -![Step 5: Allow authentication with your Arcade account](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-5-light.jpg&w=1920&q=75)![Step 5: Allow authentication with your Arcade account](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-5.jpg&w=1920&q=75) - -### Configure the MCP Gateway - -Now Claude Desktop is connected to your Gateway, it’s a good time to configure the to your needs. Click on the “Configure” button to open the configuration dialog. - -![Step 6: Configure the MCP Gateway](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-6-light.jpg&w=1920&q=75)![Step 6: Configure the MCP Gateway](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-6.jpg&w=1920&q=75) - -In this configuration pane, you can configure which tools are available to Claude Desktop, and whether or not they require human confirmation. On this example gateway, we require human confirmation for all that may have destructive actions, or actions with potentially undesired consequences. - -![Step 7: Configure the MCP Gateway](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-7-light.jpg&w=1920&q=75)![Step 7: Configure the MCP Gateway](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fclaude-desktop%2Fstep-7.jpg&w=1920&q=75) - -### Try it out! - -You can now open a new chat within Claude Desktop. Ensure that your connector is enabled, and the ask the to use a ! - -Last updated on January 30, 2026 - -[Cursor](/en/get-started/mcp-clients/cursor.md) -[Visual Studio Code](/en/get-started/mcp-clients/visual-studio-code.md) diff --git a/public/_markdown/en/get-started/mcp-clients/copilot-studio.md b/public/_markdown/en/get-started/mcp-clients/copilot-studio.md deleted file mode 100644 index 8d04dec57..000000000 --- a/public/_markdown/en/get-started/mcp-clients/copilot-studio.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: "Use Arcade in Microsoft Copilot Studio" -description: "Arcade - AI platform for developers" ---- -[MCP Clients](/en/get-started/mcp-clients.md) -Microsoft Copilot Studio - -# Use Arcade in Microsoft Copilot Studio - -## Outcomes - -Connect Microsoft Copilot Studio to an Arcade Gateway. - -### Prerequisites - -1. A Microsoft 365 subscription with access to Copilot Studio -2. Create an [Arcade](https://app.arcade.dev/register) - -3. Get an [Arcade API key](/get-started/setup/api-keys.md) - -4. Create an [Arcade MCP Gateway](/guides/mcp-gateways.md) - and select the you want to use - -### Create or open your agent - -In [Copilot Studio](https://copilotstudio.microsoft.com/) , create a new or open an existing one that you want to connect to Arcade . - -### Add a new MCP tool - -1. Inside your , click the tab in the navigation panel -2. Click on **Add a ** -3. In the Add panel, select -4. Click on **New ** to configure a new connection - -![Step 1: Navigate to the Tools menu in Copilot Studio](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fcopilot-studio%2Fstep-1.png&w=1080&q=75) - -### Configure the MCP Gateway connection - -In the Protocol configuration dialog: - -1. Enter a **Server name** for your connection (for example, “PersonalAssistantTools”) -2. Add a **Server description** to help identify the available -3. Enter your Arcade Gateway URL in the **Server URL** field: `https://api.arcade.dev/mcp/` -4. Under **Authentication**, select **OAuth 2.0** -5. Under **Type**, select **Dynamic discovery** to authorize the gateway automatically using OAuth - -![Step 2: Configure MCP Gateway with OAuth 2.0 and Dynamic Discovery](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fcopilot-studio%2Fstep-2.png&w=1920&q=75) - -### Complete the authorization flow - -After saving the gateway configuration, you’ll be redirected to the Arcade authorization page. Review the permissions requested and click **Allow** to authorize Copilot Studio to access your resources. - -![Step 3: Authorize Copilot Studio to access your Arcade account](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fcopilot-studio%2Fstep-3.png&w=1080&q=75) - -### Start using your tools - -Once the connection is established, return to your and start a conversation. Now you can directly interact with your . - -Arcade provides just-in-time authorization. When you use a that requires access to an external service, Copilot Studio will display an authorization link. Click the link to grant access and continue. This works seamlessly with tools like SharePoint, Outlook, Teams, Stripe, and Gmail. - -![Step 4: Chat with your agent using Arcade tools](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fcopilot-studio%2Fstep-4.png&w=1920&q=75) - -Last updated on January 30, 2026 - -[Visual Studio Code](/en/get-started/mcp-clients/visual-studio-code.md) -[Overview](/en/resources/integrations.md) diff --git a/public/_markdown/en/get-started/mcp-clients/cursor.md b/public/_markdown/en/get-started/mcp-clients/cursor.md deleted file mode 100644 index 6ebb63ca8..000000000 --- a/public/_markdown/en/get-started/mcp-clients/cursor.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "Use Arcade in Cursor" -description: "Arcade - AI platform for developers" ---- -[MCP Clients](/en/get-started/mcp-clients.md) -Cursor - -# Use Arcade in Cursor - -## Outcomes - -Connect Cursor to an Arcade Gateway. - -### Prerequisites - -1. Create an [Arcade](https://app.arcade.dev/register) - -2. Get an [Arcade API key](/get-started/setup/api-keys.md) - -3. Create an [Arcade MCP Gateway](/guides/mcp-gateways.md) - and select the you want to use - -Cursor currently does not refresh the OAuth tokens automatically. To set a persistent connection between Cursor and your , you should set the `Authorization` field to “Arcade Headers” in the dashboard. - -### Set up Cursor - -1. Open the Command Palette (`Cmd + Shift + P` on macOS, `Ctrl + Shift + P` on Windows/Linux) and select **Open Settings** -2. Click on the “New Server” button - -Cursor will open the settings file, and you can add a new entry to the `mcpServers` object: - -### Arcade Auth - -```json -{ - "mcpServers": { - "mcp-arcade": { - "url": "https://api.arcade.dev/mcp/" - } - } -} -``` - -### Arcade Headers - -```json -{ - "mcpServers": { - "mcp-arcade": { - "url": "https://api.arcade.dev/mcp/", - "headers": { - "Authorization": "Bearer {arcade_api_key}", - "Arcade-User-ID": "{arcade_user_id}" - } - } - } -} -``` - -### Try it out - -1. Open the chat pane (typically command-l) -2. Make sure you are in mode -3. Ask the to use a ! - -Last updated on January 30, 2026 - -[Overview](/en/get-started/mcp-clients.md) -[Claude Desktop](/en/get-started/mcp-clients/claude-desktop.md) diff --git a/public/_markdown/en/get-started/mcp-clients/visual-studio-code.md b/public/_markdown/en/get-started/mcp-clients/visual-studio-code.md deleted file mode 100644 index e9c765e13..000000000 --- a/public/_markdown/en/get-started/mcp-clients/visual-studio-code.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "Use Arcade in Visual Studio Code" -description: "Arcade - AI platform for developers" ---- -[MCP Clients](/en/get-started/mcp-clients.md) -Visual Studio Code - -# Use Arcade in Visual Studio Code - -In this guide, you’ll learn how to connect Visual Studio Code to an Arcade Gateway. - -### Prerequisites - -1. Create an [Arcade](https://app.arcade.dev/register) - -2. Create an [Arcade MCP Gateway](/guides/mcp-gateways.md) - and select the you want to use - -### Set up Visual Studio Code - -3. Download and open [Visual Studio Code](https://code.visualstudio.com/download) -   (version 1.107.0 or higher) -4. Open the command palette and select **: Add Server…** -5. Choose **HTTP** -6. Paste the URL of your Gateway. -7. Give your server a name, like `mcp-arcade` - -Visual Studio Code will update your `mcp.json` file. - -### Start the MCP Server in Visual Studio Code - -8. In the `mcp.json` file or in the “Extensions” > “ Servers - Installed” pane, click the “Start” button next to your . -9. Visual Studio Code will prompt you to authenticate, and you may see a prompt about opening an external site: `cloud.arcade.dev`. You can safely allow both of these. -10. If you see an Arcade login screen, authenticate with your Arcade . -11. You should see an Arcade consent screen asking you to authorize Visual Studio Code to access your Arcade . Click “Allow” to continue. -12. You should then see a webpage from Visual Studio Code saying the sign in was successful. You may see a prompt from your browser to open a link in Visual Studio Code. You can safely allow this. - -Your Server should now be running and you can use it in Visual Studio Code. - -### Try it out - -13. Open your IDE’s chat pane. -14. Make sure you are in mode -15. Ask the to use a ! - -Note: if you are using the Arcade Header auth mode for your Gateway, you will manually need to add the headers property in your `mcp.json` file: - -```json -{ - "servers": { - "mcp-arcade": { - "url": "https://api.arcade.dev/mcp/", - "type": "http", - "headers": { - "Authorization": "Bearer {arcade_api_key}", - "Arcade-User-ID": "{arcade_user_id}" - } - } - } -} -``` - -You will not see the authentication prompts when you start the Server in Visual Studio Code because the is passed directly. - -Last updated on January 30, 2026 - -[Claude Desktop](/en/get-started/mcp-clients/claude-desktop.md) -[Microsoft Copilot Studio](/en/get-started/mcp-clients/copilot-studio.md) diff --git a/public/_markdown/en/get-started/quickstarts/call-tool-agent.md b/public/_markdown/en/get-started/quickstarts/call-tool-agent.md deleted file mode 100644 index d62d446ae..000000000 --- a/public/_markdown/en/get-started/quickstarts/call-tool-agent.md +++ /dev/null @@ -1,564 +0,0 @@ ---- -title: "Calling tools in your agent" -description: "Learn how to call tools in your agent" ---- -QuickstartsCall tools in agents - -# Calling tools in your agent with Arcade - -Arcade gives your AI the power to act. With Arcade-hosted , your AI-powered apps can send Gmail, update Notion, message in Slack, and more. - -## Outcomes - -Install and use the to call Arcade Hosted . - -### You will Learn - -- Install the -- Build a workflow that uses to: - - search for news with Google News - - Create a Google Doc with the news - - Email a link to the Google Doc to the -- Present an OAuth URL to the user when the requires authorization - -### Prerequisites - -- An [Arcade](https://app.arcade.dev/register) - -- An [Arcade API key](/get-started/setup/api-keys.md) - -- The [`uv` package manager](https://docs.astral.sh/uv/getting-started/installation/) - if you are using Python -- The [`bun` runtime](https://bun.com/) - if you are using TypeScript - -### Install the Arcade client - -### Python - -In your terminal, run the following command to create a new `uv` : - -```bash -mkdir arcade-quickstart -cd arcade-quickstart -uv init -``` - -Then, run the following command to create and activate a new virtual environment, isolating the dependencies from your system: - -```bash -uv venv -``` - -### Bash/Zsh (macOS/Linux) - -```bash -source .venv/bin/activate -``` - -### PowerShell (Windows) - -```powershell -. ".venv\Scripts\Activate.ps1" -``` - -Then, run the following command to install the Python client package `arcadepy`: - -```powershell -uv add arcadepy -``` - -### TypeScript - -In your terminal, run the following command to install the JavaScript client package `@arcadeai/arcadejs`: - -```bash -bun install @arcadeai/arcadejs -``` - -### Setup the client - -### Python - -Open the `main.py` file and replace the content with the following: - -```python -# main.py -from arcadepy import Arcade - -# You can also set the `ARCADE_API_KEY` environment variable instead of passing it as a parameter. -client = Arcade(api_key="{arcade_api_key}") - -# Arcade needs a unique identifier for your application user (this could be an email address, a UUID, etc). -# In this example, use the email you used to sign up for Arcade.dev: -user_id = "{arcade_user_id}" -``` - -### TypeScript - -Create a new script called `example.ts`: - -```typescript -// example.ts -import Arcade from "@arcadeai/arcadejs"; - -// You can also set the `ARCADE_API_KEY` environment variable instead of passing it as a parameter. -const client = new Arcade({ - apiKey: "{arcade_api_key}", -}); - -// Arcade needs a unique identifier for your application user (this could be an email address, a UUID, etc). -// In this example, use the email you used to sign up for Arcade.dev: -let userId = "{arcade_user_id}"; -``` - -### Write a helper function to authorize and run tools - -This helper function will check if a requires authorization and if so, it will print the authorization URL and wait for the to authorize the tool call. If the tool does not require authorization, it will run the tool directly without interrupting the flow. - -### Python - -```python -# main.py -# Helper function to authorize and run any tool -def authorize_and_run_tool(tool_name, input, user_id): - # Start the authorization process - auth_response = client.tools.authorize( - tool_name=tool_name, - user_id=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}:\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 - return client.tools.execute(tool_name=tool_name, input=input, user_id=user_id) -``` - -### JavaScript - -```typescript -// example.ts -// Helper function to authorize and run any tool -async function authorize_and_run_tool({ - tool_name, - input, - user_id, -}: { - tool_name: string; - input: any; - user_id: string; -}) { - // Start the authorization process - const authResponse = await client.tools.authorize({ - tool_name: tool_name, - user_id: 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 (authResponse.status !== "completed") { - console.log( - `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); - } - - // Run the tool - const response = await client.tools.execute({ - tool_name: tool_name, - input: input, - user_id: user_id, - }); - return response; -} -``` - -### Implement the workflow - -In this example workflow, we: - -- Get the latest news about URL mode elicitation -- Create a Google Doc with the news -- Send a link to the Google Doc to the - -### Python - -```python -# 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( - tool_name="GoogleNews.SearchNewsStories", - input={ - "keywords": "MCP URL mode elicitation", - }, - user_id=user_id, -) - -# Get the news results from the response -news = response_search.output.value["news_results"] - -# Format the news results into a string -output = "latest news about MCP URL mode elicitation:\n" -for search_result in news: - output += f"{search_result['source']} - {search_result['title']}\n" - output += f"{search_result['link']}\n\n" - -# Create a Google Doc with the news results -# If the user has not previously authorized the Google Docs tool, they will be prompted to authorize the tool call. -response_create_doc = authorize_and_run_tool( - tool_name="GoogleDocs.CreateDocumentFromText", - input={ - "title": "News about MCP URL mode elicitation", - "text_content": output, - }, - user_id=user_id, -) - -# Get the Google Doc from the response -google_doc = response_create_doc.output.value - -email_body = f"You can find the news about MCP URL mode elicitation in the following Google Doc: {google_doc['documentUrl']}" - -# Send an email with the link to the Google Doc -response_send_email = authorize_and_run_tool( - tool_name="Gmail.SendEmail", - input={ - "recipient": user_id, - "subject": "News about MCP URL mode elicitation", - "body": email_body, - }, - user_id=user_id, -) - -# Print the response from the tool call -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) -``` - -### TypeScript - -```typescript -// example.ts -// This tool does not require authorization, so it will return the results -// without prompting the user to authorize the app. -const response_search = await authorize_and_run_tool({ - tool_name: "GoogleNews.SearchNewsStories", - input: { - keywords: "MCP URL mode elicitation", - }, - user_id: userId, -}); - -// Get the news results from the response -const news = response_search.output?.value?.news_results; - -// Format the news results into a string -let output = "latest news about MCP URL mode elicitation:\n"; -for (const search_result of news) { - output += "--------------------------------\n"; - output += `${search_result.source} - ${search_result.title}\n`; - output += `${search_result.link ?? ""}\n`; -} - -// Create a Google Doc with the news results -// If the user has not previously authorized the Google Docs tool, they will be prompted to authorize the tool call. -const respose_create_doc = await authorize_and_run_tool({ - tool_name: "GoogleDocs.CreateDocumentFromText", - input: { - title: "News about MCP URL mode elicitation", - text_content: output, - }, - user_id: userId, -}); - -const google_doc = respose_create_doc.output?.value; - -// Send an email with the link to the Google Doc -const email_body = `You can find the news about MCP URL mode elicitation in the following Google Doc: ${google_doc.documentUrl}`; - -// Here again, if the user has not previously authorized the Gmail tool, they will be prompted to authorize the tool call. -const respose_send_email = await authorize_and_run_tool({ - tool_name: "Gmail.SendEmail", - input: { - recipient: userId, - subject: "News about MCP URL mode elicitation", - body: email_body, - }, - user_id: userId, -}); - -// Print the response from the tool call -console.log( - `Success! Check your email at ${userId}\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:` -); -console.log(respose_send_email.output?.value); -``` - -### Run the code - -### Python - -```bash -uv run main.py -``` - -TEXT - -``` -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 - 2. Created a Google Doc with the results - 3. Sent yourself an email with the document link - -Email metadata: -{'id': '19ba...', 'label_ids': ['UNREAD', 'SENT', 'INBOX'], 'thread_id': '19ba...', 'url': 'https://mail.google.com/mail/u/0/#sent/19ba...'} -``` - -### TypeScript - -```bash -bun run example.ts -``` - -TEXT - -``` -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 - 2. Created a Google Doc with the results - 3. Sent yourself an email with the document link - -Email metadata: -{ - id: "19ba...", - label_ids: [ "UNREAD", "SENT", "INBOX" ], - thread_id: "19ba...", - url: "https://mail.google.com/mail/u/0/#sent/19ba...", -} -``` - -## Next Steps - -In this example, we call the tool methods directly. In your real applications and , you’ll likely be letting the LLM decide which to call. Learn more about using Arcade with Frameworks in the [Frameworks](/get-started/agent-frameworks.md) section, or [how to build your own tools](/guides/create-tools/tool-basics/build-mcp-server.md). - -[![CrewAI logo](https://avatars.githubusercontent.com/u/170677839?s=200&v=4) CrewAI Agent Framework](/en/get-started/agent-frameworks/crewai/use-arcade-tools.md) -[![Google ADK logo](https://avatars.githubusercontent.com/u/1342004?s=200&v=4) Google ADK Agent Framework](/en/get-started/agent-frameworks/google-adk/setup-python.md) -[![LangChain logo](/images/icons/langchain.svg) LangChain Agent Framework](/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py.md) -[![OpenAI Agents logo](https://avatars.githubusercontent.com/u/14957082?s=200&v=4) OpenAI Agents Agent Framework](/en/get-started/agent-frameworks/openai-agents/setup-python.md) -[![Vanilla Python logo](/images/icons/python.svg) Vanilla Python MCP Client](/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python.md) - -[![LangChain logo](/images/icons/langchain.svg) LangChain Agent Framework](/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-ts.md) -[![Google ADK logo](https://avatars.githubusercontent.com/u/1342004?s=200&v=4) Google ADK Agent Framework](/en/get-started/agent-frameworks/google-adk/setup-typescript.md) -[![Mastra logo](/images/icons/mastra.svg) Mastra Agent Framework](/en/get-started/agent-frameworks/mastra.md) -[![Vercel AI logo](/images/icons/vercel.svg) Vercel AI Agent Framework](/en/get-started/agent-frameworks/vercelai.md) -[![TanStack AI logo](https://avatars.githubusercontent.com/u/72518640?s=200&v=4) TanStack AI Agent Framework](/en/get-started/agent-frameworks/tanstack-ai.md) - -## Full Example Code - -### Python - -### **main.py** (full file) - -```python -# example.py -from arcadepy import Arcade - -# You can also set the `ARCADE_API_KEY` environment variable instead of passing it as a parameter. -client = Arcade(api_key="{arcade_api_key}") - -# Arcade needs a unique identifier for your application user (this could be an email address, a UUID, etc). -# In this example, use the email you used to sign up for Arcade.dev: -user_id = "{arcade_user_id}" - - -# Helper function to authorize and run any tool -def authorize_and_run_tool(tool_name, input, user_id): - # Start the authorization process - auth_response = client.tools.authorize( - tool_name=tool_name, - user_id=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}:\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 - return client.tools.execute(tool_name=tool_name, input=input, user_id=user_id) - -# 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( - tool_name="GoogleNews.SearchNewsStories", - input={ - "keywords": "MCP URL mode elicitation", - }, - user_id=user_id, -) - -# Get the news results from the response -news = response_search.output.value["news_results"] - -# Format the news results into a string -output = "latest news about MCP URL mode elicitation:\n" -for search_result in news: - output += "----------------------------\n" - output += f"{search_result['source']} - {search_result['title']}\n" - output += f"{search_result['link']}\n" - -# Create a Google Doc with the news results -# If the user has not previously authorized the Google Docs tool, they will be prompted to authorize the tool call. -response_create_doc = authorize_and_run_tool( - tool_name="GoogleDocs.CreateDocumentFromText", - input={ - "title": "News about MCP URL mode elicitation", - "text_content": output, - }, - user_id=user_id, -) - -# Get the Google Doc from the response -google_doc = response_create_doc.output.value - -email_body = f"You can find the news about MCP URL mode elicitation in the following Google Doc: {google_doc['documentUrl']}" - -# Send an email with the link to the Google Doc -response_send_email = authorize_and_run_tool( - tool_name="Gmail.SendEmail", - input={ - "recipient": user_id, - "subject": "News about MCP URL mode elicitation", - "body": email_body, - }, - user_id=user_id, -) - -# Print the response from the tool call -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) -``` - -### TypeScript - -### **example.ts** (full file) - -```typescript -// example.ts -import Arcade from "@arcadeai/arcadejs"; - -// You can also set the `ARCADE_API_KEY` environment variable instead of passing it as a parameter. -const client = new Arcade( - apiKey: "{arcade_api_key}", -); - -// Arcade needs a unique identifier for your application user (this could be an email address, a UUID, etc). -// In this example, use the email you used to sign up for Arcade.dev: -let userId = "{arcade_user_id}"; - - -// Helper function to authorize and run any tool -async function authorize_and_run_tool({ - tool_name, - input, - user_id, -}: { - tool_name: string, - input: any, - user_id: string, -}) { - - - // Start the authorization process - const authResponse = await client.tools.authorize({ - tool_name: tool_name, - user_id: 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 (authResponse.status !== "completed") { - console.log( - `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); - } - - // Run the tool - const response = await client.tools.execute({ - tool_name: tool_name, - input: input, - user_id: user_id, - }); - return response; -} - -// This tool does not require authorization, so it will return the results -// without prompting the user to authorize the app. -const response_search = await authorize_and_run_tool({ - tool_name: "GoogleNews.SearchNewsStories", - input: { - keywords: "MCP URL mode elicitation", - }, - user_id: userId, -}); - -// Get the news results from the response -const news = response_search.output?.value?.news_results; - -// Format the news results into a string -let output = "latest news about MCP URL mode elicitation:\n"; -for (const search_result of news) { - output += "--------------------------------\n"; - output += `${search_result.source} - ${search_result.title}\n`; - output += `${search_result.link ?? ""}\n`; -} - -// Create a Google Doc with the news results -// If the user has not previously authorized the Google Docs tool, they will be prompted to authorize the tool call. -const respose_create_doc = await authorize_and_run_tool({ - tool_name: "GoogleDocs.CreateDocumentFromText", - input: { - title: "News about MCP URL mode elicitation", - text_content: output, - }, - user_id: userId, -}); - -const google_doc = respose_create_doc.output?.value; - -// Send an email with the link to the Google Doc -const email_body = `You can find the news about MCP URL mode elicitation in the following Google Doc: ${google_doc.documentUrl}`; - -// Here again, if the user has not previously authorized the Gmail tool, they will be prompted to authorize the tool call. -const respose_send_email = await authorize_and_run_tool({ - tool_name: "Gmail.SendEmail", - input: { - recipient: userId, - subject: "News about MCP URL mode elicitation", - body: email_body, - }, - user_id: userId, -}); - -// Print the response from the tool call -console.log( - `Success! Check your email at ${userId}\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:` -); -console.log(respose_send_email.output?.value); -``` - -Last updated on February 10, 2026 - -[Windows environment setup](/en/get-started/setup/windows-environment.md) -[Call tools in IDE/MCP clients](/en/get-started/quickstarts/call-tool-client.md) diff --git a/public/_markdown/en/get-started/quickstarts/call-tool-client.md b/public/_markdown/en/get-started/quickstarts/call-tool-client.md deleted file mode 100644 index a22cede59..000000000 --- a/public/_markdown/en/get-started/quickstarts/call-tool-client.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: "Call a tool in your IDE/MCP Client" -description: "Learn how to call a tool in your IDE/MCP Client" ---- -[Quickstarts](/en/get-started/quickstarts/call-tool-agent.md) -Call tools in IDE/MCP clients - -# Call a tool in your IDE/MCP Client - -Tools enable your AI agents to perform actions on your behalf. For specific workflows and use cases, this may involve calling tools from multiple servers. Arcade facilitates this by allowing you to create MCP Gateways to federate the tools from multiple MCP servers into a single collection for convenient management, control, and access. For example, if your agent specializes in solving specific tickets in Linear, you may want to use tools from the GitHub, Slack and Linear servers in your agent. These add up to 88 tools, which could be overwhelming for an LLM to use effectively. What you want is to get from these servers only the tools that matter for your agent. An allows you to do just that: pick only the tools required for this workflow, and you can connect it to any MCP client, making it possible to port your to multiple platforms and IDEs, and even share it with other . - -## Outcomes - -Create a coding agent using an Gateway to call tools from multiple . - -### You will Learn - -- Create an Gateway -- Connect the Gateway to Cursor or VS Code -- Call tools from the Gateway in your - -### Prerequisites - -- An [Arcade](https://app.arcade.dev/register) - - -### Create an MCP Gateway - -**Create a new Gateway.** Go to the [MCP Gateways dashboard](https://api.arcade.dev/dashboard/mcp-gateways) , and click the “Create ” button. - -![Create MCP Gateway](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fcreate-mcp-gateway-light.png&w=1080&q=75)![Create MCP Gateway](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fcreate-mcp-gateway-dark.png&w=1080&q=75) - -Give your gateway: - -- A name -- A description -- 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 Gateway - - **Arcade Auth**: To access the Gateway, you’ll need to authenticate with your Arcade 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 Gateway, you’ll need to authenticate with your Arcade account by passing an key in the `Authorization` header and the 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 - -**Click the “Select ” button** in the form to select the tools you want to include in the gateway. You can select tools from any server available to the active . For this example, select the following tools: - -- the GitHub server -- the Linear server - -Feel free to select any you want to include in your specific use case. - -![Tool Picker](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fmcp-gateway-tool-filter-dev-light.png&w=3840&q=75)![Tool Picker](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fmcp-gateway-tool-filter-dev-dark.png&w=3840&q=75) - -Once you’ve selected the tools you want to include in the gateway, click the “Use N tools” button in the tool picker, and then click the “Create Gateway” button to create the gateway. - -You can select as many tools for your Gateway as you want, but be mindful of how the MCP clients will handle the large number of tools. Some clients may not handle a large number of tools well, and may consume a significant portion of the LLM’s context window. We recommend keeping the number of tools in a single below 80.\`\`\` - -### Connect the MCP Gateway to an MCP client - -Arcade Gateways are compatible with any MCP client that supports: - -- Streamable HTTP transport -- OAuth, or support for setting up headers for the HTTP transport - -Get the URL of your Gateway by clicking the “Copy URL” button in the details page. - -![MCP Gateway URL](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fproject-mcp-gateways-light.png&w=3840&q=75)![MCP Gateway URL](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fproject-mcp-gateways-dark.png&w=3840&q=75) - -Select the client you want to use to read the instructions to connect to the : - -[![Cursor logo](/images/icons/cursor.png) Cursor AI-powered code editor with built-in MCP support](/guides/tool-calling/mcp-clients/cursor.md) -[![Claude Desktop logo](/images/icons/claude.png) Claude Desktop Anthropic's desktop app for Claude with MCP integration](/guides/tool-calling/mcp-clients/claude-desktop.md) -[![Visual Studio Code logo](/images/icons/vscode.svg) Visual Studio Code Microsoft's code editor with MCP extensions](/guides/tool-calling/mcp-clients/visual-studio-code.md) -[![Microsoft Copilot Studio logo](/images/icons/microsoft-copilot-studio.png) Microsoft Copilot Studio Microsoft's AI agent platform with MCP integration](/guides/tool-calling/mcp-clients/copilot-studio.md) - -### Try it out - -1. Open your IDE’s chat pane. -2. Ask the to do something! For example, “Check the latest linear issue assigned to me. Then, create a new GitHub branch, implement the fix, and add tests. If all the tests pass, create a pull request and assign it to me.” - -As you interact with the agent, it will call the tools from the Gateway. Your should prompt you to visit links to authorize access to Linear and GitHub. After this, it will start using to carry out the task! Subsequent calls will not require authorization. - -## Next Steps - -- Learn more about [MCP Gateways](/guides/mcp-gateways.md) - . -- Learn how to use Gateways with: - - [Cursor](/get-started/mcp-clients/cursor.md) - - - [Visual Studio Code](/get-started/mcp-clients/visual-studio-code.md) - -- Build your own servers with [arcade-mcp](/get-started/quickstarts/mcp-server-quickstart.md) - . - -Last updated on January 30, 2026 - -[Call tools in agents](/en/get-started/quickstarts/call-tool-agent.md) -[Build an MCP server for custom tools](/en/get-started/quickstarts/mcp-server-quickstart.md) diff --git a/public/_markdown/en/get-started/quickstarts/mcp-server-quickstart.md b/public/_markdown/en/get-started/quickstarts/mcp-server-quickstart.md deleted file mode 100644 index e2fe9a03c..000000000 --- a/public/_markdown/en/get-started/quickstarts/mcp-server-quickstart.md +++ /dev/null @@ -1,238 +0,0 @@ ---- -title: "Build MCP Server QuickStart" -description: "Create your custom MCP Server with Arcade MCP" ---- -[Quickstarts](/en/get-started/quickstarts/call-tool-agent.md) -Build an MCP server for custom tools - -# Build MCP Server QuickStart - -## Outcomes - -Build and run an Server with that you create. - -### You will Learn - -- Install `arcade-mcp`, the secure framework for building servers -- Start your server and connect to it from your favorite MCP client -- Call a basic -- Call a that requires a secret -- Create an Arcade -- Call a that requires auth - -### Prerequisites - -- Python 3.10 or higher -- The [`uv` package manager](https://docs.astral.sh/uv/getting-started/installation/) - - -## Install the Arcade CLI - -In your terminal, run the following command to install the `arcade-mcp` package - Arcade’s CLI: - -### uv - -```bash -uv tool install arcade-mcp -``` - -This will install the Arcade CLI as a [uv tool](https://docs.astral.sh/uv/guides/tools/#installing-tools) , making it available system wide. - -### pip - -```bash -pip install arcade-mcp -``` - -## Create Your Server - -In your terminal, run the following command to scaffold a new Server called `my_server`: - -```bash -arcade new my_server -cd my_server/src/my_server -``` - -This generates a Python module with the following structure: - -```bash -my_server/ -├── .env.example -├── src/ -│ └── my_server/ -│ ├── __init__.py -│ └── server.py -└── pyproject.toml -``` - -- **server.py** with MCPApp and example -- **pyproject.toml** Dependencies and configuration -- **.env.example** Example `.env` file at the root containing a secret required by one of the generated in `server.py`. Arcade automatically discovers `.env` files by traversing upward from the current directory. - -`server.py` includes proper structure with command-line argument handling. It creates an `MCPApp` with three sample : - -- **`greet`**: This has a single argument, the name of the person to greet. It requires no secrets or auth -- **`whisper_secret`**: This requires no arguments, and will output the last 4 characters of a `MY_SECRET_KEY` secret that you can define in your `.env` file. -- **`get_posts_in_subreddit`**: This has a single argument, a subreddit, and will return the latest posts on that subreddit, it requires the to authorize the tool to perform a read operation on their behalf. - -> If you’re having issues with the `arcade` command, please see the [Troubleshooting](#troubleshooting) section. - -## Setup the secrets in your environment - -Secrets are sensitive strings like passwords, , or other tokens that grant access to a protected resource or API. Arcade includes the “whisper\_secret” that requires you to set a secret key in your environment. If you don’t set the secret, the tool will return an error. - -### .env file - -You can create a `.env` file at your root directory and add your secret: - -```bash -# .env -MY_SECRET_KEY="my-secret-value" -``` - -The generated includes a `.env.example` file at the project root with the secret key name and example value. You can rename it to `.env` to start using it. - -### Bash/Zsh (macOS/Linux) - -```bash -mv ../../.env.example ../../.env -``` - -### PowerShell (Windows) - -```powershell -Copy-Item .env.example .env -``` - -### Environment Variable - -You can set the environment variable in your terminal directly with this command: - -### Bash/Zsh (macOS/Linux) - -```bash -export MY_SECRET_KEY="my-secret-value" -``` - -### PowerShell (Windows) - -```powershell -$env:MY_SECRET_KEY="my-secret-value" -``` - -## Connect to Arcade to unlock authorized tool calling - -Since the Reddit tool accesses information only available to your Reddit , you’ll need to authorize it. For this, you’ll need to create an Arcade account and connect from the terminal, run: - -```bash -arcade login -``` - -Follow the instructions in your browser to connect your terminal to your Arcade . - -## Run your MCP Server - -Run your Server using one of the following commands in your terminal: - -### stdio transport (default) - -```bash -uv run server.py stdio -``` - -When using the stdio transport, clients typically launch the as a subprocess. Because of this, the server may run in a different environment and not have access to secrets defined in your local `.env` file. Please refer to the [create a tool with secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) guide for more information. - -### http transport - -```bash -uv run server.py http -``` - -For HTTP transport, view your server’s API docs at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) . - -For security reasons, Local HTTP servers do not currently support tool-level authorization and secrets. If you need to use tool-level authorization or secrets locally, you should use the stdio transport and configure the Arcade API key and secrets in your connection settings. Otherwise, if you intend to expose your HTTP to the public internet with \-level authorization and secrets, please follow the [deploying to the cloud with Arcade Deploy](/guides/deployment-hosting/arcade-deploy.md) guide or the [on-prem MCP server](/guides/deployment-hosting/on-prem.md) guide for secure remote deployment. - -You should see output like this in your terminal: - -```bash -2025-11-03 13:46:11.041 | DEBUG | arcade_mcp_server.mcp_app:add_tool:242 - Added tool: greet -2025-11-03 13:46:11.042 | DEBUG | arcade_mcp_server.mcp_app:add_tool:242 - Added tool: whisper_secret -2025-11-03 13:46:11.043 | DEBUG | arcade_mcp_server.mcp_app:add_tool:242 - Added tool: get_posts_in_subreddit -INFO | 13:46:11 | arcade_mcp_server.mcp_app:299 | Starting my_server v1.0.0 with 3 tools -``` - -## Configure your MCP Clients - -Now you can connect Clients to your : - -### Cursor IDE - -```bash -# Configure Cursor to use your MCP server with the default transport (stdio) -arcade configure cursor - -# Configure Cursor to use your MCP server with the http transport -arcade configure cursor --transport http -``` - -### VS Code - -```bash -# Configure VS Code to use your MCP server with the default transport (stdio) -arcade configure vscode - -# Configure VS Code to use your MCP server with the http transport -arcade configure vscode --transport http -``` - -### Claude Desktop - -```bash -# Configure Claude Desktop to use your MCP server with the default transport (stdio) -arcade configure claude - -# Configure Claude Desktop to use your MCP server with the http transport -arcade configure claude --transport http -``` - -## Try it out - -Try calling your inside your assistant. - -Here’s some prompts you can try: - -- “Get some posts from the r/ subreddit” -- “What’s the last 4 characters of my secret key?” -- “Greet me as Supreme Lord of ” - -## Troubleshooting - -### `arcade` command not found or not working - -If you’re getting issues with the `arcade` command, please make sure you did not install it outside of your virtual environment. For example, if your system-wide Python installation older than 3.10, you may need to uninstall arcade from that Python installation in order for the terminal to recognize the `arcade` command installed in your virtual environment. - -### The Reddit tool is not working - -Ensure you run `arcade login` and follow the instructions in your browser to connect your terminal to your Arcade . - -### The Whisper Secret tool is not working - -Ensure you have set the environment variable in your terminal or `.env` file, and that it matches the secret key defined in the `@app.tool` decorator. If you are using the stdio transport, then ensure you add the environment variable to the client’s configuration file. - -## Next Steps - -- **Learn how to write a with auth**: [Create a tool with auth](/guides/create-tools/tool-basics/create-tool-auth.md) - -- **Learn how to write a with secrets**: [Create a tool with secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) - -- **Learn more about the object**: [Tools and Context](/guides/create-tools/tool-basics/runtime-data-access.md) - -- **Learn how to write evaluations**: [Create an evaluation suite](/guides/create-tools/evaluate-tools/create-evaluation-suite.md) - to optimize them for LLM usage -- **Learn how to deploy your server**: [Deploy your MCP server](/guides/deployment-hosting/arcade-deploy.md) - - -Last updated on February 10, 2026 - -[Call tools in IDE/MCP clients](/en/get-started/quickstarts/call-tool-client.md) -[Overview](/en/get-started/agent-frameworks.md) diff --git a/public/_markdown/en/get-started/setup/api-keys.md b/public/_markdown/en/get-started/setup/api-keys.md deleted file mode 100644 index 655ff9f8d..000000000 --- a/public/_markdown/en/get-started/setup/api-keys.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: "Getting Your API Key" -description: "Learn how to obtain and manage your Arcade API key" ---- -SetupGet an API key - -# Getting Your API Key - -Before you begin, you’ll need an Arcade - if you haven’t created one yet, you can [sign up here](https://app.arcade.dev/register). Once you have an account, you can generate through either the dashboard or CLI. - -### Dashboard - -### Using the Dashboard - -### Navigate to API Keys page - -Visit the [API Keys page](https://api.arcade.dev/dashboard/api-keys)  in Arcade Dashboard. - -### Create a new API key - -1. Click the `Create API Key` button in the top right -2. Enter a descriptive name to help identify your key -3. Click `Create API Key` to generate your key - -### Save your API key securely - -1. Copy your immediately. It only shows once. -2. Store it securely -3. You can always generate new keys if needed - -### CLI - -### Using the CLI - -### Install and login - -1. Install the Arcade CLI: - -### uv - -```bash -uv tool install arcade-mcp -``` - -This will install the Arcade CLI as a [uv tool](https://docs.astral.sh/uv/guides/tools/#installing-tools) , making it available system wide. - -### pip - -```bash -pip install arcade-mcp -``` - -2. Start the login process: - -```bash -arcade login -``` - -### Complete setup - -The CLI will automatically: - -- Print your to the console -- Save your credentials to `~/.arcade/credentials.yaml` (or `%USERPROFILE%\.arcade\credentials.yaml` on Windows) - -API keys are administrator credentials. Anyone who has your can make requests to Arcade as you. Always store your API keys in a safe place, such as system environment variables, and never commit them to version control, share them publicly, or use them in browser or frontend code. - -## Next Steps - -Once you have your , you can: - -- [Start using tools](/get-started/quickstarts/call-tool-agent.md) - -- [Create custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) - - -Last updated on February 10, 2026 - -[About Arcade](/en/get-started/about-arcade.md) -[Connect Arcade docs to your IDE](/en/get-started/setup/connect-arcade-docs.md) diff --git a/public/_markdown/en/get-started/setup/connect-arcade-docs.md b/public/_markdown/en/get-started/setup/connect-arcade-docs.md deleted file mode 100644 index ec8763311..000000000 --- a/public/_markdown/en/get-started/setup/connect-arcade-docs.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: "Agentic Development" -description: "Learn how to speed up your development with agents in your IDEs" ---- -[Setup](/en/get-started/setup/api-keys.md) -Connect Arcade docs to your IDE - -# Agentic Development - -Every page on the Arcade docs site renders as clean markdown. When an AI or coding assistant visits any docs URL, the site automatically returns `Content-Type: text/markdown` instead of HTML if: - -- The request `User-Agent` header matches a known AI (Claude, ChatGPT, Cursor, etc.) -- The request includes the `Accept: text/markdown` header - -This means you can point your directly at any docs page—no need to copy and paste or use the `llms.txt` file. The agent will receive well-formatted markdown out of the box. - -For example, you can tell your to visit `docs.arcade.dev/get-started/quickstarts/call-tool-agent` and it will automatically get the markdown version of that page. - -## LLMs.txt - -Give your AI IDE access to Arcade.dev’s documentation using the [llms.txt](/llms.txt) file. This allows Claude Code, Cursor, and other AI IDEs to access the documentation and help you write code. - -LLMs.txt is a file format that allows you to give your AI IDE access to Arcade.dev’s documentation in a format that the LLM can parse. All you need to do is paste in the content of the file into your IDE’s settings, or reference the docs, for example via [Cursor’s `@docs` annotation](https://cursor.com/docs/context/symbols#docs). - -The LLMs.txt files are available at [`/llms.txt`](/llms.txt). - -![LLMs.txt example](/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcursor-llms-txt.7d1a9738.png&w=3840&q=75) - -Learn more about the LLMs.txt file format [here](https://llmstxt.org/) . - -Last updated on February 10, 2026 - -[Get an API key](/en/get-started/setup/api-keys.md) -[Windows environment setup](/en/get-started/setup/windows-environment.md) diff --git a/public/_markdown/en/get-started/setup/windows-environment.md b/public/_markdown/en/get-started/setup/windows-environment.md deleted file mode 100644 index 53edd11fe..000000000 --- a/public/_markdown/en/get-started/setup/windows-environment.md +++ /dev/null @@ -1,259 +0,0 @@ ---- -title: "Windows environment setup" -description: "Install and use Arcade CLI on Windows" ---- -[Setup](/en/get-started/setup/api-keys.md) -Windows environment setup - -# Windows environment setup - -Set up Arcade CLI on Windows using `uv` (recommended), with optional `pip` fallback guidance. - -## Before you start - -- Windows with PowerShell -- Python 3.10 or later -- Internet access for package download - -Validate which commands exist on your machine: - -### Bash/Zsh (macOS/Linux) - -```bash -uv --version -python3 --version -command -v arcade || true -``` - -### PowerShell (Windows) - -```powershell -uv --version -python --version -py --version -Get-Command arcade -ErrorAction SilentlyContinue -``` - -If both `python` and `py` are missing, the pip-only instructions will fail until you install Python and add it to `PATH`. - -## Install `uv` - -Use one of the following methods. - -### Bash/Zsh (macOS/Linux) - -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -export PATH="$HOME/.local/bin:$PATH" -uv --version -``` - -### PowerShell (Windows) - -```powershell -irm https://astral.sh/uv/install.ps1 | iex -uv --version -``` - -If `uv` is still not found in the same shell session: - -```powershell -$env:Path = "$env:USERPROFILE\.local\bin;$env:Path" -uv --version -``` - -## Optional binary package managers on Windows - -`winget`, `scoop`, and `choco` are optional convenience methods. Keep them secondary to official install instructions. - -### winget - -```bash -winget --version -winget install --id=astral-sh.uv -e -``` - -### scoop - -```bash -scoop --version -scoop install main/uv -``` - -### chocolatey - -```bash -choco --version -choco install uv -``` - -## Install Arcade CLI with `uv` (recommended) - -### Global install - -### Bash/Zsh (macOS/Linux) - -```bash -uv tool install --upgrade arcade-mcp -arcade -v -command -v arcade -``` - -### PowerShell (Windows) - -```powershell -uv tool install --upgrade arcade-mcp -arcade -v -Get-Command arcade -``` - -### Virtual environment install - -### Bash/Zsh (macOS/Linux) - -```bash -uv venv .venv -uv pip install --python .venv/bin/python arcade-mcp -.venv/bin/arcade -v -``` - -Optional activation: - -```bash -source .venv/bin/activate -arcade -v -``` - -### PowerShell (Windows) - -```powershell -uv venv ".venv" -uv pip install --python ".venv\Scripts\python.exe" arcade-mcp -& ".venv\Scripts\arcade.exe" -v -``` - -Optional activation: - -```powershell -. ".venv\Scripts\Activate.ps1" -arcade -v -``` - -## Install without `uv` (pip fallback) - -Use this only if `uv` is unavailable. - -### Bash/Zsh (macOS/Linux) - -```bash -python3 -m venv .venv -source .venv/bin/activate -python -m pip install --upgrade pip -python -m pip install arcade-mcp -arcade -v -``` - -\-level install: - -```bash -python3 -m pip install --user arcade-mcp -arcade -v -``` - -### PowerShell (Windows) - -```powershell -python -m venv ".venv" -. ".venv\Scripts\Activate.ps1" -python -m pip install --upgrade pip -python -m pip install arcade-mcp -arcade -v -``` - -Global install: - -```powershell -python -m pip install --user arcade-mcp -arcade -v -``` - -## Validate your installation - -Run these after install: - -### Bash/Zsh (macOS/Linux) - -```bash -arcade -v -arcade --help -arcade mcp --help -``` - -If validating a venv without activation: - -```bash -.venv/bin/arcade -v -``` - -### PowerShell (Windows) - -```powershell -arcade -v -arcade --help -arcade mcp --help -``` - -If validating a venv without activation: - -```powershell -& ".venv\Scripts\arcade.exe" -v -``` - -## Troubleshoot Windows setup - -### `uv` not found - -```powershell -$env:Path = "$env:USERPROFILE\.local\bin;$env:Path" -uv --version -``` - -### `arcade` resolves to a venv instead of global install - -```powershell -Get-Command arcade -``` - -If this points to `.venv\Scripts\arcade.exe`, open a new shell or deactivate the venv before validating global install. - -### Execution policy blocks `Activate.ps1` - -```powershell -Get-ExecutionPolicy -List | Format-Table -AutoSize -Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -. ".venv\Scripts\Activate.ps1" -``` - -### Install takes longer than expected - -The first install can download many dependencies. This is normal on fresh environments. - -### Docs mention `brew install` commands - -Some Arcade docs include `brew` commands for macOS examples. On native Windows, prefer `winget`, `scoop`, or `choco`. - -If you are following a guide that uses Homebrew, treat it as optional unless you are using WSL or Git Bash with Homebrew. - -## Next steps - -- [Get an API key](/get-started/setup/api-keys.md) - -- [Run the MCP server quickstart](/get-started/quickstarts/mcp-server-quickstart.md) - -- [Browse the Arcade CLI reference](/references/arcade-cli.md) - - -Last updated on February 10, 2026 - -[Connect Arcade docs to your IDE](/en/get-started/setup/connect-arcade-docs.md) -[Call tools in agents](/en/get-started/quickstarts/call-tool-agent.md) diff --git a/public/_markdown/en/guides/contextual-access.md b/public/_markdown/en/guides/contextual-access.md deleted file mode 100644 index f4546b8ae..000000000 --- a/public/_markdown/en/guides/contextual-access.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: "Contextual Access" -description: "Control who can see and use which tools, validate requests, and filter outputs — per user, per org, per project" ---- -Contextual AccessContextual Access - -# Contextual Access - - have broad access to tools, but enterprises need fine-grained control. Arcade’s contextual access lets you govern visibility and behavior at every stage of execution — who can see a tool, what inputs are allowed, and what comes back. - -You connect your own access-control, compliance, or transformation logic to the Arcade Engine. Your rules run inline during execution with no changes to your tools or required. - -## What you can do - -- **Control visibility** — Decide which tools each can see based on role, team, entitlement, or any signal from your IDP -- **Validate requests** — Enforce policies before execution (e.g., block certain domains, require org-scoped inputs) -- **Transform payloads** — Enrich inputs, inject secrets, redact PII from outputs, or filter content -- **Audit every interaction** — Route all calls through your security and logging infrastructure - -## How it works - -Contextual access is powered by **Logic Extensions**, a framework that allows you to hook in and add logic directly to Arcade’s execution flow. Arcade calls your server at three predefined hook points in the lifecycle: - -![Contextual Access Flow Diagram](/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fflow_diagram.60cf61a4.png&w=3840&q=75) - -Hook point - -When it runs - -What it can do - -**Access Hook** - -When listing tools for a user - -Allow or deny tools the user can see - -**Pre-Execution Hook** - -Before each tool execution - -Allow, deny, or modify inputs - -**Post-Execution Hook** - -After tool execution - -Allow, deny, or modify the output - -You only implement the hooks you need. Configure everything through the Dashboard. - -## Get started - -[How Hooks Work](/guides/contextual-access/how-hooks-work.md) -[Run an Extension](/guides/contextual-access/examples.md) -[Build Your Own](/guides/contextual-access/build-your-own.md) -[API Reference](/references/contextual-access-webhook-api.md) - -Last updated on February 10, 2026 - -[Arcade Registry Early Access](/en/resources/registry-early-access.md) -[How Hooks Work](/en/guides/contextual-access/how-hooks-work.md) diff --git a/public/_markdown/en/guides/contextual-access/api-reference.md b/public/_markdown/en/guides/contextual-access/api-reference.md deleted file mode 100644 index cf327f10e..000000000 --- a/public/_markdown/en/guides/contextual-access/api-reference.md +++ /dev/null @@ -1,7 +0,0 @@ -# API Reference - -Interactive Swagger documentation for the Logic Extensions webhook contract is available in the Arcade docs site. - -The OpenAPI 3.0 spec is available for download and code generation: - -- **GitHub:** [logic_extensions/http/1.0/schema.yaml](https://github.com/ArcadeAI/schemas/blob/main/logic_extensions/http/1.0/schema.yaml) diff --git a/public/_markdown/en/guides/contextual-access/build-your-own.md b/public/_markdown/en/guides/contextual-access/build-your-own.md deleted file mode 100644 index 48a644b7c..000000000 --- a/public/_markdown/en/guides/contextual-access/build-your-own.md +++ /dev/null @@ -1,342 +0,0 @@ ---- -title: "Build Your Own" -description: "Build your own Logic Extensions webhook server from the OpenAPI spec" ---- -[Contextual Access](/en/guides/contextual-access.md) -Build Your Own - -# Build Your Own Contextual Access Server - -This guide walks through implementing a webhook server that satisfies the Contextual Access Webhook contract. You can write it in any language — the contract is defined by an OpenAPI 3.0 spec. - -## OpenAPI spec - -The canonical contract is maintained in the [ArcadeAI/schemas](https://github.com/ArcadeAI/schemas)  repository: - -- **OpenAPI 3.0 spec:** [logic\_extensions/http/1.0/schema.yaml](https://github.com/ArcadeAI/schemas/blob/main/logic_extensions/http/1.0/schema.yaml) -   - -You can browse it interactively on the [API Reference](/references/contextual-access-webhook-api.md) page. - -## Generate server stubs - -Use the OpenAPI spec with standard code generators to scaffold your server: - -Language - -Generator - -**Go** - -[oapi-codegen](https://github.com/deepmap/oapi-codegen) - , [ogen](https://github.com/ogen-go/ogen) -  - -**Python** - -[openapi-generator](https://openapi-generator.tech/) - , [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator) -  - -**TypeScript** - -[openapi-typescript](https://github.com/drwpow/openapi-typescript) - , [openapi-generator](https://openapi-generator.tech/) -  - -**Example (Go with oapi-codegen):** - -```bash -curl -sL https://raw.githubusercontent.com/ArcadeAI/schemas/main/logic_extensions/http/1.0/schema.yaml -o schema.yaml -oapi-codegen -generate types,server -package server schema.yaml -``` - -The [logic-extensions-examples](https://github.com/ArcadeAI/logic-extensions-examples)  repo uses this approach — see `pkg/server/` for the generated types. - -## Endpoints to implement - -Your server needs to expose HTTP POST endpoints for each hook point you want to handle. Endpoint paths are fully configurable in the Dashboard — the defaults are shown below. - -Endpoint - -Hook point - -Required - -`POST /access` - -Access Hook - -Only if you need tool access control - -`POST /pre` - -Pre-Execution Hook - -Only if you need tool request validation/modification - -`POST /post` - -Post-Execution Hook - -Only if you need tool output filtering/modification - -You do not need to implement all endpoints. Only implement the ones you configure in the Dashboard. - -## Pre-Execution Hook - -**When:** Before each execution. - -### Request (Arcade sends to your server) - -Field - -Type - -Description - -`execution_id` - -string - -Correlates request/response across hooks - -`tool` - -object - -`name`, `toolkit`, `version` - -`inputs` - -object - -Tool inputs (name → value) - -`context` - -object - -`authorization` (OAuth per provider), `secrets` (key names only), `metadata`, `user_id` - -### Response (your server returns) - -Field - -Type - -Required - -Description - -`code` - -string - -Yes - -`OK`, `CHECK_FAILED`, or `RATE_LIMIT_EXCEEDED` - -`error_message` - -string - -No - -Shown to the agent when denying - -`override` - -object - -No - -`inputs` and/or `secrets` to modify the request - -## Post-Execution Hook - -**When:** After execution. - -### Request - -Field - -Type - -Description - -`execution_id` - -string - -Correlates with pre-execution hook - -`tool` - -object - -`name`, `toolkit`, `version` - -`inputs` - -object - -Tool inputs - -`success` - -boolean - -Whether the tool call succeeded - -`output` - -any - -The execution output (any JSON type) - -`execution_code` - -string - -Status from worker - -`execution_error` - -string - -Error message from tool call - -`context` - -object - -Same as pre-execution hook - -### Response - -Field - -Type - -Required - -Description - -`code` - -string - -Yes - -`OK`, `CHECK_FAILED`, or `RATE_LIMIT_EXCEEDED` - -`error_message` - -string - -No - -Shown to the agent when denying - -`override` - -object - -No - -`output` to replace the response returned to the agent - -## Access Hook - -**When:** When Arcade needs to know which a can see. Supports batch requests. - -### Request - -Field - -Type - -Description - -`user_id` - -string - -User to check - -`toolkits` - -object - -Map of toolkit name → toolkit info (tools, versions, requirements) - -### Response - -Return either an allow list or a deny list (not both): - -Field - -Type - -Description - -`only` - -object - -If present, **only** these tools are allowed (deny list ignored) - -`deny` - -object - -Tools listed here are denied (ignored if `only` is present) - -If you return neither field, the Arcade treats it as “no change” — all remain allowed at this hook. - -## Response codes (pre and post) - -Code - -Meaning - -`OK` - -Proceed; optional `override` is applied - -`CHECK_FAILED` - -Deny the operation; `error_message` is shown to the agent - -`RATE_LIMIT_EXCEEDED` - -Deny with rate-limit semantics - -## Authentication - -Arcade sends one of: - -- **Bearer:** `Authorization: Bearer ` (token from extension config) -- **mTLS:** Client certificate during TLS handshake; optional CA cert for server verification - -Configure the auth method when creating your extension in the Dashboard. - -## Failure handling and retries - -- **Timeout:** Configurable per extension (default 5s); can be overridden per hook. On timeout, the hook’s failure mode applies (Fail closed = block, Fail open = allow). -- **Retries:** Optional at the extension level; only for transient failures (5xx, timeout, connection error). 4xx responses are not retried. - -## Next steps - -- [API Reference](/references/contextual-access-webhook-api.md) - — Interactive Swagger documentation for the full schema -- [Run an extension](/guides/contextual-access/examples.md) - — Try the open-source example servers as reference -- [How Hooks Work](/guides/contextual-access/how-hooks-work.md) - — Understand execution order, phases, and failure modes - -Last updated on February 10, 2026 - -[Running an Extension](/en/guides/contextual-access/examples.md) -[MCP Gateways](/en/guides/mcp-gateways.md) diff --git a/public/_markdown/en/guides/contextual-access/examples.md b/public/_markdown/en/guides/contextual-access/examples.md deleted file mode 100644 index 6557f0ad0..000000000 --- a/public/_markdown/en/guides/contextual-access/examples.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -title: "Running a Server" -description: "Run open-source example webhook servers to get started with Contextual Access" ---- -[Contextual Access](/en/guides/contextual-access.md) -Running an Extension - -# Running an Server - -The fastest way to get started with Contextual Access is to run one of the open-source example servers. These are example Go implementations you can use as-is or as a starting point for your own server. - -## Example servers - -The [ArcadeAI/logic-extensions-examples](https://github.com/ArcadeAI/logic-extensions-examples)  repository contains runnable servers that implement the Contextual Access webhook contract. - -### Advanced Server (full-featured) - -A single server that demonstrates access control, PII redaction, A/B testing, and includes a browser-based dashboard for configuration. - -- **Location:** [examples/contextual\_access/advanced\_server/](https://github.com/ArcadeAI/logic-extensions-examples/tree/main/examples/contextual_access/advanced_server) -   -- **Features:** Access rules, PII redaction, A/B testing, web UI -- **Hook points:** Access, Pre-Execution, Post-Execution - -### Focused examples - -Minimal servers, each demonstrating one capability: - -Example - -Description - -Hook points - -**[user\_blocking](https://github.com/ArcadeAI/logic-extensions-examples/tree/main/examples/contextual_access/user_blocking)**  - -Block specific users from tools - -Access, Pre - -**[content\_filter](https://github.com/ArcadeAI/logic-extensions-examples/tree/main/examples/contextual_access/content_filter)**  - -Filter or block based on content - -Access, Pre, Post - -**[pii\_redactor](https://github.com/ArcadeAI/logic-extensions-examples/tree/main/examples/contextual_access/pii_redactor)**  - -Detect and redact PII in tool outputs - -Post - -**[ab\_testing](https://github.com/ArcadeAI/logic-extensions-examples/tree/main/examples/contextual_access/ab_testing)**  - -A/B and canary test tool versions - -Pre - -**[basic\_rules](https://github.com/ArcadeAI/logic-extensions-examples/tree/main/examples/contextual_access/basic_rules)**  - -Configurable rules for all hooks (YAML/config) - -Access, Pre, Post - -## Quick start - -Clone the repo and run a server: - -```bash -git clone https://github.com/ArcadeAI/logic-extensions-examples.git -cd logic-extensions-examples/ -``` - -**Advanced server (with web dashboard):** - -```go -go run ./examples/contextual_access/advanced_server -config ./examples/advanced_server/example-config.yaml -``` - -**Focused examples:** - -```go -# PII redactor (post-hook only) -go run ./examples/contextual_access/pii_redactor -types "email,ssn,credit_card" - -# User blocking (access + pre) -go run ./examples/contextual_access/user_blocking -block "user1,user2" - -# Content filter (access, pre, post) -go run ./examples/contextual_access/content_filter -config ./examples/content_filter/example-config.yaml - -# A/B testing (pre-hook) -go run ./examples/contextual_access/ab_testing -config ./examples/ab_testing/example-config.yaml - -# Basic rules (all hooks, configurable via YAML) -go run ./examples/contextual_access/basic_rules -config ./examples/basic_rules/example-config.yaml -``` - -Each server exposes `GET /health`, `POST /access`, `POST /pre`, and `POST /post` (or a subset, depending on which hook points it implements). - -## Connect to the Arcade - -Once your server is running: - -1. Open the **Arcade Dashboard** and navigate to **Contextual Access** -2. Click **Create Extension** and enter your server’s base URL and endpoint paths -3. Create **hook configurations** to attach the extension to the hook points you want - -See [How Hooks Work](/guides/contextual-access/how-hooks-work.md) for details on configuring extensions and hook points. - -## Next steps - -- [Build your own](/guides/contextual-access/build-your-own.md) - — Implement the webhook contract in any language -- [API Reference](/references/contextual-access-webhook-api.md) - — Interactive schema documentation for the webhook contract - -Last updated on February 10, 2026 - -[How Hooks Work](/en/guides/contextual-access/how-hooks-work.md) -[Build Your Own](/en/guides/contextual-access/build-your-own.md) diff --git a/public/_markdown/en/guides/contextual-access/how-hooks-work.md b/public/_markdown/en/guides/contextual-access/how-hooks-work.md deleted file mode 100644 index 00509359b..000000000 --- a/public/_markdown/en/guides/contextual-access/how-hooks-work.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -title: "How Hooks Work" -description: "Understand hook points, execution order, extensions, failure modes, and how the Arcade calls your webhook server" ---- -[Contextual Access](/en/guides/contextual-access.md) -How Hooks Work - -# How Hooks Work - -This page explains how Arcade invokes your external logic at each stage of execution, how you configure extensions and hook points, and how ordering and failures are handled. - -## Hook points - -Arcade defines three hook points. You do not create new ones, you configure behavior at the ones that exist: - -Hook point - -When it fires - -What you can do - -**Access Hook** - -When listing tools for a user - -Return an allow list or deny list to control which tools the user can see. Results are cached. - -**Pre-Execution Hook** - -Before each tool execution - -Allow, deny, or modify the request (inputs, secrets, routing). - -**Post-Execution Hook** - -After tool execution completes - -Allow, deny, or modify the output (e.g. PII redaction, content filtering). - -You only need to implement the hook points you care about. If you only need post-execution filtering, you only implement the post-execution endpoint. - -## Extensions - -A Webhook **extension** is the connection between Arcade and your webhook server. It stores: - -- **Endpoint URLs** for each hook point you implement (access, pre-execution, post-execution) -- **Authentication** method (Bearer token or mTLS) -- **Timeout** (default 5s), **retry**, and **cache** settings -- **Scope** — bound to an organization or a specific - -One extension can serve multiple hook configurations. When you change a URL or rotate a credential, you update the extension once and every hook that uses it picks up the change. - -### Scoping - -Scope - -Who can use it - -Use case - -**Organization** - -Any hook configuration in the organization - -Company-wide compliance, access control - -**Project** - -Only hook configurations in that project - -Project-specific validation or enrichment - -## Hook configurations - -A **hook configuration** attaches an extension to a specific hook point and controls its behavior: - -- **Hook point** — Which of the three hooks to run at -- **Extension** — Which extension to call (must be in scope) -- **Phase** — `before` or `after` (organization hooks only; see execution order below) -- **Priority** — Order within the same phase and extension scope (lower number = runs first) -- **Failure mode** — What happens when the webhook is unreachable - -You can have multiple configurations on the same hook point (e.g. an organization access check and a \-level access check). They all run in a defined order. - -## Execution order - -Hooks at both organization and scope run together. Arcade executes them in this fixed order: - -1. **Organization before** — Organization-scoped hook configs with phase `before`, ordered by priority -2. — Project-scoped hook configs, ordered by priority -3. **Organization after** — Organization-scoped hook configs with phase `after`, ordered by priority - -Organization hooks have a **phase** setting (`before` or `after`) that controls whether they run before or after hooks. Project hooks always run in the middle and do not have a phase setting. - -Within each group, lower priority numbers run first. Each hook sees the accumulated result from all previous hooks (e.g. modified inputs from an earlier hook). **Any denial stops the entire pipeline** and the operation fails immediately. - -Goal - -Scope - -Configuration - -Company-wide check runs first - -Organization - -Phase: `before` (default) - -Company-wide audit runs last - -Organization - -Phase: `after` - -Project-specific validation - -Project - -No phase needed - -## Failure modes - -When your webhook is unreachable (timeout, 5xx, connection error, etc), Arcade applies the hook’s failure mode: - -Mode - -Behavior - -**Fail closed** - -Block the operation. Use for security-critical checks (e.g. access control). - -**Fail open** - -Allow the operation to proceed. Use for non-critical enhancements (e.g. metrics, optional enrichment). - -Set the failure mode per hook configuration. Timeout can be overridden per hook; otherwise the extension default applies. - -## Setting up in the Dashboard - -You configure extensions and hook points from the **Arcade Dashboard**: - -1. **Create an extension** — Navigate to **Contextual Access**, click **Create Extension**, fill in endpoint URLs, auth, scope, and timeout/retry settings. -2. **Create a hook configuration** — Navigate to **Logic Extensions → Hook Points**, click **Create Hook Configuration**, select the extension, hook point, phase, priority, and failure mode. - -## Next steps - -- [Run an extension](/guides/contextual-access/examples.md) - — Try the open-source example servers -- [Build your own](/guides/contextual-access/build-your-own.md) - — Implement the webhook contract from the OpenAPI spec -- [API Reference](/references/contextual-access-webhook-api.md) - — Interactive schema documentation for the webhook contract - -Last updated on February 10, 2026 - -[Contextual Access](/en/guides/contextual-access.md) -[Running an Extension](/en/guides/contextual-access/examples.md) diff --git a/public/_markdown/en/guides/create-tools/error-handling.md b/public/_markdown/en/guides/create-tools/error-handling.md deleted file mode 100644 index b80058bf9..000000000 --- a/public/_markdown/en/guides/create-tools/error-handling.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: "Handle Errors" -description: "Learn how to implement robust error handling in your tools for better user experience" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -Handle errorsOverview - -# Handle Errors - -Learn how to implement robust error handling that improves user experience and enables smart retry behavior. Apply these patterns as you develop to make them more resilient and \-friendly in production. - -Robust error handling is crucial for building reliable that provide great experiences. Arcade provides a comprehensive error handling system that helps you manage different types of errors gracefully. - -- [Retry Tools with Improved Prompt](/guides/create-tools/error-handling/retry-tools.md) - -- [Provide Useful Tool Errors](/guides/create-tools/error-handling/useful-tool-errors.md) - - -Last updated on January 30, 2026 - -[Types of Tools](/en/guides/create-tools/improve/types-of-tools.md) -[Retry Tools with Improved Prompt](/en/guides/create-tools/error-handling/retry-tools.md) diff --git a/public/_markdown/en/guides/create-tools/error-handling/retry-tools.md b/public/_markdown/en/guides/create-tools/error-handling/retry-tools.md deleted file mode 100644 index cbcac49b9..000000000 --- a/public/_markdown/en/guides/create-tools/error-handling/retry-tools.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: "Retry Tools with Improved Prompt" -description: "Documentation for retrying tools with improved prompts in the Arcade Tool SDK" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -[Handle errors](/en/guides/create-tools/error-handling.md) -Retry Tools with Improved Prompt - -# RetryableToolError in Arcade - -Sometimes you may want to retry a tool call with additional to improve the call’s input parameters predicted by the model and try again. You can do this by raising a `RetryableToolError` within the tool. - -### Understanding RetryableToolError - -Raising the `RetryableToolError` is useful when you want to retry the tool call and give the model that is generating the tool call’s input parameters additional to improve the parameters for the next call. - -### When to Use RetryableToolError - -A RetryableToolError should be raised from within a if additional prompt content would likely improve the tool call outcome. - -### Example: Sending a Direct Message in Slack - -Below is an example of a that sends a direct message to a in Slack: - -1. If the specified user is not found, the retrieves a list of all valid inputs for the `user_name` parameter. -2. The then raises a `RetryableToolError` with the list of valid inputs. -3. This allows the model to generate a valid input for the `user_name` parameter in the next call iteration. - -```python -from typing import Annotated - -from slack_sdk import WebClient - -from arcade_core.errors import RetryableToolError -from arcade_mcp_server import Context, tool -from arcade_mcp_server.auth import Slack - - -@tool( - requires_auth=Slack( - scopes=[ - "chat:write", - "im:write", - "users.profile:read", - "users:read", - ], - ) -) -def send_dm_to_user( - context: Context, - user_name: Annotated[ - str, - "The Slack username of the person you want to message. Slack usernames are ALWAYS lowercase.", - ], - message: Annotated[str, "The message you want to send"], -) -> Annotated[str, "A confirmation message that the DM was sent"]: - """Send a direct message to a user in Slack.""" - - slackClient = WebClient(token=context.authorization.token) - - # Step 1: Retrieve the user's Slack ID based on their username - userListResponse = slackClient.users_list() - user_id = None - for user in userListResponse["members"]: - if user["name"].lower() == user_name.lower(): - user_id = user["id"] - break - - # If the user is not found, raise a RetryableToolError with a - # list of all valid inputs for the user_name parameter - if not user_id: - raise RetryableToolError( - "User not found", - developer_message=f"User with username '{user_name}' not found.", - additional_prompt_content=f"Valid values for user_name input param: {userListResponse}", - retry_after_ms=500, - ) - - # Step 2: Retrieve the DM channel ID with the user - im_response = slackClient.conversations_open(users=[user_id]) - dm_channel_id = im_response["channel"]["id"] - - # Step 3: Send the DM - slackClient.chat_postMessage(channel=dm_channel_id, text=message) - - return "DM sent successfully" -``` - -Last updated on January 30, 2026 - -[Overview](/en/guides/create-tools/error-handling.md) -[Provide Useful Tool Errors](/en/guides/create-tools/error-handling/useful-tool-errors.md) diff --git a/public/_markdown/en/guides/create-tools/error-handling/useful-tool-errors.md b/public/_markdown/en/guides/create-tools/error-handling/useful-tool-errors.md deleted file mode 100644 index 77535449f..000000000 --- a/public/_markdown/en/guides/create-tools/error-handling/useful-tool-errors.md +++ /dev/null @@ -1,259 +0,0 @@ ---- -title: "Provide Useful Tool Errors" -description: "Learn how to provide good errors when building tools with Arcade MCP" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -[Handle errors](/en/guides/create-tools/error-handling.md) -Provide Useful Tool Errors - -# Providing useful tool errors - -When building tools with Arcade , understanding error handling is crucial for creating robust and reliable tools. This guide covers everything you need to know about handling errors from a developer’s perspective. - -## Error handling philosophy - -Arcade’s error handling is designed to minimize boilerplate code while providing rich error information. In most cases, you don’t need to explicitly handle errors in your because the `@tool` decorator automatically adapts common exceptions into appropriate Arcade errors. - -## Error hierarchy - -Arcade uses a structured error hierarchy to categorize different types of errors. - -```python - -ToolkitError # (Abstract base class) -├── ToolkitLoadError # Occurs during MCP Server import -└── ToolError # (Abstract) - ├── ToolDefinitionError # Detected when tool is added to catalog - │ ├── ToolInputSchemaError # Invalid input parameter types/annotations - │ └── ToolOutputSchemaError # Invalid return type annotations - └── ToolRuntimeError # Errors during tool execution - ├── ToolSerializationError # (Abstract) - │ ├── ToolInputError # JSON to Python conversion fails - │ └── ToolOutputError # Python to JSON conversion fails - └── ToolExecutionError # Errors during tool execution - ├── RetryableToolError # Tool can be retried with extra context - ├── ContextRequiredToolError # Additional context needed before retry - ├── FatalToolError # Unhandled bugs in the tool implementation - └── UpstreamError # HTTP/API errors from external services - └── UpstreamRateLimitError # Rate limiting errors from service - -``` - -## Error adapters - -Error adapters automatically translate common exceptions (from `httpx`, `requests`, SDKs, etc.) into appropriate Arcade errors. This means zero boilerplate error handling code for you. To see which SDKs already have error adapters, see [arcade\_tdk/error\_adapters/**init**.py](https://github.com/ArcadeAI/arcade-ai/blob/main/libs/arcade-tdk/arcade_tdk/error_adapters/__init__.py). You may want to create your own error adapter or contribute an error adapter to the TDK. If so, see the [HTTP Error Adapter](https://github.com/ArcadeAI/arcade-ai/blob/main/libs/arcade-tdk/arcade_tdk/providers/http/error_adapter.py)  for an example. Ensure your error adapter implements the [ErrorAdapter protocol](https://github.com/ArcadeAI/arcade-ai/blob/main/libs/arcade-tdk/arcade_tdk/error_adapters/base.py) . - -### Automatic error adaptation - -For using `httpx` or `requests`, error adaptation happens automatically: - -```python -from typing import Annotated -from arcade_mcp_server import tool -import httpx - -@tool -def fetch_data( - url: Annotated[str, "The URL to fetch data from"], -) -> Annotated[dict, "The data fetched from the API endpoint"]: - """Fetch data from an API endpoint.""" - # No need to wrap in try/catch - Arcade handles HTTP errors automatically - response = httpx.get(url) - response.raise_for_status() # This will be adapted to UpstreamError if it raises - return response.json() -``` - -### Explicit error adapters - -For using specific SDKs, you can specify error adapters explicitly: - -```python -import googleapiclient -from typing import Annotated -from arcade_mcp_server import tool -from arcade_tdk.error_adapters import GoogleErrorAdapter - -@tool( - requires_auth=Google(scopes=["https://www.googleapis.com/auth/gmail.readonly"]), - error_adapters=[GoogleErrorAdapter] # note the tool opts-into the error adapter -) -def send_email( - num_emails: Annotated[int, "The number of emails to send"], -) -> Annotated[dict, "The emails sent using the Gmail API"]: - """Send an email using the Gmail API.""" - # Google API Client errors will be automatically adapted to Upstream Arcade errors for you - service = _build_gmail_service(context) - emails = service.users.messages().get( - userId="me", - id=num_emails - ).execute() # This will be adapted to UpstreamError if it raises - parsed_emails = _parse_emails(emails) - return parsed_emails -``` - -## When to raise errors explicitly - -While Arcade handles most errors automatically, there are specific cases where you should raise errors explicitly: - -### RetryableToolError - -Use when the LLM can retry the tool call with more to improve the call’s input parameters: - -```python -from typing import Annotated -from arcade_mcp_server import tool -from arcade_tdk.errors import RetryableToolError - -@tool(requires_auth=Reddit(scopes=["read"])) -def search_posts( - subreddit: Annotated[str, "The subreddit to search in"], - query: Annotated[str, "The query to search for"], -) -> Annotated[list[dict], "The posts found in the subreddit"]: - """Search for posts in a subreddit.""" - if is_invalid_subreddit(subreddit): - # additional_prompt_content should be provided back to the LLM - raise RetryableToolError( - "Please specify a subreddit name, such as 'python' or 'programming'", - additional_prompt_content=f"{subreddit} is an invalid subreddit name. Please specify a valid subreddit name" - ) - # ... rest of implementation -``` - -Learn more about [RetryableToolError](/guides/create-tools/error-handling/retry-tools.md). - -### ContextRequiredToolError - -Use when additional from the user or orchestrator is needed before the call can be retried by an LLM: - -```python -from os import path -from typing import Annotated -from arcade_mcp_server import tool -from arcade_tdk.errors import ContextRequiredToolError - -@tool -def delete_file(filename: Annotated[str, "The filename to delete"]) -> Annotated[str, "The filename that was deleted"]: - """Delete a file from the system.""" - if not os.path.exists(filename): - raise ContextRequiredToolError( - "File with provided filename does not exist", - additional_prompt_content=f"{filename} does not exist. Did you mean one of these: {get_valid_filenames()}", - ) - # ... deletion logic -``` - -### ToolExecutionError - -Use for unrecoverable, but known, errors when you want to provide specific error : - -```python -from typing import Annotated -from arcade_mcp_server import tool -from arcade_tdk.errors import ToolExecutionError - -@tool -def process_data(data_id: Annotated[str, "The ID of the data to process"]) -> Annotated[dict, "The processed data"]: - """Process data by ID.""" - try: - data = get_data_from_database(data_id) - except Exception as e: - raise ToolExecutionError("Database connection failed.") from e - # ... processing logic -``` - -### UpstreamError - -Use for custom handling of upstream service errors: - -```json -from arcade_mcp_server import tool -from arcade_tdk.errors import UpstreamError -import httpx - -@tool -def create_issue(title: str, description: str) -> dict: - """Create a GitHub issue.""" - try: - response = httpx.post("/repos/owner/repo/issues", json={ - "title": title, - "body": description - }) - response.raise_for_status() - except httpx.HTTPStatusError as e: - if e.response.status_code == 422: - raise UpstreamError( - "Invalid issue data provided. Check title and description.", - status_code=422 - ) from e - # Let other HTTP errors be handled automatically - raise - - return response.json() -``` - -## Common error scenarios - -### Tool definition errors - -These errors occur when your has invalid definitions and are caught when the tool is loaded: - -#### Invalid input parameter types - -```python -from arcade_mcp_server import tool - -@tool -def invalid_tool(data: tuple[str, str, str]) -> str: # ❌ Tuples not supported - """This will raise a ToolInputSchemaError.""" - return f"Hello {data[0]}" -``` - -#### Missing return type annotation - -```python -from arcade_mcp_server import tool - -@tool -def invalid_tool(name: str): # ❌ Missing return type - """This will raise a ToolOutputSchemaError.""" - return f"Hello {name}" -``` - -#### Invalid parameter annotations - -```python -from typing import Annotated -from arcade_mcp_server import tool - -@tool -def invalid_tool(name: Annotated[str, "desc1", "desc2", "extra"]) -> str: # ❌ Too many annotations - """This will raise a ToolInputSchemaError.""" - return f"Hello {name}" -``` - -### Runtime errors - -These errors occur during execution: - -#### Output type mismatch - -```python -from typing import Annotated -from arcade_mcp_server import tool - -@tool -def invalid_output(name: Annotated[str, "Name to greet"]) -> str: - """Says hello to a friend.""" - return ["hello", name] # ❌ Returns list instead of string -``` - -This will raise a `ToolOutputError` because the return type doesn’t match the annotation. - -## Handling tool errors in agents - -To learn more about how to handle tool errors in your , see the [Use Tools](/guides/tool-calling/error-handling.md) section. - -Last updated on January 30, 2026 - -[Retry Tools with Improved Prompt](/en/guides/create-tools/error-handling/retry-tools.md) -[Migrate from toolkits to MCP servers](/en/guides/create-tools/migrate-toolkits.md) diff --git a/public/_markdown/en/guides/create-tools/evaluate-tools.md b/public/_markdown/en/guides/create-tools/evaluate-tools.md deleted file mode 100644 index 412676a72..000000000 --- a/public/_markdown/en/guides/create-tools/evaluate-tools.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: "Evaluate Tools" -description: "Learn how to systematically test and improve your tools with Arcade's evaluation framework" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -Evaluate toolsOverview - -# Evaluate Tools - -Learn how to systematically test and improve your to ensure they work reliably in production. Use these techniques after you’ve built your initial tools to validate their performance and guide iterative improvements. - -- [Why evaluate tools?](/guides/create-tools/evaluate-tools/why-evaluate.md) - -- [Create an evaluation suite](/guides/create-tools/evaluate-tools/create-evaluation-suite.md) - -- [Run evaluations](/guides/create-tools/evaluate-tools/run-evaluations.md) - -- [Capture mode](/guides/create-tools/evaluate-tools/capture-mode.md) - -- [Comparative evaluations](/guides/create-tools/evaluate-tools/comparative-evaluations.md) - - -Last updated on January 30, 2026 - -[Organize your MCP server and tools](/en/guides/create-tools/tool-basics/organize-mcp-tools.md) -[Why evaluate tools?](/en/guides/create-tools/evaluate-tools/why-evaluate.md) diff --git a/public/_markdown/en/guides/create-tools/evaluate-tools/capture-mode.md b/public/_markdown/en/guides/create-tools/evaluate-tools/capture-mode.md deleted file mode 100644 index 04846eb2f..000000000 --- a/public/_markdown/en/guides/create-tools/evaluate-tools/capture-mode.md +++ /dev/null @@ -1,445 +0,0 @@ ---- -title: "Capture mode" -description: "Record tool calls without scoring to bootstrap test expectations" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -[Evaluate tools](/en/guides/create-tools/evaluate-tools.md) -Capture mode - -# Capture mode - -Capture mode records calls without evaluating them. Use it to bootstrap test expectations or debug model behavior. - -**Backward compatibility**: Capture mode works with existing evaluation suites. Simply add the `--capture` flag to any `arcade evals` command. No code changes needed. - -## When to use capture mode - -**Bootstrapping test expectations**: when you don’t know what calls to expect, run capture mode to see what the model actually calls. - -**Debugging model behavior**: when evaluations fail unexpectedly, capture mode shows exactly what the model is doing. - -**Exploring new **: when adding new tools, capture mode helps you understand how models interpret them. - -**Documenting usage**: create examples of how models use your tools in different scenarios. - -### Typical workflow - -PLAINTEXT - -``` -1. Create suite with empty expected_tool_calls - ↓ -2. Run: arcade evals . --capture -o captures.json - ↓ -3. Review captured tool calls in output file - ↓ -4. Copy tool calls into expected_tool_calls - ↓ -5. Add critics for validation - ↓ -6. Run: arcade evals . --details -``` - -## Basic usage - -### Create an evaluation suite without expectations - -Create a suite with test cases but empty `expected_tool_calls`: - -```python -from arcade_evals import EvalSuite, tool_eval - -@tool_eval() -async def capture_weather_suite(): - suite = EvalSuite( - name="Weather Capture", - system_message="You are a weather assistant.", - ) - - await suite.add_mcp_stdio_server(["python", "weather_server.py"]) - - # Add cases without expected tool calls - suite.add_case( - name="Simple weather query", - user_message="What's the weather in Seattle?", - expected_tool_calls=[], # Empty for capture - ) - - suite.add_case( - name="Multi-city comparison", - user_message="Compare the weather in Seattle and Portland", - expected_tool_calls=[], - ) - - return suite -``` - -### Run in capture mode - -Run evaluations with the `--capture` flag: - -```bash -arcade evals . --capture -o captures/weather.json -``` - -This creates `captures/weather.json` with all calls. - -### Review captured output - -Open the JSON file to see what the model called: - -```json -{ - "suite_name": "Weather Capture", - "model": "gpt-4o", - "provider": "openai", - "captured_cases": [ - { - "case_name": "Simple weather query", - "user_message": "What's the weather in Seattle?", - "tool_calls": [ - { - "name": "Weather_GetCurrent", - "args": { - "location": "Seattle", - "units": "fahrenheit" - } - } - ] - } - ] -} -``` - -If you set `--num-runs` > 1, each case also includes `runs`: - -```json -{ - "case_name": "Simple weather query", - "tool_calls": [{"name": "Weather_GetCurrent", "args": {"location": "Seattle"}}], - "runs": [ - {"tool_calls": [{"name": "Weather_GetCurrent", "args": {"location": "Seattle"}}]}, - {"tool_calls": [{"name": "Weather_GetCurrent", "args": {"location": "Seattle"}}]} - ] -} -``` - -Arcade keeps `tool_calls` for backward compatibility. It matches the first run. - -### Convert to test expectations - -Copy the captured calls into your evaluation suite: - -```python -from arcade_evals import ExpectedMCPToolCall, BinaryCritic - -suite.add_case( - name="Simple weather query", - user_message="What's the weather in Seattle?", - expected_tool_calls=[ - ExpectedMCPToolCall( - "Weather_GetCurrent", - {"location": "Seattle", "units": "fahrenheit"} - ) - ], - critics=[ - BinaryCritic(critic_field="location", weight=0.7), - BinaryCritic(critic_field="units", weight=0.3), - ], -) -``` - -## CLI options - -### Basic capture - -Record calls to JSON: - -```bash -arcade evals . --capture -o captures/baseline.json -``` - -### Multi-run capture - -Run each case multiple times: - -```bash -arcade evals . --capture --num-runs 3 --seed random -o captures/multi-run.json -``` - -`--seed` only affects OpenAI runs. Arcade ignores it for Anthropic. - -### Include conversation context - -Capture system messages and conversation history: - -```bash -arcade evals . --capture --include-context -o captures/detailed.json -``` - -Output includes: - -```json -{ - "case_name": "Weather with context", - "user_message": "What about the weather there?", - "system_message": "You are a weather assistant.", - "additional_messages": [ - {"role": "user", "content": "I'm traveling to Tokyo"}, - {"role": "assistant", "content": "Tokyo is a great city!"} - ], - "tool_calls": [...] -} -``` - -### Multiple formats - -Save captures in multiple formats: - -```bash -arcade evals . --capture -o captures/out.json -o captures/out.md -``` - -Markdown format is more readable for quick review: - -```markdown -## Weather Capture - -### Model: gpt-4o - -#### Case: Simple weather query - -**Input:** What's the weather in Seattle? - -**Tool Calls:** - -- `Weather_GetCurrent` - - location: Seattle - - units: fahrenheit -``` - -### Multiple providers - -Capture from multiple providers to compare behavior: - -```bash -arcade evals . --capture \ - -p openai:gpt-4o \ - -p anthropic:claude-sonnet-4-5-20250929 \ - -k openai:sk-... \ - -k anthropic:sk-ant-... \ - -o captures/comparison.json -``` - -## Capture with comparative tracks - -Capture from multiple sources to see how different implementations behave: - -```python -@tool_eval() -async def capture_comparative(): - suite = EvalSuite( - name="Weather Comparison", - system_message="You are a weather assistant.", - ) - - # Register different tool sources - await suite.add_mcp_server( - "http://weather-api-1.example/mcp", - track="Weather API v1" - ) - - await suite.add_mcp_server( - "http://weather-api-2.example/mcp", - track="Weather API v2" - ) - - # Capture will run against each track - suite.add_case( - name="get_weather", - user_message="What's the weather in Seattle?", - expected_tool_calls=[], - ) - - return suite -``` - -Run capture: - -```bash -arcade evals . --capture -o captures/apis.json -``` - -Output shows captures per track: - -```json -{ - "captured_cases": [ - { - "case_name": "get_weather", - "track_name": "Weather API v1", - "tool_calls": [ - {"name": "GetCurrentWeather", "args": {...}} - ] - }, - { - "case_name": "get_weather", - "track_name": "Weather API v2", - "tool_calls": [ - {"name": "Weather_Current", "args": {...}} - ] - } - ] -} -``` - -## Best practices - -### Start with broad queries - -Begin with open-ended prompts to see natural model behavior: - -```python -suite.add_case( - name="explore_weather_tools", - user_message="Show me everything you can do with weather", - expected_tool_calls=[], -) -``` - -### Capture edge cases - -Record model behavior on unusual inputs: - -```python -suite.add_case( - name="ambiguous_location", - user_message="What's the weather in Portland?", # OR or ME? - expected_tool_calls=[], -) -``` - -### Include context variations - -Capture with different conversation : - -```python -suite.add_case( - name="weather_from_context", - user_message="How about the weather there?", - additional_messages=[ - {"role": "user", "content": "I'm going to Seattle"}, - ], - expected_tool_calls=[], -) -``` - -### Capture multiple providers - -Compare how different models interpret your : - -```bash -arcade evals . --capture \ - -p openai:gpt-4o,gpt-4o-mini \ - -p anthropic:claude-sonnet-4-5-20250929 \ - -k openai:sk-... \ - -k anthropic:sk-ant-... \ - -o captures/models.json -o captures/models.md -``` - -## Converting captures to tests - -### Step 1: Identify patterns - -Review captured calls to find patterns: - -```json -// Most queries use "fahrenheit" -{"location": "Seattle", "units": "fahrenheit"} -{"location": "Portland", "units": "fahrenheit"} - -// Some use "celsius" -{"location": "Tokyo", "units": "celsius"} -``` - -### Step 2: Create base expectations - -Create expected calls based on patterns: - -```python -# Default to fahrenheit for US cities -ExpectedMCPToolCall("GetWeather", {"location": "Seattle", "units": "fahrenheit"}) - -# Use celsius for international cities -ExpectedMCPToolCall("GetWeather", {"location": "Tokyo", "units": "celsius"}) -``` - -### Step 3: Add critics - -Add critics to validate parameters. See [Critics](/guides/create-tools/evaluate-tools/create-evaluation-suite.md#critics) for options. - -### Step 4: Run evaluations - -Test with real evaluations: - -```bash -arcade evals . --details -``` - -### Step 5: Iterate - -Use failures to refine: - -- Adjust expected values -- Change critic weights -- Modify descriptions -- Add more test cases - -## Troubleshooting - -### No tool calls captured - -**Symptom:** empty `tool_calls` arrays - -**Possible causes:** - -1. Model didn’t call any -2. not properly registered -3. System message doesn’t encourage use - -**Solution:** - -```python -suite = EvalSuite( - name="Weather", - system_message="You are a weather assistant. Use the available weather tools to answer questions.", -) -``` - -### Missing parameters - -**Symptom:** some parameters are missing from captured calls - -**Explanation:** models may omit optional parameters. - -**Solution:** check if parameters have defaults in your schema. The evaluation framework applies defaults automatically. - -### Different results per provider - -**Symptom:** calls differ between OpenAI and Anthropic - -**Explanation:** providers interpret descriptions differently. - -**Solution:** use captures to understand provider-specific behavior, then create provider-agnostic tests. - -## Next steps - -- Learn about [comparative evaluations](/guides/create-tools/evaluate-tools/comparative-evaluations.md) - to compare sources -- [Create evaluation suites](/guides/create-tools/evaluate-tools/create-evaluation-suite.md) - with expectations - -Last updated on January 30, 2026 - -[Run evaluations](/en/guides/create-tools/evaluate-tools/run-evaluations.md) -[Comparative evaluations](/en/guides/create-tools/evaluate-tools/comparative-evaluations.md) diff --git a/public/_markdown/en/guides/create-tools/evaluate-tools/comparative-evaluations.md b/public/_markdown/en/guides/create-tools/evaluate-tools/comparative-evaluations.md deleted file mode 100644 index 6f5ca432f..000000000 --- a/public/_markdown/en/guides/create-tools/evaluate-tools/comparative-evaluations.md +++ /dev/null @@ -1,763 +0,0 @@ ---- -title: "Comparative evaluations" -description: "Compare different tool implementations with the same test cases" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -[Evaluate tools](/en/guides/create-tools/evaluate-tools.md) -Comparative evaluations - -# Comparative evaluations - -Comparative evaluations let you test how well AI models select and use tools from different, isolated sources. Each “track” represents a separate tool registry, allowing you to compare implementations side-by-side. - -## What are tracks? - -**Tracks are isolated registries** within a single evaluation suite. Each track has its own set of tools that are **not shared** with other tracks. This isolation lets you test how models perform when given different tool options for the same task. - -**Key concept**: Comparative evaluations test **selection** across different tool sets. Each track provides a different (set of tools) to the model. - -**Common use cases:** - -- **Compare providers**: Test Google Weather vs OpenWeather API -- **Implementation comparison**: Test different servers offering similar features -- **A/B testing**: Evaluate alternative designs - -### When to use comparative evaluations - -Use **comparative evaluations** when: - -- ✅ Testing multiple implementations of the same feature -- ✅ Comparing different providers -- ✅ Evaluating how models choose between different sets - -Use **regular evaluations** when: - -- ✅ Testing a single implementation -- ✅ Testing mixed tools from multiple sources in the same -- ✅ Regression testing - -### Testing mixed tool sources - -To test how multiple servers work **together** in the same (not isolated), use a regular evaluation and load multiple sources: - -```python -@tool_eval() -async def mixed_tools_eval(): - suite = EvalSuite(name="Mixed Tools", system_message="You are helpful.") - - # All tools available to the model in the same context - await suite.add_mcp_server("http://server1.example") - await suite.add_mcp_server("http://server2.example") - suite.add_tool_definitions([{"name": "CustomTool", ...}]) - - # Model can use any tool from any source - suite.add_case(...) - return suite -``` - -Alternatively, use an Arcade Gateway which aggregates from multiple sources. - -## Basic comparative evaluation - -### Register tools per track - -Create a suite and register for each track: - -```python -from arcade_evals import EvalSuite, tool_eval, ExpectedMCPToolCall, BinaryCritic - -@tool_eval() -async def weather_comparison(): - suite = EvalSuite( - name="Weather API Comparison", - system_message="You are a weather assistant.", - ) - - # Track A: Weather API v1 - await suite.add_mcp_server( - "http://weather-v1.example/mcp", - track="Weather v1" - ) - - # Track B: Weather API v2 - await suite.add_mcp_server( - "http://weather-v2.example/mcp", - track="Weather v2" - ) - - return suite -``` - -### Create comparative test case - -Add a test case with track-specific expectations: - -```python -suite.add_comparative_case( - name="get_current_weather", - user_message="What's the weather in Seattle?", -).for_track( - "Weather v1", - expected_tool_calls=[ - ExpectedMCPToolCall( - "GetWeather", - {"city": "Seattle", "type": "current"} - ) - ], - critics=[ - BinaryCritic(critic_field="city", weight=0.7), - BinaryCritic(critic_field="type", weight=0.3), - ], -).for_track( - "Weather v2", - expected_tool_calls=[ - ExpectedMCPToolCall( - "Weather_GetCurrent", - {"location": "Seattle"} - ) - ], - critics=[ - BinaryCritic(critic_field="location", weight=1.0), - ], -) -``` - -### Run comparative evaluation - -```bash -arcade evals . -``` - -Results show per-track scores: - -PLAINTEXT - -``` -Suite: Weather API Comparison - Case: get_current_weather - Track: Weather v1 -- Score: 100.00% -- PASSED - Track: Weather v2 -- Score: 100.00% -- PASSED -``` - -## Track registration - -### From MCP HTTP server - -```python -await suite.add_mcp_server( - url="http://localhost:8000", - headers={"Authorization": "Bearer token"}, - track="Production API", -) -``` - -### From MCP stdio server - -```bash -await suite.add_mcp_stdio_server( - command=["python", "server_v2.py"], - env={"API_KEY": "secret"}, - track="Version 2", -) -``` - -### From Arcade Gateway - -```python -await suite.add_arcade_gateway( - gateway_slug="weather-gateway", - track="Arcade Gateway", -) -``` - -### Manual tool definitions - -```python -suite.add_tool_definitions( - tools=[ - { - "name": "GetWeather", - "description": "Get weather for a location", - "inputSchema": {...}, - } - ], - track="Custom Tools", -) -``` - - must be registered before creating comparative cases that reference their tracks. - -## Comparative case builder - -The `add_comparative_case()` method returns a builder for defining track-specific expectations. - -### Basic structure - -```python -suite.add_comparative_case( - name="test_case", - user_message="Do something", -).for_track( - "Track A", - expected_tool_calls=[...], - critics=[...], -).for_track( - "Track B", - expected_tool_calls=[...], - critics=[...], -) -``` - -### Optional parameters - -Add conversation to comparative cases: - -```python -suite.add_comparative_case( - name="weather_with_context", - user_message="What about the weather there?", - system_message="You are helpful.", # Optional override - additional_messages=[ - {"role": "user", "content": "I'm going to Seattle"}, - ], -).for_track("Weather v1", ...).for_track("Weather v2", ...) -``` - -**Bias-aware message design:** - -Design `additional_messages` to avoid leading the model. Keep them neutral so you measure behavior, not prompt hints: - -```python -# ✅ Good - Neutral -additional_messages=[ - {"role": "user", "content": "I need weather information"}, - {"role": "assistant", "content": "I can help with that. Which location?"}, -] - -# ❌ Avoid - Tells the model which tool to call -additional_messages=[ - {"role": "user", "content": "Use the GetWeather tool for Seattle"}, -] -``` - -Keep messages generic so the model chooses naturally based on what is available in the track. - -### Different expectations per track - -Tracks can expose different and schemas. Because of that, you may need different critics per track: - -```python -suite.add_comparative_case( - name="search_query", - user_message="Search for Python tutorials", -).for_track( - "Google Search", - expected_tool_calls=[ - ExpectedMCPToolCall("Google_Search", {"query": "Python tutorials"}) - ], - critics=[BinaryCritic(critic_field="query", weight=1.0)], -).for_track( - "Bing Search", - expected_tool_calls=[ - ExpectedMCPToolCall("Bing_WebSearch", {"q": "Python tutorials"}) - ], - # Different schema, so validate the matching field for this track - critics=[BinaryCritic(critic_field="q", weight=1.0)], -) -``` - -## Complete example - -Here’s a full comparative evaluation: - -```python -from arcade_evals import ( - EvalSuite, - tool_eval, - ExpectedMCPToolCall, - BinaryCritic, - SimilarityCritic, -) - -@tool_eval() -async def search_comparison(): - """Compare different search APIs.""" - suite = EvalSuite( - name="Search API Comparison", - system_message="You are a search assistant. Use the available tools to search for information.", - ) - - # Register search providers (MCP servers) - await suite.add_mcp_server( - "http://google-search.example/mcp", - track="Google", - ) - - await suite.add_mcp_server( - "http://bing-search.example/mcp", - track="Bing", - ) - - # Mix with manual tool definitions - suite.add_tool_definitions( - tools=[{ - "name": "DDG_Search", - "description": "Search using DuckDuckGo", - "inputSchema": { - "type": "object", - "properties": { - "query": {"type": "string"} - }, - "required": ["query"] - } - }], - track="DuckDuckGo", - ) - - # Simple query - suite.add_comparative_case( - name="basic_search", - user_message="Search for Python tutorials", - ).for_track( - "Google", - expected_tool_calls=[ - ExpectedMCPToolCall("Search", {"query": "Python tutorials"}) - ], - critics=[BinaryCritic(critic_field="query", weight=1.0)], - ).for_track( - "Bing", - expected_tool_calls=[ - ExpectedMCPToolCall("WebSearch", {"q": "Python tutorials"}) - ], - critics=[BinaryCritic(critic_field="q", weight=1.0)], - ).for_track( - "DuckDuckGo", - expected_tool_calls=[ - ExpectedMCPToolCall("DDG_Search", {"query": "Python tutorials"}) - ], - critics=[BinaryCritic(critic_field="query", weight=1.0)], - ) - - # Query with filters - suite.add_comparative_case( - name="search_with_filters", - user_message="Search for Python tutorials from the last month", - ).for_track( - "Google", - expected_tool_calls=[ - ExpectedMCPToolCall( - "Search", - {"query": "Python tutorials", "time_range": "month"} - ) - ], - critics=[ - SimilarityCritic(critic_field="query", weight=0.7), - BinaryCritic(critic_field="time_range", weight=0.3), - ], - ).for_track( - "Bing", - expected_tool_calls=[ - ExpectedMCPToolCall( - "WebSearch", - {"q": "Python tutorials", "freshness": "Month"} - ) - ], - critics=[ - SimilarityCritic(critic_field="q", weight=0.7), - BinaryCritic(critic_field="freshness", weight=0.3), - ], - ).for_track( - "DuckDuckGo", - expected_tool_calls=[ - ExpectedMCPToolCall( - "DDG_Search", - {"query": "Python tutorials"} - ) - ], - critics=[ - SimilarityCritic(critic_field="query", weight=1.0), - ], - ) - - return suite -``` - -Run the comparison: - -```bash -arcade evals . --details -``` - -Output shows side-by-side results: - -PLAINTEXT - -``` -Suite: Search API Comparison - -Case: basic_search - Track: Google -- Score: 100.00% -- PASSED - Track: Bing -- Score: 100.00% -- PASSED - Track: DuckDuckGo -- Score: 100.00% -- PASSED - -Case: search_with_filters - Track: Google -- Score: 100.00% -- PASSED - Track: Bing -- Score: 85.00% -- WARNED - Track: DuckDuckGo -- Score: 90.00% -- WARNED -``` - -## Result structure - -Comparative results are organized by track: - -```python -{ - "Google": { - "model": "gpt-4o", - "suite_name": "Search API Comparison", - "track_name": "Google", - "rubric": {...}, - "cases": [ - { - "name": "basic_search", - "track": "Google", - "input": "Search for Python tutorials", - "expected_tool_calls": [...], - "predicted_tool_calls": [...], - "evaluation": { - "score": 1.0, - "result": "passed", - ... - } - } - ] - }, - "Bing": {...}, - "DuckDuckGo": {...} -} -``` - -## Mixing regular and comparative cases - -A suite can have both regular and comparative cases: - -```python -@tool_eval() -async def mixed_suite(): - suite = EvalSuite( - name="Mixed Evaluation", - system_message="You are helpful.", - ) - - # Register default tools - await suite.add_mcp_stdio_server(["python", "server.py"]) - - # Regular case (uses default tools) - suite.add_case( - name="regular_test", - user_message="Do something", - expected_tool_calls=[...], - ) - - # Register track-specific tools - await suite.add_mcp_server("http://api-v2.example", track="v2") - - # Comparative case - suite.add_comparative_case( - name="compare_versions", - user_message="Do something else", - ).for_track( - "default", # Uses default tools - expected_tool_calls=[...], - ).for_track( - "v2", # Uses v2 tools - expected_tool_calls=[...], - ) - - return suite -``` - -Use track name `"default"` to reference registered without a track. - -## Capture mode with tracks - -Capture calls from each track separately: - -```bash -arcade evals . --capture -o captures/comparison.json -``` - -Output includes track names: - -```json -{ - "captured_cases": [ - { - "case_name": "get_weather", - "track_name": "Weather v1", - "tool_calls": [ - {"name": "GetWeather", "args": {...}} - ] - }, - { - "case_name": "get_weather", - "track_name": "Weather v2", - "tool_calls": [ - {"name": "Weather_GetCurrent", "args": {...}} - ] - } - ] -} -``` - -## Multi-model comparative evaluations - -Combine comparative tracks with multiple models: - -```bash -arcade evals . -p openai:gpt-4o,gpt-4o-mini -p anthropic:claude-sonnet-4-5-20250929 -``` - -Multi-run flags (`--num-runs`, `--seed`, `--multi-run-pass-rule`) also work with comparative tracks. See [Run evaluations](/guides/create-tools/evaluate-tools/run-evaluations.md). - -Results show: - -- Per-track scores for each model -- Cross-track comparisons for each model -- Cross-model comparisons for each track - -Example output: - -PLAINTEXT - -``` -Suite: Weather API Comparison - -Model: gpt-4o - Case: get_weather - Track: Weather v1 -- Score: 100.00% -- PASSED - Track: Weather v2 -- Score: 100.00% -- PASSED - -Model: gpt-4o-mini - Case: get_weather - Track: Weather v1 -- Score: 90.00% -- WARNED - Track: Weather v2 -- Score: 95.00% -- PASSED - -Model: claude-sonnet-4-5-20250929 - Case: get_weather - Track: Weather v1 -- Score: 100.00% -- PASSED - Track: Weather v2 -- Score: 85.00% -- WARNED -``` - -## Best practices - -### Use descriptive track names - -Choose clear names that indicate what’s being compared: - -```python -# ✅ Good -track="Weather API v1" -track="OpenWeather Production" -track="Google Weather (Staging)" - -# ❌ Avoid -track="A" -track="Test1" -track="Track2" -``` - -### Keep test cases consistent - -Use the same user message and across tracks: - -```python -suite.add_comparative_case( - name="get_weather", - user_message="What's the weather in Seattle?", # Same for all tracks -).for_track("v1", ...).for_track("v2", ...) -``` - -### Adjust critics to track differences - -Different may have different parameter names or types: - -```python -.for_track( - "Weather v1", - expected_tool_calls=[ - ExpectedMCPToolCall("GetWeather", {"city": "Seattle"}) - ], - critics=[ - BinaryCritic(critic_field="city", weight=1.0), # v1 uses "city" - ], -).for_track( - "Weather v2", - expected_tool_calls=[ - ExpectedMCPToolCall("GetWeather", {"location": "Seattle"}) - ], - critics=[ - BinaryCritic(critic_field="location", weight=1.0), # v2 uses "location" - ], -) -``` - -### Start with capture mode - -Use capture mode to discover track-specific signatures: - -```bash -arcade evals . --capture -``` - -Then create expectations based on captured calls. - -### Test edge cases per track - -Different implementations may handle edge cases differently: - -```python -suite.add_comparative_case( - name="ambiguous_location", - user_message="What's the weather in Portland?", # OR or ME? -).for_track( - "Weather v1", - # v1 defaults to most populous - expected_tool_calls=[ - ExpectedMCPToolCall("GetWeather", {"city": "Portland", "state": "OR"}) - ], -).for_track( - "Weather v2", - # v2 requires disambiguation - expected_tool_calls=[ - ExpectedMCPToolCall("DisambiguateLocation", {"city": "Portland"}), - ExpectedMCPToolCall("GetWeather", {"city": "Portland", "state": "OR"}), - ], -) -``` - -## Troubleshooting - -### Track not found - -**Symptom:** `ValueError: Track 'TrackName' not registered` - -**Solution:** Register the track before adding comparative cases: - -```python -# ✅ Correct order -await suite.add_mcp_server(url, track="TrackName") -suite.add_comparative_case(...).for_track("TrackName", ...) - -# ❌ Wrong order - will fail -suite.add_comparative_case(...).for_track("TrackName", ...) -await suite.add_mcp_server(url, track="TrackName") -``` - -### Missing track expectations - -**Symptom:** Case runs against some tracks but not others - -**Explanation:** Comparative cases only run against tracks with `.for_track()` defined. - -**Solution:** Add expectations for all registered tracks: - -```python -suite.add_comparative_case( - name="test", - user_message="...", -).for_track("Track A", ...).for_track("Track B", ...) -``` - -### Tool name mismatches - -**Symptom:** “ not found” errors in specific tracks - -**Solution:** Check names in each track: - -```python -# List tools per track -print(suite.list_tool_names(track="Track A")) -print(suite.list_tool_names(track="Track B")) -``` - -Use the exact names from the output. - -### Inconsistent results across tracks - -**Symptom:** Same message produces different scores across tracks - -**Explanation:** This is expected. Different implementations may work differently. - -**Solution:** Adjust expectations and critics per track to for implementation differences. - -## Advanced patterns - -### Baseline comparison - -Compare new implementations against a baseline: - -```python -await suite.add_mcp_server( - "http://production.example/mcp", - track="Production (Baseline)" -) - -await suite.add_mcp_server( - "http://staging.example/mcp", - track="Staging (New)" -) -``` - -Results show deviations from baseline. - -### Progressive feature testing - -Test feature support across versions: - -```python -suite.add_comparative_case( - name="advanced_filters", - user_message="Search with advanced filters", -).for_track( - "v1", - expected_tool_calls=[], # Not supported -).for_track( - "v2", - expected_tool_calls=[ - ExpectedMCPToolCall("SearchWithFilters", {...}) - ], -) -``` - -### Tool catalog comparison - -Compare Arcade catalogs: - -```python -from arcade_core import ToolCatalog -from my_tools import weather_v1, weather_v2 - -catalog_v1 = ToolCatalog() -catalog_v1.add_tool(weather_v1, "Weather") - -catalog_v2 = ToolCatalog() -catalog_v2.add_tool(weather_v2, "Weather") - -suite.add_tool_catalog(catalog_v1, track="Python v1") -suite.add_tool_catalog(catalog_v2, track="Python v2") -``` - -## Next steps - -- [Create an evaluation suite](/guides/create-tools/evaluate-tools/create-evaluation-suite.md) - with tracks -- Use [capture mode](/guides/create-tools/evaluate-tools/capture-mode.md) - to discover track-specific calls -- [Run evaluations](/guides/create-tools/evaluate-tools/run-evaluations.md) - with multiple models and tracks - -Last updated on January 30, 2026 - -[Capture mode](/en/guides/create-tools/evaluate-tools/capture-mode.md) -[Types of Tools](/en/guides/create-tools/improve/types-of-tools.md) diff --git a/public/_markdown/en/guides/create-tools/evaluate-tools/create-evaluation-suite.md b/public/_markdown/en/guides/create-tools/evaluate-tools/create-evaluation-suite.md deleted file mode 100644 index 8565533e5..000000000 --- a/public/_markdown/en/guides/create-tools/evaluate-tools/create-evaluation-suite.md +++ /dev/null @@ -1,395 +0,0 @@ ---- -title: "Create an evaluation suite" -description: "Learn how to evaluate your tools using Arcade" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -[Evaluate tools](/en/guides/create-tools/evaluate-tools.md) -Create an evaluation suite - -# Create an evaluation suite - -Evaluation suites help you test whether AI models use your tools correctly. This guide shows you how to create test cases that measure selection and parameter accuracy. - -### Prerequisites - -- [Create an MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - -- Install the evaluation dependencies: - -### uv - -```bash -uv tool install 'arcade-mcp[evals]' -``` - -### pip - -```bash -pip install 'arcade-mcp[evals]' -``` - -### Create an evaluation file - -Navigate to your server directory and create a file starting with `eval_`: - -```bash -cd my_server -touch eval_server.py -``` - -Evaluation files must start with `eval_` and use the `.py` extension. The CLI automatically discovers these files. - -### Define your evaluation suite - -Create an evaluation suite that loads tools from your server and defines test cases: - -```python -from arcade_evals import ( - EvalSuite, - tool_eval, - ExpectedMCPToolCall, - BinaryCritic, -) - -@tool_eval() -async def weather_eval_suite() -> EvalSuite: - """Evaluate weather tool usage.""" - suite = EvalSuite( - name="Weather Tools", - system_message="You are a helpful weather assistant.", - ) - - # Load tools from your MCP server - await suite.add_mcp_stdio_server( - command=["python", "server.py"], - ) - - # Add a test case - suite.add_case( - name="Get weather for city", - user_message="What's the weather in Seattle?", - expected_tool_calls=[ - ExpectedMCPToolCall( - "Weather_GetCurrent", - {"location": "Seattle", "units": "celsius"} - ) - ], - critics=[ - BinaryCritic(critic_field="location", weight=0.7), - BinaryCritic(critic_field="units", weight=0.3), - ], - ) - - return suite -``` - -### Run the evaluation - -Set your OpenAI and run the evaluation: - -### Bash/Zsh (macOS/Linux) - -```bash -export OPENAI_API_KEY= -arcade evals . -``` - -### PowerShell (Windows) - -```powershell -$env:OPENAI_API_KEY="" -arcade evals . -``` - -The command discovers all `eval_*.py` files and executes them using OpenAI’s `gpt-4o` model by default. - -**Using different providers:** - -### Bash/Zsh (macOS/Linux) - -```bash -# Anthropic -export ANTHROPIC_API_KEY= -arcade evals . -p anthropic - -# Or specify API key directly -arcade evals . -p anthropic -k anthropic: - -# Multiple models -arcade evals . -p openai:gpt-4o,gpt-4o-mini - -# Multiple providers (repeat `-p`) -arcade evals . -p openai -p anthropic -k openai:sk-... -k anthropic:sk-ant-... - -# Multi-run evaluation -arcade evals . --num-runs 3 --seed random --multi-run-pass-rule majority -``` - -### PowerShell (Windows) - -```powershell -# Anthropic -$env:ANTHROPIC_API_KEY="" -arcade evals . -p anthropic - -# Or specify API key directly -arcade evals . -p anthropic -k anthropic: - -# Multiple models -arcade evals . -p openai:gpt-4o,gpt-4o-mini - -# Multiple providers (repeat `-p`) -arcade evals . -p openai -p anthropic -k openai:sk-... -k anthropic:sk-ant-... - -# Multi-run evaluation -arcade evals . --num-runs 3 --seed random --multi-run-pass-rule majority -``` - -See [Run evaluations](/guides/create-tools/evaluate-tools/run-evaluations.md) for all available options. - -### Understand the results - -Evaluation results show: - -- **Passed**: Score meets or exceeds the fail threshold (default: 0.8) -- **Failed**: Score falls below the fail threshold -- **Warned**: Score is between warn and fail thresholds (default: 0.9) - -Example output: - -PLAINTEXT - -``` -Suite: Weather Tools - Model: gpt-4o - PASSED Get weather for city -- Score: 100.00% - -Summary -- Total: 1 -- Passed: 1 -- Failed: 0 -``` - -Use `--details` to see critic feedback: - -```bash -arcade evals . --details -``` - -Detailed output includes per-critic scores: - -PLAINTEXT - -``` -PASSED Get weather for city -- Score: 100.00% - Details: - location: - Match: True, Score: 0.70/0.70 - units: - Match: True, Score: 0.30/0.30 -``` - -## Loading tools - -You can load from different sources. All methods are async and must be awaited in your `@tool_eval()` decorated function. - -### From MCP HTTP server - -Load tools from an HTTP or SSE server: - -```python -await suite.add_mcp_server( - url="http://localhost:8000", - headers={"Authorization": "Bearer token"}, # Optional - timeout=10, # Optional: Connection timeout (default: 10) - use_sse=False, # Optional: Use SSE transport (default: False) -) -``` - -The URL is automatically normalized (appends `/mcp` if not present). - -### From MCP stdio server - -Load tools from a stdio server process: - -```bash -await suite.add_mcp_stdio_server( - command=["python", "server.py"], - env={"API_KEY": "secret"}, # Optional: Environment variables - timeout=10, # Optional: Connection timeout (default: 10) -) -``` - -### From Arcade Gateway - -Load tools from an Arcade Gateway: - -```python -await suite.add_arcade_gateway( - gateway_slug="my-gateway", - arcade_api_key="your-api-key", # Optional: Defaults to ARCADE_API_KEY env var - arcade_user_id="user-id", # Optional: Defaults to ARCADE_USER_ID env var - base_url=None, # Optional: Override gateway URL - timeout=10, # Optional: Connection timeout (default: 10) -) -``` - -### Manual tool definitions - -Define tools manually using format: - -```python -suite.add_tool_definitions([ - { - "name": "Weather.GetCurrent", - "description": "Get current weather for a location", - "inputSchema": { - "type": "object", - "properties": { - "location": {"type": "string"}, - "units": { - "type": "string", - "enum": ["celsius", "fahrenheit"], - "default": "celsius" - }, - }, - "required": ["location"], - }, - } -]) -``` - -### Mixing tool sources - -You can load from multiple sources into the same suite: - -```python -# Load from multiple MCP servers -await suite.add_mcp_server("http://server1.example") -await suite.add_mcp_server("http://server2.example") - -# Mix with manual definitions -suite.add_tool_definitions([{"name": "CustomTool", ...}]) -``` - -All are accumulated in the suite’s registry and available to the model. - -## Expected tool calls - -Expected calls define what the model should predict. Use `ExpectedMCPToolCall` with \-style tool names: - -```python -ExpectedMCPToolCall( - "Weather_GetCurrent", - {"location": "Seattle", "units": "celsius"} -) -``` - - names are normalized for compatibility with model tool calling. Dots (`.`) become underscores (`_`). For example, `Weather.GetCurrent` becomes `Weather_GetCurrent`. - -## Critics - -Critics validate call parameters. Each critic type handles different validation needs: - -Critic - -Use case - -Example - -`BinaryCritic` - -Exact match - -`BinaryCritic(critic_field="user_id", weight=1.0)` - -`SimilarityCritic` - -Text similarity - -`SimilarityCritic(critic_field="message", weight=0.8)` - -`NumericCritic` - -Numeric range - -`NumericCritic(critic_field="temp", tolerance=2.0)` - -`DatetimeCritic` - -Time window - -`DatetimeCritic(critic_field="due", tolerance=timedelta(hours=1))` - -```python -from arcade_evals import BinaryCritic, SimilarityCritic - -critics=[ - BinaryCritic(critic_field="location", weight=0.7), - SimilarityCritic(critic_field="message", weight=0.3), -] -``` - -All weights are normalized proportionally to sum to 1.0. Use numeric values or `FuzzyWeight` (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`). - -## Multiple tool calls - -Test cases can include multiple expected calls: - -```python -suite.add_case( - name="Check weather in multiple cities", - user_message="What's the weather in Seattle and Portland?", - expected_tool_calls=[ - ExpectedMCPToolCall("Weather_GetCurrent", {"location": "Seattle"}), - ExpectedMCPToolCall("Weather_GetCurrent", {"location": "Portland"}), - ], -) -``` - -## Conversation context - -Add conversation history to test cases that require : - -```python -suite.add_case( - name="Weather based on previous location", - user_message="What about the weather there?", - expected_tool_calls=[ - ExpectedMCPToolCall("Weather_GetCurrent", {"location": "Tokyo"}), - ], - additional_messages=[ - {"role": "user", "content": "I'm planning to visit Tokyo next week."}, - {"role": "assistant", "content": "That sounds exciting! What would you like to know about Tokyo?"}, - ], -) -``` - -Use OpenAI message format for `additional_messages`. Arcade converts it automatically for Anthropic. - -## Rubrics - -Customize pass/fail thresholds with `EvalRubric`. Default: fail at 0.8, warn at 0.9. - -```python -from arcade_evals import EvalRubric - -suite = EvalSuite( - name="Strict Evaluation", - system_message="You are helpful.", - rubric=EvalRubric(fail_threshold=0.85, warn_threshold=0.95), -) -``` - -If you want stricter suites, increase thresholds (for example `fail_threshold=0.95`). For exploratory testing, lower them (for example `fail_threshold=0.6`). - -## Next steps - -- Learn how to [run evaluations with different providers](/guides/create-tools/evaluate-tools/run-evaluations.md) - -- Explore [capture mode](/guides/create-tools/evaluate-tools/capture-mode.md) - to record calls -- Compare sources with [comparative evaluations](/guides/create-tools/evaluate-tools/comparative-evaluations.md) - - -Last updated on February 10, 2026 - -[Why evaluate tools?](/en/guides/create-tools/evaluate-tools/why-evaluate.md) -[Run evaluations](/en/guides/create-tools/evaluate-tools/run-evaluations.md) diff --git a/public/_markdown/en/guides/create-tools/evaluate-tools/run-evaluations.md b/public/_markdown/en/guides/create-tools/evaluate-tools/run-evaluations.md deleted file mode 100644 index 539427256..000000000 --- a/public/_markdown/en/guides/create-tools/evaluate-tools/run-evaluations.md +++ /dev/null @@ -1,651 +0,0 @@ ---- -title: "Run evaluations" -description: "Learn how to run evaluations using Arcade" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -[Evaluate tools](/en/guides/create-tools/evaluate-tools.md) -Run evaluations - -# Run evaluations - -The `arcade evals` command discovers and executes evaluation suites with support for multiple providers, models, and output formats. - -**Backward compatibility**: All new features (multi-provider support, multi-run evaluation, capture mode, output formats) work with existing evaluation suites. No code changes required. - -## Basic usage - -Run all evaluations in the current directory: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . -``` - -The command searches for files starting with `eval_` and ending with `.py`. - -Show detailed results with critic feedback: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --details -``` - -Filter to show only failures: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --only-failed -``` - -## Multi-provider support - -### Single provider with default model - -Use OpenAI with default model (`gpt-4o`): - -### Bash/Zsh (macOS/Linux) - -```bash -export OPENAI_API_KEY=sk-... -arcade evals . -``` - -### PowerShell (Windows) - -```powershell -$env:OPENAI_API_KEY="sk-..." -arcade evals . -``` - -Use Anthropic with default model (`claude-sonnet-4-5-20250929`): - -### Bash/Zsh (macOS/Linux) - -```bash -export ANTHROPIC_API_KEY=sk-ant-... -arcade evals . --use-provider anthropic -``` - -### PowerShell (Windows) - -```powershell -$env:ANTHROPIC_API_KEY="sk-ant-..." -arcade evals . --use-provider anthropic -``` - -### Specific models - -Specify one or more models for a provider: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --use-provider openai:gpt-4o,gpt-4o-mini -``` - -### Multiple providers - -Compare performance across providers (repeat `--use-provider`): - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . \ - --use-provider openai:gpt-4o \ - --use-provider anthropic:claude-sonnet-4-5-20250929 \ - --api-key openai:sk-... \ - --api-key anthropic:sk-ant-... -``` - -When you specify multiple models, results show side-by-side comparisons. - -## API keys - - are resolved in the following order: - -Priority - -Format - -1\. Explicit flag - -`--api-key provider:key` (can repeat) - -2\. Environment - -`OPENAI_API_KEY`, `ANTHROPIC_API_KEY` - -3\. `.env` file - -`OPENAI_API_KEY=...`, `ANTHROPIC_API_KEY=...` - -Create a `.env` file in your directory to avoid setting keys in every terminal session. - -**Examples:** - -```bash -# results.txtresults.mdresults.htmlresults.json -# Single provider -arcade evals . --api-key openai:sk-... - -# Multiple providers -arcade evals . \ - --api-key openai:sk-... \ - --api-key anthropic:sk-ant-... -``` - -## Capture mode - -Record calls without scoring to bootstrap test expectations: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --capture --output captures/baseline.json -``` - -Include conversation in captured output: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --capture --include-context --output captures/detailed.json -``` - -Capture mode is useful for: - -- Creating initial test expectations -- Debugging model behavior -- Understanding call patterns - -See [Capture mode](/guides/create-tools/evaluate-tools/capture-mode.md) for details. - -## Output formats - -### Save results to files - -Specify output files with extensions - format is auto-detected: - -```bash -# results.txtresults.mdresults.htmlresults.json -# Single format -arcade evals . --output results.md - -# Multiple formats -arcade evals . --output results.md --output results.html --output results.json - -# All formats (no extension) -arcade evals . --output results -``` - -### Available formats - -Extension - -Format - -Description - -`.txt` - -Plain text - -Pytest-style output - -`.md` - -Markdown - -Tables and collapsible sections - -`.html` - -HTML - -Interactive report - -`.json` - -JSON - -Structured data for programmatic use - -(none) - -All formats - -Generates all four formats - -## Multi-run evaluation - -Run each case multiple times to measure stability: - -```bash -# results.txtresults.mdresults.htmlresults.json -# Run each case 5 times with random seeds -# Pass if the majority of runs pass -arcade evals . \ - --num-runs 5 \ - --seed random \ - --multi-run-pass-rule majority \ - --details \ - -o results.html -``` - -When you use `--num-runs` > 1, Arcade adds per-case `run_stats` to results. If your case has critics, Arcade also adds `critic_stats`. - -`--seed` only affects OpenAI runs. Arcade ignores it for Anthropic. - -Valid values: - -- `--seed`: `constant` (default), `random`, or a non-negative integer -- `--multi-run-pass-rule`: `last` (default), `mean`, or `majority` - -### What multi-run returns - -In JSON output, each case includes multi-run data: - -```json -# results.txtresults.mdresults.htmlresults.json -{ - "name": "Get weather for city", - "status": "passed", - "score": 93.0, - "passed": true, - "warning": false, - "run_stats": { - "num_runs": 3, - "scores": [1.0, 0.8, 1.0], - "mean_score": 0.93, - "std_deviation": 0.09, - "passed": [true, false, true], - "warned": [false, true, false], - "seed_policy": "random", - "run_seeds": [123, 456, 789], - "pass_rule": "majority", - "runs": [ - {"score": 1.0, "passed": true, "warning": false, "failure_reason": null, "details": []}, - {"score": 0.8, "passed": false, "warning": true, "failure_reason": null, "details": []}, - {"score": 1.0, "passed": true, "warning": false, "failure_reason": null, "details": []} - ] - }, - "critic_stats": { - "location": { - "run_scores": [0.7, 0.56, 0.7], - "weight": 0.7, - "mean_score_normalized": 0.95, - "std_deviation_normalized": 0.07, - "mean_score": 0.67, - "std_deviation": 0.05 - } - } -} -``` - -Arcade uses `--multi-run-pass-rule` to set the overall `status`, `passed`, and `warning` fields. It sets `run_stats.mean_score` to the average raw score across runs, and the top-level `score` is that aggregate score in percent. - -## Command options - -### Quick reference - -Flag - -Short - -Purpose - -Example - -`--use-provider` - -`-p` - -Select provider/model - -`-p openai:gpt-4o` - -`--api-key` - -`-k` - -Provider API key - -`-k openai:sk-...` - -`--capture` - -\- - -Record without scoring - -`--capture` - -`--details` - -`-d` - -Show critic feedback - -`--details` - -`--only-failed` - -`-f` - -Filter failures - -`--only-failed` - -`--output` - -`-o` - -Output file (repeatable) - -`-o results.md` - -`--include-context` - -\- - -Add messages to output - -`--include-context` - -`--max-concurrent` - -`-c` - -Parallel limit - -`-c 10` - -`--num-runs` - -`-n` - -Run each case multiple times - -`-n 5` - -`--seed` - -\- - -Seed policy for OpenAI runs - -`--seed random` - -`--multi-run-pass-rule` - -\- - -Pass/warn rule for multi-run - -`--multi-run-pass-rule majority` - -`--debug` - -\- - -Debug info - -`--debug` - -### `--use-provider`, `-p` - -Specify a provider and optional model list. Repeat the flag for multiple providers: - -```bash -# results.txtresults.mdresults.htmlresults.json ---use-provider [:,] -``` - -**Supported providers:** - -- `openai` (default: `gpt-4o`) -- `anthropic` (default: `claude-sonnet-4-5-20250929`) - -Anthropic model names include date stamps. Check [Anthropic’s model documentation](https://docs.anthropic.com/en/docs/about-claude/models)  for the latest model versions. - -**Examples:** - -```bash -# results.txtresults.mdresults.htmlresults.json -# Default model for provider -arcade evals . -p anthropic - -# Specific model -arcade evals . -p openai:gpt-4o-mini - -# Multiple models from same provider -arcade evals . -p openai:gpt-4o,gpt-4o-mini - -# Multiple providers (repeat `-p`) -arcade evals . -p openai:gpt-4o -p anthropic:claude-sonnet-4-5-20250929 -``` - -### `--api-key`, `-k` - -Provide explicitly (repeatable): - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . -k openai:sk-... -k anthropic:sk-ant-... -``` - -### `--capture` - -Enable capture mode to record calls without scoring: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --capture -``` - -### `--include-context` - -Include system messages and conversation history in output: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --include-context --output results.md -``` - -### `--output`, `-o` - -Specify one or more output files. Format is auto-detected from extension: - -```bash -# results.txtresults.mdresults.htmlresults.json -# Single format -arcade evals . -o results.md - -# Multiple formats (repeat flag) -arcade evals . -o results.md -o results.html - -# All formats (no extension) -arcade evals . -o results -``` - -### `--details`, `-d` - -Show detailed results including critic feedback: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --details -``` - -### `--only-failed`, `-f` - -Show only failed test cases: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --only-failed -``` - -### `--max-concurrent`, `-c` - -Set maximum concurrent evaluations: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --max-concurrent 10 -``` - -Default is 1 concurrent evaluation. - -### `--debug` - -Show debug information for troubleshooting: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --debug -``` - -Displays detailed error traces and connection information. - -## Understanding results - -Results are formatted based on evaluation type (regular, multi-model, or comparative) and selected flags. - -### Summary format - -Results show overall performance: - -PLAINTEXT - -``` -# results.txtresults.mdresults.htmlresults.json -Summary -- Total: 5 -- Passed: 4 -- Failed: 1 -``` - -**How flags affect output:** - -- `--details`: Adds per-critic breakdown for each case -- `--only-failed`: Filters to show only failed cases (summary shows original totals) -- `--include-context`: Includes system messages and conversation history -- `--num-runs`: Adds per-run statistics and aggregate scores -- Multiple models: Switches to comparison table format -- Comparative tracks: Shows side-by-side track comparison - -### Case results - -Each case displays status and score: - -PLAINTEXT - -``` -# results.txtresults.mdresults.htmlresults.json -PASSED Get weather for city -- Score: 100.00% -FAILED Weather with invalid city -- Score: 65.00% -``` - -### Detailed feedback - -Use `--details` to see critic-level analysis: - -PLAINTEXT - -``` -# results.txtresults.mdresults.htmlresults.json -Details: - location: - Match: False, Score: 0.00/0.70 - Expected: Seattle - Actual: Seatle - units: - Match: True, Score: 0.30/0.30 -``` - -### Multi-model results - -When using multiple models, results show comparison tables: - -PLAINTEXT - -``` -# results.txtresults.mdresults.htmlresults.json -Case: Get weather for city - Model: gpt-4o -- Score: 100.00% -- PASSED - Model: gpt-4o-mini -- Score: 95.00% -- WARNED -``` - -## Advanced usage - -### High concurrency for fast execution - -Increase concurrent evaluations: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --max-concurrent 20 -``` - -High concurrency may hit API rate limits. Start with default (1) and increase gradually. - -### Save comprehensive results - -Generate all formats with full details: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --details --include-context --output results -``` - -This creates: - -- `results.txt` -- `results.md` -- `results.html` -- `results.json` - -## Troubleshooting - -### Missing dependencies - -If you see `ImportError: MCP SDK is required`, install the full package: - -```bash -# results.txtresults.mdresults.htmlresults.json -pip install 'arcade-mcp[evals]' -``` - -For Anthropic support: - -```bash -# results.txtresults.mdresults.htmlresults.json -pip install anthropic -``` - -### Tool name mismatches - - names are normalized (dots become underscores). Check your tool definitions if you see unexpected names. - -### API rate limits - -Reduce `--max-concurrent` value: - -```bash -# results.txtresults.mdresults.htmlresults.json -arcade evals . --max-concurrent 2 -``` - -### No evaluation files found - -Ensure your evaluation files: - -- Start with `eval_` -- End with `.py` -- Contain functions decorated with `@tool_eval()` - -## Next steps - -- Explore [capture mode](/guides/create-tools/evaluate-tools/capture-mode.md) - for recording calls -- Learn about [comparative evaluations](/guides/create-tools/evaluate-tools/comparative-evaluations.md) - for comparing sources - -Last updated on February 10, 2026 - -[Create an evaluation suite](/en/guides/create-tools/evaluate-tools/create-evaluation-suite.md) -[Capture mode](/en/guides/create-tools/evaluate-tools/capture-mode.md) diff --git a/public/_markdown/en/guides/create-tools/evaluate-tools/why-evaluate.md b/public/_markdown/en/guides/create-tools/evaluate-tools/why-evaluate.md deleted file mode 100644 index 43c13e208..000000000 --- a/public/_markdown/en/guides/create-tools/evaluate-tools/why-evaluate.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: "Why evaluate tools?" -description: "Learn why evaluating your tools is important" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -[Evaluate tools](/en/guides/create-tools/evaluate-tools.md) -Why evaluate tools? - -# Why evaluate tools? - - evaluations ensure AI models use your tools correctly in production. Unlike traditional testing, evaluations measure two key aspects: - -1. ** selection**: Does the model choose the right tools for the task? -2. **Parameter accuracy**: Does the model provide correct arguments? - -Arcade’s evaluation framework helps you validate tool-calling capabilities before deployment, ensuring reliability in real-world applications. You can evaluate tools from servers, Arcade Gateways, or custom implementations. - -## What can go wrong? - -Without proper evaluation, AI models might: - -- **Misinterpret intents**, selecting the wrong -- **Provide incorrect arguments**, causing failures or unexpected behavior -- **Skip necessary calls**, missing steps in multi-step tasks -- **Make incorrect assumptions** about parameter defaults or formats - -## How evaluation works - -Evaluations compare the model’s actual calls with expected tool calls for each test case. - -### Scoring components - -1. ** selection**: Did the model choose the correct tool? -2. **Parameter evaluation**: Are the arguments correct? (evaluated by critics) -3. **Weighted scoring**: Each aspect has a weight that affects the final score - -### Evaluation results - -Each test case receives: - -- **Score**: Calculated from weighted critic scores, normalized proportionally (weights can be any positive value) -- **Status**: - - **Passed**: Score meets or exceeds fail threshold (default: 0.8) - - **Failed**: Score falls below fail threshold - - **Warned**: Score is between warn and fail thresholds (default: 0.9) - -Example output: - -PLAINTEXT - -``` -PASSED Get weather for city -- Score: 100.00% -WARNED Send message with typo -- Score: 85.00% -FAILED Wrong tool selected -- Score: 50.00% -``` - -## Next steps - -- [Create an evaluation suite](/guides/create-tools/evaluate-tools/create-evaluation-suite.md) - to start testing your -- [Run evaluations](/guides/create-tools/evaluate-tools/run-evaluations.md) - with multiple providers -- Explore [capture mode](/guides/create-tools/evaluate-tools/capture-mode.md) - to bootstrap test expectations -- Compare sources with [comparative evaluations](/guides/create-tools/evaluate-tools/comparative-evaluations.md) - - -## Advanced features - -Once you’re comfortable with basic evaluations, explore these advanced capabilities: - -### Capture mode - -Record calls without scoring to discover what models actually call. Useful for bootstrapping test expectations and debugging. [Learn more →](/guides/create-tools/evaluate-tools/capture-mode.md) - -### Comparative evaluations - -Test the same cases against different sources (tracks) with isolated registries. Compare how models perform with different tool implementations. [Learn more →](/guides/create-tools/evaluate-tools/comparative-evaluations.md) - -### Output formats - -Save results in multiple formats (txt, md, html, json) for reporting and analysis. Specify output files with extensions or use no extension for all formats. [Learn more →](/guides/create-tools/evaluate-tools/run-evaluations.md#output-formats) - -Last updated on January 30, 2026 - -[Overview](/en/guides/create-tools/evaluate-tools.md) -[Create an evaluation suite](/en/guides/create-tools/evaluate-tools/create-evaluation-suite.md) diff --git a/public/_markdown/en/guides/create-tools/improve/types-of-tools.md b/public/_markdown/en/guides/create-tools/improve/types-of-tools.md deleted file mode 100644 index 9a5940dc7..000000000 --- a/public/_markdown/en/guides/create-tools/improve/types-of-tools.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: "Types of Tools" -description: "Learn about Optimized and Unoptimized tools" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -Improve an existing toolkitTypes of Tools - -# Types of Tools - -Arcade offers two types of : - -- -- - -The distinction is merely a matter of how Arcade designs them. Both types of can be used seamlessly in the same way. No difference exists in their interfaces, the way they are called, or how you interact with them through the Arcade [Dashboard](https://api.arcade.dev/dashboard/)  or the Arcade [SDK clients](/references.md). - -Before we understand the two types, let’s first understand the background for why we need to differentiate between them. - -## Why LLMs perform poorly when calling HTTP APIs - -Traditionally, the HTTP APIs offered by upstream services such as GitHub, Google, Slack, etc., were designed to be consumed by human software engineers. When we expose such interfaces for LLMs to call as , they usually do not perform very well. - -One of the main reasons is that the data model of the HTTP API rarely matches the data model of an AI-powered chat interface. - -For instance, consider the following prompt: - -> “Send a DM to John asking about a update” - -The data model mismatches are: - -Dimension - -Chat interface - -Slack HTTP API - -Action - -Send message to a **_person_** - -Send message to a **_channel_** - -Argument - -`username = "John"` - -`channel_id = ???` - -In order to bridge the gap in the data models, the LLM has to make multiple API calls: - -1. Retrieve the current ’s Slack ID -2. Browse the list of to find John’s ID -3. Open a DM (direct message) channel between the and John, and get this channel’s ID -4. Send the message to the channel - -Even the most powerful LLMs usually perform poorly when they need to reason such complex workflows on the fly, not to mention the increased cost and risk of hallucinations. As a result, AI and chatbots that rely on HTTP APIs often end up being unreliable. - -## Optimized tools - -Arcade’s Optimized Servers are designed to match the typical data models expected in AI-powered chat interfaces and are subject to evaluation suites to ensure LLMs can safely use them. - -Following the example above, our Slack Server offers the [`Slack.SendMessage`](/resources/integrations/social/slack.md#slacksendmessage) , which accepts a `username` as argument, matching exactly both the action and argument value expected to be present in the LLM window. - -When a user says “Send a DM to John asking about a update”, the LLM can directly call the `Slack.SendMessage` with the `username` argument, and the tool will take care of the rest. - - dramatically improve the speed, reliability and cost-effectiveness of AI and chatbots. - -Since they require careful design and evaluation, Optimized tools take time and effort to build. Arcade understands that your Agent or chatbot project might need capabilities not yet covered by Arcade’s Optimized Servers. For this reason, Arcade also offers low-level Unoptimized (formerly known as Starter MCP Servers). - -## Unoptimized tools - -To provide your Agent or chatbot with more freedom to interact with upstream services, we offer Unoptimized Servers. - - are heavily influenced by the original API design. Each mirrors one HTTP endpoint. - -Although we redesign the tool name and argument descriptions to make them more suitable for LLMs, are still not optimized for LLM usage. Also, they are not subject to evaluation suites like . For those reasons, we recommend thoroughly evaluating each Unoptimized tool with your or chatbots before using it in production. - -When an Optimized tool covers your Agent’s needs, we recommend using it instead of an Unoptimized one. Use as a complement. Carefully engineer your prompts to ensure your can call them safely. - -Last updated on January 30, 2026 - -[Comparative evaluations](/en/guides/create-tools/evaluate-tools/comparative-evaluations.md) -[Overview](/en/guides/create-tools/error-handling.md) diff --git a/public/_markdown/en/guides/create-tools/migrate-toolkits.md b/public/_markdown/en/guides/create-tools/migrate-toolkits.md deleted file mode 100644 index bfcccb20e..000000000 --- a/public/_markdown/en/guides/create-tools/migrate-toolkits.md +++ /dev/null @@ -1,310 +0,0 @@ ---- -title: "Migrate from toolkits to MCP servers" -description: "Learn how to migrate your existing Arcade toolkit to the new MCP Server framework" ---- -[Create tools](/en/guides/create-tools/tool-basics.md) -Migrate from toolkits to MCP servers - -# Migrate from toolkits to MCP servers - -This guide helps you migrate your existing Arcade toolkit to the new Server framework. The `arcade-tdk` package has been deprecated in favor of `arcade-mcp-server`, and the `arcade-ai` CLI has been replaced by `arcade-mcp`. - -If you’re building a new server from scratch, check out the [Create an MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) guide instead. - -If you’re migrating an existing toolkit to a new server, it may be useful to read through our quickstart guide to get a sense of the new framework: [Create an MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - -## Understanding the changes - -Before migrating, it’s helpful to understand what has changed: - -### Terminology updates - -- **Workers** are now called **servers** or ** servers** -- **Toolkits** are now called **servers**, ** servers**, or depending on the - -### Package changes - -- **arcade-ai** (old CLI) → **arcade-** (new CLI) -- **arcade-tdk** (old development kit) → **arcade-\-server** (new framework) - -The new `arcade-mcp-server` framework should feel familiar if you’ve used `arcade-tdk`, but there are important differences to be aware of. - -## Update your dependencies - -Open your `pyproject.toml` file and update the dependencies: - -### Replace the main dependency - -Replace `arcade-tdk` with `arcade-mcp-server`: - -```toml -[project] -dependencies = [ - "arcade-mcp-server>=1.4.0,<2.0.0", - # ... other dependencies -] -``` - -### Update development dependencies - -If your toolkit used `arcade-ai` or `arcade-serve` as development dependencies, replace them with `arcade-mcp[all]`: - -```toml -[project.optional-dependencies] -dev = [ - "arcade-mcp[all]>=1.3.0,<2.0.0", - # ... other dev dependencies -] -``` - -### Install the updated dependencies - -Run the following command to install the updated dependencies and development dependencies: - -```bash -uv sync --extra dev -``` - -## Update your imports - -Replace all imports from `arcade-tdk` with imports from `arcade-mcp-server`. Most import paths have remained the same or have only slight variations: - -### Auth imports - -```python -# Before -from arcade_tdk.auth import Google - -# After -from arcade_mcp_server.auth import Google -``` - -### Tool decorator - -```python -# Before -from arcade_tdk import tool - -# After -from arcade_mcp_server import tool -``` - -### Error handling - -```python -# Before -from arcade_tdk.errors import ToolExecutionError - -# After -from arcade_mcp_server.exceptions import ToolExecutionError -``` - -### Context object - -Replace `ToolContext` with `Context`: - -```python -# Before -from arcade_tdk import ToolContext - -@tool -def my_tool(context: ToolContext) -> str: - """My tool that uses context""" - return "Hello" - -# After -from arcade_mcp_server import Context - -@tool -def my_tool(context: Context) -> str: - """My tool that uses context""" - return "Hello" -``` - -The `ToolContext` class is no longer used. Make sure to replace all instances of `ToolContext` with `Context` in your functions. - -## Create an entrypoint file - -Previously, you would run your toolkit using the `arcade serve` CLI command. Now, you need to create an entrypoint file that runs your server. This allows you to define your own startup and teardown logic for your . - -An is a Python file that creates and runs an `MCPApp` when invoked. `MCPApp` is the developer-facing API for creating and managing your server. - -### Option 1: Use the tool decorator - -You can register directly on the app using the `@app.tool` decorator: - -```python -#!/usr/bin/env python3 -"""My MCP Server""" - -import sys -from arcade_mcp_server import MCPApp - -app = MCPApp(name="my_server", version="1.0.0") - -@app.tool -def echo_hello() -> str: - """Tool that just says hello""" - return "Hello" - -@app.tool -def echo_goodbye() -> str: - """Tool that just says goodbye""" - return "Goodbye" - -if __name__ == "__main__": - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - app.run(transport=transport) -``` - -### Option 2: Register tools explicitly - -You can also use the standalone `@tool` decorator and register explicitly: - -```python -#!/usr/bin/env python3 -"""My MCP Server""" - -import sys -from arcade_mcp_server import MCPApp, tool - -@tool -def echo_hello() -> str: - """Tool that just says hello""" - return "Hello" - -@tool -def echo_goodbye() -> str: - """Tool that just says goodbye""" - return "Goodbye" - -app = MCPApp(name="my_server", version="1.0.0") -app.add_tool(echo_hello) -app.add_tool(echo_goodbye) - -if __name__ == "__main__": - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - app.run(transport=transport) -``` - -### Option 3: Register tools from modules - -If your old toolkit had many , you may want to use the `add_tools_from_module` method to register all your tools at once: - -```python -#!/usr/bin/env python3 -"""My MCP Server""" - -import sys -import my_module_with_tools - -from arcade_mcp_server import MCPApp - -app = MCPApp(name="my_server", version="1.0.0") -app.add_tools_from_module(my_module_with_tools) - -if __name__ == "__main__": - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - app.run(transport=transport) -``` - -For large toolkits with many , using `add_tools_from_module` is the recommended approach. This keeps your clean and maintainable. - -## Run your MCP server - -Replace the old `arcade serve` command with direct execution of your : - -```bash -# Before -arcade serve - -# After -uv run server.py -``` - -You can specify the transport type as a command line argument: - -```bash -# Run with stdio transport (default) -uv run server.py stdio - -# Run with HTTP transport -uv run server.py http -``` - -## Update deployment configuration - -The `arcade deploy` command still exists for deploying servers, but the deployment process has been simplified. - -### Before (with toolkits) - -Previously, you would deploy your toolkit using: - -```bash -arcade deploy -``` - -And configure your deployment with a `worker.toml` file. - -### After (with MCP servers) - -You still use `arcade deploy`, but you no longer need a `worker.toml` file: - -```bash -arcade deploy -``` - -The deployment configuration is now inferred from your `MCPApp` and structure. - -You’re no longer deploying a “worker” you’re deploying an server. The deployment process has been streamlined to require less configuration. - -## Quick reference - -Here’s a quick reference table for common changes: - -Old (toolkit) - -New (MCP server) - -`arcade-tdk` - -`arcade-mcp-server` - -`arcade-ai` - -`arcade-mcp` - -`arcade serve` - -`uv run server.py` - -`from arcade_tdk import tool` - -`from arcade_mcp_server import tool` - -`from arcade_tdk import ToolContext` - -`from arcade_mcp_server import Context` - -`from arcade_tdk.errors import ToolExecutionError` - -`from arcade_mcp_server.exceptions import ToolExecutionError` - -`worker.toml` - -Not needed - -## Next steps - -After migrating your toolkit to an server: - -- **Test your server**: Run your server locally and verify all work correctly -- **Update your CI/CD**: Update any automated workflows to use the new CLI and commands -- **Deploy your server**: Use `arcade deploy` to deploy your server -- **Configure clients**: Connect your server to [MCP clients](/guides/create-tools/tool-basics/call-tools-mcp.md) - like Claude Desktop, Cursor, or VS Code - -Last updated on January 30, 2026 - -[Provide Useful Tool Errors](/en/guides/create-tools/error-handling/useful-tool-errors.md) -[Secure Auth in Production](/en/guides/user-facing-agents/secure-auth-production.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics.md b/public/_markdown/en/guides/create-tools/tool-basics.md deleted file mode 100644 index d44f4db5b..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: "Build a Tool" -description: "Learn the fundamentals of building tools with Arcade's MCP Server framework" ---- -Create toolsBuild a toolOverview - -# Build a Tool - -Learn how to create custom tools that extend AI agents with powerful capabilities. Start here when you’re ready to build your first tool or need to add new functionality to your existing servers. - -Building tools with Arcade allows you to extend AI agents with custom capabilities. This section covers everything you need to know about creating powerful, reusable tools using the Model Context Protocol (). - -- [Compare MCP server types](/guides/create-tools/tool-basics/compare-server-types.md) - -- [Build an MCP Server to write custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) - -- [Create a tool with auth](/guides/create-tools/tool-basics/create-tool-auth.md) - -- [Create a tool with secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) - -- [Access runtime data](/guides/create-tools/tool-basics/runtime-data-access.md) - -- [Call tools from MCP clients](/guides/create-tools/tool-basics/call-tools-mcp.md) - -- [Organize your MCP server and tools](/guides/create-tools/tool-basics/organize-mcp-tools.md) - - -Last updated on January 30, 2026 - -[Get formatted tool definitions](/en/guides/tool-calling/custom-apps/get-tool-definitions.md) -[Compare MCP server types](/en/guides/create-tools/tool-basics/compare-server-types.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics/add-tool-metadata.md b/public/_markdown/en/guides/create-tools/tool-basics/add-tool-metadata.md deleted file mode 100644 index 9f40a79f1..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics/add-tool-metadata.md +++ /dev/null @@ -1,352 +0,0 @@ ---- -title: "Add metadata to your tools" -description: "Learn how to annotate your MCP tools with ToolMetadata so that MCP clients, policy engines, and tool-selection systems understand what each tool does." ---- -Create tools[Build a tool](/en/guides/create-tools/tool-basics.md) -Add metadata to your tools - -# Add metadata to your tools - -## Outcomes - -Annotate your tools with structured metadata so that clients, policy engines, and \-selection systems understand what each tool does and how it behaves. - -### You will Learn - -- What `ToolMetadata` is and how its three axes work -- How to classify by service domain -- How to describe tool behavior with operations and \-aligned flags -- How behavior flags map to annotations like `readOnlyHint` and `destructiveHint` - -### Prerequisites - -- [An MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - - -## What is ToolMetadata? - -`ToolMetadata` is a structured annotation you attach to your `@app.tool` and `@tool` functions. It has three independent axes, each of which is optional: - -- **Classification** — What type of service does this interface with? Used for tool discovery and search boosting when deployed to Arcade. -- **Behavior** — What happens when you run this tool? Describes operations (CRUD) and safety flags. These are projected to annotations (`readOnlyHint`, `destructiveHint`, etc.) for MCP clients. -- **Extras** — Arbitrary key/value pairs for custom logic like feature flags or routing info. - -Three systems consume this metadata: - -1. ** selection** — Classification feeds a scoring boost that surfaces relevant tools when callers provide categories that match the tool’s classification. -2. **Policy engines** — Behavior enables rules like “require human approval for DELETE operations” or “only allow read-only in this gateway.” -3. ** clients** — Behavior flags are projected to MCP annotations so clients like Claude Desktop, Cursor, and VS Code can make informed decisions about . - -## Add metadata to a tool - -### Import the metadata classes - -Add the following import to the top of your file: - -```python -# server.py -from arcade_mcp_server.metadata import ( - Behavior, - Classification, - Operation, - ServiceDomain, - ToolMetadata, -) -``` - -### Add `metadata` to the `@app.tool` decorator - -Pass a `ToolMetadata` instance to the `metadata` parameter of `@app.tool`. Here is a mutating that sends a message in Slack: - -```python -# server.py -@app.tool( - metadata=ToolMetadata( - classification=Classification( - service_domains=[ServiceDomain.MESSAGING], - ), - behavior=Behavior( - operations=[Operation.CREATE], - read_only=False, - destructive=False, - idempotent=False, - open_world=True, - ), - ), - requires_auth=Slack(scopes=["chat:write"]), -) -async def send_slack_message( - context: Context, - channel: Annotated[str, "The channel to send the message to"], - message: Annotated[str, "The message text"], -) -> dict: - """Send a message to a Slack channel.""" - ... -``` - -## Classification - -Classification answers one question: **“What type of software service does this interface with?”** - -It contains a single field: `service_domains`, a list of `ServiceDomain` enum values. - -### How to pick a ServiceDomain - -`ServiceDomain` classifies the **target service** whose data or functionality the provides access to. It is not about the tool’s action, the infrastructure used to reach the service, or how your organization uses the tool. - -Think of it this way: if you looked up the service on a software review site (G2, Capterra), what market category would it appear under? That’s the `ServiceDomain`. - -Three principles guide assignment: - -1. **Target, not infrastructure.** Classify by the service whose data the exposes, not the intermediary. A tool that uses SerpAPI to query Google Flights is `TRAVEL`, not `WEB_SCRAPING`. -2. **Service-level, not \-level.** All tools that connect to the same service share the same domain(s). A search tool and a send tool within Gmail both get `EMAIL` because Gmail is an email service. -3. **`None` is always valid.** If no enum value clearly fits, omit `classification` entirely. This is correct, not incomplete. - -Some services genuinely span multiple categories. A service gets multiple domains only when each domain independently applies. Don’t add a second domain just because a service has a minor feature in that space. - -### Available ServiceDomain values - -Value - -Description - -`PROJECT_MANAGEMENT` - -Project tracking, issue management, and work item software - -`CRM` - -Customer relationship management — contacts, deals, pipelines - -`EMAIL` - -Email services for sending, receiving, and managing messages - -`CALENDAR` - -Calendar and scheduling services - -`MESSAGING` - -Real-time team and business messaging platforms - -`DOCUMENTS` - -Document editing, wikis, and knowledge base platforms - -`CLOUD_STORAGE` - -Cloud file storage and sharing services - -`SPREADSHEETS` - -Spreadsheet and tabular data software - -`PRESENTATIONS` - -Presentation and slideshow software - -`DESIGN` - -UI/UX design and prototyping tools - -`SOURCE_CODE` - -Source code management, version control, and code review - -`PAYMENTS` - -Payment processing, invoicing, and billing - -`SOCIAL_MEDIA` - -Platforms where users publish content to a public audience through a social feed - -`VIDEO_HOSTING` - -Video hosting, streaming, and distribution platforms - -`MUSIC_STREAMING` - -Music streaming and playback platforms - -`CUSTOMER_SUPPORT` - -Help desk, ticketing, and customer service software - -`ECOMMERCE` - -Online shopping, product catalogs, and retail platforms - -`INCIDENT_MANAGEMENT` - -Incident response, on-call management, and operational alerting - -`WEB_SCRAPING` - -Web data extraction and crawling services - -`CODE_SANDBOX` - -Cloud code execution and sandboxed runtime environments - -`VIDEO_CONFERENCING` - -Video meeting and conferencing platforms - -`GEOSPATIAL` - -Maps, navigation, directions, and geocoding services - -`FINANCIAL_DATA` - -Financial market data and stock information services - -`TRAVEL` - -Travel search, flight and hotel booking platforms - -## Behavior - -Behavior answers: **“What happens when you run this ?”** It has two parts: **operations** and **\-aligned flags**. - -### Operations - -Operations classify the ’s effect on resources in the target system. Ask yourself: “After this tool runs, what changed?” - -Operation - -When to use - -`READ` - -The tool only observes. No state was created, modified, or removed. - -`CREATE` - -A resource that did not exist before now does (messages sent, files uploaded, records inserted). - -`UPDATE` - -An existing resource changed, but the resource identity persists (rename, archive, patch). - -`DELETE` - -A resource is no longer retrievable (permanent deletion, soft-delete, cancellation). - -`OPAQUE` - -The effect depends entirely on runtime inputs and cannot be predicted from the tool definition. - -Compound operations are valid. For example, an upsert uses `[Operation.CREATE, Operation.UPDATE]`, and a clone tool uses `[Operation.READ, Operation.CREATE]`. - -For with no external service and no resource effects, `operations` can be `None`. The combination of `read_only=True` and `open_world=False` gives policy engines the safety signal they need. - -### MCP-aligned flags - -These four booleans are projected directly to annotations. Always specify all four for production metadata. - -**`read_only`** — Does this only observe, with zero side effects? - -Set `True` when the never mutates any state in the target system. If there’s any doubt, set it to `False`. - -**`destructive`** — Can this cause irreversible data loss? - -Set `True` when the can delete or permanently destroy data. Be conservative — when in doubt, mark it `True`. Even soft-deletes that auto-purge should be `destructive=True`. The exception: archive operations that are fully reversible should use `Operation.UPDATE` with `destructive=False`. - -**`idempotent`** — If you call this twice with the same input, does the second call change anything? - -Set `True` when repeated calls with identical input produce no additional effect. A practical test: would an accidental retry cause a problem? If no, it’s idempotent. If it would create a duplicate, it’s not. - -**`open_world`** — Does this talk to anything outside the process? - -Set `True` for any that calls an external API, queries a database, or accesses a file system. Set `False` only for pure computation with no network, disk, or OS calls. - -### Flag-to-annotation mapping - -Behavior flag - -MCP annotation - -`read_only` - -`readOnlyHint` - -`destructive` - -`destructiveHint` - -`idempotent` - -`idempotentHint` - -`open_world` - -`openWorldHint` - -## Extras - -`extras` is a `dict[str, Any]` for arbitrary key/value pairs that downstream systems need but that don’t affect selection. Use it for things like IDP routing info, feature flags, compliance requirements, or rate limits. - -```python -metadata=ToolMetadata( - extras={"idp": "entraID", "requires_mfa": True}, -) -``` - -## Validation - -By default, `ToolMetadata` validates for logical contradictions when your server starts. This catches common mistakes early: - -Condition - -Why it’s a contradiction - -Mutating operations + `read_only=True` - -Can’t be read-only if it creates, updates, or deletes - -`OPAQUE` operation + `read_only=True` - -Can’t guarantee read-only when the effect is indeterminate - -`DELETE` operation + `destructive=False` - -Deletion is inherently destructive - -`ServiceDomain` present + `open_world=False` - -An external service implies open-world interaction - -If you hit a validation error for a legitimate edge case, you can bypass it: - -```python -metadata=ToolMetadata( - strict=False, - # ... your metadata ... -) -``` - -Only set `strict=False` when you understand and accept the apparent contradiction. In most cases, a validation error means the metadata needs to be corrected. - -## Key takeaways - -- **Three axes** — Classification (what service), Behavior (what effect), and Extras (custom data) are independent and optional. -- ** projection** — Behavior flags map directly to MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). -- **Classify the service, not the ** — `ServiceDomain` follows the target service, not the tool’s action or the infrastructure used to reach it. -- **Be explicit about behavior** — Always specify all four flags (`read_only`, `destructive`, `idempotent`, `open_world`) for production . -- **Built-in validation** — Strict mode catches contradictions like marking a DELETE as non-destructive. - -## Next steps - -- [Organize your MCP server and tools](/guides/create-tools/tool-basics/organize-mcp-tools.md) - — Structure your as it grows -- [Evaluate your tools](/guides/create-tools/evaluate-tools.md) - — Test reliability and performance -- [Handle errors](/guides/create-tools/error-handling.md) - — Return useful errors from your - -Last updated on February 10, 2026 - -[Organize your MCP server and tools](/en/guides/create-tools/tool-basics/organize-mcp-tools.md) -[Overview](/en/guides/create-tools/evaluate-tools.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics/build-mcp-server.md b/public/_markdown/en/guides/create-tools/tool-basics/build-mcp-server.md deleted file mode 100644 index 7b35cdc56..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics/build-mcp-server.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -title: "Creating an MCP Server with Arcade" -description: "Learn how to create, test, deploy, and publish a custom MCP Server with Arcade" ---- -Create tools[Build a tool](/en/guides/create-tools/tool-basics.md) -Build an MCP Server to write custom tools - -# Creating an MCP Server with Arcade - -The `arcade_mcp_server` package is the secure framework to build and run servers with your Arcade . It is easiest to use with the `arcade-mcp` package (Arcade’s CLI) which can scaffold your with all the necessary files and dependencies, configure MCP Clients to connect to your server, deploy your server to the cloud, and more. This guide walks you through the complete process of creating a custom MCP server with Arcade. - -## Outcomes - -Build and run a secure server with that you define. - -### You will Learn - -- How to run servers with Arcade using the [`arcade_mcp_server`](/references/mcp/python.md) - package -- How to use `arcade new` from the `arcade-mcp` CLI to create your server with all necessary files and dependencies. -- How to run your local Server with the Arcade CLI and register it with the so that your can find and use your . - -### Prerequisites - -- The [`uv` package manager](https://docs.astral.sh/uv/getting-started/installation/) - - -## Install the Arcade CLI - -In your terminal, run the following command to install the `arcade-mcp` package - Arcade’s CLI: - -### uv - -```bash -uv tool install arcade-mcp -``` - -This will install the Arcade CLI as a [uv tool](https://docs.astral.sh/uv/guides/tools/#installing-tools) , making it available system wide. - -### pip - -```bash -pip install arcade-mcp -``` - -## Create Your Server - -In your terminal, run the following command to scaffold a new Server called `my_server`: - -```bash -arcade new my_server -cd my_server/src/my_server -``` - -This generates a Python module with the following structure: - -```bash -my_server/ -├── .env.example -├── src/ -│ └── my_server/ -│ ├── __init__.py -│ └── server.py -└── pyproject.toml -``` - -1. **server.py** Main server file with MCPApp and example . It creates an `MCPApp`, defines tools with `@app.tool`, and will start the server with `app.run()` when the file is executed directly. -2. **pyproject.toml** Dependencies and configuration -3. **.env.example** Example `.env` file at the root containing a secret required by one of the generated in `server.py`. Arcade automatically discovers `.env` files by traversing upward from the current directory, so placing it at the project root makes it accessible from any subdirectory. Environments are loaded on server start, so **if you update the `.env` file, you will need to restart your server.** - -```python -# server.py -#!/usr/bin/env python3 -"""my_server MCP server""" - -import sys -from typing import Annotated - -import httpx -from arcade_mcp_server import Context, MCPApp -from arcade_mcp_server.auth import Reddit - -app = MCPApp(name="my_server", version="1.0.0", log_level="DEBUG") - - -@app.tool -def greet(name: Annotated[str, "The name of the person to greet"]) -> str: - """Greet a person by name.""" - return f"Hello, {name}!" - - -# To use this tool locally, you need to either set the secret in the .env file or as an environment variable -@app.tool(requires_secrets=["MY_SECRET_KEY"]) -def whisper_secret(context: Context) -> Annotated[str, "The last 4 characters of the secret"]: - """Reveal the last 4 characters of a secret""" - # Secrets are injected into the context at runtime. - # LLMs and MCP clients cannot see or access your secrets - # You can define secrets in a .env file. - try: - secret = context.get_secret("MY_SECRET_KEY") - except Exception as e: - return str(e) - - return "The last 4 characters of the secret are: " + secret[-4:] - -# To use this tool locally, you need to install the Arcade CLI (uv tool install arcade-mcp) -# and then run 'arcade login' to authenticate. -@app.tool(requires_auth=Reddit(scopes=["read"])) -async def get_posts_in_subreddit( - context: Context, subreddit: Annotated[str, "The name of the subreddit"] -) -> dict: - """Get posts from a specific subreddit""" - # Normalize the subreddit name - subreddit = subreddit.lower().replace("r/", "").replace(" ", "") - - # Prepare the httpx request - # OAuth token is injected into the context at runtime. - # LLMs and MCP clients cannot see or access your OAuth tokens. - oauth_token = context.get_auth_token_or_empty() - headers = { - "Authorization": f"Bearer {oauth_token}", - "User-Agent": "finally-mcp-server", - } - params = {"limit": 5} - url = f"https://oauth.reddit.com/r/{subreddit}/hot" - - # Make the request - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers, params=params) - response.raise_for_status() - - # Return the response - return response.json() - -# Run with specific transport -if __name__ == "__main__": - # Get transport from command line argument, default to "stdio" - # - "stdio" (default): Standard I/O for Claude Desktop, CLI tools, etc. - # Supports tools that require_auth or require_secrets out-of-the-box - # - "http": HTTPS streaming for Cursor, VS Code, etc. - # Does not support tools that require_auth or require_secrets unless the server is deployed - # using 'arcade deploy' or added in the Arcade Developer Dashboard with 'Arcade' server type - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - - # Run the server - app.run(transport=transport, host="127.0.0.1", port=8000) -``` - -## Setup the secrets in your environment - -Secrets are sensitive strings like passwords, , or other tokens that grant access to a protected resource or API. Arcade includes the “whisper\_secret” that requires a secret key to be set in your environment. If the secret is not set, the tool will return an error. - -### .env file - -You can create a `.env` file at your root directory and add your secret: - -```bash -# .env -MY_SECRET_KEY="my-secret-value" -``` - -Arcade automatically discovers `.env` files by traversing upward from the current directory through parent directories. This means you can place your `.env` file at the root (`my_server/`), and it will be found even when running your server from a subdirectory like `src/my_server/`. - -The generated includes a `.env.example` file at the project root with the secret key name and example value. You can rename it to `.env` to start using it. - -### Bash/Zsh (macOS/Linux) - -```bash -mv ../../.env.example ../../.env -``` - -### PowerShell (Windows) - -```powershell -Copy-Item .env.example .env -``` - -### Environment Variable - -You can set the environment variable in your terminal directly with this command: - -### Bash/Zsh (macOS/Linux) - -```bash -export MY_SECRET_KEY="my-secret-value" -``` - -### PowerShell (Windows) - -```powershell -$env:MY_SECRET_KEY="my-secret-value" -``` - -## Connect to Arcade to unlock authorized tool calling - -Since the Reddit tool accesses information only available to your Reddit , you’ll need to authorize it. For this, you’ll need to create an Arcade account and connect to it from the terminal, run: - -```bash -arcade login -``` - -Follow the instructions in your browser, and once you’ve finished, your terminal will be connected to your Arcade . - -## Run your MCP Server - -Run your Server using one of the following commands in your terminal: - -### stdio transport (default) - -```bash -uv run server.py stdio -``` - -When using the stdio transport, clients typically launch the as a subprocess. Because of this, the server may run in a different environment and not have access to secrets defined in your local `.env` file. Please refer to the [create a tool with secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) guide for more information. - -### http transport - -```bash -uv run server.py http -``` - -For HTTP transport, view your server’s API docs at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) . - -For security reasons, Local HTTP servers do not currently support tool-level authorization and secrets. If you need to use tool-level authorization or secrets locally, you should use the stdio transport and configure the Arcade API key and secrets in your connection settings. Otherwise, if you intend to expose your HTTP to the public internet with \-level authorization and secrets, please follow the [deploying to the cloud with Arcade Deploy](/guides/deployment-hosting/arcade-deploy.md) guide or the [on-prem MCP server](/guides/deployment-hosting/on-prem.md) guide for secure remote deployment. - -You should see output like this in your terminal: - -```bash -2025-11-03 13:46:11.041 | DEBUG | arcade_mcp_server.mcp_app:add_tool:242 - Added tool: greet -2025-11-03 13:46:11.042 | DEBUG | arcade_mcp_server.mcp_app:add_tool:242 - Added tool: whisper_secret -2025-11-03 13:46:11.043 | DEBUG | arcade_mcp_server.mcp_app:add_tool:242 - Added tool: get_posts_in_subreddit -INFO | 13:46:11 | arcade_mcp_server.mcp_app:299 | Starting my_server v1.0.0 with 3 tools -``` - -## Configure your MCP Client(s) - -Now you can connect Clients to your : - -### Cursor IDE - -```bash -# Configure Cursor to use your MCP server with the default transport (stdio) -arcade configure cursor - -# Configure Cursor to use your MCP server with the http transport -arcade configure cursor --transport http -``` - -### VS Code - -```bash -# Configure VS Code to use your MCP server with the default transport (stdio) -arcade configure vscode - -# Configure VS Code to use your MCP server with the http transport -arcade configure vscode --transport http -``` - -### Claude Desktop - -```bash -# Configure Claude Desktop to use your MCP server with the default transport (stdio) -arcade configure claude - -# Configure Claude Desktop to use your MCP server with the http transport -arcade configure claude --transport http -``` - -That’s it! Your server is running and connected to your AI assistant. - -## Key takeaways - -- **Minimal Setup** Create `MCPApp`, define with `@app.tool`, and run with `app.run()` -- **Direct Execution** Run your server file directly with `uv run` or `python` -- **Transport Flexibility** Works with both stdio and HTTP -- **Type Annotations** Use `Annotated` from the builtin typing library to provide descriptions to the language model for parameters and return values -- ** Docstrings** Use docstrings to provide a description of a tool to the language model -- **Command Line Arguments** Pass transport type as command line argument - -### Next steps - -- **Create custom that use auth**: [Learn how to create tools with authorization](/guides/create-tools/tool-basics/create-tool-auth.md) - -- **Create custom that use secrets**: [Learn how to create tools with secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) - -- **Learn the capabilities of the `Context` object**: [Understanding the Context object](/guides/create-tools/tool-basics/runtime-data-access.md) - -- **Evaluate your **: [Explore how to evaluate tool performance](/guides/create-tools/evaluate-tools/why-evaluate.md) - -- **Deploy your server**: [Learn how to deploy your MCP server](/guides/deployment-hosting/arcade-deploy.md) - - -Last updated on February 10, 2026 - -[Compare MCP server types](/en/guides/create-tools/tool-basics/compare-server-types.md) -[Create a tool with auth](/en/guides/create-tools/tool-basics/create-tool-auth.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics/call-tools-mcp.md b/public/_markdown/en/guides/create-tools/tool-basics/call-tools-mcp.md deleted file mode 100644 index a12c1cf36..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics/call-tools-mcp.md +++ /dev/null @@ -1,254 +0,0 @@ ---- -title: "Call tools from MCP clients" -description: "Learn how to call tools from MCP clients" ---- -Create tools[Build a tool](/en/guides/create-tools/tool-basics.md) -Call tools from MCP clients - -# Call tools from MCP clients - -## Outcomes - -Configure your clients to call tools from your . - -### You will Learn - -- How to configure your clients depending on the transport type. -- How to set the secrets for your server in your client’s configuration file. - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Arcade CLI](/references/arcade-cli.md) - -- [An MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - -- [uv package manager](https://docs.astral.sh/uv/getting-started/installation/) -   - -## Using the `arcade configure` command - -For popular clients, you can use the `arcade configure` command to configure your MCP client to call your MCP server. This will automatically add the MCP server to your client’s configuration file. By default, it will use the stdio transport. You must run this command from the directory of your . - -### Cursor IDE - -```bash -arcade configure cursor -``` - -### VS Code - -```bash -arcade configure vscode -``` - -### Claude Desktop - -```bash -arcade configure claude -``` - -You can customize a lot of the configuration passing options to `arcade configure`. For example, to change the transport type to http, you can pass the `--transport` (or `-t`) option: - -### Cursor IDE - -```bash -arcade configure cursor --transport http -``` - -### VS Code - -```bash -arcade configure vscode --transport http -``` - -### Claude Desktop - -Claude Desktop does not currently support the HTTP transport via JSON configuration files. - -### stdio specific configuration - -If you are using the stdio transport, `arcade configure` will assume the (the script that contains the `MCPApp` instance and calls `app.run()`) to your server is `server.py` and will set the working directory to the path of your entrypoint file. You can override this with the `--entrypoint` (or `-e`) option: - -Note that the `--entrypoint` accepts only the filename of the entrypoint file, not the path to the script. - -When using the stdio transport, `arcade configure` will automatically load the secrets from the `.env` file at the directory of your entrypoint file into the appropriate configuration file for your client. - -### Cursor IDE - -```bash -arcade configure cursor --entrypoint my_server.py -``` - -### VS Code - -```bash -arcade configure vscode --entrypoint my_server.py -``` - -### Claude Desktop - -```bash -arcade configure claude --entrypoint my_server.py -``` - -### HTTP specific configuration - -If you are using the streamable HTTP transport, `arcade configure` will assume the server is running on the default port `8000` and that the is running locally (in localhost). You can override this with the `--host` (or `-h`) and `--port` (or `-p`) options: - -### Cursor IDE - -Run from a different port: - -```bash -arcade configure cursor -t http --port 8000 -``` - -If you want to configure an server running on the Arcade Cloud, you can use the `--host` option: - -```bash -arcade configure cursor -t http --host arcade -``` - -### VS Code - -Run from a different port: - -```bash -arcade configure vscode -t http --port 8000 -``` - -If you want to configure an server running on the Arcade Cloud, you can use the `--host` option: - -```bash -arcade configure vscode -t http --host arcade -``` - -### Claude Desktop - -Claude Desktop does not currently support the HTTP transport via JSON configuration files. - -### Other configuration options - -If you have modified the default configuration of your client, or want to use a profile/workspace specific configuration file, then you can pass the `--config` (or `-c`) option to `arcade configure` to use your custom configuration file: - -### Cursor IDE - -```bash -arcade configure cursor --config /path/to/your/config.json -``` - -### VS Code - -```bash -arcade configure vscode --config /path/to/your/config.json -``` - -### Claude Desktop - -```bash -arcade configure claude --config /path/to/your/config.json -``` - -By default, `arcade configure` will use the current directory as the name of the server. You can override this with the `--name` (or `-n`) option: - -### Cursor IDE - -```bash -arcade configure cursor --name my_server -``` - -### VS Code - -```bash -arcade configure vscode --name my_server -``` - -### Claude Desktop - -```bash -arcade configure claude --name my_server -``` - -## Manually configuring your MCP client - -If your client is not supported by the `arcade configure` command, you can manually add the to your client’s configuration file. - -Each client has a different way of configuring MCP servers. This section covers the most common ways of configuring MCP servers adopted by the most popular MCP clients. However, you may find inconsistencies such as the need to specify the ’s type as its transport, or as “local” and “remote”. Some MCP clients will use “env” to pass environment variables, while others may use “environment” or “inputs”. Use this guide as a conceptual reference, but always refer to your MCP client’s documentation for the most up-to-date information. - -### stdio (default) - -When configuring your client using the stdio transport, you need to ensure that the is called using the right version of Python and within the correct working directory. For example, let’s pretend this is your setup: - -- Your virtual environment is located at `/path/to/your/project/.venv` -- Your is located at `/path/to/your/project` -- The entrypoint to your server is located at `/path/to/your/server.py` -- One of your requires the `MY_SECRET_KEY` environment variable to be set. -- Your secrets are stored in the `/path/to/your/project/.env` file - -Then, your client’s configuration file should look like this: - -```bash -{ - "mcpServers": { - "my_server": { - "command": "/path/to/your/project/.venv/bin/python", - "args": ["/path/to/your/project/server.py", "stdio"], - "env": { - "MY_SECRET_KEY": "my-secret-value" - } - } - } -} -``` - -This will ensure that the command used by the client to start the is the correct version of Python and that the environment variables are set correctly. - -### Streamable HTTP - -When configuring your client using the Streamable HTTP transport, ensure the is started on the correct port and from the correct working directory. For example, if your setup is: - -- Your virtual environment is located at `/path/to/your/project/.venv` -- Your server is located at `/path/to/your/project/server.py` -- Your secrets are stored in the `/path/to/your/project/.env` file - -Activate the virtual environment: - -### Bash/Zsh (macOS/Linux) - -```bash -source /path/to/your/project/.venv/bin/activate -``` - -### PowerShell (Windows) - -```powershell -. "/path/to/your/project/.venv/Scripts/Activate.ps1" -``` - -run the server using the http transport. The secrets will be loaded from the `.env` file that is located at the directory of your : - -```powershell -uv run server.py http -``` - -Then, your client’s configuration file should look like this: - -```powershell -{ - "mcpServers": { - "my_server": { - "url": "http://127.0.0.1:8000", - "transport": "http" - } - } -} -``` - -For security reasons, Local HTTP servers do not currently support managed authorization and secrets. If you need to use authorization or secrets, you should use the stdio transport and configure the Arcade API key and secrets in your connection settings. If you intend to expose your HTTP to the public internet, please follow the [on-prem MCP server](/guides/deployment-hosting/on-prem.md) guide for secure remote deployment. - -Last updated on February 10, 2026 - -[Access runtime data](/en/guides/create-tools/tool-basics/runtime-data-access.md) -[Organize your MCP server and tools](/en/guides/create-tools/tool-basics/organize-mcp-tools.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics/compare-server-types.md b/public/_markdown/en/guides/create-tools/tool-basics/compare-server-types.md deleted file mode 100644 index 91c68c23c..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics/compare-server-types.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: "Compare Server Types" -description: "Compare the different types of MCP servers" ---- -Create tools[Build a tool](/en/guides/create-tools/tool-basics.md) -Compare MCP server types - -# Compare MCP Server Types - -Depending on the transport you use and where you want to run your server, Arcade offers different functionalities and features. Below is a comparison of the different server types and their capabilities. - -Transport - -Deployment - -Tools without requirements - -Tools with secrets - -Tools with auth (single-user) - -Tools with auth (multi-user) - -stdio - -local - -✅ - -✅ - -✅ - -❌ - -http - -local ([unprotected](/resources/glossary.md#unprotected-mcp-servers) -) - -✅ - -❌ - -❌ - -❌ - -http - -remote ([unprotected](/resources/glossary.md#unprotected-mcp-servers) -) - -✅ - -❌ - -❌ - -❌ - -http - -local ([protected](/resources/glossary.md#protected-mcp-servers) -) - -✅ - -✅ - -✅ - -✅ - -http - -remote ([protected](/resources/glossary.md#protected-mcp-servers) -) - -✅ - -✅ - -✅ - -✅ - -http - -Arcade Cloud - -✅ - -✅ - -✅ - -✅ - -Last updated on January 30, 2026 - -[Overview](/en/guides/create-tools/tool-basics.md) -[Build an MCP Server to write custom tools](/en/guides/create-tools/tool-basics/build-mcp-server.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics/create-tool-auth.md b/public/_markdown/en/guides/create-tools/tool-basics/create-tool-auth.md deleted file mode 100644 index da22297cf..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics/create-tool-auth.md +++ /dev/null @@ -1,326 +0,0 @@ ---- -title: "Add user authorization to your MCP tools" -description: "Learn how to build custom MCP tools that require user authorization using Arcade, auth providers, and OAuth." ---- -Create tools[Build a tool](/en/guides/create-tools/tool-basics.md) -Create a tool with auth - -# Add user authorization to your MCP tools - -## Outcomes - -Create and use an that requires OAuth to access Reddit, prompting to authorize the action when called. Jump to [Example Code](#example-code) to see the complete code. - -### You will Learn - -- How work. -- How to add user authorization to your custom with Arcade. -- How to use the to make authenticated requests to external APIs. -- How to use the Reddit to authorize your . - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [uv package manager](https://docs.astral.sh/uv/getting-started/installation/) -   -- [Create an MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - - -An is the service that issues and manages the OAuth token your uses. It is the identity “source of truth” your tool integrates with to request permissions and obtain OAuth tokens. - -When you create a with `requires_auth`, you specify which provider to use. In this example, **`arcade_mcp_server.auth.Reddit` specifies the Reddit .** - -## How auth providers work during execution - -1. When the is invoked, Arcade checks if the has already authorized the scopes required by the tool. - -2. If the ’s requirements are not met, Arcade initiates the provider-specific OAuth flow for the requested scopes. - - > 2a). The is presented with a URL to complete the OAuth challenge. The user will need to visit this URL and log in and explicitly grant consent for the action to be performed on their behalf. This is the “OAuth challenge”. - - > 2b). The provider issues the token, and Arcade will securely inject it into the ’s [`Context`](/guides/create-tools/tool-basics/runtime-data-access.md) on its next invocation. The client and the LLM will never see the token. - - > 2c). The needs to be re-invoked - this time its requirements will be met. - -3. The is executed, and uses the token injected into its `Context` to call the provider’s API (e.g., `https://oauth.reddit.com`), without the LLM or client ever seeing the token. - - -The defines where the identity lives, what permissions are available (scopes), and how tokens are issued and refreshed. In code, it’s the class you pass to `requires_auth` (e.g., `Reddit(scopes=["read"])`) that encodes the OAuth details for that service. - -## Add user authorization to your MCP tools - -### Import the necessary modules - -Create a new Python file, e.g., `auth_tools.py`, and import the necessary modules and classes: - -```python -# auth_tools.py -import sys -from typing import Annotated - -import httpx -from arcade_mcp_server import Context, MCPApp -from arcade_mcp_server.auth import Reddit -``` - -### Create the MCP Server - -Create an instance of the `MCPApp` class: - -```python -# auth_tools.py -app = MCPApp(name="auth_example", version="1.0.0", log_level="DEBUG") -``` - -### Define your MCP tool - -Now, define your using the `@app.tool` decorator and specify the required authorization, in this case, by using [Arcade’s Reddit auth provider](/references/auth-providers/reddit.md). - -Specifying the `requires_auth` parameter in the `@app.tool` decorator indicates that the needs authorization. In this example, we’re using the `Reddit` with the `read` scope: - -```python -# auth_tools.py -@app.tool( - requires_auth=Reddit( - scopes=["read"] - ) -) -async def get_posts_in_subreddit( - context: Context, subreddit: Annotated[str, "The name of the subreddit"] -) -> dict: - """Get posts from a specific subreddit""" - # Normalize the subreddit name - subreddit = subreddit.lower().replace("r/", "").replace(" ", "") - - # Prepare the httpx request - # OAuth token is injected into the context at runtime. - # LLMs and MCP clients cannot see or access your OAuth tokens. - oauth_token = context.get_auth_token_or_empty() - headers = { - "Authorization": f"Bearer {oauth_token}", - "User-Agent": "mcp_server-mcp-server", - } - params = {"limit": 5} - url = f"https://oauth.reddit.com/r/{subreddit}/hot" - - # Make the request - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers, params=params) - response.raise_for_status() - - # Return the response - return response.json() -``` - -To use this , you need to install the Arcade CLI and run ‘arcade login’ to authenticate: - -```bash -uv tool install arcade-mcp -arcade login -``` - -Arcade offers a number of [built-in auth providers](/references/auth-providers.md), including Slack, Google, and GitHub. You can also require authorization with a custom , using the `OAuth2` class, a subclass of the `ToolAuthorization` class: - -```python -@app.tool( - requires_auth=OAuth2( - id="your-oauth-provider-id", - scopes=["scope1", "scope2"], - ) -) -``` - -The `OAuth2` class requires an `id` parameter to identify the in the . For built-in providers like `Slack`, you can skip the `id`. The Arcade Engine will find the right provider using your credentials. While you can specify an `id` for built-in providers, only do this for private that won’t be shared. - -### Specify OAuth scopes - -Specify the OAuth scopes you need for your . In this example, you already are using the `read` scope, but you can specify multiple scopes for more permissions (like `identity`): - -```python -# auth_tools.py -# Multiple scopes for more permissions -@app.tool(requires_auth=Reddit(scopes=["read", "identity"])) -async def identity_tool(context: Context) -> dict: - """Tool that accesses user identity.""" - pass -``` - -Scopes are defined by the , and they vary between providers. Each service exposes its own set of scopes that determine what your can access. You’ll need to review the provider’s documentation to see which scopes are available and what permissions each one grants. - -### Run the MCP Server - -```python -# auth_tools.py -if __name__ == "__main__": - # Get transport from command line argument, default to "stdio" - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - - print(f"Starting auth example server with {transport} transport") - print("Prerequisites:") - print(" 1. Install: uv tool install arcade-mcp") - print(" 2. Login: arcade login\n") - - # Run the server - # - "stdio" (default): Standard I/O transport - # - "http": HTTP streamable transport - app.run(transport=transport, host="127.0.0.1", port=8000) -``` - -Verify your Server can run successfully by executing the following command in your terminal: - -```bash -uv run auth_tools.py stdio -``` - -## Configure your MCP Client(s) - -Now you can connect your server to apps that support MCP Clients, like AI assistants and IDEs. : - -### Claude Desktop - -```bash -arcade configure claude -``` - -### Cursor IDE - -```bash -arcade configure cursor -``` - -### VS Code - -```bash -arcade configure vscode -``` - -Now restart your Client and try calling your . - -### Handle authorization - -Since the requires authorization, the first time a uses it, the user will go through an OAuth flow to authorize the tool to act on their behalf. Once the user has completed the OAuth flow, the tool will need to be re-invoked. See the [how auth providers work during execution](#how-auth-providers-work-during-execution) section for more details. - -## How it works - -Arcade manages the OAuth flow, and provides the token via `context.get_auth_token_or_empty()`. Arcade also remembers the ’s authorization tokens, so they won’t have to go through the authorization process again until the token is revoked by the user. - -### Accessing OAuth tokens - -To get the authorization token, use the `context.get_auth_token_or_empty()` method. - -```python -# auth_tools.py -# Get the token (returns empty string if not authenticated) -oauth_token = context.get_auth_token_or_empty() - -# Use token in API requests -headers = { - "Authorization": f"Bearer {oauth_token}", - "User-Agent": "my-app", -} -``` - -### Making Authenticated API Requests - -Use the OAuth token with httpx or other HTTP clients: - -```python -# auth_tools.py -import httpx - -async with httpx.AsyncClient() as client: - response = await client.get( - "https://oauth.reddit.com/api/endpoint", - headers={"Authorization": f"Bearer {oauth_token}"} - ) - response.raise_for_status() - return response.json() -``` - -## Security Best Practices - -- Never log tokens: OAuth tokens should never be logged or exposed -- Use appropriate scopes: Request only the scopes your actually needs - -## Key takeaways - -- **OAuth Support:** Arcade handles OAuth flows and token management -- **Secure Token Injection:** Tokens are injected into at runtime -- **Scope Management:** Specify exactly which permissions your needs -- **Provider Support:** Multiple OAuth providers available out of the box -- ** Privacy:** LLMs and clients never see OAuth tokens - -## Next steps - -- Try adding more authorized -- Explore how to handle different authorization providers and scopes -- Learn how to [build a tool with secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) - - -## Example Code - -```python -# auth_tools.py -#!/usr/bin/env python3 -import sys -from typing import Annotated - -import httpx -from arcade_mcp_server import Context, MCPApp -from arcade_mcp_server.auth import Reddit - -# Create the app -app = MCPApp(name="auth_example", version="1.0.0", log_level="DEBUG") - - -# To use this tool, you need to use the Arcade CLI (uv pip install arcade-mcp) -# and run 'arcade login' to authenticate. -@app.tool(requires_auth=Reddit(scopes=["read"])) -async def get_posts_in_subreddit( - context: Context, subreddit: Annotated[str, "The name of the subreddit"] -) -> dict: - """Get posts from a specific subreddit""" - # Normalize the subreddit name - subreddit = subreddit.lower().replace("r/", "").replace(" ", "") - - # Prepare the httpx request - # OAuth token is injected into the context at runtime. - # LLMs and MCP clients cannot see or access your OAuth tokens. - oauth_token = context.get_auth_token_or_empty() - headers = { - "Authorization": f"Bearer {oauth_token}", - "User-Agent": "mcp_server-mcp-server", - } - params = {"limit": 5} - url = f"https://oauth.reddit.com/r/{subreddit}/hot" - - # Make the request - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers, params=params) - response.raise_for_status() - - # Return the response - return response.json() - - -if __name__ == "__main__": - # Get transport from command line argument, default to "stdio" - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - - print(f"Starting auth example server with {transport} transport") - print("Prerequisites:") - print(" 1. Install: uv tool install arcade-mcp") - print(" 2. Login: arcade login\n") - - # Run the server - # - "stdio" (default): Standard I/O transport - # - "http": HTTP streamable transport - app.run(transport=transport, host="127.0.0.1", port=8000) - -``` - -Last updated on January 30, 2026 - -[Build an MCP Server to write custom tools](/en/guides/create-tools/tool-basics/build-mcp-server.md) -[Create a tool with secrets](/en/guides/create-tools/tool-basics/create-tool-secrets.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics/create-tool-secrets.md b/public/_markdown/en/guides/create-tools/tool-basics/create-tool-secrets.md deleted file mode 100644 index dc1aac720..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics/create-tool-secrets.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -title: "Create an MCP tool with secrets" -description: "Learn how to build custom MCP tools that require secrets using Arcade" ---- -Create tools[Build a tool](/en/guides/create-tools/tool-basics.md) -Create a tool with secrets - -# Create an MCP tool with secrets - -## Outcomes - -Build an tool that can read a secret from and return a masked confirmation string. Jump to [Example Code](#example-code) to see the complete code. - -### You will Learn - -- How to read secrets from environment and `.env` files securely using a tool’s . -- How to configure secrets in the Arcade Dashboard. - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Arcade CLI](/get-started/quickstarts/call-tool-agent.md) - -- [An MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - -- [uv package manager](https://docs.astral.sh/uv/getting-started/installation/) -   - -Secrets are sensitive strings like passwords, , or other tokens that grant access to a protected resource or API. You can also use secrets to provide other static configuration values your needs, such as parameters for calling a remote API. - -## Why use secrets in your tools? - -Secrets enable you to securely deploy a function that requires sensitive information at runtime. And while these can be consumed directly from the runtime environment inside your function, this becomes very inconvenient, expensive, hard to maintain, and insecure when deploying at scale. - -For example, if your tool requires an to use an external service, but only _after_ doing some computationally expensive work, you need to ensure that the API key is present _before_ the computationally expensive work is done. The function below would fail if the API key is not present. - -```python -import os - -def my_tool(task: str) -> str: - result = expensive_computation(task) - - API_KEY = os.getenv("API_KEY") - - # The line below will fail if the API key is not present - success = upload_result_to_service(result, API_KEY) - - if success: - return "Result uploaded successfully" - else: - return "Failed to upload result" -``` - -We can work around this by carefully checking for the before doing the computationally expensive work, of course, but this is error prone and difficult to maintain, and you may only become aware of the issue after deploying multiple instances of your server. - -Arcade provides a way to securely store and access secrets inside your tools in a way that is easy to manage across multiple instances of your servers, and that will prevent the from running if the secret is not provided. In this guide, you’ll learn how to use secrets in your custom Arcade tools. - -## Store your secret - -Depending on where you’re running your server, you can store your secret in a few different ways. - -### .env file - -You can create a `.env` file in your root directory and add your secret: - -```bash -# .env -MY_SECRET_KEY="my-secret-value" -``` - -The includes a `.env.example` file at the project root with the secret key name and example value. You can rename it to `.env` to start using it. - -### Bash/Zsh (macOS/Linux) - -```bash -mv .env.example .env -``` - -### PowerShell (Windows) - -```powershell -Copy-Item .env.example .env -``` - -Using a `.env` file is okay for local development, but you should use the Arcade Dashboard or Arcade CLI for production deployments. - -### Arcade Dashboard - -You can store your secret in the Arcade Dashboard by: - -- Navigating to the “Secrets” section of the Arcade Dashboard -- Clicking the “Add Secret” button -- Entering the secret ID and value -- Clicking the “Create” button - -This will make the secret available to your server, when deployed to Arcade. - -The Arcade Dashboard will make the secret available to your server when it is deployed. Secrets set in the Arcade Dashboard are not available to your when it is running locally. - -### Arcade CLI - -You can set the secret in your terminal directly with this command: - -```bash -arcade secret set MY_SECRET_KEY="my-secret-value" -``` - -The Arcade CLI will make the secret available to your server when it is deployed, because it upserts the secret into the Arcade Cloud. Secrets set in the Arcade CLI are not available to your when it is running locally. - -### Environment Variable - -You can set the environment variable in your terminal directly with this command: - -### Bash/Zsh (macOS/Linux) - -```bash -export MY_SECRET_KEY="my-secret-value" -``` - -### PowerShell (Windows) - -```powershell -$env:MY_SECRET_KEY="my-secret-value" -``` - -Using environment variables is okay for local development, but you should use the Arcade Dashboard or Arcade CLI for production deployments. - -### Using secrets with stdio transport - -When using the stdio transport, clients typically launch the as a subprocess. Because of this, the server may run in a different environment and not have access to secrets defined in your local `.env` file or your terminal environment variables. - -To ensure your stdio server has access to the secrets, you can either - -1. Utilize the [`arcade configure` CLI command](/references/arcade-cli.md) to configure your client to pass the secrets to your , or - -2. Manually configure your client to pass the secrets to your . For example, if you are using Cursor IDE, you can add the following to your `mcp.json` file: - - ```json - { - "mcpServers": { - "simple": { - "command": "uv", - "args": [ - "run", - "--directory", - "/absoulute/path/to/your/entrypoint/file/parent/directory", - "python", - "server.py" - ], - "env": { - "MY_SECRET_KEY": "my-secret-value" - } - } - } - } - ``` - - -This will make the secret available to your server when the MCP client starts the subprocess. Note that the specific key name may vary depending on the MCP client you are using. - -### Define your tool and access the secret - -This is only an illustrative example of how Arcade will ensure that the secret is present before the tool is executed. In a real world application, you would use this secret to store sensitive information like , database credentials, etc, and not to simply print a confirmation string. - -In your [MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md), create a new that uses the secret: - -- Use the `requires_secrets` parameter to declare which secrets your needs (`"SECRET_KEY"` in this example). -- The tool’s object has a `get_secret` method that you can use to access the secret value. - -```python -# secrets.py -@app.tool( - requires_secrets=["SECRET_KEY"], # declare we need SECRET_KEY -) -def use_secret(context: Context) -> str: - """Read SECRET_KEY from context and return a masked confirmation string.""" - try: - value = context.get_secret("SECRET_KEY") - masked = value[:2] + "***" if len(value) >= 2 else "***" - return f"Got SECRET_KEY of length {len(value)} -> {masked}" - except ValueError as e: - return f"Error getting secret: {e}" -``` - -When your is executed, it will return: `"Got SECRET_KEY of length..."`. In a real world application, you would use this secret to connect to a remote database, API, etc. - -**Security Best Practices** - -- **Never log secret values:** Always mask or truncate when displaying -- **Declare requirements:** Use `requires_secrets` to document dependencies -- **Handle missing secrets:** Use try/except when accessing secrets -- **Use descriptive names:** Make it clear what each secret is for - -## Key Concepts - -- **Secure Access:** Secrets are accessed through , not imported directly -- **Environment Integration:** Works with both environment variables and `.env` files -- **Error Handling:** Always handle the case where a secret might be missing -- **Masking:** Never expose full secret values in logs or return values -- **Declaration:** Use `requires_secrets` to make dependencies explicit - -## Example Code - -### Environment Variables - -```bash -# .env -SECRET_KEY="supersecret" -``` - -For the code to work, you must define your environment variables locally or in a `.env` file. Arcade will automatically search upward from the current directory to find your `.env` file. - -```python -# secrets.py -#!/usr/bin/env python3 -import sys - -from arcade_mcp_server import Context, MCPApp - -# Create the MCP application -app = MCPApp( - name="secrets_example", - version="1.0.0", - instructions="Example server demonstrating secrets usage", -) - - -@app.tool( - requires_secrets=["SECRET_KEY"], # declare we need SECRET_KEY -) -def use_secret(context: Context) -> str: - """Read SECRET_KEY from context and return a masked confirmation string.""" - try: - value = context.get_secret("SECRET_KEY") - masked = value[:2] + "***" if len(value) >= 2 else "***" - return f"Got SECRET_KEY of length {len(value)} -> {masked}" - except ValueError as e: - return f"Error getting secret: {e}" - - -if __name__ == "__main__": - # Get transport from command line argument, default to "stdio" - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - - # Run the server - app.run(transport=transport, host="127.0.0.1", port=8000) -``` - -### Run your MCP server - -### stdio transport (default) - -```bash -uv run secrets.py stdio -``` - -### HTTP transport - -```bash -uv run secrets.py http -``` - -For HTTP transport, view your server’s API docs at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) . - -For security reasons, Local HTTP servers do not currently support tool-level authorization and secrets. If you need to use tool-level authorization or secrets locally, you should use the stdio transport and configure the Arcade API key and secrets in your connection settings. Otherwise, if you intend to expose your HTTP to the public internet with \-level authorization and secrets, please follow the [deploying to the cloud with Arcade Deploy](/guides/deployment-hosting/arcade-deploy.md) guide or the [on-prem MCP server](/guides/deployment-hosting/on-prem.md) guide for secure remote deployment. - -Last updated on February 10, 2026 - -[Create a tool with auth](/en/guides/create-tools/tool-basics/create-tool-auth.md) -[Access runtime data](/en/guides/create-tools/tool-basics/runtime-data-access.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics/organize-mcp-tools.md b/public/_markdown/en/guides/create-tools/tool-basics/organize-mcp-tools.md deleted file mode 100644 index 4b0564025..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics/organize-mcp-tools.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -title: "Organize your MCP server and tools" -description: "Learn best practices for organizing your MCP server and tools, how to import tools from other packages and how to use them together." ---- -Create tools[Build a tool](/en/guides/create-tools/tool-basics.md) -Organize your MCP server and tools - -# Organize your MCP server and tools - -## Outcomes - -Learn best practices for organizing your server and , how to import tools from other packages and how to use them together. Jump to [Example Code](#example-code) to see the complete code. - -### You will Learn - -- How to define in separate files and import them -- How to import from other Arcade packages -- How to use `@app.tool` decorators and imported together - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [An MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - -- [uv package manager](https://docs.astral.sh/uv/getting-started/installation/) -   - -## Project Structure - -We recommend keeping your server file and in separate files in a `tools` directory like so: - -```bash - my_server/ - ├── src/ - │ └── my_server/ - │ ├── .env - │ ├── server.py # Entrypoint file with your MCPApp - │ ├── tools/ - │ │ ├── __init__.py - │ │ ├── math_tools.py # @tool decorated functions - │ │ └── text_tools.py # @tool decorated functions - │ ├── pyproject.toml - │ └── README.md - └── pyproject.toml -``` - -## Defining tools - -You can use the `@app.tool` decorator to define your tools in your server file directly on the app object (`MCPApp`) like this: - -```python -# server.py -@app.tool -def server_info() -> Annotated[dict, "Information about this server"]: - """Return information about this MCP server.""" - return { - "name": "Organized Server", - "version": "1.0.0", - "description": "Demonstrates modular tool organization", - "total_tools": 6, # 4 imported + 2 defined here - } -``` - -However, if you need to define more than a few tools, this can make your server file longer and harder to read and maintain. Instead, you can define your in separate files and import them. - -## Importing tools - -You can import from separate files like this: - -```python -# server.py -from tools.math_tools import add, multiply -from tools.text_tools import capitalize_string, word_count -``` - -You could also import specific from Arcade PyPI packages: - -```python -# server.py -# This is a prebuilt Arcade server - `pip install arcade-gmail` -from arcade_gmail.tools import list_emails -``` - -You can also import whole modules that contain like this: - -```python -# server.py -# This is a prebuilt Arcade server - `pip install arcade-reddit` -import arcade_reddit -``` - -Add imported explicitly to the `MCPApp` instance like this: - -```python -# server.py -app.add_tool(add) -app.add_tool(list_emails) -app.add_tools_from_module(arcade_reddit) -``` - -## Key takeaways - -- Keep your server file clean and readable by defining in separate files -- Store your in a `tools` directory in your project alongside your server file -- You can import from other Arcade packages and your own files -- Add imported tools explicitly to the server app object - -## Example Code - -```python -# server.py -#!/usr/bin/env python3 - -import sys -from typing import Annotated - -from arcade_mcp_server import MCPApp - -# Import tools from our mock 'tools' directory -# In a real project, these could come from actual separate files -from tools.math_tools import add, multiply -from tools.text_tools import capitalize_string, word_count - -# In a real project, you could import from Arcade PyPI packages - -# e.g. `pip install arcade-gmail` -# import arcade_gmail - -# Create the MCP application -app = MCPApp( - name="organized_server", - version="1.0.0", - instructions="Example server demonstrating modular tool organization", -) - -# Method 1: Add imported tools explicitly -app.add_tool(add) -app.add_tool(multiply) -app.add_tool(capitalize_string) -app.add_tool(word_count) -# app.add_tools_from_module(arcade_gmail) - - -# Method 2: Define tools directly on the app -@app.tool -def server_info() -> Annotated[dict, "Information about this server"]: - """Return information about this MCP server.""" - return { - "name": "Organized Server", - "version": "1.0.0", - "description": "Demonstrates modular tool organization", - "total_tools": 6, # 4 imported + 2 defined here - } - - -@app.tool -def combine_results( - text: Annotated[str, "Text to process"], - add_num: Annotated[int, "Number to add"], - multiply_num: Annotated[int, "Number to multiply"], -) -> Annotated[dict, "Combined results from multiple tools"]: - """Demonstrate using multiple tools together.""" - return { - "original_text": text, - "capitalized": capitalize_string(text), - "word_count": word_count(text), - "math_result": multiply(add(5, add_num), multiply_num), - } - - -if __name__ == "__main__": - # Check if stdio transport was requested - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - - print(f"Starting {app.name} v{app.version}") - print(f"Transport: {transport}") - print("Setting up database...") - # simulate a database setup - print("Database setup complete") - - # Run the server - app.run(transport=transport, host="127.0.0.1", port=8000) -``` - -### Run your MCP server - -### stdio transport (default) - -```bash -uv run server.py -``` - -### HTTP transport - -```bash -uv run server.py http -``` - -For HTTP transport, view your server’s API docs at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) . - -Last updated on January 30, 2026 - -[Call tools from MCP clients](/en/guides/create-tools/tool-basics/call-tools-mcp.md) -[Overview](/en/guides/create-tools/evaluate-tools.md) diff --git a/public/_markdown/en/guides/create-tools/tool-basics/runtime-data-access.md b/public/_markdown/en/guides/create-tools/tool-basics/runtime-data-access.md deleted file mode 100644 index fb5ffe023..000000000 --- a/public/_markdown/en/guides/create-tools/tool-basics/runtime-data-access.md +++ /dev/null @@ -1,285 +0,0 @@ ---- -title: "Context and MCP tools" -description: "What Arcade's Tool Context is and how to use it." ---- -Create tools[Build a tool](/en/guides/create-tools/tool-basics.md) -Access runtime data - -# Understanding `Context` and tools - -The `Context` class is used by tools for both runtime capabilities and \-specific data access. Tools receive a populated `Context` instance as a parameter. Tools should not create `Context` instances directly. - -## Outcomes - -Understand how to use the `Context` object to access runtime capabilities and \-specific data. - -### You will Learn - -1. \-specific data that can be accessed using the `Context` object: - - Access OAuth token via `context.get_auth_token_or_empty` - - Access secrets via `context.get_secret` - - Access user\_id via `context.user_id` -2. Runtime capabilities that can be accessed using the `Context` object: - - Log messages via `context.log` - - Request LLM sampling via `context.sampling` - - Request elicitation via `context.ui.elicit` - - Report progress via `context.progress.report` - -## How `Context` Works - -When a tool that requires authorization is invoked, the server automatically: - -1. Creates an instance of `Context` and populates it with the runtime capabilities and \-specific data -2. Passes this object to your tool’s first parameter named “” - -You can then use the `Context` object to make requests to external APIs that require an OAuth token. - -Let’s walk through an example (or skip to [the full code](#example-code)). - -## Context Features - -### Tool-Specific Data - -#### Tool secrets - -Specify required secrets using the `requires_secrets` argument of the `@tool` decorator and access them securely using the `get_secret` method: - -```python -@tool(requires_secrets=["DATABASE_URL", "API_KEY"]) -async def my_tool(context: Context) -> str: - try: - api_key = context.get_secret("API_KEY") - except ValueError: - # Handle missing secret - return "Do something interesting with the secret" -``` - -#### OAuth token - -Specify required authorization using the `requires_auth` argument of the `@tool` decorator and access the OAuth token securely using the `get_auth_token_or_empty` method: - -```python -@tool(requires_auth=ClickUp()) -async def my_tool(context: Context) -> str: - oauth_token = context.get_auth_token_or_empty() - - return "Do something interesting with the OAuth token" -``` - -#### User Info - -Access information about the user that authorized the using the `authorization.user_info` attribute. Note that this is only available if the tool’s supports it (e.g., the LinkedIn auth provider provides the ID): - -```python -user_info = context.authorization.user_info -``` - -### Runtime Capabilities - -#### Logging - -[Logging in MCP](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging)  provides a standardized channel for servers to emit structured, leveled messages to clients. Logging is useful for observability, debugging, and user feedback during execution. - -You can send log messages at different levels using the `log` attribute of the `Context` object like this: - -```python -await context.log.debug("Debug message") -await context.log.info("Information message") -await context.log.warning("Warning message") -await context.log.error("Error message") -await context.log.log("info", "Info message") # equivalent to await context.log.info("Info message") -``` - -#### Sampling - -[Sampling in MCP](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling)  is a way for servers to request LLM sampling (“completions” or “generations”) from language models via clients. This flow allows clients to maintain control over model access, selection, and permissions while enabling servers to leverage AI capabilities—with no server necessary. - -```python -await context.sampling.create_message(messages, system_prompt) -``` - -#### Elicitation - -[Elicitation in MCP](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation)  is a way for servers to request additional information from users through the client during interactions. This flow allows clients to maintain control over interactions and data sharing while enabling servers to gather necessary information dynamically. - -```python -elicitation_schema = {"type": "object", "properties": {"nickname": {"type": "string"}}} - -await context.ui.elicit("What is your nickname?", elicitation_schema) -``` - -#### Progress Reporting - -[Progress reporting in MCP](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress)  is a way for servers to report progress for long-running operations to clients. - -```python -await context.progress.report(current, total, "Processing...") -``` - -## Example Code - -The following is an example that shows how can access runtime features through `Context`, including logging, secrets, and progress reporting. - -### Environment Variables - -```bash -API_KEY="your-secret-key" -DATABASE_URL="postgresql://localhost/mydb" -``` - -For the code to work, you must define your environment variables in a `.env` file at the same directory as your . - -```python -# server.py -#!/usr/bin/env python3 - -import sys -from typing import Annotated - -from arcade_mcp_server import Context, MCPApp - -# Create the MCP application -app = MCPApp( - name="context_example", - version="1.0.0", - instructions="Example server demonstrating Context usage", -) - - -@app.tool(requires_secrets=["API_KEY"]) -async def secure_api_call( - context: Context, - endpoint: Annotated[str, "API endpoint to call"], - method: Annotated[str, "HTTP method (GET, POST, etc.)"] = "GET", -) -> Annotated[str, "API response or error message"]: - """Make a secure API call using secrets from context.""" - - # Access secrets from environment via Context helper - try: - api_key = context.get_secret("API_KEY") - except ValueError: - await context.log.error("API_KEY not found in environment") - return "Error: API_KEY not configured" - - # Log the API call - await context.log.info(f"Making {method} request to {endpoint}") - - # Simulate API call (in real code, use httpx) - return f"Successfully called {endpoint} with API key: {api_key[:4]}..." - - -# Don't forget to add the secret to the .env file or export it as an environment variable -@app.tool(requires_secrets=["DATABASE_URL"]) -async def database_info( - context: Context, table_name: Annotated[str | None, "Specific table to check"] = None -) -> Annotated[str, "Database connection info"]: - """Get database connection information from context.""" - - # Get database URL from secrets - try: - db_url = context.get_secret("DATABASE_URL") - except ValueError: - db_url = "Not configured" - - if table_name: - return f"Database: {db_url}\nChecking table: {table_name}" - - return f"Database: {db_url}" - -@app.tool -async def logging_tool(context: Context, message: Annotated[str, "The message to log"]) -> str: - """Log a message at varying levels.""" - await context.log.log("debug", f"Debug via log.log: {message}") - await context.log.debug(f"Debug via log.debug: {message}") - await context.log.info(f"Info via log.info: {message}") - await context.log.warning(f"Warning via log.warning: {message}") - await context.log.error(f"Error via log.error: {message}") - - return "Logged!" - -@app.tool -async def reporting_progress(context: Context) -> str: - """Report progress back to the client""" - total = 5 - - for i in range(total): - await context.progress.report(i + 1, total=total, message=f"Step {i + 1} of {total}") - - return "All progress reported successfully" - -@app.tool -async def sampling( - context: Context, text: Annotated[str, "The text to be summarized by the client's model"] -) -> str: - """Summarize the text using the client's model.""" - result = await context.sampling.create_message( - messages=text, - system_prompt=( - "You are a helpful assistant that summarizes text. " - "Given a text, you should summarize it in a few sentences." - ), - ) - return result.text - -@app.tool -async def elicit_nickname(context: Context) -> str: - """Ask the end user for their nickname, and then use it to greet them.""" - elicitation_schema = {"type": "object", "properties": {"nickname": {"type": "string"}}} - result = await context.ui.elicit( - "What is your nickname?", - schema=elicitation_schema, - ) - - if result.action == "accept": - return f"Hello, {result.content['nickname']}!" - elif result.action == "decline": - return "User declined to provide a nickname." - elif result.action == "cancel": - return "User cancelled the elicitation." - - return "Unknown response from client" - - -if __name__ == "__main__": - # Check if stdio transport was requested - transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" - - # Run the server - app.run(transport=transport, host="127.0.0.1", port=8000) -``` - -### Run your MCP server - -### stdio transport (default) - -```bash -uv run server.py -``` - -### HTTP transport - -```bash -uv run server.py http -``` - -For HTTP transport, view your server’s API docs at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) . - -## Key Concepts - -- ** Parameter** can receive a populated `Context` as their first parameter -- **Async Functions** Use `async def` for tools that use runtime features -- **Secure Secrets** Secrets are accessed through , not hardcoded -- **Structured Logging** Log at appropriate levels for debugging -- **Progress Updates** Keep informed during long operations - -### Next Steps - -- [Build a custom tool that requires user authorization](/guides/create-tools/tool-basics/create-tool-auth.md) - -- [Build a custom tool with secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) - - -Last updated on January 30, 2026 - -[Create a tool with secrets](/en/guides/create-tools/tool-basics/create-tool-secrets.md) -[Call tools from MCP clients](/en/guides/create-tools/tool-basics/call-tools-mcp.md) diff --git a/public/_markdown/en/guides/deployment-hosting.md b/public/_markdown/en/guides/deployment-hosting.md deleted file mode 100644 index 3f73295cb..000000000 --- a/public/_markdown/en/guides/deployment-hosting.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: "Overview" -description: "Learn about the different ways to host Arcade" ---- -Deployment & hostingOverview - -# Hosting Options Overview - -The easiest and best way to use Arcade is via the Arcade Cloud service - sign up for free at [https://api.arcade.dev](https://api.arcade.dev) . However, you might need to connect your to local resources (e.g. a local database or filesystem) or keep data within your own infrastructure. Don’t worry, Arcade has you covered via either Arcade Cloud or our on-premise deployment options. - -## Arcade Cloud - -Arcade Cloud is the default option — sign up and start building immediately: - -- **Zero Infrastructure**: No servers or databases to manage -- **Automatic Updates**: Always access the latest and features -- **Built-in Scaling**: Handles traffic spikes automatically -- **Free Tier**: Start building without a credit card - -### MCP Server Deployment - -You can route and manage tool calls from your agents to servers hosted anywhere—on your machine, on your own infrastructure, in a private cloud, or on Arcade Cloud. This allows you to mix the best public with your own private tools. - -Connect on-premises servers to Arcade Cloud for a hybrid deployment: - -- **Private Resources**: Access databases and APIs within your network -- **Data Control**: Keep sensitive data in your environment -- **Custom Dependencies**: Use specific runtime requirements or configurations -- **Compliance**: Meet regulatory requirements while using Arcade’s capabilities - -See [On-premise MCP Servers](/guides/deployment-hosting/on-prem.md) for more information about how to use your own servers running anywhere, and see [Arcade Deploy](/guides/deployment-hosting/arcade-deploy.md) to learn how to deploy to Arcade Cloud. - -### Customizing Auth - -You don’t have to self-host Arcade to customize your auth experiences. Arcade Cloud supports a number of out of the box, and you can provide your own OAuth app credentials to brand your end-user experience. We recommend doing this for all production use cases, so that you can have isolated rate limits with the OAuth service provider and you can give your users a consistent experience when they go through an auth flow. You can still use the same when you customize your auth, no code changes are required. - -See [Customizing Auth](/references/auth-providers.md) for more information. - -### Arcade Cloud Pricing - -Arcade Cloud offers a generous free tier to get started: - -- **Free Tier**: Includes access to all public Servers and basic features -- **Usage-Based**: Pay only for what you use as you scale - -Visit [https://api.arcade.dev](https://api.arcade.dev)  for current pricing details. - -## On-Premise Deployments - -Fully on-premise deployments of the Arcade platform are available! Arcade can be deployed on Kubernetes via our Helm chart and Docker images as part of our enterprise offering. [Contact us to learn more](/resources/contact-us.md). - -The requirements for deploying Arcade on-premise are: - -- Kubernetes cluster (1.30+) (We have tested this helm chart on AKS, GKE, and EKS). -- Helm 3.x -- kubectl configured to access your cluster -- Cert Manager for securing Redis and Postgres and public ingress (see below) -- Nginx Ingress for accessing Arcade.dev from outside the cluster (see below) - -Last updated on January 30, 2026 - -[Secure Auth in Production](/en/guides/user-facing-agents/secure-auth-production.md) -[Arcade Cloud](/en/guides/deployment-hosting/arcade-cloud.md) diff --git a/public/_markdown/en/guides/deployment-hosting/arcade-cloud.md b/public/_markdown/en/guides/deployment-hosting/arcade-cloud.md deleted file mode 100644 index a4c8e8567..000000000 --- a/public/_markdown/en/guides/deployment-hosting/arcade-cloud.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -title: "Arcade Cloud infrastructure" -description: "Learn about the infrastructure, data storage, and data sovereignty options for Arcade Cloud" ---- -[Deployment & hosting](/en/guides/deployment-hosting.md) -Arcade Cloud - -# Arcade Cloud infrastructure - -Arcade Cloud is Arcade’s fully-managed SaaS platform: Arcade handles hosting, scaling, and operations so you can focus on building. This page covers the infrastructure behind Arcade Cloud, including networking, data storage, data protection, and sovereignty options. - -This page applies to **Arcade Cloud** only. If you self-host Arcade, you control your own infrastructure and data residency. See [on-premises deployment](/guides/deployment-hosting.md#on-premise-deployments) for details. - -## Sovereignty - -All of Arcade Cloud’s infrastructure is located in the United States, including data storage and processing. - -## Egress IP addresses - -Traffic from Arcade Cloud exits from the following IP addresses: - -```bash -# Control Plane (Located in the USA) -3.140.92.118 -3.13.58.74 -3.149.34.246 - -# MCP Server Cluster 1 (Located in the USA) -3.150.210.23 -3.135.113.210 -3.131.234.5 -``` - -## VPC peering - -VPC peering is available for enterprise customers upon request. If you want VPC peering, [contact Arcade](/resources/contact-us.md). - -## Data storage - -As part of Arcade’s service, Arcade stores multiple categories of data: - -### Application data (user-controlled) - -Data stored in application databases where you have direct control over retention and deletion. - -Data type - -Examples - -**User secrets** - -OAuth tokens - -**User identities** - -Email addresses or usernames - -**Project secrets** - -API keys, OAuth provider configurations - -**Execution logs** - -Tool execution logs for monitoring and debugging - -**Audit trails** - -Changes to significant data or configuration for your projects or organization - -You can disable log collection, set retention periods, and purge data on demand through your organization or settings. - -### Monitoring and analytics (platform-controlled) - -Operational data that Arcade uses for system health, debugging, and analytics. - -Data type - -Retention - -**Application logs** - -Up to 1 year - -**Audit trails** - -Up to 1 year - -**Metrics and traces** - -Up to 1 year - -**Billing and transaction records** - -Retained indefinitely for legal and financial compliance - -**Vulnerability and security reports** - -Up to 6 months - -## Training data - -### What Training Data Covers - -Training data includes data which Arcade uses to improve its ML/AI models. This includes data which could contain personally identifiable information (PII): - -- ** queries** — Natural language inputs sent to tools -- ** inputs** — Parameters and arguments passed to -- ** results** — Outputs returned from , including data returned from external services - -Training consent does **not** apply to: - -- Aggregated, anonymized usage metrics -- System performance data -- Billing and transaction records - -### Consent model - -Arcade follows a customer-governed consent model that prioritizes transparency and control: - -- **Opt-out is available anytime** through your organization or settings—no review process, and it takes effect immediately. -- **Consent is scoped to the organization level.** Consent for data training is controlled buy the Arcade customer, and not end-users. For enterprise , an organization admin manages consent for the team. - -### When you opt out - -When you revoke training consent: - -- Data collection for training stops immediately -- Existing data is excluded from future training runs - -### Retention - -Training data is retained for up to 5 years, which is sufficient for model development and consistent with industry precedent. - -## Data protection - -### Encryption - -Layer - -Standard - -**At rest** - -All production systems use AES-256 encrypted storage volumes - -**In transit** - -TLS 1.2+ for all API communications - -**Application-level** - -Sensitive fields (tokens, secrets) use additional AES-256 encryption before persisting to storage - -### Deployment options - -Deployment mode - -Data location control - -Best for - -**Arcade Cloud** - -United States only - -Customers without data residency requirements - -**Self-hosted** - -Full customer control - -Regulated industries and strict sovereignty requirements - -### Regulated Customers - -If your organization has strict data residency requirements—for example, in financial services, healthcare, or government—or you operate within a legal regime that requires data storage in a specific country, you can deploy Arcade on-premises or in your preferred cloud region using a [self-hosted deployment](/guides/deployment-hosting.md#on-premise-deployments). This keeps all sensitive data within your own infrastructure. - -## Questions - -For compliance inquiries or data protection questions, [contact us](/resources/contact-us.md). - -Last updated on January 30, 2026 - -[Overview](/en/guides/deployment-hosting.md) -[On-premises MCP servers](/en/guides/deployment-hosting/on-prem.md) diff --git a/public/_markdown/en/guides/deployment-hosting/arcade-deploy.md b/public/_markdown/en/guides/deployment-hosting/arcade-deploy.md deleted file mode 100644 index 00f125fc5..000000000 --- a/public/_markdown/en/guides/deployment-hosting/arcade-deploy.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: "Cloud deployments with Arcade Deploy" -description: "Learn how to deploy a worker with Arcade Deploy" ---- -[Deployment & hosting](/en/guides/deployment-hosting.md) -Arcade Deploy - -# Deploying to the cloud with Arcade Deploy - -Running your servers locally is very convenient during development and testing. Once your MCP server is mature, however, you may want to access it from any MCP client, or to facilitate multi-user support. Doing all that from your computer comes with the complexity of running and maintaining a server, handling auth and high availability for all your users and all the integrations you want to support. Arcade Deploy takes care of all that for you. Your MCP server will be registered to Arcade, adding all the tools you created to the larger tool catalog. From there, you can create to pick and choose which tools you want to use in your MCP clients, which can be from any connected . - -## Outcomes - -This guide shows you how to deploy your Server with Arcade Deploy. - -### You will Learn - -- How to deploy your existing Server to the cloud with the `arcade deploy` CLI command. -- How to create an Gateway to pick and choose which you want to use in your MCP clients. -- How to use Arcade clients to call the tools in your Server. - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [uv package manager](https://docs.astral.sh/uv/getting-started/installation/) -   -- [Create an MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - - -### uv - -```bash -uv tool install arcade-mcp -``` - -This will install the Arcade CLI as a [uv tool](https://docs.astral.sh/uv/guides/tools/#installing-tools) , making it available system wide. - -### pip - -```bash -pip install arcade-mcp -``` - -## Create an MCP server using Arcade MCP - -If you have not created an server yet, then follow the steps outlined in [this guide](/guides/create-tools/tool-basics/build-mcp-server.md) before deploying. - -## Deploy your MCP Server - -Run the deploy command in the directory where you started your server (containing your `pyproject.toml` file) and specify the relative path to your via the `--entrypoint/-e` option. - -```bash -arcade deploy -e src/my_server/server.py -``` - -You must run the command from the directory containing your `pyproject.toml` file. - -By default, running `arcade deploy` looks for a file named `server.py` in the current directory as the entrypoint file to your server. If your is in a different directory, then you need to specify the relative path. - -```python -from arcade_mcp_server import MCPApp - -app = MCPApp() - -@app.tool -def echo(phrase: Annotated[str, "The phrase to echo"]) -> str: - """Echo a phrase""" - return phrase - -if __name__ == "__main__": - app.run() -``` - -It is important that your executes `MCPApp.run()` (or `app.run()` if `app` is of type `MCPApp`) when invoked directly. - -We recommend to do it inside an `if __name__ == "__main__":` statement. - -You should see output like the following: - -```bash -Validating user is logged in... -✓ {arcade_user_id} is logged in - -Validating pyproject.toml exists in current directory... -✓ pyproject.toml found at /path/to/your/project/pyproject.toml - -Searching for .env file... -✓ Loaded environment from /path/to/your/project/.env - -Validating server is healthy and extracting metadata before deploying... -✓ Server started on port 8001 -✓ Server is healthy -✓ Found server name: my_server -✓ Found server version: 1.0.0 -✓ Found 3 tools -✓ Found 1 required secret(s) - -Uploading 1 required secret(s) to Arcade... -✓ Uploading 'MY_SECRET_KEY' with value ending in ...ime! -✓ Secret 'MY_SECRET_KEY' uploaded - -Creating deployment package... -✓ Package created (1.8 KB) - - -⠧ Deployment in progress (this may take a few minutes)... - -Recent logs: - data: {"Timestamp":"2025-10-27T16:03:45.429460682Z","Line":"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details."} - data: {"Timestamp":"2025-10-27T16:03:45.429466232Z","Line":"[GIN-debug] Listening and serving HTTP on :8002"} - data: {"Timestamp":"2025-10-27T16:03:47.577492621Z","Line":"[GIN] 2025/10/27 - 16:03:47 | 200 | 2.888808ms | 10.30.253.232 | GET \"/worker/health\""} - data: {"Timestamp":"2025-10-27T16:03:48.230570179Z","Line":"INFO: 127.0.0.1:53384 - \"GET /worker/health HTTP/1.1\" 200 OK"} - data: {"Timestamp":"2025-10-27T16:03:48.231072632Z","Line":"[GIN] 2025/10/27 - 16:03:48 | 200 | 4.526797ms | 10.30.253.232 | GET \"/worker/health\""} - - -You can safely exit with Ctrl+C at any time. The deployment will continue normally. - -✓ Deployment successful! Server is running. -``` - -## Manage your MCP servers in Arcade - -Navigate to the [Servers](https://api.arcade.dev/dashboard/servers)  page in your Arcade dashboard. From there, you will be able to: - -- Monitor the health status of the server -- Delete the server -- Test and execute all the -- Manage users connected to the -- Manage the secrets for the server -- Create [MCP Gateways](/guides/mcp-gateways.md) - - -## Create an MCP Gateway to call the tools in your MCP Server - -Once the server is deployed to Arcade, all the in the server will be available in the [tool catalog](https://api.arcade.dev/dashboard/tools)  page in your Arcade dashboard. To call the tools from an MCP client, you first need to [create an MCP Gateway](/guides/mcp-gateways.md) to pick and choose which tools you want to use in your MCP clients. - -When creating an gateway, you can select the tools you want to include in the Gateway from any available to the , including the one you just deployed. - -## Use Arcade clients to call the tools in your MCP Server - -You can use any of the available [Arcade clients](/references.md) to call the tools in your Server. When using the clients, you are not required to create an , as the client will handle the connection to all tools in your Arcade directly. - -Your Server is now deployed and managed by Arcade, and ready to be used in your MCP clients! - -Last updated on January 5, 2026 - -[Configure Arcade's engine](/en/guides/deployment-hosting/configure-engine.md) -[Overview](/en/guides/security.md) diff --git a/public/_markdown/en/guides/deployment-hosting/configure-engine.md b/public/_markdown/en/guides/deployment-hosting/configure-engine.md deleted file mode 100644 index 7aa847c53..000000000 --- a/public/_markdown/en/guides/deployment-hosting/configure-engine.md +++ /dev/null @@ -1,714 +0,0 @@ ---- -title: "Engine Configuration Templates" -description: "Arcade Engine Configuration Templates" ---- -[Deployment & hosting](/en/guides/deployment-hosting.md) -Configure Arcade's engine - -# Engine Configuration - -This page is for enterprise customers who are self-hosting the . This is page contains advanced configuration options that are not applicable for most customers. - -## Getting the Engine - -### macOS (Homebrew) - -```bash -brew install ArcadeAI/tap/arcade-engine -``` - -Run it with: `arcade-engine` - -Troubleshooting: - -```bash -❌ Engine binary not found -``` - -or - -```bash -command not found: arcade-engine -``` - -This means that the cannot be found in your path. Brew and Apt will automatically add the binary to your path. - -Check that the binary has been properly installed. These are the common installation locations): - -**Brew** - -```bash -ls $HOMEBREW_REPOSITORY/Cellar/arcade-engine//bin/arcade-engine -``` - -**Apt** - -```bash -ls /usr/bin/arcade-engine -``` - -If the binary is found, add it to your path with: - -```bash -export PATH=$PATH:/path/to/your/binary -``` - -### Ubuntu/Debian (APT) - -```bash -wget -qO - https://deb.arcade.dev/public-key.asc | sudo apt-key add - -echo "deb https://deb.arcade.dev/ubuntu stable main" | sudo tee /etc/apt/sources.list.d/arcade-ai.list -sudo apt update -sudo apt install arcade-engine -``` - -### Docker - -The docker image for the engine can be pulled with - -```bash -docker pull ghcr.io/arcadeai/engine:latest -``` - -The engine can be run with: - -```bash -docker run -d -p 9099:9099 -v ./engine.yaml:/bin/engine.yaml ghcr.io/arcadeai/engine:latest -``` - -where config.yaml is the path to the [configuration file](/guides/deployment-hosting/configure-engine.md). - -The Homebrew tab is macOS-only. For native Windows environments, use Docker for . For Arcade CLI setup on Windows, see [Windows environment setup](/get-started/setup/windows-environment.md). - -Arcade uses configuration files to manage engine settings and default values. When you install the , two files are created: - -- The `engine.yaml` file for engine configuration. -- The `engine.env` file for environment variables. Let’s explore each file to understand their purpose and how to locate them. - -## Engine configuration file - -The `engine.yaml` file controls settings. It supports variable expansion so you can integrate secrets and environment values seamlessly. You can customize this file to suit your setup. For more details, check the [Engine Configuration](/guides/deployment-hosting/configure-engine.md) page. - -Choose your installation method to view the default location of `engine.yaml`: - -### macOS (Homebrew) - -```bash -$HOMEBREW_REPOSITORY/etc/arcade-engine/engine.yaml -``` - -### Ubuntu/Debian (APT) - -```bash -/etc/arcade-ai/engine.yaml -``` - -### Manual Download - -```bash -$HOME/.arcade/engine.yaml -``` - -To manually download the engine.yaml, you can get an example from the [Configuration Templates](/guides/deployment-hosting/configure-engine.md#engineyaml) - and add it to `$HOME/.arcade/engine.yaml`. - -## Engine environment file - -The `engine.env` file contains default environment variables that power . You can override these defaults by exporting your own variables or by editing the file directly. - -Select your installation method below to see the default path for `engine.env`: - -### macOS (Homebrew) - -```bash -$HOMEBREW_REPOSITORY/etc/arcade-engine/engine.env -``` - -### Ubuntu/Debian (APT) - -```bash -/etc/arcade-ai/engine.env -``` - -### Manual Download - -```bash -$HOME/.arcade/engine.env -``` - -To manually download the `engine.env`, refer to the [Configuration Templates](/guides/deployment-hosting/configure-engine.md#engineenv) -. - -’s configuration is a [YAML file](https://yaml.org/)  with the following sections: - -Section - -Description - -API Configuration - -Configures the server for specific protocols - -Auth Configuration - -Configures user authorization providers and token storage - -Cache Configuration - -Configures the short-lived cache - -Security Configuration - -Configures security and encryption - -Storage Configuration - -Configures persistent storage - -Telemetry Configuration - -Configures telemetry and observability (OTEL) - -Tools Configuration - -Configures tools for AI models to use - -## Specify a config file - -To start the , pass a config file with `-c` or `--config`: - -```bash -# engine.yaml -arcade-engine -c /path/to/config.yaml -``` - -## Dotenv files - - automatically loads environment variables from `.env` files in the directory where it was called. Use the `-e` or`--env` flag to specify a path: - -```bash -# engine.yaml -arcade-engine -e .env.dev -c config.yaml -``` - -## Secrets - - supports two ways of passing sensitive information like without storing them directly in the config file. - -Environment variables: - -```yaml -# engine.yaml -topic: - area: - - id: primary - vendor: - api_key: ${env:OPENAI_API_KEY} -``` - -External files (useful in cloud setups): - -```yaml -# engine.yaml -topic: - area: - - id: primary - vendor: - api_key: ${file:/path/to/secret} -``` - -## API configuration - -HTTP is the supported protocol for ’s API. The following configuration options are available: - -- `api.development` _(optional, default: `false`)_ - Enable development mode, with more logging. -- `api.host` _(default: `localhost`)_ - Address to which binds its server (e.g., `localhost` or `0.0.0.0`) -- `api.port` _(default: `9099`)_ - Port to which binds its server (e.g., `9099` or `8080`) -- `api.public_host` _(optional)_ - External hostname of the API (e.g., `my-public-host.com`), if it differs from `api.host` (for example, when is behind a reverse proxy) -- `api.read_timeout` _(optional, default: `30s`)_ - Timeout for reading data from clients -- `api.write_timeout` _(optional, default: `1m`)_ - Timeout for writing data to clients -- `api.idle_timeout` _(optional, default: `30s`)_ - Timeout for idle connections -- `api.max_request_body_size` _(optional, default: `4Mb`)_ - Maximum request body size - -A typical configuration for production looks like: - -```yaml -# engine.yaml -api: - development: false - host: localhost - port: 9099 -``` - -When the is hosted in a container or behind a reverse proxy, set `api.public_host` to the external hostname of the API: - -```yaml -# engine.yaml -api: - development: false - host: localhost - port: 9099 - public_host: my-public-host.com -``` - -For local development, set `api.development = true`. - -## Auth configuration - - manages auth for [AI tools](/guides/tool-calling/custom-apps/auth-tool-calling.md) and [direct API calls](/guides/tool-calling/call-third-party-apis.md). It supports many built-in [auth providers](/references/auth-providers.md), and can also connect to any [OAuth 2.0](/references/auth-providers/oauth2.md) authorization server. - -The `auth.providers` section defines the providers that can authorize with. Each provider must have a unique `id` in the array. There are two ways to configure a provider: - -For [built-in providers](/references/auth-providers.md), use the `provider_id` field to reference the pre-built configuration. For example: - -```yaml -# engine.yaml -auth: - providers: - - id: default-github - description: The default GitHub provider - enabled: true - type: oauth2 - provider_id: github - client_id: ${env:GITHUB_CLIENT_ID} - client_secret: ${env:GITHUB_CLIENT_SECRET} -``` - -For custom OAuth 2.0 providers, specify the full connection details in the `oauth2` sub-section. For full documentation on the custom provider configuration, see the [OAuth 2.0 provider configuration](/references/auth-providers/oauth2.md) page. - -You can specify a mix of built-in and custom providers. - -## Cache configuration - -The `cache` section configures the short-lived cache. - -Configuring the cache is optional. If not configured, the cache will default to an in-memory cache implementation suitable for a single-node deployment. - -The `cache` section has the following configuration options: - -- `api_key_ttl` _(optional, default: `10s`)_ - The time-to-live for in the cache - -Two cache implementations are available: - -- `in_memory` - _(default)_ An in-memory cache implementation suitable for a single-node deployment. -- `redis` - A Redis cache implementation suitable for a multi-node deployment: - -```yaml -# engine.yaml -cache: - api_key_ttl: 10s - redis: - addr: "localhost:6379" - password: "" - db: 0 -``` - -## Security configuration - -The `security` section configures the root encryption keys that the uses to encrypt and decrypt data at rest. See the [storage configuration](#storage-configuration) section below to configure where data is stored. - -A typical configuration looks like this: - -```yaml -# engine.yaml -security: - root_keys: - - id: key1 - default: true - value: ${env:ROOT_KEY_1} - - id: key2 - value: ${env:ROOT_KEY_2} -``` - -Keys should be a long random string of characters. For example: - -```bash -# engine.yaml -openssl rand -base64 32 -``` - -### Default root key - -When you [install Arcade Engine locally](/guides/deployment-hosting/configure-engine.md), an `engine.env` file is created with a default root key: - -```bash -# engine.yaml -# Encryption keys (change this when deploying to production) -ROOT_KEY_1=default-key-value -``` - -This default value can only be used in development mode (see [API configuration](#api-configuration) above). - -You **must** replace the value of `ROOT_KEY_1` in `engine.env` before deploying to production. - -## Storage configuration - -The `storage` section configures the storage backend that the uses to store persistent data. - -There are three storage implementations available: - -- `in_memory` - _(default)_ An in-memory database, suitable for testing. -- `sqlite` - A SQLite file on disk, suitable for local development: - -```yaml -# engine.yaml -storage: - sqlite: - # Stores DB in ~/.arcade/arcade-engine.sqlite3 - connection_string: "@ARCADE_HOME/arcade-engine.sqlite3" -``` - -- `postgres` - A PostgreSQL database, suitable for production: - -```yaml -# engine.yaml -storage: - postgres: - user: ${env:POSTGRES_USER} - password: ${env:POSTGRES_PASSWORD} - host: ${env:POSTGRES_HOST} - port: ${env:POSTGRES_PORT} - db: ${env:POSTGRES_DB} - sslmode: require -``` - -## Telemetry configuration - -Arcade supports logs, metrics, and traces with [OpenTelemetry](https://opentelemetry.io/) . - -If you are using the locally, you can set the `environment` field to `local`. This will only output logs to the console: - -```yaml -# engine.yaml -telemetry: - environment: local - logging: - # debug, info, warn, error, fatal - level: debug - encoding: console -``` - -To connect to OpenTelemetry compatible collectors, set the necessary [OpenTelemetry environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/)  in the `engine.env` file. `environment` and `version` are fields that are added to the telemetry attributes, which can be filtered on later. - -```yaml -# engine.yaml -telemetry: - environment: prod - logging: - level: info - encoding: console -``` - -### Notes - -- The Engine service name is set to `arcade_engine` -- Traces currently cover the `/v1/health` endpoints, as well as authentication attempts - -## Tools configuration - - orchestrates [tools](/guides/tool-calling.md) that AI models can use. - -The `tools.directors` section configures the servers that are available to service calls: - -```yaml -# engine.yaml -tools: - directors: - - id: default - enabled: true - max_tools: 64 - workers: - - id: local_worker - enabled: true - http: - uri: "http://localhost:8002" - timeout: 30 - retry: 3 - secret: ${env:ARCADE_WORKER_SECRET} -``` - -When an server is added to an enabled director, all of the tools hosted by that will be available to the model and through the . - -### HTTP MCP Server configuration - -The `http` sub-section configures the HTTP client used to call the Server’s : - -- `uri` _(required)_ - The base URL of the Server’s -- `secret` _(required)_ - Secret used to authenticate with the Server -- `timeout` _(optional, default: `30s`)_ - Timeout for calling the Server’s -- `retry` _(optional, default: `3`)_ - Number of times to retry a failed call - - Servers must be configured with a `secret` that is used to authenticate with the . This ensures that MCP Servers are not exposed to the public internet without security. - -If `api.development = true`, the secret will default to `"dev"` for local development **only**. In production, the secret must be set to a random value. - -## Config file version history - -- 1.0: [schema](https://raw.githubusercontent.com/ArcadeAI/schemas/refs/heads/main/engine/config/1.0/schema.json) -   - -## Engine Config Templates - -### engine.yaml - -```yaml -# engine.yaml -# yaml-language-server: $schema=https://raw.githubusercontent.com/ArcadeAI/schemas/main/engine/config/1.0/schema.json -$schema: https://raw.githubusercontent.com/ArcadeAI/schemas/main/engine/config/1.0/schema.json - -api: - development: ${env:API_DEVELOPMENT} - host: ${env:ARCADE_API_HOST} - port: ${env:ARCADE_API_PORT} - # Optionally set public_host, in case the Arcade Engine is hosted in a container or behind a reverse proxy - #public_host: ${env:ARCADE_API_PUBLIC_HOST} - -auth: - providers: - - id: default-atlassian - description: "The default Atlassian provider" - enabled: false - type: oauth2 - provider_id: atlassian - client_id: ${env:ATLASSIAN_CLIENT_ID} - client_secret: ${env:ATLASSIAN_CLIENT_SECRET} - - - id: default-discord - description: "The default Discord provider" - enabled: false - type: oauth2 - provider_id: discord - client_id: ${env:DISCORD_CLIENT_ID} - client_secret: ${env:DISCORD_CLIENT_SECRET} - - - id: default-dropbox - description: "The default Dropbox provider" - enabled: false - type: oauth2 - provider_id: dropbox - client_id: ${env:DROPBOX_CLIENT_ID} - client_secret: ${env:DROPBOX_CLIENT_SECRET} - - - id: default-github - description: "The default GitHub provider" - enabled: false - type: oauth2 - provider_id: github - client_id: ${env:GITHUB_CLIENT_ID} - client_secret: ${env:GITHUB_CLIENT_SECRET} - - - id: default-google - description: "The default Google provider" - enabled: false - type: oauth2 - provider_id: google - client_id: ${env:GOOGLE_CLIENT_ID} - client_secret: ${env:GOOGLE_CLIENT_SECRET} - - - id: default-linkedin - description: "The default LinkedIn provider" - enabled: false - type: oauth2 - provider_id: linkedin - client_id: ${env:LINKEDIN_CLIENT_ID} - client_secret: ${env:LINKEDIN_CLIENT_SECRET} - - - id: default-microsoft - description: "The default Microsoft provider" - enabled: false - type: oauth2 - provider_id: microsoft - client_id: ${env:MICROSOFT_CLIENT_ID} - client_secret: ${env:MICROSOFT_CLIENT_SECRET} - - - id: default-reddit - description: "The default Reddit provider" - enabled: false - type: oauth2 - provider_id: reddit - client_id: ${env:REDDIT_CLIENT_ID} - client_secret: ${env:REDDIT_CLIENT_SECRET} - - - id: default-slack - description: "The default Slack provider" - enabled: false - type: oauth2 - provider_id: slack - client_id: ${env:SLACK_CLIENT_ID} - client_secret: ${env:SLACK_CLIENT_SECRET} - - - id: default-spotify - description: "The default Spotify provider" - enabled: false - type: oauth2 - provider_id: spotify - client_id: ${env:SPOTIFY_CLIENT_ID} - client_secret: ${env:SPOTIFY_CLIENT_SECRET} - - - id: default-twitch - description: "The default Twitch provider" - enabled: false - type: oauth2 - provider_id: twitch - client_id: ${env:TWITCH_CLIENT_ID} - client_secret: ${env:TWITCH_CLIENT_SECRET} - - - id: default-x - description: "The default X provider" - enabled: false - type: oauth2 - provider_id: x - client_id: ${env:X_CLIENT_ID} - client_secret: ${env:X_CLIENT_SECRET} - - - id: default-zoom - description: "The default Zoom provider" - enabled: false - type: oauth2 - provider_id: zoom - client_id: ${env:ZOOM_CLIENT_ID} - client_secret: ${env:ZOOM_CLIENT_SECRET} - -llm: - models: - - id: my-openai-model-provider - openai: - api_key: ${env:OPENAI_API_KEY} - #- id: my-anthropic-model-provider - # anthropic: - # api_key: ${env:ANTHROPIC_API_KEY} - # - id: my-ollama-model-provider - # openai: - # base_url: http://localhost:11434 - # chat_endpoint: /v1/chat/completions - # model: llama3.2 - # api_key: ollama - #- id: my-groq-model-provider - # openai: - # base_url: 'https://api.groq.com/openai/v1' - # api_key: ${env:GROQ_API_KEY} - -security: - root_keys: - - id: key1 - default: true - value: ${env:ROOT_KEY_1} - -storage: - postgres: - user: ${env:POSTGRES_USER} - password: ${env:POSTGRES_PASSWORD} - host: ${env:POSTGRES_HOST} - port: ${env:POSTGRES_PORT} - db: ${env:POSTGRES_DB} - sslmode: require - -telemetry: - environment: ${env:TELEMETRY_ENVIRONMENT} - logging: - # debug, info, warn, error - level: ${env:TELEMETRY_LOGGING_LEVEL} - encoding: ${env:TELEMETRY_LOGGING_ENCODING} - -tools: - directors: - - id: default - enabled: true - max_tools: 64 - workers: - - id: worker - enabled: true - http: - uri: ${env:ARCADE_WORKER_URI} - timeout: 30 - retry: 3 - secret: ${env:ARCADE_WORKER_SECRET} -``` - -### engine.env - -```bash -# engine.yaml -### Engine configuration ### -API_DEVELOPMENT=true -ARCADE_API_HOST=localhost -ARCADE_API_PORT=9099 -ANALYTICS_ENABLED=true - -# Encryption keys (change this when deploying to production) -ROOT_KEY_1=default-key-value - -### Model Provider API keys ### -# OPENAI_API_KEY= -# ANTHROPIC_API_KEY= -# GROQ_API_KEY= - - -### Security configuration ### -ROOT_KEY_1= - - -### Storage configuration ### -# POSTGRES_USER= -# POSTGRES_PASSWORD= -# POSTGRES_HOST= -# POSTGRES_PORT= -# POSTGRES_DB= - - -### Telemetry (OTEL) configuration ### -TELEMETRY_ENVIRONMENT=local -TELEMETRY_LOGGING_LEVEL=debug -TELEMETRY_LOGGING_ENCODING=console - - -### Worker Configuration ### -ARCADE_WORKER_URI=http://localhost:8002 -ARCADE_WORKER_SECRET=dev - - -# OAuth Providers -ATLASSIAN_CLIENT_ID="" -ATLASSIAN_CLIENT_SECRET= - -DISCORD_CLIENT_ID="" -DISCORD_CLIENT_SECRET= - -DROPBOX_CLIENT_ID="" -DROPBOX_CLIENT_SECRET= - -GITHUB_CLIENT_ID="" -GITHUB_CLIENT_SECRET= - -GOOGLE_CLIENT_ID="" -GOOGLE_CLIENT_SECRET= - -LINKEDIN_CLIENT_ID="" -LINKEDIN_CLIENT_SECRET= - -MICROSOFT_CLIENT_ID="" -MICROSOFT_CLIENT_SECRET= - -REDDIT_CLIENT_ID="" -REDDIT_CLIENT_SECRET= - -SLACK_CLIENT_ID="" -SLACK_CLIENT_SECRET= - -SPOTIFY_CLIENT_ID="" -SPOTIFY_CLIENT_SECRET= - -TWITCH_CLIENT_ID="" -SPOTIFY_CLIENT_SECRET= - -X_CLIENT_ID="" -X_CLIENT_SECRET= - -ZOOM_CLIENT_ID="" -ZOOM_CLIENT_SECRET= -``` - -Last updated on February 10, 2026 - -[On-premises MCP servers](/en/guides/deployment-hosting/on-prem.md) -[Arcade Deploy](/en/guides/deployment-hosting/arcade-deploy.md) diff --git a/public/_markdown/en/guides/deployment-hosting/on-prem.md b/public/_markdown/en/guides/deployment-hosting/on-prem.md deleted file mode 100644 index 38e3b8dfa..000000000 --- a/public/_markdown/en/guides/deployment-hosting/on-prem.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -title: "On-premises MCP Servers" -description: "Learn how to deploy MCP servers in a hybrid architecture" ---- -[Deployment & hosting](/en/guides/deployment-hosting.md) -On-premises MCP servers - -# On-premise MCP Servers - -## Outcomes - -An on-premises server deployment allows you to execute tools in your own environment while still leveraging Arcade’s cloud Engine infrastructure. This gives you the flexibility to access private resources, maintain data security, and customize your environment while leveraging Arcade’s management and federation capabilities. - -### You will Learn - -- How to run your server with HTTP transport -- How to create a secure tunnel to expose it publicly -- How to register your server in Arcade -- How to test your server with the Arcade Playground - -### Prerequisites - -- [Arcade account](https://app.arcade.dev/register) - -- [Arcade CLI](/get-started/quickstarts/call-tool-agent.md) - -- [uv package manager](https://docs.astral.sh/uv/getting-started/installation/) -   - -## How on-premises MCP servers work - -You can make your server accessible to others by exposing it through a secure tunnel and registering it with Arcade. This allows remote users and services to interact with your without deploying to a cloud platform. - -The on-premises server model uses a bidirectional connection between your local environment and Arcade’s cloud engine: - -1. You run the Arcade server in your environment (on-premises, private cloud, etc.) -2. Your server is exposed to Arcade’s cloud engine using a public URL -3. The Arcade cloud engine routes tool calls to your server -4. Your server processes the requests and returns responses to the engine - -## Benefits of on-premises MCP servers - -- **Resource access**: Access private databases, APIs, and other resources not accessible from Arcade’s cloud -- **Data control**: Keep sensitive data within your environment while still using Arcade’s capabilities -- **Custom environments**: Use specific dependencies or configurations required by your -- **Compliance**: Meet regulatory requirements by keeping data processing within your infrastructure - -## Setting up an on-premises MCP server - -### Setup your MCP Servers - -Follow the [Creating a MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) guide to create your Server. - -### Start your local MCP Server - -Ensure you are logged in to Arcade: - -### CLI - -```bash -arcade login -``` - -### Environment Variable - -Add the environment variables to your shell: - -### Bash/Zsh (macOS/Linux) - -```bash -export ARCADE_API_KEY= -export ARCADE_USER_ID= -``` - -### PowerShell (Windows) - -```powershell -$env:ARCADE_API_KEY="" -$env:ARCADE_USER_ID="" -``` - -or to a `.env` file: - -```powershell -# .env -ARCADE_API_KEY= -ARCADE_USER_ID= -``` - -Start your server with HTTP transport: - -```bash -# Navigate to your entrypoint file -cd my_server/src/my_server - -# Run with HTTP transport (default) -uv run server.py -uv run server.py http -``` - -Your server will start on `http://localhost:8000`. Keep this terminal running. - -## Create a Secure Tunnel - -Open a **separate terminal** and create a tunnel using one of these options: - -- [ngrok](https://ngrok.com) -   is easy to set up and works across all platforms. -- [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/) -   provides persistent URLs and advanced features. -- [Tailscale Funnel](https://tailscale.com/kb/1223/tailscale-funnel) -   is ideal for sharing within a team or organization. - -### ngrok (Recommended for Getting Started) - -[ngrok](https://ngrok.com)  is easy to set up and works across all platforms. - -1. **Install ngrok:** - - ```bash - # macOS - brew install ngrok - - # Or download from https://ngrok.com/download - ``` - - On Windows, install ngrok from [ngrok.com/download](https://ngrok.com/download) . - -2. **Create a tunnel:** - - ```bash - ngrok http 8000 - ``` - -3. **Copy your URL:** Look for the “Forwarding” line in the ngrok output: - - PLAINTEXT - - ``` - Forwarding https://abc123.ngrok-free.app -> http://localhost:8000 - ``` - - Copy the `https://abc123.ngrok-free.app` URL - this is your public URL. - - -**Pros:** - -- Quick setup, no required for basic use -- Automatic HTTPS -- Web dashboard to inspect requests - -**Cons:** - -- Free tier URLs change on each restart -- May show interstitial page for free tier - -### Cloudflare Tunnel - -[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/)  provides persistent URLs and advanced features. - -1. **Install cloudflared:** - - ```bash - # macOS - brew install cloudflare/cloudflare/cloudflared - - # Or download from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/ - ``` - - On Windows, install cloudflared from [Cloudflare Tunnel install docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/) . - -2. **Create a tunnel:** - - ```bash - cloudflared tunnel --url http://localhost:8000 - ``` - -3. **Copy your URL:** Look for the “Your quick Tunnel has been created” message with your URL. - - -**Pros:** - -- Free tier includes persistent URLs (with setup) -- Built-in DDoS protection -- Access control features - -**Cons:** - -- Requires Cloudflare for persistent URLs -- More complex setup for advanced features - -### Tailscale Funnel - -[Tailscale Funnel](https://tailscale.com/kb/1223/tailscale-funnel)  is ideal for sharing within a team or organization. - -1. **Install Tailscale:** - - ```bash - # macOS - brew install tailscale - - # Or download from https://tailscale.com/download - ``` - - On Windows, install Tailscale from [tailscale.com/download](https://tailscale.com/download) . - -2. **Authenticate:** - - ```bash - tailscale up - ``` - -3. **Create a funnel:** - - ```bash - tailscale funnel 8000 - ``` - -4. **Get your URL:** Tailscale will display your funnel URL (e.g., `https://my-machine.tail-scale.ts.net`) - - -**Pros:** - -- Persistent URLs tied to your machine -- Private by default (only shared with specified ) -- No bandwidth limits - -**Cons:** - -- Requires Tailscale -- Best for team/organization use cases - -### Register your MCP Server in Arcade - -Once you have a public URL, register your server in the Arcade dashboard to make it accessible through the . - -1. **Navigate to the Servers page** in your [Arcade dashboard](https://api.arcade.dev/dashboard/servers)  - -2. **Click “Add Server”** - -3. **Fill in the registration form:** - - - **ID**: Choose a unique identifier (e.g., `my-mcp-server`) - - **Server Type**: Select “Arcade” - - **URL**: Enter your public tunnel URL from Step 2 - - **Secret**: Enter a secret for your server (or use `dev` for testing) - - **Timeout**: Configure request timeout (default: 30s) - - **Retry**: Configure retry attempts (default: 3) -4. **Click “Create”** - - -Here’s an example of a configuration: - -```yaml -ID: my-mcp-server -Server Type: Arcade -URL: https://abc123.ngrok-free.app -Secret: my-secure-secret-123 -Timeout: 30s -Retry: 3 -``` - -### Test the connection to your MCP Server - -You can now test your Server by making requests using the Playground, or an MCP client: - -### Playground - -1. **Go to the [Arcade Playground](https://api.arcade.dev/dashboard/playground/chat)**  - -2. **Select your server** from the dropdown - -3. **Choose a ** from your server - -4. **Execute the ** with test parameters - -5. **Verify the response:** - - - Check that the response is correct - - View request logs in your local server terminal - - Inspect the tunnel dashboard for request details - -### MCP Client - -1. Use an app that supports clients, like AI assistants and IDEs: - - [Visual Studio Code](/get-started/mcp-clients/visual-studio-code.md) - - - [Claude Desktop](/get-started/mcp-clients/claude-desktop.md) - - - [Cursor](/get-started/mcp-clients/cursor.md) - -2. Enable your Server from the list of available -3. Verify that the response is correct and you see request logs in your Server - -## Key Concepts - -- **HTTP Transport** Run your server with HTTP transport to expose your via a REST/SSE API -- **Secure Tunnels** Create a secure tunnel to expose your server publicly -- **Arcade Registration** Register your server in Arcade to make it accessible through the -- **Playground Testing** Test your server with the Arcade Playground - -## Best practices - -- **Persistent URLs**: For production use, set up a persistent public URL rather than ephemeral ones -- **TLS**: Use a TLS-enabled URL for production use -- **Monitoring**: Set up monitoring for your Server to ensure availability - -## Troubleshooting - -- **Connection issues**: Ensure your public URL is accessible and that your local Server is running -- **Timeout errors**: If your Server takes too long to respond, increase the timeout value in the configuration - -## Next steps - -- [Create custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) - for your Server -- [Set up authentication](/guides/create-tools/tool-basics/create-tool-auth.md) - for secure access to resources -- [Configure secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) - for your Server - -Last updated on February 10, 2026 - -[Arcade Cloud](/en/guides/deployment-hosting/arcade-cloud.md) -[Configure Arcade's engine](/en/guides/deployment-hosting/configure-engine.md) diff --git a/public/_markdown/en/guides/logic-extensions/build-your-own.md b/public/_markdown/en/guides/logic-extensions/build-your-own.md deleted file mode 100644 index 48a644b7c..000000000 --- a/public/_markdown/en/guides/logic-extensions/build-your-own.md +++ /dev/null @@ -1,342 +0,0 @@ ---- -title: "Build Your Own" -description: "Build your own Logic Extensions webhook server from the OpenAPI spec" ---- -[Contextual Access](/en/guides/contextual-access.md) -Build Your Own - -# Build Your Own Contextual Access Server - -This guide walks through implementing a webhook server that satisfies the Contextual Access Webhook contract. You can write it in any language — the contract is defined by an OpenAPI 3.0 spec. - -## OpenAPI spec - -The canonical contract is maintained in the [ArcadeAI/schemas](https://github.com/ArcadeAI/schemas)  repository: - -- **OpenAPI 3.0 spec:** [logic\_extensions/http/1.0/schema.yaml](https://github.com/ArcadeAI/schemas/blob/main/logic_extensions/http/1.0/schema.yaml) -   - -You can browse it interactively on the [API Reference](/references/contextual-access-webhook-api.md) page. - -## Generate server stubs - -Use the OpenAPI spec with standard code generators to scaffold your server: - -Language - -Generator - -**Go** - -[oapi-codegen](https://github.com/deepmap/oapi-codegen) - , [ogen](https://github.com/ogen-go/ogen) -  - -**Python** - -[openapi-generator](https://openapi-generator.tech/) - , [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator) -  - -**TypeScript** - -[openapi-typescript](https://github.com/drwpow/openapi-typescript) - , [openapi-generator](https://openapi-generator.tech/) -  - -**Example (Go with oapi-codegen):** - -```bash -curl -sL https://raw.githubusercontent.com/ArcadeAI/schemas/main/logic_extensions/http/1.0/schema.yaml -o schema.yaml -oapi-codegen -generate types,server -package server schema.yaml -``` - -The [logic-extensions-examples](https://github.com/ArcadeAI/logic-extensions-examples)  repo uses this approach — see `pkg/server/` for the generated types. - -## Endpoints to implement - -Your server needs to expose HTTP POST endpoints for each hook point you want to handle. Endpoint paths are fully configurable in the Dashboard — the defaults are shown below. - -Endpoint - -Hook point - -Required - -`POST /access` - -Access Hook - -Only if you need tool access control - -`POST /pre` - -Pre-Execution Hook - -Only if you need tool request validation/modification - -`POST /post` - -Post-Execution Hook - -Only if you need tool output filtering/modification - -You do not need to implement all endpoints. Only implement the ones you configure in the Dashboard. - -## Pre-Execution Hook - -**When:** Before each execution. - -### Request (Arcade sends to your server) - -Field - -Type - -Description - -`execution_id` - -string - -Correlates request/response across hooks - -`tool` - -object - -`name`, `toolkit`, `version` - -`inputs` - -object - -Tool inputs (name → value) - -`context` - -object - -`authorization` (OAuth per provider), `secrets` (key names only), `metadata`, `user_id` - -### Response (your server returns) - -Field - -Type - -Required - -Description - -`code` - -string - -Yes - -`OK`, `CHECK_FAILED`, or `RATE_LIMIT_EXCEEDED` - -`error_message` - -string - -No - -Shown to the agent when denying - -`override` - -object - -No - -`inputs` and/or `secrets` to modify the request - -## Post-Execution Hook - -**When:** After execution. - -### Request - -Field - -Type - -Description - -`execution_id` - -string - -Correlates with pre-execution hook - -`tool` - -object - -`name`, `toolkit`, `version` - -`inputs` - -object - -Tool inputs - -`success` - -boolean - -Whether the tool call succeeded - -`output` - -any - -The execution output (any JSON type) - -`execution_code` - -string - -Status from worker - -`execution_error` - -string - -Error message from tool call - -`context` - -object - -Same as pre-execution hook - -### Response - -Field - -Type - -Required - -Description - -`code` - -string - -Yes - -`OK`, `CHECK_FAILED`, or `RATE_LIMIT_EXCEEDED` - -`error_message` - -string - -No - -Shown to the agent when denying - -`override` - -object - -No - -`output` to replace the response returned to the agent - -## Access Hook - -**When:** When Arcade needs to know which a can see. Supports batch requests. - -### Request - -Field - -Type - -Description - -`user_id` - -string - -User to check - -`toolkits` - -object - -Map of toolkit name → toolkit info (tools, versions, requirements) - -### Response - -Return either an allow list or a deny list (not both): - -Field - -Type - -Description - -`only` - -object - -If present, **only** these tools are allowed (deny list ignored) - -`deny` - -object - -Tools listed here are denied (ignored if `only` is present) - -If you return neither field, the Arcade treats it as “no change” — all remain allowed at this hook. - -## Response codes (pre and post) - -Code - -Meaning - -`OK` - -Proceed; optional `override` is applied - -`CHECK_FAILED` - -Deny the operation; `error_message` is shown to the agent - -`RATE_LIMIT_EXCEEDED` - -Deny with rate-limit semantics - -## Authentication - -Arcade sends one of: - -- **Bearer:** `Authorization: Bearer ` (token from extension config) -- **mTLS:** Client certificate during TLS handshake; optional CA cert for server verification - -Configure the auth method when creating your extension in the Dashboard. - -## Failure handling and retries - -- **Timeout:** Configurable per extension (default 5s); can be overridden per hook. On timeout, the hook’s failure mode applies (Fail closed = block, Fail open = allow). -- **Retries:** Optional at the extension level; only for transient failures (5xx, timeout, connection error). 4xx responses are not retried. - -## Next steps - -- [API Reference](/references/contextual-access-webhook-api.md) - — Interactive Swagger documentation for the full schema -- [Run an extension](/guides/contextual-access/examples.md) - — Try the open-source example servers as reference -- [How Hooks Work](/guides/contextual-access/how-hooks-work.md) - — Understand execution order, phases, and failure modes - -Last updated on February 10, 2026 - -[Running an Extension](/en/guides/contextual-access/examples.md) -[MCP Gateways](/en/guides/mcp-gateways.md) diff --git a/public/_markdown/en/guides/mcp-gateways.md b/public/_markdown/en/guides/mcp-gateways.md deleted file mode 100644 index 3c42a76f2..000000000 --- a/public/_markdown/en/guides/mcp-gateways.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: "MCP Gateways" -description: "Connect multiple MCP servers to your agent, application, or IDE with Arcade MCP Gateways" ---- -MCP GatewaysMCP Gateways - -# MCP Gateways - - Gateways are a way to connect multiple MCP Servers to your agent, application, or IDE. allow you to federate the tools from multiple into a single collection for easier management, control, and access. You can mix and match tools from different MCP Servers in the same . - -## Why use MCP Gateways? - -- Federate tools - Combine tools from multiple servers into a single endpoint -- Control access - Choose exactly which are available to each gateway -- Mix and match - Use different combinations for different use cases -- Simplify configuration - One gateway URL instead of multiple server configurations -- Server Instructions - Set instructions for how the client should use your gateway to help the LLM better understand your use case - -## Create an MCP Gateway - -You can create an Gateway in two ways: - -[Create via Dashboard](/guides/mcp-gateways/create-via-dashboard.md) -[Create via AI Assistant](/guides/mcp-gateways/create-via-ai.md) - -Dashboard - Use the web interface for full control over gateway settings, authentication modes, and selection. Best for production configurations and when you need to use tools that you built yourself, or were not built by Arcade. - -AI Assistant - Describe what you want in natural language and let AI select the right for you. Best for quickly creating a gateway without ever leaving your chat interface. - -## Add remote MCP servers - -Use remote servers when your live outside Arcade. Register the server once, then select its tools in your gateways. - -[Add remote MCP servers](/guides/mcp-gateways/add-remote-servers.md) - -## Connect to an MCP Gateway - -Any client that supports the Streamable HTTP transport can use an Arcade . Use your gateway URL in the following format: - -PLAINTEXT - -``` -https://api.arcade.dev/mcp/ -``` - -Learn how to [connect MCP Gateways to your preferred client](/get-started/mcp-clients.md). - -## Authentication - - Gateways support two authentication modes: - -Mode - -Best For - -How It Works - -**Arcade Auth (Recommended)** - -Development, testing, internal use - -Users authenticate with their Arcade account via OAuth - -**Arcade Headers** - -Production when end-users shouldn’t authenticate via Arcade - -Pass `Authorization: Bearer {your_api_key}` header and `Arcade-User-ID` header with the end-user identifier - -See [Create via Dashboard](/guides/mcp-gateways/create-via-dashboard.md) for detailed authentication configuration. - -## Next Steps - -- [Create an Arcade](https://app.arcade.dev/register) - if you haven’t already -- [Browse available integrations](/resources/integrations.md) - to see what you can add to your gateway - -Last updated on January 30, 2026 - -[Build Your Own](/en/guides/contextual-access/build-your-own.md) -[Add remote MCP servers](/en/guides/mcp-gateways/add-remote-servers.md) diff --git a/public/_markdown/en/guides/mcp-gateways/add-remote-servers.md b/public/_markdown/en/guides/mcp-gateways/add-remote-servers.md deleted file mode 100644 index 09bf000cf..000000000 --- a/public/_markdown/en/guides/mcp-gateways/add-remote-servers.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: "Add remote MCP servers" -description: "Register a remote MCP server in Arcade and use its tools in gateways and SDKs" ---- -[MCP Gateways](/en/guides/mcp-gateways.md) -Add remote MCP servers - -# Add remote MCP servers - -Use this guide if you want to connect an existing server that Arcade does not host. You will add the server to your Arcade project, expose its tools in , and call those from Arcade SDKs. - -## Outcomes - -Register a remote server and use its in gateways and SDKs. - -### You will Learn - -- Add a remote server to an Arcade -- Configure advanced connection settings -- Use the server tools in Gateways and SDKs - -### Prerequisites - -- An [Arcade](https://app.arcade.dev/register) - -- A remote server URL that Arcade can reach -- Access to any auth credentials your server requires - -## Why use MCP Gateways for Remote MCP Servers - - Gateways let you manage and filter tools in one place, so your agent or team uses a curated set of tools across clients. This is especially useful for remote MCP servers, where you may not have control over the tools available. By using , you can select the you want to use and hide the ones you don’t want to use. - -## Add the remote server to your project - -### Open the MCP servers page - -Go to the [MCP servers dashboard](https://api.arcade.dev/dashboard/servers)  for your and click **Add server**. - -### Enter the required fields - -Arcade requires two fields for remote servers: - -- **ID**: A unique, human-readable identifier (for example, `render-mcp-server`) -- **URI**: The public URL for your server (for example, `https://mcp.render.com/mcp`) - -Use the [MCP Debugger](https://mcpdebugger.dev)  to verify the remote server’s compliance before adding it to Arcade. - -![Remote MCP server setup](/_next/image?url=%2Fimages%2Fmcp-gateway%2Fremote-mcp-server-setup.png&w=750&q=75) - -### Save and confirm the connection - -Create the server and confirm that Arcade lists the server tools in your project. If the remote server requires authentication, Arcade will prompt you to complete the OAuth flow. - -Arcade pre-loads the list of tools available to the user who configures the remote server so that you can filter them by your own criteria in your . Be sure to connect as an ‘admin’ user who has access to the broadest selection of tools in the remote server. Arcade then re-load the list of tools for every user using your - if a is not available to the agent’s end-, it will not be available via the gateway. - -## Configure advanced settings - -Remote servers often require more than a URL. Use **Advanced settings** to configure connection, OAuth, and header details. - -Common settings include: - -- **Connection settings**: Configure timeout and retry values for calls. -- **OAuth2 authorization (optional)**: Add client ID and client secret, and set an authorization URL if it differs from the server URI. Use the provided redirect URI when configuring your OAuth app. -- **Custom headers**: Add headers such as `Authorization` or `X-API-Key` and reference secrets with `${secret:NAME}`. -- **Header secrets**: Store API tokens or passwords and reference them in headers. - -## Use remote tools in MCP Gateways - -Once the server is registered, its tools show up in the Playground for this project, as well as in the Gateway picker. - -1. Create or edit an Gateway in the [MCP Gateways dashboard](https://app.arcade.dev/mcp-gateways) -  . -2. Open **Select ** and filter by your remote server name. -3. Choose the you want to expose through the gateway. - -## Call remote tools from Arcade SDKs - -Remote tools behave like any other Arcade . Try it out in the Playground first to learn any nuances about the name and arguments. Copy the tool name from the tool picker or tool catalog, then call it with the SDK. - -### Python - -```python -from arcade import Arcade - -client = Arcade(api_key="ARCADE_API_KEY") - -result = client.tools.execute( - tool_name="render-mcp-server.get_key_value", - input={"keyValueId": "foo-bar"}, - user_id="user-123", -) - -print(result) -``` - -### TypeScript - -```typescript -import { Arcade } from "@arcadeai/arcade"; - -const client = new Arcade({ apiKey: "ARCADE_API_KEY" }); - -const result = await client.tools.execute({ - tool_name: "render-mcp-server.get_key_value", - input: { keyValueId: "foo-bar" }, - user_id: "user-123", -}); - -console.log(result); -``` - -## Limitations and caveats - -Remote servers must be reachable from Arcade and must support the Streamable HTTP(s) transport. If your server depends on non-HTTP transports, Arcade cannot proxy it. - -- If the remote server is offline, gateway and SDK calls will fail until it is reachable again. -- Gateways only expose the tools you select, not every on the server. -- Arcade only supports tools from remote servers today. Prompts, resources, and sampling are not supported yet. - -## Next steps - -- [Create an MCP Gateway](/guides/mcp-gateways/create-via-dashboard.md) - -- [Connect to MCP clients](/get-started/mcp-clients.md) - - -Last updated on January 30, 2026 - -[MCP Gateways](/en/guides/mcp-gateways.md) -[Create via Dashboard](/en/guides/mcp-gateways/create-via-dashboard.md) diff --git a/public/_markdown/en/guides/mcp-gateways/create-via-ai.md b/public/_markdown/en/guides/mcp-gateways/create-via-ai.md deleted file mode 100644 index dc6a6b4ed..000000000 --- a/public/_markdown/en/guides/mcp-gateways/create-via-ai.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: "Arcade Gateway Assistant" -description: "Create and manage MCP Gateways using AI directly from your chat interface" ---- -[MCP Gateways](/en/guides/mcp-gateways.md) -Create via AI Assistant - -# Arcade Gateway Assistant - -## Outcomes - -Create and manage gateways directly from your chat interface using natural language. - -### Prerequisites - -1. Create an [Arcade](https://app.arcade.dev/register) - -2. An \-compatible chat client ([Cursor](/get-started/mcp-clients/cursor.md) - , [Claude Desktop](/get-started/mcp-clients/claude-desktop.md) - , [VS Code](/get-started/mcp-clients/visual-studio-code.md) - , etc.) - -## Setup - -### Connect to the Gateway Assistant - -The Gateway Assistant is an server that creates and manages . To use it, add the Arcade Gateway Assistant (using Arcade Auth) to your MCP client using this URL: - -PLAINTEXT - -``` -https://ctl.arcade.dev/mcp -``` - -The Gateway Assistant uses **Arcade Auth**. This means you’ll authenticate with your Arcade to access the assistant. - -Each client has a different setup process. See [Connect to MCP clients](/get-started/mcp-clients.md) for detailed instructions for adding the Gateway Assistant to Cursor, Claude Desktop, VS Code, and other supported clients with Arcade Auth. - -### Authenticate - -When you first use the Gateway Assistant, the system will prompt you to authenticate with your Arcade . This is a one-time setup that allows the assistant to create and manage gateways on your behalf. - -### Start creating gateways - -Ask your AI assistant to create a gateway by describing what you want to do. For example: - -> “I want to send emails with Gmail and manage my calendar with Google Calendar. Create a gateway for this.” - -> “Create a gateway that creates and manages gateways for GitHub PRs and post updates to Slack.” - -The assistant will select the appropriate from Arcade’s catalog and create a gateway for you. - -## After creating a gateway - -Once you’ve created a gateway, you’ll need to add it to your chat client as a separate server. The assistant will provide your new gateway’s MCP URL (for example, `https://api.arcade.dev/mcp/`). - -Follow the same process you used to add the Gateway Assistant - see [Connect to MCP clients](/get-started/mcp-clients.md) for setup instructions specific to your client. - -The Gateway Assistant creates gateways that use **Arcade Auth** by default. This means you’ll authenticate with your Arcade to access the gateway. For production use cases with end who don’t have Arcade accounts, you can modify the gateway’s authentication settings in the [dashboard](https://api.arcade.dev/dashboard/mcp-gateways) . - -Last updated on January 30, 2026 - -[Create via Dashboard](/en/guides/mcp-gateways/create-via-dashboard.md) -[Overview](/en/guides/tool-calling.md) diff --git a/public/_markdown/en/guides/mcp-gateways/create-via-dashboard.md b/public/_markdown/en/guides/mcp-gateways/create-via-dashboard.md deleted file mode 100644 index f3f673f9b..000000000 --- a/public/_markdown/en/guides/mcp-gateways/create-via-dashboard.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Create via Dashboard" -description: "Create and configure MCP Gateways using the Arcade dashboard" ---- -[MCP Gateways](/en/guides/mcp-gateways.md) -Create via Dashboard - -# Create via Dashboard - -## Outcomes - -Create and configure an Gateway using the Arcade dashboard with full control over settings. - -### Prerequisites - -1. Create an [Arcade](https://app.arcade.dev/register) - - -## Create a Gateway - -To create an Gateway, go to the [MCP Gateways dashboard](https://api.arcade.dev/dashboard/mcp-gateways)  and click the “Create ” button. - -When configuring an Gateway, you can select the tools you want to include in the Gateway from any available to the : - -![MCP Gateway Tool Filter](/_next/image?url=%2Fimages%2Fmcp-gateway-tool-filter-light.png&w=3840&q=75) ![MCP Gateway Tool Filter](/_next/image?url=%2Fimages%2Fmcp-gateway-tool-filter-dark.png&w=3840&q=75) - -## Configuration Options - -The options available when configuring an Gateway are: - -- **Name**: The name of the Gateway. Informative only. -- **Description**: The description of the Gateway. This is useful for humans and some MCP clients may surface this information to the . -- **LLM Instructions**: Optional instructions for the LLM about how to use the Gateway. -- **Slug**: The slug of the Gateway. This is the URL slug that will be used to access the . It must be unique. -- **Authentication**: The authentication mode to use for the Gateway. This determines how the will authenticate requests to the . Users will still need to authenticate to the within the MCP Gateway as normal. - - **Arcade Auth**: To access the Gateway, you’ll need to authenticate with your Arcade account. This authentication mode is recommended for in development or testing phase, or for internal use when you know all the users will have Arcade . - - **Arcade Headers**: To access the Gateway, you’ll need to authenticate with your Arcade account by passing an key in the `Authorization` header and the ID of your end-user in the `Arcade-User-ID` header. This authentication mode is recommended for in production when your agent or application has users without Arcade . -- **Allowed **: A selection of tools in the Arcade Tool Catalog that will be available to the Gateway. - -## After Creating a Gateway - -Once you’ve created a gateway, you’ll need to add it to your chat client. The assistant will provide the URL (for example, `https://api.arcade.dev/mcp/`). - -See [Connect to MCP clients](/get-started/mcp-clients.md) for setup instructions specific to your client. - -Last updated on January 30, 2026 - -[Add remote MCP servers](/en/guides/mcp-gateways/add-remote-servers.md) -[Create via AI Assistant](/en/guides/mcp-gateways/create-via-ai.md) diff --git a/public/_markdown/en/guides/security.md b/public/_markdown/en/guides/security.md deleted file mode 100644 index 1b8bae03f..000000000 --- a/public/_markdown/en/guides/security.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: "Security" -description: "Learn about security best practices for MCP servers and Arcade tools, plus information about our security research program" ---- -Security & ComplianceOverview - -# Security - -Learn about security best practices for building and deploying secure servers and Arcade . Use these resources when implementing production-ready tools that handle sensitive data or require robust security measures. - -Security is crucial when building that interact with external services, handle data, or operate in production environments. Arcade provides comprehensive security guidance and actively maintains a security research program. - -- [Securing Arcade MCP](/guides/security/securing-arcade-mcp.md) - -- [Secure your MCP server](/guides/security/secure-your-mcp-server.md) - -- [Security research program](/guides/security/security-research-program.md) - - -Last updated on January 30, 2026 - -[Arcade Deploy](/en/guides/deployment-hosting/arcade-deploy.md) -[Securing Arcade MCP](/en/guides/security/securing-arcade-mcp.md) diff --git a/public/_markdown/en/guides/security/secure-your-mcp-server.md b/public/_markdown/en/guides/security/secure-your-mcp-server.md deleted file mode 100644 index 0515d4033..000000000 --- a/public/_markdown/en/guides/security/secure-your-mcp-server.md +++ /dev/null @@ -1,458 +0,0 @@ ---- -title: "Adding Resource Server Auth" -description: "Secure your HTTP MCP server with OAuth 2.1 Resource Server auth" ---- -[Security & Compliance](/en/guides/security.md) -Secure your MCP server - -# Adding Resource Server Auth to Your MCP Server - -You’ve built an server with that require authorization or secrets. Now you want to deploy it over HTTP so others can use it. But how do you secure it so only authorized can access your tools? - -**Want Arcade to handle this for you?** Use [`arcade deploy`](/guides/deployment-hosting/arcade-deploy.md) to deploy your server to Arcade. We’ll secure it automatically with no OAuth configuration on your end required. This guide is for self-hosted deployments where you manage your own authorization server. - -Resource Server auth enables your HTTP server to act as an OAuth 2.1 Protected Resource (compliant with [MCP’s specification for Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) ), validating Bearer tokens on every request. This unlocks support for tool-level authorization and secrets on HTTP servers, allowing you to host secure anywhere (local, on-premise, or third-party hosted). - -## Outcomes - -Add [MCP compliant OAuth 2.1 front-door authentication](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)  to your HTTP server to enable \-level authorization and secrets. - -### You will Learn - -- What Resource Server auth is and why it’s needed -- How to configure your server to validate OAuth tokens -- How to support multiple authorization servers -- How to use environment variables for production deployments - -### Prerequisites - -- An existing server created with `arcade new` (see [Create an MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - ) -- Understanding of [MCP Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) -   -- An OAuth 2.1 compliant authorization server (e.g., WorkOS AuthKit, Auth0, Descope, etc.) -- Authorization server’s JWKS endpoint URL - -## Understanding Resource Server Auth - -### What is it? - -Resource Server auth turns your server into an [OAuth 2.1 Protected Resource](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles)  that validates Bearer tokens on every HTTP request. Your trusts one or more [Authorization Servers](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles)  to issue valid tokens for accessing your MCP server. - -### Why is it needed? - -By default, HTTP servers cannot use that require authorization or secrets for security reasons. - -Resource Server auth solves this by: - -1. **Authenticating every request** - Validates the Bearer token before processing any messages -2. **Extracting identity** - The token’s `sub` claim becomes the `context.user_id` for execution -3. **Enabling secure ** - Tools requiring authorization or secrets can now safely execute over HTTP, but tools with tool-level auth will still require authenticating to the downstream service -4. **Supporting OAuth discovery** - clients can automatically discover your authentication requirements - -**Resource Server auth vs \-level auth**: Resource Server auth secures _access to your server_, while tool-level authorization secures _access to third-party APIs that your tools use_. They work together: Resource Server auth identifies _who_ is calling your server, and tool-level auth enables tools to act _on behalf of that _. - -## Choose Your Configuration Approach - -The `arcade_mcp_server.resource_server` module provides two validators: - -### ResourceServerAuth (Recommended) - -**`ResourceServerAuth`** - Full-featured OAuth 2.1 Resource Server with: - -- Support for multiple authorization servers (multi-IdP, regional endpoints) -- OAuth discovery metadata endpoint -- Environment variable configuration -- Best for production deployments - -### JWKSTokenValidator (Simple) - -**`JWKSTokenValidator`** - Direct JWKS-based validation with: - -- Simple setup for single authorization server -- No OAuth discovery endpoint -- Requires explicit configuration -- Best for development or simple use cases - -## Configure Your Authorization Server - -First, gather these details from your authorization server: - -- **Authorization Server URL** - The base URL of your authorization server (e.g., `https://your-app.authkit.app`) -- **Issuer** - The expected `iss` claim in tokens (usually same as authorization server URL) -- **JWKS URI** - Where to fetch public keys for token verification (e.g., `https://your-app.authkit.app/oauth2/jwks`) -- **Canonical URL** - Your server’s public URL (e.g., `http://127.0.0.1:8000/mcp` if running locally) - -By default, your server expects the **Canonical URL** to match the `aud` (audience) claim in tokens. If your authorization server uses a different audience, you can override this with the `expected_audiences` parameter on `AuthorizationServerEntry`. - -## Add Authentication to Your Server - -Update your `server.py` to add the `auth` parameter to `MCPApp`: - -### ResourceServerAuth - -```python -# server.py -#!/usr/bin/env python3 -"""my_server MCP server""" - -from arcade_mcp_server import MCPApp -from arcade_mcp_server.resource_server import ( - AccessTokenValidationOptions, - AuthorizationServerEntry, - ResourceServerAuth, -) - -# Setup your resource server that trusts a single Authkit authorization server -resource_server_auth = ResourceServerAuth( - canonical_url="http://127.0.0.1:8000/mcp", - authorization_servers=[ - AuthorizationServerEntry( - authorization_server_url="https://your-workos.authkit.app", - issuer="https://your-workos.authkit.app", - jwks_uri="https://your-workos.authkit.app/oauth2/jwks", - algorithm="RS256", - # Authkit doesn't set the aud claim as the MCP server's canonical URL by default - expected_audiences=["your-authkit-client-id"], - ) - ], -) - -# Pass the resource_server_auth to MCPApp -app = MCPApp( - name="my_server", - version="1.0.0", - auth=resource_server_auth # Enable Resource Server auth -) - -# Your tools here... -@app.tool -def greet(name: Annotated[str, "The name of the person to greet"]) -> str: - """Greet a person by name.""" - return f"Hello, {name}!" - -if __name__ == "__main__": - app.run(transport="http", host="127.0.0.1", port=8000) -``` - -### Multiple Auth Servers - -```python -# server.py -#!/usr/bin/env python3 -"""my_server MCP server""" - -from arcade_mcp_server import MCPApp -from arcade_mcp_server.resource_server import ( - ResourceServerAuth, - AuthorizationServerEntry, -) - -# Support multiple authorization servers (multi-IdP) -resource_server_auth = ResourceServerAuth( - canonical_url="http://127.0.0.1:8000/mcp", - authorization_servers=[ - AuthorizationServerEntry( - authorization_server_url="https://your-workos.authkit.app", - issuer="https://your-workos.authkit.app", - jwks_uri="https://your-workos.authkit.app/oauth2/jwks", - # Authkit doesn't set the aud claim as the MCP server's canonical URL - expected_audiences=["your-authkit-client-id"], - ), - AuthorizationServerEntry( # Keycloak example configuration - authorization_server_url="http://localhost:8080/realms/mcp-test", - issuer="http://localhost:8080/realms/mcp-test", - jwks_uri="http://localhost:8080/realms/mcp-test/protocol/openid-connect/certs", - algorithm="RS256", - expected_audiences=["your-keycloak-client-id"], - ) - ], -) - -app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth) -``` - -Multiple authorization servers enable scenarios like: - -- **Multi-IdP**: Accept tokens from WorkOS _and_ GitHub -- **Regional endpoints**: Multiple authorization server URLs with shared keys -- **Migration**: Smoothly transition between authorization servers - -### Environment Variables - -```python -# server.py -#!/usr/bin/env python3 -"""my_server MCP server""" - -from arcade_mcp_server import MCPApp -from arcade_mcp_server.resource_server import ResourceServerAuth - -# Configuration loaded from environment variables -# No parameters needed! -resource_server_auth = ResourceServerAuth() - -app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth) -``` - -Create a `.env` file: - -```bash -# .env -MCP_SERVER_AUTH_CANONICAL_URL=http://127.0.0.1:8000/mcp -MCP_SERVER_AUTH_AUTHORIZATION_SERVERS='[ - { - "authorization_server_url": "https://your-workos.authkit.app", - "issuer": "https://your-workos.authkit.app", - "jwks_uri": "https://your-workos.authkit.app/oauth2/jwks", - "algorithm": "RS256", - "expected_audiences": ["my-client-id"] - } -]' -``` - -Environment variable configuration is **recommended for production** as it separates auth configuration from your code and allows deployment-time configuration. Note that **explicit parameters take precedence over environment variables**, allowing you to override specific settings when needed. - -### JWKSTokenValidator - -```python -# server.py -#!/usr/bin/env python3 -"""my_server MCP server""" - -from arcade_mcp_server import MCPApp -from arcade_mcp_server.resource_server import JWKSTokenValidator - -# Configure JWKS token validation -validator = JWKSTokenValidator( - jwks_uri="https://your-workos.authkit.app/oauth2/jwks", - issuer="https://your-workos.authkit.app", - audience="http://127.0.0.1:8000/mcp", -) - -app = MCPApp( - name="my_server", - version="1.0.0", - auth=validator -) - -# Your tools here... -``` - -## Run Your Authenticated Server - -Start your server with HTTP transport: - -```bash -uv run server.py -``` - -Your server now requires valid Bearer tokens for all requests. You should see output like: - -```bash -INFO | 14:23:45 | Starting my_server v1.0.0 with 3 tools -INFO | 14:23:45 | Resource Server auth enabled: True -INFO | 14:23:45 | Accepted authorization server(s): https://your-app.authkit.app -``` - -## OAuth Discovery - -Now that your server is protected, you can see that your server exposes an OAuth discovery endpoint at `http://127.0.0.1:8000/mcp/.well-known/oauth-protected-resource`. This endpoint is used by clients to discover the authorization servers that are trusted by your server. - -```bash -curl http://127.0.0.1:8000/.well-known/oauth-protected-resource -``` - -You should see a response like: - -HTTP - -``` -{ - "resource":"http://127.0.0.1:8000/mcp", - "authorization_servers":["https://your-workos.authkit.app"] -} -``` - - clients can use this endpoint to automatically discover which authorization servers issue valid tokens for your server. - -## Verify Your Server is Protected - -Try calling your server without a token: - -```bash -curl -i http://127.0.0.1:8000/mcp/ -``` - -You should receive a 401 response with `WWW-Authenticate` header: - -HTTP - -``` -HTTP/1.1 401 Unauthorized -date: Tue, 02 Dec 2025 01:00:54 GMT -server: uvicorn -www-authenticate: Bearer, resource_metadata="http://127.0.0.1:8000/mcp/.well-known/oauth-protected-resource" -access-control-allow-origin: * -access-control-allow-methods: GET, POST, DELETE -access-control-allow-headers: Content-Type, Authorization, Mcp-Session-Id -access-control-expose-headers: WWW-Authenticate, Mcp-Session-Id -content-length: 12 - -Unauthorized -``` - -### Test Your Server with a Valid Token - -The easiest way to test your secure server by using the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector)  as your client & connecting to your server from it. - -## Advanced Configuration - -### Custom Token Validation Options - -Disable specific validations when needed: - -```python -from arcade_mcp_server.resource_server import ( - ResourceServerAuth, - AuthorizationServerEntry, - AccessTokenValidationOptions, -) - -resource_server_auth = ResourceServerAuth( - canonical_url="http://127.0.0.1:8000/mcp", - authorization_servers=[ - AuthorizationServerEntry( - authorization_server_url="https://your-app.authkit.app", - issuer="https://your-app.authkit.app", - jwks_uri="https://your-app.authkit.app/oauth2/jwks", - expected_audiences=["my-client-id"], - validation_options=AccessTokenValidationOptions( - verify_exp=True, # Still verify expiration (default) - verify_iat=True, # Still verify issued-at (default) - verify_iss=True, # Still verify issuer (default) - ), - ) - ], -) -``` - -**Security Note**: Token signature verification is always enabled and cannot be disabled. Additionally, the `sub` claim must always be present. Only disable other validations if your authorization server doesn’t comply with and you accept the risk of not validating all claims in the token. - -### Custom Expected Audiences - -By default, your server expects the token’s `aud` claim to match the `canonical_url`. If your authorization server uses a different audience value (like a client ID), override it with `expected_audiences`: - -```python -AuthorizationServerEntry( - authorization_server_url="https://your-app.authkit.app", - issuer="https://your-app.authkit.app", - jwks_uri="https://your-app.authkit.app/oauth2/jwks", - expected_audiences=["my-client-id"], # Override audience validation -) -``` - -You can also accept multiple audiences: - -```python -AuthorizationServerEntry( - authorization_server_url="https://auth.example.com", - issuer="https://auth.example.com", - jwks_uri="https://auth.example.com/jwks", - expected_audiences=["client-id-1", "client-id-2"], # Accept tokens for either audience -) -``` - -### Different JWT Algorithms - -```python -AuthorizationServerEntry( - authorization_server_url="https://auth.example.com", - issuer="https://auth.example.com", - jwks_uri="https://auth.example.com/jwks", - algorithm="ES256", # Use ECDSA instead of RSA -) -``` - -Supported algorithms: `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512` - -### Regional Authorization Servers with Shared Keys - -```python -resource_server_auth = ResourceServerAuth( - canonical_url="https://mcp.example.com", - authorization_servers=[ - AuthorizationServerEntry( - authorization_server_url="https://auth-us.example.com", - issuer="https://auth.example.com", # Same issuer - jwks_uri="https://auth.example.com/jwks", # Shared JWKS - ), - AuthorizationServerEntry( - authorization_server_url="https://auth-eu.example.com", - issuer="https://auth.example.com", # Same issuer - jwks_uri="https://auth.example.com/jwks", # Shared JWKS - ), - ], -) -``` - -## How It Works - -1. **Client makes request** with `Authorization: Bearer ` header -2. **Middleware intercepts** every HTTP request before processing -3. **Token validation** occurs: - - Fetches JWKS from authorization server - - Verifies token signature - - Checks expiration, issuer, and audience - - Extracts `sub` claim as ID -4. **Resource owner stored** in request -5. ** processing continues** with authenticated -6. ** execute** with `context.user_id` set from token’s `sub` claim - -### Security Features - -- ✅ **No token caching** - Every request validates the token fresh (per spec) -- ✅ **JWKS caching** - Public keys cached for performance (default 1 hour) -- ✅ **Algorithm enforcement** - Prevents algorithm confusion attacks -- ✅ **Signature verification** - Always enabled, cannot be disabled -- ✅ **RFC 6750 compliant** - Standard OAuth 2.0 Bearer token usage -- ✅ **RFC 9728 compliant** - OAuth 2.0 Protected Resource Metadata - -## Common Authorization Server Configurations - -### WorkOS AuthKit - -```python -AuthorizationServerEntry( - authorization_server_url="https://your-app.authkit.app", - issuer="https://your-app.authkit.app", - jwks_uri="https://your-app.authkit.app/oauth2/jwks", - expected_audiences=["my-client-id"], -) -``` - -### Keycloak - -```python -AuthorizationServerEntry( - authorization_server_url="http://localhost:8080/realms/mcp-test", - issuer="http://localhost:8080/realms/mcp-test", - jwks_uri="http://localhost:8080/realms/mcp-test/protocol/openid-connect/certs", - algorithm="RS256", - expected_audiences=["your-keycloak-client-id"], -) -``` - -## Next Steps - -- **Let Arcade secure your server instead**: [Learn about `arcade deploy`](/guides/deployment-hosting/arcade-deploy.md) - -- **Build with authorization**: [Create tools that use OAuth](/guides/create-tools/tool-basics/create-tool-auth.md) - -- **Use secrets securely**: [Create tools with secrets](/guides/create-tools/tool-basics/create-tool-secrets.md) - - -Last updated on January 30, 2026 - -[Securing Arcade MCP](/en/guides/security/securing-arcade-mcp.md) -[Security research program](/en/guides/security/security-research-program.md) diff --git a/public/_markdown/en/guides/security/securing-arcade-mcp.md b/public/_markdown/en/guides/security/securing-arcade-mcp.md deleted file mode 100644 index f692e7825..000000000 --- a/public/_markdown/en/guides/security/securing-arcade-mcp.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: "Securing Arcade MCP Deployments" -description: "Arcade - AI platform for developers" ---- -[Security & Compliance](/en/guides/security.md) -Securing Arcade MCP - -# Securing Arcade MCP Deployments - -You may have noticed that when you connected to the server you created with `arcade-mcp`, you could immediately call your tools from local MCP Clients and , like Claude and Cursor. This is because the `arcade-mcp` server is _not_ secured by any mechanism by default. Most use-cases for today are local development or local to a single machine, and we optimize for that use-case. - -However, you can secure your server in two ways: deploying it to Arcade or adding front-door OAuth. - -## Arcade Deploy - -When you `arcade deploy` your server, it will be secured behind the Arcade platform. - -Under the hood, we disable the routes provided by `arcade-mcp`, and use the as a gateway for your , which has a number of additional features. Arcade will create a randomized secure secret for your MCP server (via the `ARCADE_WORKER_SECRET` environment variable) so that your server is protected from unauthorized access, as well as being isolated from direct access from outside of the Arcade platform. Servers managed by Arcade (servers that are `arcade deploy`ed) serve `/worker` endpoints that are protected by this secret. The worker endpoints are `worker/health`, `/worker/tools`, and `/worker/tools/invoke`. The health endpoint is not protected by this secret, but the listing tools and invocations are. You can explore this behavior locally by setting the same environment variable in your local environment. - -Learn more about how to deploy your server to Arcade [here](/guides/deployment-hosting/arcade-deploy.md). - -## OAuth Resource Server Auth - -You can secure your server’s `/mcp` endpoints with OAuth 2.1 Resource Server auth. This turns your into a protected resource, enabling you to use with authorization and secrets over HTTP. - -This approach is ideal when: - -- You want to host your server yourself (local, on-premise, or third-party hosting) -- You already have an OAuth 2.1 compliant Authorization Server (e.g., WorkOS AuthKit, Auth0, Descope) - -Resource Server auth works alongside tool-level authorization. Resource Server auth secures access to the server itself, while \-level auth enables your tools to access third-party APIs on behalf of the authenticated . - -Learn more about adding front-door OAuth to your server [here](/guides/security/secure-your-mcp-server.md). - -### Client ID Metadata Documents (Coming soon) - -Coming soon, you will be able to secure your server using Client ID Metadata Documents (CIMD) for authorization. Learn more about how MCP integrates with OAuth [here](https://blog.modelcontextprotocol.io/posts/client_registration/) . - -Last updated on January 30, 2026 - -[Overview](/en/guides/security.md) -[Secure your MCP server](/en/guides/security/secure-your-mcp-server.md) diff --git a/public/_markdown/en/guides/security/security-research-program.md b/public/_markdown/en/guides/security/security-research-program.md deleted file mode 100644 index 3fb7015b1..000000000 --- a/public/_markdown/en/guides/security/security-research-program.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: "Security Research Program" -description: "Report security vulnerabilities and help us build safe and reliable tools" ---- -[Security & Compliance](/en/guides/security.md) -Security research program - -# Security Research Program - -At Arcade, security is fundamental to our mission of building safe and reliable . We recognize that the security research community plays a valuable role in identifying potential vulnerabilities. - -## Scope - -Our program covers security issues in: - -- Arcade production services and APIs -- authentication and authorization mechanisms -- Data handling and storage systems -- Published open-source components - -## What we’re looking for - -We’re interested in reports about: - -- Authentication or authorization bypasses -- Data exposure or leakage -- Injection vulnerabilities -- Logic flaws affecting behavior -- Issues that could compromise user data or integrity - -## Reporting process - -Please email [security@arcade.dev](mailto:security@arcade.dev) with: - -- A clear description of the issue -- Steps to reproduce -- Potential impact assessment -- Any relevant proof-of-concept code (please be responsible) - -We’ll acknowledge receipt within 72 hours and aim to provide an initial assessment within one week. - -## Guidelines - -- Please allow us reasonable time to address issues before public disclosure -- Avoid automated scanning that could impact service availability -- Do not access or modify other ’ data -- Keep any discovered vulnerabilities confidential until resolved - -## Recognition - -While we’re a small team with limited resources, we appreciate the effort researchers put into improving our security. We’ll credit researchers (with permission) in our security updates and may provide modest rewards for significant findings on a case-by-case basis. - -For questions about this program, please contact [security@arcade.dev](mailto:security@arcade.dev). - -Last updated on January 30, 2026 - -[Secure your MCP server](/en/guides/security/secure-your-mcp-server.md) -[Build Your Own](/en/guides/logic-extensions/build-your-own.md) diff --git a/public/_markdown/en/guides/tool-calling.md b/public/_markdown/en/guides/tool-calling.md deleted file mode 100644 index c1627dc2c..000000000 --- a/public/_markdown/en/guides/tool-calling.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: "Overview" -description: "Overview of language model tool calling and how to use tools with Arcade" ---- -Call toolsOverview - -# What are tools? - -Language models excel at text generation but struggle with tasks like calculations, data retrieval, or interacting with external systems. To make an analogy, these models have impressive _brains_ or reasoning capabilities, but lack the _hands_ to interact with the digital world. - -To solve this, many AI models support calling (sometimes referred to as ‘function calling’). - -## The use case for tool-calling - -Say a colleague shares a document with you on Google Drive, and you’d like an LLM to help you analyze it. - -You could go to your Drive/Docs, open the document, copy its contents, and paste it into your chat. But what if the LLM could do this for you? The Arcade Google Docs Server provides a [`SearchAndRetrieveDocuments`](/resources/integrations/productivity/googledocs.md#googledocssearchandretrievedocuments) . By calling it, the LLM can find and read the document without you having to do anything. - -After analyzing the document, you decide that a meeting is needed with your colleague. You can ask the LLM to schedule a meeting and it will use the [Google Calendar MCP Server](/resources/integrations/productivity/googlecalendar.md) to do it without you needing to leave the chat. - -Or you could ask the LLM to send a summary of the analysis to your colleague by email and it would use the [Gmail MCP Server](/resources/integrations/productivity/gmail.md) for that. - -## Possibilities for Application and AI Agent developers - -\-calling combines the reasoning power of LLMs with virtually any action available through APIs or code execution. - -With the Arcade SDKs, it is easy to build applications and AI that can leverage \-calling in order to provide an LLM-powered experience to in a secure and privacy-forward way. - -## How tool calling works - -AI models that support calling can determine when and how to use specific tools to fulfill a ’s request. The developer decides which tools to make available to the model, whether existing tools or tools they’ve built themselves. - -In the example above, when the asks: “_help me analyze the ‘ XYZ’ Google document shared by John_”, the LLM can use its reasoning capabilities to: - -1. Recognize that it needs to access external data; -2. Evaluate that the `GoogleDocs.SearchAndRetrieveDocuments` is the best way to get that data; -3. Call the with the appropriate parameters; -4. Read the document content and use it to answer the ’s questions. - -## The authorization problem - -One challenge to make all that happen is authorization. How do you give the LLM permission to access someone’s Google Docs and Gmail in a secure and convenient way? - -Arcade solves this problem by providing a standardized [interface for authorization](/get-started/about-arcade.md), as well as pre-built for [popular services](/references/auth-providers.md) such as Google, Dropbox, GitHub, Notion, and many more. - -Our SDK also [allows you to integrate](/references/auth-providers/oauth2.md) LLMs with any OAuth 2.0-compliant API. - -## Tool Augmented Generation (TAG) - -Similar to Retrieval Augmented Generation (RAG), calling allows the AI to use external data to answer questions. Unlike RAG, tool calling is more flexible and allows the AI to use tools that are much more diverse than text or vector search alone. - -The following is a diagram of how tool calling is used to provide to a language model similar to RAG. - -![Tool calling diagram 1](/images/tool-call-diagrams/tool-call-1.png) - -First, a language model is given a user’s request. The model then determines if it needs to use a to fulfill the request. If so, the model selects the appropriate tool from the tools listed in the request. - -The model then predicts the parameters of that and passes these parameters back to the client application. - -![Tool calling diagram 2](/images/tool-call-diagrams/tool-call-2.png) - -Now that the has been executed, the model can use the output to generate a response. - -![Tool calling diagram 3](/images/tool-call-diagrams/tool-call-3.png) - -This process shows the general outline of the Augmented Generation (TAG) process at a high level. - -### Next steps - -- Explore the [MCP Servers](/resources/integrations.md) - available on Arcade -- Build your own [custom MCP Server](/guides/create-tools/tool-basics/build-mcp-server.md) - - -Last updated on January 30, 2026 - -[Create via AI Assistant](/en/guides/mcp-gateways/create-via-ai.md) -[Handling errors](/en/guides/tool-calling/error-handling.md) diff --git a/public/_markdown/en/guides/tool-calling/call-third-party-apis.md b/public/_markdown/en/guides/tool-calling/call-third-party-apis.md deleted file mode 100644 index ef359fdbe..000000000 --- a/public/_markdown/en/guides/tool-calling/call-third-party-apis.md +++ /dev/null @@ -1,213 +0,0 @@ ---- -title: "Directly call third-party APIs" -description: "Guide on how to retrieve an authorization token to call third-party APIs directly" ---- -[Call tools](/en/guides/tool-calling.md) -Call third-party APIs - -# Directly call third-party APIs - -In this guide, you’ll learn how to use Arcade to obtain user authorization and interact with third-party services by calling their API endpoints directly, without using Arcade for execution or definition. We’ll use Google’s Gmail API as an example to demonstrate how to: - -- Get authorization tokens through Arcade -- Handle authentication flows -- Use tokens with external services - -This can be useful when you need to manage authorization flows in your application. - -### Prerequisites - -- Sign up for an [Arcade](https://app.arcade.dev/register) - if you haven’t already -- Generate an [Arcade API key](/get-started/setup/api-keys.md) - and take note of it - -### Install required libraries - -### Python - -`bash pip install arcadepy google-api-python-client google-auth-httplib2 google-auth-oauthlib` - -### JavaScript - -`bash npm install @arcadeai/arcadejs googleapis` - -### Start coding - -### Python - -Create a new file `direct_api_call.py` and import all libraries we’re going to use: - -```python -from arcadepy import Arcade -from google.oauth2.credentials import Credentials -from googleapiclient.discovery import build -``` - -### JavaScript - -Create a new file `direct_api_call.js` and import all libraries we’re going to use: - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; -import { google } from "googleapis"; -``` - -### Initialize the Arcade client - -Create an instance of the : - -### Python - -```python -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable -``` - -### JavaScript - -```javascript -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable -``` - -### Initiate an authorization request - -Use `client.auth.start()` to initiate the authorization process: - -### Python - -```python -# This would be your app's internal ID for the user (an email, UUID, etc.) -user_id = "{arcade_user_id}" - -# Start the authorization process - -auth_response = client.auth.start( -user_id=user_id, -provider="google", -scopes=["https://www.googleapis.com/auth/gmail.readonly"], -) - -``` - -### JavaScript - -```javascript -// Your app's internal ID for the user (an email, UUID, etc). -// It's used internally to identify your user in Arcade, not to identify with the Gmail service. -// Use your Arcade account email for testing: -const user_id = "{arcade_user_id}"; - -// Start the authorization process -let auth_response = await client.auth.start(user_id, "google", { - scopes: ["https://www.googleapis.com/auth/gmail.readonly"], -}); -``` - -### Guide the user through authorization - -If authorization is not completed, prompt the to visit the authorization URL: - -### Python - -```python -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) -``` - -### JavaScript - -```javascript -if (auth_response.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(auth_response.url); -} -``` - -### Wait for the user to authorize the request - -### Python - -```python -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) -``` - -### JavaScript - -```javascript -// Wait for the authorization to complete -auth_response = await client.auth.waitForCompletion(auth_response); -``` - -### Use the obtained token - -Once authorization is complete, you can use the obtained token to access the third-party service: - -### Python - -```python -credentials = Credentials(auth_response.context.token) -gmail = build("gmail", "v1", credentials=credentials) - -email_messages = ( -gmail.users().messages().list(userId="me").execute().get("messages", []) -) - -print(email_messages) - -``` - -### JavaScript - -```javascript -const auth = new google.auth.OAuth2(); -auth.setCredentials({ access_token: auth_response.context.token }); -const gmail = google.gmail({ version: "v1", auth }); - -// List email messages -const response = await gmail.users.messages.list({ - userId: "me", -}); - -const email_messages = response.data.messages || []; -console.log(email_messages); -``` - -### Execute the code - -### Python - -`python python3 direct_api_call.py` - -### JavaScript - -`javascript node direct_api_call.js` - -You should see an output similar to this:, which is a list of the email messages returned by the Gmail API: - -TEXT - -``` -[{'id': '195f77a8ce90f2c1', 'threadId': '195f77a8ce90f2c1'}, {'id': '195ed467a90e8538', 'threadId': '195ed467a90e8538'}, ...] -``` - -For each item in the list/array, you could use the [`users.messages.get`](https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages/get) endpoint to get the full message details. - -Consider using the [Arcade Gmail MCP Server](/resources/integrations/productivity/gmail.md) -, which simplifies the process for retrieving email messages even further! The pattern described here is useful if you need to directly get a token to use with Google in other parts of your codebase. - -### How it works - -By using `client.auth.start` and `client.auth.wait_for_completion`, you leverage Arcade to manage the OAuth flow for authorization. - -Arcade handles the authorization challenges and tokens, simplifying the process for you. - -### Next steps - -Integrate this authorization flow into your application, and explore how you can manage different [auth providers](/references/auth-providers.md) and scopes. - -Last updated on January 30, 2026 - -[Handling errors](/en/guides/tool-calling/error-handling.md) -[Overview](/en/guides/tool-calling/custom-apps.md) diff --git a/public/_markdown/en/guides/tool-calling/custom-apps.md b/public/_markdown/en/guides/tool-calling/custom-apps.md deleted file mode 100644 index 48f7dc0c8..000000000 --- a/public/_markdown/en/guides/tool-calling/custom-apps.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: "In Custom Applications" -description: "Learn how to integrate Arcade tools into your custom applications with authentication and tool management" ---- -[Call tools](/en/guides/tool-calling.md) -In custom applicationsOverview - -# In Custom Applications - -Learn how to integrate Arcade tools into your custom applications. Use these guides when building your own \-calling interfaces and need to handle authentication, status checking, and tool definitions programmatically. - -Building custom applications with Arcade tools requires understanding how to manage user authentication, check authorization status, and retrieve properly formatted definitions for your specific use case. - -- [Authorize tool calling](/guides/tool-calling/custom-apps/auth-tool-calling.md) - -- [Check authorization status](/guides/tool-calling/custom-apps/check-auth-status.md) - -- [Get formatted tool definitions](/guides/tool-calling/custom-apps/get-tool-definitions.md) - - -Last updated on January 30, 2026 - -[Call third-party APIs](/en/guides/tool-calling/call-third-party-apis.md) -[Authorize tool calling](/en/guides/tool-calling/custom-apps/auth-tool-calling.md) diff --git a/public/_markdown/en/guides/tool-calling/custom-apps/auth-tool-calling.md b/public/_markdown/en/guides/tool-calling/custom-apps/auth-tool-calling.md deleted file mode 100644 index f79992013..000000000 --- a/public/_markdown/en/guides/tool-calling/custom-apps/auth-tool-calling.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -title: "Authorized Tool Calling" -description: "Guide on calling tools that require authorization" ---- -[Call tools](/en/guides/tool-calling.md) -[In custom applications](/en/guides/tool-calling/custom-apps.md) -Authorize tool calling - -# Authorized Tool Calling - -Arcade provides an authorization system that handles OAuth 2.0, , and user tokens needed by AI to access external services through . This means your AI agents can now act on behalf of securely and privately. - -With Arcade, developers can now create that can as _as the end of their application_ to perform tasks like: - -- Creating a new Zoom meeting -- Sending or reading email -- Answering questions about files in Google Drive. - -Arcade also allows for actions (tools) to be authorized directly. For example, to access a user’s Gmail , you can utilize the following guide. - -### Initialize the client - -Import the in a Python/Javascript script. The client automatically finds set by `arcade login`. - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -``` - -### JavaScript - -```javascript -import Arcade from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable -``` - -### Authorize a tool directly - -Many require authorization. For example, the `Gmail.ListEmails` tool requires authorization with Google. - -This authorization must be approved by the user. By approving, the user allows your or app to access only the data they’ve approved. - -### Python - -```python -# As the developer, you must identify the user you're authorizing -# and pass a unique identifier for them (e.g. an email or user ID) to Arcade: -USER_ID = "{arcade_user_id}" - -# Request access to the user's Gmail account -auth_response = client.tools.authorize( - tool_name="Gmail.ListEmails", - user_id=USER_ID, -) - -if auth_response.status != "completed": - print(f"Click this link to authorize: {auth_response.url}") -``` - -### JavaScript - -```javascript -// As the developer, you must identify the user you're authorizing -// and pass a unique identifier for them (e.g. an email or user ID) to Arcade: -const userId = "{arcade_user_id}"; - -// Request access to the user's Gmail account -const authResponse = await client.tools.authorize({ -tool_name: "Gmail.ListEmails", -user_id: userId, -}); - -if (authResponse.status !== "completed") { -console.log(`Click this link to authorize: ${authResponse.url}`); -} - -``` - -This will print a URL that the must visit to approve the authorization. - -### Check for authorization status - -You can wait for the authorization to complete using the following methods: - -### Python - -```python -client.auth.wait_for_completion(auth_response) -``` - -### JavaScript - -```javascript -await client.auth.waitForCompletion(authResponse); -``` - -### Call the tool with authorization - -Once the user has approved the action, you can run the . You only need to pass the `user_id`: - -### Python - -```python -emails_response = client.tools.execute( - tool_name="Gmail.ListEmails", - user_id=USER_ID, -) -print(emails_response) -``` - -### JavaScript - -```javascript -const emailsResponse = await client.tools.execute({ - tool_name: "Gmail.ListEmails", - user_id: userId, -}); - -console.log(emailsResponse.output.value); -``` - -Arcade remembers the user’s authorization tokens, so you don’t have to! Next time the user runs the same , they won’t have to go through the authorization process again until the auth expires or is revoked. - -### How it works - -When you call a with `client.tools.execute()`, Arcade: - -1. Checks for authorization. -2. Routes the request to the ’s provider. -3. Returns the ’s response. - -With `client.tools.authorize()`, you can also authorize for later use. - -These APIs give you programmatic control over calling. - -### Next steps - -Arcade also allows you to [build your own tools](/guides/create-tools/tool-basics/build-mcp-server.md) to integrate any custom functionality or API to your or AI workflows. - -Your can use the [service providers supported by Arcade](/references/auth-providers.md) or you can integrate with any [OAuth2-compatible service](/references/auth-providers/oauth2.md). - -Last updated on January 30, 2026 - -[Overview](/en/guides/tool-calling/custom-apps.md) -[Check authorization status](/en/guides/tool-calling/custom-apps/check-auth-status.md) diff --git a/public/_markdown/en/guides/tool-calling/custom-apps/check-auth-status.md b/public/_markdown/en/guides/tool-calling/custom-apps/check-auth-status.md deleted file mode 100644 index 71b857b35..000000000 --- a/public/_markdown/en/guides/tool-calling/custom-apps/check-auth-status.md +++ /dev/null @@ -1,212 +0,0 @@ ---- -title: "Checking Tool Auth Status" -description: "Guide on checking the auth status of a tool" ---- -[Call tools](/en/guides/tool-calling.md) -[In custom applications](/en/guides/tool-calling/custom-apps.md) -Check authorization status - -# Checking Tool Authorization Status - -Before executing that require authorization, you can check their authorization status to understand what permissions are needed and whether they’re currently available for a . - -This is useful for: - -- Displaying authorization requirements in your UI -- Pre-checking availability before execution -- Understanding which need approval -- Debugging authorization issues - -### Initialize the client - -Import the in a Python/Javascript script. - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable -``` - -### JavaScript - -```javascript -import Arcade from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable -``` - -### Check authorization status for all tools - -You can get a list of all available and check their authorization status for a specific : - -### Python - -```python -USER_ID = "{arcade_user_id}" - -# Get all tools for the user -tools = client.tools.list(user_id=USER_ID) - -for tool in tools: - print(f"Tool: {tool.name}") - - if tool.requirements: - # Check if all requirements are met - print(f"Requirements met: {tool.requirements.met}") - - # Check authorization status - if tool.requirements.authorization: - print(f"Authorization status: {tool.requirements.authorization.status}") - print(f"Token status: {tool.requirements.authorization.token_status}") - - # Check secret requirements - if tool.requirements.secrets: - for secret in tool.requirements.secrets: - print(f"Secret '{secret.key}' met: {secret.met}") - if not secret.met and secret.status_reason: - print(f"Reason: {secret.status_reason}") - - print("---") -``` - -### JavaScript - -```javascript -const userId = "{arcade_user_id}"; - -// Get all tools for the user -const tools = await client.tools.list({ user_id: userId }); - -tools.items.forEach(tool => { - console.log(`Tool: ${tool.name}`); - - if (tool.requirements) { - // Check if all requirements are met - console.log(`Requirements met: ${tool.requirements.met}`); - - // Check authorization status - if (tool.requirements.authorization) { - console.log(`Authorization status: ${tool.requirements.authorization.status}`); - console.log(`Token status: ${tool.requirements.authorization.token_status}`); - } - - // Check secret requirements - if (tool.requirements.secrets) { - tool.requirements.secrets.forEach(secret => { - console.log(`Secret '${secret.key}' met: ${secret.met}`); - if (!secret.met && secret.status_reason) { - console.log(`Reason: ${secret.status_reason}`); - } - }); - } - } - - console.log("---"); -}); -``` - -If a username is not provided, the Token Status will be excluded and only the requirements for the provider will be shown. - -### Check authorization status for a specific tool - -You can also check the authorization status for a specific by name: - -### Python - -```python -USER_ID = "{arcade_user_id}" -TOOL_NAME = "Gmail.ListEmails" - -# Get specific tool details -tool = client.tools.get(tool_name=TOOL_NAME, user_id=USER_ID) - -print(f"Tool: {tool.name}") -print(f"Description: {tool.description}") - -if tool.requirements: - print(f"All requirements met: {tool.requirements.met}") - - if tool.requirements.authorization: - auth = tool.requirements.authorization - print(f"Authorization required: {auth.provider_type}") - print(f"Authorization status: {auth.status}") - print(f"Token status: {auth.token_status}") - - if auth.status_reason: - print(f"Status reason: {auth.status_reason}") - - if tool.requirements.secrets: - print("Secret requirements:") - for secret in tool.requirements.secrets: - status = "✓" if secret.met else "✗" - print(f" {status} {secret.key}") -``` - -### JavaScript - -```javascript -const userId = "{arcade_user_id}"; -const toolName = "Gmail.ListEmails"; - -// Get specific tool details -const tool = await client.tools.get(toolName, { - user_id: userId -}); - -console.log(`Tool: ${tool.name}`); -console.log(`Description: ${tool.description}`); - -if (tool.requirements) { - console.log(`All requirements met: ${tool.requirements.met}`); - - if (tool.requirements.authorization) { - const auth = tool.requirements.authorization; - console.log(`Authorization required: ${auth.provider_type}`); - console.log(`Authorization status: ${auth.status}`); - console.log(`Token status: ${auth.token_status}`); - - if (auth.status_reason) { - console.log(`Status reason: ${auth.status_reason}`); - } - } - - if (tool.requirements.secrets) { - console.log("Secret requirements:"); - tool.requirements.secrets.forEach(secret => { - const status = secret.met ? "✓" : "✗"; - console.log(` ${status} ${secret.key}`); - }); - } -} -``` - -### Understanding the status values - -#### Authorization Status - -- `active`: If the provider is configured and enabled -- `inactive`: Authorization is not found or is disabled - -#### Token Status - -- `not_started`: Authorization process hasn’t begun -- `pending`: Authorization is in progress ( needs to approve) -- `completed`: Authorization is complete and tokens are available -- `failed`: Authorization process failed - -#### Requirements Met - -- `true`: All requirements for the are satisfied -- `false`: Some requirements are missing (authorization, secrets, etc.) - -#### Secret Met - -- `true`: The secret exists for the -- `false`: The secret does not exist for the - -Last updated on January 30, 2026 - -[Authorize tool calling](/en/guides/tool-calling/custom-apps/auth-tool-calling.md) -[Get formatted tool definitions](/en/guides/tool-calling/custom-apps/get-tool-definitions.md) diff --git a/public/_markdown/en/guides/tool-calling/custom-apps/get-tool-definitions.md b/public/_markdown/en/guides/tool-calling/custom-apps/get-tool-definitions.md deleted file mode 100644 index 1d2026c4e..000000000 --- a/public/_markdown/en/guides/tool-calling/custom-apps/get-tool-definitions.md +++ /dev/null @@ -1,266 +0,0 @@ ---- -title: "Get Formatted Tool Definitions" -description: "Learn how to get formatted tool definitions using Arcade" ---- -[Call tools](/en/guides/tool-calling.md) -[In custom applications](/en/guides/tool-calling/custom-apps.md) -Get formatted tool definitions - -# Get Formatted Tool Definitions - -When calling tools directly, it can be useful to get tool definitions in a specific model provider’s format. The provides methods for getting a ’s definition and also for listing the definitions of multiple tools in a specific model provider’s format. - -## Get a single tool definition formatted for a model - -It can be useful to get a ’s definition in a specific model provider’s format. For example, you may want to get the `Github.SetStarred` tool’s definition in OpenAI’s format. - -To do this, you can use the `client.tools.formatted.get` method and specify the name and format. - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() - -# Get a specific tool formatted for OpenAI -github_star_repo = client.tools.formatted.get(name="Github.SetStarred", format="openai") - -print(github_star_repo) -``` - -### JavaScript - -```javascript -import Arcade from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -// Get a specific tool formatted for OpenAI -const githubStarRepo = await client.tools.formatted.get("Github.SetStarred", { - format: "openai", -}); - -console.log(githubStarRepo); -``` - -## Get all tool definitions in a MCP Server formatted for a model - -It can be useful to list tool definitions for a Server in a specific model provider’s format. For example, you may want to get the definitions of in the `Github` in OpenAI’s format. - -To do this, you can use the `client.tools.formatted.list` method and specify the Server and format. Since this method returns an iterator of pages, you can cast to a list to get all the . - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() - -# Get all tools in the Github MCP Server formatted for OpenAI -github_tools = list(client.tools.formatted.list(format="openai", toolkit="github")) - -# Print the number of tools in the Github MCP Server -print(len(github_tools)) -``` - -### JavaScript - -```javascript -import Arcade from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -// Get all tools in the Github MCP Server formatted for OpenAI -const githubTools = await client.tools.formatted.list({ - format: "openai", - toolkit: "github", -}); - -// Print the number of tools in the Github MCP Server -console.log(githubTools.total_count); -``` - -## Get all tool definitions formatted for a model - -To get all formatted for OpenAI, you can use the `client.tools.formatted.list` method without specifying a Server. - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() - -# Get all tools formatted for OpenAI -all_tools = list(client.tools.formatted.list(format="openai")) - -# Print the number of tools -print(len(all_tools)) -``` - -### JavaScript - -```javascript -import Arcade from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -// Get all tools formatted for OpenAI -const allTools = await client.tools.formatted.list({ format: "openai" }); - -// Print the number of tools -console.log(allTools.total_count); -``` - -## Get Zod Tool Definitions - -[Zod](https://zod.dev)  is a TypeScript-first schema validation library that helps you define and validate data structures. The [Arcade JS](https://github.com/ArcadeAI/arcade-js)  client offers methods to convert Arcade definitions into Zod schemas, providing type safety and validation while enabling seamless integration with AI frameworks like LangChain, Vercel AI SDK, Mastra AI, and TanStack AI. Using Zod with Arcade provides: - -1. **Type Safety**: Runtime validation of inputs and outputs against their defined types -2. **TypeScript Integration**: Provides TypeScript support with automatic type inference -3. **Framework Compatibility**: Direct integration with LangChain, Vercel AI SDK, Mastra AI, and TanStack AI - -### Convert to Zod Format - -Arcade offers three ways to convert your into Zod schemas, each for different use cases: - -#### 1\. Convert to array of Zod tools - -This method returns an array of with Zod validation. - -```typescript -import { toZod } from "@arcadeai/arcadejs/lib/index" - -const googleToolkit = await arcade.tools.list({ - limit: 20, - toolkit: "gmail", -}); - -const tools = toZod({ - tools: googleToolkit.items, - client: arcade, - userId: "", -}) -``` - -#### 2\. Convert to object of Zod tools - -This method returns an object with names as keys, allowing direct access to tools by name: - -```typescript -import { toZodToolSet } from "@arcadeai/arcadejs/lib/index" - -const googleToolkit = await arcade.tools.list({ - limit: 20, - toolkit: "gmail", -}); - -const tools = toZodToolSet({ - tools: googleToolkit.items, - client: arcade, - userId: "", -}) - -const emails = await tools.Gmail_ListEmails.execute({ - limit: 10, -}); -``` - -#### 3\. Convert a single tool - -When you only need to work with a specific , use this method to convert just that tool to a Zod schema: - -```typescript -import { createZodTool } from "@arcadeai/arcadejs/lib/index" - -const listEmails = await arcade.tools.get("Gmail_ListEmails"); - -const listEmailsTool = createZodTool({ - tool: listEmails, - client: arcade, - userId: "", -}); - -const emails = await listEmailsTool.execute({ - limit: 10, -}); -``` - -### Handle Authorization - -When working with that require authorization (like Gmail, GitHub, Slack, etc.), Arcade provides two approaches to handle the authorization flow when using Zod-converted tools: - -#### Option 1: Manual handling - -When you convert Arcade to Zod without adding an `executeFactory`, Arcade will try to run tools directly. For tools that need permission (like Gmail or Slack), you’ll see a `PermissionDeniedError` if the hasn’t given access yet. - -This approach gives you complete control over the authorization flow, making it perfect for custom UI implementations or complex workflows. You’ll have full flexibility to design your own experience, but you’ll need to handle authorization flows and error states manually in your code. - -```typescript -import { PermissionDeniedError } from "@arcadeai/arcadejs" - -const tools = toZodToolSet({ - tools: googleToolkit.items, - client: arcade, - userId: "", -}) - -try { - const result = await tools.Gmail_ListEmails.execute({ - limit: 10, - }); - console.log(result); -} catch (error) { - if (error instanceof PermissionDeniedError) { - // You can use the `arcade.tools.authorize` method to get an authorization URL for the user - const authorizationResponse = await arcade.tools.authorize({ - tool_name: "Gmail.ListEmails", - user_id: "", - }); - console.log(authorizationResponse.url); - } else { - console.error("Error executing tool:", error); - } -} -``` - -#### Option 2: Execute and authorize tool - -Arcade offers a more convenient way to handle execution and initial authorization steps. When converting tools to Zod, you can add the `executeOrAuthorizeZodTool` helper to the `executeFactory`. With this helper, your code no longer needs to catch a `PermissionDeniedError` for tools requiring permissions (as shown in Option 1). Instead, if the hasn’t yet granted access, the `execute` method will return an `ToolAuthorizationResponse` object that contains the authorization URL. - -This approach simplifies your code by: - -1. Attempting to execute the . -2. If permissions are missing, it returns an object containing the authorization URL. This eliminates the need for both a `try...catch` block for `PermissionDeniedError` and a separate call (like `arcade.tools.authorize`) just to retrieve this URL. -3. If the is already authorized, it executes directly. Arcade remembers authorizations, so once a user approves access, subsequent calls using this helper will execute the tool without prompting for authorization again. - -While this helper streamlines obtaining the authorization URL, you are still responsible for presenting this URL to the . It’s particularly useful for straightforward implementations where you want to reduce boilerplate. - -```typescript -import { executeOrAuthorizeZodTool } from "@arcadeai/arcadejs" - -const tools = toZodToolSet({ - tools: googleToolkit.items, - client: arcade, - userId: "", - executeFactory: executeOrAuthorizeZodTool, // Automatically handles tool authorization flows, including generating auth URLs -}); - -const result = await tools.Gmail_ListEmails.execute({ - limit: 10, -}); - -if ("authorization_required" in result && result.authorization_required) { - console.log( - `Please visit ${result.authorization_response.url} to authorize the tool`, - ); -} else { - console.log(result); -} -``` - -Last updated on February 10, 2026 - -[Check authorization status](/en/guides/tool-calling/custom-apps/check-auth-status.md) -[Overview](/en/guides/create-tools/tool-basics.md) diff --git a/public/_markdown/en/guides/tool-calling/error-handling.md b/public/_markdown/en/guides/tool-calling/error-handling.md deleted file mode 100644 index c20a2cc3d..000000000 --- a/public/_markdown/en/guides/tool-calling/error-handling.md +++ /dev/null @@ -1,205 +0,0 @@ ---- -title: "Tool Error Handling" -description: "Learn how to handle errors when using tools with Arcade's Tool Development Kit (TDK)" ---- -[Call tools](/en/guides/tool-calling.md) -Handling errors - -# Tool error handling - -When calling tools from your , smart error handling is crucial for creating robust and reliable applications. This guide covers everything you need to know about handling errors from a ’s perspective. - -## Error handling philosophy - -Arcade’s error handling is designed to provide you with as much information as possible about the error that occurred. When an error occurs, Arcade’s Engine will return a structured error object that you can use to understand the error and take appropriate action. - -## Error hierarchy - -Arcade uses a structured error hierarchy to categorize different types of errors. - -```python - -ToolkitError # (Abstract base class) -├── ToolkitLoadError # Occurs during MCP Server import -└── ToolError # (Abstract) - ├── ToolDefinitionError # Detected when tool is added to catalog - │ ├── ToolInputSchemaError # Invalid input parameter types/annotations - │ └── ToolOutputSchemaError # Invalid return type annotations - └── ToolRuntimeError # Errors during tool execution - ├── ToolSerializationError # (Abstract) - │ ├── ToolInputError # JSON to Python conversion fails - │ └── ToolOutputError # Python to JSON conversion fails - └── ToolExecutionError # Errors during tool execution - ├── RetryableToolError # Tool can be retried with extra context - ├── ContextRequiredToolError # Additional context needed before retry - ├── FatalToolError # Unhandled bugs in the tool implementation - └── UpstreamError # HTTP/API errors from external services - └── UpstreamRateLimitError # Rate limiting errors from service - -``` - -## Client error handling examples - -Here’s how to handle different types of output errors when executing with Arcade’s client libraries: - -### Python - -```python -""" -This example demonstrates how to handle different kinds of output errors when executing a tool. -""" - -from arcadepy import Arcade # pip install arcadepy -from arcadepy.types.execute_tool_response import OutputError - - -# Requires arcadepy >= 1.8.0 -def handle_tool_error(error: OutputError) -> None: - """Example of how to identify different kinds of output errors.""" - error_kind = error.kind - if error_kind == OutputError.Kind.TOOL_RUNTIME_BAD_INPUT_VALUE: - # You provided the executed tool with an invalid input value - print(error.message) - elif error_kind == OutputError.Kind.TOOL_RUNTIME_RETRY: - # The tool returned a retryable error. Provide the additional - # prompt content to the LLM and retry the tool call - instructions_for_llm = error.additional_prompt_content - print(instructions_for_llm) - elif error_kind == OutputError.Kind.TOOL_RUNTIME_CONTEXT_REQUIRED: - # The tool requires extra context from the user or orchestrator. - # Provide the additional prompt content to them and then retry the - # tool call with the new context - request_for_context = error.additional_prompt_content - print(request_for_context) - elif error_kind == OutputError.Kind.TOOL_RUNTIME_FATAL: - # The tool encountered a fatal error during execution - print(error.message) - elif error_kind == OutputError.Kind.UPSTREAM_RUNTIME_RATE_LIMIT: - # The tool encountered a rate limit error from an upstream service - # Wait for the specified amount of time and then retry the tool call - seconds_to_wait = error.retry_after_ms / 1000 - print(f"Wait for {seconds_to_wait} seconds before retrying the tool call") - elif error_kind.startswith("UPSTREAM_"): - # The tool encountered an error from an upstream service - print(error.message) - - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable -user_id = "{arcade_user_id}" -tool_name = "Reddit.GetPostsInSubreddit" -tool_input = {"subreddit": "programming", "limit": 1} - -# Go through the OAuth flow for the tool -auth_response = client.tools.authorize( - tool_name=tool_name, - user_id=user_id, -) -if auth_response.status != "completed": - print(f"Click this link to authorize: {auth_response.url}") - -client.auth.wait_for_completion(auth_response) - -# Execute the tool -response = client.tools.execute( - tool_name=tool_name, - input=tool_input, - user_id=user_id, - include_error_stacktrace=True, -) -if response.output.error: - handle_tool_error(response.output.error) -``` - -### JavaScript - -```javascript -/** - * This example demonstrates how to handle different kinds of output errors when executing a tool. - */ - -import { Arcade } from "@arcadeai/arcadejs"; // npm install @arcadeai/arcadejs - -// Requires @arcadeai/arcadejs >= 1.10.0 -function handleToolError(error) { - const errorKind = error.kind; - if (errorKind === "TOOL_RUNTIME_BAD_INPUT_VALUE") { - // You provided the executed tool with an invalid input value - console.log(error.message); - } else if (errorKind === "TOOL_RUNTIME_RETRY") { - // The tool returned a retryable error. Provide the additional - // prompt content to the LLM and retry the tool call - const instructionsForLLM = error.additional_prompt_content; - console.log(instructionsForLLM); - } else if (errorKind === "TOOL_RUNTIME_CONTEXT_REQUIRED") { - // The tool requires extra context from the user or orchestrator. - // Provide the additional prompt content to them and then retry the - // tool call with the new context - const requestForContext = error.additional_prompt_content; - console.log(requestForContext); - } else if (errorKind === "TOOL_RUNTIME_FATAL") { - // The tool encountered a fatal error during execution - console.log(error.message); - } else if (errorKind === "UPSTREAM_RUNTIME_RATE_LIMIT") { - // The tool encountered a rate limit error from an upstream service - // Wait for the specified amount of time and then retry the tool call - const secondsToWait = error.retry_after_ms / 1000; - console.log(`Wait for ${secondsToWait} seconds before retrying the tool call`); - } else if (errorKind.startsWith("UPSTREAM_")) { - // The tool encountered an error from an upstream service - console.log(error.message); - } -} - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable -const userId = "{arcade_user_id}"; -const toolName = "Reddit.GetPostsInSubreddit"; -const toolInput = { subreddit: "programming", limit: 1 }; - -// Go through the OAuth flow for the tool -const authResponse = await client.tools.authorize({ - tool_name: toolName, - user_id: userId, -}); -if (authResponse.status !== "completed") { - console.log(`Click this link to authorize: ${authResponse.url}`); -} - -await client.auth.waitForCompletion(authResponse); - -// Execute the tool -const response = await client.tools.execute({ - tool_name: toolName, - input: toolInput, - user_id: userId, - include_error_stacktrace: true, -}); -if (response.output.error) { - handleToolError(response.output.error); -} -``` - -## Error types in Arcade client libraries - -To see the full structure of an OutputError, see [arcade-py OutputError](https://github.com/ArcadeAI/arcade-py/blob/942eb2cf41bc14b6c82f0e4acd8b11ef1978cb8d/src/arcadepy/types/execute_tool_response.py#L12)  and [arcade-js OutputError](https://github.com/ArcadeAI/arcade-js/blob/902ef0ce9ff0412ca0d66a862cb4301759d3f87f/src/resources/tools/tools.ts#L166) . - -## Error types in MCP clients - -As of now, Clients do not return structured error information, only an error message. Arcade will attempt to include the type of error in the error message, but it is not guaranteed. - -## Best practices - -1. **Let Arcade handle most errors**: There’s no need to wrap your logic in try/catch blocks unless you need custom error handling. - -2. **Use specific error types**: When you do need to raise errors explicitly, use the most specific error type available. - -3. **Include additional **: For `RetryableToolError` and `ContextRequiredToolError`, use the `additional_prompt_content` parameter to guide the LLM or . - - -## Building tools with error handling - -To learn more about how to build with error handling, see the [Build Tools](/guides/create-tools/error-handling/useful-tool-errors.md) section. - -Last updated on January 30, 2026 - -[Overview](/en/guides/tool-calling.md) -[Call third-party APIs](/en/guides/tool-calling/call-third-party-apis.md) diff --git a/public/_markdown/en/guides/user-facing-agents/secure-auth-production.md b/public/_markdown/en/guides/user-facing-agents/secure-auth-production.md deleted file mode 100644 index e21874bc4..000000000 --- a/public/_markdown/en/guides/user-facing-agents/secure-auth-production.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: "Secure Auth in Production" -description: "How to secure and brand your auth flows in production" ---- -User-facing agentsSecure Auth in Production - -# Secure and Brand the Auth Flow in Production - -To keep your users safe, Arcade.dev performs a user verification check when a is authorized for the first time. This check verifies that the who is authorizing the tool is the same user who started the authorization flow, which helps prevent phishing attacks. - -There are two ways to secure your auth flows with Arcade.dev: - -- Use the **Arcade verifier** for development (enabled by default) -- Implement a **custom verifier** for production - -This setting is configured in the [Auth > Settings section](https://api.arcade.dev/dashboard/auth/settings)  of the Arcade Dashboard. - -## Use the Arcade user verifier - -If you’re building a proof-of-concept app or a solo project, use the Arcade user verifier. This option requires no custom development and is on by default when you create a new Arcade.dev . - -This setting is configured in the [Auth > Settings section](https://api.arcade.dev/dashboard/auth/settings)  of the Arcade Dashboard: - -![An image showing how to pick the Arcade user verifier option in the Arcade Dashboard](/images/docs/auth/dashboard-arcade-verifier.png) - -When you authorize a tool, you’ll be prompted to sign in to your Arcade.dev . If you are already signed in (to the Arcade Dashboard, for example), this verification will succeed silently. - -The Arcade.dev user verifier helps keep your auth flows secure while you are building and testing your or app. When you’re ready to share your work with others, implement a [custom user verifier](#build-a-custom-user-verifier) so your don’t need to sign in to Arcade.dev. - -Arcade’s default OAuth apps can _only_ be used with the Arcade verifier. If you are building a multi-user production app, you must obtain your own OAuth app credentials and add them to Arcade. For example, see our docs on [configuring Google auth in the Arcade Dashboard](/references/auth-providers/google.md#access-the-arcade-dashboard). - -## Build a custom user verifier - -In a production application or , end-users are verified by your code, not Arcade.dev. This allows you to fully control the experience of the auth flow. To enable this, build a custom verifier route and add the URL to the Arcade Dashboard. - -When your users authorize a , Arcade.dev will redirect the ’s browser to your verifier route with some information in the query string. Your custom verifier route must send a response back to Arcade.dev to confirm the user’s ID. - -If you need help, join the [Implementing a custom user verifier](https://github.com/ArcadeAI/arcade-ai/discussions/486)  GitHub discussion and we’ll be happy to assist. - -### Build a new route - -Create a public route in your app or API that can accept a browser redirect (HTTP 303) from Arcade.dev after a user has authorized a . - -The route must gather the following information: - -- The `flow_id` from the current URL’s query string -- The unique ID of the currently signed in, commonly an ID from your application’s database, an email address, or similar. - -How the ’s unique ID is retrieved varies depending on how your app is built, but it is typically retrieved from a session cookie or other secure storage. It **must** match the user ID that your code specified at the start of the authorization flow. - -### Verify the user’s identity - -Use the Arcade SDK (or our REST API) to verify the ’s identity. - -Because this request uses your key, it _must not_ be made from the client side (a browser or desktop app). This code must be run on a server. - -### JavaScript - -```typescript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Looks for process.env.ARCADE_API_KEY by default - -// Within a server GET handler: -// Validate required parameters -if (!flow_id) { - throw new Error("Missing required parameters: flow_id"); -} - -// Confirm the user's identity -try { - const result = await client.auth.confirmUser({ - flow_id: flow_id as string, - user_id: user_id_from_your_app_session, // Replace with the user's ID - }); -} catch (error) { - console.error( - "Error during verification", - "status code:", - error.status, - "data:", - error.data - ); - throw error; -} -``` - -### Python - -```python -from arcadepy import AsyncArcade - -client = AsyncArcade() # Looks for ARCADE_API_KEY environment variable by default - -# Within a server GET handler: -# Validate required parameters -if not flow_id: - raise Exception("Missing required parameters: flow_id") - -# Confirm the user's identity -try: - result = await client.auth.confirm_user( - flow_id=flow_id, - user_id=user_id_from_your_app_session, # Replace with the user's ID - ) -except Exception as error: - print("Error during verification:", error) - raise Exception("Failed to verify the request") -``` - -### REST - -TEXT - -``` -POST https://cloud.arcade.dev/api/v1/oauth/confirm_user -Authorization: Bearer -Content-Type: application/json - -{ - "flow_id": "", - "user_id": "" -} - -``` - -**Valid Response** - -If the ’s ID matches the ID specified at the start of the authorization flow, the response will contain some information about the auth flow. You can either: - -- Redirect the ’s browser to Arcade’s `next_uri` -- Redirect to a different route in your application -- Look up the auth flow’s status in the and render a success page - -### JavaScript - -```typescript -// Wait for the auth flow to be completed by the user: -const authResponse = await client.auth.waitForCompletion(result.auth_id); - -if (authResponse.status === "completed") { - // Either redirect to a URL, or render your own success page: - return new Response(null, { - status: 303, - headers: { - Location: "https://your-app.com/auth/success", - }, - }); -} else { - return "Something went wrong. Please try again."; -} -``` - -### Python - -```python -from starlette.responses import Response - -# Wait for the auth flow to be completed by the user: -auth_response = await client.auth.wait_for_completion(result.auth_id) - -if auth_response.status == "completed": - # Either redirect to a URL, or render your own success page: - return Response( - status_code=303, - headers={"Location": "https://your-app.com/auth/success"} - ) -else: - return "Something went wrong. Please try again." -``` - -### REST - -TEXT - -``` -HTTP 200 OK -Content-Type: application/json - -{ - // Can be used to look up the auth flow's status in the Arcade API - "auth_id": "ac_2zKml...", - - // Optional: URL to redirect the user to after the authorization flow is complete - "next_uri": "https://..." -} - -``` - -**Invalid Response** - -If the ’s ID does not match the ID specified at the start of the authorization flow, the response will contain an error. - -### JavaScript - -```typescript -console.error( - "Error during verification", - "status code:", - error.status, - "data:", - error.data -); -throw error; -``` - -### Python - -```python -print("Error during verification:", error) -raise Exception("Failed to verify the request") -``` - -### REST - -TEXT - -``` -HTTP 400 Bad Request -Content-Type: application/json - -{ - "code": 400, - "msg": "An error occurred during verification" -} -``` - -### Add your custom verifier route to Arcade - -In the [Auth > Settings section](https://api.arcade.dev/dashboard/auth/settings)  of the Arcade Dashboard, pick the **Custom verifier** option and add the URL of your verifier route. - -![An image showing how to pick the custom verifier option in the Arcade Dashboard](/images/docs/auth/dashboard-custom-verifier.png) - -Arcade’s default OAuth apps _only_ support the Arcade verifier. Authorization flows using Arcade’s default OAuth apps will use the Arcade user verifier even if you have a custom verifier route set up. - -Last updated on January 30, 2026 - -[Migrate from toolkits to MCP servers](/en/guides/create-tools/migrate-toolkits.md) -[Overview](/en/guides/deployment-hosting.md) diff --git a/public/_markdown/en/home.md b/public/_markdown/en/home.md deleted file mode 100644 index 763c9f586..000000000 --- a/public/_markdown/en/home.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: "Arcade Docs" -description: "Guides, resources, sample agents, and references for Arcade auth providers and MCP servers for agents and developers." ---- -# MCP Runtime for AI agents that get things done. - -Arcade handles OAuth, manages user tokens, and gives you 100+pre-built integrations so your agents can take real action in production. - -[Get Started](/get-started/quickstarts/call-tool-agent.md) -[Explore Tools](/resources/integrations.md) - -## Get Tools - -[Pre-built Integrations Browse 100+ ready-to-use integrations for Gmail, Slack, GitHub, and more. ![Gmail](/images/icons/gmail.png)![Slack](/images/icons/slack.png)![GitHub](/images/icons/github.png)![Google Sheets](/images/icons/google_sheets.svg)![Jira](/images/icons/jira.svg)![Notion](/images/icons/notion.png)![Linear](/images/icons/linear.svg)![HubSpot](/images/icons/hubspot.png)![Stripe](/images/icons/stripe.svg)![Google Drive](/images/icons/google_drive.png)![Dropbox](/images/icons/dropbox.png)![Confluence](/images/icons/confluence.svg)![Reddit](/images/icons/reddit.png) Learn more](/resources/integrations.md) - -[Build Custom Tools Create your own MCP servers and custom tools with our SDK. Learn more](/guides/create-tools/tool-basics/build-mcp-server.md) - -## Use Arcade - -[Connect to Your IDE Add tools to Cursor, VS Code, Claude Desktop, or any MCP client. ![Cursor](/images/icons/cursor.png)![VS Code](/images/icons/vscode.svg)![Claude Desktop](/images/icons/claude.png) Learn more](/get-started/mcp-clients.md) - -[Power Your Agent Integrate with LangChain, OpenAI Agents, CrewAI, Vercel AI, and more. ![LangChain](/images/icons/langchain.svg)![OpenAI](/images/icons/openai.png)![CrewAI](https://avatars.githubusercontent.com/u/170677839?s=200&v=4)![Vercel AI](/images/icons/vercel.svg)![Google ADK](/images/icons/google.png)![Mastra](/images/icons/mastra.svg) Learn more](/get-started/agent-frameworks.md) - -## Popular Integrations - -Pre-built MCP servers ready to use with your agents. - -[See all 100+](/resources/integrations.md) - -[![Google Sheets](/images/icons/google_sheets.svg) Google Sheets](/resources/integrations/productivity/google-sheets.md) -[![Jira](/images/icons/jira.svg) Jira](/resources/integrations/productivity/jira.md) -[![Gmail](/_next/image?url=%2Fimages%2Ficons%2Fgmail.png&w=64&q=75) Gmail](/resources/integrations/productivity/gmail.md) -[![Confluence](/images/icons/confluence.svg) Confluence](/resources/integrations/productivity/confluence.md) -[![Slack](/_next/image?url=%2Fimages%2Ficons%2Fslack.png&w=64&q=75) Slack](/resources/integrations/social-communication/slack.md) -[![Google Docs](/_next/image?url=%2Fimages%2Ficons%2Fgoogle_docs.png&w=64&q=75) Google Docs](/resources/integrations/productivity/google-docs.md) -[![Google Slides](/_next/image?url=%2Fimages%2Ficons%2Fgoogle_slides.png&w=64&q=75) Google Slides](/resources/integrations/productivity/google-slides.md) -[![HubSpot](/_next/image?url=%2Fimages%2Ficons%2Fhubspot.png&w=64&q=75) HubSpot](/resources/integrations/sales/hubspot.md) -[![Linear](/images/icons/linear.svg) Linear](/resources/integrations/productivity/linear.md) -[![Google Drive](/_next/image?url=%2Fimages%2Ficons%2Fgoogle_drive.png&w=64&q=75) Google Drive](/resources/integrations/productivity/google-drive.md) - -[![GitHub](/_next/image?url=%2Fimages%2Ficons%2Fgithub.png&w=64&q=75) GitHub](/resources/integrations/development/github.md) -[![X](/_next/image?url=%2Fimages%2Ficons%2Ftwitter.png&w=64&q=75) X](/resources/integrations/social-communication/x.md) -[![MS Teams](/_next/image?url=%2Fimages%2Ficons%2Fms_teams.png&w=64&q=75) MS Teams](/resources/integrations/social-communication/microsoft-teams.md) -[![Outlook](/_next/image?url=%2Fimages%2Ficons%2Foutlook_mail.png&w=64&q=75) Outlook](/resources/integrations/productivity/outlook-mail.md) -[![Stripe](/images/icons/stripe.svg) Stripe](/resources/integrations/payments/stripe.md) -[![Notion](/_next/image?url=%2Fimages%2Ficons%2Fnotion.png&w=64&q=75) Notion](/resources/integrations/productivity/notion.md) -[![Asana](/images/icons/asana.svg) Asana](/resources/integrations/productivity/asana.md) -[![Reddit](/_next/image?url=%2Fimages%2Ficons%2Freddit.png&w=64&q=75) Reddit](/resources/integrations/social-communication/reddit.md) -[![YouTube](/_next/image?url=%2Fimages%2Ficons%2Fyoutube.png&w=64&q=75) YouTube](/resources/integrations/search/youtube.md) -[![Dropbox](/_next/image?url=%2Fimages%2Ficons%2Fdropbox.png&w=64&q=75) Dropbox](/resources/integrations/productivity/dropbox.md) - -## Works With Your Stack - -Arcade integrates with popular agent frameworks and LLM providers. - -[![LangChain](/images/icons/langchain.svg) LangChain](/guides/agent-frameworks/langchain/use-arcade-tools.md) -[![OpenAI Agents](/images/icons/openai.png) OpenAI Agents](/get-started/agent-frameworks/openai-agents/overview.md) -[![CrewAI](https://avatars.githubusercontent.com/u/170677839?s=200&v=4) CrewAI](/guides/agent-frameworks/crewai/use-arcade-tools.md) -[![Vercel AI](/images/icons/vercel.svg) Vercel AI](/guides/agent-frameworks/vercelai.md) -[![Google ADK](/images/icons/google.png) Google ADK](/guides/agent-frameworks/google-adk/use-arcade-tools.md) -[![Mastra](/images/icons/mastra.svg) Mastra](/guides/agent-frameworks/mastra/use-arcade-tools.md) - -## How Arcade Works - -Three core components that power your AI agents. - -[Runtime Your MCP server and agentic tool provider. Manages authentication, tool registration, and execution. Learn more](/get-started/about-arcade.md) - -[Tool Catalog Catalog of pre-built tools and integrations. Browse 100+ ready-to-use MCP servers. Learn more](/resources/integrations.md) - -[Agent Authorization Let agents act on behalf of users. Handle OAuth, API keys, and tokens for tools like Gmail and Google Drive. Learn more](/guides/tool-calling/custom-apps/auth-tool-calling.md) - -## Sample Applications - -See Arcade in action with these example applications. - -[See all examples](/resources/examples.md) - -[### Arcade Chat A chatbot that can help you with your daily tasks.](https://chat.arcade.dev/) - -[### Archer A bot for Slack that can act on your behalf.](https://github.com/ArcadeAI/ArcadeSlackAgent) - -[### YouTube Podcast Summarizer A Slack bot that extracts and summarizes YouTube transcripts.](https://github.com/dforwardfeed/slack-AIpodcast-summaries) - -[### Connect Your IDE with Arcade's LLMs.txt Give Cursor, Claude Code, and other AI IDEs access to Arcade's documentation so they can write integration code for you.](/get-started/setup/connect-arcade-docs.md) - -## Quick Links - -[API Reference](/references/api.md) -[CLI Cheat Sheet](/references/cli-cheat-sheet.md) -[FAQ](/resources/faq.md) -[Changelog](/references/changelog.md) - -Last updated on January 30, 2026 - -[About Arcade](/en/get-started/about-arcade.md) diff --git a/public/_markdown/en/learn/server-level-vs-tool-level-auth.md b/public/_markdown/en/learn/server-level-vs-tool-level-auth.md deleted file mode 100644 index 5fe427a89..000000000 --- a/public/_markdown/en/learn/server-level-vs-tool-level-auth.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -title: "Server-Level vs Tool-Level Authorization" -description: "Understanding the difference between Resource Server auth and tool-level authorization in Arcade MCP servers" ---- -Server-Level vs Tool-Level Authorization - -# Server-Level vs Tool-Level Authorization - -Arcade servers support two distinct layers of authorization that work together to provide comprehensive security. Understanding the difference is crucial for building secure, production-ready . - -**Using `arcade deploy`?** If you deploy your server with [`arcade deploy`](/guides/deployment-hosting/arcade-deploy.md), Arcade handles server-level security for you automatically. - -## Quick Comparison - -Aspect - -[Resource Server Auth (Front-Door)](/guides/security/secure-your-mcp-server.md) - -[Tool-Level Authorization](/guides/create-tools/tool-basics/create-tool-auth.md) - -**What it secures** - -Access to your MCP server - -Access to third-party APIs - -**Who authenticates** - -The user calling your server - -The user’s access to external services - -**When it happens** - -Every HTTP request to your server - -When a tool calls an external API - -**Token source** - -Authorization Server (e.g., WorkOS, Auth0) - -Arcade authorization platform - -**Required for** - -HTTP servers in production - -Tools that access user data from APIs - -**Configuration** - -`MCPApp(auth=resource_server)` - -`@app.tool(requires_auth=GitHub(...))` - -## Resource Server Auth (Server-Level) - -Resource Server auth (also called “front-door auth”) validates Bearer tokens on **every HTTP request** to your server. The end user only needs to go through the OAuth flow once. Afterwards, the MCP client will send the token in the Authorization header for every request to your . Your MCP server acts as an OAuth 2.1 Protected Resource. - -Resource Server auth ensures every request identifies the caller. It blocks unauthenticated requests at the door, so only users with valid tokens can access your server. This security lets you run that require authorization or secrets over HTTP. - -### When You Need It - -✅ **You need Resource Server auth if:** - -- You’ve determined that [arcade deploy](/guides/deployment-hosting/arcade-deploy.md) - is not a good fit for your use case -- You’re running an HTTP server in production -- Your server has that require authorization or secrets -- You need to identify which is calling your server -- You want to control who can access your server - -❌ **You don’t need it if:** - -- You’re using [arcade deploy](/guides/deployment-hosting/arcade-deploy.md) - to secure your server -- You’re using stdio transport -- Your server only has public (no auth/secrets required) -- You’re doing local development only - -### Example - -```python -# server.py -from arcade_mcp_server import MCPApp -from arcade_mcp_server.resource_server import ResourceServerAuth, AuthorizationServerEntry - -# Configure who can access your MCP server -resource_server_auth = ResourceServerAuth( - canonical_url="http://127.0.0.1:8000/mcp", - authorization_servers=[ - AuthorizationServerEntry( - authorization_server_url="https://auth.example.com", - issuer="https://auth.example.com", - jwks_uri="https://auth.example.com/jwks", - ) - ], -) - -app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth) -``` - -**Result**: Only with valid tokens from `https://auth.example.com` can call ANY on your server. - -## Tool-Level Authorization - -\-level authorization enables individual tools to access third-party APIs on behalf of the authenticated . Arcade manages the OAuth flow and token storage. - -Tool-level authorization lets your tools authenticate to external APIs using OAuth tokens for services like Gmail or GitHub. Each tool acts on behalf of the user by using their connected , and requests only the scopes it needs. Arcade manages the entire OAuth flow, including token refresh and secure storage, so you don’t have to handle these details yourself. - -### When You Need It - -✅ **You need \-level auth if:** - -- Your calls external APIs (Gmail, GitHub, Slack, etc.) that require \-specific OAuth tokens -- You want to access data from third-party services -- The needs to act on behalf of the - -❌ **You don’t need it if:** - -- Your doesn’t call external APIs -- The API uses instead of OAuth -- The accesses public data only - -### Example - -```json -# server.py -from typing import Annotated - -from arcade_mcp_server import Context, MCPApp -from arcade_mcp_server.auth import GitHub -import httpx - -app = MCPApp(name="my_server", version="1.0.0") - -# This tool requires GitHub auth -@app.tool(requires_auth=GitHub(scopes=["repo", "read:user"])) -async def create_github_issue( - context: Context, - repo: Annotated[str, "The repository to create the issue in"], - title: Annotated[str, "The title of the issue"], - body: Annotated[str, "The body of the issue"], -) -> Annotated[dict, "The created issue"]: - """Create a GitHub issue""" - # Arcade provides the OAuth token for this user in the context - token = context.get_auth_token_or_empty() - - headers = {"Authorization": f"Bearer {token}"} - url = f"https://api.github.com/repos/{repo}/issues" - - async with httpx.AsyncClient() as client: - response = await client.post( - url, - headers=headers, - json={"title": title, "body": body} - ) - return response.json() - -if __name__ == "__main__": - app.run(transport="stdio") -``` - -**stdio transport doesn’t need Resource Server auth** because the connection is local and doesn’t go over the network. - -## How They Work Together - -The two authorization layers complement each other. Below is an example of a protected HTTP server with both server-level and \-level authorization. - -```json -# server.py -from typing import Annotated - -import httpx -from arcade_mcp_server import Context, MCPApp -from arcade_mcp_server.auth import GitHub -from arcade_mcp_server.resource_server import AuthorizationServerEntry, ResourceServerAuth - -# Configure who can access your MCP server -resource_server_auth = ResourceServerAuth( - canonical_url="http://127.0.0.1:8000/mcp", - authorization_servers=[ - AuthorizationServerEntry( - authorization_server_url="https://auth.example.com", - issuer="https://auth.example.com", - jwks_uri="https://auth.example.com/jwks", - ) - ], -) - -app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth) - - -# This tool requires GitHub auth -@app.tool(requires_auth=GitHub(scopes=["repo", "read:user"])) -async def create_github_issue( - context: Context, - repo: Annotated[str, "The repository to create the issue in"], - title: Annotated[str, "The title of the issue"], - body: Annotated[str, "The body of the issue"], -) -> Annotated[dict, "The created issue"]: - """Create a GitHub issue""" - # Arcade provides the OAuth token for this user in the context - token = context.get_auth_token_or_empty() - - headers = {"Authorization": f"Bearer {token}"} - url = f"https://api.github.com/repos/{repo}/issues" - - async with httpx.AsyncClient() as client: - response = await client.post( - url, - headers=headers, - json={"title": title, "body": body}, - ) - return response.json() - - -if __name__ == "__main__": - app.run(transport="http") - -``` - -**Flow:** - -1. client sends request with Bearer token to `http://127.0.0.1:8000/mcp` -2. Resource Server middleware validates token → extracts `user_id` from `sub` claim -3. processes tool call with authenticated user -4. requests GitHub token from Arcade for this `user_id` -5. uses GitHub token to call GitHub API -6. Response returns to client - -## Common Questions - -### Q: Can I use tool-level auth without Resource Server auth? - -**A:** Yes, but only for stdio transport or when using [arcade deploy](/guides/deployment-hosting/arcade-deploy.md) (Arcade will protect your server for you). - -### Q: Do I need Resource Server auth for local development? - -**A:** No, you can use stdio transport for local development. Resource Server auth is primarily for production HTTP servers. - -### Q: Does Resource Server auth replace tool-level auth? - -**A:** No, they serve different purposes. Resource Server auth secures _your server_, \-level auth secures _external APIs_. - -### Q: Can I have different authorization servers for different tools in the same server? - -**A:** No. Resource Server auth applies to the entire server. However, you can accept tokens from multiple authorization servers (multi-IdP). - -## Key Takeaways - -- **Resource Server auth secures your server** - Controls who can call your -- **\-level auth secures external APIs** - Controls what your tools can access -- **They work together** - Resource Server provides user identity, \-level provides API access -- **HTTP requires Resource Server auth** - For with auth/secrets in production -- **stdio doesn’t need Resource Server auth** - Local connections are already secure -- **Choose based on transport and requirements** - Different scenarios need different combinations - -Last updated on January 30, 2026 - -[Build Your Own](/en/guides/logic-extensions/build-your-own.md) -[Overview](/en/references.md) diff --git a/public/_markdown/en/references.md b/public/_markdown/en/references.md deleted file mode 100644 index 42c1a5493..000000000 --- a/public/_markdown/en/references.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "References" -description: "Complete reference documentation for Arcade's APIs, MCP servers, and available auth providers." ---- -Overview - -# References - -Complete reference documentation for Arcade’s APIs, servers, and available . - -### - -API - -Arcade’s REST API is for orchestrating tools, managing authentication, and controlling agent workflows at runtime. Use this API to integrate Arcade’s execution and permission management into your applications. - -[View API Reference](/references/api.md) - -### - -Arcade ( SDK) - -Arcade , the secure framework for building , provides a FastAPI-like interface for creating custom and exposing them through the standardized MCP protocol. - -[View SDK Reference](/references/mcp/python.md) - -### - -Arcade provides clients for several languages. These clients make it easy to use Arcade’s tools within your and applications. - -#### - -Python Client - -Install with: - -` pip install arcadepy `[Learn more about the Python Client](https://github.com/ArcadeAI/arcade-py) - -#### - -JavaScript / TypeScript Client - -Install with: - -` npm install @arcadeai/arcadejs `[Learn more about the JavaScript / TypeScript Client](https://github.com/ArcadeAI/arcade-js) - -#### - -Go Client - -Install with: - -` go get -u ‘github.com/ArcadeAI/arcade-go’ `[Learn more about the Go Client](https://github.com/ArcadeAI/arcade-go) - -Note: \-compatible versions of these clients are in development and will be documented soon. - -Last updated on January 30, 2026 - -[Server-Level vs Tool-Level Authorization](/en/learn/server-level-vs-tool-level-auth.md) -[Changelog](/en/references/changelog.md) diff --git a/public/_markdown/en/references/api.md b/public/_markdown/en/references/api.md deleted file mode 100644 index 61efae523..000000000 --- a/public/_markdown/en/references/api.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: "Arcade API Reference" -description: "Learn about the Arcade API. It's swagger time!" ---- -API - -# Arcade API Reference - -The base URL for all API requests is `https://api.arcade.dev`. - -The use of this API is subject to our [Terms of Service](https://arcade.dev/terms-of-service) , and you are required to have an in good standing. - -Our OpenAPI 3.0 specification is [available here](https://api.arcade.dev/v1/swagger) . - -Last updated on January 30, 2026 - -[Changelog](/en/references/changelog.md) -[Overview](/en/references/mcp/python.md) diff --git a/public/_markdown/en/references/arcade-cli.md b/public/_markdown/en/references/arcade-cli.md deleted file mode 100644 index 68b7b5b00..000000000 --- a/public/_markdown/en/references/arcade-cli.md +++ /dev/null @@ -1,496 +0,0 @@ ---- -title: "Arcade CLI" -description: "Learn how to install and use the Arcade CLI" ---- -Arcade CLI - -# The Arcade CLI - -The Arcade CLI is a command-line tool that allows you to manage your Arcade deployments, generate, test, and manage your servers, and more. - -## Install the Arcade CLI - -In your terminal, run the following command to install the published `arcade-mcp` package from PyPI: - -### uv - -```bash -uv tool install arcade-mcp -``` - -This will install the Arcade CLI as a [uv tool](https://docs.astral.sh/uv/guides/tools/#installing-tools) , making it available system wide. - -### pip - -```bash -pip install arcade-mcp -``` - -Using Windows and PowerShell? Follow the [Windows environment setup](/get-started/setup/windows-environment.md) guide for install options with and without `uv`. - -## Upgrade the Arcade CLI - -To upgrade to the latest version of the Arcade CLI, run the appropriate command for your package manager: - -### uv - -```bash -uv tool upgrade arcade-mcp -``` - -### pip - -```bash -pip install --upgrade arcade-mcp -``` - -After upgrading, verify the installation: - -```bash -arcade --version -``` - -If you previously had the `arcade-ai` package installed, you should uninstall it first. The old `arcade-ai` CLI has been replaced by `arcade-mcp`. See the [migration guide](/guides/create-tools/migrate-toolkits.md) for details on migrating from legacy toolkits. - -### Re-authenticate after upgrading - -After upgrading, you may need to refresh your credentials: - -```bash -arcade logout -arcade login -``` - -## Usage - -```bash - Usage: arcade [OPTIONS] COMMAND [ARGS]... - - Arcade CLI - Build, deploy, and manage MCP servers and AI tools. Create - new projects, run servers with multiple transports, configure clients, - and deploy to Arcade Cloud. - -╭─ Options ──────────────────────────────────────────────────────────────╮ -│ --version -v Print version and exit. │ -│ --help -h Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────╯ -╭─ User ─────────────────────────────────────────────────────────────────╮ -│ login Log in to Arcade │ -│ logout Log out of Arcade │ -│ whoami Show current login status and active context │ -│ dashboard Open the Arcade Dashboard in a web browser │ -│ org Manage organizations (list, set active) │ -│ project Manage projects (list, set active) │ -╰────────────────────────────────────────────────────────────────────────╯ -╭─ Build ────────────────────────────────────────────────────────────────╮ -│ new Create a new server package directory. Example usage: arcade │ -│ new my_mcp_server │ -│ show Show the installed tools or details of a specific tool │ -│ evals Run tool calling evaluations │ -╰────────────────────────────────────────────────────────────────────────╯ -╭─ Run ──────────────────────────────────────────────────────────────────╮ -│ mcp Run MCP servers with different transports │ -│ deploy Deploy MCP servers to Arcade │ -╰────────────────────────────────────────────────────────────────────────╯ -╭─ Manage ───────────────────────────────────────────────────────────────╮ -│ configure Configure MCP clients to connect to your server │ -│ server Manage deployments of tool servers (logs, list, etc) │ -│ secret Manage tool secrets in the cloud (set, unset, list) │ -╰────────────────────────────────────────────────────────────────────────╯ - - Pro tip: use --help after any command to see command-specific options. -``` - -You can learn more about any of the commands by running `arcade --help`, for example, `arcade new --help`. - -## `arcade login` - -```bash - Usage: arcade login [OPTIONS] - - Log in to Arcade - -╭─ Options ──────────────────────────────────────────────────────────────╮ -│ --host -h TEXT The Arcade Coordinator host to log in to. │ -│ [default: cloud.arcade.dev] │ -│ --port -p INTEGER The port of the Arcade Coordinator host (if │ -│ running locally). │ -│ [default: None] │ -│ --debug -d Show debug information │ -│ --help Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade logout` - -```bash - Usage: arcade logout [OPTIONS] - - Log out of Arcade - -╭─ Options ──────────────────────────────────────────────────────────────╮ -│ --debug -d Show debug information │ -│ --help -h Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade whoami` - -```bash - Usage: arcade whoami [OPTIONS] - - Show current login status and active context - -╭─ Options ──────────────────────────────────────────────────────────────╮ -│ --debug -d Show debug information │ -│ --help -h Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade dashboard` - -```bash - Usage: arcade dashboard [OPTIONS] - - Open the Arcade Dashboard in a web browser - -╭─ Options ──────────────────────────────────────────────────────────────╮ -│ --host -h TEXT The Arcade Engine host that serves the │ -│ dashboard. │ -│ [default: api.arcade.dev] │ -│ --port -p INTEGER The port of the Arcade Engine. │ -│ [default: None] │ -│ --local -l Open the local dashboard instead of the │ -│ default remote dashboard. │ -│ --tls Whether to force TLS for the connection to │ -│ the Arcade Engine. │ -│ --no-tls Whether to disable TLS for the connection │ -│ to the Arcade Engine. │ -│ --debug -d Show debug information │ -│ --help Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade org` - -```bash - Usage: arcade org [OPTIONS] COMMAND [ARGS]... - - Manage organizations (list, set active) - -╭─ Options ──────────────────────────────────────────────────────────────╮ -│ --host -h TEXT The Arcade Coordinator host. │ -│ [default: cloud.arcade.dev] │ -│ --port -p INTEGER The port of the Arcade Coordinator host. │ -│ [default: None] │ -│ --tls Whether to force TLS for the connection to │ -│ Arcade Coordinator. │ -│ --no-tls Whether to disable TLS for the connection │ -│ to Arcade Coordinator. │ -│ --help Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────╯ -╭─ Commands ─────────────────────────────────────────────────────────────╮ -│ list List organizations you belong to │ -│ set Set the active organization │ -╰────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade project` - -```bash - Usage: arcade project [OPTIONS] COMMAND [ARGS]... - - Manage projects (list, set active) - -╭─ Options ──────────────────────────────────────────────────────────────╮ -│ --host -h TEXT The Arcade Coordinator host. │ -│ [default: cloud.arcade.dev] │ -│ --port -p INTEGER The port of the Arcade Coordinator host. │ -│ [default: None] │ -│ --tls Whether to force TLS for the connection to │ -│ Arcade Coordinator. │ -│ --no-tls Whether to disable TLS for the connection │ -│ to Arcade Coordinator. │ -│ --help Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────╯ -╭─ Commands ─────────────────────────────────────────────────────────────╮ -│ list List projects in the active organization │ -│ set Set the active project │ -╰────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade new` - -```bash - Usage: arcade new [OPTIONS] SERVER_NAME - - Create a new server package directory. Example usage: arcade new - my_mcp_server - -╭─ Arguments ──────────────────────────────────────────────────────────────╮ -│ * server_name TEXT The name of the server to create │ -│ [default: None] │ -│ [required] │ -╰──────────────────────────────────────────────────────────────────────────╯ -╭─ Options ────────────────────────────────────────────────────────────────╮ -│ --dir TEXT tools directory path │ -│ [default: current directory] │ -│ --debug -d Show debug information │ -│ --full -f Create a starter MCP server (pyproject.toml, │ -│ server.py, .env.example) │ -│ --help -h Show this message and exit. │ -╰──────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade show` - -```bash - Usage: arcade show [OPTIONS] - - Show the installed tools or details of a specific tool - -╭─ Options ────────────────────────────────────────────────────────────────╮ -│ --server -T TEXT The server to show the tools of │ -│ [default: None] │ -│ --tool -t TEXT The specific tool to show details for │ -│ [default: None] │ -│ --host -h TEXT The Arcade Engine address to show the │ -│ tools/servers of. │ -│ [default: api.arcade.dev] │ -│ --local -l Show the local environment's catalog instead │ -│ of an Arcade Engine's catalog. │ -│ --port -p INTEGER The port of the Arcade Engine. │ -│ [default: None] │ -│ --tls Whether to force TLS for the connection to │ -│ the Arcade Engine. If not specified, the │ -│ connection will use TLS if the engine URL │ -│ uses a 'https' scheme. │ -│ --no-tls Whether to disable TLS for the connection to │ -│ the Arcade Engine. │ -│ --full -f Show full server response structure including │ -│ error, logs, and authorization fields (only │ -│ applies when used with -t/--tool). │ -│ --debug -d Show debug information │ -│ --help Show this message and exit. │ -╰──────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade evals` - -```bash - Usage: arcade evals [OPTIONS] [DIRECTORY] - - Run tool calling evaluations - -╭─ Arguments ──────────────────────────────────────────────────────────────╮ -│ directory [DIRECTORY] Directory containing evaluation files │ -│ [default: .] │ -╰──────────────────────────────────────────────────────────────────────────╯ -╭─ Options ────────────────────────────────────────────────────────────────╮ -│ --details -d Show detailed results │ -│ --only-failed -f Show only failed cases │ -│ --capture Record tool calls without scoring│ -│ --include-context Include conversation context │ -│ --output -o TEXT Output file(s) (repeatable) │ -│ --use-provider -p TEXT Provider and models (repeatable)│ -│ --api-key -k TEXT Provider API key (repeatable) │ -│ --max-concurrent -c INTEGER Maximum number of concurrent │ -│ evaluations (default: 1) │ -│ [default: 1] │ -│ --num-runs -n INTEGER Number of runs per case │ -│ [default: 1] │ -│ --seed TEXT Seed policy for OpenAI runs │ -│ [default: constant] │ -│ --multi-run-pass-rule TEXT Pass/warn aggregation rule │ -│ [default: last] │ -│ --debug Show debug information │ -│ --help -h Show this message and exit. │ -╰──────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade mcp` - -```bash -Usage: arcade mcp [OPTIONS] [TRANSPORT] - - Run MCP servers with different transports - -╭─ Arguments ────────────────────────────────────────────────────────────────────────╮ -│ transport [TRANSPORT] Transport type: stdio, http │ -│ [default: http] │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Options ──────────────────────────────────────────────────────────────────────────╮ -│ --host TEXT Host to bind to (HTTP mode only) │ -│ [default: 127.0.0.1] │ -│ --port INTEGER Port to bind to (HTTP mode only) │ -│ [default: 8000] │ -│ --tool-package,--package -p TEXT Specific tool package to load (e.g., │ -│ 'github' for arcade-github) │ -│ [default: None] │ -│ --discover-installed,--all Discover all installed arcade tool │ -│ packages │ -│ --show-packages Show loaded packages during discovery │ -│ --reload Enable auto-reload on code changes │ -│ (HTTP mode only) │ -│ --debug Enable debug mode with verbose │ -│ logging │ -│ --otel-enable Send logs to OpenTelemetry │ -│ --env-file TEXT Path to environment file │ -│ [default: None] │ -│ --name TEXT Server name │ -│ [default: None] │ -│ --version TEXT Server version │ -│ [default: None] │ -│ --cwd TEXT Working directory to run from │ -│ [default: None] │ -│ --help -h Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade deploy` - -```python - Usage: arcade deploy [OPTIONS] - - Deploy MCP servers to Arcade - -╭─ Options ──────────────────────────────────────────────────────────────────────────╮ -│ --entrypoint -e TEXT Relative path to the Python file that runs the MCPApp │ -│ instance (relative to project root). This file must │ -│ execute the run() method on your MCPApp instance when │ -│ invoked directly. │ -│ [default: server.py] │ -│ --debug -d Show debug information │ -│ --help Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Advanced ─────────────────────────────────────────────────────────────────────────╮ -│ --skip-validate,--yolo Skip running the server locally │ -│ for health/metadata checks. When │ -│ set, you must provide │ -│ --server-name and │ -│ --server-version. Secret handling │ -│ is controlled by --secrets. │ -│ --server-name -n TEXT Explicit server name to use when │ -│ --skip-validate is set. Only used │ -│ when --skip-validate is set. │ -│ [default: None] │ -│ --server-version -v TEXT Explicit server version to use │ -│ when --skip-validate is set. Only │ -│ used when --skip-validate is set. │ -│ [default: None] │ -│ --secrets -s [auto|all|skip] How to upsert secrets before │ -│ deploy: auto (default): During │ -│ validation, discover required │ -│ secret KEYS and upsert only │ -│ those. If --skip-validate is set, │ -│ auto becomes skip. all: Upsert │ -│ every key/value pair from your │ -│ server's .env file regardless of │ -│ what the server needs. skip: Do │ -│ not upsert any secrets (assumes │ -│ they are already present in │ -│ Arcade). │ -│ [default: auto] │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade configure` - -```python - Usage: arcade configure [OPTIONS] CLIENT:{claude|cursor|vscode} - - Configure MCP clients to connect to your server - -╭─ Arguments ────────────────────────────────────────────────────────────────────────╮ -│ * client CLIENT:{claude|cursor|vscode} The MCP client to configure │ -│ (claude, cursor, vscode) │ -│ [default: None] │ -│ [required] │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Options ──────────────────────────────────────────────────────────────────────────╮ -│ --transport -t [stdio|http] The transport to use for the MCP server │ -│ configuration │ -│ [default: stdio] │ -│ --debug -d Show debug information │ -│ --help Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Stdio Options ────────────────────────────────────────────────────────────────────╮ -│ --entrypoint -e TEXT The name of the Python file in the current directory │ -│ that runs the server. This file must run the server │ -│ when invoked directly. Only used for stdio servers. │ -│ [default: server.py] │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Configuration File Options ───────────────────────────────────────────────────────╮ -│ --name -n TEXT Optional name of the server to set in the configuration │ -│ file (defaults to the name of the current directory) │ -│ [default: None] │ -│ --config -c PATH Optional path to a specific MCP client config file │ -│ (overrides default path) │ -│ [default: None] │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -╭─ HTTP Options ─────────────────────────────────────────────────────────────────────╮ -│ --host -h [local|arcade] The host of the HTTP server to configure. Use │ -│ 'local' to connect to a local MCP server or │ -│ 'arcade' to connect to an Arcade Cloud MCP server. │ -│ [default: local] │ -│ --port -p INTEGER Port for local HTTP servers │ -│ [default: 8000] │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade server` - -```bash -Usage: arcade server [OPTIONS] COMMAND [ARGS]... - - Manage deployments of tool servers (logs, list, etc) - -╭─ Options ──────────────────────────────────────────────────────────────────────────╮ -│ --host -h TEXT The Arcade Engine host. │ -│ [default: api.arcade.dev] │ -│ --port -p INTEGER The port of the Arcade Engine host. │ -│ [default: None] │ -│ --tls Whether to force TLS for the connection to the Arcade │ -│ Engine. │ -│ --no-tls Whether to disable TLS for the connection to the Arcade │ -│ Engine. │ -│ --help Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Commands ─────────────────────────────────────────────────────────────────────────╮ -│ list List all workers │ -│ enable Enable a worker │ -│ disable Disable a worker │ -│ rm Remove a worker │ -│ logs Get logs for a worker │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -``` - -## `arcade secret` - -```bash -Usage: arcade secret [OPTIONS] COMMAND [ARGS]... - - Manage tool secrets in the cloud (set, unset, list) - -╭─ Options ──────────────────────────────────────────────────────────────────────────╮ -│ --host -h TEXT The Arcade Engine host. │ -│ [default: api.arcade.dev] │ -│ --port -p INTEGER The port of the Arcade Engine host. │ -│ [default: None] │ -│ --tls Whether to force TLS for the connection to the Arcade │ -│ Engine. │ -│ --no-tls Whether to disable TLS for the connection to the Arcade │ -│ Engine. │ -│ --help Show this message and exit. │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -╭─ Commands ─────────────────────────────────────────────────────────────────────────╮ -│ set Set tool secret(s) using KEY=VALUE pairs or from .env file │ -│ list List all tool secrets in Arcade Cloud │ -│ unset Delete tool secret(s) by key names │ -╰────────────────────────────────────────────────────────────────────────────────────╯ -``` - -Last updated on February 10, 2026 - -[Telemetry](/en/references/mcp/telemetry.md) -[CLI Cheat Sheet](/en/references/cli-cheat-sheet.md) diff --git a/public/_markdown/en/references/auth-providers.md b/public/_markdown/en/references/auth-providers.md deleted file mode 100644 index 7db8f8e9c..000000000 --- a/public/_markdown/en/references/auth-providers.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: "Auth Providers" -description: "Registry of all auth providers available in the Arcade ecosystem" ---- -Auth ProvidersOverview - -# Auth Providers - - enable users to seamlessly and securely allow Arcade to access their data. - -Arcade has several available in the Arcade Cloud Platform so you don’t have to configure your own. However, using Arcade’s auth providers means that your will see the Arcade brand (name and logo) on the auth screen and your authentications will share any rate limits from those providers with other Arcade customers. - -It can be useful to configure your own for the following reasons: - -- You want to use your own brand on the auth screen -- You want to isolate your rate limits from other Arcade customers -- You want to use a service that Arcade [does not have a built-in auth provider for](/references/auth-providers/oauth2.md) - - -After adding an used by an Arcade , executing the tool will automatically use your auth provider. Even in the Arcade Cloud Platform, your auth provider will take precedence over the arcade-provided auth provider. - -Adding multiple providers of the same type, not including the arcade-provided ones, can cause Arcade’s authorization to fail, see [Using multiple providers of the same type](#using-multiple-providers-of-the-same-type) for more information. - -## Catalog of providers - -For more information on how to customize your , select an auth provider from the list below: - - - -[![Asana logo](/images/icons/asana.svg) ## Asana #### Auth Authorize tools and agents with Asana](/references/auth-providers/asana.md) -[![Atlassian logo](/images/icons/atlassian.png) ## Atlassian #### Auth Authorize tools and agents with Atlassian](/references/auth-providers/atlassian.md) -[![Discord logo](/images/icons/discord.png) ## Discord #### Auth Authorize tools and agents with Discord in a user's context](/references/auth-providers/discord.md) -[![Dropbox logo](/images/icons/dropbox.png) ## Dropbox #### Auth Authorize tools and agents with Dropbox in a user's context](/references/auth-providers/dropbox.md) -[![GitHub logo](/images/icons/github.png) ## GitHub #### Auth Authorize tools and agents with GitHub, including private repositories](/references/auth-providers/github.md) -[![Google logo](/images/icons/google.png) ## Google #### Auth Authorize tools and agents with Google: Gmail, Calendar, YouTube, Drive, and more](/references/auth-providers/google.md) -[![HubSpot logo](/images/icons/hubspot.png) ## HubSpot #### Auth Authorize tools and agents with HubSpot](/references/auth-providers/hubspot.md) -[![Linear logo](/images/icons/linear.svg) ## Linear #### Auth Authorize tools and agents with Linear](/references/auth-providers/linear.md) -[![LinkedIn logo](/images/icons/linkedin.png) ## LinkedIn #### Auth Authorize tools and agents with LinkedIn](/references/auth-providers/linkedin.md) -[![Microsoft logo](/images/icons/msft.png) ## Microsoft #### Auth Authorize tools and agents with Microsoft Graph](/references/auth-providers/microsoft.md) -[![Notion logo](/images/icons/notion.png) ## Notion #### Auth Authorize tools and agents with Notion](/references/auth-providers/notion.md) -[![Reddit logo](/images/icons/reddit.png) ## Reddit #### Auth Authorize tools and agents with Reddit](/references/auth-providers/reddit.md) -[![Slack logo](/images/icons/slack.png) ## Slack #### Auth Authorize tools and agents with Slack](/references/auth-providers/slack.md) -[![Spotify logo](/images/icons/spotify.png) ## Spotify #### Auth Authorize tools and agents with Spotify](/references/auth-providers/spotify.md) -[![Twitch logo](/images/icons/twitch.png) ## Twitch #### Auth Authorize tools and agents with Twitch](/references/auth-providers/twitch.md) -[![X logo](/images/icons/twitter.png) ## X #### Auth Authorize tools and agents with X (Twitter)](/references/auth-providers/x.md) -[![Zoom logo](/images/icons/zoom_fav.svg) ## Zoom #### Auth Authorize tools and agents with Zoom](/references/auth-providers/zoom.md) -[![OAuth 2.0 logo](/images/icons/oauth2.png) ## OAuth 2.0 #### Auth Authorize tools and agents with any OAuth 2.0-compatible provider](/references/auth-providers/oauth2.md) - -If the you need is not listed, try the [OAuth 2.0](/references/auth-providers/oauth2.md) provider, or [get in touch](mailto:contact@arcade.dev) with us! - -## Using multiple providers of the same type - -You can create multiple of the same type, for example, you can have multiple Google auth providers, each with their own client ID and secret. This might be useful if you want separate Google clients to handle calendar and email scopes, for example. - -However, Arcade’s tools are configured to select an by the type. This means that if you have multiple auth providers of the same type, Arcade will not know which one to use and authorizing the will fail. - -To work around this, you can fork Arcade’s tools and modify them to specify your own by the unique ID that you give each of them. For example, if you have two Google auth providers, `acme-google-calendar` and `acme-google-email`, you can modify Arcade’s Gmail.ListEmails like this: - -```python -@tool( - requires_auth=Google( - id="acme-google-email", # This is the unique ID you gave your auth provider - scopes=["https://www.googleapis.com/auth/gmail.readonly"], - ) -) -async def list_emails( -# ... -``` - -This is similar to the pattern used in the generic OAuth2 provider, but instead of using the `OAuth2` class, you use the `Google` class and specify the `id` of the you want to use. - -See the docs about [Authoring Tools](/guides/create-tools/tool-basics/build-mcp-server.md) for more information on how to create and serve a Server. - -Last updated on January 30, 2026 - -[Contextual Access Webhook API](/en/references/contextual-access-webhook-api.md) -[OAuth 2.0](/en/references/auth-providers/oauth2.md) diff --git a/public/_markdown/en/references/auth-providers/airtable.md b/public/_markdown/en/references/auth-providers/airtable.md deleted file mode 100644 index 776a877b0..000000000 --- a/public/_markdown/en/references/auth-providers/airtable.md +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: "Airtable" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Airtable - -# Airtable - -The Airtable enables tools and to call [Airtable APIs](https://airtable.com/developers/web/api/introduction)  on behalf of a using OAuth 2.0 authentication. - -Want to quickly get started with Airtable in your or AI app? The pre-built [Arcade Airtable MCP Server](/resources/integrations/productivity/airtableapi.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Airtable auth with Arcade. - -This is used by: - -- The [Arcade Airtable MCP Server](/resources/integrations/productivity/airtableapi.md) - , which provides pre-built for interacting with Airtable -- Your [app code](#using-airtable-auth-in-app-code) - that needs to call the Airtable API -- Or, your [custom tools](#using-airtable-auth-in-custom-tools) - that need to call the Airtable API - -## Configuring Airtable auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Airtable app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Airtable app credentials, let’s go through the steps to create an Airtable app. - -### Create an Airtable app - -To integrate with Airtable’s API, you’ll need to create an OAuth integration: - -#### Access the Airtable Developer Hub - -Navigate to the [Airtable Developer Hub](https://airtable.com/developers/web)  and sign in with your Airtable . - -#### Create a new OAuth integration - -1. Go to the [Create OAuth Integration](https://airtable.com/create/oauth) -   page -2. Fill in the required details: - - **Integration Name**: Choose a descriptive name for your application - - **Description**: Provide a brief description of your app’s purpose - -#### Configure OAuth settings - -1. Set the **Redirect URL** to the redirect URL generated by Arcade (see configuration section below) -2. Configure the required **Scopes** for your application: - - `data.records:read` - Read records from tables - - `data.records:write` - Create, update, and delete records - - `schema.bases:read` - Read base schema - - Add other scopes as needed for your use case - -#### Obtain your credentials - -1. After creating your integration, you’ll receive a **Client ID** -2. Generate a **Client Secret** from your integration settings -3. Copy both values for use in Arcade configuration - -For detailed instructions, refer to Airtable’s [OAuth documentation](https://airtable.com/developers/web/guides/oauth-integrations) . - -Next, add the Airtable app to Arcade. - -## Configuring your own Airtable Auth Provider in Arcade - -### Dashboard GUI - -### Configure Airtable Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **OAuth** section of the Arcade Dashboard left-side menu, click **Providers**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “arcade-airtable”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Airtable integration. -- Configure the OAuth 2.0 endpoints: - - **Authorization URL**: `https://airtable.com/oauth2/v1/authorize` - - **Token URL**: `https://airtable.com/oauth2/v1/token` -- Note the **Redirect URL** generated by Arcade. This must be set as your Airtable integration’s Redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure Airtable Auth Using Configuration File - -This method is only available when you are [self-hosting the engine](/guides/deployment-hosting/on-prem.md) - -#### Set environment variables - -Set the following environment variables: - -```bash -export AIRTABLE_CLIENT_ID="" -export AIRTABLE_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -AIRTABLE_CLIENT_ID="" -AIRTABLE_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: arcade-airtable - description: Airtable OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:AIRTABLE_CLIENT_ID} - client_secret: ${env:AIRTABLE_CLIENT_SECRET} - oauth2: - scope_delimiter: " " - pkce: - enabled: true - code_challenge_method: S256 - authorize_request: - endpoint: "https://airtable.com/oauth2/v1/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}}" - state: "{{state}}" - token_request: - endpoint: "https://airtable.com/oauth2/v1/token" - params: - grant_type: authorization_code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - response_content_type: application/json -``` - -When you use tools that require Airtable auth using your Arcade credentials, Arcade will automatically use this Airtable OAuth provider. If you have multiple Airtable providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Airtable auth in app code - -Use the Airtable in your own and AI apps to get a token for the Airtable API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Airtable API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="arcade-airtable", - scopes=["data.records:read", "data.records:write", "schema.bases:read"] -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "arcade-airtable", [ - "data.records:read", - "data.records:write", - "schema.bases:read", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Airtable auth in custom tools - -You can use the pre-built [Arcade Airtable MCP Server](/resources/integrations/productivity/airtableapi.md) to quickly build and AI apps that interact with Airtable. - -If the pre-built tools in the Airtable Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Airtable API. - -Use the `OAuth2()` auth class to specify that a requires authorization with Airtable. The `context.authorization.token` field will be automatically populated with the ’s Airtable token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - - -@tool( - requires_auth=OAuth2( - provider_id="arcade-airtable", - scopes=["data.records:read", "schema.bases:read"] - ) -) -async def list_bases( - context: ToolContext, -) -> Annotated[dict, "The user's bases."]: - """ - Retrieve the list of bases accessible to the authenticated user. - """ - url = "https://api.airtable.com/v0/meta/bases" - headers = { - "Authorization": context.authorization.token, - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return dict(response.json()) -``` - -## Available Scopes - -Airtable supports various OAuth scopes that determine the level of access your application has: - -- `data.records:read` - Read records from tables -- `data.records:write` - Create, update, and delete records -- `data.recordComments:read` - Read comments on records -- `data.recordComments:write` - Create and update comments on records -- `schema.bases:read` - Read base schema information -- `schema.bases:write` - Modify base schema -- `webhook:manage` - Create and manage webhooks -- `user.email:read` - Read email address - -For a complete list of available scopes, refer to the [Airtable Scopes documentation](https://airtable.com/developers/web/api/scopes) . - -Last updated on January 30, 2026 - -[OAuth 2.0](/en/references/auth-providers/oauth2.md) -[Asana](/en/references/auth-providers/asana.md) diff --git a/public/_markdown/en/references/auth-providers/asana.md b/public/_markdown/en/references/auth-providers/asana.md deleted file mode 100644 index 26d6e46d2..000000000 --- a/public/_markdown/en/references/auth-providers/asana.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -title: "Asana" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Asana - -# Asana - -The Asana enables tools and to call Asana APIs on behalf of a . - -Want to quickly get started with Asana services in your or AI app? The pre-built [Arcade Asana MCP Server](/resources/integrations/productivity/asana.md) is what you want! - -## What’s documented here - -This page describes how to use and configure Asana auth with Arcade. - -This is used by: - -- The [Arcade Asana MCP Server](/resources/integrations/productivity/asana.md) - , which provides pre-built for interacting with Asana -- Your [app code](#using-asana-auth-in-app-code) - that needs to call Asana APIs -- Or, your [custom tools](#using-asana-auth-in-custom-tools) - that need to call Asana APIs - -## Use Arcade’s Default Asana Auth Provider - -Arcade offers a default Asana that you can use in the Arcade Cloud Platform. In this case, your will see `Arcade` as the name of the application that’s requesting permission. - -If you choose to use Arcade’s Asana auth, you don’t need to configure anything. Follow the [Asana MCP Server examples](/resources/integrations/productivity/asana.md) to get started calling Asana . - -## Use Your Own Asana App Credentials - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Asana app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Asana app credentials, let’s go through the steps to create an Asana app. - -## Create an Asana App - -Follow the documentation on [Building an App with Asana](https://developers.asana.com/docs/overview) . You may create a [developer sandbox account](https://developers.asana.com/docs/developer-sandbox)  to test your app before moving to production. - -When creating your app, use the following settings: - -- Set an appropriate App name, description and icon. This will be visible to your authorizing access to your app. -- Take note of the **Client ID** and **Client Secret**. -- In the OAuth settings: - - Under “Redirect URLs”, click **Add Redirect URL** and add the redirect URL generated by Arcade (see below). - - Under “Permission Scopes”, select “Full Permissions” -- In the “App Listing Details” section, optionally add more information about your app. -- In the “Manage Distribution” section, under “Choose a distribution method”, select “Any workspace”. -- Optionally, submit your app for the Asana team review. - -Asana [recently introduced](https://forum.asana.com/t/new-oauth-permission-scopes/1048556)  granular permission scopes. This feature is still in preview and the scopes available at the moment do not include all endpoints/actions that the Asana Servers needs. For those reasons, Arcade uses the “Full Permissions” scope. - -## Configuring your own Asana Auth Provider in Arcade - -### Dashboard GUI - -### Configure Asana Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Asana**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-asana-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Asana app. -- Note the **Redirect URL** generated by Arcade. This must be added to your Asana app’s Redirect URLs. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Asana auth using your Arcade credentials, Arcade will automatically use this Asana OAuth provider. If you have multiple Asana providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using the Arcade Asana MCP Servers - -The [Arcade Asana MCP Server](/resources/integrations/productivity/asana.md) provides tools to interact with various Asana objects, such as tasks, , teams, and . - -Refer to the [MCP Server documentation and examples](/resources/integrations/productivity/asana.md) to learn how to use the Server to build and AI apps that interact with Asana services. - -## Using Asana auth in app code - -Use the Asana in your own and AI apps to get a \-scoped token for the Asana API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Asana API: - -As explained [above](#create-an-asana-app), the Asana granular permission scopes are in preview and not yet supported. The `"default"` scope should be used to authorize any action/endpoint you need to call in the Asana API. - -### Python - -```python -from arcadepy import Arcade - -client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="asana", - scopes=["default"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -# Do something interesting with the token... -auth_token = auth_response.context.token -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "asana", { - scopes: ["default"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -const response = await client.auth.waitForCompletion(authResponse); - -// Do something interesting with the token... -const auth_token = response.context.token; -``` - -You can use the auth token to call the [Get multiple tasks endpoint](https://developers.asana.com/reference/gettasks)  and read information about tasks, for example. Any Asana API endpoint can be called with the auth token. - -## Using Asana auth in custom tools - -You can use the pre-built [Arcade Asana MCP Server](/resources/integrations/productivity/asana.md) to quickly build and AI apps that interact with Asana. - -If the pre-built tools in the Asana Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with Asana API. - -Use the `Asana()` auth class to specify that a tool requires authorization with Asana. The authentication token needed to call the Asana API is available in the through the `context.get_auth_token_or_empty()` method. - -As explained [above](#create-an-asana-app), the Asana granular permission scopes are in preview and not yet supported. The `"default"` scope should be used as the only scope in all . - -```python -from typing import Annotated - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Asana - - -@tool(requires_auth=Asana(scopes=["default"])) -async def delete_task( - context: ToolContext, - task_id: Annotated[str, "The ID of the task to delete."], -) -> Annotated[dict, "Details of the deletion response"]: - """Deletes a task.""" - url = f"https://api.asana.com/api/1.0/tasks/{task_id}" - headers = { - "Authorization": f"Bearer {context.get_auth_token_or_empty()}", - "Accept": "application/json", - } - - async with httpx.AsyncClient() as client: - response = await client.delete(url, headers=headers) - response.raise_for_status() - return response.json() -``` - -Your new can be called like demonstrated below: - -### Python - -If you are self-hosting, change the `base_url` parameter in the `Arcade` constructor to match your URL. By default, the Engine will be available at `http://localhost:9099`. - -```python -from arcadepy import Arcade - -client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable - -USER_ID = "{arcade_user_id}" -TOOL_NAME = "Asana.DeleteTask" - -auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) - -if auth_response.status != "completed": - print(f"Click this link to authorize: {auth_response.url}") - -# Wait for the authorization to complete -client.auth.wait_for_completion(auth_response) - -tool_input = { - "task_id": "1234567890", -} - -response = client.tools.execute( - tool_name=TOOL_NAME, - input=tool_input, - user_id=USER_ID, -) -print(response.output.value) -``` - -### JavaScript - -If you are self-hosting, change the `baseURL` parameter in the `Arcade` constructor to match your URL. By default, the Engine will be available at `http://localhost:9099`. - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable - -const USER_ID = "{arcade_user_id}"; -const TOOL_NAME = "Asana.DeleteTask"; - -// Start the authorization process -const authResponse = await client.tools.authorize({ - tool_name: TOOL_NAME, - user_id: USER_ID, -}); - -if (authResponse.status !== "completed") { - console.log(`Click this link to authorize: ${authResponse.url}`); -} - -// Wait for the authorization to complete -await client.auth.waitForCompletion(authResponse); - -const toolInput = { - task_id: "1234567890", -}; - -const response = await client.tools.execute({ - tool_name: TOOL_NAME, - input: toolInput, - user_id: USER_ID, -}); - -console.log(response.output.value); -``` - -Last updated on January 30, 2026 - -[Airtable](/en/references/auth-providers/airtable.md) -[Atlassian](/en/references/auth-providers/atlassian.md) diff --git a/public/_markdown/en/references/auth-providers/atlassian.md b/public/_markdown/en/references/auth-providers/atlassian.md deleted file mode 100644 index adf53be3a..000000000 --- a/public/_markdown/en/references/auth-providers/atlassian.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: "Atlassian" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Atlassian - -# Atlassian - -At this time, Arcade does not offer a default Atlassian . To use Atlassian auth, you must create a custom Auth Provider with your own Atlassian OAuth 2.0 credentials as described below. - -The Atlassian enables tools and to call the Atlassian API on behalf of a . - -### What’s documented here - -This page describes how to use and configure Atlassian auth with Arcade. - -This is used by: - -- Your [app code](#using-atlassian-auth-in-app-code) - that needs to call Atlassian APIs -- Or, your [custom tools](#using-atlassian-auth-in-custom-tools) - that need to call Atlassian APIs - -## Configuring Atlassian auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Atlassian app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Atlassian app credentials, let’s go through the steps to create an Atlassian app. - -### Create an Atlassian app - -- Create a Atlassian app in the [Atlassian developer console](https://developer.atlassian.com/console/myapps/) -   -- In the Authorization tab, click the “Add” action button and set the Callback URL to the redirect URL generated by Arcade (see below) -- In the Permissions tab, enable any permissions you need for your app -- In the Settings tab, copy the Client ID and Secret to use below - -## Configuring your own Atlassian Auth Provider in Arcade - -### Dashboard GUI - -### Configure Atlassian Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Atlassian**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-atlassian-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Atlassian app. -- Note the **Redirect URL** generated by Arcade. This must be added to your Atlassian app as a Callback URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Atlassian auth using your Arcade credentials, Arcade will automatically use this Atlassian OAuth provider. If you have multiple Atlassian providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Atlassian auth in app code - -Use the Atlassian in your own and AI apps to get a token for the Atlassian API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Atlassian API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="atlassian", - scopes=["read:me", "read:jira-user", "read:confluence-user"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -let authResponse = await client.auth.start(userId, "atlassian", { - scopes: ["read:me", "read:jira-user", "read:confluence-user"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Atlassian auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Atlassian API. - -Use the `Atlassian()` auth class to specify that a requires authorization with Atlassian. The `context.authorization.token` field will be automatically populated with the ’s Atlassian token: - -```python -from typing import Annotated, Optional - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Atlassian - - -@tool( - requires_auth=Atlassian( - scopes=["read:jira-work"], - ) -) -async def list_projects( - context: ToolContext, - query: Annotated[ - Optional[str], - "The query to filter the projects by. Defaults to empty string to list all projects.", - ] = "", -) -> Annotated[dict, "The list of projects in a user's Jira instance"]: - """List a Jira user's projects.""" - url = f"https://api.atlassian.com/ex/jira//rest/api/3/project/search?query={query}" - headers = {"Authorization": f"Bearer {context.authorization.token}"} - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return response.json() -``` - -Last updated on January 30, 2026 - -[Asana](/en/references/auth-providers/asana.md) -[Calendly](/en/references/auth-providers/calendly.md) diff --git a/public/_markdown/en/references/auth-providers/calendly.md b/public/_markdown/en/references/auth-providers/calendly.md deleted file mode 100644 index 0918f9074..000000000 --- a/public/_markdown/en/references/auth-providers/calendly.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -title: "Calendly" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Calendly - -# Calendly - -The Calendly enables tools and to call [Calendly APIs](https://developer.calendly.com/api-docs)  on behalf of a using OAuth 2.0 authentication. - -Want to quickly get started with Calendly in your or AI app? The pre-built [Arcade Calendly MCP Server](/resources/integrations/productivity/calendlyapi.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Calendly auth with Arcade. - -This is used by: - -- The [Arcade Calendly MCP Server](/resources/integrations/productivity/calendlyapi.md) - , which provides pre-built for interacting with Calendly -- Your [app code](#using-calendly-auth-in-app-code) - that needs to call the Calendly API -- Or, your [custom tools](#using-calendly-auth-in-custom-tools) - that need to call the Calendly API - -## Configuring Calendly auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Calendly app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Calendly app credentials, let’s go through the steps to create a Calendly app. - -### Create a Calendly app - -To integrate with Calendly’s API, you’ll need to create a developer and register an OAuth application: - -#### Create a Calendly developer account - -1. Navigate to [developer.calendly.com/create-a-developer-account](https://developer.calendly.com/create-a-developer-account) -   -2. Sign up using your GitHub or Google -3. Complete the registration process - -#### Register a new OAuth application - -1. Click on **Create New App** or **Register an App** -2. Fill in the required details: - - **Application Name**: Choose a descriptive name for your application - - **Type**: Select **Web** or **Native** based on your application type - - **Environment**: Choose **Sandbox** (for testing) or **Production** - - **Redirect URI**: Add the redirect URL generated by Arcade (see configuration section below) - - For Sandbox: HTTP with localhost is allowed (e.g., `http://localhost:1234`) - - For Production: HTTPS is required - -#### Save your credentials - -1. After registration, you’ll receive your **Client ID**, **Client Secret**, and **Webhook Signing Key** -2. **Important**: Copy and save these credentials immediately, as the Client Secret and Webhook Signing Key won’t be accessible again - -For detailed instructions, refer to Calendly’s [Getting Started guide](https://developer.calendly.com/getting-started)  and [OAuth documentation](https://developer.calendly.com/api-docs/0b07b0a0b0b07-get-authorization-code) . - -Next, add the Calendly app to Arcade. - -## Configuring your own Calendly Auth Provider in Arcade - -### Dashboard GUI - -### Configure Calendly Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “arcade-calendly”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Calendly app. -- Configure the OAuth 2.0 endpoints: - - **Authorization URL**: `https://auth.calendly.com/oauth/authorize` - - **Token URL**: `https://auth.calendly.com/oauth/token` -- Note the **Redirect URL** generated by Arcade. This must be set as your Calendly app’s Redirect URI. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure Calendly Auth Using Configuration File - -This method is only available when you are [self-hosting the engine](/guides/deployment-hosting/on-prem.md) - -#### Set environment variables - -Set the following environment variables: - -```bash -export CALENDLY_CLIENT_ID="" -export CALENDLY_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -CALENDLY_CLIENT_ID="" -CALENDLY_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: arcade-calendly - description: Calendly OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:CALENDLY_CLIENT_ID} - client_secret: ${env:CALENDLY_CLIENT_SECRET} - oauth2: - authorize_request: - endpoint: "https://auth.calendly.com/oauth/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - state: "{{state}}" - token_request: - endpoint: "https://auth.calendly.com/oauth/token" - params: - grant_type: authorization_code - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - redirect_uri: "{{redirect_uri}}" - response_content_type: application/json -``` - -When you use tools that require Calendly auth using your Arcade credentials, Arcade will automatically use this Calendly OAuth provider. If you have multiple Calendly providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Calendly auth in app code - -Use the Calendly in your own and AI apps to get a token for the Calendly API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Calendly API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="arcade-calendly" -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "arcade-calendly"); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Calendly auth in custom tools - -You can use the pre-built [Arcade Calendly MCP Server](/resources/integrations/productivity/calendlyapi.md) to quickly build and AI apps that interact with Calendly. - -If the pre-built tools in the Calendly Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Calendly API. - -Use the `OAuth2()` auth class to specify that a requires authorization with Calendly. The `context.authorization.token` field will be automatically populated with the ’s Calendly token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - - -@tool( - requires_auth=OAuth2(provider_id="arcade-calendly") -) -async def get_user_info( - context: ToolContext, -) -> Annotated[dict, "The user information."]: - """ - Retrieve the authenticated user's information from Calendly. - """ - url = "https://api.calendly.com/users/me" - headers = { - "Authorization": context.authorization.token, - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return dict(response.json()) -``` - -For more details about Calendly’s authentication, refer to the [Calendly Authentication documentation](https://developer.calendly.com/how-to-guides/authentication) . - -Last updated on January 30, 2026 - -[Atlassian](/en/references/auth-providers/atlassian.md) -[ClickUp](/en/references/auth-providers/clickup.md) diff --git a/public/_markdown/en/references/auth-providers/clickup.md b/public/_markdown/en/references/auth-providers/clickup.md deleted file mode 100644 index 0462f24e3..000000000 --- a/public/_markdown/en/references/auth-providers/clickup.md +++ /dev/null @@ -1,163 +0,0 @@ ---- -title: "ClickUp" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -ClickUp - -# ClickUp - -The ClickUp enables tools and to call the ClickUp API on behalf of a . - -### What’s documented here - -This page describes how to use and configure ClickUp auth with Arcade. - -This is used by: - -- Your [app code](#using-clickup-auth-in-app-code) - that needs to call ClickUp APIs -- Or, your [custom tools](#using-clickup-auth-in-custom-tools) - that need to call ClickUp APIs - -## Configuring ClickUp auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own ClickUp app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your ClickUp app credentials, let’s go through the steps to create a ClickUp app. - -### Create a ClickUp app - -- Navigate to your ClickUp workspace and go to **Settings → Apps** -- Click **Create new app** in the OAuth Apps section -- Fill in your app name and description -- Set the OAuth Redirect URL to: `https://cloud.arcade.dev/api/v1/oauth/callback` -- Copy the Client ID and Client Secret, which you’ll need below - -## Configuring your own ClickUp Auth Provider in Arcade - -### Dashboard GUI - -### Configure ClickUp Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at `http://localhost:9099/dashboard`. Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **ClickUp**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-clickup-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your ClickUp app. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require ClickUp auth using your Arcade credentials, Arcade will automatically use this ClickUp OAuth provider. If you have multiple ClickUp providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using ClickUp auth in app code - -Use the ClickUp in your own and AI apps to get a \-scoped token for the ClickUp API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the ClickUp API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="clickup", -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# TODO: Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "arcade-js"; - -const client = new Arcade(); - -const auth = await client.auth.start({ - provider: "clickup", -}); - -if (auth.status !== "completed") { - console.log("Finish authorization at:", auth.url); - await client.auth.waitForCompletion(auth); -} - -const { token } = auth.context; -// Use the token in ClickUp API requests -``` - -## Using ClickUp auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the ClickUp API. - -Use the `ClickUp()` auth class to specify that a requires authorization with ClickUp. The `context.authorization.token` field will be automatically populated with the ’s ClickUp token: - -```python -import httpx -from arcade_tdk import tool, ToolContext -from arcade_tdk.auth import ClickUp - -@tool(requires_auth=ClickUp()) -async def get_my_workspaces(context: ToolContext) -> dict: - """Get the authenticated user's workspaces (teams) from ClickUp.""" - token = context.authorization.token - - # Make authenticated request to ClickUp API - async with httpx.AsyncClient() as client: - response = await client.get( - "https://api.clickup.com/api/v2/team", - headers={ - "Authorization": token, - "Content-Type": "application/json" - } - ) - - if response.status_code == 200: - data = response.json() - teams = data.get("teams", []) - return { - "success": True, - "teams": [{"id": team["id"], "name": team["name"]} for team in teams] - } - else: - return { - "success": False, - "error": f"ClickUp API error: {response.status_code} - {response.text}" - } -``` - -Last updated on January 30, 2026 - -[Calendly](/en/references/auth-providers/calendly.md) -[Discord](/en/references/auth-providers/discord.md) diff --git a/public/_markdown/en/references/auth-providers/discord.md b/public/_markdown/en/references/auth-providers/discord.md deleted file mode 100644 index 4c0e7b083..000000000 --- a/public/_markdown/en/references/auth-providers/discord.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -title: "Discord" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Discord - -# Discord - -At this time, Arcade does not offer a default Discord . To use Discord auth, you must create a custom Auth Provider with your own Discord OAuth 2.0 credentials as described below. - -The Discord enables tools and to call the Discord API on behalf of a . - -### What’s documented here - -This page describes how to use and configure Discord auth with Arcade. - -This is used by: - -- Your [app code](#using-discord-auth-in-app-code) - that needs to call Discord APIs -- Or, your [custom tools](#using-discord-auth-in-custom-tools) - that need to call Discord APIs - -## Configuring Discord auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Discord app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Discord app credentials, let’s go through the steps to create a Discord app. - -### Create a Discord app - -- Create a Discord Application in the [Discord developer portal](https://discord.com/developers/applications) -   -- In the OAuth2 tab, set the redirect URI to the redirect URL generated by Arcade (see below) -- Copy the Client ID and Client Secret (you may need to reset the secret to see it) - -## Configuring your own Discord Auth Provider in Arcade - -### Dashboard GUI - -### Configure Discord Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Discord**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-discord-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Discord app. -- Note the **Redirect URL** generated by Arcade. This must be added to your Discord app’s Redirect URIs. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Discord auth using your Arcade credentials, Arcade will automatically use this Discord OAuth provider. If you have multiple Discord providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Discord auth in app code - -Use the Discord in your own and AI apps to get a token for the Discord API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Discord API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="discord", - scopes=["identify", "email", "guilds", "guilds.join"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "discord", { - scopes: ["identify", "email", "guilds", "guilds.join"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Discord auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Discord API. - -Use the `Discord()` auth class to specify that a requires authorization with Discord. The `context.authorization.token` field will be automatically populated with the ’s Discord token: - -```python -from typing import Annotated, Optional - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Discord - - -@tool( - requires_auth=Discord( - scopes=["guilds"], - ) -) -async def list_servers( - context: ToolContext, - user_id: Annotated[ - Optional[str], - "The user's user ID. Defaults to '@me' for the current user.", - ] = "@me", -) -> Annotated[dict, "List of servers the user is a member of"]: - """List a Discord user's servers they are a member of.""" - url = f"https://discord.com/api/users/{user_id}/guilds" - headers = {"Authorization": f"Bearer {context.authorization.token}"} - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return response.json() -``` - -Last updated on January 30, 2026 - -[ClickUp](/en/references/auth-providers/clickup.md) -[Dropbox](/en/references/auth-providers/dropbox.md) diff --git a/public/_markdown/en/references/auth-providers/dropbox.md b/public/_markdown/en/references/auth-providers/dropbox.md deleted file mode 100644 index 949bdcee0..000000000 --- a/public/_markdown/en/references/auth-providers/dropbox.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -title: "Dropbox" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Dropbox - -# Dropbox - -At this time, Arcade does not offer a default Dropbox . To use Dropbox auth, you must create a custom Auth Provider with your own Dropbox OAuth 2.0 credentials as described below. - -The Dropbox enables tools and to call the Dropbox API on behalf of a . - -### What’s documented here - -This page describes how to use and configure Dropbox auth with Arcade. - -This is used by: - -- Your [app code](#using-dropbox-auth-in-app-code) - that needs to call Dropbox APIs -- Or, your [custom tools](#using-dropbox-auth-in-custom-tools) - that need to call Dropbox APIs - -## Configuring Dropbox auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Dropbox app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Dropbox app credentials, let’s go through the steps to create a Dropbox app. - -### Create a Dropbox app - -- Create a Dropbox Application in the [Dropbox App Console](https://www.dropbox.com/developers/apps) -   -- In the Settings tab, under the “OAuth 2” section, set the redirect URI to the redirect URL generated by Arcade (see below) -- In the Permissions tab, add any scopes that your app will need -- In the Settings tab, copy the App key (Client ID) and App secret (Client Secret), which you’ll need below - -Next, add the Dropbox app to Arcade. - -## Configuring your own Dropbox Auth Provider in Arcade - -### Dashboard GUI - -### Configure Dropbox Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Dropbox**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-dropbox-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Dropbox app. -- Note the **Redirect URL** generated by Arcade. This must be added to your Dropbox app’s Redirect URIs. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Dropbox auth using your Arcade credentials, Arcade will automatically use this Dropbox OAuth provider. If you have multiple Dropbox providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Dropbox auth in app code - -Use the Dropbox in your own and AI apps to get a \-scoped token for the Dropbox API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Dropbox API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="dropbox", - scopes=["openid", "sharing.read", "files.metadata.read"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "dropbox", { - scopes: ["openid", "sharing.read", "files.metadata.read"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Dropbox auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Dropbox API. - -Use the `Dropbox()` auth class to specify that a requires authorization with Dropbox. The `context.authorization.token` field will be automatically populated with the ’s Dropbox token: - -```json -from typing import Annotated, Optional - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Dropbox - - -@tool( - requires_auth=Dropbox( - scopes=["files.metadata.read"], - ) -) -async def list_files( - context: ToolContext, - path: Annotated[ - Optional[str], - "The path to the folder to list the contents of. Defaults to empty string to list the root folder.", - ] = "", -) -> Annotated[dict, "List of servers the user is a member of"]: - """Starts returning the contents of a folder.""" - url = "https://api.dropboxapi.com/2/files/list_folder" - headers = {"Authorization": f"Bearer {context.authorization.token}"} - - async with httpx.AsyncClient() as client: - response = await client.post(url, headers=headers, json={"path": path}) - response.raise_for_status() - return response.json() -``` - -Last updated on January 30, 2026 - -[Discord](/en/references/auth-providers/discord.md) -[Figma](/en/references/auth-providers/figma.md) diff --git a/public/_markdown/en/references/auth-providers/figma.md b/public/_markdown/en/references/auth-providers/figma.md deleted file mode 100644 index 148ccd8ba..000000000 --- a/public/_markdown/en/references/auth-providers/figma.md +++ /dev/null @@ -1,289 +0,0 @@ ---- -title: "Figma" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Figma - -# Figma - -The Figma enables tools and to call [Figma APIs](https://developers.figma.com/docs/rest-api/)  on behalf of a using OAuth 2.0 authentication. - -Want to quickly get started with Figma in your or AI app? The pre-built [Arcade Figma MCP Server](/resources/integrations/development/figma.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Figma auth with Arcade. - -This is used by: - -- The [Arcade Figma MCP Server](/resources/integrations/development/figma.md) - , which provides pre-built for interacting with Figma -- Your [app code](#using-figma-auth-in-app-code) - that needs to call the Figma API -- Or, your [custom tools](#using-figma-auth-in-custom-tools) - that need to call the Figma API - -### Required scopes for the Figma MCP Server - -If you’re using the [Arcade Figma MCP Server](/resources/integrations/development/figma.md), you’ll need to configure these scopes based on which you plan to use: - -- `file_content:read` - File structure, pages, nodes, and image exports -- `library_content:read` - Published components, styles, and component sets from files -- `team_library_content:read` - Team library content -- `library_assets:read` - Individual component, style, and component set metadata -- `file_comments:read` / `file_comments:write` - Reading and creating comments -- `current_user:read` - profile information -- `projects:read` - Team and files (**requires private OAuth app**) - -The `projects:read` scope is **only available in private Figma OAuth apps**. If you need to access team and files, you must create a private OAuth app through your Figma organization. - -For detailed descriptions of all available Figma OAuth scopes, refer to the [Figma OAuth Scopes documentation](https://developers.figma.com/docs/rest-api/scopes/) . - -## Configuring Figma auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Figma app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Figma app credentials, let’s go through the steps to create a Figma app. - -### Create a Figma app - -To integrate with Figma’s API, you’ll need to set up OAuth 2.0 authentication by creating an app in the Figma Developer Portal: - -#### Access the Figma Developer Portal - -Navigate to the [Figma Developer Portal](https://www.figma.com/developers/)  and sign in with your existing Figma credentials or create a new . - -#### Create a new app - -1. Once logged in, go to your developer dashboard and select “My Apps” -2. Click on “Create a new app” -3. Fill in the required details: - - **App Name**: Choose a descriptive name for your application - - **Website**: Provide the URL of your application’s website - - **App Logo**: Upload a 100x100px PNG image representing your app - -#### Set up OAuth configuration - -1. After creating your app, you’ll receive a `client_id` and `client_secret` -2. Set the redirect URI to the redirect URL generated by Arcade (see configuration section below) -3. Configure the required scopes for your application based on the you need: - - `file_content:read` - Read access to file content and structure - - `library_content:read` - Read access to published library content - - `team_library_content:read` - Read access to team library content - - `library_assets:read` - Read access to individual library assets - - `file_comments:read` - Read access to file comments - - `file_comments:write` - Write access to file comments - - `current_user:read` - Read access to profile - - `projects:read` - Read access to team (private apps only) - -For a complete list of available scopes, refer to the [Figma OAuth Scopes documentation](https://developers.figma.com/docs/rest-api/scopes/)  - -For detailed instructions, refer to Figma’s official [Authentication documentation](https://developers.figma.com/docs/rest-api/authentication/) . - -Next, add the Figma app to Arcade. - -## Configuring your own Figma Auth Provider in Arcade - -### Dashboard GUI - -### Configure Figma Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “figma”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Figma app. -- Configure the OAuth 2.0 endpoints: - - **Authorization URL**: `https://www.figma.com/oauth` - - **Token URL**: `https://api.figma.com/v1/oauth/token` - - **Scope Delimiter**: (space) - - **Use PKCE**: Enabled (S256) -- Note the **Redirect URL** generated by Arcade. This must be set as your Figma app’s redirect URI. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure Figma Auth Using Configuration File - -This method is only available when you are \[self-hosting the engine\](/guides/deployment-hosting/on-prem - -#### Set environment variables - -Set the following environment variables: - -```bash -export FIGMA_CLIENT_ID="" -export FIGMA_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -FIGMA_CLIENT_ID="" -FIGMA_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: figma - description: Figma OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:FIGMA_CLIENT_ID} - client_secret: ${env:FIGMA_CLIENT_SECRET} - oauth2: - scope_delimiter: " " - use_pkce: true - pkce_code_challenge_method: S256 - authorize_request: - endpoint: "https://www.figma.com/oauth" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}} {{existing_scopes}}" - state: "{{state}}" - token_request: - endpoint: "https://api.figma.com/v1/oauth/token" - auth_method: client_secret_basic - params: - grant_type: authorization_code - redirect_uri: "{{redirect_uri}}" - request_content_type: application/x-www-form-urlencoded - response_content_type: application/json - refresh_request: - endpoint: "https://api.figma.com/v1/oauth/token" - auth_method: client_secret_basic - params: - grant_type: refresh_token - refresh_token: "{{refresh_token}}" - request_content_type: application/x-www-form-urlencoded - response_content_type: application/json -``` - -**Note on `projects:read` scope:** If you need access to the `projects:read` scope for team and files navigation, you must create a **private Figma OAuth app**. This scope is not available in public OAuth apps. Learn more in the [Figma OAuth Scopes documentation](https://developers.figma.com/docs/rest-api/scopes/) . - -When you use tools that require Figma auth using your Arcade credentials, Arcade will automatically use this Figma OAuth provider. If you have multiple Figma providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Figma auth in app code - -Use the Figma in your own and AI apps to get a token for the Figma API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Figma API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="figma", - scopes=["file_content:read", "file_comments:write"] -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "figma", [ - "file_content:read", - "file_comments:write", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Figma auth in custom tools - -You can use the pre-built [Arcade Figma MCP Server](/resources/integrations/development/figma.md) to quickly build and AI apps that interact with Figma. - -If the pre-built tools in the Figma Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Figma API. - -Use the `Figma()` auth class to specify that a requires authorization with Figma. The `context.authorization.token` field will be automatically populated with the ’s Figma token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Figma - - -@tool(requires_auth=Figma(scopes=["file_content:read"])) -async def get_figma_file( - context: ToolContext, - file_key: Annotated[str, "The Figma file key to retrieve."], -) -> Annotated[dict, "The Figma file data."]: - """ - Retrieve a Figma file by its key. - """ - url = f"https://api.figma.com/v1/files/{file_key}" - headers = { - "Authorization": f"Bearer {context.authorization.token}", - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return dict(response.json()) -``` - -For a complete list of available Figma OAuth scopes and their descriptions, refer to the [Figma OAuth Scopes documentation](https://developers.figma.com/docs/rest-api/scopes/) . - -Last updated on February 10, 2026 - -[Dropbox](/en/references/auth-providers/dropbox.md) -[GitHub](/en/references/auth-providers/github.md) diff --git a/public/_markdown/en/references/auth-providers/github.md b/public/_markdown/en/references/auth-providers/github.md deleted file mode 100644 index 5d7463ca1..000000000 --- a/public/_markdown/en/references/auth-providers/github.md +++ /dev/null @@ -1,1248 +0,0 @@ ---- -title: "GitHub" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -GitHub - -# GitHub - -The GitHub enables tools and to call [GitHub APIs](https://docs.github.com/en/rest/overview/resources-in-the-rest-api)  on behalf of a . - -Want to quickly get started with GitHub in your or AI app? The pre-built [Arcade GitHub MCP Server](/resources/integrations/development/github.md) is what you want! - -## What’s documented here - -This page describes how to use and configure GitHub auth with Arcade. - -This is used by: - -- The [Arcade GitHub MCP Server](/resources/integrations/development/github.md) - , which provides pre-built for interacting with GitHub -- Your [app code](#using-github-auth-in-app-code) - that needs to call the GitHub API -- Or, your [custom tools](#using-github-auth-in-custom-tools) - that need to call the GitHub API - -* * * - -## Why Arcade Uses GitHub Apps (Not OAuth Apps) - -### Arcade’s Decision - -Arcade’s security team selected **GitHub Apps** over OAuth Apps for the GitHub toolkit based on three critical factors: - -1. **🎯 GitHub’s Recommendation**: OAuth Apps are soft-deprecated. GitHub actively recommends Apps for new integrations and invests in their development. - -2. **🔐 Fine-Grained Security**: GitHub Apps support granular permissions (e.g., “Read pull requests”), while OAuth Apps only offer coarse scopes (e.g., `repo` = full repository access). - -3. **🏢 Enterprise Control**: Admins can approve exact permissions and see all app installations. OAuth Apps bypass organizational oversight. - - -**Important**: When creating your GitHub integration with Arcade, you must use a **GitHub App** (not an OAuth App). GitHub Apps provide the security and permission model required for production use. - -### Quick Comparison - -Aspect - -🏆 GitHub Apps (Required) - -OAuth Apps (Not Supported) - -**Permissions** - -Fine-grained (e.g., “Read contents”) - -Broad scopes (e.g., `repo` = full access) - -**Installation** - -Per repository/org (admin approval) - -Per user (no approval) - -**Access** - -Only installed repositories - -All user repositories - -**Tokens** - -Scoped, short-lived - -Broad, long-lived - -**Identity** - -Acts as app - -Acts as user - -**Security** - -⭐⭐⭐⭐⭐ Highest - -⭐⭐⭐ Good - -**Best For** - -Production, CI/CD, Enterprise - -Personal, Prototypes - -**GitHub Enterprise Server (GHES) Limitation** - -GitHub Apps created on github.com **cannot** be installed on GitHub Enterprise Server instances, and vice versa. Each GHES instance requires its own separate GitHub App registration. - -- ✅ Apps on github.com work for all github.com -- ❌ Apps on github.com **DO NOT** work for GHES instances -- ✅ Each GHES instance must register its own GitHub App -- ✅ You can use the same manifest/configuration for multiple instances - -[Learn more about GHES GitHub Apps](https://docs.github.com/en/apps/sharing-github-apps/making-your-github-app-available-for-github-enterprise-server) -  - -### Why Enterprises Choose GitHub Apps - -**🔐 Permission Model** - -**Least-privilege access.** Grant only exact permissions needed (e.g., “Read contents” vs full `repo` access). Minimizes blast radius and supports compliance (SOC 2, ISO 27001). - -**🏢 Installation** - -**Centralized control.** IT/security teams see all app installations, enforce policies, prevent shadow IT. Admin approval ensures integrations are vetted and documented. - -**🎯 Access Scope** - -**Reduced attack surface.** Apps only access explicitly installed repositories. Critical for organizations with repositories at different sensitivity levels. - -**🔑 Token Type** - -**Better security posture.** Tokens are scoped and revocable instantly. Long-lived OAuth tokens remain valid for months if compromised. - -**👤 Identity** - -**Clear accountability.** Actions attributed to app, not . Essential for compliance audits and security investigations. - -**📊 Audit Trail** - -**Clear audit logs.** Easy to identify automated vs human actions. Essential for SOC 2, HIPAA compliance. - -* * * - -## Creating a GitHub App - -You **must** create a GitHub App (not an OAuth App). The “OAuth App” option in GitHub does not provide the fine-grained permissions model required for secure production use. - -### Navigate to GitHub App Settings - -1. Log in to GitHub and click your profile picture (top-right corner) -2. Click **Settings** from the dropdown menu -3. Scroll down in the left sidebar and click **Developer settings** -4. Click **GitHub Apps** in the left sidebar -5. Click **New GitHub App** button - -### Configure Basic Information - -Fill in the following fields: - -- **GitHub App name**: Enter a descriptive name (e.g., “My Company GitHub Integration”) - - This name will be visible to when installing the app -- **Homepage URL**: Your application homepage URL -- ** authorization callback URL**: The redirect URL generated by Arcade (you’ll get this in step 4) -- **Webhook URL**: Leave blank unless you need webhooks -- **Webhook secret**: Leave blank unless you need webhooks - -**Important settings:** - -- Leave “Expire authorization tokens” **checked** ✅ -- **Enable** “Request authorization (OAuth) during installation” **checked** ✅ - - **Critical**: This enables \-to-server authentication flow - - Allows the app to act on behalf of authenticated - - Required for Arcade’s authentication model - - [Learn more about user authorization](https://docs.github.com/en/apps/using-github-apps/authorizing-github-apps) -   -- Leave “Setup URL” blank and “Redirect on update” **unchecked** ❌ -- Ensure “Optional features > \-to-server token expiration” is **enabled** ✅ - -### Set Permissions - -Configure permissions based on your needs. At minimum: - -**Repository Permissions:** - -- **Contents**: Read (required for most ), Write (for file operations and branch creation) -- **Issues**: Read & Write (for issue management and labels) -- **Metadata**: Read (automatically selected) -- **Pull requests**: Read & Write (for PR management and reviews) -- **Statuses**: Read (for CI/CD status checks) - -**Organization Permissions:** - -- **Members**: Read (for collaborators, teams, org ) -- : Read & Write (for Projects V2 management) - -** Permissions:** - -- **Email addresses**: Read (minimum required) -- **Read profile**: Read (for user ) -- **Act on behalf of **: Enable for user-attributed actions (starring repos, user activity feed) - -**“Act on behalf of ”** is only needed when actions should be attributed to the user rather than the app. Without this permission: - -- ✅ The app can still access and read /org data -- ✅ The app can perform actions (attributed to the app, not the ) -- ❌ \-specific actions (starring repos) won’t work -- ❌ Actions won’t appear in the ’s activity feed - -**When you need it**: Starring repositories, activity attribution, user-specific rate limits. - -[Learn more](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-with-a-github-app-on-behalf-of-a-user) -  - -See the [Complete Permissions Reference](#tool-permissions-reference) below for detailed permissions needed by each . - -### Set Installation Options - -- **Personal use**: Select “Only on this ” -- **Organization use**: Select “Any ” - -### Create the App - -1. Review all settings -2. Click **Create GitHub App** -3. You’ll be redirected to your app’s settings page - -### Note Your Credentials - -After creating the app, you’ll need these credentials for Arcade configuration: - -1. **Client ID**: Found in the “About” section of your app settings -2. **Client Secret**: - - Click **Generate a new client secret** - - Copy it immediately (you cannot view it again!) - - Store it securely - -**Arcade Does Not Require Private Keys or Installation IDs** - -Unlike some GitHub App integrations, Arcade’s architecture uses \-to-server tokens (OAuth flow) rather than installation tokens. This means: - -- ✅ You only need: Client ID and Client Secret -- ❌ You do NOT need: Private Key (.pem file) or Installation ID -- ✅ Simpler setup with fewer credentials to manage -- ✅ Users authorize the app directly when they use your - -[Learn more about GitHub App authentication methods](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) -  - -### Install the App - -After creating your GitHub App, you need to install it to make it functional. Installation grants the app access to specific repositories. - -1. In your GitHub App settings page, click **Install App** in the left sidebar -2. Click **Install** next to the where you want to install the app -3. Choose repository access: - - **All repositories**: Access to all current and future repos (convenient but less secure) - - **Only select repositories**: Only specific repos (recommended for production) -4. If selecting specific repositories, choose which repos the app can access -5. Click **Install** to complete the installation - -**For Organizations**: If you’re installing on an organization, an admin may need to approve the installation. Organization members can request installation, which sends a notification to org owners. [Learn more about GitHub App installations](https://docs.github.com/en/apps/using-github-apps/installing-a-github-app-from-a-third-party)  - -**Important**: Users must authorize the app separately from installation. When a user first uses your /app, Arcade will redirect them to GitHub to authorize the app. This grants the app permission to act on their behalf. [Learn more about authorization](https://docs.github.com/en/apps/using-github-apps/authorizing-github-apps)  - -### For Private Organization Repositories - -If you need to access private repositories in an organization: - -1. **Make the app public**: In app settings → **Advanced** → **Make public** - - Public apps can be installed on any - - Private apps can only be installed on the owning -2. **Install on the organization**: **Install app** → Select your organization -3. **Organization admin approval**: An org owner must approve the installation -4. ** authorization**: Each user must authorize the app when they first use it - -Organization admins can review and manage all GitHub App installations at: Settings → Integrations → Applications → Installed GitHub Apps. [Learn more about reviewing installations](https://docs.github.com/en/apps/using-github-apps/reviewing-your-authorized-integrations)  - -* * * - -## Understanding GitHub App Authorization Flow - -GitHub Apps use a two-step process that separates installation from authorization: - -### 1\. Installation (One-time, Admin Action) - -**Who**: Repository admin or organization owner **What**: Installs the app on specific repositories **Grants**: App can access those repositories’ data - -This happens once when setting up the app. [Installation guide](https://docs.github.com/en/apps/using-github-apps/installing-your-own-github-app)  - -### 2\. User Authorization (Per-User, First Use) - -**Who**: Each individual **What**: Authorizes the app to act on their behalf **Grants**: App can perform actions as that user - -This happens when a user first interacts with your /app. Arcade handles this automatically. [Authorization guide](https://docs.github.com/en/apps/using-github-apps/authorizing-github-apps)  - -### Key Differences - -Aspect - -Installation - -Authorization - -**When** - -Once per repository/org - -Once per user - -**Who** - -Admin/Owner - -Individual user - -**Grants** - -Repository access - -User-level permissions - -**Revocable by** - -Admin or user - -User only - -**Required for** - -App to see repos - -App to act as user - -**Why Both?** This two-layer model provides security and control: - -- **Installation** = “Which repos can this app access?” -- **Authorization** = “Can this app act on my behalf?” - - can revoke authorization without affecting the installation. Admins can remove installations without affecting other users’ authorizations. - -* * * - -## Managing User Authorizations - -### How Users Revoke Authorization - -Users can revoke their authorization to your GitHub App at any time. This is important for privacy and security. - -**Steps for :** - -1. Go to GitHub Settings → Applications → Authorized GitHub Apps -2. Find your app in the list -3. Click **Revoke** next to the app name -4. Confirm the revocation - -**Direct Link**: [github.com/settings/apps/authorizations](https://github.com/settings/apps/authorizations)  - -When a revokes authorization: - -- Their access token is immediately invalidated -- The app can no longer act on their behalf -- The app installation remains active (admin can still manage it) -- User can reauthorize later by using your /app again - -[Learn more about reviewing and revoking authorizations](https://docs.github.com/en/apps/using-github-apps/reviewing-and-revoking-authorization-of-github-apps) -  - -### How Admins Revoke App Installation - -Organization owners and repository admins can remove app installations: - -**Steps for Admins:** - -1. Go to Settings → Applications → Installed GitHub Apps -2. Find the app in the list -3. Click **Configure** next to the app -4. Scroll to the bottom and click **Uninstall** -5. Confirm the uninstallation - -**Direct Link**: [github.com/settings/installations](https://github.com/settings/installations)  - -When an admin uninstalls an app: - -- The app loses access to all repositories in that installation -- All authorizations for that installation are revoked -- will need to wait for reinstallation before reauthorizing -- This affects all who were using the app - -### Difference: Revoke Authorization vs Uninstall - -Action - -Who Can Do It - -What Happens - -Scope - -**Revoke Authorization** - -Individual user - -User’s token invalidated - -Only that user - -**Uninstall App** - -Admin/Owner - -App loses repo access - -All users - -**Best Practices:** - -- Users should revoke authorization when they stop using your -- Admins should uninstall when the app is no longer needed organization-wide -- After permission changes, should revoke and reauthorize to get updated permissions - -* * * - -## Configuring GitHub Auth in Arcade - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -### Dashboard GUI - -### Configure via Arcade Dashboard - -#### Access the Arcade Dashboard - -Go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If self-hosting, use `http://localhost:9099/dashboard` (adjust host/port as needed). - -#### Navigate to OAuth Providers - -- Under **OAuth** section, click **Providers** -- Click **Add OAuth Provider** (top-right) -- Select **Included Providers** tab -- In **Provider** dropdown, select **GitHub** - -#### Enter Provider Details - -- **ID**: Choose unique ID (e.g., “my-github-provider”) -- **Description**: Optional description -- **Client ID**: From your GitHub app -- **Client Secret**: From your GitHub app -- **Redirect URL**: Note the URL generated by Arcade - add this to your GitHub app’s “Callback URL” - -#### Create the Provider - -Click **Create** button. The provider is now ready to use. - -When you use tools requiring GitHub auth with your Arcade , Arcade automatically uses this provider. For multiple providers, see [using multiple auth providers](/references/auth-providers.md#using-multiple-providers-of-the-same-type). - -* * * - -## GitHub Enterprise Server Setup - -If your organization uses GitHub Enterprise Server (GHES), you’ll need to create a separate GitHub App and configure a custom OAuth provider in Arcade. - -**Important**: The included GitHub provider in Arcade is configured for github.com only. For GHES, you must create a **Custom Provider** with your instance-specific URLs. - -### GHES vs GitHub.com: Key Differences - -Aspect - -GitHub.com - -GitHub Enterprise Server - -**App Creation** - -Apps work for all github.com users - -Each GHES instance needs its own app - -**Arcade Provider Type** - -Use “Included Provider” (GitHub) - -Use “Custom Provider” - -**Base URL** - -github.com - -Your GHES hostname (e.g., ghes.mycompany.com) - -**API Endpoint** - -api.github.com - -`{your-ghes-host}/api/v3` - -**Rate Limits** - -Fixed by GitHub - -Configurable by GHES admin - -**Feature Availability** - -Latest features - -May lag behind github.com - -### Creating a GitHub App on GHES - -#### Access Your GHES Instance - -1. Log in to your GitHub Enterprise Server instance -2. Click your profile picture (top-right corner) -3. Follow the same steps as [Creating a GitHub App](#creating-a-github-app) - above - -**Note**: All permission requirements and settings are identical to the github.com setup. - -#### Important GHES-Specific Settings - -- **Homepage URL**: Use your GHES-accessible application URL -- **Callback URL**: Use the redirect URL from your Arcade custom provider (see next section) -- All other settings match the github.com configuration - -### Configuring Custom Provider in Arcade - -#### Navigate to OAuth Providers - -1. Go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) -   -2. Click **OAuth** → **Providers** → **Add OAuth Provider** -3. Select **Custom Providers** tab (NOT “Included Providers”) - -You **cannot** use the “Included Providers” option for GHES. The included GitHub provider only works with github.com. - -#### Enter Basic Information - -- **ID**: Unique identifier (e.g., `my-company-github-enterprise`) -- **Description**: Optional (e.g., “MyCompany GHES Production”) -- **Client ID**: From your GHES GitHub App -- **Client Secret**: From your GHES GitHub App -- **Copy the Redirect URL**: You’ll need this for your GHES GitHub App callback URL - -#### Configure Endpoint URLs - -Replace `{your-ghes-host}` with your actual GHES hostname (e.g., `ghes.mycompany.com`): - -Endpoint - -GitHub.com - -GitHub Enterprise Server - -**Authorization** - -`https://github.com/login/oauth/authorize` - -`https://{your-ghes-host}/login/oauth/authorize` - -**Token** - -`https://github.com/login/oauth/access_token` - -`https://{your-ghes-host}/login/oauth/access_token` - -**User Info** - -`https://api.github.com/user` - -`https://{your-ghes-host}/api/v3/user` - -**Example** (for GHES at `ghes.mycompany.com`): - -- Authorization: `https://ghes.mycompany.com/login/oauth/authorize` -- Token: `https://ghes.mycompany.com/login/oauth/access_token` -- Info: `https://ghes.mycompany.com/api/v3/user` - -#### PKCE Settings - -- **PKCE**: Leave **disabled** (default) - -#### Authorization & Token Settings - -- **Authorization Settings**: No changes needed (use defaults) -- **Token Settings**: No changes needed (use defaults) - -#### Refresh Token Settings - -- **Refresh Token Endpoint**: Set to the **same URL** as Token Endpoint - - Example: `https://{your-ghes-host}/login/oauth/access_token` - -#### Token Introspection Settings - -**Enable** Token Introspection and configure: - -- **Introspection Endpoint**: `https://{your-ghes-host}/api/v3/user` -- **Authentication Method**: **Bearer Access Token** (change from default “Basic”) -- **Request Content Type**: **application/json** (change from default) -- **Trigger**: Enable both: - - ✅ **OnTokenGrant** - - ✅ **OnTokenRefresh** - -These Token Introspection settings are critical for GHES. They allow Arcade to verify tokens against your GHES instance’s API. - -#### Create the Provider - -Click **Create** to save your custom GHES provider. - -### Using Your GHES Provider - -When calling Arcade or auth methods, specify your custom provider ID: - -### Python - -```python -client = Arcade() - -auth_response = client.auth.start( - user_id=user_id, - provider="my-company-github-enterprise", # Your custom provider ID -) -``` - -### JavaScript - -```javascript -const client = new Arcade(); - -let authResponse = await client.auth.start( - userId, - "my-company-github-enterprise" // Your custom provider ID -); -``` - -### GHES Version Compatibility - -**Version Differences**: GitHub Enterprise Server may not have all the latest features available on github.com. API responses include an `x-github-enterprise-version` header to help identify the GHES version. - -- New REST endpoints, GraphQL objects, and webhooks are released to GHES later than github.com -- Older GHES versions may have different API capabilities -- Your app code should handle version differences gracefully - -[Check GHES version differences](https://docs.github.com/en/enterprise-server/admin/overview/about-upgrades-to-new-releases) -  - -### Rate Limits on GHES - -Unlike github.com’s fixed rate limits, GHES administrators can configure custom rate limits for their instance. - -**If you hit rate limits:** - -1. Verify your app is properly implementing rate limit headers -2. Contact your GHES administrator about the configured limits -3. Request an increase if needed for your use case - -[Learn more about GHES rate limit configuration](https://docs.github.com/en/enterprise-server/admin/configuration/configuring-rate-limits) -  - -* * * - -## Using GitHub Auth in App Code - -Use the GitHub to get a token for the GitHub API. See [authorizing agents with Arcade](/get-started/about-arcade.md) for details. - -Use `client.auth.start()` to get a token: - -### Python - -```python -import requests -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -""" -In this example, we use Arcade to authenticate with GitHub and retrieve -the number of stargazers of the ArcadeAI/arcade-ai repository. - -There is a tool for that in the Arcade SDK, which simplifies the process. -Below we're showing how to use Arcade as an auth provider directly. -""" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="github", -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -if not auth_response.context.token: - raise ValueError("No token found in auth response") - -token = auth_response.context.token - -owner = "ArcadeAI" -name = "arcade-ai" -headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {token}", - "X-GitHub-Api-Version": "2022-11-28", -} -url = f"https://api.github.com/repos/{owner}/{name}" - -response = requests.get(url, headers=headers) -response.raise_for_status() - -print(response.json().get("stargazers_count")) -``` - -### JavaScript - -```json -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -/* -In this example, we use Arcade to authenticate with GitHub and retrieve -the number of stargazers of the ArcadeAI/arcade-ai repository. - -There is a tool for that in the Arcade SDK, which simplifies the process. -Below we're showing how to use Arcade as an auth provider directly. -*/ - -// Start the authorization process -let authResponse = await client.auth.start(userId, "github"); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -if (!authResponse.context.token) { - throw new Error("No token found in auth response"); -} - -const token = authResponse.context.token; - -const owner = "ArcadeAI"; -const name = "arcade-ai"; -const headers = { - Accept: "application/vnd.github+json", - Authorization: `Bearer ${token}`, - "X-GitHub-Api-Version": "2022-11-28", -}; -const url = `https://api.github.com/repos/${owner}/${name}`; - -const response = await fetch(url, { headers }); -if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); -} -const data = await response.json(); -console.log(data.stargazers_count); -``` - -* * * - -## Using GitHub Auth in Custom Tools - -Use the pre-built [Arcade GitHub MCP Server](/resources/integrations/development/github.md) for quick GitHub integration. - -For custom , use the `GitHub()` auth class. The `context.authorization.token` field will be automatically populated: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import GitHub - -@tool(requires_auth=GitHub()) -async def count_stargazers( - context: ToolContext, - owner: Annotated[str, "The owner of the repository"], - name: Annotated[str, "The name of the repository"], -) -> Annotated[int, "The number of stargazers (stars) for the specified repository"]: - """Count the number of stargazers (stars) for a GitHub repository.""" - if not context.authorization or not context.authorization.token: - raise ValueError("No token found in context") - - headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {context.authorization.token}", - "X-GitHub-Api-Version": "2022-11-28", - } - url = f"https://api.github.com/repos/{owner}/{name}" - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return response.json().get("stargazers_count", 0) -``` - -* * * - -## Tool Permissions Reference - -This section lists the GitHub App permissions required by tools in the Arcade GitHub Server. - -### Summary Table - -Tool Category - -Required Permissions - -**Issues** - -Contents (Read), Issues (Read/Write), Metadata (Read) - -**Pull Requests** - -Contents (Read), Pull requests (Read/Write), Metadata (Read) - -**Projects** - -Contents (Read), Metadata (Read), Projects (Read/Write), Members (Read) - -**Repositories** - -Contents (Read/Write for file ops), Metadata (Read), Members (Read for org repos) - -**Reviews** - -Contents (Read), Pull requests (Read/Write), Metadata (Read) - -**User Context** - -Read user profile, Members (Read for orgs) - -**Notifications** - -⚠️ Classic PAT with `notifications` scope (GitHub Apps limitation) - -### Detailed Permissions by Category - -### Issues - -**Issues ** (7 tools): - -- `create_issue`, `update_issue`, `create_issue_comment`, `list_issues`, `get_issue`, `list_repository_labels`, `manage_labels` (for issues) - -**Required:** - -- Repository: Contents (Read), Issues (Read/Write), Metadata (Read) -- Organization: (Read/Write) - only for `create_issue` when adding to org project - -### Pull Requests - -**Pull Request ** (16 tools): - -**Read-only :** - -- `list_pull_requests`, `get_pull_request`, `list_pull_request_commits`, `list_review_comments_on_pull_request` -- Required: Contents (Read), Pull requests (Read), Metadata (Read) - -**Write :** - -- `update_pull_request`, `create_pull_request`, `merge_pull_request`, `submit_pull_request_review`, `manage_pull_request`, `manage_pull_request_reviewers`, `create_reply_for_review_comment`, `create_review_comment`, `resolve_review_thread`, `manage_labels` (for pull requests) -- Required: Contents (Read), Pull requests (Write), Metadata (Read) -- `merge_pull_request` also needs: Contents (Read & Write) - -**Special:** - -- `assign_pull_request_user`: Needs Issues (Write) + Members (Read) -- `check_pull_request_merge_status`: Needs Statuses (Read) - -### Projects - - (5 tools): - -- `list_projects`, `list_project_items`, `search_project_item`, `list_project_fields`, `update_project_item` - -**Required:** - -- Repository: Contents (Read), Metadata (Read) -- Organization: (Read/Write), Members (Read) - -### Repositories - -**Repository ** (11 tools): - -**Basic (Read-only):** - -- `count_stargazers`, `get_repository`, `list_repository_activities`, `list_stargazers`, `get_file_contents` -- Required: Contents (Read), Metadata (Read) - -**Write Operations:** - -- `create_branch`, `create_or_update_file`, `update_file_lines` -- Required: Contents (Read & Write), Metadata (Read) - -** Actions:** - -- `set_starred` -- Required: Contents (Read), Metadata (Read), “Act on behalf of ” permission - -**Organization:** - -- `list_org_repositories`, `search_my_repos`, `list_repository_collaborators` -- Required: Contents (Read), Metadata (Read), Members (Read) - -**Reviews:** - -- `list_review_comments_in_a_repository` -- Required: Contents (Read), Pull requests (Read), Metadata (Read) - -### User Context - -**User ** (4 tools): - -- `who_am_i`: Members (Read), Read profile -- `get_user_recent_activity`, `get_user_open_items`: Contents (Read), Metadata (Read), Read profile -- `get_review_workload`: Contents (Read), Pull requests (Read), Metadata (Read), Read profile - -### Special Cases - -**⚠️ Notifications ** - -GitHub Apps **cannot** access the notifications API. This is a [platform limitation by design](https://docs.github.com/en/rest/activity/notifications) . - -**Workaround:** - -- : `get_notification_summary`, `list_notifications` -- **Required**: Classic Personal Access Token with `notifications` scope -- **Create at**: [github.com/settings/tokens](https://github.com/settings/tokens) -   -- **Why**: Notifications are personal data, not accessible to apps by GitHub’s design - -**\-on-Behalf-of Actions:** - -The “Act on behalf of ” permission is only needed for specific user-attributed actions: - -- **When needed**: Starring repositories, actions appearing in ’s activity feed -- **Not needed for**: Reading data, app-attributed actions, organization operations -- Enable in app settings: ** permissions** → “Act on behalf of user” -- These actions act as the (with user’s avatar/name), not as the app -- must authorize these permissions during their first use -- [Learn more about user-to-server authentication](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-with-a-github-app-on-behalf-of-a-user) -   - -* * * - -## Important Gotchas - -These are common issues encounter. Read this section to avoid frustration! - -### Permission Changes Require User Reauthorization - -When you modify permissions in your GitHub App settings, existing authorizations don’t automatically receive the new permissions. - -**What Happens:** - -1. You update permissions in GitHub App settings -2. Organization admins may need to approve the changes -3. Existing tokens still have old permissions -4. must reauthorize to get new permissions - -**Solution:** - -- should revoke their authorization in Arcade and reauthorize -- Or, admins can revoke all authorizations in GitHub App settings -- New will automatically get the updated permissions - -[Learn more about permission updates](https://docs.github.com/en/apps/using-github-apps/approving-updated-permissions-for-a-github-app) -  - -### 404 Errors Can Mean Missing Permissions or Installation - -GitHub returns `404 Not Found` (not 403 Forbidden) in several scenarios: - -**Possible Causes:** - -1. App not installed on the target repository -2. Permissions not approved by organization admin -3. Repository doesn’t exist or you lack access -4. hasn’t authorized the app yet - -**Why 404 Instead of 403?** GitHub doesn’t reveal which repositories exist when you lack access (security by obscurity). - -**Solution:** - -1. Verify the app is installed on the target repository -2. Check that all required permissions are approved -3. Ensure the has authorized the app -4. Confirm the repository path is correct - -### Installing on Additional Repositories - -After initial installation, you can add more repositories: - -**Via GitHub Settings:** - -1. Go to Settings → Applications → Installed GitHub Apps -2. Click on your app name -3. Click **Configure** -4. Under “Repository access”, click **Select repositories** -5. Choose additional repositories -6. Click **Save** - -[Official guide for managing installations](https://docs.github.com/en/apps/using-github-apps/reviewing-your-authorized-integrations) -  - -### Permissions ≠ Scopes - -- GitHub Apps use **permissions** (fine-grained), not **scopes** (OAuth Apps) -- Scopes aren’t present in GitHub App tokens -- is still limited by their own GitHub permissions (can’t elevate privileges) - -### Personal vs Organization GitHub Apps - -**Functionally the same**, but: - -- **Personal app**: You own and manage it -- **Organization app**: Org owns it, can be managed by org admins -- Both can be installed on any if “Any account” is selected - -* * * - -## Troubleshooting - -### Common Errors - -Error - -Cause - -Solution - -**”Resource not accessible by integration”** - -Missing permissions or not installed on repo - -Verify permissions and installation - -**”Installation not found”** - -Wrong Installation ID - -Check Installation ID in URL - -**”403 Forbidden”** - -Missing permissions, not installed, or rate limited - -Review permissions, check installation - -**”404 Not Found” on existing repo** - -Permissions not approved by admin - -Check app settings → verify permissions approved - -**Notifications not working** - -GitHub Apps can’t access notifications - -Use classic PAT with `notifications` scope - -**Can’t star repositories** - -Missing “Act on behalf of user” permission - -Enable in User permissions settings - -### Debugging Steps - -1. **Verify Installation** - - - Go to Settings → Applications → Installed GitHub Apps - - Confirm app is installed on target repository - - [Review your installations](https://docs.github.com/en/apps/using-github-apps/reviewing-your-authorized-integrations) -   -2. **Check Authorization** - - - Verify the has authorized the app - - can check at: Settings → Applications → Authorized GitHub Apps - - If not authorized, user needs to use your /app to trigger authorization flow -3. **Check Permissions** - - - Review app settings → Permissions & events - - Ensure all required permissions are granted - - Verify organization approval (if applicable) - - **After changing permissions**: must reauthorize -4. **Test Credentials** - - - Verify Client ID and Client Secret are correct in Arcade dashboard - - Ensure redirect URL in GitHub matches Arcade’s generated URL - - Check that “Request authorization (OAuth) during installation” is enabled - -* * * - -## Frequently Asked Questions - -### General - -**Q: Is GitHub Apps free?** A: Yes! Free GitHub support GitHub Apps. No paid plan needed. - -**Q: Can I use one app for multiple ?** A: Yes! Install the same app on multiple repositories. Each gets a unique Installation ID. - -**Q: What if I lose my private key?** A: Generate a new one from app settings. The old key will be invalidated. You cannot recover lost keys. - -### Installation - -**Q: Do I need to be an org owner?** A: To **create** an app: No (any can create). To **install** on org: Yes (or need owner approval). Organization owners control which apps can be installed. [Learn more](https://docs.github.com/en/apps/using-github-apps/installing-a-github-app-from-a-third-party)  - -**Q: How do I install the app on additional repositories?** A: Settings → Applications → Installed GitHub Apps → Click your app → Configure → Select additional repositories. - -**Q: Can I change permissions later?** A: Yes, anytime. After changing: - -- Organization installs require admin approval for new permissions -- ** must reauthorize** for new permissions to take effect -- Best practice: should revoke old authorization and reauthorize -- [Guide to updating permissions](https://docs.github.com/en/apps/using-github-apps/approving-updated-permissions-for-a-github-app) -   - -### Usage - -**Q: Which need org permissions?** A: Project management, collaborators listing, org repo listing, and user search within org . - -**Q: What’s the rate limit?** A: GitHub Apps: 5,000/hour per installation + 12,500/hour for endpoints. OAuth: 5,000/hour total. - -**Q: Can I use with GitHub Enterprise Server?** A: Yes! See the [GitHub Enterprise Server Setup](#github-enterprise-server-setup) section above. You’ll need to create a custom provider with your GHES instance URLs. - -**Q: Why can’t I star repositories?** A: Starring repositories requires the “Act on behalf of ” permission: - -- Enable “Act on behalf of ” in User permissions section -- This permission makes actions appear as the (not the app) -- Users must authorize this permission when they first use your -- The app can still read and perform other actions without this permission -- [Why this permission exists](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-with-a-github-app-on-behalf-of-a-user) -   - -**Q: Why can’t I access notifications?** A: Notifications require a classic PAT (GitHub Apps cannot access notifications API by design): - -- This is a platform limitation, not an Arcade limitation -- Create a classic PAT at [github.com/settings/tokens](https://github.com/settings/tokens) -   with `notifications` scope -- [Why GitHub Apps can’t access notifications](https://docs.github.com/en/rest/activity/notifications) -   - -### Security - -**Q: What if my credentials are compromised?** A: Immediately take these steps: - -1. Revoke the Client Secret in GitHub App settings -2. Generate a new Client Secret -3. Update your Arcade OAuth provider configuration -4. Review GitHub audit logs for suspicious activity -5. Consider revoking all authorizations -6. Notify affected if necessary - -[GitHub security best practices](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/best-practices-for-creating-a-github-app) -  - -**Q: Why can’t the app access all my repositories?** A: Security by design. Apps only access repositories they’re explicitly installed on. This limits blast radius if compromised. - -### GitHub Enterprise Server - -**Q: Can I use the same GitHub App for github.com and GHES?** A: No. GitHub Apps created on github.com cannot be installed on GHES instances, and vice versa. Each GHES instance requires its own separate GitHub App registration. This is a GitHub platform limitation, not an Arcade limitation. - -**Q: Can I use the same app configuration for multiple GHES instances?** A: Yes! You can share the same manifest or URL parameters with multiple GHES instances. Each instance will register their own GitHub App with identical settings, but you’ll need to create separate custom providers in Arcade for each instance. - -**Q: Does GHES support all the same features as github.com?** A: Not always. GHES may have feature differences and typically receives new API endpoints, GraphQL objects, and webhooks later than github.com. Check the `x-github-enterprise-version` header in API responses to identify the GHES version. [Learn more about GHES version differences](https://docs.github.com/en/enterprise-server/admin/overview/about-upgrades-to-new-releases)  - -**Q: How do I know what GHES version my instance is running?** A: Contact your GHES administrator, or check the API response headers which include `x-github-enterprise-version`. You can also see the version in the GHES footer when logged in. - -**Q: Can I use the “Included Provider” for GHES?** A: No. The included GitHub provider in Arcade is configured for github.com only. You must create a custom provider with your GHES instance URLs. See the [GHES setup guide](#github-enterprise-server-setup) above. - -**Q: Do GHES rate limits work the same as github.com?** A: No. GHES administrators can configure custom rate limits for their instance. If you’re hitting rate limits, contact your GHES admin about the configured limits and request an increase if needed. - -* * * - -## Configuration Checklist - -Use this checklist to ensure proper setup: - -- Created GitHub App (not OAuth App) -- Set all required permissions (Contents, Issues, Pull requests, etc.) -- **Enabled** “Request authorization (OAuth) during installation” ✅ -- Enabled “Act on behalf of ” for user-level actions -- Generated and securely stored Client Secret -- Noted Client ID -- Installed app on target repositories (or made public for any ) -- Added Arcade redirect URL to GitHub app callback URL -- Configured OAuth provider in Arcade dashboard -- Tested with a simple call -- Generated private key (Not needed for Arcade) -- Noted Installation ID (Not needed for Arcade) - -### For GitHub Enterprise Server: - -- Created Custom Provider in Arcade (NOT included provider) -- Configured all endpoint URLs with correct GHES hostname -- Set Authorization endpoint: `https://{your-ghes-host}/login/oauth/authorize` -- Set Token endpoint: `https://{your-ghes-host}/login/oauth/access_token` -- Set Info endpoint: `https://{your-ghes-host}/api/v3/user` -- Set Refresh Token endpoint (same as Token endpoint) -- Enabled Token Introspection with correct settings: - - Introspection endpoint: `https://{your-ghes-host}/api/v3/user` - - Authentication Method: Bearer Access Token - - Content-Type: application/json - - Triggers: OnTokenGrant and OnTokenRefresh enabled -- Tested with GHES-specific provider ID in auth calls -- Verified GHES version compatibility with required features - -* * * - -## Additional Resources - -### Official GitHub Documentation - -- [GitHub Apps Overview](https://docs.github.com/en/apps) -   -- [Installing Your Own GitHub App](https://docs.github.com/en/apps/using-github-apps/installing-your-own-github-app) -   -- [Installing a GitHub App from a Third Party](https://docs.github.com/en/apps/using-github-apps/installing-a-github-app-from-a-third-party) -   -- [Authorizing GitHub Apps](https://docs.github.com/en/apps/using-github-apps/authorizing-github-apps) -   -- [Reviewing and Revoking Authorization](https://docs.github.com/en/apps/using-github-apps/reviewing-and-revoking-authorization-of-github-apps) -   -- [Approving Updated Permissions](https://docs.github.com/en/apps/using-github-apps/approving-updated-permissions-for-a-github-app) -   -- [GitHub API Documentation](https://docs.github.com/en/rest) -   -- [Security Best Practices](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/best-practices-for-creating-a-github-app) -   - -### Arcade Resources - -- [Arcade GitHub MCP Server](/resources/integrations/development/github.md) - -- [Arcade Authorization Guide](/get-started/about-arcade.md) - -- [Video Tutorial: GitHub App Setup](https://www.youtube.com/watch?v=KcO2SruCdt0) -   - -* * * - -_💡 Remember: Always use GitHub Apps (not OAuth Apps), enable authorization during installation, and store credentials securely!_ - -Last updated on January 30, 2026 - -[Figma](/en/references/auth-providers/figma.md) -[Google](/en/references/auth-providers/google.md) diff --git a/public/_markdown/en/references/auth-providers/google.md b/public/_markdown/en/references/auth-providers/google.md deleted file mode 100644 index 5cec815ae..000000000 --- a/public/_markdown/en/references/auth-providers/google.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -title: "Google" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Google - -# Google - -The Google enables tools and to call Google/Google Workspace APIs on behalf of a . - -Want to quickly get started with Google services in your or AI app? The pre-built [Arcade Gmail MCP Server](/resources/integrations/productivity/gmail.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Google auth with Arcade. - -This is used by: - -- The [Arcade Gmail MCP Server](/resources/integrations/productivity/gmail.md) - , which provides pre-built for interacting with Google services -- Your [app code](#using-google-auth-in-app-code) - that needs to call Google APIs -- Or, your [custom tools](#using-google-auth-in-custom-tools) - that need to call Google APIs - -## Configuring Google auth - -You can either use Arcade’s default Google OAuth provider (fastest way to get started), or configure your own Google OAuth provider for production. - -## Arcade’s default Google OAuth provider - -Arcade provides a default Google OAuth provider (the Arcade-managed Google app) that you can use to quickly get started. This provider supports a fixed set of scopes and is shared across all Arcade . - -### Supported scopes - -The default Arcade Google OAuth provider supports the following scopes: - -- `https://www.googleapis.com/auth/calendar.readonly` -- `https://www.googleapis.com/auth/calendar.events` -- `https://www.googleapis.com/auth/calendar.events.readonly` -- `https://www.googleapis.com/auth/calendar.settings.readonly` -- `https://www.googleapis.com/auth/contacts` -- `https://www.googleapis.com/auth/contacts.readonly` -- `https://www.googleapis.com/auth/drive.file` -- `https://www.googleapis.com/auth/gmail.readonly` -- `https://www.googleapis.com/auth/gmail.compose` -- `https://www.googleapis.com/auth/gmail.send` -- `https://www.googleapis.com/auth/gmail.modify` -- `https://www.googleapis.com/auth/gmail.labels` -- `https://www.googleapis.com/auth/userinfo.email` -- `https://www.googleapis.com/auth/userinfo.profile` -- `openid` - -If you try to use a scope that is not in this list with the default Arcade Google provider, you will get a `400 invalid authorization challenge: requesting unsupported scopes` error. For example, scopes like `https://www.googleapis.com/auth/drive.readonly` are not supported. - -To use scopes beyond this list, you must create your own Google OAuth provider (see below). - -### Limitations - -The default provider has some limitations: - -- **Fixed scope list**: You can only use the scopes listed above -- **Shared rate limits**: All Arcade share the same rate limits -- **Arcade branding**: Your will see “Arcade” as the requesting application - -For production use, we strongly recommend creating your own Google OAuth provider. This gives you: - -- Full control over which scopes to request -- Dedicated rate limits for your application -- Your own branding in the OAuth consent screen -- Better security and compliance with your organization’s policies - -## Configuring your own Google OAuth provider - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Google app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Google app credentials, let’s go through the steps to create a Google app. - -### Create a Google app - -- Follow Google’s guide to [setting up OAuth credentials](https://support.google.com/cloud/answer/6158849?hl=en)  - -- Choose the [scopes](https://developers.google.com/identity/protocols/oauth2/scopes)  (permissions) you need for your app - -- At a minimum, you must enable these scopes: - - - `https://www.googleapis.com/auth/userinfo.email` - - `https://www.googleapis.com/auth/userinfo.profile` -- Add the redirect URL generated by Arcade (see below) to the Authorized redirect URIs list - -- Copy the client ID and client secret to use below - - -Next, add the Google app to Arcade. - -### Setting up your Google OAuth provider in Arcade - -### Dashboard GUI - -### Configure Google Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Google**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-google-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Google app. -- Note the **Redirect URL** generated by Arcade. This must be added to your Google app’s Authorized redirect URIs list. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Google auth using your Arcade credentials, Arcade will automatically use this Google OAuth provider. If you have multiple Google providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Google auth in app code - -Use the Google in your own and AI apps to get a token for Google APIs. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for Google APIs: - -### Python - -```python -from arcadepy import Arcade -from google.oauth2.credentials import Credentials -from googleapiclient.discovery import build - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -""" -In this example, we will use Arcade to authenticate with Google and -retrieve Gmail messages. - -There is a tool for that in the Arcade SDK, which simplifies the process for -you to retrieve email messages either through our Python or JavaScript -SDKs or via LLM tool calling. - -Below we are just showing how to use Arcade as an auth provider, if you ever -need to. -""" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="google", - scopes=["https://www.googleapis.com/auth/gmail.readonly"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token - -if not token: - raise ValueError("No token found in auth response") - -credentials = Credentials(token) -gmail = build("gmail", "v1", credentials=credentials) - -email_messages = ( - gmail.users().messages().list(userId="me").execute().get("messages", []) -) - -print(email_messages) -``` - -### JavaScript - -```json -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -/* -In this example, we will use Arcade to authenticate with Google and -retrieve Gmail messages. - -There is a tool for that in the Arcade SDK, which simplifies the process for -you to retrieve email messages either through our Python or JavaScript -SDKs or via LLM tool calling. - -Below we are just showing how to use Arcade as an auth provider, if you ever -need to. -*/ - -// Start the authorization process -let authResponse = await client.auth.start(userId, "google", { - scopes: ["https://www.googleapis.com/auth/gmail.readonly"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -if (!authResponse.context.token) { - throw new Error("No token found in auth response"); -} - -const token = authResponse.context.token; - -// Use the Google Gmail API -const response = await fetch( - "https://gmail.googleapis.com/gmail/v1/users/me/messages", - { - headers: { - Authorization: `Bearer ${token}`, - }, - } -); - -if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); -} - -const data = await response.json(); -const emailMessages = data.messages || []; - -// Return a list of ids and thread ids -console.log(emailMessages); -``` - -## Using Google auth in custom tools - -You can use the pre-built Arcade Google Servers, like [Arcade Gmail MCP Server](/resources/integrations/productivity/gmail.md), to quickly build and AI apps that interact with Google services like Gmail, Calendar, Drive, and more. - -If the pre-built tools in the Google Servers don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with Google APIs. - -Use the `Google()` auth class to specify that a requires authorization with Google. The `context.authorization.token` field will be automatically populated with the ’s Google token: - -```python -from typing import Annotated - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Google - -from google.oauth2.credentials import Credentials -from googleapiclient.discovery import build - - -@tool( - requires_auth=Google( - scopes=["https://www.googleapis.com/auth/gmail.readonly"], - ) -) -async def list_emails( - context: ToolContext, - subject: Annotated[str, "The subject of the email"], - body: Annotated[str, "The body of the email"], - recipient: Annotated[str, "The recipient of the email"], -) -> Annotated[str, "A confirmation message with the sent email ID and URL"]: - """ - Send an email using the Gmail API. - """ - if not context.authorization or not context.authorization.token: - raise ValueError("No token found in context") - - credentials = Credentials(context.authorization.token) - gmail = build("gmail", "v1", credentials=credentials) - - email_messages = ( - gmail.users().messages().list(userId="me").execute().get("messages", []) - ) - - return email_messages -``` - -Last updated on January 30, 2026 - -[GitHub](/en/references/auth-providers/github.md) -[Hubspot](/en/references/auth-providers/hubspot.md) diff --git a/public/_markdown/en/references/auth-providers/hubspot.md b/public/_markdown/en/references/auth-providers/hubspot.md deleted file mode 100644 index 57ff9fd6f..000000000 --- a/public/_markdown/en/references/auth-providers/hubspot.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -title: "Hubspot" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Hubspot - -# Hubspot - -The Hubspot enables tools and to call Hubspot APIs on behalf of a . - -Want to quickly get started with Hubspot services in your or AI app? The pre-built [Arcade Hubspot MCP Server](/resources/integrations/sales/hubspot.md) is what you want! - -## What’s documented here - -This page describes how to use and configure Hubspot auth with Arcade. - -This is used by: - -- The [Arcade Hubspot MCP Server](/resources/integrations/sales/hubspot.md) - , which provides pre-built for interacting with Hubspot -- Your [app code](#using-hubspot-auth-in-app-code) - that needs to call Hubspot APIs -- Or, your [custom tools](#using-hubspot-auth-in-custom-tools) - that need to call Hubspot APIs - -## Use Arcade’s Default Hubspot Auth Provider - -Arcade offers a default Hubspot that you can use in the Arcade Cloud Platform. In this case, your will see `Arcade` as the name of the application that’s requesting permission. - -If you choose to use Arcade’s Hubspot auth, you don’t need to configure anything. Follow the [Hubspot MCP Server examples](/resources/integrations/sales/hubspot.md) to get started calling Hubspot . - -## Use Your Own Hubspot App Credentials - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Hubspot app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Hubspot app credentials, let’s go through the steps to create a Hubspot app. - -## Create a Hubspot App - -Hubspot has two types of apps: Public and Private. You will need to create a Public one. - -Follow [Hubspot’s tutorial](https://developers.hubspot.com/docs/guides/apps/public-apps/overview)  to create your Public App. You will need a [developer account](https://app.hubspot.com/signup-hubspot/developers)  to do this. - -When creating your app, use the following settings: - -- Under **App Info**, choose a name, description, and logo for your app -- Under **Auth**, enter the **Redirect URL** generated by Arcade (see below) -- In the **Scopes** section, click **Add Scope** and add the following scopes with the **Conditionally Required** scope type: - - `crm.objects.companies.read` - - `crm.objects.contacts.read` - - `crm.objects.contacts.write` - - `crm.objects.deals.read` - - `sales-email-read` - -Create the app and take note of the **Client ID** and **Client Secret**. You don’t need to follow Hubspot’s instructions to install the app. - -If you are implementing your own [Hubspot custom tools](#using-hubspot-auth-in-custom-tools), make sure to also add [any extra scopes](https://developers.hubspot.com/docs/guides/apps/authentication/scopes)  necessary for the actions your need to perform. All extra scopes must be added to the app as `Conditionally Required` or `Optional`, never as `Required`, otherwise the Arcade Hubspot Server will not work. Read more about [Hubspot scope types](https://developers.hubspot.com/changelog/advanced-auth-and-scope-settings-for-public-apps) . - -## Configuring your own Hubspot Auth Provider in Arcade - -### Dashboard GUI - -### Configure Hubspot Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Hubspot**. - -#### Enter the provider details - -- Enter `hubspot` as the **ID** for your provider -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Hubspot app. -- Note the **Redirect URL** generated by Arcade. This must be set as your Hubspot app’s Redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Hubspot auth using your Arcade credentials, Arcade will automatically use this Hubspot OAuth provider. If you have multiple Hubspot providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using the Arcade Hubspot MCP Servers - -The [Arcade Hubspot MCP Server](/resources/integrations/sales/hubspot.md) provides to interact with various Hubspot objects, such as companies, contacts, deals, and email messages. - -Refer to the [MCP Server documentation and examples](/resources/integrations/sales/hubspot.md) to learn how to use the Server to build and AI apps that interact with Hubspot services. - -## Using Hubspot auth in app code - -Use the Hubspot in your own and AI apps to get a \-scoped token for the Hubspot API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Hubspot API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="hubspot", - scopes=["oauth", "crm.objects.companies.read"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "hubspot", { - scopes: ["oauth", "crm.objects.companies.read"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -const response = await client.auth.waitForCompletion(authResponse); - -const token = response.context.token; -// Do something interesting with the token... -``` - -You can use the auth token to call [Hubspot Companies endpoints](https://developers.hubspot.com/docs/guides/api/crm/objects/companies)  and read information about companies. By changing or adding scopes to the `client.auth.start` call, you can call other Hubspot endpoints. - -The scopes supported by the Arcade Hubspot are the ones [listed above](#create-a-hubspot-app). If you created your own Hubspot app, make sure to add the scopes you intend to use in the `client.auth.start` call. - -## Using Hubspot auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Hubspot API. - -Use the `Hubspot()` auth class to specify that a tool requires authorization with Hubspot. The authentication token needed to call the Hubspot API is available in the through the `context.get_auth_token_or_empty()` method. - -```python -from typing import Annotated - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Hubspot - - -@tool( - requires_auth=Hubspot( - scopes=["oauth", "crm.objects.companies.read"], - ) -) -async def get_company_details( - context: ToolContext, - company_id: Annotated[ - str, - "The ID of the company to get the details of.", - ], -) -> Annotated[dict, "Details of the company"]: - """Gets the details of a company.""" - url = f"https://api.hubapi.com/crm//v3/objects/companies/{company_id}" - headers = {"Authorization": f"Bearer {context.get_auth_token_or_empty()}"} - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return response.json() -``` - -Your new can be called like demonstrated below: - -### Python - -If you are self-hosting, change the `base_url` parameter in the `Arcade` constructor to match your URL. By default, the Engine will be available at `http://localhost:9099`. - -```python -from arcadepy import Arcade - -client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable - -USER_ID = "{arcade_user_id}" -TOOL_NAME = "Hubspot.GetCompanyDetails" - -auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) - -if auth_response.status != "completed": - print(f"Click this link to authorize: {auth_response.url}") - -# Wait for the authorization to complete -client.auth.wait_for_completion(auth_response) - -tool_input = { - "company_id": "32134490789", -} - -response = client.tools.execute( - tool_name=TOOL_NAME, - input=tool_input, - user_id=USER_ID, -) -print(response.output.value) -``` - -### JavaScript - -If you are self-hosting, change the `baseURL` parameter in the `Arcade` constructor to match your URL. By default, the Engine will be available at `http://localhost:9099`. - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable - -const USER_ID = "{arcade_user_id}"; -const TOOL_NAME = "Hubspot.GetCompanyDetails"; - -// Start the authorization process -const authResponse = await client.tools.authorize({ - tool_name: TOOL_NAME, - user_id: USER_ID, -}); - -if (authResponse.status !== "completed") { - console.log(`Click this link to authorize: ${authResponse.url}`); -} - -// Wait for the authorization to complete -await client.auth.waitForCompletion(authResponse); - -const toolInput = { - company_id: "1234567890", -}; - -const response = await client.tools.execute({ - tool_name: TOOL_NAME, - input: toolInput, - user_id: USER_ID, -}); - -console.log(response.output.value); -``` - -Last updated on January 30, 2026 - -[Google](/en/references/auth-providers/google.md) -[Linear](/en/references/auth-providers/linear.md) diff --git a/public/_markdown/en/references/auth-providers/linear.md b/public/_markdown/en/references/auth-providers/linear.md deleted file mode 100644 index a440d1a1e..000000000 --- a/public/_markdown/en/references/auth-providers/linear.md +++ /dev/null @@ -1,280 +0,0 @@ ---- -title: "Linear" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Linear - -# Linear - -The Linear enables tools and to call [Linear APIs](https://linear.app/developers/graphql)  on behalf of a . - -Want to quickly get started with Linear in your or AI app? The pre-built [Arcade Linear MCP Server](/resources/integrations/productivity/linear.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Linear auth with Arcade. - -This is used by: - -- The [Arcade Linear MCP Server](/resources/integrations/productivity/linear.md) - , which provides pre-built for interacting with Linear -- Your [app code](#using-linear-auth-in-app-code) - that needs to call Linear APIs -- Or, your [custom tools](#using-linear-auth-in-custom-tools) - that need to call Linear APIs - -## Configuring Linear auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Linear app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Linear app credentials, let’s go through the steps to create a Linear app. - -### Create a Linear app - -- It is **highly recommended** to first [create a new Linear workspace](https://linear.app/join) -   for the purpose of managing the OAuth2 Application, as each admin will have access -- Create a new public OAuth2 Application in your [integration settings page](https://linear.app/settings/api/applications/new) -   -- Fill out your application specific information such as application name and description -- Choose the [scopes](https://linear.app/developers/oauth-2-0-authentication#redirect-user-access-requests-to-linear) -   (permissions) you need for your app -- Add the redirect URL generated by Arcade (see below) to the Callback URL field -- Toggle the **Public** switch if you want other workspaces to be able to use your application -- Copy the client ID and client secret to use below - -Next, add the Linear app to Arcade. - -## Configuring your own Linear Auth Provider in Arcade - -### Dashboard GUI - -### Configure Linear Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Linear**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-linear-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Linear app. -- Note the **Redirect URL** generated by Arcade. This must be added to your Linear app’s Callback URL field. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Linear auth using your Arcade credentials, Arcade will automatically use this Linear OAuth provider. If you have multiple Linear providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Linear auth in app code - -Use the Linear in your own and AI apps to get a token for Linear APIs. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for Linear APIs: - -### Python - -```json -from arcadepy import Arcade -import httpx - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -""" -In this example, we will use Arcade to authenticate with Linear and -retrieve teams. - -There is a tool for that in the Arcade Linear MCP Server, which simplifies -the process for you to interact with Linear either through our Python or -JavaScript SDKs or via LLM tool calling. - -Below we are just showing how to use Arcade as an auth provider, if you ever -need to. -""" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="linear", - scopes=["read"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token - -if not token: - raise ValueError("No token found in auth response") - -# Use the Linear GraphQL API -url = "https://api.linear.app/graphql" -headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", -} - -query = """ -query Teams { - teams { - nodes { - id - name - key - } - } -} -""" - -response = httpx.post(url, json={"query": query}, headers=headers) -data = response.json() -teams = data["data"]["teams"]["nodes"] - -print(teams) -``` - -### JavaScript - -```json -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -/* -In this example, we will use Arcade to authenticate with Linear and -retrieve teams. - -There is a tool for that in the Arcade Linear MCP Server, which simplifies -the process for you to interact with Linear either through our Python or -JavaScript SDKs or via LLM tool calling. - -Below we are just showing how to use Arcade as an auth provider, if you ever -need to. -*/ - -// Start the authorization process -let authResponse = await client.auth.start(userId, "linear", { - scopes: ["read"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -if (!authResponse.context.token) { - throw new Error("No token found in auth response"); -} - -const token = authResponse.context.token; - -// Use the Linear GraphQL API -const response = await fetch("https://api.linear.app/graphql", { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: ` - query Teams { - teams { - nodes { - id - name - key - } - } - } - `, - }), -}); - -if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); -} - -const data = await response.json(); -const teams = data.data.teams.nodes; - -console.log(teams); -``` - -## Using Linear auth in custom tools - -You can use the pre-built [Arcade Linear MCP Server](/resources/integrations/productivity/linear.md) to quickly build and AI apps that interact with Linear. - -If the pre-built tools in the Linear Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Linear API. - -Use the `Linear()` auth class to specify that a requires authorization with Linear. The `context.authorization.token` field will be automatically populated with the ’s Linear token: - -```json -from typing import Annotated, Any - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Linear - -import httpx - - -@tool(requires_auth=Linear(scopes=["read"])) -async def get_teams( - context: ToolContext, -) -> Annotated[dict[str, Any], "Teams in the workspace with member information"]: - """Get Linear teams and team information including team members""" - if not context.authorization or not context.authorization.token: - raise ValueError("No token found in context") - - token = context.authorization.token - url = "https://api.linear.app/graphql" - headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - } - - query = """ - query Teams { - teams { - nodes { - id - name - key - } - } - } - """ - - async with httpx.AsyncClient() as client: - resp = await client.post(url, json={"query": query}, headers=headers) - resp.raise_for_status() - data = resp.json() - teams = data["data"]["teams"]["nodes"] - return teams -``` - -Last updated on January 30, 2026 - -[Hubspot](/en/references/auth-providers/hubspot.md) -[LinkedIn](/en/references/auth-providers/linkedin.md) diff --git a/public/_markdown/en/references/auth-providers/linkedin.md b/public/_markdown/en/references/auth-providers/linkedin.md deleted file mode 100644 index d045caf73..000000000 --- a/public/_markdown/en/references/auth-providers/linkedin.md +++ /dev/null @@ -1,270 +0,0 @@ ---- -title: "LinkedIn" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -LinkedIn - -# LinkedIn - -The LinkedIn enables tools and to call the LinkedIn API on behalf of a . - -### What’s documented here - -This page describes how to use and configure LinkedIn auth with Arcade. - -This is used by: - -- Your [app code](#using-linkedin-auth-in-app-code) - that needs to call LinkedIn APIs -- Or, your [custom tools](#using-linkedin-auth-in-custom-tools) - that need to call LinkedIn APIs - -## Configuring LinkedIn auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own LinkedIn app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your LinkedIn app credentials, let’s go through the steps to create a LinkedIn app. - -### Create a LinkedIn app - -- Follow LinkedIn’s guide to [setting up user authorization](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authentication) -   -- On the Products tab, add the products you need for your app (e.g. “Share on LinkedIn”) - - At a minimum, you _must_ add the “Sign In with LinkedIn using OpenID Connect” product -- On the Auth tab, set the redirect URL to the redirect URL generated by Arcade (see below) -- Copy the client ID and client secret to use below - -Next, add the LinkedIn app to Arcade. - -## Configuring your own LinkedIn Auth Provider in Arcade - -## Using LinkedIn auth in app code - -Use the LinkedIn in your own and AI apps to get a token for LinkedIn APIs. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for LinkedIn APIs: - -### Python - -```json -import requests - -from arcadepy import Arcade - - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -""" -In this example, we will use Arcade to authenticate with LinkedIn and post a -message to the user's LinkedIn feed. - -There is a tool for that in the Arcade SDK, which simplifies the process for -you to post messages to the user's LinkedIn feed either through our Python or -JavaScript SDKs or via LLM tool calling. - -Below we are just showing how to use Arcade as an auth provider, if you ever -need to. -""" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="linkedin", - scopes=["w_member_social"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -if not auth_response.context.token: - raise ValueError("No token found in auth response") - -token = auth_response.context.token - -user_id = ( - None - if not auth_response.context.authorization - else auth_response.context.authorization.user_info.get("sub") -) - -if not user_id: - raise ValueError("User ID not found.") - -# Prepare the payload data for the LinkedIn API -message = "Hello, from Arcade.dev!" -payload = { - "author": f"urn:li:person:{user_id}", - "lifecycleState": "PUBLISHED", - "specificContent": { - "com.linkedin.ugc.ShareContent": { - "shareCommentary": {"text": message}, - "shareMediaCategory": "NONE", - } - }, - "visibility": {"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"}, -} - -response = requests.post( - "https://api.linkedin.com/v2/ugcPosts", - headers={"Authorization": f"Bearer {token}"}, - json=payload, -) -response.raise_for_status() -print(response.json()) -``` - -### JavaScript - -```json -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -/* -In this example, we will use Arcade to authenticate with LinkedIn and post a -message to the user's LinkedIn feed. - -There is a tool for that in the Arcade SDK, which simplifies the process for -you to post messages to the user's LinkedIn feed either through our Python or -JavaScript SDKs or via LLM tool calling. - -Below we are just showing how to use Arcade as an auth provider, if you ever -need to. -*/ - -// Start the authorization process -let authResponse = await client.auth.start(userId, "linkedin", { - scopes: ["w_member_social"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -if (!authResponse.context.token) { - throw new Error("No token found in auth response"); -} - -const token = authResponse.context.token; - -const linkedInUserId = authResponse.context.authorization?.user_info?.sub; - -if (!linkedInUserId) { - throw new Error("User ID not found."); -} - -// Prepare the payload data for the LinkedIn API -const message = "Hello, from Arcade.dev!"; -const payload = { - author: `urn:li:person:${linkedInUserId}`, - lifecycleState: "PUBLISHED", - specificContent: { - "com.linkedin.ugc.ShareContent": { - shareCommentary: { text: message }, - shareMediaCategory: "NONE", - }, - }, - visibility: { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" }, -}; - -const response = await fetch("https://api.linkedin.com/v2/ugcPosts", { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), -}); - -if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); -} - -const data = await response.json(); -console.log(data); -``` - -## Using LinkedIn auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with LinkedIn APIs. - -Use the `LinkedIn()` auth class to specify that a requires authorization with LinkedIn. The `context.authorization.token` field will be automatically populated with the ’s LinkedIn token: - -```json -from typing import Annotated - -import httpx - -from arcade_tdk.errors import ToolExecutionError -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import LinkedIn - - -@tool( - requires_auth=LinkedIn( - scopes=["w_member_social"], - ) -) -async def create_text_post( - context: ToolContext, - text: Annotated[str, "The text content of the post"], -) -> Annotated[str, "URL of the shared post"]: - """Share a new text post to LinkedIn.""" - endpoint = "/ugcPosts" - - # The LinkedIn user ID is required to create a post, even though we're using the user's access token. - # Arcade Engine gets the current user's info from LinkedIn and automatically populates context.authorization.user_info. - # LinkedIn calls the user ID "sub" in their user_info data payload. See: - # https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2#api-request-to-retreive-member-details - user_id = context.authorization.user_info.get("sub") - if not user_id: - raise ToolExecutionError( - "User ID not found.", - developer_message="User ID not found in `context.authorization.user_info.sub`", - ) - - headers = {"Authorization": f"Bearer {context.authorization.token}"} - - author_id = f"urn:li:person:{user_id}" - payload = { - "author": author_id, - "lifecycleState": "PUBLISHED", - "specificContent": { - "com.linkedin.ugc.ShareContent": { - "shareCommentary": {"text": text}, - "shareMediaCategory": "NONE", - } - }, - "visibility": {"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"}, - } - - async with httpx.AsyncClient() as client: - response = await client.post( - url=f"https://api.linkedin.com/v2/{endpoint}", - headers=headers, - json=payload, - ) - response.raise_for_status() - share_id = response.json().get("id") - return f"https://www.linkedin.com/feed/update/{share_id}/" -``` - -Last updated on January 30, 2026 - -[Linear](/en/references/auth-providers/linear.md) -[Mailchimp](/en/references/auth-providers/mailchimp.md) diff --git a/public/_markdown/en/references/auth-providers/mailchimp.md b/public/_markdown/en/references/auth-providers/mailchimp.md deleted file mode 100644 index 3a469365f..000000000 --- a/public/_markdown/en/references/auth-providers/mailchimp.md +++ /dev/null @@ -1,298 +0,0 @@ ---- -title: "Mailchimp" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Mailchimp - -# Mailchimp - -The Mailchimp enables tools and to call [Mailchimp Marketing APIs](https://mailchimp.com/developer/marketing/api/)  on behalf of a using OAuth 2.0 authentication. - -Want to quickly get started with Mailchimp in your or AI app? The pre-built [Arcade Mailchimp Marketing MCP Server](/resources/integrations/productivity/mailchimp-api.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Mailchimp auth with Arcade. - -This is used by: - -- The [Arcade Mailchimp Marketing MCP Server](/resources/integrations/productivity/mailchimp-api.md) - , which provides pre-built for interacting with Mailchimp -- Your [app code](#using-mailchimp-auth-in-app-code) - that needs to call the Mailchimp API -- Or, your [custom tools](#using-mailchimp-auth-in-custom-tools) - that need to call the Mailchimp API - -## Configuring Mailchimp auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Mailchimp app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Mailchimp app credentials, let’s go through the steps to create a Mailchimp app. - -### Create a Mailchimp app - -To integrate with Mailchimp’s API, you’ll need to register an OAuth application: - -#### Log into your Mailchimp account - -1. Navigate to [mailchimp.com](https://mailchimp.com) -   and log in to your -2. Go to > **Extras** > **Registered Apps** -3. Alternatively, you can directly access the [Registered Apps page](https://admin.mailchimp.com/account/oauth2/) -   - -#### Register a new OAuth application - -1. Click on **Register an App** -2. Fill in the required details: - - **Application Name**: Choose a descriptive name for your application - - **Company/Organization**: Enter your company or organization name - - **Website URL**: Your application’s website URL - - **Description**: Brief description of your application - - **Redirect URI**: Add the redirect URL generated by Arcade (see configuration section below) - - This is the URL where Mailchimp will redirect after authorization - - For development, you can use `http://localhost:9099/oauth/callback` or your Arcade instance URL - -#### Save your credentials - -1. After registration, you’ll receive your **Client ID** and **Client Secret** -2. **Important**: Copy and save these credentials immediately in a secure location -3. You can always view your Client ID later, but the Client Secret should be stored securely - -For detailed instructions, refer to Mailchimp’s [OAuth 2.0 documentation](https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/)  and [API documentation](https://mailchimp.com/developer/marketing/api/) . - -Next, add the Mailchimp app to Arcade. - -## Configuring your own Mailchimp Auth Provider in Arcade - -### Dashboard GUI - -### Configure Mailchimp Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “arcade-mailchimp”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Mailchimp app. -- Configure the OAuth 2.0 endpoints: - - **Authorization URL**: `https://login.mailchimp.com/oauth2/authorize` - - **Token URL**: `https://login.mailchimp.com/oauth2/token` -- Note the **Redirect URL** generated by Arcade. This must be set as your Mailchimp app’s Redirect URI. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure Mailchimp Auth Using Configuration File - -This method is only available when you are \[self-hosting the engine\](/guides/deployment-hosting/on-prem - -#### Set environment variables - -Set the following environment variables: - -```bash -export MAILCHIMP_CLIENT_ID="" -export MAILCHIMP_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -MAILCHIMP_CLIENT_ID="" -MAILCHIMP_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: arcade-mailchimp - description: Mailchimp OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:MAILCHIMP_CLIENT_ID} - client_secret: ${env:MAILCHIMP_CLIENT_SECRET} - oauth2: - authorize_request: - endpoint: "https://login.mailchimp.com/oauth2/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - state: "{{state}}" - token_request: - endpoint: "https://login.mailchimp.com/oauth2/token" - params: - grant_type: authorization_code - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - redirect_uri: "{{redirect_uri}}" - response_content_type: application/json -``` - -When you use tools that require Mailchimp auth using your Arcade credentials, Arcade will automatically use this Mailchimp OAuth provider. If you have multiple Mailchimp providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Mailchimp auth in app code - -Use the Mailchimp in your own and AI apps to get a token for the Mailchimp API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Mailchimp API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="arcade-mailchimp" -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "arcade-mailchimp"); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -### Getting the API endpoint - -Mailchimp requires you to determine the correct API endpoint for each . After obtaining the access token, make a request to the metadata endpoint: - -### Python - -```python -import httpx - -# Get the user's API endpoint -async with httpx.AsyncClient() as client: - metadata_response = await client.get( - "https://login.mailchimp.com/oauth2/metadata", - headers={"Authorization": f"Bearer {token}"} - ) - metadata = metadata_response.json() - api_endpoint = metadata["api_endpoint"] - -# Now use the api_endpoint for all API calls -# Example: f"{api_endpoint}/3.0/lists" -``` - -### JavaScript - -```json -// Get the user's API endpoint -const metadataResponse = await fetch( - "https://login.mailchimp.com/oauth2/metadata", - { - headers: { Authorization: `Bearer ${token}` } - } -); -const metadata = await metadataResponse.json(); -const apiEndpoint = metadata.api_endpoint; - -// Now use the apiEndpoint for all API calls -// Example: `${apiEndpoint}/3.0/lists` -``` - -## Using Mailchimp auth in custom tools - -You can use the pre-built [Arcade Mailchimp Marketing MCP Server](/resources/integrations/productivity/mailchimp-api.md) to quickly build and AI apps that interact with Mailchimp. - -If the pre-built tools in the Mailchimp Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Mailchimp API. - -Use the `OAuth2()` auth class to specify that a requires authorization with Mailchimp. The `context.authorization.token` field will be automatically populated with the ’s Mailchimp token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - - -@tool( - requires_auth=OAuth2(provider_id="arcade-mailchimp") -) -async def get_mailchimp_lists( - context: ToolContext, -) -> Annotated[dict, "The user's Mailchimp lists."]: - """ - Retrieve all mailing lists from the authenticated user's Mailchimp account. - """ - # First, get the API endpoint for this user - async with httpx.AsyncClient() as client: - metadata_response = await client.get( - "https://login.mailchimp.com/oauth2/metadata", - headers={"Authorization": f"Bearer {context.authorization.token}"} - ) - api_endpoint = metadata_response.json()["api_endpoint"] - - # Now get the lists - response = await client.get( - f"{api_endpoint}/3.0/lists", - headers={"Authorization": f"Bearer {context.authorization.token}"} - ) - response.raise_for_status() - return dict(response.json()) -``` - -**Important**: Mailchimp access tokens do not expire unless the revokes access. However, it’s good practice to handle potential errors gracefully and provide users with options to re-authenticate if necessary. - -For more details about Mailchimp’s authentication, refer to the [Mailchimp OAuth 2.0 documentation](https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/) . - -Last updated on January 30, 2026 - -[LinkedIn](/en/references/auth-providers/linkedin.md) -[Microsoft](/en/references/auth-providers/microsoft.md) diff --git a/public/_markdown/en/references/auth-providers/microsoft.md b/public/_markdown/en/references/auth-providers/microsoft.md deleted file mode 100644 index 292572610..000000000 --- a/public/_markdown/en/references/auth-providers/microsoft.md +++ /dev/null @@ -1,210 +0,0 @@ ---- -title: "Microsoft" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Microsoft - -# Microsoft - -At this time, Arcade does not offer a default Microsoft . To use Microsoft auth, you must create a custom Auth Provider with your own Microsoft OAuth 2.0 credentials as described below. - -The Microsoft enables tools and to call the Microsoft Graph API on behalf of a . - -### What’s documented here - -This page describes how to use and configure Microsoft auth with Arcade. - -This is used by: - -- Your [app code](#using-microsoft-auth-in-app-code) - that needs to call Microsoft Graph APIs -- Or, your [custom tools](#using-microsoft-auth-in-custom-tools) - that need to call Microsoft Graph APIs - -## Configuring Microsoft auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Microsoft app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Microsoft app credentials, let’s go through the steps to create a Microsoft app. - -### Create a Microsoft app - -- Follow Microsoft’s guide to [registering an app with the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) -   -- Choose the permissions (scopes) you need for your app. Refer to the \[section below\](#arcade-microsoft- Servers-scopes) for a list of scopes needed by the Arcade Microsoft , in case you intend to use them. -- Set the redirect URL to the redirect URL generated by Arcade (see below) -- Copy the client ID and client secret to use below - -Next, add the Microsoft app to Arcade. - -### Arcade Microsoft MCP Servers Scopes - -Below is the list of scopes required by the Arcade Microsoft Servers: - -MCP Server - -Required Permissions - -[Outlook Calendar](/resources/integrations/productivity/outlook-calendar.md) - -`Calendars.ReadBasic` -`Calendars.ReadWrite` -`MailboxSettings.Read` - -[Outlook Mail](/resources/integrations/productivity/outlook-mail.md) - -`Mail.Read` -`Mail.ReadWrite` -`Mail.Send` - -[Teams](/resources/integrations/social/microsoft-teams.md) - -`Channel.ReadBasic.All` -`ChannelMessage.Read.All` -`ChannelMessage.Send` -`Chat.Create` -`Chat.Read` -`ChatMessage.Read` -`ChatMessage.Send` -`People.Read` -`Team.ReadBasic.All` -`TeamMember.Read.All` -`User.Read` - -[SharePoint](/resources/integrations/productivity/sharepoint.md) - -`Sites.Read.All` - -## Configuring your own Microsoft Auth Provider in Arcade - -### Dashboard GUI - -### Configure Microsoft Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Microsoft**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-microsoft-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Microsoft app. -- Note the **Redirect URL** generated by Arcade. This must be set as your Microsoft app’s redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Microsoft auth using your Arcade credentials, Arcade will automatically use this Microsoft OAuth provider. If you have multiple Microsoft providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Microsoft auth in app code - -Use the Microsoft in your own and AI apps to get a token for Microsoft Graph APIs. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for Microsoft Graph APIs: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="microsoft", - scopes=["User.Read", "Files.Read"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# TODO: Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -let authResponse = await client.auth.start(userId, "microsoft", { - scopes: ["User.Read", "Files.Read"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// TODO: Do something interesting with the token... -``` - -## Using Microsoft auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with Microsoft Graph APIs. - -Use the `Microsoft()` auth class to specify that a requires authorization with Microsoft. The `context.authorization.token` field will be automatically populated with the ’s Microsoft token: - -```python -from typing import Annotated - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Microsoft - - -@tool( - requires_auth=Microsoft( - scopes=["User.Read", "Files.Read"], - ) -) -async def get_file_contents( - context: ToolContext, - file_id: Annotated[str, "The ID of the file to get the contents of"], -) -> Annotated[str, "The contents of the file"]: - """Get the contents of a file from Microsoft Graph.""" - url = f"https://graph.microsoft.com/v1.0/me/drive/items/{file_id}" - headers = {"Authorization": f"Bearer {context.authorization.token}"} - - async with httpx.AsyncClient() as client: - response = await client.get( - url=url, - headers=headers, - ) - response.raise_for_status() - return response.json() -``` - -Last updated on January 30, 2026 - -[Mailchimp](/en/references/auth-providers/mailchimp.md) -[Miro](/en/references/auth-providers/miro.md) diff --git a/public/_markdown/en/references/auth-providers/miro.md b/public/_markdown/en/references/auth-providers/miro.md deleted file mode 100644 index 1aea1a66a..000000000 --- a/public/_markdown/en/references/auth-providers/miro.md +++ /dev/null @@ -1,272 +0,0 @@ ---- -title: "Miro" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Miro - -# Miro - -The Miro enables tools and to call [Miro APIs](https://developers.miro.com/reference/api-reference)  on behalf of a using OAuth 2.0 authentication. - -Want to quickly get started with Miro in your or AI app? The pre-built [Arcade Miro MCP Server](/resources/integrations/productivity/miroapi.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Miro auth with Arcade. - -This is used by: - -- The [Arcade Miro MCP Server](/resources/integrations/productivity/miroapi.md) - , which provides pre-built for interacting with Miro -- Your [app code](#using-miro-auth-in-app-code) - that needs to call the Miro API -- Or, your [custom tools](#using-miro-auth-in-custom-tools) - that need to call the Miro API - -## Configuring Miro auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Miro app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Miro app credentials, let’s go through the steps to create a Miro app. - -### Create a Miro app - -To integrate with Miro’s API, you’ll need to create an app in the Miro Developer Platform: - -#### Access the Miro Developer Platform - -Navigate to the [Miro Developer Platform](https://developers.miro.com/)  and sign in with your Miro or create a new one. - -#### Create a new app - -1. Go to [Your Apps](https://miro.com/app/settings/user-profile/apps) -   in your Miro profile settings -2. Click on “Create new app” -3. Fill in the required details: - - **App Name**: Choose a descriptive name for your application - - **Description**: Provide a brief description of your app - -#### Configure OAuth settings - -1. After creating your app, navigate to the **OAuth & Permissions** section -2. Set the **Redirect URI** to the redirect URL generated by Arcade (see configuration section below) -3. Configure the required **Scopes** for your application: - - `boards:read` - Read board information - - `boards:write` - Create and modify boards - - Add other scopes as needed for your use case - -#### Obtain your credentials - -1. In your app’s settings, you’ll find your **Client ID** and **Client Secret** -2. Copy both values for use in Arcade configuration - -For detailed instructions, refer to Miro’s [OAuth documentation](https://developers.miro.com/docs/getting-started-with-oauth) . - -Next, add the Miro app to Arcade. - -## Configuring your own Miro Auth Provider in Arcade - -### Dashboard GUI - -### Configure Miro Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “arcade-miro”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Miro app. -- Configure the OAuth 2.0 endpoints: - - **Authorization URL**: `https://miro.com/oauth/authorize` - - **Token URL**: `https://api.miro.com/v1/oauth/token` -- Note the **Redirect URL** generated by Arcade. This must be set as your Miro app’s Redirect URI. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure Miro Auth Using Configuration File - -This method is only available when you are \[self-hosting the engine\](/guides/deployment-hosting/on-prem - -#### Set environment variables - -Set the following environment variables: - -```bash -export MIRO_CLIENT_ID="" -export MIRO_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -MIRO_CLIENT_ID="" -MIRO_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: arcade-miro - description: Miro OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:MIRO_CLIENT_ID} - client_secret: ${env:MIRO_CLIENT_SECRET} - oauth2: - scope_delimiter: " " - authorize_request: - endpoint: "https://miro.com/oauth/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}}" - state: "{{state}}" - token_request: - endpoint: "https://api.miro.com/v1/oauth/token" - params: - grant_type: authorization_code - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - redirect_uri: "{{redirect_uri}}" - response_content_type: application/json -``` - -When you use tools that require Miro auth using your Arcade credentials, Arcade will automatically use this Miro OAuth provider. If you have multiple Miro providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Miro auth in app code - -Use the Miro in your own and AI apps to get a token for the Miro API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Miro API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="arcade-miro", - scopes=["boards:read", "boards:write"] -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "arcade-miro", [ - "boards:read", - "boards:write", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Miro auth in custom tools - -You can use the pre-built [Arcade Miro MCP Server](/resources/integrations/productivity/miroapi.md) to quickly build and AI apps that interact with Miro. - -If the pre-built tools in the Miro Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Miro API. - -Use the `OAuth2()` auth class to specify that a requires authorization with Miro. The `context.authorization.token` field will be automatically populated with the ’s Miro token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - - -@tool( - requires_auth=OAuth2( - provider_id="arcade-miro", - scopes=["boards:read"] - ) -) -async def get_user_boards( - context: ToolContext, -) -> Annotated[dict, "The user's boards."]: - """ - Retrieve the list of boards for the authenticated user. - """ - url = "https://api.miro.com/v2/boards" - headers = { - "Authorization": context.authorization.token, - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return dict(response.json()) -``` - -## Available Scopes - -Miro supports various OAuth scopes that determine the level of access your application has: - -- `boards:read` - Read board information -- `boards:write` - Create and modify boards -- `team:read` - Read team information -- `team:write` - Manage team settings -- `organizations:read` - Read organization information -- `organizations:teams:read` - Read organization teams - -For a complete list of available scopes, refer to the [Miro Scopes documentation](https://developers.miro.com/docs/scopes) . - -Last updated on January 30, 2026 - -[Microsoft](/en/references/auth-providers/microsoft.md) -[Notion](/en/references/auth-providers/notion.md) diff --git a/public/_markdown/en/references/auth-providers/notion.md b/public/_markdown/en/references/auth-providers/notion.md deleted file mode 100644 index c923365f0..000000000 --- a/public/_markdown/en/references/auth-providers/notion.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: "Notion" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Notion - -# Notion - -The Notion enables tools and to call [Notion APIs](https://developers.notion.com/reference/intro)  on behalf of a . - -### What’s documented here - -This page describes how to use and configure Notion auth with Arcade. - -This is used by: - -- Your [app code](#using-notion-auth-in-app-code) - that needs to call the Notion API -- Or, your [custom tools](#using-notion-auth-in-custom-tools) - that need to call the Notion API - -## Configuring Notion auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Notion app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Notion app credentials, let’s go through the steps to create a Notion app. - -### Create a Notion app - -- Create a new public integration in your [integration’s settings page](https://www.notion.so/profile/integrations) -   -- Set the redirect URI to the redirect URL generated by Arcade (see below) -- Once you complete creating your integration, copy the client ID and client secret to use below - -Next, add the Notion app to Arcade. - -## Configuring your own Notion Auth Provider in Arcade - -### Dashboard GUI - -### Configure Notion Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Notion**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-notion-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Notion app. -- Note the **Redirect URL** generated by Arcade. This must be set as your Notion app’s redirect URI. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Notion auth using your Arcade credentials, Arcade will automatically use this Notion OAuth provider. If you have multiple Notion providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Notion auth in app code - -Use the Notion in your own and AI apps to get a token for the Notion API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Notion API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="notion" -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "notion"); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Notion auth in custom tools - -You can use the pre-built [Arcade Notion MCP Server](/resources/integrations/productivity/notiontoolkit.md) to quickly build and AI apps that interact with Notion. - -If the pre-built tools in the Notion Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Notion API. - -Use the `Notion()` auth class to specify that a requires authorization with Notion. The `context.authorization.token` field will be automatically populated with the ’s Notion token: - -```json -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Notion - - -@tool(requires_auth=Notion()) -async def search_page_by_title( - context: ToolContext, - title_includes: Annotated[str, "The text to compare against page and database titles."], -) -> Annotated[dict, "The matching pages."]: - """ - Search for a Notion page by its title. - """ - url = "https://api.notion.com/v1/search" - headers = { - "Authorization": context.authorization.token, - "Content-Type": "application/json", - "Notion-Version": "2022-06-28", - } - payload = {"query": title_includes, "filter": {"property": "object", "value": "page"}} - - async with httpx.AsyncClient() as client: - response = await client.post(url, headers=headers, json=payload) - response.raise_for_status() - return dict(response.json()) -``` - -Last updated on January 30, 2026 - -[Miro](/en/references/auth-providers/miro.md) -[PagerDuty](/en/references/auth-providers/pagerduty.md) diff --git a/public/_markdown/en/references/auth-providers/oauth2.md b/public/_markdown/en/references/auth-providers/oauth2.md deleted file mode 100644 index abec3b324..000000000 --- a/public/_markdown/en/references/auth-providers/oauth2.md +++ /dev/null @@ -1,489 +0,0 @@ ---- -title: "OAuth 2.0" -description: "Authorize tools and agents with any OAuth 2.0-compatible provider" ---- -[Auth Providers](/en/references/auth-providers.md) -OAuth 2.0 - -# OAuth 2.0 - -The OAuth 2.0 enables tools and to authorize with any OAuth 2.0-compatible API on behalf of a . - -Arcade has [pre-built auth providers](/references/auth-providers.md) for many popular OAuth 2.0 services. Use this OAuth 2.0 provider to connect to other systems that aren’t pre-built. - -### What’s documented here - -This page describes how to configure OAuth 2.0 with Arcade, and use it in: - -- Your [app code](#using-oauth-20-in-app-code) - that needs to call APIs protected by OAuth 2.0 -- Or, your [custom tools](#using-oauth-20-in-custom-tools) - that need to call APIs protected by OAuth 2.0 - -### Supported OAuth 2.0 flows - -The only supported OAuth 2.0 flow is the authorization code grant flow (with or without PKCE). - -## Configuring OAuth 2.0 - -How you configure the OAuth 2.0 provider depends on whether you use the Arcade Cloud Engine or a [self-hosted Engine](/guides/deployment-hosting/on-prem.md). If you use the Cloud Engine, you must configure your provider in the Dashboard. - -When configuring your app in the OAuth 2.0 enabled service, you must use the redirect URL generated by Arcade (see below) as the redirect URL (sometimes called the redirect URI or callback URL). - -### Using the Arcade Dashboard - -When using the Arcade Cloud Platform, the Dashboard is available at [`https://api.arcade.dev/dashboard`](https://api.arcade.dev/dashboard). If you are [self-hosting Arcade](/guides/deployment-hosting/on-prem.md), by default the Dashboard is available at [`http://localhost:9099/dashboard`](http://localhost:9099/dashboard). Adjust the host and port, if necessary, to match your environment. - -1. Navigate to the OAuth section of the Arcade Dashboard and click **Add OAuth Provider**. -2. Select **OAuth 2.0** as the provider. -3. Choose a unique **ID** for your provider (e.g. “my-oauth2-provider”) with an optional **Description**. -4. Enter your **Client ID** and **Client Secret** from your OAuth 2.0 provider. -5. Note the **Redirect URL** generated by Arcade. This must be set as the redirect URL in the external service’s configuration. -6. Click **Save**. - -When you use tools that require OAuth 2.0 authorization using your Arcade credentials, Arcade will automatically use this OAuth 2.0 provider. - -### Using the `engine.yaml` configuration file - -This method is only available when you are \[self-hosting the engine\](/guides/deployment-hosting/on-prem - -### Set environment variables - -Set the following environment variables. Replace `HOOLI` with the name of the OAuth 2.0 provider you are configuring: - -```bash -export HOOLI_CLIENT_ID="" -export HOOLI_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -HOOLI_CLIENT_ID="" -HOOLI_CLIENT_SECRET="" -``` - -See [configuration](/guides/deployment-hosting/configure-engine.md) for more information on how to set environment variables and configure the Arcade Engine. - -### Edit the Engine configuration - -To locate the `engine.yaml` file in your OS after installing the Arcade Engine, check the [Engine configuration file](/guides/deployment-hosting/configure-engine.md) documentation. - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: default-github - description: The default GitHub provider - enabled: true - type: oauth2 - provider_id: github - client_id: ${env:GITHUB_CLIENT_ID} - client_secret: ${env:GITHUB_CLIENT_SECRET} - - - id: hooli - description: Hooli OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:HOOLI_CLIENT_ID} - client_secret: ${env:HOOLI_CLIENT_SECRET} - oauth2: - # For a custom OAuth 2.0 provider, specify the full OAuth configuration: -``` - -The ID of the provider (`hooli` in this example) can be any string. It’s used to reference the provider in your app code and must be unique. - -Each service expects a slightly different set of values in the `oauth2` section. Refer to the configuration reference below, and to your service’s documentation to understand what values are required. - -If you need help configuring a specific provider, [get in touch with us](mailto:contact@arcade.dev)! - -### Configuration reference - -For a full example, see the [full configuration example](#full-configuration-example) below. - -`scope_delimiter` _(optional, defaults to the space character)_: The delimiter to use between scopes. - -`pkce` _(optional)_: - -- `enabled` _(optional, default `false`)_: If `true`, PKCE will be used. -- `code_challenge_method` _(optional, default `S256`)_: The code challenge method to use. The only supported method is `S256`. This parameter is ignored if PKCE is not enabled. - -#### `authorize_request` - -This section configures the initial authorization request. - -- `endpoint`: The authorization endpoint for your OAuth 2.0 server, e.g. `/oauth2/authorize` -- `params`: A map of parameter keys and values to include in the authorization request. - -These placeholders are available in `params`: - -- `{{client_id}}`: The configured client ID. -- `{{redirect_uri}}`: The redirect URI managed by Arcade. -- `{{scopes}}`: The scopes to request, if any -- `{{existing_scopes}}`: The scopes that the has already granted, if any. - -The `state` parameter is automatically added to the request, and does not need to be included in `params`. If PKCE is enabled, `code_challenge` and `code_challenge_method` are also added automatically. - -For most providers, `oauth2.authorize_request` will look like: - -```yaml -authorize_request: - endpoint: "https://example.com/oauth2/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}} {{existing_scopes}}" -``` - -Some providers support additional parameters in the authorization request. These can be added to `params` as well. For example: - -```yaml -authorize_request: - endpoint: "https://example.com/oauth2/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}} {{existing_scopes}}" - prompt: consent - access_type: offline -``` - -#### `token_request` - -This section configures the code/token exchange request, after the has approved access to your app. - -- `endpoint`: The token endpoint for your OAuth 2.0 server, e.g. `/oauth2/token` -- `auth_method` _(optional, default none)_: The authentication method to use. Supported values are none (omitted) and `client_secret_basic`. -- `params`: A map of parameter keys and values to include in the token request. - -These placeholders are available in `params`: - -- `{{client_id}}`: The configured client ID. -- `{{client_secret}}`: The configured client secret. -- `{{redirect_uri}}`: The redirect URI managed by Arcade. - -The `code` parameter is automatically added to the request, and does not need to be included in `params`. If PKCE is enabled, the `code_verifier` parameter is also added to the request automatically. - -For most providers, `oauth2.token_request.params` will look like: - -```yaml -token_request: - endpoint: "https://example.com/oauth2/token" - params: - grant_type: authorization_code - redirect_uri: "{{redirect_uri}}" - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" # Omit if using PKCE -``` - -- `response_content_type` _(optional, default `application/json`)_: The expected content type of the response. Supported values are `application/json` and `application/x-www-form-urlencoded`. -- `response_map` _(optional)_: A map of keys and values to extract from the response. Supported keys are `access_token`, `expires_in`, `refresh_token`, `scope`, and `token_type`. Supports simple JSONPath expressions. Only applicable if `response_content_type` is `application/json`. See the [JSONPath reference](#jsonpath-expressions-in-response_map) - for details on extracting values using JSONPath. - -#### `refresh_request` - -This section configures the refresh token request, if your OAuth 2.0 server supports refresh tokens. If not provided, refresh tokens will not be used. - -- `endpoint`: The refresh token endpoint for your OAuth 2.0 server, e.g. `/oauth2/token` -- `auth_method` _(optional, default none)_: The authentication method to use. Supported values are none (omitted), `client_secret_basic`, and `bearer_access_token`. -- `params`: A map of parameter keys and values to include in the refresh token request. - -These placeholders are available in `params`: - -- `{{refresh_token}}`: The refresh token to use. -- `{{client_id}}`: The configured client ID. -- `{{client_secret}}`: The configured client secret. - -For most providers, `oauth2.refresh_request.params` will look like: - -```yaml -refresh_request: - endpoint: "https://example.com/oauth2/token" - params: - grant_type: refresh_token - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" -``` - -- `response_content_type` _(optional, default `application/json`)_: The expected content type of the response. Supported values are `application/json` and `application/x-www-form-urlencoded`. -- `response_map` _(optional)_: A map of keys and values to extract from the response. Supported keys are `access_token`, `expires_in`, `refresh_token`, `scope`, and `token_type`. Supports simple JSONPath expressions. Only applicable if `response_content_type` is `application/json`. See the [JSONPath reference](#jsonpath-expressions-in-response_map) - for details on extracting values using JSONPath. - -#### `user_info_request` - -Some OAuth 2.0 APIs provide a user info endpoint that returns information about the user. can automatically request info from servers that support it. If the `user_info_request` section is omitted, user info will not be requested. - -- `endpoint`: The info endpoint for your OAuth 2.0 server, e.g. `/oauth2/userinfo` -- `auth_method` _(optional, default `bearer_access_token`)_: The authentication method to use. The only supported value is `bearer_access_token`. -- `response_content_type` _(optional, default `application/json`)_: The expected content type of the response. The only supported value is `application/json`. -- `response_map` _(optional)_: A map of keys and values to extract from the response. If no `response_map` is provided, the entire response will be extracted verbatim. Supports simple JSONPath expressions. Only applicable if `response_content_type` is `application/json`. See the [JSONPath reference](#jsonpath-expressions-in-response_map) - for details on extracting values using JSONPath. -- `triggers`: Controls when the info request is made. - - `on_token_grant`: If `true`, the info request will be made when a token is granted. This is typically only once for each user, unless new scopes are granted. - - `on_token_refresh`: If `true`, the info request will be made every time a token is refreshed. - -#### `token_introspection_request` - -Some OAuth 2.0 APIs provide a token introspection endpoint that can be used to check the validity of a token and retrieve additional information such as token expiration time. - -An example of a token introspection request configuration: - -```yaml -auth: - providers: - - id: custom-provider - description: "Custom provider" - enabled: true - type: oauth2 - client_id: ${env:CUSTOM_CLIENT_ID} - client_secret: ${env:CUSTOM_CLIENT_SECRET} - oauth2: - token_introspection_request: - enabled: true - endpoint: "https://example.oauth.com/services/oauth2/introspect" - method: POST - params: - token: "{{access_token}}" - auth_method: "client_secret_basic" - request_content_type: application/x-www-form-urlencoded - response_content_type: application/json - response_map: - expires_in: "$.exp" - scope: "$.scope" - expiration_format: absolute_unix_timestamp - triggers: - on_token_grant: true - on_token_refresh: true -``` - -- `enabled` _(optional, default `false`)_: If `true`, the token introspection request will be made. -- `endpoint` _(required)_: The endpoint to use for the token introspection request. -- `method` _(optional, default `GET`)_: The HTTP method to use for the token introspection request. -- `params` _(optional)_: A map of parameter keys and values to include in the token introspection request. Currently, only mapping a field to the internal `access_token` field is supported. -- `auth_method` _(optional, default `client_secret_basic`)_: The authentication method to use for the token introspection request. Supported values are `client_secret_basic` and `bearer_access_token`. -- `request_content_type` _(optional, default `application/x-www-form-urlencoded`)_: The content type of the request body. -- `response_content_type` _(optional, default `application/json`)_: The content type of the response body. -- `response_map` _(required)_: A map of keys and values to extract from the response. Supported keys are `expires_in` and `scope`. Supports simple JSONPath expressions. -- `expiration_format` _(optional, default `absolute_unix_timestamp`)_: The format of the expiration time. Supported values are `absolute_unix_timestamp` and `relative_seconds`. -- `triggers` _(required)_: Controls when the token introspection request is made. - - `on_token_grant`: If `true`, the token introspection request will be made when a token is granted. This is typically only once for each , unless new scopes are granted. - - `on_token_refresh`: If `true`, the token introspection request will be made every time a token is refreshed. - -#### JSONPath expressions in `response_map` - -In the `token_request`, `refresh_request`, `token_introspection_request`, and `user_info_request` sections, you can specify a `response_map`. Configuring a response map is useful if your OAuth 2.0 server returns a JSON object with nested properties, or properties with non-standard names. - -For example, for the token request, most OAuth 2.0 servers return a JSON payload that looks like this: - -```json -{ - "access_token": "...", - "refresh_token": "...", - "expires_in": 3600, - "scope": "scope1 scope2" -} -``` - -If your server returns a payload of this shape, you don’t need `response_map`! - -But if your server returns: - -```json -{ - "data": { - "access_token": "...", - "expires_in": 3600, - "refresh_token": "...", - "scope": "scope1 scope2" - } -} -``` - -Then you need to configure `response_map` to extract the nested properties from inside the `data` object. Use [JSONPath](https://en.wikipedia.org/wiki/JSONPath)  expressions to select the properties you need: - -```yaml -token_request: - response_map: - access_token: "$.data.access_token" - expires_in: "$.data.expires_in" - refresh_token: "$.data.refresh_token" # Only needed if refresh tokens are used - scope: "$.data.scope" # Only needed if scopes are used -``` - -Similarly, for info or token introspection requests, you can use `response_map` to extract custom properties from the response. - -#### Handling scope arrays - -Most OAuth 2.0 servers return scopes as a delimited string, such as `scope1 scope2` or `scope1,scope2`. If your server follows this convention, configuring the top-level `scope_delimiter` property in the `oauth2` section is all you need to do. - -Some OAuth 2.0 servers return an array of scopes instead of a delimited string. For example: - -```json -{ - "access_token": "...", - "expires_in": 3600, - "scope": ["scope1", "scope2"] -} -``` - -In this case, you need to use the `join()` function in `response_map` to join the scopes into a delimited string: - -```yaml -token_request: - response_map: - access_token: "$.access_token" - expires_in: "$.expires_in" - scope: "join('$.scope', ' ')" -``` - -`join()` takes two arguments: - -- `path`: The JSONPath expression to select the array to join. -- `delimiter`: The delimiter to use between array elements. Make sure this matches the `scope_delimiter` setting in the `oauth2` section. - -#### Extracting values from JWTs - -Some OAuth 2.0 servers return JWT tokens that contain claims you might need to extract. For example, the token might contain scopes or other information about the . You can use the `jwt_decode()` function in `response_map` to extract values from JWT tokens: - -```yaml -token_request: - response_map: - access_token: "$.access_token" - expires_in: "$.expires_in" - scope: "jwt_decode('$.access_token', '$.scope')" -``` - -`jwt_decode()` takes two arguments: - -- `token_path`: The JSONPath expression to select the JWT token. -- `claim_path`: The JSONPath expression to select the claim within the decoded JWT payload. - -If the claim is an array (like scopes often are), you can combine `jwt_decode()` with `join()` to extract and format the values: - -```yaml -token_request: - response_map: - access_token: "$.access_token" - expires_in: "$.expires_in" - scope: "join(jwt_decode('$.access_token', '$.scp'), ' ')" -``` - -This is particularly useful when the JWT token contains scopes in an array format (like `scp: ["scope1", "scope2"]`) and you need to convert them to a space-delimited string. - -You can also extract nested claims from the JWT payload using dot notation in the claim path: - -```yaml -token_request: - response_map: - access_token: "$.access_token" - expires_in: "$.expires_in" - scope: "jwt_decode('$.access_token', '$.nested.scopes')" -``` - -#### Full configuration example - -Here’s a full example of the YAML configuration for a custom OAuth 2.0 provider: - -## Using OAuth 2.0 in app code - -Use the OAuth 2.0 in your own and AI apps to get a token for any OAuth 2.0-compatible APIs. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get an access token: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="hooli", - scopes=["scope1", "scope2"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# TODO: Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -let authResponse = await client.auth.start(userId, "hooli", [ - "scope1", - "scope2", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// TODO: Do something interesting with the token... -``` - -## Using OAuth 2.0 in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with any OAuth 2.0-compatible APIs. - -Use the `OAuth2()` auth class to specify that a requires OAuth 2.0 authorization. In your tool function, `context.authorization` will be automatically populated with the following properties: - -- `context.authorization.token` contains the ’s access token. -- If `oauth2.user_info_request` is configured, the info will be fetched and placed in `context.authorization.user_info`. The data payload is specific to each provider. - -```python -from typing import Annotated - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - - -@tool( - requires_auth=OAuth2( - provider_id="hooli", - scopes=["scope1", "scope2"], - ) -) -async def reticulate_splines( - context: ToolContext, - num_splines: Annotated[int, "The number of splines to reticulate"], -): - """Reticulate the specified number of splines.""" - - # Get an access token to call an API - token = context.authorization.token - - # Get user info (if configured and supported by the OAuth 2.0 server): - user_id = context.authorization.user_info.get("sub") -``` - -Last updated on January 30, 2026 - -[Overview](/en/references/auth-providers.md) -[Airtable](/en/references/auth-providers/airtable.md) diff --git a/public/_markdown/en/references/auth-providers/pagerduty.md b/public/_markdown/en/references/auth-providers/pagerduty.md deleted file mode 100644 index 0516bf454..000000000 --- a/public/_markdown/en/references/auth-providers/pagerduty.md +++ /dev/null @@ -1,264 +0,0 @@ ---- -title: "PagerDuty" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -PagerDuty - -# PagerDuty - -The PagerDuty enables tools and to call [PagerDuty APIs](https://developer.pagerduty.com/api-reference/)  on behalf of a using OAuth 2.0 authentication. - -Arcade currently supports **Classic** PagerDuty OAuth apps only. Choose Classic and select either **read-only** or **read/write** access. Newer Web App or other models are not supported. See [PagerDuty OAuth functionality](https://developer.pagerduty.com/docs/oauth-functionality) . - -Want to quickly get started with PagerDuty in your or AI app? The pre-built [Arcade PagerDuty MCP Server](/resources/integrations/development/pagerdutyapi.md) is what you want! - -### What’s documented here - -This page describes how to use and configure PagerDuty auth with Arcade. - -This is used by: - -- The [Arcade PagerDuty MCP Server](/resources/integrations/development/pagerdutyapi.md) - , which provides pre-built for interacting with PagerDuty -- Your [app code](#using-pagerduty-auth-in-app-code) - that needs to call the PagerDuty API -- Or, your [custom tools](#using-pagerduty-auth-in-custom-tools) - that need to call the PagerDuty API - -## Configuring PagerDuty auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own PagerDuty app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your PagerDuty app credentials, let’s go through the steps to create a PagerDuty app. - -### Create a PagerDuty app - -To integrate with PagerDuty’s API using OAuth 2.0, you’ll need to register an app in your PagerDuty : - -#### Access PagerDuty App Registration - -1. Log in to your PagerDuty -2. Navigate to **Integrations** → **App Registration** -3. This is where you’ll create and manage your OAuth applications - -#### Create a new app - -1. Click on **New App** or **Register New App** -2. Fill in the required details: - - **Application Name**: Choose a descriptive name for your application - - **Description**: Provide a brief description of your app’s purpose - - **Functionality**: Select both **OAuth 2.0** and **Events Integration** (if needed) - -#### Configure OAuth settings - -1. Choose **Classic** OAuth and select the permission level: - - **Read-only** (recommended; all current only read data) - - Or **Read/Write** if you plan custom that modify data -2. Add the **Redirect URL** generated by Arcade (see configuration section below) to your app’s redirect URLs - -#### Save your credentials - -1. After creating your app, you’ll receive your **Client ID** and **Client Secret** -2. **Important**: Copy and save these credentials immediately, as the Client Secret won’t be accessible again - -For detailed instructions, refer to PagerDuty’s [OAuth 2.0 guide](https://www.pagerduty.com/blog/insights/build-sophisticated-apps-for-your-pagerduty-environment-using-oauth-2-0-and-api-scopes/)  and [OAuth documentation](https://developer.pagerduty.com/docs/oauth-functionality) . - -Next, add the PagerDuty app to Arcade. - -## Configuring your own PagerDuty Auth Provider in Arcade - -### Dashboard GUI - -### Configure PagerDuty Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “arcade-pagerduty”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your PagerDuty app. -- Configure the OAuth 2.0 endpoints: - - **Authorization URL**: `https://app.pagerduty.com/oauth/authorize` - - **Token URL**: `https://app.pagerduty.com/oauth/token` -- Note the **Redirect URL** generated by Arcade. This must be set as one of your PagerDuty app’s Redirect URLs. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure PagerDuty Auth Using Configuration File - -This method is only available when you are \[self-hosting the engine\](/guides/deployment-hosting/on-prem - -#### Set environment variables - -Set the following environment variables: - -```bash -export PAGERDUTY_CLIENT_ID="" -export PAGERDUTY_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -PAGERDUTY_CLIENT_ID="" -PAGERDUTY_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: pagerduty - description: PagerDuty OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:PAGERDUTY_CLIENT_ID} - client_secret: ${env:PAGERDUTY_CLIENT_SECRET} - oauth2: - scope_delimiter: " " - authorize_request: - endpoint: "https://app.pagerduty.com/oauth/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}}" - state: "{{state}}" - token_request: - endpoint: "https://app.pagerduty.com/oauth/token" - params: - grant_type: authorization_code - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - redirect_uri: "{{redirect_uri}}" - response_content_type: application/json -``` - -When you use tools that require PagerDuty auth using your Arcade credentials, Arcade will automatically use this PagerDuty OAuth provider. If you have multiple PagerDuty providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using PagerDuty auth in app code - -Use the PagerDuty in your own and AI apps to get a token for the PagerDuty API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the PagerDuty API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="pagerduty", - scopes=[] -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "pagerduty", []); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using PagerDuty auth in custom tools - -You can use the pre-built [Arcade PagerDuty MCP Server](/resources/integrations/development/pagerdutyapi.md) to quickly build and AI apps that interact with PagerDuty. - -If the pre-built tools in the PagerDuty Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the PagerDuty API. - -Use the `PagerDuty()` auth class to specify that a requires authorization with PagerDuty. The `context.authorization.token` field will be automatically populated with the ’s PagerDuty token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import PagerDuty - - -@tool( - requires_auth=PagerDuty() -) -async def get_current_user( - context: ToolContext, -) -> Annotated[dict, "The current user information."]: - """ - Retrieve information about the authenticated user from PagerDuty. - """ - url = "https://api.pagerduty.com/users/me" - headers = { - "Authorization": context.authorization.token, - "Accept": "application/vnd.pagerduty+json;version=2", - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return dict(response.json()) -``` - -## Permissions - -PagerDuty Classic apps use two permission levels: - -- **read** — Read-only access to PagerDuty resources (recommended; all current are read-only) -- **write** — Full read/write access (only needed if you add custom write ) - -For more details about PagerDuty’s OAuth permissions, refer to the [PagerDuty OAuth functionality docs](https://developer.pagerduty.com/docs/oauth-functionality#scopes) . - -Last updated on January 30, 2026 - -[Notion](/en/references/auth-providers/notion.md) -[Reddit](/en/references/auth-providers/reddit.md) diff --git a/public/_markdown/en/references/auth-providers/reddit.md b/public/_markdown/en/references/auth-providers/reddit.md deleted file mode 100644 index b8024de65..000000000 --- a/public/_markdown/en/references/auth-providers/reddit.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: "Reddit" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Reddit - -# Reddit - -At this time, Arcade does not offer a default Reddit . To use Reddit auth, you must create a custom Auth Provider with your own Reddit OAuth 2.0 credentials as described below. - -The Reddit enables tools and to call the Reddit API on behalf of a . - -### What’s documented here - -This page describes how to use and configure Reddit auth with Arcade. - -This is used by: - -- Your [app code](#using-reddit-auth-in-app-code) - that needs to call Reddit APIs -- Or, your [custom tools](#using-reddit-auth-in-custom-tools) - that need to call Reddit APIs - -## Configuring Reddit auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Reddit app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Reddit app credentials, let’s go through the steps to create a Reddit app. - -### Create a Reddit app - -- Create a Reddit Application in the [Reddit App Console](https://www.reddit.com/prefs/apps) -   -- Set the OAuth Redirect URL to the redirect URL generated by Arcade (see below) -- Copy the App key (Client ID) and App secret (Client Secret), which you’ll need below - -Next, add the Reddit app to Arcade. - -## Configuring your own Reddit Auth Provider in Arcade - -### Dashboard GUI - -### Configure Reddit Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Reddit**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-reddit-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Reddit app. -- Note the **Redirect URL** generated by Arcade. This must be set as your Reddit app’s OAuth Redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Reddit auth using your Arcade credentials, Arcade will automatically use this Reddit OAuth provider. If you have multiple Reddit providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Reddit auth in app code - -Use the Reddit in your own and AI apps to get a \-scoped token for the Reddit API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Reddit API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="reddit", - scopes=["identity"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# TODO: Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "reddit", { - scopes: ["identity"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Reddit auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Reddit API. - -Use the `Reddit()` auth class to specify that a requires authorization with Reddit. The `context.authorization.token` field will be automatically populated with the ’s Reddit token: - -```python -from typing import Annotated - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Reddit - - -@tool( - requires_auth=Reddit( - scopes=["identity"], - ) -) -async def get_user_info( - context: ToolContext, -) -> Annotated[dict, "The user info"]: - """Get the user info for the current user.""" - url = "https://oauth.reddit.com/api/v1/me" - headers = { - "Authorization": f"Bearer {context.authorization.token}", - "User-Agent": "YourAppName v1.0 by u/YourRedditUsername", - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return response.json() -``` - -Last updated on January 30, 2026 - -[PagerDuty](/en/references/auth-providers/pagerduty.md) -[Salesforce](/en/references/auth-providers/salesforce.md) diff --git a/public/_markdown/en/references/auth-providers/salesforce.md b/public/_markdown/en/references/auth-providers/salesforce.md deleted file mode 100644 index e93852468..000000000 --- a/public/_markdown/en/references/auth-providers/salesforce.md +++ /dev/null @@ -1,390 +0,0 @@ ---- -title: "Salesforce" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Salesforce - -# Salesforce - -The Salesforce enables tools and to call Salesforce APIs on behalf of a . - -## What’s documented here - -This page describes how to use and configure Salesforce auth with Arcade. - -This is used by: - -- The [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce.md) - , which provides pre-built for interacting with Salesforce services -- Your [app code](#calling-salesforce-apis-directly) - that needs to call Salesforce APIs -- Or, your [custom tools](#create-your-own-salesforce-tools) - that need to call Salesforce APIs - -## Create a Salesforce app - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -**Salesforce Spring ‘26 Update:** Starting Spring ‘26, Salesforce is recommending [External Client Apps](https://help.salesforce.com/s/articleView?id=xcloud.external_client_apps.htm&type=5)  instead of Connected Apps for new OAuth integrations. If you haven’t created an app yet, use an External Client App - the OAuth configuration is identical and works the same way with Arcade. Existing Connected Apps will continue to work without any changes. - -For this guide, we’ll create an **External Client App**. Make sure to follow the instructions below while you [create your External Client App](https://help.salesforce.com/s/articleView?id=xcloud.create_a_local_external_client_app.htm&type=5) . - -When creating your app, make sure to: - -- Under “API (Enable OAuth Settings)”, check the **Enable OAuth** option -- Set the callback URL to the redirect URL generated by Arcade (see below) or set any temporary URL for now, you can change it later. -- In the **OAuth Scopes** section, add the two following scopes: - - “Manage Data Via APIs (api)” - - “Perform requests at any time (refresh\_token, offline\_access)” -- Check “Enable Token Exchange Flow” and keep the “Require secret for Token Exchange Flow” also checked. -- Check “Enable Refresh Token Rotation” -- Leave all other settings as default and save your app - -Right after creating the app, Salesforce will redirect you to the app’s page. In the “Settings” tab, under “OAuth Settings”, click the “Consumer Key and Secret” button. It will open a page showing the **Consumer Key** and **Consumer Secret** values. Take note of these values, you will need them to configure Salesforce in Arcade. - -Go back to the App’s page and click the **Policies** tab and follow the instructions below: - -- Under “OAuth Settings”, select the custom OAuth scopes listed in the [Create and Assign Custom Scopes to your External Client App](#create-and-assign-custom-scopes-to-your-external-client-app) - section. -- In the “App Authorization” area: - - Under “Refresh Token Policy”, check the option “Refresh token is valid until revoked”. - - In “IP Relaxation”, select **Relax IP Restrictions**. -- Click the “Save” button to save your changes. - -With that, your Salesforce app is ready to be used with Arcade. - -## Get your Salesforce Org Subdomain - -Follow the steps below to find your Salesforce Org Subdomain: - -1. In the Setup menu, click on **Quick Find** in the top left corner and type `"my domain"`. -2. In the search results, under **Company Settings**, click on **My Domain**. -3. Under **My Domain Details**, check the value of the **Current My Domain URL** field. - -Your **Salesforce Org Subdomain** is the value before the `.my.salesforce.com` part. For example, if your Salesforce domain is `https://acme-inc.my.salesforce.com`, your Salesforce Org Subdomain is `acme-inc`. If you have a developer , your URL might look like `https://acme-inc.develop.my.salesforce.com`. In this case, your Salesforce Org Subdomain is `acme-inc.develop`. - -Take note of your Salesforce Org Subdomain. You will need this value in the next steps. - -## Set the Salesforce Org Subdomain Secret - -Refer to the [previous step](#get-your-salesforce-org-subdomain) to find your Salesforce Org Subdomain. - -Set the `SALESFORCE_ORG_SUBDOMAIN` secret in the Arcade Dashboard: - -- Click on the “Secrets” section in the Arcade Dashboard left-side menu. -- Click on the “Add Secret” button. -- Enter `SALESFORCE_ORG_SUBDOMAIN` as the secret ID. -- Enter your Salesforce Org Subdomain as the secret value. -- Click on the “Create” button. - -## Create and Assign Custom Scopes to your External Client App - -The Salesforce API requires the App developer to create [OAuth custom scopes](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_customscopes.htm&type=5)  defining granular permissions for their application to authorize. - -The custom scopes required by the [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce.md) are listed below, along with their descriptions: - -The custom scopes listed below are only required if you are using the [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce.md). - -If you’re creating your own [custom Salesforce tools](/guides/create-tools/tool-basics/build-mcp-server.md) or using Arcade to authorize and call Salesforce APIs directly, you are free to define custom scope(s) that fit best your application use cases. Observe that you must have at least one custom scope assigned to your Salesforce app in order to use the Salesforce API. - -- `read_account`: Read access to data. -- `read_contact`: Read access to contact data. -- `read_lead`: Read access to lead data. -- `read_note`: Read access to note data. -- `read_opportunity`: Read access to opportunity data. -- `read_task`: Read access to task data. -- `write_contact`: Write access to create contact. - -Follow the [Create an OAuth Custom Scope](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_customscopes_create.htm&type=5)  and [Assign an OAuth Custom Scope to an External Client App](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_customscopes_assign.htm&type=5)  Salesforce documentation to understand how to define and assign these scopes to your Salesforce app. - -The scope names aren’t really attached to any endpoint or action. It’s the developer’s job to honor the permissions communicated to the when authorizing the app. You could, in theory, assign one single scope (e.g. `fullaccess`) and use it to query any Salesforce API endpoint. - -## Configuring Salesforce Auth - -### Dashboard GUI - -### Configure Salesforce Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -Go to the [Arcade Dashboard](https://api.arcade.dev/dashboard)  and log in with your Arcade credentials. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Custom Provider** tab at the top. - -#### Enter the provider details - -- Enter `salesforce` as the **ID** for your provider (the ID must be `salesforce` to use the [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce.md) - ). -- Optionally enter a **Description**. -- Enter your **Client ID** (Consumer Key) and **Client Secret** (Consumer Secret) from your Salesforce External Client App. -- Note the **Redirect URL** generated by Arcade. This must be set as your Salesforce External Client App’s callback URL. - -#### Configure the auth endpoints - -Replace `salesforce-org-subdomain` with your [Salesforce Org Subdomain](#get-your-salesforce-org-subdomain). - -- Enter the auth endpoints: - - **Authorization Endpoint**: `https://salesforce-org-subdomain.my.salesforce.com/services/oauth2/authorize` - - **Token Endpoint**: `https://salesforce-org-subdomain.my.salesforce.com/services/oauth2/token` -- Under **Refresh Token Settings**: - - Enter the **Refresh Token Endpoint**: `https://salesforce-org-subdomain.my.salesforce.com/services/oauth2/token` - - In **Response Content Type**, select `application/json`. -- Under **Token Introspection Settings**: - - Check the **Enable Token Introspection** option. - - Enter the **Token Introspection Endpoint**: `https://salesforce-org-subdomain.my.salesforce.com/services/oauth2/introspect` - - In **HTTP Method**, select `POST` - - In **Authentication Method**, select `Client Secret Basic` - - In **Request Content Type**, select `application/x-www-form-urlencoded`. - - Under **Request Parameters** section, add the following key-value pair: - - **Key**: `token` - - **Value**: `{{access_token}}` - - In **Response Content Type**, select `application/json`. - - In **Expiration Format**, select `Absolute Unix Timestamp`. - - Under the **Response Map** section: - - Set the **expires\_in** field to `$.exp` - - Set the **scope** field to `$.scope` - - Leave the other fields as default - - Under **Triggers** section, enable the **On Token Grant** and **On Token Refresh** options. - -#### Optional Auth Settings - -- Under **PKCE Settings**, check the **Enable PKCE** option if you have enabled PKCE when creating your Salesforce app. -- Leave the **Authorization Settings** and **Token Settings** sections as default. - -#### Create the provider - -Click the **Create** button and the provider will be ready to be used in the . - -## Using the Arcade Salesforce MCP Server - -The [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce.md) provides tools to interact with various Salesforce objects, such as , contacts, leads, opportunities, notes, tasks, email messages, call logs, etc. - -Refer to the [MCP Server documentation and examples](/resources/integrations/sales/salesforce.md) to learn how to use the Server to build and AI apps that interact with Salesforce services. - -Check our introductory documentation to understand what are and how [tool calling works](/guides/tool-calling.md). - -## Calling Salesforce APIs directly - -Use the Salesforce to get a user authorization token and call Salesforce API endpoints directly, without the use of any . See [How Arcade helps with Agent Authorization](/get-started/about-arcade.md) to understand how this works. - -### Prerequisites - -1. Create an [Arcade](https://app.arcade.dev/register) - -2. Get an [Arcade API key](/get-started/setup/api-keys.md) - . -3. Set the `ARCADE_API_KEY` environment variable (`export ARCADE_API_KEY=` on Bash, `$env:ARCADE_API_KEY=""` on PowerShell). -4. Make sure to have Python 3.10+ or Node.js 18+ installed. - -### Python - -### Install the Arcade Python Client - -```python -pip install arcadepy -``` - -### Import necessary modules and instantiate the client - -Create a new script called `salesforce_example.py`. Import the necessary modules and instantiate the : - -The service is available at `http://localhost:9099` by default. Replace the host and port, if necessary, to match your environment. - -```python -import requests -from arcadepy import Arcade - -client = Arcade(base_url="http://localhost:9099") # Automatically finds the `ARCADE_API_KEY` env variable -``` - -### Set the values required for the Salesforce API call - -```python -salesforce_provider_id = "salesforce" -salesforce_org_subdomain = "salesforce-org-subdomain" -user_id = "{arcade_user_id}" -scopes = ["read_account"] -``` - -Here’s a break down of each value: - -- **`salesforce_provider_id`**: the ID you entered when setting up the [Salesforce auth provider](#configuring-salesforce-auth) - ; -- **`salesforce_org_subdomain`**: your [Salesforce Org Subdomain](#get-your-salesforce-org-subdomain) - ; -- **`user_id`**: an internal identifier for your application (it could be an email address, a username, UUID, etc); for demonstration purposes, in this example, enter your own email address; -- **`scopes`**: the list of scopes you want to request from the ; if you assigned the [custom scopes required by the Arcade Salesforce MCP Server](#create-and-assign-custom-scopes-to-your-external-client-app) - use `["read_account"]` in this example. - -### Start the authorization process and wait for completion - -The will prompt the to access a URL and authorize the app to access their Salesforce data. At the end of the auth process, you will have a token that can be used to call Salesforce APIs on behalf of that user. - -```python -auth_response = client.auth.start( - user_id=user_id, - provider=salesforce_provider_id, - scopes=scopes, -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token - -if not token: - raise ValueError("No token found in auth response") -``` - -If the same scopes have already been authorized by the before and the token is still valid, the auth process will be skipped and the token will be returned immediately, without prompting with the authorization URL. The Arcade Engine associates a previously authorized token with the `user_id` you provided. - -### Call the Salesforce API - -We will now call the Salesforce `/parameterizedSearch` API endpoint to search and retrieve data. - -Replace the `q` value of `"acme"` with any keyword combination of your choice. In a real-world scenario, this value would most likely come from a ’s input. Observe that the `q` argument must be a string with two or more characters. - -```json -response = requests.post( - f"https://{salesforce_org_subdomain}.my.salesforce.com/services/data/v63.0/parameterizedSearch", - headers={"Authorization": f"Bearer {token}"}, - json={ - "q": "acme", - "sobjects": [ - {"name": "Account", "fields": ["Id", "Name", "Website", "Phone"]}, - ], - "in": "ALL", - "overallLimit": 10, - "offset": 0, - }, -) - -if not response.ok: - raise ValueError( - f"Failed to retrieve Salesforce data: {response.status_code} - {response.text}" - ) -``` - -### JavaScript - -### Install the Arcade JavaScript Client - -```javascript -npm install @arcadeai/arcadejs -``` - -### Import necessary modules and instantiate the client - -Create a new script called `salesforce_example.js`. Import the necessary modules and instantiate the : - -Replace `http://localhost:9099` with the URL of your . - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade((baseURL = "http://localhost:9099")); // Automatically finds the `ARCADE_API_KEY` env variable -``` - -### Set the values required for the Salesforce API call - -```javascript -const salesforceProviderId = "salesforce"; -const salesforceOrgSubdomain = "salesforce-org-subdomain"; -const userId = "{arcade_user_id}"; -const scopes = ["read_account"]; -``` - -Here’s a break down of each value: - -- **`salesforceProviderId`**: the ID you entered when setting up the [Salesforce auth provider](#configuring-salesforce-auth) - ; -- **`salesforceOrgSubdomain`**: your [Salesforce Org Subdomain](#get-your-salesforce-org-subdomain) - ; -- **`userId`**: an internal identifier for your application (it could be an email address, a username, UUID, etc); for demonstration purposes, in this example, enter your own email address; -- **`scopes`**: the list of scopes you want to request from the ; if you assigned the [custom scopes required by the Arcade Salesforce MCP Server](#create-and-assign-custom-scopes-to-your-external-client-app) - use `["read_account"]` in this example. - -### Start the authorization process and wait for completion - -The will prompt the to access a URL and authorize the app to access their Salesforce data. At the end of the auth process, you will have a token that can be used to call Salesforce APIs on behalf of that user. - -```javascript -let authResponse = await client.auth.start(userId, { - provider: salesforceProviderId, - scopes: scopes, -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -if (!authResponse.context.token) { - throw new Error("No token found in auth response"); -} - -const token = authResponse.context.token; - -if (!token) { - throw new Error("No token found in auth response"); -``` - -If the same scopes have already been authorized by the before and the token is still valid, the auth process will be skipped and the token will be returned immediately, without prompting with the authorization URL. The Arcade Engine associates a previously authorized token with the `user_id` you provide. - -### Call the Salesforce API - -We will now call the Salesforce `/parameterizedSearch` API endpoint to search and retrieve data. - -Replace the `q` value of `"acme"` with any keyword combination of your choice. In a real-world scenario, this value would most likely come from a ’s input. Observe that the `q` argument must be a string with two or more characters. - -```json -// Use the Salesforce API -const response = await fetch( - `https://${salesforceOrgSubdomain}.my.salesforce.com/services/data/v63.0/parameterizedSearch`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - method: "POST", - body: JSON.stringify({ - q: "acme", - sobjects: [ - { name: "Account", fields: ["Id", "Name", "Website", "Phone"] }, - ], - in: "ALL", - overallLimit: 10, - offset: 0, - }), - }, -); - -if (!response.ok) { - throw new Error( - `HTTP error! status: ${response.status} - ${await response.text()}`, - ); -``` - -## Create your own Salesforce Tools - -If the pre-built in the [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce.md) don’t meet your needs, you can create your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Salesforce APIs. - -The code implemented in the Arcade Salesforce is the best guide for you to understand how to implement your own. - -PLAINTEXT - -Last updated on February 10, 2026 - -[Reddit](/en/references/auth-providers/reddit.md) -[Slack](/en/references/auth-providers/slack.md) diff --git a/public/_markdown/en/references/auth-providers/slack.md b/public/_markdown/en/references/auth-providers/slack.md deleted file mode 100644 index 9ee34316f..000000000 --- a/public/_markdown/en/references/auth-providers/slack.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -title: "Slack" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Slack - -# Slack - -The Slack enables tools and to call [Slack APIs](https://api.slack.com/docs)  on behalf of a . - -Want to quickly get started with Slack in your or AI app? The pre-built [Arcade Slack MCP Server](/resources/integrations/social/slack.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Slack auth with Arcade. - -This is used by: - -- The [Arcade Slack MCP Server](/resources/integrations/social/slack.md) - , which provides pre-built for interacting with Slack -- Your [app code](#using-slack-auth-in-app-code) - that needs to call the Slack API -- Or, your [custom tools](#using-slack-auth-in-custom-tools) - that need to call the Slack API - -## Configuring Slack auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Slack app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Slack app credentials, let’s go through the steps to create a Slack app. - -### Create a Slack app - -In May 29, 2025, [Slack announced](https://api.slack.com/changelog/2025-05-terms-rate-limit-update-and-faq)  changes to their API rate-limits and terms of service for apps that are not approved for the Slack Marketplace. - -The `conversations.history` and `conversations.replies` endpoints are now limited to 1 request/minute and up to 15 objects returned per request. This affects various in the [Arcade Slack MCP Server](/resources/integrations/social/slack.md). Additionally, the [API Terms of Service](https://slack.com/terms-of-service/api)  now requires [Slack Marketplace](https://api.slack.com/slack-marketplace/using)  approval for commercial distribution. - -- Follow Slack’s guide to [registering a Slack app](https://api.slack.com/quickstart) -   -- If you plan to use the [Arcade Slack MCP Server](/resources/integrations/social/slack.md) - , select the scopes below (include additional scopes for your application’s authorization needs or custom , in any): - - `channels:history` - - `channels:read` - - `chat:write` - - `groups:read` - - `groups:history` - - `groups:write` - - `im:history` - - `im:read` - - `im:write` - - `mpim:history` - - `mpim:read` - - `mpim:write` - - `users:read` - - `users:read.email` -- Set the redirect URL to the redirect URL generated by Arcade (see below) -- Copy the client ID and client secret - -Next, add the Slack app to Arcade. - -## Configuring your own Slack Auth Provider in Arcade - -### Dashboard GUI - -### Configure Slack Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Slack**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-slack-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Slack app. -- Note the **Redirect URL** generated by Arcade. This must be set as your Slack app’s redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Slack auth using your Arcade credentials, Arcade will automatically use this Slack OAuth provider. If you have multiple Slack providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Slack auth in app code - -Use the Slack in your own and AI apps to get a token for the Slack API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Slack API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="slack", - scopes=[ - "chat:write", - "im:write", - "users.profile:read", - "users:read", - ], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -let authResponse = await client.auth.start(userId, "slack", { - scopes: ["chat:write", "im:write", "users.profile:read", "users:read"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Slack auth in custom tools - -You can use the pre-built [Arcade Slack MCP Server](/resources/integrations/social/slack.md) to quickly build and AI apps that interact with Slack. - -If the pre-built tools in the Slack Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Slack API. - -Use the `Slack()` auth class to specify that a requires authorization with Slack. The `context.authorization.token` field will be automatically populated with the ’s Slack token: - -```python -from typing import Annotated - -from slack_sdk import WebClient - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Slack -from arcade_tdk.errors import RetryableToolError - - -@tool( - requires_auth=Slack( - scopes=[ - "chat:write", - "im:write", - "users.profile:read", - "users:read", - ], - ) -) -def send_dm_to_user( - context: ToolContext, - user_name: Annotated[ - str, - "The Slack username of the person you want to message. Slack usernames are ALWAYS lowercase.", - ], - message: Annotated[str, "The message you want to send"], -): - """Send a direct message to a user in Slack.""" - - slackClient = WebClient(token=context.authorization.token) - - # Retrieve the user's Slack ID based on their username - userListResponse = slackClient.users_list() - user_id = None - for user in userListResponse["members"]: - if user["name"].lower() == user_name.lower(): - user_id = user["id"] - break - - if not user_id: - raise RetryableToolError( - "User not found", - developer_message=f"User with username '{user_name}' not found.", - ) - - # Step 2: Retrieve the DM channel ID with the user - im_response = slackClient.conversations_open(users=[user_id]) - dm_channel_id = im_response["channel"]["id"] - - # Step 3: Send the message as if it's from you (because we're using a user token) - slackClient.chat_postMessage(channel=dm_channel_id, text=message) -``` - -Last updated on January 30, 2026 - -[Salesforce](/en/references/auth-providers/salesforce.md) -[Spotify](/en/references/auth-providers/spotify.md) diff --git a/public/_markdown/en/references/auth-providers/spotify.md b/public/_markdown/en/references/auth-providers/spotify.md deleted file mode 100644 index 1f8d25038..000000000 --- a/public/_markdown/en/references/auth-providers/spotify.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -title: "Spotify" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Spotify - -# Spotify - -At this time, Arcade does not offer a default Spotify . To use Spotify auth, you must create a custom Auth Provider with your own Spotify OAuth 2.0 credentials as described below. - -The Spotify enables tools and to call the Spotify API on behalf of a . - -### What’s documented here - -This page describes how to use and configure Spotify auth with Arcade. - -This is used by: - -- Your [app code](#using-spotify-auth-in-app-code) - that needs to call Spotify APIs -- Or, your [custom tools](#using-spotify-auth-in-custom-tools) - that need to call Spotify APIs - -## Configuring Spotify auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Spotify app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Spotify app credentials, let’s go through the steps to create a Spotify app. - -### Create a Spotify app - -- Follow Spotify’s guide to [registering an app](https://developer.spotify.com/documentation/web-api/tutorials/getting-started) -   -- Choose the “Web API” product (at a minimum) -- Set the redirect URL to the redirect URL generated by Arcade (see below) -- Copy the client ID and client secret to use below - -Next, add the Spotify app to Arcade. - -## Configuring your own Spotify Auth Provider in Arcade - -### Dashboard GUI - -### Configure Spotify Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Spotify**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-spotify-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Spotify app. -- Note the **Redirect URL** generated by Arcade. This must be set as your Spotify app’s redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Spotify auth using your Arcade credentials, Arcade will automatically use this Spotify OAuth provider. If you have multiple Spotify providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Spotify auth in app code - -Use the Spotify in your own and AI apps to get a token for the Spotify API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Spotify API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="spotify", - scopes=["user-read-playback-state"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -let authResponse = await client.auth.start(userId, "spotify", [ - "user-read-playback-state", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Spotify auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Spotify API. - -Use the `Spotify()` auth class to specify that a requires authorization with Spotify. The `context.authorization.token` field will be automatically populated with the ’s Spotify token: - -```python -from typing import Annotated - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Spotify - - -@tool( - requires_auth=Spotify( - scopes=["user-read-playback-state"], - ) -) -async def get_playback_state( - context: ToolContext, -) -> Annotated[dict, "Information about the user's current playback state"]: - """Get information about the user's current playback state, including track or episode, progress, and active device.""" - endpoint = "/me/player" - headers = {"Authorization": f"Bearer {context.authorization.token}"} - - async with httpx.AsyncClient() as client: - response = await client.get( - f"https://api.spotify.com/v1/{endpoint}", - headers=headers, - ) - response.raise_for_status() - - if response.status_code == 204: - return {"status": "Playback not available or active"} - return response.json() -``` - -Last updated on January 30, 2026 - -[Slack](/en/references/auth-providers/slack.md) -[Square](/en/references/auth-providers/square.md) diff --git a/public/_markdown/en/references/auth-providers/square.md b/public/_markdown/en/references/auth-providers/square.md deleted file mode 100644 index 5f0ae4a94..000000000 --- a/public/_markdown/en/references/auth-providers/square.md +++ /dev/null @@ -1,278 +0,0 @@ ---- -title: "Square" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Square - -# Square - -The Square enables tools and to call [Square APIs](https://developer.squareup.com/reference/square)  on behalf of a using OAuth 2.0 authentication. - -Want to quickly get started with Square in your or AI app? The pre-built [Arcade Square MCP Server](/resources/integrations/productivity/squareupapi.md) is what you want! - -### What’s documented here - -This page describes how to use and configure Square auth with Arcade. - -This is used by: - -- The [Arcade Square MCP Server](/resources/integrations/productivity/squareupapi.md) - , which provides pre-built for interacting with Square -- Your [app code](#using-square-auth-in-app-code) - that needs to call the Square API -- Or, your [custom tools](#using-square-auth-in-custom-tools) - that need to call the Square API - -## Configuring Square auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Square app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Square app credentials, let’s go through the steps to create a Square app. - -### Create a Square app - -To integrate with Square’s API, you’ll need to create an application in the Square Developer Dashboard: - -#### Access the Square Developer Dashboard - -Navigate to the [Square Developer Portal](https://developer.squareup.com/)  and sign in with your Square or create a new one. - -#### Create a new application - -1. Once logged in, go to the [Developer Dashboard](https://developer.squareup.com/apps) -   -2. Click on “Create App” or ”+” button -3. Fill in the required details: - - **App Name**: Choose a descriptive name for your application - - **Description**: Provide a brief description of your app’s purpose - -#### Configure OAuth settings - -1. After creating your app, navigate to the **OAuth** tab -2. Set the **Redirect URL** to the redirect URL generated by Arcade (see configuration section below) -3. Configure the required **Permissions** (scopes) for your application: - - `MERCHANT_PROFILE_READ` - Read merchant profile information - - `PAYMENTS_READ` - Read payment information - - `PAYMENTS_WRITE` - Process payments - - Add other scopes as needed for your use case - -#### Obtain your credentials - -1. In your app’s dashboard, go to the **Credentials** tab -2. You’ll find your **Application ID** (Client ID) and **Application Secret** (Client Secret) -3. Copy both values for use in Arcade configuration - -For detailed instructions, refer to Square’s [OAuth documentation](https://developer.squareup.com/docs/oauth-api/overview) . - -Next, add the Square app to Arcade. - -## Configuring your own Square Auth Provider in Arcade - -### Dashboard GUI - -### Configure Square Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “arcade-square”). -- Optionally enter a **Description**. -- Enter the **Client ID** (Application ID) and **Client Secret** (Application Secret) from your Square app. -- Configure the OAuth 2.0 endpoints: - - **Authorization URL**: `https://connect.squareup.com/oauth2/authorize` - - **Token URL**: `https://connect.squareup.com/oauth2/token` -- Note the **Redirect URL** generated by Arcade. This must be set as your Square app’s Redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure Square Auth Using Configuration File - -This method is only available when you are \[self-hosting the engine\](/guides/deployment-hosting/on-prem - -#### Set environment variables - -Set the following environment variables: - -```bash -export SQUARE_CLIENT_ID="" -export SQUARE_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -SQUARE_CLIENT_ID="" -SQUARE_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: arcade-square - description: Square OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:SQUARE_CLIENT_ID} - client_secret: ${env:SQUARE_CLIENT_SECRET} - oauth2: - scope_delimiter: " " - authorize_request: - endpoint: "https://connect.squareup.com/oauth2/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}}" - state: "{{state}}" - token_request: - endpoint: "https://connect.squareup.com/oauth2/token" - params: - grant_type: authorization_code - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - redirect_uri: "{{redirect_uri}}" - response_content_type: application/json -``` - -When you use tools that require Square auth using your Arcade credentials, Arcade will automatically use this Square OAuth provider. If you have multiple Square providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Square auth in app code - -Use the Square in your own and AI apps to get a token for the Square API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Square API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="arcade-square", - scopes=["MERCHANT_PROFILE_READ", "PAYMENTS_READ"] -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "arcade-square", [ - "MERCHANT_PROFILE_READ", - "PAYMENTS_READ", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Square auth in custom tools - -You can use the pre-built [Arcade Square MCP Server](/resources/integrations/productivity/squareupapi.md) to quickly build and AI apps that interact with Square. - -If the pre-built tools in the Square Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Square API. - -Use the `OAuth2()` auth class to specify that a requires authorization with Square. The `context.authorization.token` field will be automatically populated with the ’s Square token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - - -@tool( - requires_auth=OAuth2( - provider_id="arcade-square", - scopes=["MERCHANT_PROFILE_READ"] - ) -) -async def get_merchant_info( - context: ToolContext, -) -> Annotated[dict, "The merchant information."]: - """ - Retrieve merchant profile information from Square. - """ - url = "https://connect.squareup.com/v2/merchants/me" - headers = { - "Authorization": context.authorization.token, - "Content-Type": "application/json", - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return dict(response.json()) -``` - -## Available Scopes - -Square supports various OAuth scopes that determine the level of access your application has: - -- `MERCHANT_PROFILE_READ` - Read merchant profile information -- `PAYMENTS_READ` - Read payment information -- `PAYMENTS_WRITE` - Process payments -- `CUSTOMERS_READ` - Read customer information -- `CUSTOMERS_WRITE` - Create and update customers -- `ORDERS_READ` - Read order information -- `ORDERS_WRITE` - Create and update orders -- `INVENTORY_READ` - Read inventory information -- `INVENTORY_WRITE` - Update inventory - -For a complete list of available scopes, refer to the [Square OAuth Permissions documentation](https://developer.squareup.com/docs/oauth-api/square-permissions) . - -Last updated on January 30, 2026 - -[Spotify](/en/references/auth-providers/spotify.md) -[TickTick](/en/references/auth-providers/ticktick.md) diff --git a/public/_markdown/en/references/auth-providers/ticktick.md b/public/_markdown/en/references/auth-providers/ticktick.md deleted file mode 100644 index 08e5e73ce..000000000 --- a/public/_markdown/en/references/auth-providers/ticktick.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -title: "TickTick" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -TickTick - -# TickTick - -The TickTick enables tools and to call [TickTick APIs](https://developer.ticktick.com/docs)  on behalf of a using OAuth 2.0 authentication. - -Want to quickly get started with TickTick in your or AI app? The pre-built [Arcade TickTick API MCP Server](/resources/integrations/productivity/ticktickapi.md) is what you want! - -### What’s documented here - -This page describes how to use and configure TickTick auth with Arcade. - -This is used by: - -- The [Arcade TickTick API MCP Server](/resources/integrations/productivity/ticktickapi.md) - , which provides pre-built for interacting with TickTick -- Your [app code](#using-ticktick-auth-in-app-code) - that needs to call the TickTick API -- Or, your [custom tools](#using-ticktick-auth-in-custom-tools) - that need to call the TickTick API - -## Configuring TickTick auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own TickTick app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your TickTick app credentials, let’s go through the steps to create a TickTick app. - -### Create a TickTick app - -To integrate with TickTick’s API, you’ll need to set up OAuth 2.0 authentication by creating an app in the TickTick Developer Portal: - -#### Access the TickTick Developer Portal - -Navigate to the [TickTick Developer Portal](https://developer.ticktick.com/manage)  and sign in with your existing TickTick credentials or create a new . - -#### Create a new app - -1. Once logged in, click on “New App” -2. Fill in the required details: - - **App Name**: Choose a descriptive name for your application - - **App Description**: Provide a brief description of your application’s purpose - - **App Icon**: Upload an icon to represent your application (optional) - -#### Configure OAuth settings - -1. After creating your app, you’ll receive a `Client ID` and `Client Secret` -2. Set the **Redirect URI** to the redirect URL generated by Arcade (see configuration section below) -3. Configure the required scopes for your application: - - `tasks:read` - Read access to tasks - - `tasks:write` - Write access to tasks (create, update, delete) - - Add other scopes as needed for your use case - -For detailed instructions, refer to TickTick’s official [API Documentation](https://developer.ticktick.com/docs) . - -Next, add the TickTick app to Arcade. - -## Configuring your own TickTick Auth Provider in Arcade - -### Dashboard GUI - -### Configure TickTick Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “arcade-ticktick”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your TickTick app. -- Configure the OAuth 2.0 endpoints: - - **Authorization URL**: `https://ticktick.com/oauth/authorize` - - **Token URL**: `https://ticktick.com/oauth/token` -- Note the **Redirect URL** generated by Arcade. This must be set as your TickTick app’s redirect URI. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure TickTick Auth Using Configuration File - -This method is only available when you are \[self-hosting the engine\](/guides/deployment-hosting/on-prem - -#### Set environment variables - -Set the following environment variables: - -```bash -export TICKTICK_CLIENT_ID="" -export TICKTICK_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -TICKTICK_CLIENT_ID="" -TICKTICK_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section: - -```yaml -auth: - providers: - - id: arcade-ticktick - description: TickTick OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:TICKTICK_CLIENT_ID} - client_secret: ${env:TICKTICK_CLIENT_SECRET} - oauth2: - scope_delimiter: " " - authorize_request: - endpoint: "https://ticktick.com/oauth/authorize" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}}" - state: "{{state}}" - token_request: - endpoint: "https://ticktick.com/oauth/token" - auth_method: client_secret_post - params: - grant_type: authorization_code - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - code: "{{code}}" - redirect_uri: "{{redirect_uri}}" - response_content_type: application/x-www-form-urlencoded - refresh_request: - endpoint: "https://ticktick.com/oauth/token" - auth_method: client_secret_post - params: - grant_type: refresh_token - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - refresh_token: "{{refresh_token}}" - response_content_type: application/x-www-form-urlencoded -``` - -When you use tools that require TickTick auth using your Arcade credentials, Arcade will automatically use this TickTick OAuth provider. If you have multiple TickTick providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using TickTick auth in app code - -Use the TickTick in your own and AI apps to get a token for the TickTick API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the TickTick API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="arcade-ticktick", - scopes=["tasks:read", "tasks:write"] -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "arcade-ticktick", [ - "tasks:read", - "tasks:write", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using TickTick auth in custom tools - -You can use the pre-built [Arcade TickTick API MCP Server](/resources/integrations/productivity/ticktickapi.md) to quickly build and AI apps that interact with TickTick. - -If the pre-built tools in the TickTick Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the TickTick API. - -Use the `OAuth2()` auth class to specify that a requires authorization with TickTick. The `context.authorization.token` field will be automatically populated with the ’s TickTick token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - - -@tool( - requires_auth=OAuth2( - provider_id="arcade-ticktick", - scopes=["tasks:read"] - ) -) -async def get_ticktick_projects( - context: ToolContext, -) -> Annotated[dict, "The list of projects."]: - """ - Retrieve all projects from TickTick. - """ - url = "https://api.ticktick.com/open/v1/project" - headers = { - "Authorization": f"Bearer {context.authorization.token}", - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return dict(response.json()) -``` - -## Available Scopes - -TickTick supports the following OAuth scopes that determine the level of access your application has: - -- `tasks:read` - Read access to tasks, , and related data -- `tasks:write` - Full access to create, update, and delete tasks and - -For a complete list of available API endpoints and their required scopes, refer to the [TickTick API documentation](https://developer.ticktick.com/docs) . - -## API Endpoints - -TickTick provides a comprehensive REST API for managing tasks and . The base URL for all API requests is: - -PLAINTEXT - -``` -https://api.ticktick.com/open/v1 -``` - -Common endpoints include: - -- `/project` - Manage -- `/task` - Manage tasks -- `/user` - Get information - -All API requests must include the OAuth access token in the `Authorization` header: - -PLAINTEXT - -``` -Authorization: Bearer {access_token} -``` - -For detailed API documentation, including request/response formats and examples, visit the [TickTick API Reference](https://developer.ticktick.com/docs) . - -Last updated on January 30, 2026 - -[Square](/en/references/auth-providers/square.md) -[Twitch](/en/references/auth-providers/twitch.md) diff --git a/public/_markdown/en/references/auth-providers/twitch.md b/public/_markdown/en/references/auth-providers/twitch.md deleted file mode 100644 index 4abfb5736..000000000 --- a/public/_markdown/en/references/auth-providers/twitch.md +++ /dev/null @@ -1,197 +0,0 @@ ---- -title: "Twitch" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Twitch - -# Twitch - -At this time, Arcade does not offer a default Twitch . To use Twitch auth, you must create a custom Auth Provider with your own Twitch OAuth 2.0 credentials as described below. - -The Twitch enables tools and to call the Twitch API on behalf of a . - -### What’s documented here - -This page describes how to use and configure Twitch auth with Arcade. - -This is used by: - -- Your [app code](#using-twitch-auth-in-app-code) - that needs to call Twitch APIs -- Or, your [custom tools](#using-twitch-auth-in-custom-tools) - that need to call Twitch APIs - -## Configuring Twitch auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Twitch app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Twitch app credentials, let’s go through the steps to create a Twitch app. - -### Create a Twitch app - -- Twitch requires that you have two-factor authentication enabled for your . Enable in your [account security seetings](https://www.twitch.tv/settings/security) -   -- Create a Twitch Application in the [Twitch App Console](https://dev.twitch.tv/console/apps) -   -- Set the OAuth Redirect URL to the redirect URL generated by Arcade (see below) -- Select your Application category -- Select the ‘Confidential’ Client Type -- Copy the App key (Client ID) and App secret (Client Secret), which you’ll need below - -Next, add the Twitch app to Arcade. - -## Configuring your own Twitch Auth Provider in Arcade - -### Dashboard GUI - -### Configure Twitch Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Twitch**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-twitch-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Twitch app. -- Note the **Redirect URL** generated by Arcade. This must be set as your Twitch app’s OAuth Redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Twitch auth using your Arcade credentials, Arcade will automatically use this Twitch OAuth provider. If you have multiple Twitch providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Twitch auth in app code - -Use the Twitch in your own and AI apps to get a \-scoped token for the Twitch API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Twitch API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="twitch", - scopes=["channel:manage:polls"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "twitch", { - scopes: ["channel:manage:polls"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Twitch auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Twitch API. - -Use the `Twitch()` auth class to specify that a requires authorization with Twitch. The `context.authorization.token` field will be automatically populated with the ’s Twitch token: - -```json -from typing import Annotated, Optional - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Twitch - - -@tool( - requires_auth=Twitch( - scopes=["channel:manage:polls"], - ) -) -async def create_poll( - context: ToolContext, - broadcaster_id: Annotated[ - str, - "The ID of the broadcaster to create the poll for.", - ], - title: Annotated[ - str, - "The title of the poll.", - ], - choices: Annotated[ - list[str], - "The choices of the poll.", - ], - duration: Annotated[ - int, - "The duration of the poll in seconds.", - ], -) -> Annotated[dict, "The poll that was created"]: - """Create a poll for a Twitch channel.""" - url = "https://api.twitch.tv/helix/polls" - headers = { - "Authorization": f"Bearer {context.authorization.token}", - "Client-Id": "your_client_id", - "Content-Type": "application/json", - } - payload = { - "broadcaster_id": broadcaster_id, - "title": title, - "choices": [{"title": choice} for choice in choices], - "duration": duration, - } - - async with httpx.AsyncClient() as client: - response = await client.post(url, headers=headers, json=payload) - response.raise_for_status() - return response.json() -``` - -Last updated on January 30, 2026 - -[TickTick](/en/references/auth-providers/ticktick.md) -[X](/en/references/auth-providers/x.md) diff --git a/public/_markdown/en/references/auth-providers/x.md b/public/_markdown/en/references/auth-providers/x.md deleted file mode 100644 index adda9de83..000000000 --- a/public/_markdown/en/references/auth-providers/x.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -title: "X" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -X - -# X - -The X enables tools and to call the X (Twitter) API on behalf of a . - -Want to quickly get started with X services in your or AI app? The pre-built [Arcade X MCP Server](/resources/integrations/social/x.md) is what you want! - -### What’s documented here - -This page describes how to use and configure X auth with Arcade. - -This is used by: - -- The [Arcade X MCP Server](/resources/integrations/social/x.md) - , which provides pre-built for interacting with X -- Your [app code](#using-x-auth-in-app-code) - that needs to call X APIs -- Or, your [custom tools](#using-x-auth-in-custom-tools) - that need to call X APIs - -## Configuring X auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own X app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your X app credentials, let’s go through the steps to create a X app. - -### Create an X app - -- Follow X’s guide to [creating an app](https://developer.x.com/en/docs/x-api/getting-started/getting-access-to-the-x-api) -   -- Enable authentication for your new app, and set its type to “Web App, Automated App or Bot” -- Set the redirect URL to the redirect URL generated by Arcade (see below) -- Copy the client ID and client secret to use below - -Next, add the X app to Arcade. - -## Configuring your own X Auth Provider in Arcade - -### Dashboard GUI - -### Configure X Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **X**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-x-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your X app. -- Note the **Redirect URL** generated by Arcade. This must be set as your X app’s redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require X auth using your Arcade credentials, Arcade will automatically use this X OAuth provider. If you have multiple X providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using X auth in app code - -Use the X in your own and AI apps to get a token for the X API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for X: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="x", - scopes=["tweet.read", "tweet.write", "users.read"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -let authResponse = await client.auth.start(userId, "x", [ - "tweet.read", - "tweet.write", - "users.read", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using X auth in custom tools - -You can use the pre-built [Arcade X MCP Server](/resources/integrations/social/x.md) to quickly build and AI apps that interact with X. - -If the pre-built tools in the X Server don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the X API. - -Use the `X()` auth class to specify that a requires authorization with X. The `context.authorization.token` field will be automatically populated with the ’s X token: - -```json -from typing import Annotated - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import X - - -@tool( - requires_auth=X( - scopes=["tweet.read", "tweet.write", "users.read"], - ) -) -async def post_tweet( - context: ToolContext, - tweet_text: Annotated[str, "The text content of the tweet you want to post"], -) -> Annotated[str, "Success string and the URL of the tweet"]: - """Post a tweet to X (Twitter).""" - url = "https://api.x.com/2/tweets" - headers = { - "Authorization": f"Bearer {context.authorization.token}", - "Content-Type": "application/json", - } - payload = {"text": tweet_text} - - async with httpx.AsyncClient() as client: - response = await client.post(url, headers=headers, json=payload) - response.raise_for_status() - - tweet_id = response.json()["data"]["id"] - return f"Tweet with id {tweet_id} posted successfully. URL: https://x.com/x/status/{tweet_id}" -``` - -Last updated on January 30, 2026 - -[Twitch](/en/references/auth-providers/twitch.md) -[Zendesk](/en/references/auth-providers/zendesk.md) diff --git a/public/_markdown/en/references/auth-providers/zendesk.md b/public/_markdown/en/references/auth-providers/zendesk.md deleted file mode 100644 index 4a4936edf..000000000 --- a/public/_markdown/en/references/auth-providers/zendesk.md +++ /dev/null @@ -1,215 +0,0 @@ ---- -title: "Zendesk" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Zendesk - -# Zendesk - -Arcade does not offer a default Zendesk . To use Zendesk auth, you must create a [custom provider configuration](/references/auth-providers/oauth2.md) as described below. - -The Zendesk enables tools and to call Zendesk APIs on behalf of a . - -## What’s documented here - -This page describes how to use and configure Zendesk auth with Arcade. - -This is used by: - -- The [Arcade Zendesk MCP Server](/resources/integrations/customer-support/zendesk.md) - , which provides pre-built for interacting with Zendesk services -- Your [app code](#using-zendesk-auth-in-app-code) - that needs to call Zendesk APIs -- Or, your [custom tools](#using-zendesk-auth-in-custom-tools) - that need to call Zendesk APIs - -## Create a Zendesk app - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -### Additional guides - -The following three guides from Zendesk will be helpful additional information as you progress through this guide: - -1. [Using OAuth authentication with your application](https://support.zendesk.com/hc/en-us/articles/4408845965210-Using-OAuth-authentication-with-your-application) -   -2. [Set up a global OAuth client](https://developer.zendesk.com/documentation/marketplace/building-a-marketplace-app/set-up-a-global-oauth-client/) -   -3. [Getting a trial or sponsored account for development](https://developer.zendesk.com/documentation/api-basics/getting-started/getting-a-trial-or-sponsored-account-for-development/) -   - -### Creating a Zendesk app for Arcade - -1. Create your Organization in the [Zendesk Marketplace portal](https://apps.zendesk.com/) -  . -2. Create a Zendesk support at [https://www.zendesk.com/login](https://www.zendesk.com/login) -   . If you need a global OAuth client, then the subdomain MUST begin with “d3v-”. You will need a global OAuth client if your app will use for multiple customers with their own Zendesk instances (multiple subdomains). -3. In [the Admin Center](https://support.zendesk.com/hc/en-us/articles/4581766374554#topic_hfg_dyz_1hb) -  , click “Apps and integrations” in the sidebar, then select APIs > OAuth clients > Add OAuth client. - - Ensure your identifier is prefixed with “zdg-” if you will need a global OAuth client. - - Select “Public” for “Client kind”. - - Use the redirect URL generated by Arcade (see below) as your “Redirect URL”. -4. Copy and store your identifier for later. This will be your **Client ID**. -5. Copy and store your generated secret for later. This will be your **Client Secret**. -6. (Only for Global OAuth client) Request a global OAuth client. - - First, you will need to request a sponsored and wait for approval from Zendesk. You can request a sponsored account [here](https://developer.zendesk.com/documentation/api-basics/getting-started/getting-a-trial-or-sponsored-account-for-development/#requesting-a-sponsored-test-account) -  . - - When filling out the sponsored request form, ensure you select “App Developer / ISV” as your Developer Type. - - After you have a sponsored , sign into the [Zendesk Marketplace portal](https://apps.zendesk.com/) -   - - Organization > Global OAuth Request and fill out the form. Zendesk will typically review your request within 1 week. - -## Get your Zendesk subdomain - -Your Zendesk subdomain is the value before the `.zendesk.com` part. For example, if your Zendesk domain is `https://d3v-acme-inc.zendesk.com`, your Zendesk subdomain is `d3v-acme-inc`. Take note of your Zendesk subdomain. You will need this value in the next steps. - -## Set the Zendesk Subdomain Secret - -Set the `ZENDESK_SUBDOMAIN` secret in the [Arcade Dashboard](https://api.arcade.dev/dashboard/auth/secrets) . - -## Configuring Zendesk Auth - -### Dashboard GUI - -### Configure Zendesk Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -Navigate to the [Arcade Dashboard](https://api.arcade.dev/dashboard/auth/oauth)  OAuth Providers page. - -#### Navigate to the Add Custom Provider page - -- Click **Add OAuth Provider** in the top right corner. -- Click the **Custom Provider** tab at the top. - -#### Enter the provider details - -- ID: `zendesk` -- Description: `` -- Client ID: `` (This is prefixed with `zdg-` if you are using a global OAuth client) -- Client Secret: `` -- Authorization Endpoint: `https://.zendesk.com/oauth/authorizations/new` -- Token Endpoint: `https://.zendesk.com/oauth/tokens` -- PKCE Settings: - - Enable PKCE: `enabled` - - PKCE Method: `S256` (Default) -- Authorization Settings: - - Response Type: `code` (Default) - - Scope: `{{scopes}} {{existing_scopes}}` (Default) -- Token Settings: - - Authentication Method: `Client Secret Basic` (Default) - - Response Content Type: `application/json` (Default) -- Refresh Token Settings: - - Refresh Token Endpoint: `https://.zendesk.com/oauth/tokens` - - Authentication Method: `Client Secret Basic` (Default) - - Response Content Type: `application/json` -- Token Introspection Settings: - - Enable Token Introspection: `disabled` (Default) - -Note the **Redirect URL** generated by Arcade. This must be set as your Zendesk app’s redirect URL. - -## Using Zendesk auth in app code - -Use the Zendesk you just created in your own and AI apps to get a token for Zendesk APIs. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for Zendesk APIs: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -# Start the authorization process -auth_response = client.auth.start( - user_id="{arcade_user_id}", - provider="zendesk", - scopes=["read_account"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token - -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -let authResponse = await client.auth.start(userId, { - provider: "zendesk", - scopes: ["read_account"], -}); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; - -// Do something interesting with the token... -``` - -## Using Zendesk auth in custom tools - -If the [Arcade Zendesk MCP Server](/resources/integrations/customer-support/zendesk.md) does not meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with Zendesk APIs. - -Use the `OAuth2()` auth class to specify that a requires authorization with Zendesk. The `context.authorization.token` field will be automatically populated with the ’s Zendesk token: - -```python -from typing import Annotated, Any - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - -import httpx - - -@tool( - requires_auth=OAuth2(id="zendesk", scopes=["read"]), - requires_secrets=["ZENDESK_SUBDOMAIN"], -) -async def get_tickets( - context: ToolContext -) -> Annotated[dict[str, Any], "Recent tickets from Zendesk"]: - """Get recent tickets from Zendesk including basic ticket information""" - token = context.get_auth_token_or_empty() - subdomain = context.get_secret("ZENDESK_SUBDOMAIN") - url = f"https://{subdomain}.zendesk.com/api/v2/tickets.json" - headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - "Accept": "application/json", - } - - async with httpx.AsyncClient() as client: - resp = await client.get(url, headers=headers) - resp.raise_for_status() - data = resp.json() - - return {"tickets": data} -``` - -Last updated on January 30, 2026 - -[X](/en/references/auth-providers/x.md) -[Zoho](/en/references/auth-providers/zoho.md) diff --git a/public/_markdown/en/references/auth-providers/zoho.md b/public/_markdown/en/references/auth-providers/zoho.md deleted file mode 100644 index f65243fc7..000000000 --- a/public/_markdown/en/references/auth-providers/zoho.md +++ /dev/null @@ -1,361 +0,0 @@ ---- -title: "Zoho" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Zoho - -# Zoho - -The Zoho enables tools and to call [Zoho APIs](https://www.zoho.com/developer/)  on behalf of a using OAuth 2.0 authentication. - -Want to quickly get started with Zoho in your agent or AI app? Check out the pre-built Arcade Zoho Servers: - [Zoho Books API MCP Server](/resources/integrations/payments/zoho-books-api.md) - -- Zoho Creator API Server (coming soon) - -### What’s documented here - -This page describes how to use and configure Zoho auth with Arcade. - -This is used by: - -- The [Arcade Zoho Books API MCP Server](/resources/integrations/payments/zoho-books-api.md) - , which provides pre-built for interacting with Zoho Books -- The Arcade Zoho Creator API Server (coming soon), which will provide pre-built for interacting with Zoho Creator -- Your [app code](#using-zoho-auth-in-app-code) - that needs to call Zoho APIs -- Or, your [custom tools](#using-zoho-auth-in-custom-tools) - that need to call Zoho APIs - -## Configuring Zoho auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Zoho app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Zoho app credentials, let’s go through the steps to create a Zoho app. - -### Create a Zoho app - -To integrate with Zoho’s APIs, you’ll need to set up OAuth 2.0 authentication by creating a client in the Zoho API Console: - -#### Access the Zoho API Console - -Navigate to the [Zoho API Console](https://api-console.zoho.com/)  and sign in with your existing Zoho credentials or create a new . - -#### Create a new client - -1. Once logged in, click on “Add Client” or “Get Started” -2. Select “Server-based Applications” as the client type -3. Fill in the required details: - - **Client Name**: Choose a descriptive name for your application - - **Homepage URL**: Provide the URL of your application’s website - - **Authorized Redirect URIs**: Add the redirect URL generated by Arcade (see configuration section below) - -#### Configure client settings - -1. After creating your client, you’ll receive a `Client ID` and `Client Secret` -2. Configure the required scopes for your application based on which Zoho services you’ll be using: - - For Zoho Books: `ZohoBooks.fullaccess.all` or specific scopes like `ZohoBooks.invoices.READ` - - For Zoho Creator: `ZohoCreator.meta.READ`, `ZohoCreator.report.READ`, etc. - - Add other scopes as needed for your use case - -#### Note your data center - -Zoho operates in multiple data centers. Make note of which data center your uses, as this affects the API endpoints: - -- **US**: `.com` (e.g., `https://accounts.zoho.com`) -- **EU**: `.eu` (e.g., `https://accounts.zoho.eu`) -- **India**: `.in` (e.g., `https://accounts.zoho.in`) -- **Australia**: `.com.au` (e.g., `https://accounts.zoho.com.au`) -- **China**: `.com.cn` (e.g., `https://accounts.zoho.com.cn`) - -For detailed instructions, refer to Zoho’s official documentation: - -- [Zoho OAuth 2.0 Authentication](https://www.zoho.com/accounts/protocol/oauth.html) -   -- [Zoho Books API Documentation](https://www.zoho.com/books/api/v3/) -   -- [Zoho Creator API Documentation](https://www.zoho.com/creator/help/api/v2/) -   - -Next, add the Zoho app to Arcade. - -## Configuring your own Zoho Auth Provider in Arcade - -### Dashboard GUI - -### Configure Zoho Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **OAuth 2.0** tab at the top. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “arcade-zoho”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Zoho client. -- Configure the OAuth 2.0 endpoints (adjust the domain based on your data center): - - **Authorization URL**: `https://accounts.zoho.com/oauth/v2/auth` (or `.eu`, `.in`, `.com.au`, `.com.cn`) - - **Token URL**: `https://accounts.zoho.com/oauth/v2/token` (or `.eu`, `.in`, `.com.au`, `.com.cn`) -- Note the **Redirect URL** generated by Arcade. This must be set as your Zoho client’s authorized redirect URI. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -### Configuration File - -### Configure Zoho Auth Using Configuration File - -This method is only available when you are \[self-hosting the engine\](/guides/deployment-hosting/on-prem - -#### Set environment variables - -Set the following environment variables: - -```bash -export ZOHO_CLIENT_ID="" -export ZOHO_CLIENT_SECRET="" -``` - -Or, you can set these values in a `.env` file: - -```bash -ZOHO_CLIENT_ID="" -ZOHO_CLIENT_SECRET="" -``` - -#### Edit the Engine configuration - -Edit the `engine.yaml` file and add a new item to the `auth.providers` section. - -For US data center: - -```yaml -auth: - providers: - - id: arcade-zoho - description: Zoho OAuth 2.0 provider - enabled: true - type: oauth2 - client_id: ${env:ZOHO_CLIENT_ID} - client_secret: ${env:ZOHO_CLIENT_SECRET} - oauth2: - scope_delimiter: "," - authorize_request: - endpoint: "https://accounts.zoho.com/oauth/v2/auth" - params: - response_type: code - client_id: "{{client_id}}" - redirect_uri: "{{redirect_uri}}" - scope: "{{scopes}}" - state: "{{state}}" - access_type: offline - prompt: consent - token_request: - endpoint: "https://accounts.zoho.com/oauth/v2/token" - auth_method: client_secret_post - params: - grant_type: authorization_code - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - redirect_uri: "{{redirect_uri}}" - response_content_type: application/json - refresh_request: - endpoint: "https://accounts.zoho.com/oauth/v2/token" - auth_method: client_secret_post - params: - grant_type: refresh_token - client_id: "{{client_id}}" - client_secret: "{{client_secret}}" - response_content_type: application/json -``` - -If your Zoho is in a different data center (EU, India, Australia, or China), replace `accounts.zoho.com` with the appropriate domain: - EU: `accounts.zoho.eu` - India: `accounts.zoho.in` - Australia: `accounts.zoho.com.au` - China: `accounts.zoho.com.cn` - -When you use tools that require Zoho auth using your Arcade credentials, Arcade will automatically use this Zoho OAuth provider. If you have multiple Zoho providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Zoho auth in app code - -Use the Zoho in your own and AI apps to get a token for Zoho APIs. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for Zoho APIs: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="arcade-zoho", - scopes=["ZohoBooks.fullaccess.all"] -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); - -const userId = "{arcade_user_id}"; - -// Start the authorization process -const authResponse = await client.auth.start(userId, "arcade-zoho", [ - "ZohoBooks.fullaccess.all", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Zoho auth in custom tools - -You can use the pre-built Arcade Zoho Servers ([Zoho Books](/resources/integrations/payments/zoho-books-api.md), Zoho Creator (coming soon)) to quickly build and AI apps that interact with Zoho. - -If the pre-built don’t meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with Zoho APIs. - -Use the `OAuth2()` auth class to specify that a requires authorization with Zoho. The `context.authorization.token` field will be automatically populated with the ’s Zoho token: - -```python -from typing import Annotated - -import httpx -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 - - -@tool( - requires_auth=OAuth2( - provider_id="arcade-zoho", - scopes=["ZohoBooks.invoices.READ"] - ) -) -async def get_zoho_invoices( - context: ToolContext, - organization_id: Annotated[str, "The Zoho Books organization ID."], -) -> Annotated[dict, "The list of invoices."]: - """ - Retrieve invoices from Zoho Books. - """ - url = f"https://books.zoho.com/api/v3/invoices?organization_id={organization_id}" - headers = { - "Authorization": f"Zoho-oauthtoken {context.authorization.token}", - } - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return dict(response.json()) -``` - -## Available Scopes - -Zoho supports various OAuth scopes that determine the level of access your application has. Scopes vary by Zoho service: - -### Zoho Books Scopes - -- `ZohoBooks.fullaccess.all` - Full access to all Zoho Books data -- `ZohoBooks.invoices.READ` - Read access to invoices -- `ZohoBooks.invoices.CREATE` - Create invoices -- `ZohoBooks.invoices.UPDATE` - Update invoices -- `ZohoBooks.invoices.DELETE` - Delete invoices -- `ZohoBooks.contacts.READ` - Read access to contacts -- `ZohoBooks.contacts.CREATE` - Create contacts -- And many more… - -### Zoho Creator Scopes - -- `ZohoCreator.meta.READ` - Read access to application metadata -- `ZohoCreator.report.READ` - Read access to reports -- `ZohoCreator.report.CREATE` - Create records in reports -- `ZohoCreator.report.UPDATE` - Update records in reports -- `ZohoCreator.report.DELETE` - Delete records from reports -- And many more… - -For a complete list of available scopes, refer to the official documentation: - -- [Zoho Books API Scopes](https://www.zoho.com/books/api/v3/oauth/#scopes) -   -- [Zoho Creator API Scopes](https://www.zoho.com/creator/help/api/v2/oauth-scopes.html) -   - -## Data Center Considerations - -Zoho operates in multiple data centers around the world. When making API calls, you must use the correct domain for your ’s data center: - -Data Center - -Accounts Domain - -API Domain Example - -US - -`accounts.zoho.com` - -`books.zoho.com`, `creator.zoho.com` - -EU - -`accounts.zoho.eu` - -`books.zoho.eu`, `creator.zoho.eu` - -India - -`accounts.zoho.in` - -`books.zoho.in`, `creator.zoho.in` - -Australia - -`accounts.zoho.com.au` - -`books.zoho.com.au`, `creator.zoho.com.au` - -China - -`accounts.zoho.com.cn` - -`books.zoho.com.cn`, `creator.zoho.com.cn` - -Make sure to configure your OAuth provider and API calls to use the correct domain for your ’s data center. - -Last updated on January 30, 2026 - -[Zendesk](/en/references/auth-providers/zendesk.md) -[Zoom](/en/references/auth-providers/zoom.md) diff --git a/public/_markdown/en/references/auth-providers/zoom.md b/public/_markdown/en/references/auth-providers/zoom.md deleted file mode 100644 index 6abf225fe..000000000 --- a/public/_markdown/en/references/auth-providers/zoom.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: "Zoom" -description: "Arcade - AI platform for developers" ---- -[Auth Providers](/en/references/auth-providers.md) -Zoom - -# Zoom - -At this time, Arcade does not offer a default Zoom . To use Zoom auth, you must create a custom Auth Provider with your own Zoom OAuth 2.0 credentials as described below. - -The Zoom enables tools and to call the Zoom API on behalf of a . - -### What’s documented here - -This page describes how to use and configure Zoom auth with Arcade. - -This is used by: - -- Your [app code](#using-zoom-auth-in-app-code) - that needs to call Zoom APIs -- Or, your [custom tools](#using-zoom-auth-in-custom-tools) - that need to call Zoom APIs - -## Configuring Zoom auth - -When using your own app credentials, make sure you configure your to use a [custom user verifier](/guides/user-facing-agents/secure-auth-production.md#build-a-custom-user-verifier). Without this, your end-users will not be able to use your app or in production. - -In a production environment, you will most likely want to use your own Zoom app credentials. This way, your will see your application’s name requesting permission. - -Before showing how to configure your Zoom app credentials, let’s go through the steps to create a Zoom app. - -### Create a Zoom app - -- Follow Zoom’s guide to [registering an app](https://developers.zoom.us/docs/integrations/create/) -   on the Zoom marketplace -- Set the redirect URL to the redirect URL generated by Arcade (see below) and enable Strict Mode -- Enable the Zoom features and permissions (scopes) that your app needs -- Copy the client ID and client secret to use below - -Next, add the Zoom app to Arcade. - -## Configuring your own Zoom Auth Provider in Arcade - -### Dashboard GUI - -### Configure Zoom Auth Using the Arcade Dashboard GUI - -#### Access the Arcade Dashboard - -To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard) . If you are self-hosting, by default the dashboard will be available at [http://localhost:9099/dashboard](http://localhost:9099/dashboard) . Adjust the host and port number to match your environment. - -#### Navigate to the OAuth Providers page - -- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. -- Click **Add OAuth Provider** in the top right corner. -- Select the **Included Providers** tab at the top. -- In the **Provider** dropdown, select **Zoom**. - -#### Enter the provider details - -- Choose a unique **ID** for your provider (e.g. “my-zoom-provider”). -- Optionally enter a **Description**. -- Enter the **Client ID** and **Client Secret** from your Zoom app. -- Note the **Redirect URL** generated by Arcade. This must be set as your Zoom app’s redirect URL. - -#### Create the provider - -Hit the **Create** button and the provider will be ready to be used. - -When you use tools that require Zoom auth using your Arcade credentials, Arcade will automatically use this Zoom OAuth provider. If you have multiple Zoom providers, see [using multiple auth providers of the same type](/references/auth-providers.md#using-multiple-providers-of-the-same-type) for more information. - -## Using Zoom auth in app code - -Use the Zoom in your own and AI apps to get a token for the Zoom API. See [authorizing agents with Arcade](/get-started/about-arcade.md) to understand how this works. - -Use `client.auth.start()` to get a token for the Zoom API: - -### Python - -```python -from arcadepy import Arcade - -client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable - -user_id = "{arcade_user_id}" - -# Start the authorization process -auth_response = client.auth.start( - user_id=user_id, - provider="zoom", - scopes=["meeting:read:list_upcoming_meetings"], -) - -if auth_response.status != "completed": - print("Please complete the authorization challenge in your browser:") - print(auth_response.url) - -# Wait for the authorization to complete -auth_response = client.auth.wait_for_completion(auth_response) - -token = auth_response.context.token -# Do something interesting with the token... -``` - -### JavaScript - -```javascript -import { Arcade } from "@arcadeai/arcadejs"; - -const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable - -const userId = "{arcade_user_id}"; - -// Start the authorization process -let authResponse = await client.auth.start(userId, "zoom", [ - "meeting:read:list_upcoming_meetings", -]); - -if (authResponse.status !== "completed") { - console.log("Please complete the authorization challenge in your browser:"); - console.log(authResponse.url); -} - -// Wait for the authorization to complete -authResponse = await client.auth.waitForCompletion(authResponse); - -const token = authResponse.context.token; -// Do something interesting with the token... -``` - -## Using Zoom auth in custom tools - -You can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server.md) that interact with the Zoom API. - -Use the `Zoom()` auth class to specify that a requires authorization with Zoom. The `context.authorization.token` field will be automatically populated with the ’s Zoom token: - -```python -from typing import Annotated, Optional - -import httpx - -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import Zoom - - -@tool( - requires_auth=Zoom( - scopes=["meeting:read:list_upcoming_meetings"], - ) -) -async def list_upcoming_meetings( - context: ToolContext, - user_id: Annotated[ - Optional[str], - "The user's user ID or email address. Defaults to 'me' for the current user.", - ] = "me", -) -> Annotated[dict, "List of upcoming meetings within the next 24 hours"]: - """List a Zoom user's upcoming meetings within the next 24 hours.""" - url = f"https://api.zoom.us/v2/users/{user_id}/upcoming_meetings" - headers = {"Authorization": f"Bearer {context.authorization.token}"} - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - return response.json() -``` - -Last updated on January 30, 2026 - -[Zoho](/en/references/auth-providers/zoho.md) diff --git a/public/_markdown/en/references/changelog.md b/public/_markdown/en/references/changelog.md deleted file mode 100644 index 87b7f520c..000000000 --- a/public/_markdown/en/references/changelog.md +++ /dev/null @@ -1,689 +0,0 @@ ---- -title: "Changelog" -description: "What's new at Arcade.dev" ---- -Changelog - -# Changelog - -_Here’s what’s new at Arcade.dev!_ - -## 2026-02-20 - -Contextual Access for Executions is Live! Learn more [here](/guides/contextual-access.md) - -**Arcade Servers** - -- `[feature - 🚀]` Add Tool Metadata for Servers -- `[feature - 🚀]` Add Attio Server -- `[documentation - 📝]` Arcade Server documentation now includes per- scope documentation and tool to help build needed OAuth Provide scopes - -**Platform and Engine** - -- `[feature - 🚀]` Audit logs clear filters button -- `[maintenance - 🔧]` Cleanup and DI improvements - -**Misc** - -- `[documentation - 📝]` Update Salesforce docs with new External Client App (\[docs PR #761\]) -- `[documentation - 📝]` OpenAI tutorial rewrite (\[docs PR #743\]) -- `[documentation - 📝]` Consolidate Google ADK tutorials and add TypeScript setup (\[docs PR #746\]) -- `[documentation - 📝]` Fixing the links in the framework overview (\[docs PR #772\]) - -## 2026-02-06 - -**Arcade Servers** - -- `[maintenance - 🔧]` Fix Google Docs Edit to work with documents that have multiple tabs -- `[maintenance - 🔧]` Improve LLM Instructions for Google Drive file picker - -**Platform and Engine** - -- `[feature - 🚀]` Batch Reconcile -- `[feature - 🚀]` Improve gateway error message -- `[maintenance - 🔧]` Hide OAuth UI when server is already authorized (Dashboard) -- `[feature - 🚀]` Added grouped overview for toolkits (Dashboard) - -**Misc** - -- `[documentation - 📝]` Editorial improvements for Windows environment setup page -- `[documentation - 📝]` Add clean markdown generation for LLM-friendly page content -- `[documentation - 📝]` Public data storage information -- `[documentation - 📝]` Rename `Starter tools` to `Unoptimized tools` -- `[documentation - 📝]` Add documentation for remote servers - -## 2026-01-23 - -**Arcade Servers** - -- `[feature - 🚀]` Launched `https://ctl.arcade.dev/mcp` - Arcade’s Gateway Assistant. Connect your LLM to help build Gateways and for any use case. Learn more about it [here](/guides/mcp-gateways/create-via-ai.md) - ! - -**Platform and Engine** - -- `[maintenance - 🔧]` Fixed a race condition in `arcade deploy` - -**Misc** - -- `[documentation - 📝]` Updated OpenAI guide in Python -- `[documentation - 📝]` Fixed \-frameworks page displaying as raw code -- `[documentation - 📝]` Quickstart now walks through setting up a `uv` -- `[documentation - 📝]` Added connecting arcade to your llm page -- `[documentation - 📝]` Added Copilot Studio docs - -## 2026-01-16 - -**Arcade Servers** - -- `[feature - 🚀]` [`arcade-mcp`](https://github.com/ArcadeAI/arcade-mcp) - Support Ed25519 Algorithm -- `[bugfix - 🐛]` [`arcade-mcp`](https://github.com/ArcadeAI/arcade-mcp) - Fix dateutil dependency issue -- `[bugfix - 🐛]` [`arcade-mcp`](https://github.com/ArcadeAI/arcade-mcp) - Fix PostHog dependency issue - -**Platform and Engine** - -- `[bugfix - 🐛]` fix: Allow long custom verifier URLs -- `[feature - 🚀]` Add Dashboard support for expiring - -## 2026-01-09 - -We’ve dramatically revamped our documentation to focus on making it easier to get started with Arcade. Update your links and let us know what you think [here](/resources/contact-us.md)! - - Gateways now support OAuth! Learn more about it [here](/guides/mcp-gateways.md)! - -**Arcade Servers** - -- `[feature - 🚀]` Add support for Arcade Evals on Servers -- `[maintenance - 🔧]` Replace fcntl with cross-platform portalocker to fix Windows/Powershell errors - -**Platform and Engine** - -- `[feature - 🚀]` Optional image URL and hex color for organizations and -- `[feature - 🚀]` Support for listing members and invited users for -- `[maintenance - 🔧]` Allows the same secret in multiple -- `[feature - 🚀]` OAuth for Gateways - -**Toolkits** - -- `[feature - 🚀]` \[PagerDuty\] Optimized Toolkit -- `[feature - 🚀]` \[Pylon\] Starter Toolkit -- `[feature - 🚀]` \[Google contacts\] phone numbers support -- `[feature - 🚀]` \[Gmail\] Improved performance and conversion to App -- `[feature - 🚀]` \[Google Sheets\] Bug fix and App update -- `[maintenance - 🔧]` `arcade-mcp` Fix typing by using typing\_extensions - -## 2025-12-12 - -**Arcade Servers** - -- `[feature - 🚀]` OAuth authentication for `arcade-mcp` servers. Learn more about it \[here\](/guides/security/secure-your-\-server! -- `[maintenance - 🔧]` Ability to run multiple uvicorn workers -- `[maintenance - 🔧]` Include type annotations for `arcade_mcp_server` - -**Arcade CLI** - -- `[feature - 🚀]` Support multiple orgs & in Arcade’s CLI. Learn more about it [here](/references/arcade-cli.md) - ! - -**Platform and Engine** - -- `[bugfix - 🐛]` Idempotent invite acceptance - -**Toolkits** - -- `[feature - 🚀]` Support phone numbers in Google contacts -- `[feature - 🚀]` Support downloading and uploading files to Google Drive -- `[feature - 🚀]` Figma Optimized Toolkit -- `[bugfix - 🐛]` Fix bugs with bad data types in Jira and Confluence -- `[maintenance - 🔧]` Gmail list enforce page-size limits - -## 2025-12-05 - -[A medium-severity security vulnerability](https://github.com/ArcadeAI/arcade-mcp/security/advisories/GHSA-g2jx-37x6-6438)  has been identified and fixed in the Arcade . Please upgrade to version 1.9.1 or higher of `arcade-mcp-server` to fix this issue. - -As of December 1, 2025, we have migrated the servers deployed via `arcade deploy` to our own managed infrastructure. Please ensure you have the latest version of the arcade CLI installed and that you are using the latest version of the `arcade-mcp-server` package. - -**Arcade Servers** - -- `[feature - 🚀]` Add tools for project management, pull request, and projects to the Github Server -- `[feature - 🚀]` Add Optimized Linear toolkit -- `[feature - 🚀]` Add Optimized Ashby toolkit -- `[feature - 🚀]` Shorten Jira names exceeding Cursor limit -- `[feature - 🚀]` Host both the latest and previous major version of optimized Arcade toolkits for backwards compatibility - -**Arcade ** - -- `[feature - 🚀]` Add startup warnings for missing secrets -- `[bugfix - 🐛]` Handle client disconnect for large payloads -- `[bugfix - 🐛]` Only serve worker endpoints if `ARCADE_WORKER_SECRET` environment variable is set -- `[maintenance - 🔧]` Increase Worker Termination Grace Period - -**Arcade CLI** - -- `[feature - 🚀]` CLI config and WhoAmI endpoints - -**Platform and Engine** - -- `[feature - 🚀]` New users can be invited to projects by email, regardless of whether they have an on Arcade, or already belong to your organization. -- `[maintenance - 🔧]` Allows users to update organization and names\\ - -## 2025-11-21 - -** Servers** - -- `[feature - 🚀]` Updated Github Sever to support , issues, and pull requests - -**Platform and Engine** - -- `[feature - 🚀]` Invite users to by email - -## 2025-11-14 - -** Servers** - -- `[feature - 🚀]` Customer.io Starter Servers added -- `[feature - 🚀]` Intercom Starter Server added - -**Arcade ** - -- `[maintenance - 🔧]` Do not require entrypoint for `arcade configure` for HTTP server - -**Platform and Engine** - -- `[maintenance - 🔧]` Update `arcade deploy` command to support Servers built with `arcade-mcp` -- `[maintenance - 🔧]` Improve performance of execution with large collections of tools - -## 2025-11-07 - -**Toolkits** - -- `[feature - 🚀]` AddedMailchimp market toolkit -- `[feature - 🚀]` Enhanced Hubspot Marketing & CRM toolkit - -** Servers** - -- `[maintenance - 🔧]` Better Handling of \-specific `Context` usage for managed servers -- `[maintenance - 🔧]` Set server version for `@app.tool` and `MCPApp.add_tool` -- `[maintenance - 🔧]` Better errors in UI and CLI if `arcade deploy` fails t **Platform and Engine** -- `[feature - 🚀]` Optional customization of OAuth request header format for upstrem Servers -- `[bugfix - 🐛]` Fix token refresh -- `[maintenance - 🔧]` Add log viewing for managed Servers - -**Misc** - -- `[documentation - 📝]` Fix site search - -## 2025-10-31 - -**Toolkits** - -- `[feature - 🚀]` Added new HubSpot Marketing & CRM starter -- `[feature - 🚀]` Added Exa.ai Starter Server -- `[feature - 🚀]` Added Asana starter toolkit -- `[feature - 🚀]` Added Github starter toolkit -- `[feature - 🚀]` Added Pylon Starter Toolkit -- `[feature - 🚀]` Added Posthog Starter Toolkit -- `[feature - 🚀]` Added Clickup Starter Toolkit - -**CLI and TDK** - -- `[feature - 🚀]` `arcade deploy` CLI Command - -**Platform and Engine** - -- `[feature - 🚀]` Add non-root to platform image for improved security - -**Misc** - -- `[documentation - 📝]` Fix reference examples - -## 2025-10-24 - -**Toolkits** - -- `[feature - 🚀]` \[Toolkits/Ticktick\] Added Ticktick Starter Toolkit -- `[feature - 🚀]` \[Toolkits/Weaviate\] Added Weaviate Starter Toolkit -- `[feature - 🚀]` \[Toolkits/Vercel\] Added Vercel Starter Toolkit -- `[feature - 🚀]` \[Toolkits/Datadog\] Added Datadog Starter Toolkit -- `[feature - 🚀]` \[Toolkits/Freshservice\] New Freshservice with complex objects handling - -**Platform and Engine** - -- `[feature - 🚀]` Dashboard: Add redirect\_uri to Servers -- `[feature - 🚀]` Dashboard: Add OAuth fields to Servers - -## 2025-10-17 - -We’ve updated our documentation to be more clear, consistent, and easier to navigate. This includes updated quickstarts, guides, and reference information. [Let us know what you think](/resources/contact-us.md)! - -This week we released `arcade-mcp`, the best way to build Servers. `arcade-mcp` supersedes the Arcade TDK. Learn more about it [here](/get-started/quickstarts/mcp-server-quickstart.md)! Detailed reference information for `arcade-mcp` is available [here](/references/mcp/python.md). - -This week Gateways are now generally available! allow you to federate the tools from multiple into a single collection for easier management, control, and access. Learn more about them \[here\](/guides/create-/mcp-gateways! - -This week projects are now generally available! Projects are a new way to organize your Servers, , and secrets for easier management, control, and access. - -**Toolkits** - -- `[feature - 🚀]` `arcade-mcp` is now generally available! Learn more about it [here](/get-started/quickstarts/mcp-server-quickstart.md) - ! -- `[feature - 🚀]` \[Toolkits/BrightData\] Added BrightData Toolkit -- `[feature - 🚀]` \[Toolkits/Figma\] Added Figma Starter Server -- `[feature - 🚀]` \[Toolkits/Freshservice\] Added Freshservice Starter Server -- `[feature - 🚀]` \[Toolkits/Cursor Agents\] Added Cursor Agents Starter Server -- `[feature - 🚀]` \[Toolkits/AirTable\] Added AirTable starter Server -- `[feature - 🚀]` \[Toolkits/Miro\] Added Miro Starter Server -- `[feature - 🚀]` \[Toolkits/PagerDuty\] Added PagerDuty Starter Server -- `[feature - 🚀]` `arcade deploy` for Servers built with `arcade-mcp` - -**Platform and Engine** - -- `[feature - 🚀]` Dashboard: Allow OAuth on Servers -- `[feature - 🚀]` Gateways are now generally available! Learn more about them \[here\](/guides/create-/mcp-gateways! -- `[feature - 🚀]` are now generally available. -- `[maintenance - 🔧]` Support remote servers which require DCR (dynamic client registration). - -**Misc** - -- `[documentation - 📝]` Updated documentation to be more clear, consistent, and easier to navigate. This includes updated quickstarts, guides, and reference information. -- `[documentation - 📝]` `llms.txt` is now kept up to date and simplified. We’ve also added a new section to the docs for [agentic development](/get-started/setup/connect-arcade-docs.md) - . - -## 2025-10-10 - -**Toolkits** - -- `[feature - 🚀]` \[Toolkits/Trello\] Added Trello -- `[feature - 🚀]` \[Toolkits/Calendly\] Added Calendly starter toolkit -- `[feature - 🚀]` \[Toolkits/SquareUp\] Added SquareUp toolkit -- `[feature - 🚀]` \[Toolkits/Xero\] Xero API Starter server - -** Servers** - -- `[feature - 🚀]` Added reference area to `arcade-mcp` docs ([PR #488](https://github.com/ArcadeAI/docs/pull/488) -  ) - -**Platform and Engine** - -- `[feature - 🚀]` \[Engine/OAuth\] Adding SquareUp OAuth - -**Platform and Engine** - -- `[bugfix - 🔧]` Dashboard: Hide edit and delete button text in mobile - -## 2025-10-03 - -**Toolkits** - -- `[feature - 🚀]` Box.com Starter Server released ([docs](/resources/integrations/productivity/boxapi.md) - ) -- `[feature - 🚀]` Stripe Starter Server released ([docs](/resources/integrations/payments/stripeapi.md) - ) - -**Misc** - -- `[documentation - 📝]` Add FAQ explaining personal vs - -## 2025-09-26 - -**Toolkits** - -- `[feature - 🚀]` Introduce [Unoptimized tools](/guides/create-tools/improve/types-of-tools.md) - , a new type of that mirrors the original HTTP API design of the upstream service. -- `[feature - 🚀]` Release Slack started Server which contains support for most of the Slack API. -- `[feature - 🚀]` Include advanced error handling in the following Servers: Google, Microsoft, Slack, and Asana. Learn more about handling errors [here](/guides/create-tools/error-handling/useful-tool-errors.md) - . -- `[bugfix - 🐛]` \[ Servers/MS Teams\] Fix get\_chat\_metadata by chat’s -- `[feature - 🚀]` \[ Servers/confluence\] Adding WhoAmI for Confluence - -**CLI and TDK** - -- `[bugfix - 🐛]` Fix reference in `arcade docs` Python example template to USER\_ID instead of TOOL\_NAME - -**Misc** - -- `[documentation - 📝]` Documents API wrapper vs LLM-native Servers; includes Slack API wrapper docs - -## 2025-09-19 - -**Toolkits** - -- `[feature - 🚀]` \[Toolkits/ClickUp\] Removing no content additional messages in Evals -- `[feature - 🚀]` \[Toolkits/MongoDB\] Add analytics MongoDB Server ([PR #548](https://github.com/ArcadeAI/arcade-ai/pull/548) -  ) -- `[feature - 🚀]` \[ Servers/HubSpot\] Adding HubSpot enhancements ([PR #441](https://github.com/ArcadeAI/docs/pull/441) -  ) - -**CLI and TDK** - -- `[maintenance - 🔧]` Update Mastra example Server - -**Misc** - -- `[documentation - 📝]` Term consistency ([PR #445](https://github.com/ArcadeAI/docs/pull/445) -  ) -- `[documentation - 📝]` Update Error Handling ([PR #438](https://github.com/ArcadeAI/docs/pull/438) -  ) -- `[maintenance - 🔧]` Update Mastra example docs to better match the example repo ([PR #444](https://github.com/ArcadeAI/docs/pull/444) -  ) - -## 2025-09-12 - -**CLI and TDK** - -- `[feature - 🚀]` Added support for multiple types of errors from , and updated client libraries to aid in disambiguating rate-limiting and other forms of upstream errors ([Docs](https://github.com/ArcadeAI/docs/pull/438/files) -  ). Added in v1.10.0 in `aracde-js`, v1.8.0 in `aracde-py`, and v0.1.0-alpha.6 in `aracde-go`. -- `[maintenance - 🔧]`Update langchain version for Arcade integrations - -**Toolkits** - -- `[feature - 🚀]` Google Calendar improvements to video call scheduling ([Docs](https://github.com/ArcadeAI/docs/pull/436) -  ) -- `[feature - 🚀]` \[ Servers/Jira\] Added `WhoAmI` tool to Jira, Google, Clickup, Slack, and Zendesk ([Docs](https://github.com/ArcadeAI/docs/pull/426) -  ) - -**Platform and Engine** - -- `[bugfix - 🐛]` Engine: Fix rate limiting algorithm -- `[feature - 🚀]` Engine: Improve Error Handling - -**Misc** - -- `[documentation - 📝]` Add a FAQ for requesting over-scoped permissions for Google Drive and similar ([docs PR #440](https://github.com/ArcadeAI/docs/pull/440) -  ) - -## 2025-09-05 - -**Toolkits** - -- `[feature - 🚀]` Imgflip Server: for memes ([docs PR #424](https://github.com/ArcadeAI/docs/pull/424) -  ) -- `[feature - 🚀]` Edit Google Document ([docs PR #427](https://github.com/ArcadeAI/docs/pull/427) -  ) -- `[bugfix - 🐛]` \[Toolkits/clickup\] fix fuzzy match search - -**Platform and Engine** - -- `[maintenance - 🔧]` Engine: updated stainless to generate SDK specs -- `[feature - 🚀]` Dashboard: New sidebar and user-verification page & prepare for \-based resources - -**CLI and TDK** - -- `[maintenance - 🔧]` upgraded langchain\_arcade ([PR #546](https://github.com/ArcadeAI/arcade-ai/pull/546) -  ) - -**Misc** - -- `[documentation - 📝]` Adding ClickUp documentation ([PR #413](https://github.com/ArcadeAI/docs/pull/413) -  ) -- `[documentation - 📝]` updated instructions on GH OAuth customization ([PR #425](https://github.com/ArcadeAI/docs/pull/425) -  ) - -## 2025-08-29 - -**Toolkits** - -- `[feature - 🚀]` Re-add GoogleNews Server - -**Platform and Engine** - -- `[feature - 🚀]` Dashboard: Update Server and selection UI in playground -- `[feature - 🚀]` Dashboard: Add Servers and OAuth providers from the design system -- `[feature - 🚀]` Dashboard: Add optional request parameters when adding OAuth providers - -**CLI and TDK** - -- `[feature - 🚀]` Improve Typedict and Basemodel support ([PR #523](https://github.com/ArcadeAI/arcade-ai/pull/523) -  ) - -**Misc** - -- `[documentation - 📝]` Add ClickUp documentation ([PR #404](https://github.com/ArcadeAI/docs/pull/404) -  ) -- `[documentation - 📝]` Fix glossary: change ‘Authentication Scope’ to ‘’ ([PR #419](https://github.com/ArcadeAI/docs/pull/419) -  ) -- `[documentation - 📝]` Added missing parameter to the usage example templates ([PR #537](https://github.com/ArcadeAI/arcade-ai/pull/537) -  ) - -## 2025-08-22 - -This week we released a new pricing model for Arcade which will be better for hobbyists and enterprises alike. Learn more here: [https://blog.arcade.dev/pricing-updates](https://blog.arcade.dev/pricing-updates)  - -**Toolkits** - -- `[feature - 🚀]` \[X (Twitter)\] Reply to Tweet ([PR #415](https://github.com/ArcadeAI/docs/pull/415) -  ) -- `[feature - 🚀]` \[Jira Toolkit\] Add “Add To Sprint” and “Remove from Sprint” ([PR #412](https://github.com/ArcadeAI/docs/pull/412) -  ) -- `[bugfix - 🐛]` \[Google Drive, Docs, Sheets, Slides Toolkits\] Remove file picker url from response - -**Platform and Engine** - -- `[feature - 🚀]` Arcade Cloud: New pricing model -- `[feature - 🚀]` Authenticate communication between Engine and Coordinator via key exchange -- `[feature - 🚀]` Engine: Add additional redis cert check options - -## 2025-08-15 - -This week we enforced a new requirement for all OAuth providers: they must have a unique callback URL. This is a minor security change, but does require you to update your OAuth configuration. This can be updated from the dashboard. - -**Toolkits** - -- `[feature - 🚀]` Sharepoint Toolkit added ([docs](/resources/integrations/productivity/sharepoint.md) - ) -- `[feature - 🚀]` Google Slides Toolkit added -- `[feature - 🚀]` Commenting on Google Docs added -- `[bugfix - 🐛]` Improvements in Microsoft Teams message search for better agentic experience. Fix bug when no messages match the search query. -- `[bugfix - 🐛]` Fix bugs in Google Workspace search - -**Platform and Engine** - -- `[feature - 🚀]` Custom OAuth providers now require a unique callback URL -- `[bugfix - 🐛]` Engine: Resolve dynamic provider IDs when checking auth status -- `[bugfix - 🐛]` Engine: Refresh token when checking the status of a completed request - -**Misc** - -- `[documentation - 📝]` Document Microsoft scopes required by Arcade Servers ([PR #409](https://github.com/ArcadeAI/docs/pull/409) -  ) -- `[documentation - 📝]` Microsoft SharePoint Server documentation ([PR #400](https://github.com/ArcadeAI/docs/pull/400) -  ) - -## 2025-08-08 - -**Toolkits** - -- `[feature - 🚀]` Clickhouse Toolkit ([PR #527](https://github.com/ArcadeAI/arcade-ai/pull/527) -  ) -- `[feature - 🚀]` Add search to Google Drive -- `[bugfix - 🐛]` Fix and docstring improvement in MS Teams Server - -**Platform and Engine** - -- `[feature - 🚀]` Add support for GPT-5 models -- `[feature - 🚀]` Per-app redirect URI info - -## 2025-08-01 - -**Toolkits** - -- `[feature - 🚀]` Microsoft Teams Server added -- `[feature - 🚀]` Jira Toolkit: Add List Sprints & Boards -- `[feature - 🚀]` Google Sheets Server: Add pagination to GetSpreadsheet -- `[bugfix - 🐛]` Jira Server: Return UI URL for items again -- `[feature - 🚀]` Salesforce Server: Configure subdomain & max concurrency through secrets -- `[feature - 🚀]` Confluence Server supports Atlassian multi-cloud - -**CLI and TDK** - -- `[bugfix - 🐛]` Fixes for the CLI docs generator ([PR #524](https://github.com/ArcadeAI/arcade-ai/pull/524) -  ) -- `[feature - 🚀]` CLI: Rename auto-docs command to ‘docs’ and other improvements ([PR #518](https://github.com/ArcadeAI/arcade-ai/pull/518) -  ) - -## 2025-07-25 - -Most Arcade Servers have been removed from the `github.com/ArcadeAI/arcade-ai` repository, and transitioned to closed-source. Toolkit source code remains available upon request for our paying customers. This enables us to iterate more quickly and provide a better experience for our customers. The previously open-sourced are still available in the public repository’s git history. - -**Toolkits** - -- `[feature - 🚀]` Support for multiple Atlassian Clouds in the Jira Toolkit ([PR #506](https://github.com/ArcadeAI/arcade-ai/pull/506) -  ) - -**CLI and TDK** - -- `[bugfix - 🐛]` Fix `arcade worker list` endpoints ([PR #504](https://github.com/ArcadeAI/arcade-ai/pull/504) -  ) -- `[feature - 🚀]` Support Output in ValueSchema of ToolDefinition ([PR #487](https://github.com/ArcadeAI/arcade-ai/pull/487) -  ) - -**Platform and Engine** - -- `[feature - 🚀]` Self-service plan selection for Arcade Cloud and payment is now available. -- `[bugfix - 🐛]` Dashboard: Userinfo config must respect response\_map property -- `[feature - 🚀]` Dashboard: Add Types in Metrics - -**Misc** - -- `[documentation - 📝]` Update OAuth docs with user\_info\_request.response\_map ([PR #360](https://github.com/ArcadeAI/docs/pull/360) -  ) -- `[documentation - 📝]` Update Zendesk Custom OAuth ([PR #359](https://github.com/ArcadeAI/docs/pull/359) -  ) -- `[documentation - 📝]` Add code samples & screenshots to verification doc ([PR #363](https://github.com/ArcadeAI/docs/pull/363) -  ) - -## 2025-07-18 - -Version 2.0.0 of the was released this week. Upgrading to version 2.0.0 is recommended for all self-hosted developers, and includes an important security fix for [secure OAuth flows](/guides/user-facing-agents/secure-auth-production.md). After upgrading, all will default to using the Arcade user verifier. If desired, you can then implement a custom user verifier in your application/ and make the switch via the Arcade Dashboard. - -Self-hosed Arcade developers cannot be grandfathered into the old (insecure) behavior of skipping verification once the Engine is upgraded to version 2.0.0 or higher. - -**Frameworks** - -**Toolkits** - -- `[feature - 🚀]` Add Linear Toolkit ([PR #465](https://github.com/ArcadeAI/arcade-ai/pull/465) -  ) -- `[feature - 🚀]` Add Zendesk Toolkit ([PR #458](https://github.com/ArcadeAI/arcade-ai/pull/458) -  ) -- `[bugfix - 🐛]` Fix bug in Slack processing ([PR #488](https://github.com/ArcadeAI/arcade-ai/pull/488) -  ) -- `[bugfix - 🐛]` fix URL expansion in Twitter ([PR #500](https://github.com/ArcadeAI/arcade-ai/pull/500) -  ) - -**CLI and TDK** - -- `[feature - 🚀]` Toolkit docs generator command for Arcade CLI ([PR #414](https://github.com/ArcadeAI/arcade-ai/pull/414) -  ) -- `[feature - 🚀]` custom `callback_host` for arcade login ([PR #481](https://github.com/ArcadeAI/arcade-ai/pull/481) -  ) - -**Platform and Engine** - -- `[feature - 🚀]` Dashboard: Add filter for user id and providers in Connected -- `[feature - 🚀]` Add new endpoint for upcoming scheduled subs -- `[bugfix - 🐛]` Engine OAuth hardening: secure defaults, config updates, validation, additional API flags, and route for confirmation -- `[feature - 🚀]` Dashboard: UI for security settings -- `[bugfix - 🐛]` Engine: Correctly handle nils in responses -- `[bugfix - 🐛]` Platform: Improved success & error pages for OAuth - -**Misc** - -- `[documentation - 📝]` replaced creating Server video with full tutorial ([PR #349](https://github.com/ArcadeAI/docs/pull/349) -  ) -- `[documentation - 📝]` Add secure/brand auth in production doc ([PR #341](https://github.com/ArcadeAI/docs/pull/341) -  ) - -## 2025-07-11 - -**Frameworks** - -**Toolkits** - -- `[feature - 🚀]` Split previously combined Google, Microsoft, and other Toolkits into separate Servers to aid in retrieval and maintenance ([PR #438](https://github.com/ArcadeAI/arcade-ai/pull/438) -  ) -- `[feature - 🚀]` Slack Toolkit: Major refactor and improvements ([PR #453](https://github.com/ArcadeAI/arcade-ai/pull/453) -  ) - -**CLI and TDK** - -- `[feature - 🚀]` `--debug` flag added for CLI commands ([PR #454](https://github.com/ArcadeAI/arcade-ai/pull/454) -  ) - -**Platform and Engine** - -- `[bugfix - 🐛]` Fix token refresh bug - -**Misc** - -- `[documentation - 📝]` Document the OAuth scopes required by the Slack Server ([PR #344](https://github.com/ArcadeAI/docs/pull/344) -  ) - -## 2025-07-04 - -**Toolkits** - -- `[bugfix - 🐛]` patching Server template generator for outside the main repo ([PR #460](https://github.com/ArcadeAI/arcade-ai/pull/460) -  ) -- `[bugfix - 🐛]` Filter out unneeded files/directories before deploying workers ([PR #464](https://github.com/ArcadeAI/arcade-ai/pull/464) -  ) - -**Platform and Engine** - -- `[feature - 🚀]` Concurrent auth requests for the same user and same scopes use the same authentication flow and URLs. This means that your users only have to authenticate once if the chooses to use multiple at once with the same scopes. -- `[bugfix - 🐛]` Fix secret deletion - -**Cloud** - -- `[bugfix - 🐛]` Update cross-origin-opener-policy header to allow Google Drive File Picker popup - -**Platform and Engine** - -- `[feature - 🚀]` Dashboard: Allow editing the description of a secret -- `[feature - 🚀]` Dashboard: Preserve when resetting parameters - -## 2025-06-28 - -**Toolkits** - -- `[bugfix - 🐛]` Jira Server: deduplicate cloud data in Atlassian’s available-resources response ([PR #456](https://github.com/ArcadeAI/arcade-ai/pull/456) -  ) - -## 2025-06-20 - -**Frameworks** - -- `[feature - 🚀]` Support for OpenAI SDK in Typescript ([docs](/get-started/agent-frameworks/openai-agents/overview.md) - ) - -**Toolkits** - -- `[feature - 🚀]` Jira Server released ([docs](/resources/integrations/productivity/jira.md) - ) - -**CLI and TDK** - -- `[feature - 🚀]` V2.0 of Python Development Kit (TDK) -- `[feature - 🚀]` Admin API client support - - Requires v1.6.0 of `arcade-py`, or v1.8.0 of `arcade-js`, or v0.1.0-alpha.4 of `arcade-go` - -**Platform and Engine** - -- `[feature - 🚀]` Admin APIs released for managing users, secrets, and ([API References](https://reference.arcade.dev/api-reference#tag/admin) -  ) -- `[bugfix - 🐛]` Unauthenticated servers can be called anonymously -- `[feature - 🚀]` End- credentials and auth status can be fetched in batches ([docs](/guides/tool-calling/custom-apps/check-auth-status.md) - ) - -**Misc** - -- `[feature - 🚀]` Launched Github Discussions for product feedback and support ([link](https://github.com/ArcadeAI/arcade-ai/discussions) -  ) -- `[feature - 🚀]` Launched status.arcade.dev for monitoring platform status ([link](https://status.arcade.dev) -  ) - -Last updated on February 10, 2026 - -[Overview](/en/references.md) -[API](/en/references/api.md) diff --git a/public/_markdown/en/references/cli-cheat-sheet.md b/public/_markdown/en/references/cli-cheat-sheet.md deleted file mode 100644 index 9e45f8694..000000000 --- a/public/_markdown/en/references/cli-cheat-sheet.md +++ /dev/null @@ -1,824 +0,0 @@ ---- -title: "Arcade CLI cheat sheet" -description: "Quick reference for all Arcade CLI commands, perfect for printing." ---- -CLI Cheat Sheet - -# Arcade CLI cheat sheet - -**📄 Print-friendly!** Use your browser’s print function (Ctrl/Cmd + P) to get a landscape-oriented version perfect for events and quick reference. The layout will automatically adjust for optimal printing. - -🚀Getting Started - -Install Arcade CLI globally using `uv` (recommended) or `pip`. - -```bash -uv tool install arcade-mcp # Recommended -pip install arcade-mcp # Alternative -``` - -Upgrade to the latest version: - -```bash -uv tool upgrade arcade-mcp # Recommended -pip install --upgrade arcade-mcp # Alternative -``` - -Verify installation: - -```bash -arcade --version -``` - -Create and run your first server: - -```bash -arcade new my_server -cd my_server -arcade mcp http -``` - -Get help on any command: - -```bash -arcade --help -arcade --help -``` - -Use `uv` for faster installs and better dependency management - -Using Windows and PowerShell? See [Windows environment setup](/get-started/setup/windows-environment.md) for install options with and without `uv`. - -🔐Authentication - -Authenticate with Arcade Cloud for deployments and secrets management. - -Command - -Description - -`arcade login` - -Opens browser for OAuth authentication - -`arcade login --host ` - -Login to custom Arcade instance - -`arcade logout` - -Clear local credentials - -`arcade whoami` - -Show logged-in user and active context - -`arcade dashboard` - -Open Arcade web UI in browser - -`arcade dashboard --local` - -Open local dashboard - -Credentials are stored in `~/.arcade/credentials.yaml` (or `%USERPROFILE%\.arcade\credentials.yaml` on Windows). - -🏢Organizations & Projects - -Organizations group and team members. Projects contain servers, secrets, and configurations. - -```bash -# List all organizations -arcade org list - -# Switch active organization -arcade org set -``` - -Switching organization also resets your active to that org’s default. - - contain servers, secrets, and configurations. - -```bash -# List projects in active org -arcade project list - -# Switch active project -arcade project set -``` - -All deploy/secret commands use your active project . - -Use `arcade whoami` to see current org/. - -✨Create New Server - -Scaffold a new server with boilerplate code. - -### Minimal Template (Quick Start) - -```bash -arcade new my_server -``` - -Creates **pyproject.toml**, **src/my\_server/**init**.py**, **src/my\_server/server.py**. - -### Full Template (Production) - -```bash -arcade new my_server --full -``` - -Creates: **pyproject.toml**, **my\_server/** (package with ), **tests/**, **evals/**, **Makefile**, **.pre-commit-config.yaml**, **.ruff.toml**, **LICENSE**, **README.md**. - -### Options - -Flag - -Description - -`--dir ` - -Output directory (default: current) - -`--full`, `-f` - -Create full starter project - -⚡Run MCP Server - -Start your server locally for development and testing. - -### Transport Types - -```bash -# For MCP clients (Claude, Cursor) -arcade mcp stdio - -# For web/API testing -arcade mcp http -``` - -### Examples - -```bash -arcade mcp http --port 8080 --reload --debug -arcade mcp stdio --tool-package github -arcade mcp http --discover-installed --show-packages -``` - -Use `--reload` for faster development iteration - -🔍Show & Inspect Tools - -View available and their schemas from local or remote servers. - -```bash -# List all tools -arcade show - -# Show local tools only -arcade show --local - -# Show tool details -arcade show -t - -# Full response structure -arcade show -t --full - -# Filter by server -arcade show -T -``` - -### Options - -Flag - -Description - -`-t`, `--tool ` - -Show specific tool details - -`-T`, `--server ` - -Filter by server - -`--local`, `-l` - -Show local catalog only - -`--full`, `-f` - -Show complete response (auth, logs) - -🔧Configure Clients - -Auto-configure clients to connect to your server. - -### Supported Clients - -Client - -Command - -Claude Desktop -(`stdio` only) - -`arcade configure claude` - -Cursor IDE -(`stdio` or `http`) - -`arcade configure cursor` - -VS Code -(`stdio` or `http`) - -`arcade configure vscode` - -Claude Desktop only supports `stdio` transport via configuration file. - -### Options - -Flag - -Description - -Default - -`--transport ` - -`stdio` or `http` - -`stdio` - -`--host ` - -`local` or `arcade` - -`local` - -`--port ` - -Port for HTTP transport - -`8000` - -`--name ` - -Server name in config - -directory name - -`--entrypoint ` - -Entry file for stdio - -`server.py` - -☁️Deploy to Cloud - -Deploy your server to Arcade Cloud for production use. - -```bash -arcade deploy -``` - -### Options - -Flag - -Description - -Default - -`-e`, `--entrypoint ` - -Python file that runs MCPApp - -`server.py` - -`--server-name ` - -Explicit server name - -auto-detected - -`--server-version ` - -Explicit server version - -auto-detected - -`--skip-validate` - -Skip local health checks - -off - -`--secrets ` - -Secret sync mode (see below) - -`auto` - -### Secrets Handling - -Mode - -Description - -`auto` - -Sync only required secret keys (default) - -`all` - -Sync entire .env file - -`skip` - -Don’t sync any secrets - -Run from your project root (where `pyproject.toml` is located). - -🖥️Server Management - -Manage deployed servers in Arcade Cloud. - -```bash -# List all servers -arcade server list - -# Get server details -arcade server get - -# Enable a server -arcade server enable - -# Disable a server -arcade server disable - -# Delete a server (permanent!) -arcade server delete -``` - -Delete is permanent and cannot be undone - -📋Server Logs - -View and stream logs from deployed servers. - -```bash -# View recent logs (last 1h) -arcade server logs - -# Stream live logs -arcade server logs -f # Stream live logs -``` - -### Time Range Options - -Flag - -Description - -Example - -`-s`, `--since