@@ -4,6 +4,7 @@ import type { AppSettings } from '../../shared/settings.js';
44import type { TransportInfo } from '../../shared/transport-info.js' ;
55import { BOUQUET_FALLBACK } from '../mcp-server.js' ;
66import { ALL_BUILTIN_TOOL_IDS } from '@llmindset/hf-mcp' ;
7+ import { normalizeBuiltInTools } from '../../shared/tool-normalizer.js' ;
78import { apiMetrics } from '../utils/api-metrics.js' ;
89export interface ToolStateChangeCallback {
910 ( toolId : string , enabled : boolean ) : void ;
@@ -27,6 +28,14 @@ export interface ApiClientConfig {
2728 staticGradioEndpoints ?: GradioEndpoint [ ] ;
2829}
2930
31+ function withNormalizedFlags ( settings : AppSettings ) : AppSettings {
32+ const normalizedBuiltInTools = normalizeBuiltInTools ( settings . builtInTools ) ;
33+ const isIdentical =
34+ normalizedBuiltInTools . length === settings . builtInTools . length &&
35+ normalizedBuiltInTools . every ( ( value , index ) => value === settings . builtInTools [ index ] ) ;
36+ return isIdentical ? settings : { ...settings , builtInTools : normalizedBuiltInTools } ;
37+ }
38+
3039export class McpApiClient extends EventEmitter {
3140 private config : ApiClientConfig ;
3241 private pollTimer : NodeJS . Timeout | null = null ;
@@ -56,32 +65,32 @@ export class McpApiClient extends EventEmitter {
5665 case 'polling' :
5766 if ( ! this . config . baseUrl ) {
5867 logger . error ( 'baseUrl required for polling mode' ) ;
59- return BOUQUET_FALLBACK ;
68+ return withNormalizedFlags ( BOUQUET_FALLBACK ) ;
6069 }
6170 try {
6271 const response = await fetch ( `${ this . config . baseUrl } /api/settings` ) ;
6372 if ( ! response . ok ) {
6473 logger . error ( `Failed to fetch settings: ${ response . status . toString ( ) } ${ response . statusText } ` ) ;
65- return BOUQUET_FALLBACK ;
74+ return withNormalizedFlags ( BOUQUET_FALLBACK ) ;
6675 }
67- return ( await response . json ( ) ) as AppSettings ;
76+ return withNormalizedFlags ( ( await response . json ( ) ) as AppSettings ) ;
6877 } catch ( error ) {
6978 logger . error ( { error } , 'Error fetching settings from local API' ) ;
70- return BOUQUET_FALLBACK ;
79+ return withNormalizedFlags ( BOUQUET_FALLBACK ) ;
7180 }
7281
7382 case 'external' :
7483 if ( ! this . config . externalUrl ) {
7584 logger . error ( 'externalUrl required for external mode' ) ;
76- return BOUQUET_FALLBACK ;
85+ return withNormalizedFlags ( BOUQUET_FALLBACK ) ;
7786 }
7887 try {
7988 const token = overrideToken || this . config . hfToken ;
8089 if ( ! token || token . trim ( ) === '' ) {
8190 // Record anonymous access (successful fallback usage)
8291 apiMetrics . recordCall ( false , 200 ) ;
8392 logger . debug ( 'No HF token available for external config API - using fallback' ) ;
84- return BOUQUET_FALLBACK ;
93+ return withNormalizedFlags ( BOUQUET_FALLBACK ) ;
8594 }
8695
8796 const headers : Record < string , string > = { } ;
@@ -114,20 +123,20 @@ export class McpApiClient extends EventEmitter {
114123 logger . debug (
115124 `Failed to fetch external settings: ${ response . status . toString ( ) } ${ response . statusText } - using fallback bouquet`
116125 ) ;
117- return BOUQUET_FALLBACK ;
126+ return withNormalizedFlags ( BOUQUET_FALLBACK ) ;
118127 }
119128
120129 // Record metrics for successful responses
121130 apiMetrics . recordCall ( hasToken , response . status ) ;
122- return ( await response . json ( ) ) as AppSettings ;
131+ return withNormalizedFlags ( ( await response . json ( ) ) as AppSettings ) ;
123132 } catch ( error ) {
124133 logger . warn ( { error } , 'Error fetching settings from external API - defaulting to fallback bouquet' ) ;
125- return BOUQUET_FALLBACK ;
134+ return withNormalizedFlags ( BOUQUET_FALLBACK ) ;
126135 }
127136
128137 default :
129138 logger . error ( `Unknown API client type: ${ String ( this . config . type ) } ` ) ;
130- return BOUQUET_FALLBACK ;
139+ return withNormalizedFlags ( BOUQUET_FALLBACK ) ;
131140 }
132141 }
133142
@@ -149,20 +158,20 @@ export class McpApiClient extends EventEmitter {
149158 logger . trace ( { gradioEndpoints : this . gradioEndpoints } , 'Updated gradio endpoints from external API' ) ;
150159 }
151160
152- // Create tool states: enabled tools = true, rest = false
153- const toolStates : Record < string , boolean > = { } ;
154- for ( const toolId of ALL_BUILTIN_TOOL_IDS ) {
155- toolStates [ toolId ] = settings . builtInTools . includes ( toolId ) ;
156- }
157-
158- // Include virtual/behavior flags that aren't real tools (e.g., INCLUDE_README )
159- // Anything present in builtInTools but not in ALL_BUILTIN_TOOL_IDS is treated as an enabled flag.
160- for ( const id of settings . builtInTools ) {
161- if ( ! ( id in toolStates ) ) {
162- toolStates [ id ] = true ;
163- }
164- }
165- return toolStates ;
161+ // Create tool states: enabled tools = true, rest = false
162+ const toolStates : Record < string , boolean > = { } ;
163+ for ( const toolId of ALL_BUILTIN_TOOL_IDS ) {
164+ toolStates [ toolId ] = settings . builtInTools . includes ( toolId ) ;
165+ }
166+
167+ // Include virtual/behavior flags that aren't real tools (e.g., ALLOW_README_INCLUDE )
168+ // Anything present in builtInTools but not in ALL_BUILTIN_TOOL_IDS is treated as an enabled flag.
169+ for ( const id of settings . builtInTools ) {
170+ if ( ! ( id in toolStates ) ) {
171+ toolStates [ id ] = true ;
172+ }
173+ }
174+ return toolStates ;
166175 }
167176
168177 getGradioEndpoints ( ) : GradioEndpoint [ ] {
0 commit comments