@@ -13,6 +13,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
1313import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' ;
1414import { createServer } from './server/factory.js' ;
1515import { startHttpServer } from './server/http.js' ;
16+ import { loadServerConfig } from './server/config.js' ;
1617import {
1718 CallToolRequestSchema ,
1819 ListToolsRequestSchema ,
@@ -46,6 +47,7 @@ import {
4647 getProjectPathFromContextResourceUri ,
4748 isContextResourceUri
4849} from './resources/uri.js' ;
50+ import { EXCLUDED_GLOB_PATTERNS } from './constants/codebase-context.js' ;
4951import {
5052 discoverProjectsWithinRoot ,
5153 findNearestProjectBoundary ,
@@ -102,6 +104,8 @@ function resolveRootPath(): string | undefined {
102104const primaryRootPath = resolveRootPath ( ) ;
103105const toolNames = new Set ( TOOLS . map ( ( tool ) => tool . name ) ) ;
104106const knownRoots = new Map < string , { rootPath : string ; label ? : string } > ( ) ;
107+ /** Roots loaded from config file — preserved across syncKnownRoots() refreshes. */
108+ const configRoots = new Map < string , { rootPath : string } > ( ) ;
105109const discoveredProjectPaths = new Map < string , string > ( ) ;
106110let clientRootsEnabled = false ;
107111const projectSourcesByKey = new Map < string , ProjectDescriptor [ 'source' ] > ( ) ;
@@ -337,6 +341,13 @@ function syncKnownRoots(rootEntries: Array<{ rootPath: string; label?: string }>
337341 } ) ;
338342 }
339343
344+ // Always include config-registered roots — config is additive (REPO-03)
345+ for ( const [ rootKey , rootEntry ] of configRoots . entries ( ) ) {
346+ if ( ! nextRoots . has ( rootKey ) ) {
347+ nextRoots . set ( rootKey , rootEntry ) ;
348+ }
349+ }
350+
340351 for ( const [ rootKey , existingRoot ] of knownRoots . entries ( ) ) {
341352 if ( ! nextRoots . has ( rootKey ) ) {
342353 removeProject ( existingRoot . rootPath ) ;
@@ -1240,6 +1251,9 @@ async function performIndexingOnce(
12401251 let lastLoggedProgress = { phase : '' , percentage : - 1 } ;
12411252 const indexer = new CodebaseIndexer ( {
12421253 rootPath : project . rootPath ,
1254+ ...( project . extraExcludePatterns ?. length
1255+ ? { config : { exclude : [ ...EXCLUDED_GLOB_PATTERNS , ...project . extraExcludePatterns ] } }
1256+ : { } ) ,
12431257 incrementalOnly,
12441258 onProgress : ( progress ) => {
12451259 // Only log when phase or percentage actually changes (prevents duplicate logs)
@@ -1587,7 +1601,33 @@ async function initProject(
15871601 }
15881602}
15891603
1604+ async function applyServerConfig (
1605+ serverConfig : Awaited < ReturnType < typeof loadServerConfig >>
1606+ ) : Promise < void > {
1607+ for ( const proj of serverConfig ?. projects ?? [ ] ) {
1608+ try {
1609+ const stats = await fs . stat ( proj . root ) ;
1610+ if ( ! stats . isDirectory ( ) ) {
1611+ console . error ( `[config] Skipping non-directory project root: ${ proj . root } ` ) ;
1612+ continue ;
1613+ }
1614+ const rootKey = normalizeRootKey ( proj . root ) ;
1615+ configRoots . set ( rootKey , { rootPath : proj . root } ) ;
1616+ registerKnownRoot ( proj . root ) ;
1617+ if ( proj . excludePatterns ?. length ) {
1618+ const project = getOrCreateProject ( proj . root ) ;
1619+ project . extraExcludePatterns = proj . excludePatterns ;
1620+ }
1621+ } catch {
1622+ console . error ( `[config] Skipping inaccessible project root: ${ proj . root } ` ) ;
1623+ }
1624+ }
1625+ }
1626+
15901627async function main ( ) {
1628+ const serverConfig = await loadServerConfig ( ) ;
1629+ await applyServerConfig ( serverConfig ) ;
1630+
15911631 if ( primaryRootPath ) {
15921632 // Validate bootstrap root path exists and is a directory when explicitly configured.
15931633 try {
@@ -1711,7 +1751,18 @@ export { performIndexing };
17111751 * Each connecting MCP client gets its own Server+Transport pair,
17121752 * sharing the same module-level project state.
17131753 */
1714- async function startHttp ( port : number ) : Promise < void > {
1754+ async function startHttp ( explicitPort ? : number ) : Promise < void > {
1755+ const serverConfig = await loadServerConfig ( ) ;
1756+ await applyServerConfig ( serverConfig ) ;
1757+
1758+ // Port resolution priority: CLI flag > env var > config file > built-in default (3100)
1759+ const portFromEnv = process . env . CODEBASE_CONTEXT_PORT
1760+ ? Number . parseInt ( process . env . CODEBASE_CONTEXT_PORT , 10 )
1761+ : undefined ;
1762+ const resolvedEnvPort = portFromEnv && Number . isFinite ( portFromEnv ) ? portFromEnv : undefined ;
1763+ const port = explicitPort ?? resolvedEnvPort ?? serverConfig ?. server ?. port ?? 3100 ;
1764+ const host = serverConfig ?. server ?. host ?? '127.0.0.1' ;
1765+
17151766 // Validate bootstrap root the same way main() does
17161767 if ( primaryRootPath ) {
17171768 try {
@@ -1730,6 +1781,7 @@ async function startHttp(port: number): Promise<void> {
17301781 name : 'codebase-context' ,
17311782 version : PKG_VERSION ,
17321783 port,
1784+ host,
17331785 registerHandlers,
17341786 onSessionReady : ( sessionServer ) => {
17351787 // Per-session roots change handler
@@ -1803,20 +1855,14 @@ if (isDirectRun) {
18031855 const httpFlag = process . argv . includes ( '--http' ) || process . env . CODEBASE_CONTEXT_HTTP === '1' ;
18041856
18051857 if ( httpFlag ) {
1858+ // Extract only the CLI flag value. Env var, config, and default
1859+ // are resolved inside startHttp() in priority order: flag > env > config > 3100.
18061860 const portFlagIdx = process . argv . indexOf ( '--port' ) ;
18071861 const portFromFlag =
18081862 portFlagIdx !== - 1 ? Number . parseInt ( process . argv [ portFlagIdx + 1 ] , 10 ) : undefined ;
1809- const portFromEnv = process . env . CODEBASE_CONTEXT_PORT
1810- ? Number . parseInt ( process . env . CODEBASE_CONTEXT_PORT , 10 )
1811- : undefined ;
1812- const port =
1813- portFromFlag && Number . isFinite ( portFromFlag )
1814- ? portFromFlag
1815- : portFromEnv && Number . isFinite ( portFromEnv )
1816- ? portFromEnv
1817- : 3100 ;
1818-
1819- startHttp ( port ) . catch ( ( error ) => {
1863+ const explicitPort = portFromFlag && Number . isFinite ( portFromFlag ) ? portFromFlag : undefined ;
1864+
1865+ startHttp ( explicitPort ) . catch ( ( error ) => {
18201866 console . error ( 'Fatal:' , error ) ;
18211867 process . exit ( 1 ) ;
18221868 } ) ;
0 commit comments