pcp-mcp 1.1.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/PKG-INFO +1 -1
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/pyproject.toml +1 -1
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/icons.py +2 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/models.py +20 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/tools/system.py +115 -2
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/README.md +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/AGENTS.md +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/__init__.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/client.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/config.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/context.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/errors.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/middleware.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/prompts/__init__.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/py.typed +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/resources/__init__.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/resources/catalog.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/resources/health.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/server.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/tools/AGENTS.md +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/tools/__init__.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/tools/metrics.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/utils/__init__.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/utils/builders.py +0 -0
- {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/utils/extractors.py +0 -0
|
@@ -17,6 +17,7 @@ ICON_CPU = Icon(src="data:,🖥️", mimeType="text/plain")
|
|
|
17
17
|
ICON_MEMORY = Icon(src="data:,🧠", mimeType="text/plain")
|
|
18
18
|
ICON_DISK = Icon(src="data:,💾", mimeType="text/plain")
|
|
19
19
|
ICON_NETWORK = Icon(src="data:,🌐", mimeType="text/plain")
|
|
20
|
+
ICON_FILESYSTEM = Icon(src="data:,📁", mimeType="text/plain")
|
|
20
21
|
|
|
21
22
|
TAGS_METRICS = {"metrics", "pcp"}
|
|
22
23
|
TAGS_SYSTEM = {"system", "monitoring", "performance"}
|
|
@@ -29,3 +30,4 @@ TAGS_MEMORY = {"memory", "troubleshooting", "performance"}
|
|
|
29
30
|
TAGS_DISK = {"disk", "io", "troubleshooting", "performance"}
|
|
30
31
|
TAGS_NETWORK = {"network", "troubleshooting", "performance"}
|
|
31
32
|
TAGS_DIAGNOSE = {"diagnosis", "troubleshooting", "workflow"}
|
|
33
|
+
TAGS_FILESYSTEM = {"filesystem", "storage", "disk", "capacity"}
|
|
@@ -162,3 +162,23 @@ class DiagnosisResult(BaseModel):
|
|
|
162
162
|
diagnosis: str = Field(description="LLM-generated analysis of system health")
|
|
163
163
|
severity: str = Field(description="Severity level: healthy, warning, or critical")
|
|
164
164
|
recommendations: list[str] = Field(description="Actionable recommendations")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class FilesystemInfo(BaseModel):
|
|
168
|
+
"""Information about a single mounted filesystem."""
|
|
169
|
+
|
|
170
|
+
mount_point: str = Field(description="Filesystem mount point (e.g., /, /home)")
|
|
171
|
+
fs_type: str = Field(description="Filesystem type (e.g., ext4, xfs, tmpfs)")
|
|
172
|
+
capacity_bytes: int = Field(description="Total filesystem capacity in bytes")
|
|
173
|
+
used_bytes: int = Field(description="Used space in bytes")
|
|
174
|
+
available_bytes: int = Field(description="Available space in bytes (for non-root users)")
|
|
175
|
+
percent_full: float = Field(description="Percentage of filesystem in use")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class FilesystemSnapshot(BaseModel):
|
|
179
|
+
"""Point-in-time filesystem usage overview."""
|
|
180
|
+
|
|
181
|
+
timestamp: str = Field(description="ISO8601 timestamp")
|
|
182
|
+
hostname: str = Field(description="Target host name")
|
|
183
|
+
filesystems: list[FilesystemInfo] = Field(description="List of mounted filesystems")
|
|
184
|
+
assessment: str = Field(description="Brief interpretation of filesystem state")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""System health tools for clumped metric queries."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
-
from typing import TYPE_CHECKING, Annotated, Literal, Optional
|
|
4
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal, Optional
|
|
5
5
|
|
|
6
6
|
from fastmcp import Context
|
|
7
7
|
from mcp.types import ToolAnnotations
|
|
@@ -10,15 +10,23 @@ from pydantic import Field
|
|
|
10
10
|
from pcp_mcp.context import get_client_for_host
|
|
11
11
|
from pcp_mcp.icons import (
|
|
12
12
|
ICON_DIAGNOSE,
|
|
13
|
+
ICON_FILESYSTEM,
|
|
13
14
|
ICON_HEALTH,
|
|
14
15
|
ICON_PROCESS,
|
|
15
16
|
ICON_SYSTEM,
|
|
16
17
|
TAGS_DIAGNOSE,
|
|
18
|
+
TAGS_FILESYSTEM,
|
|
17
19
|
TAGS_HEALTH,
|
|
18
20
|
TAGS_PROCESS,
|
|
19
21
|
TAGS_SYSTEM,
|
|
20
22
|
)
|
|
21
|
-
from pcp_mcp.models import
|
|
23
|
+
from pcp_mcp.models import (
|
|
24
|
+
DiagnosisResult,
|
|
25
|
+
FilesystemInfo,
|
|
26
|
+
FilesystemSnapshot,
|
|
27
|
+
ProcessTopResult,
|
|
28
|
+
SystemSnapshot,
|
|
29
|
+
)
|
|
22
30
|
from pcp_mcp.utils.builders import (
|
|
23
31
|
assess_processes,
|
|
24
32
|
build_cpu_metrics,
|
|
@@ -96,6 +104,15 @@ PROCESS_METRICS = {
|
|
|
96
104
|
"info": ["proc.psinfo.pid", "proc.psinfo.cmd", "proc.psinfo.psargs"],
|
|
97
105
|
}
|
|
98
106
|
|
|
107
|
+
FILESYSTEM_METRICS = [
|
|
108
|
+
"filesys.mountdir",
|
|
109
|
+
"filesys.capacity",
|
|
110
|
+
"filesys.used",
|
|
111
|
+
"filesys.avail",
|
|
112
|
+
"filesys.full",
|
|
113
|
+
"filesys.type",
|
|
114
|
+
]
|
|
115
|
+
|
|
99
116
|
|
|
100
117
|
async def _fetch_system_snapshot(
|
|
101
118
|
ctx: Context,
|
|
@@ -379,6 +396,102 @@ def register_system_tools(mcp: "FastMCP") -> None:
|
|
|
379
396
|
except Exception:
|
|
380
397
|
return _build_fallback_diagnosis(snapshot)
|
|
381
398
|
|
|
399
|
+
@mcp.tool(
|
|
400
|
+
annotations=TOOL_ANNOTATIONS,
|
|
401
|
+
output_schema=FilesystemSnapshot.model_json_schema(),
|
|
402
|
+
icons=[ICON_FILESYSTEM],
|
|
403
|
+
tags=TAGS_FILESYSTEM,
|
|
404
|
+
)
|
|
405
|
+
async def get_filesystem_usage(
|
|
406
|
+
ctx: Context,
|
|
407
|
+
host: Annotated[
|
|
408
|
+
Optional[str],
|
|
409
|
+
Field(description="Target pmcd host to query (default: server's configured target)"),
|
|
410
|
+
] = None,
|
|
411
|
+
) -> FilesystemSnapshot:
|
|
412
|
+
"""Get mounted filesystem usage (similar to df command).
|
|
413
|
+
|
|
414
|
+
Returns capacity, used, available, and percent full for each mounted
|
|
415
|
+
filesystem. Useful for monitoring disk space and identifying filesystems
|
|
416
|
+
that may need attention.
|
|
417
|
+
|
|
418
|
+
Examples:
|
|
419
|
+
get_filesystem_usage() - Check all filesystems on default host
|
|
420
|
+
get_filesystem_usage(host="db1.example.com") - Check remote host
|
|
421
|
+
"""
|
|
422
|
+
from pcp_mcp.errors import handle_pcp_error
|
|
423
|
+
|
|
424
|
+
async with get_client_for_host(ctx, host) as client:
|
|
425
|
+
try:
|
|
426
|
+
response = await client.fetch(FILESYSTEM_METRICS)
|
|
427
|
+
except Exception as e:
|
|
428
|
+
raise handle_pcp_error(e, "fetching filesystem metrics") from e
|
|
429
|
+
|
|
430
|
+
filesystems = _build_filesystem_list(response)
|
|
431
|
+
assessment = _assess_filesystems(filesystems)
|
|
432
|
+
|
|
433
|
+
return FilesystemSnapshot(
|
|
434
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
435
|
+
hostname=client.target_host,
|
|
436
|
+
filesystems=filesystems,
|
|
437
|
+
assessment=assessment,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _build_filesystem_list(response: dict) -> list[FilesystemInfo]:
|
|
442
|
+
"""Build list of FilesystemInfo from pmproxy response."""
|
|
443
|
+
values = response.get("values", [])
|
|
444
|
+
|
|
445
|
+
metrics_by_name: dict[str, dict[int, Any]] = {}
|
|
446
|
+
for metric in values:
|
|
447
|
+
name = metric.get("name", "")
|
|
448
|
+
instances = metric.get("instances", [])
|
|
449
|
+
metrics_by_name[name] = {inst.get("instance", -1): inst.get("value") for inst in instances}
|
|
450
|
+
|
|
451
|
+
mountdir_instances = metrics_by_name.get("filesys.mountdir", {})
|
|
452
|
+
|
|
453
|
+
filesystems: list[FilesystemInfo] = []
|
|
454
|
+
for instance_id, mount_point in mountdir_instances.items():
|
|
455
|
+
if mount_point is None:
|
|
456
|
+
continue
|
|
457
|
+
|
|
458
|
+
capacity_kb = metrics_by_name.get("filesys.capacity", {}).get(instance_id, 0) or 0
|
|
459
|
+
used_kb = metrics_by_name.get("filesys.used", {}).get(instance_id, 0) or 0
|
|
460
|
+
avail_kb = metrics_by_name.get("filesys.avail", {}).get(instance_id, 0) or 0
|
|
461
|
+
percent_full = metrics_by_name.get("filesys.full", {}).get(instance_id, 0.0) or 0.0
|
|
462
|
+
fs_type = metrics_by_name.get("filesys.type", {}).get(instance_id, "unknown") or "unknown"
|
|
463
|
+
|
|
464
|
+
filesystems.append(
|
|
465
|
+
FilesystemInfo(
|
|
466
|
+
mount_point=mount_point,
|
|
467
|
+
fs_type=fs_type,
|
|
468
|
+
capacity_bytes=int(capacity_kb) * 1024,
|
|
469
|
+
used_bytes=int(used_kb) * 1024,
|
|
470
|
+
available_bytes=int(avail_kb) * 1024,
|
|
471
|
+
percent_full=float(percent_full),
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
filesystems.sort(key=lambda fs: fs.mount_point)
|
|
476
|
+
return filesystems
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _assess_filesystems(filesystems: list[FilesystemInfo]) -> str:
|
|
480
|
+
"""Generate assessment string for filesystem state."""
|
|
481
|
+
if not filesystems:
|
|
482
|
+
return "No filesystems found"
|
|
483
|
+
|
|
484
|
+
critical = [fs for fs in filesystems if fs.percent_full >= 90]
|
|
485
|
+
warning = [fs for fs in filesystems if 80 <= fs.percent_full < 90]
|
|
486
|
+
|
|
487
|
+
if critical:
|
|
488
|
+
mounts = ", ".join(fs.mount_point for fs in critical)
|
|
489
|
+
return f"🔴 Critical: {mounts} at 90%+ capacity"
|
|
490
|
+
if warning:
|
|
491
|
+
mounts = ", ".join(fs.mount_point for fs in warning)
|
|
492
|
+
return f"🟡 Warning: {mounts} at 80%+ capacity"
|
|
493
|
+
return "🟢 All filesystems healthy"
|
|
494
|
+
|
|
382
495
|
|
|
383
496
|
def _format_snapshot_for_llm(snapshot: SystemSnapshot) -> str:
|
|
384
497
|
"""Format a system snapshot as text for LLM analysis."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|