44
55from pathlib import Path
66
7+ from fastmcp .exceptions import ToolError
78from mcp .types import ToolAnnotations
89from pydantic import Field
10+ from pydantic .functional_validators import BeforeValidator
911
1012from linux_mcp_server .audit import log_tool_call
1113from linux_mcp_server .commands import get_command
1719from linux_mcp_server .utils .decorators import disallow_local_execution_in_containers
1820from linux_mcp_server .utils .types import Host
1921from linux_mcp_server .utils .validation import is_empty_output
20- from linux_mcp_server .utils .validation import PathValidationError
2122from linux_mcp_server .utils .validation import validate_path
2223
2324
@@ -143,9 +144,10 @@ async def get_journal_logs(
143144)
144145@log_tool_call
145146@disallow_local_execution_in_containers
146- async def read_log_file ( # noqa: C901
147+ async def read_log_file (
147148 log_path : t .Annotated [
148- str ,
149+ Path ,
150+ BeforeValidator (validate_path ),
149151 Field (
150152 description = "Absolute path to the log file (must be in allowed list)" ,
151153 examples = ["/var/log/messages" , "/var/log/secure" , "/var/log/audit/audit.log" , "/var/log/dnf.log" ],
@@ -163,22 +165,16 @@ async def read_log_file( # noqa: C901
163165 allowed_paths_env = CONFIG .allowed_log_paths
164166
165167 if not allowed_paths_env :
166- return (
168+ raise ToolError (
167169 "No log files are allowed. Set LINUX_MCP_ALLOWED_LOG_PATHS environment variable "
168170 "with comma-separated list of allowed log file paths."
169171 )
170172
171- allowed_paths = [p .strip () for p in allowed_paths_env .split ("," ) if p .strip ()]
172-
173- # Validate path for injection attacks (applies to both local and remote)
174- try :
175- validated_path = validate_path (log_path )
176- except PathValidationError as e :
177- return f"Invalid log file path: { e } "
173+ allowed_paths = [Path (p .strip ()) for p in allowed_paths_env .split ("," ) if p .strip ()]
178174
179175 if not host :
180176 # For local execution, resolve and check against allowlist
181- requested_path = Path ( validated_path ) .resolve ()
177+ requested_path = log_path .resolve ()
182178
183179 is_allowed = False
184180 for allowed_path in allowed_paths :
@@ -188,36 +184,32 @@ async def read_log_file( # noqa: C901
188184 break
189185
190186 if not is_allowed :
191- return (
192- f"Access to log file '{ log_path } ' is not allowed.\n "
193- f"Allowed log files: { ', ' .join (allowed_paths )} "
194- ) # nofmt
187+ raise ToolError (f"Access to log file '{ log_path } ' is not allowed." )
195188
196189 if not requested_path .exists ():
197- return f"Log file not found: { log_path } "
190+ raise ToolError ( f"Log file not found: { log_path } " )
198191
199192 if not requested_path .is_file ():
200- return f"Path is not a file: { log_path } "
193+ raise ToolError ( f"Path is not a file: { log_path } " )
201194
202195 log_path_str = str (requested_path )
203196 else :
204197 # For remote execution, check against allowlist without resolving
205- if validated_path not in allowed_paths :
206- return (
207- f"Access to log file '{ log_path } ' is not allowed.\n "
208- f"Allowed log files: { ', ' .join (allowed_paths )} "
209- ) # nofmt
210- log_path_str = validated_path
198+ if log_path not in allowed_paths :
199+ raise ToolError (f"Access to log file '{ log_path } ' is not allowed." )
200+
201+ log_path_str = str (log_path )
211202
212203 cmd = get_command ("read_log_file" )
213204 returncode , stdout , stderr = await cmd .run (host = host , lines = lines , log_path = log_path_str )
214205
215206 if returncode != 0 :
216207 if "Permission denied" in stderr :
217- return f"Permission denied reading log file: { log_path } "
218- return f"Error reading log file: { stderr } "
208+ raise ToolError (f"Permission denied reading log file: { log_path } " )
209+
210+ raise ToolError (f"Error reading log file: { stderr } " )
219211
220212 if is_empty_output (stdout ):
221- return f"Log file is empty: { log_path } "
213+ raise ToolError ( f"Log file is empty: { log_path } " )
222214
223215 return format_log_file (stdout , log_path , lines )
0 commit comments