File Coverage

blib/lib/MCP/Run/Bash.pm
Criterion Covered Total %
statement 63 66 95.4
branch 11 12 91.6
condition 3 6 50.0
subroutine 7 7 100.0
pod 1 1 100.0
total 85 92 92.3


line stmt bran cond sub pod time code
1             package MCP::Run::Bash;
2             our $VERSION = '0.001';
3 3     3   280686 use Mojo::Base 'MCP::Run', -signatures;
  3         30082  
  3         23  
4              
5             # ABSTRACT: MCP server that executes commands via bash
6              
7              
8 3     3   2908 use IPC::Open3;
  3         12444  
  3         256  
9 3     3   1911 use IO::Select;
  3         6612  
  3         247  
10 3     3   27 use Symbol 'gensym';
  3         9  
  3         219  
11 3     3   27 use POSIX ':sys_wait_h';
  3         8  
  3         39  
12              
13 8     8 1 39 sub execute ($self, $command, $working_directory, $timeout) {
  8         16  
  8         26  
  8         19  
  8         15  
  8         12  
14 8         16 my $full_command = $command;
15 8 100 66     57 if (defined $working_directory && length $working_directory) {
16 2         9 my $escaped = $working_directory;
17 2         18 $escaped =~ s/'/'\\''/g;
18 2         14 $full_command = "cd '$escaped' && $command";
19             }
20              
21 8         25 my ($stdout, $stderr) = ('', '');
22 8         54 my ($exit_code, $error);
23              
24 8         39 eval {
25 8         90 my $err = gensym;
26 8         264 my $pid = open3(my $in, my $out, $err, 'bash', '-c', $full_command);
27 8         104697 close $in;
28              
29 8         259 my $select = IO::Select->new($out, $err);
30 8         1256 my $timed_out = 0;
31              
32 8     1   429 local $SIG{ALRM} = sub { $timed_out = 1; die "alarm\n" };
  1         1000150  
  1         26  
33 8         93 alarm($timeout);
34              
35 8         39 eval {
36 8         85 while (my @ready = $select->can_read) {
37 14         19611 for my $fh (@ready) {
38 20         44 my $buf;
39 20         11326 my $bytes = sysread($fh, $buf, 4096);
40 20 100       146 if (!$bytes) {
41 14         93 $select->remove($fh);
42 14         906 next;
43             }
44 6 100       68 if ($fh == $out) { $stdout .= $buf }
  5         61  
45 1         23 else { $stderr .= $buf }
46             }
47             }
48             };
49              
50 8         157 alarm(0);
51              
52 8 100       63 if ($timed_out) {
53 1         58 kill 'TERM', $pid;
54 1         303 waitpid($pid, 0);
55 1         10 $exit_code = 124;
56 1         9 $error = "Command timed out after ${timeout}s";
57             }
58             else {
59 7         198 waitpid($pid, 0);
60 7         76 $exit_code = $? >> 8;
61             }
62              
63 8         197 close $out;
64 8         422 close $err;
65             };
66              
67 8 50 33     64 if ($@ && !defined $exit_code) {
68 0         0 $exit_code = 1;
69 0         0 $error = "$@";
70 0         0 chomp $error;
71             }
72              
73 8         32 chomp $stdout;
74 8         28 chomp $stderr;
75              
76 8 100       174 return { exit_code => $exit_code, stdout => $stdout, stderr => $stderr, (defined $error ? (error => $error) : ()) };
77             }
78              
79              
80              
81             1;
82              
83             __END__