pcp-mcp 1.3.2__py3-none-any.whl → 1.4.0__py3-none-any.whl
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/errors.py +23 -16
- pcp_mcp/prompts/__init__.py +20 -292
- pcp_mcp/prompts/cpu.py +69 -0
- pcp_mcp/prompts/diagnose.py +54 -0
- pcp_mcp/prompts/disk.py +69 -0
- pcp_mcp/prompts/memory.py +60 -0
- pcp_mcp/prompts/network.py +67 -0
- pcp_mcp/server.py +9 -5
- pcp_mcp/tools/__init__.py +20 -4
- pcp_mcp/tools/metrics.py +174 -160
- pcp_mcp/tools/system.py +293 -262
- {pcp_mcp-1.3.2.dist-info → pcp_mcp-1.4.0.dist-info}/METADATA +2 -2
- pcp_mcp-1.4.0.dist-info/RECORD +28 -0
- {pcp_mcp-1.3.2.dist-info → pcp_mcp-1.4.0.dist-info}/WHEEL +1 -1
- pcp_mcp-1.3.2.dist-info/RECORD +0 -23
- {pcp_mcp-1.3.2.dist-info → pcp_mcp-1.4.0.dist-info}/entry_points.txt +0 -0
pcp_mcp/tools/system.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""System health tools for clumped metric queries."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Annotated, Any, Literal, Optional
|
|
5
5
|
|
|
6
6
|
from fastmcp import Context
|
|
7
|
+
from fastmcp.tools import tool
|
|
8
|
+
from fastmcp.tools.tool import ToolResult
|
|
7
9
|
from mcp.types import ToolAnnotations
|
|
8
10
|
from pydantic import Field
|
|
9
11
|
|
|
@@ -39,8 +41,13 @@ from pcp_mcp.utils.builders import (
|
|
|
39
41
|
)
|
|
40
42
|
from pcp_mcp.utils.extractors import get_scalar_value
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
__all__ = [
|
|
45
|
+
"get_system_snapshot",
|
|
46
|
+
"quick_health",
|
|
47
|
+
"get_process_top",
|
|
48
|
+
"smart_diagnose",
|
|
49
|
+
"get_filesystem_usage",
|
|
50
|
+
]
|
|
44
51
|
|
|
45
52
|
TOOL_ANNOTATIONS = ToolAnnotations(readOnlyHint=True, openWorldHint=True)
|
|
46
53
|
|
|
@@ -164,278 +171,302 @@ async def _fetch_system_snapshot(
|
|
|
164
171
|
return snapshot
|
|
165
172
|
|
|
166
173
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
description=(
|
|
183
|
-
"Categories to include: cpu, memory, disk, network, load. "
|
|
184
|
-
"Defaults to all five if not specified."
|
|
185
|
-
),
|
|
186
|
-
),
|
|
187
|
-
] = None,
|
|
188
|
-
sample_interval: Annotated[
|
|
189
|
-
float,
|
|
190
|
-
Field(
|
|
191
|
-
default=1.0,
|
|
192
|
-
ge=0.1,
|
|
193
|
-
le=10.0,
|
|
194
|
-
description="Seconds between samples for rate calculation",
|
|
174
|
+
@tool(
|
|
175
|
+
annotations=TOOL_ANNOTATIONS,
|
|
176
|
+
icons=[ICON_SYSTEM],
|
|
177
|
+
tags=TAGS_SYSTEM,
|
|
178
|
+
timeout=30.0,
|
|
179
|
+
)
|
|
180
|
+
async def get_system_snapshot(
|
|
181
|
+
ctx: Context,
|
|
182
|
+
categories: Annotated[
|
|
183
|
+
Optional[list[str]],
|
|
184
|
+
Field(
|
|
185
|
+
default=None,
|
|
186
|
+
description=(
|
|
187
|
+
"Categories to include: cpu, memory, disk, network, load. "
|
|
188
|
+
"Defaults to all five if not specified."
|
|
195
189
|
),
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
190
|
+
),
|
|
191
|
+
] = None,
|
|
192
|
+
sample_interval: Annotated[
|
|
193
|
+
float,
|
|
194
|
+
Field(
|
|
195
|
+
default=1.0,
|
|
196
|
+
ge=0.1,
|
|
197
|
+
le=10.0,
|
|
198
|
+
description="Seconds between samples for rate calculation",
|
|
199
|
+
),
|
|
200
|
+
] = 1.0,
|
|
201
|
+
host: Annotated[
|
|
202
|
+
Optional[str],
|
|
203
|
+
Field(description="Target pmcd host to query (default: server's configured target)"),
|
|
204
|
+
] = None,
|
|
205
|
+
) -> ToolResult:
|
|
206
|
+
"""Get a point-in-time system health overview.
|
|
207
|
+
|
|
208
|
+
Returns CPU, memory, disk I/O, network I/O, and load metrics in a single
|
|
209
|
+
call. For rate metrics (CPU %, disk I/O, network throughput), takes two
|
|
210
|
+
samples to calculate per-second rates.
|
|
211
|
+
|
|
212
|
+
Use this tool FIRST for system troubleshooting. It automatically handles
|
|
213
|
+
counter-to-rate conversion. Do NOT use query_metrics() for CPU, disk, or
|
|
214
|
+
network counters - those return raw cumulative values since boot.
|
|
215
|
+
|
|
216
|
+
Examples:
|
|
217
|
+
get_system_snapshot() - Quick health check (all categories)
|
|
218
|
+
get_system_snapshot(categories=["cpu", "memory"]) - CPU and memory only
|
|
219
|
+
get_system_snapshot(categories=["cpu", "load"]) - CPU and load averages
|
|
220
|
+
get_system_snapshot(categories=["disk", "network"]) - I/O analysis
|
|
221
|
+
get_system_snapshot(host="web1.example.com") - Query remote host
|
|
222
|
+
"""
|
|
223
|
+
if categories is None:
|
|
224
|
+
categories = ["cpu", "memory", "disk", "network", "load"]
|
|
225
|
+
result = await _fetch_system_snapshot(ctx, categories, sample_interval, host)
|
|
226
|
+
return ToolResult(
|
|
227
|
+
content=result.model_dump_json(),
|
|
228
|
+
structured_content=result.model_dump(),
|
|
228
229
|
)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@tool(
|
|
233
|
+
annotations=TOOL_ANNOTATIONS,
|
|
234
|
+
icons=[ICON_HEALTH],
|
|
235
|
+
tags=TAGS_HEALTH,
|
|
236
|
+
timeout=30.0,
|
|
237
|
+
)
|
|
238
|
+
async def quick_health(
|
|
239
|
+
ctx: Context,
|
|
240
|
+
host: Annotated[
|
|
241
|
+
Optional[str],
|
|
242
|
+
Field(description="Target pmcd host to query (default: server's configured target)"),
|
|
243
|
+
] = None,
|
|
244
|
+
) -> ToolResult:
|
|
245
|
+
"""Fast system health check returning only CPU and memory metrics.
|
|
246
|
+
|
|
247
|
+
Use this for rapid status checks when you don't need disk/network/load
|
|
248
|
+
details. Uses a shorter sample interval (0.5s) for faster results.
|
|
249
|
+
|
|
250
|
+
Examples:
|
|
251
|
+
quick_health() - Fast health check on default host
|
|
252
|
+
quick_health(host="web1.example.com") - Fast check on remote host
|
|
253
|
+
"""
|
|
254
|
+
result = await _fetch_system_snapshot(ctx, ["cpu", "memory"], 0.5, host)
|
|
255
|
+
return ToolResult(
|
|
256
|
+
content=result.model_dump_json(),
|
|
257
|
+
structured_content=result.model_dump(),
|
|
252
258
|
)
|
|
253
|
-
async def get_process_top(
|
|
254
|
-
ctx: Context,
|
|
255
|
-
sort_by: Annotated[
|
|
256
|
-
Literal["cpu", "memory", "io"],
|
|
257
|
-
Field(description="Resource to sort by"),
|
|
258
|
-
] = "cpu",
|
|
259
|
-
limit: Annotated[
|
|
260
|
-
int,
|
|
261
|
-
Field(default=10, ge=1, le=50, description="Number of processes to return"),
|
|
262
|
-
] = 10,
|
|
263
|
-
sample_interval: Annotated[
|
|
264
|
-
float,
|
|
265
|
-
Field(
|
|
266
|
-
default=1.0,
|
|
267
|
-
ge=0.5,
|
|
268
|
-
le=5.0,
|
|
269
|
-
description="Seconds to sample for CPU/IO rates",
|
|
270
|
-
),
|
|
271
|
-
] = 1.0,
|
|
272
|
-
host: Annotated[
|
|
273
|
-
Optional[str],
|
|
274
|
-
Field(description="Target pmcd host to query (default: server's configured target)"),
|
|
275
|
-
] = None,
|
|
276
|
-
) -> ProcessTopResult:
|
|
277
|
-
"""Get top processes by resource consumption.
|
|
278
|
-
|
|
279
|
-
For CPU and I/O, takes two samples to calculate rates. Memory is instantaneous.
|
|
280
|
-
Returns the top N processes sorted by the requested resource.
|
|
281
|
-
|
|
282
|
-
Examples:
|
|
283
|
-
get_process_top() - Top 10 by CPU (default)
|
|
284
|
-
get_process_top(sort_by="memory", limit=20) - Top 20 memory consumers
|
|
285
|
-
get_process_top(sort_by="io", sample_interval=2.0) - Top I/O with longer sample
|
|
286
|
-
get_process_top(host="db1.example.com") - Query remote host
|
|
287
|
-
"""
|
|
288
|
-
all_metrics = (
|
|
289
|
-
PROCESS_METRICS["info"] + PROCESS_METRICS["memory"] + PROCESS_METRICS.get(sort_by, [])
|
|
290
|
-
)
|
|
291
|
-
if sort_by == "cpu":
|
|
292
|
-
all_metrics.extend(PROCESS_METRICS["cpu"])
|
|
293
|
-
elif sort_by == "io":
|
|
294
|
-
all_metrics.extend(PROCESS_METRICS["io"])
|
|
295
|
-
|
|
296
|
-
all_metrics = list(set(all_metrics))
|
|
297
|
-
system_metrics = ["hinv.ncpu", "mem.physmem"]
|
|
298
|
-
|
|
299
|
-
counter_metrics = {
|
|
300
|
-
"proc.psinfo.utime",
|
|
301
|
-
"proc.psinfo.stime",
|
|
302
|
-
"proc.io.read_bytes",
|
|
303
|
-
"proc.io.write_bytes",
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
from pcp_mcp.errors import handle_pcp_error
|
|
307
|
-
|
|
308
|
-
async def report_progress(current: float, total: float, message: str) -> None:
|
|
309
|
-
await ctx.report_progress(current, total, message)
|
|
310
|
-
|
|
311
|
-
async with get_client_for_host(ctx, host) as client:
|
|
312
|
-
try:
|
|
313
|
-
proc_data = await client.fetch_with_rates(
|
|
314
|
-
all_metrics, counter_metrics, sample_interval, progress_callback=report_progress
|
|
315
|
-
)
|
|
316
|
-
sys_data = await client.fetch(system_metrics)
|
|
317
|
-
except Exception as e:
|
|
318
|
-
raise handle_pcp_error(e, "fetching process data") from e
|
|
319
|
-
|
|
320
|
-
await ctx.report_progress(92, 100, "Processing results...")
|
|
321
|
-
|
|
322
|
-
ncpu = get_scalar_value(sys_data, "hinv.ncpu", 1)
|
|
323
|
-
total_mem = get_scalar_value(sys_data, "mem.physmem", 1) * 1024
|
|
324
|
-
|
|
325
|
-
processes = build_process_list(proc_data, sort_by, total_mem, ncpu)
|
|
326
|
-
processes.sort(key=lambda p: get_sort_key(p, sort_by), reverse=True)
|
|
327
|
-
processes = processes[:limit]
|
|
328
|
-
|
|
329
|
-
assessment = assess_processes(processes, sort_by, ncpu)
|
|
330
|
-
|
|
331
|
-
await ctx.report_progress(100, 100, "Complete")
|
|
332
|
-
return ProcessTopResult(
|
|
333
|
-
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
334
|
-
hostname=client.target_host,
|
|
335
|
-
sort_by=sort_by,
|
|
336
|
-
sample_interval=sample_interval,
|
|
337
|
-
processes=processes,
|
|
338
|
-
total_memory_bytes=int(total_mem),
|
|
339
|
-
ncpu=ncpu,
|
|
340
|
-
assessment=assessment,
|
|
341
|
-
)
|
|
342
259
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
260
|
+
|
|
261
|
+
@tool(
|
|
262
|
+
annotations=TOOL_ANNOTATIONS,
|
|
263
|
+
icons=[ICON_PROCESS],
|
|
264
|
+
tags=TAGS_PROCESS,
|
|
265
|
+
timeout=30.0,
|
|
266
|
+
)
|
|
267
|
+
async def get_process_top(
|
|
268
|
+
ctx: Context,
|
|
269
|
+
sort_by: Annotated[
|
|
270
|
+
Literal["cpu", "memory", "io"],
|
|
271
|
+
Field(description="Resource to sort by"),
|
|
272
|
+
] = "cpu",
|
|
273
|
+
limit: Annotated[
|
|
274
|
+
int,
|
|
275
|
+
Field(default=10, ge=1, le=50, description="Number of processes to return"),
|
|
276
|
+
] = 10,
|
|
277
|
+
sample_interval: Annotated[
|
|
278
|
+
float,
|
|
279
|
+
Field(
|
|
280
|
+
default=1.0,
|
|
281
|
+
ge=0.5,
|
|
282
|
+
le=5.0,
|
|
283
|
+
description="Seconds to sample for CPU/IO rates",
|
|
284
|
+
),
|
|
285
|
+
] = 1.0,
|
|
286
|
+
host: Annotated[
|
|
287
|
+
Optional[str],
|
|
288
|
+
Field(description="Target pmcd host to query (default: server's configured target)"),
|
|
289
|
+
] = None,
|
|
290
|
+
) -> ToolResult:
|
|
291
|
+
"""Get top processes by resource consumption.
|
|
292
|
+
|
|
293
|
+
For CPU and I/O, takes two samples to calculate rates. Memory is instantaneous.
|
|
294
|
+
Returns the top N processes sorted by the requested resource.
|
|
295
|
+
|
|
296
|
+
Examples:
|
|
297
|
+
get_process_top() - Top 10 by CPU (default)
|
|
298
|
+
get_process_top(sort_by="memory", limit=20) - Top 20 memory consumers
|
|
299
|
+
get_process_top(sort_by="io", sample_interval=2.0) - Top I/O with longer sample
|
|
300
|
+
get_process_top(host="db1.example.com") - Query remote host
|
|
301
|
+
"""
|
|
302
|
+
all_metrics = (
|
|
303
|
+
PROCESS_METRICS["info"] + PROCESS_METRICS["memory"] + PROCESS_METRICS.get(sort_by, [])
|
|
348
304
|
)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
Field(description="Target pmcd host to query (default: server's configured target)"),
|
|
354
|
-
] = None,
|
|
355
|
-
) -> DiagnosisResult:
|
|
356
|
-
"""Use LLM to analyze system metrics and provide diagnosis.
|
|
357
|
-
|
|
358
|
-
Collects a quick system snapshot (CPU, memory, load) and asks the
|
|
359
|
-
connected LLM to analyze the metrics and provide actionable insights.
|
|
360
|
-
|
|
361
|
-
This tool demonstrates FastMCP's LLM sampling capability, where the
|
|
362
|
-
MCP server can request LLM assistance for complex analysis tasks.
|
|
363
|
-
|
|
364
|
-
Examples:
|
|
365
|
-
smart_diagnose() - Analyze default host
|
|
366
|
-
smart_diagnose(host="db1.example.com") - Analyze remote host
|
|
367
|
-
"""
|
|
368
|
-
from pcp_mcp.errors import handle_pcp_error
|
|
305
|
+
if sort_by == "cpu":
|
|
306
|
+
all_metrics.extend(PROCESS_METRICS["cpu"])
|
|
307
|
+
elif sort_by == "io":
|
|
308
|
+
all_metrics.extend(PROCESS_METRICS["io"])
|
|
369
309
|
|
|
310
|
+
all_metrics = list(set(all_metrics))
|
|
311
|
+
system_metrics = ["hinv.ncpu", "mem.physmem"]
|
|
312
|
+
|
|
313
|
+
counter_metrics = {
|
|
314
|
+
"proc.psinfo.utime",
|
|
315
|
+
"proc.psinfo.stime",
|
|
316
|
+
"proc.io.read_bytes",
|
|
317
|
+
"proc.io.write_bytes",
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
from pcp_mcp.errors import handle_pcp_error
|
|
321
|
+
|
|
322
|
+
async def report_progress(current: float, total: float, message: str) -> None:
|
|
323
|
+
await ctx.report_progress(current, total, message)
|
|
324
|
+
|
|
325
|
+
async with get_client_for_host(ctx, host) as client:
|
|
370
326
|
try:
|
|
371
|
-
|
|
327
|
+
proc_data = await client.fetch_with_rates(
|
|
328
|
+
all_metrics, counter_metrics, sample_interval, progress_callback=report_progress
|
|
329
|
+
)
|
|
330
|
+
sys_data = await client.fetch(system_metrics)
|
|
372
331
|
except Exception as e:
|
|
373
|
-
raise handle_pcp_error(e, "fetching
|
|
332
|
+
raise handle_pcp_error(e, "fetching process data") from e
|
|
333
|
+
|
|
334
|
+
await ctx.report_progress(92, 100, "Processing results...")
|
|
335
|
+
|
|
336
|
+
ncpu = get_scalar_value(sys_data, "hinv.ncpu", 1)
|
|
337
|
+
total_mem = get_scalar_value(sys_data, "mem.physmem", 1) * 1024
|
|
338
|
+
|
|
339
|
+
processes = build_process_list(proc_data, sort_by, total_mem, ncpu)
|
|
340
|
+
processes.sort(key=lambda p: get_sort_key(p, sort_by), reverse=True)
|
|
341
|
+
processes = processes[:limit]
|
|
374
342
|
|
|
375
|
-
|
|
343
|
+
assessment = assess_processes(processes, sort_by, ncpu)
|
|
376
344
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
345
|
+
await ctx.report_progress(100, 100, "Complete")
|
|
346
|
+
result = ProcessTopResult(
|
|
347
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
348
|
+
hostname=client.target_host,
|
|
349
|
+
sort_by=sort_by,
|
|
350
|
+
sample_interval=sample_interval,
|
|
351
|
+
processes=processes,
|
|
352
|
+
total_memory_bytes=int(total_mem),
|
|
353
|
+
ncpu=ncpu,
|
|
354
|
+
assessment=assessment,
|
|
355
|
+
)
|
|
356
|
+
return ToolResult(
|
|
357
|
+
content=result.model_dump_json(),
|
|
358
|
+
structured_content=result.model_dump(),
|
|
383
359
|
)
|
|
384
360
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
361
|
+
|
|
362
|
+
@tool(
|
|
363
|
+
annotations=TOOL_ANNOTATIONS,
|
|
364
|
+
icons=[ICON_DIAGNOSE],
|
|
365
|
+
tags=TAGS_DIAGNOSE,
|
|
366
|
+
timeout=30.0,
|
|
367
|
+
)
|
|
368
|
+
async def smart_diagnose(
|
|
369
|
+
ctx: Context,
|
|
370
|
+
host: Annotated[
|
|
371
|
+
Optional[str],
|
|
372
|
+
Field(description="Target pmcd host to query (default: server's configured target)"),
|
|
373
|
+
] = None,
|
|
374
|
+
) -> ToolResult:
|
|
375
|
+
"""Use LLM to analyze system metrics and provide diagnosis.
|
|
376
|
+
|
|
377
|
+
Collects a quick system snapshot (CPU, memory, load) and asks the
|
|
378
|
+
connected LLM to analyze the metrics and provide actionable insights.
|
|
379
|
+
|
|
380
|
+
This tool demonstrates FastMCP's LLM sampling capability, where the
|
|
381
|
+
MCP server can request LLM assistance for complex analysis tasks.
|
|
382
|
+
|
|
383
|
+
Examples:
|
|
384
|
+
smart_diagnose() - Analyze default host
|
|
385
|
+
smart_diagnose(host="db1.example.com") - Analyze remote host
|
|
386
|
+
"""
|
|
387
|
+
from pcp_mcp.errors import handle_pcp_error
|
|
388
|
+
|
|
389
|
+
try:
|
|
390
|
+
snapshot = await _fetch_system_snapshot(ctx, ["cpu", "memory", "load"], 0.5, host)
|
|
391
|
+
except Exception as e:
|
|
392
|
+
raise handle_pcp_error(e, "fetching metrics for diagnosis") from e
|
|
393
|
+
|
|
394
|
+
metrics_summary = _format_snapshot_for_llm(snapshot)
|
|
395
|
+
|
|
396
|
+
system_prompt = (
|
|
397
|
+
"You are a system performance analyst. Analyze the metrics and provide:\n"
|
|
398
|
+
"1. A brief diagnosis (2-3 sentences)\n"
|
|
399
|
+
"2. A severity level: 'healthy', 'warning', or 'critical'\n"
|
|
400
|
+
"3. Up to 3 actionable recommendations\n\n"
|
|
401
|
+
"Be concise and focus on actionable insights."
|
|
404
402
|
)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
403
|
+
|
|
404
|
+
try:
|
|
405
|
+
sampling_result = await ctx.sample(
|
|
406
|
+
messages=f"Analyze these system metrics:\n\n{metrics_summary}",
|
|
407
|
+
system_prompt=system_prompt,
|
|
408
|
+
max_tokens=500,
|
|
409
|
+
result_type=DiagnosisResult,
|
|
410
|
+
)
|
|
411
|
+
result = sampling_result.result
|
|
412
|
+
result.timestamp = snapshot.timestamp
|
|
413
|
+
result.hostname = snapshot.hostname
|
|
414
|
+
return ToolResult(
|
|
415
|
+
content=result.model_dump_json(),
|
|
416
|
+
structured_content=result.model_dump(),
|
|
417
|
+
)
|
|
418
|
+
except Exception:
|
|
419
|
+
result = _build_fallback_diagnosis(snapshot)
|
|
420
|
+
return ToolResult(
|
|
421
|
+
content=result.model_dump_json(),
|
|
422
|
+
structured_content=result.model_dump(),
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@tool(
|
|
427
|
+
annotations=TOOL_ANNOTATIONS,
|
|
428
|
+
icons=[ICON_FILESYSTEM],
|
|
429
|
+
tags=TAGS_FILESYSTEM,
|
|
430
|
+
timeout=30.0,
|
|
431
|
+
)
|
|
432
|
+
async def get_filesystem_usage(
|
|
433
|
+
ctx: Context,
|
|
434
|
+
host: Annotated[
|
|
435
|
+
Optional[str],
|
|
436
|
+
Field(description="Target pmcd host to query (default: server's configured target)"),
|
|
437
|
+
] = None,
|
|
438
|
+
) -> ToolResult:
|
|
439
|
+
"""Get mounted filesystem usage (similar to df command).
|
|
440
|
+
|
|
441
|
+
Returns capacity, used, available, and percent full for each mounted
|
|
442
|
+
filesystem. Useful for monitoring disk space and identifying filesystems
|
|
443
|
+
that may need attention.
|
|
444
|
+
|
|
445
|
+
Examples:
|
|
446
|
+
get_filesystem_usage() - Check all filesystems on default host
|
|
447
|
+
get_filesystem_usage(host="db1.example.com") - Check remote host
|
|
448
|
+
"""
|
|
449
|
+
from pcp_mcp.errors import handle_pcp_error
|
|
450
|
+
|
|
451
|
+
async with get_client_for_host(ctx, host) as client:
|
|
452
|
+
try:
|
|
453
|
+
response = await client.fetch(FILESYSTEM_METRICS)
|
|
454
|
+
except Exception as e:
|
|
455
|
+
raise handle_pcp_error(e, "fetching filesystem metrics") from e
|
|
456
|
+
|
|
457
|
+
filesystems = _build_filesystem_list(response)
|
|
458
|
+
assessment = _assess_filesystems(filesystems)
|
|
459
|
+
|
|
460
|
+
result = FilesystemSnapshot(
|
|
461
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
462
|
+
hostname=client.target_host,
|
|
463
|
+
filesystems=filesystems,
|
|
464
|
+
assessment=assessment,
|
|
465
|
+
)
|
|
466
|
+
return ToolResult(
|
|
467
|
+
content=result.model_dump_json(),
|
|
468
|
+
structured_content=result.model_dump(),
|
|
469
|
+
)
|
|
439
470
|
|
|
440
471
|
|
|
441
472
|
def _build_filesystem_list(response: dict) -> list[FilesystemInfo]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pcp-mcp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.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
|
|
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
18
18
|
Classifier: Topic :: System :: Monitoring
|
|
19
19
|
Classifier: Typing :: Typed
|
|
20
20
|
Requires-Dist: cachetools>=5.0
|
|
21
|
-
Requires-Dist: fastmcp>=
|
|
21
|
+
Requires-Dist: fastmcp>=3.0.0b1
|
|
22
22
|
Requires-Dist: httpx>=0.27
|
|
23
23
|
Requires-Dist: pydantic-settings>=2.0.0
|
|
24
24
|
Requires-Dist: typing-extensions>=4.0 ; python_full_version < '3.11'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
pcp_mcp/AGENTS.md,sha256=kfitTd6NuPieWLTAl9-m-i93URL1DD7yu-AnK8kA8Yw,2407
|
|
2
|
+
pcp_mcp/__init__.py,sha256=5SKlrOQTqKxcWRvmBwmeXJapTqKggal8L89UxxwHTaQ,1949
|
|
3
|
+
pcp_mcp/client.py,sha256=ZGWGXYn77_hbZ81O0vxjXubY9eRpOZWs2cLxqLO3pf8,9188
|
|
4
|
+
pcp_mcp/config.py,sha256=gm-Sp1y-f3ZGZQk_ercMuKCojG145Fu6UjrvjRQUnpg,3526
|
|
5
|
+
pcp_mcp/context.py,sha256=5M6l72phsijPr96PXpZDm1rd_Uvo7mY_LuiLdeiW2SE,2801
|
|
6
|
+
pcp_mcp/errors.py,sha256=uixDH3NiteHuHr4-EIVM9XrsKirxkhe2L90fnjsKxOY,1737
|
|
7
|
+
pcp_mcp/icons.py,sha256=_ZrllbTM8Jp74EBpIIX5BaL8pN_1Ld6n29NWKoGAJD8,1625
|
|
8
|
+
pcp_mcp/middleware.py,sha256=oUSdaCHSy1gVkKyeC2J8ASfhJep-3KvY8GFYRFWUvJ0,2387
|
|
9
|
+
pcp_mcp/models.py,sha256=EPm1R7_qRLDFoqAzwegiv15KpHrmTJTjPD1LpxQdgoc,8202
|
|
10
|
+
pcp_mcp/prompts/__init__.py,sha256=g97146MaOnWDx2KdhRXdwXiUR5wx71lvU4iPwbC_Zj4,1015
|
|
11
|
+
pcp_mcp/prompts/cpu.py,sha256=s_sux0Q9YulPTuvbgw8StzpfB-omzBmMe7Wyf4kBlv4,2909
|
|
12
|
+
pcp_mcp/prompts/diagnose.py,sha256=y_pMKBdmiZ6Q6PjEtCd_9tgR9Sm3udMHyDgqt1BJe4E,2076
|
|
13
|
+
pcp_mcp/prompts/disk.py,sha256=8QJumxpcPvPV0jA4fDIj1q0v1yT1mNA1Apg9nBQFpsc,2701
|
|
14
|
+
pcp_mcp/prompts/memory.py,sha256=WyJEvEG0rQUJwmlCuJEIp5uTH3EudZF-OSb-U_K-TK0,2118
|
|
15
|
+
pcp_mcp/prompts/network.py,sha256=-9ei4yNgQVgvkMKb5OgBsYU5CiPWZhJDyP3kCO5Ihmo,2879
|
|
16
|
+
pcp_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
pcp_mcp/server.py,sha256=2A-1KzAp436qy29cP6jf6Npah6EvFJ-eiDCHUMhfQ4A,4779
|
|
18
|
+
pcp_mcp/tools/AGENTS.md,sha256=1yt_W-TYlGA0aWJYCM7D0i2D4899E6_kIhyoqP1np-g,1963
|
|
19
|
+
pcp_mcp/tools/__init__.py,sha256=7TRaPUx4pYzAj2aW0lULA1vws04K6_CUsnkO5aAUEK8,889
|
|
20
|
+
pcp_mcp/tools/metrics.py,sha256=MrctKTC6eNvj6GPQw4ZMbuE6lb5-G1HtdRAYzL7Oc78,6574
|
|
21
|
+
pcp_mcp/tools/system.py,sha256=T9WceT9D35WPTu9xCO9VpCoDny3YF476SQTsMVKGe8o,20454
|
|
22
|
+
pcp_mcp/utils/__init__.py,sha256=tTbcqrCV9pBBm7N3MwEI37Lc0JM1CVbw_etw36ejRWc,884
|
|
23
|
+
pcp_mcp/utils/builders.py,sha256=n13Ou6cb1-YToG-M31J8_jWajq8ioJx6tJTKnqaQiio,10293
|
|
24
|
+
pcp_mcp/utils/extractors.py,sha256=fy6aCI23JuGt73oIDxwPW_K4B0fJkFCF1VxYkBst0Y4,2279
|
|
25
|
+
pcp_mcp-1.4.0.dist-info/WHEEL,sha256=iHtWm8nRfs0VRdCYVXocAWFW8ppjHL-uTJkAdZJKOBM,80
|
|
26
|
+
pcp_mcp-1.4.0.dist-info/entry_points.txt,sha256=PhVo92EGoS05yEpHVRyKEsxKya_bWlPLodp-g4tr2Rg,42
|
|
27
|
+
pcp_mcp-1.4.0.dist-info/METADATA,sha256=Xpcjk0GKCQLp38fNZYwJMmnhdCGIsAkMA7WvS3JMI-8,6847
|
|
28
|
+
pcp_mcp-1.4.0.dist-info/RECORD,,
|