arch-ops-server 3.3.1__py3-none-any.whl → 3.3.2__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 +9 -1
- arch_ops_server/pacman.py +254 -0
- arch_ops_server/server.py +67 -184
- arch_ops_server/system_health_check.py +7 -5
- {arch_ops_server-3.3.1.dist-info → arch_ops_server-3.3.2.dist-info}/METADATA +1 -1
- {arch_ops_server-3.3.1.dist-info → arch_ops_server-3.3.2.dist-info}/RECORD +8 -8
- {arch_ops_server-3.3.1.dist-info → arch_ops_server-3.3.2.dist-info}/WHEEL +1 -1
- {arch_ops_server-3.3.1.dist-info → arch_ops_server-3.3.2.dist-info}/entry_points.txt +0 -0
arch_ops_server/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ A Model Context Protocol server that bridges AI assistants with the Arch Linux
|
|
|
6
6
|
ecosystem, providing access to the Arch Wiki, AUR, and official repositories.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__version__ = "3.3.
|
|
9
|
+
__version__ = "3.3.2"
|
|
10
10
|
|
|
11
11
|
from .wiki import search_wiki, get_wiki_page, get_wiki_page_as_text
|
|
12
12
|
from .aur import (
|
|
@@ -23,17 +23,21 @@ from .pacman import (
|
|
|
23
23
|
check_updates_dry_run,
|
|
24
24
|
remove_package,
|
|
25
25
|
remove_packages_batch,
|
|
26
|
+
remove_packages,
|
|
26
27
|
list_orphan_packages,
|
|
27
28
|
remove_orphans,
|
|
29
|
+
manage_orphans,
|
|
28
30
|
find_package_owner,
|
|
29
31
|
list_package_files,
|
|
30
32
|
search_package_files,
|
|
33
|
+
query_file_ownership,
|
|
31
34
|
verify_package_integrity,
|
|
32
35
|
list_package_groups,
|
|
33
36
|
list_group_packages,
|
|
34
37
|
list_explicit_packages,
|
|
35
38
|
mark_as_explicit,
|
|
36
39
|
mark_as_dependency,
|
|
40
|
+
manage_install_reason,
|
|
37
41
|
check_database_freshness
|
|
38
42
|
)
|
|
39
43
|
from .system import (
|
|
@@ -130,17 +134,21 @@ __all__ = [
|
|
|
130
134
|
"check_updates_dry_run",
|
|
131
135
|
"remove_package",
|
|
132
136
|
"remove_packages_batch",
|
|
137
|
+
"remove_packages",
|
|
133
138
|
"list_orphan_packages",
|
|
134
139
|
"remove_orphans",
|
|
140
|
+
"manage_orphans",
|
|
135
141
|
"find_package_owner",
|
|
136
142
|
"list_package_files",
|
|
137
143
|
"search_package_files",
|
|
144
|
+
"query_file_ownership",
|
|
138
145
|
"verify_package_integrity",
|
|
139
146
|
"list_package_groups",
|
|
140
147
|
"list_group_packages",
|
|
141
148
|
"list_explicit_packages",
|
|
142
149
|
"mark_as_explicit",
|
|
143
150
|
"mark_as_dependency",
|
|
151
|
+
"manage_install_reason",
|
|
144
152
|
"check_database_freshness",
|
|
145
153
|
# System
|
|
146
154
|
"get_system_info",
|
arch_ops_server/pacman.py
CHANGED
|
@@ -472,6 +472,70 @@ async def remove_packages_batch(
|
|
|
472
472
|
)
|
|
473
473
|
|
|
474
474
|
|
|
475
|
+
async def remove_packages(
|
|
476
|
+
packages: Union[str, List[str]],
|
|
477
|
+
remove_dependencies: bool = False,
|
|
478
|
+
force: bool = False
|
|
479
|
+
) -> Dict[str, Any]:
|
|
480
|
+
"""
|
|
481
|
+
Unified tool for removing packages (single or multiple).
|
|
482
|
+
|
|
483
|
+
This consolidates two operations:
|
|
484
|
+
- Single package removal (replaces remove_package)
|
|
485
|
+
- Batch package removal (replaces remove_packages_batch)
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
packages: Package name (string) or list of package names to remove
|
|
489
|
+
remove_dependencies: If True, remove unneeded dependencies (pacman -Rs)
|
|
490
|
+
force: If True, force removal ignoring dependencies (pacman -Rdd). Only works for single package.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Dict with removal status and information
|
|
494
|
+
"""
|
|
495
|
+
if not IS_ARCH:
|
|
496
|
+
return create_error_response(
|
|
497
|
+
"NotSupported",
|
|
498
|
+
"Package removal is only available on Arch Linux"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
if not check_command_exists("pacman"):
|
|
502
|
+
return create_error_response(
|
|
503
|
+
"CommandNotFound",
|
|
504
|
+
"pacman command not found"
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Normalize input to list
|
|
508
|
+
if isinstance(packages, str):
|
|
509
|
+
package_list = [packages]
|
|
510
|
+
is_single = True
|
|
511
|
+
else:
|
|
512
|
+
package_list = packages
|
|
513
|
+
is_single = False
|
|
514
|
+
|
|
515
|
+
if not package_list:
|
|
516
|
+
return create_error_response(
|
|
517
|
+
"ValidationError",
|
|
518
|
+
"No packages specified for removal"
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
# Validate force flag usage
|
|
522
|
+
if force and not is_single:
|
|
523
|
+
return create_error_response(
|
|
524
|
+
"ValidationError",
|
|
525
|
+
"force flag can only be used with single package removal"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
logger.info(f"Removing {len(package_list)} package(s): {package_list} (deps={remove_dependencies}, force={force})")
|
|
529
|
+
|
|
530
|
+
# Route to appropriate implementation based on input type and flags
|
|
531
|
+
if is_single:
|
|
532
|
+
# Single package removal
|
|
533
|
+
return await remove_package(package_list[0], remove_dependencies, force)
|
|
534
|
+
else:
|
|
535
|
+
# Batch package removal (force not supported)
|
|
536
|
+
return await remove_packages_batch(package_list, remove_dependencies)
|
|
537
|
+
|
|
538
|
+
|
|
475
539
|
async def list_orphan_packages() -> Dict[str, Any]:
|
|
476
540
|
"""
|
|
477
541
|
List all orphaned packages (dependencies no longer required).
|
|
@@ -628,6 +692,64 @@ async def remove_orphans(dry_run: bool = True, exclude: Optional[List[str]] = No
|
|
|
628
692
|
)
|
|
629
693
|
|
|
630
694
|
|
|
695
|
+
async def manage_orphans(
|
|
696
|
+
action: str,
|
|
697
|
+
dry_run: bool = True,
|
|
698
|
+
exclude: Optional[List[str]] = None
|
|
699
|
+
) -> Dict[str, Any]:
|
|
700
|
+
"""
|
|
701
|
+
Unified tool for managing orphaned packages.
|
|
702
|
+
|
|
703
|
+
This consolidates two operations:
|
|
704
|
+
- list: List all orphaned packages (replaces list_orphan_packages)
|
|
705
|
+
- remove: Remove orphaned packages (replaces remove_orphans)
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
action: Action to perform - "list" or "remove"
|
|
709
|
+
dry_run: If True (default), show what would be removed without removing (only for remove action)
|
|
710
|
+
exclude: List of packages to exclude from removal (only for remove action)
|
|
711
|
+
|
|
712
|
+
Returns:
|
|
713
|
+
Dict with action results
|
|
714
|
+
"""
|
|
715
|
+
if not IS_ARCH:
|
|
716
|
+
return create_error_response(
|
|
717
|
+
"NotSupported",
|
|
718
|
+
"Orphan package management is only available on Arch Linux"
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
if not check_command_exists("pacman"):
|
|
722
|
+
return create_error_response(
|
|
723
|
+
"CommandNotFound",
|
|
724
|
+
"pacman command not found"
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
# Validate action
|
|
728
|
+
valid_actions = ["list", "remove"]
|
|
729
|
+
if action not in valid_actions:
|
|
730
|
+
return create_error_response(
|
|
731
|
+
"ValidationError",
|
|
732
|
+
f"Invalid action '{action}'. Must be one of: {', '.join(valid_actions)}"
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
logger.info(f"Orphan management: action={action}, dry_run={dry_run}")
|
|
736
|
+
|
|
737
|
+
# Route to appropriate implementation based on action
|
|
738
|
+
if action == "list":
|
|
739
|
+
# List orphaned packages
|
|
740
|
+
return await list_orphan_packages()
|
|
741
|
+
|
|
742
|
+
elif action == "remove":
|
|
743
|
+
# Remove orphaned packages
|
|
744
|
+
return await remove_orphans(dry_run, exclude)
|
|
745
|
+
|
|
746
|
+
# This should never be reached due to validation above
|
|
747
|
+
return create_error_response(
|
|
748
|
+
"InternalError",
|
|
749
|
+
f"Unexpected action: {action}"
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
|
|
631
753
|
async def find_package_owner(file_path: str) -> Dict[str, Any]:
|
|
632
754
|
"""
|
|
633
755
|
Find which package owns a specific file.
|
|
@@ -861,6 +983,69 @@ async def search_package_files(filename_pattern: str) -> Dict[str, Any]:
|
|
|
861
983
|
)
|
|
862
984
|
|
|
863
985
|
|
|
986
|
+
async def query_file_ownership(
|
|
987
|
+
query: str,
|
|
988
|
+
mode: str,
|
|
989
|
+
filter_pattern: Optional[str] = None
|
|
990
|
+
) -> Dict[str, Any]:
|
|
991
|
+
"""
|
|
992
|
+
Unified tool for querying file ownership relationships.
|
|
993
|
+
|
|
994
|
+
This consolidates three operations:
|
|
995
|
+
- file_to_package: Find which package owns a specific file (replaces find_package_owner)
|
|
996
|
+
- package_to_files: List all files owned by a package (replaces list_package_files)
|
|
997
|
+
- filename_search: Search for files across all packages (replaces search_package_files)
|
|
998
|
+
|
|
999
|
+
Args:
|
|
1000
|
+
query: The query string (file path, package name, or filename pattern depending on mode)
|
|
1001
|
+
mode: Query mode - "file_to_package", "package_to_files", or "filename_search"
|
|
1002
|
+
filter_pattern: Optional regex pattern to filter files (only used in package_to_files mode)
|
|
1003
|
+
|
|
1004
|
+
Returns:
|
|
1005
|
+
Dict with query results appropriate to the mode
|
|
1006
|
+
"""
|
|
1007
|
+
if not IS_ARCH:
|
|
1008
|
+
return create_error_response(
|
|
1009
|
+
"NotSupported",
|
|
1010
|
+
"File ownership queries are only available on Arch Linux"
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
if not check_command_exists("pacman"):
|
|
1014
|
+
return create_error_response(
|
|
1015
|
+
"CommandNotFound",
|
|
1016
|
+
"pacman command not found"
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
# Validate mode
|
|
1020
|
+
valid_modes = ["file_to_package", "package_to_files", "filename_search"]
|
|
1021
|
+
if mode not in valid_modes:
|
|
1022
|
+
return create_error_response(
|
|
1023
|
+
"ValidationError",
|
|
1024
|
+
f"Invalid mode '{mode}'. Must be one of: {', '.join(valid_modes)}"
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
logger.info(f"File ownership query: mode={mode}, query={query}")
|
|
1028
|
+
|
|
1029
|
+
# Route to appropriate implementation based on mode
|
|
1030
|
+
if mode == "file_to_package":
|
|
1031
|
+
# Find which package owns a specific file (replaces find_package_owner)
|
|
1032
|
+
return await find_package_owner(query)
|
|
1033
|
+
|
|
1034
|
+
elif mode == "package_to_files":
|
|
1035
|
+
# List all files owned by a package (replaces list_package_files)
|
|
1036
|
+
return await list_package_files(query, filter_pattern)
|
|
1037
|
+
|
|
1038
|
+
elif mode == "filename_search":
|
|
1039
|
+
# Search for files across all packages (replaces search_package_files)
|
|
1040
|
+
return await search_package_files(query)
|
|
1041
|
+
|
|
1042
|
+
# This should never be reached due to validation above
|
|
1043
|
+
return create_error_response(
|
|
1044
|
+
"InternalError",
|
|
1045
|
+
f"Unexpected mode: {mode}"
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
|
|
864
1049
|
async def verify_package_integrity(package_name: str, thorough: bool = False) -> Dict[str, Any]:
|
|
865
1050
|
"""
|
|
866
1051
|
Verify integrity of an installed package.
|
|
@@ -1213,6 +1398,75 @@ async def mark_as_dependency(package_name: str) -> Dict[str, Any]:
|
|
|
1213
1398
|
)
|
|
1214
1399
|
|
|
1215
1400
|
|
|
1401
|
+
async def manage_install_reason(
|
|
1402
|
+
action: str,
|
|
1403
|
+
package_name: Optional[str] = None
|
|
1404
|
+
) -> Dict[str, Any]:
|
|
1405
|
+
"""
|
|
1406
|
+
Unified tool for managing package install reasons.
|
|
1407
|
+
|
|
1408
|
+
This consolidates three operations:
|
|
1409
|
+
- list: List all explicitly installed packages (replaces list_explicit_packages)
|
|
1410
|
+
- mark_explicit: Mark a package as explicitly installed (replaces mark_as_explicit)
|
|
1411
|
+
- mark_dependency: Mark a package as a dependency (replaces mark_as_dependency)
|
|
1412
|
+
|
|
1413
|
+
Args:
|
|
1414
|
+
action: Action to perform - "list", "mark_explicit", or "mark_dependency"
|
|
1415
|
+
package_name: Package name (required for mark_explicit and mark_dependency actions)
|
|
1416
|
+
|
|
1417
|
+
Returns:
|
|
1418
|
+
Dict with operation results appropriate to the action
|
|
1419
|
+
"""
|
|
1420
|
+
if not IS_ARCH:
|
|
1421
|
+
return create_error_response(
|
|
1422
|
+
"NotSupported",
|
|
1423
|
+
"Install reason management is only available on Arch Linux"
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
if not check_command_exists("pacman"):
|
|
1427
|
+
return create_error_response(
|
|
1428
|
+
"CommandNotFound",
|
|
1429
|
+
"pacman command not found"
|
|
1430
|
+
)
|
|
1431
|
+
|
|
1432
|
+
# Validate action
|
|
1433
|
+
valid_actions = ["list", "mark_explicit", "mark_dependency"]
|
|
1434
|
+
if action not in valid_actions:
|
|
1435
|
+
return create_error_response(
|
|
1436
|
+
"ValidationError",
|
|
1437
|
+
f"Invalid action '{action}'. Must be one of: {', '.join(valid_actions)}"
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
# Validate package_name for marking actions
|
|
1441
|
+
if action in ["mark_explicit", "mark_dependency"] and not package_name:
|
|
1442
|
+
return create_error_response(
|
|
1443
|
+
"ValidationError",
|
|
1444
|
+
f"package_name is required for action '{action}'"
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
logger.info(f"Install reason management: action={action}, package={package_name}")
|
|
1448
|
+
|
|
1449
|
+
# Route to appropriate implementation based on action
|
|
1450
|
+
if action == "list":
|
|
1451
|
+
# List all explicitly installed packages
|
|
1452
|
+
return await list_explicit_packages()
|
|
1453
|
+
|
|
1454
|
+
elif action == "mark_explicit":
|
|
1455
|
+
# Mark package as explicitly installed
|
|
1456
|
+
return await mark_as_explicit(package_name)
|
|
1457
|
+
|
|
1458
|
+
elif action == "mark_dependency":
|
|
1459
|
+
# Mark package as dependency
|
|
1460
|
+
return await mark_as_dependency(package_name)
|
|
1461
|
+
|
|
1462
|
+
# This should never be reached due to validation above
|
|
1463
|
+
return create_error_response(
|
|
1464
|
+
"InternalError",
|
|
1465
|
+
f"Unexpected action: {action}"
|
|
1466
|
+
)
|
|
1467
|
+
|
|
1468
|
+
|
|
1469
|
+
|
|
1216
1470
|
async def check_database_freshness() -> Dict[str, Any]:
|
|
1217
1471
|
"""
|
|
1218
1472
|
Check when package databases were last synchronized.
|
arch_ops_server/server.py
CHANGED
|
@@ -40,17 +40,15 @@ from . import (
|
|
|
40
40
|
check_updates_dry_run,
|
|
41
41
|
remove_package,
|
|
42
42
|
remove_packages_batch,
|
|
43
|
+
remove_packages,
|
|
43
44
|
list_orphan_packages,
|
|
44
45
|
remove_orphans,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
search_package_files,
|
|
46
|
+
manage_orphans,
|
|
47
|
+
query_file_ownership,
|
|
48
48
|
verify_package_integrity,
|
|
49
49
|
list_package_groups,
|
|
50
50
|
list_group_packages,
|
|
51
|
-
|
|
52
|
-
mark_as_explicit,
|
|
53
|
-
mark_as_dependency,
|
|
51
|
+
manage_install_reason,
|
|
54
52
|
check_database_freshness,
|
|
55
53
|
# System functions
|
|
56
54
|
get_system_info,
|
|
@@ -751,20 +749,23 @@ async def list_tools() -> list[Tool]:
|
|
|
751
749
|
annotations=ToolAnnotations(readOnlyHint=True)
|
|
752
750
|
),
|
|
753
751
|
|
|
754
|
-
# Package Removal
|
|
752
|
+
# Package Removal
|
|
755
753
|
Tool(
|
|
756
|
-
name="
|
|
757
|
-
description="[LIFECYCLE]
|
|
754
|
+
name="remove_packages",
|
|
755
|
+
description="[LIFECYCLE] Unified tool for removing packages (single or multiple). Accepts either a single package name or a list of packages. Supports removal with dependencies and forced removal. Only works on Arch Linux. Requires sudo access. Examples: packages='firefox', remove_dependencies=true → removes Firefox with its dependencies; packages=['pkg1', 'pkg2', 'pkg3'] → batch removal of multiple packages; packages='lib', force=true → force removal ignoring dependencies (dangerous!).",
|
|
758
756
|
inputSchema={
|
|
759
757
|
"type": "object",
|
|
760
758
|
"properties": {
|
|
761
|
-
"
|
|
762
|
-
"
|
|
763
|
-
|
|
759
|
+
"packages": {
|
|
760
|
+
"oneOf": [
|
|
761
|
+
{"type": "string"},
|
|
762
|
+
{"type": "array", "items": {"type": "string"}}
|
|
763
|
+
],
|
|
764
|
+
"description": "Package name (string) or list of package names (array) to remove"
|
|
764
765
|
},
|
|
765
766
|
"remove_dependencies": {
|
|
766
767
|
"type": "boolean",
|
|
767
|
-
"description": "Remove
|
|
768
|
+
"description": "Remove packages and their dependencies (pacman -Rs). Default: false",
|
|
768
769
|
"default": False
|
|
769
770
|
},
|
|
770
771
|
"force": {
|
|
@@ -773,118 +774,65 @@ async def list_tools() -> list[Tool]:
|
|
|
773
774
|
"default": False
|
|
774
775
|
}
|
|
775
776
|
},
|
|
776
|
-
"required": ["
|
|
777
|
-
},
|
|
778
|
-
annotations=ToolAnnotations(destructiveHint=True)
|
|
779
|
-
),
|
|
780
|
-
|
|
781
|
-
Tool(
|
|
782
|
-
name="remove_packages_batch",
|
|
783
|
-
description="[LIFECYCLE] Remove multiple packages in a single transaction. More efficient than removing packages one by one. Only works on Arch Linux. Requires sudo access. Use case: Clean up multiple packages at once: ['package1', 'package2', 'package3'] with optional dependency removal.",
|
|
784
|
-
inputSchema={
|
|
785
|
-
"type": "object",
|
|
786
|
-
"properties": {
|
|
787
|
-
"package_names": {
|
|
788
|
-
"type": "array",
|
|
789
|
-
"items": {"type": "string"},
|
|
790
|
-
"description": "List of package names to remove"
|
|
791
|
-
},
|
|
792
|
-
"remove_dependencies": {
|
|
793
|
-
"type": "boolean",
|
|
794
|
-
"description": "Remove packages and their dependencies. Default: false",
|
|
795
|
-
"default": False
|
|
796
|
-
}
|
|
797
|
-
},
|
|
798
|
-
"required": ["package_names"]
|
|
777
|
+
"required": ["packages"]
|
|
799
778
|
},
|
|
800
779
|
annotations=ToolAnnotations(destructiveHint=True)
|
|
801
780
|
),
|
|
802
781
|
|
|
803
782
|
# Orphan Package Management
|
|
804
783
|
Tool(
|
|
805
|
-
name="
|
|
806
|
-
description="[MAINTENANCE]
|
|
807
|
-
inputSchema={
|
|
808
|
-
"type": "object",
|
|
809
|
-
"properties": {}
|
|
810
|
-
},
|
|
811
|
-
annotations=ToolAnnotations(readOnlyHint=True)
|
|
812
|
-
),
|
|
813
|
-
|
|
814
|
-
Tool(
|
|
815
|
-
name="remove_orphans",
|
|
816
|
-
description="[MAINTENANCE] 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. Example: Use dry_run=true first to preview, then dry_run=false to actually remove. Exclude critical packages with exclude=['pkg1'].",
|
|
784
|
+
name="manage_orphans",
|
|
785
|
+
description="[MAINTENANCE] Unified tool for managing orphaned packages (dependencies no longer required). Supports two actions: 'list' (show orphaned packages) and 'remove' (remove orphaned packages). Only works on Arch Linux. Requires sudo access for removal. Examples: action='list' → shows all orphaned packages with disk usage; action='remove', dry_run=true → preview what would be removed; action='remove', dry_run=false, exclude=['pkg1'] → remove all orphans except 'pkg1'.",
|
|
817
786
|
inputSchema={
|
|
818
787
|
"type": "object",
|
|
819
788
|
"properties": {
|
|
789
|
+
"action": {
|
|
790
|
+
"type": "string",
|
|
791
|
+
"enum": ["list", "remove"],
|
|
792
|
+
"description": "Action to perform: 'list' (list orphaned packages) or 'remove' (remove orphaned packages)"
|
|
793
|
+
},
|
|
820
794
|
"dry_run": {
|
|
821
795
|
"type": "boolean",
|
|
822
|
-
"description": "Preview what would be removed without actually removing. Default: true",
|
|
796
|
+
"description": "Preview what would be removed without actually removing (only for remove action). Default: true",
|
|
823
797
|
"default": True
|
|
824
798
|
},
|
|
825
799
|
"exclude": {
|
|
826
800
|
"type": "array",
|
|
827
801
|
"items": {"type": "string"},
|
|
828
|
-
"description": "List of package names to exclude from removal"
|
|
802
|
+
"description": "List of package names to exclude from removal (only for remove action)"
|
|
829
803
|
}
|
|
830
804
|
},
|
|
831
|
-
"required": []
|
|
805
|
+
"required": ["action"]
|
|
832
806
|
},
|
|
833
|
-
annotations=ToolAnnotations(destructiveHint=
|
|
807
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False) # Mixed: list is read-only, remove is destructive
|
|
834
808
|
),
|
|
835
809
|
|
|
836
|
-
#
|
|
810
|
+
# File Ownership Query (Consolidated)
|
|
837
811
|
Tool(
|
|
838
|
-
name="
|
|
839
|
-
description="[ORGANIZATION]
|
|
812
|
+
name="query_file_ownership",
|
|
813
|
+
description="[ORGANIZATION] Unified tool for querying file-package ownership relationships. Supports three modes: 'file_to_package' (find which package owns a file), 'package_to_files' (list all files in a package with optional filtering), and 'filename_search' (search for files across all packages). Only works on Arch Linux. Examples: mode='file_to_package', query='/usr/bin/python' → returns 'python' package; mode='package_to_files', query='systemd', filter_pattern='*.service' → lists all systemd service files; mode='filename_search', query='*.desktop' → finds all packages with desktop entries.",
|
|
840
814
|
inputSchema={
|
|
841
815
|
"type": "object",
|
|
842
816
|
"properties": {
|
|
843
|
-
"
|
|
817
|
+
"query": {
|
|
844
818
|
"type": "string",
|
|
845
|
-
"description": "
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
"required": ["file_path"]
|
|
849
|
-
},
|
|
850
|
-
annotations=ToolAnnotations(readOnlyHint=True)
|
|
851
|
-
),
|
|
852
|
-
|
|
853
|
-
Tool(
|
|
854
|
-
name="list_package_files",
|
|
855
|
-
description="[ORGANIZATION] List all files owned by a package. Supports optional filtering by pattern. Only works on Arch Linux. Use case: See all files installed by 'systemd' package, optionally filter with pattern='*.service'.",
|
|
856
|
-
inputSchema={
|
|
857
|
-
"type": "object",
|
|
858
|
-
"properties": {
|
|
859
|
-
"package_name": {
|
|
819
|
+
"description": "Query string: file path for file_to_package mode, package name for package_to_files mode, or filename pattern for filename_search mode"
|
|
820
|
+
},
|
|
821
|
+
"mode": {
|
|
860
822
|
"type": "string",
|
|
861
|
-
"
|
|
823
|
+
"enum": ["file_to_package", "package_to_files", "filename_search"],
|
|
824
|
+
"description": "Query mode: 'file_to_package' (find package owner), 'package_to_files' (list package files), or 'filename_search' (search across packages)"
|
|
862
825
|
},
|
|
863
826
|
"filter_pattern": {
|
|
864
827
|
"type": "string",
|
|
865
|
-
"description": "Optional regex pattern to filter files (e.g., '*.conf' or '/etc/')"
|
|
828
|
+
"description": "Optional regex pattern to filter files (only used in package_to_files mode, e.g., '*.conf' or '/etc/')"
|
|
866
829
|
}
|
|
867
830
|
},
|
|
868
|
-
"required": ["
|
|
831
|
+
"required": ["query", "mode"]
|
|
869
832
|
},
|
|
870
833
|
annotations=ToolAnnotations(readOnlyHint=True)
|
|
871
834
|
),
|
|
872
835
|
|
|
873
|
-
Tool(
|
|
874
|
-
name="search_package_files",
|
|
875
|
-
description="[ORGANIZATION] Search for files across all packages in repositories. Requires package database sync (pacman -Fy). Only works on Arch Linux. Example: Search for '*.desktop' to find all packages that install desktop entries.",
|
|
876
|
-
inputSchema={
|
|
877
|
-
"type": "object",
|
|
878
|
-
"properties": {
|
|
879
|
-
"filename_pattern": {
|
|
880
|
-
"type": "string",
|
|
881
|
-
"description": "File name or pattern to search for (e.g., 'vim' or '*.service')"
|
|
882
|
-
}
|
|
883
|
-
},
|
|
884
|
-
"required": ["filename_pattern"]
|
|
885
|
-
},
|
|
886
|
-
annotations=ToolAnnotations(readOnlyHint=True)
|
|
887
|
-
),
|
|
888
836
|
|
|
889
837
|
# Package Verification
|
|
890
838
|
Tool(
|
|
@@ -937,45 +885,24 @@ async def list_tools() -> list[Tool]:
|
|
|
937
885
|
|
|
938
886
|
# Install Reason Management
|
|
939
887
|
Tool(
|
|
940
|
-
name="
|
|
941
|
-
description="[MAINTENANCE]
|
|
942
|
-
inputSchema={
|
|
943
|
-
"type": "object",
|
|
944
|
-
"properties": {}
|
|
945
|
-
},
|
|
946
|
-
annotations=ToolAnnotations(readOnlyHint=True)
|
|
947
|
-
),
|
|
948
|
-
|
|
949
|
-
Tool(
|
|
950
|
-
name="mark_as_explicit",
|
|
951
|
-
description="[MAINTENANCE] Mark a package as explicitly installed. Prevents it from being removed as an orphan. Only works on Arch Linux. Example: Mark 'python-pip' as explicit if you want to keep it even when dependencies change.",
|
|
888
|
+
name="manage_install_reason",
|
|
889
|
+
description="[MAINTENANCE] Unified tool for managing package install reasons. Supports three actions: 'list' (list all explicitly installed packages), 'mark_explicit' (prevent package from being removed as orphan), and 'mark_dependency' (allow package to be auto-removed with orphans). Only works on Arch Linux. Examples: action='list' → returns all user-installed packages; action='mark_explicit', package_name='python-pip' → keeps package even when dependencies change; action='mark_dependency', package_name='lib32-gcc-libs' → allows auto-removal with orphans.",
|
|
952
890
|
inputSchema={
|
|
953
891
|
"type": "object",
|
|
954
892
|
"properties": {
|
|
955
|
-
"
|
|
893
|
+
"action": {
|
|
956
894
|
"type": "string",
|
|
957
|
-
"
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
"required": ["package_name"]
|
|
961
|
-
},
|
|
962
|
-
annotations=ToolAnnotations(destructiveHint=True)
|
|
963
|
-
),
|
|
964
|
-
|
|
965
|
-
Tool(
|
|
966
|
-
name="mark_as_dependency",
|
|
967
|
-
description="[MAINTENANCE] 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. Use case: Mark 'lib32-gcc-libs' as dependency so it can be auto-removed with orphans later.",
|
|
968
|
-
inputSchema={
|
|
969
|
-
"type": "object",
|
|
970
|
-
"properties": {
|
|
895
|
+
"enum": ["list", "mark_explicit", "mark_dependency"],
|
|
896
|
+
"description": "Action to perform: 'list' (list explicit packages), 'mark_explicit' (mark as user-installed), or 'mark_dependency' (mark as auto-removable)"
|
|
897
|
+
},
|
|
971
898
|
"package_name": {
|
|
972
899
|
"type": "string",
|
|
973
|
-
"description": "
|
|
900
|
+
"description": "Package name (required for mark_explicit and mark_dependency actions)"
|
|
974
901
|
}
|
|
975
902
|
},
|
|
976
|
-
"required": ["
|
|
903
|
+
"required": ["action"]
|
|
977
904
|
},
|
|
978
|
-
annotations=ToolAnnotations(destructiveHint=
|
|
905
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False) # Mixed: list is read-only, marking is destructive
|
|
979
906
|
),
|
|
980
907
|
|
|
981
908
|
# System Diagnostic Tools
|
|
@@ -1334,66 +1261,36 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent |
|
|
|
1334
1261
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1335
1262
|
|
|
1336
1263
|
# Package Removal Tools
|
|
1337
|
-
elif name == "
|
|
1264
|
+
elif name == "remove_packages":
|
|
1338
1265
|
if not IS_ARCH:
|
|
1339
|
-
return [TextContent(type="text", text=create_platform_error_message("
|
|
1266
|
+
return [TextContent(type="text", text=create_platform_error_message("remove_packages"))]
|
|
1340
1267
|
|
|
1341
|
-
|
|
1268
|
+
packages = arguments["packages"]
|
|
1342
1269
|
remove_dependencies = arguments.get("remove_dependencies", False)
|
|
1343
1270
|
force = arguments.get("force", False)
|
|
1344
|
-
result = await
|
|
1345
|
-
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1346
|
-
|
|
1347
|
-
elif name == "remove_packages_batch":
|
|
1348
|
-
if not IS_ARCH:
|
|
1349
|
-
return [TextContent(type="text", text=create_platform_error_message("remove_packages_batch"))]
|
|
1350
|
-
|
|
1351
|
-
package_names = arguments["package_names"]
|
|
1352
|
-
remove_dependencies = arguments.get("remove_dependencies", False)
|
|
1353
|
-
result = await remove_packages_batch(package_names, remove_dependencies)
|
|
1271
|
+
result = await remove_packages(packages, remove_dependencies, force)
|
|
1354
1272
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1355
1273
|
|
|
1356
1274
|
# Orphan Package Management
|
|
1357
|
-
elif name == "
|
|
1358
|
-
if not IS_ARCH:
|
|
1359
|
-
return [TextContent(type="text", text=create_platform_error_message("list_orphan_packages"))]
|
|
1360
|
-
|
|
1361
|
-
result = await list_orphan_packages()
|
|
1362
|
-
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1363
|
-
|
|
1364
|
-
elif name == "remove_orphans":
|
|
1275
|
+
elif name == "manage_orphans":
|
|
1365
1276
|
if not IS_ARCH:
|
|
1366
|
-
return [TextContent(type="text", text=create_platform_error_message("
|
|
1277
|
+
return [TextContent(type="text", text=create_platform_error_message("manage_orphans"))]
|
|
1367
1278
|
|
|
1279
|
+
action = arguments["action"]
|
|
1368
1280
|
dry_run = arguments.get("dry_run", True)
|
|
1369
1281
|
exclude = arguments.get("exclude", None)
|
|
1370
|
-
result = await
|
|
1282
|
+
result = await manage_orphans(action, dry_run, exclude)
|
|
1371
1283
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1372
1284
|
|
|
1373
|
-
#
|
|
1374
|
-
elif name == "
|
|
1285
|
+
# File Ownership Query
|
|
1286
|
+
elif name == "query_file_ownership":
|
|
1375
1287
|
if not IS_ARCH:
|
|
1376
|
-
return [TextContent(type="text", text=create_platform_error_message("
|
|
1288
|
+
return [TextContent(type="text", text=create_platform_error_message("query_file_ownership"))]
|
|
1377
1289
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1381
|
-
|
|
1382
|
-
elif name == "list_package_files":
|
|
1383
|
-
if not IS_ARCH:
|
|
1384
|
-
return [TextContent(type="text", text=create_platform_error_message("list_package_files"))]
|
|
1385
|
-
|
|
1386
|
-
package_name = arguments["package_name"]
|
|
1290
|
+
query = arguments["query"]
|
|
1291
|
+
mode = arguments["mode"]
|
|
1387
1292
|
filter_pattern = arguments.get("filter_pattern", None)
|
|
1388
|
-
result = await
|
|
1389
|
-
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1390
|
-
|
|
1391
|
-
elif name == "search_package_files":
|
|
1392
|
-
if not IS_ARCH:
|
|
1393
|
-
return [TextContent(type="text", text=create_platform_error_message("search_package_files"))]
|
|
1394
|
-
|
|
1395
|
-
filename_pattern = arguments["filename_pattern"]
|
|
1396
|
-
result = await search_package_files(filename_pattern)
|
|
1293
|
+
result = await query_file_ownership(query, mode, filter_pattern)
|
|
1397
1294
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1398
1295
|
|
|
1399
1296
|
# Package Verification
|
|
@@ -1423,27 +1320,13 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent |
|
|
|
1423
1320
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1424
1321
|
|
|
1425
1322
|
# Install Reason Management
|
|
1426
|
-
elif name == "
|
|
1427
|
-
if not IS_ARCH:
|
|
1428
|
-
return [TextContent(type="text", text=create_platform_error_message("list_explicit_packages"))]
|
|
1429
|
-
|
|
1430
|
-
result = await list_explicit_packages()
|
|
1431
|
-
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1432
|
-
|
|
1433
|
-
elif name == "mark_as_explicit":
|
|
1323
|
+
elif name == "manage_install_reason":
|
|
1434
1324
|
if not IS_ARCH:
|
|
1435
|
-
return [TextContent(type="text", text=create_platform_error_message("
|
|
1325
|
+
return [TextContent(type="text", text=create_platform_error_message("manage_install_reason"))]
|
|
1436
1326
|
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
elif name == "mark_as_dependency":
|
|
1442
|
-
if not IS_ARCH:
|
|
1443
|
-
return [TextContent(type="text", text=create_platform_error_message("mark_as_dependency"))]
|
|
1444
|
-
|
|
1445
|
-
package_name = arguments["package_name"]
|
|
1446
|
-
result = await mark_as_dependency(package_name)
|
|
1327
|
+
action = arguments["action"]
|
|
1328
|
+
package_name = arguments.get("package_name", None)
|
|
1329
|
+
result = await manage_install_reason(action, package_name)
|
|
1447
1330
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
1448
1331
|
|
|
1449
1332
|
# System Diagnostic Tools
|
|
@@ -2107,7 +1990,7 @@ paru -S {package_name} # or yay -S {package_name}
|
|
|
2107
1990
|
text=f"""Please perform a comprehensive system cleanup:
|
|
2108
1991
|
|
|
2109
1992
|
1. **Check Orphaned Packages**:
|
|
2110
|
-
- Run
|
|
1993
|
+
- Run manage_orphans with action='list'
|
|
2111
1994
|
- Review the list for packages that can be safely removed
|
|
2112
1995
|
{' - Be aggressive: remove all orphans unless critical' if aggressive else ' - Be conservative: keep packages that might be useful'}
|
|
2113
1996
|
|
|
@@ -2303,7 +2186,7 @@ Be detailed and provide specific mirror URLs and configuration commands."""
|
|
|
2303
2186
|
- Identify any package operation failures
|
|
2304
2187
|
|
|
2305
2188
|
5. **Package Integrity**:
|
|
2306
|
-
- Run
|
|
2189
|
+
- Run manage_orphans with action='list'
|
|
2307
2190
|
- Count orphaned packages and space used
|
|
2308
2191
|
- Suggest running verify_package_integrity on critical packages
|
|
2309
2192
|
|
|
@@ -8,17 +8,19 @@ import logging
|
|
|
8
8
|
from typing import Dict, Any
|
|
9
9
|
|
|
10
10
|
from .utils import IS_ARCH
|
|
11
|
-
from . import (
|
|
11
|
+
from .system import (
|
|
12
12
|
get_system_info,
|
|
13
13
|
check_disk_space,
|
|
14
14
|
check_failed_services,
|
|
15
|
-
get_pacman_cache_stats
|
|
15
|
+
get_pacman_cache_stats
|
|
16
|
+
)
|
|
17
|
+
from .pacman import (
|
|
16
18
|
check_updates_dry_run,
|
|
17
|
-
check_critical_news,
|
|
18
19
|
list_orphan_packages,
|
|
19
|
-
check_database_freshness
|
|
20
|
-
check_mirrorlist_health
|
|
20
|
+
check_database_freshness
|
|
21
21
|
)
|
|
22
|
+
from .news import check_critical_news
|
|
23
|
+
from .mirrors import check_mirrorlist_health
|
|
22
24
|
|
|
23
25
|
logger = logging.getLogger(__name__)
|
|
24
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: arch-ops-server
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.2
|
|
4
4
|
Summary: MCP server bridging AI assistants with Arch Linux ecosystem (Wiki, AUR, official repos)
|
|
5
5
|
Keywords: arch-linux,mcp,model-context-protocol,aur,pacman,wiki,ai-assistant
|
|
6
6
|
Author: Nihal
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
arch_ops_server/__init__.py,sha256=
|
|
1
|
+
arch_ops_server/__init__.py,sha256=1N7UkiBwBxfPve-DXzSRkg1oOgWeZK24XPlh6Mh11W0,4539
|
|
2
2
|
arch_ops_server/aur.py,sha256=poYbh2DW7I1tZfCeNp_7e10fh9ZZx8HTnYZnKKZtflQ,49808
|
|
3
3
|
arch_ops_server/config.py,sha256=4mtpS28vXSMeEVGrTWTMwZEzgIyfl0oCAYEzF7SKxE8,11076
|
|
4
4
|
arch_ops_server/http_server.py,sha256=wZ3hY6o6EftbN1OZiTUau7861LB9ihKWap6gevev_No,31810
|
|
5
5
|
arch_ops_server/logs.py,sha256=qWExDvluHmQvbZHu87veQxMnuMK8BNLBYBppZJlemEc,10558
|
|
6
6
|
arch_ops_server/mirrors.py,sha256=Evt-g20cMOTZQl9FbbkbklFd0gKWz-I7vVNrmyQO19U,13403
|
|
7
7
|
arch_ops_server/news.py,sha256=E97eASR24tq_EaVDYuamIoBl4a7QtBkpscOaUPuU0W4,9359
|
|
8
|
-
arch_ops_server/pacman.py,sha256=
|
|
8
|
+
arch_ops_server/pacman.py,sha256=y0-0wrGx2bWjLyBRyLYOz1ogtQnH2RYjISC-MCJOB-k,46860
|
|
9
9
|
arch_ops_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
arch_ops_server/server.py,sha256=
|
|
10
|
+
arch_ops_server/server.py,sha256=sBkE0oaDE_bVFSMJXLQiC2owejgGMXW-soy0ME7dKGQ,89906
|
|
11
11
|
arch_ops_server/system.py,sha256=JfBUB3KD0veulQ-IIK8IOC8Jn6lqtLMCtlnryiL1n7w,9221
|
|
12
|
-
arch_ops_server/system_health_check.py,sha256=
|
|
12
|
+
arch_ops_server/system_health_check.py,sha256=81Li_9L_LMVhNZrFd89qhdXcbt17hFM7YHOoy903xuc,7254
|
|
13
13
|
arch_ops_server/tool_metadata.py,sha256=Z0ZhtS1bxo9T_erJlGx9Xf0fKk4oZ0L6UQ8gJu7WEcA,20468
|
|
14
14
|
arch_ops_server/utils.py,sha256=po7MVqCx-hsdx-lOgs7uGicjoUVMf6HvuNNYl2qyFH0,10112
|
|
15
15
|
arch_ops_server/wiki.py,sha256=XB_emMGXYF3Vn5likRICkGOa72YDZvOhtZBgp_d1gg8,7350
|
|
16
|
-
arch_ops_server-3.3.
|
|
17
|
-
arch_ops_server-3.3.
|
|
18
|
-
arch_ops_server-3.3.
|
|
19
|
-
arch_ops_server-3.3.
|
|
16
|
+
arch_ops_server-3.3.2.dist-info/WHEEL,sha256=5DEXXimM34_d4Gx1AuF9ysMr1_maoEtGKjaILM3s4w4,80
|
|
17
|
+
arch_ops_server-3.3.2.dist-info/entry_points.txt,sha256=ZS2crFEqE9TteC4j2HmYS1wKvoBOCCXT2FJXJW5C4-E,117
|
|
18
|
+
arch_ops_server-3.3.2.dist-info/METADATA,sha256=EuVUE9MwTQo75MK_CrfdI8FAGoqmn7pGbqVAppQR-FE,11356
|
|
19
|
+
arch_ops_server-3.3.2.dist-info/RECORD,,
|
|
File without changes
|