File Coverage

blib/lib/MCP/Server/Transport/HTTP.pm
Criterion Covered Total %
statement 72 72 100.0
branch 11 14 78.5
condition 5 8 62.5
subroutine 13 13 100.0
pod 1 1 100.0
total 102 108 94.4


line stmt bran cond sub pod time code
1             package MCP::Server::Transport::HTTP;
2 2     2   14 use Mojo::Base 'MCP::Server::Transport', -signatures;
  2         4  
  2         14  
3              
4 2     2   334 use Crypt::Misc qw(random_v4uuid);
  2         27  
  2         210  
5 2     2   11 use Mojo::JSON qw(to_json true);
  2         3  
  2         104  
6 2     2   10 use Mojo::Util qw(dumper);
  2         20  
  2         85  
7 2     2   10 use Scalar::Util qw(blessed);
  2         4  
  2         147  
8              
9 2   50 2   10 use constant DEBUG => $ENV{MCP_DEBUG} || 0;
  2         4  
  2         2134  
10              
11 53     53 1 145 sub handle_request ($self, $c) {
  53         123  
  53         131  
  53         92  
12 53         194 my $method = $c->req->method;
13 53 100       1238 return $self->_handle_post($c) if $method eq 'POST';
14 1         7 return $c->render(json => {error => 'Method not allowed'}, status => 405);
15             }
16              
17 52     52   131 sub _extract_session_id ($self, $c) { return $c->req->headers->header('Mcp-Session-Id') }
  52         87  
  52         105  
  52         80  
  52         146  
18              
19 52     52   102 sub _handle ($self, $data, $context) {
  52         89  
  52         91  
  52         102  
  52         80  
20 52         78 warn "-- MCP Request\n@{[dumper($data)]}\n" if DEBUG;
21 52         248 my $result = $self->server->handle($data, $context);
22 52         445 warn "-- MCP Response\n@{[dumper($result)]}\n" if DEBUG && $result;
23 52         228 return $result;
24             }
25              
26 3     3   6 sub _handle_initialization ($self, $c, $data) {
  3         6  
  3         7  
  3         5  
  3         22  
27 3         32 my $session_id = random_v4uuid;
28 3         511 my $result = $self->_handle($data, {});
29 3         57 $c->res->headers->header('Mcp-Session-Id' => $session_id);
30 3         210 $c->render(json => $result, status => 200);
31             }
32              
33 52     52   102 sub _handle_post ($self, $c) {
  52         127  
  52         107  
  52         85  
34 52         183 my $session_id = $self->_extract_session_id($c);
35              
36 52 50       1891 return $c->render(json => {error => 'Invalid JSON'}, status => 400) unless my $data = $c->req->json;
37 52 50       4285 return $c->render(json => {error => 'Invalid JSON', status => 400}) unless ref $data eq 'HASH';
38              
39 52 100 66     385 if ($data->{method} && $data->{method} eq 'initialize') { $self->_handle_initialization($c, $data) }
  3         16  
40 49         255 else { $self->_handle_regular_request($c, $data, $session_id) }
41             }
42              
43 49     49   90 sub _handle_regular_request ($self, $c, $data, $session_id) {
  49         103  
  49         86  
  49         91  
  49         105  
  49         81  
44 49 50       154 return $c->render(json => {error => 'Missing session ID'}, status => 400) unless $session_id;
45              
46 49         234 $c->res->headers->header('Mcp-Session-Id' => $session_id);
47 49 100       7046 return $c->render(data => '', status => 202)
48             unless defined(my $result = $self->_handle($data, {session_id => $session_id, controller => $c}));
49              
50             # Sync
51 46 100 66     4532 return $c->render(json => $result, status => 200) if !blessed($result) || !$result->isa('Mojo::Promise');
52              
53             # Async
54 5         49 $c->inactivity_timeout(0);
55 5         544 $c->write_sse;
56 5     5   1574 $result->then(sub { $c->write_sse({text => to_json($_[0])})->finish });
  5         759  
57             }
58              
59             1;
60              
61             =encoding utf8
62              
63             =head1 NAME
64              
65             MCP::Server::Transport::HTTP - HTTP transport for MCP servers
66              
67             =head1 SYNOPSIS
68              
69             use MCP::Server::Transport::HTTP;
70              
71             my $http = MCP::Server::Transport::HTTP->new;
72              
73             =head1 DESCRIPTION
74              
75             L is a transport for MCP (Model Context Protocol) server that uses HTTP as the
76             underlying transport mechanism.
77              
78             =head1 ATTRIBUTES
79              
80             L inherits all attributes from L.
81              
82             =head1 METHODS
83              
84             L inherits all methods from L and implements the following new
85             ones.
86              
87             =head2 handle_request
88              
89             $http->handle_request(Mojolicious::Controller->new);
90              
91             Handles an incoming HTTP request.
92              
93             =head1 SEE ALSO
94              
95             L, L, L.
96              
97             =cut