File Coverage

blib/lib/MCP/Tool.pm
Criterion Covered Total %
statement 79 79 100.0
branch 12 18 66.6
condition 11 20 55.0
subroutine 15 15 100.0
pod 8 8 100.0
total 125 140 89.2


line stmt bran cond sub pod time code
1             package MCP::Tool;
2 2     2   16 use Mojo::Base -base, -signatures;
  2         6  
  2         15  
3              
4 2     2   1889 use JSON::Validator;
  2         91157  
  2         12  
5 2     2   87 use Mojo::JSON qw(false to_json true);
  2         2  
  2         136  
6 2     2   9 use Mojo::Util qw(b64_encode);
  2         4  
  2         92  
7 2     2   38 use Scalar::Util qw(blessed);
  2         6  
  2         2032  
8              
9             has annotations => sub { {} };
10             has code => sub { die 'Tool code not implemented' };
11             has description => 'Generic MCP tool';
12             has input_schema => sub { {type => 'object'} };
13             has name => 'tool';
14             has 'output_schema';
15              
16 1     1 1 627 sub audio_result ($self, $audio, $options = {}, $is_error = 0) {
  1         3  
  1         4  
  1         4  
  1         3  
  1         3  
17             return {
18 1 50 50     39 content => [{type => 'audio', data => b64_encode($audio, ''), mimeType => $options->{mime_type} // 'audio/wav'}],
19             isError => $is_error ? true : false
20             };
21             }
22              
23 14     14 1 39 sub call ($self, $args, $context) {
  14         33  
  14         40  
  14         24  
  14         26  
24 14         81 local $self->{context} = $context;
25 14         68 my $result = $self->code->($self, $args);
26 14 100 66 2   700 return $result->then(sub { $self->_type_check($_[0]) }) if blessed($result) && $result->isa('Mojo::Promise');
  2         997676  
27 12         55 return $self->_type_check($result);
28             }
29              
30 1 50   1 1 19 sub context ($self) { $self->{context} || {} }
  1         3  
  1         3  
  1         6  
31              
32 1     1 1 2429 sub image_result ($self, $image, $options = {}, $is_error = 0) {
  1         3  
  1         2  
  1         3  
  1         3  
  1         2  
33             return {
34             content => [{
35             type => 'image',
36             data => b64_encode($image, ''),
37             mimeType => $options->{mime_type} // 'image/png',
38             annotations => $options->{annotations} // {}
39 1 50 50     64 }],
      50        
40             isError => $is_error ? true : false
41             };
42             }
43              
44 1     1 1 29 sub resource_link_result ($self, $uri, $options = {}, $is_error = 0) {
  1         3  
  1         3  
  1         3  
  1         3  
  1         2  
45             return {
46             content => [{
47             type => 'resource_link',
48             uri => $uri,
49             name => $options->{name} // '',
50             description => $options->{description} // '',
51             mimeType => $options->{mime_type} // 'text/plain',
52             annotations => $options->{annotations} // {}
53 1 50 50     43 }],
      50        
      50        
      50        
54             isError => $is_error ? true : false
55             };
56             }
57              
58 2     2 1 62 sub structured_result ($self, $data, $is_error = 0) {
  2         5  
  2         5  
  2         5  
  2         5  
59 2         15 my $result = $self->text_result(to_json($data), $is_error);
60 2         36 $result->{structuredContent} = $data;
61 2         9 return $result;
62             }
63              
64 11     11 1 66 sub text_result ($self, $text, $is_error = 0) {
  11         23  
  11         29  
  11         19  
  11         21  
65 11 50       135 return {content => [{type => 'text', text => "$text"}], isError => $is_error ? true : false};
66             }
67              
68 15     15 1 31 sub validate_input ($self, $args) {
  15         30  
  15         29  
  15         25  
69 15 100       91 unless ($self->{validator}) {
70 10         142 my $validator = $self->{validator} = JSON::Validator->new;
71 10         230 $validator->schema($self->input_schema);
72             }
73              
74 15         12840 my @errors = $self->{validator}->validate($args);
75 15 100       6353 return @errors ? 1 : 0;
76             }
77              
78 14     14   32 sub _type_check ($self, $result) {
  14         34  
  14         27  
  14         40  
79 14 50 66     136 return $result if ref $result eq 'HASH' && exists $result->{content};
80 9         40 return $self->text_result($result);
81             }
82              
83             1;
84              
85             =encoding utf8
86              
87             =head1 NAME
88              
89             MCP::Tool - Tool container
90              
91             =head1 SYNOPSIS
92              
93             use MCP::Tool;
94              
95             my $tool = MCP::Tool->new;
96              
97             =head1 DESCRIPTION
98              
99             L is a container for tools to be called.
100              
101             =head1 ATTRIBUTES
102              
103             L implements the following attributes.
104              
105             =head2 annotations
106              
107             my $annotations = $tool->annotations;
108             $tool = $tool->annotations({title => '...'});
109              
110             Optional annotations for the tool which provide additional metadata about the tool behavior.
111              
112             =head2 code
113              
114             my $code = $tool->code;
115             $tool = $tool->code(sub { ... });
116              
117             Tool code.
118              
119             =head2 description
120              
121             my $description = $tool->description;
122             $tool = $tool->description('A brief description of the tool');
123              
124             Description of the tool.
125              
126             =head2 input_schema
127              
128             my $schema = $tool->input_schema;
129             $tool = $tool->input_schema({type => 'object', properties => {foo => {type => 'string'}}});
130              
131             JSON schema for validating input arguments.
132              
133             =head2 name
134              
135             my $name = $tool->name;
136             $tool = $tool->name('my_tool');
137              
138             Name of the tool.
139              
140             =head2 output_schema
141              
142             my $schema = $tool->output_schema;
143             $tool = $tool->output_schema({type => 'object', properties => {foo => {type => 'string'}}});
144              
145             JSON schema for validating output results.
146              
147             =head1 METHODS
148              
149             L inherits all methods from L and implements the following new ones.
150              
151             =head2 audio_result
152              
153             my $result = $tool->audio_result($bytes, $options, $is_error);
154              
155             Returns an audio result in the expected format, optionally marking it as an error.
156              
157             These options are currently available:
158              
159             =over 2
160              
161             =item mime_type
162              
163             mime_type => 'audio/wav'
164              
165             Specifies the MIME type of the audio, defaults to C
166              
167             =back
168              
169             =head2 call
170              
171             my $result = $tool->call($args, $context);
172              
173             Calls the tool with the given arguments and context, returning a result. The result can be a promise or a direct value.
174              
175             =head2 context
176              
177             my $context = $tool->context;
178              
179             Returns the context in which the tool is executed.
180              
181             # Get controller for requests using the HTTP transport
182             my $c = $tool->context->{controller};
183              
184             =head2 image_result
185              
186             my $result = $tool->image_result($bytes, $options, $is_error);
187              
188             Returns an image result in the expected format, optionally marking it as an error.
189              
190             These options are currently available:
191              
192             =over 2
193              
194             =item annotations
195              
196             annotations => {audience => ['user']}
197              
198             Annotations for the image.
199              
200             =item mime_type
201              
202             mime_type => 'image/png'
203              
204             Specifies the MIME type of the image, defaults to C.
205              
206             =back
207              
208             =head2 resource_link_result
209              
210             my $result = $tool->resource_link_result($uri, $options, $is_error);
211              
212             Returns a resource link result in the expected format, optionally marking it as an error.
213              
214             These options are currently available:
215              
216             =over 2
217              
218             =item annotations
219              
220             annotations => {audience => ['user']}
221              
222             Annotations for the resource link.
223              
224             =item description
225              
226             description => 'A brief description of the resource'
227              
228             Description of the resource.
229              
230             =item mime_type
231              
232             mime_type => 'text/x-perl'
233              
234             Specifies the MIME type of the resource, defaults to C.
235              
236             =item name
237              
238             name => 'Resource Name'
239              
240             Name of the resource.
241              
242             =back
243              
244             =head2 structured_result
245              
246             my $result = $tool->structured_result({foo => 'bar'}, $is_error);
247              
248             Returns a structured result in the format of L, optionally marking it as an error.
249              
250             =head2 text_result
251              
252             my $result = $tool->text_result('Some text', $is_error);
253              
254             Returns a text result in the expected format, optionally marking it as an error.
255              
256             =head2 validate_input
257              
258             my $bool = $tool->validate_input($args);
259              
260             Validates the input arguments against the tool's input schema. Returns true if validation failed.
261              
262             =head1 SEE ALSO
263              
264             L, L, L.
265              
266             =cut