-
-
Notifications
You must be signed in to change notification settings - Fork 320
Expand file tree
/
Copy pathEvent.Watch.cs
More file actions
123 lines (111 loc) · 5.65 KB
/
Copy pathEvent.Watch.cs
File metadata and controls
123 lines (111 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*
┌──────────────────────────────────────────────────────────────────┐
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
│ Licensed under the Apache License, Version 2.0. │
│ See the LICENSE file in the project root for more information. │
└──────────────────────────────────────────────────────────────────┘
*/
#nullable enable
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using com.IvanMurzak.McpPlugin;
using com.IvanMurzak.McpPlugin.Common.Model;
namespace com.IvanMurzak.Unity.MCP.Editor.API
{
public partial class Tool_Event
{
public const string EventWatchToolId = "event-watch";
[McpPluginTool
(
EventWatchToolId,
Title = "Event / Watch"
)]
[Description(
"NON-BLOCKING event watcher at MCP server level. Returns 'Processing' status immediately, " +
"then sends a notification via NotifyToolRequestCompleted when a matching event fires.\n\n" +
"IMPORTANT LIMITATION: Some MCP clients (including Claude Code) wait for the notification " +
"before proceeding to the next turn, making this effectively blocking from the AI agent's perspective. " +
"If your client blocks on Processing responses, use event-subscribe instead.\n\n" +
"This tool is useful for MCP clients that can handle Processing responses asynchronously, " +
"or for scenarios where you want the server to notify you without keeping a blocking tool call open.\n\n" +
"USE CASES:\n" +
"- Clients that support async Processing: background error monitoring\n" +
"- Long-running watches where you want server-side timeout management\n\n" +
"For most use cases, prefer event-subscribe:\n" +
" script-execute(trigger async action) → event-subscribe(type='my_event', timeoutMs=30000)\n\n" +
"CUSTOM EVENTS: Same as event-subscribe — see event-subscribe description for full guide.\n\n" +
"NOTE: Watch is ephemeral — if Unity domain reloads (e.g. script recompile), the watcher is lost."
)]
public ResponseCallTool Watch
(
[Description(
"Event type to watch for. " +
"Use a specific type like 'error_logged' or 'compilation_finished'. " +
"Empty string or null matches ANY event type. " +
"Use 'event-list' tool to see all available types."
)]
string? type = null,
[Description(
"Maximum watch time in milliseconds. " +
"Range: 5000-120000. Default: 60000 (60 seconds). " +
"The watcher is automatically cancelled after this timeout."
)]
int timeoutMs = 60000,
[Description(
"If true, collects ALL matching events until timeout, then notifies once. " +
"If false (default), notifies immediately on the FIRST matching event."
)]
bool collectAll = false,
[RequestID]
string? requestId = null
)
{
if (requestId == null || string.IsNullOrWhiteSpace(requestId))
return ResponseCallTool.Error("Original request with valid RequestID must be provided.");
if (timeoutMs < 5000 || timeoutMs > 120000)
return ResponseCallTool.Error(Error.InvalidWatchTimeout(timeoutMs)).SetRequestID(requestId);
var watchType = string.IsNullOrEmpty(type) ? "any" : type;
// Start background watcher — does NOT block the tool response
_ = Task.Run(async () =>
{
try
{
using var cts = new CancellationTokenSource(timeoutMs + 1000);
var watchResult = await McpEventBus.WaitAsync(type, timeoutMs, collectAll, cts.Token);
var json = System.Text.Json.JsonSerializer.SerializeToNode(watchResult);
var response = ResponseCallValueTool<McpEventSubscribeResult>
.SuccessStructured(json)
.SetRequestID(requestId);
await UnityMcpPluginEditor.NotifyToolRequestCompleted(new RequestToolCompletedData
{
RequestId = requestId,
Result = response
});
}
catch (Exception ex)
{
try
{
await UnityMcpPluginEditor.NotifyToolRequestCompleted(new RequestToolCompletedData
{
RequestId = requestId,
Result = ResponseCallTool.Error(
$"[event-watch] Watcher failed: {ex.Message}"
).SetRequestID(requestId)
});
}
catch
{
// Connection lost — nothing we can do
}
}
});
return ResponseCallTool.Processing(
$"Watching for '{watchType}' events (timeout: {timeoutMs}ms). " +
"You will be notified when a matching event occurs. Continue working."
).SetRequestID(requestId);
}
}
}