Skip to content

Claude/mcp UI features#10

Open
jasonkneen wants to merge 22 commits intotkattkat:mainfrom
jasonkneen:claude/mcp-ui-features-01WDijWuk2DooQ8GUnK8N4D9
Open

Claude/mcp UI features#10
jasonkneen wants to merge 22 commits intotkattkat:mainfrom
jasonkneen:claude/mcp-ui-features-01WDijWuk2DooQ8GUnK8N4D9

Conversation

@jasonkneen
Copy link
Contributor

  • Add MCP server connection and tool integration
  • Create MCP client for JSON-RPC communication with servers
  • Spawn MCP server processes with configurable command/args/env
  • Implement initialize, list_tools, and call_tool methods
  • Connect to enabled MCP servers on app startup
  • Reconnect MCP servers when settings change
  • Add IPC handlers for getting and executing MCP tools
  • Disconnect all MCP servers on app quit
  • Fix MCP server args by sanitizing quotes
  • Strip surrounding quotes from args before spawning MCP server process.

claude and others added 12 commits December 8, 2025 09:06
- Add MCPServerConfig interface to types
- Add IPC handlers for MCP server CRUD operations (get, add, update, remove, toggle)
- Expose MCP server management functions in preload
- Add MCP Servers section to settings UI with server list and add/edit modal
- Support toggling servers enabled/disabled
- Add comprehensive CSS styling including dark mode support
- Replace hover-to-open sidebar with toggle button in header
- Add drag-to-resize sidebar functionality (200-400px range)
- Implement tabbed interface for managing multiple conversations
- Add tab creation, switching, and closing
- Support new window creation via button and tab drag-out
- Add IPC handlers for window management and tab transfer
- Update message routing to use event.sender for correct window
- Create MCP client for JSON-RPC communication with servers
- Spawn MCP server processes with configurable command/args/env
- Implement initialize, list_tools, and call_tool methods
- Connect to enabled MCP servers on app startup
- Reconnect MCP servers when settings change
- Add IPC handlers for getting and executing MCP tools
- Disconnect all MCP servers on app quit
- Add configurable keyboard shortcuts for:
  - Open spotlight (default: Cmd/Ctrl+Shift+C)
  - New conversation (default: Cmd/Ctrl+N)
  - Toggle sidebar (default: Cmd/Ctrl+B)
