@@ -3,13 +3,16 @@ import type { ArgumentsCamelCase } from 'yargs'
3
3
import { handler , InstallArgv } from './install'
4
4
import * as clientConfig from '../client-config'
5
5
import { logger } from '../logger'
6
+ import * as detectTransport from '../detect-transport'
6
7
7
8
// Mock dependencies
8
9
jest . mock ( '../client-config' )
9
10
jest . mock ( '../logger' )
11
+ jest . mock ( '../detect-transport' )
10
12
11
13
const mockClientConfig = clientConfig as jest . Mocked < typeof clientConfig >
12
14
const mockLogger = logger as jest . Mocked < typeof logger >
15
+ const mockDetectTransport = detectTransport as jest . Mocked < typeof detectTransport >
13
16
14
17
describe ( 'install command' , ( ) => {
15
18
beforeEach ( ( ) => {
@@ -38,6 +41,7 @@ describe('install command', () => {
38
41
configKey : 'mcpServers' ,
39
42
} )
40
43
mockLogger . prompt . mockResolvedValue ( 'test-package' )
44
+ mockDetectTransport . detectMcpTransport . mockResolvedValue ( 'unknown' )
41
45
} )
42
46
43
47
afterEach ( ( ) => {
@@ -106,11 +110,21 @@ describe('install command', () => {
106
110
$0 : 'install-mcp' ,
107
111
}
108
112
109
- // Mock the transport confirmation prompts (defaults to http)
110
- mockLogger . prompt . mockResolvedValueOnce ( true ) // supports streamable HTTP
113
+ // Mock auto-detection returns http and user confirms
114
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'http' )
115
+ mockLogger . prompt . mockResolvedValueOnce ( true ) // confirms detected transport
111
116
112
117
await handler ( argv )
113
118
119
+ expect ( mockDetectTransport . detectMcpTransport ) . toHaveBeenCalledWith ( 'https://example.com/server' , {
120
+ timeoutMs : 5000 ,
121
+ headers : undefined ,
122
+ } )
123
+ expect ( mockLogger . info ) . toHaveBeenCalledWith ( 'Detecting transport type... this may take a few seconds.' )
124
+ expect ( mockLogger . prompt ) . toHaveBeenCalledWith (
125
+ "We've detected that this server uses the streamable HTTP transport method. Is this correct?" ,
126
+ { type : 'confirm' } ,
127
+ )
114
128
expect ( mockClientConfig . writeConfig ) . toHaveBeenCalledWith (
115
129
expect . objectContaining ( {
116
130
mcpServers : {
@@ -186,14 +200,16 @@ describe('install command', () => {
186
200
$0 : 'install-mcp' ,
187
201
}
188
202
189
- // Mock the transport confirmation prompts
190
- mockLogger . prompt . mockResolvedValueOnce ( true ) // supports streamable HTTP
203
+ // Mock auto-detection returns http and user confirms
204
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'http' )
205
+ mockLogger . prompt . mockResolvedValueOnce ( true ) // confirms detected transport
191
206
192
207
await handler ( argv )
193
208
194
- expect ( mockLogger . prompt ) . toHaveBeenCalledWith ( 'Does this server support the streamable HTTP transport method?' , {
195
- type : 'confirm' ,
196
- } )
209
+ expect ( mockLogger . prompt ) . toHaveBeenCalledWith (
210
+ "We've detected that this server uses the streamable HTTP transport method. Is this correct?" ,
211
+ { type : 'confirm' } ,
212
+ )
197
213
} )
198
214
199
215
it ( 'should fall back to SSE when HTTP is not supported' , async ( ) => {
@@ -205,7 +221,8 @@ describe('install command', () => {
205
221
$0 : 'install-mcp' ,
206
222
}
207
223
208
- // Mock the transport confirmation prompts
224
+ // Mock auto-detection returns unknown, then manual prompts
225
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'unknown' )
209
226
mockLogger . prompt
210
227
. mockResolvedValueOnce ( false ) // doesn't support streamable HTTP
211
228
. mockResolvedValueOnce ( true ) // uses legacy SSE
@@ -239,15 +256,16 @@ describe('install command', () => {
239
256
$0 : 'install-mcp' ,
240
257
}
241
258
242
- // Mock the transport confirmation prompts
259
+ // Mock auto-detection returns unknown, then manual prompts fail
260
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'unknown' )
243
261
mockLogger . prompt
244
262
. mockResolvedValueOnce ( false ) // doesn't support streamable HTTP
245
263
. mockResolvedValueOnce ( false ) // doesn't use legacy SSE
246
264
247
265
await handler ( argv )
248
266
249
267
expect ( mockLogger . error ) . toHaveBeenCalledWith (
250
- 'Server must support either streamable HTTP or legacy SSE transport method.' ,
268
+ 'Remote servers must support either streamable HTTP or legacy SSE transport method.' ,
251
269
)
252
270
expect ( mockClientConfig . writeConfig ) . not . toHaveBeenCalled ( )
253
271
} )
@@ -276,6 +294,10 @@ describe('install command', () => {
276
294
$0 : 'install-mcp' ,
277
295
}
278
296
297
+ // Mock auto-detection returns http and user confirms
298
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'http' )
299
+ mockLogger . prompt . mockResolvedValueOnce ( true ) // confirms detected transport
300
+
279
301
await handler ( argv )
280
302
281
303
expect ( mockClientConfig . writeConfig ) . toHaveBeenCalledWith (
@@ -409,8 +431,9 @@ describe('install command', () => {
409
431
$0 : 'install-mcp' ,
410
432
}
411
433
412
- // Mock the transport confirmation prompts (defaults to http)
413
- mockLogger . prompt . mockResolvedValueOnce ( true ) // supports streamable HTTP
434
+ // Mock auto-detection returns http and user confirms
435
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'http' )
436
+ mockLogger . prompt . mockResolvedValueOnce ( true ) // confirms detected transport
414
437
415
438
await handler ( argv )
416
439
@@ -580,6 +603,128 @@ describe('install command', () => {
580
603
undefined ,
581
604
)
582
605
} )
606
+
607
+ it ( 'should auto-detect SSE transport and install with confirmation' , async ( ) => {
608
+ const argv : ArgumentsCamelCase < InstallArgv > = {
609
+ client : 'cline' ,
610
+ target : 'https://example.com/server' ,
611
+ yes : true ,
612
+ _ : [ ] ,
613
+ $0 : 'install-mcp' ,
614
+ }
615
+
616
+ // Mock auto-detection returns sse and user confirms
617
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'sse' )
618
+ mockLogger . prompt . mockResolvedValueOnce ( true ) // confirms detected transport
619
+
620
+ await handler ( argv )
621
+
622
+ expect ( mockLogger . prompt ) . toHaveBeenCalledWith (
623
+ "We've detected that this server uses the SSE transport method. Is this correct?" ,
624
+ { type : 'confirm' } ,
625
+ )
626
+ expect ( mockClientConfig . writeConfig ) . toHaveBeenCalledWith (
627
+ expect . objectContaining ( {
628
+ mcpServers : {
629
+ 'example-com' : {
630
+ command : 'npx' ,
631
+ args : [ '-y' , 'supergateway' , '--sse' , 'https://example.com/server' ] ,
632
+ } ,
633
+ } ,
634
+ } ) ,
635
+ 'cline' ,
636
+ undefined ,
637
+ )
638
+ } )
639
+
640
+ it ( 'should use opposite transport when user rejects auto-detection' , async ( ) => {
641
+ const argv : ArgumentsCamelCase < InstallArgv > = {
642
+ client : 'cline' ,
643
+ target : 'https://example.com/server' ,
644
+ yes : true ,
645
+ _ : [ ] ,
646
+ $0 : 'install-mcp' ,
647
+ }
648
+
649
+ // Mock auto-detection returns http but user rejects it
650
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'http' )
651
+ mockLogger . prompt . mockResolvedValueOnce ( false ) // rejects detected transport
652
+
653
+ await handler ( argv )
654
+
655
+ expect ( mockLogger . info ) . toHaveBeenCalledWith ( 'Installing as SSE transport method.' )
656
+ expect ( mockClientConfig . writeConfig ) . toHaveBeenCalledWith (
657
+ expect . objectContaining ( {
658
+ mcpServers : {
659
+ 'example-com' : {
660
+ command : 'npx' ,
661
+ args : [ '-y' , 'supergateway' , '--sse' , 'https://example.com/server' ] ,
662
+ } ,
663
+ } ,
664
+ } ) ,
665
+ 'cline' ,
666
+ undefined ,
667
+ )
668
+ } )
669
+
670
+ it ( 'should fall back to manual questions when auto-detection returns unknown' , async ( ) => {
671
+ const argv : ArgumentsCamelCase < InstallArgv > = {
672
+ client : 'cline' ,
673
+ target : 'https://example.com/server' ,
674
+ yes : true ,
675
+ _ : [ ] ,
676
+ $0 : 'install-mcp' ,
677
+ }
678
+
679
+ // Mock auto-detection returns unknown
680
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'unknown' )
681
+ mockLogger . prompt . mockResolvedValueOnce ( true ) // supports streamable HTTP
682
+
683
+ await handler ( argv )
684
+
685
+ expect ( mockLogger . info ) . toHaveBeenCalledWith (
686
+ 'Could not auto-detect transport type, please answer the following questions:' ,
687
+ )
688
+ expect ( mockLogger . prompt ) . toHaveBeenCalledWith ( 'Does this server support the streamable HTTP transport method?' , {
689
+ type : 'confirm' ,
690
+ } )
691
+ expect ( mockClientConfig . writeConfig ) . toHaveBeenCalledWith (
692
+ expect . objectContaining ( {
693
+ mcpServers : {
694
+ 'example-com' : {
695
+ command : 'npx' ,
696
+ args : [ '-y' , 'supergateway' , '--streamableHttp' , 'https://example.com/server' ] ,
697
+ } ,
698
+ } ,
699
+ } ) ,
700
+ 'cline' ,
701
+ undefined ,
702
+ )
703
+ } )
704
+
705
+ it ( 'should pass headers to detectMcpTransport when provided' , async ( ) => {
706
+ const argv : ArgumentsCamelCase < InstallArgv > = {
707
+ client : 'cline' ,
708
+ target : 'https://example.com/server' ,
709
+ header : [ 'Authorization: Bearer token123' ] ,
710
+ yes : true ,
711
+ _ : [ ] ,
712
+ $0 : 'install-mcp' ,
713
+ }
714
+
715
+ // Mock auto-detection returns http and user confirms
716
+ mockDetectTransport . detectMcpTransport . mockResolvedValueOnce ( 'http' )
717
+ mockLogger . prompt . mockResolvedValueOnce ( true ) // confirms detected transport
718
+
719
+ await handler ( argv )
720
+
721
+ expect ( mockDetectTransport . detectMcpTransport ) . toHaveBeenCalledWith ( 'https://example.com/server' , {
722
+ timeoutMs : 5000 ,
723
+ headers : {
724
+ Authorization : 'Bearer token123' ,
725
+ } ,
726
+ } )
727
+ } )
583
728
} )
584
729
585
730
describe ( 'name inference' , ( ) => {
0 commit comments