Skip to content

Conversation

@elasticdotventures
Copy link

Add MCP Server Support to PM2

This PR adds Model Context Protocol (MCP) server support to PM2, enabling process management through MCP-compatible clients like Claude Code and Codex.

🎯 Overview

The MCP server exposes PM2's core process management capabilities through a standardized protocol, making it easy to integrate PM2 with AI assistants and other MCP clients.

✨ Features

New Binary

  • pm2-mcp - Standalone MCP server binary

12 MCP Tools

  • pm2_list_processes - List all PM2 processes with metrics
  • pm2_describe_process - Get detailed process information
  • pm2_start_process - Start a new process or ecosystem file
  • pm2_restart_process - Restart a process by id/name
  • pm2_reload_process - Zero-downtime reload (cluster mode)
  • pm2_stop_process - Stop a process
  • pm2_delete_process - Delete a process from PM2
  • pm2_flush_logs - Flush log files
  • pm2_reload_logs - Rotate and reopen logs
  • pm2_dump - Save process list to disk
  • pm2_tail_logs - Read last N lines from logs
  • pm2_kill_daemon - Stop PM2 daemon

2 MCP Resources

  • pm2://processes - Current PM2 process list as JSON
  • pm2://process/{id} - Detailed process information as JSON

Sandbox Detection

  • Automatic detection of sandboxed environments
  • Dynamic PM2_HOME selection for writable locations
  • Client notifications with sandbox status and recommendations
  • Fallback locations: /tmp/pm2-mcp, ./.pm2-mcp

Multiple Transports

  • stdio transport (default, for MCP clients)
  • HTTP/Streamable transport (for long-lived usage)
  • Configurable via CLI args or environment variables

📦 Implementation

New Files

  • lib/mcp/server.js (891 lines) - Full MCP server implementation
  • bin/pm2-mcp - MCP server entry point with argument parsing
  • Justfile - Setup recipes for Claude Code and Codex
  • Updated README.md - Comprehensive MCP documentation

Dependencies

  • @modelcontextprotocol/sdk@^1.23.0 - MCP protocol implementation

Configuration
Extensive environment variable support:

  • PM2_MCP_TRANSPORT - Transport type (stdio/http)
  • PM2_MCP_PORT, PM2_MCP_HOST, PM2_MCP_PATH - HTTP config
  • PM2_MCP_NO_DAEMON - No-daemon mode (default: true)
  • PM2_MCP_DEBUG - Debug logging
  • PM2_HOME, PM2_MCP_HOME - PM2 home directory
  • And more (see README for full list)

🚀 Usage

Quick Start with Claude Code

claude mcp add pm2-mcp -- pm2-mcp

Quick Start with Codex

codex mcp add pm2-mcp -- pm2-mcp

HTTP Transport

pm2-mcp --transport http --port 8849

Run under PM2

pm2-mcp --pm2 --pm2-name mcp-server --transport http --port 8849

🔗 Related

🧪 Testing

The MCP server has been tested with:

  • Claude Code (stdio and HTTP transports)
  • Codex (stdio transport)
  • Sandboxed and non-sandboxed environments
  • All 12 tools and 2 resources

📝 Documentation

Complete documentation added to README.md including:

  • MCP server overview and features
  • Quick setup for Claude Code and Codex
  • Environment variables reference table
  • Sandbox detection explanation
  • Available tools and resources
  • Justfile recipes for easy setup

💡 Design Decisions

  1. No-daemon mode by default - Ensures compatibility with sandboxed MCP clients that may not have persistent daemon access

  2. Sandbox detection - Automatically adapts to restricted environments by selecting writable locations for PM2_HOME

  3. Client notifications - Uses MCP logging protocol to inform clients about sandbox status and provide helpful recommendations

  4. Dual transport support - stdio for MCP clients, HTTP for long-lived/remote access

  5. Minimal API surface - Focuses on core PM2 operations most useful for programmatic access

✅ Checklist

  • New MCP server implementation in lib/mcp/server.js
  • New pm2-mcp binary with CLI argument parsing
  • Added @modelcontextprotocol/sdk dependency
  • Updated package.json with new binary and dependency
  • Comprehensive README documentation
  • Justfile recipes for easy MCP client registration
  • Sandbox detection and environment adaptation
  • Support for stdio and HTTP transports
  • Client notifications via MCP logging protocol
  • Tested with Claude Code and Codex
  • No breaking changes to existing PM2 functionality

🙏 Acknowledgments

This implementation enables PM2 to be easily integrated into the growing MCP ecosystem, making process management accessible to AI assistants and other MCP-compatible tools.

Co-authored-by: Claude [email protected]

Add MCP server support to PM2 for process management through MCP-compatible clients.

