From 6e5f4786a6451583dd0a40701a987dad943f6bff Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Tue, 3 Feb 2026 10:05:06 -0600 Subject: [PATCH] Work around `tsx watch` hang on Windows in quickstart On Windows, `tsx watch` can hang when running under `concurrently` due to stdin handling conflicts (see [privatenumber/tsx#623][]). Adding the `--raw` flag to `concurrently` avoids this by preventing stdin interception. Since `--raw` mode disables concurrently's automatic output prefixing, a custom Vite logger is added to prefix messages with `[vite]` so users can still distinguish which process produced each line of output. [privatenumber/tsx#623]: https://github.com/privatenumber/tsx/issues/623 Co-Authored-By: Claude Opus 4.5 --- docs/quickstart.md | 13 ++++++++++--- examples/quickstart/package.json | 2 +- examples/quickstart/vite.config.ts | 9 ++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 3fb11984e..36c305e33 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -44,11 +44,11 @@ Configure your [`package.json`](https://github.com/modelcontextprotocol/ext-apps ```bash npm pkg set type=module npm pkg set scripts.build="tsc --noEmit && tsc -p tsconfig.server.json && cross-env INPUT=mcp-app.html vite build" -npm pkg set scripts.start='concurrently "cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch" "tsx watch main.ts"' +npm pkg set scripts.start='concurrently --raw "cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch" "tsx watch main.ts"' ``` > [!NOTE] -> Windows `cmd.exe` users will need to convert quotes in the above command: `npm pkg set scripts.start="concurrently ""cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch"" ""tsx watch main.ts"""`. +> Windows `cmd.exe` users will need to convert quotes in the above command: `npm pkg set scripts.start="concurrently --raw ""cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch"" ""tsx watch main.ts"""`.
Create tsconfig.json: @@ -111,7 +111,7 @@ npm pkg set scripts.start='concurrently "cross-env NODE_ENV=development INPUT=mc ```ts source="../examples/quickstart/vite.config.ts" -import { defineConfig } from "vite"; +import { createLogger, defineConfig } from "vite"; import { viteSingleFile } from "vite-plugin-singlefile"; const INPUT = process.env.INPUT; @@ -121,7 +121,14 @@ if (!INPUT) { const isDevelopment = process.env.NODE_ENV === "development"; +const prefixedLogger = createLogger(); +for (const level of ["info", "warn", "error"] as const) { + const fn = prefixedLogger[level]; + prefixedLogger[level] = (msg, opts) => fn(msg.replace(/^/mg, "[vite] "), opts); +} + export default defineConfig({ + customLogger: prefixedLogger, plugins: [viteSingleFile()], build: { sourcemap: isDevelopment ? "inline" : undefined, diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json index aa0adc62a..eaca5c948 100644 --- a/examples/quickstart/package.json +++ b/examples/quickstart/package.json @@ -12,7 +12,7 @@ "license": "MIT", "scripts": { "build": "tsc --noEmit && tsc -p tsconfig.server.json && cross-env INPUT=mcp-app.html vite build", - "start": "concurrently \"cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch\" \"tsx watch main.ts\"" + "start": "concurrently --raw \"cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch\" \"tsx watch main.ts\"" }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.0.0", diff --git a/examples/quickstart/vite.config.ts b/examples/quickstart/vite.config.ts index 6ff6d9979..d34af5367 100644 --- a/examples/quickstart/vite.config.ts +++ b/examples/quickstart/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from "vite"; +import { createLogger, defineConfig } from "vite"; import { viteSingleFile } from "vite-plugin-singlefile"; const INPUT = process.env.INPUT; @@ -8,7 +8,14 @@ if (!INPUT) { const isDevelopment = process.env.NODE_ENV === "development"; +const prefixedLogger = createLogger(); +for (const level of ["info", "warn", "error"] as const) { + const fn = prefixedLogger[level]; + prefixedLogger[level] = (msg, opts) => fn(msg.replace(/^/mg, "[vite] "), opts); +} + export default defineConfig({ + customLogger: prefixedLogger, plugins: [viteSingleFile()], build: { sourcemap: isDevelopment ? "inline" : undefined,