devctl-mcp
devctl-mcp
An MCP (Model Context Protocol) server that lets Claude manage long-running dev processes seamlessly — across Flutter, Next.js, Spring Boot, Vite, and any custom stack.
The problem it solves: When Claude spawns flutter run, next dev, or gradle bootRun via bash, it immediately loses control. No hot reload, no log access, no restart. This server runs as a persistent daemon that owns all dev processes and exposes simple tools Claude can call.
Features
Flutter — auto-detect FVM, target specific devices, hot reload & hot restart via Dart VM service
Next.js — auto-detect package manager (npm / pnpm / yarn / bun) from lockfiles
Spring Boot — auto-detect Maven or Gradle, use
mvnw/gradlewwrappers when presentVite / React — detect
vite.config.*orreact-scriptsinpackage.jsonGeneric — run any custom command with full log capture
Log streaming — circular buffer (1000 lines), filter by string, timestamps on demand
Full process lifecycle — start, stop, restart with status tracking (starting → running → stopped/crashed)
Installation
Prerequisites
Node.js 20+
Claude Code CLI
Setup
git clone git@github.com:adarsh-devappsys/devctl-mcp.git
cd devctl-mcp
npm install
npm run buildRegister globally with Claude Code
claude mcp add -s user devctl -- node /absolute/path/to/devctl-mcp/dist/index.jsVerify it's connected:
claude mcp list
# devctl: node .../dist/index.js - ✓ ConnectedThe server will be available in all your Claude Code sessions globally.
Project-level (alternative)
The repo ships with a .mcp.json — if you open this repo in Claude Code, it's auto-loaded without any extra setup.
Available Tools
Core — works for all frameworks
Tool | Description |
| List all managed processes with status, uptime, PID |
| Start a dev server — auto-detects the framework |
| Gracefully stop a process (SIGTERM → SIGKILL after 5s) |
| Stop + restart with original configuration |
| Get recent log output, with optional line count and filter |
| Clear the log buffer for a process |
| Send raw text to process stdin (escape hatch) |
Flutter-specific
Tool | Description |
| Hot reload via Dart VM service, falls back to stdin |
| Hot restart via Dart VM service, falls back to stdin |
| List connected Flutter devices and emulators |
Usage Examples
Flutter
List my connected Flutter devicesStart my Flutter app at /Users/me/projects/myapp, name it "myapp", on my Pixel deviceHot reload myappHot restart myappGet the last 100 logs from myapp, filter by "error"With FVM (auto-detected):
If your project has .fvm/flutter_sdk or fvm_config.json, FVM is used automatically. You can also force it:
Start myapp at /path/to/project with use_fvm true, device id "32211JEHN06807"Next.js
Start the Next.js project at /Users/me/projects/website, name it "web"Package manager is auto-detected from lockfiles (bun.lockb → bun, pnpm-lock.yaml → pnpm, yarn.lock → yarn, else npm). Override with package_manager: "pnpm".
Spring Boot
Start /Users/me/projects/api, name it "api"Maven vs Gradle auto-detected from pom.xml / build.gradle. Uses ./mvnw or ./gradlew wrappers when present.
Any other stack
Start /Users/me/projects/backend, name it "django", command "python manage.py runserver"Checking logs
Show me the logs for myappGet last 50 lines from api, filter by "Exception"Get logs from web with timestampsFramework Auto-Detection
The adapter is chosen automatically based on what's in your project directory:
Framework | Detection |
Flutter |
|
Spring Boot |
|
Next.js |
|
Vite |
|
Generic | Fallback — requires |
Priority: Flutter → Spring Boot → Next.js → Vite → Generic
FVM Auto-Detection (Flutter)
FVM is auto-enabled when both are true:
fvmbinary is inPATHProject has
.fvm/flutter_sdk,fvm_config.json, or.fvmrc
Override: pass use_fvm: true or use_fvm: false explicitly.
Package Manager Auto-Detection (JS projects)
Detected from lockfiles in priority order:
bun.lockb→ bunpnpm-lock.yaml→ pnpmyarn.lock→ yarnFallback → npm
How Flutter Hot Reload Works
The server uses a two-tier system for maximum reliability:
Tier 1 — Dart VM service (preferred)
When flutter run starts, it prints a line like:
A Dart VM Service on Pixel 7a is available at: http://127.0.0.1:56789/TOKEN=/The server captures this URL and derives a WebSocket endpoint. Hot reload/restart is sent as JSON-RPC over WebSocket — the same protocol Flutter DevTools uses.
Hot reload sequence: getVM → reloadSources → callServiceExtension('ext.flutter.reassemble')
Hot restart sequence: getVM → callService('hotRestart')
Tier 2 — stdin fallback
If the VM service URL isn't available yet (app still starting up), the server sends r or R to the process stdin — Flutter's interactive keyboard commands.
Development
# Run without building (tsx watches for changes)
npm run dev
# Build
npm run build
# Type-check only
npm run typecheckProject Structure
src/
index.ts ← MCP server entry, stdio transport
types.ts ← Shared TypeScript interfaces
process-manager.ts ← Process registry, spawn, lifecycle, VM service URL parsing
log-store.ts ← Circular buffer (1000 lines, stdout + stderr + timestamps)
adapters/
flutter.ts ← Flutter + FVM detection
nextjs.ts ← Next.js + package manager detection
spring-boot.ts ← Maven/Gradle + wrapper detection
vite.ts ← Vite/React-scripts detection
generic.ts ← Fallback for custom commands
registry.ts ← Ordered detection chain
tools/
process-tools.ts ← 7 core MCP tools
flutter-tools.ts ← Flutter hot reload/restart + list_devicesImportant Notes
Session persistence: Managed processes live as long as the Claude Code session is open. If you close the session, running processes are stopped. This is a fundamental constraint of the stdio MCP transport.
stdout is reserved: The MCP protocol uses
process.stdoutfor JSON-RPC messages. All internal server logs go tostderr.PATH inheritance: The server inherits your shell
PATH, sofvm,flutter,mvn,gradle, etc. must be on your PATH as they would be in a terminal.