From 22e6e9d26ea4b9f78a78602885e4758d93b7f0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Voigt?= Date: Tue, 10 Feb 2026 10:06:05 +0100 Subject: [PATCH 1/9] initial mcp app setup --- .github/agents/generate.agent.md | 125 + .github/agents/implement.agent.md | 19 + .github/agents/plan.agent.md | 81 + .vscode/mcp.json | 10 + AGENTS.md | 4 + Editor/Tools/GetPlayModeStatusTool.cs | 49 + Editor/Tools/GetPlayModeStatusTool.cs.meta | 2 + Editor/UnityBridge/McpUnityServer.cs | 4 + README.md | 12 + Server~/package-lock.json | 3003 ++++++++++++++++- Server~/package.json | 4 +- Server~/scripts/copy-ui.mjs | 20 + Server~/src/index.ts | 8 + .../resources/unityDashboardAppResource.ts | 107 + Server~/src/tools/getConsoleLogsTool.ts | 11 +- Server~/src/tools/getPlayModeStatusTool.ts | 63 + Server~/src/tools/getScenesHierarchyTool.ts | 59 + Server~/src/tools/showUnityDashboardTool.ts | 57 + Server~/src/ui/unity-dashboard.html | 261 ++ plans.meta | 8 + plans/unity-dashboard-mcp-app.meta | 8 + .../unity-dashboard-mcp-app/implementation.md | 2800 +++++++++++++++ .../implementation.md.meta | 7 + plans/unity-dashboard-mcp-app/plan.md | 135 + plans/unity-dashboard-mcp-app/plan.md.meta | 7 + plans/unity-dashboard-mcp-app/prompt.md | 141 + plans/unity-dashboard-mcp-app/prompt.md.meta | 7 + plans/unity-dashboard-mcp-app/usage.md | 23 + plans/unity-dashboard-mcp-app/usage.md.meta | 7 + 29 files changed, 6937 insertions(+), 105 deletions(-) create mode 100644 .github/agents/generate.agent.md create mode 100644 .github/agents/implement.agent.md create mode 100644 .github/agents/plan.agent.md create mode 100644 .vscode/mcp.json create mode 100644 Editor/Tools/GetPlayModeStatusTool.cs create mode 100644 Editor/Tools/GetPlayModeStatusTool.cs.meta create mode 100644 Server~/scripts/copy-ui.mjs create mode 100644 Server~/src/resources/unityDashboardAppResource.ts create mode 100644 Server~/src/tools/getPlayModeStatusTool.ts create mode 100644 Server~/src/tools/getScenesHierarchyTool.ts create mode 100644 Server~/src/tools/showUnityDashboardTool.ts create mode 100644 Server~/src/ui/unity-dashboard.html create mode 100644 plans.meta create mode 100644 plans/unity-dashboard-mcp-app.meta create mode 100644 plans/unity-dashboard-mcp-app/implementation.md create mode 100644 plans/unity-dashboard-mcp-app/implementation.md.meta create mode 100644 plans/unity-dashboard-mcp-app/plan.md create mode 100644 plans/unity-dashboard-mcp-app/plan.md.meta create mode 100644 plans/unity-dashboard-mcp-app/prompt.md create mode 100644 plans/unity-dashboard-mcp-app/prompt.md.meta create mode 100644 plans/unity-dashboard-mcp-app/usage.md create mode 100644 plans/unity-dashboard-mcp-app/usage.md.meta diff --git a/.github/agents/generate.agent.md b/.github/agents/generate.agent.md new file mode 100644 index 00000000..b524541c --- /dev/null +++ b/.github/agents/generate.agent.md @@ -0,0 +1,125 @@ +--- +description: Structured Autonomy Implementation Generator Prompt +model: GPT-5.2-Codex (copilot) +--- + +You are a PR implementation plan generator that creates complete, copy-paste ready implementation documentation. + +Your SOLE responsibility is to: +1. Accept a complete PR plan (plan.md in plans/{feature-name}/) +2. Extract all implementation steps from the plan +3. Generate comprehensive step documentation with complete code +4. Save plan to: `plans/{feature-name}/implementation.md` + +Follow the below to generate and save implementation files for each step in the plan. + + + +## Step 1: Parse Plan & Research Codebase + +1. Read the plan.md file to extract: + - Feature name and branch (determines root folder: `plans/{feature-name}/`) + - Implementation steps (numbered 1, 2, 3, etc.) + - Files affected by each step +2. Run comprehensive research ONE TIME using . Use `runSubagent` to execute. Do NOT pause. +3. Once research returns, proceed to Step 2 (file generation). + +## Step 2: Generate Implementation File + +Output the plan as a COMPLETE markdown document using the , ready to be saved as a `.md` file. + +The plan MUST include: +- Complete, copy-paste ready code blocks with ZERO modifications needed +- Exact file paths appropriate to the project structure +- Markdown checkboxes for EVERY action item +- Specific, observable, testable verification points +- NO ambiguity - every instruction is concrete +- NO "decide for yourself" moments - all decisions made based on research +- Technology stack and dependencies explicitly stated +- Build/test commands specific to the project type + + + + +For the entire project described in the master plan, research and gather: + +1. **Project-Wide Analysis:** + - Project type, technology stack, versions + - Project structure and folder organization + - Coding conventions and naming patterns + - Build/test/run commands + - Dependency management approach + +2. **Code Patterns Library:** + - Collect all existing code patterns + - Document error handling patterns + - Record logging/debugging approaches + - Identify utility/helper patterns + - Note configuration approaches + +3. **Architecture Documentation:** + - How components interact + - Data flow patterns + - API conventions + - State management (if applicable) + - Testing strategies + +4. **Official Documentation:** + - Fetch official docs for all major libraries/frameworks + - Document APIs, syntax, parameters + - Note version-specific details + - Record known limitations and gotchas + - Identify permission/capability requirements + +Return a comprehensive research package covering the entire project context. + + + +# {FEATURE_NAME} + +## Goal +{One sentence describing exactly what this implementation accomplishes} + +## Prerequisites +Make sure that the use is currently on the `{feature-name}` branch before beginning implementation. +If not, move them to the correct branch. If the branch does not exist, create it from main. + +### Step-by-Step Instructions + +#### Step 1: {Action} +- [ ] {Specific instruction 1} +- [ ] Copy and paste code below into `{file}`: + +```{language} +{COMPLETE, TESTED CODE - NO PLACEHOLDERS - NO "TODO" COMMENTS} +``` + +- [ ] {Specific instruction 2} +- [ ] Copy and paste code below into `{file}`: + +```{language} +{COMPLETE, TESTED CODE - NO PLACEHOLDERS - NO "TODO" COMMENTS} +``` + +##### Step 1 Verification Checklist +- [ ] No build errors +- [ ] Specific instructions for UI verification (if applicable) + +#### Step 1 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. + +#### Step 2: {Action} +- [ ] {Specific Instruction 1} +- [ ] Copy and paste code below into `{file}`: + +```{language} +{COMPLETE, TESTED CODE - NO PLACEHOLDERS - NO "TODO" COMMENTS} +``` + +##### Step 2 Verification Checklist +- [ ] No build errors +- [ ] Specific instructions for UI verification (if applicable) + +#### Step 2 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. + \ No newline at end of file diff --git a/.github/agents/implement.agent.md b/.github/agents/implement.agent.md new file mode 100644 index 00000000..82381052 --- /dev/null +++ b/.github/agents/implement.agent.md @@ -0,0 +1,19 @@ +--- +description: 'Structured Autonomy Implementation Prompt' +model: GPT-5 mini (copilot) +--- + +You are an implementation agent responsible for carrying out the implementation plan without deviating from it. + +Only make the changes explicitly specified in the plan. If the user has not passed the plan as an input, respond with: "Implementation plan is required." + +Follow the workflow below to ensure accurate and focused implementation. + + +- Follow the plan exactly as it is written, picking up with the next unchecked step in the implementation plan document. You MUST NOT skip any steps. +- Implement ONLY what is specified in the implementation plan. DO NOT WRITE ANY CODE OUTSIDE OF WHAT IS SPECIFIED IN THE PLAN. +- Update the plan document inline as you complete each item in the current Step, checking off items using standard markdown syntax. +- Complete every item in the current Step. +- Check your work by running the build or test commands specified in the plan. +- STOP when you reach the STOP instructions in the plan and return control to the user. + \ No newline at end of file diff --git a/.github/agents/plan.agent.md b/.github/agents/plan.agent.md new file mode 100644 index 00000000..a21e9d55 --- /dev/null +++ b/.github/agents/plan.agent.md @@ -0,0 +1,81 @@ +--- +description: Structured Autonomy Planning Prompt +model: Claude Sonnet 4.5 (copilot) +--- + +You are a Project Planning Agent that collaborates with users to design development plans. + +A development plan defines a clear path to implement the user's request. During this step you will **not write any code**. Instead, you will research, analyze, and outline a plan. + +Assume that this entire plan will be implemented in a single pull request (PR) on a dedicated branch. Your job is to define the plan in steps that correspond to individual commits within that PR. + + + +## Step 1: Research and Gather Context + +MANDATORY: Run #tool:agent tool instructing the agent to work autonomously following to gather context. Return all findings. + +DO NOT do any other tool calls after #tool:agent returns! + +If #tool:agent is unavailable, execute via tools yourself. + +## Step 2: Determine Commits + +Analyze the user's request and break it down into commits: + +- For **SIMPLE** features, consolidate into 1 commit with all changes. +- For **COMPLEX** features, break into multiple commits, each representing a testable step toward the final goal. + +## Step 3: Plan Generation + +1. Generate draft plan using with `[NEEDS CLARIFICATION]` markers where the user's input is needed. +2. Save the plan to "plans/{feature-name}/plan.md" +4. Ask clarifying questions for any `[NEEDS CLARIFICATION]` sections +5. MANDATORY: Pause for feedback +6. If feedback received, revise plan and go back to Step 1 for any research needed + + + + +**File:** `plans/{feature-name}/plan.md` + +```markdown +# {Feature Name} + +**Branch:** `{kebab-case-branch-name}` +**Description:** {One sentence describing what gets accomplished} + +## Goal +{1-2 sentences describing the feature and why it matters} + +## Implementation Steps + +### Step 1: {Step Name} [SIMPLE features have only this step] +**Files:** {List affected files: Service/HotKeyManager.cs, Models/PresetSize.cs, etc.} +**What:** {1-2 sentences describing the change} +**Testing:** {How to verify this step works} + +### Step 2: {Step Name} [COMPLEX features continue] +**Files:** {affected files} +**What:** {description} +**Testing:** {verification method} + +### Step 3: {Step Name} +... +``` + + + + +Research the user's feature request comprehensively: + +1. **Code Context:** Semantic search for related features, existing patterns, affected services +2. **Documentation:** Read existing feature documentation, architecture decisions in codebase +3. **Dependencies:** Research any external APIs, libraries, or Windows APIs needed. Use #context7 if available to read relevant documentation. ALWAYS READ THE DOCUMENTATION FIRST. +4. **Patterns:** Identify how similar features are implemented in ResizeMe + +Use official documentation and reputable sources. If uncertain about patterns, research before proposing. + +Stop research at 80% confidence you can break down the feature into testable phases. + + \ No newline at end of file diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 00000000..e9ceae03 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,10 @@ +{ + "servers": { + "mcp-unity": { + "command": "node", + "args": [ + "C:/Projects/private/mcp-unity/Server~/build/index.js" + ] + } + } +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index c1bca0ff..b03f7310 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,6 +146,9 @@ Node reads config from `../ProjectSettings/McpUnitySettings.json` relative to ** - `modify_material` — Modify material properties (colors, floats, textures) - `get_material_info` — Get material details including all properties +### Available apps (current) +- `show_unity_dashboard` — Open the Unity dashboard MCP App in VS Code + ### Available resources (current) - `unity://menu-items` — List of available menu items - `unity://scenes-hierarchy` — Current scene hierarchy @@ -154,6 +157,7 @@ Node reads config from `../ProjectSettings/McpUnitySettings.json` relative to ** - `unity://packages` — Installed and available packages - `unity://assets` — Asset database information - `unity://tests/{testMode}` — Test Runner test information +- `unity://ui/dashboard` — Unity dashboard MCP App UI ### Update policy (for agents) - Update this file when: diff --git a/Editor/Tools/GetPlayModeStatusTool.cs b/Editor/Tools/GetPlayModeStatusTool.cs new file mode 100644 index 00000000..7010389e --- /dev/null +++ b/Editor/Tools/GetPlayModeStatusTool.cs @@ -0,0 +1,49 @@ +using System; +using Newtonsoft.Json.Linq; +using UnityEditor; +using McpUnity.Unity; +using McpUnity.Utils; + +namespace McpUnity.Tools +{ + /// + /// Tool for getting Unity play mode status + /// + public class GetPlayModeStatusTool : McpToolBase + { + public GetPlayModeStatusTool() + { + Name = "get_play_mode_status"; + Description = "Gets Unity play mode status (isPlaying, isPaused)."; + } + + public override JObject Execute(JObject parameters) + { + try + { + bool isPlaying = EditorApplication.isPlaying; + bool isPaused = EditorApplication.isPaused; + + var result = new JObject + { + ["success"] = true, + ["type"] = "text", + ["message"] = isPlaying ? (isPaused ? "Play mode (paused)" : "Play mode") : "Edit mode", + ["isPlaying"] = isPlaying, + ["isPaused"] = isPaused + }; + + McpLogger.LogInfo($"Play mode status requested: isPlaying={isPlaying}, isPaused={isPaused}"); + + return result; + } + catch (Exception ex) + { + return McpUnitySocketHandler.CreateErrorResponse( + $"Error getting play mode status: {ex.Message}", + "play_mode_error" + ); + } + } + } +} diff --git a/Editor/Tools/GetPlayModeStatusTool.cs.meta b/Editor/Tools/GetPlayModeStatusTool.cs.meta new file mode 100644 index 00000000..631b4b58 --- /dev/null +++ b/Editor/Tools/GetPlayModeStatusTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dffebe6143c5cbb4bae56ce6088a4b91 \ No newline at end of file diff --git a/Editor/UnityBridge/McpUnityServer.cs b/Editor/UnityBridge/McpUnityServer.cs index d9849174..f5558e1a 100644 --- a/Editor/UnityBridge/McpUnityServer.cs +++ b/Editor/UnityBridge/McpUnityServer.cs @@ -361,6 +361,10 @@ private void RegisterTools() GetSceneInfoTool getSceneInfoTool = new GetSceneInfoTool(); _tools.Add(getSceneInfoTool.Name, getSceneInfoTool); + // Register GetPlayModeStatusTool + GetPlayModeStatusTool getPlayModeStatusTool = new GetPlayModeStatusTool(); + _tools.Add(getPlayModeStatusTool.Name, getPlayModeStatusTool); + // Register UnloadSceneTool UnloadSceneTool unloadSceneTool = new UnloadSceneTool(); _tools.Add(unloadSceneTool.Name, unloadSceneTool); diff --git a/README.md b/README.md index 635642f4..5d4e4315 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,15 @@ The following tools are available for manipulating and querying Unity scenes and - `batch_execute`: Executes multiple tool operations in a single batch request, reducing round-trips and enabling atomic operations with optional rollback on failure > **Example prompt:** "Create 10 empty GameObjects named Enemy_1 through Enemy_10 in a single batch operation" +### MCP App tools + +- `show_unity_dashboard`: Opens the Unity dashboard MCP App in VS Code (requires VS Code 1.109+) + > **Example prompt:** "Open the Unity dashboard app" + +- `get_play_mode_status`: Gets Unity play mode status (isPlaying, isPaused) + > **Example prompt:** "Is Unity in play mode?" + + ### MCP Server Resources - `unity://menu-items`: Retrieves a list of all available menu items in the Unity Editor to facilitate `execute_menu_item` tool @@ -170,6 +179,9 @@ The following tools are available for manipulating and querying Unity scenes and - `unity://tests/{testMode}`: Retrieves information about tests in the Unity Test Runner > **Example prompt:** "List all available tests in my Unity project" +- `unity://ui/dashboard`: Unity dashboard MCP App UI + > **Example prompt:** "Open the Unity dashboard app" + ## Requirements - Unity 6 or later - to [install the server](#install-server) - Node.js 18 or later - to [start the server](#start-server) diff --git a/Server~/package-lock.json b/Server~/package-lock.json index 924b7804..f915c48f 100644 --- a/Server~/package-lock.json +++ b/Server~/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@modelcontextprotocol/ext-apps": "^1.0.0", "@modelcontextprotocol/sdk": "^1.7.0", "axios": "^1.8.4", "cors": "^2.8.5", @@ -23,6 +24,7 @@ "mcp-unity-server": "build/index.js" }, "devDependencies": { + "@modelcontextprotocol/inspector": "^0.20.0", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", @@ -66,6 +68,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -531,6 +534,84 @@ "dev": true, "license": "MIT" }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -882,45 +963,1643 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mcp-ui/client": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@mcp-ui/client/-/client-6.0.0.tgz", + "integrity": "sha512-dHIQGjFOoBWBntSRUJH5YFeq7xi2rEPS0EwokeNAnMg6xrjGjvNd6vTWDHFRC04OlO/ogvM1r5+xUoo0OETaaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/ext-apps": "^0.3.1", + "@modelcontextprotocol/sdk": "^1.24.0", + "@quilted/threads": "^3.1.3", + "@r2wc/react-to-web-component": "^2.0.4", + "@remote-dom/core": "^1.8.0", + "@remote-dom/react": "^1.2.2", + "zod": "^3.23.8" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "node_modules/@mcp-ui/client/node_modules/@modelcontextprotocol/ext-apps": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-0.3.1.tgz", + "integrity": "sha512-Iivz2KwWK8xlRbiWwFB/C4NXqE8VJBoRCbBkJCN98ST2UbQvA6kfyebcLsypiqylJS467XOOaBcI9DeQ3t+zqA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "^1.2.21", + "@oven/bun-darwin-x64": "^1.2.21", + "@oven/bun-darwin-x64-baseline": "^1.2.21", + "@oven/bun-linux-aarch64": "^1.2.21", + "@oven/bun-linux-aarch64-musl": "^1.2.21", + "@oven/bun-linux-x64": "^1.2.21", + "@oven/bun-linux-x64-baseline": "^1.2.21", + "@oven/bun-linux-x64-musl": "^1.2.21", + "@oven/bun-linux-x64-musl-baseline": "^1.2.21", + "@oven/bun-windows-x64": "^1.2.21", + "@oven/bun-windows-x64-baseline": "^1.2.21", + "@rollup/rollup-darwin-arm64": "^4.53.3", + "@rollup/rollup-darwin-x64": "^4.53.3", + "@rollup/rollup-linux-arm64-gnu": "^4.53.3", + "@rollup/rollup-linux-x64-gnu": "^4.53.3", + "@rollup/rollup-win32-arm64-msvc": "^4.53.3", + "@rollup/rollup-win32-x64-msvc": "^4.53.3" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.24.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.0.1.tgz", + "integrity": "sha512-rAPzBbB5GNgYk216paQjGKUgbNXSy/yeR95c0ni6Y4uvhWI2AeF+ztEOqQFLBMQy/MPM+02pbVK1HaQmQjMwYQ==", + "hasInstallScript": true, + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "^1.2.21", + "@oven/bun-darwin-x64": "^1.2.21", + "@oven/bun-darwin-x64-baseline": "^1.2.21", + "@oven/bun-linux-aarch64": "^1.2.21", + "@oven/bun-linux-aarch64-musl": "^1.2.21", + "@oven/bun-linux-x64": "^1.2.21", + "@oven/bun-linux-x64-baseline": "^1.2.21", + "@oven/bun-linux-x64-musl": "^1.2.21", + "@oven/bun-linux-x64-musl-baseline": "^1.2.21", + "@oven/bun-windows-x64": "^1.2.21", + "@oven/bun-windows-x64-baseline": "^1.2.21", + "@rollup/rollup-darwin-arm64": "^4.53.3", + "@rollup/rollup-darwin-x64": "^4.53.3", + "@rollup/rollup-linux-arm64-gnu": "^4.53.3", + "@rollup/rollup-linux-x64-gnu": "^4.53.3", + "@rollup/rollup-win32-arm64-msvc": "^4.53.3", + "@rollup/rollup-win32-x64-msvc": "^4.53.3" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.24.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/inspector": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector/-/inspector-0.20.0.tgz", + "integrity": "sha512-GlCBFK1Vqe5xiLT5LfUCJb/yfHrCMXmO4rKOfsZ/jDcWYkwIZyUwPekK7aS8BsbscpcyNSSL2XV7qBypDTR6bw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "workspaces": [ + "client", + "server", + "cli" + ], + "dependencies": { + "@modelcontextprotocol/inspector-cli": "^0.20.0", + "@modelcontextprotocol/inspector-client": "^0.20.0", + "@modelcontextprotocol/inspector-server": "^0.20.0", + "@modelcontextprotocol/sdk": "^1.25.2", + "concurrently": "^9.2.0", + "node-fetch": "^3.3.2", + "open": "^10.2.0", + "shell-quote": "^1.8.3", + "spawn-rx": "^5.1.2", + "ts-node": "^10.9.2", + "zod": "^3.25.76" + }, + "bin": { + "mcp-inspector": "cli/build/cli.js" + }, + "engines": { + "node": ">=22.7.5" + } + }, + "node_modules/@modelcontextprotocol/inspector-cli": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector-cli/-/inspector-cli-0.20.0.tgz", + "integrity": "sha512-T5lbD4tPOPZHCElnPW26eZEnHapsCMEhRdiis/9RHjDdgZfuXk1lee1iPc4llt0UmymG3wrFBs5Ywyal8nFZ2Q==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.2", + "commander": "^13.1.0", + "express": "^5.2.1", + "spawn-rx": "^5.1.2" + }, + "bin": { + "mcp-inspector-cli": "build/cli.js" + } + }, + "node_modules/@modelcontextprotocol/inspector-client": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector-client/-/inspector-client-0.20.0.tgz", + "integrity": "sha512-dxV9dj5SiIV0aoWNu/WscDXGsAHK9wJ9CtQZHDwy8dfhJQ4FKBvKxpEstt5K7KXawMV/sHRm4AGvm92h5FbOtw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@mcp-ui/client": "^6.0.0", + "@modelcontextprotocol/ext-apps": "^1.0.0", + "@modelcontextprotocol/sdk": "^1.25.2", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.3", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.3", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.6", + "@radix-ui/react-tooltip": "^1.1.8", + "ajv": "^6.12.6", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.4", + "lucide-react": "^0.523.0", + "pkce-challenge": "^4.1.0", + "prismjs": "^1.30.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-simple-code-editor": "^0.14.1", + "serve-handler": "^6.1.6", + "tailwind-merge": "^2.5.3", + "zod": "^3.25.76" + }, + "bin": { + "mcp-inspector-client": "bin/start.js" + } + }, + "node_modules/@modelcontextprotocol/inspector-server": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector-server/-/inspector-server-0.20.0.tgz", + "integrity": "sha512-ZQnR3axv2s7qJfbo6wEvkD0ctveugJqgH1vBjAb5FdgJb5CIPkLOQkK5qS3lqxM5g0L/OO4ep82eFvnCz7qSSw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.2", + "cors": "^2.8.5", + "express": "^5.1.0", + "express-rate-limit": "^8.2.1", + "shell-quote": "^1.8.3", + "shx": "^0.3.4", + "spawn-rx": "^5.1.2", + "ws": "^8.18.0", + "zod": "^3.25.76" + }, + "bin": { + "mcp-inspector-server": "build/index.js" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.9.tgz", + "integrity": "sha512-df7smckMWSUfaT5mzwN9Lfpd3ZGkOqo+vmQ8VV2a32gl14v6uZ/qeeo+1RlANXn8M0uzXPWWCkrKZIWSZUR0qw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.3.9.tgz", + "integrity": "sha512-YiLxfsPzQqaVvT2a+nxH9do0YfUjrlxF3tKP0b1DDgvfgCcVKGsrQH3Wa82qHgL4dnT8h2bqi94JxXESEuPmcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.3.9.tgz", + "integrity": "sha512-XbhsA2XAFzvFr0vPSV6SNqGxab4xHKdPmVTLqoSHAx9tffrSq/012BDptOskulwnD+YNsrJUx2D2Ve1xvfgGcg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.3.9.tgz", + "integrity": "sha512-VaNQTu0Up4gnwZLQ6/Hmho6jAlLxTQ1PwxEth8EsXHf82FOXXPV5OCQ6KC9mmmocjKlmWFaIGebThrOy8DUo4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-aarch64-musl": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.9.tgz", + "integrity": "sha512-t8uimCVBTw5f9K2QTZE5wN6UOrFETNrh/Xr7qtXT9nAOzaOnIFvYA+HcHbGfi31fRlCVfTxqm/EiCwJ1gEw9YQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.3.9.tgz", + "integrity": "sha512-oQyAW3+ugulvXTZ+XYeUMmNPR94sJeMokfHQoKwPvVwhVkgRuMhcLGV2ZesHCADVu30Oz2MFXbgdC8x4/o9dRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-baseline": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.3.9.tgz", + "integrity": "sha512-nZ12g22cy7pEOBwAxz2tp0wVqekaCn9QRKuGTHqOdLlyAqR4SCdErDvDhUWd51bIyHTQoCmj72TegGTgG0WNPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-musl": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.3.9.tgz", + "integrity": "sha512-4ZjIUgCxEyKwcKXideB5sX0KJpnHTZtu778w73VNq2uNH2fNpMZv98+DBgJyQ9OfFoRhmKn1bmLmSefvnHzI9w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-musl-baseline": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.3.9.tgz", + "integrity": "sha512-3FXQgtYFsT0YOmAdMcJn56pLM5kzSl6y942rJJIl5l2KummB9Ea3J/vMJMzQk7NCAGhleZGWU/pJSS/uXKGa7w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-windows-x64": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.3.9.tgz", + "integrity": "sha512-/d6vAmgKvkoYlsGPsRPlPmOK1slPis/F40UG02pYwypTH0wmY0smgzdFqR4YmryxFh17XrW1kITv+U99Oajk9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oven/bun-windows-x64-baseline": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.3.9.tgz", + "integrity": "sha512-a/+hSrrDpMD7THyXvE2KJy1skxzAD0cnW4K1WjuI/91VqsphjNzvf5t/ZgxEVL4wb6f+hKrSJ5J3aH47zPr61g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@preact/signals-core": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.13.0.tgz", + "integrity": "sha512-slT6XeTCAbdql61GVLlGU4x7XHI7kCZV5Um5uhE4zLX4ApgiiXc0UYFvVOKq06xcovzp7p+61l68oPi563ARKg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@quilted/events": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@quilted/events/-/events-2.1.3.tgz", + "integrity": "sha512-4fHaSLND8rmZ+tce9/4FNmG5UWTRpFtM54kOekf3tLON4ZLLnYzjjldELD35efd7+lT5+E3cdkacqc56d+kCrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@quilted/threads": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@quilted/threads/-/threads-3.3.1.tgz", + "integrity": "sha512-0ASnjTH+hOu1Qwzi9NnsVcsbMhWVx8pEE8SXIHknqcc/1rXAU0QlKw9ARq0W43FAdzyVeuXeXtZN27ZC0iALKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quilted/events": "^2.1.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@preact/signals-core": "^1.8.0" + }, + "peerDependenciesMeta": { + "@preact/signals-core": { + "optional": true + } + } + }, + "node_modules/@r2wc/core": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@r2wc/core/-/core-1.3.0.tgz", + "integrity": "sha512-aPBnND92Itl+SWWbWyyxdFFF0+RqKB6dptGHEdiPB8ZvnHWHlVzOfEvbEcyUYGtB6HBdsfkVuBiaGYyBFVTzVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@r2wc/react-to-web-component": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@r2wc/react-to-web-component/-/react-to-web-component-2.1.0.tgz", + "integrity": "sha512-m/PzgUOEiL1HxmvfP5LgBLqB7sHeRj+d1QAeZklwS4OEI2HUU+xTpT3hhJipH5DQoFInDqDTfe0lNFFKcrqk4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@r2wc/core": "^1.3.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@remote-dom/core": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@remote-dom/core/-/core-1.10.1.tgz", + "integrity": "sha512-MlbUOGuHjOrB7uOkaYkIoLUG8lDK8/H1D7MHnGkgqbG6jwjwQSlGPHhbwnD6HYWsTGpAPOP02Byd8wBt9U6TEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@remote-dom/polyfill": "^1.5.1", + "htm": "^3.1.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@preact/signals-core": "^1.3.0" + }, + "peerDependenciesMeta": { + "@preact/signals-core": { + "optional": true + }, + "preact": { + "optional": true + } + } + }, + "node_modules/@remote-dom/polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@remote-dom/polyfill/-/polyfill-1.5.1.tgz", + "integrity": "sha512-eaWdIVKZpNfbqspKkRQLVxiFv/7vIw8u0FVA5oy52YANFbO/WVT0GU+PQmRt/QUSijaB36HBAqx7stjo8HGpVQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", - "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", + "node_modules/@remote-dom/react": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@remote-dom/react/-/react-1.2.2.tgz", + "integrity": "sha512-PkvioODONTr1M0StGDYsR4Ssf5M0Rd4+IlWVvVoK3Zrw8nr7+5mJkgNofaj/z7i8Aep78L28PCW8/WduUt4unA==", + "dev": true, "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^4.1.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "@remote-dom/core": "^1.7.0", + "@types/react": "^18.0.0", + "htm": "^3.1.1" }, "engines": { - "node": ">=18" + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } } }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -948,6 +2627,34 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1121,6 +2828,13 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.9.18", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", @@ -1135,6 +2849,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -1219,6 +2944,88 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1275,6 +3082,13 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1285,6 +3099,19 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1436,23 +3263,27 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/brace-expansion": { @@ -1499,6 +3330,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1543,6 +3375,22 @@ "dev": true, "license": "MIT" }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1672,6 +3520,19 @@ "dev": true, "license": "MIT" }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1687,6 +3548,33 @@ "node": ">=12" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -1737,6 +3625,16 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1744,6 +3642,47 @@ "dev": true, "license": "MIT" }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -1825,6 +3764,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1839,10 +3785,27 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1871,14 +3834,57 @@ } } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/delayed-stream": { @@ -1909,6 +3915,23 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -2151,18 +4174,20 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -2193,10 +4218,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -2204,9 +4232,15 @@ "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" + "express": ">= 4.11" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2214,6 +4248,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -2224,6 +4274,30 @@ "bser": "2.1.1" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2324,6 +4398,19 @@ "node": ">= 0.6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2417,6 +4504,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -2565,6 +4662,23 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2573,19 +4687,23 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/human-signals": { @@ -2599,15 +4717,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/import-local": { @@ -2658,6 +4780,25 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2690,6 +4831,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2710,6 +4867,25 @@ "node": ">=6" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2739,6 +4915,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2835,6 +5027,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -3425,11 +5618,20 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -3466,6 +5668,19 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3526,6 +5741,19 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3536,6 +5764,16 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.523.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.523.0.tgz", + "integrity": "sha512-rUjQoy7egZT9XYVXBK1je9ckBnNp7qzRZOhLQx5RcEp2dCGlXo+mv6vf7Am4LimEcFBJIIZzSGfgTqc9QCrPSw==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -3716,6 +5954,46 @@ "dev": true, "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -3811,6 +6089,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3914,6 +6211,13 @@ "node": ">=0.10.0" } }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3973,6 +6277,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.20.0" @@ -4019,6 +6324,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -4052,6 +6367,16 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -4069,51 +6394,175 @@ ], "license": "MIT" }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "dev": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.1.0" + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" }, "engines": { - "node": ">=0.6" + "node": ">=10" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "dev": true, "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "node_modules/react-simple-code-editor": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.14.1.tgz", + "integrity": "sha512-BR5DtNRy+AswWJECyA17qhUDvrrCZ6zXOCfkQY5zSmb96BVUbpVAv03WpcjcwtCwiLbIANx3gebHOcXYn1EHow==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", "dev": true, - "license": "MIT" + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } }, "node_modules/require-directory": { "version": "2.1.1", @@ -4125,6 +6574,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -4195,6 +6653,29 @@ "node": ">= 18" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4221,6 +6702,16 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4253,6 +6744,82 @@ "node": ">= 18" } }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-handler/node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-handler/node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", @@ -4295,6 +6862,54 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -4412,6 +7027,17 @@ "source-map": "^0.6.0" } }, + "node_modules/spawn-rx": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.2.tgz", + "integrity": "sha512-/y7tJKALVZ1lPzeZZB9jYnmtrL7d0N2zkorii5a7r7dhHkWIuLTzZpZzMJLK1dmYRgX/NCc4iarTO3F7BS2c/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7", + "rxjs": "^7.8.1" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4433,9 +7059,9 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -4542,6 +7168,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -4586,6 +7223,16 @@ "node": ">=0.6" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-jest": { "version": "29.4.6", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", @@ -4665,6 +7312,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -4777,6 +7475,61 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -4790,6 +7543,13 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -4824,6 +7584,16 @@ "makeerror": "1.0.12" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4911,6 +7681,22 @@ } } }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4957,6 +7743,16 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -4971,21 +7767,22 @@ } }, "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25 || ^4" } } } diff --git a/Server~/package.json b/Server~/package.json index 3d81c894..fc6a504e 100644 --- a/Server~/package.json +++ b/Server~/package.json @@ -12,7 +12,7 @@ "build" ], "scripts": { - "build": "tsc", + "build": "tsc && node scripts/copy-ui.mjs", "start": "node build/index.js", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js", @@ -29,6 +29,7 @@ "author": "CoderGamester", "license": "MIT", "devDependencies": { + "@modelcontextprotocol/inspector": "^0.20.0", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", @@ -41,6 +42,7 @@ "typescript": "^5.8.2" }, "dependencies": { + "@modelcontextprotocol/ext-apps": "^1.0.0", "@modelcontextprotocol/sdk": "^1.7.0", "axios": "^1.8.4", "cors": "^2.8.5", diff --git a/Server~/scripts/copy-ui.mjs b/Server~/scripts/copy-ui.mjs new file mode 100644 index 00000000..c85754d3 --- /dev/null +++ b/Server~/scripts/copy-ui.mjs @@ -0,0 +1,20 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const serverRoot = path.resolve(here, '..'); + +const srcHtml = path.join(serverRoot, 'src', 'ui', 'unity-dashboard.html'); +const outDir = path.join(serverRoot, 'build', 'ui'); +const outHtml = path.join(outDir, 'unity-dashboard.html'); + +if (!fs.existsSync(srcHtml)) { + console.error(`UI source file not found: ${srcHtml}`); + process.exit(1); +} + +fs.mkdirSync(outDir, { recursive: true }); +fs.copyFileSync(srcHtml, outHtml); + +console.log(`Copied UI: ${srcHtml} -> ${outHtml}`); diff --git a/Server~/src/index.ts b/Server~/src/index.ts index 80af160c..f162155d 100644 --- a/Server~/src/index.ts +++ b/Server~/src/index.ts @@ -18,6 +18,7 @@ import { registerDeleteSceneTool } from './tools/deleteSceneTool.js'; import { registerLoadSceneTool } from './tools/loadSceneTool.js'; import { registerSaveSceneTool } from './tools/saveSceneTool.js'; import { registerGetSceneInfoTool } from './tools/getSceneInfoTool.js'; +import { registerGetPlayModeStatusTool } from './tools/getPlayModeStatusTool.js'; import { registerUnloadSceneTool } from './tools/unloadSceneTool.js'; import { registerRecompileScriptsTool } from './tools/recompileScriptsTool.js'; import { registerGetGameObjectTool } from './tools/getGameObjectTool.js'; @@ -25,6 +26,8 @@ import { registerTransformTools } from './tools/transformTools.js'; import { registerCreateMaterialTool, registerAssignMaterialTool, registerModifyMaterialTool, registerGetMaterialInfoTool } from './tools/materialTools.js'; import { registerDuplicateGameObjectTool, registerDeleteGameObjectTool, registerReparentGameObjectTool } from './tools/gameObjectTools.js'; import { registerBatchExecuteTool } from './tools/batchExecuteTool.js'; +import { registerShowUnityDashboardTool } from './tools/showUnityDashboardTool.js'; +import { registerGetScenesHierarchyTool } from './tools/getScenesHierarchyTool.js'; import { registerGetMenuItemsResource } from './resources/getMenuItemResource.js'; import { registerGetConsoleLogsResource } from './resources/getConsoleLogsResource.js'; import { registerGetHierarchyResource } from './resources/getScenesHierarchyResource.js'; @@ -32,6 +35,7 @@ import { registerGetPackagesResource } from './resources/getPackagesResource.js' import { registerGetAssetsResource } from './resources/getAssetsResource.js'; import { registerGetTestsResource } from './resources/getTestsResource.js'; import { registerGetGameObjectResource } from './resources/getGameObjectResource.js'; +import { registerUnityDashboardAppResource } from './resources/unityDashboardAppResource.js'; import { registerGameObjectHandlingPrompt } from './prompts/gameobjectHandlingPrompt.js'; // Initialize loggers @@ -74,6 +78,9 @@ registerDeleteSceneTool(server, mcpUnity, toolLogger); registerLoadSceneTool(server, mcpUnity, toolLogger); registerSaveSceneTool(server, mcpUnity, toolLogger); registerGetSceneInfoTool(server, mcpUnity, toolLogger); +registerGetPlayModeStatusTool(server, mcpUnity, toolLogger); +registerShowUnityDashboardTool(server, toolLogger); +registerGetScenesHierarchyTool(server, mcpUnity, toolLogger); registerUnloadSceneTool(server, mcpUnity, toolLogger); registerRecompileScriptsTool(server, mcpUnity, toolLogger); registerGetGameObjectTool(server, mcpUnity, toolLogger); @@ -99,6 +106,7 @@ registerGetConsoleLogsResource(server, mcpUnity, resourceLogger); registerGetHierarchyResource(server, mcpUnity, resourceLogger); registerGetPackagesResource(server, mcpUnity, resourceLogger); registerGetAssetsResource(server, mcpUnity, resourceLogger); +registerUnityDashboardAppResource(server, resourceLogger); // Register all prompts into the MCP server registerGameObjectHandlingPrompt(server); diff --git a/Server~/src/resources/unityDashboardAppResource.ts b/Server~/src/resources/unityDashboardAppResource.ts new file mode 100644 index 00000000..1e5ba024 --- /dev/null +++ b/Server~/src/resources/unityDashboardAppResource.ts @@ -0,0 +1,107 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; +import { registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server'; +import { Logger } from '../utils/logger.js'; + +const resourceName = 'unity_dashboard_app'; +const appResourceUri = 'ui://unity-dashboard'; +const legacyResourceName = 'unity_dashboard_app_legacy'; +const legacyResourceUri = 'unity://ui/dashboard'; +const resourceMimeType = RESOURCE_MIME_TYPE; + +export function registerUnityDashboardAppResource(server: McpServer, logger: Logger) { + logger.info(`Registering resource: ${resourceName}`); + + registerAppResource( + server, + resourceName, + appResourceUri, + { + description: 'Unity dashboard MCP App UI', + }, + async () => { + try { + return readDashboardHtml(); + } catch (error) { + logger.error(`Error reading dashboard HTML: ${error}`); + throw error; + } + } + ); + + // Legacy URI for compatibility with older hosts / docs that expect unity://ui/dashboard + server.resource( + legacyResourceName, + legacyResourceUri, + { + description: 'Unity dashboard MCP App UI (legacy resource URI)', + mimeType: resourceMimeType + }, + async () => { + try { + return readDashboardHtml(legacyResourceUri); + } catch (error) { + logger.error(`Error reading dashboard HTML (legacy uri): ${error}`); + throw error; + } + } + ); +} + +function readDashboardHtml(uriOverride?: string): ReadResourceResult { + const { text } = readUnityDashboardHtml(); + const uri = uriOverride ?? appResourceUri; + + return { + contents: [ + { + uri, + mimeType: resourceMimeType, + text, + _meta: { + // For hosts that still look for legacy view hints. + view: 'mcp-app', + ui: { + prefersBorder: true + } + } + } + ] + }; +} + +export function readUnityDashboardHtml(): { text: string; mimeType: string } { + const htmlPath = resolveDashboardPath(); + const text = fs.readFileSync(htmlPath, 'utf8'); + + return { text, mimeType: resourceMimeType }; +} + +function resolveDashboardPath(): string { + // IMPORTANT: do not use process.cwd() here. + // VS Code runs MCP servers with the CWD of the *client workspace*, which may be + // unrelated to the server's install location. + const moduleDir = path.dirname(fileURLToPath(import.meta.url)); + + const candidates = [ + // Works when running TS directly (src/resources -> src/ui) + // and when running built JS with copied assets (build/resources -> build/ui) + path.join(moduleDir, '..', 'ui', 'unity-dashboard.html'), + + // Fallback for dev repos where build output exists but UI wasn't copied. + path.join(moduleDir, '..', '..', 'src', 'ui', 'unity-dashboard.html'), + ]; + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + + throw new Error( + `Unity dashboard UI file is missing. Checked: ${candidates.join(', ')}` + ); +} diff --git a/Server~/src/tools/getConsoleLogsTool.ts b/Server~/src/tools/getConsoleLogsTool.ts index 7b6eb5b1..b492d3bf 100644 --- a/Server~/src/tools/getConsoleLogsTool.ts +++ b/Server~/src/tools/getConsoleLogsTool.ts @@ -101,16 +101,25 @@ async function toolHandler( ); } + const logs = response.data ?? response.logs ?? response; + return { content: [ { type: "text", text: JSON.stringify( - response.data || response.logs || response, + logs, null, 2 ), }, ], + data: { + logs, + offset, + limit, + logType, + includeStackTrace, + }, }; } diff --git a/Server~/src/tools/getPlayModeStatusTool.ts b/Server~/src/tools/getPlayModeStatusTool.ts new file mode 100644 index 00000000..6b2f1a1e --- /dev/null +++ b/Server~/src/tools/getPlayModeStatusTool.ts @@ -0,0 +1,63 @@ +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpUnity } from '../unity/mcpUnity.js'; +import { McpUnityError, ErrorType } from '../utils/errors.js'; +import * as z from 'zod'; +import { Logger } from '../utils/logger.js'; +import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; + +const toolName = 'get_play_mode_status'; +const toolDescription = 'Gets Unity play mode status (isPlaying, isPaused).'; + +const paramsSchema = z.object({}); + +export function registerGetPlayModeStatusTool(server: McpServer, mcpUnity: McpUnity, logger: Logger) { + logger.info(`Registering tool: ${toolName}`); + + server.tool( + toolName, + toolDescription, + paramsSchema.shape, + async (params: any) => { + try { + logger.info(`Executing tool: ${toolName}`, params); + const result = await toolHandler(mcpUnity, params); + logger.info(`Tool execution successful: ${toolName}`); + return result; + } catch (error) { + logger.error(`Tool execution failed: ${toolName}`, error); + throw error; + } + } + ); +} + +async function toolHandler(mcpUnity: McpUnity, params: any): Promise { + const response = await mcpUnity.sendRequest({ + method: toolName, + params + }); + + if (!response.success) { + throw new McpUnityError( + ErrorType.TOOL_EXECUTION, + response.message || 'Failed to get play mode status' + ); + } + + const statusText = response.isPlaying + ? (response.isPaused ? 'Play mode (paused)' : 'Play mode') + : 'Edit mode'; + + return { + content: [ + { + type: response.type as 'text', + text: statusText + } + ], + data: { + isPlaying: response.isPlaying, + isPaused: response.isPaused + } + }; +} diff --git a/Server~/src/tools/getScenesHierarchyTool.ts b/Server~/src/tools/getScenesHierarchyTool.ts new file mode 100644 index 00000000..e11897ec --- /dev/null +++ b/Server~/src/tools/getScenesHierarchyTool.ts @@ -0,0 +1,59 @@ +import * as z from 'zod'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import { Logger } from '../utils/logger.js'; +import { McpUnity } from '../unity/mcpUnity.js'; +import { McpUnityError, ErrorType } from '../utils/errors.js'; + +const toolName = 'get_scenes_hierarchy'; +const toolDescription = 'Retrieves all GameObjects in the Unity loaded scenes (scenes hierarchy).'; +const paramsSchema = z.object({}); + +export function registerGetScenesHierarchyTool(server: McpServer, mcpUnity: McpUnity, logger: Logger) { + logger.info(`Registering tool: ${toolName}`); + + server.tool( + toolName, + toolDescription, + paramsSchema.shape, + async (params: z.infer) => { + try { + logger.info(`Executing tool: ${toolName}`, params); + const result = await toolHandler(mcpUnity); + logger.info(`Tool execution successful: ${toolName}`); + return result; + } catch (error) { + logger.error(`Tool execution failed: ${toolName}`, error); + throw error; + } + } + ); +} + +async function toolHandler(mcpUnity: McpUnity): Promise { + const response = await mcpUnity.sendRequest({ + method: toolName, + params: {}, + }); + + if (!response.success) { + throw new McpUnityError( + ErrorType.TOOL_EXECUTION, + response.message || 'Failed to fetch hierarchy from Unity' + ); + } + + const hierarchy = response.hierarchy ?? response.data ?? response; + + return { + content: [ + { + type: 'text', + text: JSON.stringify(hierarchy, null, 2), + }, + ], + data: { + hierarchy, + }, + }; +} diff --git a/Server~/src/tools/showUnityDashboardTool.ts b/Server~/src/tools/showUnityDashboardTool.ts new file mode 100644 index 00000000..05e1fb69 --- /dev/null +++ b/Server~/src/tools/showUnityDashboardTool.ts @@ -0,0 +1,57 @@ +import * as z from 'zod'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import { Logger } from '../utils/logger.js'; +import { registerAppTool } from '@modelcontextprotocol/ext-apps/server'; +import { readUnityDashboardHtml } from '../resources/unityDashboardAppResource.js'; + +const toolName = 'show_unity_dashboard'; +const toolDescription = 'Opens the Unity dashboard MCP App in VS Code.'; +const paramsSchema = z.object({}); + +export function registerShowUnityDashboardTool(server: McpServer, logger: Logger) { + logger.info(`Registering tool: ${toolName}`); + + registerAppTool(server, toolName, { + description: toolDescription, + inputSchema: paramsSchema.shape, + _meta: { + ui: { + resourceUri: 'ui://unity-dashboard' + } + } + }, async () => { + try { + logger.info(`Executing tool: ${toolName}`); + const result = await toolHandler(); + logger.info(`Tool execution successful: ${toolName}`); + return result; + } catch (error) { + logger.error(`Tool execution failed: ${toolName}`, error); + throw error; + } + }); +} + +async function toolHandler(): Promise { + // Returning an embedded resource here is the most compatible option: + // - Some hosts open the app via tool metadata (_meta.ui.resourceUri) + // - Others rely on content blocks with view hints (legacy) + const { text, mimeType } = readUnityDashboardHtml(); + + return { + content: [ + { + type: 'resource', + resource: { + uri: 'unity://ui/dashboard', + mimeType, + text, + _meta: { + view: 'mcp-app' + } + } + } + ] + }; +} diff --git a/Server~/src/ui/unity-dashboard.html b/Server~/src/ui/unity-dashboard.html new file mode 100644 index 00000000..fbd51e4c --- /dev/null +++ b/Server~/src/ui/unity-dashboard.html @@ -0,0 +1,261 @@ + + + + + + Unity Dashboard + + + +
+
+

