diff --git a/CHANGELOG.md b/CHANGELOG.md index aeeb187d..bc05d4cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,57 @@ they are no longer release versions. Phase scope now lives in ## [Unreleased] +### Added +- **Native MCP catalog batch 3: 136 more providers.** Harvested from the + official MCP registry (54k entries swept), the claude.com/connectors + directory, and community remote-MCP lists; every endpoint live-probed + (MCP `initialize` → `/.well-known/oauth-protected-resource` → + auth-server metadata → `registration_endpoint`) on 2026-07-18: + - **TAS-managed DCR (123):** sales/GTM (Outreach, Salesloft, ZoomInfo, + Lusha, Hunter, Instantly, Crossbeam, Harmonic, Chili Piper, Day AI, + Clarify, Staircase AI), support/CX (Zendesk, Help Scout, Gorgias, Plain, + Lorikeet, Unthread, Enterpret, Dovetail, Missive), meetings (Otter.ai, + Grain, Krisp, Circleback, tl;dv), finance (Ramp, Brex, Mercury, + Expensify, Navan, Carta, Digits, GoCardless, Mercado Pago), market + intelligence (PitchBook, Morningstar, CB Insights, Quartr, Daloopa, + Consensus), HR/recruiting (Gusto, Deel, Ashby, Workable, Metaview, + Indeed, Udemy Business), compliance/e-sign (Vanta, Drata, SignNow), + productivity/design (Figma, Miro, Lucid, Productboard, Aha!, Shortcut, + Todoist, Teamwork, Calendly, Superhuman Mail, Craft, Mem, Gamma, Pitch, + Eraser, Jotform, Typeform, SurveyMonkey, Egnyte), marketing/content + (Mailchimp, Customer.io, Ahrefs, Semrush, Cloudinary, Contentful, + Sanity, Wix, WordPress.com, GitBook, Mintlify, DeepL), dev/infra + (GitLab, Supabase, Netlify, Heroku, Buildkite, Grafana, New Relic, + Honeycomb, incident.io, Rootly, BugSnag, LaunchDarkly, PlanetScale, + Prisma Postgres, InstantDB, Algolia, Statsig, Postman, Semgrep, WorkOS, + Stytch, Mux, Knock, Lovable, Retool, Telnyx, Jam, Globalping), data/AI + (Airbyte, MotherDuck, Monte Carlo, Atlan, Hugging Face), and + automation/web (Zapier, Make, IFTTT, Exa, Tavily, Firecrawl, Apify, + Bright Data). + - **Bring-your-own OAuth app (12):** DocuSign, Xero, Front, Smartsheet, + MongoDB Atlas, CircleCI, Chargebee, BigQuery (Google manual client, + like Gmail), Ironclad, Harvey, Tableau, Shopify. + - **API token (1):** Render (API key as Bearer, like GitHub). + - Agent Library categories now recognize the new providers, and the + long-tail **ATS / recruiting**, **HRIS**, **E-signature**, and + **Survey** categories flip to connectable. + - Not added (with reasons): per-tenant instance-scoped servers + (Salesforce, Snowflake, Databricks, ServiceNow, NetSuite, Glean, dbt, + Elastic…), OAuth `client_credentials`-only (Plaid), unauthenticated / + docs-only servers, and vendors with no hosted server (Workday, + Rippling, Okta, Snyk, Perplexity, Loom, Fivetran). + +### Changed +- **Rust OAuth-origin allowlist is now generated from the web catalog.** + `api/src/native_oauth_allowlist.rs` is produced from `MCP_PROVIDERS` + (`web/src/lib/mcp-providers.ts`) by `npm run gen:allowlist`, replacing the + hand-maintained duplicate (~360 lines of consts + tuples) in + `native_oauth.rs`. The allowlist-sync vitest is now a staleness check on the + generated file instead of a per-provider drift detector — the failure mode + where a catalog entry lands without its Rust twin (the Dialed regression) + is eliminated rather than just alarmed on. No behavior change: the generated + table is semantically identical to the old hand list (184 origins). + ## [v2026.7.2] — Native MCP catalog expansion, connection search, Zoom, inbox delete — shipped 2026-07-15 ### Added diff --git a/api/src/main.rs b/api/src/main.rs index 888007af..aaf01eb4 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -14,6 +14,7 @@ use tracing_subscriber::EnvFilter; mod auth; mod crypto; mod native_oauth; +mod native_oauth_allowlist; mod pricing; mod routes; mod runs; diff --git a/api/src/native_oauth.rs b/api/src/native_oauth.rs index e6426779..85a39394 100644 --- a/api/src/native_oauth.rs +++ b/api/src/native_oauth.rs @@ -36,217 +36,11 @@ use crate::crypto::MasterKey; /// first tool call. const REFRESH_SKEW_SECS: i64 = 120; -// Native-MCP providers TAS can refresh tokens for, as -// (mcp_server_url origin → allowed OAuth authorization-server origins). -// MUST mirror the web catalog in web/src/lib/mcp-providers.ts -// (mcpServerUrl + oauthAuthorizationServerOrigins) — keep both in sync -// when adding a provider, or refreshes for the new provider abort and its -// short-lived tokens 401 mid-run. -const ATTIO_MCP_ORIGIN: &str = "https://mcp.attio.com"; -const ATTIO_OAUTH_ORIGINS: &[&str] = &["https://app.attio.com"]; -const PYLON_MCP_ORIGIN: &str = "https://mcp.usepylon.com"; -const PYLON_OAUTH_ORIGINS: &[&str] = &["https://o.auth.usepylon.com"]; -// HubSpot advertises its auth server as the MCP origin itself and uses a -// CONFIDENTIAL client (no DCR) — the refresh below presents client_id + -// client_secret from workspace_native_oauth_client when the connection's -// metadata says auth_mode=manual. -const HUBSPOT_MCP_ORIGIN: &str = "https://mcp.hubspot.com"; -const HUBSPOT_OAUTH_ORIGINS: &[&str] = &["https://mcp.hubspot.com"]; -// Fathom's auth server is api.fathom.ai but its authorize endpoint lives on -// fathom.video — both origins are allowed (matches the web catalog). -const FATHOM_MCP_ORIGIN: &str = "https://api.fathom.ai"; -const FATHOM_OAUTH_ORIGINS: &[&str] = &["https://api.fathom.ai", "https://fathom.video"]; -// Dialed advertises its auth server as the apex (also the MCP origin). -const DIALED_MCP_ORIGIN: &str = "https://dialed.day"; -const DIALED_OAUTH_ORIGINS: &[&str] = &["https://dialed.day"]; -// Linear advertises its auth server as the MCP origin itself (DCR, public -// client, read+write scopes). Docs: https://linear.app/docs/mcp -const LINEAR_MCP_ORIGIN: &str = "https://mcp.linear.app"; -const LINEAR_OAUTH_ORIGINS: &[&str] = &["https://mcp.linear.app"]; -// Amplemarket advertises its auth server as https://app.amplemarket.com (DCR, -// public client, PKCE S256, scopes mcp:read/mcp:write) — TAS-managed, like Attio. -const AMPLEMARKET_MCP_ORIGIN: &str = "https://mcp.amplemarket.com"; -const AMPLEMARKET_OAUTH_ORIGINS: &[&str] = &["https://app.amplemarket.com"]; -// Clay advertises api.clay.com as its auth server (DCR, public client, PKCE S256, -// scope "mcp"), but its authorize endpoint lives on app.clay.com while -// token/registration sit on api.clay.com — both origins allowed (like Fathom). -const CLAY_MCP_ORIGIN: &str = "https://api.clay.com"; -const CLAY_OAUTH_ORIGINS: &[&str] = &["https://api.clay.com", "https://app.clay.com"]; -// Avoma advertises its auth server as https://prod-api.avoma.com (DCR, PKCE S256, -// refresh_token grant; external_api:* scopes via the catalog scopeOverride). -const AVOMA_MCP_ORIGIN: &str = "https://mcp.avoma.com"; -const AVOMA_OAUTH_ORIGINS: &[&str] = &["https://prod-api.avoma.com"]; -// Gmail (Google Workspace MCP) is a confidential/manual client on standard -// Google OAuth: the auth server is accounts.google.com but its TOKEN endpoint -// lives on a separate origin (oauth2.googleapis.com) — both must be trusted so -// the refresh-before-use sweep can renew the access token. -const GMAIL_MCP_ORIGIN: &str = "https://gmailmcp.googleapis.com"; -const GMAIL_OAUTH_ORIGINS: &[&str] = &[ - "https://accounts.google.com", - "https://oauth2.googleapis.com", -]; -// Batch sourced from anthropics/knowledge-work-plugins .mcp.json; each confirmed -// DCR (registration_endpoint at the MCP origin's auth-server metadata). Must -// stay in lockstep with the web catalog (mcp-providers.ts). -const NOTION_MCP_ORIGIN: &str = "https://mcp.notion.com"; -const NOTION_OAUTH_ORIGINS: &[&str] = &["https://mcp.notion.com"]; -const INTERCOM_MCP_ORIGIN: &str = "https://mcp.intercom.com"; -const INTERCOM_OAUTH_ORIGINS: &[&str] = &["https://mcp.intercom.com"]; -const ATLASSIAN_MCP_ORIGIN: &str = "https://mcp.atlassian.com"; -const ATLASSIAN_OAUTH_ORIGINS: &[&str] = - &["https://mcp.atlassian.com", "https://cf.mcp.atlassian.com"]; -const ASANA_MCP_ORIGIN: &str = "https://mcp.asana.com"; -const ASANA_OAUTH_ORIGINS: &[&str] = &["https://mcp.asana.com"]; -const MONDAY_MCP_ORIGIN: &str = "https://mcp.monday.com"; -const MONDAY_OAUTH_ORIGINS: &[&str] = &["https://mcp.monday.com"]; -const GURU_MCP_ORIGIN: &str = "https://mcp.api.getguru.com"; -const GURU_OAUTH_ORIGINS: &[&str] = &["https://mcp.api.getguru.com"]; -const FIREFLIES_MCP_ORIGIN: &str = "https://api.fireflies.ai"; -const FIREFLIES_OAUTH_ORIGINS: &[&str] = &["https://api.fireflies.ai"]; -const AMPLITUDE_MCP_ORIGIN: &str = "https://mcp.amplitude.com"; -const AMPLITUDE_OAUTH_ORIGINS: &[&str] = &["https://mcp.amplitude.com"]; -const APOLLO_MCP_ORIGIN: &str = "https://mcp.apollo.io"; -const APOLLO_OAUTH_ORIGINS: &[&str] = &["https://mcp.apollo.io"]; -// PostHog hosted MCP: auth server is oauth.posthog.com (region-routes US/EU). -// Docs: https://posthog.com/docs/model-context-protocol -const POSTHOG_MCP_ORIGIN: &str = "https://mcp.posthog.com"; -const POSTHOG_OAUTH_ORIGINS: &[&str] = &["https://oauth.posthog.com"]; -// Stripe hosted MCP: issuer is access.stripe.com/mcp; metadata also on mcp.stripe.com. -// Docs: https://docs.stripe.com/mcp -const STRIPE_MCP_ORIGIN: &str = "https://mcp.stripe.com"; -const STRIPE_OAUTH_ORIGINS: &[&str] = &["https://access.stripe.com", "https://mcp.stripe.com"]; -// ── Hosted OAuth MCP batch (must match web/src/lib/mcp-providers.ts) ── -const VERCEL_MCP_ORIGIN: &str = "https://mcp.vercel.com"; -const VERCEL_OAUTH_ORIGINS: &[&str] = &["https://mcp.vercel.com", "https://vercel.com"]; -const CANVA_MCP_ORIGIN: &str = "https://mcp.canva.com"; -const CANVA_OAUTH_ORIGINS: &[&str] = &["https://mcp.canva.com"]; -const CLICKUP_MCP_ORIGIN: &str = "https://mcp.clickup.com"; -const CLICKUP_OAUTH_ORIGINS: &[&str] = &["https://mcp.clickup.com"]; -const CLOSE_MCP_ORIGIN: &str = "https://mcp.close.com"; -const CLOSE_OAUTH_ORIGINS: &[&str] = &[ - "https://mcp.close.com", - "https://api.close.com", - "https://app.close.com", -]; -const SENTRY_MCP_ORIGIN: &str = "https://mcp.sentry.dev"; -const SENTRY_OAUTH_ORIGINS: &[&str] = &["https://mcp.sentry.dev"]; -const MIXPANEL_MCP_ORIGIN: &str = "https://mcp.mixpanel.com"; -const MIXPANEL_OAUTH_ORIGINS: &[&str] = &["https://mcp.mixpanel.com", "https://mixpanel.com"]; -const GRANOLA_MCP_ORIGIN: &str = "https://mcp.granola.ai"; -const GRANOLA_OAUTH_ORIGINS: &[&str] = &["https://mcp.granola.ai", "https://mcp-auth.granola.ai"]; -const DROPBOX_MCP_ORIGIN: &str = "https://mcp.dropbox.com"; -const DROPBOX_OAUTH_ORIGINS: &[&str] = &[ - "https://mcp.dropbox.com", - "https://www.dropbox.com", - "https://api.dropboxapi.com", -]; -const WEBFLOW_MCP_ORIGIN: &str = "https://mcp.webflow.com"; -const WEBFLOW_OAUTH_ORIGINS: &[&str] = &["https://mcp.webflow.com"]; -const CLOUDFLARE_MCP_ORIGIN: &str = "https://mcp.cloudflare.com"; -const CLOUDFLARE_OAUTH_ORIGINS: &[&str] = &["https://mcp.cloudflare.com"]; -const NEON_MCP_ORIGIN: &str = "https://mcp.neon.tech"; -const NEON_OAUTH_ORIGINS: &[&str] = &["https://mcp.neon.tech"]; -const CAL_MCP_ORIGIN: &str = "https://mcp.cal.com"; -const CAL_OAUTH_ORIGINS: &[&str] = &["https://mcp.cal.com"]; -const KLAVIYO_MCP_ORIGIN: &str = "https://mcp.klaviyo.com"; -const KLAVIYO_OAUTH_ORIGINS: &[&str] = &["https://mcp.klaviyo.com"]; -const PAYPAL_MCP_ORIGIN: &str = "https://mcp.paypal.com"; -const PAYPAL_OAUTH_ORIGINS: &[&str] = &["https://mcp.paypal.com"]; -const SQUARE_MCP_ORIGIN: &str = "https://mcp.squareup.com"; -const SQUARE_OAUTH_ORIGINS: &[&str] = &["https://mcp.squareup.com"]; -const AIRTABLE_MCP_ORIGIN: &str = "https://mcp.airtable.com"; -const AIRTABLE_OAUTH_ORIGINS: &[&str] = &["https://mcp.airtable.com", "https://airtable.com"]; -const RAILWAY_MCP_ORIGIN: &str = "https://mcp.railway.app"; -const RAILWAY_OAUTH_ORIGINS: &[&str] = - &["https://mcp.railway.app", "https://backboard.railway.com"]; -const RESEND_MCP_ORIGIN: &str = "https://mcp.resend.com"; -const RESEND_OAUTH_ORIGINS: &[&str] = &["https://mcp.resend.com", "https://api.resend.com"]; -const HEX_MCP_ORIGIN: &str = "https://app.hex.tech"; -const HEX_OAUTH_ORIGINS: &[&str] = &["https://app.hex.tech", "https://auth.app.hex.tech"]; -const PENDO_MCP_ORIGIN: &str = "https://app.pendo.io"; -const PENDO_OAUTH_ORIGINS: &[&str] = &["https://app.pendo.io"]; -const SIMILARWEB_MCP_ORIGIN: &str = "https://mcp.similarweb.com"; -const SIMILARWEB_OAUTH_ORIGINS: &[&str] = &[ - "https://mcp.similarweb.com", - "https://mcp-auth.similarweb.com", -]; -const DATADOG_MCP_ORIGIN: &str = "https://mcp.datadoghq.com"; -const DATADOG_OAUTH_ORIGINS: &[&str] = &["https://mcp.datadoghq.com", "https://app.datadoghq.com"]; -const COMMONROOM_MCP_ORIGIN: &str = "https://mcp.commonroom.io"; -const COMMONROOM_OAUTH_ORIGINS: &[&str] = - &["https://mcp.commonroom.io", "https://login.commonroom.io"]; -const GONG_MCP_ORIGIN: &str = "https://mcp.gong.io"; -const GONG_OAUTH_ORIGINS: &[&str] = &["https://mcp.gong.io", "https://app.gong.io"]; -const BOX_MCP_ORIGIN: &str = "https://mcp.box.com"; -const BOX_OAUTH_ORIGINS: &[&str] = &[ - "https://mcp.box.com", - "https://api.box.com", - "https://account.box.com", -]; -const PAGERDUTY_MCP_ORIGIN: &str = "https://mcp.pagerduty.com"; -const PAGERDUTY_OAUTH_ORIGINS: &[&str] = - &["https://mcp.pagerduty.com", "https://app.pagerduty.com"]; -const SLACK_MCP_ORIGIN: &str = "https://mcp.slack.com"; -const SLACK_OAUTH_ORIGINS: &[&str] = &["https://mcp.slack.com", "https://slack.com"]; -// Zoom meetings MCP (streamable); AS is zoom.us (client_secret_basic only). -const ZOOM_MCP_ORIGIN: &str = "https://mcp.zoom.us"; -const ZOOM_OAUTH_ORIGINS: &[&str] = &[ - "https://zoom.us", - "https://mcp.zoom.us", - "https://mcp-us.zoom.us", -]; - -const NATIVE_MCP_OAUTH_ALLOWLIST: &[(&str, &[&str])] = &[ - (ATTIO_MCP_ORIGIN, ATTIO_OAUTH_ORIGINS), - (PYLON_MCP_ORIGIN, PYLON_OAUTH_ORIGINS), - (HUBSPOT_MCP_ORIGIN, HUBSPOT_OAUTH_ORIGINS), - (FATHOM_MCP_ORIGIN, FATHOM_OAUTH_ORIGINS), - (DIALED_MCP_ORIGIN, DIALED_OAUTH_ORIGINS), - (LINEAR_MCP_ORIGIN, LINEAR_OAUTH_ORIGINS), - (AMPLEMARKET_MCP_ORIGIN, AMPLEMARKET_OAUTH_ORIGINS), - (CLAY_MCP_ORIGIN, CLAY_OAUTH_ORIGINS), - (AVOMA_MCP_ORIGIN, AVOMA_OAUTH_ORIGINS), - (GMAIL_MCP_ORIGIN, GMAIL_OAUTH_ORIGINS), - (NOTION_MCP_ORIGIN, NOTION_OAUTH_ORIGINS), - (INTERCOM_MCP_ORIGIN, INTERCOM_OAUTH_ORIGINS), - (ATLASSIAN_MCP_ORIGIN, ATLASSIAN_OAUTH_ORIGINS), - (ASANA_MCP_ORIGIN, ASANA_OAUTH_ORIGINS), - (MONDAY_MCP_ORIGIN, MONDAY_OAUTH_ORIGINS), - (GURU_MCP_ORIGIN, GURU_OAUTH_ORIGINS), - (FIREFLIES_MCP_ORIGIN, FIREFLIES_OAUTH_ORIGINS), - (AMPLITUDE_MCP_ORIGIN, AMPLITUDE_OAUTH_ORIGINS), - (APOLLO_MCP_ORIGIN, APOLLO_OAUTH_ORIGINS), - (POSTHOG_MCP_ORIGIN, POSTHOG_OAUTH_ORIGINS), - (STRIPE_MCP_ORIGIN, STRIPE_OAUTH_ORIGINS), - (VERCEL_MCP_ORIGIN, VERCEL_OAUTH_ORIGINS), - (CANVA_MCP_ORIGIN, CANVA_OAUTH_ORIGINS), - (CLICKUP_MCP_ORIGIN, CLICKUP_OAUTH_ORIGINS), - (CLOSE_MCP_ORIGIN, CLOSE_OAUTH_ORIGINS), - (SENTRY_MCP_ORIGIN, SENTRY_OAUTH_ORIGINS), - (MIXPANEL_MCP_ORIGIN, MIXPANEL_OAUTH_ORIGINS), - (GRANOLA_MCP_ORIGIN, GRANOLA_OAUTH_ORIGINS), - (DROPBOX_MCP_ORIGIN, DROPBOX_OAUTH_ORIGINS), - (WEBFLOW_MCP_ORIGIN, WEBFLOW_OAUTH_ORIGINS), - (CLOUDFLARE_MCP_ORIGIN, CLOUDFLARE_OAUTH_ORIGINS), - (NEON_MCP_ORIGIN, NEON_OAUTH_ORIGINS), - (CAL_MCP_ORIGIN, CAL_OAUTH_ORIGINS), - (KLAVIYO_MCP_ORIGIN, KLAVIYO_OAUTH_ORIGINS), - (PAYPAL_MCP_ORIGIN, PAYPAL_OAUTH_ORIGINS), - (SQUARE_MCP_ORIGIN, SQUARE_OAUTH_ORIGINS), - (AIRTABLE_MCP_ORIGIN, AIRTABLE_OAUTH_ORIGINS), - (RAILWAY_MCP_ORIGIN, RAILWAY_OAUTH_ORIGINS), - (RESEND_MCP_ORIGIN, RESEND_OAUTH_ORIGINS), - (HEX_MCP_ORIGIN, HEX_OAUTH_ORIGINS), - (PENDO_MCP_ORIGIN, PENDO_OAUTH_ORIGINS), - (SIMILARWEB_MCP_ORIGIN, SIMILARWEB_OAUTH_ORIGINS), - (DATADOG_MCP_ORIGIN, DATADOG_OAUTH_ORIGINS), - (COMMONROOM_MCP_ORIGIN, COMMONROOM_OAUTH_ORIGINS), - (GONG_MCP_ORIGIN, GONG_OAUTH_ORIGINS), - (BOX_MCP_ORIGIN, BOX_OAUTH_ORIGINS), - (PAGERDUTY_MCP_ORIGIN, PAGERDUTY_OAUTH_ORIGINS), - (SLACK_MCP_ORIGIN, SLACK_OAUTH_ORIGINS), - (ZOOM_MCP_ORIGIN, ZOOM_OAUTH_ORIGINS), -]; +// The (mcp_server_url origin → allowed OAuth authorization-server origins) +// allowlist lives in native_oauth_allowlist.rs, GENERATED from the web catalog +// (web/src/lib/mcp-providers.ts) by web/scripts/gen-native-oauth-allowlist.ts. +// Never edit it by hand — regenerate with `npm run gen:allowlist` in web/. +use crate::native_oauth_allowlist::NATIVE_MCP_OAUTH_ALLOWLIST; #[derive(Deserialize)] struct ProtectedResourceMeta { @@ -801,21 +595,27 @@ mod tests { #[test] fn enforces_https_and_allowed_oauth_origin() { + // Resolve Attio's allowed origins through the generated allowlist, so + // this also exercises the table lookup against real generated data. + let attio_oauth_origins = allowed_oauth_origins_for_mcp_origin("https://mcp.attio.com") + .expect("attio is in the generated allowlist"); + assert_eq!(attio_oauth_origins, ["https://app.attio.com"]); + assert!(parse_trusted_oauth_url( "https://app.attio.com/oidc/token", - ATTIO_OAUTH_ORIGINS, + attio_oauth_origins, "token endpoint" ) .is_ok()); assert!(parse_trusted_oauth_url( "http://app.attio.com/oidc/token", - ATTIO_OAUTH_ORIGINS, + attio_oauth_origins, "token endpoint" ) .is_err()); assert!(parse_trusted_oauth_url( "https://evil.example/oidc/token", - ATTIO_OAUTH_ORIGINS, + attio_oauth_origins, "token endpoint" ) .is_err()); diff --git a/api/src/native_oauth_allowlist.rs b/api/src/native_oauth_allowlist.rs new file mode 100644 index 00000000..d1b0063e --- /dev/null +++ b/api/src/native_oauth_allowlist.rs @@ -0,0 +1,203 @@ +// @generated by web/scripts/gen-native-oauth-allowlist.ts — DO NOT EDIT BY HAND. +// +// Source of truth: web/src/lib/mcp-providers.ts (MCP_PROVIDERS). Each tuple is +// (MCP-server origin, exact OAuth authorization-server origins that provider +// may advertise through /.well-known/oauth-protected-resource). The refresh +// path in native_oauth.rs aborts when a discovered endpoint's origin is not in +// this list — see the Dialed regression for why. +// +// Regenerate after ANY catalog change that touches mcpServerUrl or +// oauthAuthorizationServerOrigins: +// +// cd web && npm run gen:allowlist +// +// CI enforcement: web/src/lib/mcp-providers.allowlist-sync.test.ts fails when +// this file is stale. + +#[rustfmt::skip] +pub(crate) const NATIVE_MCP_OAUTH_ALLOWLIST: &[(&str, &[&str])] = &[ + ("https://mcp.attio.com", &["https://app.attio.com"]), // attio + ("https://mcp.usepylon.com", &["https://o.auth.usepylon.com"]), // pylon + ("https://mcp.hubspot.com", &["https://mcp.hubspot.com"]), // hubspot + ("https://api.fathom.ai", &["https://api.fathom.ai", "https://fathom.video"]), // fathom + ("https://dialed.day", &["https://dialed.day"]), // dialed + ("https://mcp.linear.app", &["https://mcp.linear.app"]), // linear + ("https://mcp.amplemarket.com", &["https://app.amplemarket.com"]), // amplemarket + ("https://api.clay.com", &["https://api.clay.com", "https://app.clay.com"]), // clay + ("https://mcp.avoma.com", &["https://prod-api.avoma.com"]), // avoma + ("https://gmailmcp.googleapis.com", &["https://accounts.google.com", "https://oauth2.googleapis.com"]), // gmail + ("https://mcp.notion.com", &["https://mcp.notion.com"]), // notion + ("https://mcp.intercom.com", &["https://mcp.intercom.com"]), // intercom + ("https://mcp.atlassian.com", &["https://mcp.atlassian.com", "https://cf.mcp.atlassian.com"]), // atlassian + ("https://mcp.asana.com", &["https://mcp.asana.com"]), // asana + ("https://mcp.monday.com", &["https://mcp.monday.com"]), // monday + ("https://mcp.api.getguru.com", &["https://mcp.api.getguru.com"]), // guru + ("https://api.fireflies.ai", &["https://api.fireflies.ai"]), // fireflies + ("https://mcp.amplitude.com", &["https://mcp.amplitude.com"]), // amplitude + ("https://mcp.apollo.io", &["https://mcp.apollo.io"]), // apollo + ("https://mcp.posthog.com", &["https://oauth.posthog.com"]), // posthog + ("https://mcp.stripe.com", &["https://access.stripe.com", "https://mcp.stripe.com"]), // stripe + ("https://mcp.vercel.com", &["https://mcp.vercel.com", "https://vercel.com"]), // vercel + ("https://mcp.canva.com", &["https://mcp.canva.com"]), // canva + ("https://mcp.clickup.com", &["https://mcp.clickup.com"]), // clickup + ("https://mcp.close.com", &["https://mcp.close.com", "https://api.close.com", "https://app.close.com"]), // close + ("https://mcp.sentry.dev", &["https://mcp.sentry.dev"]), // sentry + ("https://mcp.mixpanel.com", &["https://mcp.mixpanel.com", "https://mixpanel.com"]), // mixpanel + ("https://mcp.granola.ai", &["https://mcp.granola.ai", "https://mcp-auth.granola.ai"]), // granola + ("https://mcp.dropbox.com", &["https://mcp.dropbox.com", "https://www.dropbox.com", "https://api.dropboxapi.com"]), // dropbox + ("https://mcp.webflow.com", &["https://mcp.webflow.com"]), // webflow + ("https://mcp.cloudflare.com", &["https://mcp.cloudflare.com"]), // cloudflare + ("https://mcp.neon.tech", &["https://mcp.neon.tech"]), // neon + ("https://mcp.cal.com", &["https://mcp.cal.com"]), // cal + ("https://mcp.klaviyo.com", &["https://mcp.klaviyo.com"]), // klaviyo + ("https://mcp.paypal.com", &["https://mcp.paypal.com"]), // paypal + ("https://mcp.squareup.com", &["https://mcp.squareup.com"]), // square + ("https://mcp.airtable.com", &["https://mcp.airtable.com", "https://airtable.com"]), // airtable + ("https://mcp.railway.app", &["https://mcp.railway.app", "https://backboard.railway.com"]), // railway + ("https://mcp.resend.com", &["https://mcp.resend.com", "https://api.resend.com"]), // resend + ("https://app.hex.tech", &["https://app.hex.tech", "https://auth.app.hex.tech"]), // hex + ("https://app.pendo.io", &["https://app.pendo.io"]), // pendo + ("https://mcp.similarweb.com", &["https://mcp.similarweb.com", "https://mcp-auth.similarweb.com"]), // similarweb + ("https://mcp.datadoghq.com", &["https://mcp.datadoghq.com", "https://app.datadoghq.com"]), // datadog + ("https://mcp.commonroom.io", &["https://mcp.commonroom.io", "https://login.commonroom.io"]), // commonroom + ("https://mcp.gong.io", &["https://mcp.gong.io", "https://app.gong.io"]), // gong + ("https://mcp.box.com", &["https://mcp.box.com", "https://api.box.com", "https://account.box.com"]), // box + ("https://mcp.pagerduty.com", &["https://mcp.pagerduty.com", "https://app.pagerduty.com"]), // pagerduty + ("https://mcp.slack.com", &["https://mcp.slack.com", "https://slack.com"]), // slack + ("https://mcp.zoom.us", &["https://zoom.us", "https://mcp.zoom.us", "https://mcp-us.zoom.us"]), // zoom + ("https://api.outreach.io", &["https://api.outreach.io"]), // outreach + ("https://mcp.salesloft.com", &["https://accounts.salesloft.com", "https://mcp.salesloft.com"]), // salesloft + ("https://mcp.zoominfo.com", &["https://mcp.zoominfo.com", "https://okta-login.zoominfo.com"]), // zoominfo + ("https://mcp.lusha.com", &["https://auth.lusha.com"]), // lusha + ("https://mcp.hunter.io", &["https://hunter.io"]), // hunter + ("https://mcp.instantly.ai", &["https://api.instantly.ai"]), // instantly + ("https://mcp.crossbeam.com", &["https://mcp.crossbeam.com"]), // crossbeam + ("https://mcp.api.harmonic.ai", &["https://mcp.api.harmonic.ai"]), // harmonic + ("https://fire.chilipiper.com", &["https://fire.chilipiper.com"]), // chilipiper + ("https://day.ai", &["https://day.ai"]), // dayai + ("https://api.clarify.ai", &["https://auth1.clarify.ai"]), // clarify + ("https://mcp.staircase.ai", &["https://mcp.staircase.ai"]), // staircase + ("https://mcp.zendesk.com", &["https://mcp.zendesk.com"]), // zendesk + ("https://mcp.helpscout.net", &["https://mcp.helpscout.net"]), // helpscout + ("https://mcp.gorgias.com", &["https://mcp.gorgias.com"]), // gorgias + ("https://mcp.plain.com", &["https://signin.auth.plain.com"]), // plain + ("https://api.lorikeetcx.ai", &["https://api.lorikeetcx.ai", "https://app.lorikeetcx.ai"]), // lorikeet + ("https://app.unthread.io", &["https://app.unthread.io"]), // unthread + ("https://wisdom-api.enterpret.com", &["https://oauth.enterpret.com"]), // enterpret + ("https://dovetail.com", &["https://auth.dovetail.com"]), // dovetail + ("https://mcp.missiveapp.com", &["https://auth.missiveapp.com"]), // missive + ("https://mcp.otter.ai", &["https://otter.ai"]), // otter + ("https://api.grain.com", &["https://api.grain.com", "https://grain.com"]), // grain + ("https://mcp.krisp.ai", &["https://api.krisp.ai", "https://mcp.krisp.ai"]), // krisp + ("https://app.circleback.ai", &["https://app.circleback.ai"]), // circleback + ("https://mcp.tldv.io", &["https://keycloak.tldv.io"]), // tldv + ("https://mcp.ramp.com", &["https://api.ramp.com", "https://mcp.ramp.com"]), // ramp + ("https://api.brex.com", &["https://accounts-api.brex.com", "https://api.brex.com"]), // brex + ("https://mcp.mercury.com", &["https://mcp.mercury.com"]), // mercury + ("https://mcp.expensify.com", &["https://www.expensify.com"]), // expensify + ("https://mcp.navan.com", &["https://login.navan.com"]), // navan + ("https://mcp.app.carta.com", &["https://mcp.app.carta.com"]), // carta + ("https://api.digits.com", &["https://api.digits.com"]), // digits + ("https://mcp.gocardless.com", &["https://mcp.gocardless.com"]), // gocardless + ("https://mcp.mercadopago.com", &["https://auth.mercadopago.com", "https://mcp.mercadopago.com"]), // mercadopago + ("https://premium.mcp.pitchbook.com", &["https://premium.mcp.pitchbook.com"]), // pitchbook + ("https://mcp.morningstar.com", &["https://mcp.morningstar.com"]), // morningstar + ("https://mcp.cbinsights.com", &["https://mcp.cbinsights.com"]), // cbinsights + ("https://mcp.quartr.com", &["https://mcp.quartr.com"]), // quartr + ("https://mcp.daloopa.com", &["https://mcp.daloopa.com"]), // daloopa + ("https://mcp.consensus.app", &["https://consensus.app"]), // consensus + ("https://mcp.api.gusto.com", &["https://mcp.api.gusto.com"]), // gusto + ("https://api.letsdeel.com", &["https://api.letsdeel.com"]), // deel + ("https://mcp.ashbyhq.com", &["https://mcp-auth.ashbyhq.com"]), // ashby + ("https://mcp.workable.com", &["https://mcp.workable.com", "https://workable.com"]), // workable + ("https://mcp.metaview.ai", &["https://auth.metaview.ai"]), // metaview + ("https://mcp.indeed.com", &["https://apis.indeed.com", "https://secure.indeed.com"]), // indeed + ("https://api.udemy.com", &["https://api.udemy.com"]), // udemy + ("https://mcp-server.signnow.com", &["https://mcp-server.signnow.com"]), // signnow + ("https://mcp.vanta.com", &["https://api.vanta.com", "https://app.vanta.com", "https://mcp.vanta.com"]), // vanta + ("https://mcp.drata.com", &["https://drata-prod.us.auth0.com", "https://mcp.drata.com"]), // drata + ("https://mcp.figma.com", &["https://api.figma.com", "https://www.figma.com"]), // figma + ("https://mcp.miro.com", &["https://mcp.miro.com"]), // miro + ("https://mcp.lucid.app", &["https://mcp.lucid.app"]), // lucid + ("https://mcp.productboard.com", &["https://mcp.productboard.com"]), // productboard + ("https://mcp.aha.io", &["https://mcp.aha.io"]), // aha + ("https://mcp.shortcut.com", &["https://api.app.shortcut.com"]), // shortcut + ("https://ai.todoist.net", &["https://todoist.com"]), // todoist + ("https://mcp.ai.teamwork.com", &["https://teamwork.com", "https://www.teamwork.com"]), // teamwork + ("https://mcp.calendly.com", &["https://calendly.com"]), // calendly + ("https://mcp.mail.superhuman.com", &["https://mcp.auth.mail.superhuman.com"]), // superhuman + ("https://mcp.craft.do", &["https://mcp.craft.do"]), // craft + ("https://mcp.mem.ai", &["https://api.mem.ai", "https://mem.ai"]), // mem + ("https://mcp.gamma.app", &["https://auth.gamma.app"]), // gamma + ("https://mcp.pitch.com", &["https://auth.pitch.com"]), // pitch + ("https://app.eraser.io", &["https://app.eraser.io"]), // eraser + ("https://mcp.jotform.com", &["https://oauth2.jotform.com"]), // jotform + ("https://api.typeform.com", &["https://admin.typeform.com", "https://api.typeform.com"]), // typeform + ("https://mcp.surveymonkey.com", &["https://mcp.surveymonkey.com"]), // surveymonkey + ("https://mcp-server.egnyte.com", &["https://mcp-oauth.egnyte.com"]), // egnyte + ("https://ai-inc.mailchimp.com", &["https://ai-inc.mailchimp.com"]), // mailchimp + ("https://mcp.customer.io", &["https://mcp.customer.io"]), // customerio + ("https://api.ahrefs.com", &["https://ahrefs.com", "https://api.ahrefs.com", "https://app.ahrefs.com"]), // ahrefs + ("https://mcp.semrush.com", &["https://api.semrush.com", "https://oauth.semrush.com"]), // semrush + ("https://asset-management.mcp.cloudinary.com", &["https://asset-management.mcp.cloudinary.com"]), // cloudinary + ("https://mcp.contentful.com", &["https://mcp.contentful.com"]), // contentful + ("https://mcp.sanity.io", &["https://mcp.sanity.io"]), // sanity + ("https://mcp.wix.com", &["https://mcp.wix.com"]), // wix + ("https://public-api.wordpress.com", &["https://public-api.wordpress.com"]), // wordpress + ("https://mcp.gitbook.com", &["https://oauth.gitbook.com"]), // gitbook + ("https://mcp.mintlify.com", &["https://mcp.mintlify.com"]), // mintlify + ("https://mcp.deepl.com", &["https://mcp.deepl.com"]), // deepl + ("https://gitlab.com", &["https://gitlab.com"]), // gitlab + ("https://mcp.supabase.com", &["https://api.supabase.com"]), // supabase + ("https://netlify-mcp.netlify.app", &["https://netlify-mcp.netlify.app"]), // netlify + ("https://mcp.heroku.com", &["https://mcp.heroku.com"]), // heroku + ("https://mcp.buildkite.com", &["https://mcp.buildkite.com"]), // buildkite + ("https://mcp.grafana.com", &["https://mcp.grafana.com"]), // grafana + ("https://mcp.newrelic.com", &["https://login.newrelic.com", "https://mcp.newrelic.com"]), // newrelic + ("https://mcp.honeycomb.io", &["https://ui.honeycomb.io"]), // honeycomb + ("https://mcp.incident.io", &["https://app.incident.io", "https://mcp.incident.io"]), // incidentio + ("https://mcp.rootly.com", &["https://rootly.com"]), // rootly + ("https://bugsnag.mcp.smartbear.com", &["https://oauth.bugsnag.com"]), // bugsnag + ("https://mcp.launchdarkly.com", &["https://app.launchdarkly.com", "https://mcp.launchdarkly.com"]), // launchdarkly + ("https://mcp.pscale.dev", &["https://app.planetscale.com", "https://auth.planetscale.com", "https://mcp.pscale.dev"]), // planetscale + ("https://mcp.prisma.io", &["https://auth.prisma.io", "https://mcp.prisma.io"]), // prisma + ("https://mcp.instantdb.com", &["https://mcp.instantdb.com"]), // instantdb + ("https://mcp.algolia.com", &["https://dashboard.algolia.com"]), // algolia + ("https://api.statsig.com", &["https://api.statsig.com"]), // statsig + ("https://mcp.postman.com", &["https://mcp.postman.com"]), // postman + ("https://mcp.semgrep.ai", &["https://login.semgrep.dev"]), // semgrep + ("https://mcp.workos.com", &["https://signin.workos.com"]), // workos + ("https://mcp.stytch.dev", &["https://rustic-kilogram-6347.customers.stytch.com", "https://stytch.com"]), // stytch + ("https://mcp.mux.com", &["https://auth.mux.com"]), // mux + ("https://mcp.knock.app", &["https://mcp.knock.app"]), // knock + ("https://mcp.lovable.dev", &["https://lovable.dev"]), // lovable + ("https://mcp.retool.com", &["https://mcp.retool.com"]), // retool + ("https://api.telnyx.com", &["https://api.telnyx.com"]), // telnyx + ("https://mcp.jam.dev", &["https://api.jam.dev"]), // jam + ("https://mcp.globalping.dev", &["https://mcp.globalping.dev"]), // globalping + ("https://mcp.airbyte.ai", &["https://mcp.airbyte.ai"]), // airbyte + ("https://api.motherduck.com", &["https://mcp-auth.motherduck.com"]), // motherduck + ("https://integrations.getmontecarlo.com", &["https://auth.getmontecarlo.com"]), // montecarlo + ("https://mcp.atlan.com", &["https://mcp.atlan.com"]), // atlan + ("https://huggingface.co", &["https://huggingface.co"]), // huggingface + ("https://mcp.zapier.com", &["https://mcp.zapier.com"]), // zapier + ("https://mcp.make.com", &["https://www.make.com"]), // make + ("https://ifttt.com", &["https://ifttt.com"]), // ifttt + ("https://mcp.exa.ai", &["https://auth.exa.ai"]), // exa + ("https://mcp.tavily.com", &["https://mcp.tavily.com"]), // tavily + ("https://mcp.firecrawl.dev", &["https://www.firecrawl.dev"]), // firecrawl + ("https://mcp.apify.com", &["https://console-backend.apify.com", "https://console.apify.com"]), // apify + ("https://mcp.brightdata.com", &["https://brightdata.com"]), // brightdata + ("https://mcp.docusign.com", &["https://account.docusign.com", "https://mcp.docusign.com"]), // docusign + ("https://mcp.xero.com", &["https://identity.xero.com", "https://login.xero.com"]), // xero + ("https://mcp.frontapp.com", &["https://app.frontapp.com"]), // front + ("https://mcp.smartsheet.com", &["https://api.smartsheet.com", "https://app.smartsheet.com", "https://mcp.smartsheet.com"]), // smartsheet + ("https://mcp.mongodb.com", &["https://authorize.mongodb.com", "https://cloud.mongodb.com"]), // mongodb + ("https://mcp.circleci.com", &["https://app.circleci.com", "https://circleci.com", "https://mcp.circleci.com"]), // circleci + ("https://mcp.chargebee.com", &["https://app.chargebee.com"]), // chargebee + ("https://bigquery.googleapis.com", &["https://accounts.google.com", "https://oauth2.googleapis.com"]), // bigquery + ("https://mcp.na1.ironcladapp.com", &["https://ironcladapp.com"]), // ironclad + ("https://api.harvey.ai", &["https://api.harvey.ai", "https://harvey-ai.us.auth0.com"]), // harvey + ("https://mcp.tableau.com", &["https://sso.online.tableau.com"]), // tableau + ("https://setup.shopify.com", &["https://setup.shopify.com"]), // shopify +]; diff --git a/docs/src/content/docs/connections.md b/docs/src/content/docs/connections.md index 4f796f89..19b64fd4 100644 --- a/docs/src/content/docs/connections.md +++ b/docs/src/content/docs/connections.md @@ -42,9 +42,9 @@ TAS ships native MCP support for these providers: | Provider | Auth | | --- | --- | -| Attio, Pylon, Fathom, Dialed, Linear, Amplemarket, Clay, Avoma, Metabase, Notion, Intercom, Atlassian (Jira), Asana, monday.com, Guru, Fireflies, Amplitude, Apollo, PostHog, Stripe, Vercel, Canva, ClickUp, Close, Sentry, Mixpanel, Granola, Dropbox, Webflow, Cloudflare, Neon, Cal.com, Klaviyo, PayPal, Square, Airtable, Railway, Resend, Hex, Pendo, Similarweb, Datadog, Common Room | **TAS-managed OAuth** — click **Connect**, authorize, done | -| GitHub, X | **API token** — paste a PAT (GitHub) or App-only Bearer (X) | -| HubSpot, Gmail, Slack, Gong, Box, PagerDuty, Zoom | **Bring-your-own OAuth app** — admin sets up once (below) | +| Attio, Pylon, Fathom, Dialed, Linear, Amplemarket, Clay, Avoma, Metabase, Notion, Intercom, Atlassian (Jira), Asana, monday.com, Guru, Fireflies, Amplitude, Apollo, PostHog, Stripe, Vercel, Canva, ClickUp, Close, Sentry, Mixpanel, Granola, Dropbox, Webflow, Cloudflare, Neon, Cal.com, Klaviyo, PayPal, Square, Airtable, Railway, Resend, Hex, Pendo, Similarweb, Datadog, Common Room, Outreach, Salesloft, ZoomInfo, Lusha, Hunter, Instantly, Crossbeam, Harmonic, Chili Piper, Day AI, Clarify, Staircase AI, Zendesk, Help Scout, Gorgias, Plain, Lorikeet, Unthread, Enterpret, Dovetail, Missive, Otter.ai, Grain, Krisp, Circleback, tl;dv, Ramp, Brex, Mercury, Expensify, Navan, Carta, Digits, GoCardless, Mercado Pago, PitchBook, Morningstar, CB Insights, Quartr, Daloopa, Consensus, Gusto, Deel, Ashby, Workable, Metaview, Indeed, Udemy Business, SignNow, Vanta, Drata, Figma, Miro, Lucid, Productboard, Aha!, Shortcut, Todoist, Teamwork, Calendly, Superhuman Mail, Craft, Mem, Gamma, Pitch, Eraser, Jotform, Typeform, SurveyMonkey, Egnyte, Mailchimp, Customer.io, Ahrefs, Semrush, Cloudinary, Contentful, Sanity, Wix, WordPress.com, GitBook, Mintlify, DeepL, GitLab, Supabase, Netlify, Heroku, Buildkite, Grafana, New Relic, Honeycomb, incident.io, Rootly, BugSnag, LaunchDarkly, PlanetScale, Prisma Postgres, InstantDB, Algolia, Statsig, Postman, Semgrep, WorkOS, Stytch, Mux, Knock, Lovable, Retool, Telnyx, Jam, Globalping, Airbyte, MotherDuck, Monte Carlo, Atlan, Hugging Face, Zapier, Make, IFTTT, Exa, Tavily, Firecrawl, Apify, Bright Data | **TAS-managed OAuth** — click **Connect**, authorize, done | +| GitHub, X, Render | **API token** — paste a PAT (GitHub), App-only Bearer (X), or API key (Render) | +| HubSpot, Gmail, Slack, Gong, Box, PagerDuty, Zoom, DocuSign, Xero, Front, Smartsheet, MongoDB Atlas, CircleCI, Chargebee, BigQuery, Ironclad, Harvey, Tableau, Shopify | **Bring-your-own OAuth app** — admin sets up once (below) | There's also a built-in **Tembo Agent Studio** native connection (TAS's own MCP server) that agents use to read/produce [Tasks Inbox](/agent-studio/tasks-inbox/) diff --git a/web/package.json b/web/package.json index 272510cf..bf8881d4 100644 --- a/web/package.json +++ b/web/package.json @@ -8,6 +8,7 @@ "build": "next build", "start": "next start", "gen:docs": "node scripts/gen-docs.mjs", + "gen:allowlist": "tsx scripts/gen-native-oauth-allowlist.ts", "lint": "eslint", "test": "vitest run", "test:watch": "vitest", diff --git a/web/public/mcp-logos/aha.png b/web/public/mcp-logos/aha.png new file mode 100644 index 00000000..8d0e0c0f Binary files /dev/null and b/web/public/mcp-logos/aha.png differ diff --git a/web/public/mcp-logos/atlan.png b/web/public/mcp-logos/atlan.png new file mode 100644 index 00000000..d35e9bb2 Binary files /dev/null and b/web/public/mcp-logos/atlan.png differ diff --git a/web/public/mcp-logos/bigquery.png b/web/public/mcp-logos/bigquery.png new file mode 100644 index 00000000..3ae05e33 Binary files /dev/null and b/web/public/mcp-logos/bigquery.png differ diff --git a/web/public/mcp-logos/carta.png b/web/public/mcp-logos/carta.png new file mode 100644 index 00000000..29d5e51a Binary files /dev/null and b/web/public/mcp-logos/carta.png differ diff --git a/web/public/mcp-logos/cbinsights.png b/web/public/mcp-logos/cbinsights.png new file mode 100644 index 00000000..ba6c221b Binary files /dev/null and b/web/public/mcp-logos/cbinsights.png differ diff --git a/web/public/mcp-logos/chargebee.png b/web/public/mcp-logos/chargebee.png new file mode 100644 index 00000000..361d4040 Binary files /dev/null and b/web/public/mcp-logos/chargebee.png differ diff --git a/web/public/mcp-logos/chilipiper.png b/web/public/mcp-logos/chilipiper.png new file mode 100644 index 00000000..395b1a33 Binary files /dev/null and b/web/public/mcp-logos/chilipiper.png differ diff --git a/web/public/mcp-logos/circleback.png b/web/public/mcp-logos/circleback.png new file mode 100644 index 00000000..1411eabb Binary files /dev/null and b/web/public/mcp-logos/circleback.png differ diff --git a/web/public/mcp-logos/clarify.png b/web/public/mcp-logos/clarify.png new file mode 100644 index 00000000..dcb35126 Binary files /dev/null and b/web/public/mcp-logos/clarify.png differ diff --git a/web/public/mcp-logos/commonroom.png b/web/public/mcp-logos/commonroom.png new file mode 100644 index 00000000..ad7e820b Binary files /dev/null and b/web/public/mcp-logos/commonroom.png differ diff --git a/web/public/mcp-logos/consensus.png b/web/public/mcp-logos/consensus.png new file mode 100644 index 00000000..948a8574 Binary files /dev/null and b/web/public/mcp-logos/consensus.png differ diff --git a/web/public/mcp-logos/craft.png b/web/public/mcp-logos/craft.png new file mode 100644 index 00000000..d0c261e9 Binary files /dev/null and b/web/public/mcp-logos/craft.png differ diff --git a/web/public/mcp-logos/crossbeam.png b/web/public/mcp-logos/crossbeam.png new file mode 100644 index 00000000..b7bf1545 Binary files /dev/null and b/web/public/mcp-logos/crossbeam.png differ diff --git a/web/public/mcp-logos/daloopa.png b/web/public/mcp-logos/daloopa.png new file mode 100644 index 00000000..1e57b365 Binary files /dev/null and b/web/public/mcp-logos/daloopa.png differ diff --git a/web/public/mcp-logos/dayai.png b/web/public/mcp-logos/dayai.png new file mode 100644 index 00000000..7ae3b237 Binary files /dev/null and b/web/public/mcp-logos/dayai.png differ diff --git a/web/public/mcp-logos/deepl.png b/web/public/mcp-logos/deepl.png new file mode 100644 index 00000000..219cde72 Binary files /dev/null and b/web/public/mcp-logos/deepl.png differ diff --git a/web/public/mcp-logos/digits.png b/web/public/mcp-logos/digits.png new file mode 100644 index 00000000..72e16cca Binary files /dev/null and b/web/public/mcp-logos/digits.png differ diff --git a/web/public/mcp-logos/drata.png b/web/public/mcp-logos/drata.png new file mode 100644 index 00000000..2cc21444 Binary files /dev/null and b/web/public/mcp-logos/drata.png differ diff --git a/web/public/mcp-logos/enterpret.png b/web/public/mcp-logos/enterpret.png new file mode 100644 index 00000000..33c13341 Binary files /dev/null and b/web/public/mcp-logos/enterpret.png differ diff --git a/web/public/mcp-logos/eraser.ico b/web/public/mcp-logos/eraser.ico new file mode 100644 index 00000000..98a193ee Binary files /dev/null and b/web/public/mcp-logos/eraser.ico differ diff --git a/web/public/mcp-logos/gocardless.png b/web/public/mcp-logos/gocardless.png new file mode 100644 index 00000000..5a5caed1 Binary files /dev/null and b/web/public/mcp-logos/gocardless.png differ diff --git a/web/public/mcp-logos/granola.png b/web/public/mcp-logos/granola.png new file mode 100644 index 00000000..bf71540f Binary files /dev/null and b/web/public/mcp-logos/granola.png differ diff --git a/web/public/mcp-logos/harmonic.png b/web/public/mcp-logos/harmonic.png new file mode 100644 index 00000000..73ea9ba1 Binary files /dev/null and b/web/public/mcp-logos/harmonic.png differ diff --git a/web/public/mcp-logos/harvey.png b/web/public/mcp-logos/harvey.png new file mode 100644 index 00000000..44a5f21a Binary files /dev/null and b/web/public/mcp-logos/harvey.png differ diff --git a/web/public/mcp-logos/honeycomb.png b/web/public/mcp-logos/honeycomb.png new file mode 100644 index 00000000..ac803e66 Binary files /dev/null and b/web/public/mcp-logos/honeycomb.png differ diff --git a/web/public/mcp-logos/ifttt.png b/web/public/mcp-logos/ifttt.png new file mode 100644 index 00000000..be5378d7 Binary files /dev/null and b/web/public/mcp-logos/ifttt.png differ diff --git a/web/public/mcp-logos/indeed.png b/web/public/mcp-logos/indeed.png new file mode 100644 index 00000000..a9047f4d Binary files /dev/null and b/web/public/mcp-logos/indeed.png differ diff --git a/web/public/mcp-logos/instantdb.png b/web/public/mcp-logos/instantdb.png new file mode 100644 index 00000000..e96dc9fa Binary files /dev/null and b/web/public/mcp-logos/instantdb.png differ diff --git a/web/public/mcp-logos/ironclad.png b/web/public/mcp-logos/ironclad.png new file mode 100644 index 00000000..829c7034 Binary files /dev/null and b/web/public/mcp-logos/ironclad.png differ diff --git a/web/public/mcp-logos/knock.png b/web/public/mcp-logos/knock.png new file mode 100644 index 00000000..2c904a23 Binary files /dev/null and b/web/public/mcp-logos/knock.png differ diff --git a/web/public/mcp-logos/krisp.png b/web/public/mcp-logos/krisp.png new file mode 100644 index 00000000..5a576c2f Binary files /dev/null and b/web/public/mcp-logos/krisp.png differ diff --git a/web/public/mcp-logos/lorikeet.png b/web/public/mcp-logos/lorikeet.png new file mode 100644 index 00000000..9289e903 Binary files /dev/null and b/web/public/mcp-logos/lorikeet.png differ diff --git a/web/public/mcp-logos/lovable.png b/web/public/mcp-logos/lovable.png new file mode 100644 index 00000000..09b6a1fa Binary files /dev/null and b/web/public/mcp-logos/lovable.png differ diff --git a/web/public/mcp-logos/lucid.png b/web/public/mcp-logos/lucid.png new file mode 100644 index 00000000..6ae93bf1 Binary files /dev/null and b/web/public/mcp-logos/lucid.png differ diff --git a/web/public/mcp-logos/mercadopago.png b/web/public/mcp-logos/mercadopago.png new file mode 100644 index 00000000..6ceff2f7 Binary files /dev/null and b/web/public/mcp-logos/mercadopago.png differ diff --git a/web/public/mcp-logos/mercury.png b/web/public/mcp-logos/mercury.png new file mode 100644 index 00000000..35e2713d Binary files /dev/null and b/web/public/mcp-logos/mercury.png differ diff --git a/web/public/mcp-logos/metaview.png b/web/public/mcp-logos/metaview.png new file mode 100644 index 00000000..cd542cca Binary files /dev/null and b/web/public/mcp-logos/metaview.png differ diff --git a/web/public/mcp-logos/montecarlo.png b/web/public/mcp-logos/montecarlo.png new file mode 100644 index 00000000..31f84ee4 Binary files /dev/null and b/web/public/mcp-logos/montecarlo.png differ diff --git a/web/public/mcp-logos/morningstar.png b/web/public/mcp-logos/morningstar.png new file mode 100644 index 00000000..bee9ef82 Binary files /dev/null and b/web/public/mcp-logos/morningstar.png differ diff --git a/web/public/mcp-logos/motherduck.png b/web/public/mcp-logos/motherduck.png new file mode 100644 index 00000000..653e3cdf Binary files /dev/null and b/web/public/mcp-logos/motherduck.png differ diff --git a/web/public/mcp-logos/navan.png b/web/public/mcp-logos/navan.png new file mode 100644 index 00000000..2347bb9b Binary files /dev/null and b/web/public/mcp-logos/navan.png differ diff --git a/web/public/mcp-logos/otter.png b/web/public/mcp-logos/otter.png new file mode 100644 index 00000000..a90f31dc Binary files /dev/null and b/web/public/mcp-logos/otter.png differ diff --git a/web/public/mcp-logos/pitch.jpg b/web/public/mcp-logos/pitch.jpg new file mode 100644 index 00000000..a6b97f2d Binary files /dev/null and b/web/public/mcp-logos/pitch.jpg differ diff --git a/web/public/mcp-logos/pitchbook.png b/web/public/mcp-logos/pitchbook.png new file mode 100644 index 00000000..b11fb49b Binary files /dev/null and b/web/public/mcp-logos/pitchbook.png differ diff --git a/web/public/mcp-logos/planetscale.ico b/web/public/mcp-logos/planetscale.ico new file mode 100644 index 00000000..5f5958bb Binary files /dev/null and b/web/public/mcp-logos/planetscale.ico differ diff --git a/web/public/mcp-logos/quartr.png b/web/public/mcp-logos/quartr.png new file mode 100644 index 00000000..68ce5b0d Binary files /dev/null and b/web/public/mcp-logos/quartr.png differ diff --git a/web/public/mcp-logos/retool.png b/web/public/mcp-logos/retool.png new file mode 100644 index 00000000..18ecc700 Binary files /dev/null and b/web/public/mcp-logos/retool.png differ diff --git a/web/public/mcp-logos/salesloft.jpg b/web/public/mcp-logos/salesloft.jpg new file mode 100644 index 00000000..890b197e Binary files /dev/null and b/web/public/mcp-logos/salesloft.jpg differ diff --git a/web/public/mcp-logos/semgrep.png b/web/public/mcp-logos/semgrep.png new file mode 100644 index 00000000..0c6e2f59 Binary files /dev/null and b/web/public/mcp-logos/semgrep.png differ diff --git a/web/public/mcp-logos/signnow.png b/web/public/mcp-logos/signnow.png new file mode 100644 index 00000000..4f6bd0e1 Binary files /dev/null and b/web/public/mcp-logos/signnow.png differ diff --git a/web/public/mcp-logos/similarweb.png b/web/public/mcp-logos/similarweb.png new file mode 100644 index 00000000..d9160206 Binary files /dev/null and b/web/public/mcp-logos/similarweb.png differ diff --git a/web/public/mcp-logos/smartsheet.png b/web/public/mcp-logos/smartsheet.png new file mode 100644 index 00000000..855dee41 Binary files /dev/null and b/web/public/mcp-logos/smartsheet.png differ diff --git a/web/public/mcp-logos/staircase.png b/web/public/mcp-logos/staircase.png new file mode 100644 index 00000000..4cf993d3 Binary files /dev/null and b/web/public/mcp-logos/staircase.png differ diff --git a/web/public/mcp-logos/statsig.png b/web/public/mcp-logos/statsig.png new file mode 100644 index 00000000..e63730a6 Binary files /dev/null and b/web/public/mcp-logos/statsig.png differ diff --git a/web/public/mcp-logos/stytch.png b/web/public/mcp-logos/stytch.png new file mode 100644 index 00000000..c0cda5e7 Binary files /dev/null and b/web/public/mcp-logos/stytch.png differ diff --git a/web/public/mcp-logos/superhuman.png b/web/public/mcp-logos/superhuman.png new file mode 100644 index 00000000..37dd0d30 Binary files /dev/null and b/web/public/mcp-logos/superhuman.png differ diff --git a/web/public/mcp-logos/teamwork.png b/web/public/mcp-logos/teamwork.png new file mode 100644 index 00000000..256e6a06 Binary files /dev/null and b/web/public/mcp-logos/teamwork.png differ diff --git a/web/public/mcp-logos/udemy.png b/web/public/mcp-logos/udemy.png new file mode 100644 index 00000000..0000f461 Binary files /dev/null and b/web/public/mcp-logos/udemy.png differ diff --git a/web/public/mcp-logos/unthread.png b/web/public/mcp-logos/unthread.png new file mode 100644 index 00000000..8ca92046 Binary files /dev/null and b/web/public/mcp-logos/unthread.png differ diff --git a/web/public/mcp-logos/workos.png b/web/public/mcp-logos/workos.png new file mode 100644 index 00000000..57fc8edd Binary files /dev/null and b/web/public/mcp-logos/workos.png differ diff --git a/web/public/mcp-logos/zapier.png b/web/public/mcp-logos/zapier.png new file mode 100644 index 00000000..fb47b193 Binary files /dev/null and b/web/public/mcp-logos/zapier.png differ diff --git a/web/scripts/gen-native-oauth-allowlist.ts b/web/scripts/gen-native-oauth-allowlist.ts new file mode 100644 index 00000000..e5e18080 --- /dev/null +++ b/web/scripts/gen-native-oauth-allowlist.ts @@ -0,0 +1,124 @@ +// Generates api/src/native_oauth_allowlist.rs from the MCP provider catalog. +// +// The Rust token-refresh path (api/src/native_oauth.rs) needs an origin +// allowlist — for each provider's MCP-server origin, the exact set of OAuth +// authorization-server origins it may advertise through protected-resource +// discovery. That data already lives in web/src/lib/mcp-providers.ts +// (mcpServerUrl + oauthAuthorizationServerOrigins), but the api crate can't +// import the TS catalog, so the Rust side used to keep a SECOND hand-written +// copy, guarded only by a drift-detecting vitest. This script eliminates that +// failure class: the Rust allowlist is now GENERATED from the catalog, and the +// vitest (mcp-providers.allowlist-sync.test.ts) fails only when the generated +// file is stale — the fix is always "regenerate", never "hand-sync two lists". +// +// Run from web/: npm run gen:allowlist (or: npx tsx scripts/gen-native-oauth-allowlist.ts) +// +// Deliberately NOT wired into the Next build: the output is committed source +// for a different crate, and regeneration should be an explicit, reviewable +// step (same convention as gen:docs). + +import { writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { MCP_PROVIDERS } from "../src/lib/mcp-providers"; + +export const GENERATED_FILE_REPO_PATH = "api/src/native_oauth_allowlist.rs"; + +/** + * Pure projection of the catalog into the generated Rust source. Exported so + * the allowlist-sync vitest can compare this exact string against the file on + * disk (staleness check) without touching the filesystem for the "expected" + * side. + */ +export function renderNativeOauthAllowlistRs(): string { + // (mcp-server origin → allowed OAuth origins), in catalog order. Skips + // providers with no fixed MCP URL (self-key / instance-based) and providers + // with no OAuth origins (pat) — they never hit the OAuth refresh path. + const entries: { slug: string; mcpOrigin: string; oauthOrigins: string[] }[] = []; + const byOrigin = new Map(); + + for (const provider of Object.values(MCP_PROVIDERS)) { + if (!provider.mcpServerUrl) continue; + if (provider.oauthAuthorizationServerOrigins.length === 0) continue; + + const mcpOrigin = new URL(provider.mcpServerUrl).origin; + const oauthOrigins = [...new Set(provider.oauthAuthorizationServerOrigins)]; + + for (const origin of [mcpOrigin, ...oauthOrigins]) { + if (!origin.startsWith("https://")) { + throw new Error( + `${provider.slug}: non-https origin ${JSON.stringify(origin)} — ` + + `the Rust refresher only trusts https origins.`, + ); + } + if (new URL(origin).origin !== origin) { + throw new Error( + `${provider.slug}: ${JSON.stringify(origin)} is not a bare origin ` + + `(has a path, trailing slash, or default port).`, + ); + } + } + + const dup = byOrigin.get(mcpOrigin); + if (dup) { + // Two providers sharing an MCP origin must agree on the allowed OAuth + // origins, otherwise the (origin-keyed) Rust lookup is ambiguous. + const same = + JSON.stringify([...dup.oauthOrigins].sort()) === + JSON.stringify([...oauthOrigins].sort()); + if (!same) { + throw new Error( + `MCP origin ${mcpOrigin} is claimed by both "${dup.slug}" and ` + + `"${provider.slug}" with different OAuth origins — the allowlist ` + + `is keyed by origin, so these must match.`, + ); + } + continue; // identical duplicate: first entry already covers it + } + + byOrigin.set(mcpOrigin, { slug: provider.slug, oauthOrigins }); + entries.push({ slug: provider.slug, mcpOrigin, oauthOrigins }); + } + + const tuples = entries + .map( + (e) => + ` ("${e.mcpOrigin}", &[${e.oauthOrigins + .map((o) => `"${o}"`) + .join(", ")}]), // ${e.slug}\n`, + ) + .join(""); + + return `\ +// @generated by web/scripts/gen-native-oauth-allowlist.ts — DO NOT EDIT BY HAND. +// +// Source of truth: web/src/lib/mcp-providers.ts (MCP_PROVIDERS). Each tuple is +// (MCP-server origin, exact OAuth authorization-server origins that provider +// may advertise through /.well-known/oauth-protected-resource). The refresh +// path in native_oauth.rs aborts when a discovered endpoint's origin is not in +// this list — see the Dialed regression for why. +// +// Regenerate after ANY catalog change that touches mcpServerUrl or +// oauthAuthorizationServerOrigins: +// +// cd web && npm run gen:allowlist +// +// CI enforcement: web/src/lib/mcp-providers.allowlist-sync.test.ts fails when +// this file is stale. + +#[rustfmt::skip] +pub(crate) const NATIVE_MCP_OAUTH_ALLOWLIST: &[(&str, &[&str])] = &[ +${tuples}]; +`; +} + +// Write the file when executed directly (not when imported by the vitest). +const isMain = + process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]; +if (isMain) { + const here = dirname(fileURLToPath(import.meta.url)); + const outPath = join(here, "..", "..", GENERATED_FILE_REPO_PATH); + writeFileSync(outPath, renderNativeOauthAllowlistRs()); + console.log(`wrote ${outPath}`); +} diff --git a/web/src/lib/connection-categories.ts b/web/src/lib/connection-categories.ts index d6478d8f..74430e59 100644 --- a/web/src/lib/connection-categories.ts +++ b/web/src/lib/connection-categories.ts @@ -55,33 +55,33 @@ export type CategoryMeta = { // category with no realistic TAS connector is marked supported:false rather // than pretending it's available. export const CATEGORY_META: Record = { - crm: { label: "CRM", supported: true, slugs: ["attio", "hubspot", "salesforce", "pipedrive", "close", "copper"] }, - email: { label: "Email", supported: true, slugs: ["gmail", "outlook", "microsoftoutlook", "resend", "klaviyo"] }, - calendar: { label: "Calendar", supported: true, slugs: ["googlecalendar", "outlookcalendar", "cal"] }, + crm: { label: "CRM", supported: true, slugs: ["attio", "hubspot", "salesforce", "pipedrive", "close", "copper", "dayai", "clarify"] }, + email: { label: "Email", supported: true, slugs: ["gmail", "outlook", "microsoftoutlook", "resend", "klaviyo", "superhuman"] }, + calendar: { label: "Calendar", supported: true, slugs: ["googlecalendar", "outlookcalendar", "cal", "calendly"] }, notify: { label: "Slack", supported: true, slugs: ["slack", "microsoftteams", "pagerduty"] }, - helpdesk: { label: "Helpdesk", supported: true, slugs: ["pylon", "zendesk", "intercom", "freshdesk", "front", "helpscout"] }, - recorder: { label: "Call recorder", supported: true, slugs: ["avoma", "fathom", "gong", "fireflies", "granola", "zoom"] }, - accounting: { label: "Accounting", supported: true, slugs: ["quickbooks", "xero", "netsuite"] }, - payments: { label: "Payments", supported: true, slugs: ["stripe", "paypal", "square"] }, - analytics: { label: "Product analytics", supported: true, slugs: ["metabase", "amplitude", "mixpanel", "posthog", "googleanalytics", "pendo", "hex"] }, - warehouse: { label: "Data warehouse", supported: true, slugs: ["metabase", "snowflake", "bigquery"] }, - docs: { label: "Docs / knowledge", supported: true, slugs: ["notion", "guru", "googledocs", "googledrive", "confluence", "coda", "airtable", "canva"] }, - issues: { label: "Issue tracker", supported: true, slugs: ["linear", "jira", "atlassian", "github", "asana", "monday", "clickup"] }, - forms: { label: "Forms", supported: true, slugs: ["typeform", "googleforms", "jotform"] }, - enrichment: { label: "Enrichment", supported: true, slugs: ["clay", "amplemarket", "apollo"] }, - storage: { label: "File storage", supported: true, slugs: ["googledrive", "dropbox", "box"] }, + helpdesk: { label: "Helpdesk", supported: true, slugs: ["pylon", "zendesk", "intercom", "freshdesk", "front", "helpscout", "gorgias", "plain", "lorikeet", "unthread", "missive"] }, + recorder: { label: "Call recorder", supported: true, slugs: ["avoma", "fathom", "gong", "fireflies", "granola", "zoom", "otter", "grain", "krisp", "circleback", "tldv"] }, + accounting: { label: "Accounting", supported: true, slugs: ["quickbooks", "xero", "netsuite", "digits"] }, + payments: { label: "Payments", supported: true, slugs: ["stripe", "paypal", "square", "gocardless", "mercadopago", "chargebee"] }, + analytics: { label: "Product analytics", supported: true, slugs: ["metabase", "amplitude", "mixpanel", "posthog", "googleanalytics", "pendo", "hex", "statsig"] }, + warehouse: { label: "Data warehouse", supported: true, slugs: ["metabase", "snowflake", "bigquery", "motherduck"] }, + docs: { label: "Docs / knowledge", supported: true, slugs: ["notion", "guru", "googledocs", "googledrive", "confluence", "coda", "airtable", "canva", "gitbook", "craft", "mem", "sanity"] }, + issues: { label: "Issue tracker", supported: true, slugs: ["linear", "jira", "atlassian", "github", "asana", "monday", "clickup", "gitlab", "shortcut", "aha", "productboard", "teamwork", "todoist"] }, + forms: { label: "Forms", supported: true, slugs: ["typeform", "googleforms", "jotform", "surveymonkey"] }, + enrichment: { label: "Enrichment", supported: true, slugs: ["clay", "amplemarket", "apollo", "zoominfo", "lusha", "hunter", "harmonic"] }, + storage: { label: "File storage", supported: true, slugs: ["googledrive", "dropbox", "box", "egnyte"] }, + ats: { label: "ATS / recruiting", supported: true, slugs: ["ashby", "workable", "greenhouse", "lever", "metaview", "indeed"] }, + hris: { label: "HRIS", supported: true, slugs: ["gusto", "deel", "bamboohr", "rippling"] }, // Long-tail categories TAS doesn't have a first-class connector for yet. - ats: { label: "ATS / recruiting", supported: false, slugs: [] }, - hris: { label: "HRIS", supported: false, slugs: [] }, ims: { label: "Inventory", supported: false, slugs: [] }, // Platform / infra MCPs (Vercel, Railway, Cloudflare, Neon, Datadog, Sentry). - monitoring: { label: "Monitoring", supported: true, slugs: ["datadog", "sentry", "pagerduty"] }, + monitoring: { label: "Monitoring", supported: true, slugs: ["datadog", "sentry", "pagerduty", "grafana", "newrelic", "honeycomb", "incidentio", "rootly", "bugsnag"] }, itsm: { label: "ITSM", supported: false, slugs: [] }, identity: { label: "Identity / SSO", supported: false, slugs: [] }, ads: { label: "Ads", supported: true, slugs: ["klaviyo"] }, - seo: { label: "SEO / search", supported: true, slugs: ["similarweb", "ahrefs"] }, - esign: { label: "E-signature", supported: false, slugs: [] }, - survey: { label: "Survey", supported: false, slugs: [] }, + seo: { label: "SEO / search", supported: true, slugs: ["similarweb", "ahrefs", "semrush"] }, + esign: { label: "E-signature", supported: true, slugs: ["docusign", "signnow", "dropboxsign", "pandadoc"] }, + survey: { label: "Survey", supported: true, slugs: ["surveymonkey", "typeform", "jotform"] }, psa: { label: "PSA / time tracking", supported: false, slugs: [] }, // Built-ins: no user connection required. "tasks-inbox": { label: "Tasks Inbox", supported: true, builtin: true, slugs: ["tembo-agent-studio"] }, diff --git a/web/src/lib/docs-content.ts b/web/src/lib/docs-content.ts index 58aa7419..0f2503a4 100644 --- a/web/src/lib/docs-content.ts +++ b/web/src/lib/docs-content.ts @@ -47,7 +47,7 @@ export const DOCS: Record = { "connections": { "title": "Connections", "description": "Authorize external services so agents can act on them — Composio and Native MCP, authorized per user.", - "body": "**Connections** are how agents reach outside services — Slack, Gmail, Google\nSheets, Notion, Attio, GitHub, and ~1,000 more. An agent declares what it needs\nin its `connections:` field; each operator authorizes the accounts their runs\nact as.\n\n## Per-user authorization\n\nConnections are authorized **per user, per workspace**. Because a run executes as\na specific [acting user](/agent-studio/core-concepts/), it uses that user's\nauthorized accounts. A manual run uses yours; a scheduled run uses the\nautomation owner's; an event run uses the trigger owner's. If an agent needs a\nservice nobody has connected, the sidebar surfaces an **\"Action needed\"** prompt\nwith a **Connect** button.\n\n## Three substrates\n\n| Substrate | When to pick it |\n| -------------- | -------------------------------------------------------------------------------------------------------------- |\n| **Composio** | The default. ~1,000 services wrapped as REST tools. Lowercase slugs (`slack`, `googlesheets`). |\n| **Native MCP** | The provider has an official MCP server — richer, schema-aware tools, fewer round trips, and TAS-managed OAuth. |\n| **Secrets** | A plain **API key** for a service with no OAuth (e.g. Clay), read by [sidecar Python tools](/agent-studio/sidecar-python-tools/). |\n\nPrefer **Native MCP** when the provider is in TAS's native catalog (below): it\nuses the provider's official server with richer, schema-aware tools. Most native\nproviders use **TAS-managed OAuth** (just click **Connect**); a couple are\n**bring-your-own OAuth app**. Use **Composio** for anything not in the native\ncatalog.\n\n:::caution[Slugs differ between substrates]\nThe tool slugs for the same provider differ between Composio and Native MCP. If\nyou switch a connection's `source:`, update the agent's `tools:` list to match.\n:::\n\n### Native MCP catalog\n\nTAS ships native MCP support for these providers:\n\n| Provider | Auth |\n| --- | --- |\n| Attio, Pylon, Fathom, Dialed, Linear, Amplemarket, Clay, Avoma, Metabase, Notion, Intercom, Atlassian (Jira), Asana, monday.com, Guru, Fireflies, Amplitude, Apollo, PostHog, Stripe, Vercel, Canva, ClickUp, Close, Sentry, Mixpanel, Granola, Dropbox, Webflow, Cloudflare, Neon, Cal.com, Klaviyo, PayPal, Square, Airtable, Railway, Resend, Hex, Pendo, Similarweb, Datadog, Common Room | **TAS-managed OAuth** — click **Connect**, authorize, done |\n| GitHub, X | **API token** — paste a PAT (GitHub) or App-only Bearer (X) |\n| HubSpot, Gmail, Slack, Gong, Box, PagerDuty, Zoom | **Bring-your-own OAuth app** — admin sets up once (below) |\n\nThere's also a built-in **Tembo Agent Studio** native connection (TAS's own MCP\nserver) that agents use to read/produce [Tasks Inbox](/agent-studio/tasks-inbox/)\nitems and trigger other runs.\n\n:::note[Gmail is in Google's Developer Preview]\nThe Gmail MCP server (`gmailmcp.googleapis.com`) is gated behind the Google\nWorkspace Developer Preview Program — even with the OAuth app + scopes set up,\ntool calls are denied until your Workspace org is enrolled. Until then, connect\nGmail through **Composio** instead.\n:::\n\n### Bring-your-own OAuth app (Manage providers)\n\nA few native providers (HubSpot, Gmail) need a confidential OAuth app you create\nin *their* console — TAS can't self-register one. A **workspace admin** sets this\nup once, then everyone connects normally:\n\n1. **Connections → Manage providers** → find the provider's card.\n2. Create an OAuth app in the provider's console (the card links to its guide).\n Register the **redirect URI shown on the card** and grant the scopes it lists\n (e.g. Gmail needs the Gmail API + Gmail MCP API enabled and the\n `https://mail.google.com/` scope).\n3. Paste the app's **client ID + secret** into the card, then **enable** the\n provider with its toggle.\n4. Members can now **Connect** it from the normal connect flow.\n\nUntil an admin does this, a bring-your-own provider doesn't appear in the connect\nlist — it shows *\"needs an OAuth app first.\"*\n\n## Declaring connections on an agent\n\n```yaml\nconnections:\n - { type: slack, tools: [SLACK_SEND_MESSAGE] }\n - { type: attio, source: native-mcp, name: default, tools: [run-basic-report] }\n```\n\n- `type:` is the provider slug.\n- `source:` defaults to `composio`; set `native-mcp` for native providers.\n- `name:` is the connection slot (\"default\" unless you keep multiple accounts of\n the same provider, e.g. `work` vs `personal`).\n- `tools:` narrows what the agent can call (works on both substrates).\n\n## Authorizing and reconnecting\n\nAuthorize and manage connections under **Connections**. If a credential expires\nor is revoked, the connection is marked stale and runs that need it fail with a\nclear message — reconnect from the same page. See\n[Troubleshooting](/agent-studio/troubleshooting/).\n\n### Viewing another member's connections (admins)\n\nWorkspace admins see a **Viewing** dropdown at the top of the Connections page.\nSwitch it to inspect another member's authorized accounts. When viewing someone\nelse you can **Rename** and **Refresh** their connections; **Connect**,\n**Reconnect**, and **Disconnect** are hidden because OAuth must be performed by\nthat member themselves.\n\nUse this together with the [member detail view](/agent-studio/dashboard-and-runs/#member-detail-admins)\nwhen troubleshooting \"no active connection\" failures for automations or triggers\nthat run as a specific owner.\n\nFor doing deterministic I/O over a connection from Python, see\n[Sidecar Python tools](/agent-studio/sidecar-python-tools/).\n\n## Secrets (API keys)\n\nSome services — like Clay — authenticate with a plain **API key**, not OAuth.\nFor these, use a **Secret**: a free-form, **workspace-level** key an admin sets\nonce under **Connections → Secrets** and the whole workspace shares (unlike the\nper-user OAuth connections above). Secrets are read by an agent's\n[sidecar Python tools](/agent-studio/sidecar-python-tools/) via\n`tas_tools.secret(\"name\")` — they attach no tools and are invisible to the\nmodel.\n\n- **Add one** (admin): Connections → Secrets → name (e.g. `clay`) + value. It's\n encrypted at rest and shown masked.\n- **Use it** in a tool: `tas_tools.secret(\"clay\")` returns the value.\n- **Optionally declare it** on the agent so the studio prompts an admin to set a\n missing one:\n\n ```yaml\n connections:\n - { type: clay, source: secret }\n ```" + "body": "**Connections** are how agents reach outside services — Slack, Gmail, Google\nSheets, Notion, Attio, GitHub, and ~1,000 more. An agent declares what it needs\nin its `connections:` field; each operator authorizes the accounts their runs\nact as.\n\n## Per-user authorization\n\nConnections are authorized **per user, per workspace**. Because a run executes as\na specific [acting user](/agent-studio/core-concepts/), it uses that user's\nauthorized accounts. A manual run uses yours; a scheduled run uses the\nautomation owner's; an event run uses the trigger owner's. If an agent needs a\nservice nobody has connected, the sidebar surfaces an **\"Action needed\"** prompt\nwith a **Connect** button.\n\n## Three substrates\n\n| Substrate | When to pick it |\n| -------------- | -------------------------------------------------------------------------------------------------------------- |\n| **Composio** | The default. ~1,000 services wrapped as REST tools. Lowercase slugs (`slack`, `googlesheets`). |\n| **Native MCP** | The provider has an official MCP server — richer, schema-aware tools, fewer round trips, and TAS-managed OAuth. |\n| **Secrets** | A plain **API key** for a service with no OAuth (e.g. Clay), read by [sidecar Python tools](/agent-studio/sidecar-python-tools/). |\n\nPrefer **Native MCP** when the provider is in TAS's native catalog (below): it\nuses the provider's official server with richer, schema-aware tools. Most native\nproviders use **TAS-managed OAuth** (just click **Connect**); a couple are\n**bring-your-own OAuth app**. Use **Composio** for anything not in the native\ncatalog.\n\n:::caution[Slugs differ between substrates]\nThe tool slugs for the same provider differ between Composio and Native MCP. If\nyou switch a connection's `source:`, update the agent's `tools:` list to match.\n:::\n\n### Native MCP catalog\n\nTAS ships native MCP support for these providers:\n\n| Provider | Auth |\n| --- | --- |\n| Attio, Pylon, Fathom, Dialed, Linear, Amplemarket, Clay, Avoma, Metabase, Notion, Intercom, Atlassian (Jira), Asana, monday.com, Guru, Fireflies, Amplitude, Apollo, PostHog, Stripe, Vercel, Canva, ClickUp, Close, Sentry, Mixpanel, Granola, Dropbox, Webflow, Cloudflare, Neon, Cal.com, Klaviyo, PayPal, Square, Airtable, Railway, Resend, Hex, Pendo, Similarweb, Datadog, Common Room, Outreach, Salesloft, ZoomInfo, Lusha, Hunter, Instantly, Crossbeam, Harmonic, Chili Piper, Day AI, Clarify, Staircase AI, Zendesk, Help Scout, Gorgias, Plain, Lorikeet, Unthread, Enterpret, Dovetail, Missive, Otter.ai, Grain, Krisp, Circleback, tl;dv, Ramp, Brex, Mercury, Expensify, Navan, Carta, Digits, GoCardless, Mercado Pago, PitchBook, Morningstar, CB Insights, Quartr, Daloopa, Consensus, Gusto, Deel, Ashby, Workable, Metaview, Indeed, Udemy Business, SignNow, Vanta, Drata, Figma, Miro, Lucid, Productboard, Aha!, Shortcut, Todoist, Teamwork, Calendly, Superhuman Mail, Craft, Mem, Gamma, Pitch, Eraser, Jotform, Typeform, SurveyMonkey, Egnyte, Mailchimp, Customer.io, Ahrefs, Semrush, Cloudinary, Contentful, Sanity, Wix, WordPress.com, GitBook, Mintlify, DeepL, GitLab, Supabase, Netlify, Heroku, Buildkite, Grafana, New Relic, Honeycomb, incident.io, Rootly, BugSnag, LaunchDarkly, PlanetScale, Prisma Postgres, InstantDB, Algolia, Statsig, Postman, Semgrep, WorkOS, Stytch, Mux, Knock, Lovable, Retool, Telnyx, Jam, Globalping, Airbyte, MotherDuck, Monte Carlo, Atlan, Hugging Face, Zapier, Make, IFTTT, Exa, Tavily, Firecrawl, Apify, Bright Data | **TAS-managed OAuth** — click **Connect**, authorize, done |\n| GitHub, X, Render | **API token** — paste a PAT (GitHub), App-only Bearer (X), or API key (Render) |\n| HubSpot, Gmail, Slack, Gong, Box, PagerDuty, Zoom, DocuSign, Xero, Front, Smartsheet, MongoDB Atlas, CircleCI, Chargebee, BigQuery, Ironclad, Harvey, Tableau, Shopify | **Bring-your-own OAuth app** — admin sets up once (below) |\n\nThere's also a built-in **Tembo Agent Studio** native connection (TAS's own MCP\nserver) that agents use to read/produce [Tasks Inbox](/agent-studio/tasks-inbox/)\nitems and trigger other runs.\n\n:::note[Gmail is in Google's Developer Preview]\nThe Gmail MCP server (`gmailmcp.googleapis.com`) is gated behind the Google\nWorkspace Developer Preview Program — even with the OAuth app + scopes set up,\ntool calls are denied until your Workspace org is enrolled. Until then, connect\nGmail through **Composio** instead.\n:::\n\n### Bring-your-own OAuth app (Manage providers)\n\nA few native providers (HubSpot, Gmail) need a confidential OAuth app you create\nin *their* console — TAS can't self-register one. A **workspace admin** sets this\nup once, then everyone connects normally:\n\n1. **Connections → Manage providers** → find the provider's card.\n2. Create an OAuth app in the provider's console (the card links to its guide).\n Register the **redirect URI shown on the card** and grant the scopes it lists\n (e.g. Gmail needs the Gmail API + Gmail MCP API enabled and the\n `https://mail.google.com/` scope).\n3. Paste the app's **client ID + secret** into the card, then **enable** the\n provider with its toggle.\n4. Members can now **Connect** it from the normal connect flow.\n\nUntil an admin does this, a bring-your-own provider doesn't appear in the connect\nlist — it shows *\"needs an OAuth app first.\"*\n\n## Declaring connections on an agent\n\n```yaml\nconnections:\n - { type: slack, tools: [SLACK_SEND_MESSAGE] }\n - { type: attio, source: native-mcp, name: default, tools: [run-basic-report] }\n```\n\n- `type:` is the provider slug.\n- `source:` defaults to `composio`; set `native-mcp` for native providers.\n- `name:` is the connection slot (\"default\" unless you keep multiple accounts of\n the same provider, e.g. `work` vs `personal`).\n- `tools:` narrows what the agent can call (works on both substrates).\n\n## Authorizing and reconnecting\n\nAuthorize and manage connections under **Connections**. If a credential expires\nor is revoked, the connection is marked stale and runs that need it fail with a\nclear message — reconnect from the same page. See\n[Troubleshooting](/agent-studio/troubleshooting/).\n\n### Viewing another member's connections (admins)\n\nWorkspace admins see a **Viewing** dropdown at the top of the Connections page.\nSwitch it to inspect another member's authorized accounts. When viewing someone\nelse you can **Rename** and **Refresh** their connections; **Connect**,\n**Reconnect**, and **Disconnect** are hidden because OAuth must be performed by\nthat member themselves.\n\nUse this together with the [member detail view](/agent-studio/dashboard-and-runs/#member-detail-admins)\nwhen troubleshooting \"no active connection\" failures for automations or triggers\nthat run as a specific owner.\n\nFor doing deterministic I/O over a connection from Python, see\n[Sidecar Python tools](/agent-studio/sidecar-python-tools/).\n\n## Secrets (API keys)\n\nSome services — like Clay — authenticate with a plain **API key**, not OAuth.\nFor these, use a **Secret**: a free-form, **workspace-level** key an admin sets\nonce under **Connections → Secrets** and the whole workspace shares (unlike the\nper-user OAuth connections above). Secrets are read by an agent's\n[sidecar Python tools](/agent-studio/sidecar-python-tools/) via\n`tas_tools.secret(\"name\")` — they attach no tools and are invisible to the\nmodel.\n\n- **Add one** (admin): Connections → Secrets → name (e.g. `clay`) + value. It's\n encrypted at rest and shown masked.\n- **Use it** in a tool: `tas_tools.secret(\"clay\")` returns the value.\n- **Optionally declare it** on the agent so the studio prompts an admin to set a\n missing one:\n\n ```yaml\n connections:\n - { type: clay, source: secret }\n ```" }, "core-concepts": { "title": "Core concepts", diff --git a/web/src/lib/mcp-logo.ts b/web/src/lib/mcp-logo.ts index 91d804a9..0562d205 100644 --- a/web/src/lib/mcp-logo.ts +++ b/web/src/lib/mcp-logo.ts @@ -2,25 +2,104 @@ // // Most provider logos come from Composio's public logo CDN (every Composio // toolkit slug — slack, linear, attio, hubspot, … — resolves there, and our -// native-MCP slugs mostly reuse those same slugs). A few of our native-only -// MCPs aren't in Composio's catalog (Pylon, Dialed) or are our own service -// (Tembo Agent Studio), so the CDN 404s for them — we ship local art instead. +// native-MCP slugs mostly reuse those same slugs). Two escape hatches: +// +// - CDN_SLUG_ALIASES: our slug differs from Composio's (they use underscores +// for some multi-word brands — new_relic, help_scout, …). +// - LOCAL_LOGOS: the provider isn't in Composio's catalog at all. The CDN +// doesn't 404 for unknown slugs — it serves a generic fallback glyph — so +// "missing" was detected by hashing responses against that fallback +// (2026-07-18 sweep), and we ship local art (official favicons / +// apple-touch-icons) instead. // // Pure data + string building (no server-only deps) so both server components // and the client logo widgets can call it. Callers keep their own onError // fallback to a generic glyph, so a missing/blocked URL still degrades cleanly. +// Composio catalogs these brands under different slugs than ours. +const CDN_SLUG_ALIASES: Record = { + newrelic: "new_relic", + launchdarkly: "launch_darkly", + huggingface: "hugging_face", + helpscout: "help_scout", + surveymonkey: "survey_monkey", +}; + const LOCAL_LOGOS: Record = { "tembo-agent-studio": "/favicons/default-tembo.svg", pylon: "/mcp-logos/pylon.svg", dialed: "/mcp-logos/dialed.svg", amplemarket: "/mcp-logos/amplemarket.svg", avoma: "/mcp-logos/avoma.png", + // 2026-07 batch-3 sweep: not in Composio's catalog (CDN serves its generic + // fallback for these) — official favicons/apple-touch-icons shipped locally. + aha: "/mcp-logos/aha.png", + atlan: "/mcp-logos/atlan.png", + bigquery: "/mcp-logos/bigquery.png", + carta: "/mcp-logos/carta.png", + cbinsights: "/mcp-logos/cbinsights.png", + chargebee: "/mcp-logos/chargebee.png", + chilipiper: "/mcp-logos/chilipiper.png", + circleback: "/mcp-logos/circleback.png", + clarify: "/mcp-logos/clarify.png", + commonroom: "/mcp-logos/commonroom.png", + consensus: "/mcp-logos/consensus.png", + craft: "/mcp-logos/craft.png", + crossbeam: "/mcp-logos/crossbeam.png", + daloopa: "/mcp-logos/daloopa.png", + dayai: "/mcp-logos/dayai.png", + deepl: "/mcp-logos/deepl.png", + digits: "/mcp-logos/digits.png", + drata: "/mcp-logos/drata.png", + enterpret: "/mcp-logos/enterpret.png", + eraser: "/mcp-logos/eraser.ico", + gocardless: "/mcp-logos/gocardless.png", + granola: "/mcp-logos/granola.png", + harmonic: "/mcp-logos/harmonic.png", + harvey: "/mcp-logos/harvey.png", + honeycomb: "/mcp-logos/honeycomb.png", + ifttt: "/mcp-logos/ifttt.png", + indeed: "/mcp-logos/indeed.png", + instantdb: "/mcp-logos/instantdb.png", + ironclad: "/mcp-logos/ironclad.png", + knock: "/mcp-logos/knock.png", + krisp: "/mcp-logos/krisp.png", + lorikeet: "/mcp-logos/lorikeet.png", + lovable: "/mcp-logos/lovable.png", + lucid: "/mcp-logos/lucid.png", + mercadopago: "/mcp-logos/mercadopago.png", + mercury: "/mcp-logos/mercury.png", + metaview: "/mcp-logos/metaview.png", + montecarlo: "/mcp-logos/montecarlo.png", + morningstar: "/mcp-logos/morningstar.png", + motherduck: "/mcp-logos/motherduck.png", + navan: "/mcp-logos/navan.png", + otter: "/mcp-logos/otter.png", + pitch: "/mcp-logos/pitch.jpg", + pitchbook: "/mcp-logos/pitchbook.png", + planetscale: "/mcp-logos/planetscale.ico", + quartr: "/mcp-logos/quartr.png", + retool: "/mcp-logos/retool.png", + salesloft: "/mcp-logos/salesloft.jpg", + semgrep: "/mcp-logos/semgrep.png", + signnow: "/mcp-logos/signnow.png", + similarweb: "/mcp-logos/similarweb.png", + smartsheet: "/mcp-logos/smartsheet.png", + staircase: "/mcp-logos/staircase.png", + statsig: "/mcp-logos/statsig.png", + stytch: "/mcp-logos/stytch.png", + superhuman: "/mcp-logos/superhuman.png", + teamwork: "/mcp-logos/teamwork.png", + udemy: "/mcp-logos/udemy.png", + unthread: "/mcp-logos/unthread.png", + workos: "/mcp-logos/workos.png", + zapier: "/mcp-logos/zapier.png", }; export function mcpLogoUrl(slug: string): string { const s = slug.trim().toLowerCase(); + const cdnSlug = CDN_SLUG_ALIASES[s] ?? s; return ( - LOCAL_LOGOS[s] ?? `https://logos.composio.dev/api/${encodeURIComponent(s)}` + LOCAL_LOGOS[s] ?? `https://logos.composio.dev/api/${encodeURIComponent(cdnSlug)}` ); } diff --git a/web/src/lib/mcp-providers.allowlist-sync.test.ts b/web/src/lib/mcp-providers.allowlist-sync.test.ts index b8c37159..2827402f 100644 --- a/web/src/lib/mcp-providers.allowlist-sync.test.ts +++ b/web/src/lib/mcp-providers.allowlist-sync.test.ts @@ -3,43 +3,28 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { MCP_PROVIDERS } from "./mcp-providers"; +import { + GENERATED_FILE_REPO_PATH, + renderNativeOauthAllowlistRs, +} from "../../scripts/gen-native-oauth-allowlist"; -// Drift guard. The Rust token-refresh path keeps its OWN origin allowlist -// (api/src/native_oauth.rs, NATIVE_MCP_OAUTH_ALLOWLIST) because the api crate -// can't import this TS catalog. If a new native-MCP provider is added here but -// not there, refreshes for it abort with "origin is not in the allowlist" and -// its short-lived tokens 401 mid-run — exactly the Dialed regression. This test -// fails CI when the two drift, so the Rust side can't be forgotten. +// Staleness guard. The Rust token-refresh path's origin allowlist +// (api/src/native_oauth_allowlist.rs) is GENERATED from the MCP_PROVIDERS +// catalog by web/scripts/gen-native-oauth-allowlist.ts, because the api crate +// can't import this TS catalog. If the catalog changes but the generated file +// isn't regenerated, refreshes for the new/changed provider abort with +// "origin is not in the allowlist" and its short-lived tokens 401 mid-run — +// exactly the Dialed regression. // -// CI checks out the whole repo, so api/ is reachable from the web package. -describe("native-MCP OAuth origin allowlist stays in sync with the Rust refresher", () => { - const rs = readFileSync( - join(process.cwd(), "..", "api", "src", "native_oauth.rs"), - "utf8", - ); - - for (const provider of Object.values(MCP_PROVIDERS)) { - // Only OAuth providers are refreshed (self-key / PAT providers like - // tembo-agent-studio have no auth-server origins and aren't in the sweep). - if (provider.oauthAuthorizationServerOrigins.length === 0) continue; - - it(`${provider.slug}'s MCP origin is in native_oauth.rs`, () => { - const mcpOrigin = new URL(provider.mcpServerUrl).origin; - expect( - rs.includes(`"${mcpOrigin}"`), - `${provider.slug} (${mcpOrigin}) is in the web catalog but missing from ` + - `NATIVE_MCP_OAUTH_ALLOWLIST in api/src/native_oauth.rs — add it there or ` + - `token refresh for ${provider.slug} will abort and its tokens will 401 mid-run.`, - ).toBe(true); - - // And each advertised OAuth authorization-server origin must be allowed too. - for (const oauthOrigin of provider.oauthAuthorizationServerOrigins) { - expect( - rs.includes(`"${oauthOrigin}"`), - `${provider.slug}'s OAuth origin ${oauthOrigin} is missing from native_oauth.rs.`, - ).toBe(true); - } - }); - } +// Unlike the old hand-synced drift test, the fix here is never "go edit the +// Rust file": it's always one command. CI checks out the whole repo, so api/ +// is reachable from the web package. +describe("generated native-MCP OAuth allowlist (api/src/native_oauth_allowlist.rs)", () => { + it("is up to date with the MCP_PROVIDERS catalog — if this fails, run `npm run gen:allowlist` in web/ and commit the result", () => { + const onDisk = readFileSync( + join(process.cwd(), "..", GENERATED_FILE_REPO_PATH), + "utf8", + ); + expect(onDisk).toBe(renderNativeOauthAllowlistRs()); + }); }); diff --git a/web/src/lib/mcp-providers.ts b/web/src/lib/mcp-providers.ts index 2d5a3cf3..8174d39d 100644 --- a/web/src/lib/mcp-providers.ts +++ b/web/src/lib/mcp-providers.ts @@ -71,6 +71,146 @@ export type McpProviderSlug = | "pagerduty" | "slack" | "zoom" + // Batch 2026-07 (#3): hosted MCP servers harvested from the official MCP + // registry (54k entries swept), the claude.com/connectors directory, and + // community remote-MCP lists — every endpoint live-probed (initialize + + // /.well-known discovery + registration_endpoint) on 2026-07-18. + | "outreach" + | "salesloft" + | "zoominfo" + | "lusha" + | "hunter" + | "instantly" + | "crossbeam" + | "harmonic" + | "chilipiper" + | "dayai" + | "clarify" + | "staircase" + | "zendesk" + | "helpscout" + | "gorgias" + | "plain" + | "lorikeet" + | "unthread" + | "enterpret" + | "dovetail" + | "missive" + | "otter" + | "grain" + | "krisp" + | "circleback" + | "tldv" + | "ramp" + | "brex" + | "mercury" + | "expensify" + | "navan" + | "carta" + | "digits" + | "gocardless" + | "mercadopago" + | "pitchbook" + | "morningstar" + | "cbinsights" + | "quartr" + | "daloopa" + | "consensus" + | "gusto" + | "deel" + | "ashby" + | "workable" + | "metaview" + | "indeed" + | "udemy" + | "signnow" + | "vanta" + | "drata" + | "figma" + | "miro" + | "lucid" + | "productboard" + | "aha" + | "shortcut" + | "todoist" + | "teamwork" + | "calendly" + | "superhuman" + | "craft" + | "mem" + | "gamma" + | "pitch" + | "eraser" + | "jotform" + | "typeform" + | "surveymonkey" + | "egnyte" + | "mailchimp" + | "customerio" + | "ahrefs" + | "semrush" + | "cloudinary" + | "contentful" + | "sanity" + | "wix" + | "wordpress" + | "gitbook" + | "mintlify" + | "deepl" + | "gitlab" + | "supabase" + | "netlify" + | "heroku" + | "buildkite" + | "grafana" + | "newrelic" + | "honeycomb" + | "incidentio" + | "rootly" + | "bugsnag" + | "launchdarkly" + | "planetscale" + | "prisma" + | "instantdb" + | "algolia" + | "statsig" + | "postman" + | "semgrep" + | "workos" + | "stytch" + | "mux" + | "knock" + | "lovable" + | "retool" + | "telnyx" + | "jam" + | "globalping" + | "airbyte" + | "motherduck" + | "montecarlo" + | "atlan" + | "huggingface" + | "zapier" + | "make" + | "ifttt" + | "exa" + | "tavily" + | "firecrawl" + | "apify" + | "brightdata" + | "docusign" + | "xero" + | "front" + | "smartsheet" + | "mongodb" + | "circleci" + | "chargebee" + | "bigquery" + | "ironclad" + | "harvey" + | "tableau" + | "shopify" + | "render" | "tembo-agent-studio"; export type McpProvider = { @@ -83,7 +223,13 @@ export type McpProvider = { * since the origin is env-derived rather than a constant. */ mcpServerUrl: string; /** Exact OAuth authorization-server origins this provider is allowed - * to advertise through protected-resource discovery. */ + * to advertise through protected-resource discovery. + * + * Also the source of truth for the Rust token-refresher's origin + * allowlist: api/src/native_oauth_allowlist.rs is GENERATED from this + * field (plus mcpServerUrl's origin). After changing either, run + * `npm run gen:allowlist` and commit the regenerated file — the + * allowlist-sync vitest fails CI while it's stale. */ oauthAuthorizationServerOrigins: string[]; /** * How TAS obtains an OAuth client for this provider: @@ -718,6 +864,1024 @@ export const MCP_PROVIDERS: Record = { ], authMode: "manual", }, + // ── Batch 2026-07 (#3): registry + connectors-directory sweep ── + // Endpoints + auth verified by live probe on 2026-07-18: POST initialize → + // 401 challenge → protected-resource metadata → auth-server metadata with a + // registration_endpoint (DCR) unless marked authMode manual/pat. Origin + // lists are the union of the advertised authorization server plus the + // authorize/token/registration endpoint origins (some differ, like Fathom). + // Several DCR servers advertise only confidential token auth + // (client_secret_post/basic) — the dcr_confidential path handles those, as + // with Avoma. offline_access is left at the default; add omitOfflineAccess + // reactively if a server strictly rejects it at Connect (the Amplemarket + // failure mode). Connect-verify each on the dogfood instance before + // relying on it. + // ── Sales / GTM ── + outreach: { + slug: "outreach", + displayName: "Outreach", + // Requires a licensed seat with the Amplify add-on; connects fine, tools + // 403 without it. + mcpServerUrl: "https://api.outreach.io/mcp/", + oauthAuthorizationServerOrigins: ["https://api.outreach.io"], + }, + salesloft: { + slug: "salesloft", + displayName: "Salesloft", + // Requires the Salesloft Agentic add-on (admin-enabled). + mcpServerUrl: "https://mcp.salesloft.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://accounts.salesloft.com", + "https://mcp.salesloft.com", + ], + }, + zoominfo: { + slug: "zoominfo", + displayName: "ZoomInfo", + mcpServerUrl: "https://mcp.zoominfo.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://mcp.zoominfo.com", + "https://okta-login.zoominfo.com", + ], + }, + lusha: { + slug: "lusha", + displayName: "Lusha", + mcpServerUrl: "https://mcp.lusha.com/mcp/claude", + oauthAuthorizationServerOrigins: ["https://auth.lusha.com"], + }, + hunter: { + slug: "hunter", + displayName: "Hunter", + mcpServerUrl: "https://mcp.hunter.io/mcp", + oauthAuthorizationServerOrigins: ["https://hunter.io"], + }, + instantly: { + slug: "instantly", + displayName: "Instantly", + mcpServerUrl: "https://mcp.instantly.ai/mcp", + oauthAuthorizationServerOrigins: ["https://api.instantly.ai"], + }, + crossbeam: { + slug: "crossbeam", + displayName: "Crossbeam", + // MCP endpoint is the origin root (like Pylon). + mcpServerUrl: "https://mcp.crossbeam.com/", + oauthAuthorizationServerOrigins: ["https://mcp.crossbeam.com"], + }, + harmonic: { + slug: "harmonic", + displayName: "Harmonic", + // MCP endpoint is the origin root (like Pylon). + mcpServerUrl: "https://mcp.api.harmonic.ai/", + oauthAuthorizationServerOrigins: ["https://mcp.api.harmonic.ai"], + }, + chilipiper: { + slug: "chilipiper", + displayName: "Chili Piper", + mcpServerUrl: "https://fire.chilipiper.com/api/fire-edge/v1/org/mcp", + oauthAuthorizationServerOrigins: ["https://fire.chilipiper.com"], + }, + dayai: { + slug: "dayai", + displayName: "Day AI", + mcpServerUrl: "https://day.ai/api/mcp", + oauthAuthorizationServerOrigins: ["https://day.ai"], + }, + clarify: { + slug: "clarify", + displayName: "Clarify", + mcpServerUrl: "https://api.clarify.ai/mcp", + oauthAuthorizationServerOrigins: ["https://auth1.clarify.ai"], + }, + staircase: { + slug: "staircase", + displayName: "Staircase AI", + // Gainsight's customer-intelligence product. + mcpServerUrl: "https://mcp.staircase.ai/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.staircase.ai"], + }, + // ── Support / CX ── + zendesk: { + slug: "zendesk", + displayName: "Zendesk", + // Live with DCR but Zendesk's own comms describe MCP as early-access — + // expect account gating. + mcpServerUrl: "https://mcp.zendesk.com/api/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.zendesk.com"], + }, + helpscout: { + slug: "helpscout", + displayName: "Help Scout", + // Beta endpoint (mcp.helpscout.net). + mcpServerUrl: "https://mcp.helpscout.net/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.helpscout.net"], + }, + gorgias: { + slug: "gorgias", + displayName: "Gorgias", + mcpServerUrl: "https://mcp.gorgias.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.gorgias.com"], + }, + plain: { + slug: "plain", + displayName: "Plain", + mcpServerUrl: "https://mcp.plain.com/mcp", + oauthAuthorizationServerOrigins: ["https://signin.auth.plain.com"], + }, + lorikeet: { + slug: "lorikeet", + displayName: "Lorikeet", + mcpServerUrl: "https://api.lorikeetcx.ai/v1/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.lorikeetcx.ai", + "https://app.lorikeetcx.ai", + ], + }, + unthread: { + slug: "unthread", + displayName: "Unthread", + mcpServerUrl: "https://app.unthread.io/api/mcp", + oauthAuthorizationServerOrigins: ["https://app.unthread.io"], + }, + enterpret: { + slug: "enterpret", + displayName: "Enterpret", + mcpServerUrl: "https://wisdom-api.enterpret.com/server/mcp", + oauthAuthorizationServerOrigins: ["https://oauth.enterpret.com"], + }, + dovetail: { + slug: "dovetail", + displayName: "Dovetail", + mcpServerUrl: "https://dovetail.com/api/mcp", + oauthAuthorizationServerOrigins: ["https://auth.dovetail.com"], + }, + missive: { + slug: "missive", + displayName: "Missive", + mcpServerUrl: "https://mcp.missiveapp.com/mcp", + oauthAuthorizationServerOrigins: ["https://auth.missiveapp.com"], + }, + // ── Meeting intelligence ── + otter: { + slug: "otter", + displayName: "Otter.ai", + mcpServerUrl: "https://mcp.otter.ai/mcp", + oauthAuthorizationServerOrigins: ["https://otter.ai"], + }, + grain: { + slug: "grain", + displayName: "Grain", + mcpServerUrl: "https://api.grain.com/_/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.grain.com", + "https://grain.com", + ], + }, + krisp: { + slug: "krisp", + displayName: "Krisp", + mcpServerUrl: "https://mcp.krisp.ai/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.krisp.ai", + "https://mcp.krisp.ai", + ], + }, + circleback: { + slug: "circleback", + displayName: "Circleback", + mcpServerUrl: "https://app.circleback.ai/api/mcp", + oauthAuthorizationServerOrigins: ["https://app.circleback.ai"], + }, + tldv: { + slug: "tldv", + displayName: "tl;dv", + mcpServerUrl: "https://mcp.tldv.io/mcp", + oauthAuthorizationServerOrigins: ["https://keycloak.tldv.io"], + }, + // ── Finance / spend / payments ── + ramp: { + slug: "ramp", + displayName: "Ramp", + // Write actions (approve reimbursements, edit txns) honor Ramp's admin + // access controls. + mcpServerUrl: "https://mcp.ramp.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.ramp.com", + "https://mcp.ramp.com", + ], + }, + brex: { + slug: "brex", + displayName: "Brex", + mcpServerUrl: "https://api.brex.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://accounts-api.brex.com", + "https://api.brex.com", + ], + }, + mercury: { + slug: "mercury", + displayName: "Mercury", + mcpServerUrl: "https://mcp.mercury.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.mercury.com"], + }, + expensify: { + slug: "expensify", + displayName: "Expensify", + mcpServerUrl: "https://mcp.expensify.com/mcp", + oauthAuthorizationServerOrigins: ["https://www.expensify.com"], + }, + navan: { + slug: "navan", + displayName: "Navan", + mcpServerUrl: "https://mcp.navan.com/mcp", + oauthAuthorizationServerOrigins: ["https://login.navan.com"], + }, + carta: { + slug: "carta", + displayName: "Carta", + mcpServerUrl: "https://mcp.app.carta.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.app.carta.com"], + }, + digits: { + slug: "digits", + displayName: "Digits", + mcpServerUrl: "https://api.digits.com/mcp", + oauthAuthorizationServerOrigins: ["https://api.digits.com"], + }, + gocardless: { + slug: "gocardless", + displayName: "GoCardless", + mcpServerUrl: "https://mcp.gocardless.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.gocardless.com"], + }, + mercadopago: { + slug: "mercadopago", + displayName: "Mercado Pago", + mcpServerUrl: "https://mcp.mercadopago.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://auth.mercadopago.com", + "https://mcp.mercadopago.com", + ], + }, + // ── Financial & market intelligence ── + pitchbook: { + slug: "pitchbook", + displayName: "PitchBook", + // Requires a PitchBook Premium seat. + mcpServerUrl: "https://premium.mcp.pitchbook.com/mcp", + oauthAuthorizationServerOrigins: ["https://premium.mcp.pitchbook.com"], + }, + morningstar: { + slug: "morningstar", + displayName: "Morningstar", + mcpServerUrl: "https://mcp.morningstar.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.morningstar.com"], + }, + cbinsights: { + slug: "cbinsights", + displayName: "CB Insights", + mcpServerUrl: "https://mcp.cbinsights.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.cbinsights.com"], + }, + quartr: { + slug: "quartr", + displayName: "Quartr", + mcpServerUrl: "https://mcp.quartr.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.quartr.com"], + }, + daloopa: { + slug: "daloopa", + displayName: "Daloopa", + mcpServerUrl: "https://mcp.daloopa.com/server/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.daloopa.com"], + }, + consensus: { + slug: "consensus", + displayName: "Consensus", + // Scientific-paper search/synthesis. + mcpServerUrl: "https://mcp.consensus.app/mcp", + oauthAuthorizationServerOrigins: ["https://consensus.app"], + }, + // ── HR / recruiting / learning ── + gusto: { + slug: "gusto", + displayName: "Gusto", + // Read-only tools; OAuth scoped by data category. + mcpServerUrl: "https://mcp.api.gusto.com/anthropic", + oauthAuthorizationServerOrigins: ["https://mcp.api.gusto.com"], + }, + deel: { + slug: "deel", + displayName: "Deel", + mcpServerUrl: "https://api.letsdeel.com/mcp", + oauthAuthorizationServerOrigins: ["https://api.letsdeel.com"], + }, + ashby: { + slug: "ashby", + displayName: "Ashby", + mcpServerUrl: "https://mcp.ashbyhq.com/mcp/v1", + oauthAuthorizationServerOrigins: ["https://mcp-auth.ashbyhq.com"], + }, + workable: { + slug: "workable", + displayName: "Workable", + mcpServerUrl: "https://mcp.workable.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://mcp.workable.com", + "https://workable.com", + ], + }, + metaview: { + slug: "metaview", + displayName: "Metaview", + mcpServerUrl: "https://mcp.metaview.ai/mcp", + oauthAuthorizationServerOrigins: ["https://auth.metaview.ai"], + }, + indeed: { + slug: "indeed", + displayName: "Indeed", + mcpServerUrl: "https://mcp.indeed.com/claude/mcp", + oauthAuthorizationServerOrigins: [ + "https://apis.indeed.com", + "https://secure.indeed.com", + ], + }, + udemy: { + slug: "udemy", + displayName: "Udemy Business", + mcpServerUrl: "https://api.udemy.com/mcp", + oauthAuthorizationServerOrigins: ["https://api.udemy.com"], + }, + // ── Legal / compliance ── + signnow: { + slug: "signnow", + displayName: "SignNow", + mcpServerUrl: "https://mcp-server.signnow.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp-server.signnow.com"], + }, + vanta: { + slug: "vanta", + displayName: "Vanta", + mcpServerUrl: "https://mcp.vanta.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.vanta.com", + "https://app.vanta.com", + "https://mcp.vanta.com", + ], + }, + drata: { + slug: "drata", + displayName: "Drata", + mcpServerUrl: "https://mcp.drata.com/mcp/", + oauthAuthorizationServerOrigins: [ + "https://drata-prod.us.auth0.com", + "https://mcp.drata.com", + ], + }, + // ── Productivity / PM / design ── + figma: { + slug: "figma", + displayName: "Figma", + // All plans; Starter/view-only seats are capped at ~6 tool calls/month. + mcpServerUrl: "https://mcp.figma.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.figma.com", + "https://www.figma.com", + ], + }, + miro: { + slug: "miro", + displayName: "Miro", + // Beta; Enterprise teams need admin enablement. MCP endpoint is the origin + // root. + mcpServerUrl: "https://mcp.miro.com/", + oauthAuthorizationServerOrigins: ["https://mcp.miro.com"], + }, + lucid: { + slug: "lucid", + displayName: "Lucid", + mcpServerUrl: "https://mcp.lucid.app/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.lucid.app"], + }, + productboard: { + slug: "productboard", + displayName: "Productboard", + mcpServerUrl: "https://mcp.productboard.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.productboard.com"], + }, + aha: { + slug: "aha", + displayName: "Aha!", + mcpServerUrl: "https://mcp.aha.io/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.aha.io"], + }, + shortcut: { + slug: "shortcut", + displayName: "Shortcut", + mcpServerUrl: "https://mcp.shortcut.com/mcp", + oauthAuthorizationServerOrigins: ["https://api.app.shortcut.com"], + }, + todoist: { + slug: "todoist", + displayName: "Todoist", + mcpServerUrl: "https://ai.todoist.net/mcp", + oauthAuthorizationServerOrigins: ["https://todoist.com"], + }, + teamwork: { + slug: "teamwork", + displayName: "Teamwork", + // MCP endpoint is the origin root. + mcpServerUrl: "https://mcp.ai.teamwork.com/", + oauthAuthorizationServerOrigins: [ + "https://teamwork.com", + "https://www.teamwork.com", + ], + }, + calendly: { + slug: "calendly", + displayName: "Calendly", + // MCP endpoint is the origin root; works on free plans. + mcpServerUrl: "https://mcp.calendly.com/", + oauthAuthorizationServerOrigins: ["https://calendly.com"], + }, + superhuman: { + slug: "superhuman", + displayName: "Superhuman Mail", + mcpServerUrl: "https://mcp.mail.superhuman.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.auth.mail.superhuman.com"], + }, + craft: { + slug: "craft", + displayName: "Craft", + mcpServerUrl: "https://mcp.craft.do/my/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.craft.do"], + }, + mem: { + slug: "mem", + displayName: "Mem", + mcpServerUrl: "https://mcp.mem.ai/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.mem.ai", + "https://mem.ai", + ], + }, + gamma: { + slug: "gamma", + displayName: "Gamma", + mcpServerUrl: "https://mcp.gamma.app/mcp", + oauthAuthorizationServerOrigins: ["https://auth.gamma.app"], + }, + pitch: { + slug: "pitch", + displayName: "Pitch", + mcpServerUrl: "https://mcp.pitch.com/mcp", + oauthAuthorizationServerOrigins: ["https://auth.pitch.com"], + }, + eraser: { + slug: "eraser", + displayName: "Eraser", + mcpServerUrl: "https://app.eraser.io/api/mcp", + oauthAuthorizationServerOrigins: ["https://app.eraser.io"], + }, + jotform: { + slug: "jotform", + displayName: "Jotform", + mcpServerUrl: "https://mcp.jotform.com/mcp-app", + oauthAuthorizationServerOrigins: ["https://oauth2.jotform.com"], + }, + typeform: { + slug: "typeform", + displayName: "Typeform", + // US endpoint; EU accounts use api.eu.typeform.com (not cataloged yet). + mcpServerUrl: "https://api.typeform.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://admin.typeform.com", + "https://api.typeform.com", + ], + }, + surveymonkey: { + slug: "surveymonkey", + displayName: "SurveyMonkey", + mcpServerUrl: "https://mcp.surveymonkey.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.surveymonkey.com"], + }, + egnyte: { + slug: "egnyte", + displayName: "Egnyte", + mcpServerUrl: "https://mcp-server.egnyte.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp-oauth.egnyte.com"], + }, + // ── Marketing / content / CMS ── + mailchimp: { + slug: "mailchimp", + displayName: "Mailchimp", + mcpServerUrl: "https://ai-inc.mailchimp.com/claude/mcp/v2", + oauthAuthorizationServerOrigins: ["https://ai-inc.mailchimp.com"], + }, + customerio: { + slug: "customerio", + displayName: "Customer.io", + mcpServerUrl: "https://mcp.customer.io/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.customer.io"], + }, + ahrefs: { + slug: "ahrefs", + displayName: "Ahrefs", + mcpServerUrl: "https://api.ahrefs.com/mcp/mcp", + oauthAuthorizationServerOrigins: [ + "https://ahrefs.com", + "https://api.ahrefs.com", + "https://app.ahrefs.com", + ], + }, + semrush: { + slug: "semrush", + displayName: "Semrush", + mcpServerUrl: "https://mcp.semrush.com/claude/v1/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.semrush.com", + "https://oauth.semrush.com", + ], + }, + cloudinary: { + slug: "cloudinary", + displayName: "Cloudinary", + // Asset-management server (their MCP suite has several; this is the + // primary). + mcpServerUrl: "https://asset-management.mcp.cloudinary.com/mcp", + oauthAuthorizationServerOrigins: ["https://asset-management.mcp.cloudinary.com"], + }, + contentful: { + slug: "contentful", + displayName: "Contentful", + mcpServerUrl: "https://mcp.contentful.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.contentful.com"], + }, + sanity: { + slug: "sanity", + displayName: "Sanity", + mcpServerUrl: "https://mcp.sanity.io/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.sanity.io"], + }, + wix: { + slug: "wix", + displayName: "Wix", + mcpServerUrl: "https://mcp.wix.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.wix.com"], + }, + wordpress: { + slug: "wordpress", + displayName: "WordPress.com", + // WordPress.com-hosted sites only (public-api.wordpress.com), not + // self-hosted WP. + mcpServerUrl: "https://public-api.wordpress.com/wpcom/v2/mcp/v1", + oauthAuthorizationServerOrigins: ["https://public-api.wordpress.com"], + }, + gitbook: { + slug: "gitbook", + displayName: "GitBook", + mcpServerUrl: "https://mcp.gitbook.com/mcp", + oauthAuthorizationServerOrigins: ["https://oauth.gitbook.com"], + }, + mintlify: { + slug: "mintlify", + displayName: "Mintlify", + mcpServerUrl: "https://mcp.mintlify.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.mintlify.com"], + }, + deepl: { + slug: "deepl", + displayName: "DeepL", + // Seat-based plans; fair-usage limits. + mcpServerUrl: "https://mcp.deepl.com/v1/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.deepl.com"], + }, + // ── Dev / infra / observability ── + gitlab: { + slug: "gitlab", + displayName: "GitLab", + // gitlab.com only; self-managed instances would need an instance-based + // entry. + mcpServerUrl: "https://gitlab.com/api/v4/mcp", + oauthAuthorizationServerOrigins: ["https://gitlab.com"], + }, + supabase: { + slug: "supabase", + displayName: "Supabase", + // Supports ?read_only=true and ?project_ref= query params on the MCP URL. + mcpServerUrl: "https://mcp.supabase.com/mcp", + oauthAuthorizationServerOrigins: ["https://api.supabase.com"], + }, + netlify: { + slug: "netlify", + displayName: "Netlify", + mcpServerUrl: "https://netlify-mcp.netlify.app/mcp", + oauthAuthorizationServerOrigins: ["https://netlify-mcp.netlify.app"], + }, + heroku: { + slug: "heroku", + displayName: "Heroku", + mcpServerUrl: "https://mcp.heroku.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.heroku.com"], + }, + buildkite: { + slug: "buildkite", + displayName: "Buildkite", + // A read-only variant exists at /mcp/readonly. + mcpServerUrl: "https://mcp.buildkite.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.buildkite.com"], + }, + grafana: { + slug: "grafana", + displayName: "Grafana", + // Grafana Cloud only (self-hosted OSS uses a local server). + mcpServerUrl: "https://mcp.grafana.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.grafana.com"], + }, + newrelic: { + slug: "newrelic", + displayName: "New Relic", + // US endpoint; EU is mcp.eu.newrelic.com. Not permitted for FedRAMP/HIPAA + // accounts. + mcpServerUrl: "https://mcp.newrelic.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://login.newrelic.com", + "https://mcp.newrelic.com", + ], + }, + honeycomb: { + slug: "honeycomb", + displayName: "Honeycomb", + // Requires Honeycomb Intelligence enabled; EU is mcp.eu1.honeycomb.io. + mcpServerUrl: "https://mcp.honeycomb.io/mcp", + oauthAuthorizationServerOrigins: ["https://ui.honeycomb.io"], + }, + incidentio: { + slug: "incidentio", + displayName: "incident.io", + mcpServerUrl: "https://mcp.incident.io/mcp", + oauthAuthorizationServerOrigins: [ + "https://app.incident.io", + "https://mcp.incident.io", + ], + }, + rootly: { + slug: "rootly", + displayName: "Rootly", + // SSE endpoint (/sse) — no streamable-http path advertised. + mcpServerUrl: "https://mcp.rootly.com/sse", + oauthAuthorizationServerOrigins: ["https://rootly.com"], + }, + bugsnag: { + slug: "bugsnag", + displayName: "BugSnag", + mcpServerUrl: "https://bugsnag.mcp.smartbear.com/mcp", + oauthAuthorizationServerOrigins: ["https://oauth.bugsnag.com"], + }, + launchdarkly: { + slug: "launchdarkly", + displayName: "LaunchDarkly", + // Product-area servers under /mcp/{area}; this is the core flags server. + // EU/federal instances unsupported. + mcpServerUrl: "https://mcp.launchdarkly.com/mcp/launchdarkly", + oauthAuthorizationServerOrigins: [ + "https://app.launchdarkly.com", + "https://mcp.launchdarkly.com", + ], + }, + planetscale: { + slug: "planetscale", + displayName: "PlanetScale", + mcpServerUrl: "https://mcp.pscale.dev/mcp/planetscale", + oauthAuthorizationServerOrigins: [ + "https://app.planetscale.com", + "https://auth.planetscale.com", + "https://mcp.pscale.dev", + ], + }, + prisma: { + slug: "prisma", + displayName: "Prisma Postgres", + mcpServerUrl: "https://mcp.prisma.io/mcp", + oauthAuthorizationServerOrigins: [ + "https://auth.prisma.io", + "https://mcp.prisma.io", + ], + }, + instantdb: { + slug: "instantdb", + displayName: "InstantDB", + mcpServerUrl: "https://mcp.instantdb.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.instantdb.com"], + }, + algolia: { + slug: "algolia", + displayName: "Algolia", + mcpServerUrl: "https://mcp.algolia.com/mcp", + oauthAuthorizationServerOrigins: ["https://dashboard.algolia.com"], + }, + statsig: { + slug: "statsig", + displayName: "Statsig", + mcpServerUrl: "https://api.statsig.com/v1/mcp", + oauthAuthorizationServerOrigins: ["https://api.statsig.com"], + }, + postman: { + slug: "postman", + displayName: "Postman", + // A trimmed tool surface exists at /minimal. + mcpServerUrl: "https://mcp.postman.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.postman.com"], + }, + semgrep: { + slug: "semgrep", + displayName: "Semgrep", + mcpServerUrl: "https://mcp.semgrep.ai/mcp", + oauthAuthorizationServerOrigins: ["https://login.semgrep.dev"], + }, + workos: { + slug: "workos", + displayName: "WorkOS", + mcpServerUrl: "https://mcp.workos.com/mcp", + oauthAuthorizationServerOrigins: ["https://signin.workos.com"], + }, + stytch: { + slug: "stytch", + displayName: "Stytch", + // Auth server is a per-tenant customers.stytch.com origin (their own DCR + // tenant). + mcpServerUrl: "https://mcp.stytch.dev/mcp", + oauthAuthorizationServerOrigins: [ + "https://rustic-kilogram-6347.customers.stytch.com", + "https://stytch.com", + ], + }, + mux: { + slug: "mux", + displayName: "Mux", + // MCP endpoint is the origin root. + mcpServerUrl: "https://mcp.mux.com/", + oauthAuthorizationServerOrigins: ["https://auth.mux.com"], + }, + knock: { + slug: "knock", + displayName: "Knock", + mcpServerUrl: "https://mcp.knock.app/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.knock.app"], + }, + lovable: { + slug: "lovable", + displayName: "Lovable", + // MCP endpoint is the origin root. + mcpServerUrl: "https://mcp.lovable.dev/", + oauthAuthorizationServerOrigins: ["https://lovable.dev"], + }, + retool: { + slug: "retool", + displayName: "Retool", + mcpServerUrl: "https://mcp.retool.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.retool.com"], + }, + telnyx: { + slug: "telnyx", + displayName: "Telnyx", + mcpServerUrl: "https://api.telnyx.com/v2/mcp", + oauthAuthorizationServerOrigins: ["https://api.telnyx.com"], + }, + jam: { + slug: "jam", + displayName: "Jam", + mcpServerUrl: "https://mcp.jam.dev/mcp", + oauthAuthorizationServerOrigins: ["https://api.jam.dev"], + }, + globalping: { + slug: "globalping", + displayName: "Globalping", + mcpServerUrl: "https://mcp.globalping.dev/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.globalping.dev"], + }, + // ── Data / AI ── + airbyte: { + slug: "airbyte", + displayName: "Airbyte", + // Airbyte Agents account (app.airbyte.ai). + mcpServerUrl: "https://mcp.airbyte.ai/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.airbyte.ai"], + }, + motherduck: { + slug: "motherduck", + displayName: "MotherDuck", + mcpServerUrl: "https://api.motherduck.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp-auth.motherduck.com"], + }, + montecarlo: { + slug: "montecarlo", + displayName: "Monte Carlo", + mcpServerUrl: "https://integrations.getmontecarlo.com/mcp", + oauthAuthorizationServerOrigins: ["https://auth.getmontecarlo.com"], + }, + atlan: { + slug: "atlan", + displayName: "Atlan", + mcpServerUrl: "https://mcp.atlan.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.atlan.com"], + }, + huggingface: { + slug: "huggingface", + displayName: "Hugging Face", + mcpServerUrl: "https://huggingface.co/mcp", + oauthAuthorizationServerOrigins: ["https://huggingface.co"], + }, + // ── Automation / web data / search ── + zapier: { + slug: "zapier", + displayName: "Zapier", + // Proxies 9k+ app actions; users curate the tool list at mcp.zapier.com. + mcpServerUrl: "https://mcp.zapier.com/api/mcp/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.zapier.com"], + }, + make: { + slug: "make", + displayName: "Make", + // MCP endpoint is the origin root. + mcpServerUrl: "https://mcp.make.com/", + oauthAuthorizationServerOrigins: ["https://www.make.com"], + }, + ifttt: { + slug: "ifttt", + displayName: "IFTTT", + mcpServerUrl: "https://ifttt.com/mcp", + oauthAuthorizationServerOrigins: ["https://ifttt.com"], + }, + exa: { + slug: "exa", + displayName: "Exa", + mcpServerUrl: "https://mcp.exa.ai/mcp", + oauthAuthorizationServerOrigins: ["https://auth.exa.ai"], + }, + tavily: { + slug: "tavily", + displayName: "Tavily", + mcpServerUrl: "https://mcp.tavily.com/mcp", + oauthAuthorizationServerOrigins: ["https://mcp.tavily.com"], + }, + firecrawl: { + slug: "firecrawl", + displayName: "Firecrawl", + mcpServerUrl: "https://mcp.firecrawl.dev/mcp", + oauthAuthorizationServerOrigins: ["https://www.firecrawl.dev"], + }, + apify: { + slug: "apify", + displayName: "Apify", + // MCP endpoint is the origin root. + mcpServerUrl: "https://mcp.apify.com/", + oauthAuthorizationServerOrigins: [ + "https://console-backend.apify.com", + "https://console.apify.com", + ], + }, + brightdata: { + slug: "brightdata", + displayName: "Bright Data", + mcpServerUrl: "https://mcp.brightdata.com/mcp", + oauthAuthorizationServerOrigins: ["https://brightdata.com"], + }, + // ── Bring-your-own OAuth app (no DCR) ── + docusign: { + slug: "docusign", + displayName: "DocuSign", + // Open beta; confidential authorization-code clients only (no DCR). + mcpServerUrl: "https://mcp.docusign.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://account.docusign.com", + "https://mcp.docusign.com", + ], + authMode: "manual", + }, + xero: { + slug: "xero", + displayName: "Xero", + // Standard Xero OAuth app (no DCR). + mcpServerUrl: "https://mcp.xero.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://identity.xero.com", + "https://login.xero.com", + ], + authMode: "manual", + }, + front: { + slug: "front", + displayName: "Front", + // Open beta; create a Front developer OAuth app (confidential). + mcpServerUrl: "https://mcp.frontapp.com/mcp", + oauthAuthorizationServerOrigins: ["https://app.frontapp.com"], + authMode: "manual", + }, + smartsheet: { + slug: "smartsheet", + displayName: "Smartsheet", + mcpServerUrl: "https://mcp.smartsheet.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.smartsheet.com", + "https://app.smartsheet.com", + "https://mcp.smartsheet.com", + ], + authMode: "manual", + }, + mongodb: { + slug: "mongodb", + displayName: "MongoDB Atlas", + mcpServerUrl: "https://mcp.mongodb.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://authorize.mongodb.com", + "https://cloud.mongodb.com", + ], + authMode: "manual", + }, + circleci: { + slug: "circleci", + displayName: "CircleCI", + mcpServerUrl: "https://mcp.circleci.com/v1/mcp", + oauthAuthorizationServerOrigins: [ + "https://app.circleci.com", + "https://circleci.com", + "https://mcp.circleci.com", + ], + authMode: "manual", + }, + chargebee: { + slug: "chargebee", + displayName: "Chargebee", + // Central endpoint; Chargebee also hosts per-site custom servers (not + // cataloged). + mcpServerUrl: "https://mcp.chargebee.com/mcp", + oauthAuthorizationServerOrigins: ["https://app.chargebee.com"], + authMode: "manual", + }, + bigquery: { + slug: "bigquery", + displayName: "BigQuery", + // Google OAuth like Gmail: manual confidential client + offline access + // params. + mcpServerUrl: "https://bigquery.googleapis.com/mcp", + oauthAuthorizationServerOrigins: [ + "https://accounts.google.com", + "https://oauth2.googleapis.com", + ], + authMode: "manual", + authorizeParams: { access_type: "offline", prompt: "consent" }, + }, + ironclad: { + slug: "ironclad", + displayName: "Ironclad", + // NA1 region endpoint. + mcpServerUrl: "https://mcp.na1.ironcladapp.com/mcp", + oauthAuthorizationServerOrigins: ["https://ironcladapp.com"], + authMode: "manual", + }, + harvey: { + slug: "harvey", + displayName: "Harvey", + mcpServerUrl: "https://api.harvey.ai/hosted_mcp/mcp", + oauthAuthorizationServerOrigins: [ + "https://api.harvey.ai", + "https://harvey-ai.us.auth0.com", + ], + authMode: "manual", + }, + tableau: { + slug: "tableau", + displayName: "Tableau", + // Tableau Cloud only; rolling out through 2026.2. + mcpServerUrl: "https://mcp.tableau.com/mcp", + oauthAuthorizationServerOrigins: ["https://sso.online.tableau.com"], + authMode: "manual", + }, + shopify: { + slug: "shopify", + displayName: "Shopify", + // Admin setup server; per-store storefront MCP is separate and + // unauthenticated. + mcpServerUrl: "https://setup.shopify.com/mcp", + oauthAuthorizationServerOrigins: ["https://setup.shopify.com"], + authMode: "manual", + }, + // ── API token ── + render: { + slug: "render", + displayName: "Render", + // Hosted MCP authenticates with a Render API key as the Bearer (no DCR, + // no OAuth-app product) — PAT mode like GitHub. Docs: + // https://render.com/docs/mcp-server + mcpServerUrl: "https://mcp.render.com/mcp", + oauthAuthorizationServerOrigins: [], + authMode: "pat", + patHint: + "Paste a Render API key (Dashboard → Account Settings → API Keys). It is broadly scoped to your workspaces, so prefer a dedicated account/key for agents.", + }, "tembo-agent-studio": { slug: "tembo-agent-studio", displayName: "Tembo Agent Studio",