Content
# post-deploy-monitor
MCP server with two tools for comparing error patterns between two deployment
versions of a service. Both tools SSH-grep log files on a remote host,
normalize matching lines into stable signatures, and classify each distinct
error as **NEW**, **REGRESSED**, or **PRE_EXISTING**.
## The two tools
### `diagnose_deployment` — archive host, version-vs-version
Compares `service-v1.43` against `service-v1.42` using files partitioned by
version on a long-term log archive. Use when:
- You want to compare two specific past versions.
- The deployment you're investigating is more than a day old.
### `diagnose_deployment_live` — today's host, time-windowed
Compares lines emitted **after** a deployment timestamp against lines emitted
**before** it, within the same set of files on a live rsyslog host. Use when:
- You just deployed and want to check for regressions in real time.
- The two versions are intermixed in the same log files.
## Setup
1. Install [uv](https://docs.astral.sh/uv/).
2. From this directory: `uv sync`.
3. Copy `config.example.toml` to `config.toml` and fill in your values, OR set
`SSH_HOST_ALIAS`, `LOG_ROOT`, and (optionally) `LIVE_SSH_HOST_ALIAS`,
`LIVE_LOG_ROOT` as environment variables.
4. Add matching `Host` blocks to `~/.ssh/config` — one for each host you'll
target. Example:
```
Host log-host
HostName logbackup.example.com
User dev
IdentityFile ~/.ssh/your-key
ControlMaster auto
ControlPath /tmp/ssh-%r@%h:%p
ControlPersist 60s
Host live-host
HostName rsyslog.example.com
User dev
IdentityFile ~/.ssh/your-key
ControlMaster auto
ControlPath /tmp/ssh-%r@%h:%p
ControlPersist 60s
```
## Wiring into GitHub Copilot
Add to your Copilot MCP config:
```json
{
"mcpServers": {
"post-deploy-monitor": {
"command": "uv",
"args": ["run", "--project", "<absolute-path-to-this-dir>", "post-deploy-monitor"],
"env": {
"SSH_HOST_ALIAS": "log-host",
"LOG_ROOT": "/var/log/myapp",
"LIVE_SSH_HOST_ALIAS": "live-host",
"LIVE_LOG_ROOT": "/rsyslog"
}
}
}
}
```
The live-host env vars (`LIVE_SSH_HOST_ALIAS`, `LIVE_LOG_ROOT`) are optional.
If omitted, only `diagnose_deployment` is usable; calling
`diagnose_deployment_live` returns a structured `live_host_not_configured`
error.
## Per-call host override
Both tools accept an optional `ssh_host_alias` argument that overrides the
configured value for that one call. Useful when targeting a non-default
cluster's log host (e.g., staging vs prod) without restarting the server.
The override is validated against `[A-Za-z0-9._-]+`.
## How `diagnose_deployment` works (archive host)
For each call, the server SSHes into the archive host and runs `zgrep` over
the log files for the *current* version, then again for the *previous*
version. Matching lines are normalized into stable signatures (timestamps,
UUIDs, request IDs, line numbers stripped). Each signature seen in current
is classified as:
- **NEW** — not seen at all in previous (likely caused by your change)
- **REGRESSED** — present in both, but >3× more frequent in current
- **PRE_EXISTING** — present in both at similar rates
Required path layout (`log_locator.py`):
```
<LOG_ROOT>/<YYYY>/<MM>/<DD>/<vm_host>/<service>-<version_underscored>/<app_instance>/<file>.gz
```
## How `diagnose_deployment_live` works (rsyslog host)
For each call, the server SSHes into the rsyslog host and runs `zgrep` ONCE
over all matching files for the service. Each matched line's timestamp is
extracted (preferring a bracketed `[YYYY-MM-DD HH:MM:SS]` form within the
message; falling back to the syslog-style `MMM DD HH:MM:SS` prefix anchored
against `deploy_start`). Lines are split into THREE windows:
- **Previous**: timestamp in `[deploy_start − previous_window, deploy_start)`
- **Deploy** (rollout): timestamp in `[deploy_start, deploy_end]` — **discarded**.
This interval contains mixed-version logs from instances restarting at
different times; classifying them either way pollutes the diff.
- **Current**: timestamp in `(deploy_end, now]`
The same NEW / REGRESSED / PRE_EXISTING classification then runs on the two
sides (Previous vs. Current). The number of lines dropped from the deploy
window is reported as `lines_discarded_in_deploy_window` so a reader can
sanity-check the rollout interval.
### Inputs
The developer passes:
- `service`: e.g. `"pvcore"`.
- `current_version`, `previous_version`: metadata only (versions live in the
log content, not the path on this host).
- `deploy_start`, `deploy_end`: when the rollout started and finished.
Each accepts:
- `"HH:MM"` or `"HH:MM:SS"` — today, in the server's local timezone.
If the resulting time is in the future relative to wall-clock now, it
rolls back to yesterday (handles "I deployed yesterday at 23:50").
- Full ISO-8601 with offset, e.g. `"2026-05-07T14:30:00+05:30"`.
- `previous_window` (required): duration string for the previous-side
lookback, e.g. `"2h"`, `"30m"`, `"1h15m"`. The previous side spans
`[deploy_start − previous_window, deploy_start)`.
Required path layout (`live_locator.py`):
```
<LIVE_LOG_ROOT>/<service>/*.log
<LIVE_LOG_ROOT>/<service>/*.log.<n>.gz
```
## Testing
```bash
uv run pytest
```
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers
Time
A Model Context Protocol server for time and timezone conversions.