Skip to content

Commit 02d5a95

Browse files
tylerpayneTyler PaynehusseinmozannarCopilot
authored
feat: implement per-session MCP server selection with persistence (#372)
Co-authored-by: Tyler Payne <[email protected]> Co-authored-by: Hussein Mozannar <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent bb37954 commit 02d5a95

File tree

9 files changed

+426
-64
lines changed

9 files changed

+426
-64
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* McpServerSelector specific styles */
2+
.mcp-selector {
3+
cursor: pointer;
4+
align-items: center;
5+
}
6+
7+
/* Make the root selector transparent */
8+
.mcp-selector .ant-select-selector {
9+
background-color: transparent !important;
10+
border-color: transparent !important;
11+
box-shadow: none !important;
12+
align-items: center;
13+
}
14+
15+
.mcp-selector .ant-select-selection-wrap {
16+
align-items: center;
17+
padding: 5px;
18+
}
19+
20+
/* Style the prefix with semi-transparent border */
21+
.mcp-selector-prefix {
22+
padding: 5px;
23+
border-style: solid;
24+
border-width: 1px;
25+
border-radius: 5px;
26+
border-color: transparent !important;
27+
transition: border-color 0.2s ease;
28+
display: inline-flex;
29+
}
30+
31+
/* Make border fully opaque on hover of the entire selector */
32+
.mcp-selector-prefix:hover:not(.disabled) {
33+
border-color: var(--color-text-primary) !important;
34+
transition: border-color 0.2s ease !important;
35+
}
36+
37+
/* Style the selected tags with magenta-900 */
38+
.mcp-selector .ant-select-selection-item {
39+
background-color: var(--color-magenta-900) !important;
40+
transition: background-color 0.2s ease !important;
41+
color: white !important;
42+
}
43+
44+
.mcp-selector:not(.ant-select-disabled) .ant-select-selection-item:hover {
45+
background-color: var(--color-magenta-800) !important;
46+
}
47+
48+
/* Style the remove icon on tags */
49+
.mcp-selector .ant-select-selection-item-remove {
50+
color: white !important;
51+
}
52+
53+
/* Style the dropdown selected items */
54+
.mcp-selector-dropdown .ant-select-item-option {
55+
background-color: transparent !important;
56+
transition: background-color 0.2s ease !important;
57+
}
58+
59+
.mcp-selector-dropdown .ant-select-item-option:hover {
60+
background-color: color-mix(in srgb, var(--color-text-primary) 10%, transparent) !important;
61+
}
62+
63+
.mcp-selector-dropdown .ant-select-item-option-selected {
64+
background-color: var(--color-magenta-900) !important;
65+
transition: background-color 0.2s ease !important;
66+
color: white !important;
67+
}
68+
69+
.mcp-selector-dropdown .ant-select-item-option-selected:hover {
70+
background-color: var(--color-magenta-800) !important;
71+
}
72+
73+
/* Make the checkmark icon white */
74+
.mcp-selector-dropdown .anticon-check {
75+
color: white !important;
76+
margin-left: 15px;
77+
}
78+
79+
/* Style the Add MCP Server button */
80+
.mcp-selector-dropdown .mcp-selector-add-mcp-server-button {
81+
color: var(--color-text-primary);
82+
justify-content: flex-start;
83+
background-color: transparent !important;
84+
}
85+
86+
.mcp-selector-dropdown .mcp-selector-add-mcp-server-button:hover {
87+
background-color: color-mix(in srgb, var(--color-text-primary) 10%, transparent) !important;
88+
}
89+
90+
/* Override Ant Design's inline min-width calculation */
91+
.mcp-selector-dropdown.ant-select-dropdown {
92+
min-width: fit-content !important;
93+
width: auto !important;
94+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import React, { useMemo, useCallback } from "react";
2+
import { Select, Typography, type SelectProps, Divider, Button, Flex, Tooltip } from "antd";
3+
import { MCPServerInfo } from "../McpServersConfig/types"
4+
import {
5+
PlusIcon, WrenchScrewdriverIcon
6+
} from "@heroicons/react/24/outline";
7+
import "./McpServerSelector.css";
8+
9+
interface McpServerSelectorProps {
10+
servers: MCPServerInfo[]
11+
onAddMcpServer: () => void;
12+
runStatus?: string
13+
value: string[]
14+
onChange: (value: string[]) => void
15+
}
16+
17+
type OptionsProps = SelectProps["options"]
18+
19+
export const McpServerSelector: React.FC<McpServerSelectorProps> = ({ servers, onAddMcpServer, runStatus, value, onChange }) => {
20+
const disabled = runStatus !== "created"
21+
let mcpSelectorPrefixClassNames = "mcp-selector-prefix"
22+
if (disabled) {
23+
mcpSelectorPrefixClassNames += " disabled"
24+
}
25+
26+
const handleChange = (newValue: string[]) => {
27+
onChange(newValue)
28+
}
29+
30+
const handleAddServer = useCallback(() => {
31+
onAddMcpServer();
32+
}, [onAddMcpServer])
33+
34+
const options: OptionsProps = useMemo(() => {
35+
return servers.map((server) => ({
36+
value: server.agentName,
37+
label: server.agentName
38+
}))
39+
}, [servers])
40+
41+
42+
const selectComponent = (
43+
<Select
44+
mode="multiple"
45+
disabled={disabled}
46+
showSearch={false}
47+
value={value}
48+
onChange={handleChange}
49+
options={options}
50+
className="mcp-selector"
51+
prefix={<Flex gap={4} className={mcpSelectorPrefixClassNames}><WrenchScrewdriverIcon width={20} /><Typography>Tools</Typography></Flex>}
52+
suffixIcon={null}
53+
popupMatchSelectWidth={false}
54+
popupRender={(menu) => (
55+
<>
56+
{menu}
57+
<Divider style={{ margin: '8px 0' }} />
58+
<Button
59+
type="text"
60+
onClick={handleAddServer}
61+
style={{ width: '100%', padding: '0px 12px', marginBottom: "6px" }}
62+
className="mcp-selector-add-mcp-server-button"
63+
>
64+
<Flex gap={4}>
65+
<PlusIcon width={16} />
66+
<span>Add MCP Server</span>
67+
</Flex>
68+
</Button>
69+
</>
70+
)}
71+
classNames={{
72+
popup: {
73+
root: "mcp-selector-dropdown"
74+
}
75+
}}
76+
/>
77+
)
78+
79+
return disabled ? (
80+
<Tooltip title="You cannot modify tools after starting a session">
81+
{selectComponent}
82+
</Tooltip>
83+
) : (
84+
selectComponent
85+
)
86+
}

frontend/src/components/features/McpServersConfig/McpServersList.tsx

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ const AddMcpServerCard: React.FC<{ onClick: () => void }> = ({ onClick }) => (
2727
</Card>
2828
);
2929

30+
31+
// Helper function: Extract MCP servers from agent configurations
32+
export const extractMcpServers = (agents: MCPAgentConfig[]): MCPServerInfo[] => {
33+
const serversList: MCPServerInfo[] = [];
34+
35+
agents.forEach((agent) => {
36+
agent.mcp_servers.forEach((server: NamedMCPServerConfig) => {
37+
serversList.push({
38+
agentName: agent.name,
39+
agentDescription: agent.description,
40+
serverName: server.server_name,
41+
serverType: server.server_params.type,
42+
serverParams: server.server_params,
43+
connectionStatus: server.connection_status ? {
44+
isConnected: server.connection_status.is_connected,
45+
toolsFound: server.connection_status.tools_found,
46+
} : undefined,
47+
});
48+
});
49+
});
50+
51+
return serversList;
52+
};
53+
3054
const McpServersList: React.FC = () => {
3155
const { user } = React.useContext(appContext);
3256
const [isLoading, setIsLoading] = useState(true);
@@ -36,29 +60,6 @@ const McpServersList: React.FC = () => {
3660
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
3761
const [editingServer, setEditingServer] = useState<MCPServerInfo | undefined>();
3862

39-
// Helper function: Extract MCP servers from agent configurations
40-
const extractMcpServers = (agents: MCPAgentConfig[]): MCPServerInfo[] => {
41-
const serversList: MCPServerInfo[] = [];
42-
43-
agents.forEach((agent) => {
44-
agent.mcp_servers.forEach((server: NamedMCPServerConfig) => {
45-
serversList.push({
46-
agentName: agent.name,
47-
agentDescription: agent.description,
48-
serverName: server.server_name,
49-
serverType: server.server_params.type,
50-
serverParams: server.server_params,
51-
connectionStatus: server.connection_status ? {
52-
isConnected: server.connection_status.is_connected,
53-
toolsFound: server.connection_status.tools_found,
54-
} : undefined,
55-
});
56-
});
57-
});
58-
59-
return serversList;
60-
};
61-
6263
useEffect(() => {
6364
const fetchMCPServers = async () => {
6465
if (!user?.email) {

frontend/src/components/types/datamodel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface Message extends DBModel {
9191
export interface Session extends DBModel {
9292
name: string;
9393
team_id?: number;
94+
selected_mcp_configs?: any[];
9495
}
9596

9697
export interface SessionRuns {

0 commit comments

Comments
 (0)