Features:
- New pm2-mcp binary that exposes PM2 process management via MCP
- 12 MCP tools for process lifecycle, logging, and monitoring:
  - 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_tail_logs
  - pm2_dump, pm2_kill_daemon
- 2 MCP resources for real-time process information:
  - pm2://processes (list)
  - pm2://process/{id} (detail)
- Automatic sandbox environment detection and adaptation
- Support for stdio and HTTP (Streamable) transports
- Client notifications for sandbox status and recommendations
- Compatible with Claude Code, Codex, and other MCP clients

Implementation:
- New lib/mcp/server.js with full MCP server implementation
- Uses @modelcontextprotocol/sdk for MCP protocol
- Sandbox detection checks home directory writability and environment
- Auto-selects writable PM2_HOME in sandboxed environments
- No-daemon mode by default for MCP client compatibility
- Comprehensive environment variable configuration

Documentation:
- README with MCP server quickstart and setup commands
- Environment variables table (PM2_MCP_*, PM2_HOME, etc.)
- Sandbox detection explanation
- Tool and resource documentation
- Justfile recipes for easy registration with MCP clients

Related:
- Enables pkgx packaging: pkgxdev/pantry#11219
- Development fork: https://github.com/PromptExecution/pm2-mcp
- MCP Specification: https://modelcontextprotocol.io/

Co-authored-by: Claude <[email protected]>
@elasticdotventures
Copy link
Author

📦 pkgx Package Integration

A pkgx package has been prepared to make PM2 with MCP server support easily installable via pkgx:

pkgx Pantry PR: pkgxdev/pantry#11219

Installation (after this merges)

# Install with pkgx
pkgx install pm2

# Use pm2-mcp
pkgx pm2-mcp --help

# Register with Claude Code
claude mcp add pm2-mcp -- pkgx pm2-mcp

# Register with Codex
codex mcp add pm2-mcp -- pkgx pm2-mcp

Current Testing

The pkgx package currently points to the development fork (PromptExecution/pm2-mcp) so it can be tested immediately while this PR is under review:

pkgx install github.com/PromptExecution/pm2-mcp

Once this PR is merged, the pkgx package will be updated to point to the official Unitech/pm2 repository.

Cross-References

Copilot finished reviewing on behalf of elasticdotventures December 3, 2025 12:24
Copy link

Copilot AI left a comment

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 adds Model Context Protocol (MCP) server support to PM2, enabling AI assistants like Claude Code and Codex to manage PM2 processes through a standardized protocol. The implementation introduces a new pm2-mcp binary, 12 MCP tools for process management operations, 2 MCP resources for process state access, and comprehensive sandbox detection for restricted environments.

Key Changes:

  • New MCP server implementation with stdio and HTTP transport support
  • 12 process management tools (list, start, stop, restart, reload, delete, logs, etc.)
  • Automatic sandbox environment detection and adaptation
  • Dual transport modes: stdio (for MCP clients) and HTTP (for long-lived usage)

Reviewed changes

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

Show a summary per file
File Description
package.json Added MCP SDK dependency, updated Node.js requirement to 22.0.0, added pm2-mcp binary, reorganized dependencies alphabetically
lib/mcp/server.js Complete MCP server implementation with tools, resources, sandbox detection, and dual transport support (891 lines)
bin/pm2-mcp CLI entry point with argument parsing for transport configuration and PM2 self-management
README.md Comprehensive MCP documentation including setup, environment variables, sandbox detection, and available tools/resources
Justfile Development recipes for registering with MCP clients and testing sandbox detection

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

Comment on lines +820 to +840
setTimeout(() => {
server.sendLoggingMessage({
level: 'warning',
logger: 'pm2-mcp',
data: {
message: `PM2 MCP server running in sandboxed environment`,
reasons: sandboxInfo.reasons,
pm2_home: process.env.PM2_HOME,
recommendations: [
'Process management features are available but may have limited access',
'PM2 daemon is running in no-daemon mode by default',
'Set PM2_MCP_NO_DAEMON=false to connect to an existing daemon',
'Set PM2_HOME or PM2_MCP_HOME to specify a writable location'
]
}
}).catch(err => {
if (process.env.PM2_MCP_DEBUG === 'true') {
console.error('[pm2-mcp][debug] failed to send sandbox notification', err);
}
});
}, 100);
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.

[nitpick] The magic number 100 (milliseconds) for the setTimeout delay should be extracted to a named constant for better maintainability. For example: const SANDBOX_NOTIFICATION_DELAY_MS = 100;. This makes the code more self-documenting and easier to adjust if the timing needs to change.