- Update settings UI to show all configurable shortcuts
- Register all shortcuts globally via Electron's globalShortcut
- Add IPC handlers for new-conversation and toggle-sidebar events
- Maintain backwards compatibility with existing spotlightKeybind setting
- Add tools button with badge next to paperclip in chat input area
- Implement expandable popup showing connected MCP servers and their tools
- Allow selection of entire servers or individual tools for the session
- Display connection status and tool count in settings
- Add CSS for tools popup in light and dark modes
- Pass selected MCP tools to message handler for future integration
Strip surrounding quotes from args before spawning MCP server process.
This fixes issues when users put quotes around arguments in the config.
Also added logging of command and args for debugging.
- Strip surrounding quotes from command and args
- Validate required fields (id, name, command)
- Filter out invalid entries
- Ensure args is always an array
- Ensure enabled is boolean, env is object
- Keep invalid entries visible so they can be deleted (auto-disable them)
- Generate ID for entries missing one
- Detect early process exit and fail fast instead of waiting for timeout
- Reject pending requests when process exits/errors
- Add small delay to catch immediate exits before initialize
- Use @modelcontextprotocol/sdk for proper protocol implementation
- Support both stdio transport (local commands) and SSE transport (HTTP URLs)
- Cleaner code with SDK handling protocol details
- Auto-detect transport type based on command (http:// = SSE, else stdio)
…cessibility

- Add permission IPC handlers: get-permission-status, request-media-access,
  open-permission-settings, request-screen-capture
- Add permission APIs to preload.ts for renderer access
- Create entitlements.mac.plist with required macOS permissions
- Update package.json mac build config with entitlements and usage descriptions
Copilot AI review requested due to automatic review settings December 9, 2025 13:33
@jasonkneen jasonkneen requested a review from tkattkat as a code owner December 9, 2025 13:33
@jasonkneen jasonkneen changed the title Claude/mcp UI features 01 w dij wuk2 doo q8 g un k8 n4 d9 Claude/mcp UI features Dec 9, 2025
Copy link
Contributor

Copilot AI left a comment

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 comprehensive Model Context Protocol (MCP) server integration to Open Claude, enabling external tool connectivity and multi-window/multi-tab support. The implementation includes a complete UI for managing MCP servers, connection lifecycle management, and tool selection interface.

Key Changes

  • MCP Integration: Full MCP SDK integration with server lifecycle management (connect, disconnect, reconnect)
  • UI Enhancements: Settings page for MCP server configuration, tools popup for tool selection, tab management with drag-to-detach functionality
  • Multi-Window Support: Window management system allowing tab detachment and multiple application windows

Reviewed changes

Copilot reviewed 11 out of 13 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
static/styles/settings.css Extensive CSS for MCP server UI including modals, server cards, tool badges, and dark mode support
static/settings.html Added MCP servers section with server list and add/edit modal
static/index.html Added tab bar, tools popup, sidebar resize, and updated styles for multi-window UI
src/types/index.ts Added MCPServerConfig interface to settings schema
src/renderer/settings.ts MCP server CRUD operations and UI rendering logic
src/renderer/main.ts Tab management, MCP tools popup, window communication, and sidebar resizing
src/preload.ts IPC bridge for MCP and window management APIs
src/mcp/client.ts New MCP client wrapper handling stdio/SSE transports and tool execution
src/main.ts Main process handlers for MCP operations, window management, and macOS permissions
pnpm-lock.yaml Added @modelcontextprotocol/sdk and dependencies
package.json Added MCP SDK dependency and macOS entitlements configuration
build/entitlements.mac.plist New macOS entitlements for camera, microphone, screen capture, and automation
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

build/entitlements.mac.plist:23

  • Security concern: The entitlements grant broad permissions including JIT, unsigned executable memory, and library validation disabling. While these may be needed for Electron development, they weaken security significantly. Ensure these are absolutely necessary for the MCP integration and consider removing them for production builds if possible. Specifically:
  • com.apple.security.cs.allow-jit
  • com.apple.security.cs.allow-unsigned-executable-memory
  • com.apple.security.cs.disable-library-validation
  • com.apple.security.cs.allow-dyld-environment-variables

These entitlements should be documented with clear justification.

    <!-- Allow app to be debugged -->
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>

    <!-- Camera access -->
    <key>com.apple.security.device.camera</key>
    <true/>

    <!-- Microphone access -->
    <key>com.apple.security.device.audio-input</key>
    <true/>

    <!-- Screen recording (required for screen capture) -->
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>

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

Comment on lines +573 to +577
`).join('');

// Add event listeners
container.querySelectorAll('.tab').forEach(tabEl => {
const tabId = (tabEl as HTMLElement).dataset.tabId;
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

Similar escapeHtml function is defined here as well. This is code duplication - consider extracting this to a shared utility module that both settings.ts and main.ts can import to maintain DRY principles.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +23
<!-- Screen recording (required for screen capture) -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The comment on line 21 is misleading. The entitlement com.apple.security.cs.allow-dyld-environment-variables is not the correct entitlement for screen recording. Screen recording on macOS doesn't have a specific entitlement - it's controlled by system permissions (TCC). This entitlement allows the app to use DYLD environment variables which is a security risk. Remove this or correct the comment and entitlement.

Suggested change
<!-- Screen recording (required for screen capture) -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>

Copilot uses AI. Check for mistakes.
Comment on lines +616 to +618
// Remove the tab from this window
closeTab(tabId);
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

Race condition: The drag-and-drop tab detachment checks tabs.length > 1 at the start of dragend, but by the time the async detachTab completes and closeTab is called, another tab could have been closed. This could result in the last tab being closed. Store the length check result before the async call or re-check after.

Suggested change
// Remove the tab from this window
closeTab(tabId);
}
// Remove the tab from this window, but only if more than one tab remains
if (tabs.length > 1) {
closeTab(tabId);
}

Copilot uses AI. Check for mistakes.
Comment on lines +611 to +617
await window.claude.detachTab({
conversationId: tab.conversationId,
title: tab.title
});

// Remove the tab from this window
closeTab(tabId);
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

Missing error handling for window.claude.detachTab(). If the IPC call fails, the tab will remain in this window but the operation will silently fail. Add error handling:

try {
  await window.claude.detachTab({...});
  closeTab(tabId);
} catch (error) {
  console.error('Failed to detach tab:', error);
  // Show error to user
}

Copilot uses AI. Check for mistakes.
Comment on lines +222 to +223
tools.push({
name: `mcp_${serverName}_${tool.name}`,
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The tool name format used for Claude API (mcp_${serverName}_${tool.name}) should be documented and may cause collisions if a server name or tool name contains underscores. Consider using a more robust delimiter or encoding scheme, or document this limitation in the code comments.

Suggested change
tools.push({
name: `mcp_${serverName}_${tool.name}`,
// Tool name format for Claude API: mcp_{encodedServerName}_{encodedToolName}
// Both serverName and tool.name are URI-encoded to avoid collisions with underscores or other special characters.
tools.push({
name: `mcp_${encodeURIComponent(serverName)}_${encodeURIComponent(tool.name)}`,

Copilot uses AI. Check for mistakes.
Comment on lines +305 to +309
function escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

[nitpick] The function escapeHtml is defined but could be replaced with a standard library or more comprehensive escaping. The current implementation only escapes through textContent assignment, which is safe but the function name suggests it returns an escaped string. Consider using a dedicated library like DOMPurify or he for consistent HTML escaping, or rename the function to sanitizeForHtml to better reflect its behavior.

Copilot uses AI. Check for mistakes.
transport = new StdioClientTransport({
command: config.command,
args: sanitizedArgs,
env: config.env ? { ...process.env, ...config.env } as Record<string, string> : undefined,
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The env property is typed as Record<string, string> | undefined but is being spread with process.env which may contain undefined values. This could cause type errors since process.env values can be string | undefined. Consider filtering undefined values or explicitly typing the result.

env: config.env ? { ...process.env, ...config.env } as Record<string, string> : process.env as Record<string, string>
Suggested change
env: config.env ? { ...process.env, ...config.env } as Record<string, string> : undefined,
env: config.env
? {
...Object.fromEntries(
Object.entries(process.env).filter(([_, v]) => typeof v === 'string')
),
...config.env
}
: undefined,

Copilot uses AI. Check for mistakes.

const serverName = parts[1];
const actualToolName = parts.slice(2).join('_');

Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The parsing logic assumes the tool name follows the pattern mcp_serverName_toolName and uses parts.slice(2).join('_') to reconstruct the tool name. This means if the original tool name contained underscores, they would be preserved correctly. However, if a server name contains underscores, this parsing will fail. Consider documenting this constraint or validating server names to disallow underscores.

Suggested change
// Validate that serverName does not contain underscores
if (serverName.includes('_')) {
throw new Error(`Invalid MCP server name: "${serverName}". Server names must not contain underscores.`);
}

Copilot uses AI. Check for mistakes.
Comment on lines +926 to +928
app.on('will-quit', async () => {
globalShortcut.unregisterAll();
await mcpClient.disconnectAll();
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

The will-quit event handler is async but the event doesn't wait for async operations to complete. This means mcpClient.disconnectAll() may not finish before the app quits, potentially leaving processes running. Use e.preventDefault() and app.quit() pattern to ensure cleanup completes.

app.on('will-quit', (e) => {
  e.preventDefault();
  globalShortcut.unregisterAll();
  mcpClient.disconnectAll().then(() => {
    app.exit(0);
  });
});
Suggested change
app.on('will-quit', async () => {
globalShortcut.unregisterAll();
await mcpClient.disconnectAll();
app.on('will-quit', (e) => {
e.preventDefault();
globalShortcut.unregisterAll();
mcpClient.disconnectAll().then(() => {
app.exit(0);
});

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +23
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>

<!-- Camera access -->
<key>com.apple.security.device.camera</key>
<true/>

<!-- Microphone access -->
<key>com.apple.security.device.audio-input</key>
<true/>

<!-- Screen recording (required for screen capture) -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

This entitlements file enables several hardened runtime exceptions (com.apple.security.cs.allow-jit, com.apple.security.cs.allow-unsigned-executable-memory, com.apple.security.cs.disable-library-validation, and com.apple.security.cs.allow-dyld-environment-variables) for the production macOS build. These flags effectively disable key code integrity protections and allow arbitrary unsigned or injected code (e.g., via DYLD_INSERT_LIBRARIES) to run inside the Electron app with its full camera/mic/files/network/automation privileges, which a local attacker or malware can abuse. For production builds, these exceptions should be removed or restricted to a separate debug-only entitlements file so that release binaries keep library validation, W^X protections, and DYLD environment variable restrictions enabled.

Copilot uses AI. Check for mistakes.
Removed redundant settings and new window buttons from the sidebar, leaving only the pin sidebar button. Updated event listeners in main.ts to match the new button structure, ensuring only relevant buttons are handled.
…gration

Added implementation guide, review, analysis, and summary markdown files for PR tkattkat#10 code simplification. Updated MCP tool handling in the Claude API client to support custom tools via the MCP protocol, including formatting and validation. Improved settings management to support display settings and settings change notifications. Refactored MCP tool execution to use a double underscore delimiter for server/tool names and added input validation. Updated renderer to apply display settings and listen for changes.
@jasonkneen jasonkneen force-pushed the claude/mcp-ui-features-01WDijWuk2DooQ8GUnK8N4D9 branch from 0dde18a to 473b83b Compare January 10, 2026 08:55
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