@redocly/theme 0.59.0-next.10 → 0.59.0-next.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Buttons/ConnectMCPButton.d.ts +8 -0
- package/lib/components/Buttons/ConnectMCPButton.js +145 -0
- package/lib/components/Buttons/variables.d.ts +1 -0
- package/lib/components/Buttons/variables.js +41 -1
- package/lib/components/PageActions/PageActions.js +4 -1
- package/lib/components/PageActions/variables.js +2 -0
- package/lib/components/Segmented/Segmented.d.ts +1 -8
- package/lib/components/Segmented/Segmented.js +3 -1
- package/lib/core/constants/index.d.ts +1 -0
- package/lib/core/constants/index.js +1 -0
- package/lib/core/constants/mcp.d.ts +1 -0
- package/lib/core/constants/mcp.js +5 -0
- package/lib/core/hooks/index.d.ts +2 -0
- package/lib/core/hooks/index.js +2 -0
- package/lib/core/hooks/use-connect-mcp-button.d.ts +13 -0
- package/lib/core/hooks/use-connect-mcp-button.js +50 -0
- package/lib/core/hooks/use-mcp-config.d.ts +9 -0
- package/lib/core/hooks/use-mcp-config.js +27 -0
- package/lib/core/hooks/use-page-actions.d.ts +1 -1
- package/lib/core/hooks/use-page-actions.js +98 -95
- package/lib/core/openapi/index.d.ts +1 -0
- package/lib/core/styles/global.js +1 -0
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/index.js +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/mcp.d.ts +6 -0
- package/lib/core/types/mcp.js +3 -0
- package/lib/core/types/segmented.d.ts +12 -0
- package/lib/core/types/segmented.js +3 -0
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/core/utils/mcp.d.ts +2 -0
- package/lib/core/utils/mcp.js +31 -0
- package/lib/icons/ConnectIcon/ConnectIcon.d.ts +9 -0
- package/lib/icons/ConnectIcon/ConnectIcon.js +17 -0
- package/lib/icons/VSCodeIcon/VSCodeIcon.d.ts +9 -0
- package/lib/icons/VSCodeIcon/VSCodeIcon.js +17 -0
- package/lib/markdoc/components/ConnectMCP/ConnectMCP.d.ts +8 -0
- package/lib/markdoc/components/ConnectMCP/ConnectMCP.js +19 -0
- package/lib/markdoc/components/default.d.ts +1 -0
- package/lib/markdoc/components/default.js +1 -0
- package/lib/markdoc/default.d.ts +6 -0
- package/lib/markdoc/default.js +2 -0
- package/lib/markdoc/tags/connect-mcp.d.ts +2 -0
- package/lib/markdoc/tags/connect-mcp.js +27 -0
- package/package.json +3 -3
- package/src/components/Buttons/ConnectMCPButton.tsx +180 -0
- package/src/components/Buttons/variables.ts +41 -0
- package/src/components/PageActions/PageActions.tsx +5 -1
- package/src/components/PageActions/variables.ts +2 -0
- package/src/components/Segmented/Segmented.tsx +15 -20
- package/src/core/constants/index.ts +1 -0
- package/src/core/constants/mcp.ts +1 -0
- package/src/core/hooks/index.ts +2 -0
- package/src/core/hooks/use-connect-mcp-button.ts +79 -0
- package/src/core/hooks/use-mcp-config.ts +43 -0
- package/src/core/hooks/use-page-actions.ts +148 -126
- package/src/core/openapi/index.ts +1 -0
- package/src/core/styles/global.ts +2 -1
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +9 -0
- package/src/core/types/mcp.ts +8 -0
- package/src/core/types/segmented.ts +14 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/mcp.ts +34 -0
- package/src/icons/ConnectIcon/ConnectIcon.tsx +27 -0
- package/src/icons/VSCodeIcon/VSCodeIcon.tsx +29 -0
- package/src/markdoc/components/ConnectMCP/ConnectMCP.tsx +28 -0
- package/src/markdoc/components/default.ts +1 -0
- package/src/markdoc/default.ts +2 -0
- package/src/markdoc/tags/connect-mcp.ts +25 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { JSX } from 'react';
|
|
5
|
+
import type { MCPOption } from '@redocly/theme/core/types';
|
|
6
|
+
|
|
7
|
+
import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown';
|
|
8
|
+
import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
|
|
9
|
+
import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
|
|
10
|
+
import { Button } from '@redocly/theme/components/Button/Button';
|
|
11
|
+
import { CursorIcon } from '@redocly/theme/icons/CursorIcon/CursorIcon';
|
|
12
|
+
import { VSCodeIcon } from '@redocly/theme/icons/VSCodeIcon/VSCodeIcon';
|
|
13
|
+
import { CopyIcon } from '@redocly/theme/icons/CopyIcon/CopyIcon';
|
|
14
|
+
import { CheckmarkFilledIcon } from '@redocly/theme/icons/CheckmarkFilledIcon/CheckmarkFilledIcon';
|
|
15
|
+
import { ConnectIcon } from '@redocly/theme/icons/ConnectIcon/ConnectIcon';
|
|
16
|
+
import { useConnectMCPButton } from '@redocly/theme/core/hooks/use-connect-mcp-button';
|
|
17
|
+
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
18
|
+
|
|
19
|
+
type TriggerButtonProps = {
|
|
20
|
+
text: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const TriggerButton = React.memo(({ text }: TriggerButtonProps): JSX.Element => {
|
|
24
|
+
return (
|
|
25
|
+
<StyledButton variant="secondary" icon={<ConnectIcon />}>
|
|
26
|
+
{text}
|
|
27
|
+
</StyledButton>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
type MenuOption = {
|
|
32
|
+
key: MCPOption;
|
|
33
|
+
icon: React.ComponentType;
|
|
34
|
+
titleTranslationKey: string;
|
|
35
|
+
titleDefault: string;
|
|
36
|
+
descriptionTranslationKey: string;
|
|
37
|
+
descriptionDefault: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const MENU_OPTIONS: MenuOption[] = [
|
|
41
|
+
{
|
|
42
|
+
key: 'cursor',
|
|
43
|
+
icon: CursorIcon,
|
|
44
|
+
titleTranslationKey: 'page.actions.connectMcp.cursor',
|
|
45
|
+
titleDefault: 'Connect to Cursor',
|
|
46
|
+
descriptionTranslationKey: 'page.actions.connectMcp.cursorDescription',
|
|
47
|
+
descriptionDefault: 'Install MCP server on Cursor',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: 'vscode',
|
|
51
|
+
icon: VSCodeIcon,
|
|
52
|
+
titleTranslationKey: 'page.actions.connectMcp.vscode',
|
|
53
|
+
titleDefault: 'Connect to VS Code',
|
|
54
|
+
descriptionTranslationKey: 'page.actions.connectMcp.vscodeDescription',
|
|
55
|
+
descriptionDefault: 'Install MCP server on VS Code',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: 'copy',
|
|
59
|
+
icon: CopyIcon,
|
|
60
|
+
titleTranslationKey: 'page.actions.connectMcp.copyConfig',
|
|
61
|
+
titleDefault: 'Copy MCP Configuration',
|
|
62
|
+
descriptionTranslationKey: 'page.actions.connectMcp.copyConfigDescription',
|
|
63
|
+
descriptionDefault: 'Copy MCP JSON Configuration',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export type ConnectMCPButtonProps = {
|
|
68
|
+
placement?: 'top' | 'bottom';
|
|
69
|
+
alignment?: 'start' | 'end';
|
|
70
|
+
options?: MCPOption[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export function ConnectMCPButton({
|
|
74
|
+
placement = 'bottom',
|
|
75
|
+
alignment = 'end',
|
|
76
|
+
options = ['cursor', 'vscode', 'copy'],
|
|
77
|
+
}: ConnectMCPButtonProps): JSX.Element {
|
|
78
|
+
const { useTranslate } = useThemeHooks();
|
|
79
|
+
const { translate } = useTranslate();
|
|
80
|
+
|
|
81
|
+
const { isCopied, triggerButtonText, visibleOptions, handleAction } = useConnectMCPButton({
|
|
82
|
+
options,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const menuOptions = useMemo(
|
|
86
|
+
() => MENU_OPTIONS.filter((option) => visibleOptions.includes(option.key)),
|
|
87
|
+
[visibleOptions],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<ConnectMCPButtonWrapper data-component-name="Buttons/ConnectMCPButton">
|
|
92
|
+
<Dropdown
|
|
93
|
+
trigger={<TriggerButton text={triggerButtonText} />}
|
|
94
|
+
triggerEvent="hover"
|
|
95
|
+
placement={placement}
|
|
96
|
+
alignment={alignment}
|
|
97
|
+
closeOnClick={false}
|
|
98
|
+
>
|
|
99
|
+
<DropdownMenu>
|
|
100
|
+
{menuOptions.map((option) => {
|
|
101
|
+
const Icon = option.icon;
|
|
102
|
+
const showCheckmark = option.key === 'copy' && isCopied;
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<DropdownMenuItem key={option.key} onAction={() => handleAction(option.key)}>
|
|
106
|
+
<MenuItemContent>
|
|
107
|
+
<MenuItemIcon>
|
|
108
|
+
{showCheckmark ? (
|
|
109
|
+
<CheckmarkFilledIcon color="var(--color-success-base)" />
|
|
110
|
+
) : (
|
|
111
|
+
<Icon />
|
|
112
|
+
)}
|
|
113
|
+
</MenuItemIcon>
|
|
114
|
+
<MenuItemText>
|
|
115
|
+
<MenuItemTitle>
|
|
116
|
+
{translate(option.titleTranslationKey, option.titleDefault)}
|
|
117
|
+
</MenuItemTitle>
|
|
118
|
+
<MenuItemDescription>
|
|
119
|
+
{translate(option.descriptionTranslationKey, option.descriptionDefault)}
|
|
120
|
+
</MenuItemDescription>
|
|
121
|
+
</MenuItemText>
|
|
122
|
+
</MenuItemContent>
|
|
123
|
+
</DropdownMenuItem>
|
|
124
|
+
);
|
|
125
|
+
})}
|
|
126
|
+
</DropdownMenu>
|
|
127
|
+
</Dropdown>
|
|
128
|
+
</ConnectMCPButtonWrapper>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const ConnectMCPButtonWrapper = styled.div`
|
|
133
|
+
display: inline-block;
|
|
134
|
+
position: relative;
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
const StyledButton = styled(Button)`
|
|
138
|
+
--button-gap: var(--connect-mcp-button-gap);
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
const MenuItemContent = styled.div`
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: var(--connect-mcp-button-menu-item-gap);
|
|
145
|
+
padding: var(--connect-mcp-button-menu-item-padding-block)
|
|
146
|
+
var(--connect-mcp-button-menu-item-padding-inline);
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
const MenuItemIcon = styled.div`
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
width: var(--connect-mcp-button-menu-item-icon-size);
|
|
154
|
+
height: var(--connect-mcp-button-menu-item-icon-size);
|
|
155
|
+
flex-shrink: 0;
|
|
156
|
+
border: var(--connect-mcp-button-menu-item-icon-border);
|
|
157
|
+
border-radius: var(--connect-mcp-button-menu-item-icon-border-radius);
|
|
158
|
+
color: var(--connect-mcp-button-menu-item-icon-color);
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
const MenuItemText = styled.div`
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-direction: column;
|
|
164
|
+
gap: var(--connect-mcp-button-menu-item-text-gap);
|
|
165
|
+
flex: 1;
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
const MenuItemTitle = styled.div`
|
|
169
|
+
font-size: var(--connect-mcp-button-menu-item-title-font-size);
|
|
170
|
+
font-weight: var(--connect-mcp-button-menu-item-title-font-weight);
|
|
171
|
+
line-height: var(--connect-mcp-button-menu-item-title-line-height);
|
|
172
|
+
color: var(--connect-mcp-button-menu-item-title-color);
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
const MenuItemDescription = styled.div`
|
|
176
|
+
font-size: var(--connect-mcp-button-menu-item-description-font-size);
|
|
177
|
+
font-weight: var(--connect-mcp-button-menu-item-description-font-weight);
|
|
178
|
+
line-height: var(--connect-mcp-button-menu-item-description-line-height);
|
|
179
|
+
color: var(--connect-mcp-button-menu-item-description-color);
|
|
180
|
+
`;
|
|
@@ -46,3 +46,44 @@ export const aiAssistantButton = css`
|
|
|
46
46
|
/* Transition */
|
|
47
47
|
--ai-assistant-button-transition: box-shadow 0.3s ease, transform 0.2s ease;
|
|
48
48
|
`;
|
|
49
|
+
|
|
50
|
+
export const connectMCPButton = css`
|
|
51
|
+
/**
|
|
52
|
+
* @tokens Connect MCP Button
|
|
53
|
+
* @presenter Color
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/* Button gap */
|
|
57
|
+
--connect-mcp-button-gap: var(--spacing-xs);
|
|
58
|
+
|
|
59
|
+
/* Menu item layout */
|
|
60
|
+
--connect-mcp-button-menu-item-gap: var(--spacing-sm);
|
|
61
|
+
--connect-mcp-button-menu-item-padding-block: var(--spacing-xxs);
|
|
62
|
+
--connect-mcp-button-menu-item-padding-inline: 0;
|
|
63
|
+
|
|
64
|
+
/* Menu item icon */
|
|
65
|
+
--connect-mcp-button-menu-item-icon-size: 32px;
|
|
66
|
+
--connect-mcp-button-menu-item-icon-border-color: var(--border-color-secondary);
|
|
67
|
+
--connect-mcp-button-menu-item-icon-border-style: solid;
|
|
68
|
+
--connect-mcp-button-menu-item-icon-border-width: 1px;
|
|
69
|
+
--connect-mcp-button-menu-item-icon-border: var(--connect-mcp-button-menu-item-icon-border-width)
|
|
70
|
+
var(--connect-mcp-button-menu-item-icon-border-style)
|
|
71
|
+
var(--connect-mcp-button-menu-item-icon-border-color);
|
|
72
|
+
--connect-mcp-button-menu-item-icon-border-radius: var(--border-radius);
|
|
73
|
+
--connect-mcp-button-menu-item-icon-color: var(--icon-color-secondary);
|
|
74
|
+
|
|
75
|
+
/* Menu item text */
|
|
76
|
+
--connect-mcp-button-menu-item-text-gap: var(--spacing-xxs);
|
|
77
|
+
|
|
78
|
+
/* Menu item title */
|
|
79
|
+
--connect-mcp-button-menu-item-title-font-size: var(--font-size-base);
|
|
80
|
+
--connect-mcp-button-menu-item-title-font-weight: var(--font-weight-regular);
|
|
81
|
+
--connect-mcp-button-menu-item-title-line-height: var(--line-height-base);
|
|
82
|
+
--connect-mcp-button-menu-item-title-color: var(--text-color-secondary);
|
|
83
|
+
|
|
84
|
+
/* Menu item description */
|
|
85
|
+
--connect-mcp-button-menu-item-description-font-size: var(--font-size-sm);
|
|
86
|
+
--connect-mcp-button-menu-item-description-font-weight: var(--font-weight-regular);
|
|
87
|
+
--connect-mcp-button-menu-item-description-line-height: var(--line-height-sm);
|
|
88
|
+
--connect-mcp-button-menu-item-description-color: var(--text-color-description);
|
|
89
|
+
`;
|
|
@@ -77,7 +77,7 @@ export function PageActions(props: PageActionProps): JSX.Element | null {
|
|
|
77
77
|
</Button>
|
|
78
78
|
{actions.length > 1 ? (
|
|
79
79
|
<Dropdown withArrow trigger={<Button />} placement="bottom" alignment="end">
|
|
80
|
-
<
|
|
80
|
+
<StyledDropdownMenu items={menuItems} />
|
|
81
81
|
</Dropdown>
|
|
82
82
|
) : null}
|
|
83
83
|
</ButtonGroup>
|
|
@@ -110,3 +110,7 @@ const LinkMenuItem = styled(Link)`
|
|
|
110
110
|
text-decoration: none;
|
|
111
111
|
--link-decoration-hover: none;
|
|
112
112
|
`;
|
|
113
|
+
|
|
114
|
+
const StyledDropdownMenu = styled(DropdownMenu)`
|
|
115
|
+
--dropdown-menu-max-height: var(--page-actions-dropdown-max-height);
|
|
116
|
+
`;
|
|
@@ -7,6 +7,8 @@ export const pageActions = css`
|
|
|
7
7
|
--page-actions-button-text-color: var(--text-color-secondary);
|
|
8
8
|
--page-actions-button-padding: 5px 14px 5px var(--spacing-sm);
|
|
9
9
|
|
|
10
|
+
--page-actions-dropdown-max-height: fit-content;
|
|
11
|
+
|
|
10
12
|
--page-actions-menu-item-padding: 3px 0;
|
|
11
13
|
--page-actions-menu-item-gap: var(--spacing-xs);
|
|
12
14
|
--page-actions-menu-item-icon-color: var(--icon-color-secondary);
|
|
@@ -2,18 +2,10 @@ import React, { forwardRef } from 'react';
|
|
|
2
2
|
import styled, { css } from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { ForwardedRef, ReactElement } from 'react';
|
|
5
|
-
import type {
|
|
5
|
+
import type { SegmentedProps } from '@redocly/theme/core/types/segmented';
|
|
6
6
|
|
|
7
7
|
import { typedMemo } from '@redocly/theme/core/hoc/typedMemo';
|
|
8
8
|
|
|
9
|
-
export type SegmentedProps<T> = {
|
|
10
|
-
options: SelectOption<T>[];
|
|
11
|
-
value: T;
|
|
12
|
-
onChange: ({ label, value }: SelectOption<T>) => void;
|
|
13
|
-
className?: string;
|
|
14
|
-
size?: 'regular' | 'small';
|
|
15
|
-
};
|
|
16
|
-
|
|
17
9
|
function SegmentedComponent<T>(
|
|
18
10
|
{ options, onChange, value, className = '', size = 'regular' }: SegmentedProps<T>,
|
|
19
11
|
ref?: ForwardedRef<HTMLDivElement>,
|
|
@@ -25,17 +17,20 @@ function SegmentedComponent<T>(
|
|
|
25
17
|
className={`tag-grey ${size} ${className}`}
|
|
26
18
|
role="tablist"
|
|
27
19
|
>
|
|
28
|
-
{options.map((opt) => (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
{options.map((opt, index) => (
|
|
21
|
+
<React.Fragment key={index}>
|
|
22
|
+
{opt.divider}
|
|
23
|
+
<SegmentedItem
|
|
24
|
+
key={opt.label}
|
|
25
|
+
role="tab"
|
|
26
|
+
title={opt.label}
|
|
27
|
+
onClick={() => onChange(opt)}
|
|
28
|
+
$isActive={value == opt.value}
|
|
29
|
+
$size={size}
|
|
30
|
+
>
|
|
31
|
+
{opt.element || opt.label}
|
|
32
|
+
</SegmentedItem>
|
|
33
|
+
</React.Fragment>
|
|
39
34
|
))}
|
|
40
35
|
</SegmentedGroup>
|
|
41
36
|
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_MCP_SERVER_NAME = 'mcp-server';
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import debounce from 'lodash.debounce';
|
|
3
|
+
|
|
4
|
+
import type { MCPOption } from '../types';
|
|
5
|
+
|
|
6
|
+
import { useThemeHooks } from './use-theme-hooks';
|
|
7
|
+
import { useMCPConfig } from './use-mcp-config';
|
|
8
|
+
import { ClipboardService } from '../utils/clipboard-service';
|
|
9
|
+
|
|
10
|
+
export type McpButtonOptions = {
|
|
11
|
+
options?: MCPOption[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type McpButtonSettings = {
|
|
15
|
+
isCopied: boolean;
|
|
16
|
+
cursorUrl: string;
|
|
17
|
+
vscodeUrl: string;
|
|
18
|
+
triggerButtonText: string;
|
|
19
|
+
visibleOptions: MCPOption[];
|
|
20
|
+
handleAction: (action: MCPOption) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const COPIED_RESET_TIMEOUT = 1000;
|
|
24
|
+
|
|
25
|
+
export function useConnectMCPButton({
|
|
26
|
+
options = ['cursor', 'vscode', 'copy'],
|
|
27
|
+
}: McpButtonOptions = {}): McpButtonSettings {
|
|
28
|
+
const { useTranslate } = useThemeHooks();
|
|
29
|
+
const { translate } = useTranslate();
|
|
30
|
+
const { serverName, serverUrl, cursorUrl, vscodeUrl } = useMCPConfig();
|
|
31
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
32
|
+
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
const resetCopied = useCallback(
|
|
35
|
+
debounce(() => setIsCopied(false), COPIED_RESET_TIMEOUT),
|
|
36
|
+
[],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const handleAction = useCallback(
|
|
40
|
+
(action: MCPOption) => {
|
|
41
|
+
if (action === 'copy') {
|
|
42
|
+
const config = {
|
|
43
|
+
[serverName]: {
|
|
44
|
+
url: serverUrl,
|
|
45
|
+
description: 'MCP Server',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
ClipboardService.copyCustom(JSON.stringify(config, null, 2));
|
|
49
|
+
setIsCopied(true);
|
|
50
|
+
resetCopied();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const urlMap: Record<Exclude<MCPOption, 'copy'>, string> = {
|
|
55
|
+
cursor: cursorUrl,
|
|
56
|
+
vscode: vscodeUrl,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
window.open(urlMap[action], '_blank');
|
|
60
|
+
},
|
|
61
|
+
[cursorUrl, vscodeUrl, serverUrl, serverName, resetCopied],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const triggerButtonText = useMemo(
|
|
65
|
+
() => translate('page.actions.connectMcp', 'Connect MCP'),
|
|
66
|
+
[translate],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const visibleOptions = useMemo(() => options.filter(Boolean), [options]);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
isCopied,
|
|
73
|
+
cursorUrl,
|
|
74
|
+
vscodeUrl,
|
|
75
|
+
triggerButtonText,
|
|
76
|
+
visibleOptions,
|
|
77
|
+
handleAction,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useThemeConfig } from './use-theme-config';
|
|
4
|
+
import { IS_BROWSER } from '../utils/dom';
|
|
5
|
+
import { DEFAULT_MCP_SERVER_NAME } from '../constants';
|
|
6
|
+
import { generateMCPDeepLink } from '../utils/mcp';
|
|
7
|
+
|
|
8
|
+
export type McpConfig = {
|
|
9
|
+
serverName: string;
|
|
10
|
+
origin: string;
|
|
11
|
+
serverUrl: string;
|
|
12
|
+
cursorUrl: string;
|
|
13
|
+
vscodeUrl: string;
|
|
14
|
+
isMcpDisabled: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function useMCPConfig(): McpConfig {
|
|
18
|
+
const themeConfig = useThemeConfig();
|
|
19
|
+
|
|
20
|
+
const origin = IS_BROWSER ? window.location.origin : (globalThis as any)['SSR_HOSTNAME'];
|
|
21
|
+
const serverName = themeConfig.mcp?.docs?.name || DEFAULT_MCP_SERVER_NAME;
|
|
22
|
+
const serverUrl = `${origin}/mcp`;
|
|
23
|
+
const isMcpDisabled = themeConfig.mcp?.hide || themeConfig.mcp?.docs?.hide || false;
|
|
24
|
+
|
|
25
|
+
const cursorUrl = useMemo(
|
|
26
|
+
() => generateMCPDeepLink('cursor', { serverName, url: serverUrl }),
|
|
27
|
+
[serverName, serverUrl],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const vscodeUrl = useMemo(
|
|
31
|
+
() => generateMCPDeepLink('vscode', { serverName, url: serverUrl }),
|
|
32
|
+
[serverName, serverUrl],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
serverName,
|
|
37
|
+
origin,
|
|
38
|
+
serverUrl,
|
|
39
|
+
cursorUrl,
|
|
40
|
+
vscodeUrl,
|
|
41
|
+
isMcpDisabled,
|
|
42
|
+
};
|
|
43
|
+
}
|