@@ -30,6 +30,14 @@ protected McpClientConfiguratorBase(McpClient client)
3030 public McpStatus Status => client . status ;
3131 public ConfiguredTransport ConfiguredTransport => client . configuredTransport ;
3232 public virtual bool SupportsAutoConfigure => true ;
33+ // Default to a filesystem check on the configured path. Concrete configurators
34+ // whose presence isn't path-based (CLI binaries, etc.) override this. This makes
35+ // any future configurator that forgets to override fail-closed rather than be
36+ // treated as "detected" by ConfigureAllDetectedClients.
37+ public virtual bool IsInstalled => ParentDirectoryExists ( GetConfigPath ( ) ) ;
38+ private static readonly ConfiguredTransport [ ] DefaultTransports =
39+ { ConfiguredTransport . Stdio , ConfiguredTransport . Http } ;
40+ public virtual IReadOnlyList < ConfiguredTransport > SupportedTransports => DefaultTransports ;
3341 public virtual bool SupportsSkills => false ;
3442 public virtual string GetConfigureActionLabel ( ) => "Configure" ;
3543 public virtual string GetSkillInstallPath ( ) => null ;
@@ -59,6 +67,17 @@ protected string CurrentOsPath()
5967 return client . linuxConfigPath ;
6068 }
6169
70+ protected static bool ParentDirectoryExists ( string configPath )
71+ {
72+ try
73+ {
74+ if ( string . IsNullOrEmpty ( configPath ) ) return false ;
75+ string parent = Path . GetDirectoryName ( configPath ) ;
76+ return ! string . IsNullOrEmpty ( parent ) && Directory . Exists ( parent ) ;
77+ }
78+ catch { return false ; }
79+ }
80+
6281 protected bool UrlsEqual ( string a , string b )
6382 {
6483 if ( string . IsNullOrWhiteSpace ( a ) || string . IsNullOrWhiteSpace ( b ) )
@@ -131,6 +150,8 @@ public JsonFileMcpConfigurator(McpClient client) : base(client) { }
131150
132151 public override string GetConfigPath ( ) => CurrentOsPath ( ) ;
133152
153+ public override bool IsInstalled => ParentDirectoryExists ( GetConfigPath ( ) ) ;
154+
134155 public override McpStatus CheckStatus ( bool attemptAutoRewrite = true )
135156 {
136157 try
@@ -148,43 +169,37 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
148169 string configuredUrl = null ;
149170 bool configExists = false ;
150171
151- if ( client . IsVsCodeLayout )
152172 {
153- var vsConfig = JsonConvert . DeserializeObject < JToken > ( configJson ) as JObject ;
154- if ( vsConfig != null )
173+ var rootConfig = JsonConvert . DeserializeObject < JToken > ( configJson ) as JObject ;
174+ JToken unityToken = null ;
175+ if ( rootConfig != null )
155176 {
156- var unityToken =
157- vsConfig [ "servers" ] ? [ "unityMCP" ]
158- ?? vsConfig [ "mcp" ] ? [ "servers" ] ? [ "unityMCP" ] ;
177+ unityToken = client . IsVsCodeLayout
178+ ? rootConfig [ "servers" ] ? [ "unityMCP" ]
179+ ?? rootConfig [ "mcp" ] ? [ "servers" ] ? [ "unityMCP" ]
180+ : rootConfig [ "mcpServers" ] ? [ "unityMCP" ] ;
181+ }
159182
160- if ( unityToken is JObject unityObj )
161- {
162- configExists = true ;
183+ if ( unityToken is JObject unityObj )
184+ {
185+ configExists = true ;
163186
164- var argsToken = unityObj [ "args" ] ;
165- if ( argsToken is JArray )
166- {
167- args = argsToken . ToObject < string [ ] > ( ) ;
168- }
187+ var argsToken = unityObj [ "args" ] ;
188+ if ( argsToken is JArray )
189+ {
190+ args = argsToken . ToObject < string [ ] > ( ) ;
191+ }
169192
170- var urlToken = unityObj [ "url" ] ?? unityObj [ "serverUrl" ] ;
171- if ( urlToken != null && urlToken . Type != JTokenType . Null )
172- {
173- configuredUrl = urlToken . ToString ( ) ;
174- }
193+ // Clients diverge on the HTTP URL property name: "url" (Cursor/VSCode/Claude),
194+ // "serverUrl" (Antigravity/Windsurf), "httpUrl" (Gemini CLI). Accept all three
195+ // so CheckStatus matches what Configure() actually wrote.
196+ var urlToken = unityObj [ "url" ] ?? unityObj [ "serverUrl" ] ?? unityObj [ "httpUrl" ] ;
197+ if ( urlToken != null && urlToken . Type != JTokenType . Null )
198+ {
199+ configuredUrl = urlToken . ToString ( ) ;
175200 }
176201 }
177202 }
178- else
179- {
180- McpConfig standardConfig = JsonConvert . DeserializeObject < McpConfig > ( configJson ) ;
181- if ( standardConfig ? . mcpServers ? . unityMCP != null )
182- {
183- args = standardConfig . mcpServers . unityMCP . args ;
184- configuredUrl = standardConfig . mcpServers . unityMCP . url ;
185- configExists = true ;
186- }
187- }
188203
189204 if ( ! configExists )
190205 {
@@ -357,6 +372,8 @@ public CodexMcpConfigurator(McpClient client) : base(client) { }
357372
358373 public override string GetConfigPath ( ) => CurrentOsPath ( ) ;
359374
375+ public override bool IsInstalled => ParentDirectoryExists ( GetConfigPath ( ) ) ;
376+
360377 public override McpStatus CheckStatus ( bool attemptAutoRewrite = true )
361378 {
362379 try
@@ -542,6 +559,8 @@ public ClaudeCliMcpConfigurator(McpClient client) : base(client) { }
542559
543560 public override string GetConfigPath ( ) => "Managed via Claude CLI" ;
544561
562+ public override bool IsInstalled => MCPServiceLocator . Paths . IsClaudeCliDetected ( ) ;
563+
545564 /// <summary>
546565 /// Returns the project directory that CLI-based configurators will use as the working directory
547566 /// for `claude mcp add/remove --scope local`. Checks for an explicit override in EditorPrefs
0 commit comments