Add semantic state and log analysis tools to MCP server#6
Add semantic state and log analysis tools to MCP server#6elasticdotventures merged 11 commits intomasterfrom
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.
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 | ||
| ``` |
There was a problem hiding this comment.
[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.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| 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(); | ||
| } | ||
| } |
There was a problem hiding this comment.
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');
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| #!/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" |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
| console.error('[pm2-mcp][debug] failed to send sandbox notification', err); | ||
| } | ||
| }); | ||
| }, 100); |
There was a problem hiding this comment.
[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) { |
There was a problem hiding this comment.
[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.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| 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); |
There was a problem hiding this comment.
[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)
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
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>
|
@elasticdotventures I've opened a new pull request, #7, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@elasticdotventures I've opened a new pull request, #8, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Brian Horakh <35611074+elasticdotventures@users.noreply.github.com>
Signed-off-by: Brian Horakh <35611074+elasticdotventures@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* Initial plan * Add explanation of Codex CLI tool in README Co-authored-by: elasticdotventures <35611074+elasticdotventures@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: elasticdotventures <35611074+elasticdotventures@users.noreply.github.com>
|
@elasticdotventures I've opened a new pull request, #9, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@elasticdotventures I've opened a new pull request, #10, to work on those changes. Once the pull request is ready, I'll request review from you. |
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.