Copilot uses AI. Check for mistakes.
Comment on lines +705 to +724
server.registerTool(
'pm2_kill_daemon',
{
title: 'Kill PM2 daemon',
description: 'Stops the PM2 daemon and all managed processes.'
},
wrapTool('pm2_kill_daemon', async () => {
try {
await ensureConnected();
await pm2KillDaemon();
isConnected = false;
return {
content: textContent({ action: 'killDaemon' }),
structuredContent: { action: 'killDaemon' }
};
} catch (err) {
return errorResult(err);
}
})
);
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 pm2_kill_daemon tool allows external MCP clients to kill the PM2 daemon and all managed processes without any authentication or authorization checks. This is a critical operation that should require explicit permission or at least a confirmation mechanism. Consider adding:

  1. An environment variable flag to enable/disable this dangerous operation
  2. Additional confirmation requirements
  3. Warning documentation about the security implications

This tool gives any MCP client complete control to shut down all PM2-managed services.

Copilot uses AI. Check for mistakes.
Comment on lines +316 to +343
const httpServer = http.createServer((req, res) => {
logRequests('http %s %s', req.method, req.url);
try {
const base = `http://${req.headers.host || `${host}:${port}`}`;
const url = new URL(req.url || '', base);
if (url.pathname !== pathPart) {
res.writeHead(404).end('Not Found');
return;
}
transport.handleRequest(req, res).catch(err => {
console.error('[pm2-mcp] transport request failed', err);
if (!res.headersSent) res.writeHead(500);
res.end('Internal Server Error');
});
} catch (err) {
console.error('[pm2-mcp] transport request failed', err);
if (!res.headersSent) res.writeHead(500);
res.end('Internal Server Error');
}
});

httpServer.listen(port, host, () => {
if (process.env.PM2_MCP_DEBUG === 'true') {
console.error('[pm2-mcp][debug] HTTP transport listening', `${host}:${port}${pathPart}`);
}
});

return { transport, httpServer, address: `http://${host}:${port}${pathPart}` };
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 HTTP server doesn't implement authentication or authorization. When running in HTTP mode, anyone who can reach the host:port can execute PM2 operations including starting arbitrary processes, stopping processes, and killing the daemon. The documentation mentions PM2_MCP_ALLOWED_HOSTS and PM2_MCP_ALLOWED_ORIGINS for DNS rebinding protection, but these don't provide actual authentication. Consider:

  1. Adding authentication mechanisms (API keys, tokens, etc.)
  2. Documenting that HTTP mode should only be used on localhost or within trusted networks
  3. Making authentication mandatory or at least strongly recommended for non-localhost bindings

Copilot uses AI. Check for mistakes.
Comment on lines +466 to +491
server.registerTool(
'pm2_start_process',
{
title: 'Start a process with PM2',
description: 'Start a script or JSON ecosystem file.',
inputSchema: startSchema
},
wrapTool('pm2_start_process', async args => {
try {
await ensureConnected();
const target = args.jsonConfigFile || args.script;
const options = cleanOptions({
name: args.name,
args: args.args,
cwd: args.cwd,
watch: args.watch,
instances: args.instances,
env: args.env,
interpreter: args.interpreter
});

if (process.env.PM2_MCP_DEBUG === 'true') {
console.error('[pm2-mcp][debug] starting process', target, options);
}

await pm2Start(target, options);
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 pm2_start_process tool accepts file paths (script and jsonConfigFile) and other parameters like cwd, interpreter, and env from external MCP clients without validation. This could allow an attacker to:

  1. Start arbitrary executables on the system
  2. Set malicious environment variables
  3. Specify arbitrary working directories

While PM2 itself should have some safeguards, the MCP server should implement input validation to ensure that:

  • File paths are validated and/or within allowed directories
  • Interpreter paths are validated
  • Environment variables don't contain sensitive overrides
  • Working directory is within expected bounds

Consider adding a whitelist/configuration for allowed directories or requiring explicit authorization for process starts.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +149
async function ensureConnected() {
if (isConnected) return;
log('connecting to PM2 (noDaemon default true, override with PM2_MCP_NO_DAEMON)');
await new Promise((resolve, reject) => {
// Default to no-daemon mode so the MCP server can start without needing an existing PM2 daemon.
const noDaemon =
process.env.PM2_MCP_NO_DAEMON === undefined
? true
: process.env.PM2_MCP_NO_DAEMON === 'true';
log('pm2.connect noDaemon=%s', noDaemon);
pm2.connect(noDaemon, err => {
if (err) return reject(err);
isConnected = true;
log('connected to PM2');
return resolve();
});
});
}
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 isConnected flag is a simple boolean without any locking mechanism. If multiple requests call ensureConnected() concurrently before PM2 connects, they will all attempt to call pm2.connect() simultaneously. This could lead to race conditions or multiple connection attempts. Consider implementing a proper connection state management with a promise that's reused across concurrent calls, or using a mutex/lock pattern to ensure only one connection attempt occurs at a time.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

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.

1 participant