diff --git a/BUILD_TROUBLESHOOTING.md b/BUILD_TROUBLESHOOTING.md new file mode 100644 index 00000000000..61441fd08fe --- /dev/null +++ b/BUILD_TROUBLESHOOTING.md @@ -0,0 +1,437 @@ +# Build Troubleshooting Guide + +This guide covers common build issues and their solutions when developing Void. + +## Table of Contents + +1. [Installation Issues](#installation-issues) +2. [Build Issues](#build-issues) +3. [React Build Issues](#react-build-issues) +4. [Runtime Issues](#runtime-issues) +5. [Process Management](#process-management) + +--- + +## Installation Issues + +### npm install fails or postinstall doesn't run + +**Symptoms:** +- Build fails with "Cannot find module" errors +- Missing dependencies in subdirectories +- Errors about missing native modules + +**What happens during npm install:** +The `npm install` command automatically runs `npm run postinstall`, which: +1. Installs dependencies in multiple subdirectories: + - `build/` - Build tools and scripts + - `extensions/*/` - All extension directories + - `remote/` - Remote development dependencies + - `test/*/` - Test framework dependencies +2. Configures git settings (`pull.rebase merges`, blame ignore file) +3. Removes prebuilt Parcel watcher modules to ensure proper compilation + +**Solution:** +```bash +# Manually run postinstall if it didn't complete +npm run postinstall + +# If that fails, check for permission issues or interrupted installations +# and try a clean reinstall: +rm -rf node_modules +npm install +``` + +**Common causes:** +- Network interruption during installation +- Insufficient disk space +- Permission issues in subdirectories +- Antivirus software blocking file operations + +--- + +### Node version mismatch + +**Symptoms:** +- Native module compilation errors +- Unexpected build failures +- "Unsupported engine" warnings + +**Solution:** +```bash +# Check your Node version +node --version + +# Should be 20.18.2 (see .nvmrc) +# Use nvm to switch to the correct version: +nvm install +nvm use +``` + +--- + +### Native module compilation fails + +**Symptoms:** +- Errors mentioning `node-gyp`, `python`, or C++ compiler +- Build fails during installation phase + +**Solution by platform:** + +**Mac:** +```bash +# Install XCode Command Line Tools +xcode-select --install +``` + +**Windows:** +- Install [Visual Studio 2022](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community) +- Select "Desktop development with C++" workload +- Select these individual components: + - `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)` + - `C++ ATL for latest build tools with Spectre Mitigations` + - `C++ MFC for latest build tools with Spectre Mitigations` + +**Linux (Debian/Ubuntu):** +```bash +sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3 +``` + +--- + +## Build Issues + +### Developer Mode build fails to start + +**Symptoms:** +- Pressing Ctrl+Shift+B (Cmd+Shift+B) does nothing +- Build task not found error + +**Solution:** +```bash +# Run watch mode from terminal instead +npm run watch + +# Wait for build to complete - you'll see output like: +# [watch-extensions] Finished compilation extensions with 0 errors +# [watch-client ] Finished compilation with 0 errors +``` + +--- + +### Build completes but changes aren't reflected + +**Symptoms:** +- Code changes don't appear in Developer Mode window +- Old behavior persists after edits + +**Solution:** +1. Reload the Developer Mode window: + - Press `Ctrl+R` (Windows/Linux) or `Cmd+R` (Mac) + - Or: `Ctrl+Shift+P` → "Reload Window" + +2. If that doesn't work, check that watch mode is running: + - Look for the terminal running `npm run watch` + - Verify it's not showing errors + - Check that it recompiled after your changes + +3. For React changes specifically: + ```bash + # React must be built separately + npm run buildreact + + # Or run in watch mode for continuous builds + npm run watchreact + ``` + +--- + +### Module resolution errors during build + +**Symptoms:** +- "Cannot find module" errors during compilation +- TypeScript errors about missing types + +**Solution:** +```bash +# 1. Verify postinstall completed +npm run postinstall + +# 2. Clean build artifacts and rebuild +rm -rf out/ +npm run watch + +# 3. For React-specific module errors +cd src/vs/workbench/contrib/void/browser/react/ +rm -rf out/ +npm run buildreact +cd ../../../../../../../ +``` + +--- + +## React Build Issues + +### React build fails with "Failed to fetch dynamically imported module" + +**Symptoms:** +- Error when loading Void sidebar or React components +- Console shows module fetch errors + +**Root cause:** +React imports from outside the `react/src/` directory must end with `.js` extension. + +**Solution:** +```typescript +// ❌ Wrong - will fail at runtime +import { URI } from '../../../../../../../base/common/uri' + +// ✅ Correct - include .js extension +import { URI } from '../../../../../../../base/common/uri.js' +``` + +Check all imports in your React code and add `.js` to external imports. + +--- + +### React build runs out of memory + +**Symptoms:** +- Build process crashes +- "JavaScript heap out of memory" error +- Build freezes or becomes extremely slow + +**Solution:** +```bash +# Increase Node memory limit +NODE_OPTIONS="--max-old-space-size=8192" npm run buildreact + +# For watch mode +NODE_OPTIONS="--max-old-space-size=8192" npm run watchreact +``` + +--- + +### Missing styles in React components + +**Symptoms:** +- Components render but have no styling +- Tailwind classes don't apply + +**Solution:** +1. Wait a few seconds - styles may be loading +2. Reload the window: `Ctrl+R` (Windows/Linux) or `Cmd+R` (Mac) +3. Rebuild React components: + ```bash + npm run buildreact + ``` +4. Check that Tailwind build completed without errors + +--- + +## Runtime Issues + +### Developer Mode window won't launch + +**Symptoms:** +- `./scripts/code.sh` or `./scripts/code.bat` fails +- Window opens but immediately crashes + +**Solution:** + +**Check sandbox permissions (Linux):** +```bash +# SUID sandbox error +sudo chown root:root .build/electron/chrome-sandbox +sudo chmod 4755 .build/electron/chrome-sandbox +./scripts/code.sh +``` + +**Use isolated user data directory:** +```bash +# Prevents conflicts with your main Void/VSCode installation +./scripts/code.sh --user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions + +# To reset settings, delete the temp directory: +rm -rf .tmp/ +``` + +**Check build completion:** +Make sure the watch build finished before launching. Look for: +``` +[watch-extensions] Finished compilation extensions with 0 errors +[watch-client ] Finished compilation with 0 errors +``` + +--- + +### Path contains spaces error + +**Symptoms:** +- Build fails with path-related errors +- Native modules fail to compile +- Mysterious file not found errors + +**Solution:** +Move your Void repository to a path without spaces: +```bash +# ❌ Bad +/Users/john/My Projects/void/ + +# ✅ Good +/Users/john/projects/void/ +``` + +--- + +## Process Management + +### Background processes still running after stopping build + +**Symptoms:** +- Port conflicts when trying to rebuild +- Multiple watch processes consuming resources +- Changes building multiple times + +**Solution:** + +**Stop with Ctrl+D (recommended):** +```bash +# In the terminal running the build, press: +Ctrl+D # Cleanly stops the process +``` + +**Never use Ctrl+C:** +```bash +Ctrl+C # ❌ This closes terminal but leaves processes running! +``` + +**Kill daemon processes:** +```bash +# If using background watch modes +npm run kill-watchd # Kill all background watches +npm run kill-watch-clientd # Kill only client watch +npm run kill-watch-extensionsd # Kill only extensions watch +``` + +**Find and kill orphaned processes:** +```bash +# Find Node processes +ps aux | grep node + +# Kill specific process by PID +kill + +# Nuclear option - kill all Node processes (careful!) +killall node +``` + +--- + +### Build watch mode becomes unresponsive + +**Symptoms:** +- Changes no longer trigger rebuilds +- Terminal shows no activity +- High CPU usage but no progress + +**Solution:** +```bash +# 1. Stop current watch (Ctrl+D) +# 2. Restart watch +npm run watch + +# Or use daemon restart +npm run restart-watchd +``` + +--- + +## Advanced Troubleshooting + +### Clean everything and start fresh + +When all else fails: + +```bash +# 1. Stop all running processes +npm run kill-watchd + +# 2. Clean all build artifacts +rm -rf out/ +rm -rf .build/ +rm -rf node_modules/ + +# 3. Clean React build +rm -rf src/vs/workbench/contrib/void/browser/react/out/ + +# 4. Reinstall dependencies +npm install + +# 5. Verify postinstall completed +npm run postinstall + +# 6. Build React +npm run buildreact + +# 7. Start watch mode +npm run watch + +# 8. In another terminal, launch Void +./scripts/code.sh --user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions +``` + +--- + +### Still having issues? + +1. Check the [HOW_TO_CONTRIBUTE.md](HOW_TO_CONTRIBUTE.md) for prerequisite setup +2. Verify your system meets all requirements in [CLAUDE.md](CLAUDE.md) +3. Search existing [GitHub Issues](https://github.com/voideditor/void/issues) +4. Create a new issue with: + - Your operating system and version + - Node version (`node --version`) + - Full error message and stack trace + - Steps to reproduce + +--- + +## Quick Reference + +### Essential Commands +```bash +npm install # Install dependencies (runs postinstall automatically) +npm run postinstall # Manually run postinstall script +npm run watch # Start watch mode (client + extensions) +npm run buildreact # Build React components once +npm run watchreact # Watch React components for changes +./scripts/code.sh # Launch Developer Mode window (Mac/Linux) +./scripts/code.bat # Launch Developer Mode window (Windows) +``` + +### Common Workflows + +**First time setup:** +```bash +git clone https://github.com/voideditor/void +cd void +npm install +npm run buildreact +npm run watch # In one terminal +./scripts/code.sh --user-data-dir ./.tmp/user-data # In another terminal +``` + +**Daily development:** +```bash +npm run watchd # Start background watch +npm run watchreactd # Start background React watch +./scripts/code.sh --user-data-dir ./.tmp/user-data +# Make changes, reload window with Ctrl+R +``` + +**Before submitting PR:** +```bash +npm run eslint # Check for linting errors +npm run hygiene # Check code style +npm run test-node # Run Node tests +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..445dbc9ee70 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,299 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## About Void + +Void is an open-source AI code editor forked from VSCode. It provides AI agents, checkpoint/visualize changes, and supports any model or local hosting. Most Void-specific code lives in `src/vs/workbench/contrib/void/`. + +## Development Commands + +### Setup and Build +```bash +# Install dependencies +npm install +# Note: This automatically runs 'npm run postinstall' which installs +# dependencies in subdirectories (build/, extensions/, remote/, etc.) +# If builds fail with missing dependencies, manually run: npm run postinstall + +# Start Developer Mode (watch mode for development) +# Press Ctrl+Shift+B (Windows/Linux) or Cmd+Shift+B (Mac) in VSCode/Void +# Or run from terminal: +npm run watch + +# Watch only client code +npm run watch-client + +# Watch only extensions +npm run watch-extensions + +# Build React components (required before running) +npm run buildreact + +# Watch React components during development +npm run watchreact + +# Background watch modes (run in daemon) +npm run watchd # Watch all in background +npm run watchreactd # Watch React in background +npm run kill-watchd # Stop background watch +npm run restart-watchd # Restart background watch +``` + +### Running Void +```bash +# Launch Developer Mode window (after build completes) +./scripts/code.sh # Mac/Linux +./scripts/code.bat # Windows + +# Launch with isolated user data (recommended for testing) +./scripts/code.sh --user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions +``` + +### Testing +```bash +# Run browser tests +npm run test-browser + +# Run Node tests +npm run test-node + +# Run smoke tests +npm run smoketest +``` + +### Code Quality +```bash +# Run ESLint +npm run eslint + +# Run style checks +npm run stylelint + +# Run hygiene checks +npm run hygiene +``` + +## Architecture + +### VSCode Foundation + +Void is built on VSCode's Electron architecture with two processes: + +- **Main Process** (`electron-main/`): Node.js environment, can import node_modules +- **Browser Process** (`browser/`): Renderer process, HTML/DOM access, cannot directly import node_modules +- **Common** (`common/`): Shared code usable by both processes + +### Core Void Structure + +``` +src/vs/workbench/contrib/void/ +├── browser/ # UI, services, React components +│ ├── react/ # React UI components (built separately) +│ ├── helpers/ # Browser-side utilities (e.g., findDiffs.ts) +│ ├── helperServices/ # Browser-side helper services (e.g., consistentItemService.ts) +│ ├── actionIDs.ts # Centralized action/command IDs (prevents import order errors) +│ └── void.contribution.ts # Entry point - registers all services and features +├── common/ # Shared services and types +│ ├── prompt/ # LLM prompt construction (prompts.ts - 38KB of all prompts) +│ ├── helpers/ # Common utilities (extractCodeFromResult, languageHelpers, etc.) +│ └── modelCapabilities.ts # Model configuration (57KB - update when new models release) +└── electron-main/ # Main process services + ├── llmMessage/ # LLM communication handlers + ├── sendLLMMessageChannel.ts # IPC channel for LLM communication + └── mcpChannel.ts # IPC channel for MCP communication +``` + +**Key architectural files:** +- `void.contribution.ts`: Entry point that imports and registers all Void features (import order matters!) +- `actionIDs.ts`: Centralized action IDs to prevent import order errors +- `_dummyContrib.ts`: Template for creating new services + +### Key Services + +Services are singletons registered with `registerSingleton`. Major services include: + +- **`voidSettingsService`**: Manages all Void settings (providers, models, features) +- **`chatThreadService`**: Handles chat conversations and message history +- **`editCodeService`**: Manages code edits, diffs, and Apply functionality +- **`toolsService`**: Handles LLM tool execution +- **`terminalToolService`**: Handles terminal command execution with persistent terminals +- **`sendLLMMessageService`**: Communicates with LLM providers +- **`mcpService`**: MCP (Model Context Protocol) server integration +- **`autocompleteService`**: AI-powered code autocomplete +- **`voidModelService`**: Text model and URI management +- **`contextGatheringService`**: Gathers context for LLM requests +- **`convertToLLMMessageService`**: Formats messages for LLM providers + +### Creating New Services + +Reference `browser/_dummyContrib.ts` as a template for creating services: + +1. **Define the interface** with `createDecorator()`: + ```typescript + const IMyService = createDecorator('myService'); + ``` + +2. **Implement the service** extending `Disposable`: + ```typescript + class MyService extends Disposable implements IMyService { + constructor(@IOtherService otherService: IOtherService) { + super(); + } + } + ``` + +3. **Register the service** (choose one): + - `registerSingleton(IMyService, MyService, InstantiationType.Delayed)` - Lazy-loaded when first used + - `registerWorkbenchContribution2(MyService.ID, MyService, WorkbenchPhase.*)` - Early initialization + +4. **Import in `void.contribution.ts`** - Import order matters! + +**Important**: Many services have companion `*Types.ts` files (e.g., `mcpService.ts` + `mcpServiceTypes.ts`) to keep types separate from implementation. + +### IPC/Channel Architecture + +Browser and main processes communicate via **Channels** (not direct imports): + +- **`sendLLMMessageChannel`** (`electron-main/sendLLMMessageChannel.ts`): LLM API communication +- **`mcpChannel`** (`electron-main/mcpChannel.ts`): MCP server communication + +**How Channels work:** +```typescript +// Main process: implements IServerChannel +channel.call('methodName', args) // Call from browser to main +channel.listen('eventName') // Listen to events from main + +// Events: onText_sendLLMMessage, onFinalMessage_sendLLMMessage, onError_sendLLMMessage +``` + +This architecture allows the browser process to access node_modules functionality (e.g., API calls) via the main process, bypassing Content Security Policy restrictions. + +### LLM Message Pipeline + +Messages flow from sidebar → browser process → main process → provider: + +1. User sends message in React UI (sidebar) +2. `chatThreadService` processes the message +3. `convertToLLMMessageService` formats for the provider +4. `sendLLMMessageChannel` communicates browser ↔ main process (via IPC) +5. Main process sends to LLM provider (bypasses CSP, uses node_modules) + +**Important**: Update `modelCapabilities.ts` when new models are released. This file contains: +- `defaultModelsOfProvider` - all supported models by provider +- Model capabilities and overrides +- Supported providers: Anthropic, OpenAI, Deepseek, Ollama, vLLM, OpenRouter, Gemini, Groq, xAI, Mistral, LM Studio, LiteLLM, Google Vertex, Azure, AWS Bedrock, and more + +### MCP Integration + +Void supports **Model Context Protocol (MCP)** for extending tool capabilities: + +- **`mcpService`** (`common/mcpService.ts`): Main MCP service managing server connections +- **`mcpChannel`** (`electron-main/mcpChannel.ts`): IPC channel for MCP communication with main process +- **Tool name prefixes**: MCP tools have special name prefixes; use `removeMCPToolNamePrefix()` to handle them +- **Configuration**: MCP servers are configured in Void settings + +MCP allows Void to integrate external tools and data sources beyond built-in capabilities. + +### Apply System + +Two Apply modes: + +1. **Fast Apply** (Search/Replace): LLM outputs search/replace blocks for targeted edits: + ``` + <<<<<<< ORIGINAL + // original code + ======= + // replaced code + >>>>>>> UPDATED + ``` + - Parsed by `extractCodeFromResult.ts` (`common/helpers/`) + - Retries up to N times if matching fails + - Falls back to Slow Apply on failure + +2. **Slow Apply**: Rewrites entire file + +The `editCodeService` handles both modes and is also used for: +- LLM Edit tool calls +- Cmd+K/Ctrl+K quick edits + +**DiffZone**: Region tracking red/green diffs with {startLine, endLine}. Streams changes and refreshes when file changes. + +### React Components + +React code lives in `src/vs/workbench/contrib/void/browser/react/`: + +**Build system:** +- Custom build pipeline: `build.js` (supports `--watch` flag) +- Bundler: tsup (configured in `tsup.config.js`) +- Styling: Tailwind CSS with `scope-tailwind` to avoid conflicts with VSCode styles +- Build commands: `npm run buildreact` or `npm run watchreact` +- Compiled output goes to `out/` directory + +**Critical rules:** +- ALL imports from outside `react/src/` MUST end with `.js` or build fails + ```typescript + import { URI } from '../../../../../../../base/common/uri.js' // Correct + import { URI } from '../../../../../../../base/common/uri' // Will fail + ``` +- Source files must be exactly 1 level deep in `src/` for external detection +- May need increased memory: `NODE_OPTIONS="--max-old-space-size=8192" npm run buildreact` + +## Development Guidelines + +### Code Modification Rules + +1. **Never modify files outside `src/vs/workbench/contrib/void/`** without consulting the user first +2. **Follow existing conventions**: + - Use tabs for indentation (not spaces) - see `.editorconfig` + - Don't add/remove semicolons arbitrarily + - Follow existing code style in the file you're modifying +3. **Type safety**: Don't cast to `any`. Find and use correct types +4. **Naming convention**: Maps from A→B should be named `bOfA` (e.g., `toolNameOfToolId`, `idOfPersistentTerminalName`) +5. **Action IDs**: Never inline action ID strings + - Always import from `actionIDs.ts`: `import { VOID_CTRL_L_ACTION_ID } from './actionIDs.js'` + - This prevents import order errors + - Available IDs: `VOID_CTRL_L_ACTION_ID`, `VOID_CTRL_K_ACTION_ID`, `VOID_ACCEPT_DIFF_ACTION_ID`, `VOID_REJECT_DIFF_ACTION_ID`, etc. +6. **No validation**: Don't run builds or tests yourself—tell the user what to run + +### Prerequisites + +- **Node version**: `20.18.2` (see `.nvmrc`) +- **Mac**: Python and XCode (usually pre-installed) +- **Windows**: Visual Studio 2022 with C++ build tools +- **Linux**: `build-essential`, `libx11-dev`, `libxkbfile-dev`, `libsecret-1-dev`, `libkrb5-dev`, `python-is-python3` + +### Common Issues + +- **Ensure no spaces in path to Void folder** - Build may fail with paths containing spaces +- **Missing dependencies in subdirectories**: If builds fail with module errors, run `npm run postinstall` manually +- **React out of memory**: `NODE_OPTIONS="--max-old-space-size=8192" npm run buildreact` +- **Missing styles after changes**: Wait a few seconds and reload window +- **`Failed to fetch dynamically imported module`**: React imports must end with `.js` +- **Kill build scripts properly**: Use `Ctrl+D` (not `Ctrl+C` which leaves processes running) + - Alternative: Use daemon commands (`npm run kill-watchd`) +- **Import order errors**: Use centralized `actionIDs.ts` instead of inlining action ID strings + +For more detailed troubleshooting, see [BUILD_TROUBLESHOOTING.md](BUILD_TROUBLESHOOTING.md) + +### Reloading Changes + +After code changes in Developer Mode: +- Press `Ctrl+R` (Windows/Linux) or `Cmd+R` (Mac) to reload the window +- Or `Ctrl+Shift+P` → "Reload Window" + +## VSCode Concepts + +- **Editor**: The code editing area (one editor can have multiple tabs) +- **Model** (`ITextModel`): Internal representation of file contents, shared between editors +- **URI**: File path/resource identifier +- **Workbench**: Container for editors, terminal, file tree, etc. +- **Actions/Commands**: Registered functions callable via Cmd+Shift+P or `commandService` +- **Services**: Singletons registered with `registerSingleton`, injectable via `@` in constructors + +## Resources + +- [VOID_CODEBASE_GUIDE.md](VOID_CODEBASE_GUIDE.md) - Detailed architecture diagrams +- [HOW_TO_CONTRIBUTE.md](HOW_TO_CONTRIBUTE.md) - Setup and contribution guide +- [VSCode Source Code Organization](https://github.com/microsoft/vscode/wiki/Source-Code-Organization) +- [VSCode UI Guide](https://code.visualstudio.com/docs/getstarted/userinterface) diff --git a/MCP_BUG_FIX_SUMMARY.md b/MCP_BUG_FIX_SUMMARY.md new file mode 100644 index 00000000000..c6d69a646fe --- /dev/null +++ b/MCP_BUG_FIX_SUMMARY.md @@ -0,0 +1,179 @@ +# MCP Server Bug Fix Summary + +This document summarizes critical bug fixes for Void's MCP (Model Context Protocol) integration that prevented MCP tools from being available to the LLM. + +## Bug #1: MCP Server Not Added to Internal List When Toggled On + +### Description +When toggling an MCP server ON in Void's settings, the server would connect successfully (showing green status in UI) but tools would not be available. The server was never added to the internal `infoOfClientId` mapping, causing tool calls to fail silently. + +### Root Cause +In `src/vs/workbench/contrib/void/electron-main/mcpChannel.ts`, the `_toggleMCPServer` function had a critical bug around line 280-284. When toggling a server ON: + +1. ✅ It created the client connection with `_createClientUnsafe()` +2. ✅ It fired an event to notify the browser process (making UI show green) +3. ❌ **It never updated `this.infoOfClientId[serverName]` with the new client** + +This meant: +- UI showed server as connected (green status) +- Server had valid connection and tool list +- But stored reference to client was missing +- When trying to call a tool, code looked up `this.infoOfClientId[serverName]._client` and found nothing + +### The Fix + +**File:** `src/vs/workbench/contrib/void/electron-main/mcpChannel.ts` + +**Line:** 284 (in the `_toggleMCPServer` function) + +**Change:** Added one line to store the client info when server is toggled ON: + +```typescript +private async _toggleMCPServer(serverName: string, isOn: boolean) { + const prevServer = this.infoOfClientId[serverName]?.mcpServer + if (isOn) { + // Create client and get info + const clientInfo = await this._createClientUnsafe( + this.infoOfClientId[serverName].mcpServerEntryJSON, + serverName, + isOn + ) + + // FIX: Store the client info so tool calls can find it + this.infoOfClientId[serverName] = clientInfo // <-- THIS LINE WAS MISSING + + // Fire event to update UI + this.mcpEmitters.serverEvent.onUpdate.fire({ + response: { + name: serverName, + newServer: clientInfo.mcpServer, + prevServer: prevServer, + } + }) + } else { + // ... toggle off logic + } +} +``` + +--- + +## Bug #2: Tool Naming System Used Random Prefixes + +### Description +The original MCP implementation used random prefixes (like `a1b2c3_read_file`) to make tool names unique. This was replaced with a more predictable system using the server name as the prefix (e.g., `filesystem__read_file`). + +### Root Cause +The `_addUniquePrefix()` function generated random 6-character prefixes for each tool. While this prevented naming collisions, it made tools unpredictable and harder to debug. + +### The Fix + +**File:** `src/vs/workbench/contrib/void/common/mcpService.ts` + +**Line:** 202 + +**Change:** Replaced random prefix with consistent server name prefix: + +```typescript +// OLD (removed from mcpChannel.ts): +private _addUniquePrefix(base: string) { + return `${Math.random().toString(36).slice(2, 8)}_${base}`; +} +name: this._addUniquePrefix(tool.name) // Would generate: "a1b2c3_read_file" + +// NEW (in mcpService.ts): +name: `${serverName}__${tool.name}` // Generates: "filesystem__read_file" +``` + +**Also in `mcpChannel.ts`:** +- Removed the `_addUniquePrefix()` function (lines 232-234) +- Removed calls to `toolsWithUniqueName` mapping (lines 191, 203, 215) +- Tools are now returned as-is from MCP servers + +**Benefits:** +- Predictable tool names for debugging +- Server name clearly identifies which MCP server provides the tool +- Prevents naming collisions when multiple servers have tools with the same base name +- Tool names are consistent across sessions + +--- + +## Bug #3: Error Handling in MCP Channel + +### Description +Errors in `mcpChannel.ts` `call()` method were caught but returned `undefined` instead of proper error responses, making it difficult to debug tool call failures. + +### The Fix + +**File:** `src/vs/workbench/contrib/void/electron-main/mcpChannel.ts` + +**Lines:** 109-122 + +**Change:** Return proper error response for `callTool` command: + +```typescript +catch (e) { + console.error('mcp channel: Call Error:', e) + // For callTool command, return proper error response instead of undefined + if (command === 'callTool') { + const p: MCPToolCallParams = params + const errorResponse: MCPToolErrorResponse = { + event: 'error', + text: `MCP Channel Error: ${e instanceof Error ? e.message : String(e)}`, + toolName: p.toolName || 'unknown', + serverName: p.serverName || 'unknown', + } + return errorResponse + } + // For other commands, re-throw + throw e +} +``` + +--- + +## Debug Logging Added + +Added comprehensive logging to help debug MCP issues: + +1. **mcpChannel.ts** (lines 210-226): Connection and tool fetching logs +2. **mcpService.ts** (lines 105-119, 192-208): Initialization and tool retrieval logs +3. **prompts.ts** (lines 404-417): Tool count and XML generation logs + +**Recommendation:** Keep these logs as they're essential for debugging MCP integration issues. + +--- + +## Testing + +After all fixes: +1. ✅ Toggle MCP server ON in settings +2. ✅ Server connects and shows green status +3. ✅ Tools are registered with predictable names (e.g., `filesystem__read_file`) +4. ✅ Tools are properly available and can be called +5. ✅ Internal client references are stored correctly +6. ✅ Tool calls route to the correct MCP server using the server name prefix + +--- + +## Files Changed + +- `src/vs/workbench/contrib/void/electron-main/mcpChannel.ts` (Bug #1 fix, Bug #3 fix, removed random prefix code, logging) +- `src/vs/workbench/contrib/void/common/mcpService.ts` (Bug #2 fix - server name prefix implementation, logging) +- `src/vs/workbench/contrib/void/common/prompt/prompts.ts` (logging) + +--- + +## Summary + +These fixes resolve the core issues preventing MCP tools from being available and usable in Void: + +1. **Bug #1** ensured MCP servers are properly registered internally when toggled on +2. **Bug #2** replaced random tool name prefixes with predictable server name prefixes +3. **Bug #3** improved error handling for better debugging + +The tool naming system now works as follows: +- Tools are prefixed with `serverName__toolName` for uniqueness (e.g., `filesystem__read_file`) +- The prefix prevents collisions when multiple servers provide tools with the same name +- The server name is also stored separately as `mcpServerName` for routing +- When the LLM calls a tool, Void uses both the full tool name and `mcpServerName` to route correctly diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 30f38f10ba8..64c63e70f0b 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -329,6 +329,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { @IMCPService private readonly _mcpService: IMCPService, ) { super() + console.log('[DEBUG] ChatThreadService constructor called') this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state const readThreads = this._readAllThreads() || {} @@ -751,6 +752,8 @@ class ChatThreadService extends Disposable implements IChatThreadService { const { chatMode } = this._settingsService.state.globalSettings // should not change as we loop even if user changes it, so it goes here const { overridesOfModel } = this._settingsService.state + console.log(`[CHAT AGENT] Starting chat agent in mode: ${chatMode}`) + let nMessagesSent = 0 let shouldSendAnotherMessage = true let isRunningWhenEnd: IsRunningType = undefined @@ -877,6 +880,13 @@ class ChatThreadService extends Disposable implements IChatThreadService { // llm res success const { toolCall, info } = llmRes + console.log(`[CHAT AGENT] LLM response received. Has tool call: ${!!toolCall}`) + if (toolCall) { + console.log(`[CHAT AGENT] Tool call requested: ${toolCall.name}`, toolCall.rawParams) + } else { + console.log(`[CHAT AGENT] No tool call, just text response`) + } + this._addMessageToThread(threadId, { role: 'assistant', displayContent: info.fullText, reasoning: info.fullReasoning, anthropicReasoning: info.anthropicReasoning }) this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) // just decorative for clarity @@ -885,6 +895,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { if (toolCall) { const mcpTools = this._mcpService.getMCPTools() const mcpTool = mcpTools?.find(t => t.name === toolCall.name) + console.log(`[CHAT AGENT] Looking for tool in MCP tools. Found: ${!!mcpTool}, Total MCP tools: ${mcpTools?.length ?? 0}`) const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, toolCall.name, toolCall.id, mcpTool?.mcpServerName, { preapproved: false, unvalidatedToolParams: toolCall.rawParams }) if (interrupted) { diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index acc3c6d6cdc..3f842cfd216 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -19,7 +19,7 @@ import { ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsSer import Severity from '../../../../../../../base/common/severity.js' import { getModelCapabilities, modelOverrideKeys, ModelOverrides } from '../../../../common/modelCapabilities.js'; import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransferTypes.js'; -import { MCPServer } from '../../../../common/mcpServiceTypes.js'; +import { MCPServer, removeMCPToolNamePrefix } from '../../../../common/mcpServiceTypes.js'; import { useMCPServiceState } from '../util/services.js'; import { OPT_OUT_KEY } from '../../../../common/storageKeys.js'; import { StorageScope, StorageTarget } from '../../../../../../../platform/storage/common/storage.js'; @@ -929,8 +929,6 @@ const MCPServerComponent = ({ name, server }: { name: string, server: MCPServer const voidSettings = useSettingsState() const isOn = voidSettings.mcpUserStateOfName[name]?.isOn - const removeUniquePrefix = (name: string) => name.split('_').slice(1).join('_') - return (
@@ -972,7 +970,7 @@ const MCPServerComponent = ({ name, server }: { name: string, server: MCPServer data-tooltip-content={tool.description || ''} data-tooltip-class-name='void-max-w-[300px]' > - {removeUniquePrefix(tool.name)} + {removeMCPToolNamePrefix(tool.name)} )) ) : ( diff --git a/src/vs/workbench/contrib/void/common/mcpService.ts b/src/vs/workbench/contrib/void/common/mcpService.ts index 1b559571e4a..c689690ae7c 100644 --- a/src/vs/workbench/contrib/void/common/mcpService.ts +++ b/src/vs/workbench/contrib/void/common/mcpService.ts @@ -91,6 +91,7 @@ class MCPService extends Disposable implements IMCPService { // console.log('GOT EVENT', e) this._setMCPServerState(e.response.name, e.response.newServer) } + this._register((this.channel.listen('onAdd_server') satisfies Event)(onEvent)); this._register((this.channel.listen('onUpdate_server') satisfies Event)(onEvent)); this._register((this.channel.listen('onDelete_server') satisfies Event)(onEvent)); @@ -101,19 +102,23 @@ class MCPService extends Disposable implements IMCPService { private async _initialize() { try { + console.log('[MCP SERVICE] Starting initialization...'); await this.voidSettingsService.waitForInitState; // Create .mcpConfig if it doesn't exist const mcpConfigUri = await this._getMCPConfigFilePath(); + console.log('[MCP SERVICE] Config file path:', mcpConfigUri.toString()); const fileExists = await this._configFileExists(mcpConfigUri); + console.log('[MCP SERVICE] Config file exists:', fileExists); if (!fileExists) { await this._createMCPConfigFile(mcpConfigUri); - console.log('MCP Config file created:', mcpConfigUri.toString()); + console.log('[MCP SERVICE] Config file created:', mcpConfigUri.toString()); } await this._addMCPConfigFileWatcher(); await this._refreshMCPServers(); + console.log('[MCP SERVICE] Initialization complete. Servers:', Object.keys(this.state.mcpServerOfName)); } catch (error) { - console.error('Error initializing MCPService:', error); + console.error('[MCP SERVICE] Error initializing MCPService:', error); } } @@ -184,18 +189,22 @@ class MCPService extends Disposable implements IMCPService { } public getMCPTools(): InternalToolInfo[] | undefined { + console.log('[MCP SERVICE] getMCPTools called. Server count:', Object.keys(this.state.mcpServerOfName).length); const allTools: InternalToolInfo[] = [] for (const serverName in this.state.mcpServerOfName) { const server = this.state.mcpServerOfName[serverName]; + console.log(`[MCP SERVICE] Server "${serverName}" status: ${server.status}, tools count: ${server.tools?.length ?? 0}`); server.tools?.forEach(tool => { allTools.push({ description: tool.description || '', params: this._transformInputSchemaToParams(tool.inputSchema), - name: tool.name, + // Always prefix with server name using __ separator + name: `${serverName}__${tool.name}`, mcpServerName: serverName, }) }) } + console.log('[MCP SERVICE] Total MCP tools collected:', allTools.length); if (allTools.length === 0) return undefined return allTools } diff --git a/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts b/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts index 81fc716f040..f9290159b9d 100644 --- a/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts @@ -238,5 +238,14 @@ export interface MCPToolCallParams { export const removeMCPToolNamePrefix = (name: string) => { - return name.split('_').slice(1).join('_') + // Remove server name prefix with __ separator + // Format: "server_name__tool_name" -> "tool_name" + // Handles underscores in both server and tool names correctly + const parts = name.split('__') + if (parts.length > 1) { + // Has prefix - remove first part (server name), keep rest + return parts.slice(1).join('__') + } + // No prefix found - return as is + return name } diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index fba768159cf..7b562e90958 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -378,7 +378,7 @@ export const availableTools = (chatMode: ChatMode | null, mcpTools: InternalTool } const toolCallDefinitionsXMLString = (tools: InternalToolInfo[]) => { - return `${tools.map((t, i) => { + const xmlString = `${tools.map((t, i) => { const params = Object.keys(t.params).map(paramName => `<${paramName}>${t.params[paramName].description}`).join('\n') return `\ ${i + 1}. ${t.name} @@ -387,6 +387,8 @@ const toolCallDefinitionsXMLString = (tools: InternalToolInfo[]) => { <${t.name}>${!params ? '' : `\n${params}`} ` }).join('\n\n')}` + console.log('[TOOLS XML] Full tool definitions being sent to LLM:', xmlString) + return xmlString } export const reParsedToolXMLString = (toolName: ToolName, toolParams: RawToolParamsObj) => { @@ -401,12 +403,23 @@ export const reParsedToolXMLString = (toolName: ToolName, toolParams: RawToolPar // - You are allowed to call multiple tools by specifying them consecutively. However, there should be NO text or writing between tool calls or after them. const systemToolsXMLPrompt = (chatMode: ChatMode, mcpTools: InternalToolInfo[] | undefined) => { const tools = availableTools(chatMode, mcpTools) + console.log(`[TOOLS] Chat mode: ${chatMode}, MCP tools count: ${mcpTools?.length ?? 0}, Total tools available: ${tools?.length ?? 0}`) + if (mcpTools) { + console.log(`[TOOLS] MCP tool names:`, mcpTools.map(t => t.name)) + } if (!tools || tools.length === 0) return null + const toolXMLString = toolCallDefinitionsXMLString(tools) + console.log(`[TOOLS] First 500 chars of tool XML:`, toolXMLString.substring(0, 500)) + if (mcpTools && mcpTools.length > 0) { + const mcpToolsOnly = tools?.filter(t => mcpTools.some(mcp => mcp.name === t.name)) + console.log(`[TOOLS] MCP tools in final list:`, mcpToolsOnly?.map(t => `${t.name} - ${t.description.substring(0, 50)}`)) + } + const toolXMLDefinitions = (`\ Available tools: - ${toolCallDefinitionsXMLString(tools)}`) + ${toolXMLString}`) const toolCallXMLGuidelines = (`\ Tool calling details: diff --git a/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts b/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts index e5c4fb6e72b..e5008b53f3b 100644 --- a/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts +++ b/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts @@ -106,6 +106,19 @@ export class MCPChannel implements IServerChannel { } catch (e) { console.error('mcp channel: Call Error:', e) + // For callTool command, return proper error response instead of undefined + if (command === 'callTool') { + const p: MCPToolCallParams = params + const errorResponse: MCPToolErrorResponse = { + event: 'error', + text: `MCP Channel Error: ${e instanceof Error ? e.message : String(e)}`, + toolName: p.toolName || 'unknown', + serverName: p.serverName || 'unknown', + } + return errorResponse + } + // For other commands, re-throw + throw e } } @@ -175,10 +188,9 @@ export class MCPChannel implements IServerChannel { await client.connect(transport); console.log(`Connected via HTTP to ${serverName}`); const { tools } = await client.listTools() - const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })) info = { status: isOn ? 'success' : 'offline', - tools: toolsWithUniqueName, + tools: tools, command: server.url.toString(), } } catch (httpErr) { @@ -186,16 +198,16 @@ export class MCPChannel implements IServerChannel { transport = new SSEClientTransport(server.url); await client.connect(transport); const { tools } = await client.listTools() - const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })) console.log(`Connected via SSE to ${serverName}`); info = { status: isOn ? 'success' : 'offline', - tools: toolsWithUniqueName, + tools: tools, command: server.url.toString(), } } } else if (server.command) { // console.log('ENV DATA: ', server.env) + console.log(`[MCP CHANNEL] Creating StdioClientTransport for ${serverName}`) transport = new StdioClientTransport({ command: server.command, args: server.args, @@ -205,11 +217,13 @@ export class MCPChannel implements IServerChannel { } as Record, }); + console.log(`[MCP CHANNEL] Connecting to ${serverName}...`) await client.connect(transport) + console.log(`[MCP CHANNEL] Connected to ${serverName}, now calling listTools()`) // Get the tools from the server const { tools } = await client.listTools() - const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })) + console.log(`[MCP CHANNEL] Received ${tools.length} tools from ${serverName}`) // Create a full command string for display const fullCommand = `${server.command} ${server.args?.join(' ') || ''}` @@ -217,7 +231,7 @@ export class MCPChannel implements IServerChannel { // Format server object info = { status: isOn ? 'success' : 'offline', - tools: toolsWithUniqueName, + tools: tools, command: fullCommand, } @@ -229,10 +243,6 @@ export class MCPChannel implements IServerChannel { return { _client: client, mcpServerEntryJSON: server, mcpServer: info } } - private _addUniquePrefix(base: string) { - return `${Math.random().toString(36).slice(2, 8)}_${base}`; - } - private async _createClient(serverConfig: MCPConfigFileEntryJSON, serverName: string, isOn = true): Promise { try { const c: ClientInfo = await this._createClientUnsafe(serverConfig, serverName, isOn) @@ -270,6 +280,8 @@ export class MCPChannel implements IServerChannel { if (isOn) { // this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn)) const clientInfo = await this._createClientUnsafe(this.infoOfClientId[serverName].mcpServerEntryJSON, serverName, isOn) + // Update the stored client info + this.infoOfClientId[serverName] = clientInfo this.mcpEmitters.serverEvent.onUpdate.fire({ response: { name: serverName, @@ -308,22 +320,29 @@ export class MCPChannel implements IServerChannel { const { _client: client } = server if (!client) throw new Error(`Client for server ${serverName} not found`) + const actualToolName = removeMCPToolNamePrefix(toolName) + console.log(`[MCP] Calling tool "${actualToolName}" on server "${serverName}" with params:`, JSON.stringify(params, null, 2)) + // Call the tool with the provided parameters const response = await client.callTool({ - name: removeMCPToolNamePrefix(toolName), + name: actualToolName, arguments: params }) const { content } = response as CallToolResult const returnValue = content[0] + console.log(`[MCP] Tool "${actualToolName}" response type: ${returnValue.type}, isError: ${response.isError}`) + if (returnValue.type === 'text') { // handle text response if (response.isError) { + console.error(`[MCP] Tool call error: ${returnValue.text}`) throw new Error(`Tool call error: ${returnValue.text}`) } // handle success + console.log(`[MCP] Tool call successful, result: ${returnValue.text}`) return { event: 'text', text: returnValue.text,