ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Server/JSONL.pm
Revision: 1.1
Committed: Thu Oct 8 05:27:00 2026 UTC (4 weeks, 2 days ago) by root
Branch: MAIN
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MCP::Server::JSONL - MCP over a newline-delimited byte stream
4    
5     =head1 DESCRIPTION
6    
7     A subclass of L<AnyEvent::MCP::Server> for sockets or other streaming handles.
8     Each incoming line is one UTF-8 JSON-RPC message. Replies are written as they
9     complete, without imposing request order.
10    
11     =head1 METHODS
12    
13     =head2 $handle = $server->accept ($fh, $fh_out)
14    
15     Starts handling C<$fh>. If C<$fh_out> is supplied, it receives outgoing
16     messages; otherwise C<$fh> is used in both directions. Both handles belong
17     to the server until the connection closes.
18    
19     With separate input/output handles, input EOF waits for pending replies
20     and drains the output before closing. EOF on a bidirectional socket closes
21     it immediately and cancels outstanding requests.
22    
23     C<max_size> limits a buffered incoming message, defaulting to 1 MiB.
24    
25     =head2 $server->close
26    
27     Closes the server's connections and cancels outstanding requests.
28    
29     =head1 EVENTS
30    
31     =head2 on_connection_error => $callback->($server, $handle, $message)
32    
33     Called for stream errors before closing the connection.
34    
35     =head2 on_connection_close => $callback->($server, $handle)
36    
37     Called after a connection has closed.
38    
39     =head2 on_close => $callback->($server)
40    
41     Called by C<close> after closing all connections.
42    
43     =cut
44    
45     package AnyEvent::MCP::Server::JSONL;
46    
47     use common::sense;
48     use parent "AnyEvent::MCP::Server";
49     use AnyEvent::Handle ();
50     use Scalar::Util qw(weaken);
51    
52     sub accept {
53     my ($self, $fh, $fh_out) = @_;
54    
55     delete $self->{_closed};
56     weaken $self;
57    
58     my $handle = AnyEvent::Handle->new (
59     fh => $fh,
60     rbuf_max => $self->{max_size} || 1024 * 1024,
61     on_read => sub { $self->_read ($_[0]) if $self },
62     on_error => sub {
63     my ($handle, $fatal, $message) = @_;
64    
65     $self->event (connection_error => $handle, $message) if $self;
66     $self->_close ($handle) if $self;
67     },
68     on_eof => sub {
69     return unless $self;
70     my ($handle) = @_;
71    
72     if ($handle->{mcp_out}) {
73     $handle->{mcp_eof} = 1;
74     $self->_drain ($handle);
75     } else {
76     $self->_close ($handle);
77     }
78     },
79     );
80    
81     if ($fh_out) {
82     $handle->{mcp_out} = AnyEvent::Handle->new (
83     fh => $fh_out,
84     on_error => sub {
85     $self->event (connection_error => $_[0], $_[2]) if $self;
86     $self->close if $self;
87     },
88     );
89     }
90    
91     $handle->{mcp_peer} = {};
92     $self->{connections}{$handle} = $handle;
93    
94     $handle
95     }
96    
97     sub _read {
98     my ($self, $handle) = @_;
99    
100     weaken $self;
101    
102     $handle->push_read (line => sub {
103     my ($handle, $line) = @_;
104     return unless $self;
105    
106     ++$handle->{mcp_active};
107     $self->json_rpc ($line, sub {
108     --$handle->{mcp_active};
109     return if $handle->{mcp_closed};
110    
111     ($handle->{mcp_out} || $handle)->push_write ($_[0] . "\n")
112     if defined $_[0];
113     $self->_drain ($handle) if $handle->{mcp_eof};
114     },
115     peer => $handle->{mcp_peer},
116     on_notify => sub {
117     return if $handle->{mcp_closed};
118    
119     ($handle->{mcp_out} || $handle)->push_write (JSON::XS::encode_json ($_[1]) . "\n");
120     },
121     );
122     });
123     }
124    
125     sub _drain {
126     my ($self, $handle) = @_;
127    
128     return if $handle->{mcp_active} || $handle->{mcp_closed};
129     my $output = $handle->{mcp_out};
130     if (length $output->{wbuf}) {
131     weaken $self;
132     $output->on_drain (sub { $self->_close ($handle) if $self });
133     } else {
134     $self->_close ($handle);
135     }
136     }
137    
138     sub _close {
139     my ($self, $handle) = @_;
140    
141     return if $handle->{mcp_closed};
142     $handle->{mcp_closed} = 1;
143    
144     my $requests = delete $handle->{mcp_peer}{requests} || {};
145     $_->cancel ("Connection closed") for grep defined, values %$requests;
146    
147     delete $self->{connections}{$handle};
148     $handle->{mcp_out}->destroy if $handle->{mcp_out};
149     $handle->destroy;
150     $self->event (connection_close => $handle);
151     }
152    
153     sub close {
154     my ($self) = @_;
155    
156     return if $self->{_closed}++;
157     delete $self->{listener};
158     $self->_close ($_) for values %{$self->{connections} || {}};
159     $self->event (close =>);
160     }
161    
162     sub DESTROY {
163     $_[0]->close
164     }
165    
166     1