Skip to content

Commit c98534f

Browse files
committed
Fixed the issue preventing GetTestsResource to work
Added capacity for McpResources to work asynchronously
1 parent 73230c6 commit c98534f

File tree

7 files changed

+107
-39
lines changed

7 files changed

+107
-39
lines changed

.windsurfrules

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,21 @@
5555

5656
- MCP Protocol: https://modelcontextprotocol.io
5757
- TypeScript SDK: https://github.com/modelcontextprotocol/typescript-sdk
58+
- Inspector: https://github.com/modelcontextprotocol/inspector
5859

5960
## 9. Conventions
6061

6162
- Use WebSockets for all cross-process communication.
6263
- Follow the MCP protocol for all tool/resource definitions.
6364
- All new tools/resources should be registered in both Unity and Node.js server entry points.
64-
- Follow Conventional Commits for all commit messages.
65+
- Follow Conventional Commits for all commit messages.
66+
67+
## 10. Debugging with MCP Inspector
68+
69+
To debug the MCP Node.js server using the Model Context Protocol Inspector, run the following command from the project root:
70+
71+
```shell
72+
npx @modelcontextprotocol/inspector node Server/build/index.js
73+
```
74+
75+
This will launch the MCP Inspector, allowing you to inspect and debug live MCP traffic between the Node.js server and connected clients (such as Unity or LLM IDEs).
Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
using System;
2-
using System.Linq;
31
using System.Collections.Generic;
42
using System.Threading.Tasks;
5-
using UnityEngine;
6-
using Newtonsoft.Json.Linq;
7-
using McpUnity.Unity;
83
using McpUnity.Services;
4+
using Newtonsoft.Json.Linq;
95

106
namespace McpUnity.Resources
117
{
@@ -24,25 +20,23 @@ public GetTestsResource(ITestRunnerService testRunnerService)
2420
Name = "get_tests";
2521
Description = "Gets available tests from Unity Test Runner";
2622
Uri = "unity://tests/{testMode}";
27-
23+
IsAsync = true;
2824
_testRunnerService = testRunnerService;
2925
}
3026

3127
/// <summary>
32-
/// Fetch tests based on provided parameters
28+
/// Asynchronously fetch tests based on provided parameters
3329
/// </summary>
3430
/// <param name="parameters">Resource parameters as a JObject</param>
35-
public override JObject Fetch(JObject parameters)
31+
/// <param name="tcs">TaskCompletionSource to set the result or exception</param>
32+
public override async void FetchAsync(JObject parameters, TaskCompletionSource<JObject> tcs)
3633
{
3734
// Get filter parameters
3835
string testModeFilter = parameters["testMode"]?.ToObject<string>();
39-
40-
// Get all tests from the service
41-
var allTests = _testRunnerService.GetAllTests(testModeFilter);
42-
43-
// Create the results array
36+
List<TestItemInfo> allTests = await _testRunnerService.GetAllTestsAsync(testModeFilter);
4437
var results = new JArray();
45-
foreach (var test in allTests)
38+
39+
foreach (TestItemInfo test in allTests)
4640
{
4741
results.Add(new JObject
4842
{
@@ -54,13 +48,12 @@ public override JObject Fetch(JObject parameters)
5448
});
5549
}
5650

57-
// Return the results
58-
return new JObject
51+
tcs.SetResult(new JObject
5952
{
6053
["success"] = true,
6154
["message"] = $"Retrieved {allTests.Count} tests",
6255
["tests"] = results
63-
};
56+
});
6457
}
6558
}
6659
}

Editor/Resources/McpResourceBase.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading.Tasks;
34
using UnityEngine;
45
using Newtonsoft.Json.Linq;
56

@@ -31,10 +32,33 @@ public abstract class McpResourceBase
3132
public bool IsEnabled { get; protected set; } = true;
3233

3334
/// <summary>
34-
/// Fetch the resource data with the provided parameters
35+
/// Indicates if the fetch operation is asynchronous.
3536
/// </summary>
36-
/// <param name="parameters">Resource parameters as a JObject</param>
37-
/// <returns>The result of the resource fetch as a JObject</returns>
38-
public abstract JObject Fetch(JObject parameters);
37+
public bool IsAsync { get; protected set; } = false;
38+
39+
/// <summary>
40+
/// Synchronously fetch the resource data.
41+
/// Implement this for synchronous resources (IsAsync = false).
42+
/// </summary>
43+
/// <param name="parameters">Parameters extracted from the URI or query.</param>
44+
/// <returns>Result as JObject.</returns>
45+
public virtual JObject Fetch(JObject parameters)
46+
{
47+
// Default implementation throws, forcing sync resources to override.
48+
throw new NotImplementedException($"Synchronous Fetch not implemented for resource '{Name}'. Mark IsAsync=true and implement FetchAsync, or override Fetch.");
49+
}
50+
51+
/// <summary>
52+
/// Asynchronously fetch the resource data.
53+
/// Implement this for asynchronous resources (IsAsync = true).
54+
/// The implementation MUST eventually call tcs.SetResult() or tcs.SetException().
55+
/// </summary>
56+
/// <param name="parameters">Parameters extracted from the URI or query.</param>
57+
/// <param name="tcs">TaskCompletionSource to set the result on.</param>
58+
public virtual void FetchAsync(JObject parameters, TaskCompletionSource<JObject> tcs)
59+
{
60+
// Default implementation throws, forcing async resources to override.
61+
tcs.SetException(new NotImplementedException($"Asynchronous FetchAsync not implemented for resource '{Name}'. Mark IsAsync=false and implement Fetch, or override FetchAsync."));
62+
}
3963
}
4064
}

