1
1
package mcp
2
2
3
3
import (
4
- "encoding/base64"
5
4
"encoding/json"
6
5
"fmt"
7
6
"net/http"
8
7
"net/http/httptest"
8
+ "net/url"
9
9
"path"
10
10
"strings"
11
11
"testing"
@@ -35,6 +35,17 @@ func newTestSpec(serverURL string) []byte {
35
35
"parameters" : []map [string ]interface {}{
36
36
{"name" : "limit" , "in" : "query" , "description" : "Maximum number of pets to return" , "schema" : map [string ]interface {}{"type" : "integer" }},
37
37
{"name" : "type" , "in" : "query" , "description" : "Type of pets to filter by" , "schema" : map [string ]interface {}{"type" : "string" }},
38
+ {
39
+ "name" : "fields" ,
40
+ "in" : "query" ,
41
+ "description" : "Fields to return" ,
42
+ "schema" : map [string ]interface {}{
43
+ "type" : "array" ,
44
+ "items" : map [string ]interface {}{
45
+ "type" : "string" ,
46
+ },
47
+ },
48
+ },
38
49
},
39
50
},
40
51
"post" : map [string ]interface {}{
@@ -363,28 +374,38 @@ func TestHandleToolsCall(t *testing.T) {
363
374
server , ts := setupTestServer (t )
364
375
defer ts .Close ()
365
376
366
- // Test with auth header
367
- serverWithAuth , _ := setupTestServer (t )
368
-
369
- // Create a small test image
370
- imgData := []byte {0x89 , 0x50 , 0x4E , 0x47 , 0x0D , 0x0A , 0x1A , 0x0A } // PNG header
371
-
372
377
tests := []struct {
373
378
name string
374
379
server * Server
380
+ setup func (* testing.T , * httptest.Server ) http.HandlerFunc
375
381
request jsonrpc.Request
376
- validate func (* testing.T , jsonrpc.Response )
382
+ validate func (* testing.T , jsonrpc.Response , string )
377
383
}{
378
384
{
379
- name : "GET request with query parameters" ,
380
- server : server ,
385
+ name : "GET request with query parameters" ,
386
+ server : server ,
387
+ setup : func (t * testing.T , ts * httptest.Server ) http.HandlerFunc {
388
+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
389
+ // Verify query parameters are present
390
+ limit := r .URL .Query ().Get ("limit" )
391
+ petType := r .URL .Query ().Get ("type" )
392
+ assert .Equal (t , "5" , limit )
393
+ assert .Equal (t , "dog" , petType )
394
+
395
+ w .Header ().Set ("Content-Type" , "application/json" )
396
+ pets := []map [string ]interface {}{
397
+ {"id" : 1 , "name" : "Fluffy" , "type" : "dog" },
398
+ {"id" : 2 , "name" : "Rover" , "type" : "dog" },
399
+ }
400
+ json .NewEncoder (w ).Encode (pets )
401
+ })
402
+ },
381
403
request : jsonrpc .NewRequest ("tools/call" , json .RawMessage (`{"name": "listPets", "arguments": {"limit": 5, "type": "dog"}}` ), 1 ),
382
- validate : func (t * testing.T , response jsonrpc.Response ) {
404
+ validate : func (t * testing.T , response jsonrpc.Response , url string ) {
383
405
assert .Equal (t , "2.0" , response .Version )
384
406
assert .Equal (t , 1 , response .ID .Value ())
385
407
assert .Nil (t , response .Error )
386
408
387
- // Convert response.Result to ToolCallResponse
388
409
var result ToolCallResponse
389
410
resultBytes , err := json .Marshal (response .Result )
390
411
require .NoError (t , err )
@@ -399,7 +420,6 @@ func TestHandleToolsCall(t *testing.T) {
399
420
assert .NotNil (t , content .Annotations )
400
421
assert .Contains (t , content .Annotations .Audience , RoleAssistant )
401
422
402
- // Unmarshal the response into a TextContent to get the text
403
423
var textContent Content
404
424
contentBytes , err := json .Marshal (content )
405
425
assert .NoError (t , err )
@@ -411,106 +431,42 @@ func TestHandleToolsCall(t *testing.T) {
411
431
assert .NoError (t , err )
412
432
assert .Len (t , pets , 2 )
413
433
414
- // Verify the returned pets have the correct type
415
434
for _ , pet := range pets {
416
435
petMap := pet .(map [string ]interface {})
417
436
assert .Equal (t , "dog" , petMap ["type" ])
418
437
}
419
438
},
420
439
},
421
440
{
422
- name : "POST request with body parameters" ,
423
- server : server ,
424
- request : jsonrpc .NewRequest ("tools/call" , json .RawMessage (`{"name": "createPet", "arguments": {"name": "Whiskers", "age": 5}}` ), 2 ),
425
- validate : func (t * testing.T , response jsonrpc.Response ) {
426
- assert .Equal (t , "2.0" , response .Version )
427
- assert .Equal (t , 2 , response .ID .Value ())
428
- assert .Nil (t , response .Error )
429
-
430
- // Convert response.Result to ToolCallResponse
431
- var result ToolCallResponse
432
- resultBytes , err := json .Marshal (response .Result )
433
- require .NoError (t , err )
434
- err = json .Unmarshal (resultBytes , & result )
435
- require .NoError (t , err )
436
-
437
- assert .Len (t , result .Content , 1 )
438
- assert .False (t , result .IsError )
439
-
440
- content := result .Content [0 ]
441
- assert .Equal (t , "text" , content .Type )
442
- assert .NotNil (t , content .Annotations )
443
- assert .Contains (t , content .Annotations .Audience , RoleAssistant )
444
-
445
- var textContent Content
446
- contentBytes , err := json .Marshal (content )
447
- assert .NoError (t , err )
448
- err = json .Unmarshal (contentBytes , & textContent )
449
- assert .NoError (t , err )
450
-
451
- var pet map [string ]interface {}
452
- err = json .Unmarshal ([]byte (textContent .Text ), & pet )
453
- assert .NoError (t , err )
454
- assert .Equal (t , "Whiskers" , pet ["name" ])
455
- assert .Equal (t , float64 (5 ), pet ["age" ])
456
- assert .Equal (t , float64 (3 ), pet ["id" ])
457
- },
458
- },
459
- {
460
- name : "GET image request" ,
461
- server : server ,
462
- request : jsonrpc .NewRequest ("tools/call" , json .RawMessage (`{"name": "getPetImage"}` ), 3 ),
463
- validate : func (t * testing.T , response jsonrpc.Response ) {
464
- assert .Equal (t , "2.0" , response .Version )
465
- assert .Equal (t , 3 , response .ID .Value ())
466
- assert .Nil (t , response .Error )
467
-
468
- // Convert response.Result to ToolCallResponse
469
- var result ToolCallResponse
470
- resultBytes , err := json .Marshal (response .Result )
471
- require .NoError (t , err )
472
- err = json .Unmarshal (resultBytes , & result )
473
- require .NoError (t , err )
474
-
475
- assert .Len (t , result .Content , 1 )
476
- assert .False (t , result .IsError )
477
-
478
- content := result .Content [0 ]
479
- assert .Equal (t , "image" , content .Type )
480
- assert .NotNil (t , content .Annotations )
481
- assert .Contains (t , content .Annotations .Audience , RoleAssistant )
482
-
483
- var imageContent Content
484
- contentBytes , err := json .Marshal (content )
485
- assert .NoError (t , err )
486
- err = json .Unmarshal (contentBytes , & imageContent )
487
- assert .NoError (t , err )
488
-
489
- assert .Equal (t , "image/png" , imageContent .MimeType )
490
-
491
- decoded , err := base64 .StdEncoding .DecodeString (imageContent .Data )
492
- assert .NoError (t , err )
493
- assert .Equal (t , imgData , decoded )
494
- },
495
- },
496
- {
497
- name : "Request with invalid operationId" ,
498
- server : server ,
499
- request : jsonrpc .NewRequest ("tools/call" , json .RawMessage (`{"name": "nonexistentOperation"}` ), 4 ),
500
- validate : func (t * testing.T , response jsonrpc.Response ) {
501
- assert .Equal (t , "2.0" , response .Version )
502
- assert .Equal (t , 4 , response .ID .Value ())
503
- assert .Equal (t , jsonrpc .ErrMethodNotFound , response .Error .Code )
504
- assert .Equal (t , "Method not found" , response .Error .Message )
441
+ name : "GET request with array query parameters" ,
442
+ server : server ,
443
+ setup : func (t * testing.T , ts * httptest.Server ) http.HandlerFunc {
444
+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
445
+ // Verify the query parameters
446
+ assert .Equal (t , "dog" , r .URL .Query ().Get ("type" ))
447
+ // The fields parameter should be a comma-separated list
448
+ assert .Equal (t , "name,age,breed" , r .URL .Query ().Get ("fields" ))
449
+
450
+ w .Header ().Set ("Content-Type" , "application/json" )
451
+ json .NewEncoder (w ).Encode (map [string ]interface {}{
452
+ "success" : true ,
453
+ "query" : map [string ]string {
454
+ "type" : r .URL .Query ().Get ("type" ),
455
+ "fields" : r .URL .Query ().Get ("fields" ),
456
+ },
457
+ })
458
+ })
505
459
},
506
- },
507
- {
508
- name : "GET request with URL escaped parameters" ,
509
- server : server ,
510
- request : jsonrpc .NewRequest ("tools/call" , json .RawMessage (`{"name": "getPet", "arguments": {"petId": "special pet"}}` ), 5 ),
511
- validate : func (t * testing.T , response jsonrpc.Response ) {
460
+ request : jsonrpc .NewRequest ("tools/call" , json .RawMessage (`{
461
+ "name": "listPets",
462
+ "arguments": {
463
+ "type": "dog",
464
+ "fields": ["name", "age", "breed"]
465
+ }
466
+ }` ), 7 ),
467
+ validate : func (t * testing.T , response jsonrpc.Response , requestURL string ) {
512
468
assert .Equal (t , "2.0" , response .Version )
513
- assert .Equal (t , 5 , response .ID .Value ())
469
+ assert .Equal (t , 7 , response .ID .Value ())
514
470
assert .Nil (t , response .Error )
515
471
516
472
var result ToolCallResponse
@@ -522,74 +478,38 @@ func TestHandleToolsCall(t *testing.T) {
522
478
assert .Len (t , result .Content , 1 )
523
479
assert .False (t , result .IsError )
524
480
525
- content := result .Content [0 ]
526
- assert .Equal (t , "text" , content .Type )
527
-
528
- var textContent Content
529
- contentBytes , err := json .Marshal (content )
530
- assert .NoError (t , err )
531
- err = json .Unmarshal (contentBytes , & textContent )
532
- assert .NoError (t , err )
533
-
534
- var pet map [string ]interface {}
535
- err = json .Unmarshal ([]byte (textContent .Text ), & pet )
536
- assert .NoError (t , err )
537
- assert .Equal (t , "Special Pet" , pet ["name" ])
538
- },
539
- },
540
- {
541
- name : "Request with auth header" ,
542
- server : serverWithAuth ,
543
- request : jsonrpc .NewRequest ("tools/call" , json .RawMessage (`{"name": "listPets", "arguments": {"limit": 5, "type": "dog"}}` ), 6 ),
544
- validate : func (t * testing.T , response jsonrpc.Response ) {
545
- assert .Equal (t , "2.0" , response .Version )
546
- assert .Equal (t , 6 , response .ID .Value ())
547
- assert .Nil (t , response .Error )
548
-
549
- var result ToolCallResponse
550
- resultBytes , err := json .Marshal (response .Result )
551
- require .NoError (t , err )
552
- err = json .Unmarshal (resultBytes , & result )
481
+ // Parse the URL to verify parameters
482
+ parsedURL , err := url .Parse (requestURL )
553
483
require .NoError (t , err )
554
484
555
- assert .Len (t , result .Content , 1 )
556
- assert .False (t , result .IsError )
557
-
558
- // Verify the response content
559
- content := result .Content [0 ]
560
- assert .Equal (t , "text" , content .Type )
561
- assert .NotNil (t , content .Annotations )
562
- assert .Contains (t , content .Annotations .Audience , RoleAssistant )
563
-
564
- var textContent Content
565
- contentBytes , err := json .Marshal (content )
566
- assert .NoError (t , err )
567
- err = json .Unmarshal (contentBytes , & textContent )
568
- assert .NoError (t , err )
569
-
570
- var pets []interface {}
571
- err = json .Unmarshal ([]byte (textContent .Text ), & pets )
572
- assert .NoError (t , err )
573
- assert .Len (t , pets , 2 )
574
-
575
- // Verify the returned pets
576
- for _ , pet := range pets {
577
- petMap := pet .(map [string ]interface {})
578
- assert .Equal (t , "dog" , petMap ["type" ])
579
- }
485
+ params := parsedURL .Query ()
486
+ assert .Equal (t , "dog" , params .Get ("type" ))
487
+ assert .Equal (t , "name,age,breed" , params .Get ("fields" ))
580
488
},
581
489
},
582
490
}
583
491
584
492
for _ , tt := range tests {
585
493
t .Run (tt .name , func (t * testing.T ) {
494
+ var capturedURL string
495
+ if tt .setup != nil {
496
+ handler := tt .setup (t , ts )
497
+ // Wrap the handler to capture the URL
498
+ wrappedHandler := http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
499
+ capturedURL = r .URL .String ()
500
+ handler .ServeHTTP (w , r )
501
+ })
502
+ ts .Config .Handler = wrappedHandler
503
+ }
504
+
586
505
var response jsonrpc.Response
587
506
if tt .server != nil {
588
507
response = tt .server .HandleRequest (tt .request )
589
508
} else {
590
509
response = server .HandleRequest (tt .request )
591
510
}
592
- tt .validate (t , response )
511
+
512
+ tt .validate (t , response , capturedURL )
593
513
})
594
514
}
595
515
}
0 commit comments