Skip to content

UI edits restart the MCP server and can break ChatGPT app template loading #133

Description

@Waishnav

Summary

Editing files under src/ui/** while running npm run dev restarts the entire DevSpace MCP server. This closes every active MCP session and can leave ChatGPT's separate MCP App template-fetch lifecycle pointing at an invalid session.

The tool call itself can still complete successfully, but the card then fails to render with:

Error loading app
Failed to fetch template
Image

The current development command also does not run Vite in watch mode, so UI changes are not rebuilt automatically. The UI bundle only changes after an explicit npm run build:app or full build.

Observed behavior

During a failed show_changes card render, the server logged a successful tool execution:

2026-08-06T08:45:02.291Z INFO tool_call tool="show_changes"
workspaceId="ws_595b59ba87" success=true durationMs=117

2026-08-06T08:45:02.291Z INFO http_request
method="POST" path="/mcp" status=200 durationMs=119

The app failed afterward while ChatGPT was trying to load the MCP App template. No corresponding app asset request reached DevSpace for that failed card.

The same development session showed repeated full-server restarts after source edits:

mcp_session_closed reason="server_shutdown"
devspace listening on http://127.0.0.1:7676/mcp

In an earlier matching sequence, ChatGPT sent a non-initialize MCP request without a valid session after show_changes, received HTTP 400, and then initialized a new session. This suggests the tool invocation and app-template fetch use separate MCP request/session lifecycles, and a development restart can desynchronize them.

The app registration itself was valid when tested independently:

  • tool metadata contained both ui.resourceUri and the compatibility ui/resourceUri key;
  • the resource returned text/html;profile=mcp-app;
  • the generated HTML referenced the current Vite manifest entry;
  • the current JS and CSS assets returned HTTP 200.

This makes the failure a development lifecycle problem rather than a show_changes computation or React rendering failure.

Current implementation

npm run dev runs scripts/dev-server.mjs.

That script:

  1. recursively watches all of src/;
  2. restarts tsx src/cli.ts serve after any source change;
  3. therefore treats UI-only changes exactly like backend changes.

The Vite configuration only exposes a production-style build:

"build:app": "vite build"

There is no vite build --watch process in the current dev command.

Expected behavior

  • Editing src/ui/** should automatically rebuild the MCP App without restarting the MCP server.
  • Existing MCP sessions should remain alive across CSS and React changes.
  • The next tool card created after a successful rebuild should use the latest successful UI bundle.
  • A template request that overlaps a rebuild should receive either the previous successful build or the completed new build, never a partially published manifest.
  • Backend changes may continue restarting the MCP server.

Proposed plan

1. Separate UI rebuilds from backend restarts

Run two independent development processes:

  • UI builder: vite build --watch, watching src/ui/** and writing the app bundle to dist/ui.
  • MCP server watcher: watch backend sources while excluding src/ui/** and UI build output.

Start the MCP server only after the initial UI build has completed successfully.

This is the minimum change needed to prevent UI edits from closing ChatGPT's MCP sessions.

2. Publish only complete UI builds

The server currently reads dist/ui/.vite/manifest.json for each app resource response and serves content-hashed assets with immutable caching. Keep that model, but make the development publishing path resilient:

  • retain assets from the previous successful build while a rebuild is running;
  • avoid exposing a new manifest until all assets referenced by it exist;
  • retry manifest and asset validation briefly when a resource read lands during the publish window;
  • leave the previous successful build usable when a Vite rebuild fails;
  • clean stale development assets on startup or through a bounded cleanup policy rather than deleting assets that an already-created card may still reference.

3. Verify template freshness in ChatGPT

First verify whether ChatGPT performs resources/read for the fixed URI on each new tool card. If it does, the current dynamic manifest lookup is enough for new cards once server restarts are removed.

If the host caches the fixed ui://devspace/workspace-app.html template, add an explicit development cache-busting mechanism. Possible approaches:

  • a stable, no-cache loader asset that fetches a no-cache current-manifest endpoint and dynamically loads the latest hashed JS and CSS; or
  • a build-versioned resource URI combined with tool metadata refresh and notifications/tools/list_changed.

Prefer the smallest approach that works reliably in ChatGPT and remains compatible with other MCP App hosts.

4. Improve diagnostics

Include the JSON-RPC method in MCP request logs so resources/read, tools/list, and tools/call can be distinguished directly.

Also log UI build publication events, including the active manifest entry or build identifier. This should make future template failures attributable without reconstructing them from HTTP timing.

Optional follow-up: reload already-open cards

True hot reload of an iframe that is already open is not required for the initial fix.

It could be added later as a development-only feature using polling or SSE to reload the iframe after a successful build. Normal Vite WebSocket HMR may be less reliable through ChatGPT's remote sandbox, cross-origin CSP, and tunnel setup, so it should not be required for the first implementation.

Acceptance criteria

  • Saving a file under src/ui/** automatically rebuilds the UI.
  • A UI-only edit does not restart the MCP server or close active MCP sessions.
  • The next show_changes or open_workspace card after a successful rebuild renders the updated JS and CSS in ChatGPT.
  • A resource read during a rebuild never receives a manifest that references missing assets.
  • A failed UI rebuild leaves the previous successful UI build available.
  • Backend source changes still restart the server as expected.
  • Old hashed assets remain available long enough for cards created before a rebuild to finish loading lazy chunks.
  • Logs identify resources/read requests and the active UI build.
  • The behavior is manually verified through the public tunnel in ChatGPT.

Non-goals

  • Preserving in-memory MCP sessions across backend server restarts.
  • Full Vite HMR inside an already-rendered ChatGPT iframe.
  • Changing production asset hashing or immutable caching semantics unless required by the chosen development loader.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions