From abab82336d9047b915c5fc27bfce8971551b6a29 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:22:23 -0700 Subject: [PATCH 1/3] fix(connectors): revoke notices name what the vendor page lists (TASK-282) Linear's page lists the grant as "MCP", and Sentry's lists the MCP server's app, whose revoke ends every client that server holds. The manual-revoke notice now uses per-entry copy for linear and sentry, as supabase already did, and the removed-connection line no longer names Commonly. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: kai Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/i18n/locales/en.json | 4 ++- frontend/src/i18n/locales/zh-CN.json | 4 ++- .../v2/__tests__/V2ConnectorsPage.test.tsx | 28 +++++++++++++++++-- .../src/v2/components/V2ConnectorTools.tsx | 7 +++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 3dbd8b52a..fe1a2aa69 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -2287,6 +2287,8 @@ "gateSwitch": "Relay {{pod}}", "gatesTitle": "Pods that reach this channel", "hostedMcpManualRevoke": "Your previous {{provider}} account may still have Commonly connected. Switch to it on {{provider}}, then remove Commonly there.", + "hostedMcpManualRevoke_linear": "Your previous Linear account may still have an MCP grant connected. Switch to it in Linear, then revoke the MCP grant under Linear's connected apps.", + "hostedMcpManualRevoke_sentry": "Your previous Sentry account may still have Sentry's MCP server app connected. Switch to it in Sentry, then revoke that app there. Revoking it ends every client that server holds for the account, not only Commonly.", "hostedMcpManualRevoke_supabase": "Commonly may still be authorized in the Supabase organization you connected before. Removing Commonly under Authorized apps disconnects everyone who connected that organization.", "hostedMcpReviewAuthorization": "Open {{provider}}'s connected apps", "hostedMcpReviewAuthorization_supabase": "Open Supabase Authorized apps", @@ -2472,7 +2474,7 @@ "confirmRemoveConnection": "Yes, remove", "removeConnectionError": "Could not remove the connection.", "reviewProviderAccess": "Review access at {{label}}", - "removedReviewProvider": "Connection removed. Review Commonly access at {{label}}.", + "removedReviewProvider": "Connection removed. Review its access at {{label}}.", "openProviderSettings": "Open provider settings", "noConnection": "install the GitHub App first · an admin does this once", "noSeats": "no agent in this pod yet", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 4755445bb..9bcaec24a 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -2278,6 +2278,8 @@ "gateSwitch": "转发 {{pod}}", "gatesTitle": "接入此频道的 Pod", "hostedMcpManualRevoke": "你之前的 {{provider}} 账号可能仍保留对 Commonly 的授权。请在 {{provider}} 切换到那个账号,再在那里移除 Commonly。", + "hostedMcpManualRevoke_linear": "你之前的 Linear 账号可能仍连着一个 MCP 授权。请在 Linear 切换到那个账号,再在其已连接应用中撤销该 MCP 授权。", + "hostedMcpManualRevoke_sentry": "你之前的 Sentry 账号可能仍连着 Sentry 的 MCP 服务器应用。请在 Sentry 切换到那个账号,再在那里撤销该应用。撤销会结束该服务器在此账号上持有的所有客户端,不只是 Commonly。", "hostedMcpManualRevoke_supabase": "Commonly 可能仍在你之前连接的 Supabase 组织中获得授权。在“已授权应用”中移除 Commonly,会断开所有连接该组织的成员。", "hostedMcpReviewAuthorization": "打开 {{provider}} 的已连接应用", "hostedMcpReviewAuthorization_supabase": "打开 Supabase 已授权应用", @@ -2463,7 +2465,7 @@ "confirmRemoveConnection": "确认移除", "removeConnectionError": "无法移除连接。", "reviewProviderAccess": "查看 {{label}} 的授权", - "removedReviewProvider": "连接已移除。请检查 {{label}} 中 Commonly 的授权。", + "removedReviewProvider": "连接已移除。请检查 {{label}} 中的授权。", "openProviderSettings": "打开服务提供方设置", "noConnection": "请先安装 GitHub App · 管理员只需安装一次", "noSeats": "此 Pod 中还没有智能体", diff --git a/frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx b/frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx index 34737ea0d..f8b7b6f8b 100644 --- a/frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx +++ b/frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx @@ -744,7 +744,7 @@ describe('V2ConnectorsPage', () => { expect(reviewLink).toHaveAttribute('rel', 'noopener noreferrer'); const notice = screen.getByRole('status'); expect(notice).toHaveTextContent( - 'Your previous Linear account may still have Commonly connected. Switch to it on Linear, then remove Commonly there.', + 'Your previous Linear account may still have an MCP grant connected. Switch to it in Linear, then revoke the MCP grant under Linear\'s connected apps.', ); expect(notice.closest('.v2-connector-row')).toHaveTextContent('Linear'); expect(window.location.search).toBe(''); @@ -770,7 +770,7 @@ describe('V2ConnectorsPage', () => { expect(reviewLink).toHaveAttribute('href', revokePage); const notice = screen.getByRole('status'); expect(notice).toHaveTextContent( - '你之前的 Linear 账号可能仍保留对 Commonly 的授权。请在 Linear 切换到那个账号,再在那里移除 Commonly。', + '你之前的 Linear 账号可能仍连着一个 MCP 授权。请在 Linear 切换到那个账号,再在其已连接应用中撤销该 MCP 授权。', ); expect(notice.closest('.v2-connector-row')).toHaveTextContent('Linear'); expect(window.location.search).toBe(''); @@ -803,6 +803,30 @@ describe('V2ConnectorsPage', () => { expect(screen.getByRole('status')).not.toHaveTextContent('remove Commonly there'); }); + it('warns that revoking the Sentry MCP app ends every client that server holds, and does not name Commonly', async () => { + const revokePage = 'https://sentry.io/settings/account/api/authorizations/'; + window.history.replaceState( + {}, + '', + `/v2/connectors?hostedMcp=connected&entryId=sentry&revokeAt=${encodeURIComponent(revokePage)}`, + ); + mockGets([{ + _id: 'i-sentry', type: 'hosted-mcp', status: 'connected', + config: { entryId: 'sentry', revokePage }, + }], [{ + ...hostedLinearCatalogEntry('i-sentry'), + installableId: 'sentry', label: 'Sentry', entryId: 'sentry', + }]); + renderPage(); + + const reviewLink = await screen.findByRole('link', { name: "Open Sentry's connected apps" }); + expect(reviewLink).toHaveAttribute('href', revokePage); + expect(screen.getByRole('status')).toHaveTextContent( + "Revoking it ends every client that server holds for the account, not only Commonly.", + ); + expect(screen.getByRole('status')).not.toHaveTextContent('remove Commonly there'); + }); + it('does not turn a callback query into a provider link unless it matches the row', async () => { window.history.replaceState( {}, diff --git a/frontend/src/v2/components/V2ConnectorTools.tsx b/frontend/src/v2/components/V2ConnectorTools.tsx index 9e4254da5..2226efca0 100644 --- a/frontend/src/v2/components/V2ConnectorTools.tsx +++ b/frontend/src/v2/components/V2ConnectorTools.tsx @@ -199,6 +199,8 @@ const ModeGlyph: React.FC<{ mode: GrantWriteMode }> = ({ mode }) => { return ; }; +const PER_ENTRY_REVOKE_COPY = new Set(['supabase', 'linear', 'sentry']); + const V2ConnectorTools: React.FC = ({ pods, manualRevokeNotice = null }) => { const { t } = useTranslation(); const api = useV2Api(); @@ -300,7 +302,8 @@ const V2ConnectorTools: React.FC = ({ pods, manualRevokeNotice = null }) ); const renderManualRevokeNotice = (entry: ToolCatalogEntry | null) => { if (!manualRevokeNotice || !noticeMatchesEntry(entry)) return null; - const context = manualRevokeNotice.entryId === 'supabase' ? 'supabase' : undefined; + // Per-entry copy names only what that vendor's page lists; entries without it fall back to the generic line. + const context = PER_ENTRY_REVOKE_COPY.has(manualRevokeNotice.entryId) ? manualRevokeNotice.entryId : undefined; return (

{t('connectors.hostedMcpManualRevoke', { @@ -1110,7 +1113,7 @@ const V2ConnectorTools: React.FC = ({ pods, manualRevokeNotice = null }) {error && !draft && !selected && !githubAppSetup && (

{error}

)} - {removedHiddenNotice &&

{t('tools.removedReviewProvider', { defaultValue: 'Connection removed. Review Commonly access at {{label}}.', label: removedHiddenNotice.label })} {t('tools.openProviderSettings', { defaultValue: 'Open provider settings' })}

} + {removedHiddenNotice &&

{t('tools.removedReviewProvider', { defaultValue: 'Connection removed. Review its access at {{label}}.', label: removedHiddenNotice.label })} {t('tools.openProviderSettings', { defaultValue: 'Open provider settings' })}

}
{liveRows.map(renderRow)} {notYet.map(renderNotYet)} From 84b27bed66d51fc0858b3c3834740099860181af Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:28:55 -0700 Subject: [PATCH 2/3] test(connectors): match the generic removed-connection copy (TASK-282) Co-Authored-By: Claude Haiku 5.5 Co-authored-by: kai Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/v2/__tests__/V2ConnectorTools.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/v2/__tests__/V2ConnectorTools.test.tsx b/frontend/src/v2/__tests__/V2ConnectorTools.test.tsx index 7b59276d5..cfeb6332f 100644 --- a/frontend/src/v2/__tests__/V2ConnectorTools.test.tsx +++ b/frontend/src/v2/__tests__/V2ConnectorTools.test.tsx @@ -761,7 +761,7 @@ test('a hidden hosted entry has no offering while its existing connection can be fireEvent.click(screen.getByRole('button', { name: 'Remove connection' })); fireEvent.click(screen.getByRole('button', { name: 'Yes, remove' })); await waitFor(() => expect(axios.delete).toHaveBeenCalledWith('/api/integrations/conn-hidden', expect.anything())); - expect(await screen.findByText(/Connection removed. Review Commonly access at Airtable/)).toBeInTheDocument(); + expect(await screen.findByText(/Connection removed. Review its access at Airtable/)).toBeInTheDocument(); }); test('a hosted connect start error uses fixed copy instead of response text', async () => { From c97c8003d42e86be2a3150d7b5b63f66126a0995 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:23:50 -0700 Subject: [PATCH 3/3] fix(connectors): pin the generic manual-revoke fallback and reword zh Sentry copy (TASK-282) Co-Authored-By: Claude Haiku 5.5 Co-authored-by: kai Agent-Model: claude/claude-haiku-5-5/default --- frontend/src/i18n/locales/zh-CN.json | 2 +- .../v2/__tests__/V2ConnectorsPage.test.tsx | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 9bcaec24a..8b58221a7 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -2279,7 +2279,7 @@ "gatesTitle": "接入此频道的 Pod", "hostedMcpManualRevoke": "你之前的 {{provider}} 账号可能仍保留对 Commonly 的授权。请在 {{provider}} 切换到那个账号,再在那里移除 Commonly。", "hostedMcpManualRevoke_linear": "你之前的 Linear 账号可能仍连着一个 MCP 授权。请在 Linear 切换到那个账号,再在其已连接应用中撤销该 MCP 授权。", - "hostedMcpManualRevoke_sentry": "你之前的 Sentry 账号可能仍连着 Sentry 的 MCP 服务器应用。请在 Sentry 切换到那个账号,再在那里撤销该应用。撤销会结束该服务器在此账号上持有的所有客户端,不只是 Commonly。", + "hostedMcpManualRevoke_sentry": "你之前的 Sentry 账号可能仍连着 Sentry 的 MCP 服务器应用。请在 Sentry 切换到那个账号,再在那里撤销该应用。撤销后,此账号下该服务器的所有客户端都会断开,不只是 Commonly。", "hostedMcpManualRevoke_supabase": "Commonly 可能仍在你之前连接的 Supabase 组织中获得授权。在“已授权应用”中移除 Commonly,会断开所有连接该组织的成员。", "hostedMcpReviewAuthorization": "打开 {{provider}} 的已连接应用", "hostedMcpReviewAuthorization_supabase": "打开 Supabase 已授权应用", diff --git a/frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx b/frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx index f8b7b6f8b..f759dc89e 100644 --- a/frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx +++ b/frontend/src/v2/__tests__/V2ConnectorsPage.test.tsx @@ -827,6 +827,28 @@ describe('V2ConnectorsPage', () => { expect(screen.getByRole('status')).not.toHaveTextContent('remove Commonly there'); }); + it('keeps the generic manual-revoke copy for an entry without its own', async () => { + const revokePage = 'https://airtable.com/account/security'; + window.history.replaceState( + {}, + '', + `/v2/connectors?hostedMcp=connected&entryId=airtable&revokeAt=${encodeURIComponent(revokePage)}`, + ); + mockGets([{ + _id: 'i-airtable', type: 'hosted-mcp', status: 'connected', + config: { entryId: 'airtable', revokePage }, + }], [{ + ...hostedLinearCatalogEntry('i-airtable'), + installableId: 'airtable', label: 'Airtable', entryId: 'airtable', + }]); + renderPage(); + + expect(await screen.findByRole('link', { name: "Open Airtable's connected apps" })).toHaveAttribute('href', revokePage); + expect(screen.getByRole('status')).toHaveTextContent( + 'Your previous Airtable account may still have Commonly connected. Switch to it on Airtable, then remove Commonly there.', + ); + }); + it('does not turn a callback query into a provider link unless it matches the row', async () => { window.history.replaceState( {},