Model Context Protocol server for live browser editor integration. Version 1.0.0
The blnq MCP server connects AI assistants (Claude Code, etc.) to the blnq.studio browser-based code editors in real time. It exposes a set of tools via the Model Context Protocol that allow reading, writing, searching, and validating code across four editor panels: HTML, CSS, JS, and HEAD.
The server also exposes a REST API (the "bridge") and an SSE channel for the browser client. MCP tools and REST routes share the same bridge layer — edits from either path push live to the browser via SSE.
The MCP endpoint is at POST /mcp using the Streamable HTTP transport (MCP SDK).
initialize request without a session header. The server returns an mcp-session-id header — include it on all subsequent requests.DELETE /mcp — Closes an existing session (requires mcp-session-id header).GET /mcp — Returns 405 Method Not Allowed.Parameters: None
Returns: Tab-separated table of tabId, name, projectId — or a message if no tabs are open.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID — auto-detected when only one tab is open. |
Returns: Tab ID, project name/ID, and per-editor line + character counts.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| editors | string[] optional | Which editors to read: "html", "css", "js", "head". Default: all. |
| startLine | number optional | 1-based start line (single editor only). |
| endLine | number optional | 1-based end line, inclusive (single editor only). |
Returns: Line-numbered content per editor, with range labels.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| query | string required | Text or regex pattern to search for. |
| editors | string[] optional | Editors to search. Default: all. |
| regex | boolean optional | Treat query as regex. |
Returns: Matching lines in editor:line\tcontent format, with total count.
Three modes:
editor, startLine, endLine, newText.editor, oldText, newText, optionally replaceAll.edits array of find-and-replace operations (atomic — all validated before any apply).| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| editor | enum optional | "html" | "css" | "js" | "head". Required for single edits. |
| startLine | number optional | First line to replace (1-based inclusive). |
| endLine | number optional | Last line to replace (1-based inclusive). |
| oldText | string optional | Text to find (must be unique unless replaceAll). |
| newText | string optional | Replacement text. |
| replaceAll | boolean optional | Replace all occurrences of oldText. |
| edits | object[] optional | Batch edits: [{ editor, oldText, newText, replaceAll? }]. |
Returns: Confirmation with updated line count, context snippet around the edit, and syntax warnings (bracket balance) if any.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| html | string optional | Full HTML content. |
| css | string optional | Full CSS content. |
| js | string optional | Full JS content. |
| head | string optional | Full HEAD content. |
Returns: List of updated editors, plus syntax warnings if bracket imbalance detected.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| code | string required | Full content for that editor. |
Returns: Editor name, line count, and syntax warnings if bracket imbalance detected.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
Returns: JSON array of Excalidraw element objects, or "Sketch is empty."
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| elements | object[] required | Array of Excalidraw element objects (rectangle, ellipse, diamond, text, arrow, line). |
Returns: Confirmation with element count.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
Returns: JS console errors (with line numbers) and axe-core a11y violations/incomplete checks — or "Clean" if none found.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| width | number optional | Viewport width in pixels. Default: 1280. |
Returns: JPEG image (base64) of the rendered preview.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
Returns: Confirmation that the refresh was triggered.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| name | string required | Short descriptive project name. |
Returns: Confirmation that the project name was set.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| editor | string optional | Specific editor to undo: html, css, js, or head. Default: undo all editors from last edit. |
Returns: Which editors were restored and how many undo steps remain.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
Returns: Editor name, line number, column number, and how recently the position was reported.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| selector | string required | CSS selector for the element to inspect. |
| styles | string[] optional | Computed CSS properties to return (e.g. ["color", "font-size"]). Default: common layout properties. |
Returns: Bounding rect, text content, computed styles, ARIA role/label.
| Param | Type | Description |
|---|---|---|
| tabId | string optional | Tab ID (auto-detected if one tab open). |
| code | string required | JavaScript to evaluate in the preview iframe. The return value is captured. |
Returns: JSON-serialised result, or a runtime error message.
SERPER_API_KEY env var).| Param | Type | Description |
|---|---|---|
| query | string required | Search query. |
| max_results | number optional | Number of results to return (1–10, default 5). |
Returns: Formatted snippets with title, URL, and description. Graceful error messages for missing/invalid/exhausted API key.
| Param | Type | Description |
|---|---|---|
| todos | object[] required | Array of { content: string, status: "pending" | "in_progress" | "done" }. |
Returns: Confirmation string.
These HTTP endpoints are used by the browser client and can also be called directly. All endpoints that operate on a tab require a tabId parameter.
/api/bridge/events?tabId=…
SSE stream for a browser tab. One connection per tab. Pushes editors:update and a11y:run events.
/api/bridge/tabs
List active (SSE-connected) tabs. Returns [{ tabId, projectId, projectName, updatedAt }].
/api/bridge/editors?tabId=…
Read all editor content for a tab. Returns { tabId, html, css, js, head, projectId, projectName }.
/api/bridge/sync
Browser syncs its content to the server. Body: { tabId, html?, css?, js?, head?, projectId?, projectName? }.
/api/bridge/editors
Write full editor content. Body: { tabId, html?, css?, js?, head? }. Pushes update via SSE.
/api/bridge/edit
Find-and-replace edit. Body: { tabId, editor, oldText, newText, replaceAll? } or batch: { tabId, edits: [...] }.
/api/bridge/search
Search across editors. Body: { tabId, query, editors?, regex? }.
/api/bridge/a11y/run
Trigger an accessibility check. Body: { tabId }. Query: ?timeout=15000. Waits for browser to return results via SSE round-trip.
/api/bridge/a11y/results
Browser posts axe-core results back. Body: { tabId, violations, incomplete }.
/api/bridge/a11y?tabId=…
Read the last cached a11y report for a tab.
/api/bridge/sketch?tabId=…
Read current sketch elements. Returns { tabId, elements, elementCount, projectId, projectName }.
/api/bridge/sketch
Write sketch elements. Body: { tabId, elements: [...] }. Pushes sketch:update via SSE.
/api/bridge/console/errors
Browser reports JS console errors. Body: { tabId, errors: [{ level, message, line?, col? }] }.
/api/bridge/console/clear
Clear stored console errors. Body: { tabId }.
/api/bridge/console/errors?tabId=…
Read stored console errors for a tab.
The SSE channel at /api/bridge/events sends the following events:
editors:update — Editor content changed. Data: { html?, css?, js?, head?, _highlight?: { editor, line } }.sketch:update — Sketch elements updated. Data: { elements: [...] }.a11y:run — Server is requesting the browser to run an axe-core accessibility check.The _highlight field is a hint for the browser to scroll to/highlight the edited line.
Each tab holds state for four editors:
| Editor | Content | Notes |
|---|---|---|
| html | Body content only | No <!DOCTYPE>, <html>, <head>, or <body> tags. |
| css | Full CSS stylesheet | Injected as a <style> block in the preview. |
| js | Full JavaScript | Vanilla ES6+. Runs after HTML loads. ES modules supported via importmap in HEAD. |
| head | HEAD content | CDN scripts, meta tags, importmaps, external stylesheets. |
The preview composes a full HTML document: <head> content + CSS in a <style> block + HTML as <body> content + JS in a <script> block.
Every edit operation runs a bracket balance check on the affected editor (except HEAD). If unclosed or unmatched brackets are detected, a ⚠ SYNTAX warning is returned alongside the successful edit result. This catches broken {}, (), and [] pairs immediately rather than at preview time.
Tab state is held in memory (per-process). Tabs are cleaned up 5 minutes after their SSE connection drops. Max 50 console errors stored per tab.