A read-only Model Context Protocol (MCP) server for monitoring a production VPS: PM2 processes, system resources, Nginx errors, SSL expiry and HTTP health.
Five read-only monitoring tools with clear, consistent naming (verb_noun pattern: pm2_status, system_resources, nginx_errors, ssl_expiry, http_health). All tools have descriptions (avg 120 chars, within 10-1024 baseline). Input schemas present for 3/5 tools; 2 tools (pm2_status, system_resources) have empty schemas {}. Parameter descriptions are present and specific (e.g., 'How many trailing lines of the error log to return'). However, output schemas are not documented, LLMs cannot predict response structure. Error handling uses generic fail() wrapper with no recovery guidance. No tool annotations (readOnlyHint, idempotentHint). Composition is sound: each tool does one thing; no multi-step dependencies documented.
GET a URL and return the HTTP status code and round-trip time. Read-only.
Return the last N lines of the Nginx error log (path from the NGINX_ERROR_LOG env var). Read-only.
List PM2-managed processes with status, CPU, memory, uptime and restart count. Read-only.
Check how many days until the live TLS certificate for a domain expires. Read-only.
Disk usage (df), memory (free) and load average (uptime) for the host. Read-only.
Two tools (pm2_status, system_resources) have empty input schemas {}, no parameters defined. LLMs cannot infer what inputs are accepted.
Output schemas are not documented anywhere in the code. LLMs cannot predict response structure (e.g., does pm2_status return an array or object? What fields?). This forces LLMs to guess and wastes tokens on trial-and-error.
Error handling is generic: fail() returns 'Error: <message>' with no recovery guidance. Per pattern:recovery-guide, errors should tell the LLM what to do next (e.g., 'Domain not found. Check spelling or try a different domain.').
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 53 | 2026-07-28+ | v2 |
No tool annotations present. All tools are read-only and idempotent, but lack readOnlyHint and idempotentHint annotations. This prevents agents from optimizing retry logic and understanding safety guarantees.
Parameter 'domain' in ssl_expiry lacks format validation. Description says 'e.g. example.com' but no regex pattern or length constraint. Code validates with DOMAIN_RE but LLM cannot see this, description should state 'Letters, digits, dots, hyphens only; 1-253 chars.'