82 lines
3.7 KiB
GDScript
82 lines
3.7 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
## Builds and sends the JSON-RPC result / error / notification frames to a peer,
|
|
## size-guarding results against the peer's send buffer; broadcasts a notification
|
|
## to a supplied set of authenticated peers. Shared by the editor server (Mode A)
|
|
## and the runtime autoload (Mode B), so it stays export-clean — no editor symbols,
|
|
## only WebSocketPeer / JSON / MCPToolkitError.
|
|
|
|
const JSONRPC_VERSION := "2.0"
|
|
|
|
|
|
## Sends a JSON-RPC result frame, guarding it against the peer's outbound buffer.
|
|
## A response larger than that buffer is rejected wholesale by the native WS path
|
|
## (no chunking) — without the guard it would vanish silently and the bridge would
|
|
## see a hung request. guard_response_size swaps it for a compact, deliverable
|
|
## RESPONSE_TOO_LARGE error so the caller learns to narrow/paginate. max_bytes is
|
|
## the peer's own buffer (set at accept), so this needs no ProjectSetting read.
|
|
## log_prefix tags the send-failure warning with the calling server's name.
|
|
static func send_result(peer: WebSocketPeer, id, result, log_prefix: String) -> void:
|
|
var response := {
|
|
"jsonrpc": JSONRPC_VERSION,
|
|
"id": id,
|
|
"result": result,
|
|
}
|
|
response = MCPToolkitError.guard_response_size(response, peer.outbound_buffer_size)
|
|
var send_err := peer.send_text(JSON.stringify(response))
|
|
if send_err != OK:
|
|
push_warning("%s send_text failed for id %s (err %d) - response not delivered" % [log_prefix, str(id), send_err])
|
|
|
|
|
|
## Sends a JSON-RPC error frame. Not size-guarded: error frames are compact by
|
|
## construction, so there is nothing to truncate.
|
|
static func send_error(peer: WebSocketPeer, id, code: int, error_message: String) -> void:
|
|
var response := {
|
|
"jsonrpc": JSONRPC_VERSION,
|
|
"id": id,
|
|
"error": {
|
|
"code": code,
|
|
"message": error_message,
|
|
},
|
|
}
|
|
peer.send_text(JSON.stringify(response))
|
|
|
|
|
|
## Sends a JSON-RPC notification frame (no id) to one peer, skipping a peer that
|
|
## is not OPEN. A notification carries no request id, so a too-large frame can't be
|
|
## answered with an error — at minimum surface a send failure so an over-buffer
|
|
## notification is never silently dropped. log_prefix tags that warning.
|
|
static func send_notification(peer: WebSocketPeer, method: String, params: Dictionary, log_prefix: String) -> void:
|
|
if peer.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
|
return
|
|
var send_err := peer.send_text(JSON.stringify({
|
|
"jsonrpc": JSONRPC_VERSION,
|
|
"method": method,
|
|
"params": params,
|
|
}))
|
|
if send_err != OK:
|
|
push_warning("%s notification '%s' send_text failed (err %d)" % [log_prefix, method, send_err])
|
|
|
|
|
|
## Broadcasts a config-change notification to every OPEN peer in authed_peers,
|
|
## carrying no request id (callers reload their tool list, not answer a request).
|
|
## Uses the dock-broadcast envelope ({notification, params?}), distinct from the
|
|
## per-request send_notification envelope. Returns the number of peers a frame was
|
|
## sent to, so the caller can log a summary. A too-large frame can't be answered
|
|
## with an error here either — surface a send failure so a dropped frame is visible.
|
|
static func broadcast(authed_peers: Array, notification_type: String, params: Dictionary, log_prefix: String) -> int:
|
|
var payload := {"notification": notification_type}
|
|
if not params.is_empty():
|
|
payload["params"] = params
|
|
var message := JSON.stringify(payload)
|
|
var count := 0
|
|
for peer in authed_peers:
|
|
if peer is WebSocketPeer and peer.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
# Cast: the loop var is Variant (caller-supplied Array element), so the
|
|
# call needs a typed receiver for the Error return.
|
|
var send_err := (peer as WebSocketPeer).send_text(message)
|
|
if send_err != OK:
|
|
push_warning("%s broadcast '%s' send_text failed (err %d)" % [log_prefix, notification_type, send_err])
|
|
count += 1
|
|
return count
|