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.
Files changed (25) hide show
  1. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/PKG-INFO +1 -1
  2. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/pyproject.toml +1 -1
  3. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/icons.py +2 -0
  4. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/models.py +20 -0
  5. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/tools/system.py +115 -2
  6. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/README.md +0 -0
  7. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/AGENTS.md +0 -0
  8. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/__init__.py +0 -0
  9. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/client.py +0 -0
  10. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/config.py +0 -0
  11. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/context.py +0 -0
  12. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/errors.py +0 -0
  13. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/middleware.py +0 -0
  14. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/prompts/__init__.py +0 -0
  15. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/py.typed +0 -0
  16. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/resources/__init__.py +0 -0
  17. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/resources/catalog.py +0 -0
  18. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/resources/health.py +0 -0
  19. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/server.py +0 -0
  20. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/tools/AGENTS.md +0 -0
  21. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/tools/__init__.py +0 -0
  22. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/tools/metrics.py +0 -0
  23. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/utils/__init__.py +0 -0
  24. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/utils/builders.py +0 -0
  25. {pcp_mcp-1.1.0 → pcp_mcp-1.3.0}/src/pcp_mcp/utils/extractors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pcp-mcp
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: MCP server for Performance Co-Pilot
5
5
  Keywords: mcp,pcp,performance-co-pilot,monitoring,model-context-protocol
6
6
  Author: Major Hayden
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pcp-mcp"
3
- version = "1.1.0"
3
+ version = "1.3.0"
4
4
  description = "MCP server for Performance Co-Pilot"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -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 DiagnosisResult, ProcessTopResult, SystemSnapshot
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