From 9dc7bb30da46be897b7a7901dc669fba5232273e Mon Sep 17 00:00:00 2001 From: Wenchao An Date: Wed, 30 Sep 2026 10:14:42 +0800 Subject: [PATCH] fix(capabilities): restore administrator management of shared MCP plugins (#6018) * fix(capabilities): restore administrator management of shared MCP plugins * fix(mcp): clarify control ownership and require mutation scope --- README.md | 2 +- docs/capability-center.md | 5 +- frontend/src/AGENTS.md | 4 +- .../capabilities/mcp-plugin-manager.tsx | 26 +- .../workspace/capabilities/plugin-gallery.tsx | 30 +- frontend/src/core/mcp/hooks.ts | 20 +- frontend/tests/e2e/business-plugins.spec.ts | 4 +- frontend/tests/e2e/capability-center.spec.ts | 24 +- frontend/tests/e2e/mcp-settings.spec.ts | 16 +- .../tests/e2e/platform-mcp-controls.spec.ts | 293 +++++++++++++++++ frontend/tests/e2e/plugin-icons.spec.ts | 23 +- frontend/tests/e2e/utils/mock-api.ts | 4 + .../mcp-plugin-manager.dom.test.tsx | 61 +++- frontend/tests/unit/core/mcp/hooks.test.ts | 298 ++++++++++-------- .../unit/core/mcp/ownership.dom.test.tsx | 72 ++++- 15 files changed, 688 insertions(+), 194 deletions(-) create mode 100644 frontend/tests/e2e/platform-mcp-controls.spec.ts diff --git a/README.md b/README.md index 1cd1212361..9d66da0b15 100644 --- a/README.md +++ b/README.md @@ -646,7 +646,7 @@ Signed-in users' notification toggle, default model, conversation mode, and reas Capability Center groups plugins by office collaboration, documents and knowledge, search and research, business and data, and development and operations. The directory includes setup references alongside existing MCP configurations and Lark. Recommended integrations and built-in support do not imply an installed or verified connection; the Installed filter shows configured MCP entries and installed Lark only. Personal MCP connections configured in the web interface are persisted per user. -Deployment tools remain shared. See [connection ownership](docs/capability-center.md#personal-and-deployment-mcp-configuration). +Deployment tools remain shared. Administrators can add, edit, enable, disable and delete shared MCP servers under **Platform provided**; ordinary users see their status without controls. Personal plugin switches affect only the signed-in user's connections. See [connection ownership](docs/capability-center.md#personal-and-deployment-mcp-configuration). For plugin manifests, adapter registration, and Agent capability selection, see [Capability Center integration contract](docs/capability-center.md). diff --git a/docs/capability-center.md b/docs/capability-center.md index d92b3ab8bf..4962f883c5 100644 --- a/docs/capability-center.md +++ b/docs/capability-center.md @@ -52,7 +52,7 @@ DeerFlow's incoming IM channels. Existing manually configured CLI connections are not rewritten when the catalog entry changes. Configuration saves credentials without sending a message or creating a CRM -record. Connections created in the web interface are personal: credentials, +record. Connections created under **My plugins** are personal: credentials, enabled state and edits belong to the signed-in user, including administrators. They are stored under `.deer-flow/users//integrations/mcp.json` and remain masked in the editor. Existing deployment connections in @@ -60,6 +60,9 @@ remain masked in the editor. Existing deployment connections in The page separates **Platform provided** (deployment setup guides and shared MCP entries) from **My plugins** (personal connections, configuration templates and Lark account authorization). Existing search and category filters apply to both. +Administrators can add, edit, toggle and delete shared MCP entries under +**Platform provided**. These changes apply to all users; ordinary users see +shared entries read-only. Edit, toggle and delete personal entries using the existing MCP controls. An Agent selects these connections through **Plugins and skills**, just like other MCP servers. New tool selection applies on the next run. diff --git a/frontend/src/AGENTS.md b/frontend/src/AGENTS.md index 443ebc35d4..22b7b15666 100644 --- a/frontend/src/AGENTS.md +++ b/frontend/src/AGENTS.md @@ -46,7 +46,7 @@ against raw artifact paths before encoding. Personal server management uses targeted `POST /api/mcp/personal/config/servers`, `PUT /api/mcp/personal/config/server`, and bodyless `DELETE /api/mcp/personal/config/servers/{server_name}` mutations. The deployment - `/api/mcp/config` endpoints remain operator-only and are not used by this editor. Delete names are + `/api/mcp/config` endpoints remain admin-only and are used by the Platform provided editor. Delete names are percent-encoded, including legacy empty and slash-containing names; every successful mutation invalidates `["mcpConfig"]` only after the response. Current-chat MCP background tasks use `core/background-tasks`: the header @@ -198,4 +198,4 @@ binding; explicit page-local overrides survive new-thread route replacement and reset on conversation changes. Gateway supplies defaults for clients without a selector; frontend visibility must not become a runtime enforcement boundary. -MCP editor requests use the personal configuration API. Query keys for personal MCP config and capability installations include the authenticated user ID; remount the editor when that ID changes. Deployment installations remain read-only under Platform provided, alongside deployment setup guides. My plugins contains personal configuration templates, Lark account authorization and the personal MCP editor; a deployment installation must not mark a personal template configured. +MCP hooks and the editor default to personal scope. Mutation-option builders require an explicit scope. MCP control accessible names include the localized ownership section, so same-named shared and personal connections remain distinct without region context. Query keys include scope and authenticated user ID; remount each editor when that ID changes. Platform provided uses the deployment editor only for administrators outside static mode, restoring add, edit, toggle and delete through the existing admin-only MCP API. Ordinary users see shared entries read-only. My plugins contains personal configuration templates, Lark account authorization and the personal MCP editor; a deployment installation must not mark a personal template configured. diff --git a/frontend/src/components/workspace/capabilities/mcp-plugin-manager.tsx b/frontend/src/components/workspace/capabilities/mcp-plugin-manager.tsx index 1fd0869b33..1d0f5c0c7b 100644 --- a/frontend/src/components/workspace/capabilities/mcp-plugin-manager.tsx +++ b/frontend/src/components/workspace/capabilities/mcp-plugin-manager.tsx @@ -14,8 +14,9 @@ import { } from "@/components/ui/dialog"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; +import { capabilityCopy } from "@/core/capabilities/copy"; import { useI18n } from "@/core/i18n/hooks"; -import { MCPConfigRequestError } from "@/core/mcp/api"; +import { MCPConfigRequestError, type MCPScope } from "@/core/mcp/api"; import { useEnableMCPServer, useMCPConfig, @@ -44,6 +45,7 @@ import { PluginIcon } from "./plugin-icon"; import { PluginIconPicker } from "./plugin-icon-picker"; type MCPPluginManagerProps = { + scope?: MCPScope; query?: string; catalog?: PluginDirectoryEntry[]; category?: PluginCategory | "all"; @@ -53,7 +55,7 @@ type MCPPluginManagerProps = { }; export function MCPPluginManager(props: MCPPluginManagerProps) { - const { config, isLoading, error } = useMCPConfig(); + const { config, isLoading, error } = useMCPConfig(props.scope); // Keep the directory mounted while MCP discovery completes. Replacing the // entire subtree can swallow a click on an independently available plugin. return ( @@ -68,6 +70,7 @@ export function MCPPluginManager(props: MCPPluginManagerProps) { function MCPServerList({ servers, + scope = "user", query = "", catalog = [], category = "all", @@ -81,9 +84,13 @@ function MCPServerList({ isLoading?: boolean; error?: Error | null; }) { - const { t } = useI18n(); - const { isPending, mutate: enableMCPServer } = useEnableMCPServer(); - const { isPending: isWriting, mutate: mutateServer } = useMCPServerMutation(); + const { t, locale } = useI18n(); + const labels = capabilityCopy(locale); + const scopeLabel = + scope === "deployment" ? labels.platformTitle : labels.personalTitle; + const { isPending, mutate: enableMCPServer } = useEnableMCPServer(scope); + const { isPending: isWriting, mutate: mutateServer } = + useMCPServerMutation(scope); const [editor, setEditor] = useState< { mode: "add" } | { mode: "edit"; name: string } | null >(null); @@ -250,6 +257,7 @@ function MCPServerList({ size="sm" variant="outline" disabled={readOnly || isMutating} + aria-label={`${t.capabilities.addPlugin} (${scopeLabel})`} onClick={openAddEditor} > {t.capabilities.addPlugin} @@ -308,11 +316,11 @@ function MCPServerList({ ? undefined : () => openEditEditor(name, config) } - detailsLabel={`${t.capabilities.details} ${displayName}`} + detailsLabel={`${t.capabilities.details} ${displayName} (${scopeLabel})`} > enableMCPServer({ serverName: name, enabled: checked }) @@ -321,7 +329,7 @@ function MCPServerList({