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
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:
- recursively watches all of
src/;
- restarts
tsx src/cli.ts serve after any source change;
- 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
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.
Summary
Editing files under
src/ui/**while runningnpm run devrestarts 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:
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:appor full build.Observed behavior
During a failed
show_changescard render, the server logged a successful tool execution: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:
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:
ui.resourceUriand the compatibilityui/resourceUrikey;text/html;profile=mcp-app;This makes the failure a development lifecycle problem rather than a
show_changescomputation or React rendering failure.Current implementation
npm run devrunsscripts/dev-server.mjs.That script:
src/;tsx src/cli.ts serveafter any source change;The Vite configuration only exposes a production-style build:
There is no
vite build --watchprocess in the current dev command.Expected behavior
src/ui/**should automatically rebuild the MCP App without restarting the MCP server.Proposed plan
1. Separate UI rebuilds from backend restarts
Run two independent development processes:
vite build --watch, watchingsrc/ui/**and writing the app bundle todist/ui.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.jsonfor each app resource response and serves content-hashed assets with immutable caching. Keep that model, but make the development publishing path resilient:3. Verify template freshness in ChatGPT
First verify whether ChatGPT performs
resources/readfor 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.htmltemplate, add an explicit development cache-busting mechanism. Possible approaches: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, andtools/callcan 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
src/ui/**automatically rebuilds the UI.show_changesoropen_workspacecard after a successful rebuild renders the updated JS and CSS in ChatGPT.resources/readrequests and the active UI build.Non-goals