From da57bfe8e797d3a67de6bbf8d8166dd41171aac4 Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Sat, 21 Feb 2026 04:14:14 +0800 Subject: [PATCH] fix: enhance MCP token handling (#14483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #14475 #### PR Dependency Tree * **PR #14483** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit ## Release Notes * **New Features** * Enhanced MCP server token management with improved security—tokens now display only once with redaction support. * Updated token creation and deletion workflows with clearer UI state controls. * Added tooltip guidance when copying configuration with redacted tokens. --- .../integration/mcp-server/setting-panel.tsx | 33 ++++++++++++++----- .../modules/cloud/services/access-token.ts | 4 ++- packages/frontend/i18n/src/i18n.gen.ts | 4 +++ packages/frontend/i18n/src/resources/en.json | 1 + 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/integration/mcp-server/setting-panel.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/integration/mcp-server/setting-panel.tsx index 17d1e474b..89c962eea 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/integration/mcp-server/setting-panel.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/integration/mcp-server/setting-panel.tsx @@ -1,6 +1,7 @@ import { Button, ErrorMessage, notify, Skeleton } from '@affine/component'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { AccessTokenService, ServerService } from '@affine/core/modules/cloud'; +import type { AccessToken } from '@affine/core/modules/cloud/stores/access-token'; import { WorkspaceService } from '@affine/core/modules/workspace'; import { UserFriendlyError } from '@affine/error'; import { useI18n } from '@affine/i18n'; @@ -37,23 +38,30 @@ const McpServerSetting = () => { const isRevalidating = useLiveData(accessTokenService.isRevalidating$); const error = useLiveData(accessTokenService.error$); const [mutating, setMutating] = useState(false); + const [revealedAccessToken, setRevealedAccessToken] = + useState(null); const t = useI18n(); const mcpAccessToken = useMemo(() => { return accessTokens?.find(token => token.name === 'mcp'); }, [accessTokens]); + const displayedToken = revealedAccessToken ?? mcpAccessToken; + const hasMcpToken = Boolean(revealedAccessToken || mcpAccessToken); + const hasCopyableToken = Boolean(revealedAccessToken); + const isRedactedDisplay = hasMcpToken && !hasCopyableToken; + const code = useMemo(() => { - return mcpAccessToken + return displayedToken ? JSON.stringify( { mcpServers: { - [`${workspaceName} - AFFiNE`]: { + [`affine_workspace_${workspaceService.workspace.id}`]: { type: 'streamable-http', url: `${serverService.server.baseUrl}/api/workspaces/${workspaceService.workspace.id}/mcp`, - note: 'Read docs from AFFiNE workspace', + note: `Read docs from AFFiNE workspace "${workspaceName}"`, headers: { - Authorization: `Bearer ${mcpAccessToken.token}`, + Authorization: `Bearer ${displayedToken.token}`, }, }, }, @@ -62,7 +70,12 @@ const McpServerSetting = () => { 2 ) : null; - }, [mcpAccessToken, workspaceName, workspaceService, serverService]); + }, [displayedToken, workspaceName, workspaceService, serverService]); + + const copyJsonDisabled = !code || mutating || isRedactedDisplay; + const copyJsonTooltip = isRedactedDisplay + ? t['com.affine.integration.mcp-server.copy-json.disabled-hint']() + : undefined; const showLoading = accessTokens === null && isRevalidating; const showError = accessTokens === null && error !== null; @@ -77,7 +90,9 @@ const McpServerSetting = () => { if (mcpAccessToken) { await accessTokenService.revokeUserAccessToken(mcpAccessToken.id); } - await accessTokenService.generateUserAccessToken('mcp'); + const createdToken = + await accessTokenService.generateUserAccessToken('mcp'); + setRevealedAccessToken(createdToken); } catch (err) { notify.error({ error: UserFriendlyError.fromAny(err), @@ -93,6 +108,7 @@ const McpServerSetting = () => { if (mcpAccessToken) { await accessTokenService.revokeUserAccessToken(mcpAccessToken.id); } + setRevealedAccessToken(null); } catch (err) { notify.error({ error: UserFriendlyError.fromAny(err), @@ -127,7 +143,7 @@ const McpServerSetting = () => {
Personal access token
- {!mcpAccessToken ? ( + {!hasMcpToken ? ( diff --git a/packages/frontend/core/src/modules/cloud/services/access-token.ts b/packages/frontend/core/src/modules/cloud/services/access-token.ts index 2ed4b124f..71989da79 100644 --- a/packages/frontend/core/src/modules/cloud/services/access-token.ts +++ b/packages/frontend/core/src/modules/cloud/services/access-token.ts @@ -24,7 +24,7 @@ export class AccessTokenService extends Service { isRevalidating$ = new LiveData(false); error$ = new LiveData(null); - async generateUserAccessToken(name: string) { + async generateUserAccessToken(name: string): Promise { const accessToken = await this.accessTokenStore.generateUserAccessToken(name); this.accessTokens$.value = [ @@ -33,6 +33,8 @@ export class AccessTokenService extends Service { ]; await this.waitForRevalidation(); + + return accessToken as AccessToken; } async revokeUserAccessToken(id: string) { diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts index 79e327cda..a3e32517d 100644 --- a/packages/frontend/i18n/src/i18n.gen.ts +++ b/packages/frontend/i18n/src/i18n.gen.ts @@ -8497,6 +8497,10 @@ export function useAFFiNEI18N(): { * `Enable other MCP Client to search and read the doc of AFFiNE.` */ ["com.affine.integration.mcp-server.desc"](): string; + /** + * `The MCP token is shown only once. Delete and recreate it to copy the JSON configuration.` + */ + ["com.affine.integration.mcp-server.copy-json.disabled-hint"](): string; /** * `Notes` */ diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index c36922c4b..17072ac54 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -2129,6 +2129,7 @@ "com.affine.integration.calendar.no-calendar": "No subscribed calendars yet.", "com.affine.integration.mcp-server.name": "MCP Server", "com.affine.integration.mcp-server.desc": "Enable other MCP Client to search and read the doc of AFFiNE.", + "com.affine.integration.mcp-server.copy-json.disabled-hint": "The MCP token is shown only once. Delete and recreate it to copy the JSON configuration.", "com.affine.audio.notes": "Notes", "com.affine.audio.transcribing": "Transcribing", "com.affine.audio.transcribe.non-owner.confirm.title": "Unable to retrieve AI results for others",