/ utils / claudeDesktop.ts
claudeDesktop.ts
  1  import { readdir, readFile, stat } from 'fs/promises'
  2  import { homedir } from 'os'
  3  import { join } from 'path'
  4  import {
  5    type McpServerConfig,
  6    McpStdioServerConfigSchema,
  7  } from '../services/mcp/types.js'
  8  import { getErrnoCode } from './errors.js'
  9  import { safeParseJSON } from './json.js'
 10  import { logError } from './log.js'
 11  import { getPlatform, SUPPORTED_PLATFORMS } from './platform.js'
 12  
 13  export async function getClaudeDesktopConfigPath(): Promise<string> {
 14    const platform = getPlatform()
 15  
 16    if (!SUPPORTED_PLATFORMS.includes(platform)) {
 17      throw new Error(
 18        `Unsupported platform: ${platform} - Claude Desktop integration only works on macOS and WSL.`,
 19      )
 20    }
 21  
 22    if (platform === 'macos') {
 23      return join(
 24        homedir(),
 25        'Library',
 26        'Application Support',
 27        'Claude',
 28        'claude_desktop_config.json',
 29      )
 30    }
 31  
 32    // First, try using USERPROFILE environment variable if available
 33    const windowsHome = process.env.USERPROFILE
 34      ? process.env.USERPROFILE.replace(/\\/g, '/') // Convert Windows backslashes to forward slashes
 35      : null
 36  
 37    if (windowsHome) {
 38      // Remove drive letter and convert to WSL path format
 39      const wslPath = windowsHome.replace(/^[A-Z]:/, '')
 40      const configPath = `/mnt/c${wslPath}/AppData/Roaming/Claude/claude_desktop_config.json`
 41  
 42      // Check if the file exists
 43      try {
 44        await stat(configPath)
 45        return configPath
 46      } catch {
 47        // File doesn't exist, continue
 48      }
 49    }
 50  
 51    // Alternative approach - try to construct path based on typical Windows user location
 52    try {
 53      // List the /mnt/c/Users directory to find potential user directories
 54      const usersDir = '/mnt/c/Users'
 55  
 56      try {
 57        const userDirs = await readdir(usersDir, { withFileTypes: true })
 58  
 59        // Look for Claude Desktop config in each user directory
 60        for (const user of userDirs) {
 61          if (
 62            user.name === 'Public' ||
 63            user.name === 'Default' ||
 64            user.name === 'Default User' ||
 65            user.name === 'All Users'
 66          ) {
 67            continue // Skip system directories
 68          }
 69  
 70          const potentialConfigPath = join(
 71            usersDir,
 72            user.name,
 73            'AppData',
 74            'Roaming',
 75            'Claude',
 76            'claude_desktop_config.json',
 77          )
 78  
 79          try {
 80            await stat(potentialConfigPath)
 81            return potentialConfigPath
 82          } catch {
 83            // File doesn't exist, continue
 84          }
 85        }
 86      } catch {
 87        // usersDir doesn't exist or can't be read
 88      }
 89    } catch (dirError) {
 90      logError(dirError)
 91    }
 92  
 93    throw new Error(
 94      'Could not find Claude Desktop config file in Windows. Make sure Claude Desktop is installed on Windows.',
 95    )
 96  }
 97  
 98  export async function readClaudeDesktopMcpServers(): Promise<
 99    Record<string, McpServerConfig>
100  > {
101    if (!SUPPORTED_PLATFORMS.includes(getPlatform())) {
102      throw new Error(
103        'Unsupported platform - Claude Desktop integration only works on macOS and WSL.',
104      )
105    }
106    try {
107      const configPath = await getClaudeDesktopConfigPath()
108  
109      let configContent: string
110      try {
111        configContent = await readFile(configPath, { encoding: 'utf8' })
112      } catch (e: unknown) {
113        const code = getErrnoCode(e)
114        if (code === 'ENOENT') {
115          return {}
116        }
117        throw e
118      }
119  
120      const config = safeParseJSON(configContent)
121  
122      if (!config || typeof config !== 'object') {
123        return {}
124      }
125  
126      const mcpServers = (config as Record<string, unknown>).mcpServers
127      if (!mcpServers || typeof mcpServers !== 'object') {
128        return {}
129      }
130  
131      const servers: Record<string, McpServerConfig> = {}
132  
133      for (const [name, serverConfig] of Object.entries(
134        mcpServers as Record<string, unknown>,
135      )) {
136        if (!serverConfig || typeof serverConfig !== 'object') {
137          continue
138        }
139  
140        const result = McpStdioServerConfigSchema().safeParse(serverConfig)
141  
142        if (result.success) {
143          servers[name] = result.data
144        }
145      }
146  
147      return servers
148    } catch (error) {
149      logError(error)
150      return {}
151    }
152  }