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`} + + + + )} +