@@ -28,8 +28,18 @@ interface Session {
2828 hasActivePty : boolean ;
2929}
3030
31+ interface McpServer {
32+ name : string ;
33+ command ?: string ;
34+ args ?: string [ ] ;
35+ env ?: Record < string , string > ;
36+ url ?: string ;
37+ type ?: "stdio" | "sse" ;
38+ }
39+
3140const sessions = new Map < string , Session > ( ) ;
3241let activeSessionId : string | null = null ;
42+ let mcpServers : McpServer [ ] = [ ] ;
3343
3444function createTerminalUI ( sessionId : string ) {
3545 const term = new Terminal ( {
@@ -461,3 +471,176 @@ createBtn?.addEventListener("click", () => {
461471 parentBranchSelect . innerHTML = '<option value="">Loading branches...</option>' ;
462472 codingAgentSelect . value = "claude" ;
463473} ) ;
474+
475+ // MCP Server management functions
476+ async function loadMcpServers ( ) {
477+ try {
478+ const servers = await ipcRenderer . invoke ( "list-mcp-servers" ) ;
479+ mcpServers = servers ;
480+ renderMcpServers ( ) ;
481+ } catch ( error ) {
482+ console . error ( "Failed to load MCP servers:" , error ) ;
483+ }
484+ }
485+
486+ function renderMcpServers ( ) {
487+ const list = document . getElementById ( "mcp-server-list" ) ;
488+ if ( ! list ) return ;
489+
490+ list . innerHTML = "" ;
491+
492+ mcpServers . forEach ( server => {
493+ const item = document . createElement ( "div" ) ;
494+ item . className = "session-list-item" ;
495+ item . innerHTML = `
496+ <div class="flex items-center space-x-2 flex-1">
497+ <span class="session-indicator active"></span>
498+ <span class="truncate">${ server . name } </span>
499+ </div>
500+ <button class="session-delete-btn mcp-remove-btn" data-name="${ server . name } " title="Remove server">×</button>
501+ ` ;
502+
503+ const removeBtn = item . querySelector ( ".mcp-remove-btn" ) ;
504+ removeBtn ?. addEventListener ( "click" , async ( e ) => {
505+ e . stopPropagation ( ) ;
506+ if ( confirm ( `Remove MCP server "${ server . name } "?` ) ) {
507+ try {
508+ await ipcRenderer . invoke ( "remove-mcp-server" , server . name ) ;
509+ await loadMcpServers ( ) ;
510+ } catch ( error ) {
511+ alert ( `Failed to remove server: ${ error } ` ) ;
512+ }
513+ }
514+ } ) ;
515+
516+ list . appendChild ( item ) ;
517+ } ) ;
518+ }
519+
520+ // MCP Modal handling
521+ const mcpModal = document . getElementById ( "mcp-modal" ) ;
522+ const mcpNameInput = document . getElementById ( "mcp-name" ) as HTMLInputElement ;
523+ const mcpTypeSelect = document . getElementById ( "mcp-type" ) as HTMLSelectElement ;
524+ const mcpCommandInput = document . getElementById ( "mcp-command" ) as HTMLInputElement ;
525+ const mcpArgsInput = document . getElementById ( "mcp-args" ) as HTMLInputElement ;
526+ const mcpEnvInput = document . getElementById ( "mcp-env" ) as HTMLTextAreaElement ;
527+ const mcpUrlInput = document . getElementById ( "mcp-url" ) as HTMLInputElement ;
528+ const mcpHeadersInput = document . getElementById ( "mcp-headers" ) as HTMLTextAreaElement ;
529+ const mcpAlwaysAllowInput = document . getElementById ( "mcp-always-allow" ) as HTMLInputElement ;
530+ const localFields = document . getElementById ( "local-fields" ) ;
531+ const remoteFields = document . getElementById ( "remote-fields" ) ;
532+ const cancelMcpBtn = document . getElementById ( "cancel-mcp" ) ;
533+ const addMcpBtn = document . getElementById ( "add-mcp" ) ;
534+
535+ // Toggle fields based on server type
536+ mcpTypeSelect ?. addEventListener ( "change" , ( ) => {
537+ if ( mcpTypeSelect . value === "local" ) {
538+ localFields ! . style . display = "block" ;
539+ remoteFields ! . style . display = "none" ;
540+ } else {
541+ localFields ! . style . display = "none" ;
542+ remoteFields ! . style . display = "block" ;
543+ }
544+ } ) ;
545+
546+ // Add MCP server button - opens modal
547+ document . getElementById ( "add-mcp-server" ) ?. addEventListener ( "click" , ( ) => {
548+ mcpModal ?. classList . remove ( "hidden" ) ;
549+ mcpNameInput . value = "" ;
550+ mcpTypeSelect . value = "local" ;
551+ mcpCommandInput . value = "" ;
552+ mcpArgsInput . value = "" ;
553+ mcpEnvInput . value = "" ;
554+ mcpUrlInput . value = "" ;
555+ mcpHeadersInput . value = "" ;
556+ mcpAlwaysAllowInput . value = "" ;
557+ localFields ! . style . display = "block" ;
558+ remoteFields ! . style . display = "none" ;
559+ } ) ;
560+
561+ // Cancel MCP button
562+ cancelMcpBtn ?. addEventListener ( "click" , ( ) => {
563+ mcpModal ?. classList . add ( "hidden" ) ;
564+ } ) ;
565+
566+ // Add MCP button
567+ addMcpBtn ?. addEventListener ( "click" , async ( ) => {
568+ const name = mcpNameInput . value . trim ( ) ;
569+ const serverType = mcpTypeSelect . value ;
570+
571+ if ( ! name ) {
572+ alert ( "Please enter a server name" ) ;
573+ return ;
574+ }
575+
576+ const config : any = { } ;
577+
578+ if ( serverType === "local" ) {
579+ config . type = "stdio" ;
580+
581+ const command = mcpCommandInput . value . trim ( ) ;
582+ const argsInput = mcpArgsInput . value . trim ( ) ;
583+
584+ if ( ! command ) {
585+ alert ( "Please enter a command" ) ;
586+ return ;
587+ }
588+
589+ config . command = command ;
590+ if ( argsInput ) {
591+ config . args = argsInput . split ( " " ) . filter ( a => a . trim ( ) ) ;
592+ }
593+
594+ // Parse environment variables if provided
595+ const envInput = mcpEnvInput . value . trim ( ) ;
596+ if ( envInput ) {
597+ try {
598+ config . env = JSON . parse ( envInput ) ;
599+ } catch ( error ) {
600+ alert ( "Invalid JSON for environment variables" ) ;
601+ return ;
602+ }
603+ }
604+ } else {
605+ // Remote server
606+ config . type = "sse" ;
607+
608+ const url = mcpUrlInput . value . trim ( ) ;
609+
610+ if ( ! url ) {
611+ alert ( "Please enter a server URL" ) ;
612+ return ;
613+ }
614+
615+ config . url = url ;
616+
617+ // Parse headers if provided
618+ const headersInput = mcpHeadersInput . value . trim ( ) ;
619+ if ( headersInput ) {
620+ try {
621+ config . headers = JSON . parse ( headersInput ) ;
622+ } catch ( error ) {
623+ alert ( "Invalid JSON for headers" ) ;
624+ return ;
625+ }
626+ }
627+ }
628+
629+ // Parse always allow tools
630+ const alwaysAllowInput = mcpAlwaysAllowInput . value . trim ( ) ;
631+ if ( alwaysAllowInput ) {
632+ config . alwaysAllow = alwaysAllowInput . split ( "," ) . map ( t => t . trim ( ) ) . filter ( t => t ) ;
633+ }
634+
635+ try {
636+ await ipcRenderer . invoke ( "add-mcp-server" , name , config ) ;
637+ await loadMcpServers ( ) ;
638+ mcpModal ?. classList . add ( "hidden" ) ;
639+ } catch ( error ) {
640+ console . error ( "Error adding server:" , error ) ;
641+ alert ( `Failed to add server: ${ error } ` ) ;
642+ }
643+ } ) ;
644+
645+ // Load MCP servers on startup
646+ loadMcpServers ( ) ;
0 commit comments