Skip to content

Commit fc84ac2

Browse files
authored
feat(docs): Add Cursor & VSCode integration (#2155)
closes: #2079 Add a Cursor & VSCode integration to LLM Dropdown. Opening the Cursor and VSCode with predefined MCP configuration. Icons needs to be merged first: apify/apify-core#24852 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds LLM dropdown options to copy Apify MCP config and connect via Cursor/VS Code deeplinks with web configurator fallback. > > - **LLM dropdown (`apify-docs-theme/src/theme/LLMButtons/index.jsx`)**: > - Add options: `copyMcpServer`, `connectCursor`, `connectVsCode` with corresponding icons and descriptions. > - Implement `MCP_CONFIG_JSON` and `onCopyMcpServerClick` to copy MCP config to clipboard. > - Implement deep links via `openMcpIntegration` for `cursor` and `vscode` (with `openApifyMcpConfigurator` fallback) and hook up `onConnectCursorClick`/`onConnectVsCodeClick`. > - Track analytics events for new actions. > - Minor tweak to chevron open-state toggle invocation. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1c287d9. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 9684a4f commit fc84ac2

3 files changed

Lines changed: 135 additions & 8 deletions

File tree

apify-docs-theme/src/theme/LLMButtons/index.jsx

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import {
77
CheckIcon,
88
ChevronDownIcon,
99
CopyIcon,
10+
CursorIcon,
1011
ExternalLinkIcon,
1112
LoaderIcon,
1213
MarkdownIcon,
14+
McpIcon,
1315
PerplexityIcon,
16+
VscodeIcon,
1417
} from '@apify/ui-icons';
1518
import { Menu, Text, theme } from '@apify/ui-library';
1619

@@ -31,6 +34,29 @@ const DROPDOWN_OPTIONS = [
3134
Icon: MarkdownIcon,
3235
value: 'viewAsMarkdown',
3336
},
37+
{
38+
label: 'Copy MCP server',
39+
description: 'Copy MCP Server URL to clipboard',
40+
showExternalIcon: false,
41+
Icon: McpIcon,
42+
value: 'copyMcpServer',
43+
},
44+
{
45+
label: 'Connect to Cursor',
46+
description: 'Install MCP Server on Cursor',
47+
showExternalIcon: true,
48+
// TODO: Replace with CursorIcon - we don't have one yet
49+
Icon: CursorIcon,
50+
value: 'connectCursor',
51+
},
52+
{
53+
label: 'Connect to VS Code',
54+
description: 'Install MCP server on VS Code',
55+
showExternalIcon: true,
56+
// TODO: Replace with VS Code Icon - we don't have one yet
57+
Icon: VscodeIcon,
58+
value: 'connectVsCode',
59+
},
3460
{
3561
label: 'Open in ChatGPT',
3662
description: 'Ask questions about this page',
@@ -54,6 +80,16 @@ const DROPDOWN_OPTIONS = [
5480
},
5581
];
5682

83+
const MCP_SERVER_URL = 'https://mcp.apify.com/?tools=docs';
84+
85+
const MCP_CONFIG_JSON = `{
86+
"mcpServers": {
87+
"apify": {
88+
"url": "${MCP_SERVER_URL}"
89+
}
90+
}
91+
}`;
92+
5793
const getPrompt = (currentUrl) => `Read from ${currentUrl} so I can ask questions about it.`;
5894
const getMarkdownUrl = (currentUrl) => {
5995
const url = new URL(currentUrl);
@@ -161,6 +197,87 @@ const onCopyAsMarkdownClick = async ({ setCopyingStatus }) => {
161197
}
162198
};
163199

200+
const onCopyMcpServerClick = async () => {
201+
if (window.analytics) {
202+
window.analytics.track('Clicked', {
203+
app: 'docs',
204+
button_text: 'Copy MCP server',
205+
element: 'llm-buttons.copyMcpServer',
206+
});
207+
}
208+
209+
try {
210+
await navigator.clipboard.writeText(MCP_CONFIG_JSON);
211+
} catch (error) {
212+
console.error('Failed to copy MCP configuration:', error);
213+
}
214+
};
215+
216+
const openApifyMcpConfigurator = (integration) => {
217+
try {
218+
window.open(`https://mcp.apify.com/?integration=${integration}`, '_blank');
219+
} catch (error) {
220+
console.error('Error opening fallback URL:', error);
221+
}
222+
};
223+
224+
const openMcpIntegration = async (integration) => {
225+
// Try to open the app directly using URL scheme
226+
let appUrl;
227+
if (integration === 'cursor') {
228+
// Cursor deeplink format:
229+
// cursor://anysphere.cursor-deeplink/mcp/install?name=$NAME&config=$BASE64_JSON
230+
const cursorConfig = {
231+
url: MCP_SERVER_URL,
232+
};
233+
const encodedConfig = btoa(JSON.stringify(cursorConfig));
234+
appUrl = `cursor://anysphere.cursor-deeplink/mcp/install?name=apify&config=${encodeURIComponent(encodedConfig)}`;
235+
} else if (integration === 'vscode') {
236+
// VS Code deeplink format: vscode:mcp/install?<url-encoded-json>
237+
const mcpConfig = {
238+
name: 'Apify',
239+
type: 'http',
240+
url: MCP_SERVER_URL,
241+
};
242+
const encodedConfig = encodeURIComponent(JSON.stringify(mcpConfig));
243+
appUrl = `vscode:mcp/install?${encodedConfig}`;
244+
}
245+
246+
if (appUrl) {
247+
const openedWindow = window.open(appUrl, '_blank');
248+
249+
if (openedWindow) {
250+
return;
251+
}
252+
}
253+
// Fallback to web configurator if appUrl doesn't exist or window.open failed
254+
openApifyMcpConfigurator(integration);
255+
};
256+
257+
const onConnectCursorClick = () => {
258+
if (window.analytics) {
259+
window.analytics.track('Clicked', {
260+
app: 'docs',
261+
button_text: 'Connect to Cursor',
262+
element: 'llm-buttons.connectCursor',
263+
});
264+
}
265+
266+
openMcpIntegration('cursor');
267+
};
268+
269+
const onConnectVsCodeClick = () => {
270+
if (window.analytics) {
271+
window.analytics.track('Clicked', {
272+
app: 'docs',
273+
button_text: 'Connect to VS Code',
274+
element: 'llm-buttons.connectVsCode',
275+
});
276+
}
277+
278+
openMcpIntegration('vscode');
279+
};
280+
164281
const onViewAsMarkdownClick = () => {
165282
if (window.analytics) {
166283
window.analytics.track('Clicked', {
@@ -257,6 +374,15 @@ export default function LLMButtons({ isApiReferencePage = false }) {
257374
case 'viewAsMarkdown':
258375
onViewAsMarkdownClick();
259376
break;
377+
case 'copyMcpServer':
378+
onCopyMcpServerClick();
379+
break;
380+
case 'connectCursor':
381+
onConnectCursorClick();
382+
break;
383+
case 'connectVsCode':
384+
onConnectVsCodeClick();
385+
break;
260386
case 'openInChatGPT':
261387
onOpenInChatGPTClick();
262388
break;
@@ -277,9 +403,9 @@ export default function LLMButtons({ isApiReferencePage = false }) {
277403
[styles.llmMenuApiReferencePage]: isApiReferencePage,
278404
})}
279405
onMenuOpen={(isOpen) => chevronIconRef.current?.classList.toggle(
280-
styles.chevronIconOpen,
281-
isOpen,
282-
)
406+
styles.chevronIconOpen,
407+
isOpen,
408+
)
283409
}
284410
components={{
285411
MenuBase: (props) => (

package-lock.json

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
},
6363
"dependencies": {
6464
"@apify/ui-library": "^1.97.2",
65-
"@apify/ui-icons": "^1.19.0",
65+
"@apify/ui-icons": "^1.25.0",
6666
"@docusaurus/core": "^3.8.1",
6767
"@docusaurus/faster": "^3.8.1",
6868
"@docusaurus/plugin-client-redirects": "^3.8.1",

0 commit comments

Comments
 (0)