diff --git a/.github/workflows/generate-toolkit-docs.yml b/.github/workflows/generate-toolkit-docs.yml
index 40e0c2d80..b25c8026f 100644
--- a/.github/workflows/generate-toolkit-docs.yml
+++ b/.github/workflows/generate-toolkit-docs.yml
@@ -68,6 +68,7 @@ jobs:
--llm-concurrency 15 \
--exclude-file ./remove-toolkits.txt \
--ignore-file ./skip-toolkits.txt \
+ --custom-sections ./curation \
--output data/toolkits
working-directory: toolkit-docs-generator
env:
diff --git a/biome.jsonc b/biome.jsonc
index 46f808c2a..d3d27b9f4 100644
--- a/biome.jsonc
+++ b/biome.jsonc
@@ -133,6 +133,7 @@
"!node_modules",
"!public",
"!toolkit-docs-generator/data/toolkits",
+ "!toolkit-docs-generator/curation",
"!scripts",
"!agents",
"!.vscode",
diff --git a/toolkit-docs-generator/curation/airtableapi.json b/toolkit-docs-generator/curation/airtableapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/airtableapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/arcadeengineapi.json b/toolkit-docs-generator/curation/arcadeengineapi.json
new file mode 100644
index 000000000..9043e32dd
--- /dev/null
+++ b/toolkit-docs-generator/curation/arcadeengineapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Secrets\n\nThis MCP Server requires the `ARCADE_API_KEY` secret to be configured. Learn how to [configure secrets](/guides/create-tools/tool-basics/create-tool-secrets).\n\n### Getting your Arcade API Key\n\nTo use the Arcade Engine API MCP Server, you need an Arcade API key. This key authenticates your requests to the Arcade Engine.\n\nLearn how to create and manage your Arcade API keys in the [API Keys documentation](/get-started/setup/api-keys).",
+ "header": "## Secrets"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/asana.json b/toolkit-docs-generator/curation/asana.json
new file mode 100644
index 000000000..8f477e133
--- /dev/null
+++ b/toolkit-docs-generator/curation/asana.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Asana MCP Server uses the [Asana auth provider](/references/auth-providers/asana) to connect to users' Asana accounts."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/asanaapi.json b/toolkit-docs-generator/curation/asanaapi.json
new file mode 100644
index 000000000..1751176e0
--- /dev/null
+++ b/toolkit-docs-generator/curation/asanaapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The AsanaApi MCP Server uses the Auth Provider with id `arcade-asana` to connect to users' AsanaApi accounts. In order to use the MCP Server, you will need to configure the `arcade-asana` auth provider.\nFor detailed information on configuring the Asana OAuth provider with Arcade, see the [Asana Auth Provider documentation](/references/auth-providers/asana).",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/ashbyapi.json b/toolkit-docs-generator/curation/ashbyapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/ashbyapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/boxapi.json b/toolkit-docs-generator/curation/boxapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/boxapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/brightdata.json b/toolkit-docs-generator/curation/brightdata.json
new file mode 100644
index 000000000..f35401a5f
--- /dev/null
+++ b/toolkit-docs-generator/curation/brightdata.json
@@ -0,0 +1,11 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Secrets\n\nThis tool requires the following secrets:\n\n- `BRIGHTDATA_API_KEY`\n- `BRIGHTDATA_ZONE`\n\n### Auth\n\nThe Arcade Bright Data MCP Server uses [Bright Data](https://brightdata.com/) to access proxy networks and web scraping infrastructure.\n\n**Global Environment Variables:**\n\n- `BRIGHTDATA_API_KEY`: Your Bright Data API key. You can generate this from your [Bright Data dashboard](https://brightdata.com/cp/zones) under Account Settings → API Access.\n\n- `BRIGHTDATA_ZONE`: Your Bright Data zone name (e.g., `residential_proxy1`). This is the zone identifier you created in your Bright Data dashboard under Proxies & Scraping Infrastructure → Zones.\n\n**How to get your credentials:**\n\n1. **API Key**: Navigate to your [Bright Data Control Panel](https://brightdata.com/cp) → Settings → API Access → Generate API Token\n2. **Zone**: Go to Zones section in your dashboard, find your zone name in the format shown in the zone username: `brd-customer-{customer_id}-zone-{zone_name}`\n\nFor more details, see the [Bright Data API Documentation](https://docs.brightdata.com/api-reference).",
+ "header": "## Secrets"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/calendlyapi.json b/toolkit-docs-generator/curation/calendlyapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/calendlyapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/clickup.json b/toolkit-docs-generator/curation/clickup.json
new file mode 100644
index 000000000..a95710416
--- /dev/null
+++ b/toolkit-docs-generator/curation/clickup.json
@@ -0,0 +1,25 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## TaskPriority\n\n- **URGENT**: `URGENT`\n- **HIGH**: `HIGH`\n- **NORMAL**: `NORMAL`\n- **LOW**: `LOW`",
+ "header": "## TaskPriority"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## TaskOrderBy\n\n- **CREATED**: `created`\n- **UPDATED**: `updated`\n- **DUE_DATE**: `due_date`",
+ "header": "## TaskOrderBy"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## CommentResolution\n\n- **SET_AS_RESOLVED**: `resolved`\n- **SET_AS_UNRESOLVED**: `unresolved`",
+ "header": "## CommentResolution"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/clickupapi.json b/toolkit-docs-generator/curation/clickupapi.json
new file mode 100644
index 000000000..61312ad25
--- /dev/null
+++ b/toolkit-docs-generator/curation/clickupapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The ClickupApi MCP Server uses the Auth Provider with id `arcade-clickup` to connect to users' ClickupApi accounts. In order to use the MCP Server, you will need to configure the `arcade-clickup` auth provider.\nFor detailed information on configuring the ClickUp OAuth provider with Arcade, see the [ClickUp Auth Provider documentation](/references/auth-providers/clickup).",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/confluence.json b/toolkit-docs-generator/curation/confluence.json
new file mode 100644
index 000000000..c136f1d2c
--- /dev/null
+++ b/toolkit-docs-generator/curation/confluence.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Confluence MCP Server uses the [Atlassian auth provider](/references/auth-providers/atlassian) to connect to users' Atlassian accounts.\n---"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/cursoragentsapi.json b/toolkit-docs-generator/curation/cursoragentsapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/cursoragentsapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/customerioapi.json b/toolkit-docs-generator/curation/customerioapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/customerioapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/customeriopipelinesapi.json b/toolkit-docs-generator/curation/customeriopipelinesapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/customeriopipelinesapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/customeriotrackapi.json b/toolkit-docs-generator/curation/customeriotrackapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/customeriotrackapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/datadogapi.json b/toolkit-docs-generator/curation/datadogapi.json
new file mode 100644
index 000000000..6935a526f
--- /dev/null
+++ b/toolkit-docs-generator/curation/datadogapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "before_available_tools",
+ "position": "after",
+ "content": "## Authentication\n\nThe Arcade Datadog API MCP Server requires three environment variables to authenticate with the [Datadog API](https://docs.datadoghq.com/api/latest/):\n\n- `DATADOG_API_KEY`\n- `DATADOG_APPLICATION_KEY`\n- `DATADOG_BASE_URL`\n\n**How to obtain your credentials:**\n\n1. Log in to your [Datadog dashboard](https://app.datadoghq.com/)\n2. Navigate to **Organization Settings** (click your profile icon in the bottom left)\n3. Go to **API Keys** → click **New Key** → provide a name and click **Create Key**\n4. Go to **Application Keys** → click **New Key** → provide a name and click **Create Key**\n5. Determine your **Base URL** based on your Datadog site (check the URL in your browser):\n - US1: `api.datadoghq.com`\n - US3: `api.us3.datadoghq.com`\n - US5: `api.us5.datadoghq.com`\n - EU1: `api.datadoghq.eu`\n - AP1: `api.ap1.datadoghq.com`\n - GOV: `api.ddog-gov.com`\n\nFor more details, see the [Datadog API and Application Keys documentation](https://docs.datadoghq.com/account_management/api-app-keys/).",
+ "header": "## Authentication"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/dropbox.json b/toolkit-docs-generator/curation/dropbox.json
new file mode 100644
index 000000000..47d026c6b
--- /dev/null
+++ b/toolkit-docs-generator/curation/dropbox.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Dropbox MCP Server uses the [Dropbox auth provider](/references/auth-providers/dropbox) to connect to users' Dropbox accounts."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/e2b.json b/toolkit-docs-generator/curation/e2b.json
new file mode 100644
index 000000000..6f3636451
--- /dev/null
+++ b/toolkit-docs-generator/curation/e2b.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade E2B MCP Server uses [E2B](https://e2b.dev/) to run code in a sandboxed environment.\n**Global Environment Variables:**\n- `E2B_API_KEY`: Your [E2B](https://e2b.dev/) API key."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/exaapi.json b/toolkit-docs-generator/curation/exaapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/exaapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/figma.json b/toolkit-docs-generator/curation/figma.json
new file mode 100644
index 000000000..c33b84a8a
--- /dev/null
+++ b/toolkit-docs-generator/curation/figma.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "\nThe `projects:read` scope is **ONLY available in private Figma OAuth apps**. This scope is required for the navigation tools (`GetTeamProjects` and `GetProjectFiles`). \nIf you need these navigation tools, you must create a private OAuth app through your Figma organization settings. All other tools work with public OAuth apps.\n"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/figmaapi.json b/toolkit-docs-generator/curation/figmaapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/figmaapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/firecrawl.json b/toolkit-docs-generator/curation/firecrawl.json
new file mode 100644
index 000000000..d7e6f5446
--- /dev/null
+++ b/toolkit-docs-generator/curation/firecrawl.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Firecrawl MCP Server uses [Firecrawl](https://www.firecrawl.dev/) to scrape, crawl, and map websites.\n**Global Environment Variables:**\n- `FIRECRAWL_API_KEY`: Your [Firecrawl](https://www.firecrawl.dev/) API key."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/freshserviceapi.json b/toolkit-docs-generator/curation/freshserviceapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/freshserviceapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/github.json b/toolkit-docs-generator/curation/github.json
new file mode 100644
index 000000000..090fdf479
--- /dev/null
+++ b/toolkit-docs-generator/curation/github.json
@@ -0,0 +1,40 @@
+{
+ "documentationChunks": [
+ {
+ "type": "warning",
+ "location": "description",
+ "position": "after",
+ "content": "\n **Critical**: This MCP Server is built for **GitHub Apps**, not OAuth Apps.\n \n You **must** create a GitHub App (not an OAuth App) to use this server properly. \n \n 👉 [Complete GitHub App Setup Guide](/references/auth-providers/github)\n"
+ },
+ {
+ "type": "info",
+ "location": "description",
+ "position": "after",
+ "content": "\n **Configuration**: On Arcade Cloud, these tools work out of the box. Self-hosted and GitHub Enterprise Server users can set the `GITHUB_SERVER_URL` secret in Arcade Dashboard. See [Secrets Setup](#secrets-setup) below.\n"
+ },
+ {
+ "type": "info",
+ "location": "before_available_tools",
+ "position": "after",
+ "content": "## GitHub Enterprise Support\n\n\n This MCP Server fully supports **GitHub Enterprise Server 2.22+**\n\n\n**Default Configuration:**\n- If no `GITHUB_SERVER_URL` is configured, the default is `https://api.github.com` (GitHub.com)\n- All tools work with GitHub.com out of the box\n\n**For GitHub Enterprise Server:**\n\n1. Create your GitHub App on your Enterprise instance (not github.com)\n2. Configure the `GITHUB_SERVER_URL` secret in Arcade Dashboard (see [Secrets Setup](#secrets-setup) below)\n3. Use your Enterprise server's API endpoint\n\n**Example Enterprise Server URLs:**\n- `https://github.yourcompany.com/api/v3`\n- `https://enterprise.yourorg.com/api/v3`\n- `https://git.company.internal/api/v3`\n\n\n **Note**: GitHub Enterprise Server uses the `/api/v3` path after the hostname. GitHub.com uses `https://api.github.com` (no `/api/v3` suffix).\n\n\n---",
+ "header": "## GitHub Enterprise Support"
+ },
+ {
+ "type": "section",
+ "location": "before_available_tools",
+ "position": "after",
+ "content": "## GitHub App Permissions Summary\n\nWhen creating your GitHub App, you'll need to grant specific permissions. Here's a quick reference of which tools require which permissions:\n\n### Repository Permissions\n\n| Permission | Level | Required For |\n|------------|-------|--------------|\n| **Contents** | Read | All repository and pull request tools, getting file contents |\n| **Contents** | Write | Creating/updating files, creating branches, merging PRs |\n| **Issues** | Read & Write | Issue management, PR assignments, managing labels (Issues) |\n| **Pull requests** | Read & Write | Pull request management, reviews, managing labels (PRs) |\n| **Metadata** | Read | All tools (automatically granted) |\n| **Statuses** | Read | `CheckPullRequestMergeStatus` |\n\n### Organization Permissions\n\n| Permission | Level | Required For |\n|------------|-------|--------------|\n| **Members** | Read | Projects, collaborators, org repos, user search |\n| **Projects** | Read & Write | All Projects V2 tools |\n\n### User Permissions\n\n| Permission | Level | Required For |\n|------------|-------|--------------|\n| **Read user profile** | Read | User context tools, review workload |\n| **Act on behalf of user** | Enabled | `SetStarred` (starring repositories) |\n\n### Tools by Permission Requirements\n\n\nBasic Repository Access (Contents Read + Metadata)
\n\n- `GetRepository`\n- `CountStargazers`\n- `ListStargazers`\n- `ListRepositoryActivities`\n- `GetFileContents`\n\n \n\n\nRepository Write (Contents Write + Metadata)
\n\n- `CreateBranch`\n- `CreateOrUpdateFile`\n- `UpdateFileLines`\n\n \n\n\nIssue Management (Contents Read + Issues + Metadata)
\n\n- `CreateIssue`\n- `UpdateIssue`\n- `GetIssue`\n- `ListIssues`\n- `CreateIssueComment`\n- `ListRepositoryLabels`\n- `ManageLabels` (for issues)\n\n \n\n\nPull Request Read (Contents + Pull requests Read + Metadata)
\n\n- `ListPullRequests`\n- `GetPullRequest`\n- `ListPullRequestCommits`\n- `ListReviewCommentsOnPullRequest`\n- `CheckPullRequestMergeStatus` (+ Statuses)\n\n \n\n\nPull Request Write (Contents Read + Pull requests Write + Metadata)
\n\n- `UpdatePullRequest`\n- `CreatePullRequest`\n- `SubmitPullRequestReview`\n- `ManagePullRequest`\n- `ManagePullRequestReviewers`\n- `CreateReviewComment`\n- `CreateReplyForReviewComment`\n- `ResolveReviewThread`\n- `ManageLabels` (for pull requests)\n- `MergePullRequest` (+ Contents Write)\n\n \n\n\nOrganization Tools (Contents + Metadata + Members)
\n\n- `ListOrgRepositories`\n- `SearchMyRepos`\n- `ListRepositoryCollaborators`\n- `AssignPullRequestUser` (+ Issues Write)\n\n \n\n\nProjects V2 (Contents + Metadata + Projects + Members)
\n\n- `ListProjects`\n- `ListProjectItems`\n- `SearchProjectItem`\n- `ListProjectFields`\n- `UpdateProjectItem` (Projects Write)\n\n \n\n\nUser Context (Contents + Metadata + Read user profile)
\n\n- `WhoAmI` (+ Members)\n- `GetUserRecentActivity`\n- `GetUserOpenItems`\n- `GetReviewWorkload` (+ Pull requests Read)\n\n \n\n---",
+ "header": "## GitHub App Permissions Summary"
+ },
+ {
+ "type": "warning",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Configuration & Setup\n\n### Authentication\n\n\n **Critical**: This MCP Server uses **GitHub Apps** authentication, not OAuth Apps.\n \n You **must** create a GitHub App to use this server. OAuth Apps are not supported.\n\n\nThe Arcade GitHub MCP Server uses the [GitHub auth provider](/references/auth-providers/github) to connect to users' GitHub accounts.\n\n**For Arcade Cloud:**\n- No configuration needed\n- Your users will see `Arcade` as the requesting application\n- All tools work out of the box\n\n**For Self-Hosted:**\n- You must [create your own GitHub App](/references/auth-providers/github#creating-a-github-app)\n- [Configure the GitHub auth provider](/references/auth-providers/github#configuring-github-auth-in-arcade) with your app credentials\n- Your users will see your application name\n\n\n **New to GitHub Apps?** Read [Why Arcade Uses GitHub Apps](/references/auth-providers/github#why-arcade-uses-github-apps-not-oauth-apps) \n to understand the security and compliance benefits.\n\n\n### Secrets Setup\n\nGitHub tools read an optional `GITHUB_SERVER_URL` secret from the Arcade Dashboard. It defaults to `https://api.github.com`, so you only need to set it for GitHub Enterprise Server.\n\n**Steps:**\n\n1. Go to [Arcade Dashboard](https://api.arcade.dev/dashboard)\n2. Navigate to **Secrets** in the left sidebar\n3. Click **Add Secret**\n4. Add the following secrets:\n\n| Secret Name | Value | Required For |\n|-------------|-------|--------------|\n| `GITHUB_SERVER_URL` | `https://api.github.com` (default for GitHub.com) | All tools |\n\n\n **Default**: If `GITHUB_SERVER_URL` is not configured, it defaults to `https://api.github.com` (GitHub.com)\n \n **GitHub Enterprise Users**: Set `GITHUB_SERVER_URL` to your Enterprise server's API endpoint (e.g., `https://github.yourcompany.com/api/v3`). Note that Enterprise uses `/api/v3` path. See [GitHub Enterprise Support](#github-enterprise-support) for details.\n\n\n---",
+ "header": "## Configuration & Setup"
+ }
+ ],
+ "customImports": [
+ "import { Callout, Tabs } from \"nextra/components\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/githubapi.json b/toolkit-docs-generator/curation/githubapi.json
new file mode 100644
index 000000000..b526f1580
--- /dev/null
+++ b/toolkit-docs-generator/curation/githubapi.json
@@ -0,0 +1,21 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Secrets\n\nAll tools in this toolset require the following secret: `GIT_SERVER_URL` (learn how to [configure secrets](/guides/create-tools/tool-basics/create-tool-secrets))\n\nThe `GIT_SERVER_URL` secret specifies the GitHub server URL. Use `https://api.github.com` for regular GitHub.com accounts, or your GitHub Enterprise server URL (e.g., `https://github.your-company.com/api/v3`) for GitHub Enterprise deployments.",
+ "header": "## Secrets"
+ },
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The GithubApi MCP Server uses the Auth Provider with id `arcade-github` to connect to users' GithubApi accounts. In order to use the MCP Server, you will need to configure the `arcade-github` auth provider.\nFor detailed information on configuring the GitHub OAuth provider with Arcade, see the [GitHub Auth Provider documentation](/references/auth-providers/github).",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/gmail.json b/toolkit-docs-generator/curation/gmail.json
new file mode 100644
index 000000000..5d25758f8
--- /dev/null
+++ b/toolkit-docs-generator/curation/gmail.json
@@ -0,0 +1,21 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Gmail MCP Server uses the [Google auth provider](/references/auth-providers/google) to connect to users' Google accounts.\n---"
+ },
+ {
+ "type": "markdown",
+ "location": "after_available_tools",
+ "position": "after",
+ "content": "## Adding attachments to emails\n\nThe Gmail send, draft, and reply tools take an `attachments` parameter for local files. The agent emits only the file path; a client-side `preToolUse` hook swaps in the bytes before the request leaves your machine, so file contents never enter the model's context window.\n\nThe first time you attach a file, your agent installs the one-time hook for you after you approve. Gmail caps total message size at 25 MB.\n\nAttachments work on hosts that support a client-side pre-tool hook. On any other host the tool returns a clear error and sends nothing.\n\n| Host | Status | Notes |\n| --- | --- | --- |\n| Cursor | Supported | App, plus Cursor cloud and background agents. |\n| Claude Code | Supported | v2.0.10+. |\n| Codex CLI | Supported | v0.131+. |\n| VS Code chat (GitHub Copilot) | Supported | 1.112+, agent mode. |\n| Claude Cowork | Documented limitation | Sandboxed to one folder; the hook cannot be installed from inside it. |\n| Claude Desktop | Documented limitation | No client-side hook layer. |\n| ChatGPT desktop | Documented limitation | No client-side hook surface. |\n| Microsoft 365 Copilot | Documented limitation | No host-side rewrite hook. |",
+ "header": "## Adding attachments to emails",
+ "priority": 10
+ }
+ ],
+ "customImports": [
+ "import ScopePicker from \"@/app/_components/scope-picker\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlecalendar.json b/toolkit-docs-generator/curation/googlecalendar.json
new file mode 100644
index 000000000..6e8b27b3c
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlecalendar.json
@@ -0,0 +1,21 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Calendar MCP Server uses the [Google auth provider](/references/auth-providers/google) to connect to users' Google accounts.\n---",
+ "header": "## Auth"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## UpdateGoogleMeetOptions\n\n- **`NONE`**: No action is taken.\n- **`ADD`**: Add the Google Meet link to the event.\n- **`REMOVE`**: Remove the Google Meet link from the event.\n\n",
+ "header": "## UpdateGoogleMeetOptions"
+ }
+ ],
+ "customImports": [
+ "import ScopePicker from \"@/app/_components/scope-picker\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlecontacts.json b/toolkit-docs-generator/curation/googlecontacts.json
new file mode 100644
index 000000000..4c0d079ae
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlecontacts.json
@@ -0,0 +1,13 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Contacts MCP Server uses the [Google auth provider](/references/auth-providers/google) to connect to users' Google accounts."
+ }
+ ],
+ "customImports": [
+ "import ScopePicker from \"@/app/_components/scope-picker\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googledocs.json b/toolkit-docs-generator/curation/googledocs.json
new file mode 100644
index 000000000..96710412a
--- /dev/null
+++ b/toolkit-docs-generator/curation/googledocs.json
@@ -0,0 +1,27 @@
+{
+ "documentationChunks": [
+ {
+ "type": "warning",
+ "location": "description",
+ "position": "after",
+ "content": "\n This Toolkit is not available in Arcade Cloud. You can use these tools with a\n [self-hosted](/guides/deployment-hosting/configure-engine) instance of Arcade.\n"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Tab Support\n\nGoogle Docs supports hierarchical tabs within documents. The Google Docs tools provide comprehensive support for working with tabs:\n\n- **Tab Metadata**: `GetDocumentMetadata` returns hierarchical tab structures with approximate character and word counts for each tab\n- **Tab Content**: `GetDocumentAsDocMD` and `SearchAndRetrieveDocuments` include all tab content in their output\n- **Tab Filtering**: `GetDocumentAsDocMD` supports filtering to retrieve content from a specific tab using the `tab_id` parameter\n\nTabs are represented with the following structure:\n- Each tab has a unique `tabId`, `title`, `index`, and `nestingLevel`\n- Tabs can be nested up to 3 levels deep (parent → child → grandchild)\n- Tab metadata includes approximate character and word counts for each tab's content\n\n---",
+ "header": "## Tab Support"
+ },
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Docs MCP Server uses the [Google auth provider](/references/auth-providers/google) to connect to users' Google accounts.\n---",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import ScopePicker from \"@/app/_components/scope-picker\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googledrive.json b/toolkit-docs-generator/curation/googledrive.json
new file mode 100644
index 000000000..6508f437d
--- /dev/null
+++ b/toolkit-docs-generator/curation/googledrive.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import ScopePicker from \"@/app/_components/scope-picker\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlefinance.json b/toolkit-docs-generator/curation/googlefinance.json
new file mode 100644
index 000000000..912309a8b
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlefinance.json
@@ -0,0 +1,18 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Finance MCP Server uses the [SerpAPI](https://serpapi.com/) to get stock data from Google Finance.\n- **Secret:**\n - `SERP_API_KEY`: Your SerpAPI API key.\n \n Setting the `SERP_API_KEY` secret is only required if you are\n [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're\n using Arcade Cloud, the secret is already set for you. To manage your\n secrets, go to the [Secrets\n page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade\n Dashboard.\n \n---",
+ "header": "## Auth"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## GoogleFinanceWindow\n\nDefines the time window for fetching stock data from Google Finance.\n\n- **`ONE_DAY`**: Represents a 1-day time window.\n- **`FIVE_DAYS`**: Represents a 5-day time window.\n- **`ONE_MONTH`**: Represents a 1-month time window.\n- **`SIX_MONTHS`**: Represents a 6-month time window.\n- **`YEAR_TO_DATE`**: Represents the time from the start of the year to the current date.\n- **`ONE_YEAR`**: Represents a 1-year time window.\n- **`FIVE_YEARS`**: Represents a 5-year time window.\n- **`MAX`**: Represents the maximum available time window.\n\n",
+ "header": "## GoogleFinanceWindow"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googleflights.json b/toolkit-docs-generator/curation/googleflights.json
new file mode 100644
index 000000000..368074e15
--- /dev/null
+++ b/toolkit-docs-generator/curation/googleflights.json
@@ -0,0 +1,32 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Flights MCP Server uses the [SerpAPI](https://serpapi.com/) to search for flights from Google Flights.\n- **Secret:**\n - `SERP_API_KEY`: Your SerpAPI API key.\n\n Setting the `SERP_API_KEY` secret is only required if you are\n [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're\n using Arcade Cloud, the secret is already set for you. To manage your secrets,\n go to the [Secrets page](https://api.arcade.dev/dashboard/auth/secrets) in the\n Arcade Dashboard.\n\n---",
+ "header": "## Auth"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## GoogleFlightsMaxStops\n\nDefines the maximum number of stops for flights.\n\n- **`ANY`**: Any number of stops is allowed.\n- **`NONSTOP`**: Only nonstop flights are allowed.\n- **`ONE`**: Only flights with one stop are allowed.\n- **`TWO`**: Only flights with two stops are allowed.",
+ "header": "## GoogleFlightsMaxStops"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## GoogleFlightsSortBy\n\nDefines the sorting options for flight search results.\n\n- **`TOP_FLIGHTS`**: Sort by the best available flights.\n- **`PRICE`**: Sort by the lowest price.\n- **`DEPARTURE_TIME`**: Sort by the earliest departure time.\n- **`ARRIVAL_TIME`**: Sort by the earliest arrival time.\n- **`DURATION`**: Sort by the shortest flight duration.\n- **`EMISSIONS`**: Sort by the lowest carbon emissions.",
+ "header": "## GoogleFlightsSortBy"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## GoogleFlightsTravelClass\n\nDefines the travel class options for flights.\n\n- **`ECONOMY`**: Economy class.\n- **`PREMIUM_ECONOMY`**: Premium economy class.\n- **`BUSINESS`**: Business class.\n- **`FIRST`**: First class.\n\n",
+ "header": "## GoogleFlightsTravelClass"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlehotels.json b/toolkit-docs-generator/curation/googlehotels.json
new file mode 100644
index 000000000..1e50bf32a
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlehotels.json
@@ -0,0 +1,18 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Hotels MCP Server uses the [SerpAPI](https://serpapi.com/) to search for hotels from Google Hotels.\n- **Secret:**\n - `SERP_API_KEY`: Your SerpAPI API key.\n\n Setting the `SERP_API_KEY` secret is only required if you are\n [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're\n using Arcade Cloud, the secret is already set for you. To manage your secrets,\n go to the [Secrets page](https://api.arcade.dev/dashboard/auth/secrets) in the\n Arcade Dashboard.\n",
+ "header": "## Auth"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## GoogleHotelsSortBy\n\nDefines the sorting options for hotel search results.\n\n- **`RELEVANCE`**: Sort by the most relevant results.\n- **`LOWEST_PRICE`**: Sort by the lowest price available.\n- **`HIGHEST_RATING`**: Sort by the highest customer ratings.\n- **`MOST_REVIEWED`**: Sort by the most reviewed hotels.\n\n",
+ "header": "## GoogleHotelsSortBy"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlejobs.json b/toolkit-docs-generator/curation/googlejobs.json
new file mode 100644
index 000000000..b70786f57
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlejobs.json
@@ -0,0 +1,25 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Jobs MCP Server uses the [SerpAPI](https://serpapi.com/) to get job data from Google Jobs.\n- **Secret:**\n - `SERP_API_KEY`: Your SerpAPI API key.\n \n Setting the `SERP_API_KEY` secret is only required if you are\n [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're\n using Arcade Cloud, the secret is already set for you. To manage your\n secrets, go to the [Secrets\n page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade\n Dashboard.\n ",
+ "header": "## Auth"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Default parameters\n\nLanguage is configurable through environment variables. When set, they will be used as default for Google Jobs tools.\n\nProviding a different value as `language` argument in a tool call will override the default value.\n\n**Language**\n\nThe language code is a 2-character code that determines the language in which the API will search and return news articles. There are two environment variables:\n\n- `ARCADE_GOOGLE_LANGUAGE`: a default value for all Google search tools. If not set, defaults to 'en' (English).\n- `ARCADE_GOOGLE_JOBS_LANGUAGE`: a default value for the jobs search tools. If not set, defaults to `ARCADE_GOOGLE_LANGUAGE`.\n\nA list of supported language codes can be found [here](#languagecodes).",
+ "header": "## Default parameters"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## LanguageCodes\n\n- **`ar`**: Arabic\n- **`bn`**: Bengali\n- **`da`**: Danish\n- **`de`**: German\n- **`el`**: Greek\n- **`en`**: English\n- **`es`**: Spanish\n- **`fi`**: Finnish\n- **`fr`**: French\n- **`hi`**: Hindi\n- **`hu`**: Hungarian\n- **`id`**: Indonesian\n- **`it`**: Italian\n- **`ja`**: Japanese\n- **`ko`**: Korean\n- **`ms`**: Malay\n- **`nl`**: Dutch\n- **`no`**: Norwegian\n- **`pcm`**: Nigerian Pidgin\n- **`pl`**: Polish\n- **`pt`**: Portuguese\n- **`pt-br`**: Portuguese (Brazil)\n- **`pt-pt`**: Portuguese (Portugal)\n- **`ru`**: Russian\n- **`sv`**: Swedish\n- **`tl`**: Filipino\n- **`tr`**: Turkish\n- **`uk`**: Ukrainian\n- **`zh`**: Chinese\n- **`zh-cn`**: Chinese (Simplified)\n- **`zh-tw`**: Chinese (Traditional)\n\n",
+ "header": "## LanguageCodes"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlemaps.json b/toolkit-docs-generator/curation/googlemaps.json
new file mode 100644
index 000000000..bd19d7c8a
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlemaps.json
@@ -0,0 +1,18 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Maps MCP Server uses the [SerpAPI](https://serpapi.com/) to get directions.\n- **Secret:**\n - `SERP_API_KEY`: Your SerpAPI API key.\n \n Setting the `SERP_API_KEY` secret is only required if you are\n [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're\n using Arcade Cloud, the secret is already set for you. To manage your\n secrets, go to the [Secrets\n page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade\n Dashboard.\n ",
+ "header": "## Auth"
+ },
+ {
+ "type": "info",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Default parameters\n\nLanguage, Country, Distance Unit, and Travel Mode are configurable through environment variables. When set, they will be used as default for Google Maps tools.\n\nProviding a different value as `language`, `country`, `distance_unit`, or `travel_mode` argument in a tool call will override the default value.\n\n**Language**\n\nThe language code is a 2-character code that determines the language in which the API will search and return directions. There are two environment variables:\n\n- `ARCADE_GOOGLE_LANGUAGE`: a default value for all Google tools. If not set, defaults to 'en' (English).\n- `ARCADE_GOOGLE_MAPS_LANGUAGE`: a default value for the Google Maps tools. If not set, defaults to `ARCADE_GOOGLE_LANGUAGE`.\n\nA list of supported language codes can be found [here](#languagecodes).\n\n**Country**\n\nThe country code is a 2-character code that determines the country in which the API will search for directions:\n\n- `ARCADE_GOOGLE_MAPS_COUNTRY`: a default value for the Google Maps tools. If not set, defaults to `None`.\n\nA list of supported country codes can be found [here](#countrycodes).\n\n**Distance Unit**\n\nThe distance unit is a string that determines the unit of distance to use in the Google Maps search:\n\n- `ARCADE_GOOGLE_MAPS_DISTANCE_UNIT`: a default value for the Google Maps tools. If not set, defaults to `GoogleMapsDistanceUnit.KM`.\n\nA list of supported distance units can be found [here](#googlemapsdistanceunit).\n\n**Travel Mode**\n\nThe travel mode is a string that determines the mode of travel to use in the Google Maps search:\n\n- `ARCADE_GOOGLE_MAPS_TRAVEL_MODE`: a default value for the Google Maps tools. If not set, defaults to `GoogleMapsTravelMode.BEST`.\n\nA list of supported travel modes can be found [here](#googlemapstravelmode).\n\n- **Secret:**\n - `SERP_API_KEY`: Your SerpAPI API key.\n \n Setting the `SERP_API_KEY` secret is only required if you are\n [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're\n using Arcade Cloud, the secret is already set for you. To manage your\n secrets, go to the [Secrets\n page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade\n Dashboard.\n \n\n---",
+ "header": "## Default parameters"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlenews.json b/toolkit-docs-generator/curation/googlenews.json
new file mode 100644
index 000000000..0af1fd739
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlenews.json
@@ -0,0 +1,32 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google News MCP Server uses the [SerpAPI](https://serpapi.com/) to get news data from Google News.\n- **Secret:**\n - `SERP_API_KEY`: Your SerpAPI API key.\n \n Setting the `SERP_API_KEY` secret is only required if you are\n [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're\n using Arcade Cloud, the secret is already set for you. To manage your\n secrets, go to the [Secrets\n page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade\n Dashboard.\n ",
+ "header": "## Auth"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Default parameters\n\nLanguage and Country are configurable through environment variables. When set, they will be used as default for Google News tools.\n\nProviding a different value as `language_code` or `country_code` argument in the tool call will override the default value.\n\n**Language**\n\nThe language code is a 2-character code that determines the language in which the API will search and return news articles. There are two environment variables:\n\n- `ARCADE_GOOGLE_LANGUAGE`: a default value for all Google search tools. If not set, defaults to 'en' (English).\n- `ARCADE_GOOGLE_NEWS_LANGUAGE`: a default value for the news search tools. If not set, defaults to `ARCADE_GOOGLE_LANGUAGE`.\n\nA list of supported language codes can be found [here](#languagecodes).\n\n**Country**\n\nThe country code is a 2-character code that determines the country in which the API will search for news articles. There are two environment variables:\n\n- `ARCADE_GOOGLE_NEWS_COUNTRY`: a default value for the `SearchNews` tool. If not set, defaults to `None` (search news globally).\n\nA list of supported country codes can be found [here](#countrycodes).\n\n---",
+ "header": "## Default parameters"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## LanguageCodes\n\n- **`ar`**: Arabic\n- **`bn`**: Bengali\n- **`da`**: Danish\n- **`de`**: German\n- **`el`**: Greek\n- **`en`**: English\n- **`es`**: Spanish\n- **`fi`**: Finnish\n- **`fr`**: French\n- **`hi`**: Hindi\n- **`hu`**: Hungarian\n- **`id`**: Indonesian\n- **`it`**: Italian\n- **`ja`**: Japanese\n- **`ko`**: Korean\n- **`ms`**: Malay\n- **`nl`**: Dutch\n- **`no`**: Norwegian\n- **`pcm`**: Nigerian Pidgin\n- **`pl`**: Polish\n- **`pt`**: Portuguese\n- **`pt-br`**: Portuguese (Brazil)\n- **`pt-pt`**: Portuguese (Portugal)\n- **`ru`**: Russian\n- **`sv`**: Swedish\n- **`tl`**: Filipino\n- **`tr`**: Turkish\n- **`uk`**: Ukrainian\n- **`zh`**: Chinese\n- **`zh-cn`**: Chinese (Simplified)\n- **`zh-tw`**: Chinese (Traditional)",
+ "header": "## LanguageCodes"
+ },
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## CountryCodes\n\n- **`af`**: Afghanistan\n- **`al`**: Albania\n- **`dz`**: Algeria\n- **`as`**: American Samoa\n- **`ad`**: Andorra\n- **`ao`**: Angola\n- **`ai`**: Anguilla\n- **`aq`**: Antarctica\n- **`ag`**: Antigua and Barbuda\n- **`ar`**: Argentina\n- **`am`**: Armenia\n- **`aw`**: Aruba\n- **`au`**: Australia\n- **`at`**: Austria\n- **`az`**: Azerbaijan\n- **`bs`**: Bahamas\n- **`bh`**: Bahrain\n- **`bd`**: Bangladesh\n- **`bb`**: Barbados\n- **`by`**: Belarus\n- **`be`**: Belgium\n- **`bz`**: Belize\n- **`bj`**: Benin\n- **`bm`**: Bermuda\n- **`bt`**: Bhutan\n- **`bo`**: Bolivia\n- **`ba`**: Bosnia and Herzegovina\n- **`bw`**: Botswana\n- **`bv`**: Bouvet Island\n- **`br`**: Brazil\n- **`io`**: British Indian Ocean Territory\n- **`bn`**: Brunei Darussalam\n- **`bg`**: Bulgaria\n- **`bf`**: Burkina Faso\n- **`bi`**: Burundi\n- **`kh`**: Cambodia\n- **`cm`**: Cameroon\n- **`ca`**: Canada\n- **`cv`**: Cape Verde\n- **`ky`**: Cayman Islands\n- **`cf`**: Central African Republic\n- **`td`**: Chad\n- **`cl`**: Chile\n- **`cn`**: China\n- **`cx`**: Christmas Island\n- **`cc`**: Cocos (Keeling) Islands\n- **`co`**: Colombia\n- **`km`**: Comoros\n- **`cg`**: Congo\n- **`cd`**: Congo, the Democratic Republic of the\n- **`ck`**: Cook Islands\n- **`cr`**: Costa Rica\n- **`ci`**: Cote D'ivoire\n- **`hr`**: Croatia\n- **`cu`**: Cuba\n- **`cy`**: Cyprus\n- **`cz`**: Czech Republic\n- **`dk`**: Denmark\n- **`dj`**: Djibouti\n- **`dm`**: Dominica\n- **`do`**: Dominican Republic\n- **`ec`**: Ecuador\n- **`eg`**: Egypt\n- **`sv`**: El Salvador\n- **`gq`**: Equatorial Guinea\n- **`er`**: Eritrea\n- **`ee`**: Estonia\n- **`et`**: Ethiopia\n- **`fk`**: Falkland Islands (Malvinas)\n- **`fo`**: Faroe Islands\n- **`fj`**: Fiji\n- **`fi`**: Finland\n- **`fr`**: France\n- **`gf`**: French Guiana\n- **`pf`**: French Polynesia\n- **`tf`**: French Southern Territories\n- **`ga`**: Gabon\n- **`gm`**: Gambia\n- **`ge`**: Georgia\n- **`de`**: Germany\n- **`gh`**: Ghana\n- **`gi`**: Gibraltar\n- **`gr`**: Greece\n- **`gl`**: Greenland\n- **`gd`**: Grenada\n- **`gp`**: Guadeloupe\n- **`gu`**: Guam\n- **`gt`**: Guatemala\n- **`gg`**: Guernsey\n- **`gn`**: Guinea\n- **`gw`**: Guinea-Bissau\n- **`gy`**: Guyana\n- **`ht`**: Haiti\n- **`hm`**: Heard Island and Mcdonald Islands\n- **`va`**: Holy See (Vatican City State)\n- **`hn`**: Honduras\n- **`hk`**: Hong Kong\n- **`hu`**: Hungary\n- **`is`**: Iceland\n- **`in`**: India\n- **`id`**: Indonesia\n- **`ir`**: Iran, Islamic Republic of\n- **`iq`**: Iraq\n- **`ie`**: Ireland\n- **`im`**: Isle of Man\n- **`il`**: Israel\n- **`it`**: Italy\n- **`je`**: Jersey\n- **`jm`**: Jamaica\n- **`jp`**: Japan\n- **`jo`**: Jordan\n- **`kz`**: Kazakhstan\n- **`ke`**: Kenya\n- **`ki`**: Kiribati\n- **`kp`**: Korea, Democratic People's Republic of\n- **`kr`**: Korea, Republic of\n- **`kw`**: Kuwait\n- **`kg`**: Kyrgyzstan\n- **`la`**: Lao People's Democratic Republic\n- **`lv`**: Latvia\n- **`lb`**: Lebanon\n- **`ls`**: Lesotho\n- **`lr`**: Liberia\n- **`ly`**: Libyan Arab Jamahiriya\n- **`li`**: Liechtenstein\n- **`lt`**: Lithuania\n- **`lu`**: Luxembourg\n- **`mo`**: Macao\n- **`mk`**: Macedonia, the Former Yugosalv Republic of\n- **`mg`**: Madagascar\n- **`mw`**: Malawi\n- **`my`**: Malaysia\n- **`mv`**: Maldives\n- **`ml`**: Mali\n- **`mt`**: Malta\n- **`mh`**: Marshall Islands\n- **`mq`**: Martinique\n- **`mr`**: Mauritania\n- **`mu`**: Mauritius\n- **`yt`**: Mayotte\n- **`mx`**: Mexico\n- **`fm`**: Micronesia, Federated States of\n- **`md`**: Moldova, Republic of\n- **`mc`**: Monaco\n- **`mn`**: Mongolia\n- **`me`**: Montenegro\n- **`ms`**: Montserrat\n- **`ma`**: Morocco\n- **`mz`**: Mozambique\n- **`mm`**: Myanmar\n- **`na`**: Namibia\n- **`nr`**: Nauru\n- **`np`**: Nepal\n- **`nl`**: Netherlands\n- **`an`**: Netherlands Antilles\n- **`nc`**: New Caledonia\n- **`nz`**: New Zealand\n- **`ni`**: Nicaragua\n- **`ne`**: Niger\n- **`ng`**: Nigeria\n- **`nu`**: Niue\n- **`nf`**: Norfolk Island\n- **`mp`**: Northern Mariana Islands\n- **`no`**: Norway\n- **`om`**: Oman\n- **`pk`**: Pakistan\n- **`pw`**: Palau\n- **`ps`**: Palestinian Territory, Occupied\n- **`pa`**: Panama\n- **`pg`**: Papua New Guinea\n- **`py`**: Paraguay\n- **`pe`**: Peru\n- **`ph`**: Philippines\n- **`pn`**: Pitcairn\n- **`pl`**: Poland\n- **`pt`**: Portugal\n- **`pr`**: Puerto Rico\n- **`qa`**: Qatar\n- **`re`**: Reunion\n- **`ro`**: Romania\n- **`ru`**: Russian Federation\n- **`rw`**: Rwanda\n- **`sh`**: Saint Helena\n- **`kn`**: Saint Kitts and Nevis\n- **`lc`**: Saint Lucia\n- **`pm`**: Saint Pierre and Miquelon\n- **`vc`**: Saint Vincent and the Grenadines\n- **`ws`**: Samoa\n- **`sm`**: San Marino\n- **`st`**: Sao Tome and Principe\n- **`sa`**: Saudi Arabia\n- **`sn`**: Senegal\n- **`rs`**: Serbia\n- **`sc`**: Seychelles\n- **`sl`**: Sierra Leone\n- **`sg`**: Singapore\n- **`sk`**: Slovakia\n- **`si`**: Slovenia\n- **`sb`**: Solomon Islands\n- **`so`**: Somalia\n- **`za`**: South Africa\n- **`gs`**: South Georgia and the South Sandwich Islands\n- **`es`**: Spain\n- **`lk`**: Sri Lanka\n- **`sd`**: Sudan\n- **`sr`**: Suriname\n- **`sj`**: Svalbard and Jan Mayen\n- **`sz`**: Swaziland\n- **`se`**: Sweden\n- **`ch`**: Switzerland\n- **`sy`**: Syrian Arab Republic\n- **`tw`**: Taiwan, Province of China\n- **`tj`**: Tajikistan\n- **`tz`**: Tanzania, United Republic of\n- **`th`**: Thailand\n- **`tl`**: Timor-Leste\n- **`tg`**: Togo\n- **`tk`**: Tokelau\n- **`to`**: Tonga\n- **`tt`**: Trinidad and Tobago\n- **`tn`**: Tunisia\n- **`tr`**: Turkiye\n- **`tm`**: Turkmenistan\n- **`tc`**: Turks and Caicos Islands\n- **`tv`**: Tuvalu\n- **`ug`**: Uganda\n- **`ua`**: Ukraine\n- **`ae`**: United Arab Emirates\n- **`uk`**: United Kingdom\n- **`gb`**: United Kingdom\n- **`us`**: United States\n- **`um`**: United States Minor Outlying Islands\n- **`uy`**: Uruguay\n- **`uz`**: Uzbekistan\n- **`vu`**: Vanuatu\n- **`ve`**: Venezuela\n- **`vn`**: Viet Nam\n- **`vg`**: Virgin Islands, British\n- **`vi`**: Virgin Islands, U.S.\n- **`wf`**: Wallis and Futuna\n- **`eh`**: Western Sahara\n- **`ye`**: Yemen\n- **`zm`**: Zambia\n- **`zw`**: Zimbabwe\n\n",
+ "header": "## CountryCodes"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlesearch.json b/toolkit-docs-generator/curation/googlesearch.json
new file mode 100644
index 000000000..40d27c9e4
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlesearch.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Google Search MCP Server uses the [SerpAPI](https://serpapi.com/) to get results from a Google search.\n- **Secret:**\n - `SERP_API_KEY`: Your SerpAPI API key.\n \n Setting the `SERP_API_KEY` secret is only required if you are\n [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're\n using Arcade Cloud, the secret is already set for you. To manage your\n secrets, go to the [Secrets\n page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade\n Dashboard.\n "
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googlesheets.json b/toolkit-docs-generator/curation/googlesheets.json
new file mode 100644
index 000000000..6508f437d
--- /dev/null
+++ b/toolkit-docs-generator/curation/googlesheets.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import ScopePicker from \"@/app/_components/scope-picker\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/googleslides.json b/toolkit-docs-generator/curation/googleslides.json
new file mode 100644
index 000000000..5e377680c
--- /dev/null
+++ b/toolkit-docs-generator/curation/googleslides.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## GoogleSlides Reference\n\nBelow is a reference of enumerations used by some tools in the GoogleSlides MCP Server:\n\n### OrderBy\n\n- **CREATED_TIME**: `createdTime`\n- **CREATED_TIME_DESC**: `createdTime desc`\n- **FOLDER**: `folder`\n- **FOLDER_DESC**: `folder desc`\n- **MODIFIED_BY_ME_TIME**: `modifiedByMeTime`\n- **MODIFIED_BY_ME_TIME_DESC**: `modifiedByMeTime desc`\n- **MODIFIED_TIME**: `modifiedTime`\n- **MODIFIED_TIME_DESC**: `modifiedTime desc`\n- **NAME**: `name`\n- **NAME_DESC**: `name desc`\n- **NAME_NATURAL**: `name_natural`\n- **NAME_NATURAL_DESC**: `name_natural desc`\n- **QUOTA_BYTES_USED**: `quotaBytesUsed`\n- **QUOTA_BYTES_USED_DESC**: `quotaBytesUsed desc`\n- **RECENCY**: `recency`\n- **RECENCY_DESC**: `recency desc`\n- **SHARED_WITH_ME_TIME**: `sharedWithMeTime`\n- **SHARED_WITH_ME_TIME_DESC**: `sharedWithMeTime desc`\n- **STARRED**: `starred`\n- **STARRED_DESC**: `starred desc`\n- **VIEWED_BY_ME_TIME**: `viewedByMeTime`\n- **VIEWED_BY_ME_TIME_DESC**: `viewedByMeTime desc`\n\n",
+ "header": "## GoogleSlides Reference"
+ }
+ ],
+ "customImports": [
+ "import ScopePicker from \"@/app/_components/scope-picker\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspot.json b/toolkit-docs-generator/curation/hubspot.json
new file mode 100644
index 000000000..e8750dfe2
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspot.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Cloud Platform offers a default [Hubspot auth provider](/references/auth-providers/hubspot). If you use it, there's nothing to configure. Your users will see `Arcade` as the name of the application requesting permission."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspotautomationapi.json b/toolkit-docs-generator/curation/hubspotautomationapi.json
new file mode 100644
index 000000000..56ad4f34e
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspotautomationapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The HubspotAutomationApi MCP Server uses the Auth Provider with id `arcade-hubspot` to connect to users' HubspotAutomationApi accounts. In order to use the MCP Server, you will need to configure the `arcade-hubspot` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspotcmsapi.json b/toolkit-docs-generator/curation/hubspotcmsapi.json
new file mode 100644
index 000000000..ba5475085
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspotcmsapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The HubspotCmsApi MCP Server uses the Auth Provider with id `arcade-hubspot` to connect to users' HubspotCmsApi accounts. In order to use the MCP Server, you will need to configure the `arcade-hubspot` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspotconversationsapi.json b/toolkit-docs-generator/curation/hubspotconversationsapi.json
new file mode 100644
index 000000000..8e9d0bd13
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspotconversationsapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The HubspotConversationsApi MCP Server uses the Auth Provider with id `arcade-hubspot` to connect to users' HubspotConversationsApi accounts. In order to use the MCP Server, you will need to configure the `arcade-hubspot` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspotcrmapi.json b/toolkit-docs-generator/curation/hubspotcrmapi.json
new file mode 100644
index 000000000..5bbc51148
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspotcrmapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The HubspotCrmApi MCP Server uses the Auth Provider with id `arcade-hubspot` to connect to users' HubspotCrmApi accounts. In order to use the MCP Server, you will need to configure the `arcade-hubspot` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspoteventsapi.json b/toolkit-docs-generator/curation/hubspoteventsapi.json
new file mode 100644
index 000000000..44853fd14
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspoteventsapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The HubspotEventsApi MCP Server uses the Auth Provider with id `arcade-hubspot` to connect to users' HubspotEventsApi accounts. In order to use the MCP Server, you will need to configure the `arcade-hubspot` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspotmarketingapi.json b/toolkit-docs-generator/curation/hubspotmarketingapi.json
new file mode 100644
index 000000000..de540758b
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspotmarketingapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The HubspotMarketingApi MCP Server uses the Auth Provider with id `arcade-hubspot` to connect to users' HubspotMarketingApi accounts. In order to use the MCP Server, you will need to configure the `arcade-hubspot` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspotmeetingsapi.json b/toolkit-docs-generator/curation/hubspotmeetingsapi.json
new file mode 100644
index 000000000..e13f1202c
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspotmeetingsapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The HubspotMeetingsApi MCP Server uses the Auth Provider with id `arcade-hubspot` to connect to users' HubspotMeetingsApi accounts. In order to use the MCP Server, you will need to configure the `arcade-hubspot` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/hubspotusersapi.json b/toolkit-docs-generator/curation/hubspotusersapi.json
new file mode 100644
index 000000000..ce2975fd3
--- /dev/null
+++ b/toolkit-docs-generator/curation/hubspotusersapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The HubspotUsersApi MCP Server uses the Auth Provider with id `arcade-hubspot` to connect to users' HubspotUsersApi accounts. In order to use the MCP Server, you will need to configure the `arcade-hubspot` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/intercomapi.json b/toolkit-docs-generator/curation/intercomapi.json
new file mode 100644
index 000000000..277273497
--- /dev/null
+++ b/toolkit-docs-generator/curation/intercomapi.json
@@ -0,0 +1,21 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "after_available_tools",
+ "position": "after",
+ "content": "## Intercom API Subdomain\n\nThe IntercomApi MCP Server requires setting the `INTERCOM_API_SUBDOMAIN` secret in the Arcade Dashboard. The appropriate value depends on the region you are using:\n\n- For the United States servers, set `INTERCOM_API_SUBDOMAIN` secret to `api`\n- For the European servers, set `INTERCOM_API_SUBDOMAIN` secret to `api.eu`\n- For the Australian servers, set `INTERCOM_API_SUBDOMAIN` secret to `api.au`",
+ "header": "## Intercom API Subdomain"
+ },
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The IntercomApi MCP Server uses the Auth Provider with id `arcade-intercom` to connect to users' IntercomApi accounts. In order to use the MCP Server, you will need to configure the `arcade-intercom` auth provider.",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/jira.json b/toolkit-docs-generator/curation/jira.json
new file mode 100644
index 000000000..9aa66091c
--- /dev/null
+++ b/toolkit-docs-generator/curation/jira.json
@@ -0,0 +1,17 @@
+{
+ "documentationChunks": [
+ {
+ "type": "warning",
+ "location": "description",
+ "position": "after",
+ "content": "\n\n\n Handling multiple Atlassian Clouds\n
\n\nA Jira user may have multiple Atlassian Clouds authorized via the same OAuth grant. In such cases, the Jira tools must be called with the `atlassian_cloud_id` argument. The [`Jira.GetAvailableAtlassianClouds`](/resources/integrations/productivity/jira#jiragetavailableatlassianclouds) tool can be used to get the available Atlassian Clouds and their IDs.\n\nWhen a tool call does not receive a value for `atlassian_cloud_id` and the user only has a single Atlassian Cloud authorized, the tool will use that. Otherwise, an error will be raised. The error will contain an additional content listing the available Atlassian Clouds and their IDs.\n\nYour AI Agent or AI-powered chat application can use the tool referenced above (or the exception's additional content) to guide the user into selecting the correct Atlassian Cloud.\n\nWhen the user selects an Atlassian Cloud, it may be appropriate to keep this information in the LLM's context window for subsequent tool calls, avoiding the need to ask the user multiple times.\n\n**_It is the job of the AI Agent or chat application to:_**\n\n1. Make it clear to the chat's end user which Atlassian Cloud is being used at any moment, to avoid, for example, having a Jira Issue being created in the wrong Atlassian Cloud;\n1. Appropriately instruct the LLM and keep the relevant information in its context window, enabling it to correctly call the Jira tools, **especially in multi-turn conversations**.\n\n"
+ }
+ ],
+ "subPages": [
+ {
+ "type": "environment-variables",
+ "content": "import { Callout } from \"nextra/components\";\n\n# Jira Environment Variables\n\n### `JIRA_MAX_CONCURRENT_REQUESTS`\n\nArcade uses asynchronous calls to request Jira API endpoints. In some tools, multiple concurrent HTTP requests may be made to speed up execution. This environment variable controls the maximum number of concurrent requests to Jira API in any tool execution.\n\nThe value must be a numeric string with an integer greater than or equal to 1.\n\n**Default:** `3`\n\n\n### `JIRA_API_REQUEST_TIMEOUT`\n\nControls the maximum number of seconds to wait for a response from the Jira API. This is also applied, in some cases, as a global max timeout for multiple requests that are made in a single tool execution. For instance, when a tool needs to paginate results from a given endpoint, this timeout may apply to the entire pagination process in total, not only to the individual requests.\n\nThe value must be a numeric string with an integer greater than or equal to 1.\n\n**Default:** `30`\n\n\n### `JIRA_CACHE_MAX_ITEMS`\n\n\n The caching strategy does not involve caching Jira API responses that go into tool output, but only internal values.\n\n\nThe Arcade Jira MCP Server will cache some values that are repeatedly used in tool execution to enable better performance. This environment variable controls the maximum number of items to hold in each cache.\n\nThe value must be a numeric string with an integer greater than or equal to 1.\n\n**Default:** `5000`\n",
+ "relativePath": "environment-variables/page.mdx"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/linear.json b/toolkit-docs-generator/curation/linear.json
new file mode 100644
index 000000000..580df1537
--- /dev/null
+++ b/toolkit-docs-generator/curation/linear.json
@@ -0,0 +1,11 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Auth\n\nThe Arcade Linear MCP Server uses the [Linear auth provider](/references/auth-providers/linear) to connect to users' Linear accounts. Please refer to the [Linear auth provider](/references/auth-providers/linear) documentation to learn how to configure auth.",
+ "header": "## Auth"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/linkedin.json b/toolkit-docs-generator/curation/linkedin.json
new file mode 100644
index 000000000..6f0c36a5a
--- /dev/null
+++ b/toolkit-docs-generator/curation/linkedin.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade LinkedIn MCP Server uses the [LinkedIn auth provider](/references/auth-providers/linkedin) to connect to users' LinkedIn accounts."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/lumaapi.json b/toolkit-docs-generator/curation/lumaapi.json
new file mode 100644
index 000000000..e7e30650a
--- /dev/null
+++ b/toolkit-docs-generator/curation/lumaapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "warning",
+ "location": "before_available_tools",
+ "position": "after",
+ "content": "## Authentication\n\nThe Arcade Luma API MCP Server requires one environment variable to authenticate with the [Luma API](https://docs.luma.com/reference/getting-started-with-your-api):\n\n- `LUMA_API_KEY`\n\n**How to obtain your credentials:**\n\n1. Navigate to your [Luma dashboard](https://lu.ma/)\n2. Click on your profile icon and go to **Settings**\n3. Navigate to **API** or **Developer Settings**\n4. Click **Generate API Key** or **Create New Key**\n5. Copy the API key and store it securely\n\n\n The Luma API requires a **Luma Plus** subscription. Be careful with your API\n key since it provides full access to your Luma account.\n\n\nFor more details, see the [Luma API Getting Started guide](https://docs.luma.com/reference/getting-started-with-your-api).",
+ "header": "## Authentication"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/mailchimpmarketingapi.json b/toolkit-docs-generator/curation/mailchimpmarketingapi.json
new file mode 100644
index 000000000..a72ef4656
--- /dev/null
+++ b/toolkit-docs-generator/curation/mailchimpmarketingapi.json
@@ -0,0 +1,13 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The MailchimpMarketingApi MCP Server uses the Auth Provider with id `arcade-mailchimp` to connect to users' MailchimpMarketingApi accounts. In order to use the MCP Server, you will need to configure the `arcade-mailchimp` auth provider.\nThe Mailchimp OAuth provider enables secure authentication with Mailchimp's Marketing API using OAuth 2.0. This allows your tools and agents to access user data and perform actions on their behalf. For detailed information on setting up the OAuth provider, including how to register your application with Mailchimp and configure the auth provider in Arcade, see the [Mailchimp Auth Provider documentation](/references/auth-providers/mailchimp)."
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/microsoftteams.json b/toolkit-docs-generator/curation/microsoftteams.json
new file mode 100644
index 000000000..ae80f3074
--- /dev/null
+++ b/toolkit-docs-generator/curation/microsoftteams.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "warning",
+ "location": "description",
+ "position": "after",
+ "content": "\n The Microsoft Teams MCP Server requires a Microsoft 365 account. Personal Microsoft accounts are not supported.\n"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/miroapi.json b/toolkit-docs-generator/curation/miroapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/miroapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/notiontoolkit.json b/toolkit-docs-generator/curation/notiontoolkit.json
new file mode 100644
index 000000000..85e9ddbe3
--- /dev/null
+++ b/toolkit-docs-generator/curation/notiontoolkit.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Notion MCP Server uses the [Notion auth provider](/references/auth-providers/notion) to connect to users' Notion accounts."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/pagerduty.json b/toolkit-docs-generator/curation/pagerduty.json
new file mode 100644
index 000000000..99aff24a9
--- /dev/null
+++ b/toolkit-docs-generator/curation/pagerduty.json
@@ -0,0 +1,22 @@
+{
+ "documentationChunks": [
+ {
+ "type": "warning",
+ "location": "description",
+ "position": "after",
+ "content": "\n Arcade supports Classic PagerDuty apps. Select **read-only** access; all tools\n in this MCP Server only read data. (Use read/write only if you add custom\n write tools.) See [PagerDuty OAuth\n functionality](https://developer.pagerduty.com/docs/oauth-functionality).\n"
+ },
+ {
+ "type": "info",
+ "location": "description",
+ "position": "after",
+ "content": "\n Configure PagerDuty OAuth in the [PagerDuty auth\n provider](/references/auth-providers/pagerduty) before using these tools.\n"
+ },
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "PagerDuty requires OAuth2. Configure the PagerDuty auth provider and request the scopes shown above per tool. Tokens are passed as Bearer auth:\n```\nAuthorization: Bearer \n```\nSee PagerDuty auth docs: [PagerDuty API Authentication](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTYz-authentication)."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/pagerdutyapi.json b/toolkit-docs-generator/curation/pagerdutyapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/pagerdutyapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/posthogapi.json b/toolkit-docs-generator/curation/posthogapi.json
new file mode 100644
index 000000000..7a2aa6f79
--- /dev/null
+++ b/toolkit-docs-generator/curation/posthogapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "before_available_tools",
+ "position": "after",
+ "content": "## Configuration\n\n**Secrets**\n\nThis tool requires the following secrets: `POSTHOG_SERVER_URL`, `POSTHOG_PERSONAL_API_KEY` (learn how to [configure secrets](/guides/create-tools/tool-basics/create-tool-secrets))\nThe PosthogApi MCP Server requires two secrets to authenticate with your PostHog instance:\n\n### Getting Your PostHog Server URL\n\nThe server URL depends on your PostHog deployment:\n\n- **PostHog Cloud (US Region)**: `https://us.posthog.com`\n- **PostHog Cloud (EU Region)**: `https://eu.posthog.com`\n- **Self-Hosted**: Use your instance's base URL (e.g., `https://analytics.yourdomain.com`)\n\nYou can verify your server URL by checking your PostHog account settings or the URL you use to access PostHog.\n\n### Getting Your Personal API Key\n\nTo generate a PostHog personal API key:\n\n1. Log in to your PostHog account\n2. Click your avatar in the bottom-left corner\n3. Select the gear icon to open \"Account settings\"\n4. Navigate to the \"Personal API Keys\" section\n5. Click \"+ Create a personal API key\"\n6. Provide a descriptive label for the key\n7. Select the necessary scopes (choose only the scopes required for your use case)\n8. Click \"Create key\"\n9. **Copy and securely store the key immediately** - it won't be shown again\n\nFor more details on authentication and API usage, refer to the [PostHog API documentation](https://posthog.com/docs/api).\n\nOnce you have both values, configure them as secrets when using the PosthogApi MCP Server. Learn more about [configuring secrets](/guides/create-tools/tool-basics/create-tool-secrets).",
+ "header": "## Configuration"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/pylon.json b/toolkit-docs-generator/curation/pylon.json
new file mode 100644
index 000000000..8d9624105
--- /dev/null
+++ b/toolkit-docs-generator/curation/pylon.json
@@ -0,0 +1,16 @@
+{
+ "documentationChunks": [
+ {
+ "type": "warning",
+ "location": "description",
+ "position": "after",
+ "content": "\n Pylon API tokens are admin-scoped and created in Pylon by an org admin. Store\n the token as `PYLON_API_TOKEN` in Arcade secrets. There is no user OAuth.\n"
+ },
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "Pylon uses Bearer tokens created by an org admin. There is **no OAuth flow**. Generate an API token in the Pylon dashboard and store it as the secret `PYLON_API_TOKEN` in Arcade. All tools require this secret.\n**Auth header**\n```\nAuthorization: Bearer \n```\n\n Pylon tokens are generated by admins in the Pylon UI and grant org-level\n access. Rotate tokens regularly and scope storage to your Arcade project’s\n secrets.\n\nRefer to Pylon’s authentication docs: [Pylon API Authentication](https://docs.usepylon.com/pylon-docs/developer/api/authentication)."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/reddit.json b/toolkit-docs-generator/curation/reddit.json
new file mode 100644
index 000000000..ed7ff8a8e
--- /dev/null
+++ b/toolkit-docs-generator/curation/reddit.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Reddit MCP Server uses the [Reddit auth provider](/references/auth-providers/reddit) to connect to users' Reddit accounts."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/slack.json b/toolkit-docs-generator/curation/slack.json
new file mode 100644
index 000000000..9150d89f6
--- /dev/null
+++ b/toolkit-docs-generator/curation/slack.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "header",
+ "position": "after",
+ "content": "\nManaging channels in Slack requires the `channels:manage` scope, which is only available with bot tokens. Arcade uses user tokens (the type of token Arcade supports), so creating new channels is not possible with this toolkit. You can invite users to an existing channel, but channel creation is not supported.\n"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/slackapi.json b/toolkit-docs-generator/curation/slackapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/slackapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/spotify.json b/toolkit-docs-generator/curation/spotify.json
new file mode 100644
index 000000000..bbadfa7df
--- /dev/null
+++ b/toolkit-docs-generator/curation/spotify.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "warning",
+ "location": "description",
+ "position": "after",
+ "content": "\n This Toolkit is not available in Arcade Cloud. You can use these tools with a\n [self-hosted](/guides/deployment-hosting/configure-engine) instance of Arcade.\n"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/squareupapi.json b/toolkit-docs-generator/curation/squareupapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/squareupapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/stripe.json b/toolkit-docs-generator/curation/stripe.json
new file mode 100644
index 000000000..fc71839a8
--- /dev/null
+++ b/toolkit-docs-generator/curation/stripe.json
@@ -0,0 +1,10 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Stripe MCP Server uses the [Stripe Agent Toolkit](https://github.com/stripe/agent-toolkit) to interact with the Stripe API.\n- **Required Secret:**\n - `STRIPE_SECRET_KEY`: Your Stripe API key."
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/curation/stripeapi.json b/toolkit-docs-generator/curation/stripeapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/stripeapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/ticktickapi.json b/toolkit-docs-generator/curation/ticktickapi.json
new file mode 100644
index 000000000..11e5fa653
--- /dev/null
+++ b/toolkit-docs-generator/curation/ticktickapi.json
@@ -0,0 +1,13 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The TicktickApi MCP Server uses the Auth Provider with id `arcade-ticktick` to connect to users' TickTick accounts. In order to use the MCP Server, you will need to configure the `arcade-ticktick` auth provider.\nLearn how to configure the TickTick auth provider in the [TickTick auth provider documentation](/references/auth-providers/ticktick)."
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/trelloapi.json b/toolkit-docs-generator/curation/trelloapi.json
new file mode 100644
index 000000000..6dea19427
--- /dev/null
+++ b/toolkit-docs-generator/curation/trelloapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Authentication\n\nThe Arcade Trello API MCP Server requires two environment variables to authenticate with the Trello API:\n\n- `TRELLO_API_KEY`\n- `TRELLO_API_TOKEN`\n\n**How to obtain your credentials:**\n\n1. Log in to your [Trello account](https://trello.com/)\n2. Navigate to the [Power-Ups Admin Portal](https://trello.com/power-ups/admin)\n3. Click on \"New\" to create a new Power-Up or select an existing one\n4. In your Power-Up settings, go to the **API Key** tab\n5. Your **API Key** will be displayed\n6. Click on \"Token\" link to generate a **Token** (this will require authorization)\n7. Authorize the token with the required scopes\n8. Copy both the API Key and Token for use in your configuration\n\nAlternatively, you can directly access your API key at: [https://trello.com/app-key](https://trello.com/app-key)\n\nFor more details, see the [Trello API Authentication documentation](https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/).",
+ "header": "## Authentication"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/vercelapi.json b/toolkit-docs-generator/curation/vercelapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/vercelapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/weaviateapi.json b/toolkit-docs-generator/curation/weaviateapi.json
new file mode 100644
index 000000000..6107c8705
--- /dev/null
+++ b/toolkit-docs-generator/curation/weaviateapi.json
@@ -0,0 +1,14 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "before_available_tools",
+ "position": "after",
+ "content": "## Authentication\n\nThe Arcade Weaviate API MCP Server requires two environment variables to authenticate with your Weaviate instance:\n\n- `WEAVIATE_API_KEY`\n- `WEAVIATE_SERVER_URL`\n\n**How to obtain your credentials:**\n\n1. Log in to your [Weaviate Console](https://console.weaviate.cloud/)\n2. Select your Weaviate cluster\n3. Navigate to **Details** or **API Keys** section\n4. Click **Create API Key** or use an existing key\n5. Copy your **API Key**\n6. Copy your **Cluster URL** (this is your server URL and must include `https://`)\n\n**Note:** The `WEAVIATE_SERVER_URL` must include the full URL with the `https://` protocol (e.g., `https://your-cluster.weaviate.network`).\n\nFor more details, see the [Weaviate Authentication documentation](https://weaviate.io/developers/weaviate/configuration/authentication).",
+ "header": "## Authentication"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/xeroapi.json b/toolkit-docs-generator/curation/xeroapi.json
new file mode 100644
index 000000000..36bb65a2d
--- /dev/null
+++ b/toolkit-docs-generator/curation/xeroapi.json
@@ -0,0 +1,5 @@
+{
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/zohobooksapi.json b/toolkit-docs-generator/curation/zohobooksapi.json
new file mode 100644
index 000000000..cd269b4f0
--- /dev/null
+++ b/toolkit-docs-generator/curation/zohobooksapi.json
@@ -0,0 +1,21 @@
+{
+ "documentationChunks": [
+ {
+ "type": "section",
+ "location": "custom_section",
+ "position": "after",
+ "content": "## Secrets\n\nThis MCP Server requires the `ZOHO_SERVER_URL` secret to be configured. Learn how to [configure secrets](/guides/create-tools/tool-basics/create-tool-secrets).\n\n### Getting your Zoho Server URL\n\nThe Zoho Server URL is the base URL for your Zoho account's data center. Zoho operates in multiple data centers around the world, and you must use the correct URL for your account.\n\nYour Zoho Server URL depends on which data center your account is registered in:\n\n| Data Center | Server URL |\n| ----------- | --------------------------- |\n| US | `https://books.zoho.com` |\n| EU | `https://books.zoho.eu` |\n| India | `https://books.zoho.in` |\n| Australia | `https://books.zoho.com.au` |\n| China | `https://books.zoho.com.cn` |\n\nTo determine which data center your account uses:\n\n1. Log in to your Zoho Books account\n2. Look at the URL in your browser's address bar\n3. The domain (`.com`, `.eu`, `.in`, `.com.au`, or `.com.cn`) indicates your data center\n\nFor example, if you access Zoho Books at `https://books.zoho.eu`, your server URL is `https://books.zoho.eu`.\n\nThe server URL is used as the base for all API requests. For example, when retrieving invoices, the full URL would be constructed as:\n\n```\n{zoho_server_url}/api/v3/invoices?organization_id=...\n```\n\nWhich would become `https://books.zoho.com/api/v3/invoices?organization_id=...` for US accounts.",
+ "header": "## Secrets"
+ },
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The ZohoBooksApi MCP Server uses the Auth Provider with id `arcade-zoho` to connect to users' Zoho Books accounts. In order to use the MCP Server, you will need to configure the `arcade-zoho` auth provider.\nLearn how to configure the Zoho auth provider in the [Zoho auth provider documentation](/references/auth-providers/zoho).",
+ "header": "## Auth"
+ }
+ ],
+ "customImports": [
+ "import StarterToolInfo from \"@/app/_components/starter-tool-info\";"
+ ]
+}
diff --git a/toolkit-docs-generator/curation/zoom.json b/toolkit-docs-generator/curation/zoom.json
new file mode 100644
index 000000000..0683acca2
--- /dev/null
+++ b/toolkit-docs-generator/curation/zoom.json
@@ -0,0 +1,18 @@
+{
+ "documentationChunks": [
+ {
+ "type": "markdown",
+ "location": "auth",
+ "position": "after",
+ "content": "The Arcade Zoom MCP Server uses the [Zoom auth provider](/references/auth-providers/zoom) to connect to users' Zoom accounts.",
+ "header": "## Auth"
+ }
+ ],
+ "subPages": [
+ {
+ "type": "install",
+ "content": "# Arcade for Zoom\n\nimport { Steps, Callout } from \"nextra/components\";\nimport { SignupLink } from \"@/app/_components/analytics\";\nimport { ZoomAuthLink } from \"./zoom-auth-link\";\n\n## Integrate Arcade with your Zoom account\n\nArcade securely connects your AI agents to APIs, data, code, and other systems via Tools. Our Zoom integration allows Arcade's tools to connect to your Zoom account, helping you manage meetings and gather information more efficiently.\n\nYou can leverage this app in Arcade's Playground when you log in to the Arcade Dashboard, or in your own applications.\n\nWhile the Arcade app for Zoom does not directly expose a Large Language Model (LLM) to you, you will likely use Arcade's tools in conjunction with an LLM. When using LLMs, there's always potential to generate inaccurate responses, summaries, or other output.\n\nArcade's Zoom app brings Arcade's powerful AI tool-calling capabilities to your meeting management. The Arcade app for Zoom can:\n\n- List your upcoming meetings within the next 24 hours\n- Retrieve meeting invitation details for specific meetings\n- Find the participants and/or registrants for a specific meeting\n- and more!\n\nFor more details on what tools are available and what scopes they require, see the [Zoom MCP Server documentation](/resources/integrations/social-communication/zoom).\n\n\n The Arcade Zoom app requires an active Arcade account. If you don't have one\n yet,{\" \"}\n sign up for free\n .\n\n\n## How it works\n\n\n\n### Start using Arcade's Zoom tools\n\nUse Arcade's [tools for Zoom](/resources/integrations/social-communication/zoom) to:\n\n- List your upcoming meetings\n- Get meeting invitation details\n- Find meeting participants and registrants\n- and more!\n\nTry leveraging the Arcade Zoom tools in the Arcade Playground by [chatting with an LLM](https://api.arcade.dev/dashboard/playground/chat) asking, \"What meetings do I have scheduled today?\" or [executing Zoom tools directly](https://api.arcade.dev/dashboard/playground/execute?toolId=ListUpcomingMeetings&toolkits=%5B%5D&authProviders=%5B%5D&secrets=%5B%5D&input=%7B%22user_id%22%3A%22me%22%7D) without interacting with an LLM.\n\n\n When using LLMs with Zoom, responses may sometimes contain inaccuracies.\n Always review AI-generated content before taking action.\n\n\n\n\n## Support and troubleshooting\n\nIf you encounter any issues connecting Arcade to your Zoom account:\n\n1. Verify you've granted all required permissions during authorization\n2. Ensure your Zoom account is active and in good standing\n3. Check that you're using a compatible browser (Chrome, Firefox, Safari, or Edge)\n4. Clear your browser cache and cookies, then try again\n\n### Adding the Arcade Zoom app to your Zoom account\n\nIf using the Arcade playground directly did not work, you can try adding the Arcade Zoom app to your Zoom account by clicking the \"Connect with Zoom\" button below.\n\n\n\n\n You'll need to have a Zoom account with appropriate permissions to allow\n Arcade to access your Zoom data.\n\n\n### Authorize the requested permissions\n\nWhen connecting Arcade to your Zoom account, depending on which Arcade tools you'll be using, you'll be asked to authorize specific permissions:\n\n- **user:read:user** - Allows Arcade to access basic profile information\n- **user:read:email** - Enables Arcade to access your email address\n- **meeting:read:meetings** - Enables Arcade to list your upcoming meetings\n- **meeting:read:invitation** - Enables Arcade to read meeting invitation details\n\nThese permissions ensure Arcade can perform the necessary functions while protecting your privacy and security.\n\n### Removing the Arcade Zoom app\n\nTo remove the Arcade Zoom app from your Zoom account, you can do so by going to the [Zoom App Marketplace](https://marketplace.zoom.us/user/installed) and uninstalling the app.\n\nArcade only stores authentication tokens, not your Zoom data. These tokens become invalid when you uninstall the app and will eventually expire. To remove tokens immediately, delete the Zoom Auth Provider from the [Arcade Dashboard](https://api.arcade.dev/dashboard/auth/oauth).\n\n## Privacy and security\n\nArcade takes the security of your Zoom data seriously:\n\n- We only request the minimum permissions needed for our tools to function\n- Your Zoom credentials are never stored on our servers\n- All communication between Arcade and Zoom is encrypted\n- You can revoke Arcade's access to your Zoom account at any time through your [Zoom App Marketplace](https://marketplace.zoom.us/user/installed)\n\n## Next steps\n\nThe Arcade Zoom app is a sample of what Arcade can do with your Zoom account. For your own applications, you might want to [create your own Zoom app](/references/auth-providers/zoom). Creating your own Zoom application will allow you to brand the app, customize the permissions, and more.\n\n## Need help?\n\nIf you have any questions or need assistance:\n\n- Check our [Zoom MCP Server documentation](/resources/integrations/social-communication/zoom)\n- [Contact our support team](/resources/contact-us)\n",
+ "relativePath": "install/page.mdx"
+ }
+ ]
+}
diff --git a/toolkit-docs-generator/scripts/extract-curation.ts b/toolkit-docs-generator/scripts/extract-curation.ts
new file mode 100644
index 000000000..bacf3125c
--- /dev/null
+++ b/toolkit-docs-generator/scripts/extract-curation.ts
@@ -0,0 +1,138 @@
+#!/usr/bin/env npx tsx
+/**
+ * One-time extraction of hand-authored prose out of the committed toolkit
+ * artifacts and into per-toolkit curation files.
+ *
+ * `documentationChunks`, `customImports`, and `subPages` have no upstream
+ * source — they exist only inside the generated `data/toolkits/*.json`. This
+ * script gives them a real home under `curation/`, one file per toolkit, so
+ * they stop depending on carry-forward from the previous artifact. The
+ * generator reads them back via `--custom-sections curation`.
+ *
+ * Run from the generator package root:
+ * pnpm dlx tsx scripts/extract-curation.ts
+ *
+ * The output shape matches `CustomSectionsSchema`: each file holds a single
+ * `{ documentationChunks?, customImports?, subPages?, toolChunks? }` object,
+ * keyed by nothing (the file name is the toolkit id). Empty fields are omitted
+ * so diffs stay small; the schema fills them back in with defaults on read.
+ */
+import { mkdir, readdir, readFile, writeFile } from "fs/promises";
+import { dirname, join } from "path";
+import { fileURLToPath } from "url";
+
+/**
+ * Anchored on this file rather than the working directory: the sibling
+ * generator scripts resolve their paths from the repo root, so bare relative
+ * paths here would only work when invoked from toolkit-docs-generator/.
+ */
+const GENERATOR_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
+const TOOLKITS_DIR = join(GENERATOR_ROOT, "data", "toolkits");
+const CURATION_DIR = join(GENERATOR_ROOT, "curation");
+const JSON_INDENT = 2;
+
+type DocumentationChunk = Record;
+type SubPage = string | Record;
+
+type ToolkitArtifact = {
+ id: string;
+ documentationChunks?: DocumentationChunk[];
+ customImports?: string[];
+ subPages?: SubPage[];
+ tools?: { name: string; documentationChunks?: DocumentationChunk[] }[];
+};
+
+type CurationFile = {
+ documentationChunks?: DocumentationChunk[];
+ customImports?: string[];
+ subPages?: SubPage[];
+ toolChunks?: Record;
+};
+
+const isNonEmptyArray = (value: T[] | undefined): value is T[] =>
+ Array.isArray(value) && value.length > 0;
+
+/**
+ * Pull the per-tool documentation chunks out of the artifact, keyed by tool
+ * name — that is the key the merger looks the chunks up by. Today every
+ * toolkit has zero per-tool chunks, but extract them anyway so the format is
+ * complete if that changes.
+ */
+const extractToolChunks = (
+ toolkit: ToolkitArtifact
+): Record => {
+ const toolChunks: Record = {};
+ for (const tool of toolkit.tools ?? []) {
+ if (isNonEmptyArray(tool.documentationChunks)) {
+ toolChunks[tool.name] = tool.documentationChunks;
+ }
+ }
+ return toolChunks;
+};
+
+const buildCurationFile = (toolkit: ToolkitArtifact): CurationFile | null => {
+ const curation: CurationFile = {};
+ if (isNonEmptyArray(toolkit.documentationChunks)) {
+ curation.documentationChunks = toolkit.documentationChunks;
+ }
+ if (isNonEmptyArray(toolkit.customImports)) {
+ curation.customImports = toolkit.customImports;
+ }
+ if (isNonEmptyArray(toolkit.subPages)) {
+ curation.subPages = toolkit.subPages;
+ }
+ const toolChunks = extractToolChunks(toolkit);
+ if (Object.keys(toolChunks).length > 0) {
+ curation.toolChunks = toolChunks;
+ }
+
+ return Object.keys(curation).length > 0 ? curation : null;
+};
+
+async function main(): Promise {
+ await mkdir(CURATION_DIR, { recursive: true });
+
+ const files = (await readdir(TOOLKITS_DIR))
+ .filter((file) => file.endsWith(".json") && file !== "index.json")
+ .sort();
+
+ let written = 0;
+ let chunkCount = 0;
+ let subPageCount = 0;
+ let importCount = 0;
+
+ for (const file of files) {
+ const toolkit = JSON.parse(
+ await readFile(join(TOOLKITS_DIR, file), "utf-8")
+ ) as ToolkitArtifact;
+
+ const curation = buildCurationFile(toolkit);
+ if (!curation) {
+ continue;
+ }
+
+ await writeFile(
+ join(CURATION_DIR, file),
+ `${JSON.stringify(curation, null, JSON_INDENT)}\n`,
+ "utf-8"
+ );
+
+ written++;
+ chunkCount += curation.documentationChunks?.length ?? 0;
+ for (const chunks of Object.values(curation.toolChunks ?? {})) {
+ chunkCount += chunks.length;
+ }
+ subPageCount += curation.subPages?.length ?? 0;
+ importCount += curation.customImports?.length ?? 0;
+ }
+
+ console.log(`Wrote ${written} curation files to ${CURATION_DIR}/`);
+ console.log(
+ ` documentationChunks: ${chunkCount}, subPages: ${subPageCount}, customImports: ${importCount}`
+ );
+}
+
+main().catch((error) => {
+ console.error("Extraction failed:", error);
+ process.exit(1);
+});
diff --git a/toolkit-docs-generator/src/cli/index.ts b/toolkit-docs-generator/src/cli/index.ts
index 69e2c8b0c..148ae2e75 100644
--- a/toolkit-docs-generator/src/cli/index.ts
+++ b/toolkit-docs-generator/src/cli/index.ts
@@ -21,6 +21,7 @@ import {
formatChangeSummary,
formatDetailedChanges,
getChangedToolkitIds,
+ getChangedToolkitIdsFromCustomSections,
hasChanges,
} from "../diff/index.js";
import { parsePreviousToolkitForDiff } from "../diff/previous-output.js";
@@ -885,7 +886,10 @@ program
.option("--skip-examples", "Skip LLM example generation", false)
.option("--skip-summary", "Skip LLM summary generation", false)
.option("--no-verify-output", "Skip output verification")
- .option("--custom-sections ", "Path to custom sections JSON")
+ .option(
+ "--custom-sections ",
+ "Path to custom sections: a directory of per-toolkit files (curation/) or a single JSON file"
+ )
.option(
"--resume",
"Resume from previous run, skipping already-generated toolkits",
@@ -1336,6 +1340,14 @@ program
currentToolkitDataForDiff,
previousToolkits ?? new Map()
);
+ const changedCustomSectionIds = new Set(
+ options.customSections
+ ? getChangedToolkitIdsFromCustomSections(
+ await customSectionsSource.getAllCustomSections(),
+ previousToolkits ?? new Map()
+ ).map((id) => id.toLowerCase())
+ : []
+ );
const compareDurationMs = Date.now() - compareStartedAt;
if (options.verbose) {
console.log(
@@ -1362,7 +1374,10 @@ program
}
}
- if (!hasChanges(detectedChanges)) {
+ if (
+ !hasChanges(detectedChanges) &&
+ changedCustomSectionIds.size === 0
+ ) {
spinner.succeed(
"No changes detected. All toolkits are up to date."
);
@@ -1402,7 +1417,12 @@ program
}
// Get IDs of changed toolkits
- const changedIds = getChangedToolkitIds(detectedChanges);
+ const changedIds = [
+ ...new Set([
+ ...getChangedToolkitIds(detectedChanges),
+ ...changedCustomSectionIds,
+ ]),
+ ];
changedToolkitIds = new Set(changedIds.map((id) => id.toLowerCase()));
changeResult = detectedChanges;
const changedPreview =
@@ -1953,7 +1973,10 @@ program
.option("--skip-examples", "Skip LLM example generation", false)
.option("--skip-summary", "Skip LLM summary generation", false)
.option("--no-verify-output", "Skip output verification")
- .option("--custom-sections ", "Path to custom sections JSON")
+ .option(
+ "--custom-sections ",
+ "Path to custom sections: a directory of per-toolkit files (curation/) or a single JSON file"
+ )
.option(
"--resume",
"Resume from previous run, skipping already-generated toolkits",
diff --git a/toolkit-docs-generator/src/diff/custom-sections-diff.ts b/toolkit-docs-generator/src/diff/custom-sections-diff.ts
new file mode 100644
index 000000000..cbe82e89e
--- /dev/null
+++ b/toolkit-docs-generator/src/diff/custom-sections-diff.ts
@@ -0,0 +1,51 @@
+import { stableStringify } from "../merger/data-merger.js";
+import type { CustomSections, MergedToolkit } from "../types/index.js";
+
+const customSectionsFromToolkit = (toolkit: MergedToolkit): CustomSections => ({
+ documentationChunks: toolkit.documentationChunks ?? [],
+ customImports: toolkit.customImports ?? [],
+ subPages: toolkit.subPages ?? [],
+ toolChunks: Object.fromEntries(
+ (toolkit.tools ?? [])
+ .filter((tool) => tool.documentationChunks?.length)
+ .map((tool) => [tool.name, tool.documentationChunks])
+ ),
+});
+
+export const getChangedToolkitIdsFromCustomSections = (
+ current: Readonly>,
+ previous: ReadonlyMap
+): string[] => {
+ const ids = new Set([
+ ...Object.keys(current).map((id) => id.toLowerCase()),
+ ...[...previous.keys()].map((id) => id.toLowerCase()),
+ ]);
+ const currentById = new Map(
+ Object.entries(current).map(([id, sections]) => [
+ id.toLowerCase(),
+ sections,
+ ])
+ );
+ return [...ids].filter((id) => {
+ const currentSections = currentById.get(id) ?? {
+ documentationChunks: [],
+ customImports: [],
+ subPages: [],
+ toolChunks: {},
+ };
+ const previousToolkit = [...previous.entries()].find(
+ ([previousId]) => previousId.toLowerCase() === id
+ )?.[1];
+ const previousSections = previousToolkit
+ ? customSectionsFromToolkit(previousToolkit)
+ : {
+ documentationChunks: [],
+ customImports: [],
+ subPages: [],
+ toolChunks: {},
+ };
+ return (
+ stableStringify(currentSections) !== stableStringify(previousSections)
+ );
+ });
+};
diff --git a/toolkit-docs-generator/src/diff/index.ts b/toolkit-docs-generator/src/diff/index.ts
index e818d8f84..a2956459a 100644
--- a/toolkit-docs-generator/src/diff/index.ts
+++ b/toolkit-docs-generator/src/diff/index.ts
@@ -4,6 +4,7 @@
* Exports change detection functionality for comparing API data with previous output.
*/
+export { getChangedToolkitIdsFromCustomSections } from "./custom-sections-diff.js";
export {
detectSummaryChanges,
formatSummaryChangeSummary,
diff --git a/toolkit-docs-generator/src/merger/data-merger.ts b/toolkit-docs-generator/src/merger/data-merger.ts
index 15d28def8..7bbf9d790 100644
--- a/toolkit-docs-generator/src/merger/data-merger.ts
+++ b/toolkit-docs-generator/src/merger/data-merger.ts
@@ -114,7 +114,7 @@ export interface ToolkitSummaryGenerator {
}
interface MergeToolkitOptions {
- previousToolkit?: MergedToolkit;
+ previousToolkit?: MergedToolkit | undefined;
/** Maximum concurrent LLM calls for tool examples (default: 5) */
llmConcurrency?: number;
/** Fallback resolver: toolkit ID → OAuth provider ID (design system) */
diff --git a/toolkit-docs-generator/src/sources/custom-sections-file.ts b/toolkit-docs-generator/src/sources/custom-sections-file.ts
index 2ed843062..8f1c98387 100644
--- a/toolkit-docs-generator/src/sources/custom-sections-file.ts
+++ b/toolkit-docs-generator/src/sources/custom-sections-file.ts
@@ -1,16 +1,23 @@
/**
* Custom Sections File Source
*
- * Loads custom documentation sections from a JSON file.
- * This file is produced by the one-time MDX extraction script.
+ * Loads hand-authored documentation sections that have no upstream source
+ * (documentation chunks, custom imports, sub-pages). Two layouts are
+ * supported:
+ *
+ * - A directory (e.g. `curation/`) of per-toolkit files, one file per
+ * toolkit named `.json`, each holding a single
+ * `CustomSections` object. This is the layout the generator reads today;
+ * one file per toolkit keeps prose edits to reviewable, single-toolkit
+ * diffs.
+ * - A single JSON file that maps toolkit id to `CustomSections`. This is the
+ * legacy shape produced by the one-time MDX extraction.
*/
-import { access, readFile } from "fs/promises";
+import { access, readdir, readFile, stat } from "fs/promises";
+import { basename, join } from "path";
import { z } from "zod";
import type { CustomSections } from "../types/index.js";
-import {
- DocumentationChunkSchema,
- ToolkitSubPageSchema,
-} from "../types/index.js";
+import { CustomSectionsSchema } from "../types/index.js";
import { normalizeId } from "../utils/fp.js";
import type { ICustomSectionsSource } from "./interfaces.js";
@@ -18,32 +25,25 @@ import type { ICustomSectionsSource } from "./interfaces.js";
// File Schema
// ============================================================================
-const CustomSectionsFileSchema = z.record(
- z.string(),
- z.object({
- documentationChunks: z.array(DocumentationChunkSchema).default([]),
- customImports: z.array(z.string()).default([]),
- subPages: z.array(ToolkitSubPageSchema).default([]),
- toolChunks: z
- .record(z.string(), z.array(DocumentationChunkSchema))
- .default({}),
- })
-);
+/** A single JSON file mapping toolkit id -> custom sections (legacy layout). */
+const CustomSectionsFileSchema = z.record(z.string(), CustomSectionsSchema);
-type CustomSectionsFile = z.infer;
+type CustomSectionsData = Record;
// ============================================================================
// Custom Sections File Source
// ============================================================================
export interface CustomSectionsFileConfig {
+ /** Path to either a directory of per-toolkit files or a single JSON file. */
filePath: string;
}
-const parseCustomSectionsFile = (
+const parseJsonWithSchema = (
content: string,
- filePath: string
-): CustomSectionsFile => {
+ filePath: string,
+ schema: z.ZodType
+): T => {
let parsedJson: unknown;
try {
parsedJson = JSON.parse(content) as unknown;
@@ -54,7 +54,7 @@ const parseCustomSectionsFile = (
);
}
- const parsed = CustomSectionsFileSchema.safeParse(parsedJson);
+ const parsed = schema.safeParse(parsedJson);
if (!parsed.success) {
throw new Error(
`Custom sections file has invalid schema (${filePath}): ${parsed.error.message}`
@@ -65,29 +65,54 @@ const parseCustomSectionsFile = (
};
/**
- * Source that loads custom documentation sections from a JSON file
+ * Source that loads custom documentation sections from disk.
*/
export class CustomSectionsFileSource implements ICustomSectionsSource {
private readonly filePath: string;
- private cachedData: CustomSectionsFile | null = null;
+ private cachedData: CustomSectionsData | null = null;
constructor(config: CustomSectionsFileConfig) {
this.filePath = config.filePath;
}
- private async loadFile(): Promise {
+ private async loadDirectory(dirPath: string): Promise {
+ const entries = (await readdir(dirPath)).filter((name) =>
+ name.endsWith(".json")
+ );
+
+ const data: CustomSectionsData = {};
+ for (const entry of entries) {
+ const entryPath = join(dirPath, entry);
+ const content = await readFile(entryPath, "utf-8");
+ const toolkitId = basename(entry, ".json");
+ data[toolkitId] = parseJsonWithSchema(
+ content,
+ entryPath,
+ CustomSectionsSchema
+ );
+ }
+ return data;
+ }
+
+ private async loadData(): Promise {
if (this.cachedData !== null) {
return this.cachedData;
}
try {
await access(this.filePath);
- const content = await readFile(this.filePath, "utf-8");
- this.cachedData = parseCustomSectionsFile(content, this.filePath);
+ const stats = await stat(this.filePath);
+ this.cachedData = stats.isDirectory()
+ ? await this.loadDirectory(this.filePath)
+ : parseJsonWithSchema(
+ await readFile(this.filePath, "utf-8"),
+ this.filePath,
+ CustomSectionsFileSchema
+ );
return this.cachedData;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
- // File doesn't exist - return empty data
+ // Path doesn't exist - return empty data
this.cachedData = {};
return this.cachedData;
}
@@ -96,7 +121,7 @@ export class CustomSectionsFileSource implements ICustomSectionsSource {
}
async getCustomSections(toolkitId: string): Promise {
- const data = await this.loadFile();
+ const data = await this.loadData();
// Try exact match
if (data[toolkitId]) {
@@ -115,8 +140,7 @@ export class CustomSectionsFileSource implements ICustomSectionsSource {
async getAllCustomSections(): Promise<
Readonly>
> {
- const data = await this.loadFile();
- return data;
+ return this.loadData();
}
}
diff --git a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts
new file mode 100644
index 000000000..01da56eee
--- /dev/null
+++ b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts
@@ -0,0 +1,66 @@
+import { describe, expect, it } from "vitest";
+import { getChangedToolkitIdsFromCustomSections } from "../../src/diff/index.js";
+import type { MergedToolkit } from "../../src/types/index.js";
+
+const previousToolkit = (documentation = "old"): MergedToolkit => ({
+ id: "Github",
+ label: "Github",
+ version: "1.0.0",
+ description: "GitHub",
+ metadata: null,
+ auth: null,
+ tools: [],
+ documentationChunks: [
+ {
+ type: "text",
+ location: "description",
+ position: "after",
+ content: documentation,
+ },
+ ],
+ customImports: [],
+ subPages: [],
+ generatedAt: "2026-01-01T00:00:00.000Z",
+});
+
+describe("getChangedToolkitIdsFromCustomSections", () => {
+ it("treats curation-only edits as toolkit changes", () => {
+ expect(
+ getChangedToolkitIdsFromCustomSections(
+ {
+ github: {
+ documentationChunks: [
+ {
+ type: "text",
+ location: "description",
+ position: "after",
+ content: "new",
+ },
+ ],
+ customImports: [],
+ subPages: [],
+ toolChunks: {},
+ },
+ },
+ new Map([["Github", previousToolkit()]])
+ )
+ ).toEqual(["github"]);
+ });
+
+ it("does not report identical curation", () => {
+ const toolkit = previousToolkit();
+ expect(
+ getChangedToolkitIdsFromCustomSections(
+ {
+ Github: {
+ documentationChunks: toolkit.documentationChunks,
+ customImports: [],
+ subPages: [],
+ toolChunks: {},
+ },
+ },
+ new Map([["Github", toolkit]])
+ )
+ ).toEqual([]);
+ });
+});
diff --git a/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts b/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts
new file mode 100644
index 000000000..896780aa1
--- /dev/null
+++ b/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts
@@ -0,0 +1,121 @@
+/**
+ * Scenario Test: Hand-authored prose survives --force-regenerate
+ *
+ * `documentationChunks`, `customImports`, and `subPages` have no upstream
+ * source. Before curation files, they survived only by carry-forward from the
+ * previous artifact — and `--force-regenerate` / `--overwrite-output` set the
+ * previous-output directory to undefined, discarding all of it.
+ *
+ * These tests reproduce the force-regenerate condition (no previous toolkit)
+ * and assert that prose loaded from a `curation/` directory still lands in the
+ * merged output. The final test pins the old bug: with no previous toolkit and
+ * no curation, the prose is gone.
+ */
+import { mkdtemp, rm, writeFile } from "fs/promises";
+import { tmpdir } from "os";
+import { join } from "path";
+import { afterEach, describe, expect, it } from "vitest";
+import { mergeToolkit } from "../../src/merger/data-merger.js";
+import { createCustomSectionsFileSource } from "../../src/sources/custom-sections-file.js";
+import type { ToolDefinition } from "../../src/types/index.js";
+
+const createTool = (): ToolDefinition => ({
+ name: "TestTool",
+ qualifiedName: "TestKit.TestTool",
+ fullyQualifiedName: "TestKit.TestTool@1.0.0",
+ description: "A test tool",
+ toolkitDescription: "Toolkit description",
+ parameters: [],
+ auth: null,
+ secrets: [],
+ output: { type: "object", description: "Result" },
+});
+
+const curationEntry = {
+ documentationChunks: [
+ {
+ type: "warning",
+ location: "description",
+ position: "after",
+ content: "Hand-authored guidance that has no upstream source.",
+ },
+ ],
+ customImports: ['import { Callout } from "nextra/components";'],
+ subPages: [
+ {
+ type: "environment-variables",
+ content: "# Environment Variables\n",
+ relativePath: "environment-variables/page.mdx",
+ },
+ ],
+};
+
+describe("prose survives --force-regenerate", () => {
+ let tempDir: string | null = null;
+
+ afterEach(async () => {
+ if (tempDir) {
+ await rm(tempDir, { recursive: true, force: true });
+ tempDir = null;
+ }
+ });
+
+ it("keeps curation prose when there is no previous toolkit to carry forward", async () => {
+ tempDir = await mkdtemp(join(tmpdir(), "curation-"));
+ // File name is the toolkit id; loaded from a directory like `curation/`.
+ await writeFile(
+ join(tempDir, "testkit.json"),
+ JSON.stringify(curationEntry, null, 2)
+ );
+
+ const source = createCustomSectionsFileSource(tempDir);
+ const customSections = await source.getCustomSections("TestKit");
+ expect(customSections).not.toBeNull();
+
+ // previousToolkit undefined == what --force-regenerate produces.
+ const result = await mergeToolkit(
+ "TestKit",
+ [createTool()],
+ null,
+ customSections,
+ undefined,
+ { previousToolkit: undefined }
+ );
+
+ expect(result.toolkit.documentationChunks).toHaveLength(1);
+ expect(result.toolkit.documentationChunks[0]?.content).toBe(
+ "Hand-authored guidance that has no upstream source."
+ );
+ expect(result.toolkit.customImports).toEqual(curationEntry.customImports);
+ expect(result.toolkit.subPages).toEqual(curationEntry.subPages);
+ });
+
+ it("normalizes the toolkit id when matching curation files", async () => {
+ tempDir = await mkdtemp(join(tmpdir(), "curation-"));
+ await writeFile(
+ join(tempDir, "notiontoolkit.json"),
+ JSON.stringify(curationEntry, null, 2)
+ );
+
+ const source = createCustomSectionsFileSource(tempDir);
+ // File stem "notiontoolkit" must match toolkit id "NotionToolkit".
+ const customSections = await source.getCustomSections("NotionToolkit");
+
+ expect(customSections?.documentationChunks).toHaveLength(1);
+ });
+
+ it("loses prose without curation and without a previous toolkit (the bug)", async () => {
+ const result = await mergeToolkit(
+ "TestKit",
+ [createTool()],
+ null,
+ null,
+ undefined,
+ { previousToolkit: undefined }
+ );
+
+ expect(result.toolkit.documentationChunks).toHaveLength(0);
+ expect(result.toolkit.customImports).toHaveLength(0);
+ expect(result.toolkit.subPages).toHaveLength(0);
+ });
+});
diff --git a/toolkit-docs-generator/tests/sources/custom-sections-file.test.ts b/toolkit-docs-generator/tests/sources/custom-sections-file.test.ts
index 1e4af1936..75ea213b2 100644
--- a/toolkit-docs-generator/tests/sources/custom-sections-file.test.ts
+++ b/toolkit-docs-generator/tests/sources/custom-sections-file.test.ts
@@ -1,4 +1,4 @@
-import { mkdtemp, rm, writeFile } from "fs/promises";
+import { mkdir, mkdtemp, rm, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
import { afterEach, describe, expect, it } from "vitest";
@@ -107,6 +107,63 @@ describe("CustomSectionsFileSource", () => {
);
});
+ it("loads a directory of per-toolkit files keyed by file name", async () => {
+ tempDir = await createTempDir();
+ const dirPath = join(tempDir, "curation");
+ await mkdir(dirPath);
+ await writeFile(
+ join(dirPath, "github.json"),
+ JSON.stringify(
+ {
+ documentationChunks: [
+ {
+ type: "warning",
+ location: "description",
+ position: "after",
+ content: "Prose",
+ },
+ ],
+ },
+ null,
+ 2
+ )
+ );
+ await writeFile(
+ join(dirPath, "slack.json"),
+ JSON.stringify({ customImports: ["import X from 'x';"] }, null, 2)
+ );
+
+ const source = createCustomSectionsFileSource(dirPath);
+
+ const github = await source.getCustomSections("Github");
+ expect(github?.documentationChunks).toHaveLength(1);
+ // File stem "github" matches toolkit id "Github" via normalization.
+ expect(github?.customImports).toEqual([]);
+
+ const slack = await source.getCustomSections("Slack");
+ expect(slack?.customImports).toEqual(["import X from 'x';"]);
+
+ const all = await source.getAllCustomSections();
+ expect(Object.keys(all).sort()).toEqual(["github", "slack"]);
+ });
+
+ it("throws a helpful error when a directory file has an invalid schema", async () => {
+ tempDir = await createTempDir();
+ const dirPath = join(tempDir, "curation");
+ await mkdir(dirPath);
+ const badPath = join(dirPath, "github.json");
+ await writeFile(
+ badPath,
+ JSON.stringify({ documentationChunks: "not-an-array" }, null, 2)
+ );
+
+ const source = createCustomSectionsFileSource(dirPath);
+
+ await expect(source.getAllCustomSections()).rejects.toThrow(
+ `Custom sections file has invalid schema (${badPath})`
+ );
+ });
+
it("rejects malformed rich subpage entries", async () => {
tempDir = await createTempDir();
const filePath = join(tempDir, "invalid-subpage.json");