/ services / mcp / vscodeSdkMcp.ts
vscodeSdkMcp.ts
  1  import { logForDebugging } from 'src/utils/debug.js'
  2  import { z } from 'zod/v4'
  3  import { lazySchema } from '../../utils/lazySchema.js'
  4  import {
  5    checkStatsigFeatureGate_CACHED_MAY_BE_STALE,
  6    getFeatureValue_CACHED_MAY_BE_STALE,
  7  } from '../analytics/growthbook.js'
  8  import { logEvent } from '../analytics/index.js'
  9  import type { ConnectedMCPServer, MCPServerConnection } from './types.js'
 10  
 11  // Mirror of AutoModeEnabledState in permissionSetup.ts — inlined because that
 12  // file pulls in too many deps for this thin IPC module.
 13  type AutoModeEnabledState = 'enabled' | 'disabled' | 'opt-in'
 14  function readAutoModeEnabledState(): AutoModeEnabledState | undefined {
 15    const v = getFeatureValue_CACHED_MAY_BE_STALE<{ enabled?: string }>(
 16      'tengu_auto_mode_config',
 17      {},
 18    )?.enabled
 19    return v === 'enabled' || v === 'disabled' || v === 'opt-in' ? v : undefined
 20  }
 21  
 22  export const LogEventNotificationSchema = lazySchema(() =>
 23    z.object({
 24      method: z.literal('log_event'),
 25      params: z.object({
 26        eventName: z.string(),
 27        eventData: z.object({}).passthrough(),
 28      }),
 29    }),
 30  )
 31  
 32  // Store the VSCode MCP client reference for sending notifications
 33  let vscodeMcpClient: ConnectedMCPServer | null = null
 34  
 35  /**
 36   * Sends a file_updated notification to the VSCode MCP server. This is used to
 37   * notify VSCode when files are edited or written by Claude.
 38   */
 39  export function notifyVscodeFileUpdated(
 40    filePath: string,
 41    oldContent: string | null,
 42    newContent: string | null,
 43  ): void {
 44    if (process.env.USER_TYPE !== 'ant' || !vscodeMcpClient) {
 45      return
 46    }
 47  
 48    void vscodeMcpClient.client
 49      .notification({
 50        method: 'file_updated',
 51        params: { filePath, oldContent, newContent },
 52      })
 53      .catch((error: Error) => {
 54        // Do not throw if the notification failed
 55        logForDebugging(
 56          `[VSCode] Failed to send file_updated notification: ${error.message}`,
 57        )
 58      })
 59  }
 60  
 61  /**
 62   * Sets up the speicial internal VSCode MCP for bidirectional communication using notifications.
 63   */
 64  export function setupVscodeSdkMcp(sdkClients: MCPServerConnection[]): void {
 65    const client = sdkClients.find(client => client.name === 'claude-vscode')
 66  
 67    if (client && client.type === 'connected') {
 68      // Store the client reference for later use
 69      vscodeMcpClient = client
 70  
 71      client.client.setNotificationHandler(
 72        LogEventNotificationSchema(),
 73        async notification => {
 74          const { eventName, eventData } = notification.params
 75          logEvent(
 76            `tengu_vscode_${eventName}`,
 77            eventData as { [key: string]: boolean | number | undefined },
 78          )
 79        },
 80      )
 81  
 82      // Send necessary experiment gates to VSCode immediately.
 83      const gates: Record<string, boolean | string> = {
 84        tengu_vscode_review_upsell: checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
 85          'tengu_vscode_review_upsell',
 86        ),
 87        tengu_vscode_onboarding: checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
 88          'tengu_vscode_onboarding',
 89        ),
 90        // Browser support.
 91        tengu_quiet_fern: getFeatureValue_CACHED_MAY_BE_STALE(
 92          'tengu_quiet_fern',
 93          false,
 94        ),
 95        // In-band OAuth via claude_authenticate (vs. extension-native PKCE).
 96        tengu_vscode_cc_auth: getFeatureValue_CACHED_MAY_BE_STALE(
 97          'tengu_vscode_cc_auth',
 98          false,
 99        ),
100      }
101      // Tri-state: 'enabled' | 'disabled' | 'opt-in'. Omit if unknown so VSCode
102      // fails closed (treats absent as 'disabled').
103      const autoModeState = readAutoModeEnabledState()
104      if (autoModeState !== undefined) {
105        gates.tengu_auto_mode_state = autoModeState
106      }
107      void client.client.notification({
108        method: 'experiment_gates',
109        params: { gates },
110      })
111    }
112  }