Skip to content

[Bug]: site_insert_html with an unsupported element hangs the MCP bridge for the whole session (survives reconnect and tab reload) #490

Description

@weemax

Summary

site_insert_html with an element the importer has no module for (<link>) does not
return a validation error. It hangs, and it leaves the MCP bridge permanently wedged.

After that single call, every browser-routed MCP tool fails with:

AI chat stream ended before tool result arrived.

This is not scoped to the tool or the document that triggered it. It takes down the
Site editor and the Content workspace together, and it survives every recovery I could
attempt from the client side.

Found while pushing a 7-page static site into a hosted Instatic instance over the MCP
connector. The rest of that session surfaced several smaller issues, listed at the
bottom; happy to split any of them into separate issues if you prefer.

Steps to reproduce

  1. Connect the MCP connector with the Site editor open.
  2. Confirm editor-routed tools work (site_get_node_html on any node returns HTML).
  3. Call site_insert_html with an unsupported element:
{ "parentId": "<any page rootNodeId>", "index": 0,
  "html": "<link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />" }
  1. The call never returns a result.
  2. Every subsequent browser-routed call fails with the stream error above.

Expected behavior

An unsupported element is rejected with a validation error naming the element, the way
an invalid site_apply_css selector would be. A malformed import must not be able to
wedge the bridge.

Actual behavior

The bridge stops servicing browser-routed requests for the remainder of the session.

Confirmed still broken after each of these:

  • reloading the Site editor browser tab
  • /mcp reconnect of the connector (twice)
  • reconnect first, then reload the tab
  • opening the Content workspace instead (a different workspace, same failure)

Tools verified failing after the hang: site_get_node_html, site_read_document,
site_list_code_assets (including type=style, which returns one small asset, so
payload size is not the cause), site_inspect_code_runtime,
content_set_active_document, content_set_document_fields.

Headless tools kept working normally throughout: get_context,
site_list_documents, site_publish, content_get_collection_schema,
content_list_documents, content_get_document. So the server and the data layer are
fine; it is specifically the browser bridge that is stuck. That split is also what made
it recoverable enough to finish the work: I could still publish.

One detail that may help localise it. Immediately after a /mcp reconnect, the first
editor call returned a clean, correct error:

This tool runs in the Instatic Site editor. Open the Site editor in a browser ...

and the very next call was wedged again. That looks like the stale client re-registering
onto the fresh bridge and re-poisoning it, rather than the bridge itself being
unrecoverable.

Version or commit

Hosted Railway deployment, tested 2026-09-05. Connector over HTTP MCP.

Deployment mode

Hosted (Railway)


Secondary: get_context reports a connection that services nothing

Throughout the wedge, get_context kept returning:

{"editor":{"siteConnected":true,"contentConnected":false}}

siteConnected: true while every editor call times out. It appears to reflect a
registration record rather than a live ping. This cost several diagnostic cycles,
because the obvious reading is "connected, so the problem is my call". A liveness check,
or a lastSeen timestamp, would make this self-diagnosing.


Other findings from the same session

Separate from the hang. All verified on the same deployment.

1. Routes resolve at both /path and /path/ with no canonicalisation

/web-ops and /web-ops/ both return 200 and neither redirects to the other. Because
relative hrefs resolve against the current URL, every relative link on a page breaks
when the visitor arrives at the unslashed form:

from /web-ops from /web-ops/
assessment/ -> /assessment/ (404) -> /web-ops/assessment/ (200)
why/ -> /why/ (404) -> /web-ops/why/ (200)

Any site authored with relative links is half-broken depending on entry URL, and both
forms are independently indexable. Worked around by rewriting all 78 internal links to
root-absolute. A 301 to one canonical form, or a <link rel="canonical">, would fix it.

2. site_replace_node_html silently drops <script>

Body HTML containing a <script> imports without it. No warning, no error; the returned
nodeIds simply omit it. Distinct from #411 (attributes) in that the whole element
disappears. Scripts do have a legitimate home in the code-asset layer, so the fix may
just be to say so in the tool description and/or return a notice.

3. site_apply_css can silently no-op

site_apply_css merge ".compare-head .is-us { color: var(--fg); }"
-> {"cssRulesCreated": 0, "cssRulesUpdated": 0}

No error, no rule written. An existing .compare-head span rule ties it on specificity.
Raising specificity to .compare-head span.is-us created it. A zero/zero result with
supplied CSS should report why.

4. CMS form: honeypotName injects a duplicate field

Setting honeypotName to a field name that already exists in the page markup makes
Instatic inject a second input with that name. The value then submits as an array:

"values": { "company_website_url": ["", ""], ... }

and the endpoint rejects it with {"error":"Invalid form submission"}. Every submission
fails. Deleting the authored field fixed it. Worth either detecting the collision or
saying in the prop description that the honeypot is generated and must not exist in
markup.

5. honeypotName defaults to company

base.form defaults honeypotName to "company". That is an extremely common real
field name; our lead form had a required company field. Left at the default, every
genuine submission is silently classified as spam. A default that cannot collide
(_instatic_hp, say) would be safer.

6. Re-importing a page's HTML resets form node props

site_replace_node_html recreates the node tree, so a form configured via
site_update_node_props (mode: cms, targetTableId, honeypotName, redirectUrl)
silently reverts to importer defaults, including back to mode: custom. On a live lead
form that means submissions stop being stored with no visible change. Possibly related
to #466.

7. No favicon support

Published <head> contains no <link rel="icon">. /favicon.svg serves a placeholder
that nothing references, and browsers only auto-probe /favicon.ico, which 404s. So a
site cannot have a favicon at all, and there is no MCP surface or <head> extension
point to add one.

8. #411 still reproduces

#411 (image alt dropped, published
alt sourced only from Media Library metadata) is closed, but reproduces on this
deployment as of 2026-09-05: 8 images pushed with descriptive alt all published as
alt="", giving 0 of 14 images site-wide any alt text. Combined with #416 (no media
metadata write in MCP), alt text is currently unreachable end to end for an
agent-driven workflow.

Related existing issues

Not re-reporting these, listing them so the SEO picture is in one place:
#219 (seoTitle/seoDescription unreachable for Pages), #425 (root-level
sitemap.xml/robots.txt), #416 (MCP media metadata lifecycle), #411 (image alt),
#380 (honeypot rejections unobservable).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions