Skip to content

Conversation

Copy link

Copilot AI commented Jan 25, 2026

Thanks for the feedback on #6. I've created this new PR, which merges into #6, to address your comment. I will work on the changes and keep this PR's description up to date as I make progress.

Original PR: #6
Triggering review: #6 (comment)

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


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

elasticdotventures and others added 12 commits December 3, 2025 12:12
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>
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>
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>
Base automatically changed from feature/mcp-semantic-state to master January 25, 2026 01:23
@elasticdotventures elasticdotventures marked this pull request as ready for review January 25, 2026 01:23
Copilot AI review requested due to automatic review settings January 25, 2026 01:23
@elasticdotventures elasticdotventures merged commit 3518cdd into master Jan 25, 2026
1 check failed
@elasticdotventures elasticdotventures deleted the copilot/sub-pr-6-another-one branch January 25, 2026 01:24
Copilot stopped work on behalf of elasticdotventures due to an error January 25, 2026 01: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 extends the MCP server with semantic process state and log-analysis capabilities, adds HTTP hardening timeouts, and updates documentation for platform support and Codex usage. It builds on PR #6 by enriching process descriptions, adding new tools/resources for log inspection and safe descriptions, and tightening server behavior.

Changes:

  • Add connection coalescing for ensureConnected, HTTP server timeouts, and a suite of helpers for log-tail reading, log pattern analysis, and semantic state inference.
  • Enrich existing tools/resources (pm2_list_processes, pm2_describe_process, and process resources) with a semantic_state field and introduce new tools pm2_analyze_logs and pm2_describe_process_safe for structured log analysis and privacy-safe process descriptions.
  • Update README platform support wording and add a brief section describing how to use the MCP server with OpenAI Codex.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
lib/mcp/server.js Adds connectionPromise-based connection handling, HTTP timeouts (with currently undefined timeout constants), environment redaction helpers, log pattern/progress analysis, semantic-state heuristics, new MCP tools for log analysis and safe describe, and semantic enrichment of process tools/resources.
README.md Clarifies supported Node.js/Bun versions and briefly documents using the MCP server with OpenAI’s Codex CLI.

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

Comment on lines +956 to +981
wrapTool('pm2_analyze_logs', async ({ process, timeframe_minutes, lines }) => {
try {
await ensureConnected();
const description = await pm2Describe(process);
if (!description || description.length === 0) {
throw new Error(`No process found for "${process}"`);
}
const env = description[0].pm2_env || {};
const { lines: logLines, logPath } = await readRecentLogLines(env, lines);
const logAnalysis = analyzeLogPatterns(logLines);
const semantic = buildSemanticStateFromHeuristics({
env,
monit: description[0].monit,
logAnalysis,
logInfo: { lastModified: null }
});

let suggested_action = 'none';
if (logAnalysis.errorsFound.length > 0) suggested_action = 'investigate';
else if (logAnalysis.topPattern?.semanticStatus === 'downloading') suggested_action = 'wait_for_completion';
else if (logAnalysis.topPattern?.semanticStatus === 'degraded') suggested_action = 'investigate';

const payload = {
process,
timeframe_minutes,
analysis: {
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The timeframe_minutes input parameter is accepted and returned in the pm2_analyze_logs tool, but it is not actually used to limit or filter the logs read from disk, which may confuse clients expecting time-bounded analysis. Either apply this parameter when selecting log lines (for example, by comparing mtime or parsing timestamps) or clarify/remove it from the schema and response.

Copilot uses AI. Check for mistakes.
Comment on lines +411 to +424
function sanitizeDescriptionEntry(entry, options = {}) {
const cloned = JSON.parse(JSON.stringify(entry || {}));
const env = cloned.pm2_env || {};

if (!options.includeEnvironment) {
delete env.env;
delete env.env_pm2;
} else {
env.env = filterEnvironment(env.env, options.environmentFilter, options.redactSecrets);
env.env_pm2 = filterEnvironment(env.env_pm2, options.environmentFilter, options.redactSecrets);
}

cloned.pm2_env = env;
return cloned;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

sanitizeDescriptionEntry clones the entire PM2 description via JSON.parse(JSON.stringify(entry || {})), which will drop non-JSON-safe data such as Dates, Buffers, or functions that might exist in pm2_env and could be useful for debugging. If you need a defensive copy, consider a more targeted shallow/deep clone of the properties you care about so structure is preserved without unintentionally altering types.

Copilot uses AI. Check for mistakes.
Comment on lines +1006 to +1035
server.registerTool(
'pm2_describe_process_safe',
{
title: 'Describe a PM2 process (privacy-safe)',
description: 'Describe a process with optional environment filtering and secret redaction.',
inputSchema: describeSafeSchema
},
wrapTool('pm2_describe_process_safe', async ({ process, include_environment, environment_filter, redact_secrets }) => {
try {
await ensureConnected();
const description = await pm2Describe(process);
if (!description || description.length === 0) {
throw new Error(`No process found for "${process}"`);
}

const sanitized = await Promise.all(
description.map(async item => ({
...sanitizeDescriptionEntry(item, {
includeEnvironment: include_environment,
environmentFilter: environment_filter,
redactSecrets: redact_secrets
}),
semantic_state: await buildSemanticState(item)
}))
);

return {
content: textContent(sanitized),
structuredContent: { description: sanitized }
};
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

pm2_describe_process_safe introduces new behavior around environment filtering, secret redaction, and inclusion of semantic_state, but there are no tests covering this tool in the existing MCP server test suite. To avoid regressions and to validate that redaction and filtering work as intended (including default vs. overridden options), consider adding targeted tests that exercise this tool and inspect the returned description objects.

Copilot uses AI. Check for mistakes.
Comment on lines +585 to +597
async function buildSemanticState(procLike) {
const env = procLike.pm2_env || {};
const monit = procLike.monit || procLike.pm2_env?.monit || {};
const logInfo = await readRecentLogLines(env, 200);
const logAnalysis = analyzeLogPatterns(logInfo.lines);
return buildSemanticStateFromHeuristics({ env, monit, logAnalysis, logInfo });
}

async function enrichProcess(proc) {
const base = formatProcess(proc);
base.semantic_state = await buildSemanticState(proc);
return base;
}
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The new semantic state helpers (buildSemanticStateFromHeuristics / buildSemanticState / enrichProcess) add non-trivial heuristics for inferring status and progress from logs and monit data, but there are currently no tests exercising this behavior or asserting the semantic_state shape on tools/resources. Given that this file already has MCP integration tests, it would be valuable to add tests that cover at least a few representative scenarios (e.g., online vs degraded vs stuck, and presence of progress indicators) to prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +347 to +350
// Set timeouts to protect against slow HTTP attacks
httpServer.timeout = HTTP_SERVER_TIMEOUT_MS;
httpServer.headersTimeout = HTTP_HEADERS_TIMEOUT_MS;
httpServer.requestTimeout = HTTP_REQUEST_TIMEOUT_MS;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The new HTTP timeout properties reference HTTP_SERVER_TIMEOUT_MS, HTTP_HEADERS_TIMEOUT_MS, and HTTP_REQUEST_TIMEOUT_MS, but these constants are not defined in this file or elsewhere in the repo, which will throw a ReferenceError when createTransport runs for HTTP. These values should either be defined (e.g., with sensible defaults or environment overrides) or the assignments guarded/removed.

Copilot uses AI. Check for mistakes.
Comment on lines +949 to +999
server.registerTool(
'pm2_analyze_logs',
{
title: 'Analyze PM2 logs',
description: 'Parse recent logs for activity, patterns, and errors.',
inputSchema: analyzeLogsSchema
},
wrapTool('pm2_analyze_logs', async ({ process, timeframe_minutes, lines }) => {
try {
await ensureConnected();
const description = await pm2Describe(process);
if (!description || description.length === 0) {
throw new Error(`No process found for "${process}"`);
}
const env = description[0].pm2_env || {};
const { lines: logLines, logPath } = await readRecentLogLines(env, lines);
const logAnalysis = analyzeLogPatterns(logLines);
const semantic = buildSemanticStateFromHeuristics({
env,
monit: description[0].monit,
logAnalysis,
logInfo: { lastModified: null }
});

let suggested_action = 'none';
if (logAnalysis.errorsFound.length > 0) suggested_action = 'investigate';
else if (logAnalysis.topPattern?.semanticStatus === 'downloading') suggested_action = 'wait_for_completion';
else if (logAnalysis.topPattern?.semanticStatus === 'degraded') suggested_action = 'investigate';

const payload = {
process,
timeframe_minutes,
analysis: {
current_activity: logAnalysis.topPattern?.pattern || semantic.status,
detected_patterns: logAnalysis.detectedPatterns,
errors_found: logAnalysis.errorsFound,
warnings_found: logAnalysis.warningsFound,
progress_indicators: logAnalysis.progressIndicators,
anomalies: [],
suggested_action
},
meta: {
log_path: logPath,
semantic_state: semantic
}
};

return {
content: textContent(payload),
structuredContent: payload
};
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The pm2_analyze_logs tool is newly introduced with custom log pattern and progress analysis, but there are no tests in test/mcp/server.mocha.js (or elsewhere under test/) that call this tool or validate its structured response shape. Adding at least a basic happy-path test that starts a process, invokes pm2_analyze_logs, and asserts key fields (patterns, errors/warnings arrays, semantic_state, etc.) would improve confidence and catch future changes to the analysis heuristics.

Copilot uses AI. Check for mistakes.
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.

2 participants