Work around tsx watch hang on Windows in quickstart (#428)

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 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-03-06 08:32:30 -06:00 • committed by GitHub
commit c000ba6dd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 19 additions and 5 deletions

View file

@ -46,11 +46,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"""`.
<details>
<summary>Create <a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/quickstart/tsconfig.json"><code>tsconfig.json</code></a>:</summary>
@ -113,7 +113,7 @@ npm pkg set scripts.start='concurrently "cross-env NODE_ENV=development INPUT=mc
<!-- prettier-ignore -->
```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;
@ -123,7 +123,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,

View file

@ -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",

View file

@ -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,