/ src / renderer / utils / index.ts
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  }