Editor/Services/ITestRunnerService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ public interface ITestRunnerService
1414
/// Get the TestRunnerApi instance
1515
/// </summary>
1616
TestRunnerApi TestRunnerApi { get; }
17-
17+
1818
/// <summary>
19-
/// Get a list of available tests, optionally filtered by test mode
19+
/// Async retrieval of all tests using TestRunnerApi callbacks
2020
/// </summary>
2121
/// <param name="testMode">Optional test mode filter (EditMode, PlayMode, or empty for all)</param>
2222
/// <returns>List of test items matching the specified test mode, or all tests if no mode specified</returns>
23-
List<TestItemInfo> GetAllTests(string testMode = "");
23+
Task<List<TestItemInfo>> GetAllTestsAsync(string testMode = "");
2424

2525
/// <summary>
2626
/// Execute tests with the provided parameters

Editor/Services/TestRunnerService.cs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading.Tasks;
55
using McpUnity.Unity;
66
using UnityEngine;
7+
using UnityEditor;
78
using UnityEditor.TestTools.TestRunner.Api;
89
using Newtonsoft.Json.Linq;
910

@@ -28,29 +29,59 @@ public TestRunnerService()
2829
{
2930
_testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
3031
}
31-
32+
33+
[MenuItem("Tools/MCP Unity/Debug call path")]
34+
public static async void DebugCallGetAllTests()
35+
{
36+
var service = new TestRunnerService();
37+
var tests = await service.GetAllTestsAsync();
38+
Debug.Log($"Retrieved {tests.Count} tests:");
39+
foreach (var t in tests)
40+
Debug.Log($"Test: {t.FullName} ({t.TestMode}) - State: {t.RunState}");
41+
}
42+
3243
/// <summary>
33-
/// Get a list of available tests, optionally filtered by test mode
44+
/// Async retrieval of all tests using TestRunnerApi callbacks
3445
/// </summary>
3546
/// <param name="testMode">Optional test mode filter (EditMode, PlayMode, or empty for all)</param>
3647
/// <returns>List of test items matching the specified test mode, or all tests if no mode specified</returns>
37-
public List<TestItemInfo> GetAllTests(string testMode = "")
48+
public async Task<List<TestItemInfo>> GetAllTestsAsync(string testMode = "")
3849
{
3950
var tests = new List<TestItemInfo>();
40-
41-
// Check if we need to retrieve EditMode tests
51+
var tcs = new TaskCompletionSource<bool>();
52+
int pending = 0;
53+
4254
if (string.IsNullOrEmpty(testMode) || testMode.Equals("EditMode", StringComparison.OrdinalIgnoreCase))
4355
{
44-
_testRunnerApi.RetrieveTestList(TestMode.EditMode, adaptor => CollectTestItems(adaptor, tests));
56+
Interlocked.Increment(ref pending);
57+
_testRunnerApi.RetrieveTestList(TestMode.EditMode, adaptor =>
58+
{
59+
CollectTestItems(adaptor, tests);
60+
CheckDone();
61+
});
4562
}
46-
47-
// Check if we need to retrieve PlayMode tests
4863
if (string.IsNullOrEmpty(testMode) || testMode.Equals("PlayMode", StringComparison.OrdinalIgnoreCase))
4964
{
50-
_testRunnerApi.RetrieveTestList(TestMode.PlayMode, adaptor => CollectTestItems(adaptor, tests));
65+
Interlocked.Increment(ref pending);
66+
_testRunnerApi.RetrieveTestList(TestMode.PlayMode, adaptor =>
67+
{
68+
CollectTestItems(adaptor, tests);
69+
CheckDone();
70+
});
5171
}
52-
72+
73+
if (pending == 0)
74+
tcs.SetResult(true);
75+
76+
await tcs.Task;
77+
5378
return tests;
79+
80+
void CheckDone()
81+
{
82+
if (Interlocked.Decrement(ref pending) == 0)
83+
tcs.TrySetResult(true);
84+
}
5485
}
5586

5687
/// <summary>

Editor/Tools/RunTestsTool.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,15 @@ public void RunFinished(ITestResultAdaptor result)
145145
// Create test results summary
146146
var summary = new JObject
147147
{
148+
["name"] = result.Name,
149+
["fullName"] = result.FullName,
148150
["testCount"] = result.PassCount + result.FailCount + result.SkipCount + result.InconclusiveCount,
149151
["passCount"] = result.PassCount,
150152
["failCount"] = result.FailCount,
151153
["skipCount"] = result.SkipCount,
152154
["inconclusiveCount"] = result.InconclusiveCount,
153155
["duration"] = result.Duration,
156+
["resultState"] = result.ResultState,
154157
["success"] = result.ResultState == "Passed",
155158
["status"] = "completed",
156159
["message"] = $"Test run completed: {result.Test.Name} - {result.ResultState}"

Editor/UnityBridge/McpUnitySocketHandler.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,24 @@ private IEnumerator FetchResourceCoroutine(McpResourceBase resource, JObject par
177177
{
178178
try
179179
{
180-
var result = resource.Fetch(parameters);
181-
tcs.SetResult(result);
180+
if (resource.IsAsync)
181+
{
182+
resource.FetchAsync(parameters, tcs);
183+
}
184+
else
185+
{
186+
var result = resource.Fetch(parameters);
187+
tcs.SetResult(result);
188+
}
182189
}
183190
catch (Exception ex)
184191
{
185-
McpLogger.LogError($"Error fetching resource {resource.Name}: {ex.Message}");
192+
McpLogger.LogError($"Error fetching resource {resource.Name}: {ex}");
186193
tcs.SetResult(CreateErrorResponse(
187194
$"Failed to fetch resource {resource.Name}: {ex.Message}",
188195
"resource_fetch_error"
189196
));
190197
}
191-
192198
yield return null;
193199
}
194200

0 commit comments

Comments
 (0)