File Coverage

blib/lib/MCP/Tool.pm
Criterion Covered Total %
statement 73 73 100.0
branch 12 16 75.0
condition 11 20 55.0
subroutine 14 14 100.0
pod 7 7 100.0
total 117 130 90.0


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