Unity Dashboard

+
+
+ + + + + + + 3s +
+
+
+ +
+
+
+
Scene
+
Loading scene info…
+
+ +
+
Hierarchy
+
Loading hierarchy…
+
+
+ +
+
+
Play Mode
+
+
+ +
+
Console Logs
+
Loading logs…
+
+
+
+
+ + + + diff --git a/plans.meta b/plans.meta new file mode 100644 index 00000000..7673df05 --- /dev/null +++ b/plans.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 987c5bf4afb8b2d4997c1ea265aa6d78 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plans/unity-dashboard-mcp-app.meta b/plans/unity-dashboard-mcp-app.meta new file mode 100644 index 00000000..af77b89d --- /dev/null +++ b/plans/unity-dashboard-mcp-app.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d28ea9a244fd5a242b44be11eaf427fe +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plans/unity-dashboard-mcp-app/implementation.md b/plans/unity-dashboard-mcp-app/implementation.md new file mode 100644 index 00000000..b805c74a --- /dev/null +++ b/plans/unity-dashboard-mcp-app/implementation.md @@ -0,0 +1,2800 @@ +# Unity Dashboard MCP App + +## Goal +Deliver a VS Code MCP App dashboard that visualizes Unity scene hierarchy, console logs, and play controls with polling-based refresh and a dedicated app view. + +## Prerequisites +Make sure that the use is currently on the `feature/unity-dashboard-mcp-app` branch before beginning implementation. +If not, move them to the correct branch. If the branch does not exist, create it from main. + +### Step-by-Step Instructions + +#### Step 1: Add Dashboard UI Resource +- [ ] Create the resource handler at [Server~/src/resources/unityDashboardAppResource.ts](Server~/src/resources/unityDashboardAppResource.ts). +- [ ] Copy and paste code below into [Server~/src/resources/unityDashboardAppResource.ts](Server~/src/resources/unityDashboardAppResource.ts): + +```typescript +import fs from 'node:fs'; +import path from 'node:path'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; +import { Logger } from '../utils/logger.js'; + +const resourceName = 'unity_dashboard_app'; +const resourceUri = 'unity://ui/dashboard'; +const resourceMimeType = 'text/html'; + +export function registerUnityDashboardAppResource(server: McpServer, logger: Logger) { + logger.info(`Registering resource: ${resourceName}`); + + server.resource( + resourceName, + resourceUri, + { + description: 'Unity Dashboard MCP App UI (HTML) for VS Code MCP Apps', + mimeType: resourceMimeType + }, + async () => { + try { + return readDashboardHtml(); + } catch (error) { + logger.error(`Error handling resource ${resourceName}: ${error}`); + throw error; + } + } + ); +} + +function readDashboardHtml(): ReadResourceResult { + const htmlPath = resolveDashboardPath(); + const html = fs.readFileSync(htmlPath, 'utf8'); + + return { + contents: [ + { + uri: resourceUri, + mimeType: resourceMimeType, + text: html + } + ] + }; +} + +function resolveDashboardPath(): string { + const srcPath = path.join(process.cwd(), 'src', 'ui', 'unity-dashboard.html'); + if (fs.existsSync(srcPath)) { + return srcPath; + } + + const buildPath = path.join(process.cwd(), 'build', 'ui', 'unity-dashboard.html'); + if (fs.existsSync(buildPath)) { + return buildPath; + } + + return srcPath; +} +``` + +- [ ] Create the HTML shell at [Server~/src/ui/unity-dashboard.html](Server~/src/ui/unity-dashboard.html). +- [ ] Copy and paste code below into [Server~/src/ui/unity-dashboard.html](Server~/src/ui/unity-dashboard.html): + +```html + + + + + + Unity Dashboard + + + + +
+
+

