arch-ops-server 3.0.1__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.
- arch_ops_server/__init__.py +176 -0
- arch_ops_server/aur.py +1190 -0
- arch_ops_server/config.py +361 -0
- arch_ops_server/http_server.py +829 -0
- arch_ops_server/logs.py +345 -0
- arch_ops_server/mirrors.py +397 -0
- arch_ops_server/news.py +288 -0
- arch_ops_server/pacman.py +1305 -0
- arch_ops_server/py.typed +0 -0
- arch_ops_server/server.py +1869 -0
- arch_ops_server/system.py +307 -0
- arch_ops_server/utils.py +313 -0
- arch_ops_server/wiki.py +245 -0
- arch_ops_server-3.0.1.dist-info/METADATA +253 -0
- arch_ops_server-3.0.1.dist-info/RECORD +17 -0
- arch_ops_server-3.0.1.dist-info/WHEEL +4 -0
- arch_ops_server-3.0.1.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,1869 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-only OR MIT
|
|
2
|
+
"""
|
|
3
|
+
MCP Server setup for Arch Linux operations.
|
|
4
|
+
|
|
5
|
+
This module contains the MCP server configuration, resources, tools, and prompts
|
|
6
|
+
for the Arch Linux MCP server.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import json
|
|
11
|
+
from typing import Any
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
from mcp.server import Server
|
|
15
|
+
from mcp.types import (
|
|
16
|
+
Resource,
|
|
17
|
+
Tool,
|
|
18
|
+
TextContent,
|
|
19
|
+
ImageContent,
|
|
20
|
+
EmbeddedResource,
|
|
21
|
+
Prompt,
|
|
22
|
+
PromptMessage,
|
|
23
|
+
GetPromptResult,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from . import (
|
|
27
|
+
# Wiki functions
|
|
28
|
+
search_wiki,
|
|
29
|
+
get_wiki_page_as_text,
|
|
30
|
+
# AUR functions
|
|
31
|
+
search_aur,
|
|
32
|
+
get_aur_info,
|
|
33
|
+
get_pkgbuild,
|
|
34
|
+
analyze_pkgbuild_safety,
|
|
35
|
+
analyze_package_metadata_risk,
|
|
36
|
+
install_package_secure,
|
|
37
|
+
# Pacman functions
|
|
38
|
+
get_official_package_info,
|
|
39
|
+
check_updates_dry_run,
|
|
40
|
+
remove_package,
|
|
41
|
+
remove_packages_batch,
|
|
42
|
+
list_orphan_packages,
|
|
43
|
+
remove_orphans,
|
|
44
|
+
find_package_owner,
|
|
45
|
+
list_package_files,
|
|
46
|
+
search_package_files,
|
|
47
|
+
verify_package_integrity,
|
|
48
|
+
list_package_groups,
|
|
49
|
+
list_group_packages,
|
|
50
|
+
list_explicit_packages,
|
|
51
|
+
mark_as_explicit,
|
|
52
|
+
mark_as_dependency,
|
|
53
|
+
check_database_freshness,
|
|
54
|
+
# System functions
|
|
55
|
+
get_system_info,
|
|
56
|
+
check_disk_space,
|
|
57
|
+
get_pacman_cache_stats,
|
|
58
|
+
check_failed_services,
|
|
59
|
+
get_boot_logs,
|
|
60
|
+
# News functions
|
|
61
|
+
get_latest_news,
|
|
62
|
+
check_critical_news,
|
|
63
|
+
get_news_since_last_update,
|
|
64
|
+
# Logs functions
|
|
65
|
+
get_transaction_history,
|
|
66
|
+
find_when_installed,
|
|
67
|
+
find_failed_transactions,
|
|
68
|
+
get_database_sync_history,
|
|
69
|
+
# Mirrors functions
|
|
70
|
+
list_active_mirrors,
|
|
71
|
+
test_mirror_speed,
|
|
72
|
+
suggest_fastest_mirrors,
|
|
73
|
+
check_mirrorlist_health,
|
|
74
|
+
# Config functions
|
|
75
|
+
analyze_pacman_conf,
|
|
76
|
+
analyze_makepkg_conf,
|
|
77
|
+
check_ignored_packages,
|
|
78
|
+
get_parallel_downloads_setting,
|
|
79
|
+
# Utils
|
|
80
|
+
IS_ARCH,
|
|
81
|
+
run_command,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Configure logging
|
|
85
|
+
logger = logging.getLogger(__name__)
|
|
86
|
+
|
|
87
|
+
# Initialize MCP server
|
|
88
|
+
server = Server("arch-ops-server")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ============================================================================
|
|
92
|
+
# RESOURCES
|
|
93
|
+
# ============================================================================
|
|
94
|
+
|
|
95
|
+
@server.list_resources()
|
|
96
|
+
async def list_resources() -> list[Resource]:
|
|
97
|
+
"""
|
|
98
|
+
List available resource URI schemes.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of Resource objects describing available URI schemes
|
|
102
|
+
"""
|
|
103
|
+
return [
|
|
104
|
+
# Wiki resources
|
|
105
|
+
Resource(
|
|
106
|
+
uri="archwiki://Installation_guide",
|
|
107
|
+
name="Arch Wiki - Installation Guide",
|
|
108
|
+
mimeType="text/markdown",
|
|
109
|
+
description="Example: Fetch Arch Wiki pages as Markdown"
|
|
110
|
+
),
|
|
111
|
+
# AUR resources
|
|
112
|
+
Resource(
|
|
113
|
+
uri="aur://yay/pkgbuild",
|
|
114
|
+
name="AUR - yay PKGBUILD",
|
|
115
|
+
mimeType="text/x-script.shell",
|
|
116
|
+
description="Example: Fetch AUR package PKGBUILD files"
|
|
117
|
+
),
|
|
118
|
+
Resource(
|
|
119
|
+
uri="aur://yay/info",
|
|
120
|
+
name="AUR - yay Package Info",
|
|
121
|
+
mimeType="application/json",
|
|
122
|
+
description="Example: Fetch AUR package metadata (votes, maintainer, etc)"
|
|
123
|
+
),
|
|
124
|
+
# Official repository resources
|
|
125
|
+
Resource(
|
|
126
|
+
uri="archrepo://vim",
|
|
127
|
+
name="Official Repository - Package Info",
|
|
128
|
+
mimeType="application/json",
|
|
129
|
+
description="Example: Fetch official repository package details"
|
|
130
|
+
),
|
|
131
|
+
# Pacman resources
|
|
132
|
+
Resource(
|
|
133
|
+
uri="pacman://installed",
|
|
134
|
+
name="System - Installed Packages",
|
|
135
|
+
mimeType="application/json",
|
|
136
|
+
description="List installed packages on Arch Linux system"
|
|
137
|
+
),
|
|
138
|
+
Resource(
|
|
139
|
+
uri="pacman://orphans",
|
|
140
|
+
name="System - Orphan Packages",
|
|
141
|
+
mimeType="application/json",
|
|
142
|
+
description="List orphaned packages (dependencies no longer required)"
|
|
143
|
+
),
|
|
144
|
+
Resource(
|
|
145
|
+
uri="pacman://explicit",
|
|
146
|
+
name="System - Explicitly Installed Packages",
|
|
147
|
+
mimeType="application/json",
|
|
148
|
+
description="List packages explicitly installed by user"
|
|
149
|
+
),
|
|
150
|
+
Resource(
|
|
151
|
+
uri="pacman://groups",
|
|
152
|
+
name="System - Package Groups",
|
|
153
|
+
mimeType="application/json",
|
|
154
|
+
description="List all available package groups"
|
|
155
|
+
),
|
|
156
|
+
Resource(
|
|
157
|
+
uri="pacman://group/base-devel",
|
|
158
|
+
name="System - Packages in base-devel Group",
|
|
159
|
+
mimeType="application/json",
|
|
160
|
+
description="Example: List packages in a specific group"
|
|
161
|
+
),
|
|
162
|
+
# System resources
|
|
163
|
+
Resource(
|
|
164
|
+
uri="system://info",
|
|
165
|
+
name="System - System Information",
|
|
166
|
+
mimeType="application/json",
|
|
167
|
+
description="Get system information (kernel, arch, memory, uptime)"
|
|
168
|
+
),
|
|
169
|
+
Resource(
|
|
170
|
+
uri="system://disk",
|
|
171
|
+
name="System - Disk Space",
|
|
172
|
+
mimeType="application/json",
|
|
173
|
+
description="Check disk space usage for critical paths"
|
|
174
|
+
),
|
|
175
|
+
Resource(
|
|
176
|
+
uri="system://services/failed",
|
|
177
|
+
name="System - Failed Services",
|
|
178
|
+
mimeType="application/json",
|
|
179
|
+
description="List failed systemd services"
|
|
180
|
+
),
|
|
181
|
+
Resource(
|
|
182
|
+
uri="system://logs/boot",
|
|
183
|
+
name="System - Boot Logs",
|
|
184
|
+
mimeType="text/plain",
|
|
185
|
+
description="Get recent boot logs from journalctl"
|
|
186
|
+
),
|
|
187
|
+
# News resources
|
|
188
|
+
Resource(
|
|
189
|
+
uri="archnews://latest",
|
|
190
|
+
name="Arch News - Latest",
|
|
191
|
+
mimeType="application/json",
|
|
192
|
+
description="Get latest Arch Linux news announcements"
|
|
193
|
+
),
|
|
194
|
+
Resource(
|
|
195
|
+
uri="archnews://critical",
|
|
196
|
+
name="Arch News - Critical",
|
|
197
|
+
mimeType="application/json",
|
|
198
|
+
description="Get critical Arch Linux news requiring manual intervention"
|
|
199
|
+
),
|
|
200
|
+
Resource(
|
|
201
|
+
uri="archnews://since-update",
|
|
202
|
+
name="Arch News - Since Last Update",
|
|
203
|
+
mimeType="application/json",
|
|
204
|
+
description="Get news posted since last pacman update"
|
|
205
|
+
),
|
|
206
|
+
# Transaction log resources
|
|
207
|
+
Resource(
|
|
208
|
+
uri="pacman://log/recent",
|
|
209
|
+
name="Pacman Log - Recent Transactions",
|
|
210
|
+
mimeType="application/json",
|
|
211
|
+
description="Get recent package transactions from pacman log"
|
|
212
|
+
),
|
|
213
|
+
Resource(
|
|
214
|
+
uri="pacman://log/failed",
|
|
215
|
+
name="Pacman Log - Failed Transactions",
|
|
216
|
+
mimeType="application/json",
|
|
217
|
+
description="Get failed package transactions"
|
|
218
|
+
),
|
|
219
|
+
# Mirror resources
|
|
220
|
+
Resource(
|
|
221
|
+
uri="mirrors://active",
|
|
222
|
+
name="Mirrors - Active Configuration",
|
|
223
|
+
mimeType="application/json",
|
|
224
|
+
description="Get currently configured mirrors"
|
|
225
|
+
),
|
|
226
|
+
Resource(
|
|
227
|
+
uri="mirrors://health",
|
|
228
|
+
name="Mirrors - Health Status",
|
|
229
|
+
mimeType="application/json",
|
|
230
|
+
description="Get mirror configuration health assessment"
|
|
231
|
+
),
|
|
232
|
+
# Config resources
|
|
233
|
+
Resource(
|
|
234
|
+
uri="config://pacman",
|
|
235
|
+
name="Config - pacman.conf",
|
|
236
|
+
mimeType="application/json",
|
|
237
|
+
description="Get parsed pacman.conf configuration"
|
|
238
|
+
),
|
|
239
|
+
Resource(
|
|
240
|
+
uri="config://makepkg",
|
|
241
|
+
name="Config - makepkg.conf",
|
|
242
|
+
mimeType="application/json",
|
|
243
|
+
description="Get parsed makepkg.conf configuration"
|
|
244
|
+
),
|
|
245
|
+
# Database resources
|
|
246
|
+
Resource(
|
|
247
|
+
uri="pacman://database/freshness",
|
|
248
|
+
name="Pacman - Database Freshness",
|
|
249
|
+
mimeType="application/json",
|
|
250
|
+
description="Check when package databases were last synchronized"
|
|
251
|
+
),
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@server.read_resource()
|
|
256
|
+
async def read_resource(uri: str) -> str:
|
|
257
|
+
"""
|
|
258
|
+
Read a resource by URI.
|
|
259
|
+
|
|
260
|
+
Supported schemes:
|
|
261
|
+
- archwiki://{page_title} - Returns Wiki page as Markdown
|
|
262
|
+
- aur://{package}/pkgbuild - Returns raw PKGBUILD file
|
|
263
|
+
- aur://{package}/info - Returns AUR package metadata
|
|
264
|
+
- archrepo://{package} - Returns official repository package info
|
|
265
|
+
- pacman://installed - Returns list of installed packages (Arch only)
|
|
266
|
+
- pacman://orphans - Returns list of orphaned packages (Arch only)
|
|
267
|
+
- pacman://explicit - Returns list of explicitly installed packages (Arch only)
|
|
268
|
+
- pacman://groups - Returns list of all package groups (Arch only)
|
|
269
|
+
- pacman://group/{group_name} - Returns packages in a specific group (Arch only)
|
|
270
|
+
- system://info - Returns system information
|
|
271
|
+
- system://disk - Returns disk space information
|
|
272
|
+
- system://services/failed - Returns failed systemd services
|
|
273
|
+
- system://logs/boot - Returns recent boot logs
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
uri: Resource URI (can be string or AnyUrl object)
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Resource content as string
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
ValueError: If URI scheme is unsupported or resource not found
|
|
283
|
+
"""
|
|
284
|
+
# Convert to string if it's a Pydantic AnyUrl object
|
|
285
|
+
uri_str = str(uri)
|
|
286
|
+
logger.info(f"Reading resource: {uri_str}")
|
|
287
|
+
|
|
288
|
+
parsed = urlparse(uri_str)
|
|
289
|
+
scheme = parsed.scheme
|
|
290
|
+
|
|
291
|
+
if scheme == "archwiki":
|
|
292
|
+
# Extract page title from path (remove leading /)
|
|
293
|
+
page_title = parsed.path.lstrip('/')
|
|
294
|
+
|
|
295
|
+
if not page_title:
|
|
296
|
+
# If only hostname provided, use it as title
|
|
297
|
+
page_title = parsed.netloc
|
|
298
|
+
|
|
299
|
+
if not page_title:
|
|
300
|
+
raise ValueError("Wiki page title required in URI (e.g., archwiki://Installation_guide)")
|
|
301
|
+
|
|
302
|
+
# Fetch Wiki page as Markdown
|
|
303
|
+
content = await get_wiki_page_as_text(page_title)
|
|
304
|
+
return content
|
|
305
|
+
|
|
306
|
+
elif scheme == "aur":
|
|
307
|
+
# Extract package name from netloc or path
|
|
308
|
+
package_name = parsed.netloc or parsed.path.lstrip('/').split('/')[0]
|
|
309
|
+
|
|
310
|
+
if not package_name:
|
|
311
|
+
raise ValueError("AUR package name required in URI (e.g., aur://yay/pkgbuild)")
|
|
312
|
+
|
|
313
|
+
# Determine what to fetch based on path
|
|
314
|
+
path_parts = parsed.path.lstrip('/').split('/')
|
|
315
|
+
|
|
316
|
+
if len(path_parts) > 1 and path_parts[1] == "pkgbuild":
|
|
317
|
+
# Fetch PKGBUILD
|
|
318
|
+
pkgbuild_content = await get_pkgbuild(package_name)
|
|
319
|
+
return pkgbuild_content
|
|
320
|
+
elif len(path_parts) > 1 and path_parts[1] == "info":
|
|
321
|
+
# Fetch package info
|
|
322
|
+
package_info = await get_aur_info(package_name)
|
|
323
|
+
return json.dumps(package_info, indent=2)
|
|
324
|
+
else:
|
|
325
|
+
# Default to package info
|
|
326
|
+
package_info = await get_aur_info(package_name)
|
|
327
|
+
return json.dumps(package_info, indent=2)
|
|
328
|
+
|
|
329
|
+
elif scheme == "archrepo":
|
|
330
|
+
# Extract package name from netloc or path
|
|
331
|
+
package_name = parsed.netloc or parsed.path.lstrip('/')
|
|
332
|
+
|
|
333
|
+
if not package_name:
|
|
334
|
+
raise ValueError("Package name required in URI (e.g., archrepo://vim)")
|
|
335
|
+
|
|
336
|
+
# Fetch official package info
|
|
337
|
+
package_info = await get_official_package_info(package_name)
|
|
338
|
+
return json.dumps(package_info, indent=2)
|
|
339
|
+
|
|
340
|
+
elif scheme == "pacman":
|
|
341
|
+
if not IS_ARCH:
|
|
342
|
+
raise ValueError(f"pacman:// resources only available on Arch Linux systems")
|
|
343
|
+
|
|
344
|
+
resource_path = parsed.netloc or parsed.path.lstrip('/')
|
|
345
|
+
|
|
346
|
+
if resource_path == "installed":
|
|
347
|
+
# Get installed packages
|
|
348
|
+
exit_code, stdout, stderr = await run_command(["pacman", "-Q"])
|
|
349
|
+
if exit_code != 0:
|
|
350
|
+
raise ValueError(f"Failed to get installed packages: {stderr}")
|
|
351
|
+
|
|
352
|
+
# Parse pacman output
|
|
353
|
+
packages = []
|
|
354
|
+
for line in stdout.strip().split('\n'):
|
|
355
|
+
if line.strip():
|
|
356
|
+
name, version = line.strip().rsplit(' ', 1)
|
|
357
|
+
packages.append({"name": name, "version": version})
|
|
358
|
+
|
|
359
|
+
return json.dumps(packages, indent=2)
|
|
360
|
+
|
|
361
|
+
elif resource_path == "orphans":
|
|
362
|
+
# Get orphan packages
|
|
363
|
+
result = await list_orphan_packages()
|
|
364
|
+
return json.dumps(result, indent=2)
|
|
365
|
+
|
|
366
|
+
elif resource_path == "explicit":
|
|
367
|
+
# Get explicitly installed packages
|
|
368
|
+
result = await list_explicit_packages()
|
|
369
|
+
return json.dumps(result, indent=2)
|
|
370
|
+
|
|
371
|
+
elif resource_path == "groups":
|
|
372
|
+
# Get all package groups
|
|
373
|
+
result = await list_package_groups()
|
|
374
|
+
return json.dumps(result, indent=2)
|
|
375
|
+
|
|
376
|
+
elif resource_path.startswith("group/"):
|
|
377
|
+
# Get packages in specific group
|
|
378
|
+
group_name = resource_path.split('/', 1)[1]
|
|
379
|
+
if not group_name:
|
|
380
|
+
raise ValueError("Group name required (e.g., pacman://group/base-devel)")
|
|
381
|
+
result = await list_group_packages(group_name)
|
|
382
|
+
return json.dumps(result, indent=2)
|
|
383
|
+
|
|
384
|
+
elif resource_path.startswith("log/"):
|
|
385
|
+
# Transaction log resources
|
|
386
|
+
log_type = resource_path.split('/', 1)[1] if '/' in resource_path else ""
|
|
387
|
+
|
|
388
|
+
if log_type == "recent":
|
|
389
|
+
result = await get_transaction_history()
|
|
390
|
+
return json.dumps(result, indent=2)
|
|
391
|
+
elif log_type == "failed":
|
|
392
|
+
result = await find_failed_transactions()
|
|
393
|
+
return json.dumps(result, indent=2)
|
|
394
|
+
else:
|
|
395
|
+
raise ValueError(f"Unsupported log resource: {log_type}")
|
|
396
|
+
|
|
397
|
+
elif resource_path == "database/freshness":
|
|
398
|
+
# Database freshness check
|
|
399
|
+
result = await check_database_freshness()
|
|
400
|
+
return json.dumps(result, indent=2)
|
|
401
|
+
|
|
402
|
+
else:
|
|
403
|
+
raise ValueError(f"Unsupported pacman resource: {resource_path}")
|
|
404
|
+
|
|
405
|
+
elif scheme == "system":
|
|
406
|
+
resource_path = parsed.netloc or parsed.path.lstrip('/')
|
|
407
|
+
|
|
408
|
+
if resource_path == "info":
|
|
409
|
+
# Get system information
|
|
410
|
+
result = await get_system_info()
|
|
411
|
+
return json.dumps(result, indent=2)
|
|
412
|
+
|
|
413
|
+
elif resource_path == "disk":
|
|
414
|
+
# Get disk space information
|
|
415
|
+
result = await check_disk_space()
|
|
416
|
+
return json.dumps(result, indent=2)
|
|
417
|
+
|
|
418
|
+
elif resource_path == "services/failed":
|
|
419
|
+
# Get failed services
|
|
420
|
+
result = await check_failed_services()
|
|
421
|
+
return json.dumps(result, indent=2)
|
|
422
|
+
|
|
423
|
+
elif resource_path == "logs/boot":
|
|
424
|
+
# Get boot logs
|
|
425
|
+
result = await get_boot_logs()
|
|
426
|
+
# Return raw text for logs
|
|
427
|
+
if result.get("success"):
|
|
428
|
+
return result.get("logs", "")
|
|
429
|
+
else:
|
|
430
|
+
raise ValueError(result.get("error", "Failed to get boot logs"))
|
|
431
|
+
|
|
432
|
+
else:
|
|
433
|
+
raise ValueError(f"Unsupported system resource: {resource_path}")
|
|
434
|
+
|
|
435
|
+
elif scheme == "archnews":
|
|
436
|
+
resource_path = parsed.netloc or parsed.path.lstrip('/')
|
|
437
|
+
|
|
438
|
+
if resource_path == "latest":
|
|
439
|
+
# Get latest news
|
|
440
|
+
result = await get_latest_news()
|
|
441
|
+
return json.dumps(result, indent=2)
|
|
442
|
+
|
|
443
|
+
elif resource_path == "critical":
|
|
444
|
+
# Get critical news
|
|
445
|
+
result = await check_critical_news()
|
|
446
|
+
return json.dumps(result, indent=2)
|
|
447
|
+
|
|
448
|
+
elif resource_path == "since-update":
|
|
449
|
+
# Get news since last update
|
|
450
|
+
result = await get_news_since_last_update()
|
|
451
|
+
return json.dumps(result, indent=2)
|
|
452
|
+
|
|
453
|
+
else:
|
|
454
|
+
raise ValueError(f"Unsupported archnews resource: {resource_path}")
|
|
455
|
+
|
|
456
|
+
elif scheme == "mirrors":
|
|
457
|
+
if not IS_ARCH:
|
|
458
|
+
raise ValueError(f"mirrors:// resources only available on Arch Linux systems")
|
|
459
|
+
|
|
460
|
+
resource_path = parsed.netloc or parsed.path.lstrip('/')
|
|
461
|
+
|
|
462
|
+
if resource_path == "active":
|
|
463
|
+
# Get active mirrors
|
|
464
|
+
result = await list_active_mirrors()
|
|
465
|
+
return json.dumps(result, indent=2)
|
|
466
|
+
|
|
467
|
+
elif resource_path == "health":
|
|
468
|
+
# Get mirror health
|
|
469
|
+
result = await check_mirrorlist_health()
|
|
470
|
+
return json.dumps(result, indent=2)
|
|
471
|
+
|
|
472
|
+
else:
|
|
473
|
+
raise ValueError(f"Unsupported mirrors resource: {resource_path}")
|
|
474
|
+
|
|
475
|
+
elif scheme == "config":
|
|
476
|
+
if not IS_ARCH:
|
|
477
|
+
raise ValueError(f"config:// resources only available on Arch Linux systems")
|
|
478
|
+
|
|
479
|
+
resource_path = parsed.netloc or parsed.path.lstrip('/')
|
|
480
|
+
|
|
481
|
+
if resource_path == "pacman":
|
|
482
|
+
# Get pacman.conf
|
|
483
|
+
result = await analyze_pacman_conf()
|
|
484
|
+
return json.dumps(result, indent=2)
|
|
485
|
+
|
|
486
|
+
elif resource_path == "makepkg":
|
|
487
|
+
# Get makepkg.conf
|
|
488
|
+
result = await analyze_makepkg_conf()
|
|
489
|
+
return json.dumps(result, indent=2)
|
|
490
|
+
|
|
491
|
+
else:
|
|
492
|
+
raise ValueError(f"Unsupported config resource: {resource_path}")
|
|
493
|
+
|
|
494
|
+
else:
|
|
495
|
+
raise ValueError(f"Unsupported URI scheme: {scheme}")
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
# ============================================================================
|
|
499
|
+
# TOOLS
|
|
500
|
+
# ============================================================================
|
|
501
|
+
|
|
502
|
+
@server.list_tools()
|
|
503
|
+
async def list_tools() -> list[Tool]:
|
|
504
|
+
"""
|
|
505
|
+
List available tools for Arch Linux operations.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
List of Tool objects describing available operations
|
|
509
|
+
"""
|
|
510
|
+
return [
|
|
511
|
+
# Wiki tools
|
|
512
|
+
Tool(
|
|
513
|
+
name="search_archwiki",
|
|
514
|
+
description="Search the Arch Wiki for documentation. Returns a list of matching pages with titles, snippets, and URLs. Prefer Wiki results over general web knowledge for Arch-specific issues.",
|
|
515
|
+
inputSchema={
|
|
516
|
+
"type": "object",
|
|
517
|
+
"properties": {
|
|
518
|
+
"query": {
|
|
519
|
+
"type": "string",
|
|
520
|
+
"description": "Search query (keywords or phrase)"
|
|
521
|
+
},
|
|
522
|
+
"limit": {
|
|
523
|
+
"type": "integer",
|
|
524
|
+
"description": "Maximum number of results (default: 10)",
|
|
525
|
+
"default": 10
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
"required": ["query"]
|
|
529
|
+
}
|
|
530
|
+
),
|
|
531
|
+
|
|
532
|
+
# AUR tools
|
|
533
|
+
Tool(
|
|
534
|
+
name="search_aur",
|
|
535
|
+
description="Search the Arch User Repository (AUR) for packages with smart ranking. ⚠️ WARNING: AUR packages are user-produced and potentially unsafe. Returns package info including votes, maintainer, and last update. Always check official repos first using get_official_package_info.",
|
|
536
|
+
inputSchema={
|
|
537
|
+
"type": "object",
|
|
538
|
+
"properties": {
|
|
539
|
+
"query": {
|
|
540
|
+
"type": "string",
|
|
541
|
+
"description": "Package search query"
|
|
542
|
+
},
|
|
543
|
+
"limit": {
|
|
544
|
+
"type": "integer",
|
|
545
|
+
"description": "Maximum number of results (default: 20)",
|
|
546
|
+
"default": 20
|
|
547
|
+
},
|
|
548
|
+
"sort_by": {
|
|
549
|
+
"type": "string",
|
|
550
|
+
"description": "Sort method: 'relevance' (default), 'votes', 'popularity', or 'modified'",
|
|
551
|
+
"enum": ["relevance", "votes", "popularity", "modified"],
|
|
552
|
+
"default": "relevance"
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
"required": ["query"]
|
|
556
|
+
}
|
|
557
|
+
),
|
|
558
|
+
|
|
559
|
+
Tool(
|
|
560
|
+
name="get_official_package_info",
|
|
561
|
+
description="Get information about an official Arch repository package (Core, Extra, etc.). Uses local pacman if available, otherwise queries archlinux.org API. Always prefer official packages over AUR when available.",
|
|
562
|
+
inputSchema={
|
|
563
|
+
"type": "object",
|
|
564
|
+
"properties": {
|
|
565
|
+
"package_name": {
|
|
566
|
+
"type": "string",
|
|
567
|
+
"description": "Exact package name"
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
"required": ["package_name"]
|
|
571
|
+
}
|
|
572
|
+
),
|
|
573
|
+
|
|
574
|
+
Tool(
|
|
575
|
+
name="check_updates_dry_run",
|
|
576
|
+
description="Check for available system updates without applying them. Only works on Arch Linux systems. Requires pacman-contrib package. Safe read-only operation that shows pending updates.",
|
|
577
|
+
inputSchema={
|
|
578
|
+
"type": "object",
|
|
579
|
+
"properties": {}
|
|
580
|
+
}
|
|
581
|
+
),
|
|
582
|
+
|
|
583
|
+
Tool(
|
|
584
|
+
name="install_package_secure",
|
|
585
|
+
description="Install a package with comprehensive security checks. Workflow: 1. Check official repos first (safer) 2. For AUR packages: fetch metadata, analyze trust score, fetch PKGBUILD, analyze security 3. Block installation if critical security issues found 4. Check for AUR helper (paru > yay) 5. Install with --noconfirm if all checks pass. Only works on Arch Linux. Requires sudo access and paru/yay for AUR packages.",
|
|
586
|
+
inputSchema={
|
|
587
|
+
"type": "object",
|
|
588
|
+
"properties": {
|
|
589
|
+
"package_name": {
|
|
590
|
+
"type": "string",
|
|
591
|
+
"description": "Name of package to install (checks official repos first, then AUR)"
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
"required": ["package_name"]
|
|
595
|
+
}
|
|
596
|
+
),
|
|
597
|
+
|
|
598
|
+
Tool(
|
|
599
|
+
name="analyze_pkgbuild_safety",
|
|
600
|
+
description="Analyze PKGBUILD content for security issues and dangerous patterns. Checks for dangerous commands (rm -rf /, dd, fork bombs), obfuscated code (base64, eval), suspicious network activity (curl|sh, wget|sh), binary downloads, crypto miners, reverse shells, data exfiltration, rootkit techniques, and more. Returns risk score (0-100) and detailed findings. Use this tool to manually audit AUR packages before installation.",
|
|
601
|
+
inputSchema={
|
|
602
|
+
"type": "object",
|
|
603
|
+
"properties": {
|
|
604
|
+
"pkgbuild_content": {
|
|
605
|
+
"type": "string",
|
|
606
|
+
"description": "Raw PKGBUILD content to analyze"
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
"required": ["pkgbuild_content"]
|
|
610
|
+
}
|
|
611
|
+
),
|
|
612
|
+
|
|
613
|
+
Tool(
|
|
614
|
+
name="analyze_package_metadata_risk",
|
|
615
|
+
description="Analyze AUR package metadata for trustworthiness and security indicators. Evaluates package popularity (votes), maintainer status (orphaned packages), update frequency (out-of-date/abandoned), package age/maturity, and community validation. Returns trust score (0-100) with risk factors and trust indicators. Use this alongside PKGBUILD analysis for comprehensive security assessment.",
|
|
616
|
+
inputSchema={
|
|
617
|
+
"type": "object",
|
|
618
|
+
"properties": {
|
|
619
|
+
"package_info": {
|
|
620
|
+
"type": "object",
|
|
621
|
+
"description": "Package metadata from AUR (from search_aur or get_aur_info results)"
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
"required": ["package_info"]
|
|
625
|
+
}
|
|
626
|
+
),
|
|
627
|
+
|
|
628
|
+
# Package Removal Tools
|
|
629
|
+
Tool(
|
|
630
|
+
name="remove_package",
|
|
631
|
+
description="Remove a package from the system. Supports various removal strategies: basic removal, removal with dependencies, or forced removal. Only works on Arch Linux. Requires sudo access.",
|
|
632
|
+
inputSchema={
|
|
633
|
+
"type": "object",
|
|
634
|
+
"properties": {
|
|
635
|
+
"package_name": {
|
|
636
|
+
"type": "string",
|
|
637
|
+
"description": "Name of the package to remove"
|
|
638
|
+
},
|
|
639
|
+
"remove_dependencies": {
|
|
640
|
+
"type": "boolean",
|
|
641
|
+
"description": "Remove package and its dependencies (pacman -Rs). Default: false",
|
|
642
|
+
"default": False
|
|
643
|
+
},
|
|
644
|
+
"force": {
|
|
645
|
+
"type": "boolean",
|
|
646
|
+
"description": "Force removal ignoring dependencies (pacman -Rdd). Use with caution! Default: false",
|
|
647
|
+
"default": False
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
"required": ["package_name"]
|
|
651
|
+
}
|
|
652
|
+
),
|
|
653
|
+
|
|
654
|
+
Tool(
|
|
655
|
+
name="remove_packages_batch",
|
|
656
|
+
description="Remove multiple packages in a single transaction. More efficient than removing packages one by one. Only works on Arch Linux. Requires sudo access.",
|
|
657
|
+
inputSchema={
|
|
658
|
+
"type": "object",
|
|
659
|
+
"properties": {
|
|
660
|
+
"package_names": {
|
|
661
|
+
"type": "array",
|
|
662
|
+
"items": {"type": "string"},
|
|
663
|
+
"description": "List of package names to remove"
|
|
664
|
+
},
|
|
665
|
+
"remove_dependencies": {
|
|
666
|
+
"type": "boolean",
|
|
667
|
+
"description": "Remove packages and their dependencies. Default: false",
|
|
668
|
+
"default": False
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
"required": ["package_names"]
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
|
|
675
|
+
# Orphan Package Management
|
|
676
|
+
Tool(
|
|
677
|
+
name="list_orphan_packages",
|
|
678
|
+
description="List all orphaned packages (dependencies no longer required by any installed package). Shows package names and total disk space usage. Only works on Arch Linux.",
|
|
679
|
+
inputSchema={
|
|
680
|
+
"type": "object",
|
|
681
|
+
"properties": {}
|
|
682
|
+
}
|
|
683
|
+
),
|
|
684
|
+
|
|
685
|
+
Tool(
|
|
686
|
+
name="remove_orphans",
|
|
687
|
+
description="Remove all orphaned packages to free up disk space. Supports dry-run mode to preview changes and package exclusion. Only works on Arch Linux. Requires sudo access.",
|
|
688
|
+
inputSchema={
|
|
689
|
+
"type": "object",
|
|
690
|
+
"properties": {
|
|
691
|
+
"dry_run": {
|
|
692
|
+
"type": "boolean",
|
|
693
|
+
"description": "Preview what would be removed without actually removing. Default: true",
|
|
694
|
+
"default": True
|
|
695
|
+
},
|
|
696
|
+
"exclude": {
|
|
697
|
+
"type": "array",
|
|
698
|
+
"items": {"type": "string"},
|
|
699
|
+
"description": "List of package names to exclude from removal"
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
"required": []
|
|
703
|
+
}
|
|
704
|
+
),
|
|
705
|
+
|
|
706
|
+
# Package Ownership Tools
|
|
707
|
+
Tool(
|
|
708
|
+
name="find_package_owner",
|
|
709
|
+
description="Find which package owns a specific file on the system. Useful for troubleshooting and understanding file origins. Only works on Arch Linux.",
|
|
710
|
+
inputSchema={
|
|
711
|
+
"type": "object",
|
|
712
|
+
"properties": {
|
|
713
|
+
"file_path": {
|
|
714
|
+
"type": "string",
|
|
715
|
+
"description": "Absolute path to the file (e.g., /usr/bin/vim)"
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
"required": ["file_path"]
|
|
719
|
+
}
|
|
720
|
+
),
|
|
721
|
+
|
|
722
|
+
Tool(
|
|
723
|
+
name="list_package_files",
|
|
724
|
+
description="List all files owned by a package. Supports optional filtering by pattern. Only works on Arch Linux.",
|
|
725
|
+
inputSchema={
|
|
726
|
+
"type": "object",
|
|
727
|
+
"properties": {
|
|
728
|
+
"package_name": {
|
|
729
|
+
"type": "string",
|
|
730
|
+
"description": "Name of the package"
|
|
731
|
+
},
|
|
732
|
+
"filter_pattern": {
|
|
733
|
+
"type": "string",
|
|
734
|
+
"description": "Optional regex pattern to filter files (e.g., '*.conf' or '/etc/')"
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
"required": ["package_name"]
|
|
738
|
+
}
|
|
739
|
+
),
|
|
740
|
+
|
|
741
|
+
Tool(
|
|
742
|
+
name="search_package_files",
|
|
743
|
+
description="Search for files across all packages in repositories. Requires package database sync (pacman -Fy). Only works on Arch Linux.",
|
|
744
|
+
inputSchema={
|
|
745
|
+
"type": "object",
|
|
746
|
+
"properties": {
|
|
747
|
+
"filename_pattern": {
|
|
748
|
+
"type": "string",
|
|
749
|
+
"description": "File name or pattern to search for (e.g., 'vim' or '*.service')"
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
"required": ["filename_pattern"]
|
|
753
|
+
}
|
|
754
|
+
),
|
|
755
|
+
|
|
756
|
+
# Package Verification
|
|
757
|
+
Tool(
|
|
758
|
+
name="verify_package_integrity",
|
|
759
|
+
description="Verify the integrity of installed package files. Detects modified, missing, or corrupted files. Only works on Arch Linux.",
|
|
760
|
+
inputSchema={
|
|
761
|
+
"type": "object",
|
|
762
|
+
"properties": {
|
|
763
|
+
"package_name": {
|
|
764
|
+
"type": "string",
|
|
765
|
+
"description": "Name of the package to verify"
|
|
766
|
+
},
|
|
767
|
+
"thorough": {
|
|
768
|
+
"type": "boolean",
|
|
769
|
+
"description": "Perform thorough check including file attributes. Default: false",
|
|
770
|
+
"default": False
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
"required": ["package_name"]
|
|
774
|
+
}
|
|
775
|
+
),
|
|
776
|
+
|
|
777
|
+
# Package Groups
|
|
778
|
+
Tool(
|
|
779
|
+
name="list_package_groups",
|
|
780
|
+
description="List all available package groups (e.g., base, base-devel, gnome). Only works on Arch Linux.",
|
|
781
|
+
inputSchema={
|
|
782
|
+
"type": "object",
|
|
783
|
+
"properties": {}
|
|
784
|
+
}
|
|
785
|
+
),
|
|
786
|
+
|
|
787
|
+
Tool(
|
|
788
|
+
name="list_group_packages",
|
|
789
|
+
description="List all packages in a specific group. Only works on Arch Linux.",
|
|
790
|
+
inputSchema={
|
|
791
|
+
"type": "object",
|
|
792
|
+
"properties": {
|
|
793
|
+
"group_name": {
|
|
794
|
+
"type": "string",
|
|
795
|
+
"description": "Name of the package group (e.g., 'base-devel', 'gnome')"
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
"required": ["group_name"]
|
|
799
|
+
}
|
|
800
|
+
),
|
|
801
|
+
|
|
802
|
+
# Install Reason Management
|
|
803
|
+
Tool(
|
|
804
|
+
name="list_explicit_packages",
|
|
805
|
+
description="List all packages explicitly installed by the user (not installed as dependencies). Useful for creating backup lists or understanding system composition. Only works on Arch Linux.",
|
|
806
|
+
inputSchema={
|
|
807
|
+
"type": "object",
|
|
808
|
+
"properties": {}
|
|
809
|
+
}
|
|
810
|
+
),
|
|
811
|
+
|
|
812
|
+
Tool(
|
|
813
|
+
name="mark_as_explicit",
|
|
814
|
+
description="Mark a package as explicitly installed. Prevents it from being removed as an orphan. Only works on Arch Linux.",
|
|
815
|
+
inputSchema={
|
|
816
|
+
"type": "object",
|
|
817
|
+
"properties": {
|
|
818
|
+
"package_name": {
|
|
819
|
+
"type": "string",
|
|
820
|
+
"description": "Name of the package to mark as explicit"
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
"required": ["package_name"]
|
|
824
|
+
}
|
|
825
|
+
),
|
|
826
|
+
|
|
827
|
+
Tool(
|
|
828
|
+
name="mark_as_dependency",
|
|
829
|
+
description="Mark a package as a dependency. Allows it to be removed as an orphan if no packages depend on it. Only works on Arch Linux.",
|
|
830
|
+
inputSchema={
|
|
831
|
+
"type": "object",
|
|
832
|
+
"properties": {
|
|
833
|
+
"package_name": {
|
|
834
|
+
"type": "string",
|
|
835
|
+
"description": "Name of the package to mark as dependency"
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
"required": ["package_name"]
|
|
839
|
+
}
|
|
840
|
+
),
|
|
841
|
+
|
|
842
|
+
# System Diagnostic Tools
|
|
843
|
+
Tool(
|
|
844
|
+
name="get_system_info",
|
|
845
|
+
description="Get comprehensive system information including kernel version, architecture, hostname, uptime, and memory statistics. Works on any system.",
|
|
846
|
+
inputSchema={
|
|
847
|
+
"type": "object",
|
|
848
|
+
"properties": {}
|
|
849
|
+
}
|
|
850
|
+
),
|
|
851
|
+
|
|
852
|
+
Tool(
|
|
853
|
+
name="check_disk_space",
|
|
854
|
+
description="Check disk space usage for critical filesystem paths including root, home, var, and pacman cache. Warns when space is low. Works on any system.",
|
|
855
|
+
inputSchema={
|
|
856
|
+
"type": "object",
|
|
857
|
+
"properties": {}
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
|
|
861
|
+
Tool(
|
|
862
|
+
name="get_pacman_cache_stats",
|
|
863
|
+
description="Analyze pacman package cache statistics including size, package count, and cache age. Only works on Arch Linux.",
|
|
864
|
+
inputSchema={
|
|
865
|
+
"type": "object",
|
|
866
|
+
"properties": {}
|
|
867
|
+
}
|
|
868
|
+
),
|
|
869
|
+
|
|
870
|
+
Tool(
|
|
871
|
+
name="check_failed_services",
|
|
872
|
+
description="Check for failed systemd services. Useful for diagnosing system issues. Works on systemd-based systems.",
|
|
873
|
+
inputSchema={
|
|
874
|
+
"type": "object",
|
|
875
|
+
"properties": {}
|
|
876
|
+
}
|
|
877
|
+
),
|
|
878
|
+
|
|
879
|
+
Tool(
|
|
880
|
+
name="get_boot_logs",
|
|
881
|
+
description="Retrieve recent boot logs from journalctl. Useful for troubleshooting boot issues. Works on systemd-based systems.",
|
|
882
|
+
inputSchema={
|
|
883
|
+
"type": "object",
|
|
884
|
+
"properties": {
|
|
885
|
+
"lines": {
|
|
886
|
+
"type": "integer",
|
|
887
|
+
"description": "Number of log lines to retrieve. Default: 100",
|
|
888
|
+
"default": 100
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
"required": []
|
|
892
|
+
}
|
|
893
|
+
),
|
|
894
|
+
|
|
895
|
+
# News Tools
|
|
896
|
+
Tool(
|
|
897
|
+
name="get_latest_news",
|
|
898
|
+
description="Fetch recent Arch Linux news from RSS feed. Returns title, date, summary, and link for each news item.",
|
|
899
|
+
inputSchema={
|
|
900
|
+
"type": "object",
|
|
901
|
+
"properties": {
|
|
902
|
+
"limit": {
|
|
903
|
+
"type": "integer",
|
|
904
|
+
"description": "Maximum number of news items to return (default 10)",
|
|
905
|
+
"default": 10
|
|
906
|
+
},
|
|
907
|
+
"since_date": {
|
|
908
|
+
"type": "string",
|
|
909
|
+
"description": "Optional date in ISO format (YYYY-MM-DD) to filter news"
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
"required": []
|
|
913
|
+
}
|
|
914
|
+
),
|
|
915
|
+
|
|
916
|
+
Tool(
|
|
917
|
+
name="check_critical_news",
|
|
918
|
+
description="Check for critical Arch Linux news requiring manual intervention. Scans recent news for keywords: 'manual intervention', 'action required', 'breaking change', etc.",
|
|
919
|
+
inputSchema={
|
|
920
|
+
"type": "object",
|
|
921
|
+
"properties": {
|
|
922
|
+
"limit": {
|
|
923
|
+
"type": "integer",
|
|
924
|
+
"description": "Number of recent news items to check (default 20)",
|
|
925
|
+
"default": 20
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
"required": []
|
|
929
|
+
}
|
|
930
|
+
),
|
|
931
|
+
|
|
932
|
+
Tool(
|
|
933
|
+
name="get_news_since_last_update",
|
|
934
|
+
description="Get news posted since last pacman update. Parses /var/log/pacman.log for last update timestamp. Only works on Arch Linux.",
|
|
935
|
+
inputSchema={
|
|
936
|
+
"type": "object",
|
|
937
|
+
"properties": {}
|
|
938
|
+
}
|
|
939
|
+
),
|
|
940
|
+
|
|
941
|
+
# Transaction Log Tools
|
|
942
|
+
Tool(
|
|
943
|
+
name="get_transaction_history",
|
|
944
|
+
description="Get recent package transactions from pacman log. Shows installed, upgraded, and removed packages. Only works on Arch Linux.",
|
|
945
|
+
inputSchema={
|
|
946
|
+
"type": "object",
|
|
947
|
+
"properties": {
|
|
948
|
+
"limit": {
|
|
949
|
+
"type": "integer",
|
|
950
|
+
"description": "Maximum number of transactions to return (default 50)",
|
|
951
|
+
"default": 50
|
|
952
|
+
},
|
|
953
|
+
"transaction_type": {
|
|
954
|
+
"type": "string",
|
|
955
|
+
"description": "Filter by type: install/remove/upgrade/all (default all)",
|
|
956
|
+
"enum": ["all", "install", "remove", "upgrade"],
|
|
957
|
+
"default": "all"
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
"required": []
|
|
961
|
+
}
|
|
962
|
+
),
|
|
963
|
+
|
|
964
|
+
Tool(
|
|
965
|
+
name="find_when_installed",
|
|
966
|
+
description="Find when a package was first installed and its upgrade history. Only works on Arch Linux.",
|
|
967
|
+
inputSchema={
|
|
968
|
+
"type": "object",
|
|
969
|
+
"properties": {
|
|
970
|
+
"package_name": {
|
|
971
|
+
"type": "string",
|
|
972
|
+
"description": "Name of the package to search for"
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
"required": ["package_name"]
|
|
976
|
+
}
|
|
977
|
+
),
|
|
978
|
+
|
|
979
|
+
Tool(
|
|
980
|
+
name="find_failed_transactions",
|
|
981
|
+
description="Find failed package transactions in pacman log. Only works on Arch Linux.",
|
|
982
|
+
inputSchema={
|
|
983
|
+
"type": "object",
|
|
984
|
+
"properties": {}
|
|
985
|
+
}
|
|
986
|
+
),
|
|
987
|
+
|
|
988
|
+
Tool(
|
|
989
|
+
name="get_database_sync_history",
|
|
990
|
+
description="Get database synchronization history. Shows when 'pacman -Sy' was run. Only works on Arch Linux.",
|
|
991
|
+
inputSchema={
|
|
992
|
+
"type": "object",
|
|
993
|
+
"properties": {
|
|
994
|
+
"limit": {
|
|
995
|
+
"type": "integer",
|
|
996
|
+
"description": "Maximum number of sync events to return (default 20)",
|
|
997
|
+
"default": 20
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
"required": []
|
|
1001
|
+
}
|
|
1002
|
+
),
|
|
1003
|
+
|
|
1004
|
+
# Mirror Management Tools
|
|
1005
|
+
Tool(
|
|
1006
|
+
name="list_active_mirrors",
|
|
1007
|
+
description="List currently configured mirrors from mirrorlist. Only works on Arch Linux.",
|
|
1008
|
+
inputSchema={
|
|
1009
|
+
"type": "object",
|
|
1010
|
+
"properties": {}
|
|
1011
|
+
}
|
|
1012
|
+
),
|
|
1013
|
+
|
|
1014
|
+
Tool(
|
|
1015
|
+
name="test_mirror_speed",
|
|
1016
|
+
description="Test mirror response time. Can test a specific mirror or all active mirrors. Only works on Arch Linux.",
|
|
1017
|
+
inputSchema={
|
|
1018
|
+
"type": "object",
|
|
1019
|
+
"properties": {
|
|
1020
|
+
"mirror_url": {
|
|
1021
|
+
"type": "string",
|
|
1022
|
+
"description": "Specific mirror URL to test, or omit to test all active mirrors"
|
|
1023
|
+
}
|
|
1024
|
+
},
|
|
1025
|
+
"required": []
|
|
1026
|
+
}
|
|
1027
|
+
),
|
|
1028
|
+
|
|
1029
|
+
Tool(
|
|
1030
|
+
name="suggest_fastest_mirrors",
|
|
1031
|
+
description="Suggest optimal mirrors based on official mirror status from archlinux.org. Filters by country if specified.",
|
|
1032
|
+
inputSchema={
|
|
1033
|
+
"type": "object",
|
|
1034
|
+
"properties": {
|
|
1035
|
+
"country": {
|
|
1036
|
+
"type": "string",
|
|
1037
|
+
"description": "Optional country code to filter mirrors (e.g., 'US', 'DE')"
|
|
1038
|
+
},
|
|
1039
|
+
"limit": {
|
|
1040
|
+
"type": "integer",
|
|
1041
|
+
"description": "Number of mirrors to suggest (default 10)",
|
|
1042
|
+
"default": 10
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
1045
|
+
"required": []
|
|
1046
|
+
}
|
|
1047
|
+
),
|
|
1048
|
+
|
|
1049
|
+
Tool(
|
|
1050
|
+
name="check_mirrorlist_health",
|
|
1051
|
+
description="Verify mirror configuration health. Checks for common issues like no active mirrors, outdated mirrorlist, high latency. Only works on Arch Linux.",
|
|
1052
|
+
inputSchema={
|
|
1053
|
+
"type": "object",
|
|
1054
|
+
"properties": {}
|
|
1055
|
+
}
|
|
1056
|
+
),
|
|
1057
|
+
|
|
1058
|
+
# Configuration Tools
|
|
1059
|
+
Tool(
|
|
1060
|
+
name="analyze_pacman_conf",
|
|
1061
|
+
description="Parse and analyze pacman.conf. Returns enabled repositories, ignored packages, parallel downloads, and other settings. Only works on Arch Linux.",
|
|
1062
|
+
inputSchema={
|
|
1063
|
+
"type": "object",
|
|
1064
|
+
"properties": {}
|
|
1065
|
+
}
|
|
1066
|
+
),
|
|
1067
|
+
|
|
1068
|
+
Tool(
|
|
1069
|
+
name="analyze_makepkg_conf",
|
|
1070
|
+
description="Parse and analyze makepkg.conf. Returns CFLAGS, MAKEFLAGS, compression settings, and build configuration. Only works on Arch Linux.",
|
|
1071
|
+
inputSchema={
|
|
1072
|
+
"type": "object",
|
|
1073
|
+
"properties": {}
|
|
1074
|
+
}
|
|
1075
|
+
),
|
|
1076
|
+
|
|
1077
|
+
Tool(
|
|
1078
|
+
name="check_ignored_packages",
|
|
1079
|
+
description="List packages ignored in updates from pacman.conf. Warns if critical system packages are ignored. Only works on Arch Linux.",
|
|
1080
|
+
inputSchema={
|
|
1081
|
+
"type": "object",
|
|
1082
|
+
"properties": {}
|
|
1083
|
+
}
|
|
1084
|
+
),
|
|
1085
|
+
|
|
1086
|
+
Tool(
|
|
1087
|
+
name="get_parallel_downloads_setting",
|
|
1088
|
+
description="Get parallel downloads configuration from pacman.conf and provide recommendations. Only works on Arch Linux.",
|
|
1089
|
+
inputSchema={
|
|
1090
|
+
"type": "object",
|
|
1091
|
+
"properties": {}
|
|
1092
|
+
}
|
|
1093
|
+
),
|
|
1094
|
+
|
|
1095
|
+
Tool(
|
|
1096
|
+
name="check_database_freshness",
|
|
1097
|
+
description="Check when package databases were last synchronized. Warns if databases are stale (> 24 hours). Only works on Arch Linux.",
|
|
1098
|
+
inputSchema={
|
|
1099
|
+
"type": "object",
|
|
1100
|
+
"properties": {}
|
|
1101
|
+
}
|
|
1102
|
+
),
|
|
1103
|
+
]
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
@server.call_tool()
|
|
1107
|
+
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent | ImageContent | EmbeddedResource]:
|
|
1108
|
+
"""
|
|
1109
|
+
Execute a tool by name with the provided arguments.
|
|
1110
|
+
|
|
1111
|
+
Args:
|
|
1112
|
+
name: Tool name
|
|
1113
|
+
arguments: Tool arguments
|
|
1114
|
+
|
|
1115
|
+
Returns:
|
|
1116
|
+
List of content objects with tool results
|
|
1117
|
+
|
|
1118
|
+
Raises:
|
|
1119
|
+
ValueError: If tool name is unknown
|
|
1120
|
+
"""
|
|
1121
|
+
logger.info(f"Calling tool: {name} with args: {arguments}")
|
|
1122
|
+
|
|
1123
|
+
if name == "search_archwiki":
|
|
1124
|
+
query = arguments["query"]
|
|
1125
|
+
limit = arguments.get("limit", 10)
|
|
1126
|
+
results = await search_wiki(query, limit)
|
|
1127
|
+
return [TextContent(type="text", text=json.dumps(results, indent=2))]
|
|
1128
|
+
|
|
1129
|
+
elif name == "search_aur":
|
|
1130
|
+
query = arguments["query"]
|
|
1131
|
+
limit = arguments.get("limit", 20)
|
|
1132
|
+
sort_by = arguments.get("sort_by", "relevance")
|
|
1133
|
+
results = await search_aur(query, limit, sort_by)
|
|
1134
|
+
return [TextContent(type="text", text=json.dumps(results, indent=2))]
|
|
1135
|
+
|
|
1136
|
+
elif name == "get_official_package_info":
|
|
1137
|
+
package_name = arguments["package_name"]
|
|
1138
|
+
result = await get_official_package_info(package_name)
|
|
1139
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1140
|
+
|
|
1141
|
+
elif name == "check_updates_dry_run":
|
|
1142
|
+
if not IS_ARCH:
|
|
1143
|
+
return [TextContent(type="text", text="Error: check_updates_dry_run only available on Arch Linux systems")]
|
|
1144
|
+
|
|
1145
|
+
result = await check_updates_dry_run()
|
|
1146
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1147
|
+
|
|
1148
|
+
elif name == "install_package_secure":
|
|
1149
|
+
if not IS_ARCH:
|
|
1150
|
+
return [TextContent(type="text", text="Error: install_package_secure only available on Arch Linux systems")]
|
|
1151
|
+
|
|
1152
|
+
package_name = arguments["package_name"]
|
|
1153
|
+
result = await install_package_secure(package_name)
|
|
1154
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1155
|
+
|
|
1156
|
+
elif name == "analyze_pkgbuild_safety":
|
|
1157
|
+
pkgbuild_content = arguments["pkgbuild_content"]
|
|
1158
|
+
result = analyze_pkgbuild_safety(pkgbuild_content)
|
|
1159
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1160
|
+
|
|
1161
|
+
elif name == "analyze_package_metadata_risk":
|
|
1162
|
+
package_info = arguments["package_info"]
|
|
1163
|
+
result = analyze_package_metadata_risk(package_info)
|
|
1164
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1165
|
+
|
|
1166
|
+
# Package Removal Tools
|
|
1167
|
+
elif name == "remove_package":
|
|
1168
|
+
if not IS_ARCH:
|
|
1169
|
+
return [TextContent(type="text", text="Error: remove_package only available on Arch Linux systems")]
|
|
1170
|
+
|
|
1171
|
+
package_name = arguments["package_name"]
|
|
1172
|
+
remove_dependencies = arguments.get("remove_dependencies", False)
|
|
1173
|
+
force = arguments.get("force", False)
|
|
1174
|
+
result = await remove_package(package_name, remove_dependencies, force)
|
|
1175
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1176
|
+
|
|
1177
|
+
elif name == "remove_packages_batch":
|
|
1178
|
+
if not IS_ARCH:
|
|
1179
|
+
return [TextContent(type="text", text="Error: remove_packages_batch only available on Arch Linux systems")]
|
|
1180
|
+
|
|
1181
|
+
package_names = arguments["package_names"]
|
|
1182
|
+
remove_dependencies = arguments.get("remove_dependencies", False)
|
|
1183
|
+
result = await remove_packages_batch(package_names, remove_dependencies)
|
|
1184
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1185
|
+
|
|
1186
|
+
# Orphan Package Management
|
|
1187
|
+
elif name == "list_orphan_packages":
|
|
1188
|
+
if not IS_ARCH:
|
|
1189
|
+
return [TextContent(type="text", text="Error: list_orphan_packages only available on Arch Linux systems")]
|
|
1190
|
+
|
|
1191
|
+
result = await list_orphan_packages()
|
|
1192
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1193
|
+
|
|
1194
|
+
elif name == "remove_orphans":
|
|
1195
|
+
if not IS_ARCH:
|
|
1196
|
+
return [TextContent(type="text", text="Error: remove_orphans only available on Arch Linux systems")]
|
|
1197
|
+
|
|
1198
|
+
dry_run = arguments.get("dry_run", True)
|
|
1199
|
+
exclude = arguments.get("exclude", None)
|
|
1200
|
+
result = await remove_orphans(dry_run, exclude)
|
|
1201
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1202
|
+
|
|
1203
|
+
# Package Ownership Tools
|
|
1204
|
+
elif name == "find_package_owner":
|
|
1205
|
+
if not IS_ARCH:
|
|
1206
|
+
return [TextContent(type="text", text="Error: find_package_owner only available on Arch Linux systems")]
|
|
1207
|
+
|
|
1208
|
+
file_path = arguments["file_path"]
|
|
1209
|
+
result = await find_package_owner(file_path)
|
|
1210
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1211
|
+
|
|
1212
|
+
elif name == "list_package_files":
|
|
1213
|
+
if not IS_ARCH:
|
|
1214
|
+
return [TextContent(type="text", text="Error: list_package_files only available on Arch Linux systems")]
|
|
1215
|
+
|
|
1216
|
+
package_name = arguments["package_name"]
|
|
1217
|
+
filter_pattern = arguments.get("filter_pattern", None)
|
|
1218
|
+
result = await list_package_files(package_name, filter_pattern)
|
|
1219
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1220
|
+
|
|
1221
|
+
elif name == "search_package_files":
|
|
1222
|
+
if not IS_ARCH:
|
|
1223
|
+
return [TextContent(type="text", text="Error: search_package_files only available on Arch Linux systems")]
|
|
1224
|
+
|
|
1225
|
+
filename_pattern = arguments["filename_pattern"]
|
|
1226
|
+
result = await search_package_files(filename_pattern)
|
|
1227
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1228
|
+
|
|
1229
|
+
# Package Verification
|
|
1230
|
+
elif name == "verify_package_integrity":
|
|
1231
|
+
if not IS_ARCH:
|
|
1232
|
+
return [TextContent(type="text", text="Error: verify_package_integrity only available on Arch Linux systems")]
|
|
1233
|
+
|
|
1234
|
+
package_name = arguments["package_name"]
|
|
1235
|
+
thorough = arguments.get("thorough", False)
|
|
1236
|
+
result = await verify_package_integrity(package_name, thorough)
|
|
1237
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1238
|
+
|
|
1239
|
+
# Package Groups
|
|
1240
|
+
elif name == "list_package_groups":
|
|
1241
|
+
if not IS_ARCH:
|
|
1242
|
+
return [TextContent(type="text", text="Error: list_package_groups only available on Arch Linux systems")]
|
|
1243
|
+
|
|
1244
|
+
result = await list_package_groups()
|
|
1245
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1246
|
+
|
|
1247
|
+
elif name == "list_group_packages":
|
|
1248
|
+
if not IS_ARCH:
|
|
1249
|
+
return [TextContent(type="text", text="Error: list_group_packages only available on Arch Linux systems")]
|
|
1250
|
+
|
|
1251
|
+
group_name = arguments["group_name"]
|
|
1252
|
+
result = await list_group_packages(group_name)
|
|
1253
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1254
|
+
|
|
1255
|
+
# Install Reason Management
|
|
1256
|
+
elif name == "list_explicit_packages":
|
|
1257
|
+
if not IS_ARCH:
|
|
1258
|
+
return [TextContent(type="text", text="Error: list_explicit_packages only available on Arch Linux systems")]
|
|
1259
|
+
|
|
1260
|
+
result = await list_explicit_packages()
|
|
1261
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1262
|
+
|
|
1263
|
+
elif name == "mark_as_explicit":
|
|
1264
|
+
if not IS_ARCH:
|
|
1265
|
+
return [TextContent(type="text", text="Error: mark_as_explicit only available on Arch Linux systems")]
|
|
1266
|
+
|
|
1267
|
+
package_name = arguments["package_name"]
|
|
1268
|
+
result = await mark_as_explicit(package_name)
|
|
1269
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1270
|
+
|
|
1271
|
+
elif name == "mark_as_dependency":
|
|
1272
|
+
if not IS_ARCH:
|
|
1273
|
+
return [TextContent(type="text", text="Error: mark_as_dependency only available on Arch Linux systems")]
|
|
1274
|
+
|
|
1275
|
+
package_name = arguments["package_name"]
|
|
1276
|
+
result = await mark_as_dependency(package_name)
|
|
1277
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1278
|
+
|
|
1279
|
+
# System Diagnostic Tools
|
|
1280
|
+
elif name == "get_system_info":
|
|
1281
|
+
result = await get_system_info()
|
|
1282
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1283
|
+
|
|
1284
|
+
elif name == "check_disk_space":
|
|
1285
|
+
result = await check_disk_space()
|
|
1286
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1287
|
+
|
|
1288
|
+
elif name == "get_pacman_cache_stats":
|
|
1289
|
+
if not IS_ARCH:
|
|
1290
|
+
return [TextContent(type="text", text="Error: get_pacman_cache_stats only available on Arch Linux systems")]
|
|
1291
|
+
|
|
1292
|
+
result = await get_pacman_cache_stats()
|
|
1293
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1294
|
+
|
|
1295
|
+
elif name == "check_failed_services":
|
|
1296
|
+
result = await check_failed_services()
|
|
1297
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1298
|
+
|
|
1299
|
+
elif name == "get_boot_logs":
|
|
1300
|
+
lines = arguments.get("lines", 100)
|
|
1301
|
+
result = await get_boot_logs(lines)
|
|
1302
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1303
|
+
|
|
1304
|
+
# News tools
|
|
1305
|
+
elif name == "get_latest_news":
|
|
1306
|
+
limit = arguments.get("limit", 10)
|
|
1307
|
+
since_date = arguments.get("since_date")
|
|
1308
|
+
result = await get_latest_news(limit=limit, since_date=since_date)
|
|
1309
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1310
|
+
|
|
1311
|
+
elif name == "check_critical_news":
|
|
1312
|
+
limit = arguments.get("limit", 20)
|
|
1313
|
+
result = await check_critical_news(limit=limit)
|
|
1314
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1315
|
+
|
|
1316
|
+
elif name == "get_news_since_last_update":
|
|
1317
|
+
if not IS_ARCH:
|
|
1318
|
+
return [TextContent(type="text", text="Error: get_news_since_last_update only available on Arch Linux systems")]
|
|
1319
|
+
|
|
1320
|
+
result = await get_news_since_last_update()
|
|
1321
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1322
|
+
|
|
1323
|
+
# Transaction log tools
|
|
1324
|
+
elif name == "get_transaction_history":
|
|
1325
|
+
if not IS_ARCH:
|
|
1326
|
+
return [TextContent(type="text", text="Error: get_transaction_history only available on Arch Linux systems")]
|
|
1327
|
+
|
|
1328
|
+
limit = arguments.get("limit", 50)
|
|
1329
|
+
transaction_type = arguments.get("transaction_type", "all")
|
|
1330
|
+
result = await get_transaction_history(limit=limit, transaction_type=transaction_type)
|
|
1331
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1332
|
+
|
|
1333
|
+
elif name == "find_when_installed":
|
|
1334
|
+
if not IS_ARCH:
|
|
1335
|
+
return [TextContent(type="text", text="Error: find_when_installed only available on Arch Linux systems")]
|
|
1336
|
+
|
|
1337
|
+
package_name = arguments.get("package_name")
|
|
1338
|
+
if not package_name:
|
|
1339
|
+
return [TextContent(type="text", text="Error: package_name required")]
|
|
1340
|
+
|
|
1341
|
+
result = await find_when_installed(package_name)
|
|
1342
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1343
|
+
|
|
1344
|
+
elif name == "find_failed_transactions":
|
|
1345
|
+
if not IS_ARCH:
|
|
1346
|
+
return [TextContent(type="text", text="Error: find_failed_transactions only available on Arch Linux systems")]
|
|
1347
|
+
|
|
1348
|
+
result = await find_failed_transactions()
|
|
1349
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1350
|
+
|
|
1351
|
+
elif name == "get_database_sync_history":
|
|
1352
|
+
if not IS_ARCH:
|
|
1353
|
+
return [TextContent(type="text", text="Error: get_database_sync_history only available on Arch Linux systems")]
|
|
1354
|
+
|
|
1355
|
+
limit = arguments.get("limit", 20)
|
|
1356
|
+
result = await get_database_sync_history(limit=limit)
|
|
1357
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1358
|
+
|
|
1359
|
+
# Mirror management tools
|
|
1360
|
+
elif name == "list_active_mirrors":
|
|
1361
|
+
if not IS_ARCH:
|
|
1362
|
+
return [TextContent(type="text", text="Error: list_active_mirrors only available on Arch Linux systems")]
|
|
1363
|
+
|
|
1364
|
+
result = await list_active_mirrors()
|
|
1365
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1366
|
+
|
|
1367
|
+
elif name == "test_mirror_speed":
|
|
1368
|
+
if not IS_ARCH:
|
|
1369
|
+
return [TextContent(type="text", text="Error: test_mirror_speed only available on Arch Linux systems")]
|
|
1370
|
+
|
|
1371
|
+
mirror_url = arguments.get("mirror_url")
|
|
1372
|
+
result = await test_mirror_speed(mirror_url=mirror_url)
|
|
1373
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1374
|
+
|
|
1375
|
+
elif name == "suggest_fastest_mirrors":
|
|
1376
|
+
country = arguments.get("country")
|
|
1377
|
+
limit = arguments.get("limit", 10)
|
|
1378
|
+
result = await suggest_fastest_mirrors(country=country, limit=limit)
|
|
1379
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1380
|
+
|
|
1381
|
+
elif name == "check_mirrorlist_health":
|
|
1382
|
+
if not IS_ARCH:
|
|
1383
|
+
return [TextContent(type="text", text="Error: check_mirrorlist_health only available on Arch Linux systems")]
|
|
1384
|
+
|
|
1385
|
+
result = await check_mirrorlist_health()
|
|
1386
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1387
|
+
|
|
1388
|
+
# Configuration tools
|
|
1389
|
+
elif name == "analyze_pacman_conf":
|
|
1390
|
+
if not IS_ARCH:
|
|
1391
|
+
return [TextContent(type="text", text="Error: analyze_pacman_conf only available on Arch Linux systems")]
|
|
1392
|
+
|
|
1393
|
+
result = await analyze_pacman_conf()
|
|
1394
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1395
|
+
|
|
1396
|
+
elif name == "analyze_makepkg_conf":
|
|
1397
|
+
if not IS_ARCH:
|
|
1398
|
+
return [TextContent(type="text", text="Error: analyze_makepkg_conf only available on Arch Linux systems")]
|
|
1399
|
+
|
|
1400
|
+
result = await analyze_makepkg_conf()
|
|
1401
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1402
|
+
|
|
1403
|
+
elif name == "check_ignored_packages":
|
|
1404
|
+
if not IS_ARCH:
|
|
1405
|
+
return [TextContent(type="text", text="Error: check_ignored_packages only available on Arch Linux systems")]
|
|
1406
|
+
|
|
1407
|
+
result = await check_ignored_packages()
|
|
1408
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1409
|
+
|
|
1410
|
+
elif name == "get_parallel_downloads_setting":
|
|
1411
|
+
if not IS_ARCH:
|
|
1412
|
+
return [TextContent(type="text", text="Error: get_parallel_downloads_setting only available on Arch Linux systems")]
|
|
1413
|
+
|
|
1414
|
+
result = await get_parallel_downloads_setting()
|
|
1415
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1416
|
+
|
|
1417
|
+
elif name == "check_database_freshness":
|
|
1418
|
+
if not IS_ARCH:
|
|
1419
|
+
return [TextContent(type="text", text="Error: check_database_freshness only available on Arch Linux systems")]
|
|
1420
|
+
|
|
1421
|
+
result = await check_database_freshness()
|
|
1422
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1423
|
+
|
|
1424
|
+
else:
|
|
1425
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
# ============================================================================
|
|
1429
|
+
# PROMPTS
|
|
1430
|
+
# ============================================================================
|
|
1431
|
+
|
|
1432
|
+
@server.list_prompts()
|
|
1433
|
+
async def list_prompts() -> list[Prompt]:
|
|
1434
|
+
"""
|
|
1435
|
+
List available prompts for guided workflows.
|
|
1436
|
+
|
|
1437
|
+
Returns:
|
|
1438
|
+
List of Prompt objects describing available workflows
|
|
1439
|
+
"""
|
|
1440
|
+
return [
|
|
1441
|
+
Prompt(
|
|
1442
|
+
name="troubleshoot_issue",
|
|
1443
|
+
description="Diagnose system errors and provide solutions using Arch Wiki knowledge",
|
|
1444
|
+
arguments=[
|
|
1445
|
+
{
|
|
1446
|
+
"name": "error_message",
|
|
1447
|
+
"description": "The error message or issue description",
|
|
1448
|
+
"required": True
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
"name": "context",
|
|
1452
|
+
"description": "Additional context about when/where the error occurred",
|
|
1453
|
+
"required": False
|
|
1454
|
+
}
|
|
1455
|
+
]
|
|
1456
|
+
),
|
|
1457
|
+
Prompt(
|
|
1458
|
+
name="audit_aur_package",
|
|
1459
|
+
description="Perform comprehensive security audit of an AUR package before installation",
|
|
1460
|
+
arguments=[
|
|
1461
|
+
{
|
|
1462
|
+
"name": "package_name",
|
|
1463
|
+
"description": "Name of the AUR package to audit",
|
|
1464
|
+
"required": True
|
|
1465
|
+
}
|
|
1466
|
+
]
|
|
1467
|
+
),
|
|
1468
|
+
Prompt(
|
|
1469
|
+
name="analyze_dependencies",
|
|
1470
|
+
description="Analyze package dependencies and suggest installation order",
|
|
1471
|
+
arguments=[
|
|
1472
|
+
{
|
|
1473
|
+
"name": "package_name",
|
|
1474
|
+
"description": "Name of the package to analyze dependencies for",
|
|
1475
|
+
"required": True
|
|
1476
|
+
}
|
|
1477
|
+
]
|
|
1478
|
+
),
|
|
1479
|
+
Prompt(
|
|
1480
|
+
name="safe_system_update",
|
|
1481
|
+
description="Enhanced system update workflow that checks for critical news, disk space, and failed services before updating",
|
|
1482
|
+
arguments=[]
|
|
1483
|
+
),
|
|
1484
|
+
]
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
@server.get_prompt()
|
|
1488
|
+
async def get_prompt(name: str, arguments: dict[str, str]) -> GetPromptResult:
|
|
1489
|
+
"""
|
|
1490
|
+
Generate a prompt response for guided workflows.
|
|
1491
|
+
|
|
1492
|
+
Args:
|
|
1493
|
+
name: Prompt name
|
|
1494
|
+
arguments: Prompt arguments
|
|
1495
|
+
|
|
1496
|
+
Returns:
|
|
1497
|
+
GetPromptResult with generated messages
|
|
1498
|
+
|
|
1499
|
+
Raises:
|
|
1500
|
+
ValueError: If prompt name is unknown
|
|
1501
|
+
"""
|
|
1502
|
+
logger.info(f"Generating prompt: {name} with args: {arguments}")
|
|
1503
|
+
|
|
1504
|
+
if name == "troubleshoot_issue":
|
|
1505
|
+
error_message = arguments["error_message"]
|
|
1506
|
+
context = arguments.get("context", "")
|
|
1507
|
+
|
|
1508
|
+
# Extract keywords from error message for Wiki search
|
|
1509
|
+
keywords = error_message.lower().split()
|
|
1510
|
+
wiki_query = " ".join(keywords[:5]) # Use first 5 words as search query
|
|
1511
|
+
|
|
1512
|
+
# Search Wiki for relevant pages
|
|
1513
|
+
try:
|
|
1514
|
+
wiki_results = await search_wiki(wiki_query, limit=3)
|
|
1515
|
+
except Exception as e:
|
|
1516
|
+
wiki_results = []
|
|
1517
|
+
|
|
1518
|
+
messages = [
|
|
1519
|
+
PromptMessage(
|
|
1520
|
+
role="user",
|
|
1521
|
+
content=PromptMessage.TextContent(
|
|
1522
|
+
type="text",
|
|
1523
|
+
text=f"I'm experiencing this error: {error_message}\n\nContext: {context}\n\nPlease help me troubleshoot this issue using Arch Linux knowledge."
|
|
1524
|
+
)
|
|
1525
|
+
)
|
|
1526
|
+
]
|
|
1527
|
+
|
|
1528
|
+
if wiki_results:
|
|
1529
|
+
wiki_content = "Here are some relevant Arch Wiki pages that might help:\n\n"
|
|
1530
|
+
for result in wiki_results:
|
|
1531
|
+
wiki_content += f"- **{result['title']}**: {result.get('snippet', 'No description available')}\n"
|
|
1532
|
+
wiki_content += f" URL: {result['url']}\n\n"
|
|
1533
|
+
|
|
1534
|
+
messages.append(
|
|
1535
|
+
PromptMessage(
|
|
1536
|
+
role="assistant",
|
|
1537
|
+
content=PromptMessage.TextContent(
|
|
1538
|
+
type="text",
|
|
1539
|
+
text=wiki_content
|
|
1540
|
+
)
|
|
1541
|
+
)
|
|
1542
|
+
)
|
|
1543
|
+
|
|
1544
|
+
return GetPromptResult(
|
|
1545
|
+
description=f"Troubleshooting guidance for: {error_message}",
|
|
1546
|
+
messages=messages
|
|
1547
|
+
)
|
|
1548
|
+
|
|
1549
|
+
elif name == "audit_aur_package":
|
|
1550
|
+
package_name = arguments["package_name"]
|
|
1551
|
+
|
|
1552
|
+
# Get package info and PKGBUILD
|
|
1553
|
+
try:
|
|
1554
|
+
package_info = await get_aur_info(package_name)
|
|
1555
|
+
pkgbuild_content = await get_pkgbuild(package_name)
|
|
1556
|
+
|
|
1557
|
+
# Analyze both metadata and PKGBUILD
|
|
1558
|
+
metadata_risk = analyze_package_metadata_risk(package_info)
|
|
1559
|
+
pkgbuild_safety = analyze_pkgbuild_safety(pkgbuild_content)
|
|
1560
|
+
|
|
1561
|
+
audit_summary = f"""
|
|
1562
|
+
# Security Audit Report for {package_name}
|
|
1563
|
+
|
|
1564
|
+
## Package Metadata Analysis
|
|
1565
|
+
- **Trust Score**: {metadata_risk.get('trust_score', 'N/A')}/100
|
|
1566
|
+
- **Risk Factors**: {', '.join(metadata_risk.get('risk_factors', []))}
|
|
1567
|
+
- **Trust Indicators**: {', '.join(metadata_risk.get('trust_indicators', []))}
|
|
1568
|
+
|
|
1569
|
+
## PKGBUILD Security Analysis
|
|
1570
|
+
- **Risk Score**: {pkgbuild_safety.get('risk_score', 'N/A')}/100
|
|
1571
|
+
- **Security Issues Found**: {len(pkgbuild_safety.get('findings', []))}
|
|
1572
|
+
- **Critical Issues**: {len([f for f in pkgbuild_safety.get('findings', []) if f.get('severity') == 'critical'])}
|
|
1573
|
+
|
|
1574
|
+
## Recommendations
|
|
1575
|
+
"""
|
|
1576
|
+
|
|
1577
|
+
if metadata_risk.get('trust_score', 0) < 50 or pkgbuild_safety.get('risk_score', 0) > 70:
|
|
1578
|
+
audit_summary += "⚠️ **HIGH RISK** - Consider finding an alternative package or reviewing the source code manually.\n"
|
|
1579
|
+
elif metadata_risk.get('trust_score', 0) < 70 or pkgbuild_safety.get('risk_score', 0) > 50:
|
|
1580
|
+
audit_summary += "⚠️ **MEDIUM RISK** - Proceed with caution and review the findings below.\n"
|
|
1581
|
+
else:
|
|
1582
|
+
audit_summary += "✅ **LOW RISK** - Package appears safe to install.\n"
|
|
1583
|
+
|
|
1584
|
+
messages = [
|
|
1585
|
+
PromptMessage(
|
|
1586
|
+
role="user",
|
|
1587
|
+
content=PromptMessage.TextContent(
|
|
1588
|
+
type="text",
|
|
1589
|
+
text=f"Please audit the AUR package '{package_name}' for security issues before installation."
|
|
1590
|
+
)
|
|
1591
|
+
),
|
|
1592
|
+
PromptMessage(
|
|
1593
|
+
role="assistant",
|
|
1594
|
+
content=PromptMessage.TextContent(
|
|
1595
|
+
type="text",
|
|
1596
|
+
text=audit_summary
|
|
1597
|
+
)
|
|
1598
|
+
)
|
|
1599
|
+
]
|
|
1600
|
+
|
|
1601
|
+
return GetPromptResult(
|
|
1602
|
+
description=f"Security audit for AUR package: {package_name}",
|
|
1603
|
+
messages=messages
|
|
1604
|
+
)
|
|
1605
|
+
|
|
1606
|
+
except Exception as e:
|
|
1607
|
+
return GetPromptResult(
|
|
1608
|
+
description=f"Security audit for AUR package: {package_name}",
|
|
1609
|
+
messages=[
|
|
1610
|
+
PromptMessage(
|
|
1611
|
+
role="assistant",
|
|
1612
|
+
content=PromptMessage.TextContent(
|
|
1613
|
+
type="text",
|
|
1614
|
+
text=f"Error auditing package '{package_name}': {str(e)}"
|
|
1615
|
+
)
|
|
1616
|
+
)
|
|
1617
|
+
]
|
|
1618
|
+
)
|
|
1619
|
+
|
|
1620
|
+
elif name == "analyze_dependencies":
|
|
1621
|
+
package_name = arguments["package_name"]
|
|
1622
|
+
|
|
1623
|
+
# Check if it's an official package first
|
|
1624
|
+
try:
|
|
1625
|
+
official_info = await get_official_package_info(package_name)
|
|
1626
|
+
if official_info.get("found"):
|
|
1627
|
+
deps = official_info.get("dependencies", [])
|
|
1628
|
+
opt_deps = official_info.get("optional_dependencies", [])
|
|
1629
|
+
|
|
1630
|
+
analysis = f"""
|
|
1631
|
+
# Dependency Analysis for {package_name} (Official Package)
|
|
1632
|
+
|
|
1633
|
+
## Required Dependencies
|
|
1634
|
+
{chr(10).join([f"- {dep}" for dep in deps]) if deps else "None"}
|
|
1635
|
+
|
|
1636
|
+
## Optional Dependencies
|
|
1637
|
+
{chr(10).join([f"- {dep}" for dep in opt_deps]) if opt_deps else "None"}
|
|
1638
|
+
|
|
1639
|
+
## Installation Order
|
|
1640
|
+
1. Install required dependencies first
|
|
1641
|
+
2. Install optional dependencies as needed
|
|
1642
|
+
3. Install {package_name} last
|
|
1643
|
+
|
|
1644
|
+
## Installation Commands
|
|
1645
|
+
```bash
|
|
1646
|
+
# Install required dependencies
|
|
1647
|
+
sudo pacman -S {' '.join(deps) if deps else '# No required dependencies'}
|
|
1648
|
+
|
|
1649
|
+
# Install optional dependencies (if needed)
|
|
1650
|
+
sudo pacman -S {' '.join(opt_deps) if opt_deps else '# No optional dependencies'}
|
|
1651
|
+
|
|
1652
|
+
# Install the package
|
|
1653
|
+
sudo pacman -S {package_name}
|
|
1654
|
+
```
|
|
1655
|
+
"""
|
|
1656
|
+
else:
|
|
1657
|
+
# Check AUR
|
|
1658
|
+
aur_info = await get_aur_info(package_name)
|
|
1659
|
+
if aur_info.get("found"):
|
|
1660
|
+
analysis = f"""
|
|
1661
|
+
# Dependency Analysis for {package_name} (AUR Package)
|
|
1662
|
+
|
|
1663
|
+
## AUR Package Information
|
|
1664
|
+
- **Maintainer**: {aur_info.get('maintainer', 'Unknown')}
|
|
1665
|
+
- **Last Updated**: {aur_info.get('last_modified', 'Unknown')}
|
|
1666
|
+
- **Votes**: {aur_info.get('votes', 'Unknown')}
|
|
1667
|
+
|
|
1668
|
+
## Installation Considerations
|
|
1669
|
+
1. **Security Check**: Run a security audit before installation
|
|
1670
|
+
2. **Dependencies**: AUR packages may have complex dependency chains
|
|
1671
|
+
3. **Build Requirements**: Check if you have all build tools installed
|
|
1672
|
+
|
|
1673
|
+
## Recommended Installation Process
|
|
1674
|
+
```bash
|
|
1675
|
+
# 1. Install build dependencies
|
|
1676
|
+
sudo pacman -S base-devel git
|
|
1677
|
+
|
|
1678
|
+
# 2. Install AUR helper (if not already installed)
|
|
1679
|
+
# Choose one: paru, yay, or manual AUR installation
|
|
1680
|
+
|
|
1681
|
+
# 3. Install the package
|
|
1682
|
+
paru -S {package_name} # or yay -S {package_name}
|
|
1683
|
+
```
|
|
1684
|
+
|
|
1685
|
+
⚠️ **Important**: Always audit AUR packages for security before installation!
|
|
1686
|
+
"""
|
|
1687
|
+
else:
|
|
1688
|
+
analysis = f"Package '{package_name}' not found in official repositories or AUR."
|
|
1689
|
+
|
|
1690
|
+
except Exception as e:
|
|
1691
|
+
analysis = f"Error analyzing dependencies for '{package_name}': {str(e)}"
|
|
1692
|
+
|
|
1693
|
+
return GetPromptResult(
|
|
1694
|
+
description=f"Dependency analysis for: {package_name}",
|
|
1695
|
+
messages=[
|
|
1696
|
+
PromptMessage(
|
|
1697
|
+
role="user",
|
|
1698
|
+
content=PromptMessage.TextContent(
|
|
1699
|
+
type="text",
|
|
1700
|
+
text=f"Please analyze the dependencies for the package '{package_name}' and suggest the best installation approach."
|
|
1701
|
+
)
|
|
1702
|
+
),
|
|
1703
|
+
PromptMessage(
|
|
1704
|
+
role="assistant",
|
|
1705
|
+
content=PromptMessage.TextContent(
|
|
1706
|
+
type="text",
|
|
1707
|
+
text=analysis
|
|
1708
|
+
)
|
|
1709
|
+
)
|
|
1710
|
+
]
|
|
1711
|
+
)
|
|
1712
|
+
|
|
1713
|
+
elif name == "safe_system_update":
|
|
1714
|
+
if not IS_ARCH:
|
|
1715
|
+
return GetPromptResult(
|
|
1716
|
+
description="Safe system update workflow",
|
|
1717
|
+
messages=[
|
|
1718
|
+
PromptMessage(
|
|
1719
|
+
role="assistant",
|
|
1720
|
+
content=PromptMessage.TextContent(
|
|
1721
|
+
type="text",
|
|
1722
|
+
text="Error: safe_system_update prompt only available on Arch Linux systems"
|
|
1723
|
+
)
|
|
1724
|
+
)
|
|
1725
|
+
]
|
|
1726
|
+
)
|
|
1727
|
+
|
|
1728
|
+
analysis = "# Safe System Update Workflow\n\n"
|
|
1729
|
+
warnings = []
|
|
1730
|
+
recommendations = []
|
|
1731
|
+
|
|
1732
|
+
# Step 1: Check for critical news
|
|
1733
|
+
try:
|
|
1734
|
+
critical_news = await check_critical_news(limit=10)
|
|
1735
|
+
|
|
1736
|
+
if critical_news.get("has_critical"):
|
|
1737
|
+
analysis += "## ⚠️ Critical Arch Linux News\n\n"
|
|
1738
|
+
for news_item in critical_news.get("critical_news", [])[:3]:
|
|
1739
|
+
analysis += f"**{news_item['title']}**\n"
|
|
1740
|
+
analysis += f"Published: {news_item['published']}\n"
|
|
1741
|
+
analysis += f"{news_item['summary'][:200]}...\n"
|
|
1742
|
+
analysis += f"[Read more]({news_item['link']})\n\n"
|
|
1743
|
+
|
|
1744
|
+
warnings.append("Critical news requiring manual intervention found!")
|
|
1745
|
+
recommendations.append("Read all critical news articles before updating")
|
|
1746
|
+
else:
|
|
1747
|
+
analysis += "## ✓ No Critical News\n\nNo manual intervention required for recent updates.\n\n"
|
|
1748
|
+
except Exception as e:
|
|
1749
|
+
analysis += f"## ⚠️ News Check Failed\n\n{str(e)}\n\n"
|
|
1750
|
+
|
|
1751
|
+
# Step 2: Check disk space
|
|
1752
|
+
try:
|
|
1753
|
+
disk_space = await check_disk_space()
|
|
1754
|
+
disk_usage = disk_space.get("disk_usage", {})
|
|
1755
|
+
|
|
1756
|
+
analysis += "## Disk Space Status\n\n"
|
|
1757
|
+
for path, info in disk_usage.items():
|
|
1758
|
+
if "warning" in info:
|
|
1759
|
+
analysis += f"- ⚠️ {path}: {info['available']} available ({info['use_percent']} used) - {info['warning']}\n"
|
|
1760
|
+
warnings.append(f"Low disk space on {path}")
|
|
1761
|
+
else:
|
|
1762
|
+
analysis += f"- ✓ {path}: {info['available']} available ({info['use_percent']} used)\n"
|
|
1763
|
+
analysis += "\n"
|
|
1764
|
+
except Exception as e:
|
|
1765
|
+
analysis += f"## ⚠️ Disk Space Check Failed\n\n{str(e)}\n\n"
|
|
1766
|
+
|
|
1767
|
+
# Step 3: Check pending updates
|
|
1768
|
+
try:
|
|
1769
|
+
updates = await check_updates_dry_run()
|
|
1770
|
+
|
|
1771
|
+
if updates.get("updates_available"):
|
|
1772
|
+
count = updates.get("count", 0)
|
|
1773
|
+
analysis += f"## Pending Updates ({count} packages)\n\n"
|
|
1774
|
+
|
|
1775
|
+
# Show first 10 updates
|
|
1776
|
+
for update in updates.get("packages", [])[:10]:
|
|
1777
|
+
analysis += f"- {update['package']}: {update['current_version']} → {update['new_version']}\n"
|
|
1778
|
+
|
|
1779
|
+
if count > 10:
|
|
1780
|
+
analysis += f"\n...and {count - 10} more packages\n"
|
|
1781
|
+
analysis += "\n"
|
|
1782
|
+
else:
|
|
1783
|
+
analysis += "## ✓ System Up to Date\n\nNo updates available.\n\n"
|
|
1784
|
+
return GetPromptResult(
|
|
1785
|
+
description="System is already up to date",
|
|
1786
|
+
messages=[
|
|
1787
|
+
PromptMessage(
|
|
1788
|
+
role="assistant",
|
|
1789
|
+
content=PromptMessage.TextContent(
|
|
1790
|
+
type="text",
|
|
1791
|
+
text=analysis
|
|
1792
|
+
)
|
|
1793
|
+
)
|
|
1794
|
+
]
|
|
1795
|
+
)
|
|
1796
|
+
except Exception as e:
|
|
1797
|
+
analysis += f"## ⚠️ Update Check Failed\n\n{str(e)}\n\n"
|
|
1798
|
+
|
|
1799
|
+
# Step 4: Check failed services
|
|
1800
|
+
try:
|
|
1801
|
+
failed_services = await check_failed_services()
|
|
1802
|
+
|
|
1803
|
+
if not failed_services.get("all_ok"):
|
|
1804
|
+
analysis += "## ⚠️ Failed Services Detected\n\n"
|
|
1805
|
+
for service in failed_services.get("failed_services", [])[:5]:
|
|
1806
|
+
analysis += f"- {service['unit']}\n"
|
|
1807
|
+
warnings.append("System has failed services")
|
|
1808
|
+
recommendations.append("Investigate failed services before updating")
|
|
1809
|
+
analysis += "\n"
|
|
1810
|
+
else:
|
|
1811
|
+
analysis += "## ✓ All Services Running\n\nNo failed systemd services.\n\n"
|
|
1812
|
+
except Exception as e:
|
|
1813
|
+
analysis += f"## ⚠️ Service Check Failed\n\n{str(e)}\n\n"
|
|
1814
|
+
|
|
1815
|
+
# Step 5: Check database freshness
|
|
1816
|
+
try:
|
|
1817
|
+
db_freshness = await check_database_freshness()
|
|
1818
|
+
|
|
1819
|
+
if db_freshness.get("needs_sync"):
|
|
1820
|
+
analysis += "## Database Synchronization\n\n"
|
|
1821
|
+
analysis += f"Databases are {db_freshness.get('oldest_age_hours', 0):.1f} hours old.\n"
|
|
1822
|
+
recommendations.append("Database will be synchronized during update")
|
|
1823
|
+
analysis += "\n"
|
|
1824
|
+
except Exception as e:
|
|
1825
|
+
logger.warning(f"Database freshness check failed: {e}")
|
|
1826
|
+
|
|
1827
|
+
# Step 6: Summary and recommendations
|
|
1828
|
+
analysis += "## Recommendations\n\n"
|
|
1829
|
+
|
|
1830
|
+
if warnings:
|
|
1831
|
+
analysis += "### Warnings:\n"
|
|
1832
|
+
for warning in warnings:
|
|
1833
|
+
analysis += f"- ⚠️ {warning}\n"
|
|
1834
|
+
analysis += "\n"
|
|
1835
|
+
|
|
1836
|
+
if recommendations:
|
|
1837
|
+
analysis += "### Before Updating:\n"
|
|
1838
|
+
for rec in recommendations:
|
|
1839
|
+
analysis += f"- {rec}\n"
|
|
1840
|
+
analysis += "\n"
|
|
1841
|
+
|
|
1842
|
+
if not warnings:
|
|
1843
|
+
analysis += "✓ System is ready for update\n\n"
|
|
1844
|
+
analysis += "Run: `sudo pacman -Syu`\n"
|
|
1845
|
+
else:
|
|
1846
|
+
analysis += "⚠️ **Address warnings before updating**\n"
|
|
1847
|
+
|
|
1848
|
+
return GetPromptResult(
|
|
1849
|
+
description="Safe system update analysis",
|
|
1850
|
+
messages=[
|
|
1851
|
+
PromptMessage(
|
|
1852
|
+
role="user",
|
|
1853
|
+
content=PromptMessage.TextContent(
|
|
1854
|
+
type="text",
|
|
1855
|
+
text="Check if my system is ready for a safe update"
|
|
1856
|
+
)
|
|
1857
|
+
),
|
|
1858
|
+
PromptMessage(
|
|
1859
|
+
role="assistant",
|
|
1860
|
+
content=PromptMessage.TextContent(
|
|
1861
|
+
type="text",
|
|
1862
|
+
text=analysis
|
|
1863
|
+
)
|
|
1864
|
+
)
|
|
1865
|
+
]
|
|
1866
|
+
)
|
|
1867
|
+
|
|
1868
|
+
else:
|
|
1869
|
+
raise ValueError(f"Unknown prompt: {name}")
|