arch-ops-server 0.1.0__py3-none-any.whl → 0.1.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/aur.py +57 -0
- arch_ops_server/server.py +4 -4
- arch_ops_server/utils.py +44 -4
- {arch_ops_server-0.1.0.dist-info → arch_ops_server-0.1.2.dist-info}/METADATA +26 -6
- arch_ops_server-0.1.2.dist-info/RECORD +11 -0
- {arch_ops_server-0.1.0.dist-info → arch_ops_server-0.1.2.dist-info}/WHEEL +1 -1
- arch_ops_server-0.1.0.dist-info/RECORD +0 -11
- {arch_ops_server-0.1.0.dist-info → arch_ops_server-0.1.2.dist-info}/entry_points.txt +0 -0
arch_ops_server/aur.py
CHANGED
|
@@ -679,6 +679,41 @@ async def install_package_secure(package_name: str) -> Dict[str, Any]:
|
|
|
679
679
|
"messages": []
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
+
# ========================================================================
|
|
683
|
+
# STEP 0: Verify sudo is configured properly
|
|
684
|
+
# ========================================================================
|
|
685
|
+
logger.info("[STEP 0/5] Verifying sudo configuration...")
|
|
686
|
+
|
|
687
|
+
# Test if sudo password is cached or passwordless sudo is configured
|
|
688
|
+
# Use skip_sudo_check=True to avoid recursive check
|
|
689
|
+
test_exit_code, _, test_stderr = await run_command(
|
|
690
|
+
["sudo", "-n", "true"],
|
|
691
|
+
timeout=5,
|
|
692
|
+
check=False,
|
|
693
|
+
skip_sudo_check=True
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
if test_exit_code != 0:
|
|
697
|
+
result["messages"].append("⚠️ SUDO PASSWORD REQUIRED")
|
|
698
|
+
result["messages"].append("")
|
|
699
|
+
result["messages"].append("Package installation requires sudo privileges.")
|
|
700
|
+
result["messages"].append("Please choose one of these options:")
|
|
701
|
+
result["messages"].append("")
|
|
702
|
+
result["messages"].append("Option 1: Configure passwordless sudo for pacman:")
|
|
703
|
+
result["messages"].append(" sudo visudo -f /etc/sudoers.d/arch-package-install")
|
|
704
|
+
result["messages"].append(" Add: your_username ALL=(ALL) NOPASSWD: /usr/bin/pacman")
|
|
705
|
+
result["messages"].append("")
|
|
706
|
+
result["messages"].append("Option 2: Cache sudo password temporarily:")
|
|
707
|
+
result["messages"].append(" Run: sudo -v")
|
|
708
|
+
result["messages"].append(" Then retry the installation")
|
|
709
|
+
result["messages"].append("")
|
|
710
|
+
result["messages"].append("Option 3: Install manually in terminal:")
|
|
711
|
+
result["messages"].append(f" sudo pacman -S {package_name}")
|
|
712
|
+
result["security_checks"]["decision"] = "SUDO_REQUIRED"
|
|
713
|
+
return result
|
|
714
|
+
|
|
715
|
+
result["messages"].append("✅ Sudo privileges verified")
|
|
716
|
+
|
|
682
717
|
# ========================================================================
|
|
683
718
|
# STEP 1: Check if package is in official repos first
|
|
684
719
|
# ========================================================================
|
|
@@ -714,6 +749,17 @@ async def install_package_secure(package_name: str) -> Dict[str, Any]:
|
|
|
714
749
|
result["messages"].append(f"❌ Installation failed: {stderr}")
|
|
715
750
|
logger.error(f"pacman installation failed: {stderr}")
|
|
716
751
|
|
|
752
|
+
# Check for sudo password issues
|
|
753
|
+
if "password" in stderr.lower() or "sudo" in stderr.lower():
|
|
754
|
+
result["messages"].append("")
|
|
755
|
+
result["messages"].append("⚠️ SUDO PASSWORD REQUIRED")
|
|
756
|
+
result["messages"].append("To enable passwordless installation, run one of these commands:")
|
|
757
|
+
result["messages"].append("1. For passwordless sudo (less secure):")
|
|
758
|
+
result["messages"].append(" sudo visudo -f /etc/sudoers.d/arch-package-install")
|
|
759
|
+
result["messages"].append(" Add: your_username ALL=(ALL) NOPASSWD: /usr/bin/pacman")
|
|
760
|
+
result["messages"].append("2. Or run the installation manually in your terminal:")
|
|
761
|
+
result["messages"].append(f" sudo pacman -S {package_name}")
|
|
762
|
+
|
|
717
763
|
result["install_output"] = stdout
|
|
718
764
|
result["install_errors"] = stderr
|
|
719
765
|
|
|
@@ -846,6 +892,17 @@ async def install_package_secure(package_name: str) -> Dict[str, Any]:
|
|
|
846
892
|
result["messages"].append(f" Error: {stderr}")
|
|
847
893
|
result["security_checks"]["decision"] = "INSTALL_FAILED"
|
|
848
894
|
logger.error(f"AUR installation failed for {package_name}: {stderr}")
|
|
895
|
+
|
|
896
|
+
# Check for sudo password issues
|
|
897
|
+
if "password" in stderr.lower() or "sudo" in stderr.lower():
|
|
898
|
+
result["messages"].append("")
|
|
899
|
+
result["messages"].append("⚠️ SUDO PASSWORD REQUIRED")
|
|
900
|
+
result["messages"].append("To enable passwordless installation for AUR packages:")
|
|
901
|
+
result["messages"].append("1. For passwordless sudo for pacman:")
|
|
902
|
+
result["messages"].append(" sudo visudo -f /etc/sudoers.d/arch-aur-install")
|
|
903
|
+
result["messages"].append(" Add: your_username ALL=(ALL) NOPASSWD: /usr/bin/pacman")
|
|
904
|
+
result["messages"].append("2. Or run the installation manually in your terminal:")
|
|
905
|
+
result["messages"].append(f" {aur_helper} -S {package_name}")
|
|
849
906
|
|
|
850
907
|
result["install_output"] = stdout
|
|
851
908
|
result["install_errors"] = stderr
|
arch_ops_server/server.py
CHANGED
|
@@ -376,12 +376,12 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent |
|
|
|
376
376
|
|
|
377
377
|
elif name == "analyze_pkgbuild_safety":
|
|
378
378
|
pkgbuild_content = arguments["pkgbuild_content"]
|
|
379
|
-
result =
|
|
379
|
+
result = analyze_pkgbuild_safety(pkgbuild_content)
|
|
380
380
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
381
381
|
|
|
382
382
|
elif name == "analyze_package_metadata_risk":
|
|
383
383
|
package_info = arguments["package_info"]
|
|
384
|
-
result =
|
|
384
|
+
result = analyze_package_metadata_risk(package_info)
|
|
385
385
|
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
386
386
|
|
|
387
387
|
else:
|
|
@@ -513,8 +513,8 @@ async def get_prompt(name: str, arguments: dict[str, str]) -> GetPromptResult:
|
|
|
513
513
|
pkgbuild_content = await get_pkgbuild(package_name)
|
|
514
514
|
|
|
515
515
|
# Analyze both metadata and PKGBUILD
|
|
516
|
-
metadata_risk =
|
|
517
|
-
pkgbuild_safety =
|
|
516
|
+
metadata_risk = analyze_package_metadata_risk(package_info)
|
|
517
|
+
pkgbuild_safety = analyze_pkgbuild_safety(pkgbuild_content)
|
|
518
518
|
|
|
519
519
|
audit_summary = f"""
|
|
520
520
|
# Security Audit Report for {package_name}
|
arch_ops_server/utils.py
CHANGED
|
@@ -57,15 +57,20 @@ IS_ARCH = is_arch_linux()
|
|
|
57
57
|
async def run_command(
|
|
58
58
|
cmd: list[str],
|
|
59
59
|
timeout: int = 10,
|
|
60
|
-
check: bool = True
|
|
60
|
+
check: bool = True,
|
|
61
|
+
skip_sudo_check: bool = False
|
|
61
62
|
) -> tuple[int, str, str]:
|
|
62
63
|
"""
|
|
63
64
|
Execute a command asynchronously with timeout protection.
|
|
64
65
|
|
|
66
|
+
Note: For sudo commands, stdin is properly connected to allow password input
|
|
67
|
+
if passwordless sudo is not configured.
|
|
68
|
+
|
|
65
69
|
Args:
|
|
66
70
|
cmd: Command and arguments as list
|
|
67
71
|
timeout: Timeout in seconds (default: 10)
|
|
68
72
|
check: If True, raise exception on non-zero exit code
|
|
73
|
+
skip_sudo_check: If True, skip the early sudo password check (for testing)
|
|
69
74
|
|
|
70
75
|
Returns:
|
|
71
76
|
Tuple of (exit_code, stdout, stderr)
|
|
@@ -76,21 +81,56 @@ async def run_command(
|
|
|
76
81
|
"""
|
|
77
82
|
logger.debug(f"Executing command: {' '.join(cmd)}")
|
|
78
83
|
|
|
84
|
+
# Check if this is a sudo command and if password is cached
|
|
85
|
+
is_sudo_command = cmd and cmd[0] == "sudo"
|
|
86
|
+
if is_sudo_command and not skip_sudo_check:
|
|
87
|
+
# Test if sudo password is cached (non-interactive mode)
|
|
88
|
+
test_cmd = ["sudo", "-n", "true"]
|
|
89
|
+
try:
|
|
90
|
+
test_process = await asyncio.create_subprocess_exec(
|
|
91
|
+
*test_cmd,
|
|
92
|
+
stdout=asyncio.subprocess.PIPE,
|
|
93
|
+
stderr=asyncio.subprocess.PIPE
|
|
94
|
+
)
|
|
95
|
+
await test_process.communicate()
|
|
96
|
+
password_cached = test_process.returncode == 0
|
|
97
|
+
logger.debug(f"Sudo password cached: {password_cached}")
|
|
98
|
+
|
|
99
|
+
if not password_cached:
|
|
100
|
+
logger.warning("Sudo password is required but not cached. "
|
|
101
|
+
"Please run 'sudo pacman -S <package>' manually in the terminal.")
|
|
102
|
+
return (
|
|
103
|
+
1,
|
|
104
|
+
"",
|
|
105
|
+
"Sudo password required. Please configure passwordless sudo for pacman/paru, "
|
|
106
|
+
"or run the installation command manually in your terminal."
|
|
107
|
+
)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.warning(f"Could not check sudo status: {e}")
|
|
110
|
+
password_cached = False
|
|
111
|
+
else:
|
|
112
|
+
password_cached = True
|
|
113
|
+
|
|
79
114
|
try:
|
|
115
|
+
# Attach stdin to subprocess for commands that might need input
|
|
116
|
+
# Use asyncio.subprocess.PIPE to allow stdin interaction
|
|
80
117
|
process = await asyncio.create_subprocess_exec(
|
|
81
118
|
*cmd,
|
|
82
119
|
stdout=asyncio.subprocess.PIPE,
|
|
83
|
-
stderr=asyncio.subprocess.PIPE
|
|
120
|
+
stderr=asyncio.subprocess.PIPE,
|
|
121
|
+
stdin=asyncio.subprocess.PIPE if is_sudo_command else None
|
|
84
122
|
)
|
|
85
123
|
|
|
124
|
+
# Communicate with the process
|
|
125
|
+
# For sudo commands, this allows password input if needed
|
|
86
126
|
stdout, stderr = await asyncio.wait_for(
|
|
87
127
|
process.communicate(),
|
|
88
128
|
timeout=timeout
|
|
89
129
|
)
|
|
90
130
|
|
|
91
131
|
exit_code = process.returncode
|
|
92
|
-
stdout_str = stdout.decode('utf-8', errors='replace')
|
|
93
|
-
stderr_str = stderr.decode('utf-8', errors='replace')
|
|
132
|
+
stdout_str = stdout.decode('utf-8', errors='replace') if stdout else ""
|
|
133
|
+
stderr_str = stderr.decode('utf-8', errors='replace') if stderr else ""
|
|
94
134
|
|
|
95
135
|
logger.debug(f"Command exit code: {exit_code}")
|
|
96
136
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: arch-ops-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.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
|
|
@@ -26,6 +26,8 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
|
|
27
27
|
# Arch Linux MCP Server
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
|
|
29
31
|
**Disclaimer:** Unofficial community project, not affiliated with Arch Linux.
|
|
30
32
|
|
|
31
33
|
A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that bridges AI assistants with the Arch Linux ecosystem. Enables intelligent, safe, and efficient access to the Arch Wiki, AUR, and official repositories for AI-assisted Arch Linux usage on Arch and non-Arch systems.
|
|
@@ -36,6 +38,20 @@ Leverage AI to get output for digestible, structured results that are ready for
|
|
|
36
38
|
|
|
37
39
|
## Sneak Peak into what's available
|
|
38
40
|
|
|
41
|
+
<details open>
|
|
42
|
+
<summary>Claude Desktop (no terminal)</summary>
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
</details>
|
|
47
|
+
|
|
48
|
+
<details>
|
|
49
|
+
<summary>VS Code (with terminal)</summary>
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
</details>
|
|
54
|
+
|
|
39
55
|
### Resources (URI-based Access)
|
|
40
56
|
|
|
41
57
|
Direct access to Arch ecosystem data via custom URI schemes:
|
|
@@ -81,7 +97,6 @@ Direct access to Arch ecosystem data via custom URI schemes:
|
|
|
81
97
|
```bash
|
|
82
98
|
uvx arch-ops-server
|
|
83
99
|
```
|
|
84
|
-
|
|
85
100
|
---
|
|
86
101
|
|
|
87
102
|
## Configuration
|
|
@@ -99,11 +114,16 @@ Claude / Cursor / Any MCP client that supports STDIO transport
|
|
|
99
114
|
}
|
|
100
115
|
```
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
If you are using Cursor, you can easily install and configure the MCP server by clicking the button below:
|
|
103
118
|
|
|
104
|
-
[
|
|
119
|
+
[](cursor://anysphere.cursor-deeplink/mcp/install?name=arch-ops&config=eyJjb21tYW5kIjogInV2eCIsICJhcmdzIjogWyJhcmNoLW9wcy1zZXJ2ZXIiXX0=)
|
|
105
120
|
|
|
106
|
-
|
|
121
|
+
## Contributing
|
|
107
122
|
|
|
123
|
+
Contibutions are greatly appreciated. Please feel free to submit a pull request or open an issue and help make things better for everyone.
|
|
124
|
+
|
|
125
|
+
[Contributing Guide](https://nxk.mintlify.app/arch-mcp/contributing)
|
|
126
|
+
|
|
127
|
+
## License
|
|
108
128
|
|
|
109
|
-
|
|
129
|
+
[GPL-3.0-only](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
arch_ops_server/__init__.py,sha256=m0KiRT4iLqY5eyP3Ul-anwQVdpjTghWWW1QOqk6n1NI,1906
|
|
2
|
+
arch_ops_server/aur.py,sha256=8Ktzz9_5tXiJdRuZvTSxqUU1N8i7kJkx4jxQiDLqM-c,49761
|
|
3
|
+
arch_ops_server/pacman.py,sha256=nD1B8KgHuwaxHtcO4sd1WpiFcpocjD6yxs1JtVkTjPU,9862
|
|
4
|
+
arch_ops_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
arch_ops_server/server.py,sha256=hN3MT_BkRwbCd5Yo7R6-KolPHxy8U3kbJoBqDjLxw3Y,25188
|
|
6
|
+
arch_ops_server/utils.py,sha256=pxsdzdMmcXUDdxe4sVITlHhMPqu6rx74HemtGQW-zp4,10065
|
|
7
|
+
arch_ops_server/wiki.py,sha256=P6znxzV2e9JVUq8yyD0e9pP0Fm7EkS9vmDb2HUdwQpc,7303
|
|
8
|
+
arch_ops_server-0.1.2.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
|
|
9
|
+
arch_ops_server-0.1.2.dist-info/entry_points.txt,sha256=nD6HtiLT-Xh1b63_LGcYNEjHqVlal7I2d5jeFJMtfiU,63
|
|
10
|
+
arch_ops_server-0.1.2.dist-info/METADATA,sha256=LwXzEy35x0g_e63wzfDHX-aWnIc8Gjq7E_d3_-D8N9g,4916
|
|
11
|
+
arch_ops_server-0.1.2.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
arch_ops_server/__init__.py,sha256=m0KiRT4iLqY5eyP3Ul-anwQVdpjTghWWW1QOqk6n1NI,1906
|
|
2
|
-
arch_ops_server/aur.py,sha256=_BoPsKyBPa2KWi9i1J_RSsqUT53wU03guS0V1sasOXg,46328
|
|
3
|
-
arch_ops_server/pacman.py,sha256=nD1B8KgHuwaxHtcO4sd1WpiFcpocjD6yxs1JtVkTjPU,9862
|
|
4
|
-
arch_ops_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
arch_ops_server/server.py,sha256=D_8uypCGmWgTjw5VThrsBY_XxrDjHmdVJT6D39Be-Sw,25212
|
|
6
|
-
arch_ops_server/utils.py,sha256=-v47tnfxRP-IqvKeqhpAIYVIPhTIh5DMe9jWvCMNRGM,8145
|
|
7
|
-
arch_ops_server/wiki.py,sha256=P6znxzV2e9JVUq8yyD0e9pP0Fm7EkS9vmDb2HUdwQpc,7303
|
|
8
|
-
arch_ops_server-0.1.0.dist-info/WHEEL,sha256=k57ZwB-NkeM_6AsPnuOHv5gI5KM5kPD6Vx85WmGEcI0,78
|
|
9
|
-
arch_ops_server-0.1.0.dist-info/entry_points.txt,sha256=nD6HtiLT-Xh1b63_LGcYNEjHqVlal7I2d5jeFJMtfiU,63
|
|
10
|
-
arch_ops_server-0.1.0.dist-info/METADATA,sha256=tEmFaZ5X5emaz6rLExwi-QieYk8StI-N0sz0ezSacnk,4183
|
|
11
|
-
arch_ops_server-0.1.0.dist-info/RECORD,,
|
|
File without changes
|