From 0587043ad70ef238c0e60d2602d6bd9a07eb52e7 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Wed, 5 Aug 2026 14:01:39 -0400 Subject: [PATCH 01/12] fix: give hand-authored toolkit prose a source home in curation/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit documentationChunks, customImports, and subPages have no upstream source — they lived only inside the generated data/toolkits/*.json and survived by carry-forward from the previous artifact. --force-regenerate and --overwrite-output set the previous-output directory to undefined, silently deleting all hand-authored prose (82 chunks and 2 subpages across 75 toolkits). Extract that prose into per-toolkit curation/.json files and read them back through --custom-sections, which the merger treats as authoritative over carry-forward. The nightly workflow now passes --custom-sections ./curation, so a forced regeneration preserves prose instead of wiping it. - custom-sections-file source now loads a directory of per-toolkit files (single-file layout still supported) - scripts/extract-curation.ts is the one-time, re-runnable extractor - curation/ is excluded from biome like its sibling data/toolkits/ - regression test asserts prose survives --force-regenerate Co-Authored-By: Claude Opus 4.8 --- .github/workflows/generate-toolkit-docs.yml | 1 + biome.jsonc | 1 + .../curation/airtableapi.json | 5 + .../curation/arcadeengineapi.json | 14 ++ toolkit-docs-generator/curation/asana.json | 10 ++ toolkit-docs-generator/curation/asanaapi.json | 14 ++ toolkit-docs-generator/curation/ashbyapi.json | 5 + toolkit-docs-generator/curation/boxapi.json | 5 + .../curation/brightdata.json | 11 ++ .../curation/calendlyapi.json | 5 + toolkit-docs-generator/curation/clickup.json | 25 ++++ .../curation/clickupapi.json | 14 ++ .../curation/confluence.json | 10 ++ .../curation/cursoragentsapi.json | 5 + .../curation/customerioapi.json | 5 + .../curation/customeriopipelinesapi.json | 5 + .../curation/customeriotrackapi.json | 5 + .../curation/datadogapi.json | 14 ++ toolkit-docs-generator/curation/dropbox.json | 10 ++ toolkit-docs-generator/curation/e2b.json | 10 ++ toolkit-docs-generator/curation/exaapi.json | 5 + toolkit-docs-generator/curation/figma.json | 10 ++ toolkit-docs-generator/curation/figmaapi.json | 5 + .../curation/firecrawl.json | 10 ++ .../curation/freshserviceapi.json | 5 + toolkit-docs-generator/curation/github.json | 40 ++++++ .../curation/githubapi.json | 21 +++ toolkit-docs-generator/curation/gmail.json | 21 +++ .../curation/googlecalendar.json | 21 +++ .../curation/googlecontacts.json | 13 ++ .../curation/googledocs.json | 27 ++++ .../curation/googledrive.json | 5 + .../curation/googlefinance.json | 18 +++ .../curation/googleflights.json | 32 +++++ .../curation/googlehotels.json | 18 +++ .../curation/googlejobs.json | 25 ++++ .../curation/googlemaps.json | 18 +++ .../curation/googlenews.json | 32 +++++ .../curation/googlesearch.json | 10 ++ .../curation/googlesheets.json | 5 + .../curation/googleslides.json | 14 ++ toolkit-docs-generator/curation/hubspot.json | 10 ++ .../curation/hubspotautomationapi.json | 14 ++ .../curation/hubspotcmsapi.json | 14 ++ .../curation/hubspotconversationsapi.json | 14 ++ .../curation/hubspotcrmapi.json | 14 ++ .../curation/hubspoteventsapi.json | 14 ++ .../curation/hubspotmarketingapi.json | 14 ++ .../curation/hubspotmeetingsapi.json | 14 ++ .../curation/hubspotusersapi.json | 14 ++ .../curation/intercomapi.json | 21 +++ toolkit-docs-generator/curation/jira.json | 17 +++ toolkit-docs-generator/curation/linear.json | 11 ++ toolkit-docs-generator/curation/linkedin.json | 10 ++ toolkit-docs-generator/curation/lumaapi.json | 14 ++ .../curation/mailchimpmarketingapi.json | 13 ++ .../curation/microsoftteams.json | 10 ++ toolkit-docs-generator/curation/miroapi.json | 5 + .../curation/notiontoolkit.json | 10 ++ .../curation/pagerduty.json | 22 +++ .../curation/pagerdutyapi.json | 5 + .../curation/posthogapi.json | 14 ++ toolkit-docs-generator/curation/pylon.json | 16 +++ toolkit-docs-generator/curation/reddit.json | 10 ++ toolkit-docs-generator/curation/slack.json | 10 ++ toolkit-docs-generator/curation/slackapi.json | 5 + toolkit-docs-generator/curation/spotify.json | 10 ++ .../curation/squareupapi.json | 5 + toolkit-docs-generator/curation/stripe.json | 10 ++ .../curation/stripeapi.json | 5 + .../curation/ticktickapi.json | 13 ++ .../curation/trelloapi.json | 14 ++ .../curation/vercelapi.json | 5 + .../curation/weaviateapi.json | 14 ++ toolkit-docs-generator/curation/xeroapi.json | 5 + .../curation/zohobooksapi.json | 21 +++ toolkit-docs-generator/curation/zoom.json | 18 +++ .../scripts/extract-curation.ts | 131 ++++++++++++++++++ toolkit-docs-generator/src/cli/index.ts | 10 +- .../src/sources/custom-sections-file.ts | 88 +++++++----- .../prose-survives-force-regenerate.test.ts | 121 ++++++++++++++++ .../sources/custom-sections-file.test.ts | 59 +++++++- 82 files changed, 1338 insertions(+), 35 deletions(-) create mode 100644 toolkit-docs-generator/curation/airtableapi.json create mode 100644 toolkit-docs-generator/curation/arcadeengineapi.json create mode 100644 toolkit-docs-generator/curation/asana.json create mode 100644 toolkit-docs-generator/curation/asanaapi.json create mode 100644 toolkit-docs-generator/curation/ashbyapi.json create mode 100644 toolkit-docs-generator/curation/boxapi.json create mode 100644 toolkit-docs-generator/curation/brightdata.json create mode 100644 toolkit-docs-generator/curation/calendlyapi.json create mode 100644 toolkit-docs-generator/curation/clickup.json create mode 100644 toolkit-docs-generator/curation/clickupapi.json create mode 100644 toolkit-docs-generator/curation/confluence.json create mode 100644 toolkit-docs-generator/curation/cursoragentsapi.json create mode 100644 toolkit-docs-generator/curation/customerioapi.json create mode 100644 toolkit-docs-generator/curation/customeriopipelinesapi.json create mode 100644 toolkit-docs-generator/curation/customeriotrackapi.json create mode 100644 toolkit-docs-generator/curation/datadogapi.json create mode 100644 toolkit-docs-generator/curation/dropbox.json create mode 100644 toolkit-docs-generator/curation/e2b.json create mode 100644 toolkit-docs-generator/curation/exaapi.json create mode 100644 toolkit-docs-generator/curation/figma.json create mode 100644 toolkit-docs-generator/curation/figmaapi.json create mode 100644 toolkit-docs-generator/curation/firecrawl.json create mode 100644 toolkit-docs-generator/curation/freshserviceapi.json create mode 100644 toolkit-docs-generator/curation/github.json create mode 100644 toolkit-docs-generator/curation/githubapi.json create mode 100644 toolkit-docs-generator/curation/gmail.json create mode 100644 toolkit-docs-generator/curation/googlecalendar.json create mode 100644 toolkit-docs-generator/curation/googlecontacts.json create mode 100644 toolkit-docs-generator/curation/googledocs.json create mode 100644 toolkit-docs-generator/curation/googledrive.json create mode 100644 toolkit-docs-generator/curation/googlefinance.json create mode 100644 toolkit-docs-generator/curation/googleflights.json create mode 100644 toolkit-docs-generator/curation/googlehotels.json create mode 100644 toolkit-docs-generator/curation/googlejobs.json create mode 100644 toolkit-docs-generator/curation/googlemaps.json create mode 100644 toolkit-docs-generator/curation/googlenews.json create mode 100644 toolkit-docs-generator/curation/googlesearch.json create mode 100644 toolkit-docs-generator/curation/googlesheets.json create mode 100644 toolkit-docs-generator/curation/googleslides.json create mode 100644 toolkit-docs-generator/curation/hubspot.json create mode 100644 toolkit-docs-generator/curation/hubspotautomationapi.json create mode 100644 toolkit-docs-generator/curation/hubspotcmsapi.json create mode 100644 toolkit-docs-generator/curation/hubspotconversationsapi.json create mode 100644 toolkit-docs-generator/curation/hubspotcrmapi.json create mode 100644 toolkit-docs-generator/curation/hubspoteventsapi.json create mode 100644 toolkit-docs-generator/curation/hubspotmarketingapi.json create mode 100644 toolkit-docs-generator/curation/hubspotmeetingsapi.json create mode 100644 toolkit-docs-generator/curation/hubspotusersapi.json create mode 100644 toolkit-docs-generator/curation/intercomapi.json create mode 100644 toolkit-docs-generator/curation/jira.json create mode 100644 toolkit-docs-generator/curation/linear.json create mode 100644 toolkit-docs-generator/curation/linkedin.json create mode 100644 toolkit-docs-generator/curation/lumaapi.json create mode 100644 toolkit-docs-generator/curation/mailchimpmarketingapi.json create mode 100644 toolkit-docs-generator/curation/microsoftteams.json create mode 100644 toolkit-docs-generator/curation/miroapi.json create mode 100644 toolkit-docs-generator/curation/notiontoolkit.json create mode 100644 toolkit-docs-generator/curation/pagerduty.json create mode 100644 toolkit-docs-generator/curation/pagerdutyapi.json create mode 100644 toolkit-docs-generator/curation/posthogapi.json create mode 100644 toolkit-docs-generator/curation/pylon.json create mode 100644 toolkit-docs-generator/curation/reddit.json create mode 100644 toolkit-docs-generator/curation/slack.json create mode 100644 toolkit-docs-generator/curation/slackapi.json create mode 100644 toolkit-docs-generator/curation/spotify.json create mode 100644 toolkit-docs-generator/curation/squareupapi.json create mode 100644 toolkit-docs-generator/curation/stripe.json create mode 100644 toolkit-docs-generator/curation/stripeapi.json create mode 100644 toolkit-docs-generator/curation/ticktickapi.json create mode 100644 toolkit-docs-generator/curation/trelloapi.json create mode 100644 toolkit-docs-generator/curation/vercelapi.json create mode 100644 toolkit-docs-generator/curation/weaviateapi.json create mode 100644 toolkit-docs-generator/curation/xeroapi.json create mode 100644 toolkit-docs-generator/curation/zohobooksapi.json create mode 100644 toolkit-docs-generator/curation/zoom.json create mode 100644 toolkit-docs-generator/scripts/extract-curation.ts create mode 100644 toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts 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..5a0b703d4 --- /dev/null +++ b/toolkit-docs-generator/scripts/extract-curation.ts @@ -0,0 +1,131 @@ +#!/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 { join } from "path"; + +const TOOLKITS_DIR = "data/toolkits"; +const CURATION_DIR = "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: unknown[] | undefined): boolean => + 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 as DocumentationChunk[]; + } + } + 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..b3c639364 100644 --- a/toolkit-docs-generator/src/cli/index.ts +++ b/toolkit-docs-generator/src/cli/index.ts @@ -885,7 +885,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", @@ -1953,7 +1956,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/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/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"); From 321c2fab6b5d715224098aebd7edfe013b9bd624 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Thu, 6 Aug 2026 11:34:50 -0400 Subject: [PATCH 02/12] fix: satisfy the strict generator typecheck in the curation extractor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/ is not type-checked today; #1106 widens the generator project to cover it, and under exactOptionalPropertyTypes plus noUncheckedIndexedAccess this PR's files do not compile. Fixing it here keeps the PR green whichever order the two land in. - isNonEmptyArray returned boolean, so it narrowed nothing and the caller compensated with an 'as DocumentationChunk[]' cast. Making it a type predicate narrows properly and lets the cast go. - previousToolkit is declared optional but --force-regenerate and --overwrite-output pass it explicitly as undefined, which exactOptionalPropertyTypes rejects. The type now says '| undefined', matching what callers actually do — that explicit undefined is the exact scenario the prose regression test covers. Co-Authored-By: Claude Opus 4.8 --- toolkit-docs-generator/scripts/extract-curation.ts | 4 ++-- toolkit-docs-generator/src/merger/data-merger.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/toolkit-docs-generator/scripts/extract-curation.ts b/toolkit-docs-generator/scripts/extract-curation.ts index 5a0b703d4..6c0faf3f9 100644 --- a/toolkit-docs-generator/scripts/extract-curation.ts +++ b/toolkit-docs-generator/scripts/extract-curation.ts @@ -42,7 +42,7 @@ type CurationFile = { toolChunks?: Record; }; -const isNonEmptyArray = (value: unknown[] | undefined): boolean => +const isNonEmptyArray = (value: T[] | undefined): value is T[] => Array.isArray(value) && value.length > 0; /** @@ -57,7 +57,7 @@ const extractToolChunks = ( const toolChunks: Record = {}; for (const tool of toolkit.tools ?? []) { if (isNonEmptyArray(tool.documentationChunks)) { - toolChunks[tool.name] = tool.documentationChunks as DocumentationChunk[]; + toolChunks[tool.name] = tool.documentationChunks; } } return toolChunks; 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) */ From f17c6d76273d95b5a0be7d479f05652dd0a4a71f Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Thu, 6 Aug 2026 14:50:28 -0400 Subject: [PATCH 03/12] fix: let the curation extractor run from any working directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sibling generator scripts (validate-merge, sync-toolkit-sidebar, check-stale-summaries, report-tool-metadata) resolve data/toolkits through the shared resolver, which anchors on the repo root. This script used bare relative paths, so it only worked when invoked from toolkit-docs-generator/ and failed from the root — two conventions for the same directory. Anchor on the script's own location instead, matching how verify-toolkit-join, sync-toolkit-sidebar, and validate-merge already use import.meta.url. It now works from either directory. Verified from both: identical output, 75 files. Co-Authored-By: Claude Opus 4.8 --- toolkit-docs-generator/scripts/extract-curation.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/toolkit-docs-generator/scripts/extract-curation.ts b/toolkit-docs-generator/scripts/extract-curation.ts index 6c0faf3f9..bacf3125c 100644 --- a/toolkit-docs-generator/scripts/extract-curation.ts +++ b/toolkit-docs-generator/scripts/extract-curation.ts @@ -18,10 +18,17 @@ * so diffs stay small; the schema fills them back in with defaults on read. */ import { mkdir, readdir, readFile, writeFile } from "fs/promises"; -import { join } from "path"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; -const TOOLKITS_DIR = "data/toolkits"; -const CURATION_DIR = "curation"; +/** + * 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; From 23fc7fd485bb8681156509876a7ad7d0573e2b7a Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Fri, 7 Aug 2026 12:54:38 -0400 Subject: [PATCH 04/12] fix: regenerate toolkits when curation changes --- toolkit-docs-generator/src/cli/index.ts | 21 +++++- .../src/diff/custom-sections-diff.ts | 51 ++++++++++++++ toolkit-docs-generator/src/diff/index.ts | 1 + .../scenarios/custom-sections-diff.test.ts | 66 +++++++++++++++++++ 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 toolkit-docs-generator/src/diff/custom-sections-diff.ts create mode 100644 toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts diff --git a/toolkit-docs-generator/src/cli/index.ts b/toolkit-docs-generator/src/cli/index.ts index b3c639364..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"; @@ -1339,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( @@ -1365,7 +1374,10 @@ program } } - if (!hasChanges(detectedChanges)) { + if ( + !hasChanges(detectedChanges) && + changedCustomSectionIds.size === 0 + ) { spinner.succeed( "No changes detected. All toolkits are up to date." ); @@ -1405,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 = 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/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([]); + }); +}); From 82c289e3147af99b7453d1e48b8fbcf5dfc86e72 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Mon, 10 Aug 2026 17:18:36 -0400 Subject: [PATCH 05/12] fix: let curation clear prose and show up in check-changes When a curation file exists, treat its contents as authoritative so an empty {} file deletes hand-authored prose instead of carrying it forward. check-changes now diffs curation against committed artifacts too. Co-authored-by: Cursor --- toolkit-docs-generator/src/cli/index.ts | 109 ++++++++++++++++-- .../src/merger/data-merger.ts | 44 +++++-- .../src/sources/custom-sections-file.ts | 3 +- .../tests/merger/data-merger.test.ts | 38 +++++- .../scenarios/custom-sections-diff.test.ts | 16 +++ .../prose-survives-force-regenerate.test.ts | 42 +++++++ 6 files changed, 231 insertions(+), 21 deletions(-) diff --git a/toolkit-docs-generator/src/cli/index.ts b/toolkit-docs-generator/src/cli/index.ts index 148ae2e75..061a5590d 100644 --- a/toolkit-docs-generator/src/cli/index.ts +++ b/toolkit-docs-generator/src/cli/index.ts @@ -13,7 +13,7 @@ import chalk from "chalk"; import { Command } from "commander"; -import { readdir, readFile } from "fs/promises"; +import { access, readdir, readFile } from "fs/promises"; import ora from "ora"; import { join, resolve } from "path"; import { @@ -220,7 +220,8 @@ const filterProvidersByMetadataPresence = async ( }; const buildChangeLogDetails = ( - result: ReturnType + result: ReturnType, + curationChangedToolkitIds: readonly string[] = [] ): string[] => { const changed = getChangedToolkitIds(result); const removed = result.toolkitChanges @@ -246,9 +247,39 @@ const buildChangeLogDetails = ( details.push(`versionOnly=${versionOnly.join(", ")}`); } + if (curationChangedToolkitIds.length > 0) { + details.push(`curationChanged=${curationChangedToolkitIds.join(", ")}`); + } + return details; }; +const resolveCustomSectionsPath = async ( + explicitPath: string | undefined +): Promise => { + if (explicitPath) { + return explicitPath; + } + + const defaultPath = join(process.cwd(), "curation"); + try { + await access(defaultPath); + return defaultPath; + } catch { + return; + } +}; + +const getCombinedChangedToolkitIds = ( + changeResult: ReturnType, + curationChangedToolkitIds: readonly string[] +): string[] => { + const apiChangedIds = getChangedToolkitIds(changeResult).map((id) => + id.toLowerCase() + ); + return [...new Set([...apiChangedIds, ...curationChangedToolkitIds])].sort(); +}; + const clearOutputDir = async ( outputDir: string, verbose: boolean @@ -2696,6 +2727,10 @@ program "--tool-metadata-key ", "Tool metadata API key (or ENGINE_API_KEY env)" ) + .option( + "--custom-sections ", + "Path to custom sections: a directory of per-toolkit files (curation/) or a single JSON file (defaults to ./curation when present)" + ) .option("--verbose", "Show detailed tool-level changes", false) .option("--json", "Output as JSON", false) .action( @@ -2710,6 +2745,7 @@ program listToolsPageSize?: number; toolMetadataUrl?: string; toolMetadataKey?: string; + customSections?: string; verbose: boolean; json: boolean; // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: legacy CLI flow @@ -2775,7 +2811,25 @@ program currentToolkitDataForDiff, previousToolkits ); + const customSectionsPath = await resolveCustomSectionsPath( + options.customSections + ); + const customSectionsSource = customSectionsPath + ? createCustomSectionsFileSource(customSectionsPath) + : createEmptyCustomSectionsSource(); + const curationChangedToolkitIds = customSectionsPath + ? getChangedToolkitIdsFromCustomSections( + await customSectionsSource.getAllCustomSections(), + previousToolkits + ) + : []; const compareDurationMs = Date.now() - compareStartedAt; + const allChangedToolkitIds = getCombinedChangedToolkitIds( + changeResult, + curationChangedToolkitIds + ); + const hasAnyChanges = + hasChanges(changeResult) || curationChangedToolkitIds.length > 0; spinner.stop(); @@ -2794,6 +2848,13 @@ program console.log( chalk.dim(` Compared signatures in ${compareDurationMs}ms`) ); + if (customSectionsPath) { + console.log( + chalk.dim( + ` Compared curation from ${resolve(customSectionsPath)} (${curationChangedToolkitIds.length} prose change(s))` + ) + ); + } if (previousToolkitLoad.stats.failedFiles.length > 0) { console.log( chalk.yellow( @@ -2822,12 +2883,18 @@ program `loadPreviousDurationMs=${loadPreviousDurationMs}`, `compareDurationMs=${compareDurationMs}`, `previousLoadStats=${formatPreviousToolkitLoadStats(previousToolkitLoad.stats)}`, - ...buildChangeLogDetails(changeResult), + ...(customSectionsPath + ? [`customSections=${resolve(customSectionsPath)}`] + : []), + ...buildChangeLogDetails(changeResult, curationChangedToolkitIds), ], }); await appendLogEntry(logPaths.changeLogPath, { title: "changes", - details: buildChangeLogDetails(changeResult), + details: buildChangeLogDetails( + changeResult, + curationChangedToolkitIds + ), }); // Output results @@ -2836,10 +2903,15 @@ program JSON.stringify( { ...changeResult, + curationChangedToolkitIds, + changedToolkitIds: allChangedToolkitIds, diagnostics: { currentToolkitCount: currentToolkitDataForDiff.size, previousToolkitCount: previousToolkits.size, previousLoad: previousToolkitLoad.stats, + customSectionsPath: customSectionsPath + ? resolve(customSectionsPath) + : null, timingMs: { fetch: fetchDurationMs, loadPrevious: loadPreviousDurationMs, @@ -2857,15 +2929,26 @@ program // Summary console.log(chalk.cyan("Summary:")); console.log(` ${formatChangeSummary(changeResult)}`); + if (curationChangedToolkitIds.length > 0) { + console.log( + chalk.cyan("Curation:"), + `${curationChangedToolkitIds.length} toolkit(s) with prose changes` + ); + } console.log(); // Check if there are any changes - if (hasChanges(changeResult)) { - // Show changed toolkits - const changedIds = getChangedToolkitIds(changeResult); + if (hasAnyChanges) { + const apiChangedIds = new Set( + getChangedToolkitIds(changeResult).map((id) => id.toLowerCase()) + ); + const curationOnlyIds = curationChangedToolkitIds.filter( + (id) => !apiChangedIds.has(id) + ); + console.log( chalk.yellow( - `⚠ ${changedIds.length} toolkit(s) need regeneration:\n` + `⚠ ${allChangedToolkitIds.length} toolkit(s) need regeneration:\n` ) ); @@ -2889,6 +2972,10 @@ program console.log(line); } } + + for (const toolkitId of curationOnlyIds) { + console.log(chalk.yellow(`[CURATION] ${toolkitId}`)); + } } else { // Compact view for (const change of changeResult.toolkitChanges) { @@ -2910,6 +2997,12 @@ program ` ${icon}${change.toolkitId} [${change.currentToolCount} tools]${toolChangeSummary}` ); } + + for (const toolkitId of curationOnlyIds) { + console.log( + ` ${chalk.yellow("~ ")}${toolkitId} [curation prose]` + ); + } } console.log(); diff --git a/toolkit-docs-generator/src/merger/data-merger.ts b/toolkit-docs-generator/src/merger/data-merger.ts index 7bbf9d790..7442aa232 100644 --- a/toolkit-docs-generator/src/merger/data-merger.ts +++ b/toolkit-docs-generator/src/merger/data-merger.ts @@ -473,15 +473,22 @@ const transformMetadata = ( const getToolDocumentationChunks = ( toolName: string, toolChunks: { [key: string]: DocumentationChunk[] }, - previousTool?: MergedTool + previousTool: MergedTool | undefined, + customSectionsAuthoritative: boolean ): DocumentationChunk[] => { - const fromSource = toolChunks[toolName] ?? []; + const fromSource = toolChunks[toolName]; + + if (customSectionsAuthoritative) { + return fromSource ?? []; + } + const fromPrevious = previousTool?.documentationChunks ?? []; + const sourceItems = fromSource ?? []; // If source has chunks, use source (it's authoritative) // If source is empty but previous has chunks, preserve previous - if (fromSource.length > 0) { - return fromSource; + if (sourceItems.length > 0) { + return sourceItems; } return fromPrevious; }; @@ -582,8 +589,13 @@ const hasToolkitOverviewChunk = (toolkit: MergedToolkit): boolean => const mergeCustomSectionsArrays = ( fromSource: readonly T[] | undefined, - fromPrevious: readonly T[] | undefined + fromPrevious: readonly T[] | undefined, + authoritative: boolean ): T[] => { + if (authoritative) { + return [...(fromSource ?? [])]; + } + const sourceItems = fromSource ?? []; const previousItems = fromPrevious ?? []; @@ -661,6 +673,7 @@ const buildMergedTools = async (options: { failedTools: FailedTool[]; previousToolByQualifiedName: ReadonlyMap; llmConcurrency: number; + customSectionsAuthoritative: boolean; }): Promise => mapWithConcurrency( options.tools, @@ -671,7 +684,8 @@ const buildMergedTools = async (options: { options.toolExampleGenerator, options.warnings, options.failedTools, - options.previousToolByQualifiedName.get(tool.qualifiedName) + options.previousToolByQualifiedName.get(tool.qualifiedName), + options.customSectionsAuthoritative ), options.llmConcurrency ); @@ -686,6 +700,7 @@ const buildMergedToolkit = (options: { customSections: CustomSections | null; previousToolkit: MergedToolkit | undefined; }): MergedToolkit => { + const customSectionsAuthoritative = options.customSections !== null; const mergedMetadata = applyToolkitTypeOverrides( options.toolkitId, options.metadata @@ -707,15 +722,18 @@ const buildMergedToolkit = (options: { tools: options.tools, documentationChunks: mergeCustomSectionsArrays( options.customSections?.documentationChunks, - options.previousToolkit?.documentationChunks + options.previousToolkit?.documentationChunks, + customSectionsAuthoritative ), customImports: mergeCustomSectionsArrays( options.customSections?.customImports, - options.previousToolkit?.customImports + options.previousToolkit?.customImports, + customSectionsAuthoritative ), subPages: mergeCustomSectionsArrays( options.customSections?.subPages, - options.previousToolkit?.subPages + options.previousToolkit?.subPages, + customSectionsAuthoritative ), generatedAt: new Date().toISOString(), }; @@ -730,12 +748,14 @@ const transformTool = async ( toolExampleGenerator: ToolExampleGenerator | undefined, warnings: string[], failedTools: FailedTool[], - previousTool?: MergedTool + previousTool: MergedTool | undefined, + customSectionsAuthoritative: boolean ): Promise => { const documentationChunks = getToolDocumentationChunks( tool.name, toolChunks, - previousTool + previousTool, + customSectionsAuthoritative ); if (previousTool && shouldReuseExample(tool, previousTool)) { @@ -875,6 +895,7 @@ export const mergeToolkit = async ( auth = { ...auth, providerId: resolvedProviderId }; } + const customSectionsAuthoritative = customSections !== null; const toolChunks = (customSections?.toolChunks ?? {}) as { [key: string]: DocumentationChunk[]; }; @@ -890,6 +911,7 @@ export const mergeToolkit = async ( failedTools, previousToolByQualifiedName, llmConcurrency, + customSectionsAuthoritative, }); const toolkit = buildMergedToolkit({ diff --git a/toolkit-docs-generator/src/sources/custom-sections-file.ts b/toolkit-docs-generator/src/sources/custom-sections-file.ts index 8f1c98387..ccd7f0100 100644 --- a/toolkit-docs-generator/src/sources/custom-sections-file.ts +++ b/toolkit-docs-generator/src/sources/custom-sections-file.ts @@ -9,7 +9,8 @@ * 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. + * diffs. An empty file (`{}`) means the toolkit has no hand-authored + * prose — the merger clears any carry-forward from a previous artifact. * - A single JSON file that maps toolkit id to `CustomSections`. This is the * legacy shape produced by the one-time MDX extraction. */ diff --git a/toolkit-docs-generator/tests/merger/data-merger.test.ts b/toolkit-docs-generator/tests/merger/data-merger.test.ts index e2277f399..1435545b9 100644 --- a/toolkit-docs-generator/tests/merger/data-merger.test.ts +++ b/toolkit-docs-generator/tests/merger/data-merger.test.ts @@ -668,7 +668,7 @@ describe("mergeToolkit", () => { createStubGenerator() ); - // Run again with empty custom sections - should preserve previous + // No curation file for this toolkit (null source) — carry forward. const result = await mergeToolkit("TestKit", tools, null, null, undefined, { previousToolkit: previousResult.toolkit, }); @@ -681,6 +681,42 @@ describe("mergeToolkit", () => { expect(result.toolkit.subPages).toHaveLength(1); }); + it("should clear toolkit-level custom sections when curation is authoritative but empty", async () => { + const tools = [createTool({ qualifiedName: "TestKit.Tool1" })]; + + const previousResult = await mergeToolkit( + "TestKit", + tools, + null, + createCustomSections({ + documentationChunks: [ + { + type: "warning", + location: "header", + position: "after", + content: "Important warning!", + }, + ], + customImports: ['import CustomComponent from "@/components/custom";'], + subPages: ["environment-variables"], + }), + createStubGenerator() + ); + + const result = await mergeToolkit( + "TestKit", + tools, + null, + createCustomSections(), + undefined, + { previousToolkit: previousResult.toolkit } + ); + + expect(result.toolkit.documentationChunks).toHaveLength(0); + expect(result.toolkit.customImports).toHaveLength(0); + expect(result.toolkit.subPages).toHaveLength(0); + }); + it("should use source custom sections over previous when source has content", async () => { const tools = [createTool({ qualifiedName: "TestKit.Tool1" })]; diff --git a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts index 01da56eee..eb83fdeb3 100644 --- a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts +++ b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts @@ -63,4 +63,20 @@ describe("getChangedToolkitIdsFromCustomSections", () => { ) ).toEqual([]); }); + + it("treats cleared curation as a prose change", () => { + expect( + getChangedToolkitIdsFromCustomSections( + { + github: { + documentationChunks: [], + customImports: [], + subPages: [], + toolChunks: {}, + }, + }, + new Map([["Github", previousToolkit()]]) + ) + ).toEqual(["github"]); + }); }); 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 index 896780aa1..fe3e57de9 100644 --- a/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts +++ b/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts @@ -118,4 +118,46 @@ describe("prose survives --force-regenerate", () => { expect(result.toolkit.customImports).toHaveLength(0); expect(result.toolkit.subPages).toHaveLength(0); }); + + it("clears prose when curation exists but is explicitly empty", async () => { + tempDir = await mkdtemp(join(tmpdir(), "curation-")); + await writeFile( + join(tempDir, "testkit.json"), + JSON.stringify(curationEntry, null, 2) + ); + + const withProse = + await createCustomSectionsFileSource(tempDir).getCustomSections( + "TestKit" + ); + + const previousResult = await mergeToolkit( + "TestKit", + [createTool()], + null, + withProse, + undefined, + { previousToolkit: undefined } + ); + expect(previousResult.toolkit.documentationChunks).toHaveLength(1); + + await writeFile(join(tempDir, "testkit.json"), "{}"); + const clearedCuration = + await createCustomSectionsFileSource(tempDir).getCustomSections( + "TestKit" + ); + + const result = await mergeToolkit( + "TestKit", + [createTool()], + null, + clearedCuration, + undefined, + { previousToolkit: previousResult.toolkit } + ); + + expect(result.toolkit.documentationChunks).toHaveLength(0); + expect(result.toolkit.customImports).toHaveLength(0); + expect(result.toolkit.subPages).toHaveLength(0); + }); }); From b80751a2d0abf1caa02e0866d4ef7a1936a4e5cf Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Tue, 11 Aug 2026 10:16:45 -0400 Subject: [PATCH 06/12] fix: satisfy strict types in custom-sections-diff test Use valid MergedToolkit metadata and documentation chunk types so the generator typecheck passes under the widened tsconfig from main. Co-authored-by: Cursor --- .../tests/scenarios/custom-sections-diff.test.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts index 0ed145e7d..f2d8b6aab 100644 --- a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts +++ b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts @@ -7,12 +7,21 @@ const previousToolkit = (documentation = "old"): MergedToolkit => ({ label: "Github", version: "1.0.0", description: "GitHub", - metadata: null, + metadata: { + category: "development", + iconUrl: "https://example.com/icon.svg", + isBYOC: false, + isPro: false, + type: "arcade", + docsLink: "https://docs.example.com", + isComingSoon: false, + isHidden: false, + }, auth: null, tools: [], documentationChunks: [ { - type: "text", + type: "warning", location: "description", position: "after", content: documentation, @@ -31,7 +40,7 @@ describe("getChangedToolkitIdsFromCustomSections", () => { github: { documentationChunks: [ { - type: "text", + type: "warning", location: "description", position: "after", content: "new", From bfe0de5920e82d5d0ca999a03cbc9d42fcb358df Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Tue, 11 Aug 2026 10:34:42 -0400 Subject: [PATCH 07/12] fix: preserve curation provenance --- toolkit-docs-generator/src/cli/index.ts | 22 +++++++++------- .../src/diff/custom-sections-diff.ts | 14 +++++++--- .../src/merger/data-merger.ts | 13 ++++++++++ .../src/shared/toolkit-schemas.ts | 5 ++++ .../tests/merger/data-merger.test.ts | 4 +++ .../scenarios/custom-sections-diff.test.ts | 26 +++++++++++++++++++ .../workflows/generate-toolkit-docs.test.ts | 1 + 7 files changed, 71 insertions(+), 14 deletions(-) diff --git a/toolkit-docs-generator/src/cli/index.ts b/toolkit-docs-generator/src/cli/index.ts index 1ac9b25a9..c17aa6f21 100644 --- a/toolkit-docs-generator/src/cli/index.ts +++ b/toolkit-docs-generator/src/cli/index.ts @@ -1278,9 +1278,14 @@ program } } - // Custom sections source - const customSectionsSource = options.customSections - ? createCustomSectionsFileSource(options.customSections) + // Custom sections source. When curation/ is present, use it by default + // so manual generation follows the same merge and diff behavior as + // check-changes and the nightly workflow. + const customSectionsPath = await resolveCustomSectionsPath( + options.customSections + ); + const customSectionsSource = customSectionsPath + ? createCustomSectionsFileSource(customSectionsPath) : createEmptyCustomSectionsSource(); // Build provider ID resolver from design system OAuth catalogue @@ -1363,7 +1368,7 @@ program previousToolkits ?? new Map() ); const changedCustomSectionIds = new Set( - options.customSections + customSectionsPath ? getChangedToolkitIdsFromCustomSections( await customSectionsSource.getAllCustomSections(), previousToolkits ?? new Map() @@ -1439,12 +1444,9 @@ program } // Get IDs of changed toolkits - const changedIds = [ - ...new Set([ - ...getChangedToolkitIds(detectedChanges), - ...changedCustomSectionIds, - ]), - ]; + const changedIds = getCombinedChangedToolkitIds(detectedChanges, [ + ...changedCustomSectionIds, + ]); changedToolkitIds = new Set(changedIds.map((id) => id.toLowerCase())); changeResult = detectedChanges; const changedPreview = diff --git a/toolkit-docs-generator/src/diff/custom-sections-diff.ts b/toolkit-docs-generator/src/diff/custom-sections-diff.ts index c3dae1e51..990f117c4 100644 --- a/toolkit-docs-generator/src/diff/custom-sections-diff.ts +++ b/toolkit-docs-generator/src/diff/custom-sections-diff.ts @@ -1,4 +1,7 @@ -import { stableStringify } from "../merger/data-merger"; +import { + getCustomSectionsSourceHash, + stableStringify, +} from "../merger/data-merger"; import type { CustomSections, MergedToolkit } from "../types/index"; const customSectionsFromToolkit = (toolkit: MergedToolkit): CustomSections => ({ @@ -47,9 +50,12 @@ export const getChangedToolkitIdsFromCustomSections = ( ? customSectionsFromToolkit(previousToolkit) : emptyCustomSections(); - if ( - stableStringify(currentSections) !== stableStringify(previousSections) - ) { + const currentHash = getCustomSectionsSourceHash(currentSections); + const hasChanged = previousToolkit?.curationSourceHash + ? currentHash !== previousToolkit.curationSourceHash + : stableStringify(currentSections) !== stableStringify(previousSections); + + if (hasChanged) { changed.push(toolkitId.toLowerCase()); } } diff --git a/toolkit-docs-generator/src/merger/data-merger.ts b/toolkit-docs-generator/src/merger/data-merger.ts index 93ecf71b2..00dd90085 100644 --- a/toolkit-docs-generator/src/merger/data-merger.ts +++ b/toolkit-docs-generator/src/merger/data-merger.ts @@ -5,6 +5,7 @@ * into the final MergedToolkit format. */ +import { createHash } from "node:crypto"; import type { ISecretEditGenerator } from "../llm/secret-edit-generator"; import { isApiSuffixedToolkitId, @@ -217,6 +218,11 @@ export const stableStringify = (value: unknown): string => { return JSON.stringify(value); }; +export const getCustomSectionsSourceHash = ( + customSections: CustomSections +): string => + createHash("sha256").update(stableStringify(customSections)).digest("hex"); + export type ToolSignatureInput = { name: string; qualifiedName: string; @@ -801,6 +807,13 @@ const buildMergedToolkit = (options: { options.previousToolkit?.subPages, customSectionsAuthoritative ), + ...(options.customSections + ? { + curationSourceHash: getCustomSectionsSourceHash( + options.customSections + ), + } + : {}), generatedAt: new Date().toISOString(), }; }; diff --git a/toolkit-docs-generator/src/shared/toolkit-schemas.ts b/toolkit-docs-generator/src/shared/toolkit-schemas.ts index eb56aab02..42f5a5f66 100644 --- a/toolkit-docs-generator/src/shared/toolkit-schemas.ts +++ b/toolkit-docs-generator/src/shared/toolkit-schemas.ts @@ -389,6 +389,11 @@ export const MergedToolkitSchema = z.object({ * accepted an explicit override here, so it stays part of the contract. */ pipPackageName: z.string().optional(), + /** + * SHA-256 fingerprint of hand-authored curation before generated + * post-processing edits. Used only for incremental generation. + */ + curationSourceHash: z.string().optional(), /** Generation metadata */ generatedAt: z.string().optional(), }); diff --git a/toolkit-docs-generator/tests/merger/data-merger.test.ts b/toolkit-docs-generator/tests/merger/data-merger.test.ts index 7b2d82ec1..16a5f2fd0 100644 --- a/toolkit-docs-generator/tests/merger/data-merger.test.ts +++ b/toolkit-docs-generator/tests/merger/data-merger.test.ts @@ -10,6 +10,7 @@ import { computeAllScopes, DataMerger, determineAuthType, + getCustomSectionsSourceHash, getProviderId, groupToolsByToolkit, mergeToolkit, @@ -425,6 +426,9 @@ describe("mergeToolkit", () => { expect(result.toolkit.auth?.allScopes).toContain("scope1"); expect(result.toolkit.auth?.allScopes).toContain("scope2"); expect(result.toolkit.documentationChunks).toHaveLength(1); + expect(result.toolkit.curationSourceHash).toBe( + getCustomSectionsSourceHash(customSections) + ); expect(result.warnings).toHaveLength(0); }); diff --git a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts index f2d8b6aab..0d3c0f859 100644 --- a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts +++ b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { getChangedToolkitIdsFromCustomSections } from "../../src/diff/index"; +import { getCustomSectionsSourceHash } from "../../src/merger/data-merger"; import type { MergedToolkit } from "../../src/types/index"; const previousToolkit = (documentation = "old"): MergedToolkit => ({ @@ -73,6 +74,31 @@ describe("getChangedToolkitIdsFromCustomSections", () => { ).toEqual([]); }); + it("uses the curation fingerprint after generated prose is edited", () => { + const curation = { + documentationChunks: [ + { + type: "warning" as const, + location: "description" as const, + position: "after" as const, + content: "hand-authored source", + }, + ], + customImports: [], + subPages: [], + toolChunks: {}, + }; + const toolkit = previousToolkit("secret-coherence edited output"); + toolkit.curationSourceHash = getCustomSectionsSourceHash(curation); + + expect( + getChangedToolkitIdsFromCustomSections( + { github: curation }, + new Map([["Github", toolkit]]) + ) + ).toEqual([]); + }); + it("treats cleared curation as a prose change", () => { expect( getChangedToolkitIdsFromCustomSections( diff --git a/toolkit-docs-generator/tests/workflows/generate-toolkit-docs.test.ts b/toolkit-docs-generator/tests/workflows/generate-toolkit-docs.test.ts index 94990321d..2e739f501 100644 --- a/toolkit-docs-generator/tests/workflows/generate-toolkit-docs.test.ts +++ b/toolkit-docs-generator/tests/workflows/generate-toolkit-docs.test.ts @@ -38,6 +38,7 @@ test("porter workflow generates docs and opens a PR", () => { expect(workflowContents).toContain("--llm-max-tokens 8192"); expect(workflowContents).toContain("--exclude-file ./remove-toolkits.txt"); expect(workflowContents).toContain("--ignore-file ./skip-toolkits.txt"); + expect(workflowContents).toContain("--custom-sections ./curation"); expect(workflowContents).toContain("--remove-empty-sections=false"); expect(workflowContents).toContain("peter-evans/create-pull-request"); expect(workflowContents).toContain("HUSKY: 0"); From b68fafedb2f135c81fe45fa834810996f3c2f564 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Tue, 11 Aug 2026 12:55:51 -0400 Subject: [PATCH 08/12] refactor toolkit curation to MDX --- app/_lib/toolkit-markdown.ts | 148 +++++++- package.json | 1 + pnpm-lock.yaml | 3 + specs/mdx-curation/README.md | 52 +++ specs/mdx-curation/slices/01-compile-mdx.md | 22 ++ .../slices/02-authority-and-migration.md | 23 ++ .../slices/03-markdown-projection.md | 19 + tests/toolkit-markdown.test.ts | 60 +++ toolkit-docs-generator/ARCHITECTURE.md | 10 +- toolkit-docs-generator/README.md | 15 +- .../curation/airtableapi.json | 5 - .../curation/arcadeengineapi.json | 14 - .../arcadeengineapi/chunks/001-secrets.mdx | 15 + toolkit-docs-generator/curation/asana.json | 10 - .../asana/chunks/001-auth-after-markdown.mdx | 6 + toolkit-docs-generator/curation/asanaapi.json | 14 - .../curation/asanaapi/chunks/001-auth.mdx | 8 + toolkit-docs-generator/curation/ashbyapi.json | 5 - toolkit-docs-generator/curation/boxapi.json | 5 - .../curation/brightdata.json | 11 - .../brightdata/chunks/001-secrets.mdx | 29 ++ .../curation/calendlyapi.json | 5 - toolkit-docs-generator/curation/clickup.json | 25 -- .../clickup/chunks/001-taskpriority.mdx | 12 + .../clickup/chunks/002-taskorderby.mdx | 11 + .../clickup/chunks/003-commentresolution.mdx | 10 + .../curation/clickupapi.json | 14 - .../curation/clickupapi/chunks/001-auth.mdx | 8 + .../curation/confluence.json | 10 - .../chunks/001-auth-after-markdown.mdx | 7 + .../curation/cursoragentsapi.json | 5 - .../curation/customerioapi.json | 5 - .../curation/customeriopipelinesapi.json | 5 - .../curation/customeriotrackapi.json | 5 - .../curation/datadogapi.json | 14 - .../datadogapi/chunks/001-authentication.mdx | 29 ++ toolkit-docs-generator/curation/dropbox.json | 10 - .../chunks/001-auth-after-markdown.mdx | 6 + toolkit-docs-generator/curation/e2b.json | 10 - .../e2b/chunks/001-auth-after-markdown.mdx | 8 + toolkit-docs-generator/curation/exaapi.json | 5 - toolkit-docs-generator/curation/figma.json | 10 - .../figma/chunks/001-auth-after-markdown.mdx | 9 + toolkit-docs-generator/curation/figmaapi.json | 5 - .../curation/firecrawl.json | 10 - .../chunks/001-auth-after-markdown.mdx | 8 + .../curation/freshserviceapi.json | 5 - toolkit-docs-generator/curation/github.json | 40 -- .../chunks/001-description-after-warning.mdx | 12 + .../chunks/002-description-after-info.mdx | 8 + .../chunks/003-github-enterprise-support.mdx | 32 ++ .../004-github-app-permissions-summary.mdx | 129 +++++++ .../github/chunks/005-configuration-setup.mdx | 55 +++ .../curation/githubapi.json | 21 -- .../curation/githubapi/chunks/001-secrets.mdx | 11 + .../curation/githubapi/chunks/002-auth.mdx | 8 + toolkit-docs-generator/curation/gmail.json | 21 -- .../gmail/chunks/001-auth-after-markdown.mdx | 7 + .../002-adding-attachments-to-emails.mdx | 25 ++ .../curation/googlecalendar.json | 21 -- .../googlecalendar/chunks/001-auth.mdx | 8 + .../chunks/002-updategooglemeetoptions.mdx | 13 + .../curation/googlecontacts.json | 13 - .../chunks/001-auth-after-markdown.mdx | 6 + .../curation/googledocs.json | 27 -- .../chunks/001-description-after-warning.mdx | 9 + .../googledocs/chunks/002-tab-support.mdx | 20 + .../curation/googledocs/chunks/003-auth.mdx | 8 + .../curation/googledrive.json | 5 - .../curation/googlefinance.json | 18 - .../googlefinance/chunks/001-auth.mdx | 18 + .../chunks/002-googlefinancewindow.mdx | 20 + .../curation/googleflights.json | 32 -- .../googleflights/chunks/001-auth.mdx | 17 + .../chunks/002-googleflightsmaxstops.mdx | 14 + .../chunks/003-googleflightssortby.mdx | 16 + .../chunks/004-googleflightstravelclass.mdx | 16 + .../curation/googlehotels.json | 18 - .../curation/googlehotels/chunks/001-auth.mdx | 16 + .../chunks/002-googlehotelssortby.mdx | 16 + .../curation/googlejobs.json | 25 -- .../curation/googlejobs/chunks/001-auth.mdx | 17 + .../chunks/002-default-parameters.mdx | 20 + .../googlejobs/chunks/003-languagecodes.mdx | 41 ++ .../curation/googlemaps.json | 18 - .../curation/googlemaps/chunks/001-auth.mdx | 17 + .../chunks/002-default-parameters.mdx | 57 +++ .../curation/googlenews.json | 32 -- .../curation/googlenews/chunks/001-auth.mdx | 17 + .../chunks/002-default-parameters.mdx | 30 ++ .../googlenews/chunks/003-languagecodes.mdx | 39 ++ .../googlenews/chunks/004-countrycodes.mdx | 254 +++++++++++++ .../curation/googlesearch.json | 10 - .../chunks/001-auth-after-markdown.mdx | 16 + .../curation/googlesheets.json | 5 - .../curation/googleslides.json | 14 - .../chunks/001-googleslides-reference.mdx | 36 ++ toolkit-docs-generator/curation/hubspot.json | 10 - .../chunks/001-auth-after-markdown.mdx | 6 + .../curation/hubspotautomationapi.json | 14 - .../hubspotautomationapi/chunks/001-auth.mdx | 7 + .../curation/hubspotcmsapi.json | 14 - .../hubspotcmsapi/chunks/001-auth.mdx | 7 + .../curation/hubspotconversationsapi.json | 14 - .../chunks/001-auth.mdx | 7 + .../curation/hubspotcrmapi.json | 14 - .../hubspotcrmapi/chunks/001-auth.mdx | 7 + .../curation/hubspoteventsapi.json | 14 - .../hubspoteventsapi/chunks/001-auth.mdx | 7 + .../curation/hubspotmarketingapi.json | 14 - .../hubspotmarketingapi/chunks/001-auth.mdx | 7 + .../curation/hubspotmeetingsapi.json | 14 - .../hubspotmeetingsapi/chunks/001-auth.mdx | 7 + .../curation/hubspotusersapi.json | 14 - .../hubspotusersapi/chunks/001-auth.mdx | 7 + .../curation/intercomapi.json | 21 -- .../chunks/001-intercom-api-subdomain.mdx | 13 + .../curation/intercomapi/chunks/002-auth.mdx | 7 + toolkit-docs-generator/curation/jira.json | 17 - .../chunks/001-description-after-warning.mdx | 25 ++ .../jira/pages/environment-variables/page.mdx | 36 ++ toolkit-docs-generator/curation/linear.json | 11 - .../curation/linear/chunks/001-auth.mdx | 9 + toolkit-docs-generator/curation/linkedin.json | 10 - .../chunks/001-auth-after-markdown.mdx | 6 + toolkit-docs-generator/curation/lumaapi.json | 14 - .../lumaapi/chunks/001-authentication.mdx | 26 ++ .../curation/mailchimpmarketingapi.json | 13 - .../chunks/001-auth-after-markdown.mdx | 7 + .../curation/microsoftteams.json | 10 - .../chunks/001-description-after-warning.mdx | 8 + toolkit-docs-generator/curation/miroapi.json | 5 - .../curation/notiontoolkit.json | 10 - .../chunks/001-auth-after-markdown.mdx | 6 + .../curation/pagerduty.json | 22 -- .../chunks/001-description-after-warning.mdx | 11 + .../chunks/002-description-after-info.mdx | 9 + .../chunks/003-auth-after-markdown.mdx | 10 + .../curation/pagerdutyapi.json | 5 - .../curation/posthogapi.json | 14 - .../posthogapi/chunks/001-configuration.mdx | 40 ++ toolkit-docs-generator/curation/pylon.json | 16 - .../chunks/001-description-after-warning.mdx | 9 + .../pylon/chunks/002-auth-after-markdown.mdx | 16 + toolkit-docs-generator/curation/reddit.json | 10 - .../reddit/chunks/001-auth-after-markdown.mdx | 6 + toolkit-docs-generator/curation/slack.json | 10 - .../chunks/001-header-after-markdown.mdx | 8 + toolkit-docs-generator/curation/slackapi.json | 5 - toolkit-docs-generator/curation/spotify.json | 10 - .../chunks/001-description-after-warning.mdx | 9 + .../curation/squareupapi.json | 5 - toolkit-docs-generator/curation/stripe.json | 10 - .../stripe/chunks/001-auth-after-markdown.mdx | 8 + .../curation/stripeapi.json | 5 - .../curation/ticktickapi.json | 13 - .../chunks/001-auth-after-markdown.mdx | 7 + .../curation/trelloapi.json | 14 - .../trelloapi/chunks/001-authentication.mdx | 27 ++ .../curation/vercelapi.json | 5 - .../curation/weaviateapi.json | 14 - .../weaviateapi/chunks/001-authentication.mdx | 25 ++ toolkit-docs-generator/curation/xeroapi.json | 5 - .../curation/zohobooksapi.json | 21 -- .../zohobooksapi/chunks/001-secrets.mdx | 39 ++ .../curation/zohobooksapi/chunks/002-auth.mdx | 8 + toolkit-docs-generator/curation/zoom.json | 18 - .../curation/zoom/chunks/001-auth.mdx | 7 + .../curation/zoom/pages/install/page.mdx | 111 ++++++ .../scripts/extract-curation.ts | 184 ++++----- toolkit-docs-generator/src/cli/index.ts | 14 +- .../src/diff/custom-sections-diff.ts | 24 +- .../src/merger/data-merger.ts | 106 +++++- .../src/sources/custom-sections-file.ts | 154 -------- toolkit-docs-generator/src/sources/index.ts | 2 +- .../src/sources/interfaces.ts | 2 +- .../src/sources/markdown-curation.ts | 352 ++++++++++++++++++ .../tests/merger/data-merger.test.ts | 188 +++++++--- .../tests/scenarios/curation-corpus.test.ts | 49 +++ .../scenarios/custom-sections-diff.test.ts | 11 +- .../prose-survives-force-regenerate.test.ts | 92 ++--- .../sources/custom-sections-file.test.ts | 181 --------- .../tests/sources/markdown-curation.test.ts | 214 +++++++++++ 183 files changed, 3101 insertions(+), 1536 deletions(-) create mode 100644 specs/mdx-curation/README.md create mode 100644 specs/mdx-curation/slices/01-compile-mdx.md create mode 100644 specs/mdx-curation/slices/02-authority-and-migration.md create mode 100644 specs/mdx-curation/slices/03-markdown-projection.md delete mode 100644 toolkit-docs-generator/curation/airtableapi.json delete mode 100644 toolkit-docs-generator/curation/arcadeengineapi.json create mode 100644 toolkit-docs-generator/curation/arcadeengineapi/chunks/001-secrets.mdx delete mode 100644 toolkit-docs-generator/curation/asana.json create mode 100644 toolkit-docs-generator/curation/asana/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/asanaapi.json create mode 100644 toolkit-docs-generator/curation/asanaapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/ashbyapi.json delete mode 100644 toolkit-docs-generator/curation/boxapi.json delete mode 100644 toolkit-docs-generator/curation/brightdata.json create mode 100644 toolkit-docs-generator/curation/brightdata/chunks/001-secrets.mdx delete mode 100644 toolkit-docs-generator/curation/calendlyapi.json delete mode 100644 toolkit-docs-generator/curation/clickup.json create mode 100644 toolkit-docs-generator/curation/clickup/chunks/001-taskpriority.mdx create mode 100644 toolkit-docs-generator/curation/clickup/chunks/002-taskorderby.mdx create mode 100644 toolkit-docs-generator/curation/clickup/chunks/003-commentresolution.mdx delete mode 100644 toolkit-docs-generator/curation/clickupapi.json create mode 100644 toolkit-docs-generator/curation/clickupapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/confluence.json create mode 100644 toolkit-docs-generator/curation/confluence/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/cursoragentsapi.json delete mode 100644 toolkit-docs-generator/curation/customerioapi.json delete mode 100644 toolkit-docs-generator/curation/customeriopipelinesapi.json delete mode 100644 toolkit-docs-generator/curation/customeriotrackapi.json delete mode 100644 toolkit-docs-generator/curation/datadogapi.json create mode 100644 toolkit-docs-generator/curation/datadogapi/chunks/001-authentication.mdx delete mode 100644 toolkit-docs-generator/curation/dropbox.json create mode 100644 toolkit-docs-generator/curation/dropbox/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/e2b.json create mode 100644 toolkit-docs-generator/curation/e2b/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/exaapi.json delete mode 100644 toolkit-docs-generator/curation/figma.json create mode 100644 toolkit-docs-generator/curation/figma/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/figmaapi.json delete mode 100644 toolkit-docs-generator/curation/firecrawl.json create mode 100644 toolkit-docs-generator/curation/firecrawl/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/freshserviceapi.json delete mode 100644 toolkit-docs-generator/curation/github.json create mode 100644 toolkit-docs-generator/curation/github/chunks/001-description-after-warning.mdx create mode 100644 toolkit-docs-generator/curation/github/chunks/002-description-after-info.mdx create mode 100644 toolkit-docs-generator/curation/github/chunks/003-github-enterprise-support.mdx create mode 100644 toolkit-docs-generator/curation/github/chunks/004-github-app-permissions-summary.mdx create mode 100644 toolkit-docs-generator/curation/github/chunks/005-configuration-setup.mdx delete mode 100644 toolkit-docs-generator/curation/githubapi.json create mode 100644 toolkit-docs-generator/curation/githubapi/chunks/001-secrets.mdx create mode 100644 toolkit-docs-generator/curation/githubapi/chunks/002-auth.mdx delete mode 100644 toolkit-docs-generator/curation/gmail.json create mode 100644 toolkit-docs-generator/curation/gmail/chunks/001-auth-after-markdown.mdx create mode 100644 toolkit-docs-generator/curation/gmail/chunks/002-adding-attachments-to-emails.mdx delete mode 100644 toolkit-docs-generator/curation/googlecalendar.json create mode 100644 toolkit-docs-generator/curation/googlecalendar/chunks/001-auth.mdx create mode 100644 toolkit-docs-generator/curation/googlecalendar/chunks/002-updategooglemeetoptions.mdx delete mode 100644 toolkit-docs-generator/curation/googlecontacts.json create mode 100644 toolkit-docs-generator/curation/googlecontacts/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/googledocs.json create mode 100644 toolkit-docs-generator/curation/googledocs/chunks/001-description-after-warning.mdx create mode 100644 toolkit-docs-generator/curation/googledocs/chunks/002-tab-support.mdx create mode 100644 toolkit-docs-generator/curation/googledocs/chunks/003-auth.mdx delete mode 100644 toolkit-docs-generator/curation/googledrive.json delete mode 100644 toolkit-docs-generator/curation/googlefinance.json create mode 100644 toolkit-docs-generator/curation/googlefinance/chunks/001-auth.mdx create mode 100644 toolkit-docs-generator/curation/googlefinance/chunks/002-googlefinancewindow.mdx delete mode 100644 toolkit-docs-generator/curation/googleflights.json create mode 100644 toolkit-docs-generator/curation/googleflights/chunks/001-auth.mdx create mode 100644 toolkit-docs-generator/curation/googleflights/chunks/002-googleflightsmaxstops.mdx create mode 100644 toolkit-docs-generator/curation/googleflights/chunks/003-googleflightssortby.mdx create mode 100644 toolkit-docs-generator/curation/googleflights/chunks/004-googleflightstravelclass.mdx delete mode 100644 toolkit-docs-generator/curation/googlehotels.json create mode 100644 toolkit-docs-generator/curation/googlehotels/chunks/001-auth.mdx create mode 100644 toolkit-docs-generator/curation/googlehotels/chunks/002-googlehotelssortby.mdx delete mode 100644 toolkit-docs-generator/curation/googlejobs.json create mode 100644 toolkit-docs-generator/curation/googlejobs/chunks/001-auth.mdx create mode 100644 toolkit-docs-generator/curation/googlejobs/chunks/002-default-parameters.mdx create mode 100644 toolkit-docs-generator/curation/googlejobs/chunks/003-languagecodes.mdx delete mode 100644 toolkit-docs-generator/curation/googlemaps.json create mode 100644 toolkit-docs-generator/curation/googlemaps/chunks/001-auth.mdx create mode 100644 toolkit-docs-generator/curation/googlemaps/chunks/002-default-parameters.mdx delete mode 100644 toolkit-docs-generator/curation/googlenews.json create mode 100644 toolkit-docs-generator/curation/googlenews/chunks/001-auth.mdx create mode 100644 toolkit-docs-generator/curation/googlenews/chunks/002-default-parameters.mdx create mode 100644 toolkit-docs-generator/curation/googlenews/chunks/003-languagecodes.mdx create mode 100644 toolkit-docs-generator/curation/googlenews/chunks/004-countrycodes.mdx delete mode 100644 toolkit-docs-generator/curation/googlesearch.json create mode 100644 toolkit-docs-generator/curation/googlesearch/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/googlesheets.json delete mode 100644 toolkit-docs-generator/curation/googleslides.json create mode 100644 toolkit-docs-generator/curation/googleslides/chunks/001-googleslides-reference.mdx delete mode 100644 toolkit-docs-generator/curation/hubspot.json create mode 100644 toolkit-docs-generator/curation/hubspot/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/hubspotautomationapi.json create mode 100644 toolkit-docs-generator/curation/hubspotautomationapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/hubspotcmsapi.json create mode 100644 toolkit-docs-generator/curation/hubspotcmsapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/hubspotconversationsapi.json create mode 100644 toolkit-docs-generator/curation/hubspotconversationsapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/hubspotcrmapi.json create mode 100644 toolkit-docs-generator/curation/hubspotcrmapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/hubspoteventsapi.json create mode 100644 toolkit-docs-generator/curation/hubspoteventsapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/hubspotmarketingapi.json create mode 100644 toolkit-docs-generator/curation/hubspotmarketingapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/hubspotmeetingsapi.json create mode 100644 toolkit-docs-generator/curation/hubspotmeetingsapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/hubspotusersapi.json create mode 100644 toolkit-docs-generator/curation/hubspotusersapi/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/intercomapi.json create mode 100644 toolkit-docs-generator/curation/intercomapi/chunks/001-intercom-api-subdomain.mdx create mode 100644 toolkit-docs-generator/curation/intercomapi/chunks/002-auth.mdx delete mode 100644 toolkit-docs-generator/curation/jira.json create mode 100644 toolkit-docs-generator/curation/jira/chunks/001-description-after-warning.mdx create mode 100644 toolkit-docs-generator/curation/jira/pages/environment-variables/page.mdx delete mode 100644 toolkit-docs-generator/curation/linear.json create mode 100644 toolkit-docs-generator/curation/linear/chunks/001-auth.mdx delete mode 100644 toolkit-docs-generator/curation/linkedin.json create mode 100644 toolkit-docs-generator/curation/linkedin/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/lumaapi.json create mode 100644 toolkit-docs-generator/curation/lumaapi/chunks/001-authentication.mdx delete mode 100644 toolkit-docs-generator/curation/mailchimpmarketingapi.json create mode 100644 toolkit-docs-generator/curation/mailchimpmarketingapi/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/microsoftteams.json create mode 100644 toolkit-docs-generator/curation/microsoftteams/chunks/001-description-after-warning.mdx delete mode 100644 toolkit-docs-generator/curation/miroapi.json delete mode 100644 toolkit-docs-generator/curation/notiontoolkit.json create mode 100644 toolkit-docs-generator/curation/notiontoolkit/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/pagerduty.json create mode 100644 toolkit-docs-generator/curation/pagerduty/chunks/001-description-after-warning.mdx create mode 100644 toolkit-docs-generator/curation/pagerduty/chunks/002-description-after-info.mdx create mode 100644 toolkit-docs-generator/curation/pagerduty/chunks/003-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/pagerdutyapi.json delete mode 100644 toolkit-docs-generator/curation/posthogapi.json create mode 100644 toolkit-docs-generator/curation/posthogapi/chunks/001-configuration.mdx delete mode 100644 toolkit-docs-generator/curation/pylon.json create mode 100644 toolkit-docs-generator/curation/pylon/chunks/001-description-after-warning.mdx create mode 100644 toolkit-docs-generator/curation/pylon/chunks/002-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/reddit.json create mode 100644 toolkit-docs-generator/curation/reddit/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/slack.json create mode 100644 toolkit-docs-generator/curation/slack/chunks/001-header-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/slackapi.json delete mode 100644 toolkit-docs-generator/curation/spotify.json create mode 100644 toolkit-docs-generator/curation/spotify/chunks/001-description-after-warning.mdx delete mode 100644 toolkit-docs-generator/curation/squareupapi.json delete mode 100644 toolkit-docs-generator/curation/stripe.json create mode 100644 toolkit-docs-generator/curation/stripe/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/stripeapi.json delete mode 100644 toolkit-docs-generator/curation/ticktickapi.json create mode 100644 toolkit-docs-generator/curation/ticktickapi/chunks/001-auth-after-markdown.mdx delete mode 100644 toolkit-docs-generator/curation/trelloapi.json create mode 100644 toolkit-docs-generator/curation/trelloapi/chunks/001-authentication.mdx delete mode 100644 toolkit-docs-generator/curation/vercelapi.json delete mode 100644 toolkit-docs-generator/curation/weaviateapi.json create mode 100644 toolkit-docs-generator/curation/weaviateapi/chunks/001-authentication.mdx delete mode 100644 toolkit-docs-generator/curation/xeroapi.json delete mode 100644 toolkit-docs-generator/curation/zohobooksapi.json create mode 100644 toolkit-docs-generator/curation/zohobooksapi/chunks/001-secrets.mdx create mode 100644 toolkit-docs-generator/curation/zohobooksapi/chunks/002-auth.mdx delete mode 100644 toolkit-docs-generator/curation/zoom.json create mode 100644 toolkit-docs-generator/curation/zoom/chunks/001-auth.mdx create mode 100644 toolkit-docs-generator/curation/zoom/pages/install/page.mdx delete mode 100644 toolkit-docs-generator/src/sources/custom-sections-file.ts create mode 100644 toolkit-docs-generator/src/sources/markdown-curation.ts create mode 100644 toolkit-docs-generator/tests/scenarios/curation-corpus.test.ts delete mode 100644 toolkit-docs-generator/tests/sources/custom-sections-file.test.ts create mode 100644 toolkit-docs-generator/tests/sources/markdown-curation.test.ts diff --git a/app/_lib/toolkit-markdown.ts b/app/_lib/toolkit-markdown.ts index 1c4629c9a..190f60a3b 100644 --- a/app/_lib/toolkit-markdown.ts +++ b/app/_lib/toolkit-markdown.ts @@ -1,4 +1,6 @@ import type { + DocumentationChunk, + DocumentationChunkLocation, ToolDefinition, ToolkitData, ToolParameter, @@ -12,6 +14,75 @@ import type { * independent of the rendered HTML. */ const JSON_INDENT = 2; +const DEFAULT_CHUNK_PRIORITY = 100; +const HEADER_PREFIX_REGEX = /^#+\s*/; + +function compareChunks( + left: DocumentationChunk, + right: DocumentationChunk +): number { + const priorityDifference = + (left.priority ?? DEFAULT_CHUNK_PRIORITY) - + (right.priority ?? DEFAULT_CHUNK_PRIORITY); + if (priorityDifference !== 0) { + return priorityDifference; + } + + const leftHeader = (left.header ?? "") + .replace(HEADER_PREFIX_REGEX, "") + .trim(); + const rightHeader = (right.header ?? "") + .replace(HEADER_PREFIX_REGEX, "") + .trim(); + if (leftHeader && rightHeader) { + return leftHeader.localeCompare(rightHeader); + } + if (leftHeader) { + return -1; + } + if (rightHeader) { + return 1; + } + return left.content.localeCompare(right.content); +} + +function chunkBlocks( + chunks: readonly DocumentationChunk[], + location: DocumentationChunkLocation, + position: DocumentationChunk["position"] +): string[] { + return chunks + .filter( + (chunk) => chunk.location === location && chunk.position === position + ) + .sort(compareChunks) + .map((chunk) => { + if (chunk.type === "code") { + return `\`\`\`\n${chunk.content.trim()}\n\`\`\``; + } + if (chunk.title) { + return `**${chunk.title}**\n\n${chunk.content.trim()}`; + } + return chunk.content.trim(); + }); +} + +function sectionBlocks( + chunks: readonly DocumentationChunk[], + location: DocumentationChunkLocation, + defaultBlock: string | null +): string[] { + const before = chunkBlocks(chunks, location, "before"); + const replacement = chunkBlocks(chunks, location, "replace"); + const after = chunkBlocks(chunks, location, "after"); + let middle: string[] = []; + if (replacement.length > 0) { + middle = replacement; + } else if (defaultBlock) { + middle = [defaultBlock]; + } + return [...before, ...middle, ...after]; +} /** Collapse newlines and escape pipes so a value is safe inside a table cell. */ function cell(value: string | null | undefined): string { @@ -47,38 +118,59 @@ function exampleBlock(tool: ToolDefinition): string | null { function toolBlock(tool: ToolDefinition): string { const blocks: string[] = [`### ${tool.qualifiedName}`]; + const chunks = tool.documentationChunks ?? []; - if (tool.description) { - blocks.push(tool.description.trim()); - } + blocks.push( + ...sectionBlocks(chunks, "description", tool.description?.trim() ?? null) + ); const scopes = tool.auth?.scopes ?? []; - if (scopes.length > 0) { - blocks.push( - `**Required OAuth scopes:** ${scopes.map((s) => `\`${s}\``).join(", ")}` - ); - } + blocks.push( + ...sectionBlocks( + chunks, + "auth", + scopes.length > 0 + ? `**Required OAuth scopes:** ${scopes.map((s) => `\`${s}\``).join(", ")}` + : null + ) + ); const secrets = tool.secrets ?? []; - if (secrets.length > 0) { - blocks.push(`**Secrets:** ${secrets.map((s) => `\`${s}\``).join(", ")}`); - } - + blocks.push( + ...sectionBlocks( + chunks, + "secrets", + secrets.length > 0 + ? `**Secrets:** ${secrets.map((s) => `\`${s}\``).join(", ")}` + : null + ) + ); + + let parametersBlock: string; if (tool.parameters && tool.parameters.length > 0) { const rows = [ "| Name | Type | Required | Description |", "| --- | --- | --- | --- |", ...tool.parameters.map(parameterRow), ]; - blocks.push(`**Parameters**\n\n${rows.join("\n")}`); + parametersBlock = `**Parameters**\n\n${rows.join("\n")}`; } else { - blocks.push("_No parameters._"); - } - - if (tool.output) { - const desc = tool.output.description ? ` — ${tool.output.description}` : ""; - blocks.push(`**Output:** \`${tool.output.type}\`${desc}`); + parametersBlock = "_No parameters._"; } + blocks.push(...sectionBlocks(chunks, "parameters", parametersBlock)); + + const outputDescription = tool.output?.description + ? ` — ${tool.output.description}` + : ""; + blocks.push( + ...sectionBlocks( + chunks, + "output", + tool.output + ? `**Output:** \`${tool.output.type}\`${outputDescription}` + : null + ) + ); const example = exampleBlock(tool); if (example) { @@ -90,19 +182,37 @@ function toolBlock(tool: ToolDefinition): string { export function toToolkitMarkdown(data: ToolkitData): string { const blocks: string[] = [`# ${data.label || data.id}`]; + const chunks = data.documentationChunks ?? []; if (data.description) { blocks.push(data.description.trim()); } + blocks.push(...chunkBlocks(chunks, "header", "before")); + blocks.push(...chunkBlocks(chunks, "description", "before")); + blocks.push(...chunkBlocks(chunks, "description", "after")); + blocks.push(...chunkBlocks(chunks, "header", "replace")); + blocks.push(...chunkBlocks(chunks, "header", "after")); if (data.summary) { blocks.push(data.summary.trim()); } + blocks.push(...chunkBlocks(chunks, "auth", "before")); + blocks.push(...chunkBlocks(chunks, "auth", "after")); + blocks.push(...chunkBlocks(chunks, "before_available_tools", "before")); + blocks.push(...chunkBlocks(chunks, "before_available_tools", "after")); + blocks.push(...chunkBlocks(chunks, "custom_section", "before")); + blocks.push(...chunkBlocks(chunks, "custom_section", "after")); const tools = data.tools ?? []; blocks.push(`## Tools (${tools.length})`); + blocks.push(...chunkBlocks(chunks, "after_available_tools", "before")); + blocks.push(...chunkBlocks(chunks, "after_available_tools", "after")); for (const tool of tools) { blocks.push(toolBlock(tool)); } + blocks.push(...chunkBlocks(chunks, "footer", "before")); + blocks.push(...chunkBlocks(chunks, "footer", "replace")); + blocks.push(...chunkBlocks(chunks, "footer", "after")); + return `${blocks.join("\n\n")}\n`; } diff --git a/package.json b/package.json index 7baa0100f..8abe37f56 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "swagger-ui-react": "5.32.6", "tailwindcss-animate": "1.0.7", "unist-util-visit": "5.1.0", + "yaml": "2.8.3", "zod": "4.3.6" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b42aeaca..8936629c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,6 +88,9 @@ importers: unist-util-visit: specifier: 5.1.0 version: 5.1.0 + yaml: + specifier: 2.8.3 + version: 2.8.3 zod: specifier: 4.3.6 version: 4.3.6 diff --git a/specs/mdx-curation/README.md b/specs/mdx-curation/README.md new file mode 100644 index 000000000..688568020 --- /dev/null +++ b/specs/mdx-curation/README.md @@ -0,0 +1,52 @@ +# MDX curation source + +## Goal + +Make Markdown and MDX files the durable source for hand-authored toolkit prose while keeping generated toolkit JSON as the application contract. + +## Next agent prompt + +Last updated: 2026-08-11. + +Implement the slices in order. Keep the curation directory globally authoritative, preserve previous JSON only for generated enrichment, and update this section before ending a pass. + +- [x] Compile checked-in MDX into `CustomSections` ([slice 1](./slices/01-compile-mdx.md)). +- [x] Apply deletion-safe authority and migrate the corpus ([slice 2](./slices/02-authority-and-migration.md)). +- [x] Include curated chunks in the secondary Markdown projection ([slice 3](./slices/03-markdown-projection.md)). + +## Source contract + +```text +curation// + chunks/*.mdx + pages/**/*.mdx +``` + +Chunk files use strict frontmatter for `type`, `location`, `position`, and optional chunk metadata. An optional fully qualified `tool` field targets one tool. The file body is the authored content. + +Page files use a required `type` field. Their path below `pages/` becomes `relativePath`, and their body becomes the page content. + +## Invariants + +- A configured curation root is authoritative for every toolkit. +- A missing toolkit directory means empty curation. Deleting the final file clears prior prose. +- A missing configured root, invalid frontmatter, malformed MDX, unsafe page path, or leftover JSON file fails the run. +- Previous generated JSON may supply summaries, examples, secret metadata, and last-known-good upstream data. It never restores authored chunks, imports, or subpages over current curation. +- The compiler preserves normalized source-path order. Renderers apply chunk priority at presentation time. +- Generated toolkit JSON remains the only input to the docs app and Markdown serializer. +- MDX source is never silently rewritten. Secret-coherence scans may warn about curated prose, but automated edits apply only to generated summaries. + +## Non-goals + +- Add toolkit subpage routes. +- Keep JSON or aggregate-file curation compatibility. +- Rename the unreleased `--custom-sections` option. +- Add automated writeback into source MDX. +- Modify PR #1113 in this branch; it must consume this compiler when rebased. + +## Verification + +- Focused source, diff, merger, workflow, and Markdown serializer tests. +- Full curation-tree compilation. +- Typecheck and lint. +- Re-extraction reproduces all authored chunks and subpages from committed toolkit artifacts. diff --git a/specs/mdx-curation/slices/01-compile-mdx.md b/specs/mdx-curation/slices/01-compile-mdx.md new file mode 100644 index 000000000..01aad503b --- /dev/null +++ b/specs/mdx-curation/slices/01-compile-mdx.md @@ -0,0 +1,22 @@ +# Slice 1: Compile MDX curation + +## Contract + +Replace the JSON reader with one cached compiler that projects the configured directory into existing `CustomSections` values. + +## API seam + +- `createMarkdownCurationSource(root)` owns traversal, frontmatter parsing, MDX syntax validation, normalization, and deterministic ordering. +- A configured source returns an empty `CustomSections` value for missing toolkits. +- `createEmptyCustomSectionsSource()` remains the signal that no curation source was configured. + +## Verification + +- Compile toolkit chunks, tool chunks, and nested pages. +- Reject invalid frontmatter, MDX, paths, duplicate normalized toolkits, and JSON files. +- Reject a missing configured root. +- Return empty curation for a missing toolkit. + +## Delegated decisions + +Internal helper names and diagnostic wording may change. The directory layout and authority semantics may not. diff --git a/specs/mdx-curation/slices/02-authority-and-migration.md b/specs/mdx-curation/slices/02-authority-and-migration.md new file mode 100644 index 000000000..069aff4c8 --- /dev/null +++ b/specs/mdx-curation/slices/02-authority-and-migration.md @@ -0,0 +1,23 @@ +# Slice 2: Apply authority and migrate content + +## Contract + +Make file deletion observable and prevent recovery behavior from resurrecting prose. Convert the checked-in JSON corpus to MDX atomically. + +## API seam + +- Curation diff compares the union of current sources and previous artifacts. +- Last-known-good recovery overlays current `CustomSections`, including empty arrays, on preserved artifacts. +- The extraction script emits the checked-in MDX directory layout. + +## Verification + +- Deleting the final file marks its toolkit changed. +- Missing toolkit curation clears previous toolkit and tool chunks. +- Recovery preserves upstream data but retains the current empty curation projection. +- Force regeneration includes authored MDX without previous output. +- The full checked-in curation tree compiles. + +## Delegated decisions + +Mechanical filenames may vary if they remain stable and descriptive. diff --git a/specs/mdx-curation/slices/03-markdown-projection.md b/specs/mdx-curation/slices/03-markdown-projection.md new file mode 100644 index 000000000..ade433258 --- /dev/null +++ b/specs/mdx-curation/slices/03-markdown-projection.md @@ -0,0 +1,19 @@ +# Slice 3: Preserve prose in Markdown output + +## Contract + +Make the agent-facing and copy-page Markdown representation include the curated chunks already present in generated toolkit JSON. + +## API seam + +`toToolkitMarkdown()` remains a projection of `ToolkitData`. It does not read source MDX. + +## Verification + +- Toolkit and tool chunks appear in deterministic order. +- `before`, `after`, and `replace` placement matches the generated sections that the serializer supports. +- Existing parameter, auth, secret, output, and example coverage stays green. + +## Delegated decisions + +Allowed MDX tags may remain verbatim in textual Markdown output. diff --git a/tests/toolkit-markdown.test.ts b/tests/toolkit-markdown.test.ts index e2a166be1..17b99866f 100644 --- a/tests/toolkit-markdown.test.ts +++ b/tests/toolkit-markdown.test.ts @@ -76,4 +76,64 @@ describe("toToolkitMarkdown", () => { expect(md).toContain("API_KEY"); expect(md).toContain("Example input"); }); + + test("includes toolkit and tool curation in deterministic order", () => { + const curated: ToolkitData = { + ...fixture, + documentationChunks: [ + { + type: "markdown", + location: "custom_section", + position: "after", + content: "Later toolkit prose.", + priority: 20, + }, + { + type: "markdown", + location: "custom_section", + position: "after", + content: "Earlier toolkit prose.", + priority: 10, + }, + ], + tools: fixture.tools.map((tool) => ({ + ...tool, + documentationChunks: [ + { + type: "markdown", + location: "description", + position: "after", + content: "Curated tool prose.", + }, + ], + })), + }; + + const result = toToolkitMarkdown(curated); + expect(result).toContain("Curated tool prose."); + expect(result.indexOf("Earlier toolkit prose.")).toBeLessThan( + result.indexOf("Later toolkit prose.") + ); + }); + + test("uses replacement curation instead of a generated section", () => { + const curated: ToolkitData = { + ...fixture, + tools: fixture.tools.map((tool) => ({ + ...tool, + documentationChunks: [ + { + type: "markdown", + location: "output", + position: "replace", + content: "A hand-authored output contract.", + }, + ], + })), + }; + + const result = toToolkitMarkdown(curated); + expect(result).toContain("A hand-authored output contract."); + expect(result).not.toContain("**Output:** `json` — The result"); + }); }); diff --git a/toolkit-docs-generator/ARCHITECTURE.md b/toolkit-docs-generator/ARCHITECTURE.md index 86654dd08..1f5ad8d14 100644 --- a/toolkit-docs-generator/ARCHITECTURE.md +++ b/toolkit-docs-generator/ARCHITECTURE.md @@ -12,7 +12,7 @@ The generator does **not** render HTML. It produces structured JSON and optional 1. Fetch tool definitions from the Engine API or Arcade API. 2. Load toolkit metadata from the design system or mock metadata. -3. Load custom sections from JSON files (optional). +3. Compile hand-authored Markdown and MDX curation (optional). 4. Merge all data into `MergedToolkit` objects. 5. Write a JSON file per toolkit and an `index.json` file. 6. Optionally verify output and compute diffs. @@ -24,7 +24,10 @@ The generator does **not** render HTML. It produces structured JSON and optional - `EngineApiSource` fetches tool metadata from the Engine API. - `ArcadeApiSource` fetches tool metadata from the Arcade API. - `DesignSystemMetadataSource` loads toolkit metadata from `@arcadeai/design-system`. -- `CustomSectionsFileSource` loads custom documentation chunks from a JSON file. +- `MarkdownCurationSource` compiles documentation chunks and subpages from the + configured curation directory. When configured, that directory is globally + authoritative: a missing toolkit directory means the toolkit has no authored + curation. - `CombinedToolkitDataSource` merges tools and metadata into one interface. ### Merger @@ -60,6 +63,8 @@ The generator output is consumed by the Next.js app: - The app loads JSON from `toolkit-docs-generator/data/toolkits/`. - `generateStaticParams` enumerates the toolkit routes and disables unknown dynamic parameters. - Custom documentation chunks are rendered as MDX in the UI. +- Copy-page and agent-facing Markdown is projected from the generated JSON and + includes those curated chunks. If you need HTML output, add a separate build step in the app. The generator intentionally avoids HTML to keep the pipeline deterministic. @@ -90,6 +95,7 @@ public, read-only values configured through these Vercel environment variables: ## Key files - `src/sources/engine-api.ts` — tool metadata from Engine API +- `src/sources/markdown-curation.ts` — Markdown and MDX curation compiler - `src/sources/toolkit-data-source.ts` — unified data source - `src/merger/data-merger.ts` — merge pipeline - `src/generator/json-generator.ts` — output writer diff --git a/toolkit-docs-generator/README.md b/toolkit-docs-generator/README.md index 0264ce7fb..7765f1d09 100644 --- a/toolkit-docs-generator/README.md +++ b/toolkit-docs-generator/README.md @@ -232,12 +232,25 @@ deletes it and rebuilds `index.json`. - `--api-source` select `tool-metadata` (default with Engine creds), `list-tools` (only with the explicit flag), or `mock` - `--previous-output` compare against a previous output directory -- `--custom-sections` load curated docs sections +- `--custom-sections` load an authoritative Markdown/MDX curation directory - `--skip-examples`, `--skip-summary` disable LLM steps - `--skip-secret-coherence` disable the stale-reference scan + coverage fill (see the Secret coherence section) - `--llm-editor-provider`, `--llm-editor-model`, `--llm-editor-api-key` configure the secret-coherence editor (Sonnet 4.6 by default) - `--no-verify-output` skip output verification +## Authored curation + +Store authored content below `curation//`. Put injectable sections in +`chunks/*.mdx` and rich subpages in `pages/**/*.mdx`. Each file begins with YAML +frontmatter for structured placement metadata; its body is the Markdown or MDX +that readers see. + +When `--custom-sections` is set, the directory is authoritative for every +toolkit. Removing the final curation file for a toolkit clears that toolkit's +authored prose on the next generation run. Invalid frontmatter, invalid MDX, +unknown tool targets, symlinks, unsafe subpage paths, and leftover JSON curation +fail generation instead of silently falling back to stale generated content. + ## Troubleshooting - **Nothing regenerated**: `--skip-unchanged` exits early when tool definitions did not change. diff --git a/toolkit-docs-generator/curation/airtableapi.json b/toolkit-docs-generator/curation/airtableapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/airtableapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 9043e32dd..000000000 --- a/toolkit-docs-generator/curation/arcadeengineapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/arcadeengineapi/chunks/001-secrets.mdx b/toolkit-docs-generator/curation/arcadeengineapi/chunks/001-secrets.mdx new file mode 100644 index 000000000..06249daef --- /dev/null +++ b/toolkit-docs-generator/curation/arcadeengineapi/chunks/001-secrets.mdx @@ -0,0 +1,15 @@ +--- +type: section +location: custom_section +position: after +header: "## Secrets" +--- +## Secrets + +This MCP Server requires the `ARCADE_API_KEY` secret to be configured. Learn how to [configure secrets](/guides/create-tools/tool-basics/create-tool-secrets). + +### Getting your Arcade API Key + +To use the Arcade Engine API MCP Server, you need an Arcade API key. This key authenticates your requests to the Arcade Engine. + +Learn how to create and manage your Arcade API keys in the [API Keys documentation](/get-started/setup/api-keys). diff --git a/toolkit-docs-generator/curation/asana.json b/toolkit-docs-generator/curation/asana.json deleted file mode 100644 index 8f477e133..000000000 --- a/toolkit-docs-generator/curation/asana.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/asana/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/asana/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..e4043b1b0 --- /dev/null +++ b/toolkit-docs-generator/curation/asana/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,6 @@ +--- +type: markdown +location: auth +position: after +--- +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 deleted file mode 100644 index 1751176e0..000000000 --- a/toolkit-docs-generator/curation/asanaapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/asanaapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/asanaapi/chunks/001-auth.mdx new file mode 100644 index 000000000..3ca2533a3 --- /dev/null +++ b/toolkit-docs-generator/curation/asanaapi/chunks/001-auth.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. +For detailed information on configuring the Asana OAuth provider with Arcade, see the [Asana Auth Provider documentation](/references/auth-providers/asana). diff --git a/toolkit-docs-generator/curation/ashbyapi.json b/toolkit-docs-generator/curation/ashbyapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/ashbyapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/boxapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index f35401a5f..000000000 --- a/toolkit-docs-generator/curation/brightdata.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "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/brightdata/chunks/001-secrets.mdx b/toolkit-docs-generator/curation/brightdata/chunks/001-secrets.mdx new file mode 100644 index 000000000..25ccfc481 --- /dev/null +++ b/toolkit-docs-generator/curation/brightdata/chunks/001-secrets.mdx @@ -0,0 +1,29 @@ +--- +type: section +location: custom_section +position: after +header: "## Secrets" +--- +## Secrets + +This tool requires the following secrets: + +- `BRIGHTDATA_API_KEY` +- `BRIGHTDATA_ZONE` + +### Auth + +The Arcade Bright Data MCP Server uses [Bright Data](https://brightdata.com/) to access proxy networks and web scraping infrastructure. + +**Global Environment Variables:** + +- `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. + +- `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. + +**How to get your credentials:** + +1. **API Key**: Navigate to your [Bright Data Control Panel](https://brightdata.com/cp) → Settings → API Access → Generate API Token +2. **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}` + +For more details, see the [Bright Data API Documentation](https://docs.brightdata.com/api-reference). diff --git a/toolkit-docs-generator/curation/calendlyapi.json b/toolkit-docs-generator/curation/calendlyapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/calendlyapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index a95710416..000000000 --- a/toolkit-docs-generator/curation/clickup.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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/clickup/chunks/001-taskpriority.mdx b/toolkit-docs-generator/curation/clickup/chunks/001-taskpriority.mdx new file mode 100644 index 000000000..d0b0265be --- /dev/null +++ b/toolkit-docs-generator/curation/clickup/chunks/001-taskpriority.mdx @@ -0,0 +1,12 @@ +--- +type: section +location: custom_section +position: after +header: "## TaskPriority" +--- +## TaskPriority + +- **URGENT**: `URGENT` +- **HIGH**: `HIGH` +- **NORMAL**: `NORMAL` +- **LOW**: `LOW` diff --git a/toolkit-docs-generator/curation/clickup/chunks/002-taskorderby.mdx b/toolkit-docs-generator/curation/clickup/chunks/002-taskorderby.mdx new file mode 100644 index 000000000..969e17f56 --- /dev/null +++ b/toolkit-docs-generator/curation/clickup/chunks/002-taskorderby.mdx @@ -0,0 +1,11 @@ +--- +type: section +location: custom_section +position: after +header: "## TaskOrderBy" +--- +## TaskOrderBy + +- **CREATED**: `created` +- **UPDATED**: `updated` +- **DUE_DATE**: `due_date` diff --git a/toolkit-docs-generator/curation/clickup/chunks/003-commentresolution.mdx b/toolkit-docs-generator/curation/clickup/chunks/003-commentresolution.mdx new file mode 100644 index 000000000..9c280cedb --- /dev/null +++ b/toolkit-docs-generator/curation/clickup/chunks/003-commentresolution.mdx @@ -0,0 +1,10 @@ +--- +type: section +location: custom_section +position: after +header: "## CommentResolution" +--- +## CommentResolution + +- **SET_AS_RESOLVED**: `resolved` +- **SET_AS_UNRESOLVED**: `unresolved` diff --git a/toolkit-docs-generator/curation/clickupapi.json b/toolkit-docs-generator/curation/clickupapi.json deleted file mode 100644 index 61312ad25..000000000 --- a/toolkit-docs-generator/curation/clickupapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/clickupapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/clickupapi/chunks/001-auth.mdx new file mode 100644 index 000000000..34e7d6fa2 --- /dev/null +++ b/toolkit-docs-generator/curation/clickupapi/chunks/001-auth.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. +For detailed information on configuring the ClickUp OAuth provider with Arcade, see the [ClickUp Auth Provider documentation](/references/auth-providers/clickup). diff --git a/toolkit-docs-generator/curation/confluence.json b/toolkit-docs-generator/curation/confluence.json deleted file mode 100644 index c136f1d2c..000000000 --- a/toolkit-docs-generator/curation/confluence.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/confluence/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/confluence/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..e37061f5b --- /dev/null +++ b/toolkit-docs-generator/curation/confluence/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +--- +The Arcade Confluence MCP Server uses the [Atlassian auth provider](/references/auth-providers/atlassian) to connect to users' Atlassian accounts. +--- diff --git a/toolkit-docs-generator/curation/cursoragentsapi.json b/toolkit-docs-generator/curation/cursoragentsapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/cursoragentsapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/customerioapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/customeriopipelinesapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/customeriotrackapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 6935a526f..000000000 --- a/toolkit-docs-generator/curation/datadogapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/datadogapi/chunks/001-authentication.mdx b/toolkit-docs-generator/curation/datadogapi/chunks/001-authentication.mdx new file mode 100644 index 000000000..708fd20fe --- /dev/null +++ b/toolkit-docs-generator/curation/datadogapi/chunks/001-authentication.mdx @@ -0,0 +1,29 @@ +--- +type: section +location: before_available_tools +position: after +header: "## Authentication" +--- +## Authentication + +The Arcade Datadog API MCP Server requires three environment variables to authenticate with the [Datadog API](https://docs.datadoghq.com/api/latest/): + +- `DATADOG_API_KEY` +- `DATADOG_APPLICATION_KEY` +- `DATADOG_BASE_URL` + +**How to obtain your credentials:** + +1. Log in to your [Datadog dashboard](https://app.datadoghq.com/) +2. Navigate to **Organization Settings** (click your profile icon in the bottom left) +3. Go to **API Keys** → click **New Key** → provide a name and click **Create Key** +4. Go to **Application Keys** → click **New Key** → provide a name and click **Create Key** +5. Determine your **Base URL** based on your Datadog site (check the URL in your browser): + - US1: `api.datadoghq.com` + - US3: `api.us3.datadoghq.com` + - US5: `api.us5.datadoghq.com` + - EU1: `api.datadoghq.eu` + - AP1: `api.ap1.datadoghq.com` + - GOV: `api.ddog-gov.com` + +For more details, see the [Datadog API and Application Keys documentation](https://docs.datadoghq.com/account_management/api-app-keys/). diff --git a/toolkit-docs-generator/curation/dropbox.json b/toolkit-docs-generator/curation/dropbox.json deleted file mode 100644 index 47d026c6b..000000000 --- a/toolkit-docs-generator/curation/dropbox.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/dropbox/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/dropbox/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..4cd2dff96 --- /dev/null +++ b/toolkit-docs-generator/curation/dropbox/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,6 @@ +--- +type: markdown +location: auth +position: after +--- +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 deleted file mode 100644 index 6f3636451..000000000 --- a/toolkit-docs-generator/curation/e2b.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/e2b/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/e2b/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..5f8ce34fc --- /dev/null +++ b/toolkit-docs-generator/curation/e2b/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +--- +The Arcade E2B MCP Server uses [E2B](https://e2b.dev/) to run code in a sandboxed environment. +**Global Environment Variables:** +- `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 deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/exaapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index c33b84a8a..000000000 --- a/toolkit-docs-generator/curation/figma.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/figma/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/figma/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..84e87a248 --- /dev/null +++ b/toolkit-docs-generator/curation/figma/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,9 @@ +--- +type: markdown +location: auth +position: after +--- + +The `projects:read` scope is **ONLY available in private Figma OAuth apps**. This scope is required for the navigation tools (`GetTeamProjects` and `GetProjectFiles`). +If 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. + diff --git a/toolkit-docs-generator/curation/figmaapi.json b/toolkit-docs-generator/curation/figmaapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/figmaapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index d7e6f5446..000000000 --- a/toolkit-docs-generator/curation/firecrawl.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/firecrawl/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/firecrawl/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..672ba3b5c --- /dev/null +++ b/toolkit-docs-generator/curation/firecrawl/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +--- +The Arcade Firecrawl MCP Server uses [Firecrawl](https://www.firecrawl.dev/) to scrape, crawl, and map websites. +**Global Environment Variables:** +- `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 deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/freshserviceapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 090fdf479..000000000 --- a/toolkit-docs-generator/curation/github.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "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/github/chunks/001-description-after-warning.mdx b/toolkit-docs-generator/curation/github/chunks/001-description-after-warning.mdx new file mode 100644 index 000000000..b6ae167c1 --- /dev/null +++ b/toolkit-docs-generator/curation/github/chunks/001-description-after-warning.mdx @@ -0,0 +1,12 @@ +--- +type: warning +location: description +position: after +--- + + **Critical**: This MCP Server is built for **GitHub Apps**, not OAuth Apps. + + You **must** create a GitHub App (not an OAuth App) to use this server properly. + + 👉 [Complete GitHub App Setup Guide](/references/auth-providers/github) + diff --git a/toolkit-docs-generator/curation/github/chunks/002-description-after-info.mdx b/toolkit-docs-generator/curation/github/chunks/002-description-after-info.mdx new file mode 100644 index 000000000..1d088ce4a --- /dev/null +++ b/toolkit-docs-generator/curation/github/chunks/002-description-after-info.mdx @@ -0,0 +1,8 @@ +--- +type: info +location: description +position: after +--- + + **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. + diff --git a/toolkit-docs-generator/curation/github/chunks/003-github-enterprise-support.mdx b/toolkit-docs-generator/curation/github/chunks/003-github-enterprise-support.mdx new file mode 100644 index 000000000..96aeeddf2 --- /dev/null +++ b/toolkit-docs-generator/curation/github/chunks/003-github-enterprise-support.mdx @@ -0,0 +1,32 @@ +--- +type: info +location: before_available_tools +position: after +header: "## GitHub Enterprise Support" +--- +## GitHub Enterprise Support + + + This MCP Server fully supports **GitHub Enterprise Server 2.22+** + + +**Default Configuration:** +- If no `GITHUB_SERVER_URL` is configured, the default is `https://api.github.com` (GitHub.com) +- All tools work with GitHub.com out of the box + +**For GitHub Enterprise Server:** + +1. Create your GitHub App on your Enterprise instance (not github.com) +2. Configure the `GITHUB_SERVER_URL` secret in Arcade Dashboard (see [Secrets Setup](#secrets-setup) below) +3. Use your Enterprise server's API endpoint + +**Example Enterprise Server URLs:** +- `https://github.yourcompany.com/api/v3` +- `https://enterprise.yourorg.com/api/v3` +- `https://git.company.internal/api/v3` + + + **Note**: GitHub Enterprise Server uses the `/api/v3` path after the hostname. GitHub.com uses `https://api.github.com` (no `/api/v3` suffix). + + +--- diff --git a/toolkit-docs-generator/curation/github/chunks/004-github-app-permissions-summary.mdx b/toolkit-docs-generator/curation/github/chunks/004-github-app-permissions-summary.mdx new file mode 100644 index 000000000..328d2cc7e --- /dev/null +++ b/toolkit-docs-generator/curation/github/chunks/004-github-app-permissions-summary.mdx @@ -0,0 +1,129 @@ +--- +type: section +location: before_available_tools +position: after +header: "## GitHub App Permissions Summary" +--- +## GitHub App Permissions Summary + +When creating your GitHub App, you'll need to grant specific permissions. Here's a quick reference of which tools require which permissions: + +### Repository Permissions + +| Permission | Level | Required For | +|------------|-------|--------------| +| **Contents** | Read | All repository and pull request tools, getting file contents | +| **Contents** | Write | Creating/updating files, creating branches, merging PRs | +| **Issues** | Read & Write | Issue management, PR assignments, managing labels (Issues) | +| **Pull requests** | Read & Write | Pull request management, reviews, managing labels (PRs) | +| **Metadata** | Read | All tools (automatically granted) | +| **Statuses** | Read | `CheckPullRequestMergeStatus` | + +### Organization Permissions + +| Permission | Level | Required For | +|------------|-------|--------------| +| **Members** | Read | Projects, collaborators, org repos, user search | +| **Projects** | Read & Write | All Projects V2 tools | + +### User Permissions + +| Permission | Level | Required For | +|------------|-------|--------------| +| **Read user profile** | Read | User context tools, review workload | +| **Act on behalf of user** | Enabled | `SetStarred` (starring repositories) | + +### Tools by Permission Requirements + +
+Basic Repository Access (Contents Read + Metadata) + +- `GetRepository` +- `CountStargazers` +- `ListStargazers` +- `ListRepositoryActivities` +- `GetFileContents` + +
+ +
+Repository Write (Contents Write + Metadata) + +- `CreateBranch` +- `CreateOrUpdateFile` +- `UpdateFileLines` + +
+ +
+Issue Management (Contents Read + Issues + Metadata) + +- `CreateIssue` +- `UpdateIssue` +- `GetIssue` +- `ListIssues` +- `CreateIssueComment` +- `ListRepositoryLabels` +- `ManageLabels` (for issues) + +
+ +
+Pull Request Read (Contents + Pull requests Read + Metadata) + +- `ListPullRequests` +- `GetPullRequest` +- `ListPullRequestCommits` +- `ListReviewCommentsOnPullRequest` +- `CheckPullRequestMergeStatus` (+ Statuses) + +
+ +
+Pull Request Write (Contents Read + Pull requests Write + Metadata) + +- `UpdatePullRequest` +- `CreatePullRequest` +- `SubmitPullRequestReview` +- `ManagePullRequest` +- `ManagePullRequestReviewers` +- `CreateReviewComment` +- `CreateReplyForReviewComment` +- `ResolveReviewThread` +- `ManageLabels` (for pull requests) +- `MergePullRequest` (+ Contents Write) + +
+ +
+Organization Tools (Contents + Metadata + Members) + +- `ListOrgRepositories` +- `SearchMyRepos` +- `ListRepositoryCollaborators` +- `AssignPullRequestUser` (+ Issues Write) + +
+ +
+Projects V2 (Contents + Metadata + Projects + Members) + +- `ListProjects` +- `ListProjectItems` +- `SearchProjectItem` +- `ListProjectFields` +- `UpdateProjectItem` (Projects Write) + +
+ +
+User Context (Contents + Metadata + Read user profile) + +- `WhoAmI` (+ Members) +- `GetUserRecentActivity` +- `GetUserOpenItems` +- `GetReviewWorkload` (+ Pull requests Read) + +
+ +--- diff --git a/toolkit-docs-generator/curation/github/chunks/005-configuration-setup.mdx b/toolkit-docs-generator/curation/github/chunks/005-configuration-setup.mdx new file mode 100644 index 000000000..6dde0ec40 --- /dev/null +++ b/toolkit-docs-generator/curation/github/chunks/005-configuration-setup.mdx @@ -0,0 +1,55 @@ +--- +type: warning +location: custom_section +position: after +header: "## Configuration & Setup" +--- +## Configuration & Setup + +### Authentication + + + **Critical**: This MCP Server uses **GitHub Apps** authentication, not OAuth Apps. + + You **must** create a GitHub App to use this server. OAuth Apps are not supported. + + +The Arcade GitHub MCP Server uses the [GitHub auth provider](/references/auth-providers/github) to connect to users' GitHub accounts. + +**For Arcade Cloud:** +- No configuration needed +- Your users will see `Arcade` as the requesting application +- All tools work out of the box + +**For Self-Hosted:** +- You must [create your own GitHub App](/references/auth-providers/github#creating-a-github-app) +- [Configure the GitHub auth provider](/references/auth-providers/github#configuring-github-auth-in-arcade) with your app credentials +- Your users will see your application name + + + **New to GitHub Apps?** Read [Why Arcade Uses GitHub Apps](/references/auth-providers/github#why-arcade-uses-github-apps-not-oauth-apps) + to understand the security and compliance benefits. + + +### Secrets Setup + +GitHub 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. + +**Steps:** + +1. Go to [Arcade Dashboard](https://api.arcade.dev/dashboard) +2. Navigate to **Secrets** in the left sidebar +3. Click **Add Secret** +4. Add the following secrets: + +| Secret Name | Value | Required For | +|-------------|-------|--------------| +| `GITHUB_SERVER_URL` | `https://api.github.com` (default for GitHub.com) | All tools | + + + **Default**: If `GITHUB_SERVER_URL` is not configured, it defaults to `https://api.github.com` (GitHub.com) + + **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. + + +--- diff --git a/toolkit-docs-generator/curation/githubapi.json b/toolkit-docs-generator/curation/githubapi.json deleted file mode 100644 index b526f1580..000000000 --- a/toolkit-docs-generator/curation/githubapi.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "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/githubapi/chunks/001-secrets.mdx b/toolkit-docs-generator/curation/githubapi/chunks/001-secrets.mdx new file mode 100644 index 000000000..49bf23808 --- /dev/null +++ b/toolkit-docs-generator/curation/githubapi/chunks/001-secrets.mdx @@ -0,0 +1,11 @@ +--- +type: section +location: custom_section +position: after +header: "## Secrets" +--- +## Secrets + +All tools in this toolset require the following secret: `GIT_SERVER_URL` (learn how to [configure secrets](/guides/create-tools/tool-basics/create-tool-secrets)) + +The `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. diff --git a/toolkit-docs-generator/curation/githubapi/chunks/002-auth.mdx b/toolkit-docs-generator/curation/githubapi/chunks/002-auth.mdx new file mode 100644 index 000000000..56ab1bce1 --- /dev/null +++ b/toolkit-docs-generator/curation/githubapi/chunks/002-auth.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. +For detailed information on configuring the GitHub OAuth provider with Arcade, see the [GitHub Auth Provider documentation](/references/auth-providers/github). diff --git a/toolkit-docs-generator/curation/gmail.json b/toolkit-docs-generator/curation/gmail.json deleted file mode 100644 index 5d25758f8..000000000 --- a/toolkit-docs-generator/curation/gmail.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "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/gmail/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/gmail/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..9ea589dfe --- /dev/null +++ b/toolkit-docs-generator/curation/gmail/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +--- +The Arcade Gmail MCP Server uses the [Google auth provider](/references/auth-providers/google) to connect to users' Google accounts. +--- diff --git a/toolkit-docs-generator/curation/gmail/chunks/002-adding-attachments-to-emails.mdx b/toolkit-docs-generator/curation/gmail/chunks/002-adding-attachments-to-emails.mdx new file mode 100644 index 000000000..81d75c6d1 --- /dev/null +++ b/toolkit-docs-generator/curation/gmail/chunks/002-adding-attachments-to-emails.mdx @@ -0,0 +1,25 @@ +--- +type: markdown +location: after_available_tools +position: after +header: "## Adding attachments to emails" +priority: 10 +--- +## Adding attachments to emails + +The 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. + +The 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. + +Attachments work on hosts that support a client-side pre-tool hook. On any other host the tool returns a clear error and sends nothing. + +| Host | Status | Notes | +| --- | --- | --- | +| Cursor | Supported | App, plus Cursor cloud and background agents. | +| Claude Code | Supported | v2.0.10+. | +| Codex CLI | Supported | v0.131+. | +| VS Code chat (GitHub Copilot) | Supported | 1.112+, agent mode. | +| Claude Cowork | Documented limitation | Sandboxed to one folder; the hook cannot be installed from inside it. | +| Claude Desktop | Documented limitation | No client-side hook layer. | +| ChatGPT desktop | Documented limitation | No client-side hook surface. | +| Microsoft 365 Copilot | Documented limitation | No host-side rewrite hook. | diff --git a/toolkit-docs-generator/curation/googlecalendar.json b/toolkit-docs-generator/curation/googlecalendar.json deleted file mode 100644 index 6e8b27b3c..000000000 --- a/toolkit-docs-generator/curation/googlecalendar.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "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/googlecalendar/chunks/001-auth.mdx b/toolkit-docs-generator/curation/googlecalendar/chunks/001-auth.mdx new file mode 100644 index 000000000..0b932b655 --- /dev/null +++ b/toolkit-docs-generator/curation/googlecalendar/chunks/001-auth.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Google Calendar MCP Server uses the [Google auth provider](/references/auth-providers/google) to connect to users' Google accounts. +--- diff --git a/toolkit-docs-generator/curation/googlecalendar/chunks/002-updategooglemeetoptions.mdx b/toolkit-docs-generator/curation/googlecalendar/chunks/002-updategooglemeetoptions.mdx new file mode 100644 index 000000000..166a7ee1b --- /dev/null +++ b/toolkit-docs-generator/curation/googlecalendar/chunks/002-updategooglemeetoptions.mdx @@ -0,0 +1,13 @@ +--- +type: section +location: custom_section +position: after +header: "## UpdateGoogleMeetOptions" +--- +## UpdateGoogleMeetOptions + +- **`NONE`**: No action is taken. +- **`ADD`**: Add the Google Meet link to the event. +- **`REMOVE`**: Remove the Google Meet link from the event. + + diff --git a/toolkit-docs-generator/curation/googlecontacts.json b/toolkit-docs-generator/curation/googlecontacts.json deleted file mode 100644 index 4c0d079ae..000000000 --- a/toolkit-docs-generator/curation/googlecontacts.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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/googlecontacts/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/googlecontacts/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..6413088c3 --- /dev/null +++ b/toolkit-docs-generator/curation/googlecontacts/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,6 @@ +--- +type: markdown +location: auth +position: after +--- +The Arcade Google Contacts MCP Server uses the [Google auth provider](/references/auth-providers/google) to connect to users' Google accounts. diff --git a/toolkit-docs-generator/curation/googledocs.json b/toolkit-docs-generator/curation/googledocs.json deleted file mode 100644 index 96710412a..000000000 --- a/toolkit-docs-generator/curation/googledocs.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "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/googledocs/chunks/001-description-after-warning.mdx b/toolkit-docs-generator/curation/googledocs/chunks/001-description-after-warning.mdx new file mode 100644 index 000000000..753a45512 --- /dev/null +++ b/toolkit-docs-generator/curation/googledocs/chunks/001-description-after-warning.mdx @@ -0,0 +1,9 @@ +--- +type: warning +location: description +position: after +--- + + This Toolkit is not available in Arcade Cloud. You can use these tools with a + [self-hosted](/guides/deployment-hosting/configure-engine) instance of Arcade. + diff --git a/toolkit-docs-generator/curation/googledocs/chunks/002-tab-support.mdx b/toolkit-docs-generator/curation/googledocs/chunks/002-tab-support.mdx new file mode 100644 index 000000000..b41e0b5b9 --- /dev/null +++ b/toolkit-docs-generator/curation/googledocs/chunks/002-tab-support.mdx @@ -0,0 +1,20 @@ +--- +type: section +location: custom_section +position: after +header: "## Tab Support" +--- +## Tab Support + +Google Docs supports hierarchical tabs within documents. The Google Docs tools provide comprehensive support for working with tabs: + +- **Tab Metadata**: `GetDocumentMetadata` returns hierarchical tab structures with approximate character and word counts for each tab +- **Tab Content**: `GetDocumentAsDocMD` and `SearchAndRetrieveDocuments` include all tab content in their output +- **Tab Filtering**: `GetDocumentAsDocMD` supports filtering to retrieve content from a specific tab using the `tab_id` parameter + +Tabs are represented with the following structure: +- Each tab has a unique `tabId`, `title`, `index`, and `nestingLevel` +- Tabs can be nested up to 3 levels deep (parent → child → grandchild) +- Tab metadata includes approximate character and word counts for each tab's content + +--- diff --git a/toolkit-docs-generator/curation/googledocs/chunks/003-auth.mdx b/toolkit-docs-generator/curation/googledocs/chunks/003-auth.mdx new file mode 100644 index 000000000..3dacf99eb --- /dev/null +++ b/toolkit-docs-generator/curation/googledocs/chunks/003-auth.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Google Docs MCP Server uses the [Google auth provider](/references/auth-providers/google) to connect to users' Google accounts. +--- diff --git a/toolkit-docs-generator/curation/googledrive.json b/toolkit-docs-generator/curation/googledrive.json deleted file mode 100644 index 6508f437d..000000000 --- a/toolkit-docs-generator/curation/googledrive.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "customImports": [ - "import ScopePicker from \"@/app/_components/scope-picker\";" - ] -} diff --git a/toolkit-docs-generator/curation/googlefinance.json b/toolkit-docs-generator/curation/googlefinance.json deleted file mode 100644 index 912309a8b..000000000 --- a/toolkit-docs-generator/curation/googlefinance.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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/googlefinance/chunks/001-auth.mdx b/toolkit-docs-generator/curation/googlefinance/chunks/001-auth.mdx new file mode 100644 index 000000000..666f1fd12 --- /dev/null +++ b/toolkit-docs-generator/curation/googlefinance/chunks/001-auth.mdx @@ -0,0 +1,18 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Google Finance MCP Server uses the [SerpAPI](https://serpapi.com/) to get stock data from Google Finance. +- **Secret:** + - `SERP_API_KEY`: Your SerpAPI API key. + + Setting the `SERP_API_KEY` secret is only required if you are + [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're + using Arcade Cloud, the secret is already set for you. To manage your + secrets, go to the [Secrets + page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade + Dashboard. + +--- diff --git a/toolkit-docs-generator/curation/googlefinance/chunks/002-googlefinancewindow.mdx b/toolkit-docs-generator/curation/googlefinance/chunks/002-googlefinancewindow.mdx new file mode 100644 index 000000000..11154a60a --- /dev/null +++ b/toolkit-docs-generator/curation/googlefinance/chunks/002-googlefinancewindow.mdx @@ -0,0 +1,20 @@ +--- +type: section +location: custom_section +position: after +header: "## GoogleFinanceWindow" +--- +## GoogleFinanceWindow + +Defines the time window for fetching stock data from Google Finance. + +- **`ONE_DAY`**: Represents a 1-day time window. +- **`FIVE_DAYS`**: Represents a 5-day time window. +- **`ONE_MONTH`**: Represents a 1-month time window. +- **`SIX_MONTHS`**: Represents a 6-month time window. +- **`YEAR_TO_DATE`**: Represents the time from the start of the year to the current date. +- **`ONE_YEAR`**: Represents a 1-year time window. +- **`FIVE_YEARS`**: Represents a 5-year time window. +- **`MAX`**: Represents the maximum available time window. + + diff --git a/toolkit-docs-generator/curation/googleflights.json b/toolkit-docs-generator/curation/googleflights.json deleted file mode 100644 index 368074e15..000000000 --- a/toolkit-docs-generator/curation/googleflights.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "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/googleflights/chunks/001-auth.mdx b/toolkit-docs-generator/curation/googleflights/chunks/001-auth.mdx new file mode 100644 index 000000000..d34fa8fff --- /dev/null +++ b/toolkit-docs-generator/curation/googleflights/chunks/001-auth.mdx @@ -0,0 +1,17 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Google Flights MCP Server uses the [SerpAPI](https://serpapi.com/) to search for flights from Google Flights. +- **Secret:** + - `SERP_API_KEY`: Your SerpAPI API key. + + Setting the `SERP_API_KEY` secret is only required if you are + [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're + using Arcade Cloud, the secret is already set for you. To manage your secrets, + go to the [Secrets page](https://api.arcade.dev/dashboard/auth/secrets) in the + Arcade Dashboard. + +--- diff --git a/toolkit-docs-generator/curation/googleflights/chunks/002-googleflightsmaxstops.mdx b/toolkit-docs-generator/curation/googleflights/chunks/002-googleflightsmaxstops.mdx new file mode 100644 index 000000000..5af6d4996 --- /dev/null +++ b/toolkit-docs-generator/curation/googleflights/chunks/002-googleflightsmaxstops.mdx @@ -0,0 +1,14 @@ +--- +type: section +location: custom_section +position: after +header: "## GoogleFlightsMaxStops" +--- +## GoogleFlightsMaxStops + +Defines the maximum number of stops for flights. + +- **`ANY`**: Any number of stops is allowed. +- **`NONSTOP`**: Only nonstop flights are allowed. +- **`ONE`**: Only flights with one stop are allowed. +- **`TWO`**: Only flights with two stops are allowed. diff --git a/toolkit-docs-generator/curation/googleflights/chunks/003-googleflightssortby.mdx b/toolkit-docs-generator/curation/googleflights/chunks/003-googleflightssortby.mdx new file mode 100644 index 000000000..71abfc8b6 --- /dev/null +++ b/toolkit-docs-generator/curation/googleflights/chunks/003-googleflightssortby.mdx @@ -0,0 +1,16 @@ +--- +type: section +location: custom_section +position: after +header: "## GoogleFlightsSortBy" +--- +## GoogleFlightsSortBy + +Defines the sorting options for flight search results. + +- **`TOP_FLIGHTS`**: Sort by the best available flights. +- **`PRICE`**: Sort by the lowest price. +- **`DEPARTURE_TIME`**: Sort by the earliest departure time. +- **`ARRIVAL_TIME`**: Sort by the earliest arrival time. +- **`DURATION`**: Sort by the shortest flight duration. +- **`EMISSIONS`**: Sort by the lowest carbon emissions. diff --git a/toolkit-docs-generator/curation/googleflights/chunks/004-googleflightstravelclass.mdx b/toolkit-docs-generator/curation/googleflights/chunks/004-googleflightstravelclass.mdx new file mode 100644 index 000000000..b624fe7ef --- /dev/null +++ b/toolkit-docs-generator/curation/googleflights/chunks/004-googleflightstravelclass.mdx @@ -0,0 +1,16 @@ +--- +type: section +location: custom_section +position: after +header: "## GoogleFlightsTravelClass" +--- +## GoogleFlightsTravelClass + +Defines the travel class options for flights. + +- **`ECONOMY`**: Economy class. +- **`PREMIUM_ECONOMY`**: Premium economy class. +- **`BUSINESS`**: Business class. +- **`FIRST`**: First class. + + diff --git a/toolkit-docs-generator/curation/googlehotels.json b/toolkit-docs-generator/curation/googlehotels.json deleted file mode 100644 index 1e50bf32a..000000000 --- a/toolkit-docs-generator/curation/googlehotels.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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/googlehotels/chunks/001-auth.mdx b/toolkit-docs-generator/curation/googlehotels/chunks/001-auth.mdx new file mode 100644 index 000000000..32adef9af --- /dev/null +++ b/toolkit-docs-generator/curation/googlehotels/chunks/001-auth.mdx @@ -0,0 +1,16 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Google Hotels MCP Server uses the [SerpAPI](https://serpapi.com/) to search for hotels from Google Hotels. +- **Secret:** + - `SERP_API_KEY`: Your SerpAPI API key. + + Setting the `SERP_API_KEY` secret is only required if you are + [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're + using Arcade Cloud, the secret is already set for you. To manage your secrets, + go to the [Secrets page](https://api.arcade.dev/dashboard/auth/secrets) in the + Arcade Dashboard. + diff --git a/toolkit-docs-generator/curation/googlehotels/chunks/002-googlehotelssortby.mdx b/toolkit-docs-generator/curation/googlehotels/chunks/002-googlehotelssortby.mdx new file mode 100644 index 000000000..ff669d711 --- /dev/null +++ b/toolkit-docs-generator/curation/googlehotels/chunks/002-googlehotelssortby.mdx @@ -0,0 +1,16 @@ +--- +type: section +location: custom_section +position: after +header: "## GoogleHotelsSortBy" +--- +## GoogleHotelsSortBy + +Defines the sorting options for hotel search results. + +- **`RELEVANCE`**: Sort by the most relevant results. +- **`LOWEST_PRICE`**: Sort by the lowest price available. +- **`HIGHEST_RATING`**: Sort by the highest customer ratings. +- **`MOST_REVIEWED`**: Sort by the most reviewed hotels. + + diff --git a/toolkit-docs-generator/curation/googlejobs.json b/toolkit-docs-generator/curation/googlejobs.json deleted file mode 100644 index b70786f57..000000000 --- a/toolkit-docs-generator/curation/googlejobs.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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/googlejobs/chunks/001-auth.mdx b/toolkit-docs-generator/curation/googlejobs/chunks/001-auth.mdx new file mode 100644 index 000000000..46c8fa265 --- /dev/null +++ b/toolkit-docs-generator/curation/googlejobs/chunks/001-auth.mdx @@ -0,0 +1,17 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Google Jobs MCP Server uses the [SerpAPI](https://serpapi.com/) to get job data from Google Jobs. +- **Secret:** + - `SERP_API_KEY`: Your SerpAPI API key. + + Setting the `SERP_API_KEY` secret is only required if you are + [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're + using Arcade Cloud, the secret is already set for you. To manage your + secrets, go to the [Secrets + page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade + Dashboard. + diff --git a/toolkit-docs-generator/curation/googlejobs/chunks/002-default-parameters.mdx b/toolkit-docs-generator/curation/googlejobs/chunks/002-default-parameters.mdx new file mode 100644 index 000000000..018462f5c --- /dev/null +++ b/toolkit-docs-generator/curation/googlejobs/chunks/002-default-parameters.mdx @@ -0,0 +1,20 @@ +--- +type: section +location: custom_section +position: after +header: "## Default parameters" +--- +## Default parameters + +Language is configurable through environment variables. When set, they will be used as default for Google Jobs tools. + +Providing a different value as `language` argument in a tool call will override the default value. + +**Language** + +The 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: + +- `ARCADE_GOOGLE_LANGUAGE`: a default value for all Google search tools. If not set, defaults to 'en' (English). +- `ARCADE_GOOGLE_JOBS_LANGUAGE`: a default value for the jobs search tools. If not set, defaults to `ARCADE_GOOGLE_LANGUAGE`. + +A list of supported language codes can be found [here](#languagecodes). diff --git a/toolkit-docs-generator/curation/googlejobs/chunks/003-languagecodes.mdx b/toolkit-docs-generator/curation/googlejobs/chunks/003-languagecodes.mdx new file mode 100644 index 000000000..f49a43f73 --- /dev/null +++ b/toolkit-docs-generator/curation/googlejobs/chunks/003-languagecodes.mdx @@ -0,0 +1,41 @@ +--- +type: section +location: custom_section +position: after +header: "## LanguageCodes" +--- +## LanguageCodes + +- **`ar`**: Arabic +- **`bn`**: Bengali +- **`da`**: Danish +- **`de`**: German +- **`el`**: Greek +- **`en`**: English +- **`es`**: Spanish +- **`fi`**: Finnish +- **`fr`**: French +- **`hi`**: Hindi +- **`hu`**: Hungarian +- **`id`**: Indonesian +- **`it`**: Italian +- **`ja`**: Japanese +- **`ko`**: Korean +- **`ms`**: Malay +- **`nl`**: Dutch +- **`no`**: Norwegian +- **`pcm`**: Nigerian Pidgin +- **`pl`**: Polish +- **`pt`**: Portuguese +- **`pt-br`**: Portuguese (Brazil) +- **`pt-pt`**: Portuguese (Portugal) +- **`ru`**: Russian +- **`sv`**: Swedish +- **`tl`**: Filipino +- **`tr`**: Turkish +- **`uk`**: Ukrainian +- **`zh`**: Chinese +- **`zh-cn`**: Chinese (Simplified) +- **`zh-tw`**: Chinese (Traditional) + + diff --git a/toolkit-docs-generator/curation/googlemaps.json b/toolkit-docs-generator/curation/googlemaps.json deleted file mode 100644 index bd19d7c8a..000000000 --- a/toolkit-docs-generator/curation/googlemaps.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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/googlemaps/chunks/001-auth.mdx b/toolkit-docs-generator/curation/googlemaps/chunks/001-auth.mdx new file mode 100644 index 000000000..41816b430 --- /dev/null +++ b/toolkit-docs-generator/curation/googlemaps/chunks/001-auth.mdx @@ -0,0 +1,17 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Google Maps MCP Server uses the [SerpAPI](https://serpapi.com/) to get directions. +- **Secret:** + - `SERP_API_KEY`: Your SerpAPI API key. + + Setting the `SERP_API_KEY` secret is only required if you are + [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're + using Arcade Cloud, the secret is already set for you. To manage your + secrets, go to the [Secrets + page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade + Dashboard. + diff --git a/toolkit-docs-generator/curation/googlemaps/chunks/002-default-parameters.mdx b/toolkit-docs-generator/curation/googlemaps/chunks/002-default-parameters.mdx new file mode 100644 index 000000000..404e0f3fc --- /dev/null +++ b/toolkit-docs-generator/curation/googlemaps/chunks/002-default-parameters.mdx @@ -0,0 +1,57 @@ +--- +type: info +location: custom_section +position: after +header: "## Default parameters" +--- +## Default parameters + +Language, Country, Distance Unit, and Travel Mode are configurable through environment variables. When set, they will be used as default for Google Maps tools. + +Providing a different value as `language`, `country`, `distance_unit`, or `travel_mode` argument in a tool call will override the default value. + +**Language** + +The 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: + +- `ARCADE_GOOGLE_LANGUAGE`: a default value for all Google tools. If not set, defaults to 'en' (English). +- `ARCADE_GOOGLE_MAPS_LANGUAGE`: a default value for the Google Maps tools. If not set, defaults to `ARCADE_GOOGLE_LANGUAGE`. + +A list of supported language codes can be found [here](#languagecodes). + +**Country** + +The country code is a 2-character code that determines the country in which the API will search for directions: + +- `ARCADE_GOOGLE_MAPS_COUNTRY`: a default value for the Google Maps tools. If not set, defaults to `None`. + +A list of supported country codes can be found [here](#countrycodes). + +**Distance Unit** + +The distance unit is a string that determines the unit of distance to use in the Google Maps search: + +- `ARCADE_GOOGLE_MAPS_DISTANCE_UNIT`: a default value for the Google Maps tools. If not set, defaults to `GoogleMapsDistanceUnit.KM`. + +A list of supported distance units can be found [here](#googlemapsdistanceunit). + +**Travel Mode** + +The travel mode is a string that determines the mode of travel to use in the Google Maps search: + +- `ARCADE_GOOGLE_MAPS_TRAVEL_MODE`: a default value for the Google Maps tools. If not set, defaults to `GoogleMapsTravelMode.BEST`. + +A list of supported travel modes can be found [here](#googlemapstravelmode). + +- **Secret:** + - `SERP_API_KEY`: Your SerpAPI API key. + + Setting the `SERP_API_KEY` secret is only required if you are + [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're + using Arcade Cloud, the secret is already set for you. To manage your + secrets, go to the [Secrets + page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade + Dashboard. + + +--- diff --git a/toolkit-docs-generator/curation/googlenews.json b/toolkit-docs-generator/curation/googlenews.json deleted file mode 100644 index 0af1fd739..000000000 --- a/toolkit-docs-generator/curation/googlenews.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "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/googlenews/chunks/001-auth.mdx b/toolkit-docs-generator/curation/googlenews/chunks/001-auth.mdx new file mode 100644 index 000000000..9eaf0ed57 --- /dev/null +++ b/toolkit-docs-generator/curation/googlenews/chunks/001-auth.mdx @@ -0,0 +1,17 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Google News MCP Server uses the [SerpAPI](https://serpapi.com/) to get news data from Google News. +- **Secret:** + - `SERP_API_KEY`: Your SerpAPI API key. + + Setting the `SERP_API_KEY` secret is only required if you are + [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're + using Arcade Cloud, the secret is already set for you. To manage your + secrets, go to the [Secrets + page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade + Dashboard. + diff --git a/toolkit-docs-generator/curation/googlenews/chunks/002-default-parameters.mdx b/toolkit-docs-generator/curation/googlenews/chunks/002-default-parameters.mdx new file mode 100644 index 000000000..61119065a --- /dev/null +++ b/toolkit-docs-generator/curation/googlenews/chunks/002-default-parameters.mdx @@ -0,0 +1,30 @@ +--- +type: section +location: custom_section +position: after +header: "## Default parameters" +--- +## Default parameters + +Language and Country are configurable through environment variables. When set, they will be used as default for Google News tools. + +Providing a different value as `language_code` or `country_code` argument in the tool call will override the default value. + +**Language** + +The 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: + +- `ARCADE_GOOGLE_LANGUAGE`: a default value for all Google search tools. If not set, defaults to 'en' (English). +- `ARCADE_GOOGLE_NEWS_LANGUAGE`: a default value for the news search tools. If not set, defaults to `ARCADE_GOOGLE_LANGUAGE`. + +A list of supported language codes can be found [here](#languagecodes). + +**Country** + +The 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: + +- `ARCADE_GOOGLE_NEWS_COUNTRY`: a default value for the `SearchNews` tool. If not set, defaults to `None` (search news globally). + +A list of supported country codes can be found [here](#countrycodes). + +--- diff --git a/toolkit-docs-generator/curation/googlenews/chunks/003-languagecodes.mdx b/toolkit-docs-generator/curation/googlenews/chunks/003-languagecodes.mdx new file mode 100644 index 000000000..14f18fe5e --- /dev/null +++ b/toolkit-docs-generator/curation/googlenews/chunks/003-languagecodes.mdx @@ -0,0 +1,39 @@ +--- +type: section +location: custom_section +position: after +header: "## LanguageCodes" +--- +## LanguageCodes + +- **`ar`**: Arabic +- **`bn`**: Bengali +- **`da`**: Danish +- **`de`**: German +- **`el`**: Greek +- **`en`**: English +- **`es`**: Spanish +- **`fi`**: Finnish +- **`fr`**: French +- **`hi`**: Hindi +- **`hu`**: Hungarian +- **`id`**: Indonesian +- **`it`**: Italian +- **`ja`**: Japanese +- **`ko`**: Korean +- **`ms`**: Malay +- **`nl`**: Dutch +- **`no`**: Norwegian +- **`pcm`**: Nigerian Pidgin +- **`pl`**: Polish +- **`pt`**: Portuguese +- **`pt-br`**: Portuguese (Brazil) +- **`pt-pt`**: Portuguese (Portugal) +- **`ru`**: Russian +- **`sv`**: Swedish +- **`tl`**: Filipino +- **`tr`**: Turkish +- **`uk`**: Ukrainian +- **`zh`**: Chinese +- **`zh-cn`**: Chinese (Simplified) +- **`zh-tw`**: Chinese (Traditional) diff --git a/toolkit-docs-generator/curation/googlenews/chunks/004-countrycodes.mdx b/toolkit-docs-generator/curation/googlenews/chunks/004-countrycodes.mdx new file mode 100644 index 000000000..97e3e6627 --- /dev/null +++ b/toolkit-docs-generator/curation/googlenews/chunks/004-countrycodes.mdx @@ -0,0 +1,254 @@ +--- +type: section +location: custom_section +position: after +header: "## CountryCodes" +--- +## CountryCodes + +- **`af`**: Afghanistan +- **`al`**: Albania +- **`dz`**: Algeria +- **`as`**: American Samoa +- **`ad`**: Andorra +- **`ao`**: Angola +- **`ai`**: Anguilla +- **`aq`**: Antarctica +- **`ag`**: Antigua and Barbuda +- **`ar`**: Argentina +- **`am`**: Armenia +- **`aw`**: Aruba +- **`au`**: Australia +- **`at`**: Austria +- **`az`**: Azerbaijan +- **`bs`**: Bahamas +- **`bh`**: Bahrain +- **`bd`**: Bangladesh +- **`bb`**: Barbados +- **`by`**: Belarus +- **`be`**: Belgium +- **`bz`**: Belize +- **`bj`**: Benin +- **`bm`**: Bermuda +- **`bt`**: Bhutan +- **`bo`**: Bolivia +- **`ba`**: Bosnia and Herzegovina +- **`bw`**: Botswana +- **`bv`**: Bouvet Island +- **`br`**: Brazil +- **`io`**: British Indian Ocean Territory +- **`bn`**: Brunei Darussalam +- **`bg`**: Bulgaria +- **`bf`**: Burkina Faso +- **`bi`**: Burundi +- **`kh`**: Cambodia +- **`cm`**: Cameroon +- **`ca`**: Canada +- **`cv`**: Cape Verde +- **`ky`**: Cayman Islands +- **`cf`**: Central African Republic +- **`td`**: Chad +- **`cl`**: Chile +- **`cn`**: China +- **`cx`**: Christmas Island +- **`cc`**: Cocos (Keeling) Islands +- **`co`**: Colombia +- **`km`**: Comoros +- **`cg`**: Congo +- **`cd`**: Congo, the Democratic Republic of the +- **`ck`**: Cook Islands +- **`cr`**: Costa Rica +- **`ci`**: Cote D'ivoire +- **`hr`**: Croatia +- **`cu`**: Cuba +- **`cy`**: Cyprus +- **`cz`**: Czech Republic +- **`dk`**: Denmark +- **`dj`**: Djibouti +- **`dm`**: Dominica +- **`do`**: Dominican Republic +- **`ec`**: Ecuador +- **`eg`**: Egypt +- **`sv`**: El Salvador +- **`gq`**: Equatorial Guinea +- **`er`**: Eritrea +- **`ee`**: Estonia +- **`et`**: Ethiopia +- **`fk`**: Falkland Islands (Malvinas) +- **`fo`**: Faroe Islands +- **`fj`**: Fiji +- **`fi`**: Finland +- **`fr`**: France +- **`gf`**: French Guiana +- **`pf`**: French Polynesia +- **`tf`**: French Southern Territories +- **`ga`**: Gabon +- **`gm`**: Gambia +- **`ge`**: Georgia +- **`de`**: Germany +- **`gh`**: Ghana +- **`gi`**: Gibraltar +- **`gr`**: Greece +- **`gl`**: Greenland +- **`gd`**: Grenada +- **`gp`**: Guadeloupe +- **`gu`**: Guam +- **`gt`**: Guatemala +- **`gg`**: Guernsey +- **`gn`**: Guinea +- **`gw`**: Guinea-Bissau +- **`gy`**: Guyana +- **`ht`**: Haiti +- **`hm`**: Heard Island and Mcdonald Islands +- **`va`**: Holy See (Vatican City State) +- **`hn`**: Honduras +- **`hk`**: Hong Kong +- **`hu`**: Hungary +- **`is`**: Iceland +- **`in`**: India +- **`id`**: Indonesia +- **`ir`**: Iran, Islamic Republic of +- **`iq`**: Iraq +- **`ie`**: Ireland +- **`im`**: Isle of Man +- **`il`**: Israel +- **`it`**: Italy +- **`je`**: Jersey +- **`jm`**: Jamaica +- **`jp`**: Japan +- **`jo`**: Jordan +- **`kz`**: Kazakhstan +- **`ke`**: Kenya +- **`ki`**: Kiribati +- **`kp`**: Korea, Democratic People's Republic of +- **`kr`**: Korea, Republic of +- **`kw`**: Kuwait +- **`kg`**: Kyrgyzstan +- **`la`**: Lao People's Democratic Republic +- **`lv`**: Latvia +- **`lb`**: Lebanon +- **`ls`**: Lesotho +- **`lr`**: Liberia +- **`ly`**: Libyan Arab Jamahiriya +- **`li`**: Liechtenstein +- **`lt`**: Lithuania +- **`lu`**: Luxembourg +- **`mo`**: Macao +- **`mk`**: Macedonia, the Former Yugosalv Republic of +- **`mg`**: Madagascar +- **`mw`**: Malawi +- **`my`**: Malaysia +- **`mv`**: Maldives +- **`ml`**: Mali +- **`mt`**: Malta +- **`mh`**: Marshall Islands +- **`mq`**: Martinique +- **`mr`**: Mauritania +- **`mu`**: Mauritius +- **`yt`**: Mayotte +- **`mx`**: Mexico +- **`fm`**: Micronesia, Federated States of +- **`md`**: Moldova, Republic of +- **`mc`**: Monaco +- **`mn`**: Mongolia +- **`me`**: Montenegro +- **`ms`**: Montserrat +- **`ma`**: Morocco +- **`mz`**: Mozambique +- **`mm`**: Myanmar +- **`na`**: Namibia +- **`nr`**: Nauru +- **`np`**: Nepal +- **`nl`**: Netherlands +- **`an`**: Netherlands Antilles +- **`nc`**: New Caledonia +- **`nz`**: New Zealand +- **`ni`**: Nicaragua +- **`ne`**: Niger +- **`ng`**: Nigeria +- **`nu`**: Niue +- **`nf`**: Norfolk Island +- **`mp`**: Northern Mariana Islands +- **`no`**: Norway +- **`om`**: Oman +- **`pk`**: Pakistan +- **`pw`**: Palau +- **`ps`**: Palestinian Territory, Occupied +- **`pa`**: Panama +- **`pg`**: Papua New Guinea +- **`py`**: Paraguay +- **`pe`**: Peru +- **`ph`**: Philippines +- **`pn`**: Pitcairn +- **`pl`**: Poland +- **`pt`**: Portugal +- **`pr`**: Puerto Rico +- **`qa`**: Qatar +- **`re`**: Reunion +- **`ro`**: Romania +- **`ru`**: Russian Federation +- **`rw`**: Rwanda +- **`sh`**: Saint Helena +- **`kn`**: Saint Kitts and Nevis +- **`lc`**: Saint Lucia +- **`pm`**: Saint Pierre and Miquelon +- **`vc`**: Saint Vincent and the Grenadines +- **`ws`**: Samoa +- **`sm`**: San Marino +- **`st`**: Sao Tome and Principe +- **`sa`**: Saudi Arabia +- **`sn`**: Senegal +- **`rs`**: Serbia +- **`sc`**: Seychelles +- **`sl`**: Sierra Leone +- **`sg`**: Singapore +- **`sk`**: Slovakia +- **`si`**: Slovenia +- **`sb`**: Solomon Islands +- **`so`**: Somalia +- **`za`**: South Africa +- **`gs`**: South Georgia and the South Sandwich Islands +- **`es`**: Spain +- **`lk`**: Sri Lanka +- **`sd`**: Sudan +- **`sr`**: Suriname +- **`sj`**: Svalbard and Jan Mayen +- **`sz`**: Swaziland +- **`se`**: Sweden +- **`ch`**: Switzerland +- **`sy`**: Syrian Arab Republic +- **`tw`**: Taiwan, Province of China +- **`tj`**: Tajikistan +- **`tz`**: Tanzania, United Republic of +- **`th`**: Thailand +- **`tl`**: Timor-Leste +- **`tg`**: Togo +- **`tk`**: Tokelau +- **`to`**: Tonga +- **`tt`**: Trinidad and Tobago +- **`tn`**: Tunisia +- **`tr`**: Turkiye +- **`tm`**: Turkmenistan +- **`tc`**: Turks and Caicos Islands +- **`tv`**: Tuvalu +- **`ug`**: Uganda +- **`ua`**: Ukraine +- **`ae`**: United Arab Emirates +- **`uk`**: United Kingdom +- **`gb`**: United Kingdom +- **`us`**: United States +- **`um`**: United States Minor Outlying Islands +- **`uy`**: Uruguay +- **`uz`**: Uzbekistan +- **`vu`**: Vanuatu +- **`ve`**: Venezuela +- **`vn`**: Viet Nam +- **`vg`**: Virgin Islands, British +- **`vi`**: Virgin Islands, U.S. +- **`wf`**: Wallis and Futuna +- **`eh`**: Western Sahara +- **`ye`**: Yemen +- **`zm`**: Zambia +- **`zw`**: Zimbabwe + + diff --git a/toolkit-docs-generator/curation/googlesearch.json b/toolkit-docs-generator/curation/googlesearch.json deleted file mode 100644 index 40d27c9e4..000000000 --- a/toolkit-docs-generator/curation/googlesearch.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/googlesearch/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/googlesearch/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..5dfc63b0c --- /dev/null +++ b/toolkit-docs-generator/curation/googlesearch/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,16 @@ +--- +type: markdown +location: auth +position: after +--- +The Arcade Google Search MCP Server uses the [SerpAPI](https://serpapi.com/) to get results from a Google search. +- **Secret:** + - `SERP_API_KEY`: Your SerpAPI API key. + + Setting the `SERP_API_KEY` secret is only required if you are + [self-hosting](/guides/deployment-hosting/configure-engine) Arcade. If you're + using Arcade Cloud, the secret is already set for you. To manage your + secrets, go to the [Secrets + page](https://api.arcade.dev/dashboard/auth/secrets) in the Arcade + Dashboard. + diff --git a/toolkit-docs-generator/curation/googlesheets.json b/toolkit-docs-generator/curation/googlesheets.json deleted file mode 100644 index 6508f437d..000000000 --- a/toolkit-docs-generator/curation/googlesheets.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "customImports": [ - "import ScopePicker from \"@/app/_components/scope-picker\";" - ] -} diff --git a/toolkit-docs-generator/curation/googleslides.json b/toolkit-docs-generator/curation/googleslides.json deleted file mode 100644 index 5e377680c..000000000 --- a/toolkit-docs-generator/curation/googleslides.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/googleslides/chunks/001-googleslides-reference.mdx b/toolkit-docs-generator/curation/googleslides/chunks/001-googleslides-reference.mdx new file mode 100644 index 000000000..5fd307a32 --- /dev/null +++ b/toolkit-docs-generator/curation/googleslides/chunks/001-googleslides-reference.mdx @@ -0,0 +1,36 @@ +--- +type: section +location: custom_section +position: after +header: "## GoogleSlides Reference" +--- +## GoogleSlides Reference + +Below is a reference of enumerations used by some tools in the GoogleSlides MCP Server: + +### OrderBy + +- **CREATED_TIME**: `createdTime` +- **CREATED_TIME_DESC**: `createdTime desc` +- **FOLDER**: `folder` +- **FOLDER_DESC**: `folder desc` +- **MODIFIED_BY_ME_TIME**: `modifiedByMeTime` +- **MODIFIED_BY_ME_TIME_DESC**: `modifiedByMeTime desc` +- **MODIFIED_TIME**: `modifiedTime` +- **MODIFIED_TIME_DESC**: `modifiedTime desc` +- **NAME**: `name` +- **NAME_DESC**: `name desc` +- **NAME_NATURAL**: `name_natural` +- **NAME_NATURAL_DESC**: `name_natural desc` +- **QUOTA_BYTES_USED**: `quotaBytesUsed` +- **QUOTA_BYTES_USED_DESC**: `quotaBytesUsed desc` +- **RECENCY**: `recency` +- **RECENCY_DESC**: `recency desc` +- **SHARED_WITH_ME_TIME**: `sharedWithMeTime` +- **SHARED_WITH_ME_TIME_DESC**: `sharedWithMeTime desc` +- **STARRED**: `starred` +- **STARRED_DESC**: `starred desc` +- **VIEWED_BY_ME_TIME**: `viewedByMeTime` +- **VIEWED_BY_ME_TIME_DESC**: `viewedByMeTime desc` + + diff --git a/toolkit-docs-generator/curation/hubspot.json b/toolkit-docs-generator/curation/hubspot.json deleted file mode 100644 index e8750dfe2..000000000 --- a/toolkit-docs-generator/curation/hubspot.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/hubspot/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/hubspot/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..dfbe679f4 --- /dev/null +++ b/toolkit-docs-generator/curation/hubspot/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,6 @@ +--- +type: markdown +location: auth +position: after +--- +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 deleted file mode 100644 index 56ad4f34e..000000000 --- a/toolkit-docs-generator/curation/hubspotautomationapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/hubspotautomationapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/hubspotautomationapi/chunks/001-auth.mdx new file mode 100644 index 000000000..3c776f380 --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotautomationapi/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/hubspotcmsapi.json b/toolkit-docs-generator/curation/hubspotcmsapi.json deleted file mode 100644 index ba5475085..000000000 --- a/toolkit-docs-generator/curation/hubspotcmsapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/hubspotcmsapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/hubspotcmsapi/chunks/001-auth.mdx new file mode 100644 index 000000000..c521822c8 --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotcmsapi/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/hubspotconversationsapi.json b/toolkit-docs-generator/curation/hubspotconversationsapi.json deleted file mode 100644 index 8e9d0bd13..000000000 --- a/toolkit-docs-generator/curation/hubspotconversationsapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/hubspotconversationsapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/hubspotconversationsapi/chunks/001-auth.mdx new file mode 100644 index 000000000..1f74510ca --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotconversationsapi/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/hubspotcrmapi.json b/toolkit-docs-generator/curation/hubspotcrmapi.json deleted file mode 100644 index 5bbc51148..000000000 --- a/toolkit-docs-generator/curation/hubspotcrmapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/hubspotcrmapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/hubspotcrmapi/chunks/001-auth.mdx new file mode 100644 index 000000000..6febae813 --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotcrmapi/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/hubspoteventsapi.json b/toolkit-docs-generator/curation/hubspoteventsapi.json deleted file mode 100644 index 44853fd14..000000000 --- a/toolkit-docs-generator/curation/hubspoteventsapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/hubspoteventsapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/hubspoteventsapi/chunks/001-auth.mdx new file mode 100644 index 000000000..c0296e264 --- /dev/null +++ b/toolkit-docs-generator/curation/hubspoteventsapi/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/hubspotmarketingapi.json b/toolkit-docs-generator/curation/hubspotmarketingapi.json deleted file mode 100644 index de540758b..000000000 --- a/toolkit-docs-generator/curation/hubspotmarketingapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/hubspotmarketingapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/hubspotmarketingapi/chunks/001-auth.mdx new file mode 100644 index 000000000..e048b5530 --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotmarketingapi/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/hubspotmeetingsapi.json b/toolkit-docs-generator/curation/hubspotmeetingsapi.json deleted file mode 100644 index e13f1202c..000000000 --- a/toolkit-docs-generator/curation/hubspotmeetingsapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/hubspotmeetingsapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/hubspotmeetingsapi/chunks/001-auth.mdx new file mode 100644 index 000000000..a006e4136 --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotmeetingsapi/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/hubspotusersapi.json b/toolkit-docs-generator/curation/hubspotusersapi.json deleted file mode 100644 index ce2975fd3..000000000 --- a/toolkit-docs-generator/curation/hubspotusersapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/hubspotusersapi/chunks/001-auth.mdx b/toolkit-docs-generator/curation/hubspotusersapi/chunks/001-auth.mdx new file mode 100644 index 000000000..47295b480 --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotusersapi/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/intercomapi.json b/toolkit-docs-generator/curation/intercomapi.json deleted file mode 100644 index 277273497..000000000 --- a/toolkit-docs-generator/curation/intercomapi.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "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/intercomapi/chunks/001-intercom-api-subdomain.mdx b/toolkit-docs-generator/curation/intercomapi/chunks/001-intercom-api-subdomain.mdx new file mode 100644 index 000000000..c4974e50e --- /dev/null +++ b/toolkit-docs-generator/curation/intercomapi/chunks/001-intercom-api-subdomain.mdx @@ -0,0 +1,13 @@ +--- +type: section +location: after_available_tools +position: after +header: "## Intercom API Subdomain" +--- +## Intercom API Subdomain + +The IntercomApi MCP Server requires setting the `INTERCOM_API_SUBDOMAIN` secret in the Arcade Dashboard. The appropriate value depends on the region you are using: + +- For the United States servers, set `INTERCOM_API_SUBDOMAIN` secret to `api` +- For the European servers, set `INTERCOM_API_SUBDOMAIN` secret to `api.eu` +- For the Australian servers, set `INTERCOM_API_SUBDOMAIN` secret to `api.au` diff --git a/toolkit-docs-generator/curation/intercomapi/chunks/002-auth.mdx b/toolkit-docs-generator/curation/intercomapi/chunks/002-auth.mdx new file mode 100644 index 000000000..a407d465b --- /dev/null +++ b/toolkit-docs-generator/curation/intercomapi/chunks/002-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. diff --git a/toolkit-docs-generator/curation/jira.json b/toolkit-docs-generator/curation/jira.json deleted file mode 100644 index 9aa66091c..000000000 --- a/toolkit-docs-generator/curation/jira.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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/jira/chunks/001-description-after-warning.mdx b/toolkit-docs-generator/curation/jira/chunks/001-description-after-warning.mdx new file mode 100644 index 000000000..f4677fd0b --- /dev/null +++ b/toolkit-docs-generator/curation/jira/chunks/001-description-after-warning.mdx @@ -0,0 +1,25 @@ +--- +type: warning +location: description +position: after +--- + + +

