Skip to content

perf(install): parallelize tool validation and stream live progress#113

Merged
Minitour merged 2 commits into
developfrom
feat/parallel-install-validation
Jun 11, 2026
Merged

perf(install): parallelize tool validation and stream live progress#113
Minitour merged 2 commits into
developfrom
feat/parallel-install-validation

Conversation

@Minitour

@Minitour Minitour commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

capa install against a project with many MCP tools used to take roughly a second per tool while the CLI sat on a single static "Configuring tools — validating…" spinner. On a reference project with 65 tools across 6 MCP servers, the configure step took ~62s end-to-end. After this change it takes ~4.8s (~13× faster) and shows live per-server progress.

The win comes from two changes on the server plus a streaming channel that lets the CLI surface the progress:

  1. Tool validation is now per-server, not per-tool. validateTools groups MCP tools by serverId, fires one listTools per server in parallel via Promise.all, then matches expected tool names against the returned set in memory. Wall time goes from Σ Tᵢ (sum of all servers, sequential) to max(Tᵢ) (slowest server, parallel).
  2. OAuth2 detection runs in parallel. The for (const server of capabilities.servers) { await detectOAuth2Requirement(...) } loop in handleProjectConfigure is now Promise.all, with order-preserved result collection.
  3. NDJSON progress streaming. When the client sends Accept: application/x-ndjson, the /api/projects/:id/configure endpoint returns a chunked stream of progress events terminated by a {type: "result", ...} line. The CLI consumes these and updates the spinner per event. Backward compatible: any client that doesn't set the header (or any older CLI hitting a newer server) still gets the original single-JSON response.

What the user sees now

❯ Configuring tools
  › OAuth2 6/7
  › 20/65 validated · server-a ✓
  › 23/65 validated · server-b ✓
  › 25/65 validated · server-c ✓
  › 28/65 validated · server-d ✓
  › 34/65 validated · server-e ✓
  › 65/65 validated · server-f ✓
✔ Configuring tools — 65 validated

Files

  • src/server/mcp-handler.ts — rewrite validateTools, add ValidationProgressEvent export.
  • src/server/index.ts — extract runProjectConfigure(...) from handleProjectConfigure, parallelize OAuth2 detection, add NDJSON streaming branch.
  • src/cli/commands/install-tasks/configure-tools.ts — stream-consume NDJSON, update task.output per event, fall back to response.json() if Content-Type isn't NDJSON.
  • src/cli/ui/index.ts — export TaskWrapper type so the stream consumer can be typed.

Test plan

  • bunx tsc --noEmit passes
  • npm run build succeeds
  • Verified against a 65-tool / 6-MCP-server / 7-OAuth2-server project: 62s → 4.8s install
  • Live spinner updates per OAuth2 probe and per server validation
  • Backward compat: a client that omits Accept: application/x-ndjson should still get the same single-JSON body it always did (verify by hitting the endpoint with curl -H "Accept: application/json" against a configured project)
  • Failure path: inject a non-existent tool name into a project's capabilities.yaml, run install, confirm the failed-tools error message still surfaces correctly through the streamed result event

🤖 Generated with Claude Code

`capa install` for a 65-tool / 6-server project dropped from ~62s to ~4.8s
(~13x) and now shows a live "N/M validated · last-server ✓" counter
instead of a static spinner.

- `validateTools` groups MCP tools by server and fans out one `listTools`
  per server with `Promise.all`, then matches expected names against the
  returned set in memory. Accepts an optional `onProgress` callback and
  emits `validation_init` / `server_done` events.
- `handleProjectConfigure` extracted into `runProjectConfigure(...)` so
  the existing logic can be reused with progress hooks. OAuth2 detection
  now runs in parallel across servers.
- The endpoint emits NDJSON progress events when the client sends
  `Accept: application/x-ndjson`; otherwise it returns the same single
  JSON body as before — fully backward compatible.
- The CLI's configure-tools task consumes the NDJSON stream and updates
  `task.output` per event, with a graceful fallback to `response.json()`
  for older servers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR speeds up capa install project configuration for projects with many MCP tools by parallelizing server-side validation work and adding an opt-in NDJSON progress stream so the CLI can render live progress (per OAuth2 probe and per MCP server validation) while keeping a JSON fallback for older clients.

Changes:

  • Server: parallelize tool validation per MCP server (one listTools() per server, Promise.all) and export typed validation progress events.
  • Server: parallelize OAuth2 requirement detection across servers and add an NDJSON streaming response mode for /api/projects/:id/configure.
  • CLI: request NDJSON streaming, consume progress events to update task spinner live, and fall back to single-JSON parsing when streaming isn’t available.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/server/mcp-handler.ts Reworks validateTools() to validate per-server in parallel and emits progress events for streaming clients.
src/server/index.ts Extracts runProjectConfigure(), parallelizes OAuth2 probing, and adds NDJSON streaming support for live configure progress.
src/cli/commands/install-tasks/configure-tools.ts Requests NDJSON, consumes progress stream to update spinner output, and preserves JSON fallback.
src/cli/ui/index.ts Re-exports TaskWrapper type for typing streamed progress consumption.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/server/index.ts Outdated
Comment thread src/server/index.ts
Two issues raised by review on the streaming branch of
handleProjectConfigure:

1. After `cancel()`, `controllerRef` was nulled and subsequent `emit`
   calls fell through to `pending.push(...)` — but `start()` will never
   run again, so the buffer grew unbounded for the rest of the (possibly
   long-running) runProjectConfigure work.
2. The `work.then(...)` completion handler called `controller.enqueue`
   and `controller.close` without try/catch. If the client disconnected
   before completion, both throw and surface as an unhandled rejection.

Fix: collapse the two paths through a single `writeLine` that respects
a `streamClosed` flag, set both on client cancel and on terminal write,
and drop the pending buffer on cancel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Minitour Minitour merged commit b545a9c into develop Jun 11, 2026
7 checks passed
@Minitour Minitour deleted the feat/parallel-install-validation branch June 11, 2026 09:57
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