@@ -45,6 +45,11 @@ protected McpClientConfiguratorBase(McpClient client)
4545 public abstract string GetConfigPath ( ) ;
4646 public abstract McpStatus CheckStatus ( bool attemptAutoRewrite = true ) ;
4747 public abstract void Configure ( ) ;
48+
49+ /// <summary>Default Unregister is a no-op. Override in JsonFileMcpConfigurator /
50+ /// ClaudeCliMcpConfigurator etc. where removal has a concrete implementation.</summary>
51+ public virtual void Unregister ( ) { }
52+
4853 public abstract string GetManualSnippet ( ) ;
4954 public abstract IList < string > GetInstallationSteps ( ) ;
5055
@@ -334,6 +339,9 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
334339
335340 public override void Configure ( )
336341 {
342+ // Always idempotent-write. The per-client UI button routes through Unregister
343+ // when the user clicks while the client is already Configured; the bulk
344+ // "Configure All" path calls this directly and expects an unconditional write.
337345 string path = GetConfigPath ( ) ;
338346 McpConfigurationHelper . EnsureConfigDirectoryExists ( path ) ;
339347 string result = McpConfigurationHelper . WriteMcpConfiguration ( path , client ) ;
@@ -348,6 +356,59 @@ public override void Configure()
348356 }
349357 }
350358
359+ public override string GetConfigureActionLabel ( )
360+ => client . status == McpStatus . Configured ? "Unregister" : "Configure" ;
361+
362+ /// <summary>
363+ /// Removes the unityMCP entry from the client's JSON config (both VS Code-style
364+ /// `servers` / `mcp.servers` layouts and the standard `mcpServers` layout). Leaves
365+ /// the file in place so we don't clobber other servers the user has configured.
366+ /// </summary>
367+ public override void Unregister ( )
368+ {
369+ string path = GetConfigPath ( ) ;
370+ try
371+ {
372+ if ( ! File . Exists ( path ) )
373+ {
374+ client . SetStatus ( McpStatus . NotConfigured ) ;
375+ client . configuredTransport = Models . ConfiguredTransport . Unknown ;
376+ return ;
377+ }
378+
379+ var root = JsonConvert . DeserializeObject < JToken > ( File . ReadAllText ( path ) ) as JObject ;
380+ if ( root == null )
381+ {
382+ client . SetStatus ( McpStatus . NotConfigured ) ;
383+ client . configuredTransport = Models . ConfiguredTransport . Unknown ;
384+ return ;
385+ }
386+
387+ bool removed = false ;
388+ if ( client . IsVsCodeLayout )
389+ {
390+ if ( ( root [ "servers" ] as JObject ) ? . Remove ( "unityMCP" ) == true ) removed = true ;
391+ if ( ( root [ "mcp" ] ? [ "servers" ] as JObject ) ? . Remove ( "unityMCP" ) == true ) removed = true ;
392+ }
393+ else
394+ {
395+ if ( ( root [ "mcpServers" ] as JObject ) ? . Remove ( "unityMCP" ) == true ) removed = true ;
396+ }
397+
398+ if ( removed )
399+ {
400+ File . WriteAllText ( path , root . ToString ( Formatting . Indented ) ) ;
401+ }
402+
403+ client . SetStatus ( McpStatus . NotConfigured ) ;
404+ client . configuredTransport = Models . ConfiguredTransport . Unknown ;
405+ }
406+ catch ( Exception ex )
407+ {
408+ throw new InvalidOperationException ( $ "Failed to unregister: { ex . Message } ", ex ) ;
409+ }
410+ }
411+
351412 public override string GetManualSnippet ( )
352413 {
353414 try
@@ -964,7 +1025,7 @@ private void Register()
9641025 client . configuredTransport = HttpEndpointUtility . GetCurrentServerTransport ( ) ;
9651026 }
9661027
967- private void Unregister ( )
1028+ public override void Unregister ( )
9681029 {
9691030 var pathService = MCPServiceLocator . Paths ;
9701031 string claudePath = pathService . GetClaudeCliPath ( ) ;
0 commit comments