Skip to content
22 changes: 21 additions & 1 deletion MCPForUnity/Editor/Tools/CommandRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,21 @@ public static object ExecuteCommand(string commandName, JObject @params, TaskCom
throw new InvalidOperationException($"Handler for '{commandName}' does not provide a synchronous implementation");
}

return handlerInfo.SyncHandler(@params);
object result = handlerInfo.SyncHandler(@params);
// A synchronous handler may return a Task<object> to answer later, as manage_scene's
// play-mode screenshot does. Only Task<object> is awaited here: a handler that needs
// another Task type should be declared async, which registration handles.
if (result is Task<object> returnedTask)
{
ExecuteAsyncHandler(
new HandlerInfo(commandName, null, _ => returnedTask),
@params,
commandName,
tcs);
return null;
}

return result;
}

/// <summary>
Expand Down Expand Up @@ -322,6 +336,12 @@ public static Task<object> InvokeCommandAsync(string commandName, JObject @param
}

object result = handlerInfo.SyncHandler(payload);
// Same contract as ExecuteCommand: a returned Task<object> is the answer to await.
if (result is Task<object> returnedTask)
{
return returnedTask;
}

return Task.FromResult(result);
}

Expand Down
58 changes: 46 additions & 12 deletions MCPForUnity/Editor/Tools/ManageScene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MCPForUnity.Editor.Helpers; // For Response class
using MCPForUnity.Runtime.Helpers; // For ScreenshotUtility
using Newtonsoft.Json.Linq;
Expand Down Expand Up @@ -611,18 +612,9 @@ private static object CaptureScreenshot(SceneCommand cmd)
if (includeImage && Application.isPlaying)
{
if (!Application.isBatchMode) EnsureGameView();

string folderOverride = ScreenshotPreferences.Resolve(cmd.outputFolder);
ScreenshotCaptureResult result = ScreenshotUtility.CaptureComposited(
fileName, resolvedSuperSize, ensureUniqueFileName: true,
includeImage: true, maxResolution: maxResolution,
folderOverride: folderOverride);

if (ScreenshotUtility.IsUnderAssets(result.ProjectRelativePath))
AssetDatabase.ImportAsset(result.ProjectRelativePath, ImportAssetOptions.ForceSynchronousImport);
string cameraName = Camera.main != null ? Camera.main.name : "composited";
string message = $"Screenshot captured to '{result.ProjectRelativePath}' (camera: {cameraName}).";
return new SuccessResponse(message, BuildScreenshotResponseData(result, cameraName, includeImage: true));
// A Task<object>, not a response: the capture waits for the end of the frame,
// and CommandRegistry awaits a Task<object> that a synchronous handler returns.
return CaptureCompositedScreenshotAsync(cmd, fileName, resolvedSuperSize, maxResolution);
}

if (includeImage)
Expand Down Expand Up @@ -751,9 +743,51 @@ private static Dictionary<string, object> BuildScreenshotResponseData(
data["imageHeight"] = result.ImageHeight;
}

if (result.FallbackReason != null)
{
data["captureSource"] = "camera_fallback";
data["fallbackReason"] = result.FallbackReason;
}

return data;
}

private static async Task<object> CaptureCompositedScreenshotAsync(
SceneCommand cmd,
string fileName,
int resolvedSuperSize,
int maxResolution)
{
string folderOverride = ScreenshotPreferences.Resolve(cmd.outputFolder);
ScreenshotCaptureResult result;
try
{
result = await ScreenshotUtility.CaptureCompositedAsync(
fileName, resolvedSuperSize, ensureUniqueFileName: true,
includeImage: true, maxResolution: maxResolution,
folderOverride: folderOverride).ConfigureAwait(true);
}
catch (TimeoutException ex)
{
return new ErrorResponse(ex.Message);
}
catch (InvalidOperationException ex)
{
return new ErrorResponse(ex.Message);
}

if (ScreenshotUtility.IsUnderAssets(result.ProjectRelativePath))
AssetDatabase.ImportAsset(result.ProjectRelativePath, ImportAssetOptions.ForceSynchronousImport);

// A fallback names the camera that actually rendered, which need not be Camera.main.
string cameraName = result.FallbackCameraName
?? (Camera.main != null ? Camera.main.name : "composited");
string message = $"Screenshot captured to '{result.ProjectRelativePath}' (camera: {cameraName}).";
if (result.FallbackReason != null)
message += " " + result.FallbackReason;
return new SuccessResponse(message, BuildScreenshotResponseData(result, cameraName, includeImage: true));
}

private static object CaptureSceneViewScreenshot(
SceneCommand cmd,
string fileName,
Expand Down
8 changes: 8 additions & 0 deletions MCPForUnity/Editor/Tools/ManageUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,14 @@ private static object RenderUI(JObject @params)
playFullPath = EnsureUniqueFilePath(playFullPath);
string playProjectRelPath = ScreenshotUtility.ToProjectRelativePath(playFullPath);

if (s_pendingCaptureDone && s_pendingCaptureTex == null)
{
s_pendingCaptureDone = false;
s_pendingCaptureStarted = false;
return new ErrorResponse(
"Play-mode screenshot timed out or captured nothing. Keep the Game view visible and the editor unpaused.");
}

// ── Case 1: capture is ready ──────────────────────────────────────
if (s_pendingCaptureDone && s_pendingCaptureTex != null)
{
Expand Down
Loading
Loading