Skip to content
Open
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
130 changes: 127 additions & 3 deletions apify-docs-theme/src/theme/LLMButtons/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ExternalLinkIcon,
LoaderIcon,
MarkdownIcon,
McpIcon,
PerplexityIcon,
} from '@apify/ui-icons';
import { Menu, Text, theme } from '@apify/ui-library';
Expand All @@ -31,6 +32,29 @@ const DROPDOWN_OPTIONS = [
Icon: MarkdownIcon,
value: 'viewAsMarkdown',
},
{
label: 'Copy MCP server',
description: 'Copy Apify MCP configuration',
showExternalIcon: false,
Icon: McpIcon,
value: 'copyMcpServer',
},
{
label: 'Connect to Cursor',
description: 'Open MCP configurator for Cursor',
showExternalIcon: true,
// TODO: Replace with CursorIcon - we don't have one yet
Icon: ExternalLinkIcon,
value: 'connectCursor',
},
{
label: 'Connect to VS Code',
description: 'Open MCP configurator for VS Code',
showExternalIcon: true,
// TODO: Replace with VS Code Icon - we don't have one yet
Icon: ExternalLinkIcon,
value: 'connectVsCode',
},
{
label: 'Open in ChatGPT',
description: 'Ask questions about this page',
Expand All @@ -54,6 +78,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 +195,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 +372,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 +401,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
Loading