File Coverage

blib/lib/Net/Async/MCP.pm
Criterion Covered Total %
statement 72 87 82.7
branch 10 18 55.5
condition 4 9 44.4
subroutine 17 20 85.0
pod 12 12 100.0
total 115 146 78.7


line stmt bran cond sub pod time code
1             package Net::Async::MCP;
2             # ABSTRACT: Async MCP (Model Context Protocol) client for IO::Async
3              
4 3     3   981395 use strict;
  3         8  
  3         150  
5 3     3   29 use warnings;
  3         8  
  3         211  
6 3     3   1046 use parent 'IO::Async::Notifier';
  3         806  
  3         47  
7              
8 3     3   58242 use Future::AsyncAwait;
  3         12386  
  3         17  
9 3     3   217 use Carp qw( croak );
  3         7  
  3         6446  
10              
11             our $VERSION = '0.002';
12              
13              
14             sub _init {
15 2     2   1647577 my ( $self, $params ) = @_;
16 2         7 for my $key (qw( server command url )) {
17 6 100       27 $self->{$key} = delete $params->{$key} if exists $params->{$key};
18             }
19 2         6 $self->{_initialized} = 0;
20 2         14 $self->SUPER::_init($params);
21             }
22              
23             sub configure {
24 2     2 1 17 my ( $self, %params ) = @_;
25 2         5 for my $key (qw( server command url )) {
26 6 50       15 $self->{$key} = delete $params{$key} if exists $params{$key};
27             }
28 2         11 $self->SUPER::configure(%params);
29             }
30              
31             sub _add_to_loop {
32 2     2   200 my ( $self, $loop ) = @_;
33 2         13 $self->SUPER::_add_to_loop($loop);
34 2         10 $self->_ensure_transport;
35             }
36              
37             sub _ensure_transport {
38 4     4   13 my ( $self ) = @_;
39 4 100       27 return if $self->{transport};
40              
41 2 100       9 if ($self->{server}) {
    50          
    0          
42 1         751 require Net::Async::MCP::Transport::InProcess;
43             $self->{transport} = Net::Async::MCP::Transport::InProcess->new(
44             server => $self->{server},
45 1         14 );
46             }
47             elsif ($self->{command}) {
48 1 50       3 croak "Stdio transport requires being added to an IO::Async::Loop"
49             unless $self->loop;
50 1         524 require Net::Async::MCP::Transport::Stdio;
51             my $transport = Net::Async::MCP::Transport::Stdio->new(
52             command => $self->{command},
53 1         15 );
54 1         13 $self->{transport} = $transport;
55 1         5 $self->add_child($transport);
56             }
57             elsif ($self->{url}) {
58 0 0       0 croak "HTTP transport requires being added to an IO::Async::Loop"
59             unless $self->loop;
60 0         0 require Net::Async::MCP::Transport::HTTP;
61             my $transport = Net::Async::MCP::Transport::HTTP->new(
62             url => $self->{url},
63 0         0 );
64 0         0 $self->{transport} = $transport;
65 0         0 $self->add_child($transport);
66             }
67             else {
68 0         0 croak "Must provide server, command, or url";
69             }
70             }
71              
72 2     2 1 16672 sub server_info { $_[0]->{server_info} }
73              
74              
75 0     0 1 0 sub server_capabilities { $_[0]->{server_capabilities} }
76              
77              
78 2     2 1 58056 async sub initialize {
79 2         12 my ( $self ) = @_;
80 2         42 $self->_ensure_transport;
81              
82 2         38 my $result = await $self->{transport}->send_request('initialize', {
83             protocolVersion => '2025-11-25',
84             capabilities => {},
85             clientInfo => {
86             name => 'Net::Async::MCP',
87             version => $VERSION,
88             },
89             });
90              
91 2         255 $self->{server_info} = $result->{serverInfo};
92 2         6 $self->{server_capabilities} = $result->{capabilities};
93 2         6 $self->{_initialized} = 1;
94              
95 2         11 await $self->{transport}->send_notification('notifications/initialized');
96              
97 2         161 return $result;
98             }
99              
100              
101 2     2 1 458 async sub list_tools {
102 2         6 my ( $self ) = @_;
103 2         7 my $result = await $self->{transport}->send_request('tools/list');
104 2   50     126 return $result->{tools} // [];
105             }
106              
107              
108 10     10 1 4565 async sub call_tool {
109 10         25 my ( $self, $name, $arguments ) = @_;
110 10         51 my $result = await $self->{transport}->send_request('tools/call', {
111             name => $name,
112             arguments => $arguments // {},
113             });
114 9         574 return $result;
115             }
116              
117              
118 1     1 1 865 async sub list_prompts {
119 1         2 my ( $self ) = @_;
120 1         4 my $result = await $self->{transport}->send_request('prompts/list');
121 1   50     38 return $result->{prompts} // [];
122             }
123              
124              
125 0     0 1 0 async sub get_prompt {
126 0         0 my ( $self, $name, $arguments ) = @_;
127 0         0 my $result = await $self->{transport}->send_request('prompts/get', {
128             name => $name,
129             arguments => $arguments // {},
130             });
131 0         0 return $result;
132             }
133              
134              
135 1     1 1 339 async sub list_resources {
136 1         2 my ( $self ) = @_;
137 1         4 my $result = await $self->{transport}->send_request('resources/list');
138 1   50     35 return $result->{resources} // [];
139             }
140              
141              
142 0     0 1 0 async sub read_resource {
143 0         0 my ( $self, $uri ) = @_;
144 0         0 my $result = await $self->{transport}->send_request('resources/read', {
145             uri => $uri,
146             });
147 0         0 return $result;
148             }
149              
150              
151 2     2 1 576 async sub ping {
152 2         11 my ( $self ) = @_;
153 2         32 await $self->{transport}->send_request('ping');
154 2         106 return 1;
155             }
156              
157              
158 1     1 1 402 async sub shutdown {
159 1         2 my ( $self ) = @_;
160 1 50 33     25 if ($self->{transport} && $self->{transport}->can('close')) {
161 1         5 await $self->{transport}->close;
162             }
163 1         67 return 1;
164             }
165              
166              
167              
168             1;
169              
170             __END__