7.1 KiB
| title | description |
|---|---|
| IDE Bridge | IDE integration bridge connecting the MCP server to Laravel core-agentic via WebSocket. |
IDE Bridge
The mcp/ide package implements the IDE subsystem. It bridges the desktop MCP server to the Laravel core-agentic backend via a persistent WebSocket connection, enabling AI agents to interact with agent sessions, builds, and the platform dashboard.
Architecture
MCP Client (Claude, Cursor, etc.)
|
v MCP JSON-RPC
go-ai MCP Server
|
v ide_* tool calls
IDE Subsystem (mcp/ide/)
|
+-- Bridge -----------> Laravel core-agentic
| (WebSocket) ws://localhost:9876/ws
|
+-- ws.Hub <------------ Bridge dispatch
|
v real-time updates
Local WebSocket subscribers (browser UIs)
Subsystem Structure
type Subsystem struct {
cfg Config
bridge *Bridge // nil in headless mode
hub *ws.Hub // local WebSocket hub for real-time forwarding
}
When a ws.Hub is provided, the subsystem creates a Bridge that actively connects to Laravel. Without a hub (hub == nil), the subsystem runs in headless mode: tools are still registered and return stub responses, but no real-time forwarding occurs.
Configuration
type Config struct {
LaravelWSURL string // WebSocket endpoint (default: ws://localhost:9876/ws)
WorkspaceRoot string // local path for workspace context
Token string // Bearer token for Authorization header
ReconnectInterval time.Duration // base backoff (default: 2s)
MaxReconnectInterval time.Duration // cap for exponential backoff (default: 30s)
}
All fields are overridable via functional options:
sub := ide.New(hub,
ide.WithLaravelURL("ws://custom:9876/ws"),
ide.WithToken("my-bearer-token"),
ide.WithWorkspaceRoot("/path/to/project"),
ide.WithReconnectInterval(5 * time.Second),
)
WebSocket Bridge
The Bridge maintains a persistent WebSocket connection to Laravel and forwards inbound messages to the local ws.Hub.
Connection Lifecycle
StartBridge(ctx)
+-- go connectLoop(ctx)
+-- dial(ctx) <-- WebSocket upgrade with Bearer token
| sets b.connected = true
+-- readLoop(ctx) <-- blocks reading frames
+-- dispatch(msg) <-- routes to ws.Hub channel
[on read error]
sets b.connected = false, returns to connectLoop
Exponential Backoff
When the connection drops or fails to establish, the bridge uses exponential backoff:
delay := b.cfg.ReconnectInterval // starts at 2s
for {
if err := b.dial(ctx); err != nil {
// wait delay, then double it up to MaxReconnectInterval
delay = min(delay*2, b.cfg.MaxReconnectInterval)
continue
}
delay = b.cfg.ReconnectInterval // reset on successful connection
b.readLoop(ctx)
}
Backoff sequence with defaults: 2s, 4s, 8s, 16s, 30s, 30s, ... The delay resets to 2s on every successful connection.
Authentication
var header http.Header
if b.cfg.Token != "" {
header = http.Header{}
header.Set("Authorization", "Bearer "+b.cfg.Token)
}
conn, _, err := dialer.DialContext(ctx, b.cfg.LaravelWSURL, header)
When Token is empty, no Authorization header is sent. This is appropriate for development environments running without authentication.
Message Dispatch
Inbound frames from Laravel are deserialised into BridgeMessage:
type BridgeMessage struct {
Type string `json:"type"`
Channel string `json:"channel,omitempty"`
SessionID string `json:"sessionId,omitempty"`
Data any `json:"data,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
The dispatch method routes messages to the local ws.Hub:
func (b *Bridge) dispatch(msg BridgeMessage) {
channel := msg.Channel
if channel == "" {
channel = "ide:" + msg.Type // synthetic channel name
}
b.hub.SendToChannel(channel, ws.Message{Type: ws.TypeEvent, Data: msg.Data})
}
This allows browser-based UIs connected to the local WebSocket hub to receive real-time updates from Laravel without polling.
Outbound Messages
MCP tool handlers call bridge.Send() to push requests to Laravel:
func (b *Bridge) Send(msg BridgeMessage) error {
b.mu.Lock()
defer b.mu.Unlock()
if b.conn == nil {
return fmt.Errorf("bridge: not connected")
}
msg.Timestamp = time.Now()
data, _ := json.Marshal(msg)
return b.conn.WriteMessage(websocket.TextMessage, data)
}
A mutex ensures Send and the readLoop do not race on b.conn. If the bridge is disconnected, Send returns an error which propagates to the MCP client as a JSON-RPC error.
IDE Tool Groups
The subsystem registers 11 tools across three groups.
Chat Tools (tools_chat.go)
| Tool | Description |
|---|---|
ide_chat_send |
Send a message to an agent chat session |
ide_chat_history |
Retrieve message history for a session |
ide_session_list |
List active agent sessions |
ide_session_create |
Create a new agent session |
ide_plan_status |
Get current plan status for a session |
Build Tools (tools_build.go)
| Tool | Description |
|---|---|
ide_build_status |
Get the status of a specific build |
ide_build_list |
List recent builds, optionally filtered by repository |
ide_build_logs |
Retrieve log output for a build |
Dashboard Tools (tools_dashboard.go)
| Tool | Description |
|---|---|
ide_dashboard_overview |
High-level platform overview (repos, services, sessions, builds, bridge status) |
ide_dashboard_activity |
Recent activity feed |
ide_dashboard_metrics |
Aggregate build and agent metrics for a time period |
Tool Behaviour
All IDE tools follow a fire-and-forward pattern: the tool sends a BridgeMessage to Laravel and returns an immediate acknowledgement or stub response. Real data arrives asynchronously via the WebSocket read loop and is forwarded to ws.Hub subscribers.
The ide_dashboard_overview tool is the one exception -- it reads bridge.Connected() synchronously to populate the BridgeOnline field.
Registration
The IDE subsystem is registered during MCP server construction:
svc, err := mcp.New(
mcp.WithWSHub(hub),
mcp.WithSubsystem(ide.New(hub, ide.WithToken(token))),
)
The subsystem implements SubsystemWithShutdown, closing the bridge connection gracefully when the MCP server shuts down.
Testing
Bridge tests use net/http/httptest to stand up a real WebSocket server in-process. This keeps tests hermetic while exercising:
- Reconnection logic and exponential backoff
- Authentication header injection
- Message dispatch routing
- Server shutdown detection
The waitConnected helper polls bridge.Connected() with a deadline rather than using fixed sleeps.
All 11 tool handlers are tested under two conditions:
- nil bridge -- verifies the error path
- connected mock bridge -- verifies the success path with JSON round-trip validation