chore(settings): move mcp config to the MCP section and rework form - #400
Conversation
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
There was a problem hiding this comment.
Pull request overview
This PR refactors the MCP (Model Context Protocol) configuration interface by moving MCP settings from the Advanced Settings section to a dedicated MCP section and replacing the raw JSON textarea with a structured form for STDIO server configuration.
Changes:
- Moved
mcp_stdio_serversandmcp_prepare_scriptfields from AdvancedSettings to MCP section in the backend metadata - Built a dynamic form UI for MCP STDIO servers with fields for name, command, args, and environment variables
- Added visual separation between MCP STDIO servers and MCP HTTP servers with updated styling
- Fixed a critical bug in AgentForm.jsx where
handleInputChangewas incorrectly accessinge.target.name.target - Enhanced Docker configuration with explicit TCP daemon settings
- Added SELinux volume mount compatibility in Makefile
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| webui/react-ui/src/components/agent-form-sections/MCPServersSection.jsx | Adds structured form for MCP STDIO servers with JSON parsing/building functions and CRUD operations for servers, args, and env vars |
| webui/react-ui/src/components/AgentForm.jsx | Fixes critical bug in handleInputChange and passes additional props to MCPServersSection |
| webui/react-ui/src/App.css | Adds CSS styling to visually separate MCP STDIO and HTTP server blocks |
| docker-compose.yaml | Makes Docker daemon TCP configuration explicit with command and expose directives |
| core/state/config.go | Moves MCP fields to MCP section and adds Name field support in JSON marshal/unmarshal |
| core/agent/mcp.go | Adds Name field to MCPSTDIOServer struct |
| Makefile | Adds :z flag to Docker volume mount for SELinux compatibility |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const val = env[oldKey]; | ||
| delete env[oldKey]; | ||
| env[newKey || oldKey] = val; |
There was a problem hiding this comment.
The updateEnvKey function has a potential issue when renaming environment variable keys. If newKey is empty or already exists, it will overwrite or create an unexpected key. Consider validating that newKey is non-empty and doesn't already exist in the env object before performing the rename operation.
| const val = env[oldKey]; | |
| delete env[oldKey]; | |
| env[newKey || oldKey] = val; | |
| if (!Object.prototype.hasOwnProperty.call(env, oldKey)) { | |
| // Nothing to rename if the old key does not exist | |
| return; | |
| } | |
| // Validate the new key: it must be non-empty and must not collide with an existing key | |
| if (!newKey || newKey === oldKey || Object.prototype.hasOwnProperty.call(env, newKey)) { | |
| // Invalid or colliding new key; do not modify env | |
| return; | |
| } | |
| const val = env[oldKey]; | |
| delete env[oldKey]; | |
| env[newKey] = val; |
| const addEnv = (serverIndex, key = '', value = '') => { | ||
| const server = stdioList[serverIndex]; | ||
| if (!server) return; | ||
| const env = { ...(server.env || {}), [key || `key_${Date.now()}`]: value }; |
There was a problem hiding this comment.
The addEnv function generates a timestamp-based key (key_${Date.now()}) when the key parameter is empty. This could lead to key collisions if multiple environment variables are added rapidly (within the same millisecond). Consider using a more robust unique identifier generation or checking for existing keys to ensure uniqueness.
| const addEnv = (serverIndex, key = '', value = '') => { | |
| const server = stdioList[serverIndex]; | |
| if (!server) return; | |
| const env = { ...(server.env || {}), [key || `key_${Date.now()}`]: value }; | |
| const generateUniqueEnvKey = (existingEnv) => { | |
| const base = 'key_'; | |
| let suffix = 0; | |
| let candidate; | |
| do { | |
| candidate = `${base}${Date.now()}_${suffix++}`; | |
| } while ( | |
| existingEnv && | |
| Object.prototype.hasOwnProperty.call(existingEnv, candidate) | |
| ); | |
| return candidate; | |
| }; | |
| const addEnv = (serverIndex, key = '', value = '') => { | |
| const server = stdioList[serverIndex]; | |
| if (!server) return; | |
| const existingEnv = server.env || {}; | |
| const trimmedKey = key.trim(); | |
| const keyToUse = trimmedKey ? trimmedKey : generateUniqueEnvKey(existingEnv); | |
| const env = { ...existingEnv, [keyToUse]: value }; |
| let key = (item.name && item.name.trim()) ? item.name.trim() : `server${index}`; | ||
| while (usedKeys.has(key)) { | ||
| key = `${key}_${index}`; |
There was a problem hiding this comment.
The key collision resolution in buildStdioJson has a critical bug. When a collision is detected on line 27, the while loop on line 27 keeps appending the same _${index} suffix repeatedly, which will result in an infinite loop if there's a collision. For example, if "myserver" exists and you try to add another "myserver" at index 1, it will try "myserver_1", then "myserver_1_1", then "myserver_1_1_1" indefinitely. Instead, use a counter variable that increments each iteration to ensure uniqueness.
| let key = (item.name && item.name.trim()) ? item.name.trim() : `server${index}`; | |
| while (usedKeys.has(key)) { | |
| key = `${key}_${index}`; | |
| const baseKey = (item.name && item.name.trim()) ? item.name.trim() : `server${index}`; | |
| let key = baseKey; | |
| let suffix = 1; | |
| while (usedKeys.has(key)) { | |
| key = `${baseKey}_${suffix}`; | |
| suffix += 1; |
|
|
||
| // Handle MCP configuration field value changes (FormField passes the event) | ||
| const handleMCPFieldChange = (e) => { | ||
| const { name, value, type, checked } = e.target; |
There was a problem hiding this comment.
Unused variable type.
| const { name, value, type, checked } = e.target; | |
| const { name, value, checked } = e.target; |
this PR moves the MCP settings in advanced settings to the MCP, and builds a form for the MCP STDIO configuration rather than just letting the user to provide a JSON.