diff --git a/apify-docs-theme/src/theme/LLMButtons/index.jsx b/apify-docs-theme/src/theme/LLMButtons/index.jsx index 63c7c28a26..688cee078d 100644 --- a/apify-docs-theme/src/theme/LLMButtons/index.jsx +++ b/apify-docs-theme/src/theme/LLMButtons/index.jsx @@ -7,10 +7,13 @@ import { CheckIcon, ChevronDownIcon, CopyIcon, + CursorIcon, ExternalLinkIcon, LoaderIcon, MarkdownIcon, + McpIcon, PerplexityIcon, + VscodeIcon, } from '@apify/ui-icons'; import { Menu, Text, theme } from '@apify/ui-library'; @@ -31,6 +34,29 @@ const DROPDOWN_OPTIONS = [ Icon: MarkdownIcon, value: 'viewAsMarkdown', }, + { + label: 'Copy MCP server', + description: 'Copy MCP Server URL to clipboard', + showExternalIcon: false, + Icon: McpIcon, + value: 'copyMcpServer', + }, + { + label: 'Connect to Cursor', + description: 'Install MCP Server on Cursor', + showExternalIcon: true, + // TODO: Replace with CursorIcon - we don't have one yet + Icon: CursorIcon, + value: 'connectCursor', + }, + { + label: 'Connect to VS Code', + description: 'Install MCP server on VS Code', + showExternalIcon: true, + // TODO: Replace with VS Code Icon - we don't have one yet + Icon: VscodeIcon, + value: 'connectVsCode', + }, { label: 'Open in ChatGPT', description: 'Ask questions about this page', @@ -54,6 +80,16 @@ const DROPDOWN_OPTIONS = [ }, ]; +const MCP_SERVER_URL = 'https://mcp.apify.com/?tools=docs'; + +const MCP_CONFIG_JSON = `{ + "mcpServers": { + "apify": { + "url": "${MCP_SERVER_URL}" + } + } +}`; + const getPrompt = (currentUrl) => `Read from ${currentUrl} so I can ask questions about it.`; const getMarkdownUrl = (currentUrl) => { const url = new URL(currentUrl); @@ -161,6 +197,87 @@ const onCopyAsMarkdownClick = async ({ setCopyingStatus }) => { } }; +const onCopyMcpServerClick = async () => { + if (window.analytics) { + window.analytics.track('Clicked', { + app: 'docs', + button_text: 'Copy MCP server', + element: 'llm-buttons.copyMcpServer', + }); + } + + try { + await navigator.clipboard.writeText(MCP_CONFIG_JSON); + } catch (error) { + console.error('Failed to copy MCP configuration:', error); + } +}; + +const openApifyMcpConfigurator = (integration) => { + try { + window.open(`https://mcp.apify.com/?integration=${integration}`, '_blank'); + } catch (error) { + console.error('Error opening fallback URL:', error); + } +}; + +const openMcpIntegration = async (integration) => { + // Try to open the app directly using URL scheme + let appUrl; + if (integration === 'cursor') { + // Cursor deeplink format: + // cursor://anysphere.cursor-deeplink/mcp/install?name=$NAME&config=$BASE64_JSON + const cursorConfig = { + url: MCP_SERVER_URL, + }; + const encodedConfig = btoa(JSON.stringify(cursorConfig)); + appUrl = `cursor://anysphere.cursor-deeplink/mcp/install?name=apify&config=${encodeURIComponent(encodedConfig)}`; + } else if (integration === 'vscode') { + // VS Code deeplink format: vscode:mcp/install? + const mcpConfig = { + name: 'Apify', + type: 'http', + url: MCP_SERVER_URL, + }; + const encodedConfig = encodeURIComponent(JSON.stringify(mcpConfig)); + appUrl = `vscode:mcp/install?${encodedConfig}`; + } + + if (appUrl) { + const openedWindow = window.open(appUrl, '_blank'); + + if (openedWindow) { + return; + } + } + // Fallback to web configurator if appUrl doesn't exist or window.open failed + openApifyMcpConfigurator(integration); +}; + +const onConnectCursorClick = () => { + if (window.analytics) { + window.analytics.track('Clicked', { + app: 'docs', + button_text: 'Connect to Cursor', + element: 'llm-buttons.connectCursor', + }); + } + + openMcpIntegration('cursor'); +}; + +const onConnectVsCodeClick = () => { + if (window.analytics) { + window.analytics.track('Clicked', { + app: 'docs', + button_text: 'Connect to VS Code', + element: 'llm-buttons.connectVsCode', + }); + } + + openMcpIntegration('vscode'); +}; + const onViewAsMarkdownClick = () => { if (window.analytics) { window.analytics.track('Clicked', { @@ -257,6 +374,15 @@ export default function LLMButtons({ isApiReferencePage = false }) { case 'viewAsMarkdown': onViewAsMarkdownClick(); break; + case 'copyMcpServer': + onCopyMcpServerClick(); + break; + case 'connectCursor': + onConnectCursorClick(); + break; + case 'connectVsCode': + onConnectVsCodeClick(); + break; case 'openInChatGPT': onOpenInChatGPTClick(); break; @@ -277,9 +403,9 @@ export default function LLMButtons({ isApiReferencePage = false }) { [styles.llmMenuApiReferencePage]: isApiReferencePage, })} onMenuOpen={(isOpen) => chevronIconRef.current?.classList.toggle( - styles.chevronIconOpen, - isOpen, - ) + styles.chevronIconOpen, + isOpen, + ) } components={{ MenuBase: (props) => ( diff --git a/package-lock.json b/package-lock.json index f5f8a1943c..0a4d24bb00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "apify-docs-theme" ], "dependencies": { - "@apify/ui-icons": "^1.19.0", + "@apify/ui-icons": "^1.25.0", "@apify/ui-library": "^1.97.2", "@docusaurus/core": "^3.8.1", "@docusaurus/faster": "^3.8.1", @@ -750,10 +750,11 @@ "dev": true }, "node_modules/@apify/ui-icons": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@apify/ui-icons/-/ui-icons-1.24.1.tgz", - "integrity": "sha512-lnf3D189VjFFAe2/7a2zm04a7NKm6Dd5Fk6oHncLVDkEVph5R0ImvD4NPY08YzvrfZ7s5ewY0WPOOF3rFoMzhw==", + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@apify/ui-icons/-/ui-icons-1.25.0.tgz", + "integrity": "sha512-kD5ggDePMVz8H7wx5NSuwcP3E/mAy7oBN4ivC9Tuk9S20+LNy0jea7IPEnjLg16rjjfCPynZ2w/2CO/k5aJN0w==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "clsx": "^2.0.0" }, diff --git a/package.json b/package.json index 771cbc9122..ea0ab83cdf 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ }, "dependencies": { "@apify/ui-library": "^1.97.2", - "@apify/ui-icons": "^1.19.0", + "@apify/ui-icons": "^1.25.0", "@docusaurus/core": "^3.8.1", "@docusaurus/faster": "^3.8.1", "@docusaurus/plugin-client-redirects": "^3.8.1",