# Electron Integration Guide

This guide explains how to integrate Model Context Playground into an Electron desktop application, with file-based storage, STDIO MCP server support, and proper configuration.

## Overview

The `demo-electron` directory provides a complete example of integrating Model Context Playground into an Electron app using Electron Forge with Vite. This integration enables:

- **File-based storage**: Persistent configuration stored in `~/.mcp-desktop/config`
- **STDIO MCP servers**: Support for local MCP servers via Node.js child processes
- **Attachment handling**: Local file system integration for attachments

## Prerequisites

- Node.js 22 or later
- Electron Forge with Vite plugin

## Project Structure

```
demo-electron/
├── src/
│   ├── main.ts              # Electron main process
│   ├── App.tsx              # React app component
│   └── renderer.tsx         # Renderer entry point
├── package.json             # Dependencies and scripts
├── forge.config.ts          # Electron Forge configuration
├── vite.main.config.ts      # Vite config for main process
├── vite.renderer.config.ts  # Vite config for renderer
└── vite.preload.config.ts   # Vite config for preload (if needed)
```

## Installation

### 1. Install Dependencies

```bash
npm install model-context-playground react react-dom
npm install --save-dev electron @electron-forge/cli @electron-forge/plugin-vite
```

### 2. Configure Electron Main Process

In your `src/main.ts`, configure the BrowserWindow with **specific web preferences** required for STDIO MCP support:

```typescript
import { app, BrowserWindow } from 'electron'
import path from 'node:path'

const createWindow = async (): Promise<BrowserWindow> => {
  const mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      // CRITICAL: These settings are required for STDIO MCP servers
      nodeIntegration: true,      // Allows renderer to use Node.js APIs
      contextIsolation: false,     // Allows direct access to require()
      webSecurity: false,          // Optional: for local file access
    }
  })

  // Load your app...
  if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
    await mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL)
  } else {
    await mainWindow.loadFile(
      path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
    )
  }

  return mainWindow
}

app.whenReady().then(createWindow)
```

> **⚠️ Security Note**: Setting `contextIsolation: false` and `nodeIntegration: true` reduces security isolation. Only use this configuration if you trust all code running in your renderer process.

### 3. Set Up React App Component

In your `src/App.tsx`:

```typescript
import { useCallback, useMemo } from 'react'
import { ModelContextPlayground } from 'model-context-playground'
import { OpenAIProvider } from 'model-context-playground/providers/openai'
import { OllamaProvider } from 'model-context-playground/providers/ollama'
import { electronIntegration } from 'model-context-playground/electron'
import 'model-context-playground/styles.css'

export default function App() {
  const getProviders = useCallback(async () => [
    new OpenAIProvider(),
    new OllamaProvider(),
    // Add other providers as needed
  ], [])

  // Optional: Pre-configure MCP servers (users can still add more via UI)
  const mcpServers = useMemo(() => ([
    {
      id: 'example-stdio-server',
      options: {
        serverName: 'Example STDIO Server',
        transportMode: 'stdio' as const,
        stdioConfig: {
          command: 'npx',
          args: ['-y', '@modelcontextprotocol/server-example'],
          autoRestart: true,
        },
      },
    },
    {
      id: 'lorem-api',
      options: {
        url: 'https://lorem-api.com/api/mcp'
      },
    }
  ]), [])

  return (
    <ModelContextPlayground
      getProviders={getProviders}
      mcpServers={mcpServers}
      // IMPORTANT: Use Electron-specific storage adapter
      storageAdapter={electronIntegration.storage.adapter}
      attachmentCallbackHandler={electronIntegration.storage.attachmentCallbackHandler}
    />
  )
}
```

### 4. Configure Vite for Electron Renderer

In `vite.renderer.config.ts`:

```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { fileURLToPath } from 'node:url'

export default defineConfig({
  plugins: [
    react(),
  ],
  resolve: {
    alias: {
      // Shim electron module to prevent bundling issues
      electron: fileURLToPath(
        new URL('./node_modules/model-context-playground/dist/electron/shim.js', import.meta.url)
      ),
    },
  },
  optimizeDeps: {
    exclude: ['electron'],
  },
})
```

This configuration:
- Aliases the `electron` module to the shim provided by model-context-playground
- Excludes `electron` from Vite's dependency optimization
- No Node.js polyfills needed since `nodeIntegration: true` provides native Node.js APIs

## Key Integration Components

### File-Based Storage Adapter

The Electron integration provides a file-based storage adapter that persists configuration to disk.

#### Default Storage

```typescript
import { electronIntegration } from 'model-context-playground/electron'

// Storage is automatically saved to ~/.mcp-desktop/config/
const adapter = electronIntegration.storage.adapter
```

**Default Storage Location**: `~/.mcp-desktop/config/`

#### Custom Storage Directory

To use a custom directory for storage, create an adapter with a custom path:

```typescript
import { createElectronStorageAdapter } from 'model-context-playground/electron'

// Create adapter with custom directory
const customAdapter = createElectronStorageAdapter({
  configDirectoryPath: '~/my-app/data'  // Tilde expands to home directory
})

// Or use an absolute path
const customAdapter = createElectronStorageAdapter({
  configDirectoryPath: '/absolute/path/to/config'
})

// Use in ModelContextPlayground:
<ModelContextPlayground
  storageAdapter={customAdapter}
  // ... other props
/>
```

**Path Options**:

- Paths starting with `~` are automatically expanded to the user's home directory
- Absolute paths are supported
- The directory is automatically created if it doesn't exist

**Stored Data**:

- `mcp-server-options.json`: MCP server configurations
- `chat-sessions.json`: Chat history and sessions
- `credentials.json`: API keys and provider credentials
- `chat-settings.json`: User preferences

### Attachment Handling

For file attachments in chat:

```typescript
const attachmentHandler = electronIntegration.storage.attachmentCallbackHandler

// Use in ModelContextPlayground:
<ModelContextPlayground
  attachmentCallbackHandler={attachmentHandler}
  // ... other props
/>
```

### Custom Shim for Electron APIs

The integration includes a shim (`src/electron/shim.ts`) that provides Electron-specific APIs to the renderer process. This is automatically included when using `contextIsolation: false`.

## STDIO MCP Server Support

### How It Works

When `nodeIntegration: true` and `contextIsolation: false` are enabled:

1. The renderer process has direct access to Node.js `require()`
2. The STDIO transport plugin dynamically loads `child_process` and `readline`
3. MCP servers are spawned as child processes and communicate via stdin/stdout

### Configuration Example

```typescript
const mcpServers = [
  {
    id: 'filesystem-server',
    options: {
      serverName: 'Filesystem MCP',
      transportMode: 'stdio',
      stdioConfig: {
        command: 'npx',
        args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/allowed/directory'],
        env: {
          // Optional environment variables
          DEBUG: 'mcp:*'
        },
        autoRestart: true,  // Auto-restart on crash
      },
      fetchTimeoutMs: 10_000,  // Optional request timeout
    },
  },
]
```
