diff --git a/integrations/catalog/airtable.json b/integrations/catalog/airtable.json index 3526737..76b8033 100644 --- a/integrations/catalog/airtable.json +++ b/integrations/catalog/airtable.json @@ -30,7 +30,9 @@ "key": "AIRTABLE_API_KEY", "label": "Airtable personal access token", "type": "password", - "required": true + "required": true, + "helperText": "Personal access token from your Airtable account.", + "helperLink": "https://airtable.com/create/tokens" } ] }, diff --git a/integrations/catalog/apify.json b/integrations/catalog/apify.json index 75c6c5e..42b2957 100644 --- a/integrations/catalog/apify.json +++ b/integrations/catalog/apify.json @@ -29,7 +29,9 @@ "key": "APIFY_TOKEN", "label": "Apify token", "type": "password", - "required": true + "required": true, + "helperText": "API token from your Apify account settings.", + "helperLink": "https://console.apify.com/account/integrations" } ] }, diff --git a/integrations/catalog/brave-search.json b/integrations/catalog/brave-search.json index f161f48..073bd89 100644 --- a/integrations/catalog/brave-search.json +++ b/integrations/catalog/brave-search.json @@ -29,7 +29,9 @@ "key": "BRAVE_API_KEY", "label": "Brave API key", "type": "password", - "required": true + "required": true, + "helperText": "API key from the Brave Search API portal.", + "helperLink": "https://api-dashboard.search.brave.com/app/keys" } ] }, diff --git a/integrations/catalog/clickhouse.json b/integrations/catalog/clickhouse.json index 160cffd..4d6e64e 100644 --- a/integrations/catalog/clickhouse.json +++ b/integrations/catalog/clickhouse.json @@ -42,7 +42,9 @@ "key": "CLICKHOUSE_PASSWORD", "label": "Password", "type": "password", - "required": true + "required": true, + "helperText": "Password for your ClickHouse user.", + "helperLink": "https://clickhouse.com/docs/operations/access-rights" } ] }, diff --git a/integrations/catalog/elevenlabs.json b/integrations/catalog/elevenlabs.json index fc87a9e..bd84422 100644 --- a/integrations/catalog/elevenlabs.json +++ b/integrations/catalog/elevenlabs.json @@ -28,7 +28,9 @@ "key": "ELEVENLABS_API_KEY", "label": "ElevenLabs API key", "type": "password", - "required": true + "required": true, + "helperText": "API key from your ElevenLabs account settings.", + "helperLink": "https://elevenlabs.io/app/settings/api-keys" } ] }, diff --git a/integrations/catalog/exa.json b/integrations/catalog/exa.json index adf637f..f48473c 100644 --- a/integrations/catalog/exa.json +++ b/integrations/catalog/exa.json @@ -29,7 +29,9 @@ "key": "EXA_API_KEY", "label": "Exa API key", "type": "password", - "required": true + "required": true, + "helperText": "API key from your Exa account.", + "helperLink": "https://exa.ai/docs/reference/getting-started" } ] }, diff --git a/integrations/catalog/figma.json b/integrations/catalog/figma.json index f52460d..61d1e6b 100644 --- a/integrations/catalog/figma.json +++ b/integrations/catalog/figma.json @@ -30,7 +30,9 @@ "key": "FIGMA_API_KEY", "label": "Figma personal access token", "type": "password", - "required": true + "required": true, + "helperText": "Personal access token from your Figma account settings.", + "helperLink": "https://developers.figma.com/docs/rest-api/authentication/#personal-access-tokens" } ] }, diff --git a/integrations/catalog/firecrawl.json b/integrations/catalog/firecrawl.json index 98daf79..108aff2 100644 --- a/integrations/catalog/firecrawl.json +++ b/integrations/catalog/firecrawl.json @@ -29,7 +29,9 @@ "key": "FIRECRAWL_API_KEY", "label": "Firecrawl API key", "type": "password", - "required": true + "required": true, + "helperText": "API key from your Firecrawl dashboard.", + "helperLink": "https://www.firecrawl.dev/app/api-keys" } ] }, diff --git a/integrations/catalog/github.json b/integrations/catalog/github.json index 5fb95c9..5dd3cc1 100644 --- a/integrations/catalog/github.json +++ b/integrations/catalog/github.json @@ -37,7 +37,9 @@ "label": "Personal access token", "type": "password", "placeholder": "github_pat_...", - "required": true + "required": true, + "helperText": "Classic or fine-grained personal access token from GitHub settings.", + "helperLink": "https://github.com/settings/tokens" } ] }, diff --git a/integrations/catalog/kagi.json b/integrations/catalog/kagi.json index 9a727e4..138a048 100644 --- a/integrations/catalog/kagi.json +++ b/integrations/catalog/kagi.json @@ -28,7 +28,9 @@ "key": "KAGI_API_KEY", "label": "Kagi API key", "type": "password", - "required": true + "required": true, + "helperText": "API key from your Kagi account settings.", + "helperLink": "https://kagi.com/settings?p=api" } ] }, diff --git a/integrations/catalog/mongodb.json b/integrations/catalog/mongodb.json index 6a6b5f0..0c23004 100644 --- a/integrations/catalog/mongodb.json +++ b/integrations/catalog/mongodb.json @@ -29,7 +29,9 @@ "label": "MongoDB connection string", "type": "password", "placeholder": "mongodb+srv://user:pass@cluster.mongodb.net", - "required": true + "required": true, + "helperText": "Connection string from your MongoDB Atlas cluster.", + "helperLink": "https://www.mongodb.com/docs/atlas/connect-to-database-deployment/#connect-with-a-connection-string" } ] }, diff --git a/integrations/catalog/neon.json b/integrations/catalog/neon.json index 38e729f..0050ef6 100644 --- a/integrations/catalog/neon.json +++ b/integrations/catalog/neon.json @@ -30,7 +30,9 @@ "key": "NEON_API_KEY", "label": "Neon API key", "type": "password", - "required": true + "required": true, + "helperText": "API key from your Neon account settings.", + "helperLink": "https://neon.tech/docs/manage/api-keys" } ] }, diff --git a/integrations/catalog/notion.json b/integrations/catalog/notion.json index 65c76cc..5c8d316 100644 --- a/integrations/catalog/notion.json +++ b/integrations/catalog/notion.json @@ -32,7 +32,9 @@ "label": "Internal integration token", "type": "password", "placeholder": "ntn_...", - "required": true + "required": true, + "helperText": "Internal integration token from your Notion integrations page.", + "helperLink": "https://www.notion.so/profile/integrations" } ] }, diff --git a/integrations/catalog/obsidian.json b/integrations/catalog/obsidian.json index 10d3812..7c85f6d 100644 --- a/integrations/catalog/obsidian.json +++ b/integrations/catalog/obsidian.json @@ -30,7 +30,8 @@ "label": "Local REST API key", "type": "password", "required": true, - "helperText": "From the Obsidian 'Local REST API' community plugin." + "helperText": "From the Obsidian 'Local REST API' community plugin.", + "helperLink": "https://github.com/coddingtonbear/obsidian-local-rest-api" } ] }, diff --git a/integrations/catalog/redis.json b/integrations/catalog/redis.json index 44bda27..3b4185d 100644 --- a/integrations/catalog/redis.json +++ b/integrations/catalog/redis.json @@ -33,7 +33,8 @@ "type": "password", "placeholder": "redis://localhost:6379", "required": true, - "helperText": "Appended as --url ." + "helperText": "Connection URL for your Redis instance. Appended as --url .", + "helperLink": "https://redis.io/docs/latest/operate/rc/databases/connect/" } ] }, diff --git a/integrations/catalog/resend.json b/integrations/catalog/resend.json index 7b54452..af4c3ff 100644 --- a/integrations/catalog/resend.json +++ b/integrations/catalog/resend.json @@ -28,7 +28,9 @@ "key": "RESEND_API_KEY", "label": "Resend API key", "type": "password", - "required": true + "required": true, + "helperText": "API key from your Resend dashboard.", + "helperLink": "https://resend.com/api-keys" }, { "key": "SENDER_EMAIL_ADDRESS", diff --git a/integrations/catalog/slack.json b/integrations/catalog/slack.json index a28ab5c..b4db8c1 100644 --- a/integrations/catalog/slack.json +++ b/integrations/catalog/slack.json @@ -31,7 +31,9 @@ "label": "Bot token", "type": "password", "placeholder": "xoxb-...", - "required": true + "required": true, + "helperText": "Bot token from your Slack app's OAuth & Permissions page.", + "helperLink": "https://api.slack.com/apps" }, { "key": "SLACK_TEAM_ID", diff --git a/integrations/catalog/supabase.json b/integrations/catalog/supabase.json index 7791795..0992e1b 100644 --- a/integrations/catalog/supabase.json +++ b/integrations/catalog/supabase.json @@ -30,7 +30,8 @@ "label": "Supabase access token", "type": "password", "required": true, - "helperText": "Personal access token from your Supabase dashboard." + "helperText": "Personal access token from your Supabase dashboard.", + "helperLink": "https://supabase.com/dashboard/account/tokens" } ] }, diff --git a/integrations/catalog/tavily.json b/integrations/catalog/tavily.json index 5ec8b61..5d6af85 100644 --- a/integrations/catalog/tavily.json +++ b/integrations/catalog/tavily.json @@ -11,7 +11,7 @@ "research" ], "popularityRank": 90, - "installHint": "Paste your Tavily API key — the official tavily-mcp package runs via npx.", + "installHint": "Paste your Tavily API key - the official tavily-mcp package runs via npx.", "kind": "mcp", "defaultConnectionOptionId": "api", "connectionOptions": [ @@ -32,7 +32,9 @@ "label": "Tavily API key", "type": "password", "placeholder": "tvly-...", - "required": true + "required": true, + "helperText": "API key from your Tavily dashboard.", + "helperLink": "https://app.tavily.com/home?tab=keys" } ] }, diff --git a/integrations/index.d.ts b/integrations/index.d.ts index c94be8c..b53fa86 100644 --- a/integrations/index.d.ts +++ b/integrations/index.d.ts @@ -6,6 +6,7 @@ export interface MarketplaceField { type?: MarketplaceFieldType; placeholder?: string; helperText?: string; + helperLink?: string; required?: boolean; } diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index 3f533c2..c36362a 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -71,6 +71,29 @@ def test_catalog_entries_have_required_fields(): assert isinstance(entry["estimatedSetupMinutes"], int) +def test_credential_fields_have_helper_text_and_link(): + """All password fields must have helperText and helperLink so users know how to get credentials.""" + for entry in load_catalog_entries("integrations/catalog"): + for option in entry["connectionOptions"]: + transport = option.get("transport", {}) + for field_group in ("envFields", "argFields"): + for field in transport.get(field_group, []): + if field.get("type") == "password": + field_key = field.get("key", "") + assert "helperText" in field, ( + f"{entry['id']}: password field '{field_key}' is missing helperText" + ) + assert "helperLink" in field, ( + f"{entry['id']}: password field '{field_key}' is missing helperLink" + ) + assert field["helperText"], ( + f"{entry['id']}: password field '{field_key}' has empty helperText" + ) + assert field["helperLink"].startswith("https://"), ( + f"{entry['id']}: password field '{field_key}' helperLink must start with https://" + ) + + def test_node_package_exports_catalogs(): script = """ import { INTEGRATION_CATALOG, AUTOMATION_CATALOG } from './index.js';