# How MCP Connectors Bridge External Agents to the Instatic Live Editor Workspace

> Discover how MCP connectors create a persistent ND-JSON stream to link external AI agents with the Instatic live editor workspace for seamless tool requests and results.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: architecture
- Published: 2026-07-26

---

**The MCP connector establishes a persistent ND-JSON stream that registers a user-scoped bridge entry, enabling external AI agents to dispatch tool requests to the Instatic live editor and receive execution results through a bidirectional callback mechanism.**

The CoreBunch/Instatic platform allows external AI agents to control the visual editor workspace through a specialized bridge architecture. This system connects server-side MCP tools to the browser-based live editor using a long-lived stream that maintains real-time synchronization. Understanding this mechanism reveals how AI-driven content operations execute safely within user-scoped sessions.

## Creating the Live Editor Bridge

The connection initiates when the editor UI mounts and invokes `createEditorBridgeStream(userId, scope, abortSignal)` from [`server/ai/mcp/editorBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/editorBridge.ts). This function establishes a persistent ND-JSON stream that serves as the communication backbone between the MCP server and the browser.

The bridge registers itself using a composite key of **user ID** and **workspace scope**. The scope parameter accepts either `'site'` or `'content'`, determining whether the connector targets site-level configuration or content editing tools. The registry enforces a singleton pattern per user-scope combination: if a new connection opens for an existing user and scope, it automatically replaces the previous bridge entry.

```typescript
// Client-side bridge initialization
const abort = new AbortController();
const ndjsonStream = createEditorBridgeStream(
  currentUser.id,
  'site',
  abort.signal
);

// The UI reads events through this reader
ndjsonStream.getReader();

```

## Server-Side Bridge Registry

The bridge registry stores active connections in a nested Map structure: `Map<string, Map<EditorBridgeScope, EditorBridgeEntry>>` (referenced as `byUser` in the source). This data structure enables efficient lookup and scope separation.

Two helper functions expose bridge availability to MCP tools:

- **`getEditorBridgeForUser(userId, scope)`** returns the concrete `AiBrowserBridge` instance or `null` if disconnected
- **`hasEditorBridge(userId, scope)`** provides a boolean check for quick validation

Tools like those defined in [`server/ai/mcp/tools/contextTool.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/tools/contextTool.ts) utilize these helpers to verify workspace reachability before attempting operations. The registry lives alongside the stream handling logic in [`server/ai/mcp/editorBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/editorBridge.ts), while HTTP handlers in [`server/ai/mcp/handlers/editorBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/handlers/editorBridge.ts) wire the bridge into the MCP server via `tryHandleAiEditorBridge`.

## Dispatching Tool Calls

When an external MCP client invokes a browser-side tool, the server executes a three-phase round-trip:

1. **Emission**: The server retrieves the bridge via `getEditorBridgeForUser` and calls `bridge.callBrowser()`, emitting a `toolRequest` event down the ND-JSON stream containing the tool name and payload
2. **Execution**: The editor consumes the event through its stream reader, executes the requested operation (such as inserting HTML or applying CSS), and captures the result
3. **Resolution**: The editor POSTs the result to `/admin/api/ai/tool-result`, which resolves the original promise held by the bridge, completing the MCP tool invocation

```typescript
// Server-side tool dispatch
if (hasEditorBridge(userId, 'site')) {
  const bridge = getEditorBridgeForUser(userId, 'site')!;
  bridge.callBrowser({
    type: 'toolRequest',
    tool: 'insertHtml',
    payload: { html: '<p>Hello</p>' }
  });
}

```

## Security Model and Scope Isolation

The bridge implements strict **user-scoped isolation**. The registry keys entries by user ID, ensuring MCP connectors can only reach workspaces owned by their authenticated user. This prevents cross-user workspace access even if connection identifiers are compromised.

**EditorBridgeScope** provides secondary isolation between site and content contexts. A tool designed for site-level operations (such as theme adjustments) cannot dispatch to a content editor instance, and vice versa. This scope separation runs parallel to the user scoping, creating a two-dimensional permission boundary.

## Connection Lifecycle and Cleanup

The bridge maintains connection health through active management mechanisms:

- **Heartbeat**: The stream writes empty lines every 25 seconds to prevent proxy timeouts and keep the connection alive
- **Lease Timer**: A `STREAM_LEASE_MS` of 120,000 milliseconds (2 minutes) defines the maximum idle time before automatic cleanup
- **Signal Abort**: Passing an `AbortController` signal allows immediate bridge termination when the user navigates away or closes the editor
- **Replacement**: New connections for the same user-scope pair automatically evict old entries, preventing stale bridge references

When cleanup triggers—whether through lease expiration, signal abortion, or explicit stream closing—the registry removes the entry from the `byUser` Map, ensuring no dangling references persist.

## Summary

- The MCP connector creates a persistent ND-JSON stream via `createEditorBridgeStream` in [`server/ai/mcp/editorBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/editorBridge.ts) to link external agents with the live editor
- A user-scoped registry Map prevents unauthorized cross-user workspace access while `EditorBridgeScope` isolates site and content contexts
- Tool execution follows a complete request-response cycle: emit via `callBrowser`, execute in editor, POST result to `/admin/api/ai/tool-result`, resolve promise
- Connection maintenance relies on 25-second heartbeats and a 120-second lease timer to handle disconnections gracefully

## Frequently Asked Questions

### What protocol does the MCP connector use to stream data to the Instatic editor?

The connector uses an **ND-JSON (Newline Delimited JSON)** stream format over a persistent HTTP connection. This protocol allows the server to push individual JSON messages to the browser as events occur, with each message separated by newline characters. The implementation includes empty-line heartbeats every 25 seconds to prevent connection timeouts through proxies and load balancers.

### How does the system prevent MCP tools from accessing another user's workspace?

The bridge registry enforces **user-scoped isolation** by keying all entries with the user's unique ID. When an MCP tool calls `getEditorBridgeForUser(userId, scope)`, it receives `null` if the requested user ID does not match the authenticated session that established the bridge. This design ensures that external agents can only manipulate workspaces explicitly connected to their own authenticated user session.

### What happens if the editor disconnects while an MCP tool is executing?

The bridge implements a lease-based cleanup mechanism with `STREAM_LEASE_MS` set to 120,000 milliseconds. If the browser disconnects or stops responding, the lease expires and automatically removes the bridge entry from the registry. Additionally, the `AbortController` signal provided during `createEditorBridgeStream` initialization allows immediate cleanup when the user closes the tab or navigates away, preventing stale connections from accumulating server-side.

### Where does the MCP server check if a live editor connection exists before dispatching tools?

MCP tools verify connection availability using the `hasEditorBridge(userId, scope)` helper function, as demonstrated in [`server/ai/mcp/tools/contextTool.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/tools/contextTool.ts). This boolean check queries the `byUser` registry Map without retrieving the full bridge instance, providing an efficient way to validate workspace reachability before attempting tool dispatch operations.