Skip to content

Studio CLI: Load the MCP server only when studio mcp runs - #5235

Merged
chubes4 merged 2 commits into
trunkfrom
studio-cli/lazy-mcp-runtime
Oct 21, 2026
Merged

chubes4 merged 2 commits into
trunkfrom
studio-cli/lazy-mcp-runtime

Conversation

@chubes4

@chubes4 chubes4 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Related issues

How AI was used in this PR

Claude (via the Pi coding agent in a Roadie session) profiled studio startup, traced the import chain, wrote the change and the test, and took the timings below. I reviewed the diff and the numbers.

Proposed Changes

Every studio command loaded the whole agent runtime before running. The CLI entry imports each command module up front, and commands/mcp.ts imported the MCP server at its top, which brings every agent tool and the Pi agent with them (highlight.js, typebox, undici, …). studio --version loaded 3,765 modules, and about 4,000 in the shipped 1.24.0 app.

  • studio mcp now loads the MCP server inside the command, the same way studio ai already loads its runtime.
  • A test imports everything the CLI entry imports statically and checks the agent runtime stays unloaded. It reads the imports from index.ts, so a command added later is covered too. It fails on trunk.
  • Vitest gets the @studio/local alias the build already has. Without it, no test could load commands/ui.ts.

Testing Instructions

  • npx vitest run apps/cli/tests/startup-imports.test.ts passes; with the commands/mcp.ts change reverted, it fails.
  • npx vitest run --project cli: 1,158 pass.
  • From a built CLI (cd apps/cli && npx vite build --config ./vite.config.dev.ts), same machine, 5 runs each:
Command Trunk This branch
node dist/cli/main.mjs --version 1.8 s, 3,765 modules 0.7 s, 1,132 modules
node dist/cli/main.mjs wp --path=<site> option get siteurl 1.29–1.57 s (median 1.40) 0.93–1.15 s (median 1.00)
  • studio mcp still starts the MCP server: after initialize, tools/list returns its 30 tools.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

commands/mcp.ts imported the MCP runtime at the top, and the CLI entry imports
every command module at startup, so every studio command loaded the whole agent
runtime (the Pi agent, its tools, highlight.js, typebox) before running:
3,765 modules for studio --version. The MCP runtime now loads inside the
command, as studio ai already does. A test imports everything the entry
imports statically and checks the agent runtime stays unloaded. Vitest gets the
@studio/local alias the build already has, so commands/ui.ts can load in tests.
@chubes4
chubes4 marked this pull request as ready for review October 21, 2026 04:14
@wpmobilebot

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 017afa5 vs trunk

app-size

Metric trunk 017afa5 Diff Change
App Size (Mac) 922.00 MB 922.01 MB +0.00 MB ⚪ 0.0%

site-editor

Metric trunk 017afa5 Diff Change
load 1208 ms 1217 ms +9 ms ⚪ 0.0%

site-startup

Metric trunk 017afa5 Diff Change
siteCreation 8568 ms 7073 ms 1495 ms 🟢 -17.4%
siteStartup 5892 ms 4374 ms 1518 ms 🟢 -25.8%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@chubes4
chubes4 merged commit c80a177 into trunk Oct 21, 2026
12 checks passed
@chubes4
chubes4 deleted the studio-cli/lazy-mcp-runtime branch October 21, 2026 04:50
chubes4 added a commit that referenced this pull request Oct 23, 2026
## Related issues

- Follows #5235, which stopped every `studio` command from loading the
agent runtime. The Studio app runs the CLI for nearly everything (site
list, start, stop, events), so CLI startup time adds to each of those.

## How AI was used in this PR

Claude (via the Pi coding agent in a Roadie session) profiled `studio`
startup in the packaged app, found the cost, wrote the change and the
test, and took the timings below. I reviewed the diff and the numbers.

## Proposed Changes

