Model Context Protocol server for live browser editor integration. Tool listings generated from the shared registry.
The blnq MCP server connects an external AI harness (Claude Code, Cursor, Windsurf, any MCP-speaking client) to the blnq.studio browser editors in real time. Tool calls from the AI flow through a per-tab bridge and are pushed into the live Monaco editors via Server-Sent Events.
The same bridge is shared with the in-browser AI chat panel, so anything described here applies to both call paths. Edits from either side push to the browser over the same SSE channel.
POST /mcp (Streamable HTTP MCP transport) → bridge state → SSE → browser editor.
Three steps. Most people get stuck on step 1 because they assume the agent can see the project before they click Start MCP. It can't — the bridge is lazy by design.
blnq.studio/e. In the top-right of the header you'll see a Start MCP button — click it. The button lights up, the bridge connects, and your MCP ID (a random UUID bound to this tab) is copied to your clipboard. Leave the tab open.https://blnq.studio/mcp — no API key, no auth header, no install step on the server side.tabId and prompt you for your MCP ID — paste it then. This is often the easiest flow because you don't need to plan ahead. If you prefer, you can hand the ID over upfront ("My blnq MCP ID is abc1234, use it on every tool call") so the agent never has to ask.Every config below points the client at the same endpoint: https://blnq.studio/mcp over HTTP (Streamable HTTP transport). Pick the one that matches your tool.
One command — the CLI writes the config for you:
claude mcp add --transport http blnq https://blnq.studio/mcp
Or add it manually to ~/.claude.json (user scope) or .mcp.json (project scope):
{
"mcpServers": {
"blnq": {
"type": "http",
"url": "https://blnq.studio/mcp"
}
}
}
Verify with claude mcp list; the entry should show connected once you start a session.
Open Settings → Developer → Edit Config (this opens claude_desktop_config.json). Add:
{
"mcpServers": {
"blnq": {
"type": "http",
"url": "https://blnq.studio/mcp"
}
}
}
Restart Claude Desktop. If your build doesn't yet support remote HTTP MCP natively, use the mcp-remote stdio bridge instead:
{
"mcpServers": {
"blnq": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://blnq.studio/mcp"]
}
}
}
Edit ~/.cursor/mcp.json (global) or .cursor/mcp.json in a workspace (per-project), or use Settings → MCP → Add Server:
{
"mcpServers": {
"blnq": {
"url": "https://blnq.studio/mcp"
}
}
}
Edit ~/.codeium/windsurf/mcp_config.json, or use Settings → Cascade → MCP Servers → Add Server:
{
"mcpServers": {
"blnq": {
"serverUrl": "https://blnq.studio/mcp"
}
}
}
blnq is a stock Streamable HTTP MCP server. If your client supports remote MCP over HTTP, point it at https://blnq.studio/mcp — nothing else is required. If it only supports stdio, wrap it with mcp-remote as shown in the Claude Desktop fallback above.
Every tool that touches editor content takes a tabId parameter — that's the MCP ID copied to your clipboard when you clicked Start MCP. There is no discovery tool and no auto-resolution: in a shared-tenant deployment, listing connected tabs would leak other users' projects, so the server requires an explicit ID on every call. You have two ways to get it to the agent.
On-demand (easiest). Just start prompting — "using blnq, build me a portfolio site". The agent will hit its first tool call without a tabId, see the "MCP ID required" error, and ask you for one. Paste it then. You don't have to plan ahead; the harness handles the prompt for you.
Upfront (when you want to skip the round-trip). Paste the ID into the chat at the start of the session and ask the agent to remember it. Examples:
If the ID stops working, you'll see "MCP ID not recognised" from the tool. That means the editor tab was closed (the ID is revoked ~5 minutes after the SSE connection drops). Open a fresh tab at blnq.studio/e, click Start MCP again, and pass the new ID.
Once the agent has your MCP ID, talk to it like any other tool. A few starting points:
Using blnq, build me a one-page portfolio: sticky nav, hero
with my name, three project cards, contact section. Dark
theme, no AI blue, no gratuitous gradients.
Using blnq, read the current files, then make the hero
stack on mobile and add a subtle fade-in for the cards.
Run blnq_check_preview when you're done and fix anything
it flags.
Using blnq, generate three design variants of the landing
page — minimalist, editorial, brutalist — and show them in
the variant picker.
The bridge is session-less from the harness's point of view — the only identity that matters is the tabId, a random UUID minted by the editor tab.
┌──────────────────────┐ POST /mcp ┌──────────────────────┐
│ External AI harness │ ─────────────────────▶ │ blnq MCP server │
│ (Claude Code, etc.) │ ◀───────────────────── │ (Streamable HTTP) │
└──────────────────────┘ tool results / SSE └──────────┬───────────┘
│
bridge state
(per tabId)
│
┌───────────────────────────────┤
│ │
sendToTab(event, data) POST /api/bridge/sync
via SSE channel (content mirror)
│ │
▼ │
┌──────────────────────┐ │
│ Browser editor tab │ ◀─────────────────┘
│ (Monaco + preview) │
│ /api/bridge/events │
└──────────────────────┘
GET /api/bridge/events?tabId=…. This SSE stream is how the server pushes live updates back to the browser.POST /api/bridge/sync, seeding the in-memory state so external tools can read it. Without this click, the bridge stays idle and tools see no tab.initialize to POST /mcp. The server responds with tool definitions. Session-aware clients get an mcp-session-id header to reuse; stateless clients get a one-shot server per request.tabId parameter. The server resolves to the matching tab and pushes edits over the SSE channel.Some tools need live runtime data, not just editor text — e.g. blnq_run_js, blnq_get_element_info, blnq_check_preview, blnq_click. These use a request / response pattern over SSE:
js:eval, element:query, dom:action, a11y:run) with the request payload./api/bridge/js/results).Each pending request has a timeout (default 8–15 s depending on tool). Only one pending request per tool type per tab at a time — the new request replaces the old one.
The MCP endpoint is at POST /mcp using the Streamable HTTP transport from the official MCP SDK.
initialize request without a session header. The server returns an mcp-session-id header — include it on all subsequent requests.DELETE /mcp — close an existing session (requires mcp-session-id).GET /mcp — returns 405 Method Not Allowed.GET /mcp/spec — this page.Inspection, preview-driving, and project-state tools. Each tool is tagged read (safe to call without prompting the user) or write (mutates project state). Listings below are generated from the shared registry, so this page can't drift from the live tools.
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| query | string required | Text or regex to search for |
| filenames | string[] optional | Optional list of filenames to search (e.g. ["/index.html"]). Omit for all files. |
| regex | boolean optional | Treat query as regex |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| name | string required | Short descriptive project name |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| width | number optional | Viewport width in px (default 1280) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| steps | number optional | Number of steps (default 1) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| elements | object[] required | Array of Excalidraw element objects |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| selector | string required | CSS selector (e.g. '.hero', '#nav', 'h1') |
| styles | string[] optional | CSS property names. Defaults to a common layout set. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| code | string required | JavaScript to run in the preview. Return value + console output are captured. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| levels | string[] optional | Filter to these levels. Default: all. |
| limit | number optional | Max entries to return (1-100, default 50) |
| sinceTimestamp | number optional | Only return entries with timestamp > this (ms). Use the `now` field from a previous response to poll incrementally. |
| clear | boolean optional | Clear the log buffer after reading. Default false. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| selector | string required | CSS selector to click |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| selector | string required | CSS selector for the input/textarea/contenteditable |
| text | string required | Text to type |
| clear | boolean optional | Clear field before typing (default true) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| key | string required | KeyboardEvent.key value (e.g. 'Enter', 'ArrowUp', 'a') |
| selector | string optional | Target element (default: document.activeElement) |
| shift | boolean optional | |
| ctrl | boolean optional | |
| alt | boolean optional | |
| meta | boolean optional |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| selector | string required | CSS selector to wait for |
| text | string optional | Optional text substring the element must contain |
| timeoutMs | number optional | Max time to wait in ms (100-15000, default 5000) |
| Param | Type | Description |
|---|---|---|
| query | string required | Search query |
| max_results | number optional | Number of results (1–10, default 5) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| id | string required | Viewport preset id (mobile, tablet, desktop, etc.) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| url | string required | URL to fetch (http/https). Protocol is auto-prepended if omitted. |
| blnq_screenshot | boolean optional | Capture a screenshot too (default true) |
| contentOnly | boolean optional | Skip screenshot — return text only (default false) |
| consentSelectors | string[] optional | Extra CSS selectors for site-specific consent buttons |
Projects are a list of files keyed by path (e.g. /index.html, /page2.html, /style.css, /components/nav.js). /index.html is the default entry page and cannot be deleted; additional HTML pages can be created freely and linked with relative <a href="page2.html"> links. Other files are referenced via standard <link> and <script> tags.
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path (e.g. /index.html, /style.css) |
| startLine | number optional | 1-based start line |
| endLine | number optional | 1-based end line (inclusive) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path (e.g. /style.css) |
| content | string required | Full file content |
| force | boolean optional | Bypass shrinkage guard for intentional rewrites that are shorter than the original |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path (e.g. /utils/helpers.js) |
| content | string optional | Initial content (default: empty) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path to delete |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| path | string required | Folder path (e.g. /js or /css/components) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| path | string required | Folder path (e.g. /js or /css/components) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path (e.g. /style.css) |
| startLine | number optional | First line to replace (1-based, line-range mode) |
| endLine | number optional | Last line to replace (1-based inclusive, line-range mode) |
| oldText | string optional | Text to find (find-replace mode) |
| newText | string optional | Replacement text |
| replaceAll | boolean optional | Replace all occurrences |
The variants flow lets the AI stream multiple design options into the editor's variant picker. Plan N variants, call blnq_set_variants with the first only (the picker appears immediately), then call blnq_append_variants once per remaining variant so the user sees them arrive as they're generated. The first variant loads in the editor; the user can switch or dismiss via the picker UI.
Each variant is a { name, files: [{ filename, content }] } bundle — a complete file set, not a diff. When the user activates a variant, the browser POSTs the selected files to /api/bridge/variant/activate; dismissing the picker POSTs to /api/bridge/variant/clear. The AI can also commit one directly with blnq_select_variant (by number or name) — equivalent to the "Select" button: the server makes that variant the active project, discards the rest, and pushes variants:select so the editor loads it and closes the picker (no confirm dialog).
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| variants | object[] required | New variants to append |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| variants | object[] required | 1–4 complete design variants (use 1 for streaming — call blnq_set_variants then blnq_append_variants per variant) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| variant | string required | 1-based variant number (e.g. "2") or its name (e.g. "Editorial"). |
These tools are not exposed over MCP. They need the in-browser chat panel UI to render their effect (button cards, task lists, skill content), so external clients won't see them. They're listed here for completeness.
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| question | string required | |
| options | object[] required |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| todos | object[] required |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| id | string optional | Skill name or numeric id. Omit to list all available skills. |
These HTTP endpoints back the MCP tools and are also used directly by the browser client. All endpoints that operate on a tab require tabId (body or query depending on method).
/api/bridge/events?tabId=…
SSE stream for a browser tab. One connection per tab — reconnecting closes the old connection. Heartbeat every 30 s. Tab state is cleaned up 5 minutes after disconnect.
/api/bridge/sync
Browser mirrors its content to the server. Body: { tabId, html?, css?, js?, files?, sketch?, projectId?, projectName? }.
/api/bridge/page/ready
Browser signals that the preview iframe has finished loading. Body: { tabId }. Tools that wait for page-ready (like blnq_check_preview) resolve when this fires.
/api/bridge/approve-mcp-session
Browser approves or rejects an external MCP client's pairing request. Body: { tabId, sessionId, decision }. Resolves a held mcp:pending-approval and pushes mcp:approval-decision.
/api/bridge/search
Search across project files. Body: { tabId, query, filenames?, regex? }.
/api/bridge/variant/activate
Browser posts the selected variant's file bundle after the user picks one. Body: { tabId, files: [{ filename, content }] }. Server replaces the project files with the chosen set.
/api/bridge/variant/clear
Browser dismisses the variant picker. Body: { tabId }. Server drops stored variants and pushes variants:clear.
/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/a11y/run
Trigger an axe-core scan in the browser and wait for the result. Body: { tabId }. Query: ?timeout=15000. Returns { violations, incomplete } or 504 on timeout.
/api/bridge/a11y/results
Browser posts axe-core results. Body: { tabId, violations, incomplete }.
/api/bridge/a11y?tabId=…
Read the last cached a11y report for a tab.
/api/bridge/console/errors
Browser reports JS console errors. Body: { tabId, errors: [{ level, message, line?, col?, sourceFile? }] }. Capped at 50 errors per tab.
/api/bridge/console/clear
Clear stored console errors. Body: { tabId }.
/api/bridge/console/errors?tabId=…
Read stored console errors for a tab.
/api/bridge/console/logs
Browser reports console.log/info/debug entries (separate ring buffer from errors). Body: { tabId, logs: [{ level, message, sourceFile?, timestamp }] }. Capped at 200 entries per tab.
/api/bridge/console/logs/clear
Clear the log buffer. Body: { tabId }.
/api/bridge/cursor
Browser reports cursor position. Body: { tabId, editor, line, col }.
/api/bridge/cursor?tabId=…
Read the latest cursor position for a tab.
These are where the browser POSTs results after responding to an SSE request (the "read data back" pattern described earlier). The harness shouldn't need to call these directly.
/api/bridge/element/results
Body: { tabId, ...elementInfo }. Reply to element:query.
/api/bridge/js/results
Body: { tabId, result, error, logs? }. Reply to js:eval. logs contains any console.log/info/debug emitted during the eval window.
/api/bridge/dom/results
Body: { tabId, ok?, action, ...details, logs? }. Reply to dom:action (the unified channel for click / type / press / wait_for).
/api/bridge/screenshot/results
Body: { tabId, dataUrl, error }. Reply to a browser canvas capture request (the default screenshot path uses server-side Playwright — this is the fallback).
/api/bridge/metrics/results
Body: { tabId, ... }. Reply to metrics:run.
The SSE channel at GET /api/bridge/events?tabId=… pushes the following events. The browser client is the only intended consumer — an external harness never opens this stream.
| Event | Payload | Meaning |
|---|---|---|
| files:refresh | { files: [{ filename, content }] } | Full file list replaced — after blnq_undo or writeSketch. |
| file:create | { filename, content } | A new file was added to the project. |
| file:update | { filename, content } | An existing file's content changed. |
| file:stream | { filename, content } | Live partial content while the AI streams a file in (fine-grained tool streaming). The editor renders it as a preview as it grows; the authoritative content lands via file:create/file:update when the write completes. |
| file:delete | { filename } | A file was removed. |
| sketch:update | { elements: [...] } | Excalidraw sketch was written. |
| design:update | { css, manifest } | Compiled design tokens from /DESIGN.md changed — browser refreshes the injected design-token stylesheet. |
| variants:set | { variants: [...], originalFiles: [...] } | Variant picker was opened or extended. Payload includes full file content for every variant plus a snapshot of the pre-variants project so the user can revert. |
| variants:select | { index } | Server committed a variant (via blnq_select_variant) — browser loads it into the editor, discards the other variants, and closes the picker (the "Select" action, no confirm). |
| variants:clear | {} | Variant picker was cleared. |
| project:name | { name } | Project name was changed via blnq_set_project_name. |
| preview:refresh | {} | Browser should do a full preview rebuild. |
| a11y:run | {} | Server is requesting an axe-core scan. Browser replies via POST /api/bridge/a11y/results. |
| element:query | { selector, styles } | Server is requesting an element inspection. Browser replies via POST /api/bridge/element/results. |
| js:eval | { code } | Server is requesting a JS eval in the preview iframe. Browser replies via POST /api/bridge/js/results. |
| dom:action | { action, ...params } | Server is requesting a DOM action (click / type / press / wait_for). Browser replies via POST /api/bridge/dom/results. |
| metrics:run | {} | Server is requesting page metrics. Browser replies via POST /api/bridge/metrics/results. |
| viewport:set | { id } | Change the preview viewport preset. |
| mcp:activity | { toolName, isError, label, source } | A tool was invoked — used by the UI to flash an indicator (and drive the streaming earcon / "agenting" outline). |
| mcp:pending-approval | { sessionId, clientInfo, toolName } | An external MCP client is requesting pairing approval for this tab — browser shows a one-time approval prompt. |
| mcp:approval-decision | { sessionId, status } | A pairing request was approved or rejected — resolves the approval prompt and unblocks held tool calls. |
Projects contain multiple files, stored as a JSON array. /index.html is the default entry page (and cannot be deleted); additional HTML pages such as /about.html or /blog/post.html are fully supported. CSS and JS files are referenced with standard <link> and <script> tags.
| File | Role | Notes |
|---|---|---|
| /index.html | Default entry page | Full <!DOCTYPE html> page. Loaded for /v/:projectId. References other files with <link href="style.css"> and <script src="main.js">. |
| /*.html | Additional pages | Multiple HTML files supported. Linked via relative <a href="page2.html">; viewable at /v/:projectId/page2.html. |
| *.css | Stylesheets | Referenced via <link rel="stylesheet">. Multiple files supported. |
| *.js | JavaScript modules | Referenced via <script type="module" src="…">. Full ES module import/export. |
| /*.excalidraw | Sketch data | JSON. Rendered in the sketch tab; read/write via blnq_read_sketch / blnq_write_sketch. |
The preview uses a Service Worker that serves project files from the in-memory map — ES modules, CSS @import, and relative fetch() all resolve natively against a real origin, not srcdoc.
Bracket balance check: every edit operation runs a balance check on the affected file. If unclosed or unmatched {}, (), or [] are detected, a ⚠ SYNTAX warning is returned alongside the successful edit result. This catches broken structure immediately rather than at preview time.
Undo history: every write, edit, create, and delete pushes a snapshot onto the per-tab undo stack. The blnq_undo tool restores prior snapshots and reports which files were affected. History is capped at 30 steps per tab.