1
1
import Store from 'electron-store'
2
2
import log from '../logger'
3
+ import { getToolhivePort } from '../toolhive-manager'
4
+ import { createClient } from '@api/client'
5
+ import { getApiV1BetaWorkloads } from '@api/sdk.gen'
6
+ import type { CoreWorkload } from '@api/types.gen'
7
+ import { getHeaders } from '../headers'
8
+ import { getTearingDownState } from '../app-state'
9
+
10
+ // Chat store types
11
+ interface ChatSettingsProvider {
12
+ apiKey : string
13
+ enabledTools : string [ ]
14
+ }
15
+
16
+ interface ChatSettingsSelectedModel {
17
+ provider : string
18
+ model : string
19
+ }
20
+
21
+ interface ChatSettings {
22
+ providers : Record < string , ChatSettingsProvider >
23
+ selectedModel : ChatSettingsSelectedModel
24
+ enabledMcpTools : Record < string , string [ ] > // serverName -> [toolName1, toolName2]
25
+ }
3
26
4
27
// Type guard functions
5
28
function isRecord ( value : unknown ) : value is Record < string , unknown > {
@@ -10,9 +33,7 @@ function isStringArray(value: unknown): value is string[] {
10
33
return Array . isArray ( value ) && value . every ( ( item ) => typeof item === 'string' )
11
34
}
12
35
13
- function isProvidersRecord (
14
- value : unknown
15
- ) : value is Record < string , ChatSettings > {
36
+ function isProvidersRecord ( value : unknown ) : value is ChatSettings [ 'providers' ] {
16
37
if ( ! isRecord ( value ) ) return false
17
38
return Object . values ( value ) . every (
18
39
( item ) =>
@@ -22,14 +43,14 @@ function isProvidersRecord(
22
43
)
23
44
}
24
45
25
- function isToolsRecord ( value : unknown ) : value is Record < string , string [ ] > {
46
+ function isToolsRecord (
47
+ value : unknown
48
+ ) : value is ChatSettings [ 'enabledMcpTools' ] {
26
49
if ( ! isRecord ( value ) ) return false
27
50
return Object . values ( value ) . every ( ( item ) => isStringArray ( item ) )
28
51
}
29
52
30
- function isSelectedModel (
31
- value : unknown
32
- ) : value is { provider : string ; model : string } {
53
+ function isSelectedModel ( value : unknown ) : value is ChatSettingsSelectedModel {
33
54
return (
34
55
isRecord ( value ) &&
35
56
typeof value . provider === 'string' &&
@@ -38,34 +59,21 @@ function isSelectedModel(
38
59
}
39
60
40
61
// Create a secure store for chat settings (API keys and model selection)
41
- const chatStore = new Store ( {
62
+ const chatStore = new Store < ChatSettings > ( {
42
63
name : 'chat-settings' ,
43
64
encryptionKey : 'toolhive-chat-encryption-key' , // Basic encryption for API keys
44
65
defaults : {
45
- providers : { } as Record <
46
- string ,
47
- {
48
- apiKey : string
49
- enabledTools : string [ ]
50
- }
51
- > ,
66
+ providers : { } ,
52
67
selectedModel : {
53
68
provider : '' ,
54
69
model : '' ,
55
70
} ,
56
- // Individual tool enablement per server (single source of truth)
57
- enabledMcpTools : { } as Record < string , string [ ] > , // serverName -> [toolName1, toolName2]
71
+ enabledMcpTools : { } ,
58
72
} ,
59
73
} )
60
74
61
- // Chat settings interface
62
- interface ChatSettings {
63
- apiKey : string
64
- enabledTools : string [ ]
65
- }
66
-
67
75
// Get chat settings for a provider
68
- export function getChatSettings ( providerId : string ) : ChatSettings {
76
+ export function getChatSettings ( providerId : string ) : ChatSettingsProvider {
69
77
try {
70
78
const providers = chatStore . get ( 'providers' )
71
79
if ( isProvidersRecord ( providers ) ) {
@@ -81,7 +89,7 @@ export function getChatSettings(providerId: string): ChatSettings {
81
89
// Save chat settings for a provider
82
90
export function saveChatSettings (
83
91
providerId : string ,
84
- settings : ChatSettings
92
+ settings : ChatSettingsProvider
85
93
) : { success : boolean ; error ?: string } {
86
94
try {
87
95
const providers = chatStore . get ( 'providers' )
@@ -126,7 +134,7 @@ export function clearChatSettings(providerId?: string): {
126
134
}
127
135
128
136
// Get selected model
129
- export function getSelectedModel ( ) : { provider : string ; model : string } {
137
+ export function getSelectedModel ( ) : ChatSettingsSelectedModel {
130
138
try {
131
139
const selectedModel = chatStore . get ( 'selectedModel' )
132
140
if (
@@ -159,20 +167,6 @@ export function saveSelectedModel(
159
167
}
160
168
}
161
169
162
- // Get enabled MCP tools for a specific server
163
- // function getEnabledMcpToolsForServer(serverName: string): string[] {
164
- // try {
165
- // const enabledMcpTools = chatStore.get('enabledMcpTools')
166
- // if (isToolsRecord(enabledMcpTools)) {
167
- // return enabledMcpTools[serverName] || []
168
- // }
169
- // return []
170
- // } catch (error) {
171
- // log.error('Failed to get enabled MCP tools:', error)
172
- // return []
173
- // }
174
- // }
175
-
176
170
// Save enabled MCP tools for a server
177
171
export function saveEnabledMcpTools (
178
172
serverName : string ,
@@ -193,24 +187,87 @@ export function saveEnabledMcpTools(
193
187
}
194
188
}
195
189
196
- // Get all enabled MCP tools (global)
197
- export function getEnabledMcpTools ( ) : Record < string , string [ ] > {
190
+ // Get all enabled MCP tools (global) - filters out tools from stopped servers
191
+ export async function getEnabledMcpTools ( ) : Promise <
192
+ ChatSettings [ 'enabledMcpTools' ]
193
+ > {
198
194
try {
199
195
const enabledMcpTools = chatStore . get ( 'enabledMcpTools' )
200
- if ( isToolsRecord ( enabledMcpTools ) ) {
196
+ if ( ! isToolsRecord ( enabledMcpTools ) ) {
197
+ return { }
198
+ }
199
+
200
+ // Skip validation during shutdown to prevent interrupting teardown
201
+ if ( getTearingDownState ( ) ) {
202
+ log . debug ( 'Skipping MCP tools validation during teardown' )
203
+ return enabledMcpTools
204
+ }
205
+
206
+ // Get running servers to filter out tools from stopped servers
207
+ const port = getToolhivePort ( )
208
+
209
+ if ( ! port ) {
210
+ // If ToolHive is not running, return stored tools without validation
211
+ return enabledMcpTools
212
+ }
213
+
214
+ try {
215
+ const client = createClient ( {
216
+ baseUrl : `http://localhost:${ port } ` ,
217
+ headers : getHeaders ( ) ,
218
+ } )
219
+
220
+ const { data } = await getApiV1BetaWorkloads ( {
221
+ client,
222
+ query : { all : true } ,
223
+ } )
224
+
225
+ const runningServerNames = ( data ?. workloads || [ ] )
226
+ . filter ( ( w : CoreWorkload ) => w . status === 'running' )
227
+ . map ( ( w : CoreWorkload ) => w . name )
228
+
229
+ // Filter enabled tools to only include tools from running servers
230
+ const filteredTools : ChatSettings [ 'enabledMcpTools' ] = { }
231
+ const serversToRemove : string [ ] = [ ]
232
+
233
+ for ( const [ serverName , tools ] of Object . entries ( enabledMcpTools ) ) {
234
+ if ( runningServerNames . includes ( serverName ) ) {
235
+ filteredTools [ serverName ] = tools
236
+ } else if ( tools . length > 0 ) {
237
+ // Only log if server actually had tools to clean up
238
+ log . info ( `Cleaning up tools for stopped server: ${ serverName } ` )
239
+ serversToRemove . push ( serverName )
240
+ }
241
+ }
242
+
243
+ // Remove stopped servers from storage in one operation
244
+ if ( serversToRemove . length > 0 ) {
245
+ const updatedTools = { ...enabledMcpTools }
246
+ for ( const serverName of serversToRemove ) {
247
+ delete updatedTools [ serverName ]
248
+ }
249
+ chatStore . set ( 'enabledMcpTools' , updatedTools )
250
+ }
251
+
252
+ return filteredTools
253
+ } catch ( apiError ) {
254
+ log . warn (
255
+ 'Failed to check running servers during shutdown, returning stored tools:' ,
256
+ apiError
257
+ )
258
+ // During shutdown, just return stored tools without validation
201
259
return enabledMcpTools
202
260
}
203
- return { }
204
261
} catch ( error ) {
205
262
log . error ( 'Failed to get all enabled MCP tools:' , error )
206
263
return { }
207
264
}
208
265
}
209
266
210
267
// Get enabled MCP servers from tools (get servers that have enabled tools)
211
- export function getEnabledMcpServersFromTools ( ) : string [ ] {
268
+ export async function getEnabledMcpServersFromTools ( ) : Promise < string [ ] > {
212
269
try {
213
- const allEnabledTools = getEnabledMcpTools ( )
270
+ const allEnabledTools = await getEnabledMcpTools ( )
214
271
const enabledServerNames = Object . keys ( allEnabledTools ) . filter (
215
272
( serverName ) => {
216
273
const tools = allEnabledTools [ serverName ]
0 commit comments