From d61177edf2de85fb33b90d2f72329bdb8356915a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 02:06:54 +0000 Subject: [PATCH] Fix Figma MCP OAuth 403 and refresh built-in MCP catalog auth. Figma's DCR endpoint allowlists catalog client names and returns a plaintext 403 for "Aiden Agent", which the SDK surfaces as invalid JSON. Register the Figma preset as Codex, mark OAuth clients as native PKCE apps, and connect GitHub over a Bearer PAT. Clarify remaining DCR 403s in Settings. Co-authored-by: Sambit Biswas --- .../mcp-oauth-client-metadata.test.ts | 36 ++++++++++++ main/services/mcp-oauth-client-metadata.ts | 55 +++++++++++++++++++ main/services/mcp-oauth.ts | 26 +++++---- main/services/mcp-presets.test.ts | 29 +++++++++- main/services/mcp-presets.ts | 14 +++++ main/services/mcp.ts | 6 +- package.json | 6 +- renderer/lib/types.ts | 10 +++- renderer/shared/plugin-catalog.test.ts | 7 ++- renderer/shared/plugin-catalog.ts | 47 ++++++++++++---- 10 files changed, 203 insertions(+), 33 deletions(-) create mode 100644 main/services/mcp-oauth-client-metadata.test.ts create mode 100644 main/services/mcp-oauth-client-metadata.ts diff --git a/main/services/mcp-oauth-client-metadata.test.ts b/main/services/mcp-oauth-client-metadata.test.ts new file mode 100644 index 00000000..2afec7da --- /dev/null +++ b/main/services/mcp-oauth-client-metadata.test.ts @@ -0,0 +1,36 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + DEFAULT_MCP_OAUTH_CLIENT_NAME, + MCP_OAUTH_REDIRECT_URI, + explainMcpOAuthFailure, + mcpApiKeyHeaderValue, + mcpOAuthClientMetadata, +} from "./mcp-oauth-client-metadata.js"; + +test("default MCP OAuth metadata is a native PKCE loopback client named Aiden Agent", () => { + const metadata = mcpOAuthClientMetadata(); + assert.equal(metadata.client_name, DEFAULT_MCP_OAUTH_CLIENT_NAME); + assert.equal(metadata.application_type, "native"); + assert.equal(metadata.token_endpoint_auth_method, "none"); + assert.deepEqual(metadata.redirect_uris, [MCP_OAUTH_REDIRECT_URI]); + assert.match(MCP_OAUTH_REDIRECT_URI, /^http:\/\/127\.0\.0\.1:\d+\/callback$/); + assert.deepEqual(mcpOAuthClientMetadata(" Codex ").client_name, "Codex"); +}); + +test("Figma-style plaintext DCR 403 becomes a readable authorization error", () => { + const raw = new Error( + `ServerError: HTTP 403: Invalid OAuth error response: SyntaxError: Unexpected token 'F', "Forbidden" is not valid JSON. Raw body: Forbidden`, + ); + const explained = explainMcpOAuthFailure(raw); + assert.match(explained.message, /rejected OAuth client registration/u); + assert.equal(explained.cause, raw); + const other = new Error("Authorization denied: access_denied"); + assert.equal(explainMcpOAuthFailure(other), other); +}); + +test("API-key header values accept a Bearer token with or without the prefix", () => { + assert.equal(mcpApiKeyHeaderValue(" secret "), "secret"); + assert.equal(mcpApiKeyHeaderValue("ghp_abc", "Bearer "), "Bearer ghp_abc"); + assert.equal(mcpApiKeyHeaderValue("Bearer ghp_abc", "Bearer "), "Bearer ghp_abc"); +}); diff --git a/main/services/mcp-oauth-client-metadata.ts b/main/services/mcp-oauth-client-metadata.ts new file mode 100644 index 00000000..c2162791 --- /dev/null +++ b/main/services/mcp-oauth-client-metadata.ts @@ -0,0 +1,55 @@ +// Electron-free MCP OAuth client metadata. Hosted servers that allowlist +// Dynamic Client Registration by exact `client_name` (notably Figma) must +// receive the catalog name documented for that connector. + +export const DEFAULT_MCP_OAUTH_CLIENT_NAME = "Aiden Agent"; +export const MCP_OAUTH_LOOPBACK_PORT = 41390; +export const MCP_OAUTH_REDIRECT_URI = `http://127.0.0.1:${MCP_OAUTH_LOOPBACK_PORT}/callback`; + +export interface McpOAuthClientMetadata { + client_name: string; + redirect_uris: string[]; + grant_types: string[]; + response_types: string[]; + token_endpoint_auth_method: "none"; + application_type: "native"; +} + +export function mcpOAuthClientMetadata(clientName?: string): McpOAuthClientMetadata { + const name = clientName?.trim() || DEFAULT_MCP_OAUTH_CLIENT_NAME; + return { + client_name: name, + redirect_uris: [MCP_OAUTH_REDIRECT_URI], + grant_types: ["authorization_code", "refresh_token"], + response_types: ["code"], + token_endpoint_auth_method: "none", + application_type: "native", + }; +} + +/** Format an API-key preset header so Bearer tokens can be pasted with or without the prefix. */ +export function mcpApiKeyHeaderValue(key: string, prefix?: string): string { + const trimmed = key.trim(); + if (!prefix) return trimmed; + if (trimmed.toLowerCase().startsWith(prefix.toLowerCase())) return trimmed; + return `${prefix}${trimmed}`; +} + +/** + * The MCP SDK surfaces Figma's plaintext DCR 403 as a JSON parse failure. + * Keep the original error as `cause` for logs while giving Settings a readable line. + */ +export function explainMcpOAuthFailure(error: unknown): Error { + const message = error instanceof Error ? error.message : String(error); + if ( + /HTTP 403/i.test(message) && + /Invalid OAuth error response/i.test(message) && + /Forbidden/i.test(message) + ) { + return new Error( + "This MCP server rejected OAuth client registration (HTTP 403). Some hosts only allow listed MCP clients to register. Check Settings → Plugins for this connector's documented setup, then try Authorize again.", + { cause: error instanceof Error ? error : undefined }, + ); + } + return error instanceof Error ? error : new Error(message); +} diff --git a/main/services/mcp-oauth.ts b/main/services/mcp-oauth.ts index 583c00c4..76d06dfe 100644 --- a/main/services/mcp-oauth.ts +++ b/main/services/mcp-oauth.ts @@ -32,15 +32,21 @@ import { type McpOAuthGeneration, } from "./mcp-oauth-operation.js"; import type { McpOAuthOperation } from "./mcp-oauth-operation.js"; -import { assertMcpPresetServer } from "./mcp-presets.js"; +import { + MCP_OAUTH_LOOPBACK_PORT, + MCP_OAUTH_REDIRECT_URI, + explainMcpOAuthFailure, + mcpOAuthClientMetadata, +} from "./mcp-oauth-client-metadata.js"; +import { assertMcpPresetServer, mcpOAuthClientNameForServer } from "./mcp-presets.js"; import { closeAgainAfterSettled } from "./generation-bound-connection-cache.js"; import type { McpServer } from "./types.js"; import { withMcpConfigurationPublication } from "./mcp-config-lease.js"; // Fixed loopback redirect so the registered redirect_uri stays stable across // sessions (dynamic client registration records it once). -const OAUTH_PORT = 41390; -const OAUTH_REDIRECT_URI = `http://127.0.0.1:${OAUTH_PORT}/callback`; +const OAUTH_PORT = MCP_OAUTH_LOOPBACK_PORT; +const OAUTH_REDIRECT_URI = MCP_OAUTH_REDIRECT_URI; const AUTH_TIMEOUT_MS = 5 * 60 * 1000; const oauthOperations = new McpOAuthOperationGate(); @@ -67,6 +73,7 @@ class McpOAuthProvider implements OAuthClientProvider { private readonly requestIsCurrent: () => boolean = () => true, private readonly transaction?: McpOAuthSessionTransaction, private readonly observeTokens?: (tokens: OAuthTokens) => void, + private readonly oauthClientName: string = mcpOAuthClientMetadata().client_name, ) {} private async boundSession() { @@ -109,13 +116,7 @@ class McpOAuthProvider implements OAuthClientProvider { } get clientMetadata(): OAuthClientMetadata { - return { - client_name: "Aiden Agent", - redirect_uris: [OAUTH_REDIRECT_URI], - grant_types: ["authorization_code", "refresh_token"], - response_types: ["code"], - token_endpoint_auth_method: "none", - }; + return mcpOAuthClientMetadata(this.oauthClientName) as OAuthClientMetadata; } async clientInformation(): Promise { @@ -235,6 +236,7 @@ export function oauthProviderFor( isCurrent, undefined, observeTokens, + mcpOAuthClientNameForServer(server), ); } @@ -432,6 +434,8 @@ export async function authorizeMcpServer( operation, isCurrent, transaction, + undefined, + mcpOAuthClientNameForServer(server), ); let loopback: Loopback | null = null; let commitAttempted = false; @@ -524,7 +528,7 @@ export async function authorizeMcpServer( "mcp-oauth", `Authorization failed for "${server.name}": ${error instanceof Error ? error.message : String(error)}`, ); - throw error; + throw explainMcpOAuthFailure(error); } finally { loopback?.close(); oauthOperations.end(operation); diff --git a/main/services/mcp-presets.test.ts b/main/services/mcp-presets.test.ts index 050e2a6c..36603b6d 100644 --- a/main/services/mcp-presets.test.ts +++ b/main/services/mcp-presets.test.ts @@ -3,6 +3,7 @@ import test from "node:test"; import { assertMcpPresetServer, createNoRedirectFetch, + mcpOAuthClientNameForServer, MCP_PRESETS, getMcpPreset, getMcpPresetForServerId, @@ -41,8 +42,15 @@ test("catalog includes composio (apiKey) and hosted Codex OAuth plugins", () => } assert.equal(getMcpPreset("notion")?.auth.kind, "oauth"); assert.equal(getMcpPreset("linear")?.auth.kind, "oauth"); - assert.equal(getMcpPreset("github"), undefined); + assert.equal(getMcpPreset("github")?.auth.kind, "apiKey"); assert.equal(getMcpPreset("figma")?.url, "https://mcp.figma.com/mcp"); + const figmaAuth = getMcpPreset("figma")?.auth; + assert.equal(figmaAuth?.kind, "oauth"); + if (figmaAuth?.kind === "oauth") { + assert.equal(figmaAuth.clientName, "Codex"); + } + assert.equal(mcpOAuthClientNameForServer({ id: "preset-figma", presetId: "figma" }), "Codex"); + assert.equal(mcpOAuthClientNameForServer({ id: "preset-notion", presetId: "notion" }), "Aiden Agent"); assert.equal(getMcpPreset("superpowers"), undefined); assert.equal(getMcpPreset("nope"), undefined); }); @@ -84,7 +92,7 @@ test("serverFromPreset sets oauth and allows only provider-owned endpoint paths" ); }); -test("hosted Codex plugin credentials stay on their official origin", () => { +test("hosted Figma credentials stay on mcp.figma.com", () => { const figma = getMcpPreset("figma"); assert.ok(figma); const server = serverFromPreset(figma, "https://mcp.figma.com/mcp/session/abc"); @@ -95,6 +103,23 @@ test("hosted Codex plugin credentials stay on their official origin", () => { ); }); +test("GitHub remote MCP stays on api.githubcopilot.com with a Bearer PAT", () => { + const github = getMcpPreset("github"); + assert.ok(github); + assert.equal(github.auth.kind, "apiKey"); + if (github.auth.kind === "apiKey") { + assert.equal(github.auth.headerName, "Authorization"); + assert.equal(github.auth.headerValuePrefix, "Bearer "); + } + const server = serverFromPreset(github); + assert.equal(server.oauth, undefined); + assert.equal(server.url, "https://api.githubcopilot.com/mcp/"); + assert.throws( + () => serverFromPreset(github, "https://github.com/mcp"), + /official secure server/, + ); +}); + test("preset validation binds credentials to exact identities and HTTPS origins", () => { const composio = getMcpPreset("composio"); assert.ok(composio); diff --git a/main/services/mcp-presets.ts b/main/services/mcp-presets.ts index e0521382..e6d85270 100644 --- a/main/services/mcp-presets.ts +++ b/main/services/mcp-presets.ts @@ -12,6 +12,7 @@ import { type McpPresetAuth as SharedMcpPresetAuth, type PluginCatalogEntry, } from "../../renderer/shared/plugin-catalog.js"; +import { DEFAULT_MCP_OAUTH_CLIENT_NAME } from "./mcp-oauth-client-metadata.js"; import type { McpServer } from "./types.js"; export type McpPresetAuth = SharedMcpPresetAuth; @@ -78,6 +79,19 @@ export function getMcpPreset(presetId: string): McpPreset | undefined { return MCP_PRESETS.find((preset) => preset.id === presetId); } +/** DCR `client_name` for this server: preset override, otherwise Aiden Agent. */ +export function mcpOAuthClientNameForServer( + server: Pick, +): string { + const preset = + getMcpPresetForServerId(server.id) ?? + (server.presetId ? getMcpPreset(server.presetId) : undefined); + if (preset?.auth.kind === "oauth" && preset.auth.clientName?.trim()) { + return preset.auth.clientName.trim(); + } + return DEFAULT_MCP_OAUTH_CLIENT_NAME; +} + export function getMcpPresetForServerId(serverId: string): McpPreset | undefined { return MCP_PRESETS.find((preset) => presetServerId(preset.id) === serverId); } diff --git a/main/services/mcp.ts b/main/services/mcp.ts index 3437f47f..611349f3 100644 --- a/main/services/mcp.ts +++ b/main/services/mcp.ts @@ -10,6 +10,7 @@ import { Type } from "@earendil-works/pi-ai"; import type { AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core"; import { logger } from "../platform.js"; import { oauthProviderFor } from "./mcp-oauth.js"; +import { mcpApiKeyHeaderValue } from "./mcp-oauth-client-metadata.js"; import { assertMcpPresetServer, createNoRedirectFetch, @@ -82,7 +83,10 @@ async function resolveAuth( ); return { ...server, - headers: { ...server.headers, [preset.auth.headerName]: key }, + headers: { + ...server.headers, + [preset.auth.headerName]: mcpApiKeyHeaderValue(key, preset.auth.headerValuePrefix), + }, }; } diff --git a/package.json b/package.json index 35cb8e8d..71014e42 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "test:advisor": "tsx --test renderer/shared/advisor.test.ts main/services/advisor-context.test.ts main/services/advisor-attempt-store.test.ts main/services/advisor-runtime.test.ts main/services/advisor-integration.test.ts", "test:generative-ui": "tsx --test main/services/generative-ui-html.test.ts main/services/generative-ui-extension.test.ts main/services/generative-ui-artifact-store.test.ts main/services/generative-ui-host-libraries.test.ts main/services/generative-ui-protocol.test.ts renderer/shared/chat-artifacts.test.ts renderer/shared/generative-ui.test.ts && node --test scripts/vendor-generative-ui-libs.test.mjs && playwright test --config=playwright.generative-ui.config.ts --fail-on-flaky-tests", "test:google-provider": "tsx --test main/services/anthropic-provider.test.ts main/services/google-provider.test.ts main/services/model-runtime-core.test.ts main/services/models.test.ts main/services/provider-config-migration-core.test.ts main/services/chat-store-core.test.ts main/services/schedule-store.test.ts renderer/lib/google-provider-migration.test.ts", - "test:config-recovery": "tsx --test main/services/secret-map-core.test.ts main/services/provider-credential-rotation-core.test.ts main/services/legacy-pi-credential-migration-core.test.ts main/services/mcp-credential-cleanup-core.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-oauth-store-core.test.ts", + "test:config-recovery": "tsx --test main/services/secret-map-core.test.ts main/services/provider-credential-rotation-core.test.ts main/services/legacy-pi-credential-migration-core.test.ts main/services/mcp-credential-cleanup-core.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-client-metadata.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-oauth-store-core.test.ts", "pretest:subagents": "npm run build:worktree-remover && npm run build:subagent-run-store && node scripts/build-subagent-run-store.mjs --test && npm run build:subagent-file-mutator && node scripts/build-subagent-file-mutator.mjs --test && npm run build:subagent-shell-runner && node scripts/build-subagent-shell-runner.mjs --test && npm run test:subagents:inventory && npm run test:subagents:workspace-write && npm run test:subagents:phase5a && npm run test:subagents:phase5b && npm run test:subagents:phase5c && npm run test:subagents:phase5d && npm run test:subagents:phase5e && npm run test:subagents:phase6a && npm run test:subagents:phase6b && npm run test:subagents:phase7a && npm run test:subagents:soak:contracts", "test:subagents:inventory": "tsx --test main/services/subagents/subagent-mcp-inventory-core.test.ts main/services/subagents/subagent-inference-process-core.test.ts", "test:subagents:phase5a": "tsx --test main/services/mcp-config-lease.test.ts main/services/subagents/subagent-effect-v2.test.ts", @@ -109,8 +109,8 @@ "test:voice": "tsx --test main/services/transcription-core.test.ts main/services/gemini-live-transcription-core.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/parakeet-transcription-lane.test.ts renderer/shared/voice-models.test.ts renderer/shared/gemini-usage-scope.test.ts renderer/components/settings/gemini-voice-setup.test.tsx renderer/lib/accessibility-permission-core.test.ts renderer/lib/accessibility-refresh.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/gemini-recorded-retry.test.ts renderer/lib/live-pcm-capture.test.ts renderer/lib/voice-recorder-core.test.ts renderer/lib/wav-audio.test.ts", "test:diagnostics": "tsx --test main/services/diagnostics-contract.test.ts main/services/diagnostic-health.test.ts main/services/diagnostic-journal.test.ts main/services/diagnostic-support.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/renderer-crash-recovery.test.ts main/services/renderer-diagnostic-rate.test.ts main/services/subagents/subagent-runtime-diagnostics.test.ts renderer/components/settings/diagnostics-settings.test.tsx && node --test scripts/diagnostic-policy.test.mjs", "diagnostics:failure-receipt": "node scripts/write-diagnostic-failure-receipt.mjs", - "test": "tsx --test main/handlers/assistant-parse.test.ts main/services/assistant/system-prompt.test.ts main/services/chat-activity-core.test.ts main/services/chat-generation-start.test.ts main/services/chat-title-policy.test.ts main/services/chat-title-routing.test.ts main/services/chat-store-core.test.ts main/services/codex-provider.test.ts main/services/coding-tools.test.ts main/services/config-store-core.test.ts main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/data-store.resilience.test.ts main/services/terminal.test.ts main/services/terminal-history.test.ts main/services/aiden-config-dir.test.ts main/services/portable-config-core.test.ts main/services/portable-config-core.roundtrip.test.ts main/services/portable-config-watch-core.test.ts main/services/secret-map-core.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/dictation-cleanup-core.test.ts main/services/dictation-hotkey.test.ts main/services/dictation-key-state.test.ts main/services/dictation-keycode.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/foundation-models-connection.test.ts main/services/foundation-models-connection-core.test.ts main/services/gemini-context-cache.test.ts main/services/generation-bound-connection-cache.test.ts main/services/generation-context.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/external-editors.test.ts main/services/git.test.ts main/services/model-runtime-core.test.ts main/services/models.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-presets.test.ts renderer/shared/plugin-catalog.test.ts main/services/pi-credential-store-core.test.ts main/services/pi-provider-contract.test.ts main/services/profile-share-core.test.ts main/services/profile-share-files.test.ts main/services/profile.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-auth-owner.test.ts main/services/provider-key-policy.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/quit-barrier.test.ts main/services/scratch-workspace.test.ts main/services/skills-discovery.test.ts main/services/tool-approval.test.ts main/services/local-runtime-status.test.ts main/services/usage-store-core.test.ts main/services/workspace-files.test.ts main/windows/pill-window-security.test.ts renderer/components/assistant/use-assistant-chat.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/activity-feed.test.tsx renderer/components/environment-subagents-contract.test.ts renderer/components/subagents-panel.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/main/chat-transition.test.tsx renderer/components/usage/profile-share-card.test.tsx renderer/lib/accessibility-refresh.test.ts renderer/lib/agent-activity.test.ts renderer/lib/chat-activity.test.ts renderer/lib/assistant-dock.test.ts renderer/lib/assistant-motion-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/scrollbar-gutter-contract.test.ts renderer/lib/text-entry-focus-contract.test.ts renderer/lib/chat-deletion-cache.test.ts renderer/lib/chat-terminal-sync.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/chat-title-reveal.test.ts renderer/lib/codex-auth-session.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/composer-placeholder.test.ts renderer/lib/computer-use-notice.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/lib/editor-preference.test.ts renderer/lib/environment-panel-layout.test.ts renderer/lib/subagent-view-state.test.ts renderer/lib/truncate-path.test.ts renderer/lib/mcp-preset-state.test.ts renderer/components/settings/mcp-settings.test.tsx renderer/components/settings/mcp-preset-icons.test.ts renderer/lib/model-display.test.ts renderer/lib/model-picker-data.test.ts renderer/lib/profile-share-data.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts renderer/lib/usage-profile-data.test.ts renderer/shared/appearance.test.ts renderer/shared/provider-deployment.test.ts main/handlers/ipc-contract.test.ts main/handlers/chat.parse.test.ts main/handlers/voice-codec.test.ts main/handlers/phase2-parse.test.ts scripts/apple-developer-tools.test.mjs scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/model-snapshot-core.test.mjs scripts/prepare-macos-dev-runtime.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/hide-dmg-support-files.test.mjs scripts/update-model-capabilities.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs && npm run test:telegram && npm run test:worktree-remover:native && npm run test:computer-use:native", - "test:coverage": "tsx --test --experimental-test-coverage main/handlers/assistant-parse.test.ts main/services/assistant/system-prompt.test.ts main/services/chat-activity-core.test.ts main/services/chat-generation-start.test.ts main/services/chat-title-policy.test.ts main/services/chat-title-routing.test.ts main/services/chat-store-core.test.ts main/services/codex-provider.test.ts main/services/coding-tools.test.ts main/services/config-store-core.test.ts main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/data-store.resilience.test.ts main/services/terminal.test.ts main/services/terminal-history.test.ts main/services/aiden-config-dir.test.ts main/services/portable-config-core.test.ts main/services/portable-config-core.roundtrip.test.ts main/services/portable-config-watch-core.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/dictation-cleanup-core.test.ts main/services/dictation-hotkey.test.ts main/services/dictation-key-state.test.ts main/services/dictation-keycode.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/foundation-models-connection.test.ts main/services/foundation-models-connection-core.test.ts main/services/generation-bound-connection-cache.test.ts main/services/generation-context.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/external-editors.test.ts main/services/git.test.ts main/services/model-runtime-core.test.ts main/services/pi-compaction-core.test.ts main/services/models.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-presets.test.ts renderer/shared/plugin-catalog.test.ts main/services/pi-credential-store-core.test.ts main/services/pi-provider-contract.test.ts main/services/profile-share-core.test.ts main/services/profile-share-files.test.ts main/services/profile.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-auth-owner.test.ts main/services/provider-key-policy.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/quit-barrier.test.ts main/services/scratch-workspace.test.ts main/services/skills-discovery.test.ts main/services/tool-approval.test.ts main/services/local-runtime-status.test.ts main/services/usage-store-core.test.ts main/services/workspace-files.test.ts main/windows/pill-window-security.test.ts renderer/components/assistant/use-assistant-chat.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/environment-subagents-contract.test.ts renderer/components/subagents-panel.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/main/chat-transition.test.tsx renderer/components/usage/profile-share-card.test.tsx renderer/lib/accessibility-refresh.test.ts renderer/lib/agent-activity.test.ts renderer/lib/chat-activity.test.ts renderer/lib/assistant-dock.test.ts renderer/lib/assistant-motion-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/chat-deletion-cache.test.ts renderer/lib/chat-terminal-sync.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/chat-title-reveal.test.ts renderer/lib/codex-auth-session.test.ts renderer/lib/codex-auth-view-state.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/composer-placeholder.test.ts renderer/lib/computer-use-notice.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/lib/editor-preference.test.ts renderer/lib/environment-panel-layout.test.ts renderer/lib/subagent-view-state.test.ts renderer/lib/truncate-path.test.ts renderer/lib/mcp-preset-state.test.ts renderer/components/settings/mcp-settings.test.tsx renderer/components/settings/mcp-preset-icons.test.ts renderer/lib/model-display.test.ts renderer/lib/profile-share-data.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts renderer/lib/usage-profile-data.test.ts renderer/shared/appearance.test.ts renderer/shared/provider-deployment.test.ts main/handlers/ipc-contract.test.ts main/handlers/chat.parse.test.ts main/handlers/voice-codec.test.ts main/handlers/phase2-parse.test.ts scripts/apple-developer-tools.test.mjs scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/model-snapshot-core.test.mjs scripts/prepare-macos-dev-runtime.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/update-model-capabilities.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs", + "test": "tsx --test main/handlers/assistant-parse.test.ts main/services/assistant/system-prompt.test.ts main/services/chat-activity-core.test.ts main/services/chat-generation-start.test.ts main/services/chat-title-policy.test.ts main/services/chat-title-routing.test.ts main/services/chat-store-core.test.ts main/services/codex-provider.test.ts main/services/coding-tools.test.ts main/services/config-store-core.test.ts main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/data-store.resilience.test.ts main/services/terminal.test.ts main/services/terminal-history.test.ts main/services/aiden-config-dir.test.ts main/services/portable-config-core.test.ts main/services/portable-config-core.roundtrip.test.ts main/services/portable-config-watch-core.test.ts main/services/secret-map-core.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/dictation-cleanup-core.test.ts main/services/dictation-hotkey.test.ts main/services/dictation-key-state.test.ts main/services/dictation-keycode.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/foundation-models-connection.test.ts main/services/foundation-models-connection-core.test.ts main/services/gemini-context-cache.test.ts main/services/generation-bound-connection-cache.test.ts main/services/generation-context.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/external-editors.test.ts main/services/git.test.ts main/services/model-runtime-core.test.ts main/services/models.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-client-metadata.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-presets.test.ts renderer/shared/plugin-catalog.test.ts main/services/pi-credential-store-core.test.ts main/services/pi-provider-contract.test.ts main/services/profile-share-core.test.ts main/services/profile-share-files.test.ts main/services/profile.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-auth-owner.test.ts main/services/provider-key-policy.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/quit-barrier.test.ts main/services/scratch-workspace.test.ts main/services/skills-discovery.test.ts main/services/tool-approval.test.ts main/services/local-runtime-status.test.ts main/services/usage-store-core.test.ts main/services/workspace-files.test.ts main/windows/pill-window-security.test.ts renderer/components/assistant/use-assistant-chat.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/activity-feed.test.tsx renderer/components/environment-subagents-contract.test.ts renderer/components/subagents-panel.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/main/chat-transition.test.tsx renderer/components/usage/profile-share-card.test.tsx renderer/lib/accessibility-refresh.test.ts renderer/lib/agent-activity.test.ts renderer/lib/chat-activity.test.ts renderer/lib/assistant-dock.test.ts renderer/lib/assistant-motion-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/scrollbar-gutter-contract.test.ts renderer/lib/text-entry-focus-contract.test.ts renderer/lib/chat-deletion-cache.test.ts renderer/lib/chat-terminal-sync.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/chat-title-reveal.test.ts renderer/lib/codex-auth-session.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/composer-placeholder.test.ts renderer/lib/computer-use-notice.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/lib/editor-preference.test.ts renderer/lib/environment-panel-layout.test.ts renderer/lib/subagent-view-state.test.ts renderer/lib/truncate-path.test.ts renderer/lib/mcp-preset-state.test.ts renderer/components/settings/mcp-settings.test.tsx renderer/components/settings/mcp-preset-icons.test.ts renderer/lib/model-display.test.ts renderer/lib/model-picker-data.test.ts renderer/lib/profile-share-data.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts renderer/lib/usage-profile-data.test.ts renderer/shared/appearance.test.ts renderer/shared/provider-deployment.test.ts main/handlers/ipc-contract.test.ts main/handlers/chat.parse.test.ts main/handlers/voice-codec.test.ts main/handlers/phase2-parse.test.ts scripts/apple-developer-tools.test.mjs scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/model-snapshot-core.test.mjs scripts/prepare-macos-dev-runtime.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/hide-dmg-support-files.test.mjs scripts/update-model-capabilities.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs && npm run test:telegram && npm run test:worktree-remover:native && npm run test:computer-use:native", + "test:coverage": "tsx --test --experimental-test-coverage main/handlers/assistant-parse.test.ts main/services/assistant/system-prompt.test.ts main/services/chat-activity-core.test.ts main/services/chat-generation-start.test.ts main/services/chat-title-policy.test.ts main/services/chat-title-routing.test.ts main/services/chat-store-core.test.ts main/services/codex-provider.test.ts main/services/coding-tools.test.ts main/services/config-store-core.test.ts main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/data-store.resilience.test.ts main/services/terminal.test.ts main/services/terminal-history.test.ts main/services/aiden-config-dir.test.ts main/services/portable-config-core.test.ts main/services/portable-config-core.roundtrip.test.ts main/services/portable-config-watch-core.test.ts main/services/dev-log.test.ts main/services/process-diagnostics.test.ts main/services/dictation-coordinator.test.ts main/services/dictation-paste.test.ts main/services/dictation-cleanup-core.test.ts main/services/dictation-hotkey.test.ts main/services/dictation-key-state.test.ts main/services/dictation-keycode.test.ts main/services/parakeet-protocol.test.ts main/services/parakeet-process-core.test.ts main/services/foundation-models-connection.test.ts main/services/foundation-models-connection-core.test.ts main/services/generation-bound-connection-cache.test.ts main/services/generation-context.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/external-editors.test.ts main/services/git.test.ts main/services/model-runtime-core.test.ts main/services/pi-compaction-core.test.ts main/services/models.test.ts main/services/mcp-oauth-operation.test.ts main/services/mcp-oauth-client-metadata.test.ts main/services/mcp-oauth-session.test.ts main/services/mcp-presets.test.ts renderer/shared/plugin-catalog.test.ts main/services/pi-credential-store-core.test.ts main/services/pi-provider-contract.test.ts main/services/profile-share-core.test.ts main/services/profile-share-files.test.ts main/services/profile.test.ts main/services/provider-auth-flow-core.test.ts main/services/provider-auth-owner.test.ts main/services/provider-key-policy.test.ts main/services/provider-list-core.test.ts main/services/provider-artwork-core.test.ts main/services/quit-barrier.test.ts main/services/scratch-workspace.test.ts main/services/skills-discovery.test.ts main/services/tool-approval.test.ts main/services/local-runtime-status.test.ts main/services/usage-store-core.test.ts main/services/workspace-files.test.ts main/windows/pill-window-security.test.ts renderer/components/assistant/use-assistant-chat.test.ts renderer/components/assistant/assistant-ui.test.tsx renderer/components/environment-subagents-contract.test.ts renderer/components/subagents-panel.test.tsx renderer/components/chat-sidebar.test.tsx renderer/components/composer.test.tsx renderer/main/chat-transition.test.tsx renderer/components/usage/profile-share-card.test.tsx renderer/lib/accessibility-refresh.test.ts renderer/lib/agent-activity.test.ts renderer/lib/chat-activity.test.ts renderer/lib/assistant-dock.test.ts renderer/lib/assistant-motion-contract.test.ts renderer/lib/dialog-motion-contract.test.ts renderer/lib/chat-deletion-cache.test.ts renderer/lib/chat-terminal-sync.test.ts renderer/lib/ipc-stream.test.ts renderer/lib/chat-title-reveal.test.ts renderer/lib/codex-auth-session.test.ts renderer/lib/codex-auth-view-state.test.ts renderer/lib/codex-provider-cache.test.ts renderer/lib/composer-placeholder.test.ts renderer/lib/computer-use-notice.test.ts renderer/lib/dictation-operation-gate.test.ts renderer/lib/media-recorder-stop.test.ts renderer/lib/dictation-vad.test.ts renderer/lib/dictation-sounds.test.ts renderer/lib/editor-preference.test.ts renderer/lib/environment-panel-layout.test.ts renderer/lib/subagent-view-state.test.ts renderer/lib/truncate-path.test.ts renderer/lib/mcp-preset-state.test.ts renderer/components/settings/mcp-settings.test.tsx renderer/components/settings/mcp-preset-icons.test.ts renderer/lib/model-display.test.ts renderer/lib/profile-share-data.test.ts renderer/lib/sidebar-chat-shortcuts.test.ts renderer/lib/usage-profile-data.test.ts renderer/shared/appearance.test.ts renderer/shared/provider-deployment.test.ts main/handlers/ipc-contract.test.ts main/handlers/chat.parse.test.ts main/handlers/voice-codec.test.ts main/handlers/phase2-parse.test.ts scripts/apple-developer-tools.test.mjs scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/model-snapshot-core.test.mjs scripts/prepare-macos-dev-runtime.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/update-model-capabilities.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs", "test:computer-use": "tsx --test main/services/computer-use/computer-use-foundation.test.ts main/services/computer-use/computer-use-tool.test.ts main/services/computer-use/generation-gate.test.ts main/services/computer-use/safety.test.ts main/services/computer-use/settings-core.test.ts main/services/computer-use/status-core.test.ts main/services/data-store.test.ts main/services/generation-messages.test.ts main/services/generation-runtime.test.ts main/services/quit-barrier.test.ts main/services/tool-approval.test.ts scripts/check-macos-release.test.mjs scripts/computer-use-packaged-acceptance.test.mjs scripts/configure-electron-fuses.test.mjs scripts/prepare-macos-package-output.test.mjs scripts/run-macos-distribution.test.mjs scripts/sign-macos.test.mjs scripts/vendor-cua-driver.test.mjs scripts/verify-macos-package.test.mjs && npm run test:computer-use:native", "test:computer-use:packaged": "node scripts/computer-use-packaged-acceptance.mjs", "test:computer-use:native": "cd native/computer-use-broker && CARGO_TARGET_DIR=../../build/computer-use-broker-test cargo fmt -- --check && CARGO_TARGET_DIR=../../build/computer-use-broker-test cargo test --locked && CARGO_TARGET_DIR=../../build/computer-use-broker-test cargo clippy --locked --all-targets -- -D warnings", diff --git a/renderer/lib/types.ts b/renderer/lib/types.ts index dcbdfe6b..e829a2b7 100644 --- a/renderer/lib/types.ts +++ b/renderer/lib/types.ts @@ -689,8 +689,14 @@ export interface McpServer { } export type McpPresetAuth = - | { kind: "apiKey"; headerName: string; keyLabel: string; keyHelpUrl: string } - | { kind: "oauth" }; + | { + kind: "apiKey"; + headerName: string; + keyLabel: string; + keyHelpUrl: string; + headerValuePrefix?: string; + } + | { kind: "oauth"; clientName?: string }; /** A built-in MCP provider definition from the main-process catalog. */ export interface McpPreset { diff --git a/renderer/shared/plugin-catalog.test.ts b/renderer/shared/plugin-catalog.test.ts index 6795012c..08f248c6 100644 --- a/renderer/shared/plugin-catalog.test.ts +++ b/renderer/shared/plugin-catalog.test.ts @@ -16,10 +16,10 @@ test("plugin catalog ids are unique kebab-case entries from Codex plus Composio" assert.ok(ids.includes("superpowers")); assert.ok(ids.includes("crowdstrike-falcon-foundry")); assert.equal(PLUGIN_CATALOG.length, 65); - assert.equal(PLUGIN_CATALOG.filter(isConnectablePlugin).length, 20); + assert.equal(PLUGIN_CATALOG.filter(isConnectablePlugin).length, 21); assert.equal( PLUGIN_CATALOG.filter((plugin) => plugin.compatibility === "mcp-auth-unsupported").length, - 7, + 6, ); assert.equal(PLUGIN_CATALOG_SOURCE_URL, "https://github.com/openai/plugins"); for (const plugin of PLUGIN_CATALOG) { @@ -50,6 +50,7 @@ test("search and compatibility filters keep connectable MCP separate from skills const connectable = filterPluginCatalog(PLUGIN_CATALOG, "", "all", "connectable"); assert.ok(connectable.every(isConnectablePlugin)); assert.ok(connectable.some((plugin) => plugin.id === "notion")); + assert.ok(connectable.some((plugin) => plugin.id === "github")); assert.ok(!connectable.some((plugin) => plugin.id === "superpowers")); const skills = filterPluginCatalog(PLUGIN_CATALOG, "", "Developer Tools", "skills"); @@ -61,7 +62,7 @@ test("search and compatibility filters keep connectable MCP separate from skills ); const other = filterPluginCatalog(PLUGIN_CATALOG, "", "all", "other"); - assert.ok(other.some((plugin) => plugin.id === "github")); + assert.ok(other.some((plugin) => plugin.id === "slack")); assert.ok(other.some((plugin) => plugin.id === "teams")); assert.ok(other.some((plugin) => plugin.id === "build-ios-apps")); assert.ok(!other.some(isConnectablePlugin)); diff --git a/renderer/shared/plugin-catalog.ts b/renderer/shared/plugin-catalog.ts index 5afbd3e6..dbb96804 100644 --- a/renderer/shared/plugin-catalog.ts +++ b/renderer/shared/plugin-catalog.ts @@ -15,8 +15,22 @@ export type PluginCompatibility = export type PluginCatalogSource = "aiden" | "openai-curated"; export type McpPresetAuth = - | { kind: "apiKey"; headerName: string; keyLabel: string; keyHelpUrl: string } - | { kind: "oauth" }; + | { + kind: "apiKey"; + headerName: string; + keyLabel: string; + keyHelpUrl: string; + /** Prepended to the stored key when the vendor needs a scheme (e.g. `Bearer `). */ + headerValuePrefix?: string; + } + | { + kind: "oauth"; + /** + * Dynamic Client Registration `client_name`. Some hosts allowlist exact + * names (Figma). Omit to register as Aiden Agent. + */ + clientName?: string; + }; export interface PluginCatalogEntry { id: string; @@ -180,7 +194,7 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ tagline: "Create, refine, and review Canva designs.", vendor: "By Canva Pty Ltd.", category: "Creativity", - docsUrl: "https://www.canva.com", + docsUrl: "https://www.canva.dev/docs/mcp/", source: "openai-curated", url: "https://mcp.canva.com/mcp", }), @@ -190,9 +204,13 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ tagline: "Inspect designs, extract specs, and implement from Figma.", vendor: "By Figma", category: "Creativity", - docsUrl: "https://www.figma.com", + docsUrl: "https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/", source: "openai-curated", url: "https://mcp.figma.com/mcp", + // Figma's POST /v1/oauth/mcp/register allowlists MCP catalog client_name + // values (Codex, Claude Code, Cursor, VS Code, …) and returns plaintext + // HTTP 403 Forbidden for anything else — including "Aiden Agent". + auth: { kind: "oauth", clientName: "Codex" }, }), httpMcp({ id: "stripe", @@ -210,7 +228,7 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ tagline: "Build, preview, and deploy Vercel projects.", vendor: "By Vercel Labs", category: "Developer Tools", - docsUrl: "https://vercel.com/", + docsUrl: "https://vercel.com/docs/mcp", source: "openai-curated", url: "https://mcp.vercel.com", }), @@ -232,15 +250,22 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ docsUrl: "https://github.com/obra/superpowers", source: "openai-curated", }), - listed("mcp-auth-unsupported", AUTH_NOTE, { + httpMcp({ id: "github", name: "GitHub", tagline: "Inspect repositories, triage PRs and issues, and debug CI.", vendor: "By GitHub", category: "Developer Tools", - docsUrl: "https://github.com/mcp", + docsUrl: "https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp-in-your-ide/set-up-the-github-mcp-server", source: "openai-curated", url: "https://api.githubcopilot.com/mcp/", + auth: { + kind: "apiKey", + headerName: "Authorization", + headerValuePrefix: "Bearer ", + keyLabel: "GitHub personal access token", + keyHelpUrl: "https://github.com/settings/personal-access-tokens", + }, }), listed("skills", SKILLS_NOTE, { id: "circleci", @@ -277,7 +302,7 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ tagline: "Workers, Wrangler, and the official Cloudflare API MCP server.", vendor: "By Cloudflare", category: "Developer Tools", - docsUrl: "https://developers.cloudflare.com/", + docsUrl: "https://developers.cloudflare.com/agents/model-context-protocol/mcp-servers-for-cloudflare/", source: "openai-curated", url: "https://mcp.cloudflare.com/mcp", }), @@ -406,7 +431,7 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ tagline: "Search boards and create or update items, columns, and assignments.", vendor: "By monday.com", category: "Productivity", - docsUrl: "https://monday.com", + docsUrl: "https://developer.monday.com/api-reference/docs/integrate-with-monday-mcp", source: "openai-curated", url: "https://mcp.monday.com/mcp", }), @@ -471,7 +496,7 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ tagline: "Analyze and act on Datadog telemetry (US1 preview).", vendor: "By Datadog", category: "Developer Tools", - docsUrl: "https://docs.datadoghq.com/", + docsUrl: "https://docs.datadoghq.com/bits_ai/mcp_server/", source: "openai-curated", url: "https://mcp.datadoghq.com/v1/mcp", }), @@ -500,7 +525,7 @@ export const PLUGIN_CATALOG: readonly PluginCatalogEntry[] = [ tagline: "Ask questions and create or update records in Airtable.", vendor: "By Airtable", category: "Productivity", - docsUrl: "https://airtable.com", + docsUrl: "https://support.airtable.com/docs/using-the-airtable-mcp-server", source: "openai-curated", url: "https://mcp.airtable.com/mcp", }),