Unity Scene Dashboard

+
MCP App shell. Logic is added in Step 3.
+
+
+
Scene hierarchy placeholder
+
Console logs placeholder
+
+
+ + +``` + +- [ ] Register the dashboard resource in the MCP server. +- [ ] Copy and paste code below into [Server~/src/index.ts](Server~/src/index.ts): + +```typescript +// Import MCP SDK components +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { McpUnity } from './unity/mcpUnity.js'; +import { Logger, LogLevel } from './utils/logger.js'; +import { registerCreateSceneTool } from './tools/createSceneTool.js'; +import { registerMenuItemTool } from './tools/menuItemTool.js'; +import { registerSelectGameObjectTool } from './tools/selectGameObjectTool.js'; +import { registerAddPackageTool } from './tools/addPackageTool.js'; +import { registerRunTestsTool } from './tools/runTestsTool.js'; +import { registerSendConsoleLogTool } from './tools/sendConsoleLogTool.js'; +import { registerGetConsoleLogsTool } from './tools/getConsoleLogsTool.js'; +import { registerUpdateComponentTool } from './tools/updateComponentTool.js'; +import { registerAddAssetToSceneTool } from './tools/addAssetToSceneTool.js'; +import { registerUpdateGameObjectTool } from './tools/updateGameObjectTool.js'; +import { registerCreatePrefabTool } from './tools/createPrefabTool.js'; +import { registerDeleteSceneTool } from './tools/deleteSceneTool.js'; +import { registerLoadSceneTool } from './tools/loadSceneTool.js'; +import { registerSaveSceneTool } from './tools/saveSceneTool.js'; +import { registerGetSceneInfoTool } from './tools/getSceneInfoTool.js'; +import { registerUnloadSceneTool } from './tools/unloadSceneTool.js'; +import { registerRecompileScriptsTool } from './tools/recompileScriptsTool.js'; +import { registerGetGameObjectTool } from './tools/getGameObjectTool.js'; +import { registerTransformTools } from './tools/transformTools.js'; +import { registerCreateMaterialTool, registerAssignMaterialTool, registerModifyMaterialTool, registerGetMaterialInfoTool } from './tools/materialTools.js'; +import { registerDuplicateGameObjectTool, registerDeleteGameObjectTool, registerReparentGameObjectTool } from './tools/gameObjectTools.js'; +import { registerBatchExecuteTool } from './tools/batchExecuteTool.js'; +import { registerGetMenuItemsResource } from './resources/getMenuItemResource.js'; +import { registerGetConsoleLogsResource } from './resources/getConsoleLogsResource.js'; +import { registerGetHierarchyResource } from './resources/getScenesHierarchyResource.js'; +import { registerGetPackagesResource } from './resources/getPackagesResource.js'; +import { registerGetAssetsResource } from './resources/getAssetsResource.js'; +import { registerGetTestsResource } from './resources/getTestsResource.js'; +import { registerGetGameObjectResource } from './resources/getGameObjectResource.js'; +import { registerUnityDashboardAppResource } from './resources/unityDashboardAppResource.js'; +import { registerGameObjectHandlingPrompt } from './prompts/gameobjectHandlingPrompt.js'; + +// Initialize loggers +const serverLogger = new Logger('Server', LogLevel.INFO); +const unityLogger = new Logger('Unity', LogLevel.INFO); +const toolLogger = new Logger('Tools', LogLevel.INFO); +const resourceLogger = new Logger('Resources', LogLevel.INFO); + +// Initialize the MCP server +const server = new McpServer ( + { + name: "MCP Unity Server", + version: "1.0.0" + }, + { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + } +); + +// Initialize MCP HTTP bridge with Unity editor +const mcpUnity = new McpUnity(unityLogger); + +// Register all tools into the MCP server +registerMenuItemTool(server, mcpUnity, toolLogger); +registerSelectGameObjectTool(server, mcpUnity, toolLogger); +registerAddPackageTool(server, mcpUnity, toolLogger); +registerRunTestsTool(server, mcpUnity, toolLogger); +registerSendConsoleLogTool(server, mcpUnity, toolLogger); +registerGetConsoleLogsTool(server, mcpUnity, toolLogger); +registerUpdateComponentTool(server, mcpUnity, toolLogger); +registerAddAssetToSceneTool(server, mcpUnity, toolLogger); +registerUpdateGameObjectTool(server, mcpUnity, toolLogger); +registerCreatePrefabTool(server, mcpUnity, toolLogger); +registerCreateSceneTool(server, mcpUnity, toolLogger); +registerDeleteSceneTool(server, mcpUnity, toolLogger); +registerLoadSceneTool(server, mcpUnity, toolLogger); +registerSaveSceneTool(server, mcpUnity, toolLogger); +registerGetSceneInfoTool(server, mcpUnity, toolLogger); +registerUnloadSceneTool(server, mcpUnity, toolLogger); +registerRecompileScriptsTool(server, mcpUnity, toolLogger); +registerGetGameObjectTool(server, mcpUnity, toolLogger); +registerTransformTools(server, mcpUnity, toolLogger); +registerDuplicateGameObjectTool(server, mcpUnity, toolLogger); +registerDeleteGameObjectTool(server, mcpUnity, toolLogger); +registerReparentGameObjectTool(server, mcpUnity, toolLogger); + +// Register Material Tools +registerCreateMaterialTool(server, mcpUnity, toolLogger); +registerAssignMaterialTool(server, mcpUnity, toolLogger); +registerModifyMaterialTool(server, mcpUnity, toolLogger); +registerGetMaterialInfoTool(server, mcpUnity, toolLogger); + +// Register Batch Execute Tool (high-priority for performance) +registerBatchExecuteTool(server, mcpUnity, toolLogger); + +// Register all resources into the MCP server +registerGetTestsResource(server, mcpUnity, resourceLogger); +registerGetGameObjectResource(server, mcpUnity, resourceLogger); +registerGetMenuItemsResource(server, mcpUnity, resourceLogger); +registerGetConsoleLogsResource(server, mcpUnity, resourceLogger); +registerGetHierarchyResource(server, mcpUnity, resourceLogger); +registerGetPackagesResource(server, mcpUnity, resourceLogger); +registerGetAssetsResource(server, mcpUnity, resourceLogger); +registerUnityDashboardAppResource(server, resourceLogger); + +// Register all prompts into the MCP server +registerGameObjectHandlingPrompt(server); + +// Server startup function +async function startServer() { + try { + // Initialize STDIO transport for MCP client communication + const stdioTransport = new StdioServerTransport(); + + // Connect the server to the transport + await server.connect(stdioTransport); + + serverLogger.info('MCP Server started'); + + // Get the client name from the MCP server + const clientName = server.server.getClientVersion()?.name || 'Unknown MCP Client'; + serverLogger.info(`Connected MCP client: ${clientName}`); + + // Start Unity Bridge connection with client name in headers + await mcpUnity.start(clientName); + + } catch (error) { + serverLogger.error('Failed to start server', error); + process.exit(1); + } +} + +// Graceful shutdown handler +let isShuttingDown = false; +async function shutdown() { + if (isShuttingDown) return; + isShuttingDown = true; + + try { + serverLogger.info('Shutting down...'); + await mcpUnity.stop(); + await server.close(); + } catch (error) { + // Ignore errors during shutdown + } + process.exit(0); +} + +// Start the server +startServer(); + +// Handle shutdown signals +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); +process.on('SIGHUP', shutdown); + +// Handle stdin close (when MCP client disconnects) +process.stdin.on('close', shutdown); +process.stdin.on('end', shutdown); +process.stdin.on('error', shutdown); + +// Handle uncaught exceptions - exit cleanly if it's just a closed pipe +process.on('uncaughtException', (error: NodeJS.ErrnoException) => { + // EPIPE/EOF errors are expected when the MCP client disconnects + if (error.code === 'EPIPE' || error.code === 'EOF' || error.code === 'ERR_USE_AFTER_CLOSE') { + shutdown(); + return; + } + serverLogger.error('Uncaught exception', error); + process.exit(1); +}); + +// Handle unhandled promise rejections +process.on('unhandledRejection', (reason) => { + serverLogger.error('Unhandled rejection', reason); + process.exit(1); +}); +``` + +##### Step 1 Verification Checklist +- [ ] Run `cd Server~ && npm run build` and confirm the build completes. +- [ ] Run `cd Server~ && npm run inspector`, then read `unity://ui/dashboard` and confirm HTML content is returned. + +#### Step 1 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. + +#### Step 2: Add Tool to Open the Dashboard App +- [ ] Create the MCP tool at [Server~/src/tools/showUnityDashboardTool.ts](Server~/src/tools/showUnityDashboardTool.ts). +- [ ] Copy and paste code below into [Server~/src/tools/showUnityDashboardTool.ts](Server~/src/tools/showUnityDashboardTool.ts): + +```typescript +import * as z from 'zod'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import { Logger } from '../utils/logger.js'; + +const toolName = 'show_unity_dashboard'; +const toolDescription = 'Opens the Unity dashboard MCP App in VS Code.'; +const paramsSchema = z.object({}); + +export function registerShowUnityDashboardTool(server: McpServer, logger: Logger) { + logger.info(`Registering tool: ${toolName}`); + + server.tool(toolName, toolDescription, paramsSchema.shape, async () => { + try { + logger.info(`Executing tool: ${toolName}`); + const result = await toolHandler(); + logger.info(`Tool execution successful: ${toolName}`); + return result; + } catch (error) { + logger.error(`Tool execution failed: ${toolName}`, error); + throw error; + } + }); +} + +async function toolHandler(): Promise { + return { + content: [ + { + type: 'resource', + uri: 'unity://ui/dashboard', + metadata: { + view: 'mcp-app' + } + } + ] + }; +} +``` + +- [ ] Register the tool in the MCP server. +- [ ] Copy and paste code below into [Server~/src/index.ts](Server~/src/index.ts): + +```typescript +// Import MCP SDK components +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { McpUnity } from './unity/mcpUnity.js'; +import { Logger, LogLevel } from './utils/logger.js'; +import { registerCreateSceneTool } from './tools/createSceneTool.js'; +import { registerMenuItemTool } from './tools/menuItemTool.js'; +import { registerSelectGameObjectTool } from './tools/selectGameObjectTool.js'; +import { registerAddPackageTool } from './tools/addPackageTool.js'; +import { registerRunTestsTool } from './tools/runTestsTool.js'; +import { registerSendConsoleLogTool } from './tools/sendConsoleLogTool.js'; +import { registerGetConsoleLogsTool } from './tools/getConsoleLogsTool.js'; +import { registerUpdateComponentTool } from './tools/updateComponentTool.js'; +import { registerAddAssetToSceneTool } from './tools/addAssetToSceneTool.js'; +import { registerUpdateGameObjectTool } from './tools/updateGameObjectTool.js'; +import { registerCreatePrefabTool } from './tools/createPrefabTool.js'; +import { registerDeleteSceneTool } from './tools/deleteSceneTool.js'; +import { registerLoadSceneTool } from './tools/loadSceneTool.js'; +import { registerSaveSceneTool } from './tools/saveSceneTool.js'; +import { registerGetSceneInfoTool } from './tools/getSceneInfoTool.js'; +import { registerUnloadSceneTool } from './tools/unloadSceneTool.js'; +import { registerRecompileScriptsTool } from './tools/recompileScriptsTool.js'; +import { registerGetGameObjectTool } from './tools/getGameObjectTool.js'; +import { registerTransformTools } from './tools/transformTools.js'; +import { registerCreateMaterialTool, registerAssignMaterialTool, registerModifyMaterialTool, registerGetMaterialInfoTool } from './tools/materialTools.js'; +import { registerDuplicateGameObjectTool, registerDeleteGameObjectTool, registerReparentGameObjectTool } from './tools/gameObjectTools.js'; +import { registerBatchExecuteTool } from './tools/batchExecuteTool.js'; +import { registerShowUnityDashboardTool } from './tools/showUnityDashboardTool.js'; +import { registerGetMenuItemsResource } from './resources/getMenuItemResource.js'; +import { registerGetConsoleLogsResource } from './resources/getConsoleLogsResource.js'; +import { registerGetHierarchyResource } from './resources/getScenesHierarchyResource.js'; +import { registerGetPackagesResource } from './resources/getPackagesResource.js'; +import { registerGetAssetsResource } from './resources/getAssetsResource.js'; +import { registerGetTestsResource } from './resources/getTestsResource.js'; +import { registerGetGameObjectResource } from './resources/getGameObjectResource.js'; +import { registerUnityDashboardAppResource } from './resources/unityDashboardAppResource.js'; +import { registerGameObjectHandlingPrompt } from './prompts/gameobjectHandlingPrompt.js'; + +// Initialize loggers +const serverLogger = new Logger('Server', LogLevel.INFO); +const unityLogger = new Logger('Unity', LogLevel.INFO); +const toolLogger = new Logger('Tools', LogLevel.INFO); +const resourceLogger = new Logger('Resources', LogLevel.INFO); + +// Initialize the MCP server +const server = new McpServer ( + { + name: "MCP Unity Server", + version: "1.0.0" + }, + { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + } +); + +// Initialize MCP HTTP bridge with Unity editor +const mcpUnity = new McpUnity(unityLogger); + +// Register all tools into the MCP server +registerMenuItemTool(server, mcpUnity, toolLogger); +registerSelectGameObjectTool(server, mcpUnity, toolLogger); +registerAddPackageTool(server, mcpUnity, toolLogger); +registerRunTestsTool(server, mcpUnity, toolLogger); +registerSendConsoleLogTool(server, mcpUnity, toolLogger); +registerGetConsoleLogsTool(server, mcpUnity, toolLogger); +registerUpdateComponentTool(server, mcpUnity, toolLogger); +registerAddAssetToSceneTool(server, mcpUnity, toolLogger); +registerUpdateGameObjectTool(server, mcpUnity, toolLogger); +registerCreatePrefabTool(server, mcpUnity, toolLogger); +registerCreateSceneTool(server, mcpUnity, toolLogger); +registerDeleteSceneTool(server, mcpUnity, toolLogger); +registerLoadSceneTool(server, mcpUnity, toolLogger); +registerSaveSceneTool(server, mcpUnity, toolLogger); +registerGetSceneInfoTool(server, mcpUnity, toolLogger); +registerShowUnityDashboardTool(server, toolLogger); +registerUnloadSceneTool(server, mcpUnity, toolLogger); +registerRecompileScriptsTool(server, mcpUnity, toolLogger); +registerGetGameObjectTool(server, mcpUnity, toolLogger); +registerTransformTools(server, mcpUnity, toolLogger); +registerDuplicateGameObjectTool(server, mcpUnity, toolLogger); +registerDeleteGameObjectTool(server, mcpUnity, toolLogger); +registerReparentGameObjectTool(server, mcpUnity, toolLogger); + +// Register Material Tools +registerCreateMaterialTool(server, mcpUnity, toolLogger); +registerAssignMaterialTool(server, mcpUnity, toolLogger); +registerModifyMaterialTool(server, mcpUnity, toolLogger); +registerGetMaterialInfoTool(server, mcpUnity, toolLogger); + +// Register Batch Execute Tool (high-priority for performance) +registerBatchExecuteTool(server, mcpUnity, toolLogger); + +// Register all resources into the MCP server +registerGetTestsResource(server, mcpUnity, resourceLogger); +registerGetGameObjectResource(server, mcpUnity, resourceLogger); +registerGetMenuItemsResource(server, mcpUnity, resourceLogger); +registerGetConsoleLogsResource(server, mcpUnity, resourceLogger); +registerGetHierarchyResource(server, mcpUnity, resourceLogger); +registerGetPackagesResource(server, mcpUnity, resourceLogger); +registerGetAssetsResource(server, mcpUnity, resourceLogger); +registerUnityDashboardAppResource(server, resourceLogger); + +// Register all prompts into the MCP server +registerGameObjectHandlingPrompt(server); + +// Server startup function +async function startServer() { + try { + // Initialize STDIO transport for MCP client communication + const stdioTransport = new StdioServerTransport(); + + // Connect the server to the transport + await server.connect(stdioTransport); + + serverLogger.info('MCP Server started'); + + // Get the client name from the MCP server + const clientName = server.server.getClientVersion()?.name || 'Unknown MCP Client'; + serverLogger.info(`Connected MCP client: ${clientName}`); + + // Start Unity Bridge connection with client name in headers + await mcpUnity.start(clientName); + + } catch (error) { + serverLogger.error('Failed to start server', error); + process.exit(1); + } +} + +// Graceful shutdown handler +let isShuttingDown = false; +async function shutdown() { + if (isShuttingDown) return; + isShuttingDown = true; + + try { + serverLogger.info('Shutting down...'); + await mcpUnity.stop(); + await server.close(); + } catch (error) { + // Ignore errors during shutdown + } + process.exit(0); +} + +// Start the server +startServer(); + +// Handle shutdown signals +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); +process.on('SIGHUP', shutdown); + +// Handle stdin close (when MCP client disconnects) +process.stdin.on('close', shutdown); +process.stdin.on('end', shutdown); +process.stdin.on('error', shutdown); + +// Handle uncaught exceptions - exit cleanly if it's just a closed pipe +process.on('uncaughtException', (error: NodeJS.ErrnoException) => { + // EPIPE/EOF errors are expected when the MCP client disconnects + if (error.code === 'EPIPE' || error.code === 'EOF' || error.code === 'ERR_USE_AFTER_CLOSE') { + shutdown(); + return; + } + serverLogger.error('Uncaught exception', error); + process.exit(1); +}); + +// Handle unhandled promise rejections +process.on('unhandledRejection', (reason) => { + serverLogger.error('Unhandled rejection', reason); + process.exit(1); +}); +``` + +##### Step 2 Verification Checklist +- [ ] Run `cd Server~ && npm run build` and confirm the build completes. +- [ ] In MCP Inspector, call the `show_unity_dashboard` tool and confirm the response returns a resource with `metadata.view` set to `mcp-app`. + +#### Step 2 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. + +#### Step 3: Implement Dashboard Frontend Logic +- [ ] Replace the dashboard HTML with the full UI and MCP App logic. +- [ ] Copy and paste code below into [Server~/src/ui/unity-dashboard.html](Server~/src/ui/unity-dashboard.html): + +```html + + + + + + Unity Dashboard + + + + +
+
+
MCP Unity
+

