Skip to content
Closed

pm2-mcp #6065

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
set dotenv-load
set export
set shell := ["bash", "-c"]

# Register the stdio MCP server with Codex CLI
register-codex-stdio:
#!/usr/bin/env bash
set -euo pipefail
codex mcp add pm2-mcp -- pm2-mcp
codex mcp list | grep -F "pm2-mcp" || true

# Start the MCP server over HTTP/Streamable transport (adjust host/port/path as needed)
run-mcp-http host="127.0.0.1" port="8849" path="/mcp":
#!/usr/bin/env bash
set -euo pipefail
pm2-mcp --transport http --host {{host}} --port {{port}} --path {{path}}

Comment on lines +13 to +17
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

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

The run-mcp-http recipe starts the MCP server using cleartext http and binds to a user-provided host (default 127.0.0.1) with no authentication. If the host is set to 0.0.0.0 or a public IP, this would expose PM2 controls to remote clients without encryption or auth. Enforce localhost binding by default, support HTTPS/TLS, and require an authentication mechanism (API key/token or mTLS).

Suggested change
run-mcp-http host="127.0.0.1" port="8849" path="/mcp":
#!/usr/bin/env bash
set -euo pipefail
pm2-mcp --transport http --host {{host}} --port {{port}} --path {{path}}
run-mcp-http host="127.0.0.1" port="8849" path="/mcp" tls_cert="" tls_key="" auth_token="":
#!/usr/bin/env bash
set -euo pipefail
# Enforce localhost binding unless TLS and authentication are provided
if [[ "{{host}}" != "127.0.0.1" && "{{host}}" != "localhost" ]]; then
if [[ -z "{{tls_cert}}" || -z "{{tls_key}}" || -z "{{auth_token}}" ]]; then
echo "ERROR: Refusing to start pm2-mcp on non-localhost host without TLS and authentication."
echo "Provide tls_cert, tls_key, and auth_token to enable secure remote access."
exit 1
fi
fi
# Choose transport based on TLS parameters
if [[ -n "{{tls_cert}}" && -n "{{tls_key}}" ]]; then
TRANSPORT="https"
TLS_ARGS="--tls-cert {{tls_cert}} --tls-key {{tls_key}}"
else
TRANSPORT="http"
TLS_ARGS=""
fi
# Add authentication argument if provided
if [[ -n "{{auth_token}}" ]]; then
AUTH_ARGS="--auth-token {{auth_token}}"
else
AUTH_ARGS=""
fi
pm2-mcp --transport $TRANSPORT --host {{host}} --port {{port}} --path {{path}} $TLS_ARGS $AUTH_ARGS

Copilot uses AI. Check for mistakes.
# Register the HTTP transport endpoint with Codex CLI (server must already be running)
register-codex-http name="pm2-mcp-http" host="127.0.0.1" port="8849" path="/mcp":
#!/usr/bin/env bash
set -euo pipefail
url="http://{{host}}:{{port}}{{path}}"
codex mcp add {{name}} --url "$url"
Comment on lines +19 to +23
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

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

The register-codex-http recipe registers an HTTP endpoint using http://{{host}}:{{port}}{{path}} without any authentication. If the MCP server is exposed beyond localhost, this registration would point clients at an unauthenticated, cleartext control surface. Use https:// with TLS and include an auth mechanism (API key/token or mTLS), and recommend registering 127.0.0.1 only unless properly secured.

Suggested change
register-codex-http name="pm2-mcp-http" host="127.0.0.1" port="8849" path="/mcp":
#!/usr/bin/env bash
set -euo pipefail
url="http://{{host}}:{{port}}{{path}}"
codex mcp add {{name}} --url "$url"
# WARNING: Do not expose the MCP server beyond localhost without HTTPS and authentication!
# If you must expose externally, use https:// and provide an API key or mTLS.
register-codex-http name="pm2-mcp-http" host="127.0.0.1" port="8849" path="/mcp" api_key="":
#!/usr/bin/env bash
set -euo pipefail
url="https://{{host}}:{{port}}{{path}}"
if [ -n "{{api_key}}" ]; then
codex mcp add {{name}} --url "$url" --header "Authorization: Bearer {{api_key}}"
else
codex mcp add {{name}} --url "$url"
fi

Copilot uses AI. Check for mistakes.
codex mcp list | grep -F "{{name}}" || true
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ PM2 is constantly assailed by [more than 1800 tests](https://github.com/Unitech/

Official website: [https://pm2.keymetrics.io/](https://pm2.keymetrics.io/)

Works on Linux (stable) & macOS (stable) & Windows (stable). All Node.js versions are supported starting Node.js 12.X and Bun since v1
Works on Linux (stable) & macOS (stable) & Windows (stable). All Node.js versions are supported starting Node.js 22.0.0 and Bun since v1


## Installing PM2
Expand Down Expand Up @@ -222,6 +222,21 @@ $ pm2 update

*PM2 updates are seamless*

## MCP server

PM2 now bundles an [MCP](https://modelcontextprotocol.io/specification/2025-11-25) stdio server that exposes the core process controls (list, describe, start, restart, reload, stop, delete, log flush/rotation, dump, daemon kill) plus process resources.

- Run it with `pm2-mcp` (or `npm run mcp`) and point your MCP client at that stdio command.
- Prefer the Streamable HTTP transport for long-lived usage: `pm2-mcp --transport http --port 8849 --host 0.0.0.0 --path /mcp` (env aliases: `PM2_MCP_TRANSPORT`, `PM2_MCP_PORT`, `PM2_MCP_HOST`, `PM2_MCP_PATH`).
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

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

The guidance to run the MCP server over HTTP with --host 0.0.0.0 exposes a powerful PM2 management API to the public network without authentication or TLS. An attacker on the same network (or Internet, if reachable) could remotely start/stop/delete processes, rotate logs, or kill the PM2 daemon. Use HTTPS with TLS (e.g., behind a reverse proxy), require strong authentication (API key/token or mTLS), and default-bind to 127.0.0.1; document firewalling if remote access is required.

Suggested change
- Prefer the Streamable HTTP transport for long-lived usage: `pm2-mcp --transport http --port 8849 --host 0.0.0.0 --path /mcp` (env aliases: `PM2_MCP_TRANSPORT`, `PM2_MCP_PORT`, `PM2_MCP_HOST`, `PM2_MCP_PATH`).
- Prefer the Streamable HTTP transport for long-lived usage: `pm2-mcp --transport http --port 8849 --host 127.0.0.1 --path /mcp` (env aliases: `PM2_MCP_TRANSPORT`, `PM2_MCP_PORT`, `PM2_MCP_HOST`, `PM2_MCP_PATH`).
> ⚠️ **Security Warning:**
> The MCP server exposes powerful PM2 management APIs. **Do not bind to `0.0.0.0` or expose the MCP server to public or untrusted networks without proper security controls.**
> For production or remote access, always:
> - Use HTTPS (e.g., run behind a reverse proxy with TLS termination)
> - Require strong authentication (API key/token or mutual TLS)
> - Bind to `127.0.0.1` by default and only open firewall ports if strictly necessary
> - Document and restrict access via firewall rules
> Exposing the MCP server without these controls can allow attackers to start/stop/delete processes, rotate logs, or kill the PM2 daemon.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

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

The example command pm2-mcp --transport http --port 8849 --host 0.0.0.0 --path /mcp exposes the MCP control surface over cleartext HTTP on all interfaces without any authentication. This enables remote manipulation of PM2-managed processes by anyone who can reach the port. Prefer HTTPS with TLS, restrict binding to 127.0.0.1 by default, and enforce authentication (API key/token or mTLS).

Suggested change
- Prefer the Streamable HTTP transport for long-lived usage: `pm2-mcp --transport http --port 8849 --host 0.0.0.0 --path /mcp` (env aliases: `PM2_MCP_TRANSPORT`, `PM2_MCP_PORT`, `PM2_MCP_HOST`, `PM2_MCP_PATH`).
- **Secure usage recommended:** Always bind the MCP server to `127.0.0.1` (localhost) and use HTTPS with authentication. For example:<br>
<code>pm2-mcp --transport https --port 8849 --host 127.0.0.1 --path /mcp --auth-token &lt;YOUR_TOKEN&gt; --tls-cert &lt;PATH_TO_CERT&gt; --tls-key &lt;PATH_TO_KEY&gt;</code><br>
<br>
<b>Warning:</b> <i>Do not expose the MCP server on public interfaces (e.g., <code>0.0.0.0</code>) or use cleartext HTTP without authentication. This can allow remote manipulation of PM2-managed processes by anyone who can reach the port.</i><br>
<br>
(env aliases: <code>PM2_MCP_TRANSPORT</code>, <code>PM2_MCP_PORT</code>, <code>PM2_MCP_HOST</code>, <code>PM2_MCP_PATH</code>)

Copilot uses AI. Check for mistakes.
- Quick Codex registration: `codex mcp add pm2-mcp -- pm2-mcp` then `codex mcp list` to confirm.
- By default the server starts in PM2 no-daemon mode for compatibility with sandboxed MCP clients. Set `PM2_MCP_NO_DAEMON=false` to connect to an existing PM2 daemon instead.
- PM2 CLI noise is silenced automatically to keep stdio clean for the MCP handshake; set `PM2_SILENT=false` only if you need PM2 console output.
- Run the server under PM2 itself with `pm2-mcp --pm2 --pm2-name mcp-server --transport http --port 8849` to keep it alive across restarts.
- Logging: set `DEBUG=pm2-mcp*` to see lifecycle/activity logs (transport selection, PM2 connects, tool calls).
- Faster setup with just: `just register-codex-stdio` to register the stdio server with Codex; `just run-mcp-http` (optional) to run HTTP transport locally and `just register-codex-http` to register that endpoint.
- Tools: `pm2_list_processes`, `pm2_describe_process`, `pm2_start_process`, `pm2_restart_process`, `pm2_reload_process`, `pm2_stop_process`, `pm2_delete_process`, `pm2_flush_logs`, `pm2_reload_logs`, `pm2_dump`, `pm2_tail_logs`, `pm2_kill_daemon`.
- Resources: `pm2://processes` (list) and `pm2://process/{id}` (detail). Both return JSON payloads.

## PM2+ Monitoring

If you manage your apps with PM2, PM2+ makes it easy to monitor and manage apps across servers.
Expand Down
2 changes: 2 additions & 0 deletions bin/pm2-mcp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('../lib/mcp/server.js');
414 changes: 378 additions & 36 deletions bun.lock

Large diffs are not rendered by default.

Loading