diff --git a/.agents/skills/uloop-hello-world/SKILL.md b/.agents/skills/uloop-hello-world/SKILL.md index c1b7a6171..776e34624 100644 --- a/.agents/skills/uloop-hello-world/SKILL.md +++ b/.agents/skills/uloop-hello-world/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-hello-world -description: "Sample hello world tool via uloop CLI. Use when you need to test the MCP tool system or see an example of custom tool implementation." +description: "Sample hello world tool via uloop CLI. Use when you need to test the Unity CLI Loop tool system or see an example of custom tool implementation." --- # uloop hello-world diff --git a/.claude/rules/mcp-tools.md b/.claude/rules/mcp-tools.md deleted file mode 100644 index f5cacfade..000000000 --- a/.claude/rules/mcp-tools.md +++ /dev/null @@ -1,345 +0,0 @@ ---- -paths: Packages/src/Editor/Api/** ---- - -# MCP Tool Development Guide - -This document describes how to create new MCP tools for uLoopMCP. - -## Directory Structure - -``` -McpTools/ -├── Core/ # Base classes and infrastructure -│ ├── AbstractUnityTool.cs # Base class for all tools -│ ├── BaseToolSchema.cs # Base class for parameter schemas -│ ├── BaseToolResponse.cs # Base class for responses -│ └── McpToolAttribute.cs # Attribute for tool registration -├── YourNewTool/ # Your new tool folder -│ ├── YourNewToolSchema.cs -│ ├── YourNewToolResponse.cs -│ ├── YourNewToolTool.cs -│ └── SKILL.md # Skill documentation (optional) -└── ... -``` - -## Step-by-Step: Creating a New MCP Tool - -### Step 1: Create Tool Folder - -Create a new folder under `McpTools/` with your tool name (PascalCase): - -```bash -mkdir McpTools/YourNewTool -``` - -### Step 2: Create Schema Class - -The Schema defines the input parameters for your tool. - -`YourNewToolSchema.cs`: - -```csharp -using System.ComponentModel; - -namespace io.github.hatayama.uLoopMCP -{ - public class YourNewToolSchema : BaseToolSchema - { - [Description("Description shown in MCP tool schema")] - public string SomeParameter { get; set; } = "default value"; - - [Description("Another parameter with enum")] - public SomeEnum Mode { get; set; } = SomeEnum.Default; - - [Description("Numeric parameter")] - public float Scale { get; set; } = 1.0f; - } - - public enum SomeEnum - { - Default = 0, - Option1 = 1, - Option2 = 2 - } -} -``` - -**Key Points:** -- Inherit from `BaseToolSchema` -- Use `[Description]` attribute for parameter documentation -- Set default values for optional parameters -- Enums are automatically converted to string options in MCP schema - -### Step 3: Create Response Class - -The Response defines what your tool returns. - -`YourNewToolResponse.cs`: - -```csharp -#nullable enable - -namespace io.github.hatayama.uLoopMCP -{ - public class YourNewToolResponse : BaseToolResponse - { - public string? ResultPath { get; set; } - public int? Count { get; set; } - public bool Success { get; set; } - - public YourNewToolResponse(string resultPath, int count) - { - ResultPath = resultPath; - Count = count; - Success = true; - } - - public YourNewToolResponse(bool failure) - { - ResultPath = null; - Count = null; - Success = false; - } - - public YourNewToolResponse() - { - } - } -} -``` - -**Key Points:** -- Inherit from `BaseToolResponse` -- Use `#nullable enable` for null safety -- Provide constructors for success and failure cases -- Include default constructor for JSON deserialization - -### Step 4: Create Tool Class - -The Tool contains the main logic. - -`YourNewToolTool.cs`: - -```csharp -using System; -using System.Threading; -using System.Threading.Tasks; -using UnityEngine; -using UnityEditor; - -namespace io.github.hatayama.uLoopMCP -{ - [McpTool(Description = "Brief description of what this tool does")] - public class YourNewToolTool : AbstractUnityTool - { - public override string ToolName => "your-new-tool"; // kebab-case - - protected override async Task ExecuteAsync( - YourNewToolSchema parameters, - CancellationToken ct) - { - string correlationId = McpConstants.GenerateCorrelationId(); - - VibeLogger.LogInfo( - "your_new_tool_start", - "Tool execution started", - new { Mode = parameters.Mode.ToString() }, - correlationId: correlationId - ); - - // Validate parameters - ValidateParameters(parameters); - - // Your tool logic here - // Note: Already on main thread, no need to call MainThreadSwitcher - - VibeLogger.LogInfo( - "your_new_tool_success", - "Tool completed successfully", - new { ResultPath = "some/path" }, - correlationId: correlationId - ); - - return new YourNewToolResponse("some/path", 42); - } - - private void ValidateParameters(YourNewToolSchema parameters) - { - if (parameters.Scale < 0.1f || parameters.Scale > 2.0f) - { - throw new ArgumentException( - $"Scale must be between 0.1 and 2.0, got: {parameters.Scale}"); - } - } - } -} -``` - -**Key Points:** -- Add `[McpTool(Description = "...")]` attribute -- Inherit from `AbstractUnityTool` -- Set `ToolName` as kebab-case string -- Use `CancellationToken ct` parameter name -- Use `VibeLogger` for logging -- No try-catch needed (follow project policy) - -### Step 5: Compile and Test - -1. Compile in Unity: - ``` - mcp_uLoopMCP_compile - ``` - -2. Test via MCP: - ``` - mcp_uLoopMCP_your-new-tool - ``` - -### Step 6: Create SKILL.md (Optional) - -Create `SKILL.md` in the same folder as your tool for CLI skill support: - -`McpTools/YourNewTool/SKILL.md`: - -```markdown ---- -name: uloop-your-new-tool -description: Brief description for AI context. Use when you need to: (1) First use case, (2) Second use case. ---- - -# uloop your-new-tool - -One-line description of what this tool does. - -## Usage - -\`\`\`bash -uloop your-new-tool [--some-parameter ] [--mode ] -\`\`\` - -## Parameters - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `--some-parameter` | string | `""` | Description | -| `--mode` | enum | `Default` | Options: `Default`, `Option1`, `Option2` | -| `--scale` | number | `1.0` | Scale factor (0.1 to 2.0) | - -## Examples - -\`\`\`bash -# Basic usage -uloop your-new-tool - -# With parameters -uloop your-new-tool --mode Option1 --scale 0.5 -\`\`\` - -## Output - -Returns JSON with: -- `ResultPath`: Path to the result -- `Count`: Number of items processed -- `Success`: Whether the operation succeeded -``` - -**Note:** Add `internal: true` to frontmatter to exclude from bundled skills. - -### Step 7: Generate bundled-skills.ts - -The `bundled-skills.ts` file is **automatically generated** from SKILL.md files. - -**How it works:** -- The script `scripts/generate-bundled-skills.ts` scans: - - `Editor/Api/McpTools//SKILL.md` - - `skill-definitions/cli-only//SKILL.md` -- Skills with `internal: true` in frontmatter are excluded - -**Generate command:** -```bash -cd Packages/src/Cli~ -npx tsx scripts/generate-bundled-skills.ts -``` - -**Note:** This is also run automatically during `npm run build`. - -### Step 8: Update default-tools.json (Manual) - -Add your tool schema to `Packages/src/Cli~/src/default-tools.json`: - -```json -{ - "name": "your-new-tool", - "description": "Brief description", - "inputSchema": { - "type": "object", - "properties": { - "SomeParameter": { - "type": "string", - "description": "Description" - }, - "Mode": { - "type": "string", - "enum": ["Default", "Option1", "Option2"], - "default": "Default" - } - } - } -} -``` - -### Step 9: Run Lint and Build - -```bash -cd Packages/src/Cli~ -npm run lint && npm run build -``` - -This will: -1. Run ESLint on TypeScript files -2. Regenerate `bundled-skills.ts` from SKILL.md files -3. Bundle the CLI with esbuild - -### Step 10: Sync CLI with Unity - -The CLI uses a cache file (`.uloop/tools.json`) for tool definitions. After adding a new tool, you need to sync with Unity: - -```bash -uloop sync -``` - -**How tool loading works:** -1. CLI checks for `.uloop/tools.json` (cache file) -2. If cache exists, uses cached tool definitions -3. If cache doesn't exist, falls back to `default-tools.json` (bundled with npm package) - -**When to sync:** -- After adding/modifying MCP tools in Unity -- After updating uLoopMCP version -- If CLI commands don't match Unity tools - -## Naming Conventions - -| Item | Convention | Example | -|------|------------|---------| -| Folder | PascalCase | `YourNewTool/` | -| Schema class | PascalCase + Schema | `YourNewToolSchema` | -| Response class | PascalCase + Response | `YourNewToolResponse` | -| Tool class | PascalCase + Tool | `YourNewToolTool` | -| ToolName property | kebab-case | `"your-new-tool"` | -| SKILL.md name field | uloop- prefix + kebab-case | `uloop-your-new-tool` | - -## Tips - -- **Test with EditorWindow first**: For complex tools, create a test EditorWindow in `Assets/Editor/` before implementing the MCP tool -- **Use async/await properly**: Use `TimerDelay.Wait()` for delays, not `Task.Delay()` -- **Handle Unity Editor state**: Consider both playing and non-playing states -- **Clean up resources**: Always clean up textures, render textures, temporary objects - -## Reference Implementations - -- Simple tool: `ClearConsole/` -- Tool with enum parameter: `ControlPlayMode/` -- Complex tool with async operations: `Screenshot/` -- Tool with file output: `UnitySearch/` diff --git a/.claude/skills/uloop-hello-world/SKILL.md b/.claude/skills/uloop-hello-world/SKILL.md index c1b7a6171..776e34624 100644 --- a/.claude/skills/uloop-hello-world/SKILL.md +++ b/.claude/skills/uloop-hello-world/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-hello-world -description: "Sample hello world tool via uloop CLI. Use when you need to test the MCP tool system or see an example of custom tool implementation." +description: "Sample hello world tool via uloop CLI. Use when you need to test the Unity CLI Loop tool system or see an example of custom tool implementation." --- # uloop hello-world diff --git a/.gitignore b/.gitignore index aa930f9aa..3f2444739 100644 --- a/.gitignore +++ b/.gitignore @@ -84,8 +84,6 @@ crashlytics-build.properties # AI .claude/settings.local.json .kiro -.mcp.json -.inspector.mcp.json .serena working-notes** diff --git a/AGENTS.md b/AGENTS.md index a0c37058b..fa773ed15 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,10 @@ ## Architecture Overview This project provides a **CLI tool (`uloop`)** that communicates with Unity Editor via TCP. -AI agents interact with Unity through `uloop` CLI commands (e.g., `uloop get-logs`, `uloop compile`), NOT through MCP protocol directly. -The MCP server (TypeScriptServer~) exists as a separate component but is not the primary interface for AI agents. +AI agents interact with Unity through `uloop` CLI commands (e.g., `uloop get-logs`, `uloop compile`). +The Unity Editor side hosts a local project IPC server that accepts short-lived CLI command sessions. -The C# namespace is `io.github.hatayama.uLoopMCP` for historical reasons, but this is a CLI-based tool, not an MCP tool. +Do not rename public package, assembly, or extension API identifiers as part of cleanup-only changes. Comments in the code, commit messages, PR titles, and PR descriptions must all be written in English. diff --git a/Assets/Editor/CustomCommandSamples/GetProjectInfo/Skill/SKILL.md b/Assets/Editor/CustomCommandSamples/GetProjectInfo/Skill/SKILL.md index 653768470..6805b4393 100644 --- a/Assets/Editor/CustomCommandSamples/GetProjectInfo/Skill/SKILL.md +++ b/Assets/Editor/CustomCommandSamples/GetProjectInfo/Skill/SKILL.md @@ -37,4 +37,4 @@ Returns JSON with: ## Notes -This is a sample custom tool demonstrating how to create MCP tools. +This is a sample custom tool demonstrating how to create Unity CLI Loop tools. diff --git a/Assets/Editor/CustomCommandSamples/HelloWorld/Skill/SKILL.md b/Assets/Editor/CustomCommandSamples/HelloWorld/Skill/SKILL.md index c1b7a6171..776e34624 100644 --- a/Assets/Editor/CustomCommandSamples/HelloWorld/Skill/SKILL.md +++ b/Assets/Editor/CustomCommandSamples/HelloWorld/Skill/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-hello-world -description: "Sample hello world tool via uloop CLI. Use when you need to test the MCP tool system or see an example of custom tool implementation." +description: "Sample hello world tool via uloop CLI. Use when you need to test the Unity CLI Loop tool system or see an example of custom tool implementation." --- # uloop hello-world diff --git a/Assets/Tests/Editor/ConnectedToolsMonitoringServiceTests.cs b/Assets/Tests/Editor/ConnectedToolsMonitoringServiceTests.cs deleted file mode 100644 index 5d62aaf0d..000000000 --- a/Assets/Tests/Editor/ConnectedToolsMonitoringServiceTests.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.Linq; -using NUnit.Framework; - -namespace io.github.hatayama.UnityCliLoop.Tests.Editor -{ - public class ConnectedToolsMonitoringServiceTests - { - private McpEditorSettingsData _originalSettings; - - [SetUp] - public void SetUp() - { - _originalSettings = CloneSettings(McpEditorSettings.GetSettings()); - ConnectedToolsMonitoringService.ResetStateForTests(); - } - - [TearDown] - public void TearDown() - { - McpEditorSettings.SaveSettings(_originalSettings); - McpEditorSettings.InvalidateCache(); - ConnectedToolsMonitoringService.ResetStateForTests(); - } - - [Test] - public void ReplaceConnectedToolsForTests_ClearsStalePersistedTools_WhenNoLiveClients() - { - McpEditorSettings.SetConnectedLLMTools(new[] - { - new ConnectedLLMToolData("claude-code", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 3, 24, 22, 19, 27)) - }); - - ConnectedToolsMonitoringService.ReplaceConnectedToolsForTests(System.Array.Empty()); - - Assert.That(ConnectedToolsMonitoringService.GetConnectedToolsAsClients(), Is.Empty); - Assert.That(McpEditorSettings.GetConnectedLLMTools(), Is.Empty); - } - - [Test] - public void ReplaceConnectedToolsForTests_PersistsOnlyNamedLiveClients() - { - ConnectedClient[] liveClients = - { - new ConnectedClient("/tmp/uloop/test.sock#1", null, "claude-code"), - new ConnectedClient("/tmp/uloop/test.sock#2", null, McpConstants.UNKNOWN_CLIENT_NAME) - }; - - ConnectedToolsMonitoringService.ReplaceConnectedToolsForTests(liveClients); - - ConnectedClient[] displayedTools = ConnectedToolsMonitoringService.GetConnectedToolsAsClients().ToArray(); - ConnectedLLMToolData[] persistedTools = McpEditorSettings.GetConnectedLLMTools(); - - Assert.That(displayedTools, Has.Length.EqualTo(1)); - Assert.That(displayedTools[0].ClientName, Is.EqualTo("claude-code")); - Assert.That(displayedTools[0].Endpoint, Is.EqualTo("/tmp/uloop/test.sock#1")); - - Assert.That(persistedTools, Has.Length.EqualTo(1)); - Assert.That(persistedTools[0].Name, Is.EqualTo("claude-code")); - Assert.That(persistedTools[0].Endpoint, Is.EqualTo("/tmp/uloop/test.sock#1")); - } - - [Test] - public void ReplaceConnectedToolsForTests_ReplacesPreviousSnapshot_InsteadOfRestoringSettingsHistory() - { - McpEditorSettings.SetConnectedLLMTools(new[] - { - new ConnectedLLMToolData("claude-code", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 3, 24, 22, 19, 27)) - }); - - ConnectedClient[] liveClients = - { - new ConnectedClient("/tmp/uloop/test.sock#3", null, "cursor") - }; - - ConnectedToolsMonitoringService.ReplaceConnectedToolsForTests(liveClients); - - ConnectedClient[] displayedTools = ConnectedToolsMonitoringService.GetConnectedToolsAsClients().ToArray(); - ConnectedLLMToolData[] persistedTools = McpEditorSettings.GetConnectedLLMTools(); - - Assert.That(displayedTools.Select(tool => tool.ClientName), Is.EquivalentTo(new[] { "cursor" })); - Assert.That(persistedTools.Select(tool => tool.Name), Is.EquivalentTo(new[] { "cursor" })); - } - - [Test] - public void RestorePersistedConnectedToolsForTests_RehydratesReconnectSnapshot() - { - McpEditorSettings.SetConnectedLLMTools(new[] - { - new ConnectedLLMToolData("claude-code", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 3, 24, 22, 19, 27)) - }); - - ConnectedToolsMonitoringService.RestorePersistedConnectedToolsForTests(); - - ConnectedClient[] displayedTools = ConnectedToolsMonitoringService.GetConnectedToolsAsClients().ToArray(); - - Assert.That(displayedTools, Has.Length.EqualTo(1)); - Assert.That(displayedTools[0].ClientName, Is.EqualTo("claude-code")); - Assert.That(displayedTools[0].Endpoint, Is.EqualTo("/tmp/uloop/test.sock#1")); - } - - [Test] - public void SaveConnectedToolsWhenChanged_WhenSnapshotMatches_SkipsPersistedWrite() - { - ConnectedLLMToolData[] incomingTools = - { - new ConnectedLLMToolData("claude-code", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 4, 24, 1, 2, 3)) - }; - ConnectedLLMToolData[] persistedTools = - { - new ConnectedLLMToolData("claude-code", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 4, 24, 1, 2, 3)) - }; - int saveCount = 0; - - bool saved = ConnectedToolsMonitoringService.SaveConnectedToolsWhenChanged( - incomingTools, - () => persistedTools, - _ => saveCount++); - - Assert.That(saved, Is.False); - Assert.That(saveCount, Is.EqualTo(0)); - } - - [Test] - public void SaveConnectedToolsWhenChanged_WhenOnlyConnectedAtDiffers_SkipsPersistedWrite() - { - ConnectedLLMToolData[] incomingTools = - { - new ConnectedLLMToolData("claude-code", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 4, 24, 4, 5, 6)) - }; - ConnectedLLMToolData[] persistedTools = - { - new ConnectedLLMToolData("claude-code", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 4, 24, 1, 2, 3)) - }; - int saveCount = 0; - - bool saved = ConnectedToolsMonitoringService.SaveConnectedToolsWhenChanged( - incomingTools, - () => persistedTools, - _ => saveCount++); - - Assert.That(saved, Is.False); - Assert.That(saveCount, Is.EqualTo(0)); - } - - [Test] - public void SaveConnectedToolsWhenChanged_WhenSnapshotDiffers_PersistsTools() - { - ConnectedLLMToolData[] incomingTools = - { - new ConnectedLLMToolData("claude-code", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 4, 24, 1, 2, 3)) - }; - ConnectedLLMToolData[] persistedTools = - { - new ConnectedLLMToolData("cursor", "/tmp/uloop/test.sock#1", new System.DateTime(2026, 4, 24, 1, 2, 3)) - }; - int saveCount = 0; - - bool saved = ConnectedToolsMonitoringService.SaveConnectedToolsWhenChanged( - incomingTools, - () => persistedTools, - _ => saveCount++); - - Assert.That(saved, Is.True); - Assert.That(saveCount, Is.EqualTo(1)); - } - - private static McpEditorSettingsData CloneSettings(McpEditorSettingsData settings) - { - string json = UnityEngine.JsonUtility.ToJson(settings); - return UnityEngine.JsonUtility.FromJson(json); - } - } -} diff --git a/Assets/Tests/Editor/ConnectedToolsMonitoringServiceTests.cs.meta b/Assets/Tests/Editor/ConnectedToolsMonitoringServiceTests.cs.meta deleted file mode 100644 index f0b1d861b..000000000 --- a/Assets/Tests/Editor/ConnectedToolsMonitoringServiceTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a5457e9f4b7a04255988fa5f64daeb5d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Tests/Editor/DynamicCodeToolTests/DynamicCodeExecutorDictionaryErrorTest.cs b/Assets/Tests/Editor/DynamicCodeToolTests/DynamicCodeExecutorDictionaryErrorTest.cs index 69a9932bf..93ec997a6 100644 --- a/Assets/Tests/Editor/DynamicCodeToolTests/DynamicCodeExecutorDictionaryErrorTest.cs +++ b/Assets/Tests/Editor/DynamicCodeToolTests/DynamicCodeExecutorDictionaryErrorTest.cs @@ -82,7 +82,7 @@ public async Task IncorrectParameterType_ShouldProduceClearErrorMessage() // This test verifies the fix for the JSON serialization error // where Parameters field receives "{}" as string instead of object - // Note: This test would need to be executed through MCP protocol + // Note: This test would need to be executed through the CLI JSON-RPC path // to actually test the JSON deserialization error handling. // Here we just ensure the executor itself works correctly. diff --git a/Assets/Tests/Editor/McpEditorSettingsRecoveryTests.cs b/Assets/Tests/Editor/McpEditorSettingsRecoveryTests.cs index f0dee106d..f8c42e944 100644 --- a/Assets/Tests/Editor/McpEditorSettingsRecoveryTests.cs +++ b/Assets/Tests/Editor/McpEditorSettingsRecoveryTests.cs @@ -139,6 +139,7 @@ public void RecoverSettingsFileIfNeeded_WhenLegacyPortFieldsExist_ShouldRemoveTh StringAssert.DoesNotContain("serverTransportKind", recoveredJson); StringAssert.DoesNotContain("projectRootPath", recoveredJson); StringAssert.DoesNotContain("serverSessionId", recoveredJson); + StringAssert.DoesNotContain("connectedLLMTools", recoveredJson); StringAssert.DoesNotContain("\"Port\"", recoveredJson); } diff --git a/Assets/Tests/Editor/RunTestsToolTests.cs b/Assets/Tests/Editor/RunTestsToolTests.cs index bf7d2deaa..202b3122e 100644 --- a/Assets/Tests/Editor/RunTestsToolTests.cs +++ b/Assets/Tests/Editor/RunTestsToolTests.cs @@ -32,7 +32,7 @@ public void ToolName_ShouldReturnRunTests() public void ParseParameters_ShouldParseCorrectly() { // This test is now obsolete as the new implementation uses type-safe Schema classes - // instead of JSON parameter parsing. The parsing is handled by the MCP framework. + // instead of JSON parameter parsing. The parsing is handled by the tool dispatch layer. // Arrange - Test the Schema object directly RunTestsSchema schema = new RunTestsSchema diff --git a/Packages/docs/ARCHITECTURE_Unity.md b/Packages/docs/ARCHITECTURE_Unity.md index ba6e05ba5..a8e772830 100644 --- a/Packages/docs/ARCHITECTURE_Unity.md +++ b/Packages/docs/ARCHITECTURE_Unity.md @@ -1,59 +1,38 @@ -# uLoopMCP Unity Editor-Side Architecture +# Unity CLI Loop Unity Editor-Side Architecture ## 1. Overview -This document details the architecture of the C# code within the `Packages/src/Editor` directory. This code runs inside the Unity Editor and serves as the bridge between the Unity environment and the external TypeScript-based MCP (Model-Context-Protocol) server. +This document details the architecture of the C# code within the `Packages/src/Editor` directory. This code runs inside the Unity Editor and serves as the bridge between the Unity environment and the external `uloop` CLI. ### System Architecture Overview ```mermaid graph TB - subgraph "1. LLM Tools (MCP Clients)" - Claude[Claude Code
MCP Client] - Cursor[Cursor
MCP Client] - VSCode[VSCode
MCP Client] + subgraph "1. CLI Caller" + Agent[AI Agent or Developer Shell] + CLI[uloop CLI] end - subgraph "2. TypeScript Server (MCP Server + Unity TCP Client)" - MCP[UnityMcpServer
MCP Protocol Server
server.ts] - UC[UnityClient
TypeScript TCP Client
unity-client.ts] - UCM[UnityConnectionManager
Connection Orchestrator
unity-connection-manager.ts] - UD[UnityDiscovery
Unity Port Scanner
unity-discovery.ts] - end - - subgraph "3. Unity Editor (TCP Server)" - MB[McpBridgeServer
TCP Server
McpBridgeServer.cs] + subgraph "2. Unity Editor (Project IPC Server)" + MB[McpBridgeServer
Project IPC Server
McpBridgeServer.cs] CMD[Tool System
UnityApiHandler.cs] UI[McpEditorWindow
GUI
McpEditorWindow.cs] - CTMS[ConnectedToolsMonitoringService
Auto-startup Tool Monitoring
ConnectedToolsMonitoringService.cs] API[Unity APIs] SM[McpSessionManager
McpSessionManager.cs] end - Claude -.->|MCP Protocol
stdio/TCP| MCP - Cursor -.->|MCP Protocol
stdio/TCP| MCP - VSCode -.->|MCP Protocol
stdio/TCP| MCP - - MCP <--> UC - UCM --> UC - UCM --> UD - UD -.->|Port Discovery
Polling| MB - UC <-->|TCP/JSON-RPC
UNITY_TCP_PORT| MB - UC -->|setClientName| MB + Agent -->|executes command| CLI + CLI <-->|Project IPC JSON-RPC| MB MB <--> CMD CMD <--> API - UI --> CTMS - CTMS --> MB MB --> SM - CTMS --> SM classDef client fill:#e1f5fe,stroke:#01579b,stroke-width:2px classDef server fill:#f3e5f5,stroke:#4a148c,stroke-width:2px classDef bridge fill:#fff3e0,stroke:#e65100,stroke-width:2px - class Claude,Cursor,VSCode client - class MCP,MB server - class UC,UD,UCM bridge + class Agent,CLI client + class MB server ``` ### Client-Server Relationship Breakdown @@ -61,80 +40,64 @@ graph TB ```mermaid graph LR subgraph "Communication Layers" - LLM[LLM Tools
CLIENT] - TS[TypeScript Server
SERVER for MCP
CLIENT for Unity] - Unity[Unity Editor
SERVER for TCP] + CLI[uloop CLI
CLIENT] + Unity[Unity Editor
PROJECT IPC SERVER] end - LLM -->|"MCP Protocol
stdio/TCP
Port: Various"| TS - TS -->|"TCP/JSON-RPC
Port: 8700-9100"| Unity + CLI -->|"Project IPC JSON-RPC
Port: 8700-9100"| Unity classDef client fill:#e1f5fe,stroke:#01579b,stroke-width:2px classDef server fill:#f3e5f5,stroke:#4a148c,stroke-width:2px classDef hybrid fill:#fff3e0,stroke:#e65100,stroke-width:2px - class LLM client + class CLI client class Unity server - class TS hybrid ``` ### Protocol and Communication Details ```mermaid sequenceDiagram - participant LLM as LLM Tool
(CLIENT) - participant TS as TypeScript Server
(MCP SERVER) - participant UC as UnityClient
(TypeScript TCP CLIENT)
unity-client.ts - participant Unity as Unity Editor
(TCP SERVER)
McpBridgeServer.cs + participant Agent as AI Agent or Developer + participant CLI as uloop CLI
(CLIENT) + participant Unity as Unity Editor
(PROJECT IPC SERVER)
McpBridgeServer.cs - Note over LLM, Unity: 1. MCP Protocol Layer (stdio/TCP) - LLM->>TS: MCP initialize request - TS->>LLM: MCP initialize response + Agent->>CLI: uloop command + CLI->>Unity: Project IPC JSON-RPC request + Unity->>CLI: Project IPC JSON-RPC response + CLI->>Agent: command output - Note over LLM, Unity: 2. TCP Protocol Layer (JSON-RPC) - LLM->>TS: MCP tools/call request - TS->>UC: Parse and forward - UC->>Unity: TCP JSON-RPC request - Unity->>UC: TCP JSON-RPC response - UC->>TS: Parse and forward - TS->>LLM: MCP tools/call response - - Note over LLM, Unity: Client-Server Roles: - Note over LLM: CLIENT: Initiates requests - Note over TS: SERVER: Serves MCP protocol - Note over UC: TypeScript TCP CLIENT: Connects to Unity - Note over Unity: SERVER: Accepts TCP connections + Note over CLI, Unity: Client-Server Roles: + Note over CLI: CLIENT: opens short-lived command sessions + Note over Unity: SERVER: accepts project IPC requests ``` ### Communication Protocol Summary | Component | Role | Protocol | Port | Connection Type | |-----------|------|----------|------|----------------| -| **LLM Tools** (Claude, Cursor, VSCode) | **CLIENT** | MCP Protocol | stdio/Various | Initiates MCP requests | -| **TypeScript Server** | **SERVER** (for MCP)
**CLIENT** (for Unity) | MCP ↔ TCP/JSON-RPC | stdio ↔ 8700-9100 | Bridge between protocols | -| **Unity Editor** | **SERVER** | TCP/JSON-RPC | 8700-9100 | Accepts TCP connections | +| **uloop CLI** | **CLIENT** | Project IPC JSON-RPC | 8700-9100 | Initiates Unity tool requests | +| **Unity Editor** | **SERVER** | Project IPC JSON-RPC | 8700-9100 | Accepts local project IPC connections | ### Communication Flow Details -#### Layer 1: LLM Tools ↔ TypeScript Server (MCP Protocol) -- **Protocol**: Model Context Protocol (MCP) -- **Transport**: stdio or TCP -- **Data Format**: JSON-RPC 2.0 with MCP extensions -- **Connection**: LLM tools act as MCP clients -- **Lifecycle**: Managed by LLM tool (Claude, Cursor, VSCode) +#### Layer 1: Caller ↔ uloop CLI +- **Protocol**: Command line invocation +- **Transport**: Local process execution +- **Connection**: A caller starts `uloop` commands as needed +- **Lifecycle**: Managed by the caller process -#### Layer 2: TypeScript Server ↔ Unity Editor (TCP Protocol) +#### Layer 2: uloop CLI ↔ Unity Editor (Project IPC Protocol) - **Protocol**: Custom TCP with JSON-RPC 2.0 - **Transport**: TCP Socket - **Ports**: UNITY_TCP_PORT environment variable specified port (auto-discovery) -- **Connection**: TypeScript server acts as TCP client -- **Lifecycle**: Managed by UnityConnectionManager with automatic reconnection +- **Connection**: `uloop` acts as the project IPC client +- **Lifecycle**: Managed per command invocation #### Key Architectural Points: -1. **TypeScript Server serves as a Protocol Bridge**: Converts MCP protocol to TCP/JSON-RPC -2. **Unity Editor is the final TCP Server**: Processes tool requests and executes Unity operations -3. **LLM Tools are pure MCP Clients**: Send tool requests through standard MCP protocol -4. **Automatic Discovery**: TypeScript server discovers Unity instances through port scanning +1. **uloop CLI is the external entry point**: Commands are translated into project IPC JSON-RPC requests. +2. **Unity Editor is the project IPC server**: Processes tool requests and executes Unity operations. +3. **Automatic Discovery**: The CLI discovers the matching Unity instance for the current project. ### TCP/JSON-RPC Communication Specification @@ -162,7 +125,7 @@ Content-Length: 120 "id": 1647834567890, "method": "ping", "params": { - "Message": "Hello Unity MCP!" + "Message": "Hello Unity CLI Loop!" } } ``` @@ -173,7 +136,7 @@ Content-Length: 120 "jsonrpc": "2.0", "id": 1647834567890, "result": { - "Message": "Unity MCP Bridge received: Hello Unity MCP!", + "Message": "Unity CLI Loop bridge received: Hello Unity CLI Loop!", "ExecutionTimeMs": 5 } } @@ -199,63 +162,20 @@ Content-Length: 120 #### Connection Lifecycle 1. **Initial Connection** - - TypeScript UnityClient connects to Unity McpBridgeServer + - `uloop` CLI connects to Unity `McpBridgeServer` - TCP socket established on localhost:8700 - Connection test with ping command -2. **Client Registration** - - `set-client-name` command sent immediately after connection - - Client identity stored in Unity session manager - - UI updated to show connected client - -3. **Command Processing** +2. **Command Processing** - JSON-RPC requests processed through UnityApiHandler - Security validation via McpSecurityChecker - Tool execution through UnityCommandRegistry -4. **Connection Monitoring** +3. **Connection Lifecycle** - Automatic reconnection on connection loss - Periodic health checks via ping commands - SafeTimer cleanup on process termination -#### Push Notifications - -Unity can send real-time push notifications to all connected TypeScript clients when tools or system state changes occur: - -**Notification Format:** -```json -{ - "jsonrpc": "2.0", - "method": "notifications/tools/list_changed", - "params": { - "timestamp": "2025-07-16T12:34:56.789Z", - "message": "Unity tools have been updated" - } -} -``` - -**Notification Triggers:** -- Assembly reloads/recompilation -- Custom tool registration -- Manual tool change notifications via `TriggerToolChangeNotification()` - -**Broadcast Mechanism:** -- Sent to all connected clients simultaneously -- Uses same TCP/JSON-RPC communication channel -- Message terminated with newline character (`\n`) - -**TypeScript Client Reception:** -```typescript -// TypeScript clients receive notifications via: -socket.on('data', (buffer: Buffer) => { - const message = buffer.toString('utf8'); - if (message.includes('"method":"notifications/tools/list_changed"')) { - // Handle tool list update - this.refreshToolList(); - } -}); -``` - #### Error Handling - **SecurityBlocked**: Tool blocked by security settings @@ -271,12 +191,12 @@ socket.on('data', (buffer: Buffer) => { - **Session Management**: Client isolation and state tracking Its primary responsibilities are: -1. **Running a TCP Server (`McpBridgeServer`)**: Listens for connections from the TypeScript server to receive tool requests. +1. **Running a Project IPC Server (`McpBridgeServer`)**: Listens for local `uloop` CLI connections to receive tool requests. 2. **Executing Unity Operations**: Processes received tool requests to perform actions within the Unity Editor, such as compiling the project, running tests, or retrieving logs. 3. **Security Management**: Validates and controls tool execution through `McpSecurityChecker` to prevent unauthorized operations. -4. **Session Management**: Maintains client sessions and connection state through `McpSessionManager`. -5. **Providing a User Interface (`McpEditorWindow`)**: Offers a GUI within the Unity Editor for developers to manage and monitor the MCP server. -6. **Managing Configuration**: Handles the setup of `mcp.json` files required by LLM tools like Cursor, Claude, and VSCode. +4. **Session Management**: Maintains server session state through `McpSessionManager`. +5. **Providing a User Interface (`McpEditorWindow`)**: Offers a GUI within the Unity Editor for developers to manage the CLI setup, skills, security settings, and project IPC server state. +6. **Managing Configuration**: Persists Unity CLI Loop settings and skill installation state for supported AI tools. ## 2. Core Architectural Principles @@ -295,8 +215,8 @@ The system is centered around **Domain-Driven Design** integrated **UseCase + To - **Unified Service Results**: All services return results via `ServiceResult` - **Examples**: `CompilationExecutionService`, `LogRetrievalService`, `TestExecutionService` -#### Tool Layer (MCP Interface) -- **`IUnityTool`**: Common interface for all tools (MCP connection point) +#### Tool Layer (CLI Command Interface) +- **`IUnityTool`**: Common interface for all tools exposed through the CLI command dispatcher - **`AbstractUnityTool`**: Provides type-safe handling of parameters and responses - **`McpToolAttribute`**: Attribute for automatic tool registration - **Tool Implementation**: Each tool calls UseCases to execute business logic @@ -458,8 +378,8 @@ This design eliminates inconsistencies between the server and client and provide - **Single Responsibility Principle (SRP)**: Each class has a well-defined responsibility. - `McpBridgeServer`: Handles raw TCP communication. - `McpServerController`: Manages the server's lifecycle and state across domain reloads. - - `McpConfigRepository`: Handles file I/O for configuration. - - `McpConfigService`: Implements the business logic for configuration. + - `McpEditorSettings`: Handles Editor window preference persistence. + - `ToolSkillSynchronizer`: Handles skill installation file updates. - `JsonRpcProcessor`: Deals exclusively with parsing and formatting JSON-RPC 2.0 messages. - **UI Layer Examples**: - `McpEditorModel`: Manages application state and business logic only. @@ -478,7 +398,7 @@ The UI layer implements a sophisticated **MVP (Model-View-Presenter) + Helper Pa - **Helper Classes**: Specialized components that handle specific aspects of functionality: - Event management (`McpEditorWindowEventHandler`) - Server operations (`McpServerOperations`) - - Configuration services (`McpConfigServiceFactory`) + - Skill installation services (`ToolSkillSynchronizer`) #### Benefits of This Architecture 1. **Separation of Concerns**: Each component has a single, clear responsibility @@ -500,7 +420,6 @@ A significant challenge in the Unity Editor is the "domain reload," which resets - **`DomainReloadRecoveryUseCase`**: Orchestrates the entire domain reload workflow - **`DomainReloadDetectionService`**: Detects and determines domain reload state - **`SessionRecoveryService`**: Handles session state preservation and restoration -- **`ClientNotificationService`**: Manages client notification processing #### McpServerController Integration - **`McpServerController`**: Uses `[InitializeOnLoad]` to hook into Editor lifecycle events @@ -511,19 +430,17 @@ A significant challenge in the Unity Editor is the "domain reload," which resets #### Orchestrated Workflow 1. **Before Reload**: `DomainReloadRecoveryUseCase.ExecuteBeforeDomainReload()` saves server state 2. **After Reload**: `DomainReloadRecoveryUseCase.ExecuteAfterDomainReloadAsync()` restores state -3. **Client Notification**: Automatic tool list change notifications ensure seamless experience This UseCase integration ensures domain reload processing is managed as a single business workflow, improving maintainability and reliability. ## 3. Implemented UseCases and Tools -The system currently implements 13 production-ready features using **Domain-Driven Design** architecture with **UseCase + Tool Pattern**. Each feature provides business workflow orchestration through UseCases, single-function implementation through Application Services, and MCP interface through Tools: +The system currently implements 12 production-ready features using **Domain-Driven Design** architecture with **UseCase + Tool Pattern**. Each feature provides business workflow orchestration through UseCases, single-function implementation through Application Services, and CLI-facing tool commands: ### 3.1. Core System UseCases and Tools - **`PingTool`**: Connection health check and latency testing - **`CompileUseCase` + `CompileTool`**: Compilation state validation and execution separated by Application Services, with detailed error reporting - **`ClearConsoleTool`**: Unity Console log clearing with confirmation -- **`SetClientNameTool`**: Client identification and session management - **`GetCommandDetailsTool`**: Tool introspection and metadata retrieval ### 3.2. Information Retrieval UseCases and Tools @@ -551,7 +468,7 @@ Several UseCases and Tools are subject to security restrictions and can be disab - **`McpServerShutdownUseCase`**: Manages proper server shutdown processing - **`DomainReloadRecoveryUseCase`**: Completely orchestrates state management before/after domain reloads -These UseCases are not directly exposed as MCP Tools but are called internally by `McpServerController` to manage the system lifecycle. +These UseCases are not directly exposed as CLI tools but are called internally by `McpServerController` to manage the system lifecycle. ## 4. Key Components (Directory Breakdown) @@ -576,22 +493,19 @@ This is the heart of the command processing logic. - **`AbstractUnityCommand.cs`**: The generic base class that simplifies command creation by handling the boilerplate of parameter deserialization and response creation. - **`UnityCommandRegistry.cs`**: Discovers all classes with the `[McpTool]` attribute and registers them in a dictionary, mapping a command name to its implementation. - **`McpToolAttribute.cs`**: A simple attribute used to mark a class for automatic registration as a command. - - **Command-specific folders**: Each of the 13 implemented commands has its own folder containing: + - **Command-specific folders**: Each implemented command has its own folder containing: - `*Command.cs`: The main command implementation - `*Schema.cs`: Type-safe parameter definition - `*Response.cs`: Structured response format - - Commands include: `/Compile`, `/RunTests`, `/GetLogs`, `/Ping`, `/ClearConsole`, `/FindGameObjects`, `/GetHierarchy`, `/GetMenuItems`, `/ExecuteMenuItem`, `/SetClientName`, `/UnitySearch`, `/GetProviderDetails`, `/GetCommandDetails` + - Commands include: `/Compile`, `/RunTests`, `/GetLogs`, `/Ping`, `/ClearConsole`, `/FindGameObjects`, `/GetHierarchy`, `/GetMenuItems`, `/ExecuteMenuItem`, `/UnitySearch`, `/GetProviderDetails`, `/GetCommandDetails` - **`JsonRpcProcessor.cs`**: Responsible for parsing incoming JSON strings into `JsonRpcRequest` objects and serializing response objects back into JSON strings, adhering to the JSON-RPC 2.0 specification. - **`UnityApiHandler.cs`**: The entry point for API calls. It receives the method name and parameters from the `JsonRpcProcessor` and uses the `UnityCommandRegistry` to execute the appropriate command. Integrates with `McpSecurityChecker` for permission validation. ### `/Core` Contains core infrastructure components for session and state management. -#### Application Services Directory -- **`ConnectedToolsMonitoringService.cs`**: Auto-startup service for ConnectedLLMTools monitoring and management with `[InitializeOnLoad]` attribute. Operates even when editor window is closed, handling connected tools state management, server event processing, and settings file synchronization. Provides proper separation between presentation layer and business logic. - #### Session Management -- **`McpSessionManager.cs`**: Singleton session manager implemented as `ScriptableSingleton` that maintains client connection state, session metadata, and survives domain reloads. Provides centralized client identification and connection tracking. +- **`McpSessionManager.cs`**: Singleton session manager implemented as `ScriptableSingleton` that maintains server session metadata and survives domain reloads. ### `/UI` Contains the code for the user-facing Editor Window, implemented using the **MVP (Model-View-Presenter) + Helper Pattern**. @@ -603,7 +517,7 @@ Contains the code for the user-facing Editor Window, implemented using the **MVP - **`McpEditorWindowViewData.cs`**: Data transfer object that carries all necessary information from the Model to the View, ensuring clean separation of concerns. #### Specialized Helper Classes -- **`McpEditorWindowEventHandler.cs`**: Manages Unity Editor events (194 lines). Handles `EditorApplication.update`, `McpCommunicationLogger.OnLogUpdated`, server connection events, and state change detection. Completely isolates event management logic from the main window. +- **`McpEditorWindowEventHandler.cs`**: Manages Unity Editor events. Handles `EditorApplication.update`, `McpCommunicationLogger.OnLogUpdated`, server lifecycle events, and state change detection. Completely isolates event management logic from the main window. - **`McpServerOperations.cs`**: Handles complex server operations (131 lines). Contains server validation, starting, and stopping logic. Supports both user-interactive and internal operation modes with comprehensive error handling. - **`McpCommunicationLog.cs`**: Manages the in-memory and `SessionState`-backed log of requests and responses displayed in the "Developer Tools" section of the window. @@ -616,10 +530,12 @@ This MVP + Helper pattern provides: - **Reduced Complexity**: The main Presenter went from 1247 lines to 503 lines (59% reduction) through proper responsibility distribution ### `/Config` -Manages the creation and modification of `mcp.json` configuration files. -- **`UnityMcpPathResolver.cs`**: A utility to find the correct path for configuration files for different editors (Cursor, VSCode, etc.). -- **`McpConfigRepository.cs`**: Handles the direct reading and writing of the `mcp.json` file. -- **`McpConfigService.cs`**: Contains the logic for auto-configuring the `mcp.json` file with the correct command, arguments, and environment variables based on the user's settings in the `McpEditorWindow`. +Manages Unity CLI Loop Editor settings, tool access settings, skill installation, and project path resolution. +- **`McpEditorSettings.cs`**: Persists Editor window state and setup preferences. +- **`ULoopSettings.cs`**: Persists CLI-related setup and security-adjacent settings. +- **`ToolSettings.cs`**: Stores per-tool access settings used by the security layer. +- **`ToolSkillSynchronizer.cs`**: Installs generated skill files into supported AI tool directories. +- **`UnityMcpPathResolver.cs`**: Resolves the Unity project root and package paths. The class name is historical. ### `/Tools` Contains higher-level utilities that wrap core Unity Editor functionality. @@ -634,7 +550,7 @@ Contains higher-level utilities that wrap core Unity Editor functionality. Contains low-level, general-purpose helper classes. - **`MainThreadSwitcher.cs`**: A crucial utility that provides an `awaitable` object to switch execution from a background thread (like the TCP server's) back to Unity's main thread. This is essential because most Unity APIs can only be called from the main thread. - **`EditorDelay.cs`**: A custom, `async/await`-compatible implementation of a frame-based delay, useful for waiting a few frames for the Editor to reach a stable state, especially after domain reloads. -- **`McpLogger.cs`**: A simple, unified logging wrapper to prefix all package-related logs with `[uLoopMCP]`. +- **`VibeLogger.cs`**: AI-friendly structured logger for Unity CLI Loop diagnostics. ## 5. Key Workflows @@ -642,8 +558,8 @@ Contains low-level, general-purpose helper classes. ```mermaid sequenceDiagram - box TypeScript CLIENT - participant TS as TypeScript Client
unity-client.ts + box CLI CLIENT + participant CLI as uloop CLI end box Unity TCP SERVER @@ -658,7 +574,7 @@ sequenceDiagram participant AS as Application Service
*Service.cs end - TS->>MB: JSON String + CLI->>MB: JSON String MB->>JP: ProcessRequest(json) JP->>JP: Deserialize to JsonRpcRequest JP->>UA: ExecuteToolAsync(name, params) @@ -684,7 +600,7 @@ sequenceDiagram UA-->>JP: Response JP->>JP: Serialize to JSON JP-->>MB: JSON Response - MB-->>TS: Send Response + MB-->>CLI: Send Response ``` ### 5.2. UI Interaction Flow (MVP + Helper Pattern) @@ -694,7 +610,7 @@ sequenceDiagram 4. **Complex Operations**: For complex operations (server start/stop, validation), Presenter delegates to specialized helper classes: - `McpServerOperations` for server-related operations - `McpEditorWindowEventHandler` for event management - - `McpConfigServiceFactory` for configuration operations + - `ToolSkillSynchronizer` for skill installation operations 5. **View Data Preparation**: Model state is packaged into `McpEditorWindowViewData` transfer objects. 6. **UI Rendering**: `McpEditorWindowView` receives the transfer objects and renders the interface. 7. **Event Propagation**: `McpEditorWindowEventHandler` manages Unity Editor events and updates the Model accordingly. @@ -726,4 +642,4 @@ sequenceDiagram SC-->>UA: Validation Success end UA->>Command: Execute (if validated) -``` \ No newline at end of file +``` diff --git a/Packages/docs/ARCHITECTURE_Unity_ja.md b/Packages/docs/ARCHITECTURE_Unity_ja.md index 1dc656493..38311c3df 100644 --- a/Packages/docs/ARCHITECTURE_Unity_ja.md +++ b/Packages/docs/ARCHITECTURE_Unity_ja.md @@ -1,59 +1,38 @@ -# uLoopMCP Unity Editor側アーキテクチャ +# Unity CLI Loop Unity Editor側アーキテクチャ ## 1. 概要 -このドキュメントは、`Packages/src/Editor` ディレクトリ内のC#コードのアーキテクチャについて詳細に説明します。このコードはUnity Editor内で動作し、Unity環境と外部TypeScriptベースのMCP(Model-Context-Protocol)サーバーとの橋渡しの役割を果たします。 +このドキュメントは、`Packages/src/Editor` ディレクトリ内のC#コードのアーキテクチャについて詳細に説明します。このコードはUnity Editor内で動作し、Unity環境と外部`uloop` CLIとの橋渡しの役割を果たします。 ### システムアーキテクチャ概要 ```mermaid graph TB - subgraph "1. LLMツール(MCPクライアント)" - Claude[Claude Code
MCPクライアント] - Cursor[Cursor
MCPクライアント] - VSCode[VSCode
MCPクライアント] + subgraph "1. CLI呼び出し元" + Agent[AI Agentまたは開発者Shell] + CLI[uloop CLI] end - subgraph "2. TypeScriptサーバー(MCPサーバー + Unity TCPクライアント)" - MCP[UnityMcpServer
MCPプロトコルサーバー
server.ts] - UC[UnityClient
TypeScript TCPクライアント
unity-client.ts] - UCM[UnityConnectionManager
接続オーケストレーター
unity-connection-manager.ts] - UD[UnityDiscovery
Unityポートスキャナー
unity-discovery.ts] - end - - subgraph "3. Unity Editor(TCPサーバー)" - MB[McpBridgeServer
TCPサーバー
McpBridgeServer.cs] + subgraph "2. Unity Editor(Project IPCサーバー)" + MB[McpBridgeServer
Project IPCサーバー
McpBridgeServer.cs] CMD[ツールシステム
UnityApiHandler.cs] UI[McpEditorWindow
GUI
McpEditorWindow.cs] - CTMS[ConnectedToolsMonitoringService
自動起動ツール監視
ConnectedToolsMonitoringService.cs] API[Unity APIs] SM[McpSessionManager
McpSessionManager.cs] end - Claude -.->|MCPプロトコル
stdio/TCP| MCP - Cursor -.->|MCPプロトコル
stdio/TCP| MCP - VSCode -.->|MCPプロトコル
stdio/TCP| MCP - - MCP <--> UC - UCM --> UC - UCM --> UD - UD -.->|ポート発見
ポーリング| MB - UC <-->|TCP/JSON-RPC
UNITY_TCP_PORT| MB - UC -->|setClientName| MB + Agent -->|コマンド実行| CLI + CLI <-->|Project IPC JSON-RPC| MB MB <--> CMD CMD <--> API - UI --> CTMS - CTMS --> MB MB --> SM - CTMS --> SM classDef client fill:#e1f5fe,stroke:#01579b,stroke-width:2px classDef server fill:#f3e5f5,stroke:#4a148c,stroke-width:2px classDef bridge fill:#fff3e0,stroke:#e65100,stroke-width:2px - class Claude,Cursor,VSCode client - class MCP,MB server - class UC,UD,UCM bridge + class Agent,CLI client + class MB server ``` ### クライアント・サーバー関係の内訳 @@ -61,66 +40,52 @@ graph TB ```mermaid graph LR subgraph "通信レイヤー" - LLM[LLMツール
クライアント] - TS[TypeScriptサーバー
MCPのサーバー
Unityのクライアント] - Unity[Unity Editor
サーバー] + CLI[uloop CLI
クライアント] + Unity[Unity Editor
Project IPCサーバー] end - LLM -->|"MCPプロトコル
stdio/TCP
ポート: 様々"| TS - TS -->|"TCP/JSON-RPC
ポート: 8700-9100"| Unity + CLI -->|"Project IPC JSON-RPC
ポート: 8700-9100"| Unity classDef client fill:#e1f5fe,stroke:#01579b,stroke-width:2px classDef server fill:#f3e5f5,stroke:#4a148c,stroke-width:2px classDef hybrid fill:#fff3e0,stroke:#e65100,stroke-width:2px - class LLM client + class CLI client class Unity server - class TS hybrid ``` ### プロトコルと通信詳細 ```mermaid sequenceDiagram - participant LLM as LLMツール
(クライアント) - participant TS as TypeScriptサーバー
(MCPサーバー) - participant UC as UnityClient
(TypeScript TCPクライアント)
unity-client.ts - participant Unity as Unity Editor
(TCPサーバー)
McpBridgeServer.cs + participant Agent as AI Agentまたは開発者 + participant CLI as uloop CLI
(クライアント) + participant Unity as Unity Editor
(Project IPCサーバー)
McpBridgeServer.cs - Note over LLM, Unity: 1. MCPプロトコル層(stdio/TCP) - LLM->>TS: MCP初期化リクエスト - TS->>LLM: MCP初期化レスポンス + Agent->>CLI: uloopコマンド + CLI->>Unity: Project IPC JSON-RPCリクエスト + Unity->>CLI: Project IPC JSON-RPCレスポンス + CLI->>Agent: コマンド出力 - Note over LLM, Unity: 2. TCPプロトコル層(JSON-RPC) - LLM->>TS: MCPツール/呼び出しリクエスト - TS->>UC: 解析して転送 - UC->>Unity: TCP JSON-RPCリクエスト - Unity->>UC: TCP JSON-RPCレスポンス - UC->>TS: 解析して転送 - TS->>LLM: MCPツール/呼び出しレスポンス - - Note over LLM, Unity: クライアント・サーバーの役割: - Note over LLM: クライアント: リクエストを開始 - Note over TS: サーバー: MCPプロトコルを提供 - Note over UC: TypeScript TCPクライアント: Unityに接続 - Note over Unity: サーバー: TCP接続を受け入れ + Note over CLI, Unity: クライアント・サーバーの役割: + Note over CLI: クライアント: 短命なコマンドセッションを開く + Note over Unity: サーバー: Project IPCリクエストを受け入れ ``` ### 通信プロトコルの概要 | コンポーネント | 役割 | プロトコル | ポート | 接続タイプ | |-----------|------|----------|------|----------------| -| **LLMツール**(Claude、Cursor、VSCode) | **クライアント** | MCPプロトコル | stdio/様々 | MCPリクエストを開始 | -| **TypeScriptサーバー** | **サーバー**(MCPの)
**クライアント**(Unityの) | MCP ↔ TCP/JSON-RPC | stdio ↔ 8700-9100 | プロトコル間のブリッジ | -| **Unity Editor** | **サーバー** | TCP/JSON-RPC | 8700-9100 | TCP接続を受け入れ | +| **uloop CLI** | **クライアント** | Project IPC JSON-RPC | 8700-9100 | Unityツールリクエストを開始 | +| **Unity Editor** | **サーバー** | Project IPC JSON-RPC | 8700-9100 | ローカルProject IPC接続を受け入れ | 主な責務は以下の通りです: -1. **TCPサーバーの実行(`McpBridgeServer`)**: TypeScriptサーバーからの接続をリッスンしてツールリクエストを受信 +1. **Project IPCサーバーの実行(`McpBridgeServer`)**: ローカル`uloop` CLIからの接続をリッスンしてツールリクエストを受信 2. **Unity操作の実行**: 受信したツールリクエストを処理してUnity Editor内でアクションを実行(プロジェクトのコンパイル、テスト実行、ログ取得など) 3. **セキュリティ管理**: `McpSecurityChecker`を通じてツール実行を検証・制御し、未承認操作を防止 4. **セッション管理**: `McpSessionManager`を通じてクライアントセッションと接続状態を維持 -5. **ユーザーインターフェースの提供(`McpEditorWindow`)**: 開発者がMCPサーバーを管理・監視するためのUnity Editor内GUI -6. **設定管理**: Cursor、Claude、VSCodeなどのLLMツールに必要な`mcp.json`ファイルの設定を処理 +5. **ユーザーインターフェースの提供(`McpEditorWindow`)**: 開発者がCLIセットアップ、スキル、セキュリティ設定、Project IPCサーバー状態を管理するためのUnity Editor内GUI +6. **設定管理**: Unity CLI Loopの設定と対応AIツール向けスキルのインストール状態を永続化 ## 2. コアアーキテクチャ原則 @@ -139,8 +104,8 @@ sequenceDiagram - **Service結果の統一**: すべてのサービスが`ServiceResult`で結果を返す - **例**: `CompilationExecutionService`、`LogRetrievalService`、`TestExecutionService` -#### ツール層(MCPインターフェース) -- **`IUnityTool`**: すべてのツールの共通インターフェース(MCP接続点) +#### ツール層(CLIコマンドインターフェース) +- **`IUnityTool`**: CLIコマンドディスパッチャー経由で公開されるすべてのツールの共通インターフェース - **`AbstractUnityTool`**: パラメータとレスポンスの型安全な処理を提供 - **`McpToolAttribute`**: ツールを自動登録にマークする属性 - **ツール実装**: 各ツールはUseCaseを呼び出してビジネスロジックを実行 @@ -302,8 +267,8 @@ classDiagram - **単一責任原則(SRP)**: 各クラスは明確に定義された責任を持ちます。 - `McpBridgeServer`: 生のTCP通信を処理 - `McpServerController`: サーバーのライフサイクルとドメインリロード間の状態を管理 - - `McpConfigRepository`: 設定のファイルI/Oを処理 - - `McpConfigService`: 設定のビジネスロジックを実装 + - `McpEditorSettings`: Editor Window設定の永続化を処理 + - `ToolSkillSynchronizer`: スキルインストールファイルの更新を処理 - `JsonRpcProcessor`: JSON-RPC 2.0メッセージの解析と整形のみを扱う - **UIレイヤーの例**: - `McpEditorModel`: アプリケーション状態とビジネスロジックのみを管理 @@ -322,7 +287,7 @@ UIレイヤーは、1247行のモノリシッククラスから構造化され - **Helperクラス**: 機能の特定の側面を処理する専門コンポーネント: - イベント管理(`McpEditorWindowEventHandler`) - サーバー操作(`McpServerOperations`) - - 設定サービス(`McpConfigServiceFactory`) + - スキルインストールサービス(`ToolSkillSynchronizer`) #### このアーキテクチャの利点 1. **関心の分離**: 各コンポーネントが単一の明確な責任を持つ @@ -344,7 +309,6 @@ Unity Editorの重要な課題は、アプリケーションの状態をリセ - **`DomainReloadRecoveryUseCase`**: ドメインリロードのワークフロー全体を統制 - **`DomainReloadDetectionService`**: ドメインリロードの検出と状態判定 - **`SessionRecoveryService`**: セッション状態の保存と復元 -- **`ClientNotificationService`**: クライアントへの通知処理 #### McpServerController統合 - **`McpServerController`**: `[InitializeOnLoad]`を使用してEditorライフサイクルイベントにフック @@ -355,19 +319,17 @@ Unity Editorの重要な課題は、アプリケーションの状態をリセ #### 統制されたワークフロー 1. **リロード前**: `DomainReloadRecoveryUseCase.ExecuteBeforeDomainReload()`でサーバー状態を保存 2. **リロード後**: `DomainReloadRecoveryUseCase.ExecuteAfterDomainReloadAsync()`で状態を復元 -3. **クライアント通知**: 自動的なツールリスト変更通知でシームレスな体験を保証 このUseCase統合により、ドメインリロード処理が単一のビジネスワークフローとして管理され、保守性と信頼性が向上しています。 ## 3. 実装されたUseCaseとツール -システムは現在、**Domain-Driven Design**アーキテクチャに基づく**UseCase + ツールパターン**で実装された13の本番対応機能を提供しています。各機能はUseCaseでビジネスワークフローを統制し、Application Serviceで単一機能を実装し、ToolでMCPインターフェースを提供しています: +システムは現在、**Domain-Driven Design**アーキテクチャに基づく**UseCase + ツールパターン**で実装された12の本番対応機能を提供しています。各機能はUseCaseでビジネスワークフローを統制し、Application Serviceで単一機能を実装し、CLI向けツールコマンドとして提供しています: ### 3.1. コアシステムUseCaseとツール - **`PingTool`**: 接続ヘルスチェックと遅延テスト - **`CompileUseCase` + `CompileTool`**: コンパイル状態検証と実行をApplication Serviceで分離、詳細エラーレポート付き - **`ClearConsoleTool`**: 確認付きUnity Consoleログクリア -- **`SetClientNameTool`**: クライアント識別とセッション管理 - **`GetCommandDetailsTool`**: ツール内省とメタデータ取得 ### 3.2. 情報取得UseCaseとツール @@ -395,7 +357,7 @@ Unity Editorの重要な課題は、アプリケーションの状態をリセ - **`McpServerShutdownUseCase`**: サーバー終了の適切な処理を管理 - **`DomainReloadRecoveryUseCase`**: ドメインリロード前後の状態管理を完全に統制 -これらのUseCaseはMCP Toolとして直接公開されず、`McpServerController`の内部で呼び出されてシステムのライフサイクルを管理しています。 +これらのUseCaseはCLI向けツールコマンドとして直接公開されず、`McpServerController`の内部で呼び出されてシステムのライフサイクルを管理しています。 ## 4. 主要コンポーネント(ディレクトリ別) @@ -420,22 +382,19 @@ Unity Editorの重要な課題は、アプリケーションの状態をリセ - **`AbstractUnityCommand.cs`**: パラメータのデシリアライゼーションとレスポンス作成の定型文を処理してコマンド作成を簡素化する汎用基底クラス - **`UnityCommandRegistry.cs`**: `[McpTool]`属性を持つすべてのクラスを発見し、コマンド名をその実装にマッピングする辞書に登録 - **`McpToolAttribute.cs`**: クラスをコマンドとして自動登録にマークするために使用される単純な属性 - - **コマンド固有フォルダー**: 実装された13のコマンドにはそれぞれ以下を含むフォルダーがあります: + - **コマンド固有フォルダー**: 実装された各コマンドにはそれぞれ以下を含むフォルダーがあります: - `*Command.cs`: メインコマンド実装 - `*Schema.cs`: 型安全パラメータ定義 - `*Response.cs`: 構造化レスポンス形式 - - コマンドには以下が含まれます: `/Compile`、`/RunTests`、`/GetLogs`、`/Ping`、`/ClearConsole`、`/FindGameObjects`、`/GetHierarchy`、`/GetMenuItems`、`/ExecuteMenuItem`、`/SetClientName`、`/UnitySearch`、`/GetProviderDetails`、`/GetCommandDetails` + - コマンドには以下が含まれます: `/Compile`、`/RunTests`、`/GetLogs`、`/Ping`、`/ClearConsole`、`/FindGameObjects`、`/GetHierarchy`、`/GetMenuItems`、`/ExecuteMenuItem`、`/UnitySearch`、`/GetProviderDetails`、`/GetCommandDetails` - **`JsonRpcProcessor.cs`**: 受信JSON文字列を`JsonRpcRequest`オブジェクトに解析し、レスポンスオブジェクトをJSON文字列にシリアライズしてJSON-RPC 2.0仕様に準拠 - **`UnityApiHandler.cs`**: API呼び出しのエントリーポイント。`JsonRpcProcessor`からメソッド名とパラメータを受け取り、`UnityCommandRegistry`を使用して適切なコマンドを実行。権限検証のために`McpSecurityChecker`と統合 ### `/Core` セッションと状態管理のコアインフラストラクチャコンポーネントを含みます。 -#### Application Servicesディレクトリ -- **`ConnectedToolsMonitoringService.cs`**: `[InitializeOnLoad]`属性で自動起動するConnectedLLMToolsの監視・管理サービス。エディターウィンドウが開いていない場合でも動作し、接続されたツールの状態管理、サーバーイベント処理、設定ファイルとの同期を行う。プレゼンテーション層とビジネスロジックの適切な分離を実現 - #### セッション管理 -- **`McpSessionManager.cs`**: クライアント接続状態、セッションメタデータを維持し、ドメインリロードを乗り越える`ScriptableSingleton`として実装されたシングルトンセッションマネージャー。集中クライアント識別と接続追跡を提供 +- **`McpSessionManager.cs`**: サーバーセッションメタデータを維持し、ドメインリロードを乗り越える`ScriptableSingleton`として実装されたシングルトンセッションマネージャー ### `/UI` **MVP(Model-View-Presenter) + Helperパターン**を使用して実装されたユーザー向けEditorWindowのコードを含みます。 @@ -447,7 +406,7 @@ Unity Editorの重要な課題は、アプリケーションの状態をリセ - **`McpEditorWindowViewData.cs`**: ModelからViewにすべての必要な情報を運ぶデータ転送オブジェクト、関心の清潔な分離を保証 #### 専門ヘルパークラス -- **`McpEditorWindowEventHandler.cs`**: Unity Editorイベントを管理(194行)。`EditorApplication.update`、`McpCommunicationLogger.OnLogUpdated`、サーバー接続イベント、状態変更検出を処理。イベント管理ロジックをメインウィンドウから完全に分離 +- **`McpEditorWindowEventHandler.cs`**: Unity Editorイベントを管理。`EditorApplication.update`、`McpCommunicationLogger.OnLogUpdated`、サーバーライフサイクルイベント、状態変更検出を処理。イベント管理ロジックをメインウィンドウから完全に分離 - **`McpServerOperations.cs`**: 複雑なサーバー操作を処理(131行)。サーバー検証、開始、停止ロジックを含む。包括的エラー処理でユーザーインタラクティブと内部操作モードの両方をサポート - **`McpCommunicationLog.cs`**: ウィンドウの「開発者ツール」セクションに表示されるリクエストとレスポンスのインメモリと`SessionState`バック付きログを管理 @@ -460,10 +419,12 @@ Unity Editorの重要な課題は、アプリケーションの状態をリセ - **複雑性の軽減**: メインPresenterが適切な責任分散により1247行から503行(59%削減)に ### `/Config` -`mcp.json`設定ファイルの作成と変更を管理します。 -- **`UnityMcpPathResolver.cs`**: 異なるエディター(Cursor、VSCodeなど)の設定ファイルの正しいパスを見つけるユーティリティ -- **`McpConfigRepository.cs`**: `mcp.json`ファイルの直接読み書きを処理 -- **`McpConfigService.cs`**: `McpEditorWindow`でのユーザー設定に基づいて正しいコマンド、引数、環境変数で`mcp.json`ファイルを自動設定するロジックを含む +Unity CLI LoopのEditor設定、ツールアクセス設定、スキルインストール、プロジェクトパス解決を管理します。 +- **`McpEditorSettings.cs`**: Editor Windowの状態とセットアップ設定を永続化 +- **`ULoopSettings.cs`**: CLI関連のセットアップ設定とセキュリティ隣接設定を永続化 +- **`ToolSettings.cs`**: セキュリティ層で使うツールごとのアクセス設定を保存 +- **`ToolSkillSynchronizer.cs`**: 生成されたスキルファイルを対応AIツールのディレクトリにインストール +- **`UnityMcpPathResolver.cs`**: Unityプロジェクトルートとパッケージパスを解決。クラス名は歴史的なもの ### `/Tools` コアUnity Editor機能をラップする高レベルユーティリティを含みます。 @@ -478,7 +439,7 @@ Unity Editorの重要な課題は、アプリケーションの状態をリセ 低レベル汎用ヘルパークラスを含みます。 - **`MainThreadSwitcher.cs`**: バックグラウンドスレッド(TCPサーバーなど)からUnityのメインスレッドに実行を切り替える`awaitable`オブジェクトを提供する重要ユーティリティ。ほとんどのUnity APIはメインスレッドからのみ呼び出し可能なため不可欠 - **`EditorDelay.cs`**: フレームベース遅延のカスタム`async/await`互換実装。特にドメインリロード後にEditorが安定状態に達するのを数フレーム待つのに有用 -- **`McpLogger.cs`**: すべてのパッケージ関連ログに`[uLoopMCP]`プレフィックスを付ける単純統合ログラッパー +- **`VibeLogger.cs`**: Unity CLI Loop診断向けのAIフレンドリーな構造化ロガー ## 5. 主要ワークフロー @@ -486,8 +447,8 @@ Unity Editorの重要な課題は、アプリケーションの状態をリセ ```mermaid sequenceDiagram - box TypeScript クライアント - participant TS as TypeScript Client
unity-client.ts + box CLI クライアント + participant CLI as uloop CLI end box Unity TCP サーバー @@ -502,7 +463,7 @@ sequenceDiagram participant AS as Application Service
*Service.cs end - TS->>MB: JSON文字列 + CLI->>MB: JSON文字列 MB->>JP: ProcessRequest(json) JP->>JP: JsonRpcRequestにデシリアライズ JP->>UA: ExecuteToolAsync(name, params) @@ -528,7 +489,7 @@ sequenceDiagram UA-->>JP: レスポンス JP->>JP: JSONにシリアライズ JP-->>MB: JSONレスポンス - MB-->>TS: レスポンス送信 + MB-->>CLI: レスポンス送信 ``` ### 5.2. UIインタラクションフロー(MVP + Helperパターン) @@ -538,7 +499,7 @@ sequenceDiagram 4. **複雑な操作**: 複雑な操作(サーバー開始/停止、検証)については、Presenterが専門ヘルパークラスに委譲: - サーバー関連操作は`McpServerOperations` - イベント管理は`McpEditorWindowEventHandler` - - 設定操作は`McpConfigServiceFactory` + - スキルインストール操作は`ToolSkillSynchronizer` 5. **ビューデータ準備**: Model状態が`McpEditorWindowViewData`転送オブジェクトにパッケージ化 6. **UIレンダリング**: `McpEditorWindowView`が転送オブジェクトを受信してインターフェースをレンダリング 7. **イベント伝播**: `McpEditorWindowEventHandler`がUnity Editorイベントを管理してModelを相応に更新 @@ -570,4 +531,4 @@ sequenceDiagram SC-->>UA: 検証成功 end UA->>Command: 実行(検証済みの場合) -``` \ No newline at end of file +``` diff --git a/Packages/src/Editor/Api/McpTools/Core/AbstractUnityTool.cs b/Packages/src/Editor/Api/McpTools/Core/AbstractUnityTool.cs index b27584875..70c634597 100644 --- a/Packages/src/Editor/Api/McpTools/Core/AbstractUnityTool.cs +++ b/Packages/src/Editor/Api/McpTools/Core/AbstractUnityTool.cs @@ -11,7 +11,7 @@ namespace io.github.hatayama.UnityCliLoop // - ToolParameterSchemaGenerator: Generates the JSON schema for tool parameters. /// /// Abstract base class for type-safe Unity tools using Schema and Response types - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Tool Layer (MCP Interface) + /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Tool Layer (CLI Command Interface) /// /// Schema type for tool parameters /// Response type for tool results @@ -120,4 +120,4 @@ protected virtual TSchema ApplyDefaultValues(TSchema schema) return schema; } } -} \ No newline at end of file +} diff --git a/Packages/src/Editor/Api/McpTools/Core/CustomToolManager.cs b/Packages/src/Editor/Api/McpTools/Core/CustomToolManager.cs index a4f37b2f8..f62d50094 100644 --- a/Packages/src/Editor/Api/McpTools/Core/CustomToolManager.cs +++ b/Packages/src/Editor/Api/McpTools/Core/CustomToolManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace io.github.hatayama.UnityCliLoop { @@ -106,13 +105,8 @@ public static string GetDebugInfo() return $"Registry instance: {SharedRegistry.GetHashCode()}, Tools: [{string.Join(", ", toolNames)}]"; } - /// - /// Manually notify tool changes to MCP clients - /// Public API for users to trigger notifications when needed - /// public static void NotifyToolChanges() { - UnityToolRegistry.TriggerToolsChangedNotification(); OnToolsChanged?.Invoke(); } } diff --git a/Packages/src/Editor/Api/McpTools/Core/UnityToolRegistry.cs b/Packages/src/Editor/Api/McpTools/Core/UnityToolRegistry.cs index aeafaaf04..f42a83308 100644 --- a/Packages/src/Editor/Api/McpTools/Core/UnityToolRegistry.cs +++ b/Packages/src/Editor/Api/McpTools/Core/UnityToolRegistry.cs @@ -387,16 +387,6 @@ public bool IsToolRegistered(string toolName) return _tools.ContainsKey(toolName); } - /// - /// Manually trigger _tools changed notification - /// Used for manual notifications and post-compilation notifications - /// - public static void TriggerToolsChangedNotification() - { - // Call the public method in McpServerController - McpServerController.TriggerToolChangeNotification(); - } - } /// diff --git a/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/ExecuteDynamicCodeTool.cs b/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/ExecuteDynamicCodeTool.cs index df6aa15ec..5b19cd26c 100644 --- a/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/ExecuteDynamicCodeTool.cs +++ b/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/ExecuteDynamicCodeTool.cs @@ -4,7 +4,7 @@ namespace io.github.hatayama.UnityCliLoop { /// - /// MCP Dynamic C# Code Execution Tool. + /// Dynamic C# code execution tool exposed through the CLI. /// Delegates workflow orchestration to the dedicated execute-dynamic-code use case. /// [McpTool(Description = @"Execute C# code dynamically in Unity Editor for editor automation. diff --git a/Packages/src/Editor/Api/McpTools/SetClientName.meta b/Packages/src/Editor/Api/McpTools/SetClientName.meta deleted file mode 100644 index bf557b445..000000000 --- a/Packages/src/Editor/Api/McpTools/SetClientName.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: baf7c8e187bcf47eabd2f3990d15c3af -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameResponse.cs b/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameResponse.cs deleted file mode 100644 index 467552ae1..000000000 --- a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameResponse.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace io.github.hatayama.UnityCliLoop -{ - /// - /// Response schema for SetClientName tool - /// Confirms client name registration - /// - public class SetClientNameResponse : BaseToolResponse - { - /// - /// Success status message - /// - public string Message { get; set; } - - /// - /// Registered client name - /// - public string ClientName { get; set; } - - /// - /// Create a new SetClientNameResponse - /// - /// Success message - /// Registered client name - public SetClientNameResponse(string message, string clientName) - { - Message = message; - ClientName = clientName; - } - - /// - /// Parameterless constructor for JSON deserialization - /// - public SetClientNameResponse() - { - Message = string.Empty; - ClientName = string.Empty; - } - } -} \ No newline at end of file diff --git a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameResponse.cs.meta b/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameResponse.cs.meta deleted file mode 100644 index 0d3fc292a..000000000 --- a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameResponse.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 34ad7c17767574adfa8666eec6d14a1a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameSchema.cs b/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameSchema.cs deleted file mode 100644 index a0f89b9e2..000000000 --- a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameSchema.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.ComponentModel; - -namespace io.github.hatayama.UnityCliLoop -{ - /// - /// Schema for SetClientName command parameters - /// Allows CLI clients to register their name for identification - /// - public class SetClientNameSchema : BaseToolSchema - { - /// - /// Name of the MCP client tool (e.g., "Claude Code", "Cursor") - /// - [Description("Name of the MCP client tool")] - public string ClientName { get; set; } = McpConstants.UNKNOWN_CLIENT_NAME; - } -} \ No newline at end of file diff --git a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameSchema.cs.meta b/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameSchema.cs.meta deleted file mode 100644 index 1ec2f13c1..000000000 --- a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameSchema.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d753695f81edc481bbf5e993128d68ec -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameTool.cs b/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameTool.cs deleted file mode 100644 index 6997c2d8b..000000000 --- a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameTool.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace io.github.hatayama.UnityCliLoop -{ - /// - /// Development-only tool that lets CLI clients register their display name. - /// - [McpTool( - Description = "Register client name for identification in Unity CLI bridge", - DisplayDevelopmentOnly = true - )] - public class SetClientNameTool : AbstractUnityTool - { - public override string ToolName => "set-client-name"; - - protected override Task ExecuteAsync(SetClientNameSchema parameters, CancellationToken ct) - { - string clientName = parameters.ClientName; - - UpdateClientNameInServer(clientName); - - string message = string.Format(McpConstants.CLIENT_SUCCESS_MESSAGE_TEMPLATE, clientName); - SetClientNameResponse response = new SetClientNameResponse(message, clientName); - return Task.FromResult(response); - } - - private void UpdateClientNameInServer(string clientName) - { - McpBridgeServer server = McpServerController.CurrentServer; - if (server == null) return; - - var connectedClients = server.GetConnectedClients(); - if (connectedClients.Count == 0) return; - - // Get current client context - var clientContext = JsonRpcProcessor.CurrentClientContext; - if (clientContext == null) - { - return; - } - - // Find client by endpoint - ConnectedClient targetClient = connectedClients - .FirstOrDefault(c => c.Endpoint == clientContext.Endpoint); - - if (targetClient != null) - { - server.UpdateClientName(targetClient.Endpoint, clientName); - return; - } - } - } -} diff --git a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameTool.cs.meta b/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameTool.cs.meta deleted file mode 100644 index b710fcc1a..000000000 --- a/Packages/src/Editor/Api/McpTools/SetClientName/SetClientNameTool.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e19ab575b909d4e08a55d200dd4dcc8e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Api/McpTools/UseCases/System/DomainReloadRecoveryUseCase.cs b/Packages/src/Editor/Api/McpTools/UseCases/System/DomainReloadRecoveryUseCase.cs index 57b86e9b0..9ef864377 100644 --- a/Packages/src/Editor/Api/McpTools/UseCases/System/DomainReloadRecoveryUseCase.cs +++ b/Packages/src/Editor/Api/McpTools/UseCases/System/DomainReloadRecoveryUseCase.cs @@ -5,8 +5,8 @@ namespace io.github.hatayama.UnityCliLoop { /// /// UseCase responsible for temporal cohesion of Domain Reload recovery processing - /// Processing sequence: 1. Pre-stop processing, 2. Recovery processing, 3. Notification processing - /// Related classes: DomainReloadDetectionService, SessionRecoveryService, ClientNotificationService + /// Processing sequence: 1. Pre-stop processing, 2. Recovery processing, 3. Pending compile processing + /// Related classes: DomainReloadDetectionService, SessionRecoveryService /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - UseCase + Tool Pattern (DDD Integration) /// public class DomainReloadRecoveryUseCase @@ -45,21 +45,18 @@ public ServiceResult ExecuteBeforeDomainReload(McpBridgeServer currentSe { try { - // 4.1. Notify client of server stop - ClientNotificationService.LogServerStoppingBeforeDomainReload(correlationId); + LogServerStoppingBeforeDomainReload(correlationId); // 4.2. Stop server currentServer.Dispose(); - // 4.3. Notify client of stop completion - ClientNotificationService.LogServerStoppedAfterDomainReload(correlationId); + LogServerStoppedAfterDomainReload(correlationId); return ServiceResult.SuccessResult(correlationId); } catch (System.Exception ex) { - // 4.4. Error notification - ClientNotificationService.LogServerShutdownError(correlationId, ex); + LogServerShutdownError(correlationId, ex); DomainReloadDetectionService.RollbackDomainReloadStart(correlationId); // Server stop failure is a critical error because recovery must restart cleanly. @@ -75,7 +72,7 @@ public ServiceResult ExecuteBeforeDomainReload(McpBridgeServer currentSe /// Execute recovery processing after Domain Reload completion /// /// Processing result - public async Task> ExecuteAfterDomainReloadAsync(CancellationToken cancellationToken = default) + public Task> ExecuteAfterDomainReloadAsync(CancellationToken cancellationToken = default) { // 1. Generate tracking ID for related operations string correlationId = VibeLogger.GenerateCorrelationId(); @@ -93,26 +90,49 @@ public async Task> ExecuteAfterDomainReloadAsync(Cancellat ValidationResult restoreResult = SessionRecoveryService.RestoreServerStateIfNeeded(); if (!restoreResult.IsValid) { - return ServiceResult.FailureResult($"Server restoration failed: {restoreResult.ErrorMessage}"); + return Task.FromResult(ServiceResult.FailureResult($"Server restoration failed: {restoreResult.ErrorMessage}")); } // 5. Process pending compile requests (currently disabled) ProcessPendingCompileRequests(correlationId); - // 6. Send tool change notification if server is running - if (McpServerController.IsServerRunning) - { - try - { - await ClientNotificationService.SendToolNotificationAfterCompilationAsync(); - } - catch (System.Exception ex) - { - VibeLogger.LogWarning("tool_notification_failed", $"Failed to send tool notification: {ex.Message}", correlationId: correlationId); - } - } + return Task.FromResult(ServiceResult.SuccessResult(correlationId)); + } - return ServiceResult.SuccessResult(correlationId); + private static void LogServerStoppingBeforeDomainReload(string correlationId) + { + VibeLogger.LogInfo( + "domain_reload_server_stopping", + "Stopping Unity CLI bridge before domain reload", + new { transport = "project_ipc" }, + correlationId + ); + } + + private static void LogServerStoppedAfterDomainReload(string correlationId) + { + VibeLogger.LogInfo( + "domain_reload_server_stopped", + "Unity CLI bridge stopped successfully", + new { transport = "project_ipc" }, + correlationId + ); + } + + private static void LogServerShutdownError(string correlationId, System.Exception ex) + { + VibeLogger.LogException( + "domain_reload_server_shutdown_error", + ex, + new + { + transport = "project_ipc", + server_was_running = true + }, + correlationId, + "Critical error during server shutdown before assembly reload.", + "Investigate server shutdown process and project IPC recovery." + ); } /// diff --git a/Packages/src/Editor/Config/McpEditorSettings.cs b/Packages/src/Editor/Config/McpEditorSettings.cs index 3e9b80020..b46295d0f 100644 --- a/Packages/src/Editor/Config/McpEditorSettings.cs +++ b/Packages/src/Editor/Config/McpEditorSettings.cs @@ -51,7 +51,6 @@ public record McpEditorSettingsData public bool compileWindowHasData = false; public string[] pendingCompileRequestIds = new string[0]; public CompileRequestData[] compileRequests = new CompileRequestData[0]; - public ConnectedLLMToolData[] connectedLLMTools = new ConnectedLLMToolData[0]; } /// @@ -69,7 +68,8 @@ public static class McpEditorSettings "Port", "serverTransportKind", "projectRootPath", - "serverSessionId" + "serverSessionId", + "connectedLLMTools" }; private static McpEditorSettingsData _cachedSettings; @@ -626,76 +626,6 @@ public static void RemovePendingCompileRequest(string requestId) } } - // Connected LLM Tools management methods - - /// - /// Gets the connected LLM tools. - /// - public static ConnectedLLMToolData[] GetConnectedLLMTools() - { - return GetSettings().connectedLLMTools; - } - - /// - /// Sets the connected LLM tools. - /// - public static void SetConnectedLLMTools(ConnectedLLMToolData[] connectedLLMTools) - { - McpEditorSettingsData settings = GetSettings(); - McpEditorSettingsData newSettings = settings with { connectedLLMTools = connectedLLMTools }; - SaveSettings(newSettings); - } - - /// - /// Adds a connected LLM tool. - /// - public static void AddConnectedLLMTool(ConnectedLLMToolData toolData) - { - if (toolData == null || string.IsNullOrEmpty(toolData.Name)) - { - return; - } - - ConnectedLLMToolData[] tools = GetConnectedLLMTools(); - - // Remove existing tool with same name if present - ConnectedLLMToolData[] filteredTools = System.Array.FindAll(tools, t => t.Name != toolData.Name); - - // Add new tool - ConnectedLLMToolData[] newTools = new ConnectedLLMToolData[filteredTools.Length + 1]; - System.Array.Copy(filteredTools, newTools, filteredTools.Length); - newTools[filteredTools.Length] = toolData; - - SetConnectedLLMTools(newTools); - } - - /// - /// Removes a connected LLM tool by name. - /// - public static void RemoveConnectedLLMTool(string toolName) - { - if (string.IsNullOrEmpty(toolName)) - { - return; - } - - ConnectedLLMToolData[] tools = GetConnectedLLMTools(); - ConnectedLLMToolData[] newTools = System.Array.FindAll(tools, t => t.Name != toolName); - - if (newTools.Length != tools.Length) - { - SetConnectedLLMTools(newTools); - } - } - - /// - /// Clears all connected LLM tools. - /// - public static void ClearConnectedLLMTools() - { - SetConnectedLLMTools(new ConnectedLLMToolData[0]); - } - /// /// Loads the settings file. /// @@ -744,7 +674,7 @@ private static void LoadSettings() { // Don't suppress this exception - corrupted settings should be reported throw new InvalidOperationException( - $"Failed to load MCP Editor settings from: {SettingsFilePath}. Settings file may be corrupted.", ex); + $"Failed to load Unity CLI Loop Editor settings from: {SettingsFilePath}. Settings file may be corrupted.", ex); } } diff --git a/Packages/src/Editor/Config/McpUIConstants.cs b/Packages/src/Editor/Config/McpUIConstants.cs index 7cc7596f6..5e1ae64f6 100644 --- a/Packages/src/Editor/Config/McpUIConstants.cs +++ b/Packages/src/Editor/Config/McpUIConstants.cs @@ -18,16 +18,8 @@ public static class McpUIConstants private const float SECTION_BACKGROUND_COLOR_ONE = 0.18f; public static readonly Color SECTION_BACKGROUND_COLOR = new(SECTION_BACKGROUND_COLOR_ONE, SECTION_BACKGROUND_COLOR_ONE, SECTION_BACKGROUND_COLOR_ONE, 1f); - private const float CLIENT_ITEM_BACKGROUND_COLOR_ONE = 0.3f; - public static readonly Color CLIENT_ITEM_BACKGROUND_COLOR = new(CLIENT_ITEM_BACKGROUND_COLOR_ONE, CLIENT_ITEM_BACKGROUND_COLOR_ONE, CLIENT_ITEM_BACKGROUND_COLOR_ONE, 0.5f); - // Communication log settings public const int MAX_COMMUNICATION_LOG_ENTRIES = 20; - - // Connected clients display - public const float CLIENT_ITEM_SPACING = 3f; - public const string CONNECTED_TOOLS_FOLDOUT_TEXT = "Connected LLM Tools"; - public const string CLIENT_ICON = "● "; // Tool Settings public const string TOOL_SETTINGS_MENU_PATH = "Window > Unity CLI Loop > Settings"; diff --git a/Packages/src/Editor/Core/ApplicationServices/ClientNotificationService.cs b/Packages/src/Editor/Core/ApplicationServices/ClientNotificationService.cs deleted file mode 100644 index 0dc7a7c16..000000000 --- a/Packages/src/Editor/Core/ApplicationServices/ClientNotificationService.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System.Threading.Tasks; -using Newtonsoft.Json; - -namespace io.github.hatayama.UnityCliLoop -{ - /// - /// Application service responsible for client notification processing - /// Single responsibility: Sending notifications to MCP clients - /// Related classes: McpBridgeServer, CustomToolManager - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) - /// - public static class ClientNotificationService - { - /// - /// Send tool change notification to clients - /// - /// - /// This notification serves dual purposes in this project: - /// 1. Original MCP purpose: Notify that available tools have changed - /// 2. UnityCliLoop additional purpose: Signal that Unity is ready after Domain Reload - /// - /// After Domain Reload completes, this notification is sent to indicate Unity - /// has finished initialization and can reliably process requests. client side - /// uses this as a "Unity ready" signal. The name doesn't perfectly match this - /// secondary purpose, but it avoids adding custom notification complexity. - /// - public static void SendToolsChangedNotification() - { - McpBridgeServer currentServer = McpServerController.CurrentServer; - if (currentServer == null) - { - return; - } - - // Send only MCP standard notification - var notificationParams = new - { - timestamp = System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), - message = "Unity tools have been updated" - }; - - var mcpNotification = new - { - jsonrpc = McpServerConfig.JSONRPC_VERSION, - method = "notifications/tools/list_changed", - @params = notificationParams - }; - - string mcpNotificationJson = JsonConvert.SerializeObject(mcpNotification); - currentServer.SendNotificationToClients(mcpNotificationJson); - - // Log recording - System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace(true); - string callerInfo = stackTrace.GetFrame(1)?.GetMethod()?.Name ?? "Unknown"; - - VibeLogger.LogInfo( - "tools_list_changed_notification", - "Sent tools changed notification to MCP clients", - new { caller = callerInfo, timestamp = notificationParams.timestamp } - ); - } - - /// - /// Check if server is running and send tool change notification - /// - public static void TriggerToolChangeNotification() - { - if (McpServerController.IsServerRunning) - { - SendToolsChangedNotification(); - } - } - - /// - /// Send tool notification with frame delay after compilation - /// - /// Task for notification sending process - public static async Task SendToolNotificationAfterCompilationAsync() - { - // Frame delay for Unity editor stabilization after Domain Reload - await EditorDelay.DelayFrame(1); - - CustomToolManager.NotifyToolChanges(); - } - - /// - /// Send notification to specific clients - /// - /// JSON data of notification to send - public static void SendNotificationToClients(string notification) - { - McpBridgeServer currentServer = McpServerController.CurrentServer; - if (currentServer == null) - { - return; - } - - currentServer.SendNotificationToClients(notification); - } - - /// - /// Log before server stop - /// - /// Tracking ID for related operations - public static void LogServerStoppingBeforeDomainReload(string correlationId) - { - VibeLogger.LogInfo( - "domain_reload_server_stopping", - "Stopping Unity CLI bridge before domain reload", - new { transport = "project_ipc" }, - correlationId - ); - } - - /// - /// Log server stop completion - /// - /// Tracking ID for related operations - public static void LogServerStoppedAfterDomainReload(string correlationId) - { - VibeLogger.LogInfo( - "domain_reload_server_stopped", - "Unity CLI bridge stopped successfully", - new { transport = "project_ipc" }, - correlationId - ); - } - - /// - /// Log server stop error - /// - /// Tracking ID for related operations - /// Exception that occurred - public static void LogServerShutdownError(string correlationId, System.Exception ex) - { - VibeLogger.LogException( - "domain_reload_server_shutdown_error", - ex, - new - { - transport = "project_ipc", - server_was_running = true - }, - correlationId, - "Critical error during server shutdown before assembly reload.", - "Investigate server shutdown process and project IPC recovery." - ); - } - } -} diff --git a/Packages/src/Editor/Core/ApplicationServices/ClientNotificationService.cs.meta b/Packages/src/Editor/Core/ApplicationServices/ClientNotificationService.cs.meta deleted file mode 100644 index 972e7c67a..000000000 --- a/Packages/src/Editor/Core/ApplicationServices/ClientNotificationService.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d310cf0c71df949279b413297c985681 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Core/ApplicationServices/ConnectedToolsMonitoringService.cs b/Packages/src/Editor/Core/ApplicationServices/ConnectedToolsMonitoringService.cs deleted file mode 100644 index f65970445..000000000 --- a/Packages/src/Editor/Core/ApplicationServices/ConnectedToolsMonitoringService.cs +++ /dev/null @@ -1,436 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using UnityEditor; -using UnityEngine; - -namespace io.github.hatayama.UnityCliLoop -{ - /// - /// Application service responsible for monitoring connected LLM tools - /// Single responsibility: Track connected/disconnected tools and persist state - /// Related classes: McpBridgeServer, McpEditorSettings, ConnectedLLMToolData - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) - /// - [InitializeOnLoad] - public static class ConnectedToolsMonitoringService - { - private static List _connectedTools = new(); - private static List _toolsBackup; - private static CancellationTokenSource _cleanupCancellationTokenSource; - - // Events for UI notification - public static event System.Action OnConnectedToolsChanged; - - static ConnectedToolsMonitoringService() - { - Initialize(); - } - - /// - /// Initialize the monitoring service - /// - private static void Initialize() - { - SubscribeToServerEvents(); - if (ShouldRestorePersistedToolsForReconnect()) - { - RestoreConnectedToolsFromSettings(); - return; - } - - SynchronizeConnectedToolsWithCurrentServer(); - } - - /// - /// Subscribe to server lifecycle events - /// - private static void SubscribeToServerEvents() - { - McpBridgeServer.OnServerStopping += OnServerStopping; - McpBridgeServer.OnServerStarted += OnServerStarted; - McpBridgeServer.OnToolConnected += OnToolConnected; - McpBridgeServer.OnToolDisconnected += OnToolDisconnected; - McpBridgeServer.OnAllToolsCleared += OnAllToolsCleared; - } - - /// - /// Handle server stopping event - keep the last live snapshot for reconnecting UI - /// - private static void OnServerStopping() - { - _toolsBackup = _connectedTools - .Where(tool => tool != null && !string.IsNullOrEmpty(tool.Name) && tool.Name != McpConstants.UNKNOWN_CLIENT_NAME) - .ToList(); - - SyncConnectedToolsToSettings(); - } - - /// - /// Handle server started event - restore the reconnecting snapshot first, then cleanup stale tools - /// - private static void OnServerStarted() - { - if (_toolsBackup != null && _toolsBackup.Count > 0) - { - RestoreConnectedTools(_toolsBackup); - _toolsBackup = null; - return; - } - - if (ShouldRestorePersistedToolsForReconnect()) - { - ConnectedLLMToolData[] persistedTools = McpEditorSettings.GetConnectedLLMTools(); - if (persistedTools != null && persistedTools.Length > 0) - { - RestoreConnectedTools(persistedTools.ToList()); - return; - } - } - - SynchronizeConnectedToolsWithCurrentServer(); - } - - /// - /// Handle tool connected event - add tool to connected list - /// - private static void OnToolConnected(ConnectedClient client) - { - AddConnectedTool(client); - } - - /// - /// Handle tool disconnected event - remove tool from connected list - /// - private static void OnToolDisconnected(string toolName) - { - RemoveConnectedTool(toolName); - } - - /// - /// Handle all tools cleared event - clear all connected tools - /// - private static void OnAllToolsCleared() - { - ClearConnectedTools(); - } - - /// - /// Add a connected LLM tool - /// - public static void AddConnectedTool(ConnectedClient client) - { - if (client.ClientName == McpConstants.UNKNOWN_CLIENT_NAME) - { - return; - } - - // Remove existing tool if present, then add - _connectedTools.RemoveAll(tool => tool.Name == client.ClientName); - - ConnectedLLMToolData toolData = new( - client.ClientName, - client.Endpoint, - client.ConnectedAt - ); - _connectedTools.Add(toolData); - - // Persist to settings - McpEditorSettings.AddConnectedLLMTool(toolData); - - // Notify UI - OnConnectedToolsChanged?.Invoke(); - } - - /// - /// Remove a connected LLM tool - /// - public static void RemoveConnectedTool(string toolName) - { - _connectedTools.RemoveAll(tool => tool.Name == toolName); - - // Persist to settings - McpEditorSettings.RemoveConnectedLLMTool(toolName); - - // Notify UI - OnConnectedToolsChanged?.Invoke(); - } - - /// - /// Clear all connected LLM tools - /// - public static void ClearConnectedTools() - { - _connectedTools.Clear(); - - // Persist to settings - McpEditorSettings.ClearConnectedLLMTools(); - - // Notify UI - OnConnectedToolsChanged?.Invoke(); - } - - /// - /// Get connected tools as ConnectedClient objects for UI display, sorted by name - /// - public static IEnumerable GetConnectedToolsAsClients() - { - return _connectedTools.OrderBy(tool => tool.Name).Select(tool => ConvertToConnectedClient(tool)); - } - - /// - /// Convert stored tool data to ConnectedClient for UI display - /// - private static ConnectedClient ConvertToConnectedClient(ConnectedLLMToolData toolData) - { - return new ConnectedClient(toolData.Endpoint, null, toolData.Name); - } - - /// - /// Restore connected tools from backup after server restart. - /// The reconnecting UI should keep the last live snapshot until clients rejoin. - /// - private static void RestoreConnectedTools(List backup) - { - if (backup == null || backup.Count == 0) - { - return; - } - - RestoreConnectedToolsImmediately(backup); - - _cleanupCancellationTokenSource?.Cancel(); - _cleanupCancellationTokenSource?.Dispose(); - _cleanupCancellationTokenSource = new CancellationTokenSource(); - - DelayedCleanupAsync(_cleanupCancellationTokenSource.Token).ContinueWith(task => - { - if (task.IsFaulted && !task.IsCanceled) - { - EditorApplication.delayCall += () => - { - Debug.LogError($"[UnityCliLoop] Failed to perform delayed cleanup: {task.Exception?.GetBaseException().Message}"); - }; - } - }, TaskScheduler.FromCurrentSynchronizationContext()); - } - - private static void RestoreConnectedToolsImmediately(IEnumerable tools) - { - List restoredTools = tools - .Where(tool => tool != null && !string.IsNullOrEmpty(tool.Name) && tool.Name != McpConstants.UNKNOWN_CLIENT_NAME) - .GroupBy(tool => tool.Name) - .Select(group => group.Last()) - .OrderBy(tool => tool.Name) - .ToList(); - - _connectedTools = restoredTools; - SyncConnectedToolsToSettings(); - OnConnectedToolsChanged?.Invoke(); - } - - private static async Task DelayedCleanupAsync(CancellationToken cancellationToken = default) - { - try - { - await TimerDelay.Wait(8000, cancellationToken); - } - catch (OperationCanceledException) - { - return; - } - - if (!McpServerController.IsServerRunning || cancellationToken.IsCancellationRequested) - { - return; - } - - IReadOnlyCollection actualConnectedClients = McpServerController.CurrentServer?.GetConnectedClients(); - if (actualConnectedClients == null || cancellationToken.IsCancellationRequested) - { - return; - } - - HashSet actualClientNames = new HashSet( - actualConnectedClients - .Where(client => client.ClientName != McpConstants.UNKNOWN_CLIENT_NAME) - .Select(client => client.ClientName) - ); - - List toolsToRemove = _connectedTools - .Where(tool => !actualClientNames.Contains(tool.Name)) - .ToList(); - - foreach (ConnectedLLMToolData tool in toolsToRemove) - { - RemoveConnectedTool(tool.Name); - } - } - - /// - /// Rebuild connected tools from the current live server state. - /// The connected tools UI must reflect live MCP connections only. - /// - private static void SynchronizeConnectedToolsWithCurrentServer() - { - IReadOnlyCollection liveClients = GetLiveConnectedClients(); - ReplaceConnectedTools(liveClients); - } - - private static bool ShouldRestorePersistedToolsForReconnect() - { - return McpEditorSettings.GetShowReconnectingUI() || McpEditorSettings.GetShowPostCompileReconnectingUI(); - } - - /// - /// Get live clients from the current server, ignoring persisted settings. - /// - private static IReadOnlyCollection GetLiveConnectedClients() - { - if (!McpServerController.IsServerRunning) - { - return Array.Empty(); - } - - IReadOnlyCollection connectedClients = McpServerController.CurrentServer?.GetConnectedClients(); - return connectedClients ?? Array.Empty(); - } - - /// - /// Replace the in-memory and persisted connected tools state with the current live clients. - /// - private static void ReplaceConnectedTools(IEnumerable connectedClients) - { - List tools = connectedClients - .Where(client => client != null && client.ClientName != McpConstants.UNKNOWN_CLIENT_NAME) - .Select(client => new ConnectedLLMToolData( - client.ClientName, - client.Endpoint, - client.ConnectedAt - )) - .GroupBy(tool => tool.Name) - .Select(group => group.Last()) - .OrderBy(tool => tool.Name) - .ToList(); - - _connectedTools = tools; - - ConnectedLLMToolData[] toolsArray = _connectedTools - .Where(tool => tool != null && !string.IsNullOrEmpty(tool.Name)) - .ToArray(); - SaveConnectedToolsWhenChanged( - toolsArray, - McpEditorSettings.GetConnectedLLMTools, - McpEditorSettings.SetConnectedLLMTools); - - OnConnectedToolsChanged?.Invoke(); - } - - /// - /// Restore the persisted connected tools snapshot for reconnecting UI only. - /// This path must not be used for normal startup because it can reintroduce stale entries. - /// - private static void RestoreConnectedToolsFromSettings() - { - ConnectedLLMToolData[] savedTools = McpEditorSettings.GetConnectedLLMTools(); - if (savedTools == null || savedTools.Length == 0) - { - _connectedTools = new List(); - return; - } - - RestoreConnectedToolsImmediately(savedTools); - } - - private static void SyncConnectedToolsToSettings() - { - ConnectedLLMToolData[] toolsArray = _connectedTools - .Where(tool => tool != null && !string.IsNullOrEmpty(tool.Name) && tool.Name != McpConstants.UNKNOWN_CLIENT_NAME) - .ToArray(); - SaveConnectedToolsWhenChanged( - toolsArray, - McpEditorSettings.GetConnectedLLMTools, - McpEditorSettings.SetConnectedLLMTools); - } - - internal static bool SaveConnectedToolsWhenChanged( - ConnectedLLMToolData[] toolsArray, - Func loadPersistedTools, - Action savePersistedTools) - { - Debug.Assert(toolsArray != null, "toolsArray must not be null"); - Debug.Assert(loadPersistedTools != null, "loadPersistedTools must not be null"); - Debug.Assert(savePersistedTools != null, "savePersistedTools must not be null"); - - ConnectedLLMToolData[] persistedTools = loadPersistedTools() ?? Array.Empty(); - if (AreConnectedToolSnapshotsEquivalent(toolsArray, persistedTools)) - { - return false; - } - - savePersistedTools(toolsArray); - return true; - } - - private static bool AreConnectedToolSnapshotsEquivalent( - IReadOnlyList first, - IReadOnlyList second) - { - if (first == null || second == null) - { - return first == second; - } - - if (first.Count != second.Count) - { - return false; - } - - for (int i = 0; i < first.Count; i++) - { - ConnectedLLMToolData firstTool = first[i]; - ConnectedLLMToolData secondTool = second[i]; - if (!AreConnectedToolsEquivalent(firstTool, secondTool)) - { - return false; - } - } - - return true; - } - - private static bool AreConnectedToolsEquivalent( - ConnectedLLMToolData first, - ConnectedLLMToolData second) - { - if (first == null || second == null) - { - return first == second; - } - - return string.Equals(first.Name, second.Name, StringComparison.Ordinal) - && string.Equals(first.Endpoint, second.Endpoint, StringComparison.Ordinal); - } - - internal static void ReplaceConnectedToolsForTests(IReadOnlyCollection connectedClients) - { - ReplaceConnectedTools(connectedClients); - } - - internal static void RestorePersistedConnectedToolsForTests() - { - RestoreConnectedToolsFromSettings(); - } - - internal static void ResetStateForTests() - { - _connectedTools = new List(); - _toolsBackup = null; - _cleanupCancellationTokenSource?.Cancel(); - _cleanupCancellationTokenSource?.Dispose(); - _cleanupCancellationTokenSource = null; - } - } -} diff --git a/Packages/src/Editor/Core/ApplicationServices/ConnectedToolsMonitoringService.cs.meta b/Packages/src/Editor/Core/ApplicationServices/ConnectedToolsMonitoringService.cs.meta deleted file mode 100644 index 5a10dcdd1..000000000 --- a/Packages/src/Editor/Core/ApplicationServices/ConnectedToolsMonitoringService.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c691b46b7229e43c5807d2614f83568a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Core/ApplicationServices/SessionRecoveryService.cs b/Packages/src/Editor/Core/ApplicationServices/SessionRecoveryService.cs index 38d1e51eb..aa9d68158 100644 --- a/Packages/src/Editor/Core/ApplicationServices/SessionRecoveryService.cs +++ b/Packages/src/Editor/Core/ApplicationServices/SessionRecoveryService.cs @@ -133,20 +133,6 @@ private static async Task RetryServerRestoreAsync(int retryCount) }, TaskScheduler.FromCurrentSynchronizationContext()); } - /// - /// Clear reconnection flag - /// - public static void ClearReconnectingFlag() - { - bool wasReconnecting = McpEditorSettings.GetIsReconnecting(); - bool wasShowingUI = McpEditorSettings.GetShowReconnectingUI(); - - if (wasReconnecting || wasShowingUI) - { - McpEditorSettings.ClearReconnectingFlags(); - } - } - /// /// Start reconnection UI display timeout /// diff --git a/Packages/src/Editor/Core/ConnectedLLMToolData.cs b/Packages/src/Editor/Core/ConnectedLLMToolData.cs deleted file mode 100644 index 05488b103..000000000 --- a/Packages/src/Editor/Core/ConnectedLLMToolData.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using UnityEngine; - -namespace io.github.hatayama.UnityCliLoop -{ - /// - /// Connected LLM Tool data structure for persistence - /// Related classes: - /// - ConnectedClient: Connected client information - /// - McpEditorWindow: UI tool display control - /// - McpServerController: Server lifecycle management - /// - McpBridgeServer: Actual client connection management - /// - [Serializable] - public class ConnectedLLMToolData - { - [SerializeField] public string Name; - [SerializeField] public string Endpoint; - [SerializeField] public string ConnectedAtString; - - // Unity doesn't serialize DateTime directly, so we use a string representation - public DateTime ConnectedAt - { - get => string.IsNullOrEmpty(ConnectedAtString) ? DateTime.Now : - DateTime.TryParse(ConnectedAtString, out DateTime result) ? result : DateTime.Now; - set => ConnectedAtString = value.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz"); - } - - public ConnectedLLMToolData() - { - // Default constructor for Unity serialization - } - - public ConnectedLLMToolData(string name, string endpoint, DateTime connectedAt) - { - Name = name; - Endpoint = endpoint; - ConnectedAt = connectedAt; - } - } -} diff --git a/Packages/src/Editor/Core/ConnectedLLMToolData.cs.meta b/Packages/src/Editor/Core/ConnectedLLMToolData.cs.meta deleted file mode 100644 index 7dd71a7fd..000000000 --- a/Packages/src/Editor/Core/ConnectedLLMToolData.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a4320ff5004aa40e9ae756b620cc25a1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Core/CoreTools/ConsoleLogFetcher/LogGetter.cs b/Packages/src/Editor/Core/CoreTools/ConsoleLogFetcher/LogGetter.cs index f370f7ee8..23b32782a 100644 --- a/Packages/src/Editor/Core/CoreTools/ConsoleLogFetcher/LogGetter.cs +++ b/Packages/src/Editor/Core/CoreTools/ConsoleLogFetcher/LogGetter.cs @@ -187,7 +187,7 @@ private static List GetLogsByMcpLogType(string logType) /// /// Converts McpLogType to Unity's LogType /// - /// MCP log type + /// Unity console log type /// Corresponding Unity LogType private static LogType ConvertMcpLogTypeToLogType(string mcpLogType) { diff --git a/Packages/src/Editor/Core/CoreTools/HierarchyAnalyzer/HierarchyService.cs b/Packages/src/Editor/Core/CoreTools/HierarchyAnalyzer/HierarchyService.cs index 4fb08f358..7960cdeed 100644 --- a/Packages/src/Editor/Core/CoreTools/HierarchyAnalyzer/HierarchyService.cs +++ b/Packages/src/Editor/Core/CoreTools/HierarchyAnalyzer/HierarchyService.cs @@ -287,7 +287,7 @@ private static GameObject[] GetDontDestroyOnLoadRootObjects() GameObject probe = null; try { - probe = new GameObject("__mcp_ddol_probe__"); + probe = new GameObject("__uloop_ddol_probe__"); UnityEngine.Object.DontDestroyOnLoad(probe); Scene ddolScene = probe.scene; @@ -378,4 +378,4 @@ private void TraverseHierarchy(GameObject obj, int? parentId, int depth, Hierarc } } } -} \ No newline at end of file +} diff --git a/Packages/src/Editor/Server/McpBridgeServer.cs b/Packages/src/Editor/Server/McpBridgeServer.cs index 367b702c1..57aa0b47b 100644 --- a/Packages/src/Editor/Server/McpBridgeServer.cs +++ b/Packages/src/Editor/Server/McpBridgeServer.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.IO; -using System.Linq; -using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; @@ -12,41 +10,6 @@ namespace io.github.hatayama.UnityCliLoop { - // Related classes: - // - McpServerController: Manages the lifecycle of this server. - // - UnityCommandExecutor: Executes commands received from clients. - // - JsonRpcProcessor: Handles JSON-RPC 2.0 message processing. - public class ConnectedClient - { - public readonly string Endpoint; - public readonly string ClientName; - public readonly DateTime ConnectedAt; - public readonly Stream Stream; - - public ConnectedClient(string endpoint, Stream stream, string clientName = McpConstants.UNKNOWN_CLIENT_NAME) - { - Endpoint = endpoint; - Stream = stream; // Allow null stream for UI display purposes - ClientName = clientName; - ConnectedAt = DateTime.Now; - } - - // Private constructor for WithClientName to preserve ConnectedAt - private ConnectedClient(string endpoint, Stream stream, string clientName, DateTime connectedAt) - { - Endpoint = endpoint; - Stream = stream; // Allow null stream for UI display purposes - ClientName = clientName; - ConnectedAt = connectedAt; - } - - public ConnectedClient WithClientName(string clientName) - { - return new ConnectedClient(Endpoint, Stream, clientName, ConnectedAt); - } - - } - /// /// Unity CLI bridge server. /// Accepts project-local CLI connections and handles JSON-RPC 2.0 communication. @@ -58,11 +21,6 @@ public class McpBridgeServer : IDisposable // Events for server lifecycle notifications public static event System.Action OnServerStopping; public static event System.Action OnServerStarted; - - // Events for individual tool management - public static event System.Action OnToolConnected; - public static event System.Action OnToolDisconnected; - public static event System.Action OnAllToolsCleared; // Fired from thread pool when ServerLoopAsync exits while _isRunning is still true. // Subscribers must marshal to main thread before accessing Unity APIs. @@ -86,8 +44,7 @@ public class McpBridgeServer : IDisposable // Guard against concurrent cleanup from ServerLoopAsync finally + external disposal private int _unexpectedExitCleanupStarted = 0; - // Client management for broadcasting notifications - private readonly ConcurrentDictionary _connectedClients = new(); + private readonly ConcurrentDictionary _clientStreams = new(); /// /// Whether the server is running. @@ -96,64 +53,16 @@ public class McpBridgeServer : IDisposable public string Endpoint => _transportListener?.Endpoint.DisplayName() ?? string.Empty; - /// - /// Event on client connection. - /// - public event Action OnClientConnected; - - /// - /// Event on client disconnection. - /// - public event Action OnClientDisconnected; - /// /// Event on error. /// public event Action OnError; - /// - /// Generate unique client key using Endpoint - /// private string GenerateClientKey(string endpoint) { - // Use endpoint as unique identifier return endpoint; } - /// - /// Get list of connected clients sorted by name - /// - public IReadOnlyCollection GetConnectedClients() - { - return _connectedClients.Values.OrderBy(client => client.ClientName).ToArray(); - } - - /// - /// Update client name for a connected client - /// - public void UpdateClientName(string clientEndpoint, string clientName) - { - // Find client by endpoint (backward compatibility) - ConnectedClient targetClient = _connectedClients.Values - .FirstOrDefault(c => c.Endpoint == clientEndpoint); - - if (targetClient != null) - { - string clientKey = GenerateClientKey(targetClient.Endpoint); - ConnectedClient updatedClient = targetClient.WithClientName(clientName); - bool updateResult = _connectedClients.TryUpdate(clientKey, updatedClient, targetClient); - - // Clear reconnecting flags when client name is successfully set (client is now fully connected) - if (updateResult && clientName != McpConstants.UNKNOWN_CLIENT_NAME) - { - McpServerController.ClearReconnectingFlag(); - - // Notify tool connected - OnToolConnected?.Invoke(updatedClient); - } - } - } - public void StartServer(bool clearServerStartingLockWhenReady = true) { if (_isRunning) @@ -225,15 +134,6 @@ public void StopServer() return; } - // Determine shutdown reason based on domain reload state - ServerShutdownReason shutdownReason = McpEditorSettings.GetIsDomainReloadInProgress() - ? ServerShutdownReason.DomainReload - : ServerShutdownReason.EditorQuit; - - // Send shutdown notification to all connected clients BEFORE disconnecting - // This allows client side to differentiate between temporary and permanent shutdown - SendShutdownNotification(shutdownReason); - // Notify that server is stopping OnServerStopping?.Invoke(); @@ -285,31 +185,20 @@ public void StopServer() /// private void DisconnectAllClients() { - DisconnectAllClientsCore(notifyLifecycleEvents: true); - } - - /// - /// OnAllToolsCleared subscribers (UI components) require the main thread. - /// Background-thread callers (CleanupAfterUnexpectedLoopExit) must suppress - /// lifecycle events to avoid cross-thread Unity API violations. - /// - private void DisconnectAllClientsCore(bool notifyLifecycleEvents) - { - if (_connectedClients.IsEmpty) + if (_clientStreams.IsEmpty) { return; } List clientsToRemove = new List(); - foreach (KeyValuePair client in _connectedClients) + foreach (KeyValuePair client in _clientStreams) { try { - // Close the NetworkStream to send proper close event to CLI client - if (client.Value.Stream != null && client.Value.Stream.CanWrite) + if (client.Value != null && client.Value.CanWrite) { - client.Value.Stream.Close(); + client.Value.Close(); } clientsToRemove.Add(client.Key); } @@ -323,13 +212,7 @@ private void DisconnectAllClientsCore(bool notifyLifecycleEvents) // Remove all clients from the connected clients list foreach (string clientKey in clientsToRemove) { - _connectedClients.TryRemove(clientKey, out _); - } - - // Lifecycle events require main thread — only fire when explicitly requested - if (notifyLifecycleEvents && !McpEditorSettings.GetIsDomainReloadInProgress()) - { - OnAllToolsCleared?.Invoke(); + _clientStreams.TryRemove(clientKey, out _); } } @@ -347,7 +230,7 @@ private void CleanupAfterUnexpectedLoopExit() return; } - DisconnectAllClientsCore(notifyLifecycleEvents: false); + DisconnectAllClients(); try { @@ -384,9 +267,6 @@ private async Task ServerLoopAsync(CancellationToken cancellationToken) BridgeClientConnection client = await AcceptClientAsync(_transportListener, cancellationToken); if (client != null) { - string clientEndpoint = client.Endpoint; - OnClientConnected?.Invoke(clientEndpoint); - // Execute client handling in a separate task (fire-and-forget). Task.Run(() => HandleClientAsync(client, cancellationToken)).Forget(); } @@ -469,6 +349,7 @@ private async Task AcceptClientAsync(IBridgeTransportLis private async Task HandleClientAsync(BridgeClientConnection client, CancellationToken cancellationToken) { string clientEndpoint = client.Endpoint; + string clientKey = GenerateClientKey(clientEndpoint); // Initialize new components for Content-Length framing DynamicBufferManager bufferManager = null; @@ -481,19 +362,12 @@ private async Task HandleClientAsync(BridgeClientConnection client, Cancellation { // Check for existing connection from same endpoint and close it - string clientKey = GenerateClientKey(clientEndpoint); - if (_connectedClients.TryGetValue(clientKey, out ConnectedClient existingClient)) + if (_clientStreams.TryRemove(clientKey, out Stream existingStream)) { - existingClient.Stream?.Close(); - _connectedClients.TryRemove(clientKey, out _); - - // Notify tool disconnected - OnToolDisconnected?.Invoke(existingClient.ClientName); + existingStream?.Close(); } - // Add new client to connected clients for notification broadcasting - ConnectedClient connectedClient = new ConnectedClient(clientEndpoint, stream); - _connectedClients.TryAdd(clientKey, connectedClient); + _clientStreams.TryAdd(clientKey, stream); // Initialize new framing components bufferManager = new DynamicBufferManager(); @@ -524,7 +398,6 @@ private async Task HandleClientAsync(BridgeClientConnection client, Cancellation // JSON-RPC processing and response sending with client context string responseJson = await JsonRpcProcessor.ProcessRequest(requestJson, clientEndpoint); - // Only send response if it's not null (notifications return null) if (!string.IsNullOrEmpty(responseJson)) { // Check stream and client state before attempting write @@ -583,129 +456,12 @@ private async Task HandleClientAsync(BridgeClientConnection client, Cancellation OnError?.Invoke($"Error during client disposal: {ex.Message}"); } - // Remove client from connected clients list - // Find client by endpoint to get the correct key - ConnectedClient clientToRemove = _connectedClients.Values - .FirstOrDefault(c => c.Endpoint == clientEndpoint); - - if (clientToRemove != null) - { - string clientKey = GenerateClientKey(clientToRemove.Endpoint); - _connectedClients.TryRemove(clientKey, out _); - - // Notify tool disconnected - OnToolDisconnected?.Invoke(clientToRemove.ClientName); - } + _clientStreams.TryRemove(clientKey, out _); client.Dispose(); - OnClientDisconnected?.Invoke(clientEndpoint); - } - } - - /// - /// Sends a pre-formatted JSON-RPC notification to all connected clients using Content-Length framing. - /// - /// The complete JSON-RPC notification string - public void SendNotificationToClients(string notificationJson) - { - if (_connectedClients.IsEmpty) - { - return; - } - - // Frame the notification with Content-Length header - string framedNotification = CreateContentLengthFrame(notificationJson); - byte[] notificationData = Encoding.UTF8.GetBytes(framedNotification); - - SendNotificationDataAsync(notificationData).Forget(); - } - - /// - /// Sends a shutdown notification to all connected clients. - /// This should be called before disconnecting clients so client side can - /// differentiate between domain reload (temporary) and editor quit (permanent). - /// - /// The reason for server shutdown - public void SendShutdownNotification(ServerShutdownReason reason) - { - if (_connectedClients.IsEmpty) - { - return; } - - // Create JSON-RPC 2.0 notification with shutdown reason - string notificationJson = $"{{\"jsonrpc\":\"2.0\",\"method\":\"notifications/server/shutdown\",\"params\":{{\"reason\":\"{reason}\"}}}}"; - - // Frame the notification with Content-Length header - string framedNotification = CreateContentLengthFrame(notificationJson); - byte[] notificationData = Encoding.UTF8.GetBytes(framedNotification); - - // Send synchronously to ensure it's sent before connection closes - SendNotificationDataSync(notificationData); } - /// - /// Send notification data to all connected clients synchronously. - /// Used for shutdown notifications to ensure delivery before connection closes. - /// - private void SendNotificationDataSync(byte[] notificationData) - { - foreach (KeyValuePair client in _connectedClients) - { - try - { - if (client.Value.Stream?.CanWrite == true) - { - client.Value.Stream.Write(notificationData, 0, notificationData.Length); - client.Value.Stream.Flush(); - } - } - catch (Exception) - { - // Ignore errors during shutdown notification - client may already be disconnected - } - } - } - - /// - /// Send notification data to all connected clients - /// - private async Task SendNotificationDataAsync(byte[] notificationData) - { - List clientsToRemove = new List(); - - foreach (KeyValuePair client in _connectedClients) - { - try - { - if (client.Value.Stream?.CanWrite == true) - { - await client.Value.Stream.WriteAsync(notificationData, 0, notificationData.Length); - } - else - { - clientsToRemove.Add(client.Key); - } - } - catch (Exception ex) - { - OnError?.Invoke($"Error writing notification to client {client.Key}: {ex.Message}"); - clientsToRemove.Add(client.Key); - } - } - - // Remove disconnected clients - foreach (string clientKey in clientsToRemove) - { - if (_connectedClients.TryRemove(clientKey, out ConnectedClient removedClient)) - { - // Notify tool disconnected - OnToolDisconnected?.Invoke(removedClient.ClientName); - } - } - } - - /// /// Creates a Content-Length framed message for JSON-RPC 2.0 communication. /// diff --git a/Packages/src/Editor/Server/McpServerController.cs b/Packages/src/Editor/Server/McpServerController.cs index 72a0ea629..5c987599c 100644 --- a/Packages/src/Editor/Server/McpServerController.cs +++ b/Packages/src/Editor/Server/McpServerController.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using System.Net.Sockets; using UnityEditor; -using Newtonsoft.Json; using UnityEngine; namespace io.github.hatayama.UnityCliLoop @@ -78,10 +77,6 @@ private static void InitializeOnLoad() McpBridgeServer.OnServerLoopExited -= OnServerLoopUnexpectedlyExited; McpBridgeServer.OnServerLoopExited += OnServerLoopUnexpectedlyExited; - // Initialize connected tools monitoring service - // Note: ConnectedToolsMonitoringService has [InitializeOnLoad] so it's automatically initialized - // This comment ensures the service initialization order is documented - // Recovery binds the project IPC endpoint and may touch config files, so keep it off the // synchronous InitializeOnLoad path while preserving automatic startup. ScheduleStartupRecovery( @@ -361,7 +356,6 @@ private static void TryRestoreServerWithRetry(int retryCount) // NOTE: Do NOT clear UI display flag here - let it be cleared by timeout or client connection McpEditorSettings.SetIsReconnecting(false); - // Tools changed notification will be sent by OnAfterAssemblyReload } catch (System.Exception) { @@ -454,101 +448,6 @@ private static void OnServerLoopUnexpectedlyExited() }; } - /// - /// Processes pending compile requests. - /// - private static void ProcessPendingCompileRequests() - { - // Temporarily disabled to avoid main thread errors due to SessionState operations. - // TODO: Re-enable after resolving the main thread issue. - // CompileSessionState.StartForcedRecompile(); - } - - /// - /// Send tools changed notification to client side - /// - private static void SendToolsChangedNotification() - { - // Log with stack trace to identify caller - System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace(true); - string callerInfo = stackTrace.GetFrame(1)?.GetMethod()?.Name ?? "Unknown"; - - if (mcpServer == null) - { - return; - } - - // Send MCP standard notification only - var notificationParams = new - { - timestamp = System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), - message = "Unity tools have been updated" - }; - - var mcpNotification = new - { - jsonrpc = McpServerConfig.JSONRPC_VERSION, - method = "notifications/tools/list_changed", - @params = notificationParams - }; - - string mcpNotificationJson = JsonConvert.SerializeObject(mcpNotification); - mcpServer.SendNotificationToClients(mcpNotificationJson); - } - - /// - /// Manually trigger tool change notification - /// Public method for external calls (e.g., from UnityToolRegistry) - /// - public static void TriggerToolChangeNotification() - { - if (IsServerRunning) - { - SendToolsChangedNotification(); - } - } - - /// - /// Send tool notification after compilation with frame delay - /// - private static async Task SendToolNotificationAfterCompilationAsync() - { - // Use frame delay for timing adjustment after domain reload - // This ensures Unity Editor is in a stable state before sending notifications - await EditorDelay.DelayFrame(1); - - CustomToolManager.NotifyToolChanges(); - } - - /// - /// Restore server after compilation with frame delay. - /// Currently kept as a helper; recovery logic is unified in StartRecoveryIfNeededAsync. - /// - private static async Task RestoreServerAfterCompileAsync() - { - // Wait a short while for Unity editor state to settle after compilation. - await EditorDelay.DelayFrame(1); - - TryRestoreServerWithRetry(0); - } - - /// - /// Restore server on startup with frame delay - /// - private static async Task RestoreServerOnStartupAsync() - { - // Wait for Unity Editor to be ready before auto-starting - await EditorDelay.DelayFrame(1); - _ = StartRecoveryIfNeededAsync(false, CancellationToken.None).ContinueWith(task => - { - if (task.IsFaulted) - { - VibeLogger.LogError("server_startup_restore_failed", - $"Failed to restore server: {task.Exception?.GetBaseException().Message}"); - } - }, TaskScheduler.FromCurrentSynchronizationContext()); - } - /// /// Retry server restore with frame delay on the same project IPC endpoint. /// @@ -575,21 +474,6 @@ private static async Task StartReconnectionUITimeoutAsync() } } - /// - /// Clear reconnecting flags when client connects - /// Called by UI or bridge server when client connection is detected - /// - public static void ClearReconnectingFlag() - { - bool wasReconnecting = McpEditorSettings.GetIsReconnecting(); - bool wasShowingUI = McpEditorSettings.GetShowReconnectingUI(); - - if (wasReconnecting || wasShowingUI) - { - McpEditorSettings.ClearReconnectingFlags(); - } - } - /// /// Validates server configuration before starting /// Implements fail-fast behavior for invalid configurations diff --git a/Packages/src/Editor/Server/ServerShutdownReason.cs b/Packages/src/Editor/Server/ServerShutdownReason.cs deleted file mode 100644 index 44c119a34..000000000 --- a/Packages/src/Editor/Server/ServerShutdownReason.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace io.github.hatayama.UnityCliLoop -{ - public enum ServerShutdownReason - { - DomainReload, - - EditorQuit - } -} diff --git a/Packages/src/Editor/Server/ServerShutdownReason.cs.meta b/Packages/src/Editor/Server/ServerShutdownReason.cs.meta deleted file mode 100644 index 7c49a723a..000000000 --- a/Packages/src/Editor/Server/ServerShutdownReason.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4a899d5a46dc7492dbc927fe8659e220 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Shared/Config/McpConstants.cs b/Packages/src/Editor/Shared/Config/McpConstants.cs index 2960c6e52..37ba8b9e9 100644 --- a/Packages/src/Editor/Shared/Config/McpConstants.cs +++ b/Packages/src/Editor/Shared/Config/McpConstants.cs @@ -77,11 +77,6 @@ public static UnityEditor.PackageManager.PackageInfo PackageInfo // Environment variable keys for development mode public const string ENV_KEY_ULOOP_DEBUG = "ULOOP_DEBUG"; - public const string UNKNOWN_CLIENT_NAME = "Unknown Client"; - - // Command messages - public const string CLIENT_SUCCESS_MESSAGE_TEMPLATE = "Client name registered successfully: {0}"; - // Reconnection settings public const int RECONNECTION_TIMEOUT_SECONDS = 10; diff --git a/Packages/src/Editor/UI/McpCommunicationLog.cs b/Packages/src/Editor/UI/McpCommunicationLog.cs index 610e39b77..9c25bcd49 100644 --- a/Packages/src/Editor/UI/McpCommunicationLog.cs +++ b/Packages/src/Editor/UI/McpCommunicationLog.cs @@ -8,7 +8,7 @@ namespace io.github.hatayama.UnityCliLoop { /// - /// An entry for the MCP communication log. + /// An entry for the CLI communication log. /// Log information that includes a request/response pair. /// public record McpCommunicationLogEntry @@ -34,7 +34,7 @@ public McpCommunicationLogEntry(string commandName, DateTime timestamp, string r } /// - /// A class for managing MCP communication logs. + /// A class for managing CLI communication logs. /// public static class McpCommunicationLogger { @@ -283,4 +283,4 @@ private static string NormalizeId(JToken idToken) return idToken.ToString(); } } -} \ No newline at end of file +} diff --git a/Packages/src/Editor/UI/McpEditorModel.cs b/Packages/src/Editor/UI/McpEditorModel.cs index dcf986e9f..5205f0d4b 100644 --- a/Packages/src/Editor/UI/McpEditorModel.cs +++ b/Packages/src/Editor/UI/McpEditorModel.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using UnityEditor; using UnityEngine; namespace io.github.hatayama.UnityCliLoop @@ -51,7 +49,6 @@ public void LoadFromSettings() McpEditorSettingsData settings = McpEditorSettings.GetSettings(); UpdateUIState(ui => new UIState( - showConnectedTools: ui.ShowConnectedTools, mainScrollPosition: ui.MainScrollPosition, showSecuritySettings: settings.showSecuritySettings, showToolSettings: settings.showToolSettings, @@ -72,7 +69,6 @@ public void SaveToSettings() public void LoadFromSessionState() { UpdateUIState(ui => new UIState( - showConnectedTools: ui.ShowConnectedTools, mainScrollPosition: ui.MainScrollPosition, showSecuritySettings: ui.ShowSecuritySettings, showToolSettings: ui.ShowToolSettings, @@ -94,9 +90,7 @@ public void EnablePostCompileMode() UpdateRuntimeState(runtime => new RuntimeState( isPostCompileMode: true, needsRepaint: true, - lastServerRunning: runtime.LastServerRunning, - lastConnectedClientsCount: runtime.LastConnectedClientsCount, - lastClientsInfoHash: runtime.LastClientsInfoHash)); + lastServerRunning: runtime.LastServerRunning)); } /// @@ -107,9 +101,7 @@ public void DisablePostCompileMode() UpdateRuntimeState(runtime => new RuntimeState( isPostCompileMode: false, needsRepaint: runtime.NeedsRepaint, - lastServerRunning: runtime.LastServerRunning, - lastConnectedClientsCount: runtime.LastConnectedClientsCount, - lastClientsInfoHash: runtime.LastClientsInfoHash)); + lastServerRunning: runtime.LastServerRunning)); } /// @@ -120,9 +112,7 @@ public void RequestRepaint() UpdateRuntimeState(runtime => new RuntimeState( isPostCompileMode: runtime.IsPostCompileMode, needsRepaint: true, - lastServerRunning: runtime.LastServerRunning, - lastConnectedClientsCount: runtime.LastConnectedClientsCount, - lastClientsInfoHash: runtime.LastClientsInfoHash)); + lastServerRunning: runtime.LastServerRunning)); } /// @@ -133,46 +123,17 @@ public void ClearRepaintRequest() UpdateRuntimeState(runtime => new RuntimeState( isPostCompileMode: runtime.IsPostCompileMode, needsRepaint: false, - lastServerRunning: runtime.LastServerRunning, - lastConnectedClientsCount: runtime.LastConnectedClientsCount, - lastClientsInfoHash: runtime.LastClientsInfoHash)); - } - - /// - /// Update server state tracking for change detection - /// - public void UpdateServerStateTracking(bool isRunning, int clientCount, string clientsHash) - { - UpdateRuntimeState(runtime => new RuntimeState( - isPostCompileMode: runtime.IsPostCompileMode, - needsRepaint: runtime.NeedsRepaint, - lastServerRunning: isRunning, - lastConnectedClientsCount: clientCount, - lastClientsInfoHash: clientsHash)); + lastServerRunning: runtime.LastServerRunning)); } // UIState-specific update methods with persistence - /// - /// Update ShowConnectedTools setting - /// - public void UpdateShowConnectedTools(bool show) - { - UpdateUIState(ui => new UIState( - showConnectedTools: show, - mainScrollPosition: ui.MainScrollPosition, - showSecuritySettings: ui.ShowSecuritySettings, - showToolSettings: ui.ShowToolSettings, - showConfiguration: ui.ShowConfiguration)); - } - /// /// Update MainScrollPosition setting /// public void UpdateMainScrollPosition(Vector2 position) { UpdateUIState(ui => new UIState( - showConnectedTools: ui.ShowConnectedTools, mainScrollPosition: position, showSecuritySettings: ui.ShowSecuritySettings, showToolSettings: ui.ShowToolSettings, @@ -185,7 +146,6 @@ public void UpdateMainScrollPosition(Vector2 position) public void UpdateShowSecuritySettings(bool show) { UpdateUIState(ui => new UIState( - showConnectedTools: ui.ShowConnectedTools, mainScrollPosition: ui.MainScrollPosition, showSecuritySettings: show, showToolSettings: ui.ShowToolSettings, @@ -196,7 +156,6 @@ public void UpdateShowSecuritySettings(bool show) public void UpdateShowToolSettings(bool show) { UpdateUIState(ui => new UIState( - showConnectedTools: ui.ShowConnectedTools, mainScrollPosition: ui.MainScrollPosition, showSecuritySettings: ui.ShowSecuritySettings, showToolSettings: show, @@ -212,7 +171,6 @@ public void UpdateToolEnabled(string toolName, bool enabled) public void UpdateShowConfiguration(bool show) { UpdateUIState(ui => new UIState( - showConnectedTools: ui.ShowConnectedTools, mainScrollPosition: ui.MainScrollPosition, showSecuritySettings: ui.ShowSecuritySettings, showToolSettings: ui.ShowToolSettings, diff --git a/Packages/src/Editor/UI/McpEditorWindow.cs b/Packages/src/Editor/UI/McpEditorWindow.cs index 8b1d19edf..ab1c78551 100644 --- a/Packages/src/Editor/UI/McpEditorWindow.cs +++ b/Packages/src/Editor/UI/McpEditorWindow.cs @@ -1,7 +1,7 @@ using UnityEngine; using UnityEditor; -using System.Linq; using System.Collections.Generic; +using System.Linq; using System; using System.Threading; using System.Threading.Tasks; @@ -98,17 +98,11 @@ private void SetupViewCallbacks() }; _view.OnGroupSkillsChanged += HandleGroupSkillsChanged; _view.OnConfigurationFoldoutChanged += UpdateShowConfiguration; - _view.OnConnectedToolsFoldoutChanged += UpdateShowConnectedTools; _view.OnToolSettingsFoldoutChanged += UpdateShowToolSettings; _view.OnToolToggled += HandleToolToggled; _view.OnSecurityLevelChanged += UpdateDynamicCodeSecurityLevel; } - public IEnumerable GetConnectedToolsAsClients() - { - return ConnectedToolsMonitoringService.GetConnectedToolsAsClients(); - } - private void InitializeEventHandler() { _eventHandler = new McpEditorWindowEventHandler(_model, this); @@ -253,9 +247,6 @@ internal void RefreshAllSections( } RefreshCliSetupSection(runExpensiveChecks); - ConnectedToolsData toolsData = CreateConnectedToolsData(); - _view.UpdateConnectedTools(toolsData); - RefreshToolSettingsHeader(); if (runExpensiveChecks) { @@ -299,36 +290,6 @@ private async void HandleRefreshCliVersion() } } - public void RefreshConnectedToolsSection() - { - if (_view == null) - { - return; - } - - ConnectedToolsData toolsData = CreateConnectedToolsData(); - _view.UpdateConnectedTools(toolsData); - } - - private ConnectedToolsData CreateConnectedToolsData() - { - bool isServerRunning = McpServerController.IsServerRunning; - ConnectedClient[] connectedClients = GetConnectedToolsAsClients().ToArray(); - bool showReconnectingUIFlag = McpEditorSettings.GetShowReconnectingUI(); - bool showPostCompileUIFlag = McpEditorSettings.GetShowPostCompileReconnectingUI(); - bool hasNamedClients = connectedClients.Any(); - bool showReconnectingUI = (showReconnectingUIFlag || showPostCompileUIFlag) && !hasNamedClients; - - if (hasNamedClients && showPostCompileUIFlag) - { - McpEditorSettings.ClearPostCompileReconnectingUI(); - } - - bool showSection = isServerRunning && hasNamedClients; - - return new ConnectedToolsData(connectedClients, _model.UI.ShowConnectedTools, isServerRunning, showReconnectingUI, showSection); - } - public void InvalidateToolSettingsCatalog() { _isToolSettingsCatalogDirty = true; @@ -404,8 +365,8 @@ private ToolSettingsSectionData CreateToolSettingsData() ToolSettingsCatalogItem[] allTools = registry.GetToolSettingsCatalog(); - System.Collections.Generic.List builtIn = new(); - System.Collections.Generic.List thirdParty = new(); + List builtIn = new(); + List thirdParty = new(); foreach (ToolSettingsCatalogItem tool in allTools) { @@ -526,8 +487,6 @@ private void HandleToolToggled(string toolName, bool enabled) private async void ApplyToolToggleSideEffects(string toolName, bool enabled) { - ClientNotificationService.TriggerToolChangeNotification(); - if (!enabled) { ToolSkillSynchronizer.RemoveSkillFiles(toolName); @@ -547,11 +506,6 @@ private async void ApplyToolToggleSideEffects(string toolName, bool enabled) } } - private void UpdateShowConnectedTools(bool show) - { - _model.UpdateShowConnectedTools(show); - } - private void UpdateShowConfiguration(bool show) { _model.UpdateShowConfiguration(show); diff --git a/Packages/src/Editor/UI/McpEditorWindowEventHandler.cs b/Packages/src/Editor/UI/McpEditorWindowEventHandler.cs index 09306615c..fa36cf80b 100644 --- a/Packages/src/Editor/UI/McpEditorWindowEventHandler.cs +++ b/Packages/src/Editor/UI/McpEditorWindowEventHandler.cs @@ -69,30 +69,14 @@ private void SubscribeToServerEvents() McpBridgeServer.OnServerStarted += OnServerStateChanged; McpBridgeServer.OnServerStopping += OnServerStateChanged; - ConnectedToolsMonitoringService.OnConnectedToolsChanged += OnConnectedToolsChanged; CustomToolManager.OnToolsChanged += OnToolsChanged; - - McpBridgeServer currentServer = McpServerController.CurrentServer; - if (currentServer != null) - { - currentServer.OnClientConnected += OnClientConnected; - currentServer.OnClientDisconnected += OnClientDisconnected; - } } private void UnsubscribeFromServerEvents() { McpBridgeServer.OnServerStarted -= OnServerStateChanged; McpBridgeServer.OnServerStopping -= OnServerStateChanged; - ConnectedToolsMonitoringService.OnConnectedToolsChanged -= OnConnectedToolsChanged; CustomToolManager.OnToolsChanged -= OnToolsChanged; - - McpBridgeServer currentServer = McpServerController.CurrentServer; - if (currentServer != null) - { - currentServer.OnClientConnected -= OnClientConnected; - currentServer.OnClientDisconnected -= OnClientDisconnected; - } } private void OnServerStateChanged() @@ -101,51 +85,12 @@ private void OnServerStateChanged() _model.RequestRepaint(); } - private void OnConnectedToolsChanged() - { - _model.RequestRepaint(); - } - private void OnToolsChanged() { _window.InvalidateToolSettingsCatalog(); _model.RequestRepaint(); } - /// - /// Handle client connection event - force UI repaint for immediate update - /// - private void OnClientConnected(string clientEndpoint) - { - // Enhanced logging for debugging client connection - // Count check for debugging purposes only - - // Clear reconnecting flags when client connects - McpServerController.ClearReconnectingFlag(); - - // Mark that repaint is needed since events are called from background thread - _model.RequestRepaint(); - - // Exit post-compile mode when client connects - if (_model.Runtime.IsPostCompileMode) - { - _model.DisablePostCompileMode(); - } - } - - /// - /// Handle client disconnection event - force UI repaint for immediate update - /// - private void OnClientDisconnected(string clientEndpoint) - { - // Enhanced logging for debugging client disconnection issues - // Count check for debugging purposes only - - - // Mark that repaint is needed since events are called from background thread - _model.RequestRepaint(); - } - private void OnEditorUpdate() { using (s_onEditorUpdateMarker.Auto()) @@ -163,13 +108,6 @@ private void OnEditorUpdate() } } - /// - /// Re-subscribe to server events (called after server start) - /// - public void RefreshServerEventSubscriptions() - { - SubscribeToServerEvents(); - } } // Post-compile recovery can stay active while UI data is unchanged, so explicit repaint diff --git a/Packages/src/Editor/UI/McpEditorWindowState.cs b/Packages/src/Editor/UI/McpEditorWindowState.cs index 7903024f6..813a944ba 100644 --- a/Packages/src/Editor/UI/McpEditorWindowState.cs +++ b/Packages/src/Editor/UI/McpEditorWindowState.cs @@ -1,5 +1,4 @@ using UnityEngine; -using System.Collections.Generic; namespace io.github.hatayama.UnityCliLoop { @@ -25,20 +24,17 @@ public enum SkillsTarget public record UIState { - public bool ShowConnectedTools { get; } public Vector2 MainScrollPosition { get; } public bool ShowSecuritySettings { get; } public bool ShowToolSettings { get; } public bool ShowConfiguration { get; } public UIState( - bool showConnectedTools = true, Vector2 mainScrollPosition = default, bool showSecuritySettings = true, bool showToolSettings = true, bool showConfiguration = true) { - ShowConnectedTools = showConnectedTools; MainScrollPosition = mainScrollPosition; ShowSecuritySettings = showSecuritySettings; ShowToolSettings = showToolSettings; @@ -55,21 +51,15 @@ public record RuntimeState public bool NeedsRepaint { get; } public bool IsPostCompileMode { get; } public bool LastServerRunning { get; } - public int LastConnectedClientsCount { get; } - public string LastClientsInfoHash { get; } public RuntimeState( bool needsRepaint = false, bool isPostCompileMode = false, - bool lastServerRunning = false, - int lastConnectedClientsCount = 0, - string lastClientsInfoHash = "") + bool lastServerRunning = false) { NeedsRepaint = needsRepaint; IsPostCompileMode = isPostCompileMode; LastServerRunning = lastServerRunning; - LastConnectedClientsCount = lastConnectedClientsCount; - LastClientsInfoHash = lastClientsInfoHash; } } diff --git a/Packages/src/Editor/UI/McpEditorWindowViewData.cs b/Packages/src/Editor/UI/McpEditorWindowViewData.cs index 8773f6a8d..afeafe9c0 100644 --- a/Packages/src/Editor/UI/McpEditorWindowViewData.cs +++ b/Packages/src/Editor/UI/McpEditorWindowViewData.cs @@ -1,5 +1,4 @@ using UnityEngine; -using System.Collections.Generic; namespace io.github.hatayama.UnityCliLoop { @@ -32,24 +31,6 @@ public ServerControlsData(bool isServerRunning) } } - public record ConnectedToolsData - { - public readonly IReadOnlyCollection Clients; - public readonly bool ShowFoldout; - public readonly bool IsServerRunning; - public readonly bool ShowReconnectingUI; - public readonly bool ShowSection; - - public ConnectedToolsData(IReadOnlyCollection clients, bool showFoldout, bool isServerRunning, bool showReconnectingUI, bool showSection) - { - Clients = clients; - ShowFoldout = showFoldout; - IsServerRunning = isServerRunning; - ShowReconnectingUI = showReconnectingUI; - ShowSection = showSection; - } - } - public record ToolToggleItem { public readonly string ToolName; diff --git a/Packages/src/Editor/UI/UIToolkit/Components/ConnectedToolsSection.cs b/Packages/src/Editor/UI/UIToolkit/Components/ConnectedToolsSection.cs deleted file mode 100644 index c7d9bba69..000000000 --- a/Packages/src/Editor/UI/UIToolkit/Components/ConnectedToolsSection.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Linq; -using UnityEngine.UIElements; - -namespace io.github.hatayama.UnityCliLoop -{ - public class ConnectedToolsSection - { - private readonly VisualElement _sectionContainer; - private readonly Foldout _foldout; - private readonly VisualElement _clientsList; - - private bool _hasLastValidData; - private ConnectedToolsData _lastData; - - public event Action OnFoldoutChanged; - - public ConnectedToolsSection(VisualElement root) - { - _sectionContainer = root.Q("connected-tools-section"); - _foldout = root.Q("connected-tools-foldout"); - _clientsList = root.Q("clients-list"); - - SetupBindings(); - } - - private void SetupBindings() - { - _foldout.RegisterValueChangedCallback(evt => OnFoldoutChanged?.Invoke(evt.newValue)); - } - - public void Update(ConnectedToolsData data) - { - ViewDataBinder.SetVisible(_sectionContainer, data.ShowSection); - - if (!data.ShowSection) - { - return; - } - - if (data.ShowReconnectingUI && _hasLastValidData) - { - return; - } - - ViewDataBinder.UpdateFoldout(_foldout, data.ShowFoldout); - UpdateClientsList(data); - _lastData = data; - } - - private void UpdateClientsList(ConnectedToolsData data) - { - _clientsList.Clear(); - - if (data.Clients == null || data.Clients.Count == 0) - { - _hasLastValidData = false; - return; - } - - System.Collections.Generic.List validClients = data.Clients - .Where(client => IsValidClientName(client.ClientName)) - .ToList(); - - foreach (ConnectedClient client in validClients) - { - VisualElement clientItem = CreateClientItem(client); - _clientsList.Add(clientItem); - } - - _hasLastValidData = validClients.Count > 0; - } - - private VisualElement CreateClientItem(ConnectedClient client) - { - VisualElement container = new VisualElement(); - container.AddToClassList("mcp-client"); - - Label iconLabel = new Label(McpUIConstants.CLIENT_ICON); - iconLabel.AddToClassList("mcp-client__icon"); - container.Add(iconLabel); - - Label nameLabel = new Label(client.ClientName); - nameLabel.AddToClassList("mcp-client__name"); - container.Add(nameLabel); - - return container; - } - - private bool IsValidClientName(string clientName) - { - return !string.IsNullOrEmpty(clientName) && clientName != McpConstants.UNKNOWN_CLIENT_NAME; - } - } -} diff --git a/Packages/src/Editor/UI/UIToolkit/Components/ConnectedToolsSection.cs.meta b/Packages/src/Editor/UI/UIToolkit/Components/ConnectedToolsSection.cs.meta deleted file mode 100644 index 1bbef68e9..000000000 --- a/Packages/src/Editor/UI/UIToolkit/Components/ConnectedToolsSection.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f622365cf49674deb8b852dede89f11d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/UI/UIToolkit/McpEditorWindow.uss b/Packages/src/Editor/UI/UIToolkit/McpEditorWindow.uss index a46ca7a7a..5a467fc91 100644 --- a/Packages/src/Editor/UI/UIToolkit/McpEditorWindow.uss +++ b/Packages/src/Editor/UI/UIToolkit/McpEditorWindow.uss @@ -256,36 +256,6 @@ padding-top: 0; } -/* =========================================== - Block: Client (Connected Tools Item) - =========================================== */ -.mcp-clients-list { - margin-top: 4px; -} - -.mcp-client { - background-color: rgba(76, 76, 76, 0.5); - border-width: 1px; - border-color: rgba(128, 128, 128, 0.5); - border-radius: 3px; - padding: 8px; - margin-left: 3px; - margin-right: 3px; - margin-bottom: 6px; - flex-direction: row; - align-items: center; -} - -.mcp-client__icon { - color: var(--mcp-color-success); - margin-right: 4px; -} - -.mcp-client__name { - -unity-font-style: bold; - flex-grow: 1; -} - /* =========================================== Block: Messages =========================================== */ diff --git a/Packages/src/Editor/UI/UIToolkit/McpEditorWindow.uxml b/Packages/src/Editor/UI/UIToolkit/McpEditorWindow.uxml index 2d1b731b3..9425c8d0f 100644 --- a/Packages/src/Editor/UI/UIToolkit/McpEditorWindow.uxml +++ b/Packages/src/Editor/UI/UIToolkit/McpEditorWindow.uxml @@ -34,15 +34,6 @@ - - - - - - - - - @@ -62,7 +53,6 @@ - diff --git a/Packages/src/Editor/UI/UIToolkit/McpEditorWindowUI.cs b/Packages/src/Editor/UI/UIToolkit/McpEditorWindowUI.cs index 685509431..ba1088a4e 100644 --- a/Packages/src/Editor/UI/UIToolkit/McpEditorWindowUI.cs +++ b/Packages/src/Editor/UI/UIToolkit/McpEditorWindowUI.cs @@ -19,7 +19,6 @@ public class McpEditorWindowUI : IDisposable private readonly VisualElement _root; private CliSetupSection _cliSetupSection; - private ConnectedToolsSection _connectedToolsSection; private ToolSettingsSection _toolSettingsSection; private Foldout _configurationFoldout; @@ -35,7 +34,6 @@ public class McpEditorWindowUI : IDisposable public event Action OnSkillsTargetChanged; public event Action OnGroupSkillsChanged; public event Action OnConfigurationFoldoutChanged; - public event Action OnConnectedToolsFoldoutChanged; public event Action OnToolSettingsFoldoutChanged; public event Action OnToolToggled; public event Action OnSecurityLevelChanged; @@ -98,9 +96,6 @@ private void InitializeSections() _mainScrollView = _root.Q("main-scroll-view"); ConfigureScrollView(); - _connectedToolsSection = new ConnectedToolsSection(_root); - _connectedToolsSection.OnFoldoutChanged += value => OnConnectedToolsFoldoutChanged?.Invoke(value); - _toolSettingsSection = new ToolSettingsSection(_root); _toolSettingsSection.OnFoldoutChanged += value => OnToolSettingsFoldoutChanged?.Invoke(value); _toolSettingsSection.OnToolToggled += (toolName, enabled) => OnToolToggled?.Invoke(toolName, enabled); @@ -145,11 +140,6 @@ private void HandleGitHubHoverChanged(bool isHovered) ViewDataBinder.ToggleClass(_githubLinkIcon, "mcp-github-link__icon--hover", isHovered); } - public void UpdateConnectedTools(ConnectedToolsData data) - { - _connectedToolsSection?.Update(data); - } - public void UpdateToolSettings(ToolSettingsSectionData data) { _toolSettingsSection?.Update(data); @@ -173,7 +163,6 @@ public void UpdateCliSetup(CliSetupData data) public void Dispose() { _cliSetupSection = null; - _connectedToolsSection = null; _toolSettingsSection = null; _configurationFoldout = null; } diff --git a/Packages/src/README.md b/Packages/src/README.md index 2d38327a6..c1b35dbc2 100644 --- a/Packages/src/README.md +++ b/Packages/src/README.md @@ -19,8 +19,6 @@ Let an AI agent compile, test, and operate your Unity project from popular LLM t Designed to keep AI-driven development loops running autonomously inside your existing Unity projects. -> **Note**: This project was formerly known as **uLoopMCP**. - # Concept Unity CLI Loop is a Unity integration tool designed so that **AI can drive your Unity project forward with minimal human intervention**. Tasks that humans typically handle manually—compiling, running the Test Runner, checking logs, editing scenes, and capturing windows to verify UI layouts—are exposed as tools that LLMs can orchestrate. @@ -52,13 +50,6 @@ https://github.com/user-attachments/assets/569a2110-7351-4cf3-8281-3a83fe181817 https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src ``` -> **If you installed via git URL before v1.0.0**: The repository was renamed from `uLoopMCP` to `unity-cli-loop` in v1.0.0. Please update your `manifest.json`: -> ```text -> Old: https://github.com/hatayama/uLoopMCP.git?path=/Packages/src -> New: https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src -> ``` -> The old URL still works via GitHub redirect, but updating is recommended. OpenUPM users are not affected. - ## Via OpenUPM (Recommended) ## Using Scoped registry in Unity Package Manager diff --git a/Packages/src/README_ja.md b/Packages/src/README_ja.md index 2d65d2063..e0b11b9bf 100644 --- a/Packages/src/README_ja.md +++ b/Packages/src/README_ja.md @@ -20,8 +20,6 @@ CLIを通じて、AIエージェントがUnityプロジェクトのコンパイ AI駆動の開発ループを既存のUnityプロジェクト内で自律的に回し続けるために設計されています。 -> **Note**: このプロジェクトの旧名称は **uLoopMCP** です。 - # コンセプト Unity CLI Loopは、「AIがUnityプロジェクトの実装をできるだけ人手を介さずに進められる」ことを目指して作られた Unity連携ツールです。 人間が手で行っていたコンパイル、Test Runner の実行、ログ確認、シーン編集、画面キャプチャによるUIレイアウト確認などの作業を、LLM ツールからまとめて操作できるようにします。 @@ -53,13 +51,6 @@ https://github.com/user-attachments/assets/569a2110-7351-4cf3-8281-3a83fe181817 https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src ``` -> **v1.0.0以前にgit URL でインストールされていた方へ**: v1.0.0でリポジトリ名が `uLoopMCP` から `unity-cli-loop` に変更されました。`manifest.json` の URL を更新してください: -> ```text -> 旧: https://github.com/hatayama/uLoopMCP.git?path=/Packages/src -> 新: https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src -> ``` -> 旧URLはGitHubのリダイレクトにより引き続き動作しますが、更新を推奨します。OpenUPMユーザーは影響ありません。 - ## OpenUPM経由(推奨) ## Unity Package ManagerでScoped registryを使用 diff --git a/Packages/src/TOOL_REFERENCE.md b/Packages/src/TOOL_REFERENCE.md index d53857744..4bdfd1992 100644 --- a/Packages/src/TOOL_REFERENCE.md +++ b/Packages/src/TOOL_REFERENCE.md @@ -1,16 +1,16 @@ [日本語](TOOL_REFERENCE_ja.md) -# uLoopMCP Tool Reference +# Unity CLI Loop Tool Reference -This document provides detailed specifications for all uLoopMCP tools. +This document provides detailed specifications for all Unity CLI Loop tools. ## Common Response Format -All Unity MCP tools share the following common elements: +All Unity CLI Loop tools share the following common elements: ### Common Response Properties All tools automatically include the following property: -- `Ver` (string): uLoopMCP server version for CLI compatibility check +- `Ver` (string): Unity CLI Loop package version for CLI compatibility checks --- @@ -62,8 +62,8 @@ All tools automatically include the following property: - **Parameters**: - `FilterType` (enum): Type of test filter - "all"(0), "exact"(1), "regex"(2), "assembly"(3) (default: "all") - `FilterValue` (string): Filter value (specify when FilterType is other than all) (default: "") - - `exact`: Individual test method name (exact match) (e.g.: io.github.hatayama.uLoopMCP.ConsoleLogRetrieverTests.GetAllLogs_WithMaskAllOff_StillReturnsAllLogs) - - `regex`: Class name or namespace (regex pattern) (e.g.: io.github.hatayama.uLoopMCP.ConsoleLogRetrieverTests, io.github.hatayama.uLoopMCP) + - `exact`: Individual test method name (exact match) (e.g.: io.github.hatayama.UnityCliLoop.ConsoleLogRetrieverTests.GetAllLogs_WithMaskAllOff_StillReturnsAllLogs) + - `regex`: Class name or namespace (regex pattern) (e.g.: io.github.hatayama.UnityCliLoop.ConsoleLogRetrieverTests, io.github.hatayama.UnityCliLoop) - `assembly`: Assembly name (e.g.: uLoopMCP.Tests.Editor) - `TestMode` (enum): Test mode - "EditMode"(0), "PlayMode"(1) (default: "EditMode") - **PlayMode Warning**: During PlayMode test execution, domain reload is temporarily disabled diff --git a/Packages/src/TOOL_REFERENCE_ja.md b/Packages/src/TOOL_REFERENCE_ja.md index b6356a97e..c082def24 100644 --- a/Packages/src/TOOL_REFERENCE_ja.md +++ b/Packages/src/TOOL_REFERENCE_ja.md @@ -1,16 +1,16 @@ [English](TOOL_REFERENCE.md) -# uLoopMCP ツールリファレンス +# Unity CLI Loop ツールリファレンス -このドキュメントでは、全uLoopMCPツールの詳細仕様を提供します。 +このドキュメントでは、全Unity CLI Loopツールの詳細仕様を提供します。 ## 共通レスポンス形式 -すべてのUnity MCPツールは以下の共通要素を持ちます: +すべてのUnity CLI Loopツールは以下の共通要素を持ちます: ### 共通レスポンスプロパティ すべてのツールには以下のプロパティが自動的に含まれます: -- `Ver` (string): CLIとの互換性チェック用のuLoopMCPサーバーバージョン +- `Ver` (string): CLIとの互換性チェック用のUnity CLI Loopパッケージバージョン --- @@ -62,8 +62,8 @@ - **パラメータ**: - `FilterType` (enum): テストフィルタのタイプ - "all"(0), "exact"(1), "regex"(2), "assembly"(3)(デフォルト: "all") - `FilterValue` (string): フィルタ値(FilterTypeがall以外の場合に指定)(デフォルト: "") - - `exact`: 個別テストメソッド名(完全一致)(例:io.github.hatayama.uLoopMCP.ConsoleLogRetrieverTests.GetAllLogs_WithMaskAllOff_StillReturnsAllLogs) - - `regex`: クラス名または名前空間(正規表現パターン)(例:io.github.hatayama.uLoopMCP.ConsoleLogRetrieverTests, io.github.hatayama.uLoopMCP) + - `exact`: 個別テストメソッド名(完全一致)(例:io.github.hatayama.UnityCliLoop.ConsoleLogRetrieverTests.GetAllLogs_WithMaskAllOff_StillReturnsAllLogs) + - `regex`: クラス名または名前空間(正規表現パターン)(例:io.github.hatayama.UnityCliLoop.ConsoleLogRetrieverTests, io.github.hatayama.UnityCliLoop) - `assembly`: アセンブリ名(例:uLoopMCP.Tests.Editor) - `TestMode` (enum): テストモード - "EditMode"(0), "PlayMode"(1)(デフォルト: "EditMode") - **PlayMode注意**: PlayModeテスト実行時は、一時的にdomain reloadが無効化されます diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index d5681a65d..e63126e00 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -13,7 +13,7 @@ PlayerSettings: useOnDemandResources: 0 accelerometerFrequency: 60 companyName: DefaultCompany - productName: uLoopMCP + productName: Unity CLI Loop defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} diff --git a/README.md b/README.md index 2d38327a6..c1b35dbc2 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ Let an AI agent compile, test, and operate your Unity project from popular LLM t Designed to keep AI-driven development loops running autonomously inside your existing Unity projects. -> **Note**: This project was formerly known as **uLoopMCP**. - # Concept Unity CLI Loop is a Unity integration tool designed so that **AI can drive your Unity project forward with minimal human intervention**. Tasks that humans typically handle manually—compiling, running the Test Runner, checking logs, editing scenes, and capturing windows to verify UI layouts—are exposed as tools that LLMs can orchestrate. @@ -52,13 +50,6 @@ https://github.com/user-attachments/assets/569a2110-7351-4cf3-8281-3a83fe181817 https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src ``` -> **If you installed via git URL before v1.0.0**: The repository was renamed from `uLoopMCP` to `unity-cli-loop` in v1.0.0. Please update your `manifest.json`: -> ```text -> Old: https://github.com/hatayama/uLoopMCP.git?path=/Packages/src -> New: https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src -> ``` -> The old URL still works via GitHub redirect, but updating is recommended. OpenUPM users are not affected. - ## Via OpenUPM (Recommended) ## Using Scoped registry in Unity Package Manager diff --git a/README_ja.md b/README_ja.md index 2d65d2063..e0b11b9bf 100644 --- a/README_ja.md +++ b/README_ja.md @@ -20,8 +20,6 @@ CLIを通じて、AIエージェントがUnityプロジェクトのコンパイ AI駆動の開発ループを既存のUnityプロジェクト内で自律的に回し続けるために設計されています。 -> **Note**: このプロジェクトの旧名称は **uLoopMCP** です。 - # コンセプト Unity CLI Loopは、「AIがUnityプロジェクトの実装をできるだけ人手を介さずに進められる」ことを目指して作られた Unity連携ツールです。 人間が手で行っていたコンパイル、Test Runner の実行、ログ確認、シーン編集、画面キャプチャによるUIレイアウト確認などの作業を、LLM ツールからまとめて操作できるようにします。 @@ -53,13 +51,6 @@ https://github.com/user-attachments/assets/569a2110-7351-4cf3-8281-3a83fe181817 https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src ``` -> **v1.0.0以前にgit URL でインストールされていた方へ**: v1.0.0でリポジトリ名が `uLoopMCP` から `unity-cli-loop` に変更されました。`manifest.json` の URL を更新してください: -> ```text -> 旧: https://github.com/hatayama/uLoopMCP.git?path=/Packages/src -> 新: https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src -> ``` -> 旧URLはGitHubのリダイレクトにより引き続き動作しますが、更新を推奨します。OpenUPMユーザーは影響ありません。 - ## OpenUPM経由(推奨) ## Unity Package ManagerでScoped registryを使用 diff --git a/SECURITY.md b/SECURITY.md index 12fbbc054..1b3abe8ba 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ We take security vulnerabilities seriously. If you discover a security issue, please follow these steps: 1. **DO NOT** create a public GitHub issue for security vulnerabilities -2. Use GitHub's [Private Vulnerability Reporting](https://github.com/hatayama/uLoopMCP/security/advisories/new) feature +2. Use GitHub's [Private Vulnerability Reporting](https://github.com/hatayama/unity-cli-loop/security/advisories/new) feature 3. Or contact the maintainer directly ### What to Include @@ -56,7 +56,7 @@ The following are considered in scope for security reports: ## Security Best Practices for Users -When using uLoopMCP, we recommend: +When using Unity CLI Loop, we recommend: 1. **Use Restricted Mode**: Set Dynamic Code Security Level to "Restricted" (Level 1) unless you specifically need full access 2. **Review Third-Party Tools**: Only enable "Allow Third-Party Tools" for trusted extensions diff --git a/docs/architecture/execute-dynamic-code-rebuild.md b/docs/architecture/execute-dynamic-code-rebuild.md index c541cc889..6e1501faa 100644 --- a/docs/architecture/execute-dynamic-code-rebuild.md +++ b/docs/architecture/execute-dynamic-code-rebuild.md @@ -213,7 +213,7 @@ flowchart TD ## Class responsibilities - `ExecuteDynamicCodeTool` - - Thin entry point for the MCP/CLI tool. + - Thin entry point for the CLI tool. - Delegates the full workflow to `IExecuteDynamicCodeUseCase`. - `ExecuteDynamicCodeUseCase` diff --git a/docs/architecture/execute-dynamic-code-windows-handoff.md b/docs/architecture/execute-dynamic-code-windows-handoff.md index 84e825779..06be4791e 100644 --- a/docs/architecture/execute-dynamic-code-windows-handoff.md +++ b/docs/architecture/execute-dynamic-code-windows-handoff.md @@ -100,7 +100,7 @@ Relevant files: Run these in order on Windows: 1. `uloop compile` -2. `uloop run-tests --test-mode EditMode --filter-type regex --filter-value 'io.github.hatayama.uLoopMCP.DynamicCodeToolTests'` +2. `uloop run-tests --test-mode EditMode --filter-type regex --filter-value 'io.github.hatayama.UnityCliLoop.DynamicCodeToolTests'` 3. `uloop execute-dynamic-code --code "return 1 + 2;"` 4. `uloop execute-dynamic-code --code "StringBuilder sb = new StringBuilder(); sb.Append(\"ok\"); return sb.ToString();"` 5. `uloop execute-dynamic-code --code "UnityEngine.GameObject go = new UnityEngine.GameObject(\"bench\"); UnityEngine.Object.DestroyImmediate(go); return \"ok\";"` @@ -169,7 +169,7 @@ Goal: Suggested validation commands: 1. uloop compile -2. uloop run-tests --test-mode EditMode --filter-type regex --filter-value 'io.github.hatayama.uLoopMCP.DynamicCodeToolTests' +2. uloop run-tests --test-mode EditMode --filter-type regex --filter-value 'io.github.hatayama.UnityCliLoop.DynamicCodeToolTests' 3. uloop execute-dynamic-code --code "return 1 + 2;" 4. uloop execute-dynamic-code --code "StringBuilder builder1002 = new StringBuilder(); builder1002.Append(\"ok-1002\"); return builder1002.ToString();" 5. uloop execute-dynamic-code --code "DynamicAssemblyTest test1004 = new DynamicAssemblyTest(); return test1004.HelloWorld();"