@@ -58,12 +58,12 @@ async function listGitHubDir(
5858 try {
5959 const resp = await ghFetch ( url , token ) ;
6060 if ( ! resp . ok ) return [ ] ;
61- const data = await resp . json ( ) as any ;
61+ const data : unknown = await resp . json ( ) ;
6262 if ( ! Array . isArray ( data ) ) return [ ] ;
63- return data . map ( ( item : any ) => ( {
64- name : item . name ,
65- path : item . path ,
66- type : item . type === 'dir' ? 'dir' : 'file' ,
63+ return data . map ( ( item : Record < string , unknown > ) => ( {
64+ name : String ( item . name ?? '' ) ,
65+ path : String ( item . path ?? '' ) ,
66+ type : ( item . type === 'dir' ? 'dir' : 'file' ) as 'file' | 'dir ',
6767 } ) ) ;
6868 } catch {
6969 return [ ] ;
@@ -81,8 +81,8 @@ async function readGitHubFile(
8181 try {
8282 const resp = await ghFetch ( url , token ) ;
8383 if ( ! resp . ok ) return null ;
84- const data = await resp . json ( ) as any ;
85- if ( data . encoding === 'base64' && data . content ) {
84+ const data = await resp . json ( ) as Record < string , unknown > ;
85+ if ( data . encoding === 'base64' && typeof data . content === 'string' ) {
8686 return atob ( data . content . replace ( / \n / g, '' ) ) ;
8787 }
8888 if ( typeof data . content === 'string' ) return data . content ;
@@ -139,14 +139,14 @@ function validateConfig(raw: unknown): { config: MCPServerConfig | null; error:
139139}
140140
141141function inferConfigFromPackageJson ( pkgRaw : string ) : { config : MCPServerConfig | null ; error : string | null } {
142- let parsed : any ;
142+ let parsed : Record < string , unknown > ;
143143 try {
144- parsed = JSON . parse ( pkgRaw ) ;
144+ parsed = JSON . parse ( pkgRaw ) as Record < string , unknown > ;
145145 } catch {
146146 return { config : null , error : 'package.json is not valid JSON' } ;
147147 }
148148
149- const scripts = parsed ? .scripts ;
149+ const scripts = parsed . scripts as Record < string , unknown > | undefined ;
150150 if ( ! scripts || typeof scripts !== 'object' ) {
151151 return { config : null , error : 'package.json has no scripts' } ;
152152 }
@@ -201,7 +201,7 @@ async function getPackageDescription(
201201 const pkgRaw = await readGitHubFile ( owner , repo , `${ serverPath } /package.json` , branch , ghToken ) ;
202202 if ( ! pkgRaw ) return undefined ;
203203 try {
204- const pkg = JSON . parse ( pkgRaw ) ;
204+ const pkg = JSON . parse ( pkgRaw ) as Record < string , unknown > ;
205205 return typeof pkg . description === 'string' ? pkg . description : undefined ;
206206 } catch {
207207 return undefined ;
@@ -222,8 +222,9 @@ async function probeRemoteEndpoint(url: string): Promise<{ reachable: boolean; d
222222 return { reachable : true , detail : `Responded ${ resp . status } ` } ;
223223 }
224224 return { reachable : false , detail : `HTTP ${ resp . status } ${ resp . statusText } ` } ;
225- } catch ( e : any ) {
226- const msg = e ?. name === 'AbortError' ? 'Timeout (5s)' : ( e ?. message || 'Connection failed' ) ;
225+ } catch ( e : unknown ) {
226+ const err = e instanceof Error ? e : null ;
227+ const msg = err ?. name === 'AbortError' ? 'Timeout (5s)' : ( err ?. message || 'Connection failed' ) ;
227228 return { reachable : false , detail : msg } ;
228229 }
229230}
@@ -418,6 +419,75 @@ export function loadEffectiveMCPConfig(
418419 } ;
419420}
420421
422+ // ---------------------------------------------------------------------------
423+ // Merge config-declared (external) MCP servers
424+ // ---------------------------------------------------------------------------
425+
426+ /**
427+ * Merge externally declared MCP servers (from DA config `mcp-servers` key)
428+ * into a discovery result. Config servers override repo-discovered servers
429+ * on ID conflict. Remote servers are probed for reachability.
430+ */
431+ export async function mergeConfigServers (
432+ discovery : DiscoveredMCP ,
433+ configServers : Record < string , unknown > ,
434+ ) : Promise < DiscoveredMCP > {
435+ const result : DiscoveredMCP = {
436+ ...discovery ,
437+ mcpServers : { ...discovery . mcpServers } ,
438+ warnings : [ ...discovery . warnings ] ,
439+ servers : [ ...discovery . servers ] ,
440+ } ;
441+
442+ for ( const [ id , raw ] of Object . entries ( configServers ) ) {
443+ const idError = validateServerId ( id ) ;
444+ if ( idError ) {
445+ result . warnings . push ( { serverId : id , message : idError } ) ;
446+ result . servers . push ( { id, sourcePath : 'da-config' , status : 'error' , statusDetail : idError } ) ;
447+ continue ;
448+ }
449+
450+ const { config, error } = validateConfig ( raw ) ;
451+ if ( ! config || error ) {
452+ const msg = error ?? 'Invalid MCP server config' ;
453+ result . warnings . push ( { serverId : id , message : msg } ) ;
454+ result . servers . push ( { id, sourcePath : 'da-config' , status : 'error' , statusDetail : msg } ) ;
455+ continue ;
456+ }
457+
458+ // Remove any repo-discovered entry with the same ID (config wins)
459+ result . servers = result . servers . filter ( ( s ) => s . id !== id ) ;
460+
461+ if ( isRemoteConfig ( config ) ) {
462+ const probe = await probeRemoteEndpoint ( config . url ) ;
463+ result . mcpServers [ id ] = config ;
464+ result . servers . push ( {
465+ id,
466+ sourcePath : 'da-config' ,
467+ status : probe . reachable ? 'reachable' : 'unreachable' ,
468+ transport : config . type ,
469+ endpoint : config . url ,
470+ statusDetail : probe . detail ,
471+ } ) ;
472+ } else if ( isStdioConfig ( config ) ) {
473+ result . mcpServers [ id ] = config ;
474+ result . servers . push ( {
475+ id,
476+ sourcePath : 'da-config' ,
477+ status : 'ok' ,
478+ transport : 'stdio' ,
479+ endpoint : `${ config . command } ${ ( config . args ?? [ ] ) . join ( ' ' ) } ` . trim ( ) ,
480+ statusDetail : 'Configured via DA config (stdio — requires local runtime)' ,
481+ } ) ;
482+ } else {
483+ result . mcpServers [ id ] = config ;
484+ result . servers . push ( { id, sourcePath : 'da-config' , status : 'ok' } ) ;
485+ }
486+ }
487+
488+ return result ;
489+ }
490+
421491// ---------------------------------------------------------------------------
422492// Cache I/O via DAAdminClient
423493// ---------------------------------------------------------------------------
@@ -431,7 +501,7 @@ export async function readDiscoveryCache(
431501) : Promise < DiscoveredMCP | null > {
432502 try {
433503 const source = await client . getSource ( org , repo , CACHE_PATH ) ;
434- const raw = typeof source === 'string' ? source : ( source as any ) . content ;
504+ const raw = typeof source === 'string' ? source : ( source as unknown as { content : string } ) . content ;
435505 return JSON . parse ( raw ) as DiscoveredMCP ;
436506 } catch {
437507 return null ;
0 commit comments