@@ -19,6 +19,8 @@ export type McpConfigResult =
1919 | { kind : 'file' ; path : string ; content : string }
2020 | { kind : 'command' ; args : string [ ] } ;
2121
22+ type JsonObject = Record < string , unknown > ;
23+
2224export function generateMcpConfig ( client : Client ) : McpConfigResult {
2325 switch ( client ) {
2426 case 'claude-code' :
@@ -121,6 +123,70 @@ export async function _appendInstructionBlock(filePath: string): Promise<'writte
121123 return 'written' ;
122124}
123125
126+ function isJsonObject ( value : unknown ) : value is JsonObject {
127+ return typeof value === 'object' && value !== null && ! Array . isArray ( value ) ;
128+ }
129+
130+ /**
131+ * Merge generated MCP config into an existing JSON config file when possible.
132+ * Preserves unrelated keys and only updates the codebase-context server entry.
133+ */
134+ export async function _buildMergedMcpContent (
135+ filePath : string ,
136+ generatedContent : string ,
137+ client : Extract < Client , 'cursor' | 'opencode' >
138+ ) : Promise < { content : string ; mergedFromExisting : boolean } > {
139+ let existing : unknown ;
140+ try {
141+ existing = JSON . parse ( await fs . readFile ( filePath , 'utf8' ) ) ;
142+ } catch {
143+ return { content : generatedContent , mergedFromExisting : false } ;
144+ }
145+
146+ const generated = JSON . parse ( generatedContent ) as unknown ;
147+ if ( ! isJsonObject ( existing ) || ! isJsonObject ( generated ) ) {
148+ return { content : generatedContent , mergedFromExisting : false } ;
149+ }
150+
151+ if ( client === 'cursor' ) {
152+ const existingServers = isJsonObject ( existing . mcpServers ) ? existing . mcpServers : { } ;
153+ const generatedServers = isJsonObject ( generated . mcpServers ) ? generated . mcpServers : { } ;
154+ return {
155+ content : JSON . stringify (
156+ {
157+ ...existing ,
158+ ...generated ,
159+ mcpServers : {
160+ ...existingServers ,
161+ ...generatedServers
162+ }
163+ } ,
164+ null ,
165+ 2
166+ ) ,
167+ mergedFromExisting : true
168+ } ;
169+ }
170+
171+ const existingMcp = isJsonObject ( existing . mcp ) ? existing . mcp : { } ;
172+ const generatedMcp = isJsonObject ( generated . mcp ) ? generated . mcp : { } ;
173+ return {
174+ content : JSON . stringify (
175+ {
176+ ...existing ,
177+ ...generated ,
178+ mcp : {
179+ ...existingMcp ,
180+ ...generatedMcp
181+ }
182+ } ,
183+ null ,
184+ 2
185+ ) ,
186+ mergedFromExisting : true
187+ } ;
188+ }
189+
124190export async function handleInitCli ( _argv : string [ ] ) : Promise < void > {
125191 console . log ( '\nSet up codebase-context for your AI client\n' ) ;
126192
@@ -138,6 +204,12 @@ export async function handleInitCli(_argv: string[]): Promise<void> {
138204
139205 console . log ( '\n--- MCP Config Preview ---' ) ;
140206 if ( mcpResult . kind === 'file' ) {
207+ try {
208+ await fs . access ( mcpResult . path ) ;
209+ console . log ( `Warning: ${ mcpResult . path } already exists; existing entries will be preserved.` ) ;
210+ } catch {
211+ // file does not exist
212+ }
141213 console . log ( `File: ${ mcpResult . path } \n${ mcpResult . content } ` ) ;
142214 } else {
143215 console . log ( `Command to run: ${ mcpResult . args [ 0 ] } ${ mcpResult . args . slice ( 1 ) . join ( ' ' ) } ` ) ;
@@ -181,7 +253,14 @@ export async function handleInitCli(_argv: string[]): Promise<void> {
181253 if ( dir && dir !== '.' ) {
182254 await fs . mkdir ( dir , { recursive : true } ) ;
183255 }
184- await fs . writeFile ( mcpResult . path , mcpResult . content , 'utf8' ) ;
256+ const mergedConfig =
257+ client === 'cursor' || client === 'opencode'
258+ ? await _buildMergedMcpContent ( mcpResult . path , mcpResult . content , client )
259+ : { content : mcpResult . content , mergedFromExisting : false } ;
260+ await fs . writeFile ( mcpResult . path , mergedConfig . content , 'utf8' ) ;
261+ if ( mergedConfig . mergedFromExisting ) {
262+ console . log ( `Merged: ${ mcpResult . path } (existing entries preserved)` ) ;
263+ }
185264 console . log ( `Written: ${ mcpResult . path } ` ) ;
186265 } else {
187266 const [ cmd , ...rest ] = mcpResult . args ;
0 commit comments