@atelierai/uco 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +7 -8
  3. package/bin/server.mjs +1 -1
  4. package/bin/stub-plugin.mjs +3 -3
  5. package/dist/codegen/coerce.js +1 -1
  6. package/dist/codegen/coerce.js.map +1 -1
  7. package/dist/codegen/emit.js +2 -2
  8. package/dist/codegen/emit.js.map +1 -1
  9. package/dist/codegen/refs.js +1 -1
  10. package/dist/codegen/refs.js.map +1 -1
  11. package/dist/codegen/types.js +1 -1
  12. package/dist/codegen/types.js.map +1 -1
  13. package/dist/commands/call.js +1 -1
  14. package/dist/commands/call.js.map +1 -1
  15. package/dist/commands/devops/configure.js +1 -1
  16. package/dist/commands/devops/configure.js.map +1 -1
  17. package/dist/commands/devops/index.js +0 -4
  18. package/dist/commands/devops/index.js.map +1 -1
  19. package/dist/commands/devops/install-plugin.js +1 -1
  20. package/dist/commands/devops/install-plugin.js.map +1 -1
  21. package/dist/commands/devops/open.js +7 -7
  22. package/dist/commands/devops/open.js.map +1 -1
  23. package/dist/commands/devops/status.js +3 -3
  24. package/dist/commands/devops/status.js.map +1 -1
  25. package/dist/commands/exec.js +1 -1
  26. package/dist/commands/exec.js.map +1 -1
  27. package/dist/commands/install.js +2 -2
  28. package/dist/commands/install.js.map +1 -1
  29. package/dist/commands/prompt.js +2 -2
  30. package/dist/commands/prompt.js.map +1 -1
  31. package/dist/commands/resource.js +2 -2
  32. package/dist/commands/resource.js.map +1 -1
  33. package/dist/commands/update.js +1 -26
  34. package/dist/commands/update.js.map +1 -1
  35. package/dist/config/port.js +2 -2
  36. package/dist/config/port.js.map +1 -1
  37. package/dist/config/resolve.d.ts +1 -2
  38. package/dist/config/resolve.js +0 -12
  39. package/dist/config/resolve.js.map +1 -1
  40. package/dist/devops/lib/configure.d.ts +1 -1
  41. package/dist/devops/lib/configure.js +2 -2
  42. package/dist/devops/lib/configure.js.map +1 -1
  43. package/dist/devops/lib/install-plugin.d.ts +1 -1
  44. package/dist/devops/lib/install-plugin.js +1 -1
  45. package/dist/devops/lib/install-plugin.js.map +1 -1
  46. package/dist/devops/lib/install.d.ts +2 -2
  47. package/dist/devops/lib/install.js +5 -5
  48. package/dist/devops/lib/install.js.map +1 -1
  49. package/dist/devops/lib/open.js +11 -32
  50. package/dist/devops/lib/open.js.map +1 -1
  51. package/dist/devops/lib/remove-plugin.d.ts +1 -1
  52. package/dist/devops/lib/remove-plugin.js +3 -3
  53. package/dist/devops/lib/remove-plugin.js.map +1 -1
  54. package/dist/devops/lib/run-tool.d.ts +2 -2
  55. package/dist/devops/lib/run-tool.js +2 -2
  56. package/dist/devops/lib/types.d.ts +17 -54
  57. package/dist/devops/lib/types.js +2 -2
  58. package/dist/devops/lib/types.js.map +1 -1
  59. package/dist/devops/lib/update.d.ts +0 -15
  60. package/dist/devops/lib/update.js +2 -91
  61. package/dist/devops/lib/update.js.map +1 -1
  62. package/dist/devops/utils/agents.d.ts +0 -28
  63. package/dist/devops/utils/agents.js +0 -462
  64. package/dist/devops/utils/agents.js.map +1 -1
  65. package/dist/devops/utils/config.d.ts +9 -18
  66. package/dist/devops/utils/config.js +7 -20
  67. package/dist/devops/utils/config.js.map +1 -1
  68. package/dist/devops/utils/editor-cache.js +1 -1
  69. package/dist/devops/utils/editor-cache.js.map +1 -1
  70. package/dist/devops/utils/launch-error-dismiss.d.ts +2 -2
  71. package/dist/devops/utils/launch-error-dismiss.js +5 -5
  72. package/dist/devops/utils/manifest.d.ts +2 -2
  73. package/dist/devops/utils/manifest.js +3 -3
  74. package/dist/devops/utils/manifest.js.map +1 -1
  75. package/dist/devops/utils/port.d.ts +1 -1
  76. package/dist/devops/utils/port.js +1 -1
  77. package/dist/devops/utils/probe.d.ts +1 -1
  78. package/dist/devops/utils/probe.js +1 -1
  79. package/dist/devops/utils/ui.js +2 -2
  80. package/dist/devops/utils/ui.js.map +1 -1
  81. package/dist/devops/utils/unity-editor.js +4 -4
  82. package/dist/devops/utils/unity-editor.js.map +1 -1
  83. package/dist/devops/utils/unity-hub.js +1 -1
  84. package/dist/devops/utils/unity-hub.js.map +1 -1
  85. package/dist/devops/utils/unity-shutdown.d.ts +1 -1
  86. package/dist/devops/utils/unity-shutdown.js +1 -1
  87. package/dist/devops/utils/update-check.js +3 -3
  88. package/dist/devops/utils/update-check.js.map +1 -1
  89. package/dist/devops/utils/vendor.d.ts +2 -2
  90. package/dist/devops/utils/vendor.js +4 -4
  91. package/dist/devops/utils/vendor.js.map +1 -1
  92. package/dist/generated/tools.js +1 -1
  93. package/dist/generated/tools.js.map +1 -1
  94. package/dist/program.js +3 -3
  95. package/dist/program.js.map +1 -1
  96. package/dist/server/app.d.ts +5 -5
  97. package/dist/server/app.js +3 -3
  98. package/dist/server/app.js.map +1 -1
  99. package/dist/server/index.d.ts +2 -2
  100. package/dist/server/index.js +2 -2
  101. package/dist/server/index.js.map +1 -1
  102. package/dist/server/rest/auth.d.ts +1 -1
  103. package/dist/server/rest/auth.js +3 -3
  104. package/dist/server/rest/auth.js.map +1 -1
  105. package/dist/server/rest/context.d.ts +2 -2
  106. package/dist/server/rest/forward.d.ts +2 -2
  107. package/dist/server/session/store.d.ts +3 -3
  108. package/dist/server/session/store.js +4 -4
  109. package/dist/server/session/store.js.map +1 -1
  110. package/dist/server/stub-client/scenarios.js +8 -8
  111. package/dist/server/stub-client/scenarios.js.map +1 -1
  112. package/dist/server/types.d.ts +11 -11
  113. package/dist/server/types.js +9 -9
  114. package/dist/server/types.js.map +1 -1
  115. package/dist/server/ws/hub.d.ts +8 -8
  116. package/dist/server/ws/hub.js +13 -13
  117. package/dist/server/ws/hub.js.map +1 -1
  118. package/dist/server/ws/rpc.d.ts +3 -3
  119. package/dist/server/ws/rpc.js +4 -4
  120. package/dist/server/ws/rpc.js.map +1 -1
  121. package/dist/transport/index.d.ts +1 -1
  122. package/dist/transport/index.js +3 -3
  123. package/dist/transport/index.js.map +1 -1
  124. package/dist/transport/rest.js +1 -1
  125. package/dist/transport/rest.js.map +1 -1
  126. package/docs/INSTALL.zh-CN.md +2 -3
  127. package/package.json +1 -1
  128. package/skills/uco-setup/SKILL.md +1 -1
  129. package/skills/unity-editor/references/diagnostics.md +24 -24
  130. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Components/ExtensionPanel.cs +379 -379
  131. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/MenuItems.cs +72 -72
  132. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/UnityCopilotProjectSettingsProvider.cs +64 -64
  133. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotServer.cs +372 -372
  134. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/ReflectorNet.dll +0 -0
  135. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.Common.dll +0 -0
  136. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.dll +0 -0
  137. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +175 -175
  138. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +1 -1
  139. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +368 -368
  140. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +3 -3
  141. package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
  142. package/dist/commands/devops/login.d.ts +0 -2
  143. package/dist/commands/devops/login.js +0 -29
  144. package/dist/commands/devops/login.js.map +0 -1
  145. package/dist/commands/devops/setup-mcp.d.ts +0 -2
  146. package/dist/commands/devops/setup-mcp.js +0 -41
  147. package/dist/commands/devops/setup-mcp.js.map +0 -1
  148. package/dist/devops/commands-upstream/bootstrap-local.d.ts +0 -17
  149. package/dist/devops/commands-upstream/bootstrap-local.js +0 -87
  150. package/dist/devops/commands-upstream/bootstrap-local.js.map +0 -1
  151. package/dist/devops/commands-upstream/close.d.ts +0 -57
  152. package/dist/devops/commands-upstream/close.js +0 -188
  153. package/dist/devops/commands-upstream/close.js.map +0 -1
  154. package/dist/devops/commands-upstream/configure.d.ts +0 -2
  155. package/dist/devops/commands-upstream/configure.js +0 -87
  156. package/dist/devops/commands-upstream/configure.js.map +0 -1
  157. package/dist/devops/commands-upstream/create-project.d.ts +0 -2
  158. package/dist/devops/commands-upstream/create-project.js +0 -43
  159. package/dist/devops/commands-upstream/create-project.js.map +0 -1
  160. package/dist/devops/commands-upstream/install-plugin.d.ts +0 -2
  161. package/dist/devops/commands-upstream/install-plugin.js +0 -64
  162. package/dist/devops/commands-upstream/install-plugin.js.map +0 -1
  163. package/dist/devops/commands-upstream/install-unity.d.ts +0 -2
  164. package/dist/devops/commands-upstream/install-unity.js +0 -72
  165. package/dist/devops/commands-upstream/install-unity.js.map +0 -1
  166. package/dist/devops/commands-upstream/login.d.ts +0 -2
  167. package/dist/devops/commands-upstream/login.js +0 -33
  168. package/dist/devops/commands-upstream/login.js.map +0 -1
  169. package/dist/devops/commands-upstream/open.d.ts +0 -32
  170. package/dist/devops/commands-upstream/open.js +0 -282
  171. package/dist/devops/commands-upstream/open.js.map +0 -1
  172. package/dist/devops/commands-upstream/remove-plugin.d.ts +0 -2
  173. package/dist/devops/commands-upstream/remove-plugin.js +0 -41
  174. package/dist/devops/commands-upstream/remove-plugin.js.map +0 -1
  175. package/dist/devops/commands-upstream/run-system-tool.d.ts +0 -1
  176. package/dist/devops/commands-upstream/run-system-tool.js +0 -13
  177. package/dist/devops/commands-upstream/run-system-tool.js.map +0 -1
  178. package/dist/devops/commands-upstream/run-tool-builder.d.ts +0 -25
  179. package/dist/devops/commands-upstream/run-tool-builder.js +0 -125
  180. package/dist/devops/commands-upstream/run-tool-builder.js.map +0 -1
  181. package/dist/devops/commands-upstream/run-tool.d.ts +0 -1
  182. package/dist/devops/commands-upstream/run-tool.js +0 -13
  183. package/dist/devops/commands-upstream/run-tool.js.map +0 -1
  184. package/dist/devops/commands-upstream/setup-mcp.d.ts +0 -2
  185. package/dist/devops/commands-upstream/setup-mcp.js +0 -68
  186. package/dist/devops/commands-upstream/setup-mcp.js.map +0 -1
  187. package/dist/devops/commands-upstream/setup-skills.d.ts +0 -2
  188. package/dist/devops/commands-upstream/setup-skills.js +0 -167
  189. package/dist/devops/commands-upstream/setup-skills.js.map +0 -1
  190. package/dist/devops/commands-upstream/status.d.ts +0 -2
  191. package/dist/devops/commands-upstream/status.js +0 -87
  192. package/dist/devops/commands-upstream/status.js.map +0 -1
  193. package/dist/devops/commands-upstream/update.d.ts +0 -2
  194. package/dist/devops/commands-upstream/update.js +0 -77
  195. package/dist/devops/commands-upstream/update.js.map +0 -1
  196. package/dist/devops/commands-upstream/wait-for-ready.d.ts +0 -2
  197. package/dist/devops/commands-upstream/wait-for-ready.js +0 -93
  198. package/dist/devops/commands-upstream/wait-for-ready.js.map +0 -1
  199. package/dist/devops/lib/setup-mcp.d.ts +0 -35
  200. package/dist/devops/lib/setup-mcp.js +0 -172
  201. package/dist/devops/lib/setup-mcp.js.map +0 -1
  202. package/dist/devops/utils/cloud-login.d.ts +0 -7
  203. package/dist/devops/utils/cloud-login.js +0 -56
  204. package/dist/devops/utils/cloud-login.js.map +0 -1