+ Handling multiple Atlassian Clouds +

+ +A 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. + +When 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. + +Your 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. + +When 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. + +**_It is the job of the AI Agent or chat application to:_** + +1. 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; +1. 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**. + +
diff --git a/toolkit-docs-generator/curation/jira/pages/environment-variables/page.mdx b/toolkit-docs-generator/curation/jira/pages/environment-variables/page.mdx new file mode 100644 index 000000000..c9965fb69 --- /dev/null +++ b/toolkit-docs-generator/curation/jira/pages/environment-variables/page.mdx @@ -0,0 +1,36 @@ +--- +type: environment-variables +--- +import { Callout } from "nextra/components"; + +# Jira Environment Variables + +### `JIRA_MAX_CONCURRENT_REQUESTS` + +Arcade 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. + +The value must be a numeric string with an integer greater than or equal to 1. + +**Default:** `3` + + +### `JIRA_API_REQUEST_TIMEOUT` + +Controls 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. + +The value must be a numeric string with an integer greater than or equal to 1. + +**Default:** `30` + + +### `JIRA_CACHE_MAX_ITEMS` + + + The caching strategy does not involve caching Jira API responses that go into tool output, but only internal values. + + +The 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. + +The value must be a numeric string with an integer greater than or equal to 1. + +**Default:** `5000` diff --git a/toolkit-docs-generator/curation/linear.json b/toolkit-docs-generator/curation/linear.json deleted file mode 100644 index 580df1537..000000000 --- a/toolkit-docs-generator/curation/linear.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "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/linear/chunks/001-auth.mdx b/toolkit-docs-generator/curation/linear/chunks/001-auth.mdx new file mode 100644 index 000000000..bc89b6f24 --- /dev/null +++ b/toolkit-docs-generator/curation/linear/chunks/001-auth.mdx @@ -0,0 +1,9 @@ +--- +type: markdown +location: custom_section +position: after +header: "## Auth" +--- +## Auth + +The 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. diff --git a/toolkit-docs-generator/curation/linkedin.json b/toolkit-docs-generator/curation/linkedin.json deleted file mode 100644 index 6f0c36a5a..000000000 --- a/toolkit-docs-generator/curation/linkedin.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/linkedin/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/linkedin/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..d7d5acf07 --- /dev/null +++ b/toolkit-docs-generator/curation/linkedin/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,6 @@ +--- +type: markdown +location: auth +position: after +--- +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 deleted file mode 100644 index e7e30650a..000000000 --- a/toolkit-docs-generator/curation/lumaapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/lumaapi/chunks/001-authentication.mdx b/toolkit-docs-generator/curation/lumaapi/chunks/001-authentication.mdx new file mode 100644 index 000000000..38b7833e7 --- /dev/null +++ b/toolkit-docs-generator/curation/lumaapi/chunks/001-authentication.mdx @@ -0,0 +1,26 @@ +--- +type: warning +location: before_available_tools +position: after +header: "## Authentication" +--- +## Authentication + +The 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): + +- `LUMA_API_KEY` + +**How to obtain your credentials:** + +1. Navigate to your [Luma dashboard](https://lu.ma/) +2. Click on your profile icon and go to **Settings** +3. Navigate to **API** or **Developer Settings** +4. Click **Generate API Key** or **Create New Key** +5. Copy the API key and store it securely + + + The Luma API requires a **Luma Plus** subscription. Be careful with your API + key since it provides full access to your Luma account. + + +For more details, see the [Luma API Getting Started guide](https://docs.luma.com/reference/getting-started-with-your-api). diff --git a/toolkit-docs-generator/curation/mailchimpmarketingapi.json b/toolkit-docs-generator/curation/mailchimpmarketingapi.json deleted file mode 100644 index a72ef4656..000000000 --- a/toolkit-docs-generator/curation/mailchimpmarketingapi.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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/mailchimpmarketingapi/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/mailchimpmarketingapi/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..284cb15a7 --- /dev/null +++ b/toolkit-docs-generator/curation/mailchimpmarketingapi/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +--- +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. +The 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). diff --git a/toolkit-docs-generator/curation/microsoftteams.json b/toolkit-docs-generator/curation/microsoftteams.json deleted file mode 100644 index ae80f3074..000000000 --- a/toolkit-docs-generator/curation/microsoftteams.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/microsoftteams/chunks/001-description-after-warning.mdx b/toolkit-docs-generator/curation/microsoftteams/chunks/001-description-after-warning.mdx new file mode 100644 index 000000000..ae2e9f7d4 --- /dev/null +++ b/toolkit-docs-generator/curation/microsoftteams/chunks/001-description-after-warning.mdx @@ -0,0 +1,8 @@ +--- +type: warning +location: description +position: after +--- + + The Microsoft Teams MCP Server requires a Microsoft 365 account. Personal Microsoft accounts are not supported. + diff --git a/toolkit-docs-generator/curation/miroapi.json b/toolkit-docs-generator/curation/miroapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/miroapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 85e9ddbe3..000000000 --- a/toolkit-docs-generator/curation/notiontoolkit.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/notiontoolkit/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/notiontoolkit/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..2fdec3bc0 --- /dev/null +++ b/toolkit-docs-generator/curation/notiontoolkit/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,6 @@ +--- +type: markdown +location: auth +position: after +--- +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 deleted file mode 100644 index 99aff24a9..000000000 --- a/toolkit-docs-generator/curation/pagerduty.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "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/pagerduty/chunks/001-description-after-warning.mdx b/toolkit-docs-generator/curation/pagerduty/chunks/001-description-after-warning.mdx new file mode 100644 index 000000000..8abaa6798 --- /dev/null +++ b/toolkit-docs-generator/curation/pagerduty/chunks/001-description-after-warning.mdx @@ -0,0 +1,11 @@ +--- +type: warning +location: description +position: after +--- + + Arcade supports Classic PagerDuty apps. Select **read-only** access; all tools + in this MCP Server only read data. (Use read/write only if you add custom + write tools.) See [PagerDuty OAuth + functionality](https://developer.pagerduty.com/docs/oauth-functionality). + diff --git a/toolkit-docs-generator/curation/pagerduty/chunks/002-description-after-info.mdx b/toolkit-docs-generator/curation/pagerduty/chunks/002-description-after-info.mdx new file mode 100644 index 000000000..ad4dde867 --- /dev/null +++ b/toolkit-docs-generator/curation/pagerduty/chunks/002-description-after-info.mdx @@ -0,0 +1,9 @@ +--- +type: info +location: description +position: after +--- + + Configure PagerDuty OAuth in the [PagerDuty auth + provider](/references/auth-providers/pagerduty) before using these tools. + diff --git a/toolkit-docs-generator/curation/pagerduty/chunks/003-auth-after-markdown.mdx b/toolkit-docs-generator/curation/pagerduty/chunks/003-auth-after-markdown.mdx new file mode 100644 index 000000000..c0e1a45bd --- /dev/null +++ b/toolkit-docs-generator/curation/pagerduty/chunks/003-auth-after-markdown.mdx @@ -0,0 +1,10 @@ +--- +type: markdown +location: auth +position: after +--- +PagerDuty requires OAuth2. Configure the PagerDuty auth provider and request the scopes shown above per tool. Tokens are passed as Bearer auth: +``` +Authorization: Bearer +``` +See 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 deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/pagerdutyapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 7a2aa6f79..000000000 --- a/toolkit-docs-generator/curation/posthogapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/posthogapi/chunks/001-configuration.mdx b/toolkit-docs-generator/curation/posthogapi/chunks/001-configuration.mdx new file mode 100644 index 000000000..bf6b338a1 --- /dev/null +++ b/toolkit-docs-generator/curation/posthogapi/chunks/001-configuration.mdx @@ -0,0 +1,40 @@ +--- +type: section +location: before_available_tools +position: after +header: "## Configuration" +--- +## Configuration + +**Secrets** + +This 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)) +The PosthogApi MCP Server requires two secrets to authenticate with your PostHog instance: + +### Getting Your PostHog Server URL + +The server URL depends on your PostHog deployment: + +- **PostHog Cloud (US Region)**: `https://us.posthog.com` +- **PostHog Cloud (EU Region)**: `https://eu.posthog.com` +- **Self-Hosted**: Use your instance's base URL (e.g., `https://analytics.yourdomain.com`) + +You can verify your server URL by checking your PostHog account settings or the URL you use to access PostHog. + +### Getting Your Personal API Key + +To generate a PostHog personal API key: + +1. Log in to your PostHog account +2. Click your avatar in the bottom-left corner +3. Select the gear icon to open "Account settings" +4. Navigate to the "Personal API Keys" section +5. Click "+ Create a personal API key" +6. Provide a descriptive label for the key +7. Select the necessary scopes (choose only the scopes required for your use case) +8. Click "Create key" +9. **Copy and securely store the key immediately** - it won't be shown again + +For more details on authentication and API usage, refer to the [PostHog API documentation](https://posthog.com/docs/api). + +Once 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). diff --git a/toolkit-docs-generator/curation/pylon.json b/toolkit-docs-generator/curation/pylon.json deleted file mode 100644 index 8d9624105..000000000 --- a/toolkit-docs-generator/curation/pylon.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "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/pylon/chunks/001-description-after-warning.mdx b/toolkit-docs-generator/curation/pylon/chunks/001-description-after-warning.mdx new file mode 100644 index 000000000..27ec8fee2 --- /dev/null +++ b/toolkit-docs-generator/curation/pylon/chunks/001-description-after-warning.mdx @@ -0,0 +1,9 @@ +--- +type: warning +location: description +position: after +--- + + Pylon API tokens are admin-scoped and created in Pylon by an org admin. Store + the token as `PYLON_API_TOKEN` in Arcade secrets. There is no user OAuth. + diff --git a/toolkit-docs-generator/curation/pylon/chunks/002-auth-after-markdown.mdx b/toolkit-docs-generator/curation/pylon/chunks/002-auth-after-markdown.mdx new file mode 100644 index 000000000..eb6b5104f --- /dev/null +++ b/toolkit-docs-generator/curation/pylon/chunks/002-auth-after-markdown.mdx @@ -0,0 +1,16 @@ +--- +type: markdown +location: auth +position: after +--- +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. +**Auth header** +``` +Authorization: Bearer +``` + + Pylon tokens are generated by admins in the Pylon UI and grant org-level + access. Rotate tokens regularly and scope storage to your Arcade project’s + secrets. + +Refer 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 deleted file mode 100644 index ed7ff8a8e..000000000 --- a/toolkit-docs-generator/curation/reddit.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/reddit/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/reddit/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..a1988ff20 --- /dev/null +++ b/toolkit-docs-generator/curation/reddit/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,6 @@ +--- +type: markdown +location: auth +position: after +--- +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 deleted file mode 100644 index 9150d89f6..000000000 --- a/toolkit-docs-generator/curation/slack.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/slack/chunks/001-header-after-markdown.mdx b/toolkit-docs-generator/curation/slack/chunks/001-header-after-markdown.mdx new file mode 100644 index 000000000..798a7b983 --- /dev/null +++ b/toolkit-docs-generator/curation/slack/chunks/001-header-after-markdown.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: header +position: after +--- + +Managing 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. + diff --git a/toolkit-docs-generator/curation/slackapi.json b/toolkit-docs-generator/curation/slackapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/slackapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index bbadfa7df..000000000 --- a/toolkit-docs-generator/curation/spotify.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/spotify/chunks/001-description-after-warning.mdx b/toolkit-docs-generator/curation/spotify/chunks/001-description-after-warning.mdx new file mode 100644 index 000000000..753a45512 --- /dev/null +++ b/toolkit-docs-generator/curation/spotify/chunks/001-description-after-warning.mdx @@ -0,0 +1,9 @@ +--- +type: warning +location: description +position: after +--- + + This Toolkit is not available in Arcade Cloud. You can use these tools with a + [self-hosted](/guides/deployment-hosting/configure-engine) instance of Arcade. + diff --git a/toolkit-docs-generator/curation/squareupapi.json b/toolkit-docs-generator/curation/squareupapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/squareupapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index fc71839a8..000000000 --- a/toolkit-docs-generator/curation/stripe.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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/stripe/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/stripe/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..231aee28b --- /dev/null +++ b/toolkit-docs-generator/curation/stripe/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +--- +The Arcade Stripe MCP Server uses the [Stripe Agent Toolkit](https://github.com/stripe/agent-toolkit) to interact with the Stripe API. +- **Required Secret:** + - `STRIPE_SECRET_KEY`: Your Stripe API key. diff --git a/toolkit-docs-generator/curation/stripeapi.json b/toolkit-docs-generator/curation/stripeapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/stripeapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 11e5fa653..000000000 --- a/toolkit-docs-generator/curation/ticktickapi.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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/ticktickapi/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/ticktickapi/chunks/001-auth-after-markdown.mdx new file mode 100644 index 000000000..d7380f90a --- /dev/null +++ b/toolkit-docs-generator/curation/ticktickapi/chunks/001-auth-after-markdown.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +--- +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. +Learn how to configure the TickTick auth provider in the [TickTick auth provider documentation](/references/auth-providers/ticktick). diff --git a/toolkit-docs-generator/curation/trelloapi.json b/toolkit-docs-generator/curation/trelloapi.json deleted file mode 100644 index 6dea19427..000000000 --- a/toolkit-docs-generator/curation/trelloapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/trelloapi/chunks/001-authentication.mdx b/toolkit-docs-generator/curation/trelloapi/chunks/001-authentication.mdx new file mode 100644 index 000000000..3686d320a --- /dev/null +++ b/toolkit-docs-generator/curation/trelloapi/chunks/001-authentication.mdx @@ -0,0 +1,27 @@ +--- +type: section +location: custom_section +position: after +header: "## Authentication" +--- +## Authentication + +The Arcade Trello API MCP Server requires two environment variables to authenticate with the Trello API: + +- `TRELLO_API_KEY` +- `TRELLO_API_TOKEN` + +**How to obtain your credentials:** + +1. Log in to your [Trello account](https://trello.com/) +2. Navigate to the [Power-Ups Admin Portal](https://trello.com/power-ups/admin) +3. Click on "New" to create a new Power-Up or select an existing one +4. In your Power-Up settings, go to the **API Key** tab +5. Your **API Key** will be displayed +6. Click on "Token" link to generate a **Token** (this will require authorization) +7. Authorize the token with the required scopes +8. Copy both the API Key and Token for use in your configuration + +Alternatively, you can directly access your API key at: [https://trello.com/app-key](https://trello.com/app-key) + +For more details, see the [Trello API Authentication documentation](https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/). diff --git a/toolkit-docs-generator/curation/vercelapi.json b/toolkit-docs-generator/curation/vercelapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/vercelapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 6107c8705..000000000 --- a/toolkit-docs-generator/curation/weaviateapi.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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/weaviateapi/chunks/001-authentication.mdx b/toolkit-docs-generator/curation/weaviateapi/chunks/001-authentication.mdx new file mode 100644 index 000000000..e4747faa8 --- /dev/null +++ b/toolkit-docs-generator/curation/weaviateapi/chunks/001-authentication.mdx @@ -0,0 +1,25 @@ +--- +type: section +location: before_available_tools +position: after +header: "## Authentication" +--- +## Authentication + +The Arcade Weaviate API MCP Server requires two environment variables to authenticate with your Weaviate instance: + +- `WEAVIATE_API_KEY` +- `WEAVIATE_SERVER_URL` + +**How to obtain your credentials:** + +1. Log in to your [Weaviate Console](https://console.weaviate.cloud/) +2. Select your Weaviate cluster +3. Navigate to **Details** or **API Keys** section +4. Click **Create API Key** or use an existing key +5. Copy your **API Key** +6. Copy your **Cluster URL** (this is your server URL and must include `https://`) + +**Note:** The `WEAVIATE_SERVER_URL` must include the full URL with the `https://` protocol (e.g., `https://your-cluster.weaviate.network`). + +For more details, see the [Weaviate Authentication documentation](https://weaviate.io/developers/weaviate/configuration/authentication). diff --git a/toolkit-docs-generator/curation/xeroapi.json b/toolkit-docs-generator/curation/xeroapi.json deleted file mode 100644 index 36bb65a2d..000000000 --- a/toolkit-docs-generator/curation/xeroapi.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index cd269b4f0..000000000 --- a/toolkit-docs-generator/curation/zohobooksapi.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "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/zohobooksapi/chunks/001-secrets.mdx b/toolkit-docs-generator/curation/zohobooksapi/chunks/001-secrets.mdx new file mode 100644 index 000000000..fb7936777 --- /dev/null +++ b/toolkit-docs-generator/curation/zohobooksapi/chunks/001-secrets.mdx @@ -0,0 +1,39 @@ +--- +type: section +location: custom_section +position: after +header: "## Secrets" +--- +## Secrets + +This MCP Server requires the `ZOHO_SERVER_URL` secret to be configured. Learn how to [configure secrets](/guides/create-tools/tool-basics/create-tool-secrets). + +### Getting your Zoho Server URL + +The 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. + +Your Zoho Server URL depends on which data center your account is registered in: + +| Data Center | Server URL | +| ----------- | --------------------------- | +| US | `https://books.zoho.com` | +| EU | `https://books.zoho.eu` | +| India | `https://books.zoho.in` | +| Australia | `https://books.zoho.com.au` | +| China | `https://books.zoho.com.cn` | + +To determine which data center your account uses: + +1. Log in to your Zoho Books account +2. Look at the URL in your browser's address bar +3. The domain (`.com`, `.eu`, `.in`, `.com.au`, or `.com.cn`) indicates your data center + +For example, if you access Zoho Books at `https://books.zoho.eu`, your server URL is `https://books.zoho.eu`. + +The server URL is used as the base for all API requests. For example, when retrieving invoices, the full URL would be constructed as: + +``` +{zoho_server_url}/api/v3/invoices?organization_id=... +``` + +Which would become `https://books.zoho.com/api/v3/invoices?organization_id=...` for US accounts. diff --git a/toolkit-docs-generator/curation/zohobooksapi/chunks/002-auth.mdx b/toolkit-docs-generator/curation/zohobooksapi/chunks/002-auth.mdx new file mode 100644 index 000000000..da1da6c50 --- /dev/null +++ b/toolkit-docs-generator/curation/zohobooksapi/chunks/002-auth.mdx @@ -0,0 +1,8 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +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. +Learn how to configure the Zoho auth provider in the [Zoho auth provider documentation](/references/auth-providers/zoho). diff --git a/toolkit-docs-generator/curation/zoom.json b/toolkit-docs-generator/curation/zoom.json deleted file mode 100644 index 0683acca2..000000000 --- a/toolkit-docs-generator/curation/zoom.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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/curation/zoom/chunks/001-auth.mdx b/toolkit-docs-generator/curation/zoom/chunks/001-auth.mdx new file mode 100644 index 000000000..62c9f438f --- /dev/null +++ b/toolkit-docs-generator/curation/zoom/chunks/001-auth.mdx @@ -0,0 +1,7 @@ +--- +type: markdown +location: auth +position: after +header: "## Auth" +--- +The Arcade Zoom MCP Server uses the [Zoom auth provider](/references/auth-providers/zoom) to connect to users' Zoom accounts. diff --git a/toolkit-docs-generator/curation/zoom/pages/install/page.mdx b/toolkit-docs-generator/curation/zoom/pages/install/page.mdx new file mode 100644 index 000000000..c02dbf6a6 --- /dev/null +++ b/toolkit-docs-generator/curation/zoom/pages/install/page.mdx @@ -0,0 +1,111 @@ +--- +type: install +--- +# Arcade for Zoom + +import { Steps, Callout } from "nextra/components"; +import { SignupLink } from "@/app/_components/analytics"; +import { ZoomAuthLink } from "./zoom-auth-link"; + +## Integrate Arcade with your Zoom account + +Arcade 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. + +You can leverage this app in Arcade's Playground when you log in to the Arcade Dashboard, or in your own applications. + +While 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. + +Arcade's Zoom app brings Arcade's powerful AI tool-calling capabilities to your meeting management. The Arcade app for Zoom can: + +- List your upcoming meetings within the next 24 hours +- Retrieve meeting invitation details for specific meetings +- Find the participants and/or registrants for a specific meeting +- and more! + +For more details on what tools are available and what scopes they require, see the [Zoom MCP Server documentation](/resources/integrations/social-communication/zoom). + + + The Arcade Zoom app requires an active Arcade account. If you don't have one + yet,{" "} + sign up for free + . + + +## How it works + + + +### Start using Arcade's Zoom tools + +Use Arcade's [tools for Zoom](/resources/integrations/social-communication/zoom) to: + +- List your upcoming meetings +- Get meeting invitation details +- Find meeting participants and registrants +- and more! + +Try 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. + + + When using LLMs with Zoom, responses may sometimes contain inaccuracies. + Always review AI-generated content before taking action. + + + + +## Support and troubleshooting + +If you encounter any issues connecting Arcade to your Zoom account: + +1. Verify you've granted all required permissions during authorization +2. Ensure your Zoom account is active and in good standing +3. Check that you're using a compatible browser (Chrome, Firefox, Safari, or Edge) +4. Clear your browser cache and cookies, then try again + +### Adding the Arcade Zoom app to your Zoom account + +If 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. + + + + + You'll need to have a Zoom account with appropriate permissions to allow + Arcade to access your Zoom data. + + +### Authorize the requested permissions + +When connecting Arcade to your Zoom account, depending on which Arcade tools you'll be using, you'll be asked to authorize specific permissions: + +- **user:read:user** - Allows Arcade to access basic profile information +- **user:read:email** - Enables Arcade to access your email address +- **meeting:read:meetings** - Enables Arcade to list your upcoming meetings +- **meeting:read:invitation** - Enables Arcade to read meeting invitation details + +These permissions ensure Arcade can perform the necessary functions while protecting your privacy and security. + +### Removing the Arcade Zoom app + +To 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. + +Arcade 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). + +## Privacy and security + +Arcade takes the security of your Zoom data seriously: + +- We only request the minimum permissions needed for our tools to function +- Your Zoom credentials are never stored on our servers +- All communication between Arcade and Zoom is encrypted +- You can revoke Arcade's access to your Zoom account at any time through your [Zoom App Marketplace](https://marketplace.zoom.us/user/installed) + +## Next steps + +The 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. + +## Need help? + +If you have any questions or need assistance: + +- Check our [Zoom MCP Server documentation](/resources/integrations/social-communication/zoom) +- [Contact our support team](/resources/contact-us) diff --git a/toolkit-docs-generator/scripts/extract-curation.ts b/toolkit-docs-generator/scripts/extract-curation.ts index bacf3125c..599886d05 100644 --- a/toolkit-docs-generator/scripts/extract-curation.ts +++ b/toolkit-docs-generator/scripts/extract-curation.ts @@ -1,134 +1,148 @@ #!/usr/bin/env npx tsx /** - * One-time extraction of hand-authored prose out of the committed toolkit - * artifacts and into per-toolkit curation files. + * Extract hand-authored prose from committed toolkit artifacts into Markdown + * and MDX source 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. + * Each documentation chunk becomes one file under + * `curation//chunks/`. Rich subpages keep their generated relative + * path below `curation//pages/`. Structured placement metadata lives + * in frontmatter; the document body is the authored content. */ import { mkdir, readdir, readFile, writeFile } from "fs/promises"; -import { dirname, join } from "path"; +import { basename, dirname, isAbsolute, join } from "path"; import { fileURLToPath } from "url"; +import { stringify as stringifyYaml } from "yaml"; +import type { DocumentationChunk, ToolkitSubPage } from "../src/types/index"; -/** - * 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[]; + subPages?: ToolkitSubPage[]; tools?: { name: string; documentationChunks?: DocumentationChunk[] }[]; }; -type CurationFile = { - documentationChunks?: DocumentationChunk[]; - customImports?: string[]; - subPages?: SubPage[]; - toolChunks?: Record; +const slugify = (value: string): string => + value + .replace(/^#+\s*/, "") + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, "") + .slice(0, 64); + +const frontmatter = (data: Record): string => + `---\n${stringifyYaml(data).trimEnd()}\n---\n`; + +const chunkFileName = (chunk: DocumentationChunk, index: number): string => { + const label = slugify( + chunk.header ?? `${chunk.location}-${chunk.position}-${chunk.type}` + ); + return `${String(index + 1).padStart(3, "0")}-${label || "section"}.mdx`; }; -const isNonEmptyArray = (value: T[] | undefined): value is T[] => - Array.isArray(value) && value.length > 0; +const writeChunk = async ( + toolkitId: string, + chunk: DocumentationChunk, + index: number, + tool?: string +): Promise => { + const { content, ...metadata } = chunk; + const chunksDir = join(CURATION_DIR, toolkitId, "chunks"); + await mkdir(chunksDir, { recursive: true }); + await writeFile( + join(chunksDir, chunkFileName(chunk, index)), + `${frontmatter({ ...metadata, ...(tool ? { tool } : {}) })}${content}\n`, + "utf-8" + ); +}; -/** - * 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; - } +const assertSafeSubPagePath = (relativePath: string): void => { + if ( + isAbsolute(relativePath) || + relativePath.split(/[\\/]/).some((part) => part === ".." || part === "") + ) { + throw new Error(`Unsafe toolkit subpage path: ${relativePath}`); } - return toolChunks; }; -const buildCurationFile = (toolkit: ToolkitArtifact): CurationFile | null => { - const curation: CurationFile = {}; - if (isNonEmptyArray(toolkit.documentationChunks)) { - curation.documentationChunks = toolkit.documentationChunks; +const writeSubPage = async ( + toolkitId: string, + subPage: ToolkitSubPage +): Promise => { + if (typeof subPage === "string") { + throw new Error( + `Cannot extract legacy subpage without content: ${toolkitId}/${subPage}` + ); } - if (isNonEmptyArray(toolkit.customImports)) { - curation.customImports = toolkit.customImports; + assertSafeSubPagePath(subPage.relativePath); + const pagePath = join(CURATION_DIR, toolkitId, "pages", subPage.relativePath); + await mkdir(dirname(pagePath), { recursive: true }); + await writeFile( + pagePath, + `${frontmatter({ type: subPage.type })}${subPage.content}\n`, + "utf-8" + ); +}; + +const extractToolkit = async ( + fileName: string, + toolkit: ToolkitArtifact +): Promise<{ chunks: number; subPages: number }> => { + const toolkitId = basename(fileName, ".json"); + let chunkIndex = 0; + for (const chunk of toolkit.documentationChunks ?? []) { + await writeChunk(toolkitId, chunk, chunkIndex); + chunkIndex += 1; } - if (isNonEmptyArray(toolkit.subPages)) { - curation.subPages = toolkit.subPages; + for (const tool of toolkit.tools ?? []) { + for (const chunk of tool.documentationChunks ?? []) { + await writeChunk( + toolkitId, + chunk, + chunkIndex, + `${toolkit.id}.${tool.name}` + ); + chunkIndex += 1; + } } - const toolChunks = extractToolChunks(toolkit); - if (Object.keys(toolChunks).length > 0) { - curation.toolChunks = toolChunks; + + for (const subPage of toolkit.subPages ?? []) { + await writeSubPage(toolkitId, subPage); } - return Object.keys(curation).length > 0 ? curation : null; + return { + chunks: chunkIndex, + subPages: toolkit.subPages?.length ?? 0, + }; }; 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 toolkitCount = 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; + const extracted = await extractToolkit(file, toolkit); + if (extracted.chunks > 0 || extracted.subPages > 0) { + toolkitCount += 1; } - subPageCount += curation.subPages?.length ?? 0; - importCount += curation.customImports?.length ?? 0; + chunkCount += extracted.chunks; + subPageCount += extracted.subPages; } - console.log(`Wrote ${written} curation files to ${CURATION_DIR}/`); + console.log(`Wrote Markdown curation for ${toolkitCount} toolkits.`); console.log( - ` documentationChunks: ${chunkCount}, subPages: ${subPageCount}, customImports: ${importCount}` + ` documentation chunks: ${chunkCount}, subpages: ${subPageCount}` ); } diff --git a/toolkit-docs-generator/src/cli/index.ts b/toolkit-docs-generator/src/cli/index.ts index 1ac9b25a9..92d541c8c 100644 --- a/toolkit-docs-generator/src/cli/index.ts +++ b/toolkit-docs-generator/src/cli/index.ts @@ -43,9 +43,9 @@ import { assertRequireCompleteMetadata, createDataMerger, } from "../merger/data-merger"; -import { createCustomSectionsFileSource } from "../sources/custom-sections-file"; import { createDesignSystemMetadataSource } from "../sources/design-system-metadata"; import { createEmptyCustomSectionsSource } from "../sources/in-memory"; +import { createMarkdownCurationSource } from "../sources/markdown-curation"; import { createMockMetadataSource } from "../sources/mock-metadata"; import { createDesignSystemProviderIdResolver } from "../sources/oauth-provider-resolver"; import { @@ -915,7 +915,7 @@ program .option("--no-verify-output", "Skip output verification") .option( "--custom-sections ", - "Path to custom sections: a directory of per-toolkit files (curation/) or a single JSON file" + "Path to the authoritative Markdown/MDX curation directory" ) .option( "--resume", @@ -1280,7 +1280,7 @@ program // Custom sections source const customSectionsSource = options.customSections - ? createCustomSectionsFileSource(options.customSections) + ? createMarkdownCurationSource(options.customSections) : createEmptyCustomSectionsSource(); // Build provider ID resolver from design system OAuth catalogue @@ -2028,7 +2028,7 @@ program .option("--no-verify-output", "Skip output verification") .option( "--custom-sections ", - "Path to custom sections: a directory of per-toolkit files (curation/) or a single JSON file" + "Path to the authoritative Markdown/MDX curation directory" ) .option( "--resume", @@ -2269,7 +2269,7 @@ program } const customSectionsSource = options.customSections - ? createCustomSectionsFileSource(options.customSections) + ? createMarkdownCurationSource(options.customSections) : createEmptyCustomSectionsSource(); // Build provider ID resolver from design system OAuth catalogue @@ -2745,7 +2745,7 @@ program ) .option( "--custom-sections ", - "Path to custom sections: a directory of per-toolkit files (curation/) or a single JSON file (defaults to ./curation when present)" + "Path to the authoritative Markdown/MDX curation directory (defaults to ./curation when present)" ) .option("--verbose", "Show detailed tool-level changes", false) .option("--json", "Output as JSON", false) @@ -2831,7 +2831,7 @@ program options.customSections ); const customSectionsSource = customSectionsPath - ? createCustomSectionsFileSource(customSectionsPath) + ? createMarkdownCurationSource(customSectionsPath) : createEmptyCustomSectionsSource(); const curationChangedToolkitIds = customSectionsPath ? getChangedToolkitIdsFromCustomSections( diff --git a/toolkit-docs-generator/src/diff/custom-sections-diff.ts b/toolkit-docs-generator/src/diff/custom-sections-diff.ts index c3dae1e51..4af358ea9 100644 --- a/toolkit-docs-generator/src/diff/custom-sections-diff.ts +++ b/toolkit-docs-generator/src/diff/custom-sections-diff.ts @@ -28,20 +28,28 @@ const emptyCustomSections = (): CustomSections => ({ }); /** - * Return toolkit ids whose curation files differ from the prose embedded in - * the previous artifact. - * - * Only toolkits with a curation file are considered. A missing file means - * "not curated" and matches merge semantics (carry-forward), so those - * toolkits are not flagged here. + * Return toolkit ids whose authoritative curation differs from the prose + * embedded in the previous artifact. Missing current entries compare as + * empty, so deleting the final source file remains observable. */ 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, + ]) + ); const changed: string[] = []; - for (const [toolkitId, currentSections] of Object.entries(current)) { + for (const toolkitId of ids) { + const currentSections = currentById.get(toolkitId) ?? emptyCustomSections(); const previousToolkit = findPreviousToolkit(toolkitId, previous); const previousSections = previousToolkit ? customSectionsFromToolkit(previousToolkit) @@ -54,5 +62,5 @@ export const getChangedToolkitIdsFromCustomSections = ( } } - return changed; + return changed.sort(); }; diff --git a/toolkit-docs-generator/src/merger/data-merger.ts b/toolkit-docs-generator/src/merger/data-merger.ts index 93ecf71b2..8da821546 100644 --- a/toolkit-docs-generator/src/merger/data-merger.ts +++ b/toolkit-docs-generator/src/merger/data-merger.ts @@ -805,6 +805,47 @@ const buildMergedToolkit = (options: { }; }; +const assertKnownToolChunkTargets = ( + tools: readonly Pick[], + customSections: CustomSections | null +): void => { + if (customSections === null) { + return; + } + + const knownToolNames = new Set(tools.map((tool) => tool.name)); + const unknownToolNames = Object.keys(customSections.toolChunks) + .filter((toolName) => !knownToolNames.has(toolName)) + .sort(); + if (unknownToolNames.length > 0) { + throw new Error( + `Curation targets unknown tool(s): ${unknownToolNames.join(", ")}` + ); + } +}; + +/** Overlay current authored curation without disturbing generated enrichment. */ +export const applyCustomSectionsToToolkit = ( + toolkit: MergedToolkit, + customSections: CustomSections | null +): MergedToolkit => { + if (customSections === null) { + return toolkit; + } + assertKnownToolChunkTargets(toolkit.tools, customSections); + + return { + ...toolkit, + documentationChunks: customSections.documentationChunks, + customImports: customSections.customImports, + subPages: customSections.subPages, + tools: toolkit.tools.map((tool) => ({ + ...tool, + documentationChunks: customSections.toolChunks[tool.name] ?? [], + })), + }; +}; + /** * Transform a tool definition into a merged tool */ @@ -920,6 +961,8 @@ export const mergeToolkit = async ( const warnings: string[] = []; const failedTools: FailedTool[] = []; + assertKnownToolChunkTargets(tools, customSections); + appendMergeWarnings(warnings, toolkitId, tools, metadata); const version = getToolkitVersion(tools); @@ -1078,11 +1121,12 @@ export class DataMerger { private buildMergeErrorResult( toolkitId: string, message: string, - previousToolkit?: MergedToolkit + previousToolkit: MergedToolkit | undefined, + customSections: CustomSections | null ): MergeResult { if (previousToolkit) { return { - toolkit: previousToolkit, + toolkit: applyCustomSectionsToToolkit(previousToolkit, customSections), warnings: [`Error processing toolkit: ${message}`], failedTools: [], error: message, @@ -1129,7 +1173,8 @@ export class DataMerger { private async recoverMissingMetadata( toolkitId: string, - toolkitData: ToolkitData + toolkitData: ToolkitData, + customSections: CustomSections | null ): Promise { if (!this.preserveLastKnownGood || toolkitData.metadata !== null) { return; @@ -1139,7 +1184,8 @@ export class DataMerger { const result = this.buildMergeErrorResult( toolkitId, "missing design-system metadata", - previousToolkit + previousToolkit, + customSections ); if (this.onToolkitComplete && previousToolkit) { await this.onToolkitComplete(result); @@ -1151,17 +1197,20 @@ export class DataMerger { toolkitId: string, toolkitData: ToolkitData ): Promise { + // Curation is configuration. Parse it outside the recoverable merge path + // so invalid source cannot silently preserve stale generated prose. + const customSections = + await this.customSectionsSource.getCustomSections(toolkitId); try { const recovered = await this.recoverMissingMetadata( toolkitId, - toolkitData + toolkitData, + customSections ); if (recovered) { return recovered; } - const customSections = - await this.customSectionsSource.getCustomSections(toolkitId); const previousToolkit = this.getPreviousToolkit(toolkitId); const result = await mergeToolkit( toolkitId, @@ -1178,7 +1227,11 @@ export class DataMerger { } ); await this.maybeGenerateSummary(result, previousToolkit); - await this.enforceSecretCoherence(result, previousToolkit); + await this.enforceSecretCoherence( + result, + previousToolkit, + customSections !== null + ); // Write immediately if callback provided (incremental mode) if (this.onToolkitComplete) { @@ -1197,7 +1250,8 @@ export class DataMerger { const result = this.buildMergeErrorResult( toolkitId, message, - previousToolkit + previousToolkit, + customSections ); if (this.onToolkitComplete && previousToolkit) { await this.onToolkitComplete(result); @@ -1287,7 +1341,8 @@ export class DataMerger { private async enforceSecretCoherence( result: MergeResult, - previousToolkit?: MergedToolkit + previousToolkit: MergedToolkit | undefined, + customSectionsAuthoritative: boolean ): Promise { if (this.skipSecretCoherence) { // --skip-secret-coherence disables the entire step: no scan, no @@ -1311,7 +1366,11 @@ export class DataMerger { // re-detected against the edited summary. If cleanup accidentally // dropped a passage that incidentally mentioned a current secret, // the fresh scan notices and the editor restores it. - await this.applyStaleRefCleanup(result, issues); + await this.applyStaleRefCleanup( + result, + issues, + customSectionsAuthoritative + ); const postCleanupIssues = detectSecretCoherenceIssues( result.toolkit, previousToolkit @@ -1344,13 +1403,16 @@ export class DataMerger { private async applyStaleRefCleanup( result: MergeResult, - issues: SecretCoherenceIssues + issues: SecretCoherenceIssues, + customSectionsAuthoritative: boolean ): Promise { const editor = this.secretEditGenerator; if (!editor) { return; } - const targets = groupStaleRefsByTarget(issues.staleReferences); + const targets = groupStaleRefsByTarget(issues.staleReferences).filter( + (target) => !customSectionsAuthoritative || target.kind === "summary" + ); if (targets.length === 0) { return; } @@ -1429,15 +1491,17 @@ export class DataMerger { version ); - const recovered = await this.recoverMissingMetadata(toolkitId, toolkitData); + const customSections = + await this.customSectionsSource.getCustomSections(toolkitId); + const recovered = await this.recoverMissingMetadata( + toolkitId, + toolkitData, + customSections + ); if (recovered) { return recovered; } - // Fetch custom sections - const customSections = - await this.customSectionsSource.getCustomSections(toolkitId); - const previousToolkit = this.getPreviousToolkit(toolkitId); const result = await mergeToolkit( toolkitId, @@ -1454,7 +1518,11 @@ export class DataMerger { } ); await this.maybeGenerateSummary(result, previousToolkit); - await this.enforceSecretCoherence(result, previousToolkit); + await this.enforceSecretCoherence( + result, + previousToolkit, + customSections !== null + ); return result; } diff --git a/toolkit-docs-generator/src/sources/custom-sections-file.ts b/toolkit-docs-generator/src/sources/custom-sections-file.ts deleted file mode 100644 index 919f9fb68..000000000 --- a/toolkit-docs-generator/src/sources/custom-sections-file.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Custom Sections File Source - * - * 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. An empty file (`{}`) means the toolkit has no hand-authored - * prose — the merger clears any carry-forward from a previous artifact. - * - A single JSON file that maps toolkit id to `CustomSections`. This is the - * legacy shape produced by the one-time MDX extraction. - */ -import { access, readdir, readFile, stat } from "fs/promises"; -import { basename, join } from "path"; -import { z } from "zod"; -import type { CustomSections } from "../types/index"; -import { CustomSectionsSchema } from "../types/index"; -import { normalizeId } from "../utils/fp"; -import type { ICustomSectionsSource } from "./interfaces"; - -// ============================================================================ -// File Schema -// ============================================================================ - -/** A single JSON file mapping toolkit id -> custom sections (legacy layout). */ -const CustomSectionsFileSchema = z.record(z.string(), CustomSectionsSchema); - -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 parseJsonWithSchema = ( - content: string, - filePath: string, - schema: z.ZodType -): T => { - let parsedJson: unknown; - try { - parsedJson = JSON.parse(content) as unknown; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new Error( - `Custom sections file is not valid JSON (${filePath}): ${message}` - ); - } - - const parsed = schema.safeParse(parsedJson); - if (!parsed.success) { - throw new Error( - `Custom sections file has invalid schema (${filePath}): ${parsed.error.message}` - ); - } - - return parsed.data; -}; - -/** - * Source that loads custom documentation sections from disk. - */ -export class CustomSectionsFileSource implements ICustomSectionsSource { - private readonly filePath: string; - private cachedData: CustomSectionsData | null = null; - - constructor(config: CustomSectionsFileConfig) { - this.filePath = config.filePath; - } - - 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 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") { - // Path doesn't exist - return empty data - this.cachedData = {}; - return this.cachedData; - } - throw error; - } - } - - async getCustomSections(toolkitId: string): Promise { - const data = await this.loadData(); - - // Try exact match - if (data[toolkitId]) { - return data[toolkitId]; - } - - // Try normalized match - const normalizedId = normalizeId(toolkitId); - const entry = Object.entries(data).find( - ([key]) => normalizeId(key) === normalizedId - ); - - return entry ? entry[1] : null; - } - - async getAllCustomSections(): Promise< - Readonly> - > { - return this.loadData(); - } -} - -// ============================================================================ -// Factory -// ============================================================================ - -export const createCustomSectionsFileSource = ( - filePath: string -): ICustomSectionsSource => new CustomSectionsFileSource({ filePath }); diff --git a/toolkit-docs-generator/src/sources/index.ts b/toolkit-docs-generator/src/sources/index.ts index 9354aa112..7b9c7046d 100644 --- a/toolkit-docs-generator/src/sources/index.ts +++ b/toolkit-docs-generator/src/sources/index.ts @@ -4,11 +4,11 @@ export * from "./arcade-api"; export * from "./arcade-api-types"; -export * from "./custom-sections-file"; export * from "./design-system-metadata"; export * from "./engine-api"; export * from "./in-memory"; export * from "./interfaces"; +export * from "./markdown-curation"; export * from "./mock-engine-api"; export * from "./mock-metadata"; export * from "./oauth-provider-resolver"; diff --git a/toolkit-docs-generator/src/sources/interfaces.ts b/toolkit-docs-generator/src/sources/interfaces.ts index 82662645d..49991117e 100644 --- a/toolkit-docs-generator/src/sources/interfaces.ts +++ b/toolkit-docs-generator/src/sources/interfaces.ts @@ -13,7 +13,7 @@ import type { CustomSections } from "../types/index"; * Interface for fetching custom documentation sections * * Implementations: - * - CustomSectionsFileSource: Loads from extracted JSON file + * - MarkdownCurationSource: Compiles hand-authored Markdown and MDX * - EmptyCustomSectionsSource: Returns empty sections (for new toolkits) */ export interface ICustomSectionsSource { diff --git a/toolkit-docs-generator/src/sources/markdown-curation.ts b/toolkit-docs-generator/src/sources/markdown-curation.ts new file mode 100644 index 000000000..809be25c0 --- /dev/null +++ b/toolkit-docs-generator/src/sources/markdown-curation.ts @@ -0,0 +1,352 @@ +/** + * Compile hand-authored Markdown and MDX into the generator's structured + * custom-sections contract. + * + * A configured curation root is authoritative for every toolkit. Missing + * toolkit directories therefore resolve to empty custom sections instead of + * falling back to prose embedded in a previous generated artifact. + */ +import { compile } from "@mdx-js/mdx"; +import { readdir, readFile, stat } from "fs/promises"; +import { join, relative, sep } from "path"; +import { parse as parseYaml } from "yaml"; +import { z } from "zod"; +import { + type CustomSections, + CustomSectionsSchema, + type DocumentationChunk, + DocumentationChunkSchema, + type ToolkitSubPage, +} from "../types/index"; +import { normalizeId } from "../utils/fp"; +import type { ICustomSectionsSource } from "./interfaces"; + +const FRONTMATTER_PATTERN = + /^---\r?\n(?[\s\S]*?)\r?\n---(?:\r?\n|$)(?[\s\S]*)$/; +const MARKDOWN_EXTENSIONS = new Set([".md", ".mdx"]); + +const ChunkFrontmatterSchema = DocumentationChunkSchema.omit({ + content: true, +}) + .extend({ + /** Optional fully qualified tool name for a tool-level chunk. */ + tool: z.string().min(1).optional(), + }) + .strict(); + +const PageFrontmatterSchema = z + .object({ + type: z.string().min(1), + }) + .strict(); + +type MarkdownDocument = { + body: string; + frontmatter: unknown; +}; + +type CompiledChunk = { + chunk: DocumentationChunk; + sourcePath: string; + toolName?: string; +}; + +const emptyCustomSections = (): CustomSections => + CustomSectionsSchema.parse({}); + +const extensionOf = (fileName: string): string => { + const dot = fileName.lastIndexOf("."); + return dot === -1 ? "" : fileName.slice(dot).toLowerCase(); +}; + +const isMarkdownFile = (fileName: string): boolean => + MARKDOWN_EXTENSIONS.has(extensionOf(fileName)); + +const parseMarkdownDocument = ( + source: string, + sourcePath: string +): MarkdownDocument => { + const match = source.match(FRONTMATTER_PATTERN); + if (!(match?.groups?.frontmatter && match.groups.body !== undefined)) { + throw new Error( + `Curation document must start with YAML frontmatter (${sourcePath})` + ); + } + + let frontmatter: unknown; + try { + frontmatter = parseYaml(match.groups.frontmatter); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error( + `Curation frontmatter is invalid (${sourcePath}): ${message}` + ); + } + + const body = match.groups.body.replaceAll("\r\n", "\n").replace(/\n$/, ""); + if (body.trim().length === 0) { + throw new Error(`Curation document body is empty (${sourcePath})`); + } + + return { body, frontmatter }; +}; + +const validateMdx = async (body: string, sourcePath: string): Promise => { + try { + await compile(body, { development: false }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error( + `Curation document has invalid MDX (${sourcePath}): ${message}` + ); + } +}; + +const parseChunk = async ( + sourcePath: string, + toolkitId: string +): Promise => { + const document = parseMarkdownDocument( + await readFile(sourcePath, "utf-8"), + sourcePath + ); + const parsed = ChunkFrontmatterSchema.safeParse(document.frontmatter); + if (!parsed.success) { + throw new Error( + `Curation chunk frontmatter has invalid schema (${sourcePath}): ${parsed.error.message}` + ); + } + await validateMdx(document.body, sourcePath); + + let toolName: string | undefined; + if (parsed.data.tool) { + const separator = parsed.data.tool.indexOf("."); + const toolToolkitId = parsed.data.tool.slice(0, separator); + toolName = parsed.data.tool.slice(separator + 1); + if ( + separator <= 0 || + toolName.length === 0 || + normalizeId(toolToolkitId) !== normalizeId(toolkitId) + ) { + throw new Error( + `Curation tool target must be fully qualified and match toolkit ${toolkitId} (${sourcePath})` + ); + } + } + + const { tool: _tool, ...metadata } = parsed.data; + return { + chunk: DocumentationChunkSchema.parse({ + ...metadata, + content: document.body, + }), + sourcePath, + ...(toolName ? { toolName } : {}), + }; +}; + +const assertSafeRelativePath = ( + relativePath: string, + sourcePath: string +): void => { + const parts = relativePath.split(/[\\/]/); + if ( + relativePath.length === 0 || + relativePath.startsWith(sep) || + parts.some((part) => part === "" || part === "." || part === "..") + ) { + throw new Error(`Curation page path is unsafe (${sourcePath})`); + } +}; + +const parsePage = async ( + sourcePath: string, + relativePath: string +): Promise => { + assertSafeRelativePath(relativePath, sourcePath); + const document = parseMarkdownDocument( + await readFile(sourcePath, "utf-8"), + sourcePath + ); + const parsed = PageFrontmatterSchema.safeParse(document.frontmatter); + if (!parsed.success) { + throw new Error( + `Curation page frontmatter has invalid schema (${sourcePath}): ${parsed.error.message}` + ); + } + await validateMdx(document.body, sourcePath); + return { + type: parsed.data.type, + content: document.body, + relativePath, + }; +}; + +const listFilesRecursively = async (dirPath: string): Promise => { + const entries = (await readdir(dirPath, { withFileTypes: true })).sort( + (left, right) => left.name.localeCompare(right.name) + ); + const files: string[] = []; + for (const entry of entries) { + const entryPath = join(dirPath, entry.name); + if (entry.isSymbolicLink()) { + throw new Error( + `Curation directory may not contain symlinks (${entryPath})` + ); + } + if (entry.isDirectory()) { + files.push(...(await listFilesRecursively(entryPath))); + } else if (entry.isFile()) { + files.push(entryPath); + } + } + return files; +}; + +const rejectJsonFiles = (files: readonly string[]): void => { + const jsonFile = files.find((file) => extensionOf(file) === ".json"); + if (jsonFile) { + throw new Error( + `JSON curation is no longer supported; convert this file to Markdown (${jsonFile})` + ); + } +}; + +const compareChunks = (left: CompiledChunk, right: CompiledChunk): number => + left.sourcePath.localeCompare(right.sourcePath); + +const loadToolkitDirectory = async ( + toolkitPath: string, + toolkitId: string +): Promise => { + const chunksPath = join(toolkitPath, "chunks"); + const pagesPath = join(toolkitPath, "pages"); + const allFiles = await listFilesRecursively(toolkitPath); + rejectJsonFiles(allFiles); + + const chunkFiles = allFiles.filter( + (file) => file.startsWith(`${chunksPath}${sep}`) && isMarkdownFile(file) + ); + const pageFiles = allFiles.filter( + (file) => file.startsWith(`${pagesPath}${sep}`) && isMarkdownFile(file) + ); + + const chunks = ( + await Promise.all(chunkFiles.map((file) => parseChunk(file, toolkitId))) + ).sort(compareChunks); + const documentationChunks: DocumentationChunk[] = []; + const toolChunks: Record = {}; + for (const compiled of chunks) { + if (compiled.toolName) { + const chunksForTool = toolChunks[compiled.toolName] ?? []; + chunksForTool.push(compiled.chunk); + toolChunks[compiled.toolName] = chunksForTool; + } else { + documentationChunks.push(compiled.chunk); + } + } + + const subPages = await Promise.all( + pageFiles + .sort() + .map((file) => + parsePage(file, relative(pagesPath, file).split(sep).join("/")) + ) + ); + const pagePaths = subPages.map((page) => + typeof page === "string" ? page : page.relativePath.toLowerCase() + ); + if (new Set(pagePaths).size !== pagePaths.length) { + throw new Error(`Curation contains duplicate page paths (${toolkitPath})`); + } + + return CustomSectionsSchema.parse({ + documentationChunks, + customImports: [], + subPages, + toolChunks, + }); +}; + +export class MarkdownCurationSource implements ICustomSectionsSource { + private readonly rootPath: string; + private cachedData: Readonly> | null = null; + + constructor(rootPath: string) { + this.rootPath = rootPath; + } + + private async loadData(): Promise>> { + if (this.cachedData) { + return this.cachedData; + } + + let rootStats: Awaited>; + try { + rootStats = await stat(this.rootPath); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + throw new Error( + `Configured curation directory does not exist: ${this.rootPath}` + ); + } + throw error; + } + if (!rootStats.isDirectory()) { + throw new Error( + `Configured curation path is not a directory: ${this.rootPath}` + ); + } + + const entries = ( + await readdir(this.rootPath, { withFileTypes: true }) + ).sort((left, right) => left.name.localeCompare(right.name)); + const data: Record = {}; + const normalizedIds = new Map(); + for (const entry of entries) { + const entryPath = join(this.rootPath, entry.name); + if (entry.isSymbolicLink()) { + throw new Error( + `Curation directory may not contain symlinks (${entryPath})` + ); + } + if (entry.isFile() && extensionOf(entry.name) === ".json") { + rejectJsonFiles([entryPath]); + } + if (!entry.isDirectory()) { + continue; + } + const normalizedId = normalizeId(entry.name); + const duplicate = normalizedIds.get(normalizedId); + if (duplicate) { + throw new Error( + `Curation toolkit directories normalize to the same ID: ${duplicate}, ${entry.name}` + ); + } + normalizedIds.set(normalizedId, entry.name); + data[entry.name] = await loadToolkitDirectory(entryPath, entry.name); + } + + this.cachedData = data; + return data; + } + + async getCustomSections(toolkitId: string): Promise { + const data = await this.loadData(); + const normalizedId = normalizeId(toolkitId); + const entry = Object.entries(data).find( + ([key]) => normalizeId(key) === normalizedId + ); + return entry?.[1] ?? emptyCustomSections(); + } + + async getAllCustomSections(): Promise< + Readonly> + > { + return this.loadData(); + } +} + +export const createMarkdownCurationSource = ( + rootPath: string +): MarkdownCurationSource => new MarkdownCurationSource(rootPath); diff --git a/toolkit-docs-generator/tests/merger/data-merger.test.ts b/toolkit-docs-generator/tests/merger/data-merger.test.ts index 7b2d82ec1..aec2923ad 100644 --- a/toolkit-docs-generator/tests/merger/data-merger.test.ts +++ b/toolkit-docs-generator/tests/merger/data-merger.test.ts @@ -963,6 +963,29 @@ describe("mergeToolkit resolveProviderId fallback", () => { }); describe("mergeToolkit overview chunk handling", () => { + it("rejects curation that targets a tool outside the toolkit", async () => { + await expect( + mergeToolkit( + "TestKit", + [createTool()], + createMetadata(), + createCustomSections({ + toolChunks: { + MissingTool: [ + { + type: "markdown", + location: "description", + position: "after", + content: "This target is stale.", + }, + ], + }, + }), + undefined + ) + ).rejects.toThrow("Curation targets unknown tool(s): MissingTool"); + }); + it("keeps toolkit-level overview chunks from source custom sections", async () => { const result = await mergeToolkit( "TestKit", @@ -1549,6 +1572,59 @@ describe("DataMerger", () => { ).toBe(true); }); + it("warns without rewriting an authoritative curation chunk", async () => { + const currentTool = createTool({ + name: "CreateIssue", + qualifiedName: "Github.CreateIssue", + fullyQualifiedName: "Github.CreateIssue@1.0.0", + secrets: [], + }); + const previous = await mergeToolkit( + "Github", + [createTool({ ...currentTool, secrets: ["OLD_SECRET"] })], + githubMetadata, + null, + createStubGenerator() + ); + const cleanupSpy = vi.fn(async () => "Rewritten content"); + const merger = new DataMerger({ + toolkitDataSource: createCombinedToolkitDataSource({ + toolSource: new InMemoryToolDataSource([currentTool]), + metadataSource: new InMemoryMetadataSource([githubMetadata]), + }), + customSectionsSource: new InMemoryCustomSectionsSource({ + Github: createCustomSections({ + documentationChunks: [ + { + type: "warning", + location: "description", + position: "after", + content: "Source still mentions OLD_SECRET.", + }, + ], + }), + }), + toolExampleGenerator: createStubGenerator(), + secretEditGenerator: { + cleanupStaleReferences: cleanupSpy, + fillCoverageGaps: vi.fn(async ({ content }) => content), + }, + previousToolkits: new Map([["github", previous.toolkit]]), + }); + + const result = await merger.mergeToolkit("Github"); + + expect(cleanupSpy).not.toHaveBeenCalled(); + expect(result.toolkit.documentationChunks[0]?.content).toBe( + "Source still mentions OLD_SECRET." + ); + expect( + result.warnings.some((warning) => + warning.includes("Stale secret reference") + ) + ).toBe(true); + }); + it("passes the post-cleanup summary to the coverage editor, not the original", async () => { // Ordering guarantee: applyStaleRefCleanup runs before the coverage // scan is re-computed. We prove this by making cleanup mutate a @@ -1941,10 +2017,7 @@ describe("DataMerger", () => { }); }); - describe("error handling preserves previous custom sections", () => { - // buildMergeErrorResult is invoked by mergeToolkitEntry (called from - // mergeAllToolkits). We trigger it by making the customSectionsSource throw, - // which is caught by mergeToolkitEntry's try/catch. + describe("curation configuration errors", () => { const makeFailingCustomSectionsSource = (): ICustomSectionsSource => ({ getCustomSections: async () => { throw new Error("Custom sections source unavailable"); @@ -1954,7 +2027,7 @@ describe("DataMerger", () => { }, }); - it("preserves documentationChunks and customImports from previous toolkit when merge throws", async () => { + it("fails before recovery instead of preserving stale prose", async () => { const toolkitDataSource = createCombinedToolkitDataSource({ toolSource: new InMemoryToolDataSource([githubTool1]), metadataSource: new InMemoryMetadataSource([githubMetadata]), @@ -1978,62 +2051,16 @@ describe("DataMerger", () => { }), createStubGenerator() ); - const completedToolkitIds: string[] = []; - const merger = new DataMerger({ toolkitDataSource, customSectionsSource: makeFailingCustomSectionsSource(), toolExampleGenerator: createStubGenerator(), previousToolkits: new Map([["github", previousResult.toolkit]]), - onToolkitComplete: async (result) => { - completedToolkitIds.push(result.toolkit.id); - }, }); - const results = await merger.mergeAllToolkits(); - const result = results[0]; - - expect(result?.error).toBe("Custom sections source unavailable"); - expect(result?.toolkit).toEqual(previousResult.toolkit); - expect(result?.toolkit.documentationChunks).toHaveLength(1); - expect(result?.toolkit.documentationChunks[0]?.content).toBe( - "Critical: GitHub Apps only." - ); - expect(result?.toolkit.customImports).toHaveLength(1); - expect(result?.toolkit.subPages).toEqual(["setup-guide"]); - expect(result?.warnings[0]).toContain( - "Custom sections source unavailable" - ); - expect(completedToolkitIds).toEqual(["Github"]); - }); - - it("returns empty custom sections in error result when no previous toolkit exists", async () => { - const toolkitDataSource = createCombinedToolkitDataSource({ - toolSource: new InMemoryToolDataSource([githubTool1]), - metadataSource: new InMemoryMetadataSource([githubMetadata]), - }); - const completedToolkitIds: string[] = []; - - const merger = new DataMerger({ - toolkitDataSource, - customSectionsSource: makeFailingCustomSectionsSource(), - toolExampleGenerator: createStubGenerator(), - onToolkitComplete: async (result) => { - completedToolkitIds.push(result.toolkit.id); - }, - }); - - const results = await merger.mergeAllToolkits(); - const result = results[0]; - - expect(result?.error).toBe("Custom sections source unavailable"); - expect(result?.toolkit.documentationChunks).toHaveLength(0); - expect(result?.toolkit.customImports).toHaveLength(0); - expect(result?.toolkit.subPages).toHaveLength(0); - expect(result?.warnings[0]).toContain( + await expect(merger.mergeAllToolkits()).rejects.toThrow( "Custom sections source unavailable" ); - expect(completedToolkitIds).toEqual([]); }); }); @@ -2139,6 +2166,61 @@ describe("DataMerger", () => { expect(result?.error).toContain("missing design-system metadata"); }); + it("overlays authoritative empty curation on preserved prior output", async () => { + const previous = await mergeToolkit( + "Github", + [githubTool1], + githubMetadata, + createCustomSections({ + documentationChunks: [ + { + type: "warning", + location: "description", + position: "after", + content: "Delete me", + }, + ], + customImports: ["import Old from 'old';"], + subPages: ["old-page"], + toolChunks: { + CreateIssue: [ + { + type: "info", + location: "parameters", + position: "after", + content: "Delete this too", + }, + ], + }, + }), + createStubGenerator() + ); + const toolkitDataSource = createCombinedToolkitDataSource({ + toolSource: new InMemoryToolDataSource([githubTool1]), + metadataSource: new InMemoryMetadataSource([]), + }); + const merger = new DataMerger({ + toolkitDataSource, + customSectionsSource: new InMemoryCustomSectionsSource({ + Github: createCustomSections(), + }), + toolExampleGenerator: createStubGenerator(), + previousToolkits: new Map([["github", previous.toolkit]]), + preserveLastKnownGood: true, + }); + + const [result] = await merger.mergeAllToolkits(); + + expect(result?.recovery).toBe("preserved"); + expect(result?.toolkit.documentationChunks).toEqual([]); + expect(result?.toolkit.customImports).toEqual([]); + expect(result?.toolkit.subPages).toEqual([]); + expect(result?.toolkit.tools[0]?.documentationChunks).toEqual([]); + expect(result?.toolkit.tools[0]?.codeExample).toEqual( + previous.toolkit.tools[0]?.codeExample + ); + }); + it("preserves prior output for provider-mode generation when metadata is missing", async () => { const previous = await mergeToolkit( "Github", @@ -2336,7 +2418,7 @@ describe("DataMerger", () => { }); await expect(merger.mergeAllToolkits()).rejects.toThrow( - "Failed to process Github: Custom sections source unavailable" + "Custom sections source unavailable" ); }); diff --git a/toolkit-docs-generator/tests/scenarios/curation-corpus.test.ts b/toolkit-docs-generator/tests/scenarios/curation-corpus.test.ts new file mode 100644 index 000000000..915075e1c --- /dev/null +++ b/toolkit-docs-generator/tests/scenarios/curation-corpus.test.ts @@ -0,0 +1,49 @@ +import { readdir, readFile } from "fs/promises"; +import { join } from "path"; +import { describe, expect, it } from "vitest"; +import { createMarkdownCurationSource } from "../../src/sources/markdown-curation"; +import type { MergedToolkit } from "../../src/types/index"; + +const GENERATOR_ROOT = join(__dirname, "../.."); +const CURATION_DIR = join(GENERATOR_ROOT, "curation"); +const TOOLKITS_DIR = join(GENERATOR_ROOT, "data", "toolkits"); + +describe("checked-in Markdown curation", () => { + it("reproduces every authored chunk and subpage in committed toolkit data", async () => { + const source = createMarkdownCurationSource(CURATION_DIR); + const files = (await readdir(TOOLKITS_DIR)) + .filter((file) => file.endsWith(".json") && file !== "index.json") + .sort(); + + let chunkCount = 0; + let subPageCount = 0; + for (const file of files) { + const toolkit = JSON.parse( + await readFile(join(TOOLKITS_DIR, file), "utf-8") + ) as MergedToolkit; + const current = await source.getCustomSections(toolkit.id); + const expectedToolChunks = Object.fromEntries( + toolkit.tools + .filter((tool) => tool.documentationChunks.length > 0) + .map((tool) => [tool.name, tool.documentationChunks]) + ); + + expect(current.documentationChunks, file).toEqual( + toolkit.documentationChunks + ); + expect(current.toolChunks, file).toEqual(expectedToolChunks); + expect(current.subPages, file).toEqual(toolkit.subPages); + expect(current.customImports, file).toEqual([]); + + chunkCount += current.documentationChunks.length; + chunkCount += Object.values(current.toolChunks).reduce( + (total, chunks) => total + chunks.length, + 0 + ); + subPageCount += current.subPages.length; + } + + expect(chunkCount).toBe(82); + expect(subPageCount).toBe(2); + }); +}); diff --git a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts index f2d8b6aab..5c2d98161 100644 --- a/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts +++ b/toolkit-docs-generator/tests/scenarios/custom-sections-diff.test.ts @@ -89,12 +89,21 @@ describe("getChangedToolkitIdsFromCustomSections", () => { ).toEqual(["github"]); }); - it("ignores toolkits without a curation file", () => { + it("treats a missing toolkit directory as cleared curation", () => { expect( getChangedToolkitIdsFromCustomSections( {}, new Map([["Github", previousToolkit()]]) ) + ).toEqual(["github"]); + }); + + it("ignores absent curation when the previous artifact is also empty", () => { + const toolkit = previousToolkit(); + toolkit.documentationChunks = []; + + expect( + getChangedToolkitIdsFromCustomSections({}, 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 index 52274d894..c9f6b02dd 100644 --- a/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts +++ b/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts @@ -11,12 +11,12 @@ * 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 { mkdir, 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"; -import { createCustomSectionsFileSource } from "../../src/sources/custom-sections-file"; +import { createMarkdownCurationSource } from "../../src/sources/markdown-curation"; import type { ToolDefinition } from "../../src/types/index"; const createTool = (): ToolDefinition => ({ @@ -31,23 +31,29 @@ const createTool = (): ToolDefinition => ({ 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", - }, - ], +const writeCuration = async (root: string): Promise => { + await mkdir(join(root, "testkit/chunks"), { recursive: true }); + await mkdir(join(root, "testkit/pages/environment-variables"), { + recursive: true, + }); + await writeFile( + join(root, "testkit/chunks/guidance.mdx"), + `--- +type: warning +location: description +position: after +--- +Hand-authored guidance that has no upstream source. +` + ); + await writeFile( + join(root, "testkit/pages/environment-variables/page.mdx"), + `--- +type: environment-variables +--- +# Environment Variables +` + ); }; describe("prose survives --force-regenerate", () => { @@ -62,13 +68,9 @@ describe("prose survives --force-regenerate", () => { 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) - ); + await writeCuration(tempDir); - const source = createCustomSectionsFileSource(tempDir); + const source = createMarkdownCurationSource(tempDir); const customSections = await source.getCustomSections("TestKit"); expect(customSections).not.toBeNull(); @@ -86,18 +88,31 @@ describe("prose survives --force-regenerate", () => { 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); + expect(result.toolkit.customImports).toEqual([]); + expect(result.toolkit.subPages).toEqual([ + { + type: "environment-variables", + content: "# Environment Variables", + relativePath: "environment-variables/page.mdx", + }, + ]); }); it("normalizes the toolkit id when matching curation files", async () => { tempDir = await mkdtemp(join(tmpdir(), "curation-")); + await mkdir(join(tempDir, "notiontoolkit/chunks"), { recursive: true }); await writeFile( - join(tempDir, "notiontoolkit.json"), - JSON.stringify(curationEntry, null, 2) + join(tempDir, "notiontoolkit/chunks/guidance.mdx"), + `--- +type: warning +location: description +position: after +--- +Prose +` ); - const source = createCustomSectionsFileSource(tempDir); + const source = createMarkdownCurationSource(tempDir); // File stem "notiontoolkit" must match toolkit id "NotionToolkit". const customSections = await source.getCustomSections("NotionToolkit"); @@ -119,17 +134,12 @@ describe("prose survives --force-regenerate", () => { expect(result.toolkit.subPages).toHaveLength(0); }); - it("clears prose when curation exists but is explicitly empty", async () => { + it("clears prose when the toolkit directory is deleted", async () => { tempDir = await mkdtemp(join(tmpdir(), "curation-")); - await writeFile( - join(tempDir, "testkit.json"), - JSON.stringify(curationEntry, null, 2) - ); + await writeCuration(tempDir); const withProse = - await createCustomSectionsFileSource(tempDir).getCustomSections( - "TestKit" - ); + await createMarkdownCurationSource(tempDir).getCustomSections("TestKit"); const previousResult = await mergeToolkit( "TestKit", @@ -141,11 +151,9 @@ describe("prose survives --force-regenerate", () => { ); expect(previousResult.toolkit.documentationChunks).toHaveLength(1); - await writeFile(join(tempDir, "testkit.json"), "{}"); + await rm(join(tempDir, "testkit"), { recursive: true, force: true }); const clearedCuration = - await createCustomSectionsFileSource(tempDir).getCustomSections( - "TestKit" - ); + await createMarkdownCurationSource(tempDir).getCustomSections("TestKit"); const result = await mergeToolkit( "TestKit", diff --git a/toolkit-docs-generator/tests/sources/custom-sections-file.test.ts b/toolkit-docs-generator/tests/sources/custom-sections-file.test.ts deleted file mode 100644 index 48e469cee..000000000 --- a/toolkit-docs-generator/tests/sources/custom-sections-file.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { mkdir, mkdtemp, rm, writeFile } from "fs/promises"; -import { tmpdir } from "os"; -import { join } from "path"; -import { afterEach, describe, expect, it } from "vitest"; -import { createCustomSectionsFileSource } from "../../src/sources/custom-sections-file"; - -const createTempDir = async (): Promise => - mkdtemp(join(tmpdir(), "custom-sections-")); - -describe("CustomSectionsFileSource", () => { - let tempDir: string | null = null; - - afterEach(async () => { - if (tempDir) { - await rm(tempDir, { recursive: true, force: true }); - tempDir = null; - } - }); - - it("returns empty data when file is missing", async () => { - tempDir = await createTempDir(); - const filePath = join(tempDir, "missing.json"); - const source = createCustomSectionsFileSource(filePath); - - const result = await source.getCustomSections("Github"); - expect(result).toBeNull(); - - const all = await source.getAllCustomSections(); - expect(all).toEqual({}); - }); - - it("loads custom sections with defaults applied", async () => { - tempDir = await createTempDir(); - const filePath = join(tempDir, "custom-sections.json"); - await writeFile( - filePath, - JSON.stringify( - { - Github: {}, - }, - null, - 2 - ) - ); - - const source = createCustomSectionsFileSource(filePath); - const result = await source.getCustomSections("Github"); - - expect(result).not.toBeNull(); - expect(result?.documentationChunks).toEqual([]); - expect(result?.customImports).toEqual([]); - expect(result?.subPages).toEqual([]); - expect(result?.toolChunks).toEqual({}); - }); - - it("loads rich subpage entries supported by generated toolkit output", async () => { - tempDir = await createTempDir(); - const filePath = join(tempDir, "custom-sections.json"); - const subPage = { - type: "mdx", - content: "# Setup", - relativePath: "setup/page.mdx", - }; - await writeFile( - filePath, - JSON.stringify({ Github: { subPages: [subPage] } }, null, 2) - ); - - const source = createCustomSectionsFileSource(filePath); - const result = await source.getCustomSections("Github"); - - expect(result?.subPages).toEqual([subPage]); - }); - - it("throws a helpful error when JSON is invalid", async () => { - tempDir = await createTempDir(); - const filePath = join(tempDir, "invalid.json"); - await writeFile(filePath, "{ invalid-json"); - - const source = createCustomSectionsFileSource(filePath); - - await expect(source.getAllCustomSections()).rejects.toThrow( - `Custom sections file is not valid JSON (${filePath})` - ); - }); - - it("throws a helpful error when schema is invalid", async () => { - tempDir = await createTempDir(); - const filePath = join(tempDir, "invalid-schema.json"); - await writeFile( - filePath, - JSON.stringify( - { - Github: { - documentationChunks: "not-an-array", - }, - }, - null, - 2 - ) - ); - - const source = createCustomSectionsFileSource(filePath); - - await expect(source.getAllCustomSections()).rejects.toThrow( - `Custom sections file has invalid schema (${filePath})` - ); - }); - - 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"); - await writeFile( - filePath, - JSON.stringify({ Github: { subPages: [{ type: "mdx" }] } }, null, 2) - ); - - const source = createCustomSectionsFileSource(filePath); - - await expect(source.getAllCustomSections()).rejects.toThrow( - `Custom sections file has invalid schema (${filePath})` - ); - }); -}); diff --git a/toolkit-docs-generator/tests/sources/markdown-curation.test.ts b/toolkit-docs-generator/tests/sources/markdown-curation.test.ts new file mode 100644 index 000000000..c77c75328 --- /dev/null +++ b/toolkit-docs-generator/tests/sources/markdown-curation.test.ts @@ -0,0 +1,214 @@ +import { mkdir, mkdtemp, rm, symlink, writeFile } from "fs/promises"; +import { tmpdir } from "os"; +import { join } from "path"; +import { afterEach, describe, expect, it } from "vitest"; +import { createMarkdownCurationSource } from "../../src/sources/markdown-curation"; + +const createTempDir = async (): Promise => + mkdtemp(join(tmpdir(), "markdown-curation-")); + +const writeDocument = async ( + root: string, + relativePath: string, + source: string +): Promise => { + const filePath = join(root, relativePath); + await mkdir(join(filePath, ".."), { recursive: true }); + await writeFile(filePath, source); +}; + +const chunk = (overrides = "", body = "Authored prose"): string => `--- +type: warning +location: description +position: after +${overrides}--- +${body} +`; + +describe("MarkdownCurationSource", () => { + let tempDir: string | null = null; + + afterEach(async () => { + if (tempDir) { + await rm(tempDir, { recursive: true, force: true }); + tempDir = null; + } + }); + + it("fails when the configured root is missing", async () => { + tempDir = await createTempDir(); + const source = createMarkdownCurationSource(join(tempDir, "missing")); + + await expect(source.getAllCustomSections()).rejects.toThrow( + "Configured curation directory does not exist" + ); + }); + + it("returns authoritative empty sections for a missing toolkit", async () => { + tempDir = await createTempDir(); + const source = createMarkdownCurationSource(tempDir); + + await expect(source.getCustomSections("Github")).resolves.toEqual({ + documentationChunks: [], + customImports: [], + subPages: [], + toolChunks: {}, + }); + }); + + it("compiles toolkit and tool chunks from Markdown", async () => { + tempDir = await createTempDir(); + await writeDocument( + tempDir, + "github/chunks/01-toolkit.mdx", + chunk("header: '## Setup'\n", "## Setup\n\nRead this.") + ); + await writeDocument( + tempDir, + "github/chunks/02-tool.mdx", + chunk("tool: Github.CreateIssue\n", "Tool guidance") + ); + + const sections = + await createMarkdownCurationSource(tempDir).getCustomSections("GitHub"); + + expect(sections.documentationChunks).toEqual([ + expect.objectContaining({ + header: "## Setup", + content: "## Setup\n\nRead this.", + }), + ]); + expect(sections.toolChunks.CreateIssue).toEqual([ + expect.objectContaining({ content: "Tool guidance" }), + ]); + expect(sections.customImports).toEqual([]); + }); + + it("compiles nested subpages and derives their relative paths", async () => { + tempDir = await createTempDir(); + await writeDocument( + tempDir, + "jira/pages/environment-variables/page.mdx", + `--- +type: environment-variables +--- +# Environment variables +` + ); + + const sections = + await createMarkdownCurationSource(tempDir).getCustomSections("Jira"); + + expect(sections.subPages).toEqual([ + { + type: "environment-variables", + content: "# Environment variables", + relativePath: "environment-variables/page.mdx", + }, + ]); + }); + + it("orders chunks by source path while preserving priority metadata", async () => { + tempDir = await createTempDir(); + await writeDocument( + tempDir, + "github/chunks/b.mdx", + chunk("priority: 20\n", "Second") + ); + await writeDocument( + tempDir, + "github/chunks/c.mdx", + chunk("priority: 10\n", "First") + ); + await writeDocument( + tempDir, + "github/chunks/a.mdx", + chunk("priority: 20\n", "Middle") + ); + + const sections = + await createMarkdownCurationSource(tempDir).getCustomSections("Github"); + + expect( + sections.documentationChunks.map(({ content, priority }) => ({ + content, + priority, + })) + ).toEqual([ + { content: "Middle", priority: 20 }, + { content: "Second", priority: 20 }, + { content: "First", priority: 10 }, + ]); + }); + + it("rejects leftover JSON curation", async () => { + tempDir = await createTempDir(); + await writeFile(join(tempDir, "github.json"), "{}"); + + await expect( + createMarkdownCurationSource(tempDir).getAllCustomSections() + ).rejects.toThrow("JSON curation is no longer supported"); + }); + + it("rejects invalid frontmatter with the source path", async () => { + tempDir = await createTempDir(); + const filePath = join(tempDir, "github/chunks/bad.mdx"); + await writeDocument( + tempDir, + "github/chunks/bad.mdx", + chunk("unknown: true\n") + ); + + await expect( + createMarkdownCurationSource(tempDir).getAllCustomSections() + ).rejects.toThrow(`invalid schema (${filePath})`); + }); + + it("rejects malformed MDX with the source path", async () => { + tempDir = await createTempDir(); + const filePath = join(tempDir, "github/chunks/bad.mdx"); + await writeDocument( + tempDir, + "github/chunks/bad.mdx", + chunk("", "Unclosed") + ); + + await expect( + createMarkdownCurationSource(tempDir).getAllCustomSections() + ).rejects.toThrow(`invalid MDX (${filePath})`); + }); + + it("rejects a tool target from another toolkit", async () => { + tempDir = await createTempDir(); + await writeDocument( + tempDir, + "github/chunks/bad-tool.mdx", + chunk("tool: Slack.SendMessage\n") + ); + + await expect( + createMarkdownCurationSource(tempDir).getAllCustomSections() + ).rejects.toThrow("must be fully qualified and match toolkit github"); + }); + + it("rejects normalized toolkit directory collisions", async () => { + tempDir = await createTempDir(); + await mkdir(join(tempDir, "NotionToolkit")); + await mkdir(join(tempDir, "notion-toolkit")); + + await expect( + createMarkdownCurationSource(tempDir).getAllCustomSections() + ).rejects.toThrow("normalize to the same ID"); + }); + + it("rejects symlinked toolkit directories", async () => { + tempDir = await createTempDir(); + const target = join(tempDir, "target"); + await mkdir(target); + await symlink(target, join(tempDir, "github")); + + await expect( + createMarkdownCurationSource(tempDir).getAllCustomSections() + ).rejects.toThrow("may not contain symlinks"); + }); +}); From 35055a3a082dd4284b67b9905c45267bda466976 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Tue, 11 Aug 2026 13:08:35 -0400 Subject: [PATCH 09/12] narrow MDX curation refactor scope --- app/_lib/toolkit-markdown.ts | 148 +++-------------- specs/mdx-curation/README.md | 52 ------ specs/mdx-curation/slices/01-compile-mdx.md | 22 --- .../slices/02-authority-and-migration.md | 23 --- .../slices/03-markdown-projection.md | 19 --- tests/toolkit-markdown.test.ts | 60 ------- toolkit-docs-generator/ARCHITECTURE.md | 2 - .../figma/chunks/001-auth-after-markdown.mdx | 2 +- .../chunks/001-description-after-warning.mdx | 6 +- .../github/chunks/005-configuration-setup.mdx | 6 +- .../jira/pages/environment-variables/page.mdx | 1 + .../curation/zoom/pages/install/page.mdx | 1 + .../scripts/extract-curation.ts | 152 ------------------ 13 files changed, 28 insertions(+), 466 deletions(-) delete mode 100644 specs/mdx-curation/README.md delete mode 100644 specs/mdx-curation/slices/01-compile-mdx.md delete mode 100644 specs/mdx-curation/slices/02-authority-and-migration.md delete mode 100644 specs/mdx-curation/slices/03-markdown-projection.md delete mode 100644 toolkit-docs-generator/scripts/extract-curation.ts diff --git a/app/_lib/toolkit-markdown.ts b/app/_lib/toolkit-markdown.ts index 190f60a3b..1c4629c9a 100644 --- a/app/_lib/toolkit-markdown.ts +++ b/app/_lib/toolkit-markdown.ts @@ -1,6 +1,4 @@ import type { - DocumentationChunk, - DocumentationChunkLocation, ToolDefinition, ToolkitData, ToolParameter, @@ -14,75 +12,6 @@ import type { * independent of the rendered HTML. */ const JSON_INDENT = 2; -const DEFAULT_CHUNK_PRIORITY = 100; -const HEADER_PREFIX_REGEX = /^#+\s*/; - -function compareChunks( - left: DocumentationChunk, - right: DocumentationChunk -): number { - const priorityDifference = - (left.priority ?? DEFAULT_CHUNK_PRIORITY) - - (right.priority ?? DEFAULT_CHUNK_PRIORITY); - if (priorityDifference !== 0) { - return priorityDifference; - } - - const leftHeader = (left.header ?? "") - .replace(HEADER_PREFIX_REGEX, "") - .trim(); - const rightHeader = (right.header ?? "") - .replace(HEADER_PREFIX_REGEX, "") - .trim(); - if (leftHeader && rightHeader) { - return leftHeader.localeCompare(rightHeader); - } - if (leftHeader) { - return -1; - } - if (rightHeader) { - return 1; - } - return left.content.localeCompare(right.content); -} - -function chunkBlocks( - chunks: readonly DocumentationChunk[], - location: DocumentationChunkLocation, - position: DocumentationChunk["position"] -): string[] { - return chunks - .filter( - (chunk) => chunk.location === location && chunk.position === position - ) - .sort(compareChunks) - .map((chunk) => { - if (chunk.type === "code") { - return `\`\`\`\n${chunk.content.trim()}\n\`\`\``; - } - if (chunk.title) { - return `**${chunk.title}**\n\n${chunk.content.trim()}`; - } - return chunk.content.trim(); - }); -} - -function sectionBlocks( - chunks: readonly DocumentationChunk[], - location: DocumentationChunkLocation, - defaultBlock: string | null -): string[] { - const before = chunkBlocks(chunks, location, "before"); - const replacement = chunkBlocks(chunks, location, "replace"); - const after = chunkBlocks(chunks, location, "after"); - let middle: string[] = []; - if (replacement.length > 0) { - middle = replacement; - } else if (defaultBlock) { - middle = [defaultBlock]; - } - return [...before, ...middle, ...after]; -} /** Collapse newlines and escape pipes so a value is safe inside a table cell. */ function cell(value: string | null | undefined): string { @@ -118,59 +47,38 @@ function exampleBlock(tool: ToolDefinition): string | null { function toolBlock(tool: ToolDefinition): string { const blocks: string[] = [`### ${tool.qualifiedName}`]; - const chunks = tool.documentationChunks ?? []; - blocks.push( - ...sectionBlocks(chunks, "description", tool.description?.trim() ?? null) - ); + if (tool.description) { + blocks.push(tool.description.trim()); + } const scopes = tool.auth?.scopes ?? []; - blocks.push( - ...sectionBlocks( - chunks, - "auth", - scopes.length > 0 - ? `**Required OAuth scopes:** ${scopes.map((s) => `\`${s}\``).join(", ")}` - : null - ) - ); + if (scopes.length > 0) { + blocks.push( + `**Required OAuth scopes:** ${scopes.map((s) => `\`${s}\``).join(", ")}` + ); + } const secrets = tool.secrets ?? []; - blocks.push( - ...sectionBlocks( - chunks, - "secrets", - secrets.length > 0 - ? `**Secrets:** ${secrets.map((s) => `\`${s}\``).join(", ")}` - : null - ) - ); - - let parametersBlock: string; + if (secrets.length > 0) { + blocks.push(`**Secrets:** ${secrets.map((s) => `\`${s}\``).join(", ")}`); + } + if (tool.parameters && tool.parameters.length > 0) { const rows = [ "| Name | Type | Required | Description |", "| --- | --- | --- | --- |", ...tool.parameters.map(parameterRow), ]; - parametersBlock = `**Parameters**\n\n${rows.join("\n")}`; + blocks.push(`**Parameters**\n\n${rows.join("\n")}`); } else { - parametersBlock = "_No parameters._"; + blocks.push("_No parameters._"); + } + + if (tool.output) { + const desc = tool.output.description ? ` — ${tool.output.description}` : ""; + blocks.push(`**Output:** \`${tool.output.type}\`${desc}`); } - blocks.push(...sectionBlocks(chunks, "parameters", parametersBlock)); - - const outputDescription = tool.output?.description - ? ` — ${tool.output.description}` - : ""; - blocks.push( - ...sectionBlocks( - chunks, - "output", - tool.output - ? `**Output:** \`${tool.output.type}\`${outputDescription}` - : null - ) - ); const example = exampleBlock(tool); if (example) { @@ -182,37 +90,19 @@ function toolBlock(tool: ToolDefinition): string { export function toToolkitMarkdown(data: ToolkitData): string { const blocks: string[] = [`# ${data.label || data.id}`]; - const chunks = data.documentationChunks ?? []; if (data.description) { blocks.push(data.description.trim()); } - blocks.push(...chunkBlocks(chunks, "header", "before")); - blocks.push(...chunkBlocks(chunks, "description", "before")); - blocks.push(...chunkBlocks(chunks, "description", "after")); - blocks.push(...chunkBlocks(chunks, "header", "replace")); - blocks.push(...chunkBlocks(chunks, "header", "after")); if (data.summary) { blocks.push(data.summary.trim()); } - blocks.push(...chunkBlocks(chunks, "auth", "before")); - blocks.push(...chunkBlocks(chunks, "auth", "after")); - blocks.push(...chunkBlocks(chunks, "before_available_tools", "before")); - blocks.push(...chunkBlocks(chunks, "before_available_tools", "after")); - blocks.push(...chunkBlocks(chunks, "custom_section", "before")); - blocks.push(...chunkBlocks(chunks, "custom_section", "after")); const tools = data.tools ?? []; blocks.push(`## Tools (${tools.length})`); - blocks.push(...chunkBlocks(chunks, "after_available_tools", "before")); - blocks.push(...chunkBlocks(chunks, "after_available_tools", "after")); for (const tool of tools) { blocks.push(toolBlock(tool)); } - blocks.push(...chunkBlocks(chunks, "footer", "before")); - blocks.push(...chunkBlocks(chunks, "footer", "replace")); - blocks.push(...chunkBlocks(chunks, "footer", "after")); - return `${blocks.join("\n\n")}\n`; } diff --git a/specs/mdx-curation/README.md b/specs/mdx-curation/README.md deleted file mode 100644 index 688568020..000000000 --- a/specs/mdx-curation/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# MDX curation source - -## Goal - -Make Markdown and MDX files the durable source for hand-authored toolkit prose while keeping generated toolkit JSON as the application contract. - -## Next agent prompt - -Last updated: 2026-08-11. - -Implement the slices in order. Keep the curation directory globally authoritative, preserve previous JSON only for generated enrichment, and update this section before ending a pass. - -- [x] Compile checked-in MDX into `CustomSections` ([slice 1](./slices/01-compile-mdx.md)). -- [x] Apply deletion-safe authority and migrate the corpus ([slice 2](./slices/02-authority-and-migration.md)). -- [x] Include curated chunks in the secondary Markdown projection ([slice 3](./slices/03-markdown-projection.md)). - -## Source contract - -```text -curation// - chunks/*.mdx - pages/**/*.mdx -``` - -Chunk files use strict frontmatter for `type`, `location`, `position`, and optional chunk metadata. An optional fully qualified `tool` field targets one tool. The file body is the authored content. - -Page files use a required `type` field. Their path below `pages/` becomes `relativePath`, and their body becomes the page content. - -## Invariants - -- A configured curation root is authoritative for every toolkit. -- A missing toolkit directory means empty curation. Deleting the final file clears prior prose. -- A missing configured root, invalid frontmatter, malformed MDX, unsafe page path, or leftover JSON file fails the run. -- Previous generated JSON may supply summaries, examples, secret metadata, and last-known-good upstream data. It never restores authored chunks, imports, or subpages over current curation. -- The compiler preserves normalized source-path order. Renderers apply chunk priority at presentation time. -- Generated toolkit JSON remains the only input to the docs app and Markdown serializer. -- MDX source is never silently rewritten. Secret-coherence scans may warn about curated prose, but automated edits apply only to generated summaries. - -## Non-goals - -- Add toolkit subpage routes. -- Keep JSON or aggregate-file curation compatibility. -- Rename the unreleased `--custom-sections` option. -- Add automated writeback into source MDX. -- Modify PR #1113 in this branch; it must consume this compiler when rebased. - -## Verification - -- Focused source, diff, merger, workflow, and Markdown serializer tests. -- Full curation-tree compilation. -- Typecheck and lint. -- Re-extraction reproduces all authored chunks and subpages from committed toolkit artifacts. diff --git a/specs/mdx-curation/slices/01-compile-mdx.md b/specs/mdx-curation/slices/01-compile-mdx.md deleted file mode 100644 index 01aad503b..000000000 --- a/specs/mdx-curation/slices/01-compile-mdx.md +++ /dev/null @@ -1,22 +0,0 @@ -# Slice 1: Compile MDX curation - -## Contract - -Replace the JSON reader with one cached compiler that projects the configured directory into existing `CustomSections` values. - -## API seam - -- `createMarkdownCurationSource(root)` owns traversal, frontmatter parsing, MDX syntax validation, normalization, and deterministic ordering. -- A configured source returns an empty `CustomSections` value for missing toolkits. -- `createEmptyCustomSectionsSource()` remains the signal that no curation source was configured. - -## Verification - -- Compile toolkit chunks, tool chunks, and nested pages. -- Reject invalid frontmatter, MDX, paths, duplicate normalized toolkits, and JSON files. -- Reject a missing configured root. -- Return empty curation for a missing toolkit. - -## Delegated decisions - -Internal helper names and diagnostic wording may change. The directory layout and authority semantics may not. diff --git a/specs/mdx-curation/slices/02-authority-and-migration.md b/specs/mdx-curation/slices/02-authority-and-migration.md deleted file mode 100644 index 069aff4c8..000000000 --- a/specs/mdx-curation/slices/02-authority-and-migration.md +++ /dev/null @@ -1,23 +0,0 @@ -# Slice 2: Apply authority and migrate content - -## Contract - -Make file deletion observable and prevent recovery behavior from resurrecting prose. Convert the checked-in JSON corpus to MDX atomically. - -## API seam - -- Curation diff compares the union of current sources and previous artifacts. -- Last-known-good recovery overlays current `CustomSections`, including empty arrays, on preserved artifacts. -- The extraction script emits the checked-in MDX directory layout. - -## Verification - -- Deleting the final file marks its toolkit changed. -- Missing toolkit curation clears previous toolkit and tool chunks. -- Recovery preserves upstream data but retains the current empty curation projection. -- Force regeneration includes authored MDX without previous output. -- The full checked-in curation tree compiles. - -## Delegated decisions - -Mechanical filenames may vary if they remain stable and descriptive. diff --git a/specs/mdx-curation/slices/03-markdown-projection.md b/specs/mdx-curation/slices/03-markdown-projection.md deleted file mode 100644 index ade433258..000000000 --- a/specs/mdx-curation/slices/03-markdown-projection.md +++ /dev/null @@ -1,19 +0,0 @@ -# Slice 3: Preserve prose in Markdown output - -## Contract - -Make the agent-facing and copy-page Markdown representation include the curated chunks already present in generated toolkit JSON. - -## API seam - -`toToolkitMarkdown()` remains a projection of `ToolkitData`. It does not read source MDX. - -## Verification - -- Toolkit and tool chunks appear in deterministic order. -- `before`, `after`, and `replace` placement matches the generated sections that the serializer supports. -- Existing parameter, auth, secret, output, and example coverage stays green. - -## Delegated decisions - -Allowed MDX tags may remain verbatim in textual Markdown output. diff --git a/tests/toolkit-markdown.test.ts b/tests/toolkit-markdown.test.ts index 17b99866f..e2a166be1 100644 --- a/tests/toolkit-markdown.test.ts +++ b/tests/toolkit-markdown.test.ts @@ -76,64 +76,4 @@ describe("toToolkitMarkdown", () => { expect(md).toContain("API_KEY"); expect(md).toContain("Example input"); }); - - test("includes toolkit and tool curation in deterministic order", () => { - const curated: ToolkitData = { - ...fixture, - documentationChunks: [ - { - type: "markdown", - location: "custom_section", - position: "after", - content: "Later toolkit prose.", - priority: 20, - }, - { - type: "markdown", - location: "custom_section", - position: "after", - content: "Earlier toolkit prose.", - priority: 10, - }, - ], - tools: fixture.tools.map((tool) => ({ - ...tool, - documentationChunks: [ - { - type: "markdown", - location: "description", - position: "after", - content: "Curated tool prose.", - }, - ], - })), - }; - - const result = toToolkitMarkdown(curated); - expect(result).toContain("Curated tool prose."); - expect(result.indexOf("Earlier toolkit prose.")).toBeLessThan( - result.indexOf("Later toolkit prose.") - ); - }); - - test("uses replacement curation instead of a generated section", () => { - const curated: ToolkitData = { - ...fixture, - tools: fixture.tools.map((tool) => ({ - ...tool, - documentationChunks: [ - { - type: "markdown", - location: "output", - position: "replace", - content: "A hand-authored output contract.", - }, - ], - })), - }; - - const result = toToolkitMarkdown(curated); - expect(result).toContain("A hand-authored output contract."); - expect(result).not.toContain("**Output:** `json` — The result"); - }); }); diff --git a/toolkit-docs-generator/ARCHITECTURE.md b/toolkit-docs-generator/ARCHITECTURE.md index 1f5ad8d14..0b79a565a 100644 --- a/toolkit-docs-generator/ARCHITECTURE.md +++ b/toolkit-docs-generator/ARCHITECTURE.md @@ -63,8 +63,6 @@ The generator output is consumed by the Next.js app: - The app loads JSON from `toolkit-docs-generator/data/toolkits/`. - `generateStaticParams` enumerates the toolkit routes and disables unknown dynamic parameters. - Custom documentation chunks are rendered as MDX in the UI. -- Copy-page and agent-facing Markdown is projected from the generated JSON and - includes those curated chunks. If you need HTML output, add a separate build step in the app. The generator intentionally avoids HTML to keep the pipeline deterministic. diff --git a/toolkit-docs-generator/curation/figma/chunks/001-auth-after-markdown.mdx b/toolkit-docs-generator/curation/figma/chunks/001-auth-after-markdown.mdx index 84e87a248..3797d68c2 100644 --- a/toolkit-docs-generator/curation/figma/chunks/001-auth-after-markdown.mdx +++ b/toolkit-docs-generator/curation/figma/chunks/001-auth-after-markdown.mdx @@ -4,6 +4,6 @@ location: auth position: after --- -The `projects:read` scope is **ONLY available in private Figma OAuth apps**. This scope is required for the navigation tools (`GetTeamProjects` and `GetProjectFiles`). +The `projects:read` scope is **ONLY available in private Figma OAuth apps**. This scope is required for the navigation tools (`GetTeamProjects` and `GetProjectFiles`). If 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. diff --git a/toolkit-docs-generator/curation/github/chunks/001-description-after-warning.mdx b/toolkit-docs-generator/curation/github/chunks/001-description-after-warning.mdx index b6ae167c1..12c87eca7 100644 --- a/toolkit-docs-generator/curation/github/chunks/001-description-after-warning.mdx +++ b/toolkit-docs-generator/curation/github/chunks/001-description-after-warning.mdx @@ -5,8 +5,8 @@ position: after --- **Critical**: This MCP Server is built for **GitHub Apps**, not OAuth Apps. - - You **must** create a GitHub App (not an OAuth App) to use this server properly. - + + You **must** create a GitHub App (not an OAuth App) to use this server properly. + 👉 [Complete GitHub App Setup Guide](/references/auth-providers/github) diff --git a/toolkit-docs-generator/curation/github/chunks/005-configuration-setup.mdx b/toolkit-docs-generator/curation/github/chunks/005-configuration-setup.mdx index 6dde0ec40..ddded305f 100644 --- a/toolkit-docs-generator/curation/github/chunks/005-configuration-setup.mdx +++ b/toolkit-docs-generator/curation/github/chunks/005-configuration-setup.mdx @@ -10,7 +10,7 @@ header: "## Configuration & Setup" **Critical**: This MCP Server uses **GitHub Apps** authentication, not OAuth Apps. - + You **must** create a GitHub App to use this server. OAuth Apps are not supported. @@ -27,7 +27,7 @@ The Arcade GitHub MCP Server uses the [GitHub auth provider](/references/auth-pr - Your users will see your application name - **New to GitHub Apps?** Read [Why Arcade Uses GitHub Apps](/references/auth-providers/github#why-arcade-uses-github-apps-not-oauth-apps) + **New to GitHub Apps?** Read [Why Arcade Uses GitHub Apps](/references/auth-providers/github#why-arcade-uses-github-apps-not-oauth-apps) to understand the security and compliance benefits. @@ -48,7 +48,7 @@ GitHub tools read an optional `GITHUB_SERVER_URL` secret from the Arcade Dashboa **Default**: If `GITHUB_SERVER_URL` is not configured, it defaults to `https://api.github.com` (GitHub.com) - + **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. diff --git a/toolkit-docs-generator/curation/jira/pages/environment-variables/page.mdx b/toolkit-docs-generator/curation/jira/pages/environment-variables/page.mdx index c9965fb69..6ddda8cbf 100644 --- a/toolkit-docs-generator/curation/jira/pages/environment-variables/page.mdx +++ b/toolkit-docs-generator/curation/jira/pages/environment-variables/page.mdx @@ -34,3 +34,4 @@ The Arcade Jira MCP Server will cache some values that are repeatedly used in to The value must be a numeric string with an integer greater than or equal to 1. **Default:** `5000` + diff --git a/toolkit-docs-generator/curation/zoom/pages/install/page.mdx b/toolkit-docs-generator/curation/zoom/pages/install/page.mdx index c02dbf6a6..c0fc899f6 100644 --- a/toolkit-docs-generator/curation/zoom/pages/install/page.mdx +++ b/toolkit-docs-generator/curation/zoom/pages/install/page.mdx @@ -109,3 +109,4 @@ If you have any questions or need assistance: - Check our [Zoom MCP Server documentation](/resources/integrations/social-communication/zoom) - [Contact our support team](/resources/contact-us) + diff --git a/toolkit-docs-generator/scripts/extract-curation.ts b/toolkit-docs-generator/scripts/extract-curation.ts deleted file mode 100644 index 599886d05..000000000 --- a/toolkit-docs-generator/scripts/extract-curation.ts +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env npx tsx -/** - * Extract hand-authored prose from committed toolkit artifacts into Markdown - * and MDX source files. - * - * Each documentation chunk becomes one file under - * `curation//chunks/`. Rich subpages keep their generated relative - * path below `curation//pages/`. Structured placement metadata lives - * in frontmatter; the document body is the authored content. - */ -import { mkdir, readdir, readFile, writeFile } from "fs/promises"; -import { basename, dirname, isAbsolute, join } from "path"; -import { fileURLToPath } from "url"; -import { stringify as stringifyYaml } from "yaml"; -import type { DocumentationChunk, ToolkitSubPage } from "../src/types/index"; - -const GENERATOR_ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); -const TOOLKITS_DIR = join(GENERATOR_ROOT, "data", "toolkits"); -const CURATION_DIR = join(GENERATOR_ROOT, "curation"); - -type ToolkitArtifact = { - id: string; - documentationChunks?: DocumentationChunk[]; - subPages?: ToolkitSubPage[]; - tools?: { name: string; documentationChunks?: DocumentationChunk[] }[]; -}; - -const slugify = (value: string): string => - value - .replace(/^#+\s*/, "") - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-|-$/g, "") - .slice(0, 64); - -const frontmatter = (data: Record): string => - `---\n${stringifyYaml(data).trimEnd()}\n---\n`; - -const chunkFileName = (chunk: DocumentationChunk, index: number): string => { - const label = slugify( - chunk.header ?? `${chunk.location}-${chunk.position}-${chunk.type}` - ); - return `${String(index + 1).padStart(3, "0")}-${label || "section"}.mdx`; -}; - -const writeChunk = async ( - toolkitId: string, - chunk: DocumentationChunk, - index: number, - tool?: string -): Promise => { - const { content, ...metadata } = chunk; - const chunksDir = join(CURATION_DIR, toolkitId, "chunks"); - await mkdir(chunksDir, { recursive: true }); - await writeFile( - join(chunksDir, chunkFileName(chunk, index)), - `${frontmatter({ ...metadata, ...(tool ? { tool } : {}) })}${content}\n`, - "utf-8" - ); -}; - -const assertSafeSubPagePath = (relativePath: string): void => { - if ( - isAbsolute(relativePath) || - relativePath.split(/[\\/]/).some((part) => part === ".." || part === "") - ) { - throw new Error(`Unsafe toolkit subpage path: ${relativePath}`); - } -}; - -const writeSubPage = async ( - toolkitId: string, - subPage: ToolkitSubPage -): Promise => { - if (typeof subPage === "string") { - throw new Error( - `Cannot extract legacy subpage without content: ${toolkitId}/${subPage}` - ); - } - assertSafeSubPagePath(subPage.relativePath); - const pagePath = join(CURATION_DIR, toolkitId, "pages", subPage.relativePath); - await mkdir(dirname(pagePath), { recursive: true }); - await writeFile( - pagePath, - `${frontmatter({ type: subPage.type })}${subPage.content}\n`, - "utf-8" - ); -}; - -const extractToolkit = async ( - fileName: string, - toolkit: ToolkitArtifact -): Promise<{ chunks: number; subPages: number }> => { - const toolkitId = basename(fileName, ".json"); - let chunkIndex = 0; - for (const chunk of toolkit.documentationChunks ?? []) { - await writeChunk(toolkitId, chunk, chunkIndex); - chunkIndex += 1; - } - for (const tool of toolkit.tools ?? []) { - for (const chunk of tool.documentationChunks ?? []) { - await writeChunk( - toolkitId, - chunk, - chunkIndex, - `${toolkit.id}.${tool.name}` - ); - chunkIndex += 1; - } - } - - for (const subPage of toolkit.subPages ?? []) { - await writeSubPage(toolkitId, subPage); - } - - return { - chunks: chunkIndex, - subPages: toolkit.subPages?.length ?? 0, - }; -}; - -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 toolkitCount = 0; - let chunkCount = 0; - let subPageCount = 0; - for (const file of files) { - const toolkit = JSON.parse( - await readFile(join(TOOLKITS_DIR, file), "utf-8") - ) as ToolkitArtifact; - const extracted = await extractToolkit(file, toolkit); - if (extracted.chunks > 0 || extracted.subPages > 0) { - toolkitCount += 1; - } - chunkCount += extracted.chunks; - subPageCount += extracted.subPages; - } - - console.log(`Wrote Markdown curation for ${toolkitCount} toolkits.`); - console.log( - ` documentation chunks: ${chunkCount}, subpages: ${subPageCount}` - ); -} - -main().catch((error) => { - console.error("Extraction failed:", error); - process.exit(1); -}); From a1ed068c5161cc6218a157dd1bf54b0ee27014d1 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Tue, 11 Aug 2026 14:09:57 -0400 Subject: [PATCH 10/12] fix: load default curation during generation --- .../src/cli/generate-flow.ts | 46 +++++++++++++++- toolkit-docs-generator/src/cli/index.ts | 53 ++++++------------- .../tests/cli/generate-flow.test.ts | 44 ++++++++++++++- 3 files changed, 105 insertions(+), 38 deletions(-) diff --git a/toolkit-docs-generator/src/cli/generate-flow.ts b/toolkit-docs-generator/src/cli/generate-flow.ts index 0e046f618..aeabff70a 100644 --- a/toolkit-docs-generator/src/cli/generate-flow.ts +++ b/toolkit-docs-generator/src/cli/generate-flow.ts @@ -1,4 +1,48 @@ -import type { ChangeDetectionResult } from "../diff/index"; +import { access } from "fs/promises"; +import { join } from "path"; +import { + type ChangeDetectionResult, + getChangedToolkitIds, +} from "../diff/index"; + +/** + * Resolve an explicit custom-sections path, or use curation/ in the working + * directory when it exists. This keeps regular generation and change checks + * on the same source of truth. + */ +export const resolveCustomSectionsPath = async ( + explicitPath: string | undefined, + workingDir = process.cwd() +): Promise => { + if (explicitPath) { + return explicitPath; + } + + const defaultPath = join(workingDir, "curation"); + try { + await access(defaultPath); + return defaultPath; + } catch { + return; + } +}; + +/** + * Combine API and curation changes using the same case-insensitive toolkit ID + * semantics used by the generation skip set. + */ +export const getCombinedChangedToolkitIds = ( + changeResult: ChangeDetectionResult, + curationChangedToolkitIds: readonly string[] +): string[] => { + const apiChangedIds = getChangedToolkitIds(changeResult).map((id) => + id.toLowerCase() + ); + const curationChangedIds = curationChangedToolkitIds.map((id) => + id.toLowerCase() + ); + return [...new Set([...apiChangedIds, ...curationChangedIds])].sort(); +}; /** * Extract the lowercase toolkit IDs that were removed (present in previous diff --git a/toolkit-docs-generator/src/cli/index.ts b/toolkit-docs-generator/src/cli/index.ts index 92d541c8c..eeaa56693 100644 --- a/toolkit-docs-generator/src/cli/index.ts +++ b/toolkit-docs-generator/src/cli/index.ts @@ -13,7 +13,7 @@ import chalk from "chalk"; import { Command } from "commander"; -import { access, readdir, readFile } from "fs/promises"; +import { readdir, readFile } from "fs/promises"; import ora from "ora"; import { join, resolve } from "path"; import { @@ -84,6 +84,8 @@ import { collectRemovedToolkitIds, computeProcessingStats, filterProvidersBySkipIds, + getCombinedChangedToolkitIds, + resolveCustomSectionsPath, } from "./generate-flow"; const program = new Command(); @@ -250,32 +252,6 @@ const buildChangeLogDetails = ( return details; }; -const resolveCustomSectionsPath = async ( - explicitPath: string | undefined -): Promise => { - if (explicitPath) { - return explicitPath; - } - - const defaultPath = join(process.cwd(), "curation"); - try { - await access(defaultPath); - return defaultPath; - } catch { - return; - } -}; - -const getCombinedChangedToolkitIds = ( - changeResult: ReturnType, - curationChangedToolkitIds: readonly string[] -): string[] => { - const apiChangedIds = getChangedToolkitIds(changeResult).map((id) => - id.toLowerCase() - ); - return [...new Set([...apiChangedIds, ...curationChangedToolkitIds])].sort(); -}; - const clearOutputDir = async ( outputDir: string, verbose: boolean @@ -915,7 +891,7 @@ program .option("--no-verify-output", "Skip output verification") .option( "--custom-sections ", - "Path to the authoritative Markdown/MDX curation directory" + "Path to the authoritative Markdown/MDX curation directory (defaults to ./curation when present)" ) .option( "--resume", @@ -1279,8 +1255,11 @@ program } // Custom sections source - const customSectionsSource = options.customSections - ? createMarkdownCurationSource(options.customSections) + const customSectionsPath = await resolveCustomSectionsPath( + options.customSections + ); + const customSectionsSource = customSectionsPath + ? createMarkdownCurationSource(customSectionsPath) : createEmptyCustomSectionsSource(); // Build provider ID resolver from design system OAuth catalogue @@ -1363,7 +1342,7 @@ program previousToolkits ?? new Map() ); const changedCustomSectionIds = new Set( - options.customSections + customSectionsPath ? getChangedToolkitIdsFromCustomSections( await customSectionsSource.getAllCustomSections(), previousToolkits ?? new Map() @@ -1440,8 +1419,7 @@ program // Get IDs of changed toolkits const changedIds = [ - ...new Set([ - ...getChangedToolkitIds(detectedChanges), + ...getCombinedChangedToolkitIds(detectedChanges, [ ...changedCustomSectionIds, ]), ]; @@ -2028,7 +2006,7 @@ program .option("--no-verify-output", "Skip output verification") .option( "--custom-sections ", - "Path to the authoritative Markdown/MDX curation directory" + "Path to the authoritative Markdown/MDX curation directory (defaults to ./curation when present)" ) .option( "--resume", @@ -2268,8 +2246,11 @@ program } } - const customSectionsSource = options.customSections - ? createMarkdownCurationSource(options.customSections) + const customSectionsPath = await resolveCustomSectionsPath( + options.customSections + ); + const customSectionsSource = customSectionsPath + ? createMarkdownCurationSource(customSectionsPath) : createEmptyCustomSectionsSource(); // Build provider ID resolver from design system OAuth catalogue diff --git a/toolkit-docs-generator/tests/cli/generate-flow.test.ts b/toolkit-docs-generator/tests/cli/generate-flow.test.ts index 75257a978..dc9f06017 100644 --- a/toolkit-docs-generator/tests/cli/generate-flow.test.ts +++ b/toolkit-docs-generator/tests/cli/generate-flow.test.ts @@ -1,9 +1,14 @@ -import { describe, expect, it } from "vitest"; +import { mkdir, mkdtemp, rm } from "fs/promises"; +import { tmpdir } from "os"; +import { join } from "path"; +import { afterEach, describe, expect, it } from "vitest"; import { assertSafeCurrentToolkitSnapshot, collectRemovedToolkitIds, computeProcessingStats, filterProvidersBySkipIds, + getCombinedChangedToolkitIds, + resolveCustomSectionsPath, } from "../../src/cli/generate-flow"; import type { ChangeDetectionResult } from "../../src/diff/index"; import { assertRequireCompleteMetadata } from "../../src/merger/data-merger"; @@ -75,6 +80,43 @@ describe("collectRemovedToolkitIds", () => { }); }); +describe("getCombinedChangedToolkitIds", () => { + it("deduplicates API and curation changes case-insensitively", () => { + expect( + getCombinedChangedToolkitIds( + makeResult([{ toolkitId: "Github", changeType: "modified" }]), + ["github"] + ) + ).toEqual(["github"]); + }); +}); + +describe("resolveCustomSectionsPath", () => { + const tempDirs: string[] = []; + + afterEach(async () => { + await Promise.all( + tempDirs.splice(0).map((dir) => rm(dir, { recursive: true })) + ); + }); + + it("uses curation/ by default when it exists", async () => { + const workingDir = await mkdtemp(join(tmpdir(), "toolkit-curation-")); + tempDirs.push(workingDir); + await mkdir(join(workingDir, "curation")); + + await expect( + resolveCustomSectionsPath(undefined, workingDir) + ).resolves.toBe(join(workingDir, "curation")); + }); + + it("preserves an explicit custom-sections path", async () => { + await expect( + resolveCustomSectionsPath("./custom-prose", "/unused") + ).resolves.toBe("./custom-prose"); + }); +}); + describe("assertSafeCurrentToolkitSnapshot", () => { it("rejects an empty current snapshot when previous output exists", () => { expect(() => assertSafeCurrentToolkitSnapshot(0, 115)).toThrow( From 6319a5ce8ff89709dd2e370581fc9ceb67466010 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Tue, 11 Aug 2026 15:16:56 -0400 Subject: [PATCH 11/12] fix: preserve curated toolkit imports --- toolkit-docs-generator/ARCHITECTURE.md | 8 ++--- toolkit-docs-generator/README.md | 7 ++-- .../curation/airtableapi/imports/001.mdx | 4 +++ .../curation/arcadeengineapi/imports/001.mdx | 4 +++ .../curation/asanaapi/imports/001.mdx | 4 +++ .../curation/ashbyapi/imports/001.mdx | 4 +++ .../curation/boxapi/imports/001.mdx | 4 +++ .../curation/calendlyapi/imports/001.mdx | 4 +++ .../curation/clickupapi/imports/001.mdx | 4 +++ .../curation/cursoragentsapi/imports/001.mdx | 4 +++ .../curation/customerioapi/imports/001.mdx | 4 +++ .../customeriopipelinesapi/imports/001.mdx | 4 +++ .../customeriotrackapi/imports/001.mdx | 4 +++ .../curation/datadogapi/imports/001.mdx | 4 +++ .../curation/exaapi/imports/001.mdx | 4 +++ .../curation/figmaapi/imports/001.mdx | 4 +++ .../curation/freshserviceapi/imports/001.mdx | 4 +++ .../curation/github/imports/001.mdx | 4 +++ .../curation/githubapi/imports/001.mdx | 4 +++ .../curation/gmail/imports/001.mdx | 4 +++ .../curation/googlecalendar/imports/001.mdx | 4 +++ .../curation/googlecontacts/imports/001.mdx | 4 +++ .../curation/googledocs/imports/001.mdx | 4 +++ .../curation/googledrive/imports/001.mdx | 4 +++ .../curation/googlesheets/imports/001.mdx | 4 +++ .../curation/googleslides/imports/001.mdx | 4 +++ .../hubspotautomationapi/imports/001.mdx | 4 +++ .../curation/hubspotcmsapi/imports/001.mdx | 4 +++ .../hubspotconversationsapi/imports/001.mdx | 4 +++ .../curation/hubspotcrmapi/imports/001.mdx | 4 +++ .../curation/hubspoteventsapi/imports/001.mdx | 4 +++ .../hubspotmarketingapi/imports/001.mdx | 4 +++ .../hubspotmeetingsapi/imports/001.mdx | 4 +++ .../curation/hubspotusersapi/imports/001.mdx | 4 +++ .../curation/intercomapi/imports/001.mdx | 4 +++ .../curation/lumaapi/imports/001.mdx | 4 +++ .../mailchimpmarketingapi/imports/001.mdx | 4 +++ .../curation/miroapi/imports/001.mdx | 4 +++ .../curation/pagerdutyapi/imports/001.mdx | 4 +++ .../curation/posthogapi/imports/001.mdx | 4 +++ .../curation/slackapi/imports/001.mdx | 4 +++ .../curation/squareupapi/imports/001.mdx | 4 +++ .../curation/stripeapi/imports/001.mdx | 4 +++ .../curation/ticktickapi/imports/001.mdx | 4 +++ .../curation/trelloapi/imports/001.mdx | 4 +++ .../curation/vercelapi/imports/001.mdx | 4 +++ .../curation/weaviateapi/imports/001.mdx | 4 +++ .../curation/xeroapi/imports/001.mdx | 4 +++ .../curation/zohobooksapi/imports/001.mdx | 4 +++ .../src/sources/markdown-curation.ts | 34 ++++++++++++++++++- .../tests/scenarios/curation-corpus.test.ts | 4 +-- .../prose-survives-force-regenerate.test.ts | 13 ++++++- .../tests/sources/markdown-curation.test.ts | 20 +++++++++++ 53 files changed, 263 insertions(+), 11 deletions(-) create mode 100644 toolkit-docs-generator/curation/airtableapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/arcadeengineapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/asanaapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/ashbyapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/boxapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/calendlyapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/clickupapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/cursoragentsapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/customerioapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/customeriopipelinesapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/customeriotrackapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/datadogapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/exaapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/figmaapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/freshserviceapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/github/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/githubapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/gmail/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/googlecalendar/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/googlecontacts/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/googledocs/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/googledrive/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/googlesheets/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/googleslides/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/hubspotautomationapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/hubspotcmsapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/hubspotconversationsapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/hubspotcrmapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/hubspoteventsapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/hubspotmarketingapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/hubspotmeetingsapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/hubspotusersapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/intercomapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/lumaapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/mailchimpmarketingapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/miroapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/pagerdutyapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/posthogapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/slackapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/squareupapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/stripeapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/ticktickapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/trelloapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/vercelapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/weaviateapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/xeroapi/imports/001.mdx create mode 100644 toolkit-docs-generator/curation/zohobooksapi/imports/001.mdx diff --git a/toolkit-docs-generator/ARCHITECTURE.md b/toolkit-docs-generator/ARCHITECTURE.md index 0b79a565a..968cd6f6c 100644 --- a/toolkit-docs-generator/ARCHITECTURE.md +++ b/toolkit-docs-generator/ARCHITECTURE.md @@ -24,10 +24,10 @@ The generator does **not** render HTML. It produces structured JSON and optional - `EngineApiSource` fetches tool metadata from the Engine API. - `ArcadeApiSource` fetches tool metadata from the Arcade API. - `DesignSystemMetadataSource` loads toolkit metadata from `@arcadeai/design-system`. -- `MarkdownCurationSource` compiles documentation chunks and subpages from the - configured curation directory. When configured, that directory is globally - authoritative: a missing toolkit directory means the toolkit has no authored - curation. +- `MarkdownCurationSource` compiles documentation chunks, import declarations, + and subpages from the configured curation directory. When configured, that + directory is globally authoritative: a missing toolkit directory means the + toolkit has no authored curation. - `CombinedToolkitDataSource` merges tools and metadata into one interface. ### Merger diff --git a/toolkit-docs-generator/README.md b/toolkit-docs-generator/README.md index 7765f1d09..223425986 100644 --- a/toolkit-docs-generator/README.md +++ b/toolkit-docs-generator/README.md @@ -241,9 +241,10 @@ deletes it and rebuilds `index.json`. ## Authored curation Store authored content below `curation//`. Put injectable sections in -`chunks/*.mdx` and rich subpages in `pages/**/*.mdx`. Each file begins with YAML -frontmatter for structured placement metadata; its body is the Markdown or MDX -that readers see. +`chunks/*.mdx`, import declarations in `imports/*.mdx`, and rich subpages in +`pages/**/*.mdx`. Each file begins with YAML frontmatter for structured placement +metadata; its body is the Markdown or MDX that readers see. Import files use +`type: import` and contain one ESM import declaration. When `--custom-sections` is set, the directory is authoritative for every toolkit. Removing the final curation file for a toolkit clears that toolkit's diff --git a/toolkit-docs-generator/curation/airtableapi/imports/001.mdx b/toolkit-docs-generator/curation/airtableapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/airtableapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/arcadeengineapi/imports/001.mdx b/toolkit-docs-generator/curation/arcadeengineapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/arcadeengineapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/asanaapi/imports/001.mdx b/toolkit-docs-generator/curation/asanaapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/asanaapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/ashbyapi/imports/001.mdx b/toolkit-docs-generator/curation/ashbyapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/ashbyapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/boxapi/imports/001.mdx b/toolkit-docs-generator/curation/boxapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/boxapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/calendlyapi/imports/001.mdx b/toolkit-docs-generator/curation/calendlyapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/calendlyapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/clickupapi/imports/001.mdx b/toolkit-docs-generator/curation/clickupapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/clickupapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/cursoragentsapi/imports/001.mdx b/toolkit-docs-generator/curation/cursoragentsapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/cursoragentsapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/customerioapi/imports/001.mdx b/toolkit-docs-generator/curation/customerioapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/customerioapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/customeriopipelinesapi/imports/001.mdx b/toolkit-docs-generator/curation/customeriopipelinesapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/customeriopipelinesapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/customeriotrackapi/imports/001.mdx b/toolkit-docs-generator/curation/customeriotrackapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/customeriotrackapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/datadogapi/imports/001.mdx b/toolkit-docs-generator/curation/datadogapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/datadogapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/exaapi/imports/001.mdx b/toolkit-docs-generator/curation/exaapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/exaapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/figmaapi/imports/001.mdx b/toolkit-docs-generator/curation/figmaapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/figmaapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/freshserviceapi/imports/001.mdx b/toolkit-docs-generator/curation/freshserviceapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/freshserviceapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/github/imports/001.mdx b/toolkit-docs-generator/curation/github/imports/001.mdx new file mode 100644 index 000000000..7fd9ba2fa --- /dev/null +++ b/toolkit-docs-generator/curation/github/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import { Callout, Tabs } from "nextra/components"; diff --git a/toolkit-docs-generator/curation/githubapi/imports/001.mdx b/toolkit-docs-generator/curation/githubapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/githubapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/gmail/imports/001.mdx b/toolkit-docs-generator/curation/gmail/imports/001.mdx new file mode 100644 index 000000000..6826321ed --- /dev/null +++ b/toolkit-docs-generator/curation/gmail/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import ScopePicker from "@/app/_components/scope-picker"; diff --git a/toolkit-docs-generator/curation/googlecalendar/imports/001.mdx b/toolkit-docs-generator/curation/googlecalendar/imports/001.mdx new file mode 100644 index 000000000..6826321ed --- /dev/null +++ b/toolkit-docs-generator/curation/googlecalendar/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import ScopePicker from "@/app/_components/scope-picker"; diff --git a/toolkit-docs-generator/curation/googlecontacts/imports/001.mdx b/toolkit-docs-generator/curation/googlecontacts/imports/001.mdx new file mode 100644 index 000000000..6826321ed --- /dev/null +++ b/toolkit-docs-generator/curation/googlecontacts/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import ScopePicker from "@/app/_components/scope-picker"; diff --git a/toolkit-docs-generator/curation/googledocs/imports/001.mdx b/toolkit-docs-generator/curation/googledocs/imports/001.mdx new file mode 100644 index 000000000..6826321ed --- /dev/null +++ b/toolkit-docs-generator/curation/googledocs/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import ScopePicker from "@/app/_components/scope-picker"; diff --git a/toolkit-docs-generator/curation/googledrive/imports/001.mdx b/toolkit-docs-generator/curation/googledrive/imports/001.mdx new file mode 100644 index 000000000..6826321ed --- /dev/null +++ b/toolkit-docs-generator/curation/googledrive/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import ScopePicker from "@/app/_components/scope-picker"; diff --git a/toolkit-docs-generator/curation/googlesheets/imports/001.mdx b/toolkit-docs-generator/curation/googlesheets/imports/001.mdx new file mode 100644 index 000000000..6826321ed --- /dev/null +++ b/toolkit-docs-generator/curation/googlesheets/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import ScopePicker from "@/app/_components/scope-picker"; diff --git a/toolkit-docs-generator/curation/googleslides/imports/001.mdx b/toolkit-docs-generator/curation/googleslides/imports/001.mdx new file mode 100644 index 000000000..6826321ed --- /dev/null +++ b/toolkit-docs-generator/curation/googleslides/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import ScopePicker from "@/app/_components/scope-picker"; diff --git a/toolkit-docs-generator/curation/hubspotautomationapi/imports/001.mdx b/toolkit-docs-generator/curation/hubspotautomationapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotautomationapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/hubspotcmsapi/imports/001.mdx b/toolkit-docs-generator/curation/hubspotcmsapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotcmsapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/hubspotconversationsapi/imports/001.mdx b/toolkit-docs-generator/curation/hubspotconversationsapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotconversationsapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/hubspotcrmapi/imports/001.mdx b/toolkit-docs-generator/curation/hubspotcrmapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotcrmapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/hubspoteventsapi/imports/001.mdx b/toolkit-docs-generator/curation/hubspoteventsapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/hubspoteventsapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/hubspotmarketingapi/imports/001.mdx b/toolkit-docs-generator/curation/hubspotmarketingapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotmarketingapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/hubspotmeetingsapi/imports/001.mdx b/toolkit-docs-generator/curation/hubspotmeetingsapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotmeetingsapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/hubspotusersapi/imports/001.mdx b/toolkit-docs-generator/curation/hubspotusersapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/hubspotusersapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/intercomapi/imports/001.mdx b/toolkit-docs-generator/curation/intercomapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/intercomapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/lumaapi/imports/001.mdx b/toolkit-docs-generator/curation/lumaapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/lumaapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/mailchimpmarketingapi/imports/001.mdx b/toolkit-docs-generator/curation/mailchimpmarketingapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/mailchimpmarketingapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/miroapi/imports/001.mdx b/toolkit-docs-generator/curation/miroapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/miroapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/pagerdutyapi/imports/001.mdx b/toolkit-docs-generator/curation/pagerdutyapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/pagerdutyapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/posthogapi/imports/001.mdx b/toolkit-docs-generator/curation/posthogapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/posthogapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/slackapi/imports/001.mdx b/toolkit-docs-generator/curation/slackapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/slackapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/squareupapi/imports/001.mdx b/toolkit-docs-generator/curation/squareupapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/squareupapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/stripeapi/imports/001.mdx b/toolkit-docs-generator/curation/stripeapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/stripeapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/ticktickapi/imports/001.mdx b/toolkit-docs-generator/curation/ticktickapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/ticktickapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/trelloapi/imports/001.mdx b/toolkit-docs-generator/curation/trelloapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/trelloapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/vercelapi/imports/001.mdx b/toolkit-docs-generator/curation/vercelapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/vercelapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/weaviateapi/imports/001.mdx b/toolkit-docs-generator/curation/weaviateapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/weaviateapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/xeroapi/imports/001.mdx b/toolkit-docs-generator/curation/xeroapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/xeroapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/curation/zohobooksapi/imports/001.mdx b/toolkit-docs-generator/curation/zohobooksapi/imports/001.mdx new file mode 100644 index 000000000..d325b093d --- /dev/null +++ b/toolkit-docs-generator/curation/zohobooksapi/imports/001.mdx @@ -0,0 +1,4 @@ +--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; diff --git a/toolkit-docs-generator/src/sources/markdown-curation.ts b/toolkit-docs-generator/src/sources/markdown-curation.ts index 809be25c0..ae6a2d5d3 100644 --- a/toolkit-docs-generator/src/sources/markdown-curation.ts +++ b/toolkit-docs-generator/src/sources/markdown-curation.ts @@ -40,6 +40,12 @@ const PageFrontmatterSchema = z }) .strict(); +const ImportFrontmatterSchema = z + .object({ + type: z.literal("import"), + }) + .strict(); + type MarkdownDocument = { body: string; frontmatter: unknown; @@ -182,6 +188,24 @@ const parsePage = async ( }; }; +const parseImport = async (sourcePath: string): Promise => { + const document = parseMarkdownDocument( + await readFile(sourcePath, "utf-8"), + sourcePath + ); + const parsed = ImportFrontmatterSchema.safeParse(document.frontmatter); + if (!parsed.success) { + throw new Error( + `Curation import frontmatter has invalid schema (${sourcePath}): ${parsed.error.message}` + ); + } + await validateMdx(document.body, sourcePath); + if (!/^import(?:\s|\{|\*)/.test(document.body)) { + throw new Error(`Curation import must be an ESM import (${sourcePath})`); + } + return document.body; +}; + const listFilesRecursively = async (dirPath: string): Promise => { const entries = (await readdir(dirPath, { withFileTypes: true })).sort( (left, right) => left.name.localeCompare(right.name) @@ -220,6 +244,7 @@ const loadToolkitDirectory = async ( toolkitId: string ): Promise => { const chunksPath = join(toolkitPath, "chunks"); + const importsPath = join(toolkitPath, "imports"); const pagesPath = join(toolkitPath, "pages"); const allFiles = await listFilesRecursively(toolkitPath); rejectJsonFiles(allFiles); @@ -230,6 +255,9 @@ const loadToolkitDirectory = async ( const pageFiles = allFiles.filter( (file) => file.startsWith(`${pagesPath}${sep}`) && isMarkdownFile(file) ); + const importFiles = allFiles.filter( + (file) => file.startsWith(`${importsPath}${sep}`) && isMarkdownFile(file) + ); const chunks = ( await Promise.all(chunkFiles.map((file) => parseChunk(file, toolkitId))) @@ -260,9 +288,13 @@ const loadToolkitDirectory = async ( throw new Error(`Curation contains duplicate page paths (${toolkitPath})`); } + const customImports = await Promise.all( + importFiles.sort().map((file) => parseImport(file)) + ); + return CustomSectionsSchema.parse({ documentationChunks, - customImports: [], + customImports, subPages, toolChunks, }); diff --git a/toolkit-docs-generator/tests/scenarios/curation-corpus.test.ts b/toolkit-docs-generator/tests/scenarios/curation-corpus.test.ts index 915075e1c..2519ad8e7 100644 --- a/toolkit-docs-generator/tests/scenarios/curation-corpus.test.ts +++ b/toolkit-docs-generator/tests/scenarios/curation-corpus.test.ts @@ -9,7 +9,7 @@ const CURATION_DIR = join(GENERATOR_ROOT, "curation"); const TOOLKITS_DIR = join(GENERATOR_ROOT, "data", "toolkits"); describe("checked-in Markdown curation", () => { - it("reproduces every authored chunk and subpage in committed toolkit data", async () => { + it("reproduces every authored field in committed toolkit data", async () => { const source = createMarkdownCurationSource(CURATION_DIR); const files = (await readdir(TOOLKITS_DIR)) .filter((file) => file.endsWith(".json") && file !== "index.json") @@ -33,7 +33,7 @@ describe("checked-in Markdown curation", () => { ); expect(current.toolChunks, file).toEqual(expectedToolChunks); expect(current.subPages, file).toEqual(toolkit.subPages); - expect(current.customImports, file).toEqual([]); + expect(current.customImports, file).toEqual(toolkit.customImports); chunkCount += current.documentationChunks.length; chunkCount += Object.values(current.toolChunks).reduce( 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 index c9f6b02dd..b7ee5c813 100644 --- a/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts +++ b/toolkit-docs-generator/tests/scenarios/prose-survives-force-regenerate.test.ts @@ -33,6 +33,7 @@ const createTool = (): ToolDefinition => ({ const writeCuration = async (root: string): Promise => { await mkdir(join(root, "testkit/chunks"), { recursive: true }); + await mkdir(join(root, "testkit/imports"), { recursive: true }); await mkdir(join(root, "testkit/pages/environment-variables"), { recursive: true, }); @@ -52,6 +53,14 @@ Hand-authored guidance that has no upstream source. type: environment-variables --- # Environment Variables +` + ); + await writeFile( + join(root, "testkit/imports/starter-tool-info.mdx"), + `--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; ` ); }; @@ -88,7 +97,9 @@ describe("prose survives --force-regenerate", () => { expect(result.toolkit.documentationChunks[0]?.content).toBe( "Hand-authored guidance that has no upstream source." ); - expect(result.toolkit.customImports).toEqual([]); + expect(result.toolkit.customImports).toEqual([ + 'import StarterToolInfo from "@/app/_components/starter-tool-info";', + ]); expect(result.toolkit.subPages).toEqual([ { type: "environment-variables", diff --git a/toolkit-docs-generator/tests/sources/markdown-curation.test.ts b/toolkit-docs-generator/tests/sources/markdown-curation.test.ts index c77c75328..2a0e67b04 100644 --- a/toolkit-docs-generator/tests/sources/markdown-curation.test.ts +++ b/toolkit-docs-generator/tests/sources/markdown-curation.test.ts @@ -108,6 +108,26 @@ type: environment-variables ]); }); + it("loads custom imports from Markdown", async () => { + tempDir = await createTempDir(); + await writeDocument( + tempDir, + "github/imports/01-starter-tool-info.mdx", + `--- +type: import +--- +import StarterToolInfo from "@/app/_components/starter-tool-info"; +` + ); + + const sections = + await createMarkdownCurationSource(tempDir).getCustomSections("Github"); + + expect(sections.customImports).toEqual([ + 'import StarterToolInfo from "@/app/_components/starter-tool-info";', + ]); + }); + it("orders chunks by source path while preserving priority metadata", async () => { tempDir = await createTempDir(); await writeDocument( From 99ec7ab70172bf9dbf5130e318c97c1a789027b9 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Wed, 12 Aug 2026 14:51:33 -0400 Subject: [PATCH 12/12] fix: fail generation on unknown curated tool targets Validate curated `tool:` targets before the recoverable merge path so a mistyped target fails the run instead of landing in preserve-last-known-good recovery, where the toolkit would keep stale data and drop the chunk while CI stayed green. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/merger/data-merger.ts | 16 ++++-- .../tests/merger/data-merger.test.ts | 53 +++++++++++++++++-- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/toolkit-docs-generator/src/merger/data-merger.ts b/toolkit-docs-generator/src/merger/data-merger.ts index 6e407f01c..39268d257 100644 --- a/toolkit-docs-generator/src/merger/data-merger.ts +++ b/toolkit-docs-generator/src/merger/data-merger.ts @@ -819,6 +819,7 @@ const buildMergedToolkit = (options: { }; const assertKnownToolChunkTargets = ( + toolkitId: string, tools: readonly Pick[], customSections: CustomSections | null ): void => { @@ -832,7 +833,7 @@ const assertKnownToolChunkTargets = ( .sort(); if (unknownToolNames.length > 0) { throw new Error( - `Curation targets unknown tool(s): ${unknownToolNames.join(", ")}` + `Curation for ${toolkitId} targets unknown tool(s): ${unknownToolNames.join(", ")}` ); } }; @@ -847,7 +848,7 @@ export const applyCustomSectionsToToolkit = ( return toolkit; } if (!options.ignoreUnknownToolChunks) { - assertKnownToolChunkTargets(toolkit.tools, customSections); + assertKnownToolChunkTargets(toolkit.id, toolkit.tools, customSections); } return { @@ -978,7 +979,7 @@ export const mergeToolkit = async ( const warnings: string[] = []; const failedTools: FailedTool[] = []; - assertKnownToolChunkTargets(tools, customSections); + assertKnownToolChunkTargets(toolkitId, tools, customSections); appendMergeWarnings(warnings, toolkitId, tools, metadata); @@ -1222,6 +1223,15 @@ export class DataMerger { // so invalid source cannot silently preserve stale generated prose. const customSections = await this.customSectionsSource.getCustomSections(toolkitId); + // A `tool:` target that matches no tool is an authoring mistake, so it + // belongs outside the recoverable path below: there it would read as an + // upstream failure, and the run would stay green while that toolkit + // silently kept stale data and dropped the mistargeted chunk. A toolkit + // the API returned no tools for is the outage that recovery exists for, + // and it says nothing about whether the curation is correct. + if (toolkitData.tools.length > 0) { + assertKnownToolChunkTargets(toolkitId, toolkitData.tools, customSections); + } try { const recovered = await this.recoverMissingMetadata( toolkitId, diff --git a/toolkit-docs-generator/tests/merger/data-merger.test.ts b/toolkit-docs-generator/tests/merger/data-merger.test.ts index 68d2f21b8..d8e0a2940 100644 --- a/toolkit-docs-generator/tests/merger/data-merger.test.ts +++ b/toolkit-docs-generator/tests/merger/data-merger.test.ts @@ -987,7 +987,9 @@ describe("mergeToolkit overview chunk handling", () => { }), undefined ) - ).rejects.toThrow("Curation targets unknown tool(s): MissingTool"); + ).rejects.toThrow( + "Curation for TestKit targets unknown tool(s): MissingTool" + ); }); it("keeps toolkit-level overview chunks from source custom sections", async () => { @@ -2235,18 +2237,20 @@ describe("DataMerger", () => { ); const curation = createCustomSections({ toolChunks: { - NewlyAddedTool: [ + SetStarred: [ { type: "info", location: "parameters", position: "after", - content: "Applies after the API recovers.", + content: "Applies once the prior artifact catches up.", }, ], }, }); + // The API exposes SetStarred, so the curation is valid; only the + // preserved artifact predates it. const toolkitDataSource = createCombinedToolkitDataSource({ - toolSource: new InMemoryToolDataSource([githubTool1]), + toolSource: new InMemoryToolDataSource([githubTool1, githubTool2]), metadataSource: new InMemoryMetadataSource([]), }); const merger = new DataMerger({ @@ -2268,6 +2272,47 @@ describe("DataMerger", () => { ); }); + it("fails the run when curation targets a tool the API does not expose, even with preserveLastKnownGood", async () => { + // A mistyped `tool:` target is an authoring mistake, not an upstream + // outage. Recovering from it would leave the nightly job green while + // the toolkit kept stale data and lost the chunk. + const previous = await mergeToolkit( + "Github", + [githubTool1], + githubMetadata, + createCustomSections(), + createStubGenerator() + ); + const toolkitDataSource = createCombinedToolkitDataSource({ + toolSource: new InMemoryToolDataSource([githubTool1]), + metadataSource: new InMemoryMetadataSource([githubMetadata]), + }); + const merger = new DataMerger({ + toolkitDataSource, + customSectionsSource: new InMemoryCustomSectionsSource({ + Github: createCustomSections({ + toolChunks: { + CreateIsue: [ + { + type: "info", + location: "parameters", + position: "after", + content: "Typo in the target tool name.", + }, + ], + }, + }), + }), + toolExampleGenerator: createStubGenerator(), + previousToolkits: new Map([["github", previous.toolkit]]), + preserveLastKnownGood: true, + }); + + await expect(merger.mergeAllToolkits()).rejects.toThrow( + "Curation for Github targets unknown tool(s): CreateIsue" + ); + }); + it("preserves prior output for provider-mode generation when metadata is missing", async () => { const previous = await mergeToolkit( "Github",