Files
tactical-shooter/addons/godot_mcp/mcp_client.gd
T
shawn b0c83af092 Fresh start: replace with naxIO/netfox-cs-sample foundation
Complete replacement of the tactical-shooter project with the
netfox-cs-sample (MIT) — a CS 1.6 inspired multiplayer FPS built
with Godot 4 and netfox.

## What's new
- Full CS-style gameplay: teams (T/CT), rounds, economy, buy menu
- 6 weapons: Knife, Glock, USP, AK-47, M4A1, AWP
- Bomb plant/defuse with 2 bombsites
- Flashbang & smoke grenades
- Proper netfox rollback netcode at 64 tick
- Network popup UI for host/join
- HUD, crosshair, round timer, scoreboard
- All netfox singletons registered as autoloads (works in exported builds)

## Architecture
- Listen-server (host from client, no dedicated server binary)
- Multiplayer-fps game lives at examples/multiplayer-fps/
- Netfox addons registered as autoloads for exported build compat
- Godot 4.7 with Forward+ renderer

## Removed
- Old headless-server architecture (client_main, server_main, player.gd, etc.)
- Custom netfox bootstrap with ENet fallback
- Old ChaffGames FPS template (2,420 lines, 844 KB)
- SimulationServer GDExtension stub
- Godot-jolt physics (netfox sample uses default Godot physics)
- Duplicate weapon_data.gd, anti_cheat.gd, round_manager.gd, etc.
- Server browser API Python venv (87 MB)
- test_range map and modular assets

## Preserved
- Git history
- Server config at config/default_server_config.cfg
- Windows export preset
- Build directory (gitignored)

Co-authored-by: naxIO <naxIO@users.noreply.github.com>
2026-07-02 20:55:20 -04:00

157 lines
4.3 KiB
GDScript

@tool
extends Node
class_name MCPClient
## WebSocket client for communication with the MCP server.
## Handles connection, reconnection, and message routing.
signal connected
signal disconnected
signal tool_requested(request_id: String, tool_name: String, args: Dictionary)
const DEFAULT_URL := "ws://127.0.0.1:6505"
const RECONNECT_DELAY := 3.0
const MAX_RECONNECT_DELAY := 30.0
const MAX_PACKETS_PER_FRAME := 32
var socket: WebSocketPeer = WebSocketPeer.new()
var server_url: String = DEFAULT_URL
var _is_connected := false
var _reconnect_timer: Timer
var _current_reconnect_delay := RECONNECT_DELAY
var _should_reconnect := true
var _project_path: String
var _initialized := false
func _ready() -> void:
_project_path = ProjectSettings.globalize_path("res://")
# Create reconnect timer
_reconnect_timer = Timer.new()
_reconnect_timer.one_shot = true
_reconnect_timer.timeout.connect(_on_reconnect_timer)
add_child(_reconnect_timer)
_initialized = true
func _process(_delta: float) -> void:
if not _initialized:
return
if socket.get_ready_state() == WebSocketPeer.STATE_CLOSED:
if _is_connected:
_handle_disconnect()
return
socket.poll()
match socket.get_ready_state():
WebSocketPeer.STATE_OPEN:
if not _is_connected:
_handle_connect()
var packets_processed := 0
while socket.get_available_packet_count() > 0 and packets_processed < MAX_PACKETS_PER_FRAME:
_handle_message(socket.get_packet().get_string_from_utf8())
packets_processed += 1
WebSocketPeer.STATE_CLOSING:
pass # Wait for close
WebSocketPeer.STATE_CLOSED:
if _is_connected:
_handle_disconnect()
func connect_to_server(url: String = DEFAULT_URL) -> void:
server_url = url
_should_reconnect = true
_current_reconnect_delay = RECONNECT_DELAY
_attempt_connection()
func disconnect_from_server() -> void:
_should_reconnect = false
if _reconnect_timer:
_reconnect_timer.stop()
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
socket.close()
_is_connected = false
func _attempt_connection() -> void:
if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
socket.close()
print("[MCP] Connecting to ", server_url, "...")
var err := socket.connect_to_url(server_url)
if err != OK:
push_error("[MCP] Failed to connect: ", err)
_schedule_reconnect()
func _handle_connect() -> void:
_is_connected = true
_current_reconnect_delay = RECONNECT_DELAY # Reset backoff
print("[MCP] Connected to server")
# Send godot_ready message with project info
_send_message({
&"type": &"godot_ready",
&"project_path": _project_path,
})
connected.emit()
func _handle_disconnect() -> void:
_is_connected = false
print("[MCP] Disconnected from server")
disconnected.emit()
if _should_reconnect:
_schedule_reconnect()
func _schedule_reconnect() -> void:
if not _reconnect_timer:
return
print("[MCP] Reconnecting in ", _current_reconnect_delay, " seconds...")
_reconnect_timer.start(_current_reconnect_delay)
# Exponential backoff
_current_reconnect_delay = min(_current_reconnect_delay * 2, MAX_RECONNECT_DELAY)
func _on_reconnect_timer() -> void:
_attempt_connection()
func _handle_message(json_string: String) -> void:
var message = JSON.parse_string(json_string)
if message == null:
push_error("[MCP] Failed to parse message: ", json_string)
return
var msg_type: String = message.get(&"type", "")
match msg_type:
"ping":
_send_message({&"type": &"pong"})
"tool_invoke":
var request_id: String = message.get(&"id", "")
var tool_name: String = message.get(&"tool", "")
var args: Dictionary = message.get(&"args", {})
print("[MCP] Tool request: ", tool_name, " (", request_id, ")")
tool_requested.emit(request_id, tool_name, args)
_:
print("[MCP] Unknown message type: ", msg_type)
func send_tool_result(request_id: String, success: bool, result = null, error: String = "") -> void:
var response := {
&"type": &"tool_result",
&"id": request_id,
&"success": success,
}
if success:
response[&"result"] = result
else:
response[&"error"] = error
_send_message(response)
print("[MCP] Sent result for ", request_id, " (success=", success, ")")
func _send_message(message: Dictionary) -> void:
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
socket.send_text(JSON.stringify(message))
func is_connected_to_server() -> bool:
return _is_connected