Skip to main content
Glama
vinsmoke-sanji59

Java Project Runner MCP

README.md
# Java Project Runner MCP (v1.1)

Single-process MCP server (stdio) that builds and manages **registered** Spring Boot + Maven projects on macOS: list / inspect / build / start / stop / restart / status / logs / registry CRUD / reclaim.

**Locked behavior**

- Start runs the configured JAR via `java -jar` — not `spring-boot:run`
- Before start: Maven `package` (default) **unless** the JAR is fresher than `pom.xml` + `src/**` sources, or `skipBuild` is set; use `rebuild: true` to force package
- Prefer `./mvnw` when executable; otherwise system `mvn`
- `-DskipTests` default **on** (configurable per call / registry)
- Stop signals the **managed process group only** (never by port / main-class)
- Registry: hand-edit `~/.java-project-runner/projects.json` **or** use `project_register` / `project_update` / `project_unregister`
- Do **not** run multiple servers against the same registry concurrently

## Requirements

- Node.js **20+** (LTS)
- macOS (v1)
- JDK + Maven (or project `mvnw`) for the projects you register

## Install

```bash
git clone https://github.com/vinsmoke-sanji59/java-project-runner-mcp.git
cd java-project-runner-mcp
nvm use 20   # if you use nvm
npm install
npm run build
npm test
```

Dev (TypeScript directly):

```bash
npm run dev
```

Production entry after build:

```bash
node dist/index.js
```

## Registry

Create or edit `~/.java-project-runner/projects.json` by hand, or use the register/update tools:

```json
{
  "projects": [
    {
      "id": "order-service",
      "name": "Order Service",
      "path": "/Users/you/ws/order-service",
      "jarPath": "target/order-service-0.0.1-SNAPSHOT.jar",
      "javaHome": "/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home",
      "skipTests": true,
      "defaultProfile": "local",
      "profiles": ["local", "dev"],
      "jvmArgs": ["-Xmx512m"],
      "healthCheckUrl": "http://127.0.0.1:8080/actuator/health",
      "stopTimeoutSeconds": 15,
      "enabled": true
    }
  ]
}
```

Notes:

- `id` must be a single safe path segment: 1–64 chars, start/end alphanumeric; middle may include `.` `_` `-`; no `..` or path separators.
- `path` must exist and contain `pom.xml` (enforced on register/update).
- `jarPath` may be a concrete relative path or a single-glob filename such as `target/*.jar` (exactly one non-`sources`/`javadoc` match).
- `healthCheckUrl` must be loopback (`127.0.0.1` / `localhost` / `::1`).
- State + logs live under `~/.java-project-runner/state/` and `~/.java-project-runner/logs/<id>/`.

## MCP tools

| Tool | Purpose |
|------|---------|
| `project_list` | List registered projects |
| `project_inspect` | pom / wrapper / jar resolvability / config |
| `project_register` | Add a project to the registry |
| `project_update` | Patch an existing project (id immutable) |
| `project_unregister` | Remove a project (refuses if running/unknown) |
| `project_build` | Maven build |
| `project_start` | Smart package → `java -jar` (`rebuild` / `skipBuild` optional) |
| `project_stop` | SIGTERM → wait → SIGKILL on managed pgid |
| `project_force_stop` | Fingerprint-gated stop; requires `confirm: true`; allowed for `unknown` |
| `project_reclaim` | Re-verify PID fingerprint after MCP restart |
| `project_restart` | stop then start under one lock |
| `project_status` | One or all runtime states |
| `project_logs` | Tail stdout/stderr (max 200 lines) |

### Start build flags

| Flag | Effect |
|------|--------|
| _(none)_ | Skip Maven if resolved JAR mtime ≥ newest of `pom.xml` and `src/**` (`.java`/`.xml`/`.yml`/`.yaml`/`.properties`); else `package` |
| `rebuild: true` | Always Maven before start |
| `skipBuild: true` | Never Maven; JAR must already resolve |
| both true | `INVALID_ARGS` |

Response includes `buildSkipped` and `buildSkipReason` (`jar_fresh` | `skipBuild_flag` | `null`).

## Cursor stdio config

Add to Cursor MCP settings (path may be `~/.cursor/mcp.json` or the IDE MCP UI). Replace the paths with your Node binary and clone location:

```json
{
  "mcpServers": {
    "java-project-runner": {
      "command": "/absolute/path/to/node",
      "args": [
        "/absolute/path/to/java-project-runner-mcp/dist/index.js"
      ]
    }
  }
}
```

Use an absolute Node 20+ binary. After `npm run build`, point `args` at `dist/index.js`.

## Claude Code stdio config

In Claude Code MCP settings / `.mcp.json`:

```json
{
  "mcpServers": {
    "java-project-runner": {
      "command": "/absolute/path/to/node",
      "args": [
        "/absolute/path/to/java-project-runner-mcp/dist/index.js"
      ]
    }
  }
}
```

Or with `npx tsx` for local iteration (Node 20+ on PATH):

```json
{
  "mcpServers": {
    "java-project-runner": {
      "command": "npx",
      "args": [
        "tsx",
        "/absolute/path/to/java-project-runner-mcp/src/index.ts"
      ],
      "cwd": "/absolute/path/to/java-project-runner-mcp"
    }
  }
}
```

## Safety

- Only `enabled` registry projects can be built/started/stopped.
- Paths are realpath-checked against the project root.
- `spawn(..., { shell: false })` only; goals / profiles / `jvmArgs` are whitelisted.
- After MCP server restart, unverified PIDs become `unknown` (pid retained for reclaim/force_stop) — the server will **not** blind-kill them.
- `project_force_stop` requires `confirm: true` and a matching `java -jar` cmdline fingerprint.
- Do not run more than one MCP server process against the same `~/.java-project-runner` home at once (e.g. Cursor and Claude Code concurrently).

## License

MIT