index.ts
1 import type { McpServerApi } from '@/renderer/store/mcp' 2 import { useDxtStore } from '@/renderer/store/dxt' 3 4 import { 5 IpcSamplingRequestCallback, 6 IpcElicitRequestCallback, 7 IpcCommandRequestCallback, 8 IpcMcpInitRequestCallback, 9 SamplingResponse, 10 ElicitResponse, 11 CommandResponse, 12 McpInitResponse, 13 IpcFileTransferRequest, 14 IpcFileTransferResponse 15 } from '@/types/ipc' 16 17 function isValidValue(value: any): boolean { 18 if (value === null || value === undefined) return false 19 if (typeof value === 'string' && value.trim() === '') return false 20 if (Array.isArray(value) && value.length === 0) return false 21 return true 22 } 23 24 export default class Utils { 25 static getCurrentLocale(): string { 26 return navigator?.language?.split('-')[0] || 'en' 27 } 28 29 static async getAppInfo(): Promise<any> { 30 return window.mainApi.invoke('msgRequestAppInfo') 31 } 32 33 static async getApiToken(cli: string): Promise<string> { 34 return window.mainApi.invoke('msgGetApiToken', cli) 35 } 36 37 static async getDxtUrl(): Promise<any> { 38 return window.mainApi.invoke('msgRequestGetDxtUrl') 39 } 40 41 static async listenStdioProgress(progress: any): Promise<any> { 42 return window.mainApi.once('renderListenStdioProgress', progress) 43 } 44 45 static async removeListenStdioProgress(progress: any): Promise<any> { 46 return window.mainApi.removeListener('renderListenStdioProgress', progress) 47 } 48 49 static async openExternal(url: string): Promise<void> { 50 await window.mainApi.send('msgOpenExternalLink', url) 51 } 52 53 static async openDxtFilePath(name: string): Promise<void> { 54 await window.mainApi.send('msgOpenDxtFilePath', name) 55 } 56 57 static async openPath(name: string): Promise<void> { 58 await window.mainApi.send('msgOpenPath', name) 59 } 60 61 static async windowReload(): Promise<void> { 62 await window.mainApi.send('msgWindowReload') 63 } 64 65 static async openFile(type: string): Promise<any> { 66 return window.mainApi.invoke('msgOpenFile', type) 67 } 68 } 69 70 export const { 71 getCurrentLocale, 72 openExternal, 73 getAppInfo, 74 openDxtFilePath, 75 openPath, 76 openFile, 77 getApiToken, 78 getDxtUrl, 79 listenStdioProgress, 80 removeListenStdioProgress, 81 windowReload 82 } = Utils 83 84 class Sampling { 85 static async msgSamplingTransferInvoke(callback: IpcSamplingRequestCallback): Promise<void> { 86 return window.mainApi.on('msgSamplingTransferInvoke', callback) 87 } 88 89 // Channel format: "msgSamplingTransferResult-uuid4()" 90 static async msgSamplingTransferResult( 91 channel: string, 92 response: SamplingResponse 93 ): Promise<void> { 94 console.log(channel, response) 95 window.mainApi.send(channel, response) 96 } 97 } 98 99 export const SamplingTransfer = { 100 request: Sampling.msgSamplingTransferInvoke, 101 response: Sampling.msgSamplingTransferResult 102 } 103 104 class Elicitation { 105 static async msgElicitationTransferInvoke(callback: IpcElicitRequestCallback): Promise<void> { 106 return window.mainApi.on('msgElicitationTransferInvoke', callback) 107 } 108 109 // Channel format: "msgElicitationTransferResult-uuid4()" 110 static async msgElicitationTransferResult( 111 channel: string, 112 response: ElicitResponse 113 ): Promise<void> { 114 await window.mainApi.send(channel, response) 115 } 116 } 117 118 export const ElicitationTransfer = { 119 request: Elicitation.msgElicitationTransferInvoke, 120 response: Elicitation.msgElicitationTransferResult 121 } 122 123 class File { 124 static async sendFileToMainRequest(file: IpcFileTransferRequest): Promise<void> { 125 await window.mainApi.send('msgFileTransferRequest', file) 126 } 127 static async sendFileToMainResponse(count: number): Promise<IpcFileTransferResponse[]> { 128 const results: IpcFileTransferResponse[] = [] 129 let completedCount = 0 130 window.mainApi.on( 131 'msgFileTransferResponse', 132 (_event: Event, result: IpcFileTransferResponse) => { 133 results.push(result) 134 completedCount++ 135 if (completedCount === count) { 136 console.log('All done', results) 137 } 138 } 139 ) 140 return results 141 } 142 } 143 144 export const FileTransfer = { 145 request: File.sendFileToMainRequest, 146 response: File.sendFileToMainResponse 147 } 148 149 class Command { 150 static async msgCommandSelectionInvoke(callback: IpcCommandRequestCallback): Promise<void> { 151 return window.mainApi.on('msgCommandSelectionInvoke', callback) 152 } 153 154 static async msgCommandSelectionResult(response: CommandResponse): Promise<void> { 155 await window.mainApi.send('msgCommandSelectionResult', response) 156 } 157 } 158 159 export const CommandEvent = { 160 request: Command.msgCommandSelectionInvoke, 161 response: Command.msgCommandSelectionResult 162 } 163 164 class Mcp { 165 static async msgMcpServersInit(configs: McpServerApi): Promise<McpInitResponse | undefined> { 166 if (!configs) return 167 const dxtStore = useDxtStore() 168 const filteredConfigs = Object.fromEntries( 169 Object.entries(configs).map(([key, config]) => { 170 const mcpMetadata = config?.metadata 171 172 // Only dxt manifest need include user_config 173 if ( 174 !mcpMetadata || 175 mcpMetadata.type !== 'metadata__mcpb_manifest' || 176 !('user_config' in mcpMetadata.config) 177 ) { 178 return [key, mcpMetadata] 179 } 180 181 const userConfigObj = mcpMetadata.config.user_config 182 const userConfig = dxtStore.getConfig(key) 183 184 if (!userConfigObj || !userConfig) { 185 return [key, mcpMetadata] 186 } 187 188 const mergedConfig = Object.fromEntries( 189 Object.entries(userConfigObj).map(([configKey, configVal]) => { 190 const userValue = userConfig[configKey] 191 192 const value = isValidValue(userValue) ? userValue : configVal.default 193 return [configKey, value] 194 }) 195 ) 196 197 const filteredUserConfig = Object.fromEntries( 198 Object.entries(mergedConfig).filter(([, value]) => { 199 return isValidValue(value) 200 }) 201 ) 202 203 const mergedMetadata = { 204 ...mcpMetadata, 205 user_config: filteredUserConfig 206 } 207 return [key, mergedMetadata] 208 }) 209 ) 210 211 console.log(filteredConfigs) 212 return window.mainApi.invoke('msgMcpServersInit', filteredConfigs) 213 } 214 215 static async msgMcpServersStop(): Promise<void> { 216 return window.mainApi.invoke('msgMcpServersStop') 217 } 218 219 static async msgMcpServersWatch(callback: IpcMcpInitRequestCallback): Promise<void> { 220 return window.mainApi.on('msgMcpServersWatch', callback) 221 } 222 } 223 224 export const McpEvent = { 225 init: Mcp.msgMcpServersInit, 226 stop: Mcp.msgMcpServersStop, 227 watch: Mcp.msgMcpServersWatch 228 }