Skip to content

studio: show MCP "Import config" on the add-server form#6030

Open
NilayYadav wants to merge 4 commits into
unslothai:mainfrom
NilayYadav:studio-mcp-import-button-fix
Open

studio: show MCP "Import config" on the add-server form#6030
NilayYadav wants to merge 4 commits into
unslothai:mainfrom
NilayYadav:studio-mcp-import-button-fix

Conversation

@NilayYadav
Copy link
Copy Markdown
Contributor

Summary

Follow-up to #5943, fixing the UX bug reported on #5936. The "Import config" button only rendered in the MCP server list view, but the composer opens the dialog straight into the add-server form (openToCreate={true}). So users land on a screen with no import option and assume the feature is missing, it only appeared after manually adding a server first.

This surfaces the import on the add-server form too, so it's visible the moment the dialog opens.

What's changed

  • chat-mcp-servers-dialog.tsx: moved the single hidden file input to the dialog root so both views share it, and added an "Import servers from a config file" banner + Import config button to the create form.

Testing

  • Opened MCP → "Add custom MCP" → the Import config button now shows immediately on the form (previously hidden until a server was added).
  • Imported a mcpServers.json from the form → all servers added; list updates.

@NilayYadav NilayYadav requested a review from danielhanchen as a code owner June 5, 2026 15:24
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ability to bulk-import MCP servers from standard JSON configuration files (such as those from Claude Desktop, Cursor, Cline, or VS Code). It adds backend utilities to parse and join stdio commands across different platforms, exposes a new /import endpoint that handles duplicates and per-entry errors gracefully, and updates the frontend UI to support file uploads. The review feedback highlights three valuable improvements: filtering out None values in environment variables to prevent them from being coerced into the string "None", dynamically referencing the correct configuration key (mcpServers vs servers) in error messages, and wrapping the frontend toast error descriptions in a container with whitespace-pre-line to preserve newline formatting.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +31 to +32
def _coerce_str_dict(value: dict) -> dict[str, str]:
return {str(k): str(v) for k, v in value.items()}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When parsing environment variables or headers, if a value is explicitly set to null in the JSON config, converting it to the string "None" via str(v) can lead to unexpected behavior (e.g., the subprocess receiving the literal string "None" as an environment variable or header value, which is truthy).

It is safer to defensively filter out keys with None values.

Suggested change
def _coerce_str_dict(value: dict) -> dict[str, str]:
return {str(k): str(v) for k, v in value.items()}
def _coerce_str_dict(value: dict) -> dict[str, str]:
return {str(k): str(v) for k, v in value.items() if v is not None}

Comment on lines +81 to +87
servers = config.get("mcpServers")
if servers is None:
servers = config.get("servers")
if servers is None:
return [], ["Config has no 'mcpServers' (or 'servers') object."]
if not isinstance(servers, dict):
return [], ["'mcpServers' must be an object mapping name -> server."]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the configuration uses the VS Code alias "servers" instead of "mcpServers", but the value is not a dictionary, the error message still hardcodes 'mcpServers'.

Dynamically referencing the key that was actually matched in the config provides clearer and more accurate error feedback to the user.

Suggested change
servers = config.get("mcpServers")
if servers is None:
servers = config.get("servers")
if servers is None:
return [], ["Config has no 'mcpServers' (or 'servers') object."]
if not isinstance(servers, dict):
return [], ["'mcpServers' must be an object mapping name -> server."]
servers_key = "mcpServers" if "mcpServers" in config else "servers"
servers = config.get(servers_key)
if servers is None:
return [], ["Config has no 'mcpServers' (or 'servers') object."]
if not isinstance(servers, dict):
return [], [f"'{servers_key}' must be an object mapping name -> server."]

Comment on lines +349 to +351
if (result.errors.length) {
toast.warning(summary, { description: result.errors.slice(0, 5).join("\n") });
} else {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In standard HTML rendering, newlines (\n) in text content are collapsed into a single space unless the element has white-space: pre-line or similar styling. This causes the sliced error messages to run together on a single line, making them difficult to read.

Wrapping the description in a div with the Tailwind class whitespace-pre-line ensures that each error is rendered on its own line.

      if (result.errors.length) {
        toast.warning(summary, {
          description: (
            <div className="whitespace-pre-line">
              {result.errors.slice(0, 5).join("\n")}
            </div>
          ),
        });
      } else {

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b92230bf5c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if sys.platform == "win32":
import subprocess

return subprocess.list2cmdline(parts)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Windows args containing quotes during import

When importing a Windows stdio config whose args contain literal quotes (for example a JSON CLI option like {"foo":"bar"}), subprocess.list2cmdline() emits backslash-escaped quotes, but the stored string is later split by parse_stdio_command() using shlex.split(..., posix=False), which does not unescape those sequences. The MCP server process then receives '{\\"foo\\":\\"bar\\"}' instead of the original JSON argument, so these imported servers can fail even though the config is valid; either the joiner needs to match the existing parser, or the parser needs to understand Windows command-line escaping.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants