Skip to content

Add web UI for model and MCP server configuration#11

Open
Rikul wants to merge 1 commit into
mainfrom
claude/webui-mcp-model-config-us7td6
Open

Add web UI for model and MCP server configuration#11
Rikul wants to merge 1 commit into
mainfrom
claude/webui-mcp-model-config-us7td6

Conversation

@Rikul

@Rikul Rikul commented Jun 10, 2026

Copy link
Copy Markdown
Owner
  • Settings modal (gear icon) in the web channel: change the runtime
    model and enable/disable MCP servers with live toggle switches
  • Slash-command autocomplete palette: typing / in the input lists all
    registered commands with descriptions (arrow keys, Tab/Enter, click)
  • New REST endpoints: GET /api/commands, GET|POST /api/model,
    GET|POST /api/mcp/servers
  • MCPManager: runtime enable_server()/disable_server() that connect or
    disconnect clients, update the tool catalog, and persist the disabled
    flag back to mcp_servers.json
  • New /mcp enable and /mcp disable subcommands
    available on all channels
  • WebChannel.set_command_registry() wired in bg_server so the UI can
    list the BackgroundAgent's commands

https://claude.ai/code/session_01BY92TUwnhiWT5pMoZC6Hu2

- Settings modal (gear icon) in the web channel: change the runtime
  model and enable/disable MCP servers with live toggle switches
- Slash-command autocomplete palette: typing / in the input lists all
  registered commands with descriptions (arrow keys, Tab/Enter, click)
- New REST endpoints: GET /api/commands, GET|POST /api/model,
  GET|POST /api/mcp/servers
- MCPManager: runtime enable_server()/disable_server() that connect or
  disconnect clients, update the tool catalog, and persist the disabled
  flag back to mcp_servers.json
- New /mcp enable <server> and /mcp disable <server> subcommands
  available on all channels
- WebChannel.set_command_registry() wired in bg_server so the UI can
  list the BackgroundAgent's commands

https://claude.ai/code/session_01BY92TUwnhiWT5pMoZC6Hu2

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

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.

Code Review

This pull request introduces runtime enabling and disabling of MCP servers, a settings panel in the web UI for toggling servers and changing the active model, and a slash-command autocomplete palette. The feedback highlights a critical concurrency issue in MCPManager where concurrent calls to enable_server and disable_server can cause race conditions. It is recommended to use an asyncio.Lock to serialize these operations and ensure consistency between the in-memory state and the persistent configuration file.

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 thread app/core/mcp_manager.py
self._clients: dict[str, Client] = {}
self._specs: dict[str, dict] = {}
self._server_configs: dict[str, dict] = {}
self._config_path: Path | None = None

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.

high

Initialize an asyncio.Lock to serialize enable_server and disable_server operations, preventing race conditions and inconsistent states between the in-memory configuration and the persistent file.

Suggested change
self._config_path: Path | None = None
self._config_path: Path | None = None
self._lock = asyncio.Lock()

Comment thread app/core/mcp_manager.py
Comment on lines +100 to +127
async def enable_server(self, name: str) -> dict:
"""Connect a configured server at runtime and clear its disabled flag."""
cfg = self._server_configs.get(name)
if cfg is None:
raise ValueError(f"Unknown MCP server '{name}'")
cfg.pop("disabled", None)
if name not in self._clients:
await self._connect_server(name, cfg)
self._persist_disabled(name, False)
return self._status_for(name)

async def disable_server(self, name: str) -> dict:
"""Disconnect a server at runtime, drop its tools, and set its disabled flag."""
cfg = self._server_configs.get(name)
if cfg is None:
raise ValueError(f"Unknown MCP server '{name}'")
client = self._clients.pop(name, None)
if client is not None:
try:
await client.__aexit__(None, None, None)
except Exception as e:
log.warning(f"MCP server '{name}': error during disconnect — {e}")
prefix = f"{name}{self._SEP}"
for key in [k for k in self._specs if k.startswith(prefix)]:
del self._specs[key]
cfg["disabled"] = True
self._persist_disabled(name, True)
return self._status_for(name)

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.

high

There is a potential race condition when enable_server and disable_server are called concurrently for the same server. Because both methods contain await points (await self._connect_server and await client.__aexit__) before modifying the in-memory configuration and persisting it to the file, concurrent execution can lead to inconsistent states (e.g., the in-memory config and the persistent file state getting out of sync).

To prevent this, serialize these operations using async with self._lock: (using the lock initialized in __init__).

    async def enable_server(self, name: str) -> dict:
        """Connect a configured server at runtime and clear its disabled flag."""
        async with self._lock:
            cfg = self._server_configs.get(name)
            if cfg is None:
                raise ValueError(f"Unknown MCP server '{name}'")
            cfg.pop("disabled", None)
            if name not in self._clients:
                await self._connect_server(name, cfg)
            self._persist_disabled(name, False)
            return self._status_for(name)

    async def disable_server(self, name: str) -> dict:
        """Disconnect a server at runtime, drop its tools, and set its disabled flag."""
        async with self._lock:
            cfg = self._server_configs.get(name)
            if cfg is None:
                raise ValueError(f"Unknown MCP server '{name}'")
            client = self._clients.pop(name, None)
            if client is not None:
                try:
                    await client.__aexit__(None, None, None)
                except Exception as e:
                    log.warning(f"MCP server '{name}': error during disconnect — {e}")
            prefix = f"{name}{self._SEP}"
            for key in [k for k in self._specs if k.startswith(prefix)]:
                del self._specs[key]
            cfg["disabled"] = True
            self._persist_disabled(name, True)
            return self._status_for(name)

Copilot AI left a comment

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.

Pull request overview

This PR adds a web UI and supporting APIs to configure the runtime model and toggle MCP servers on/off, and extends the /mcp command to support runtime enable/disable across channels. It also wires the web channel to the BackgroundAgent’s command registry so the browser can show slash-command autocomplete.

Changes:

  • Added REST endpoints to the web channel for commands listing, model get/set, and MCP server status/toggling.
  • Implemented runtime MCPManager.enable_server() / disable_server() with persistence back to mcp_servers.json.
  • Added a slash-command autocomplete palette and a settings modal (model + MCP toggles) to the web UI.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_web_channel.py Adds tests for new web REST endpoints and verifies settings/palette UI elements and JS wiring.
tests/test_mcp_manager.py Adds unit tests for MCPManager enable/disable behavior, persistence, and status reporting.
tests/test_main.py Updates MCP initialization test to assert config_path is passed into mcp_manager.initialize().
tests/test_commands.py Adds tests for /mcp enable and /mcp disable command behavior and error messaging.
README.md Documents the new slash-command palette and settings panel, plus runtime MCP toggling behavior.
CLAUDE.md Updates architecture notes to include runtime MCP toggling and new WebChannel endpoints/UI.
app/main.py Passes config_path into MCP initialization so runtime toggles can persist to the same file.
app/core/mcp_manager.py Adds enable/disable operations, status helper, and persistence of the disabled flag.
app/core/background_agent.py Updates the /mcp command description to reflect new subcommands.
app/channels/web_channel.py Adds settings/palette markup, new REST endpoints, and a command-registry attachment hook.
app/channels/static/web_channel.js Implements slash-command palette UX and settings modal behavior (model + MCP toggles).
app/channels/static/web_channel.css Styles the new palette and settings modal UI elements.
app/channels/commands.py Extends /mcp to support enable/disable subcommands in addition to status/tools.
app/bg_server.py Wires WebChannel to the BackgroundAgent registry so /api/commands can list commands.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/channels/commands.py
Comment on lines +213 to +230
if subcmd in ("enable", "disable"):
if not subargs:
return f"Usage: /mcp {subcmd} <server>"
try:
if subcmd == "enable":
status = await mcp_manager.enable_server(subargs)
else:
status = await mcp_manager.disable_server(subargs)
except ValueError as e:
configured = [s["name"] for s in mcp_manager.get_server_status()]
return f"{e}. Configured: {', '.join(configured) or 'none'}"
if status["disabled"]:
state = "disabled"
elif status["connected"]:
state = f"enabled and connected ({status['tool_count']} tool(s))"
else:
state = "enabled but failed to connect (check logs)"
return f"MCP server '{subargs}' is now {state}."
Comment thread app/core/mcp_manager.py
Comment on lines +142 to +144
with open(self._config_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
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.

3 participants