@@ -1,372 +1,372 @@
1
- /*
2
- ┌──────────────────────────────────────────────────────────────────┐
3
- │ Author: Ivan Murzak (https://github.com/IvanMurzak) │
4
- │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
5
- │ Copyright (c) 2025 Ivan Murzak │
6
- │ Licensed under the Apache License, Version 2.0. │
7
- │ See the LICENSE file in the project root for more information. │
8
- └──────────────────────────────────────────────────────────────────┘
9
- */
10
-
11
- #nullable enable
12
- using System;
13
- using System.Threading;
14
- using com.AtelierAI.Uco.Framework.Common.Model;
15
- using com.AtelierAI.Uco.Framework.Common.Utils;
16
- using com.IvanMurzak.ReflectorNet.Utils;
17
- using com.AtelierAI.Unity.Copilot.Editor.UI.Controls;
18
- using Microsoft.Extensions.Logging;
19
- using R3;
20
- using UnityEngine;
21
- using UnityEngine.UIElements;
22
- using static com.AtelierAI.Uco.Framework.Common.Consts.MCP.Server;
23
-
24
- namespace com.AtelierAI.Unity.Copilot.Editor.UI
25
- {
26
- public partial class MainWindowEditor
27
- {
28
- private void SetupMcpServerSection(VisualElement root)
29
- {
30
- var btnStartStop = root.Q<Button>("btnStartStopServer") ?? throw new InvalidOperationException("Server start/stop button not found.");
31
- var statusCircle = root.Q<VisualElement>("mcpServerStatusCircle") ?? throw new InvalidOperationException("Server status circle not found.");
32
- var statusLabel = root.Q<Label>("mcpServerLabel") ?? throw new InvalidOperationException("Server status label not found.");
33
-
34
- var timelinePointMcpServer = root.Q<VisualElement>("TimelinePointMcpServer");
35
- if (timelinePointMcpServer != null)
36
- timelinePointMcpServer.tooltip = Tooltip_McpServerTimelineLabel;
37
- statusCircle.tooltip = Tooltip_McpServerTimelineLabel;
38
- statusLabel.tooltip = Tooltip_McpServerTimelineLabel;
39
-
40
- Observable.CombineLatest(
41
- source1: CopilotServerManager.ServerStatus,
42
- source2: UnityCopilotPluginEditor.IsConnected,
43
- resultSelector: CombineCopilotServerStatus)
44
- .ThrottleLast(TimeSpan.FromMilliseconds(50))
45
- .ObserveOnCurrentSynchronizationContext()
46
- .Subscribe(status => FetchMcpServerData(status, btnStartStop, statusCircle, statusLabel))
47
- .AddTo(_disposables);
48
-
49
- btnStartStop.RegisterCallback<ClickEvent>(evt => HandleServerButton(btnStartStop, statusLabel));
50
-
51
- // server authorization configuration UI elements
52
- var labelAuthorizationToken = root.Q<Label>("labelAuthorizationToken");
53
- var segmentAuthorization = root.Q<VisualElement>("segmentAuthorization");
54
- var inputAuthorizationToken = root.Q<TextField>("inputAuthorizationToken");
55
- var tokenSection = root.Q<VisualElement>("tokenSection");
56
- var btnGenerateToken = root.Q<Button>("btnGenerateToken");
57
-
58
- if (segmentAuthorization == null || inputAuthorizationToken == null
59
- || tokenSection == null || btnGenerateToken == null)
60
- {
61
- Debug.LogError("One or more authorization UI elements not found in UXML: " +
62
- $"segmentAuthorization={segmentAuthorization != null}, " +
63
- $"inputAuthorizationToken={inputAuthorizationToken != null}, " +
64
- $"tokenSection={tokenSection != null}, " +
65
- $"btnGenerateToken={btnGenerateToken != null}");
66
- return;
67
- }
68
-
69
- var authControl = new SegmentedControl("none", "required");
70
- authControl.SetTooltips(Tooltip_ToggleAuthNone, Tooltip_ToggleAuthRequired);
71
- segmentAuthorization.Add(authControl);
72
-
73
- inputAuthorizationToken.isPasswordField = true;
74
- inputAuthorizationToken.RegisterCallback<KeyDownEvent>(evt =>
75
- {
76
- if (evt.keyCode == KeyCode.C && (evt.ctrlKey || evt.commandKey))
77
- {
78
- GUIUtility.systemCopyBuffer = inputAuthorizationToken.value;
79
- evt.StopPropagation();
80
- }
81
- });
82
-
83
- if (labelAuthorizationToken != null) labelAuthorizationToken.tooltip = Tooltip_LabelAuthorizationToken;
84
- btnGenerateToken.tooltip = Tooltip_BtnGenerateToken;
85
-
86
- var authOption = UnityCopilotPluginEditor.AuthOption;
87
- authControl.SetValueWithoutNotify(authOption == AuthOption.none ? 0 : 1);
88
- inputAuthorizationToken.SetValueWithoutNotify(UnityCopilotPluginEditor.Token ?? string.Empty);
89
- SetTokenFieldsVisible(inputAuthorizationToken, tokenSection, authOption == AuthOption.required);
90
-
91
- authControl.RegisterCallback<ChangeEvent<int>>(evt =>
92
- {
93
- if (evt.newValue == 0)
94
- {
95
- ApplyServerSettingAndRestart(() =>
96
- {
97
- UnityCopilotPluginEditor.AuthOption = AuthOption.none;
98
- });
99
- SetTokenFieldsVisible(inputAuthorizationToken, tokenSection, false);
100
- }
101
- else
102
- {
103
- ApplyServerSettingAndRestart(() =>
104
- {
105
- UnityCopilotPluginEditor.AuthOption = AuthOption.required;
106
- });
107
- SetTokenFieldsVisible(inputAuthorizationToken, tokenSection, true);
108
- }
109
- });
110
-
111
- inputAuthorizationToken.RegisterCallback<FocusOutEvent>(_ =>
112
- {
113
- var newToken = inputAuthorizationToken.value;
114
- if (newToken == UnityCopilotPluginEditor.Token)
115
- return;
116
-
117
- ApplyServerSettingAndRestart(() =>
118
- {
119
- UnityCopilotPluginEditor.Token = newToken;
120
- });
121
- });
122
-
123
- btnGenerateToken.RegisterCallback<ClickEvent>(_ =>
124
- {
125
- var newToken = UnityCopilotPlugin.GenerateToken();
126
- inputAuthorizationToken.SetValueWithoutNotify(newToken);
127
-
128
- ApplyServerSettingAndRestart(() =>
129
- {
130
- UnityCopilotPluginEditor.Token = newToken;
131
- });
132
- });
133
-
134
- // ── Network Safety (fail-closed by default) ─────────────────────────────
135
- // Build the UI programmatically and append it to the same flex column that
136
- // hosts tokenSection so we don't have to introduce a new UXML asset. The
137
- // three toggles map 1:1 to the static properties on UnityCopilotPluginEditor;
138
- // each change is persisted immediately so the next StartServer() call sees
139
- // the new state.
140
- BuildNetworkSafetySection(tokenSection.parent);
141
- }
142
-
143
- private static void BuildNetworkSafetySection(VisualElement parent)
144
- {
145
- if (parent == null) return;
146
- // If a previous CreateGUI cycle already added the section, recreate it idempotently
147
- // so the toggles always reflect the current persisted state.
148
- var existing = parent.Q<VisualElement>("networkSafetySection");
149
- if (existing != null) parent.Remove(existing);
150
-
151
- var section = new VisualElement { name = "networkSafetySection" };
152
- section.style.flexDirection = FlexDirection.Column;
153
- section.style.marginTop = 4;
154
- section.style.marginBottom = 2;
155
-
156
- var header = new Label("Network Safety");
157
- header.AddToClassList("section-desc");
158
- header.style.marginBottom = 2;
159
- header.tooltip =
160
- "Fail-closed defaults: by default the server only binds to localhost and " +
161
- "refuses to start on a non-loopback URL until you opt in here. Existing " +
162
- "configurations that already used a LAN address before this update were " +
163
- "auto-granted on first load so behaviour is preserved — a one-time warning " +
164
- "is written to the Editor console.";
165
- section.Add(header);
166
-
167
- var toggleAllowLan = new Toggle("Allow LAN Bind (HTTP)")
168
- {
169
- value = UnityCopilotPluginEditor.AllowLanBind,
170
- tooltip =
171
- "When OFF (safe default): the plugin refuses to start the server if " +
172
- "the Server URL points at 0.0.0.0, ::, or a non-loopback IP/hostname.\n\n" +
173
- "When ON: the plugin will start the server bound to whatever interface the " +
174
- "Server URL specifies. Other machines on your network may be able to reach " +
175
- "the server. Always pair this with a strong token."
176
- };
177
- toggleAllowLan.RegisterValueChangedCallback(evt =>
178
- {
179
- UnityCopilotPluginEditor.AllowLanBind = evt.newValue;
180
- UnityCopilotPluginEditor.Instance.Save();
181
- });
182
- section.Add(toggleAllowLan);
183
-
184
- var toggleAllowInsecure = new Toggle("Allow Insecure Remote HTTP")
185
- {
186
- value = UnityCopilotPluginEditor.AllowInsecureRemoteHttp,
187
- tooltip =
188
- "When OFF (safe default): the plugin logs a warning every time it emits an " +
189
- "AI-agent client configuration that points at a remote host over plain " +
190
- "http:// (no TLS).\n\n" +
191
- "When ON: the warning is silenced. Tokens and tool payloads sent over " +
192
- "plain HTTP are visible to anyone sniffing the network — only enable this " +
193
- "for trusted local networks where TLS is not available."
194
- };
195
- toggleAllowInsecure.RegisterValueChangedCallback(evt =>
196
- {
197
- UnityCopilotPluginEditor.AllowInsecureRemoteHttp = evt.newValue;
198
- UnityCopilotPluginEditor.Instance.Save();
199
- });
200
- section.Add(toggleAllowInsecure);
201
-
202
- var toggleForceToken = new Toggle("Force Token When LAN Bind")
203
- {
204
- value = UnityCopilotPluginEditor.ForceTokenWhenLanBind,
205
- tooltip =
206
- "When ON (safe default): enabling 'Allow LAN Bind' is not enough to start " +
207
- "the server — you must also set Authorization to 'required' and have a " +
208
- "non-empty token. Prevents accidental exposure of an unauthenticated " +
209
- "endpoint to the LAN.\n\n" +
210
- "When OFF: LAN-bound servers may run with auth=none. Not recommended."
211
- };
212
- toggleForceToken.RegisterValueChangedCallback(evt =>
213
- {
214
- UnityCopilotPluginEditor.ForceTokenWhenLanBind = evt.newValue;
215
- UnityCopilotPluginEditor.Instance.Save();
216
- });
217
- section.Add(toggleForceToken);
218
-
219
- parent.Add(section);
220
- }
221
-
222
- private void ApplyServerSettingAndRestart(Action applySetting)
223
- {
224
- var wasRunning = CopilotServerManager.IsRunning;
225
- applySetting();
226
- UnityCopilotPluginEditor.Instance.Save();
227
- RestartServerIfWasRunning(wasRunning);
228
- }
229
-
230
- internal static CopilotServerStatus CombineCopilotServerStatus(CopilotServerStatus status, bool isConnected)
231
- {
232
- if (isConnected && status != CopilotServerStatus.Running)
233
- return CopilotServerStatus.External;
234
-
235
- return status;
236
- }
237
-
238
- internal static string GetServerButtonText(CopilotServerStatus status) => status switch
239
- {
240
- CopilotServerStatus.Running => "Stop",
241
- CopilotServerStatus.Starting => "Starting...",
242
- CopilotServerStatus.Stopping => "Stopping...",
243
- CopilotServerStatus.External => "External",
244
- _ => "Start"
245
- };
246
-
247
- // The Node.js server exposes a single HTTP transport (REST + WebSocket), so the
248
- // status label no longer carries a transport-mode suffix.
249
- internal static string GetServerLabelText(CopilotServerStatus status) => status switch
250
- {
251
- CopilotServerStatus.Running => "Server: Running",
252
- CopilotServerStatus.Starting => "Server: Starting...",
253
- CopilotServerStatus.Stopping => "Server: Stopping...",
254
- CopilotServerStatus.External => "Server: External",
255
- _ => "Server"
256
- };
257
-
258
- internal static string GetServerStatusClass(CopilotServerStatus status) => status switch
259
- {
260
- CopilotServerStatus.Running => USS_Connected,
261
- CopilotServerStatus.Starting or CopilotServerStatus.Stopping => USS_Connecting,
262
- CopilotServerStatus.External => USS_External,
263
- _ => USS_Disconnected
264
- };
265
-
266
- internal static bool IsServerButtonEnabled(CopilotServerStatus status) =>
267
- status == CopilotServerStatus.Running || status == CopilotServerStatus.Stopped;
268
-
269
- private static void HandleServerButton(Button btnStartStop, Label statusLabel)
270
- {
271
- // Disable button immediately to prevent double-clicks
272
- btnStartStop.SetEnabled(false);
273
-
274
- try
275
- {
276
- if (CopilotServerManager.IsRunning)
277
- {
278
- // User is stopping the server - remember not to auto-start
279
- UnityCopilotPluginEditor.KeepServerRunning = false;
280
- UnityCopilotPluginEditor.Instance.Save();
281
- statusLabel.text = "Server: Stopping...";
282
- CopilotServerManager.StopServer();
283
- }
284
- else
285
- {
286
- // User is starting the server - remember to auto-start
287
- UnityCopilotPluginEditor.KeepServerRunning = true;
288
- UnityCopilotPluginEditor.Instance.Save();
289
- statusLabel.text = "Server: Starting...";
290
- CopilotServerManager.StartServer();
291
- }
292
- }
293
- catch
294
- {
295
- // Re-enable button on exception to avoid infinite lock
296
- btnStartStop.SetEnabled(true);
297
- throw;
298
- }
299
- }
300
-
301
- private long SetMcpServerData(UcoServerData? data, CopilotServerStatus status, Button btnStartStop, VisualElement statusCircle, Label statusLabel)
302
- {
303
- var version = Interlocked.Increment(ref _mcpServerDataVersion);
304
- if (Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Trace))
305
- Logger.LogTrace("Setting server data: {status}, Data: {data}", status, data?.ToPrettyJson() ?? "null");
306
-
307
- btnStartStop.text = GetServerButtonText(status);
308
- var isStart = status == CopilotServerStatus.Stopped;
309
- btnStartStop.EnableInClassList("btn-primary", isStart);
310
- btnStartStop.EnableInClassList("btn-secondary", !isStart);
311
- btnStartStop.SetEnabled(IsServerButtonEnabled(status));
312
- statusLabel.text = GetServerLabelText(status);
313
- SetStatusIndicator(statusCircle, GetServerStatusClass(status));
314
- return version;
315
- }
316
-
317
- private void FetchMcpServerData(CopilotServerStatus status, Button btnStartStop, VisualElement statusCircle, Label statusLabel)
318
- {
319
- // Update UI immediately with current status; capture the version atomically so that
320
- // the async result can detect if a newer update has superseded it.
321
- var fetchVersion = SetMcpServerData(null, status, btnStartStop, statusCircle, statusLabel);
322
-
323
- // Then try to fetch additional data asynchronously
324
- var mcpPluginInstance = UnityCopilotPluginEditor.Instance.UcoPluginInstance;
325
- if (mcpPluginInstance == null)
326
- {
327
- Logger.LogDebug("Cannot fetch server data: UcoPluginInstance is null");
328
- return;
329
- }
330
-
331
- var mcpManagerHub = mcpPluginInstance.UcoManagerHub;
332
- if (mcpManagerHub == null)
333
- {
334
- Logger.LogDebug("Cannot fetch server data: UcoManagerHub is null");
335
- return;
336
- }
337
-
338
- var task = mcpManagerHub.GetMcpServerData();
339
- if (task == null)
340
- {
341
- Logger.LogDebug("Cannot fetch server data: GetMcpServerData returned null");
342
- return;
343
- }
344
-
345
- task.ContinueWith(t =>
346
- {
347
- if (Interlocked.Read(ref _mcpServerDataVersion) != fetchVersion)
348
- {
349
- Logger.LogTrace("Skipping server data update because a newer update was applied at {time}",
350
- DateTime.UtcNow);
351
- return;
352
- }
353
- MainThread.Instance.Run(() =>
354
- {
355
- // Second check: close the TOCTOU window between the thread-pool check above
356
- // and the main-thread callback execution.
357
- if (Interlocked.Read(ref _mcpServerDataVersion) != fetchVersion)
358
- return;
359
- if (t.IsCompletedSuccessfully)
360
- {
361
- var data = t.Result;
362
- SetMcpServerData(data, status, btnStartStop, statusCircle, statusLabel);
363
- }
364
- else if (t.IsFaulted)
365
- {
366
- Logger.LogDebug("Failed to fetch server data: {error}", t.Exception?.Message ?? "Unknown error");
367
- }
368
- });
369
- });
370
- }
371
- }
372
- }
1
+ /*
2
+ ┌──────────────────────────────────────────────────────────────────┐
3
+ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │
4
+ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
5
+ │ Copyright (c) 2025 Ivan Murzak │
6
+ │ Licensed under the Apache License, Version 2.0. │
7
+ │ See the LICENSE file in the project root for more information. │
8
+ └──────────────────────────────────────────────────────────────────┘
9
+ */
10
+
11
+ #nullable enable
12
+ using System;
13
+ using System.Threading;
14
+ using com.AtelierAI.Uco.Framework.Common.Model;
15
+ using com.AtelierAI.Uco.Framework.Common.Utils;
16
+ using com.IvanMurzak.ReflectorNet.Utils;
17
+ using com.AtelierAI.Unity.Copilot.Editor.UI.Controls;
18
+ using Microsoft.Extensions.Logging;
19
+ using R3;
20
+ using UnityEngine;
21
+ using UnityEngine.UIElements;
22
+ using static com.AtelierAI.Uco.Framework.Common.Consts.MCP.Server;
23
+
24
+ namespace com.AtelierAI.Unity.Copilot.Editor.UI
25
+ {
26
+ public partial class MainWindowEditor
27
+ {
28
+ private void SetupMcpServerSection(VisualElement root)
29
+ {
30
+ var btnStartStop = root.Q<Button>("btnStartStopServer") ?? throw new InvalidOperationException("Server start/stop button not found.");
31
+ var statusCircle = root.Q<VisualElement>("mcpServerStatusCircle") ?? throw new InvalidOperationException("Server status circle not found.");
32
+ var statusLabel = root.Q<Label>("mcpServerLabel") ?? throw new InvalidOperationException("Server status label not found.");
33
+
34
+ var timelinePointMcpServer = root.Q<VisualElement>("TimelinePointMcpServer");
35
+ if (timelinePointMcpServer != null)
36
+ timelinePointMcpServer.tooltip = Tooltip_McpServerTimelineLabel;
37
+ statusCircle.tooltip = Tooltip_McpServerTimelineLabel;
38
+ statusLabel.tooltip = Tooltip_McpServerTimelineLabel;
39
+
40
+ Observable.CombineLatest(
41
+ source1: CopilotServerManager.ServerStatus,
42
+ source2: UnityCopilotPluginEditor.IsConnected,
43
+ resultSelector: CombineCopilotServerStatus)
44
+ .ThrottleLast(TimeSpan.FromMilliseconds(50))
45
+ .ObserveOnCurrentSynchronizationContext()
46
+ .Subscribe(status => FetchMcpServerData(status, btnStartStop, statusCircle, statusLabel))
47
+ .AddTo(_disposables);
48
+
49
+ btnStartStop.RegisterCallback<ClickEvent>(evt => HandleServerButton(btnStartStop, statusLabel));
50
+
51
+ // server authorization configuration UI elements
52
+ var labelAuthorizationToken = root.Q<Label>("labelAuthorizationToken");
53
+ var segmentAuthorization = root.Q<VisualElement>("segmentAuthorization");
54
+ var inputAuthorizationToken = root.Q<TextField>("inputAuthorizationToken");
55
+ var tokenSection = root.Q<VisualElement>("tokenSection");
56
+ var btnGenerateToken = root.Q<Button>("btnGenerateToken");
57
+
58
+ if (segmentAuthorization == null || inputAuthorizationToken == null
59
+ || tokenSection == null || btnGenerateToken == null)
60
+ {
61
+ Debug.LogError("One or more authorization UI elements not found in UXML: " +
62
+ $"segmentAuthorization={segmentAuthorization != null}, " +
63
+ $"inputAuthorizationToken={inputAuthorizationToken != null}, " +
64
+ $"tokenSection={tokenSection != null}, " +
65
+ $"btnGenerateToken={btnGenerateToken != null}");
66
+ return;
67
+ }
68
+
69
+ var authControl = new SegmentedControl("none", "required");
70
+ authControl.SetTooltips(Tooltip_ToggleAuthNone, Tooltip_ToggleAuthRequired);
71
+ segmentAuthorization.Add(authControl);
72
+
73
+ inputAuthorizationToken.isPasswordField = true;
74
+ inputAuthorizationToken.RegisterCallback<KeyDownEvent>(evt =>
75
+ {
76
+ if (evt.keyCode == KeyCode.C && (evt.ctrlKey || evt.commandKey))
77
+ {
78
+ GUIUtility.systemCopyBuffer = inputAuthorizationToken.value;
79
+ evt.StopPropagation();
80
+ }
81
+ });
82
+
83
+ if (labelAuthorizationToken != null) labelAuthorizationToken.tooltip = Tooltip_LabelAuthorizationToken;
84
+ btnGenerateToken.tooltip = Tooltip_BtnGenerateToken;
85
+
86
+ var authOption = UnityCopilotPluginEditor.AuthOption;
87
+ authControl.SetValueWithoutNotify(authOption == AuthOption.none ? 0 : 1);
88
+ inputAuthorizationToken.SetValueWithoutNotify(UnityCopilotPluginEditor.Token ?? string.Empty);
89
+ SetTokenFieldsVisible(inputAuthorizationToken, tokenSection, authOption == AuthOption.required);
90
+
91
+ authControl.RegisterCallback<ChangeEvent<int>>(evt =>
92
+ {
93
+ if (evt.newValue == 0)
94
+ {
95
+ ApplyServerSettingAndRestart(() =>
96
+ {
97
+ UnityCopilotPluginEditor.AuthOption = AuthOption.none;
98
+ });
99
+ SetTokenFieldsVisible(inputAuthorizationToken, tokenSection, false);
100
+ }
101
+ else
102
+ {
103
+ ApplyServerSettingAndRestart(() =>
104
+ {
105
+ UnityCopilotPluginEditor.AuthOption = AuthOption.required;
106
+ });
107
+ SetTokenFieldsVisible(inputAuthorizationToken, tokenSection, true);
108
+ }
109
+ });
110
+
111
+ inputAuthorizationToken.RegisterCallback<FocusOutEvent>(_ =>
112
+ {
113
+ var newToken = inputAuthorizationToken.value;
114
+ if (newToken == UnityCopilotPluginEditor.Token)
115
+ return;
116
+
117
+ ApplyServerSettingAndRestart(() =>
118
+ {
119
+ UnityCopilotPluginEditor.Token = newToken;
120
+ });
121
+ });
122
+
123
+ btnGenerateToken.RegisterCallback<ClickEvent>(_ =>
124
+ {
125
+ var newToken = UnityCopilotPlugin.GenerateToken();
126
+ inputAuthorizationToken.SetValueWithoutNotify(newToken);
127
+
128
+ ApplyServerSettingAndRestart(() =>
129
+ {
130
+ UnityCopilotPluginEditor.Token = newToken;
131
+ });
132
+ });
133
+
134
+ // ── Network Safety (fail-closed by default) ─────────────────────────────
135
+ // Build the UI programmatically and append it to the same flex column that
136
+ // hosts tokenSection so we don't have to introduce a new UXML asset. The
137
+ // three toggles map 1:1 to the static properties on UnityCopilotPluginEditor;
138
+ // each change is persisted immediately so the next StartServer() call sees
139
+ // the new state.
140
+ BuildNetworkSafetySection(tokenSection.parent);
141
+ }
142
+
143
+ private static void BuildNetworkSafetySection(VisualElement parent)
144
+ {
145
+ if (parent == null) return;
146
+ // If a previous CreateGUI cycle already added the section, recreate it idempotently
147
+ // so the toggles always reflect the current persisted state.
148
+ var existing = parent.Q<VisualElement>("networkSafetySection");
149
+ if (existing != null) parent.Remove(existing);
150
+
151
+ var section = new VisualElement { name = "networkSafetySection" };
152
+ section.style.flexDirection = FlexDirection.Column;
153
+ section.style.marginTop = 4;
154
+ section.style.marginBottom = 2;
155
+
156
+ var header = new Label("Network Safety");
157
+ header.AddToClassList("section-desc");
158
+ header.style.marginBottom = 2;
159
+ header.tooltip =
160
+ "Fail-closed defaults: by default the server only binds to localhost and " +
161
+ "refuses to start on a non-loopback URL until you opt in here. Existing " +
162
+ "configurations that already used a LAN address before this update were " +
163
+ "auto-granted on first load so behaviour is preserved — a one-time warning " +
164
+ "is written to the Editor console.";
165
+ section.Add(header);
166
+
167
+ var toggleAllowLan = new Toggle("Allow LAN Bind (HTTP)")
168
+ {
169
+ value = UnityCopilotPluginEditor.AllowLanBind,
170
+ tooltip =
171
+ "When OFF (safe default): the plugin refuses to start the server if " +
172
+ "the Server URL points at 0.0.0.0, ::, or a non-loopback IP/hostname.\n\n" +
173
+ "When ON: the plugin will start the server bound to whatever interface the " +
174
+ "Server URL specifies. Other machines on your network may be able to reach " +
175
+ "the server. Always pair this with a strong token."
176
+ };
177
+ toggleAllowLan.RegisterValueChangedCallback(evt =>
178
+ {
179
+ UnityCopilotPluginEditor.AllowLanBind = evt.newValue;
180
+ UnityCopilotPluginEditor.Instance.Save();
181
+ });
182
+ section.Add(toggleAllowLan);
183
+
184
+ var toggleAllowInsecure = new Toggle("Allow Insecure Remote HTTP")
185
+ {
186
+ value = UnityCopilotPluginEditor.AllowInsecureRemoteHttp,
187
+ tooltip =
188
+ "When OFF (safe default): the plugin logs a warning every time it emits an " +
189
+ "AI-agent client configuration that points at a remote host over plain " +
190
+ "http:// (no TLS).\n\n" +
191
+ "When ON: the warning is silenced. Tokens and tool payloads sent over " +
192
+ "plain HTTP are visible to anyone sniffing the network — only enable this " +
193
+ "for trusted local networks where TLS is not available."
194
+ };
195
+ toggleAllowInsecure.RegisterValueChangedCallback(evt =>
196
+ {
197
+ UnityCopilotPluginEditor.AllowInsecureRemoteHttp = evt.newValue;
198
+ UnityCopilotPluginEditor.Instance.Save();
199
+ });
200
+ section.Add(toggleAllowInsecure);
201
+
202
+ var toggleForceToken = new Toggle("Force Token When LAN Bind")
203
+ {
204
+ value = UnityCopilotPluginEditor.ForceTokenWhenLanBind,
205
+ tooltip =
206
+ "When ON (safe default): enabling 'Allow LAN Bind' is not enough to start " +
207
+ "the server — you must also set Authorization to 'required' and have a " +
208
+ "non-empty token. Prevents accidental exposure of an unauthenticated " +
209
+ "endpoint to the LAN.\n\n" +
210
+ "When OFF: LAN-bound servers may run with auth=none. Not recommended."
211
+ };
212
+ toggleForceToken.RegisterValueChangedCallback(evt =>
213
+ {
214
+ UnityCopilotPluginEditor.ForceTokenWhenLanBind = evt.newValue;
215
+ UnityCopilotPluginEditor.Instance.Save();
216
+ });
217
+ section.Add(toggleForceToken);
218
+
219
+ parent.Add(section);
220
+ }
221
+
222
+ private void ApplyServerSettingAndRestart(Action applySetting)
223
+ {
224
+ var wasRunning = CopilotServerManager.IsRunning;
225
+ applySetting();
226
+ UnityCopilotPluginEditor.Instance.Save();
227
+ RestartServerIfWasRunning(wasRunning);
228
+ }
229
+
230
+ internal static CopilotServerStatus CombineCopilotServerStatus(CopilotServerStatus status, bool isConnected)
231
+ {
232
+ if (isConnected && status != CopilotServerStatus.Running)
233
+ return CopilotServerStatus.External;
234
+
235
+ return status;
236
+ }
237
+
238
+ internal static string GetServerButtonText(CopilotServerStatus status) => status switch
239
+ {
240
+ CopilotServerStatus.Running => "Stop",
241
+ CopilotServerStatus.Starting => "Starting...",
242
+ CopilotServerStatus.Stopping => "Stopping...",
243
+ CopilotServerStatus.External => "External",
244
+ _ => "Start"
245
+ };
246
+
247
+ // The Node.js server exposes a single HTTP transport (REST + WebSocket), so the
248
+ // status label no longer carries a transport-mode suffix.
249
+ internal static string GetServerLabelText(CopilotServerStatus status) => status switch
250
+ {
251
+ CopilotServerStatus.Running => "Server: Running",
252
+ CopilotServerStatus.Starting => "Server: Starting...",
253
+ CopilotServerStatus.Stopping => "Server: Stopping...",
254
+ CopilotServerStatus.External => "Server: External",
255
+ _ => "Server"
256
+ };
257
+
258
+ internal static string GetServerStatusClass(CopilotServerStatus status) => status switch
259
+ {
260
+ CopilotServerStatus.Running => USS_Connected,
261
+ CopilotServerStatus.Starting or CopilotServerStatus.Stopping => USS_Connecting,
262
+ CopilotServerStatus.External => USS_External,
263
+ _ => USS_Disconnected
264
+ };
265
+
266
+ internal static bool IsServerButtonEnabled(CopilotServerStatus status) =>
267
+ status == CopilotServerStatus.Running || status == CopilotServerStatus.Stopped;
268
+
269
+ private static void HandleServerButton(Button btnStartStop, Label statusLabel)
270
+ {
271
+ // Disable button immediately to prevent double-clicks
272
+ btnStartStop.SetEnabled(false);
273
+
274
+ try
275
+ {
276
+ if (CopilotServerManager.IsRunning)
277
+ {
278
+ // User is stopping the server - remember not to auto-start
279
+ UnityCopilotPluginEditor.KeepServerRunning = false;
280
+ UnityCopilotPluginEditor.Instance.Save();
281
+ statusLabel.text = "Server: Stopping...";
282
+ CopilotServerManager.StopServer();
283
+ }
284
+ else
285
+ {
286
+ // User is starting the server - remember to auto-start
287
+ UnityCopilotPluginEditor.KeepServerRunning = true;
288
+ UnityCopilotPluginEditor.Instance.Save();
289
+ statusLabel.text = "Server: Starting...";
290
+ CopilotServerManager.StartServer();
291
+ }
292
+ }
293
+ catch
294
+ {
295
+ // Re-enable button on exception to avoid infinite lock
296
+ btnStartStop.SetEnabled(true);
297
+ throw;
298
+ }
299
+ }
300
+
301
+ private long SetMcpServerData(UcoServerData? data, CopilotServerStatus status, Button btnStartStop, VisualElement statusCircle, Label statusLabel)
302
+ {
303
+ var version = Interlocked.Increment(ref _mcpServerDataVersion);
304
+ if (Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Trace))
305
+ Logger.LogTrace("Setting server data: {status}, Data: {data}", status, data?.ToPrettyJson() ?? "null");
306
+
307
+ btnStartStop.text = GetServerButtonText(status);
308
+ var isStart = status == CopilotServerStatus.Stopped;
309
+ btnStartStop.EnableInClassList("btn-primary", isStart);
310
+ btnStartStop.EnableInClassList("btn-secondary", !isStart);
311
+ btnStartStop.SetEnabled(IsServerButtonEnabled(status));
312
+ statusLabel.text = GetServerLabelText(status);
313
+ SetStatusIndicator(statusCircle, GetServerStatusClass(status));
314
+ return version;
315
+ }
316
+
317
+ private void FetchMcpServerData(CopilotServerStatus status, Button btnStartStop, VisualElement statusCircle, Label statusLabel)
318
+ {
319
+ // Update UI immediately with current status; capture the version atomically so that
320
+ // the async result can detect if a newer update has superseded it.
321
+ var fetchVersion = SetMcpServerData(null, status, btnStartStop, statusCircle, statusLabel);
322
+
323
+ // Then try to fetch additional data asynchronously
324
+ var mcpPluginInstance = UnityCopilotPluginEditor.Instance.UcoPluginInstance;
325
+ if (mcpPluginInstance == null)
326
+ {
327
+ Logger.LogDebug("Cannot fetch server data: UcoPluginInstance is null");
328
+ return;
329
+ }
330
+
331
+ var mcpManagerHub = mcpPluginInstance.UcoManagerHub;
332
+ if (mcpManagerHub == null)
333
+ {
334
+ Logger.LogDebug("Cannot fetch server data: UcoManagerHub is null");
335
+ return;
336
+ }
337
+
338
+ var task = mcpManagerHub.GetServerData();
339
+ if (task == null)
340
+ {
341
+ Logger.LogDebug("Cannot fetch server data: GetServerData returned null");
342
+ return;
343
+ }
344
+
345
+ task.ContinueWith(t =>
346
+ {
347
+ if (Interlocked.Read(ref _mcpServerDataVersion) != fetchVersion)
348
+ {
349
+ Logger.LogTrace("Skipping server data update because a newer update was applied at {time}",
350
+ DateTime.UtcNow);
351
+ return;
352
+ }
353
+ MainThread.Instance.Run(() =>
354
+ {
355
+ // Second check: close the TOCTOU window between the thread-pool check above
356
+ // and the main-thread callback execution.
357
+ if (Interlocked.Read(ref _mcpServerDataVersion) != fetchVersion)
358
+ return;
359
+ if (t.IsCompletedSuccessfully)
360
+ {
361
+ var data = t.Result;
362
+ SetMcpServerData(data, status, btnStartStop, statusCircle, statusLabel);
363
+ }
364
+ else if (t.IsFaulted)
365
+ {
366
+ Logger.LogDebug("Failed to fetch server data: {error}", t.Exception?.Message ?? "Unknown error");
367
+ }
368
+ });
369
+ });
370
+ }
371
+ }
372
+ }