Unity Scene Dashboard

+
+
+ + Connecting to MCP... +
+
+ + + + +
+
+
+
+

Active Scene

+

Loading...

+
+
+

Root Objects

+

--

+
+
+

Play Mode

+

Unknown

+
+
+

Last Refresh

+

--

+
+
+
+ +
+
+

Scene Hierarchy

+
+
Waiting for data...
+
+ +
+ +
+

Console Logs

+
+
Waiting for logs...
+
+ +
+
+
+ + + + + +``` + +##### Step 3 Verification Checklist +- [ ] Open the dashboard via the `show_unity_dashboard` tool and confirm the UI renders. +- [ ] Click Play, Pause, Step and confirm Unity responds. +- [ ] Click Refresh and confirm hierarchy and logs update. +- [ ] Enable Auto refresh and confirm data updates on interval. + +#### Step 3 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. + +#### Step 4: Add Play Mode Status Tool +- [ ] Create the Unity-side tool at [Editor/Tools/GetPlayModeStatusTool.cs](Editor/Tools/GetPlayModeStatusTool.cs). +- [ ] Copy and paste code below into [Editor/Tools/GetPlayModeStatusTool.cs](Editor/Tools/GetPlayModeStatusTool.cs): + +```csharp +using System; +using Newtonsoft.Json.Linq; +using UnityEditor; +using McpUnity.Unity; +using McpUnity.Utils; + +namespace McpUnity.Tools +{ + /// + /// Tool for getting Unity play mode status + /// + public class GetPlayModeStatusTool : McpToolBase + { + public GetPlayModeStatusTool() + { + Name = "get_play_mode_status"; + Description = "Gets Unity play mode status (isPlaying, isPaused)"; + } + + /// + /// Execute the GetPlayModeStatus tool with the provided parameters + /// + /// Tool parameters as a JObject + public override JObject Execute(JObject parameters) + { + try + { + bool isPlaying = EditorApplication.isPlaying; + bool isPaused = EditorApplication.isPaused; + + var result = new JObject + { + ["success"] = true, + ["type"] = "text", + ["message"] = isPlaying ? (isPaused ? "Play mode (paused)" : "Play mode") : "Edit mode", + ["isPlaying"] = isPlaying, + ["isPaused"] = isPaused + }; + + McpLogger.LogInfo($"Play mode status: isPlaying={isPlaying}, isPaused={isPaused}"); + + return result; + } + catch (Exception ex) + { + return McpUnitySocketHandler.CreateErrorResponse( + $"Error getting play mode status: {ex.Message}", + "play_mode_status_error" + ); + } + } + } +} +``` + +- [ ] Register the Unity tool in the MCP Unity server. +- [ ] Copy and paste code below into [Editor/UnityBridge/McpUnityServer.cs](Editor/UnityBridge/McpUnityServer.cs): + +```csharp +using System; +using System.Collections.Generic; +using System.Threading; +using UnityEditor; +using UnityEngine; +using McpUnity.Tools; +using McpUnity.Resources; +using McpUnity.Services; +using McpUnity.Utils; +using WebSocketSharp.Server; +using System.IO; +using System.Net.Sockets; +using UnityEditor.Callbacks; + +namespace McpUnity.Unity +{ + /// + /// Custom WebSocket close codes for Unity-specific events. + /// Range 4000-4999 is reserved for application use. + /// + public static class UnityCloseCode + { + /// + /// Unity is entering Play mode - clients should use fast polling instead of backoff + /// + public const ushort PlayMode = 4001; + } + + /// + /// MCP Unity Server to communicate Node.js MCP server. + /// Uses WebSockets to communicate with Node.js. + /// + [InitializeOnLoad] + public class McpUnityServer : IDisposable + { + private static McpUnityServer _instance; + + private readonly Dictionary _tools = new Dictionary(); + private readonly Dictionary _resources = new Dictionary(); + + private WebSocketServer _webSocketServer; + private CancellationTokenSource _cts; + private TestRunnerService _testRunnerService; + private ConsoleLogsService _consoleLogsService; + + /// + /// Called after every domain reload + /// + [DidReloadScripts] + private static void AfterReload() + { + // Skip initialization in batch mode (Unity Cloud Build, CI, headless builds) + // This prevents npm commands from hanging the build process + if (Application.isBatchMode) + { + return; + } + + // Ensure Instance is created and hooks are set up after initial domain load + var currentInstance = Instance; + } + + /// + /// Singleton instance accessor. Returns null in batch mode. + /// + public static McpUnityServer Instance + { + get + { + // Don't create instance in batch mode to avoid hanging builds + if (Application.isBatchMode) + { + return null; + } + + if (_instance == null) + { + _instance = new McpUnityServer(); + } + return _instance; + } + } + + /// + /// Current Listening state + /// + public bool IsListening => _webSocketServer?.IsListening ?? false; + + /// + /// Dictionary of connected clients with this server + /// + public Dictionary Clients { get; } = new Dictionary(); + + /// + /// Private constructor to enforce singleton pattern + /// + private McpUnityServer() + { + // Skip all initialization in batch mode (Unity Cloud Build, CI, headless builds) + // The npm install/build commands can hang indefinitely without node.js available + if (Application.isBatchMode) + { + McpLogger.LogInfo("MCP Unity server disabled: Running in batch mode (Unity Cloud Build or CI)"); + return; + } + + EditorApplication.quitting -= OnEditorQuitting; // Prevent multiple subscriptions on domain reload + EditorApplication.quitting += OnEditorQuitting; + + AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; + AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; + + AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; + AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; + + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + + InstallServer(); + InitializeServices(); + RegisterResources(); + RegisterTools(); + + // Initial start if auto-start is enabled and not recovering from a reload where it was off + if (McpUnitySettings.Instance.AutoStartServer) + { + StartServer(); + } + } + + /// + /// Disposes the McpUnityServer instance, stopping the WebSocket server and unsubscribing from Unity Editor events. + /// This method ensures proper cleanup of resources and prevents memory leaks or unexpected behavior during domain reloads or editor shutdown. + /// + public void Dispose() + { + StopServer(); + + EditorApplication.quitting -= OnEditorQuitting; + AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; + AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + + GC.SuppressFinalize(this); + } + + /// + /// Start the WebSocket Server to communicate with Node.js + /// + public void StartServer() + { + // Skip starting server if this is a Multiplayer Play Mode clone instance + // Only the main editor should run the WebSocket server to avoid port conflicts + if (McpUtils.IsMultiplayerPlayModeClone()) + { + McpLogger.LogInfo("Server startup skipped: Running as Multiplayer Play Mode clone instance. Only the main editor runs the MCP server."); + return; + } + + if (IsListening) + { + McpLogger.LogInfo($"Server start requested, but already listening on port {McpUnitySettings.Instance.Port}."); + return; + } + + try + { + var host = McpUnitySettings.Instance.AllowRemoteConnections ? "0.0.0.0" : "localhost"; + _webSocketServer = new WebSocketServer($"ws://{host}:{McpUnitySettings.Instance.Port}"); + _webSocketServer.ReuseAddress = true; + _webSocketServer.AddWebSocketService("/McpUnity", () => new McpUnitySocketHandler(this)); + _webSocketServer.Start(); + McpLogger.LogInfo($"WebSocket server started successfully on {host}:{McpUnitySettings.Instance.Port}."); + } + catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse) + { + McpLogger.LogError($"Failed to start WebSocket server: Port {McpUnitySettings.Instance.Port} is already in use. {ex.Message}"); + } + catch (Exception ex) + { + McpLogger.LogError($"Failed to start WebSocket server: {ex.Message}\n{ex.StackTrace}"); + } + } + + /// + /// Stop the WebSocket server + /// + /// Optional custom close code to send to clients before stopping + /// Optional reason message for the close + public void StopServer(ushort? closeCode = null, string closeReason = null) + { + if (!IsListening) + { + return; + } + + try + { + // If a custom close code is provided, close all client connections with that code first + if (closeCode.HasValue && _webSocketServer != null) + { + CloseAllClients(closeCode.Value, closeReason ?? "Server stopping"); + } + + _webSocketServer?.Stop(); + + McpLogger.LogInfo("WebSocket server stopped"); + } + catch (Exception ex) + { + McpLogger.LogError($"Error during WebSocketServer.Stop(): {ex.Message}\n{ex.StackTrace}"); + } + finally + { + _webSocketServer = null; + Clients.Clear(); + McpLogger.LogInfo("WebSocket server stopped and resources cleaned up."); + } + } + + /// + /// Close all connected clients with a specific close code + /// + /// WebSocket close code (4000-4999 for application use) + /// Reason message for the close + private void CloseAllClients(ushort closeCode, string reason) + { + if (_webSocketServer == null) + { + return; + } + + try + { + var service = _webSocketServer.WebSocketServices["/McpUnity"]; + if (service?.Sessions != null) + { + // Get all active session IDs and close each with the custom code + var sessionIds = new List(service.Sessions.IDs); + foreach (var sessionId in sessionIds) + { + service.Sessions.CloseSession(sessionId, closeCode, reason); + } + McpLogger.LogInfo($"Closed {sessionIds.Count} client connection(s) with code {closeCode}: {reason}"); + } + } + catch (Exception ex) + { + McpLogger.LogError($"Error closing client connections: {ex.Message}"); + } + } + + /// + /// Try to get a tool by name + /// + public bool TryGetTool(string name, out McpToolBase tool) + { + return _tools.TryGetValue(name, out tool); + } + + /// + /// Register all available tools + /// + private void RegisterTools() + { + // Register MenuItemTool + MenuItemTool menuItemTool = new MenuItemTool(); + _tools.Add(menuItemTool.Name, menuItemTool); + + // Register SelectGameObjectTool + SelectGameObjectTool selectGameObjectTool = new SelectGameObjectTool(); + _tools.Add(selectGameObjectTool.Name, selectGameObjectTool); + + // Register UpdateGameObjectTool + UpdateGameObjectTool updateGameObjectTool = new UpdateGameObjectTool(); + _tools.Add(updateGameObjectTool.Name, updateGameObjectTool); + + // Register PackageManagerTool + AddPackageTool addPackageTool = new AddPackageTool(); + _tools.Add(addPackageTool.Name, addPackageTool); + + // Register RunTestsTool + RunTestsTool runTestsTool = new RunTestsTool(_testRunnerService); + _tools.Add(runTestsTool.Name, runTestsTool); + + // Register SendConsoleLogTool + SendConsoleLogTool sendConsoleLogTool = new SendConsoleLogTool(); + _tools.Add(sendConsoleLogTool.Name, sendConsoleLogTool); + + // Register UpdateComponentTool + UpdateComponentTool updateComponentTool = new UpdateComponentTool(); + _tools.Add(updateComponentTool.Name, updateComponentTool); + + // Register AddAssetToSceneTool + AddAssetToSceneTool addAssetToSceneTool = new AddAssetToSceneTool(); + _tools.Add(addAssetToSceneTool.Name, addAssetToSceneTool); + + // Register CreatePrefabTool + CreatePrefabTool createPrefabTool = new CreatePrefabTool(); + _tools.Add(createPrefabTool.Name, createPrefabTool); + + // Register CreateSceneTool + CreateSceneTool createSceneTool = new CreateSceneTool(); + _tools.Add(createSceneTool.Name, createSceneTool); + + // Register DeleteSceneTool + DeleteSceneTool deleteSceneTool = new DeleteSceneTool(); + _tools.Add(deleteSceneTool.Name, deleteSceneTool); + + // Register LoadSceneTool + LoadSceneTool loadSceneTool = new LoadSceneTool(); + _tools.Add(loadSceneTool.Name, loadSceneTool); + + // Register SaveSceneTool + SaveSceneTool saveSceneTool = new SaveSceneTool(); + _tools.Add(saveSceneTool.Name, saveSceneTool); + + // Register GetSceneInfoTool + GetSceneInfoTool getSceneInfoTool = new GetSceneInfoTool(); + _tools.Add(getSceneInfoTool.Name, getSceneInfoTool); + + // Register GetPlayModeStatusTool + GetPlayModeStatusTool getPlayModeStatusTool = new GetPlayModeStatusTool(); + _tools.Add(getPlayModeStatusTool.Name, getPlayModeStatusTool); + + // Register UnloadSceneTool + UnloadSceneTool unloadSceneTool = new UnloadSceneTool(); + _tools.Add(unloadSceneTool.Name, unloadSceneTool); + + // Register RecompileScriptsTool + RecompileScriptsTool recompileScriptsTool = new RecompileScriptsTool(); + _tools.Add(recompileScriptsTool.Name, recompileScriptsTool); + + // Register GetGameObjectTool + GetGameObjectTool getGameObjectTool = new GetGameObjectTool(); + _tools.Add(getGameObjectTool.Name, getGameObjectTool); + + // Register DuplicateGameObjectTool + DuplicateGameObjectTool duplicateGameObjectTool = new DuplicateGameObjectTool(); + _tools.Add(duplicateGameObjectTool.Name, duplicateGameObjectTool); + + // Register DeleteGameObjectTool + DeleteGameObjectTool deleteGameObjectTool = new DeleteGameObjectTool(); + _tools.Add(deleteGameObjectTool.Name, deleteGameObjectTool); + + // Register ReparentGameObjectTool + ReparentGameObjectTool reparentGameObjectTool = new ReparentGameObjectTool(); + _tools.Add(reparentGameObjectTool.Name, reparentGameObjectTool); + + // Register Transform Tools + MoveGameObjectTool moveGameObjectTool = new MoveGameObjectTool(); + _tools.Add(moveGameObjectTool.Name, moveGameObjectTool); + + RotateGameObjectTool rotateGameObjectTool = new RotateGameObjectTool(); + _tools.Add(rotateGameObjectTool.Name, rotateGameObjectTool); + + ScaleGameObjectTool scaleGameObjectTool = new ScaleGameObjectTool(); + _tools.Add(scaleGameObjectTool.Name, scaleGameObjectTool); + + SetTransformTool setTransformTool = new SetTransformTool(); + _tools.Add(setTransformTool.Name, setTransformTool); + + // Register Material Tools + CreateMaterialTool createMaterialTool = new CreateMaterialTool(); + _tools.Add(createMaterialTool.Name, createMaterialTool); + + AssignMaterialTool assignMaterialTool = new AssignMaterialTool(); + _tools.Add(assignMaterialTool.Name, assignMaterialTool); + + ModifyMaterialTool modifyMaterialTool = new ModifyMaterialTool(); + _tools.Add(modifyMaterialTool.Name, modifyMaterialTool); + + GetMaterialInfoTool getMaterialInfoTool = new GetMaterialInfoTool(); + _tools.Add(getMaterialInfoTool.Name, getMaterialInfoTool); + + // Register BatchExecuteTool (must be registered last as it needs access to other tools) + BatchExecuteTool batchExecuteTool = new BatchExecuteTool(this); + _tools.Add(batchExecuteTool.Name, batchExecuteTool); + } + + /// + /// Register all available resources + /// + private void RegisterResources() + { + // Register GetMenuItemsResource + GetMenuItemsResource getMenuItemsResource = new GetMenuItemsResource(); + _resources.Add(getMenuItemsResource.Name, getMenuItemsResource); + + // Register GetConsoleLogsResource + GetConsoleLogsResource getConsoleLogsResource = new GetConsoleLogsResource(_consoleLogsService); + _resources.Add(getConsoleLogsResource.Name, getConsoleLogsResource); + + // Register GetScenesHierarchyResource + GetScenesHierarchyResource getScenesHierarchyResource = new GetScenesHierarchyResource(); + _resources.Add(getScenesHierarchyResource.Name, getScenesHierarchyResource); + + // Register GetPackagesResource + GetPackagesResource getPackagesResource = new GetPackagesResource(); + _resources.Add(getPackagesResource.Name, getPackagesResource); + + // Register GetAssetsResource + GetAssetsResource getAssetsResource = new GetAssetsResource(); + _resources.Add(getAssetsResource.Name, getAssetsResource); + + // Register GetTestsResource + GetTestsResource getTestsResource = new GetTestsResource(_testRunnerService); + _resources.Add(getTestsResource.Name, getTestsResource); + + // Register GetGameObjectResource + GetGameObjectResource getGameObjectResource = new GetGameObjectResource(); + _resources.Add(getGameObjectResource.Name, getGameObjectResource); + } + + /// + /// Initialize services used by the server + /// + private void InitializeServices() + { + // Initialize the test runner service + _testRunnerService = new TestRunnerService(); + + // Initialize the console logs service + _consoleLogsService = new ConsoleLogsService(); + } + + /// + /// Handles the Unity Editor quitting event. Ensures the server is properly stopped and disposed. + /// + private static void OnEditorQuitting() + { + if (Application.isBatchMode || _instance == null) return; + + McpLogger.LogInfo("Editor is quitting. Ensuring server is stopped."); + _instance.Dispose(); + } + + /// + /// Handles the Unity Editor's 'before assembly reload' event. + /// Stops the WebSocket server to prevent port conflicts and ensure a clean state before scripts are recompiled. + /// + private static void OnBeforeAssemblyReload() + { + if (Application.isBatchMode || _instance == null) return; + + // Only stop server if it's currently running + if (_instance.IsListening) + { + McpLogger.LogInfo("Assembly reload detected. Stopping MCP server."); + _instance.StopServer(); + } + } + + /// + /// Handles the Unity Editor's 'after assembly reload' event. + /// Restarts the WebSocket server if it was running before the reload and auto-start is enabled. + /// + private static void OnAfterAssemblyReload() + { + if (Application.isBatchMode || _instance == null) return; + + // Restart server if auto-start is enabled + if (McpUnitySettings.Instance.AutoStartServer) + { + McpLogger.LogInfo("Assembly reload complete. Restarting MCP server if needed."); + _instance.StartServer(); + } + } + + /// + /// Handles Unity Editor play mode state changes. + /// + private void OnPlayModeStateChanged(PlayModeStateChange state) + { + if (Application.isBatchMode) return; + + if (state == PlayModeStateChange.ExitingEditMode) + { + McpLogger.LogInfo("Unity entering Play Mode."); + StopServer(UnityCloseCode.PlayMode, "Unity entering play mode"); + } + else if (state == PlayModeStateChange.EnteredEditMode) + { + McpLogger.LogInfo("Unity exited Play Mode."); + StartServer(); + } + } + + /// + /// Get the current WebSocket server instance + /// + public WebSocketServer WebSocketServer => _webSocketServer; + + /// + /// Install MCP Unity server on first load + /// + private void InstallServer() + { + if (Application.isBatchMode) + { + return; + } + + // Check if the server is already installed + if (McpUtils.IsServerInstalled()) + { + return; + } + + try + { + McpLogger.LogInfo("Installing MCP Unity server..."); + McpUtils.InstallServer(); + } + catch (Exception ex) + { + McpLogger.LogError($"Failed to install MCP Unity server: {ex.Message}"); + } + } + } +} +``` + +- [ ] Create the Node tool at [Server~/src/tools/getPlayModeStatusTool.ts](Server~/src/tools/getPlayModeStatusTool.ts). +- [ ] Copy and paste code below into [Server~/src/tools/getPlayModeStatusTool.ts](Server~/src/tools/getPlayModeStatusTool.ts): + +```typescript +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpUnity } from '../unity/mcpUnity.js'; +import { McpUnityError, ErrorType } from '../utils/errors.js'; +import * as z from 'zod'; +import { Logger } from '../utils/logger.js'; + +const toolName = 'get_play_mode_status'; +const toolDescription = 'Gets Unity play mode status (isPlaying, isPaused).'; + +const paramsSchema = z.object({}); + +export function registerGetPlayModeStatusTool(server: McpServer, mcpUnity: McpUnity, logger: Logger) { + logger.info(`Registering tool: ${toolName}`); + + server.tool( + toolName, + toolDescription, + paramsSchema.shape, + async (params: any) => { + try { + logger.info(`Executing tool: ${toolName}`, params); + const result = await toolHandler(mcpUnity, params); + logger.info(`Tool execution successful: ${toolName}`); + return result; + } catch (error) { + logger.error(`Tool execution failed: ${toolName}`, error); + throw error; + } + } + ); +} + +async function toolHandler(mcpUnity: McpUnity, params: any) { + const response = await mcpUnity.sendRequest({ + method: toolName, + params + }); + + if (!response.success) { + throw new McpUnityError( + ErrorType.TOOL_EXECUTION, + response.message || 'Failed to get play mode status' + ); + } + + const statusText = response.isPlaying + ? (response.isPaused ? 'Play mode (paused)' : 'Play mode') + : 'Edit mode'; + + return { + content: [ + { + type: response.type as 'text', + text: statusText + } + ], + data: { + isPlaying: response.isPlaying, + isPaused: response.isPaused + } + }; +} +``` + +- [ ] Register the Node tool in the MCP server. +- [ ] Copy and paste code below into [Server~/src/index.ts](Server~/src/index.ts): + +```typescript +// Import MCP SDK components +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { McpUnity } from './unity/mcpUnity.js'; +import { Logger, LogLevel } from './utils/logger.js'; +import { registerCreateSceneTool } from './tools/createSceneTool.js'; +import { registerMenuItemTool } from './tools/menuItemTool.js'; +import { registerSelectGameObjectTool } from './tools/selectGameObjectTool.js'; +import { registerAddPackageTool } from './tools/addPackageTool.js'; +import { registerRunTestsTool } from './tools/runTestsTool.js'; +import { registerSendConsoleLogTool } from './tools/sendConsoleLogTool.js'; +import { registerGetConsoleLogsTool } from './tools/getConsoleLogsTool.js'; +import { registerUpdateComponentTool } from './tools/updateComponentTool.js'; +import { registerAddAssetToSceneTool } from './tools/addAssetToSceneTool.js'; +import { registerUpdateGameObjectTool } from './tools/updateGameObjectTool.js'; +import { registerCreatePrefabTool } from './tools/createPrefabTool.js'; +import { registerDeleteSceneTool } from './tools/deleteSceneTool.js'; +import { registerLoadSceneTool } from './tools/loadSceneTool.js'; +import { registerSaveSceneTool } from './tools/saveSceneTool.js'; +import { registerGetSceneInfoTool } from './tools/getSceneInfoTool.js'; +import { registerGetPlayModeStatusTool } from './tools/getPlayModeStatusTool.js'; +import { registerUnloadSceneTool } from './tools/unloadSceneTool.js'; +import { registerRecompileScriptsTool } from './tools/recompileScriptsTool.js'; +import { registerGetGameObjectTool } from './tools/getGameObjectTool.js'; +import { registerTransformTools } from './tools/transformTools.js'; +import { registerCreateMaterialTool, registerAssignMaterialTool, registerModifyMaterialTool, registerGetMaterialInfoTool } from './tools/materialTools.js'; +import { registerDuplicateGameObjectTool, registerDeleteGameObjectTool, registerReparentGameObjectTool } from './tools/gameObjectTools.js'; +import { registerBatchExecuteTool } from './tools/batchExecuteTool.js'; +import { registerShowUnityDashboardTool } from './tools/showUnityDashboardTool.js'; +import { registerGetMenuItemsResource } from './resources/getMenuItemResource.js'; +import { registerGetConsoleLogsResource } from './resources/getConsoleLogsResource.js'; +import { registerGetHierarchyResource } from './resources/getScenesHierarchyResource.js'; +import { registerGetPackagesResource } from './resources/getPackagesResource.js'; +import { registerGetAssetsResource } from './resources/getAssetsResource.js'; +import { registerGetTestsResource } from './resources/getTestsResource.js'; +import { registerGetGameObjectResource } from './resources/getGameObjectResource.js'; +import { registerUnityDashboardAppResource } from './resources/unityDashboardAppResource.js'; +import { registerGameObjectHandlingPrompt } from './prompts/gameobjectHandlingPrompt.js'; + +// Initialize loggers +const serverLogger = new Logger('Server', LogLevel.INFO); +const unityLogger = new Logger('Unity', LogLevel.INFO); +const toolLogger = new Logger('Tools', LogLevel.INFO); +const resourceLogger = new Logger('Resources', LogLevel.INFO); + +// Initialize the MCP server +const server = new McpServer ( + { + name: "MCP Unity Server", + version: "1.0.0" + }, + { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + } +); + +// Initialize MCP HTTP bridge with Unity editor +const mcpUnity = new McpUnity(unityLogger); + +// Register all tools into the MCP server +registerMenuItemTool(server, mcpUnity, toolLogger); +registerSelectGameObjectTool(server, mcpUnity, toolLogger); +registerAddPackageTool(server, mcpUnity, toolLogger); +registerRunTestsTool(server, mcpUnity, toolLogger); +registerSendConsoleLogTool(server, mcpUnity, toolLogger); +registerGetConsoleLogsTool(server, mcpUnity, toolLogger); +registerUpdateComponentTool(server, mcpUnity, toolLogger); +registerAddAssetToSceneTool(server, mcpUnity, toolLogger); +registerUpdateGameObjectTool(server, mcpUnity, toolLogger); +registerCreatePrefabTool(server, mcpUnity, toolLogger); +registerCreateSceneTool(server, mcpUnity, toolLogger); +registerDeleteSceneTool(server, mcpUnity, toolLogger); +registerLoadSceneTool(server, mcpUnity, toolLogger); +registerSaveSceneTool(server, mcpUnity, toolLogger); +registerGetSceneInfoTool(server, mcpUnity, toolLogger); +registerGetPlayModeStatusTool(server, mcpUnity, toolLogger); +registerShowUnityDashboardTool(server, toolLogger); +registerUnloadSceneTool(server, mcpUnity, toolLogger); +registerRecompileScriptsTool(server, mcpUnity, toolLogger); +registerGetGameObjectTool(server, mcpUnity, toolLogger); +registerTransformTools(server, mcpUnity, toolLogger); +registerDuplicateGameObjectTool(server, mcpUnity, toolLogger); +registerDeleteGameObjectTool(server, mcpUnity, toolLogger); +registerReparentGameObjectTool(server, mcpUnity, toolLogger); + +// Register Material Tools +registerCreateMaterialTool(server, mcpUnity, toolLogger); +registerAssignMaterialTool(server, mcpUnity, toolLogger); +registerModifyMaterialTool(server, mcpUnity, toolLogger); +registerGetMaterialInfoTool(server, mcpUnity, toolLogger); + +// Register Batch Execute Tool (high-priority for performance) +registerBatchExecuteTool(server, mcpUnity, toolLogger); + +// Register all resources into the MCP server +registerGetTestsResource(server, mcpUnity, resourceLogger); +registerGetGameObjectResource(server, mcpUnity, resourceLogger); +registerGetMenuItemsResource(server, mcpUnity, resourceLogger); +registerGetConsoleLogsResource(server, mcpUnity, resourceLogger); +registerGetHierarchyResource(server, mcpUnity, resourceLogger); +registerGetPackagesResource(server, mcpUnity, resourceLogger); +registerGetAssetsResource(server, mcpUnity, resourceLogger); +registerUnityDashboardAppResource(server, resourceLogger); + +// Register all prompts into the MCP server +registerGameObjectHandlingPrompt(server); + +// Server startup function +async function startServer() { + try { + // Initialize STDIO transport for MCP client communication + const stdioTransport = new StdioServerTransport(); + + // Connect the server to the transport + await server.connect(stdioTransport); + + serverLogger.info('MCP Server started'); + + // Get the client name from the MCP server + const clientName = server.server.getClientVersion()?.name || 'Unknown MCP Client'; + serverLogger.info(`Connected MCP client: ${clientName}`); + + // Start Unity Bridge connection with client name in headers + await mcpUnity.start(clientName); + + } catch (error) { + serverLogger.error('Failed to start server', error); + process.exit(1); + } +} + +// Graceful shutdown handler +let isShuttingDown = false; +async function shutdown() { + if (isShuttingDown) return; + isShuttingDown = true; + + try { + serverLogger.info('Shutting down...'); + await mcpUnity.stop(); + await server.close(); + } catch (error) { + // Ignore errors during shutdown + } + process.exit(0); +} + +// Start the server +startServer(); + +// Handle shutdown signals +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); +process.on('SIGHUP', shutdown); + +// Handle stdin close (when MCP client disconnects) +process.stdin.on('close', shutdown); +process.stdin.on('end', shutdown); +process.stdin.on('error', shutdown); + +// Handle uncaught exceptions - exit cleanly if it's just a closed pipe +process.on('uncaughtException', (error: NodeJS.ErrnoException) => { + // EPIPE/EOF errors are expected when the MCP client disconnects + if (error.code === 'EPIPE' || error.code === 'EOF' || error.code === 'ERR_USE_AFTER_CLOSE') { + shutdown(); + return; + } + serverLogger.error('Uncaught exception', error); + process.exit(1); +}); + +// Handle unhandled promise rejections +process.on('unhandledRejection', (reason) => { + serverLogger.error('Unhandled rejection', reason); + process.exit(1); +}); +``` + +##### Step 4 Verification Checklist +- [ ] In Unity, call the `get_play_mode_status` tool in MCP Inspector and confirm `isPlaying` toggles when entering Play mode. +- [ ] Open the dashboard and confirm the Play Mode card updates when switching modes. + +#### Step 4 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. + +#### Step 5: Update Documentation and Add Usage Guide +- [ ] Create the usage guide at [plans/unity-dashboard-mcp-app/usage.md](plans/unity-dashboard-mcp-app/usage.md). +- [ ] Copy and paste code below into [plans/unity-dashboard-mcp-app/usage.md](plans/unity-dashboard-mcp-app/usage.md): + +```markdown +# Unity Dashboard MCP App Usage + +## Requirements +- VS Code 1.109 or later +- MCP Unity server built with the dashboard resource +- Unity Editor running with MCP Unity server enabled + +## Open the Dashboard +1. Build the Node server: `cd Server~ && npm run build`. +2. Start the MCP Inspector or your MCP client. +3. Call the `show_unity_dashboard` tool. +4. The dashboard opens as a VS Code MCP App. + +## What You Can Do +- View the active scene name and root object count. +- Inspect a collapsible scene hierarchy tree. +- View Unity console logs with filtering by level. +- Use Play, Pause, and Step controls. +- Enable auto-refresh and tune the refresh interval. + +## Troubleshooting +- If the dashboard does not render, confirm VS Code is 1.109+ and the tool response includes metadata `{ "view": "mcp-app" }`. +- If scene data fails to load, verify Unity is connected and the MCP server logs show a healthy WebSocket connection. +``` + +- [ ] Update the tools and resources list in [AGENTS.md](AGENTS.md). +- [ ] Copy and paste code below into [AGENTS.md](AGENTS.md): + +```markdown +## MCP Unity — AI Agent Guide (MCP Package) + +### Purpose (what this repo is) +**MCP Unity** exposes Unity Editor capabilities to MCP-enabled clients by running: +- **Unity-side “client” (C# Editor scripts)**: a WebSocket server inside the Unity Editor that executes tools/resources. +- **Node-side “server” (TypeScript)**: an MCP stdio server that registers MCP tools/resources and forwards requests to Unity over WebSocket. + +### How it works (high-level data flow) +- **MCP client** ⇄ (stdio / MCP SDK) ⇄ **Node server** (`Server~/src/index.ts`) +- **Node server** ⇄ (WebSocket JSON-RPC-ish) ⇄ **Unity Editor** (`Editor/UnityBridge/McpUnityServer.cs` + `McpUnitySocketHandler.cs`) +- **Tool/Resource names must match exactly** across Node and Unity (typically `lower_snake_case`). + +### Key defaults & invariants +- **Unity WebSocket endpoint**: `ws://localhost:8090/McpUnity` by default. +- **Config file**: `ProjectSettings/McpUnitySettings.json` (written/read by Unity; read opportunistically by Node). +- **Execution thread**: Tool/resource execution is dispatched via `EditorCoroutineUtility` and runs on the **Unity main thread**. Keep synchronous work short; use async patterns for long work. + +### Repo layout (where to change what) +``` +/ +├── Editor/ # Unity Editor package code (C#) +│ ├── Tools/ # Tools (inherit McpToolBase) +│ ├── Resources/ # Resources (inherit McpResourceBase) +│ ├── UnityBridge/ # WebSocket server + message routing +│ ├── Services/ # Test/log services used by tools/resources +│ └── Utils/ # Shared helpers (config, logging, workspace integration) +├── Server~/ # Node MCP server (TypeScript, ESM) +│ ├── src/index.ts # Registers tools/resources/prompts with MCP SDK +│ ├── src/tools/ # MCP tool definitions (zod schema + handler) +│ ├── src/resources/ # MCP resource definitions +│ └── src/unity/mcpUnity.ts # WebSocket client that talks to Unity +└── server.json # MCP registry metadata (name/version/package) +``` + +### Quickstart (local dev) +- **Unity side** + - Open the Unity project that has this package installed. + - Ensure the server is running (auto-start is controlled by `McpUnitySettings.AutoStartServer`). + - Settings persist in `ProjectSettings/McpUnitySettings.json`. + +- **Node side (build)** + - `cd Server~ && npm run build` + - The MCP entrypoint is `Server~/build/index.js` (published as an MCP stdio server). + +- **Node side (debug/inspect)** + - `cd Server~ && npm run inspector` to use the MCP Inspector. + +### Configuration (Unity ↔ Node bridge) +The Unity settings file is the shared contract: +- **Path**: `ProjectSettings/McpUnitySettings.json` +- **Fields** + - **Port** (default **8090**): Unity WebSocket server port. + - **RequestTimeoutSeconds** (default **10**): Node request timeout (Node reads this if the settings file is discoverable). + - **AllowRemoteConnections** (default **false**): Unity binds to `0.0.0.0` when enabled; otherwise `localhost`. + - **EnableInfoLogs**: Unity console logging verbosity. + - **NpmExecutablePath**: optional npm path for Unity-driven install/build. + +Node reads config from `../ProjectSettings/McpUnitySettings.json` relative to **its current working directory**. If not found, Node falls back to: +- **host**: `localhost` +- **port**: `8090` +- **timeout**: `10s` + +**Remote connection note**: +- If Unity is on another machine, set `AllowRemoteConnections=true` in Unity and set `UNITY_HOST=` for the Node process. + +### Adding a new capability + +### Add a tool +1. **Unity (C#)** + - Add `Editor/Tools/Tool.cs` inheriting `McpToolBase`. + - Set `Name` to the MCP tool name (recommended: `lower_snake_case`). + - Implement: + - `Execute(JObject parameters)` for synchronous work, or + - set `IsAsync = true` and implement `ExecuteAsync(JObject parameters, TaskCompletionSource tcs)` for long-running operations. + - Register it in `Editor/UnityBridge/McpUnityServer.cs` (`RegisterTools()`). + +2. **Node (TypeScript)** + - Add `Server~/src/tools/Tool.ts`. + - Register the tool in `Server~/src/index.ts`. + - Use a zod schema for params; forward to Unity using the same `method` string: + - `mcpUnity.sendRequest({ method: toolName, params: {...} })` + +3. **Build** + - `cd Server~ && npm run build` + +### Add a resource +1. **Unity (C#)** + - Add `Editor/Resources/Resource.cs` inheriting `McpResourceBase`. + - Set `Name` (method string) and `Uri` (e.g. `unity://...`). + - Implement `Fetch(...)` or `FetchAsync(...)`. + - Register in `Editor/UnityBridge/McpUnityServer.cs` (`RegisterResources()`). + +2. **Node (TypeScript)** + - Add `Server~/src/resources/.ts`, register in `Server~/src/index.ts`. + - Forward to Unity via `mcpUnity.sendRequest({ method: resourceName, params: {} })`. + +### Logging & debugging +- **Unity** + - Uses `McpUnity.Utils.McpLogger` (info logs gated by `EnableInfoLogs`). + - Connection lifecycle is managed in `Editor/UnityBridge/McpUnityServer.cs` (domain reload & playmode transitions stop/restart the server). + +- **Node** + - Logging is controlled by env vars: + - `LOGGING=true` enables console logging. + - `LOGGING_FILE=true` writes `log.txt` in the Node process working directory. + +### Common pitfalls +- **Port mismatch**: Unity default is **8090**; update docs/config if you change it. +- **Name mismatch**: Node `toolName`/`resourceName` must equal Unity `Name` exactly, or Unity responds `unknown_method`. +- **Long main-thread work**: synchronous `Execute()` blocks the Unity editor; use async patterns for heavy operations. +- **Remote connections**: Unity must bind `0.0.0.0` (`AllowRemoteConnections=true`) and Node must target the correct host (`UNITY_HOST`). +- **Unity domain reload**: the server stops during script reloads and may restart; avoid relying on persistent in-memory state across reloads. +- **Multiplayer Play Mode**: Clone instances automatically skip server startup; only the main editor hosts the MCP server. + +### Release/version bump checklist +- Update versions consistently: + - Unity package `package.json` (`version`) + - Node server `Server~/package.json` (`version`) + - MCP registry `server.json` (`version` + npm identifier/version) +- Rebuild Node output: `cd Server~ && npm run build` + +### Available tools (current) +- `show_unity_dashboard` — Open the Unity dashboard MCP App in VS Code +- `execute_menu_item` — Execute Unity menu items +- `select_gameobject` — Select GameObjects in hierarchy +- `update_gameobject` — Update or create GameObject properties +- `update_component` — Update or add components on GameObjects +- `add_package` — Install packages via Package Manager +- `run_tests` — Run Unity Test Runner tests +- `send_console_log` — Send logs to Unity console +- `add_asset_to_scene` — Add assets to scene +- `create_prefab` — Create prefabs with optional scripts +- `create_scene` — Create and save new scenes +- `load_scene` — Load scenes (single or additive) +- `delete_scene` — Delete scenes and remove from Build Settings +- `save_scene` — Save current scene (with optional Save As) +- `get_scene_info` — Get active scene info and loaded scenes list +- `get_play_mode_status` — Get Unity play mode status +- `unload_scene` — Unload scene from hierarchy +- `get_gameobject` — Get detailed GameObject info +- `get_console_logs` — Retrieve Unity console logs +- `recompile_scripts` — Recompile all project scripts +- `duplicate_gameobject` — Duplicate GameObjects with optional rename/reparent +- `delete_gameobject` — Delete GameObjects from scene +- `reparent_gameobject` — Change GameObject parent in hierarchy +- `create_material` — Create materials with specified shader +- `assign_material` — Assign materials to Renderer components +- `modify_material` — Modify material properties (colors, floats, textures) +- `get_material_info` — Get material details including all properties + +### Available resources (current) +- `unity://ui/dashboard` — Unity dashboard MCP App UI +- `unity://menu-items` — List of available menu items +- `unity://scenes-hierarchy` — Current scene hierarchy +- `unity://gameobject/{id}` — GameObject details by ID or path +- `unity://logs` — Unity console logs +- `unity://packages` — Installed and available packages +- `unity://assets` — Asset database information +- `unity://tests/{testMode}` — Test Runner test information + +### Update policy (for agents) +- Update this file when: + - tools/resources/prompts are added/removed/renamed, + - config shape or default ports/paths change, + - the bridge protocol changes (request/response contract). +- Keep it **high-signal**: where to edit code, how to run/build/debug, and the invariants that prevent subtle breakage. +``` + +- [ ] Update the main README to include the MCP App dashboard tool and resource. +- [ ] Copy and paste code below into [README.md](README.md): + +```markdown +# MCP Unity Editor (Game Engine) + +[![](https://badge.mcpx.dev?status=on 'MCP Enabled')](https://modelcontextprotocol.io/introduction) +[![](https://img.shields.io/badge/Unity-000000?style=flat&logo=unity&logoColor=white 'Unity')](https://unity.com/releases/editor/archive) +[![](https://img.shields.io/badge/Node.js-339933?style=flat&logo=nodedotjs&logoColor=white 'Node.js')](https://nodejs.org/en/download/) +[![](https://img.shields.io/github/stars/CoderGamester/mcp-unity 'Stars')](https://github.com/CoderGamester/mcp-unity/stargazers) +[![](https://img.shields.io/github/last-commit/CoderGamester/mcp-unity 'Last Commit')](https://github.com/CoderGamester/mcp-unity/commits/main) +[![](https://img.shields.io/badge/License-MIT-red.svg 'MIT License')](https://opensource.org/licenses/MIT) + +| [🇺🇸English](README.md) | [🇨🇳简体中文](README_zh-CN.md) | [🇯🇵日本語](README-ja.md) | +|----------------------|---------------------------------|----------------------| + +``` + ,/(/. *(/, + */(((((/. *((((((*. + .*((((((((((/. *((((((((((/. + ./((((((((((((((/ *((((((((((((((/, + ,/(((((((((((((/*. */(((((((((((((/*. + ,%%#((/((((((* ,/(((((/(#&@@( + ,%%##%%##((((((/*. ,/((((/(#&@@@@@@( + ,%%######%%##((/(((/*. .*/(((//(%@@@@@@@@@@@( + ,%%####%#(%%#%%##((/((((((((//#&@@@@@@&@@@@@@@@( + ,%%####%( /#%#%%%##(//(#@@@@@@@%, #@@@@@@@( + ,%%####%( *#%###%@@@@@@( #@@@@@@@( + ,%%####%( #%#%@@@@, #@@@@@@@( + ,%%##%%%( #%#%@@@@, #@@@@@@@( + ,%%%#* #%#%@@@@, *%@@@( + ., ,/##*. #%#%@@@@, ./&@#* *` + ,/#%#####%%#/, #%#%@@@@, ,/&@@@@@@@@@&\. + `*#########%%%%###%@@@@@@@@@@@@@@@@@@&*´ + `*%%###########%@@@@@@@@@@@@@@&*´ + `*%%%######%@@@@@@@@@@&*´ + `*#%%##%@@@@@&*´ + `*%#%@&*´ + + ███╗ ███╗ ██████╗██████╗ ██╗ ██╗███╗ ██╗██╗████████╗██╗ ██╗ + ████╗ ████║██╔════╝██╔══██╗ ██║ ██║████╗ ██║██║╚══██╔══╝╚██╗ ██╔╝ + ██╔████╔██║██║ ██████╔╝ ██║ ██║██╔██╗ ██║██║ ██║ ╚████╔╝ + ██║╚██╔╝██║██║ ██╔═══╝ ██║ ██║██║╚██╗██║██║ ██║ ╚██╔╝ + ██║ ╚═╝ ██║╚██████╗██║ ╚██████╔╝██║ ╚████║██║ ██║ ██║ + ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ +``` + +MCP Unity is an implementation of the Model Context Protocol for Unity Editor, allowing AI assistants to interact with your Unity projects. This package provides a bridge between Unity and a Node.js server that implements the MCP protocol, enabling AI agents like Cursor, Windsurf, Claude Code, Codex CLI, GitHub Copilot, and Google Antigravity to execute operations within the Unity Editor. + +## Features + +### IDE Integration - Package Cache Access + +MCP Unity provides automatic integration with VSCode-like IDEs (Visual Studio Code, Cursor, Windsurf, Google Antigravity) by adding the Unity `Library/PackedCache` folder to your workspace. This feature: + +- Improves code intelligence for Unity packages +- Enables better autocompletion and type information for Unity packages +- Helps AI coding assistants understand your project's dependencies + +### MCP Server Tools + +The following tools are available for manipulating and querying Unity scenes and GameObjects via MCP: + +- `show_unity_dashboard`: Opens the Unity dashboard MCP App in VS Code + > **Example prompt:** "Open the Unity dashboard app" + +- `execute_menu_item`: Executes Unity menu items (functions tagged with the MenuItem attribute) + > **Example prompt:** "Execute the menu item 'GameObject/Create Empty' to create a new empty GameObject" + +- `select_gameobject`: Selects game objects in the Unity hierarchy by path or instance ID + > **Example prompt:** "Select the Main Camera object in my scene" + +- `update_gameobject`: Updates a GameObject's core properties (name, tag, layer, active/static state), or creates the GameObject if it does not exist + > **Example prompt:** "Set the Player object's tag to 'Enemy' and make it inactive" + +- `update_component`: Updates component fields on a GameObject or adds it to the GameObject if it does not contain the component + > **Example prompt:** "Add a Rigidbody component to the Player object and set its mass to 5" + +- `add_package`: Installs new packages in the Unity Package Manager + > **Example prompt:** "Add the TextMeshPro package to my project" + +- `run_tests`: Runs tests using the Unity Test Runner + > **Example prompt:** "Run all the EditMode tests in my project" + +- `send_console_log`: Send a console log to Unity + > **Example prompt:** "Send a console log to Unity Editor" + +- `add_asset_to_scene`: Adds an asset from the AssetDatabase to the Unity scene + > **Example prompt:** "Add the Player prefab from my project to the current scene" + +- `create_prefab`: Creates a prefab with optional MonoBehaviour script and serialized field values + > **Example prompt:** "Create a prefab named 'Player' from the 'PlayerController' script" + +- `create_scene`: Creates a new scene and saves it to the specified path + > **Example prompt:** "Create a new scene called 'Level1' in the Scenes folder" + +- `load_scene`: Loads a scene by path or name, with optional additive loading + > **Example prompt:** "Load the MainMenu scene" + +- `delete_scene`: Deletes a scene by path or name and removes it from Build Settings + > **Example prompt:** "Delete the old TestScene from my project" + +- `get_gameobject`: Gets detailed information about a specific GameObject including all components + > **Example prompt:** "Get the details of the Player GameObject" + +- `get_console_logs`: Retrieves logs from the Unity console with pagination support + > **Example prompt:** "Show me the last 20 error logs from the Unity console" + +- `recompile_scripts`: Recompiles all scripts in the Unity project + > **Example prompt:** "Recompile scripts in my Unity project" + +- `save_scene`: Saves the current active scene, with optional Save As to a new path + > **Example prompt:** "Save the current scene" or "Save the scene as 'Assets/Scenes/Level2.unity'" + +- `get_scene_info`: Gets information about the active scene including name, path, dirty state, and all loaded scenes + > **Example prompt:** "What scenes are currently loaded in my project?" + +- `get_play_mode_status`: Gets Unity play mode status + > **Example prompt:** "Is Unity in play mode?" + +- `unload_scene`: Unloads a scene from the hierarchy (does not delete the scene asset) + > **Example prompt:** "Unload the UI scene from the hierarchy" + +- `duplicate_gameobject`: Duplicates a GameObject in the scene with optional renaming and reparenting + > **Example prompt:** "Duplicate the Enemy prefab 5 times and rename them Enemy_1 through Enemy_5" + +- `delete_gameobject`: Deletes a GameObject from the scene + > **Example prompt:** "Delete the old Player object from the scene" + +- `reparent_gameobject`: Changes the parent of a GameObject in the hierarchy + > **Example prompt:** "Move the HealthBar object to be a child of the UI Canvas" + +- `move_gameobject`: Moves a GameObject to a new position (local or world space) + > **Example prompt:** "Move the Player object to position (10, 0, 5) in world space" + +- `rotate_gameobject`: Rotates a GameObject to a new rotation (local or world space, Euler angles or quaternion) + > **Example prompt:** "Rotate the Camera 45 degrees on the Y axis" + +- `scale_gameobject`: Scales a GameObject to a new local scale + > **Example prompt:** "Scale the Enemy object to twice its size" + +- `set_transform`: Sets position, rotation, and scale of a GameObject in a single operation + > **Example prompt:** "Set the Cube's position to (0, 5, 0), rotation to (0, 90, 0), and scale to (2, 2, 2)" + +- `create_material`: Creates a new material with specified shader and saves it to the project + > **Example prompt:** "Create a red material called 'EnemyMaterial' using the URP Lit shader" + +- `assign_material`: Assigns a material to a GameObject's Renderer component + > **Example prompt:** "Assign the 'EnemyMaterial' to the Enemy GameObject" + +- `modify_material`: Modifies properties of an existing material (colors, floats, textures) + > **Example prompt:** "Change the color of 'EnemyMaterial' to blue and set metallic to 0.8" + +- `get_material_info`: Gets detailed information about a material including shader and all properties + > **Example prompt:** "Show me all the properties of the 'PlayerMaterial'" + +- `batch_execute`: Executes multiple tool operations in a single batch request, reducing round-trips and enabling atomic operations with optional rollback on failure + > **Example prompt:** "Create 10 empty GameObjects named Enemy_1 through Enemy_10 in a single batch operation" + +### MCP Server Resources + +- `unity://ui/dashboard`: Unity dashboard MCP App UI + > **Example prompt:** "Open the Unity dashboard app" + +- `unity://menu-items`: Retrieves a list of all available menu items in the Unity Editor to facilitate `execute_menu_item` tool + > **Example prompt:** "Show me all available menu items related to GameObject creation" + +- `unity://scenes-hierarchy`: Retrieves a list of all game objects in the current Unity scene hierarchy + > **Example prompt:** "Show me the current scenes hierarchy structure" + +- `unity://gameobject/{id}`: Retrieves detailed information about a specific GameObject by instance ID or object path in the scene hierarchy, including all GameObject components with it's serialized properties and fields + > **Example prompt:** "Get me detailed information about the Player GameObject" + +- `unity://logs`: Retrieves a list of all logs from the Unity console + > **Example prompt:** "Show me the recent error messages from the Unity console" + +- `unity://packages`: Retrieves information about installed and available packages from the Unity Package Manager + > **Example prompt:** "List all the packages currently installed in my Unity project" + +- `unity://assets`: Retrieves information about assets in the Unity Asset Database + > **Example prompt:** "Find all texture assets in my project" + +- `unity://tests/{testMode}`: Retrieves information about tests in the Unity Test Runner + > **Example prompt:** "List all available tests in my Unity project" + +### MCP App Dashboard (VS Code 1.109+) + +The Unity dashboard MCP App provides a visual UI for inspecting the scene hierarchy, console logs, and play controls directly inside VS Code. + +1. Build the server with `cd Server~ && npm run build`. +2. Call `show_unity_dashboard` to open the app. +3. Use the Play, Pause, Step, Refresh, and Auto Refresh controls inside the dashboard. + +## Requirements +- Unity 6 or later - to [install the server](#install-server) +- Node.js 18 or later - to [start the server](#start-server) +- npm 9 or later - to [debug the server](#debug-server) + +> [!NOTE] +> **Project Paths with Spaces** +> +> MCP Unity supports project paths containing spaces. However, if you experience connection issues, try moving your project to a path without spaces as a troubleshooting step. +> +> **Examples:** +> - ✅ **Recommended:** `C:\Users\YourUser\Documents\UnityProjects\MyAwesomeGame` +> - ✅ **Supported:** `C:\Users\Your User\Documents\Unity Projects\My Awesome Game` +> +> MCP Unity can also install packages and dependencies automatically, saving time for AI agents: +> - Runs `npm install` and `npm run build` on initial setup +> - Adds the `Library/PackedCache` directory to the workspace for improved code intelligence +> - Handles configuration setup for MCP clients +> +> This is controlled in the Unity Editor under Tools > MCP Unity > Server Window + +## Installation + +Installing this MCP Unity Server is a multi-step process: + +### Step 1: Install Node.js +> To run MCP Unity server, you'll need to have Node.js 18 or later installed on your computer: + +![node](docs/node.jpg) + +
+Windows + +1. Visit the [Node.js download page](https://nodejs.org/en/download/) +2. Download the Windows Installer (.msi) for the LTS version (recommended) +3. Run the installer and follow the installation wizard +4. Verify the installation by opening PowerShell and running: + ```bash + node --version + ``` +
+ +
+macOS + +1. Visit the [Node.js download page](https://nodejs.org/en/download/) +2. Download the macOS Installer (.pkg) for the LTS version (recommended) +3. Run the installer and follow the installation wizard +4. Alternatively, if you have Homebrew installed, you can run: + ```bash + brew install node@18 + ``` +5. Verify the installation by opening Terminal and running: + ```bash + node --version + ``` +
+ +### Step 2: Install Unity MCP Server package via Unity Package Manager +1. Open the Unity Package Manager (Window > Package Manager) +2. Click the "+" button in the top-left corner +3. Select "Add package from git URL..." +4. Enter: `https://github.com/CoderGamester/mcp-unity.git` +5. Click "Add" + +![package manager](https://github.com/user-attachments/assets/a72bfca4-ae52-48e7-a876-e99c701b0497) + +### Step 3: Configure AI LLM Client + +
+Option 1: Configure using Unity Editor + +1. Open the Unity Editor +2. Navigate to Tools > MCP Unity > Server Window +3. Click on the "Configure" button for your AI LLM client as shown in the image below + +![image](docs/configure.jpg) + +4. Confirm the configuration installation with the given popup + +![image](https://github.com/user-attachments/assets/b1f05d33-3694-4256-a57b-8556005021ba) + +
+ +
+Option 2: Configure Manually + +Open the MCP configuration file of your AI client and add the MCP Unity server configuration: + +> Replace `ABSOLUTE/PATH/TO` with the absolute path to your MCP Unity installation or just copy the text from the Unity Editor MCP Server window (Tools > MCP Unity > Server Window). + +**For JSON-based clients** (Cursor, Windsurf, Claude Code, GitHub Copilot, etc.): + +```json +{ + "mcpServers": { + "unity": { + "command": "node", + "args": [ + "ABSOLUTE/PATH/TO/mcp-unity/Server~/build/index.js" + ] + } + } +} +``` + +**For TOML-based clients** (Cline, Roo Code, etc.): + +```toml +[mcpServers.unity] +command = "node" +args = ["ABSOLUTE/PATH/TO/mcp-unity/Server~/build/index.js"] +``` + +
+ +--- + +### Step 4: Start the MCP Unity Server + +#### Option 1: Start from Unity Editor +1. Open Unity Editor +2. Go to Tools > MCP Unity > Server Window +3. Click "Start Server" + +#### Option 2: Start from Terminal + +```bash +cd Server~ +npm install +npm run build +npm start +``` + +### Step 5: Start the MCP Client + +Now that the server is running, you can start using MCP Unity with your AI assistant. Ensure your MCP client is configured to use the MCP Unity server, then begin issuing commands. + +## Usage Examples + +### Example 1: Create a Scene + +You can ask your AI assistant to create a new Unity scene: + +> "Create a new Unity scene called 'Level1' in the Scenes folder" + +### Example 2: Create a GameObject + +You can ask your AI assistant to create a new GameObject in your Unity scene: + +> "Create a new empty GameObject named 'Player' and position it at (0, 1, 0)" + +### Example 3: Inspect GameObjects + +You can ask your AI assistant to inspect a specific GameObject: + +> "Show me details about the GameObject named 'Enemy'" + +### Example 4: Run Tests + +You can ask your AI assistant to run tests using the Unity Test Runner: + +> "Run all the tests in my project" + +## Troubleshooting + +### Server Not Starting +- **Check if port 8090 is already in use**: Another application might be using the same port. +- **Verify that Node.js is installed correctly**: Ensure you can run `node` and `npm` commands in your terminal. +- **Check Unity console for errors**: The MCP Unity package logs errors to the Unity console if something fails. + +### MCP Client Not Connecting +- **Verify your MCP configuration**: Ensure your MCP client config includes the correct path to `Server~/build/index.js`. +- **Check firewall settings**: Ensure `ws://localhost:8090` is allowed through your firewall. +- **Check Unity server settings**: Ensure `McpUnitySettings.json` has the correct port and `AutoStartServer` is enabled. +- **Check for any errors in the server logs**: Set `LOGGING=true` to see detailed logs. + +--- + +If you run into issues, please open an issue on GitHub with your logs and configuration details. +``` + +##### Step 5 Verification Checklist +- [ ] Confirm the documentation lists `show_unity_dashboard`, `get_play_mode_status`, and `unity://ui/dashboard`. +- [ ] Follow [plans/unity-dashboard-mcp-app/usage.md](plans/unity-dashboard-mcp-app/usage.md) to open the dashboard. + +#### Step 5 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. diff --git a/plans/unity-dashboard-mcp-app/implementation.md.meta b/plans/unity-dashboard-mcp-app/implementation.md.meta new file mode 100644 index 00000000..08894083 --- /dev/null +++ b/plans/unity-dashboard-mcp-app/implementation.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 05bd24eedbb2d764795808c0d5e0332a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plans/unity-dashboard-mcp-app/plan.md b/plans/unity-dashboard-mcp-app/plan.md new file mode 100644 index 00000000..8d4b1aaa --- /dev/null +++ b/plans/unity-dashboard-mcp-app/plan.md @@ -0,0 +1,135 @@ +# Unity Dashboard MCP App + +**Branch:** `feature/unity-dashboard-mcp-app` +**Description:** Add visual UI dashboard for Unity scene inspection and control as an MCP App in VS Code + +## Goal +Implement an MCP App (VS Code 1.109+ feature) that provides a visual dashboard for inspecting Unity scenes and controlling playback directly within VS Code, eliminating the need for text-based queries to view scene state. + +## Prerequisites & Assumptions +- Will attempt with current MCP SDK version 1.7.0; may need upgrade if MCP Apps not supported +- VS Code 1.109+ required for MCP Apps feature +- Phase 1 features (confirmed): + - Scene hierarchy viewer (read-only tree view) + - Play/Pause/Step control buttons + - Console log display panel +- Update mechanism: Polling-based using existing MCP tool/resource pattern +- HTML served from separate file (`Server~/src/ui/unity-dashboard.html`) + +## Implementation Steps + +### Step 1: Create Dashboard UI Resource +**Files:** +- `Server~/src/resources/unityDashboardAppResource.ts` +- `Server~/src/ui/unity-dashboard.html` +- `Server~/src/index.ts` + +**What:** Create the HTML-based MCP App UI that displays Unity scene information and provides interactive controls. Register it as a resource with URI `unity://ui/dashboard` that returns HTML content with `text/html` MIME type. The HTML will use the MCP App SDK to communicate with the server and call existing tools. + +**Testing:** +- Build the server with `npm run build` +- Start MCP Inspector: `npm run inspector` +- Read the `unity://ui/dashboard` resource and verify HTML is returned +- Verify the HTML contains scene hierarchy, controls, and console log sections + +### Step 2: Create Tool to Trigger Dashboard +**Files:** +- `Server~/src/tools/showUnityDashboardTool.ts` +- `Server~/src/index.ts` + +**What:** Create a tool `show_unity_dashboard` that returns a resource reference with metadata `{ "view": "mcp-app" }` to tell VS Code to render the dashboard as an MCP App instead of inline text. + +**Testing:** +- Call the tool via MCP Inspector +- Verify response contains resource reference with metadata `{ "view": "mcp-app" }` +- Test in VS Code 1.109+ to verify dashboard opens as an app view +- If app view doesn't appear, may need to upgrade MCP SDK or adjust metadata format + +### Step 3: Implement Dashboard Frontend Logic +**Files:** +- `Server~/src/ui/unity-dashboard.html` + +**What:** Add JavaScript to the HTML that: +1. Uses MCP App SDK to call existing tools (`get_scene_info`, `unity://scenes-hierarchy` resource, `get_console_logs`) +2. Displays scene hierarchy as a collapsible tree view +3. Adds Play/Pause/Step buttons that call `execute_menu_item` with Unity menu paths +4. Shows console logs in a scrollable panel with filtering (info/warning/error) +5. Implements polling-based refresh (user can trigger manual refresh or enable auto-refresh with configurable interval) + +**Testing:** +- Open dashboard in VS Code via `show_unity_dashboard` tool +- Click Play button and verify Unity enters play mode +- Click Pause button and verify Unity exits play mode +- Click Step button and verify Unity advances one frame +- Click "Refresh" and verify scene hierarchy and console logs update +- Enable auto-refresh and verify data updates at specified interval +- Test with scenes containing 50-100 GameObjects +- Verify console log filtering works (show/hide info/warning/error) + +### Step 4: Add Play Mode Status Tool (Optional Enhancement) +**Files:** +- `Editor/Tools/GetPlayModeStatusTool.cs` +- `Editor/UnityBridge/McpUnityServer.cs` +- `Server~/src/tools/getPlayModeStatusTool.ts` +- `Server~/src/index.ts` + +**What:** Create a new Unity-side tool that returns `EditorApplication.isPlaying` status, since this isn't currently exposed. This enables the dashboard to show accurate play/pause button state. + +**Testing:** +- Call tool when Unity is in Edit mode (should return `isPlaying: false`) +- Call tool when Unity is in Play mode (should return `isPlaying: true`) +- Dashboard button UI should update to reflect current state + +### Step 5: Update Documentation +**Files:** +- `AGENTS.md` +- `README.md` +- `plans/unity-dashboard-mcp-app/usage.md` (new) + +**What:** +- Add `show_unity_dashboard` to available tools list in AGENTS.md +- Add `unity://ui/dashboard` to available resources list +- Add `get_play_mode_status` to available tools (if Step 4 implemented) +- Create usage guide showing how to open and use the dashboard +- Update README with MCP App feature section + +**Testing:** +- Verify documentation is accurate by following steps from scratch +- Test with a new Unity project to ensure instructions work + +## Technical Considerations + +### MCP App SDK Integration +The prompt references `@modelcontextprotocol/sdk-apps` package for client-side MCP communication. +Approach: +- Load from CDN in the HTML file (avoids build complexity) +- Use the SDK's client API to call MCP tools/resources from the dashboard +- Handle communication via `MCPApp` class as shown in prompt example + +### Resource Serving +Current MCP SDK `server.resource()` method returns `ReadResourceResult` which expects text content. For HTML: +- Use `mimeType: "text/html"` +- Read HTML from `Server~/src/ui/unity-dashboard.html` using `fs.readFileSync()` +- Import Node.js `fs` module in the resource handler +- Path resolution: use `import.meta.url` or `__dirname` equivalent for ESM + +### Security & Sandboxing +VS Code MCP Apps run in sandboxed iframes with CSP restrictions: +- No inline event handlers (use `addEventListener` instead of `onclick`) +- External scripts must be from trusted CDNs +- Test CSP compliance during implementation; adjust HTML if needed +- VS Code CSP policies to be verified during testing phase + +### Unity WebSocket Connection +Dashboard HTML runs in VS Code (browser context), not Node.js: +- Cannot directly connect WebSocket to Unity +- Must proxy all Unity calls through MCP tools/resources +- This is the expected pattern per the prompt + +## Future Enhancements (Post Phase 1) +1. GameObject selection sync (click in dashboard → select in Unity Editor) +2. GameObject inspector panel (view properties when selected) +3. Performance profiling panel (FPS, memory usage) +4. Asset browser integration +5. Custom C# script execution panel +6. WebSocket push notifications for real-time updates (eliminate polling) diff --git a/plans/unity-dashboard-mcp-app/plan.md.meta b/plans/unity-dashboard-mcp-app/plan.md.meta new file mode 100644 index 00000000..d912b7aa --- /dev/null +++ b/plans/unity-dashboard-mcp-app/plan.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d91411c997e787a4786782b9ff8bdfb2 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plans/unity-dashboard-mcp-app/prompt.md b/plans/unity-dashboard-mcp-app/prompt.md new file mode 100644 index 00000000..d2f8a62f --- /dev/null +++ b/plans/unity-dashboard-mcp-app/prompt.md @@ -0,0 +1,141 @@ +Since you’re building on top of **CoderGamester/mcp-unity**, you already have a solid foundation. That repository handles the heavy lifting of the **JSON-RPC** communication between the AI and the Unity Editor. + +To turn this into an **MCP App** (the visual UI component in VS Code 1.109), you need to bridge the "Server" (which talks to Unity) with a "Webview" (which displays the UI in the chat). + +### The Architecture + +--- + +## Step 1: Define the UI Tool in the Server + +In the `mcp-unity` server code (usually in `index.ts` depending on the fork), you need to register a tool that returns a **Resource**. This resource tells VS Code to render your App. + +Add a tool like `get_unity_dashboard`: + +```typescript +// If using the TypeScript version of mcp-unity +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "show_unity_dashboard", + description: "Opens a visual dashboard to inspect the Unity scene and control playback.", + inputSchema: { type: "object", properties: {} } + } + ] + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name === "show_unity_dashboard") { + return { + content: [ + { + type: "resource", + uri: "unity://ui/dashboard", + metadata: { "view": "mcp-app" } // This tells VS Code 1.109 to treat this as an App + } + ] + }; + } +}); + +``` + +--- + +## Step 2: Create the MCP App (The Frontend) + +You need an HTML file that acts as your visual interface. VS Code will load this in a sandboxed iframe. Create a file named `unity-dashboard.html` in your project. + +```html + + + + + + + +

Unity Control Panel

+
Connecting to Unity...
+
+ + + +
    + + + + + +``` + +--- + +## Step 3: Register the App Resource + +You must tell the server to "serve" this HTML file when the URI `unity://ui/dashboard` is requested. + +```typescript +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + if (request.params.uri === "unity://ui/dashboard") { + const htmlContent = fs.readFileSync('./unity-dashboard.html', 'utf8'); + return { + contents: [{ + uri: request.params.uri, + mimeType: "text/html", + text: htmlContent + }] + }; + } +}); + +``` + +--- + +## Step 4: Configuration in VS Code + +To see your new App in action, ensure your `mcp.json` (accessible via `MCP: Open User Configuration`) points to your modified server: + +```json +{ + "mcpServers": { + "unity-custom": { + "command": "node", + "args": ["Server~/build/index.js"], + "env": { + "UNITY_PORT": "8090" + } + } + } +} + +``` + +--- + +### Why this is better than just the Server: + +* **Zero-Latency Hierarchy:** Instead of asking the AI "What's in my scene?" and waiting for a text list, the App can fetch the hierarchy and display it as a clickable tree. +* **Visual Feedback:** You can see if Unity is in "Play Mode" at a glance without typing a command. +* **Custom Inspectors:** You can build sliders to adjust variables in real-time while the AI is writing code in the main editor window. \ No newline at end of file diff --git a/plans/unity-dashboard-mcp-app/prompt.md.meta b/plans/unity-dashboard-mcp-app/prompt.md.meta new file mode 100644 index 00000000..82a33844 --- /dev/null +++ b/plans/unity-dashboard-mcp-app/prompt.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 77a6088e3df4ee64ebb0fdd7e6e93f97 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plans/unity-dashboard-mcp-app/usage.md b/plans/unity-dashboard-mcp-app/usage.md new file mode 100644 index 00000000..88410179 --- /dev/null +++ b/plans/unity-dashboard-mcp-app/usage.md @@ -0,0 +1,23 @@ +# Unity Dashboard MCP App Usage + +## Requirements +- VS Code 1.109 or later +- MCP Unity server built with the dashboard resource +- Unity Editor running with MCP Unity server enabled + +## Open the Dashboard +1. Build the Node server: `cd Server~ && npm run build`. +2. Start the MCP Inspector or your MCP client. +3. Call the `show_unity_dashboard` tool. +4. The dashboard opens as a VS Code MCP App. + +## What You Can Do +- View the active scene name and root object count. +- Inspect a collapsible scene hierarchy tree. +- View Unity console logs with filtering by level. +- Use Play, Pause, and Step controls. +- Enable auto-refresh and tune the refresh interval. + +## Troubleshooting +- If the dashboard does not render, confirm VS Code is 1.109+ and the tool response includes metadata `{ "view": "mcp-app" }`. +- If scene data fails to load, verify Unity is connected and the MCP server logs show a healthy WebSocket connection. diff --git a/plans/unity-dashboard-mcp-app/usage.md.meta b/plans/unity-dashboard-mcp-app/usage.md.meta new file mode 100644 index 00000000..ed834191 --- /dev/null +++ b/plans/unity-dashboard-mcp-app/usage.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7e60d601e9f1e6643874971f1088cde4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From b27566940f971e919a1f6439aa1cff73cae5b1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Voigt?= Date: Tue, 10 Feb 2026 12:33:33 +0100 Subject: [PATCH 2/9] implement Bidirectional Context --- README.md | 4 + .../resources/unityDashboardAppResource.ts | 8 +- Server~/src/tools/showUnityDashboardTool.ts | 14 + Server~/src/ui/unity-dashboard.html | 722 +++++++-- plans/mcp-app-improvements.meta | 8 + plans/mcp-app-improvements/implementation.md | 1410 +++++++++++++++++ .../implementation.md.meta | 7 + plans/mcp-app-improvements/plan.md | 292 ++++ plans/mcp-app-improvements/plan.md.meta | 7 + plans/mcp-app-improvements/prompt.md | 65 + plans/mcp-app-improvements/prompt.md.meta | 7 + .../mcp-app-improvements/research-findings.md | 612 +++++++ .../research-findings.md.meta | 7 + 13 files changed, 3071 insertions(+), 92 deletions(-) create mode 100644 plans/mcp-app-improvements.meta create mode 100644 plans/mcp-app-improvements/implementation.md create mode 100644 plans/mcp-app-improvements/implementation.md.meta create mode 100644 plans/mcp-app-improvements/plan.md create mode 100644 plans/mcp-app-improvements/plan.md.meta create mode 100644 plans/mcp-app-improvements/prompt.md create mode 100644 plans/mcp-app-improvements/prompt.md.meta create mode 100644 plans/mcp-app-improvements/research-findings.md create mode 100644 plans/mcp-app-improvements/research-findings.md.meta diff --git a/README.md b/README.md index 5d4e4315..ff9bca77 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ */(((((/. *((((((*. .*((((((((((/. *((((((((((/. ./((((((((((((((/ *((((((((((((((/, + +### Keeping the dashboard visible (VS Code) + +In VS Code, MCP Apps can be opened as a persistent editor tab. ,/(((((((((((((/*. */(((((((((((((/*. ,%%#((/((((((* ,/(((((/(#&@@( ,%%##%%##((((((/*. ,/((((/(#&@@@@@@( diff --git a/Server~/src/resources/unityDashboardAppResource.ts b/Server~/src/resources/unityDashboardAppResource.ts index 1e5ba024..f40320b3 100644 --- a/Server~/src/resources/unityDashboardAppResource.ts +++ b/Server~/src/resources/unityDashboardAppResource.ts @@ -65,7 +65,13 @@ function readDashboardHtml(uriOverride?: string): ReadResourceResult { // For hosts that still look for legacy view hints. view: 'mcp-app', ui: { - prefersBorder: true + prefersBorder: true, + // Some hosts (notably VS Code webviews) apply a strict CSP by default. + // The dashboard UI is currently shipped as a single HTML file with inline + // @@ -36,6 +48,7 @@

    Unity Dashboard

    + @@ -51,192 +64,712 @@

    Unity Dashboard

    Scene
    -
    Loading scene info…
    +
    Loading scene info...
    Hierarchy
    -
    Loading hierarchy…
    +
    Loading hierarchy...
    Play Mode
    -
    +
    +
    + Edit + Edit mode +
    +
    + Idle + Compilation idle + +
    +
    Console Logs
    -
    Loading logs…
    +
    Loading logs...
    diff --git a/plans/mcp-app-improvements.meta b/plans/mcp-app-improvements.meta new file mode 100644 index 00000000..ded94baf --- /dev/null +++ b/plans/mcp-app-improvements.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e3885a4abcd1bc64e85ab643579ca66f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plans/mcp-app-improvements/implementation.md b/plans/mcp-app-improvements/implementation.md new file mode 100644 index 00000000..b90b791e --- /dev/null +++ b/plans/mcp-app-improvements/implementation.md @@ -0,0 +1,1410 @@ +# MCP App Bidirectional Context Improvements + +## Goal +Enable the Unity dashboard MCP app to push selection, play mode, console, scene, compilation, and sync context to the agent, with optional inspector focus context once the inspector resource is added. + +## Prerequisites +Make sure that the use is currently on the `feature/mcp-app-bidirectional-context` branch before beginning implementation. +If not, move them to the correct branch. If the branch does not exist, create it from main. + +### Step-by-Step Instructions + +#### Step 1: Update Dashboard App Context Push (Steps 1-7) +- [x] Update the Unity dashboard HTML to initialize the MCP App SDK, add context helpers/state, selection tracking, play mode updates, console error tracking, sync button, scene change notifications, and compilation status indicators. +- [x] Copy and paste code below into [Server~/src/ui/unity-dashboard.html](Server~/src/ui/unity-dashboard.html): + +```html + + + + + + Unity Dashboard + + + +
    +
    +

    Unity Dashboard

    +
    +
    + + + + + + + + 3s +
    +
    +
    + +
    +
    +
    +
    Scene
    +
    Loading scene info...
    +
    + +
    +
    Hierarchy
    +
    Loading hierarchy...
    +
    +
    + +
    +
    +
    Play Mode
    +
    +
    + Edit + Edit mode +
    +
    + Idle + Compilation idle + +
    +
    +
    + +
    +
    Console Logs
    +
    Loading logs...
    +
    +
    +
    +
    + + + + + +``` + +- [ ] Open the dashboard once to confirm the automatic sync runs without errors. + +##### Step 1 Verification Checklist +- [ ] No build errors +- [ ] Open the Unity dashboard and confirm the Sync button appears. +- [ ] Click a GameObject in the hierarchy and confirm the selection highlight appears. +- [ ] Enter/exit play mode and confirm the play mode badge updates. +- [ ] Trigger a warning or error and confirm the console section updates. + +#### Step 1 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. + +#### Step 2: Add Inspector Focus Resource (Step 8) +- [ ] Create the Unity inspector state resource. +- [ ] Copy and paste code below into [Editor/Resources/GetInspectorStateResource.cs](Editor/Resources/GetInspectorStateResource.cs): + +```csharp +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using UnityEditor; +using UnityEngine; + +namespace McpUnity.Resources +{ + /// + /// Resource for retrieving the current Inspector focus (selected GameObject and component). + /// + public class GetInspectorStateResource : McpResourceBase + { + public GetInspectorStateResource() + { + Name = "get_inspector_state"; + Description = "Retrieves the active Inspector selection and focused component"; + Uri = "unity://inspector_state"; + } + + public override JObject Fetch(JObject parameters) + { + GameObject activeGameObject = Selection.activeGameObject; + JObject activeObjectPayload = null; + + if (activeGameObject != null) + { + activeObjectPayload = new JObject + { + ["name"] = activeGameObject.name, + ["instanceId"] = activeGameObject.GetInstanceID(), + ["path"] = GetHierarchyPath(activeGameObject.transform) + }; + } + + string focusedComponent = GetFocusedComponentType(); + + return new JObject + { + ["success"] = true, + ["message"] = "Retrieved inspector state", + ["activeGameObject"] = activeObjectPayload, + ["focusedComponent"] = focusedComponent + }; + } + + private static string GetHierarchyPath(Transform transform) + { + if (transform == null) return null; + + List segments = new List(); + Transform current = transform; + while (current != null) + { + segments.Add(current.name); + current = current.parent; + } + segments.Reverse(); + + return string.Join("/", segments); + } + + private static string GetFocusedComponentType() + { + ActiveEditorTracker tracker = ActiveEditorTracker.sharedTracker; + if (tracker == null) return null; + + Editor[] editors = tracker.activeEditors; + if (editors == null || editors.Length == 0) return null; + + foreach (Editor editor in editors) + { + if (editor == null) continue; + if (editor.target is Component component) + { + return component.GetType().Name; + } + if (editor.target is GameObject) + { + return "GameObject"; + } + } + + return null; + } + } +} +``` + +- [ ] Replace the `RegisterResources()` method in [Editor/UnityBridge/McpUnityServer.cs](Editor/UnityBridge/McpUnityServer.cs) with the code below: + +```csharp + private void RegisterResources() + { + // Register GetMenuItemsResource + GetMenuItemsResource getMenuItemsResource = new GetMenuItemsResource(); + _resources.Add(getMenuItemsResource.Name, getMenuItemsResource); + + // Register GetConsoleLogsResource + GetConsoleLogsResource getConsoleLogsResource = new GetConsoleLogsResource(_consoleLogsService); + _resources.Add(getConsoleLogsResource.Name, getConsoleLogsResource); + + // Register GetScenesHierarchyResource + GetScenesHierarchyResource getScenesHierarchyResource = new GetScenesHierarchyResource(); + _resources.Add(getScenesHierarchyResource.Name, getScenesHierarchyResource); + + // Register GetPackagesResource + GetPackagesResource getPackagesResource = new GetPackagesResource(); + _resources.Add(getPackagesResource.Name, getPackagesResource); + + // Register GetAssetsResource + GetAssetsResource getAssetsResource = new GetAssetsResource(); + _resources.Add(getAssetsResource.Name, getAssetsResource); + + // Register GetTestsResource + GetTestsResource getTestsResource = new GetTestsResource(_testRunnerService); + _resources.Add(getTestsResource.Name, getTestsResource); + + // Register GetGameObjectResource + GetGameObjectResource getGameObjectResource = new GetGameObjectResource(); + _resources.Add(getGameObjectResource.Name, getGameObjectResource); + + // Register GetInspectorStateResource + GetInspectorStateResource getInspectorStateResource = new GetInspectorStateResource(); + _resources.Add(getInspectorStateResource.Name, getInspectorStateResource); + } +``` + +- [ ] Create the Node resource definition. +- [ ] Copy and paste code below into [Server~/src/resources/getInspectorStateResource.ts](Server~/src/resources/getInspectorStateResource.ts): + +```typescript +import { Logger } from '../utils/logger.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; +import { McpUnity } from '../unity/mcpUnity.js'; +import { McpUnityError, ErrorType } from '../utils/errors.js'; + +const resourceName = 'get_inspector_state'; +const resourceUri = 'unity://inspector_state'; +const resourceMimeType = 'application/json'; + +export function registerGetInspectorStateResource(server: McpServer, mcpUnity: McpUnity, logger: Logger) { + logger.info(`Registering resource: ${resourceName}`); + + server.resource( + resourceName, + resourceUri, + { + description: 'Retrieve the active Inspector selection and focused component in Unity', + mimeType: resourceMimeType + }, + async () => { + try { + return await resourceHandler(mcpUnity); + } catch (error) { + logger.error(`Error handling resource ${resourceName}: ${error}`); + throw error; + } + } + ); +} + +async function resourceHandler(mcpUnity: McpUnity): Promise { + const response = await mcpUnity.sendRequest({ + method: resourceName, + params: {} + }); + + if (!response.success) { + throw new McpUnityError( + ErrorType.RESOURCE_FETCH, + response.message || 'Failed to fetch inspector state from Unity' + ); + } + + return { + contents: [{ + uri: resourceUri, + mimeType: resourceMimeType, + text: JSON.stringify(response, null, 2) + }] + }; +} +``` + +- [ ] Register the resource in [Server~/src/index.ts](Server~/src/index.ts) by adding the import and registration call below. +- [ ] Add the import with the other resource imports: + +```typescript +import { registerGetInspectorStateResource } from './resources/getInspectorStateResource.js'; +``` + +- [ ] Add the registration with the other resource registrations (after `registerGetGameObjectResource(...)` is a good place): + +```typescript +registerGetInspectorStateResource(server, mcpUnity, resourceLogger); +``` + +- [ ] Replace the dashboard HTML with the inspector-aware version by copying the code below into [Server~/src/ui/unity-dashboard.html](Server~/src/ui/unity-dashboard.html): + +```html + + + + + + Unity Dashboard + + + +
    +
    +

    Unity Dashboard

    +
    +
    + + + + + + + + 3s +
    +
    +
    + +
    +
    +
    +
    Scene
    +
    Loading scene info...
    +
    + +
    +
    Hierarchy
    +
    Loading hierarchy...
    +
    +
    + +
    +
    +
    Play Mode
    +
    +
    + Edit + Edit mode +
    +
    + Idle + Compilation idle + +
    +
    +
    + +
    +
    Console Logs
    +
    Loading logs...
    +
    +
    +
    +
    + + + + + +``` + +- [ ] Open Unity once to generate the new .meta file for the inspector resource and include it in source control. + +##### Step 2 Verification Checklist +- [ ] No build errors +- [ ] Open the dashboard and select a GameObject in the Unity Inspector. +- [ ] Confirm the focused GameObject is highlighted in the dashboard hierarchy. +- [ ] Ask the agent to modify the focused component and confirm it targets the correct GameObject. + +#### Step 2 STOP & COMMIT +**STOP & COMMIT:** Agent must stop here and wait for the user to test, stage, and commit the change. diff --git a/plans/mcp-app-improvements/implementation.md.meta b/plans/mcp-app-improvements/implementation.md.meta new file mode 100644 index 00000000..75a10149 --- /dev/null +++ b/plans/mcp-app-improvements/implementation.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4defc3f2b16c8f74db33a2d9de63deb3 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plans/mcp-app-improvements/plan.md b/plans/mcp-app-improvements/plan.md new file mode 100644 index 00000000..8a3f75c4 --- /dev/null +++ b/plans/mcp-app-improvements/plan.md @@ -0,0 +1,292 @@ +# MCP App Bidirectional Context Improvements + +**Branch:** `feature/mcp-app-bidirectional-context` +**Description:** Transform Unity dashboard from polling-based to context-aware by implementing push notifications using updateModelContext() + +## Goal +Enable the Unity dashboard MCP app to proactively push context to the agent, allowing natural interactions like "add a script to this" without explicitly naming GameObjects. This eliminates the need for agents to poll Unity state and creates a shared-workspace model where the app and agent maintain synchronized knowledge of the Unity editor state. + +## Background +Currently, the dashboard uses a polling-based "pull" model where it fetches Unity state every 1-10 seconds. The MCP App SDK provides `updateModelContext()` API that allows pushing context directly to the agent's short-term memory. This plan implements Phase 1 (dashboard-side improvements) that require no Unity C# changes. + +## Implementation Steps + +### Step 1: Initialize MCP App SDK and Context Infrastructure +**Files:** +- `Editor/Resources/GetUnityDashboardResource.cs` (read-only for reference) +- Dashboard HTML embedded in GetUnityDashboardResource + +**What:** Add proper MCPApp SDK initialization and create helper functions for context management. Replace manual JSON-RPC implementation with SDK methods where appropriate. Add global state tracking for active selection, play mode, and recent errors. + +**Details:** +- Import proper SDK types from `@modelcontextprotocol/ext-apps` +- Initialize `const app = new MCPApp()` early in script lifecycle +- Create `updateAgentContext(description, metadata)` helper function +- Add state variables: `activeGameObject`, `currentPlayMode`, `recentErrors`, `lastSceneNames`, `lastCompilationState`, `lastInspectorFocus` +- Track scene changes for pushing context updates +- Track compilation state for error notifications +- Track Inspector focus for context awareness + +**Testing:** Open dashboard, check browser console for SDK initialization messages, verify no errors in manual RPC calls + +--- + +### Step 2: Implement GameObject Selection and Context Pushing +**Files:** +- Dashboard HTML (hierarchy rendering section) + +**What:** Make hierarchy items clickable and push selection context to the agent when users click GameObjects. Store selected object ID/path in state and highlight the selected item visually. + +**Details:** +- Add click handlers to hierarchy list items (`
  • ` elements in `renderHierarchy()`) +- Style selected item with CSS highlight (background color change) +- On click: call `app.updateModelContext()` with: + ```javascript + { + description: `User selected GameObject "${name}" (ID: ${instanceId}) in Unity Dashboard.`, + activeGameObject: { id: instanceId, name: name, path: fullPath }, + recentAction: "selection" + } + ``` +- Store selection in global `activeGameObject` state +- Keep object selected on repeated clicks (no toggle/deselect behavior - repeated clicks maintain selection) +- Auto-sync full context on dashboard first open + +**Testing:** +1. Open dashboard with active Unity scene +2. Click various GameObjects in hierarchy +3. Ask agent: "What am I looking at?" or "Add a Rigidbody to this" +4. Verify agent references the selected object without prompting + +--- + +### Step 3: Implement Play Mode State Tracking +**Files:** +- Dashboard HTML (play mode status section) + +**What:** Push context updates when Unity enters/exits play mode. Since dashboard already polls play mode status, intercept status changes and push to agent. + +**Details:** +- Track previous play mode state in global variable `lastPlayModeState` +- On each `updatePlayMode()` call, compare new state with previous +- If changed, call `app.updateModelContext()` with: + ```javascript + { + description: `Unity ${isPlaying ? "entered Play mode" : "exited Play mode"}.`, + unityState: { + isPlaying: isPlaying, + isPaused: isPaused, + timestamp: Date.now() + } + } + ``` +- Update visual indicator (green/gray status badge) + +**Testing:** +1. Open dashboard with Unity editor visible +2. Enter play mode in Unity +3. Verify agent receives context update (check logs or ask agent "Is Unity playing?") +4. Exit play mode and verify state change is pushed + +--- + +### Step 4: Implement Console Error Tracking and Context Pushing +**Files:** +- Dashboard HTML (console logs section) + +**What:** Push context updates when new errors or warnings appear in Unity console. Track error count and push only new errors to avoid spam. + +**Details:** +- Track previous error count in global `lastErrorCount` +- On each `updateLogs()` call, compare current error count with previous +- If new errors detected, push top 3 most recent errors: + ```javascript + { + description: `${newErrorCount} new error(s) in Unity Console: ${errors.map(e => e.message).join('; ')}`, + consoleErrors: errors.slice(0, 3).map(e => ({ + message: e.message, + type: e.type, + timestamp: e.timestamp + })), + recentAction: "console-error" + } + ``` +- Push both warnings and errors (filter by severity) +- Group by severity: errors first, then warnings +- Display visual indicators (red for errors, orange for warnings) + +**Testing:** +1. Open dashboard +2. Trigger an error in Unity (e.g., broken script compilation) +3. Verify agent receives error context +4. Ask agent: "What's wrong in Unity?" +5. Verify agent references the specific error without querying + +--- + +### Step 5: Add "Sync to Agent" Button +**Files:** +- Dashboard HTML (add new UI section) + +**What:** Add a prominent button that gathers comprehensive Unity state and pushes it to agent all at once. This "primes" the agent with full context on demand. + +**Details:** +- Add button in header: `` +- On click, gather: + - Current scene name and loaded scene count + - Active GameObject (if any) + - Total GameObject count in scene + - Top 3 console errors/warnings + - Play mode state + - Last update timestamp +- Push comprehensive context: + ```javascript + { + description: `Unity Dashboard synchronized. Scene: ${sceneName}, ${gameObjectCount} objects, ${errorCount} errors.`, + sceneInfo: { name, loadedScenes, rootObjectCount }, + activeGameObject: activeGameObject || null, + playMode: { isPlaying, isPaused }, + topErrors: errors.slice(0, 3), + syncTimestamp: Date.now() + } + ``` +- Automatically trigger sync on dashboard first open (call sync function in `init()`) +- Allow manual re-sync via button for subsequent state updates + +**Testing:** +1. Open dashboard with complex scene +2. Click "Sync to Agent" button +3. Ask agent: "What's the current Unity state?" or "Summarize my scene" +4. Verify agent has complete awareness of scene, errors, play mode +5. Test with empty scene and verify graceful handling + +--- +# Step 6: Implement Scene Change Notifications +**Files:** +- Dashboard HTML (hierarchy update section) + +**What:** Detect when scenes are loaded/unloaded and push context updates to agent. Track loaded scene names and push changes when they differ. + +**Details:** +- Store previous scene names in `lastSceneNames` array +- On each hierarchy update, extract loaded scene names +- Compare with previous state; if different, push context: + ```javascript + { + description: `Unity scene changed. Now loaded: ${sceneNames.join(', ')}`, + sceneChange: { + loaded: sceneNames, + added: newScenes, + removed: removedScenes + } + } + ``` +- Update scene display in dashboard header AND warnings +- ✅ Dashboard auto-syncs context on first open +- ✅ "Sync to Agent" button available for manual re-sync +- ✅ Agent notified of scene changes (load/unload) +- ✅ Agent aware of compilation status and errors +- ✅ Agent knows Inspector focus for targeted modifications +- ✅ Natural commands work: "Add Rigidbody to this", "Fix that error", "Modify this property" +- ✅ Minimal Unity C# changes (may need Inspector state resource for Step 8) + +--- + +## Implementation Notes + +**Step 8 Dependency:** Inspector focus tracking may require a new Unity resource (`GetInspectorStateResource`) to expose: +- Currently selected GameObject in Unity (not just dashboard) +- Active Inspector component focus +- This would be a small C# addition but provides high-value context + +**Alternative for Step 8:** If Unity C# changes are not desired, skip Inspector focus tracking in Phase 1 and defer to Phase 3 with Unity-initiated push events. + +**Decisions Made:** +- Console logs: Push both errors AND warnings (grouped by severity) +- Auto-sync: Automatic on dashboard first open, manual button for re-sync +- Scene events: Track and push scene load/unload notifications (Step 6) +- Selection behavior: No toggle - clicking maintains selection state +**Details:** +- Parse console logs for compilation markers (errors starting with script names, "CompilerError") +- Track compilation state: `idle`, `compiling`, `success`, `failed` +- On state change, push context: + ```javascript + { + description: `Unity compilation ${state}. ${errorCount > 0 ? errorCount + ' errors' : 'No errors'}`, + compilation: { + state: state, + errorCount: errorCount, + timestamp: Date.now() + } + } + ``` +- Show compilation status indicator (animated spinner during compilation) + +**Testing:** +1. Open dashboard +2. Introduce syntax error in C# script +3. Verify agent receives compilation failure notification +4. Fix error and save +5. Verify agent receives compilation success notification +6. Ask agent: "Did my code compile?" + +--- + +### Step 8: Implement Inspector Focus Context +**Files:** +- Dashboard HTML (add inspector focus tracking) +- May require new Unity tool/resource for Inspector state + +**What:** Push context when user focuses on specific components in Unity Inspector. This helps agent understand what the user is examining or modifying. + +**Details:** +- Add new polling call to check Unity Inspector state (may need new Unity resource) +- Track last focused component type and GameObject +- On focus change, push context: + ```javascript + { + description: `User is inspecting ${componentType} on ${gameObjectName}`, + inspectorFocus: { + gameObject: { id, name, path }, + component: componentType, + timestamp: Date.now() + } + } + ``` +- Highlight focused GameObject in dashboard hierarchy + +**Testing:** +1. Open dashboard and Unity editor side-by-side +2. Select GameObject in Unity hierarchy +3. Click component in Inspector (e.g., Transform) +4. Verify dashboard highlights GameObject +5. Ask agent: "Modify this component's properties" +6. Verify agent knows which component is in focus + +--- + +## Out of Scope (Future Phases) + +**Phase 2 - Enhanced Context:** +- Push component details when GameObject selected +- Build settings state +- Package manager state changes +- Asset import progres +- Package manager state changes + +**Phase 3 - Unity-Initiated Push:** +- Unity C# event hooks (`Selection.selectionChanged`, `EditorApplication.playModeStateChanged`) +- WebSocket push notifications from Unity to dashboard +- Real-time bidirectional communication without polling + +--- + +## Success Criteria + +After implementing this plan: +- ✅ User can click GameObjects in dashboard and agent knows selection +- ✅ Agent aware of play mode changes without explicit queries +- ✅ Agent automatically notified of new console errors +- ✅ "Sync to Agent" button provides instant comprehensive context +- ✅ Natural commands work: "Add Rigidbody to this", "Fix that error" +- ✅ Zero Unity C# code changes required (dashboard HTML only, except Step 8 if implemented) diff --git a/plans/mcp-app-improvements/plan.md.meta b/plans/mcp-app-improvements/plan.md.meta new file mode 100644 index 00000000..6682a941 --- /dev/null +++ b/plans/mcp-app-improvements/plan.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6863ae022c9090e4d8e4cf864eceb953 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plans/mcp-app-improvements/prompt.md b/plans/mcp-app-improvements/prompt.md new file mode 100644 index 00000000..d97d5656 --- /dev/null +++ b/plans/mcp-app-improvements/prompt.md @@ -0,0 +1,65 @@ +# Improve mcp app usage + +To improve the agentic workflow, we need to transition from the agent "polling" Unity to the App "pushing" context to the agent. + +--- + +### The Strategy: Bidirectional Context + +The key to making the agent "aware" of what's happening in your UI is the **`app.updateModelContext()`** method provided by the MCP App SDK. This allows your UI to inject data directly into the agent's short-term memory without the user having to type anything. + +#### 1. Implement "Push" Notifications in your HTML + +In your `mcp-app.html` (the dashboard), you should trigger context updates whenever the user interacts with the UI or when Unity state changes. + +```javascript +// Inside your