@@ -300,6 +300,11 @@ func TestCreateFromWorkingSetPreservesAllServerFields(t *testing.T) {
300300 Image : "mycompany/myserver:v1.2.3" ,
301301 Tools : []string {"deploy" },
302302 },
303+ {
304+ Type : string (workingset .ServerTypeRemote ),
305+ Endpoint : "https://remote.example.com/sse" ,
306+ Tools : []string {"remote-tool1" , "remote-tool2" },
307+ },
303308 },
304309 Secrets : db.SecretMap {},
305310 }
@@ -319,7 +324,7 @@ func TestCreateFromWorkingSetPreservesAllServerFields(t *testing.T) {
319324
320325 assert .Equal (t , "Detailed Catalog" , catalog .Title )
321326 assert .Equal (t , "profile:detailed-ws" , catalog .Source )
322- assert .Len (t , catalog .Servers , 2 )
327+ assert .Len (t , catalog .Servers , 3 )
323328
324329 // Check registry server
325330 assert .Equal (t , workingset .ServerTypeRegistry , catalog .Servers [0 ].Type )
@@ -330,6 +335,11 @@ func TestCreateFromWorkingSetPreservesAllServerFields(t *testing.T) {
330335 assert .Equal (t , workingset .ServerTypeImage , catalog .Servers [1 ].Type )
331336 assert .Equal (t , "mycompany/myserver:v1.2.3" , catalog .Servers [1 ].Image )
332337 assert .Equal (t , []string {"deploy" }, catalog .Servers [1 ].Tools )
338+
339+ // Check remote server
340+ assert .Equal (t , workingset .ServerTypeRemote , catalog .Servers [2 ].Type )
341+ assert .Equal (t , "https://remote.example.com/sse" , catalog .Servers [2 ].Endpoint )
342+ assert .Equal (t , []string {"remote-tool1" , "remote-tool2" }, catalog .Servers [2 ].Tools )
333343}
334344
335345func TestCreateFromLegacyCatalog (t * testing.T ) {
@@ -501,3 +511,217 @@ registry:
501511 assert .Equal (t , "Test Catalog" , catalog .Title )
502512 assert .Equal (t , "legacy-catalog:test-catalog" , catalog .Source )
503513}
514+
515+ func TestCreateFromLegacyCatalogWithRemotes (t * testing.T ) {
516+ tests := []struct {
517+ name string
518+ serverYAML string
519+ expectedType workingset.ServerType
520+ validateServer func (t * testing.T , server * catalog.Server )
521+ }{
522+ {
523+ name : "basic remote with SSE transport" ,
524+ serverYAML : ` title: "AIS Fleet"
525+ type: remote
526+ remote:
527+ transport_type: sse
528+ url: https://mcp.aisfleet.com/sse` ,
529+ expectedType : workingset .ServerTypeRemote ,
530+ validateServer : func (t * testing.T , server * catalog.Server ) {
531+ t .Helper ()
532+ assert .Equal (t , "sse" , server .Remote .Transport )
533+ assert .Equal (t , "https://mcp.aisfleet.com/sse" , server .Remote .URL )
534+ assert .Empty (t , server .Remote .Headers )
535+ assert .Empty (t , server .Secrets )
536+ assert .Nil (t , server .OAuth )
537+ },
538+ },
539+ {
540+ name : "remote with streamable-http and authorization header" ,
541+ serverYAML : ` title: "Apify Remote"
542+ type: remote
543+ remote:
544+ transport_type: streamable-http
545+ url: https://mcp.apify.com
546+ headers:
547+ Authorization: "Bearer ${APIFY_API_KEY}"
548+ secrets:
549+ - name: apify.api_key
550+ env: APIFY_API_KEY
551+ example: <YOUR_API_KEY>` ,
552+ expectedType : workingset .ServerTypeRemote ,
553+ validateServer : func (t * testing.T , server * catalog.Server ) {
554+ t .Helper ()
555+ assert .Equal (t , "streamable-http" , server .Remote .Transport )
556+ assert .Equal (t , "https://mcp.apify.com" , server .Remote .URL )
557+ assert .Equal (t , "Bearer ${APIFY_API_KEY}" , server .Remote .Headers ["Authorization" ])
558+ assert .Len (t , server .Secrets , 1 )
559+ assert .Equal (t , "apify.api_key" , server .Secrets [0 ].Name )
560+ assert .Equal (t , "APIFY_API_KEY" , server .Secrets [0 ].Env )
561+ },
562+ },
563+ {
564+ name : "remote with OAuth" ,
565+ serverYAML : ` title: "Asana"
566+ type: remote
567+ remote:
568+ transport_type: streamable-http
569+ url: https://asana.com/api/mcp/v1/sse
570+ oauth:
571+ providers:
572+ - provider: asana
573+ secret: asana.personal_access_token
574+ env: ASANA_PERSONAL_ACCESS_TOKEN` ,
575+ expectedType : workingset .ServerTypeRemote ,
576+ validateServer : func (t * testing.T , server * catalog.Server ) {
577+ t .Helper ()
578+ assert .Equal (t , "streamable-http" , server .Remote .Transport )
579+ assert .Equal (t , "https://asana.com/api/mcp/v1/sse" , server .Remote .URL )
580+ require .NotNil (t , server .OAuth )
581+ assert .Len (t , server .OAuth .Providers , 1 )
582+ assert .Equal (t , "asana" , server .OAuth .Providers [0 ].Provider )
583+ assert .Equal (t , "asana.personal_access_token" , server .OAuth .Providers [0 ].Secret )
584+ assert .Equal (t , "ASANA_PERSONAL_ACCESS_TOKEN" , server .OAuth .Providers [0 ].Env )
585+ },
586+ },
587+ {
588+ name : "remote with dynamic tools" ,
589+ serverYAML : ` title: "Cloudflare Audit Logs"
590+ type: remote
591+ dynamic:
592+ tools: true
593+ remote:
594+ transport_type: sse
595+ url: https://auditlogs.mcp.cloudflare.com/sse
596+ oauth:
597+ providers:
598+ - provider: cloudflare-audit-logs
599+ secret: cloudflare-audit-logs.personal_access_token
600+ env: CLOUDFLARE_PERSONAL_ACCESS_TOKEN` ,
601+ expectedType : workingset .ServerTypeRemote ,
602+ validateServer : func (t * testing.T , server * catalog.Server ) {
603+ t .Helper ()
604+ assert .Equal (t , "sse" , server .Remote .Transport )
605+ assert .Equal (t , "https://auditlogs.mcp.cloudflare.com/sse" , server .Remote .URL )
606+ require .NotNil (t , server .OAuth )
607+ assert .Len (t , server .OAuth .Providers , 1 )
608+ assert .Equal (t , "cloudflare-audit-logs" , server .OAuth .Providers [0 ].Provider )
609+ },
610+ },
611+ {
612+ name : "remote with static tools list (no headers/secrets)" ,
613+ serverYAML : ` title: "GitMCP"
614+ type: remote
615+ remote:
616+ transport_type: streamable-http
617+ url: https://gitmcp.io/docs
618+ tools:
619+ - name: match_common_libs_owner_repo_mapping
620+ - name: fetch_generic_documentation
621+ - name: search_generic_documentation` ,
622+ expectedType : workingset .ServerTypeRemote ,
623+ validateServer : func (t * testing.T , server * catalog.Server ) {
624+ t .Helper ()
625+ assert .Equal (t , "streamable-http" , server .Remote .Transport )
626+ assert .Equal (t , "https://gitmcp.io/docs" , server .Remote .URL )
627+ assert .Empty (t , server .Remote .Headers )
628+ assert .Empty (t , server .Secrets )
629+ assert .Nil (t , server .OAuth )
630+ assert .Len (t , server .Tools , 3 )
631+ assert .Equal (t , "match_common_libs_owner_repo_mapping" , server .Tools [0 ].Name )
632+ },
633+ },
634+ {
635+ name : "remote with SSE, headers, and secrets" ,
636+ serverYAML : ` title: "Dodo Payments"
637+ type: remote
638+ remote:
639+ transport_type: sse
640+ url: https://mcp.dodopayments.com/sse
641+ headers:
642+ Authorization: "Bearer ${DODO_PAYMENTS_API_KEY}"
643+ secrets:
644+ - name: dodo-payments.api_key
645+ env: DODO_PAYMENTS_API_KEY
646+ example: <YOUR_API_KEY>` ,
647+ expectedType : workingset .ServerTypeRemote ,
648+ validateServer : func (t * testing.T , server * catalog.Server ) {
649+ t .Helper ()
650+ assert .Equal (t , "sse" , server .Remote .Transport )
651+ assert .Equal (t , "https://mcp.dodopayments.com/sse" , server .Remote .URL )
652+ assert .Equal (t , "Bearer ${DODO_PAYMENTS_API_KEY}" , server .Remote .Headers ["Authorization" ])
653+ assert .Len (t , server .Secrets , 1 )
654+ assert .Equal (t , "dodo-payments.api_key" , server .Secrets [0 ].Name )
655+ assert .Equal (t , "DODO_PAYMENTS_API_KEY" , server .Secrets [0 ].Env )
656+ },
657+ },
658+ {
659+ name : "remote documentation server (no auth)" ,
660+ serverYAML : ` title: "Cloudflare Docs"
661+ type: remote
662+ remote:
663+ transport_type: sse
664+ url: https://docs.mcp.cloudflare.com/sse
665+ tools:
666+ - name: search_cloudflare_documentation
667+ - name: migrate_pages_to_workers_guide` ,
668+ expectedType : workingset .ServerTypeRemote ,
669+ validateServer : func (t * testing.T , server * catalog.Server ) {
670+ t .Helper ()
671+ assert .Equal (t , "sse" , server .Remote .Transport )
672+ assert .Equal (t , "https://docs.mcp.cloudflare.com/sse" , server .Remote .URL )
673+ assert .Empty (t , server .Remote .Headers )
674+ assert .Empty (t , server .Secrets )
675+ assert .Nil (t , server .OAuth )
676+ assert .Len (t , server .Tools , 2 )
677+ assert .Equal (t , "search_cloudflare_documentation" , server .Tools [0 ].Name )
678+ },
679+ },
680+ }
681+
682+ for _ , tt := range tests {
683+ t .Run (tt .name , func (t * testing.T ) {
684+ dao := setupTestDB (t )
685+ ctx := t .Context ()
686+
687+ // Create a temporary legacy catalog file
688+ tempDir := t .TempDir ()
689+ catalogFile := filepath .Join (tempDir , "test-catalog.yaml" )
690+
691+ legacyCatalogYAML := "name: test-catalog\n registry:\n test-server:\n " + tt .serverYAML + "\n "
692+
693+ err := os .WriteFile (catalogFile , []byte (legacyCatalogYAML ), 0o644 )
694+ require .NoError (t , err )
695+
696+ // Create catalog from legacy catalog
697+ output := captureStdout (t , func () {
698+ err := Create (ctx , dao , "test/imported:latest" , "" , catalogFile , "Imported Catalog" )
699+ require .NoError (t , err )
700+ })
701+
702+ assert .Contains (t , output , "Catalog test/imported:latest created" )
703+
704+ // Verify the catalog was created
705+ catalogs , err := dao .ListCatalogs (ctx )
706+ require .NoError (t , err )
707+ assert .Len (t , catalogs , 1 )
708+
709+ catalog := NewFromDb (& catalogs [0 ])
710+ assert .Equal (t , "Imported Catalog" , catalog .Title )
711+ assert .Equal (t , "legacy-catalog:test-catalog" , catalog .Source )
712+ require .Len (t , catalog .Servers , 1 )
713+
714+ // Verify server basic properties
715+ server := catalog .Servers [0 ]
716+ assert .Equal (t , tt .expectedType , server .Type )
717+ require .NotNil (t , server .Snapshot )
718+ assert .Equal (t , "test-server" , server .Snapshot .Server .Name )
719+
720+ assert .NotEmpty (t , server .Endpoint , "Endpoint should be set for remote servers" )
721+ assert .Equal (t , server .Snapshot .Server .Remote .URL , server .Endpoint , "Endpoint should match the Remote.URL from snapshot" )
722+
723+ // Run custom validation
724+ tt .validateServer (t , & server .Snapshot .Server )
725+ })
726+ }
727+ }
0 commit comments