The CLI keeps its npm dependencies external, so an import of the
`date-fns` root loads every date-fns function: about 300 modules, for
the 8 functions Studio uses. With the packaged app's Node and
`node_modules`, `import( 'date-fns' )` alone takes about 0.9 s; `import(
'date-fns/isSameDay' )` takes about 15 ms. Every `studio` command paid
this, because `lib/snapshots.ts` and `lib/site-runtime-stats.ts` are on
the startup path.

- Each date-fns function is imported from its own path
(`date-fns/format`, …), in the CLI and in the two `@studio/common` files
the CLI uses. `DurationUnit` stays a type-only import from the root,
which is erased at build time.
- The startup-imports test from #5235 now also checks that nothing on
the startup path loads the `date-fns` root. It fails on trunk.

## Testing Instructions

- `npx vitest run apps/cli/tests/startup-imports.test.ts` passes; on
trunk it fails.
- CLI unit tests (124 files, 1,241 tests) and `packages/common` tests
(87 files, 862 tests) pass.
- Timings on an M-series Mac, using the packaged app's
`resources/bin/node` and `resources/cli/node_modules` with each build's
`dist/cli`, runs interleaved:

| | Trunk | This branch |
|---|---|---|
| `studio site list --help`, 12 runs | median 1.13 s (0.83–3.36) |
median 0.53 s (0.42–1.62) |
| `studio --version`, 7 runs | median 1.53 s | median 0.72 s |
| Modules loaded by `studio --version` | 863 | 621 |

- The "Performance Test Results" comment on this PR should show lower
`siteCreation` and `siteStartup`.

## Pre-merge Checklist

- [x] Have you checked for TypeScript, React or other console errors?
chubes4 added a commit that referenced this pull request Oct 27, 2026
## Related issues

- Follows #5235, #5313 and #5316, which kept the MCP server, the
`date-fns` root and `@inquirer/prompts` off CLI startup.

## How AI was used in this PR

Claude (via the Pi coding agent in a Roadie session) profiled CLI
startup, wrote the change and the test, and measured the before/after
timings. I reviewed the diff and the measurements.

## Proposed Changes

`apps/cli/index.ts` statically imported every top-level command module,
and each module held the command's whole implementation. So every
`studio` invocation loaded the implementation of every command before
running one, including the export, import, push and pull code and its
archive, upload and certificate libraries: 1,375 dependency files from
109 packages for `studio --version`.

Each top-level command now has a `.command` module that defines the
command (name, description, options) and loads the implementation with
`await import()` in its handler, the pattern `wp` and `_events` already
use. The implementation modules keep their paths and their `runCommand`
exports, so their other callers are unchanged; each one's former handler
body is now its exported `commandHandler`.

- `studio list`, `start`, `stop`, `create` and `wp` load 520 dependency
files at startup instead of 1,375.
- Help output, unknown-command errors and option validation are
byte-identical to trunk.
- A `CommandArgs< typeof builder >` type keeps each handler typed from
its command's options.

Local timings on macOS (median of 8 alternating runs, Node 24):

| Command | trunk | this PR |
| --- | --- | --- |
| `studio --version` | 0.40 s | 0.21 s |
| `studio list` | 0.39 s | 0.23 s |
| `studio status` | 0.42 s | 0.24 s |
| `studio wp option get blogname` | 0.67 s | 0.48 s |
| `studio stop` / `start` (already in that state) | 0.44 s | 0.26 s |

The saving is a fixed cost per CLI process, so it applies to every
command the desktop app and Studio Code run through the CLI.

## Testing Instructions

- `npx vitest run --project cli apps/cli/tests/startup-imports.test.ts`:
loading what `index.ts` imports statically must not load any command
implementation. Making a `.command` module import its implementation
statically fails it.
- Build the CLI and compare `studio --help`, `studio create --help` and
`studio foo` with trunk's output.
- Time `studio list` or `studio wp option get blogname` against trunk.

## Pre-merge Checklist

- [x] Have you checked for TypeScript, React or other console errors?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants