/* ┌──────────────────────────────────────────────────────────────────┐ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ │ Copyright (c) 2025 Ivan Murzak │ │ Licensed under the Apache License, Version 2.0. │ │ See the LICENSE file in the project root for more information. │ └──────────────────────────────────────────────────────────────────┘ */ #nullable enable using System.IO; using System.Text.Json.Nodes; using com.IvanMurzak.Unity.MCP.Editor.Utils; using UnityEngine.UIElements; using static com.IvanMurzak.McpPlugin.Common.Consts.MCP.Server; namespace com.IvanMurzak.Unity.MCP.Editor.UI { /// /// Configurator for Claude Code AI agent. /// public class ClaudeCodeConfigurator : AiAgentConfigurator { public override string AgentName => "Claude Code"; public override string AgentId => "claude-code"; public override string DownloadUrl => "https://docs.anthropic.com/en/docs/claude-code/overview"; public override string TutorialUrl => "https://youtu.be/Sknh2p12W8c"; public override string? SkillsPath => ".claude/skills"; protected override string? IconFileName => "claude-64.png"; private static string LocalConfigPath => Path.Combine(ProjectRootPath, ".mcp.json"); protected override AiAgentConfig CreateConfigStdioWindows() => new JsonAiAgentConfig( name: AgentName, configPath: LocalConfigPath, bodyPath: "mcpServers" ) .SetProperty("command", JsonValue.Create(McpServerManager.ExecutableFullPath.Replace('\\', '/')), requiredForConfiguration: true, comparison: ValueComparisonMode.Path) .SetProperty("args", new JsonArray { $"{Args.Port}={UnityMcpPluginEditor.Port}", $"{Args.PluginTimeout}={UnityMcpPluginEditor.TimeoutMs}", $"{Args.ClientTransportMethod}={TransportMethod.stdio}", $"{Args.Authorization}={UnityMcpPluginEditor.AuthOption}", $"{Args.Token}={UnityMcpPluginEditor.Token}" }, requiredForConfiguration: true) .SetPropertyToRemove("type") .SetPropertyToRemove("url"); protected override AiAgentConfig CreateConfigStdioMacLinux() => new JsonAiAgentConfig( name: AgentName, configPath: LocalConfigPath, bodyPath: "mcpServers" ) .SetProperty("command", JsonValue.Create(McpServerManager.ExecutableFullPath.Replace('\\', '/')), requiredForConfiguration: true, comparison: ValueComparisonMode.Path) .SetProperty("args", new JsonArray { $"{Args.Port}={UnityMcpPluginEditor.Port}", $"{Args.PluginTimeout}={UnityMcpPluginEditor.TimeoutMs}", $"{Args.ClientTransportMethod}={TransportMethod.stdio}", $"{Args.Authorization}={UnityMcpPluginEditor.AuthOption}", $"{Args.Token}={UnityMcpPluginEditor.Token}" }, requiredForConfiguration: true) .SetPropertyToRemove("type") .SetPropertyToRemove("url"); protected override AiAgentConfig CreateConfigHttpWindows() => new JsonAiAgentConfig( name: AgentName, configPath: LocalConfigPath, bodyPath: "mcpServers" ) .SetProperty("type", JsonValue.Create("http"), requiredForConfiguration: true) .SetProperty("url", JsonValue.Create(UnityMcpPluginEditor.Host), requiredForConfiguration: true, comparison: ValueComparisonMode.Url) .SetPropertyToRemove("command") .SetPropertyToRemove("args"); protected override AiAgentConfig CreateConfigHttpMacLinux() => new JsonAiAgentConfig( name: AgentName, configPath: LocalConfigPath, bodyPath: "mcpServers" ) .SetProperty("type", JsonValue.Create("http"), requiredForConfiguration: true) .SetProperty("url", JsonValue.Create(UnityMcpPluginEditor.Host), requiredForConfiguration: true, comparison: ValueComparisonMode.Url) .SetPropertyToRemove("command") .SetPropertyToRemove("args"); protected override void OnUICreated(VisualElement root) { base.OnUICreated(root); var isCloud = UnityMcpPluginEditor.ConnectionMode == ConnectionMode.Cloud; var isAuthRequired = isCloud || UnityMcpPluginEditor.AuthOption == AuthOption.required; // STDIO Configuration var startContainerStdio = TemplateFoldoutFirst("Start"); startContainerStdio!.Add(TemplateLabelDescription("Navigate to project root")); startContainerStdio!.Add(TemplateTextFieldReadOnly($"cd \"{ProjectRootPath}\"")); startContainerStdio!.Add(TemplateLabelDescription("Launch Claude Code")); startContainerStdio!.Add(TemplateTextFieldReadOnly("claude")); ContainerStdio!.Add(startContainerStdio); var manualStepsContainer = TemplateFoldout("Manual Configuration Steps"); var tokenStdio = !string.IsNullOrEmpty(UnityMcpPluginEditor.Token) ? UnityMcpPluginEditor.Token : ""; var authArgsStdio = isAuthRequired ? $" {Args.Authorization}={AuthOption.required} {Args.Token}={tokenStdio}" : string.Empty; var addMcpServerCommandStdio = $"claude mcp add {AiAgentConfig.DefaultMcpServerName} \"{McpServerManager.ExecutableFullPath}\" port={UnityMcpPluginEditor.Port} plugin-timeout={UnityMcpPluginEditor.TimeoutMs} client-transport=stdio{authArgsStdio}"; manualStepsContainer!.Add(TemplateLabelDescription("Run the following command in the folder of the Unity project to configure Claude Code")); manualStepsContainer!.Add(TemplateTextFieldReadOnly(addMcpServerCommandStdio)); manualStepsContainer!.Add(TemplateLabelDescription("Restart or start Claude Code to apply the configuration")); manualStepsContainer!.Add(TemplateTextFieldReadOnly("claude")); ContainerStdio!.Add(manualStepsContainer); var troubleshootingContainerStdio = TemplateFoldout("Troubleshooting"); troubleshootingContainerStdio.Add(TemplateLabelDescription("- Ensure Claude Code CLI is installed and accessible from terminal")); troubleshootingContainerStdio.Add(TemplateLabelDescription("- Ensure Claude Code CLI is started in the same folder where Unity project is located. This folder must contains Assets folder inside")); troubleshootingContainerStdio.Add(TemplateLabelDescription("- Ensure Claude Code is configured with the same port as it is in Unity right now")); troubleshootingContainerStdio.Add(TemplateLabelDescription("- Check that the configuration file .mcp.json exists")); troubleshootingContainerStdio.Add(TemplateLabelDescription("- Restart Claude Code after configuration changes")); ContainerStdio!.Add(troubleshootingContainerStdio); // HTTP Configuration var startContainerHttp = TemplateFoldoutFirst("Start"); startContainerHttp!.Add(TemplateLabelDescription("Navigate to project root")); startContainerHttp!.Add(TemplateTextFieldReadOnly($"cd \"{ProjectRootPath}\"")); startContainerHttp!.Add(TemplateLabelDescription("Launch Claude Code")); startContainerHttp!.Add(TemplateTextFieldReadOnly("claude")); ContainerHttp!.Add(startContainerHttp); var manualStepsContainerHttp = TemplateFoldout("Manual Configuration Steps"); var tokenHttp = !string.IsNullOrEmpty(UnityMcpPluginEditor.Token) ? UnityMcpPluginEditor.Token : ""; var authHeaderHttp = isAuthRequired ? $" --header \"Authorization: Bearer {tokenHttp}\"" : string.Empty; var addMcpServerCommandHttp = $"claude mcp add --transport http {AiAgentConfig.DefaultMcpServerName} {UnityMcpPluginEditor.Host}{authHeaderHttp}"; manualStepsContainerHttp!.Add(TemplateLabelDescription("Run the following command in the folder of the Unity project to configure Claude Code")); manualStepsContainerHttp!.Add(TemplateTextFieldReadOnly(addMcpServerCommandHttp)); manualStepsContainerHttp!.Add(TemplateLabelDescription("Restart or start Claude Code to apply the configuration")); manualStepsContainerHttp!.Add(TemplateTextFieldReadOnly("claude")); ContainerHttp!.Add(manualStepsContainerHttp); var troubleshootingContainerHttp = TemplateFoldout("Troubleshooting"); troubleshootingContainerHttp.Add(TemplateLabelDescription("- Ensure Claude Code CLI is installed and accessible from terminal")); troubleshootingContainerHttp.Add(TemplateLabelDescription("- Ensure Claude Code CLI is started in the same folder where Unity project is located. This folder must contains Assets folder inside")); troubleshootingContainerHttp.Add(TemplateLabelDescription("- Ensure Claude Code is configured with the same port as it is in Unity right now")); troubleshootingContainerHttp.Add(TemplateLabelDescription("- Check that the configuration file .mcp.json exists")); troubleshootingContainerHttp.Add(TemplateLabelDescription("- Restart Claude Code after configuration changes")); ContainerHttp!.Add(troubleshootingContainerHttp); } } }