Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 129 additions & 3 deletions apify-docs-theme/src/theme/LLMButtons/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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',
Expand All @@ -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);
Expand Down Expand Up @@ -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?<url-encoded-json>
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', {
Expand Down Expand Up @@ -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;
Expand All @@ -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) => (
Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading