v2 / vlib / mcp / mcp_test.v
139 lines · 115 sloc · 3.84 KB · 32e6394aae7fe857dc16e2acdc3f7707ae7e6319
Raw
1module mcp
2
3struct MockTransport {
4mut:
5 incoming []string
6 sent []string
7 closed bool
8}
9
10fn (mut transport MockTransport) send(message string) ! {
11 transport.sent << message
12}
13
14fn (mut transport MockTransport) receive() !string {
15 if transport.incoming.len == 0 {
16 return error('no messages queued in MockTransport')
17 }
18 message := transport.incoming[0]
19 transport.incoming = if transport.incoming.len == 1 {
20 []string{}
21 } else {
22 transport.incoming[1..].clone()
23 }
24 return message
25}
26
27fn (mut transport MockTransport) close() {
28 transport.closed = true
29}
30
31fn test_initialize_sends_the_mcp_handshake() {
32 mut transport := &MockTransport{
33 incoming: [
34 new_response(1, InitializeResult{
35 protocol_version: protocol_version
36 capabilities: '{"tools":{}}'
37 server_info: Implementation{
38 name: 'mock-server'
39 version: '1.0.0'
40 }
41 }, ResponseError{}).encode(),
42 ]
43 }
44 mut client := new_client(transport, ClientConfig{
45 client_info: Implementation{
46 name: 'mcp-test-client'
47 version: '0.1.0'
48 }
49 capabilities: '{"roots":{"listChanged":true}}'
50 })
51
52 result := client.initialize()!
53
54 assert result.server_info.name == 'mock-server'
55 assert transport.sent.len == 2
56
57 request := decode_request(transport.sent[0])!
58 params := request.decode_params[InitializeParams]()!
59 assert request.method == 'initialize'
60 assert params.protocol_version == protocol_version
61 assert params.client_info.name == 'mcp-test-client'
62 assert params.capabilities == '{"roots":{"listChanged":true}}'
63
64 notification := decode_notification(transport.sent[1])!
65 assert notification.method == 'notifications/initialized'
66 assert notification.params == ''
67}
68
69fn test_request_buffers_server_messages_after_initialize() {
70 mut transport := &MockTransport{
71 incoming: [
72 new_response(1, InitializeResult{
73 protocol_version: protocol_version
74 capabilities: '{"tools":{}}'
75 server_info: Implementation{
76 name: 'mock-server'
77 version: '1.0.0'
78 }
79 }, ResponseError{}).encode(),
80 new_notification('notifications/tools/list_changed', empty).encode(),
81 new_request('server-1', 'roots/list', empty).encode(),
82 new_response(2, true, ResponseError{}).encode(),
83 ]
84 }
85 mut client := new_client(transport, ClientConfig{})
86 client.initialize()!
87
88 response := client.request_message('ping', empty)!
89
90 assert response.result == 'true'
91 assert transport.sent.len == 3
92 assert decode_request(transport.sent[2])!.method == 'ping'
93
94 notifications := client.take_notifications()
95 assert notifications.len == 1
96 assert notifications[0].method == 'notifications/tools/list_changed'
97
98 requests := client.take_requests()
99 assert requests.len == 1
100 assert requests[0].method == 'roots/list'
101 assert requests[0].id == '"server-1"'
102}
103
104fn test_parse_sse_messages_reads_json_rpc_events() {
105 body := 'event: message\r\n' +
106 'data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progress":0.5}}\r\n' +
107 '\r\n' + 'event: message\r\n' + 'data: {"jsonrpc":"2.0","id":1,"result":true}\r\n' + '\r\n'
108
109 messages := parse_sse_messages(body)!
110
111 assert messages.len == 2
112 assert decode_notification(messages[0])!.method == 'notifications/progress'
113 assert decode_response(messages[1])!.result == 'true'
114}
115
116fn test_framed_messages_handle_partial_reads() {
117 payload := new_notification('notifications/initialized', empty).encode()
118 frame := encode_framed_message(payload)
119 mut buffer := frame[..12]
120
121 try_extract_framed_message(buffer) or { assert err.msg() == NoFrameError{}.msg() }
122
123 buffer += frame[12..]
124 extracted := try_extract_framed_message(buffer)!
125 buffer = extracted.remaining
126 message := extracted.message
127
128 assert buffer == ''
129 assert decode_notification(message)!.method == 'notifications/initialized'
130}
131
132fn test_close_delegates_to_the_transport() {
133 mut transport := &MockTransport{}
134 mut client := new_client(transport, ClientConfig{})
135
136 client.close()
137
138 assert transport.closed
139}
140