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 }