From 60848ea9037c2edd8ab90bce11c215a5d6cc9a50 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sun, 1 Mar 2026 18:37:39 +0000 Subject: [PATCH] feat(ee): mcp (#1976) * feat: MCP * sync * sync --- apps/client/package.json | 2 +- .../public/locales/en-US/translation.json | 19 + apps/client/src/App.tsx | 1 + .../components/settings/settings-sidebar.tsx | 2 +- .../src/components/ui/custom-avatar.tsx | 2 +- .../src/ee/ai/components/mcp-settings.tsx | 138 +++++++ apps/client/src/ee/ai/pages/ai-settings.tsx | 65 ++- .../components/create-api-key-modal.tsx | 6 +- .../components/update-api-key-modal.tsx | 6 +- .../src/ee/api-key/pages/user-api-keys.tsx | 39 +- .../ee/api-key/pages/workspace-api-keys.tsx | 11 +- .../ee/audit/components/audit-logs-table.tsx | 1 + apps/client/src/ee/audit/types/audit.types.ts | 1 + .../src/ee/components/cloud-login-form.tsx | 7 +- .../src/ee/components/ldap-login-modal.tsx | 6 +- .../src/ee/components/manage-hostname.tsx | 9 +- .../components/activate-license-modal.tsx | 7 +- .../mfa/components/mfa-backup-codes-modal.tsx | 6 +- .../src/ee/mfa/components/mfa-challenge.tsx | 6 +- .../ee/mfa/components/mfa-disable-modal.tsx | 8 +- .../src/ee/mfa/components/mfa-setup-modal.tsx | 6 +- .../security/components/allowed-domains.tsx | 6 +- .../security/components/sso-google-form.tsx | 6 +- .../ee/security/components/sso-ldap-form.tsx | 6 +- .../ee/security/components/sso-oidc-form.tsx | 7 +- .../ee/security/components/sso-saml-form.tsx | 6 +- .../auth/components/forgot-password-form.tsx | 18 +- .../auth/components/invite-sign-up-form.tsx | 9 +- .../features/auth/components/login-form.tsx | 18 +- .../auth/components/password-reset-form.tsx | 13 +- .../auth/components/setup-workspace-form.tsx | 18 +- .../editor/components/embed/embed-view.tsx | 6 +- .../group/components/create-group-form.tsx | 6 +- .../group/components/edit-group-form.tsx | 6 +- .../space/components/create-space-form.tsx | 6 +- .../space/components/edit-space-form.tsx | 7 +- .../user/components/account-name-form.tsx | 7 +- .../features/user/components/change-email.tsx | 11 +- .../user/components/change-password.tsx | 11 +- .../members/components/invite-action-menu.tsx | 4 +- .../components/workspace-name-form.tsx | 6 +- .../workspace/types/workspace.types.ts | 2 + apps/client/src/theme.ts | 1 + apps/server/package.json | 4 +- apps/server/src/core/search/search.module.ts | 1 + .../workspace/dto/update-workspace.dto.ts | 4 + .../workspace/services/workspace.service.ts | 18 +- apps/server/src/ee | 2 +- pnpm-lock.yaml | 378 +++++++++++++++++- 49 files changed, 781 insertions(+), 154 deletions(-) create mode 100644 apps/client/src/ee/ai/components/mcp-settings.tsx diff --git a/apps/client/package.json b/apps/client/package.json index 617bf447..60ceae35 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -55,7 +55,7 @@ "semver": "^7.7.3", "socket.io-client": "^4.8.3", "tiptap-extension-global-drag-handle": "^0.1.18", - "zod": "^3.25.76" + "zod": "^4.3.6" }, "devDependencies": { "@eslint/js": "^9.16.0", diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 7b87e3eb..5c64c3e5 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -607,6 +607,25 @@ "Generative AI (Ask AI)": "Generative AI (Ask AI)", "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.", "Toggle generative AI": "Toggle generative AI", + "Enterprise feature": "Enterprise feature", + "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + "AI & MCP": "AI & MCP", + "AI": "AI", + "MCP": "MCP", + "Model Context Protocol (MCP)": "Model Context Protocol (MCP)", + "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.", + "MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + "MCP documentation": "MCP documentation", + "MCP Server URL": "MCP Server URL", + "Use your API key for authentication. You can manage API keys in your account settings.": "Use your API key for authentication. You can manage API keys in your account settings.", + "Supported tools": "Supported tools", + "Your workspace has MCP enabled. Use your API key to connect AI assistants.": "Your workspace has MCP enabled. Use your API key to connect AI assistants.", + "MCP server URL:": "MCP server URL:", + "Learn more": "Learn more", + "View the": "View the", + "for usage details.": "for usage details.", + "for setup instructions.": "for setup instructions.", + "API documentation": "API documentation", "Sources": "Sources", "AI Answers not available for attachments": "AI Answers not available for attachments", "No answer available": "No answer available", diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index 3a1eb621..c290157c 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -103,6 +103,7 @@ export default function App() { } /> } /> } /> + } /> } /> {!isCloud() && } />} {isCloud() && } />} diff --git a/apps/client/src/components/settings/settings-sidebar.tsx b/apps/client/src/components/settings/settings-sidebar.tsx index 1c49f918..70f20782 100644 --- a/apps/client/src/components/settings/settings-sidebar.tsx +++ b/apps/client/src/components/settings/settings-sidebar.tsx @@ -113,7 +113,7 @@ const groupedData: DataGroup[] = [ showDisabledInNonEE: true, }, { - label: "AI settings", + label: "AI", icon: IconSparkles, path: "/settings/ai", isAdmin: true, diff --git a/apps/client/src/components/ui/custom-avatar.tsx b/apps/client/src/components/ui/custom-avatar.tsx index 54730127..1342cdfb 100644 --- a/apps/client/src/components/ui/custom-avatar.tsx +++ b/apps/client/src/components/ui/custom-avatar.tsx @@ -4,7 +4,7 @@ import { getAvatarUrl } from "@/lib/config.ts"; import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts"; interface CustomAvatarProps { - avatarUrl: string; + avatarUrl?: string; name: string; color?: string; size?: string | number; diff --git a/apps/client/src/ee/ai/components/mcp-settings.tsx b/apps/client/src/ee/ai/components/mcp-settings.tsx new file mode 100644 index 00000000..396e74ff --- /dev/null +++ b/apps/client/src/ee/ai/components/mcp-settings.tsx @@ -0,0 +1,138 @@ +import { + Anchor, + Group, + List, + Text, + Switch, + TextInput, + ActionIcon, + Tooltip, + Stack, + Alert, +} from "@mantine/core"; +import { useAtom } from "jotai"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts"; +import { notifications } from "@mantine/notifications"; +import { useIsCloudEE } from "@/hooks/use-is-cloud-ee.tsx"; +import { getAppUrl } from "@/lib/config.ts"; +import { IconCheck, IconCopy, IconInfoCircle } from "@tabler/icons-react"; +import { CopyButton } from "@/components/common/copy-button.tsx"; + +export default function McpSettings() { + const { t } = useTranslation(); + const [workspace, setWorkspace] = useAtom(workspaceAtom); + const [checked, setChecked] = useState(workspace?.settings?.ai?.mcp); + const hasAccess = useIsCloudEE(); + + const mcpUrl = `${getAppUrl()}/api/mcp`; + + const handleChange = async (event: React.ChangeEvent) => { + const value = event.currentTarget.checked; + try { + const updatedWorkspace = await updateWorkspace({ mcpEnabled: value }); + setChecked(value); + setWorkspace(updatedWorkspace); + } catch (err) { + notifications.show({ + message: err?.response?.data?.message, + color: "red", + }); + } + }; + + return ( + + {!hasAccess && ( + } + title={t("Enterprise feature")} + color="blue" + > + {t( + "MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + )} + + )} + + +
+ {t("Model Context Protocol (MCP)")} + + {t( + "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.", + )}{" "} + {t("View the")}{" "} + + {t("MCP documentation")} + + . + +
+ + +
+ + {checked && ( +
+ + {t("MCP Server URL")} + + + + + {({ copied, copy }) => ( + + + {copied ? : } + + + )} + + + + {t( + "Use your API key for authentication. You can manage API keys in your account settings.", + )} + + +
+ + {t("Supported tools")} + + + search_pages, get_page, create_page, update_page + list_pages, list_child_pages, duplicate_page + copy_page_to_space, move_page, move_page_to_space + get_space, list_spaces, create_space, update_space + get_comments, create_comment, update_comment + search_attachments, list_workspace_members, get_current_user + +
+
+ )} +
+ ); +} diff --git a/apps/client/src/ee/ai/pages/ai-settings.tsx b/apps/client/src/ee/ai/pages/ai-settings.tsx index 441f91b9..d0aaeafe 100644 --- a/apps/client/src/ee/ai/pages/ai-settings.tsx +++ b/apps/client/src/ee/ai/pages/ai-settings.tsx @@ -6,44 +6,75 @@ import useUserRole from "@/hooks/use-user-role.tsx"; import { useTranslation } from "react-i18next"; import EnableAiSearch from "@/ee/ai/components/enable-ai-search.tsx"; import EnableGenerativeAi from "@/ee/ai/components/enable-generative-ai.tsx"; -import { Alert, Stack } from "@mantine/core"; +import McpSettings from "@/ee/ai/components/mcp-settings.tsx"; +import { Alert, Stack, Tabs } from "@mantine/core"; import { IconInfoCircle } from "@tabler/icons-react"; import { useIsCloudEE } from "@/hooks/use-is-cloud-ee.tsx"; import { isCloud } from "@/lib/config.ts"; +import { useLocation, useNavigate } from "react-router-dom"; export default function AiSettings() { const { t } = useTranslation(); const { isAdmin } = useUserRole(); const hasAccess = useIsCloudEE(); + const location = useLocation(); + const navigate = useNavigate(); + + const activeTab = location.pathname.endsWith("/mcp") ? "mcp" : "ai"; if (!isAdmin) { return null; } + const handleTabChange = (value: string | null) => { + if (value === "mcp") { + navigate("/settings/ai/mcp"); + } else { + navigate("/settings/ai"); + } + }; + return ( <> AI - {getAppName()} - + - {!hasAccess && ( - } - title={t("Enterprise feature")} - color="blue" - mb="lg" - > - {t( - "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + + + + {t("AI")} + + + {t("MCP")} + + + + + {!hasAccess && ( + } + title={t("Enterprise feature")} + color="blue" + mb="lg" + > + {t( + "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + )} + )} - - )} - - {!isCloud() && } - - + + {!isCloud() && } + + + + + + + + ); } diff --git a/apps/client/src/ee/api-key/components/create-api-key-modal.tsx b/apps/client/src/ee/api-key/components/create-api-key-modal.tsx index cade36e8..ab19552c 100644 --- a/apps/client/src/ee/api-key/components/create-api-key-modal.tsx +++ b/apps/client/src/ee/api-key/components/create-api-key-modal.tsx @@ -1,8 +1,8 @@ import { lazy, Suspense, useState } from "react"; import { Modal, TextInput, Button, Group, Stack, Select } from "@mantine/core"; import { useForm } from "@mantine/form"; -import { zodResolver } from "mantine-form-zod-resolver"; -import { z } from "zod"; +import { zod4Resolver } from "mantine-form-zod-resolver"; +import { z } from "zod/v4"; import { useTranslation } from "react-i18next"; import { useCreateApiKeyMutation } from "@/ee/api-key/queries/api-key-query"; import { IconCalendar } from "@tabler/icons-react"; @@ -36,7 +36,7 @@ export function CreateApiKeyModal({ const createApiKeyMutation = useCreateApiKeyMutation(); const form = useForm({ - validate: zodResolver(formSchema), + validate: zod4Resolver(formSchema), initialValues: { name: "", expiresAt: "", diff --git a/apps/client/src/ee/api-key/components/update-api-key-modal.tsx b/apps/client/src/ee/api-key/components/update-api-key-modal.tsx index 6edeb1c3..e4370eac 100644 --- a/apps/client/src/ee/api-key/components/update-api-key-modal.tsx +++ b/apps/client/src/ee/api-key/components/update-api-key-modal.tsx @@ -1,7 +1,7 @@ import { Modal, TextInput, Button, Group, Stack } from "@mantine/core"; import { useForm } from "@mantine/form"; -import { zodResolver } from "mantine-form-zod-resolver"; -import { z } from "zod"; +import { zod4Resolver } from "mantine-form-zod-resolver"; +import { z } from "zod/v4"; import { useTranslation } from "react-i18next"; import { useUpdateApiKeyMutation } from "@/ee/api-key/queries/api-key-query"; import { IApiKey } from "@/ee/api-key"; @@ -27,7 +27,7 @@ export function UpdateApiKeyModal({ const updateApiKeyMutation = useUpdateApiKeyMutation(); const form = useForm({ - validate: zodResolver(formSchema), + validate: zod4Resolver(formSchema), initialValues: { name: "", }, diff --git a/apps/client/src/ee/api-key/pages/user-api-keys.tsx b/apps/client/src/ee/api-key/pages/user-api-keys.tsx index 49fd9cab..9e7c3446 100644 --- a/apps/client/src/ee/api-key/pages/user-api-keys.tsx +++ b/apps/client/src/ee/api-key/pages/user-api-keys.tsx @@ -1,9 +1,9 @@ import React, { useState } from "react"; -import { Button, Group, Space } from "@mantine/core"; +import { Anchor, Alert, Button, Group, Space, Text } from "@mantine/core"; import { Helmet } from "react-helmet-async"; import { useTranslation } from "react-i18next"; import SettingsTitle from "@/components/settings/settings-title"; -import { getAppName } from "@/lib/config"; +import { getAppName, getAppUrl } from "@/lib/config"; import { ApiKeyTable } from "@/ee/api-key/components/api-key-table"; import { CreateApiKeyModal } from "@/ee/api-key/components/create-api-key-modal"; import { ApiKeyCreatedModal } from "@/ee/api-key/components/api-key-created-modal"; @@ -13,6 +13,8 @@ import Paginate from "@/components/common/paginate"; import { useCursorPaginate } from "@/hooks/use-cursor-paginate"; import { useGetApiKeysQuery } from "@/ee/api-key/queries/api-key-query.ts"; import { IApiKey } from "@/ee/api-key"; +import { useAtom } from "jotai"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; export default function UserApiKeys() { const { t } = useTranslation(); @@ -23,6 +25,8 @@ export default function UserApiKeys() { const [revokeModalOpened, setRevokeModalOpened] = useState(false); const [selectedApiKey, setSelectedApiKey] = useState(null); const { data, isLoading } = useGetApiKeysQuery({ cursor }); + const [workspace] = useAtom(workspaceAtom); + const mcpEnabled = workspace?.settings?.ai?.mcp === true; const handleCreateSuccess = (response: IApiKey) => { setCreatedApiKey(response); @@ -48,6 +52,37 @@ export default function UserApiKeys() { + + {t("View the")}{" "} + + {t("API documentation")} + {" "} + {t("for usage details.")} + + + {mcpEnabled && ( + + + {t( + "Your workspace has MCP enabled. Use your API key to connect AI assistants.", + )}{" "} + + {t("Learn more")} + + + + {t("MCP server URL:")}{" "} + + {`${getAppUrl()}/api/mcp`} + + + + )} +