@@ -3507,7 +3507,8 @@ function requireProjectPath(projectPath: string | undefined, ctx: string): strin
35073507function toServerConfig ( server : Partial < McpServer > ) : UnknownRecord {
35083508 // Check if this is an HTTP server
35093509 if ( server . transport === 'http' ) {
3510- const config : UnknownRecord = { url : server . url } ;
3510+ const url = 'url' in server && typeof server . url === 'string' ? server . url : '' ;
3511+ const config : UnknownRecord = { url } ;
35113512
35123513 // Claude format: type field
35133514 config . type = 'http' ;
@@ -3538,23 +3539,60 @@ function toServerConfig(server: Partial<McpServer>): UnknownRecord {
35383539 // STDIO server (default)
35393540 const config : UnknownRecord = { } ;
35403541
3541- if ( typeof server . command === 'string' ) {
3542- config . command = server . command ;
3543- }
3542+ if ( 'command' in server && typeof server . command === 'string' ) config . command = server . command ;
3543+ if ( 'args' in server && Array . isArray ( server . args ) && server . args . length > 0 ) config . args = server . args ;
3544+ if ( 'env' in server && server . env && Object . keys ( server . env ) . length > 0 ) config . env = server . env ;
3545+ if ( 'cwd' in server && typeof server . cwd === 'string' && server . cwd . trim ( ) ) config . cwd = server . cwd ;
35443546
3545- if ( server . args && server . args . length > 0 ) {
3546- config . args = server . args ;
3547- }
3547+ return config ;
3548+ }
35483549
3549- if ( server . env && Object . keys ( server . env ) . length > 0 ) {
3550- config . env = server . env ;
3551- }
3550+ function _buildFallbackServer ( serverName : string , config : Partial < McpServer > ) : McpServer {
3551+ const transport = config . transport ?? 'stdio' ;
3552+ const enabled = config . enabled ?? true ;
3553+ const scope = config . scope ?? 'project' ;
35523554
3553- if ( server . cwd ) {
3554- config . cwd = server . cwd ;
3555+ if ( transport === 'http' ) {
3556+ const url = 'url' in config && typeof config . url === 'string' ? config . url : '' ;
3557+ return {
3558+ name : serverName ,
3559+ transport : 'http' ,
3560+ url,
3561+ enabled,
3562+ scope,
3563+ } ;
35553564 }
35563565
3557- return config ;
3566+ const command =
3567+ 'command' in config && typeof config . command === 'string'
3568+ ? config . command
3569+ : '' ;
3570+
3571+ const args =
3572+ 'args' in config && Array . isArray ( config . args )
3573+ ? config . args
3574+ : undefined ;
3575+
3576+ const env =
3577+ 'env' in config && config . env && typeof config . env === 'object'
3578+ ? ( config . env as Record < string , string > )
3579+ : undefined ;
3580+
3581+ const cwd =
3582+ 'cwd' in config && typeof config . cwd === 'string'
3583+ ? config . cwd
3584+ : undefined ;
3585+
3586+ return {
3587+ name : serverName ,
3588+ transport : 'stdio' ,
3589+ command,
3590+ args,
3591+ env,
3592+ cwd,
3593+ enabled,
3594+ scope,
3595+ } ;
35583596}
35593597
35603598/**
@@ -3572,12 +3610,14 @@ export async function updateMcpServer(
35723610
35733611 // Validate based on transport type
35743612 if ( config . transport === 'http' ) {
3575- if ( typeof config . url !== 'string' || ! config . url . trim ( ) ) {
3613+ const url = 'url' in config ? config . url : undefined ;
3614+ if ( typeof url !== 'string' || ! url . trim ( ) ) {
35763615 throw new Error ( 'updateMcpServer: url is required for HTTP servers' ) ;
35773616 }
35783617 } else {
35793618 // STDIO server (default)
3580- if ( typeof config . command !== 'string' || ! config . command . trim ( ) ) {
3619+ const command = 'command' in config ? config . command : undefined ;
3620+ if ( typeof command !== 'string' || ! command . trim ( ) ) {
35813621 throw new Error ( 'updateMcpServer: command is required for STDIO servers' ) ;
35823622 }
35833623 }
@@ -3619,26 +3659,13 @@ export async function updateMcpServer(
36193659
36203660 if ( options . projectPath ) {
36213661 const servers = await fetchMcpServers ( options . projectPath ) ;
3622- return [ ...servers . project , ...servers . global ] . find ( ( s ) => s . name === serverName ) ?? {
3623- name : serverName ,
3624- transport : config . transport ?? 'stdio' ,
3625- ...( config . transport === 'http' ? { url : config . url ! } : { command : config . command ! } ) ,
3626- args : config . args ,
3627- env : config . env ,
3628- enabled : config . enabled ?? true ,
3629- scope : config . scope ,
3630- } as McpServer ;
3662+ return (
3663+ [ ...servers . project , ...servers . global ] . find ( ( s ) => s . name === serverName ) ??
3664+ _buildFallbackServer ( serverName , config )
3665+ ) ;
36313666 }
36323667
3633- return {
3634- name : serverName ,
3635- transport : config . transport ?? 'stdio' ,
3636- ...( config . transport === 'http' ? { url : config . url ! } : { command : config . command ! } ) ,
3637- args : config . args ,
3638- env : config . env ,
3639- enabled : config . enabled ?? true ,
3640- scope : config . scope ,
3641- } as McpServer ;
3668+ return _buildFallbackServer ( serverName , config ) ;
36423669}
36433670
36443671/**
@@ -3756,22 +3783,23 @@ export async function toggleMcpServer(
37563783 }
37573784
37583785 const servers = await fetchMcpServers ( projectPath ) ;
3759- return [ ...servers . project , ...servers . global ] . find ( ( s ) => s . name === serverName ) ?? {
3760- name : serverName ,
3761- command : '' ,
3762- enabled,
3763- scope : 'project' ,
3764- } ;
3786+ return (
3787+ [ ...servers . project , ...servers . global ] . find ( ( s ) => s . name === serverName ) ?? {
3788+ name : serverName ,
3789+ transport : 'stdio' ,
3790+ command : '' ,
3791+ enabled,
3792+ scope : 'project' ,
3793+ }
3794+ ) ;
37653795}
37663796
37673797// ========== Codex MCP API ==========
37683798/**
37693799 * Codex MCP Server - Read-only server with config path
37703800 * Extends McpServer with optional configPath field
37713801 */
3772- export interface CodexMcpServer extends McpServer {
3773- configPath ?: string ;
3774- }
3802+ export type CodexMcpServer = McpServer & { configPath ?: string } ;
37753803
37763804export interface CodexMcpServersResponse {
37773805 servers : CodexMcpServer [ ] ;
@@ -3958,13 +3986,16 @@ export async function fetchOtherProjectsServers(
39583986 servers [ path ] = Object . entries ( projectServersRecord )
39593987 // Exclude globally-defined servers; this section is meant for project-local discovery
39603988 . filter ( ( [ name ] ) => ! ( name in userServers ) && ! ( name in enterpriseServers ) )
3961- . map ( ( [ name , raw ] ) => {
3989+ . flatMap ( ( [ name , raw ] ) => {
39623990 const normalized = normalizeServerConfig ( raw ) ;
3963- return {
3991+ if ( normalized . transport !== 'stdio' ) return [ ] ;
3992+ return [ {
39643993 name,
3965- ...normalized ,
3994+ command : normalized . command ,
3995+ args : normalized . args ,
3996+ env : normalized . env ,
39663997 enabled : ! disabledSet . has ( name ) ,
3967- } ;
3998+ } ] ;
39683999 } ) ;
39694000 }
39704001
@@ -4552,58 +4583,6 @@ export interface CcwMcpConfig {
45524583 installedScopes : ( 'global' | 'project' ) [ ] ;
45534584}
45544585
4555- /**
4556- * Platform detection for cross-platform MCP config
4557- */
4558- const isWindows = typeof navigator !== 'undefined' && navigator . platform ?. toLowerCase ( ) . includes ( 'win' ) ;
4559-
4560- /**
4561- * Build CCW MCP server config
4562- */
4563- function buildCcwMcpServerConfig ( config : {
4564- enabledTools ?: string [ ] ;
4565- projectRoot ?: string ;
4566- allowedDirs ?: string ;
4567- enableSandbox ?: boolean ;
4568- } ) : { command : string ; args : string [ ] ; env : Record < string , string > } {
4569- const env : Record < string , string > = { } ;
4570-
4571- // Only use default when enabledTools is undefined (not provided)
4572- // When enabledTools is an empty array, set to empty string to disable all tools
4573- console . log ( '[buildCcwMcpServerConfig] config.enabledTools:' , config . enabledTools ) ;
4574- if ( config . enabledTools !== undefined ) {
4575- env . CCW_ENABLED_TOOLS = config . enabledTools . join ( ',' ) ;
4576- console . log ( '[buildCcwMcpServerConfig] Set CCW_ENABLED_TOOLS to:' , env . CCW_ENABLED_TOOLS ) ;
4577- } else {
4578- env . CCW_ENABLED_TOOLS = 'write_file,edit_file,read_file,core_memory,ask_question,smart_search' ;
4579- console . log ( '[buildCcwMcpServerConfig] Using default CCW_ENABLED_TOOLS' ) ;
4580- }
4581-
4582- if ( config . projectRoot ) {
4583- env . CCW_PROJECT_ROOT = config . projectRoot ;
4584- }
4585- if ( config . allowedDirs ) {
4586- env . CCW_ALLOWED_DIRS = config . allowedDirs ;
4587- }
4588- if ( config . enableSandbox ) {
4589- env . CCW_ENABLE_SANDBOX = '1' ;
4590- }
4591-
4592- // Cross-platform config
4593- if ( isWindows ) {
4594- return {
4595- command : 'cmd' ,
4596- args : [ '/c' , 'npx' , '-y' , 'ccw-mcp' ] ,
4597- env
4598- } ;
4599- }
4600- return {
4601- command : 'npx' ,
4602- args : [ '-y' , 'ccw-mcp' ] ,
4603- env
4604- } ;
4605- }
4606-
46074586/**
46084587 * Fetch CCW Tools MCP configuration by checking if ccw-tools server exists
46094588 */
@@ -4698,13 +4677,14 @@ export async function updateCcwConfig(config: {
46984677 allowedDirs ?: string ;
46994678 enableSandbox ?: boolean ;
47004679} ) : Promise < CcwMcpConfig > {
4701- const serverConfig = buildCcwMcpServerConfig ( config ) ;
4702-
4703- // Install/update to global config
4704- const result = await addGlobalMcpServer ( 'ccw-tools' , serverConfig ) ;
4705- if ( ! result . success ) {
4706- throw new Error ( result . error || 'Failed to update CCW config' ) ;
4707- }
4680+ const result = await fetchApi < { success ?: boolean ; error ?: string } > ( '/api/mcp-install-ccw' , {
4681+ method : 'POST' ,
4682+ body : JSON . stringify ( {
4683+ scope : 'global' ,
4684+ env : config ,
4685+ } ) ,
4686+ } ) ;
4687+ if ( result ?. error ) throw new Error ( result . error || 'Failed to update CCW config' ) ;
47084688
47094689 return fetchCcwMcpConfig ( ) ;
47104690}
@@ -4716,31 +4696,21 @@ export async function installCcwMcp(
47164696 scope : 'global' | 'project' = 'global' ,
47174697 projectPath ?: string
47184698) : Promise < CcwMcpConfig > {
4719- const serverConfig = buildCcwMcpServerConfig ( {
4720- enabledTools : [ 'write_file' , 'edit_file' , 'read_file' , 'core_memory' , 'ask_question' , 'smart_search' ] ,
4721- } ) ;
4699+ const path = scope === 'project' ? requireProjectPath ( projectPath , 'installCcwMcp' ) : undefined ;
47224700
4723- if ( scope === 'project' && projectPath ) {
4724- const result = await fetchApi < { success ?: boolean ; error ?: string } > ( '/api/mcp-copy-server' , {
4725- method : 'POST' ,
4726- body : JSON . stringify ( {
4727- projectPath,
4728- serverName : 'ccw-tools' ,
4729- serverConfig,
4730- configType : 'mcp' ,
4731- } ) ,
4732- } ) ;
4733- if ( result ?. error ) {
4734- throw new Error ( result . error || 'Failed to install CCW MCP to project' ) ;
4735- }
4736- } else {
4737- const result = await addGlobalMcpServer ( 'ccw-tools' , serverConfig ) ;
4738- if ( ! result . success ) {
4739- throw new Error ( result . error || 'Failed to install CCW MCP' ) ;
4740- }
4741- }
4701+ const result = await fetchApi < { success ?: boolean ; error ?: string } > ( '/api/mcp-install-ccw' , {
4702+ method : 'POST' ,
4703+ body : JSON . stringify ( {
4704+ scope,
4705+ projectPath : path ,
4706+ env : {
4707+ enabledTools : [ 'write_file' , 'edit_file' , 'read_file' , 'core_memory' , 'ask_question' , 'smart_search' ] ,
4708+ } ,
4709+ } ) ,
4710+ } ) ;
4711+ if ( result ?. error ) throw new Error ( result . error || `Failed to install CCW MCP (${ scope } )` ) ;
47424712
4743- return fetchCcwMcpConfig ( ) ;
4713+ return fetchCcwMcpConfig ( path ) ;
47444714}
47454715
47464716/**
@@ -4811,7 +4781,7 @@ export async function fetchCcwMcpConfigForCodex(): Promise<CcwMcpConfig> {
48114781 return { isInstalled : false , enabledTools : [ ] , installedScopes : [ ] } ;
48124782 }
48134783
4814- const env = ccwServer . env || { } ;
4784+ const env = isStdioMcpServer ( ccwServer ) ? ( ccwServer . env || { } ) : { } ;
48154785 // Note: CCW_ENABLED_TOOLS can be empty string (all tools disabled), 'all' (default set), or comma-separated list
48164786 const enabledToolsStr = env . CCW_ENABLED_TOOLS ;
48174787 let enabledTools : string [ ] ;
0 commit comments