-
Notifications
You must be signed in to change notification settings - Fork 0
Add semantic state and log analysis tools to MCP server #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
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 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this 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 capabilities to PM2, enabling AI assistants and MCP clients to manage PM2 processes through a standardized interface. It ports upstream PR 6067 and includes semantic state enrichment, log analysis tools, and privacy-safe describe functionality.
Key Changes:
- Adds a new MCP server implementation that exposes PM2 process management through MCP tools and resources
- Implements semantic state analysis with log pattern detection and health heuristics
- Upgrades Node.js requirement from 16.0.0 to 22.0.0
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Updates Node version requirement to 22.0.0, adds MCP SDK and Zod dependencies, adds pm2-mcp binary and npm script |
| lib/mcp/server.js | New MCP server implementation with 15 tools for process management, semantic state analysis, log parsing, and privacy-safe environment filtering |
| bin/pm2-mcp | New CLI entry point for starting the MCP server with support for stdio and HTTP transports |
| README.md | Documents the new MCP server feature, setup instructions, environment variables, and available tools/resources |
| Justfile | Adds development recipes for registering and testing the MCP server with Claude Code and Codex |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #### Codex (stdio) | ||
| ```bash | ||
| # Add pm2-mcp to Codex | ||
| codex mcp add pm2-mcp -- pm2-mcp | ||
|
|
||
| # Verify registration | ||
| codex mcp list | ||
| ``` |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Inconsistent terminology: The text uses "Codex" to refer to a CLI tool but doesn't clarify what Codex is. Since this appears alongside Claude Code, consider adding a brief explanation or ensuring readers understand what Codex refers to in this context.
| async function tailFile(filePath, lineCount) { | ||
| const fh = await fs.promises.open(filePath, 'r'); | ||
| try { | ||
| const stats = await fh.stat(); | ||
| let position = stats.size; | ||
| const chunkSize = 8192; | ||
| let buffer = ''; | ||
|
|
||
| while (position > 0 && buffer.split(/\r?\n/).length <= lineCount + 1) { | ||
| const readSize = Math.min(chunkSize, position); | ||
| position -= readSize; | ||
| const result = await fh.read({ buffer: Buffer.alloc(readSize), position }); | ||
| buffer = result.buffer.slice(0, result.bytesRead).toString('utf8') + buffer; | ||
| } | ||
|
|
||
| const lines = buffer.trimEnd().split(/\r?\n/); | ||
| return lines.slice(-lineCount); | ||
| } finally { | ||
| await fh.close(); | ||
| } | ||
| } |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing input validation: The lines parameter in tailFile should be validated to ensure it's a positive integer. If a negative or zero value is passed, the logic in line 361 (buffer.split(/\r?\n/).length <= lineCount + 1) could behave unexpectedly. Add validation: if (lineCount <= 0) throw new Error('lineCount must be positive');
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| echo "Testing normal environment:" | ||
| node -e "const {detectSandbox} = require('./lib/mcp/server.js'); console.log(detectSandbox ? 'Available' : 'Not exported');" || echo "Normal detection test" |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test-sandbox recipe attempts to require a function that isn't exported from the module. Line 59 tries to require detectSandbox from ./lib/mcp/server.js, but this function is not exported in the module.exports at the end of that file. Either export the function or update this test to use a different approach.
| function redactValue(value) { | ||
| if (typeof value !== 'string') return value; | ||
| if (value.length === 0) return value; | ||
|
|
||
| const upper = value.toUpperCase(); | ||
| const looksSecret = | ||
| SECRET_KEYS.some(key => upper.includes(key)) || | ||
| /(?:sk-[A-Za-z0-9]{32,}|gh[pousr]_[A-Za-z0-9]{36,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,48}|[A-Za-z0-9]{40,})/.test(value); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The secret detection logic checks if the value string contains keywords like "KEY", "TOKEN", etc., which will cause false positives. For example, a value like "This is the KEY to success" would be redacted. The keyword check should only apply to the environment variable name (key), not the value. Consider refactoring to: function redactValue(value, key) { ... } and check the key name for keywords while only checking the value for secret patterns.
| httpServer.timeout = 120000; // 2 minutes | ||
| httpServer.headersTimeout = 60000; // 1 minute | ||
| httpServer.requestTimeout = 120000; // 2 minutes |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The HTTP server timeout values (120000ms, 60000ms) are hardcoded magic numbers. Extract them as named constants:
const HTTP_SERVER_TIMEOUT_MS = 120000; // 2 minutes
const HTTP_HEADERS_TIMEOUT_MS = 60000; // 1 minute
const HTTP_REQUEST_TIMEOUT_MS = 120000; // 2 minutesThis improves maintainability and makes it clear these are configurable values.
| httpServer.timeout = 120000; // 2 minutes | |
| httpServer.headersTimeout = 60000; // 1 minute | |
| httpServer.requestTimeout = 120000; // 2 minutes | |
| httpServer.timeout = HTTP_SERVER_TIMEOUT_MS; | |
| httpServer.headersTimeout = HTTP_HEADERS_TIMEOUT_MS; | |
| httpServer.requestTimeout = HTTP_REQUEST_TIMEOUT_MS; |
| console.error('[pm2-mcp][debug] failed to send sandbox notification', err); | ||
| } | ||
| }); | ||
| }, 100); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The timeout value of 100ms on line 1167 is a magic number with unclear reasoning. This should be documented or extracted to a named constant like SANDBOX_NOTIFICATION_DELAY_MS = 100 with a comment explaining why this delay is necessary (e.g., "Allow client connection handshake to complete").
| } | ||
|
|
||
| const restartCount = env.restart_time || 0; | ||
| if (baseStatus === 'online' && restartCount >= 3 && confidence < 0.85) { |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The restart count threshold of 3 on line 537 is a magic number. Extract it as a named constant like DEGRADED_RESTART_THRESHOLD = 3 to make it clear this is a configurable heuristic for detecting degraded processes.
| function buildSemanticStateFromHeuristics(opts) { | ||
| const { env = {}, monit = {}, logAnalysis, logInfo } = opts; | ||
| const baseStatus = env.status || 'unknown'; | ||
|
|
||
| let status = baseStatus === 'online' ? 'online' : baseStatus; | ||
| let context; | ||
| let inferredFrom = 'status'; | ||
| let confidence = 0.4; | ||
| let progress; | ||
|
|
||
| if (logAnalysis?.topPattern) { | ||
| status = logAnalysis.topPattern.semanticStatus || status; | ||
| context = logAnalysis.topPattern.sample; | ||
| inferredFrom = 'log_pattern_match'; | ||
| confidence = 0.9; | ||
| } | ||
|
|
||
| if (logAnalysis?.progressIndicators?.length) { | ||
| const latest = logAnalysis.progressIndicators[logAnalysis.progressIndicators.length - 1]; | ||
| progress = latest.percent ?? latest.current; | ||
| } | ||
|
|
||
| const restartCount = env.restart_time || 0; | ||
| if (baseStatus === 'online' && restartCount >= 3 && confidence < 0.85) { | ||
| status = 'degraded'; | ||
| context = `Restarted ${restartCount} times`; | ||
| inferredFrom = 'restart_count'; | ||
| confidence = Math.max(confidence, 0.65); | ||
| } | ||
|
|
||
| const cpu = typeof monit.cpu === 'number' ? monit.cpu : null; | ||
| const now = Date.now(); | ||
| const logAgeMs = logInfo?.lastModified ? now - logInfo.lastModified : null; | ||
| const uptimeMs = env.pm_uptime || null; | ||
|
|
||
| if ( | ||
| baseStatus === 'online' && | ||
| logAgeMs !== null && | ||
| uptimeMs && | ||
| uptimeMs > 2 * 60 * 1000 && | ||
| logAgeMs > 5 * 60 * 1000 && | ||
| cpu !== null && | ||
| cpu < 1 | ||
| ) { | ||
| status = 'stuck'; | ||
| context = `No logs for ${Math.round(logAgeMs / 60000)}m, cpu ${cpu}%`; | ||
| inferredFrom = 'log_silence'; | ||
| confidence = Math.max(confidence, 0.7); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The confidence values (0.4, 0.9, 0.85, 0.65, 0.7) throughout the semantic state building logic are magic numbers without clear explanation. Consider:
- Extracting them as named constants with documentation explaining the confidence scale
- Adding a comment explaining how these values were determined
- Documenting what each confidence level means (e.g., 0.4 = low confidence from status alone, 0.9 = high confidence from log pattern match)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Brian Horakh <35611074+elasticdotventures@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Brian Horakh <35611074+elasticdotventures@users.noreply.github.com>
Port of upstream PR 6067 into PromptExecution/pm2-mcp. Adds semantic state enrichment, log analysis tool, and privacy-safe describe as implemented in feature/mcp-semantic-state.