1
1
using ModelContextProtocol . Client ;
2
+ using ModelContextProtocol . Protocol ;
2
3
using ModelContextProtocol . Tests . Utils ;
3
4
using System . Runtime . InteropServices ;
4
5
using System . Text ;
@@ -15,17 +16,14 @@ public async Task CreateAsync_ValidProcessInvalidServer_Throws()
15
16
string id = Guid . NewGuid ( ) . ToString ( "N" ) ;
16
17
17
18
StdioClientTransport transport = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ?
18
- new ( new ( ) { Command = "cmd" , Arguments = [ "/C " , $ "echo \" { id } \" >&2"] } , LoggerFactory ) :
19
- new ( new ( ) { Command = "ls " , Arguments = [ id ] } , LoggerFactory ) ;
19
+ new ( new ( ) { Command = "cmd" , Arguments = [ "/c " , $ "echo { id } >&2 & exit /b 1 "] } , LoggerFactory ) :
20
+ new ( new ( ) { Command = "sh " , Arguments = [ "-c" , $ "echo { id } >&2; exit 1" ] } , LoggerFactory ) ;
20
21
21
- IOException e = await Assert . ThrowsAsync < IOException > ( ( ) => McpClient . CreateAsync ( transport , loggerFactory : LoggerFactory , cancellationToken : TestContext . Current . CancellationToken ) ) ;
22
- if ( ! RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
23
- {
24
- Assert . Contains ( id , e . ToString ( ) ) ;
25
- }
22
+ await Assert . ThrowsAsync < IOException > ( ( ) => McpClient . CreateAsync ( transport , loggerFactory : LoggerFactory , cancellationToken : TestContext . Current . CancellationToken ) ) ;
26
23
}
27
24
28
- [ Fact ( Skip = "Platform not supported by this test." , SkipUnless = nameof ( IsStdErrCallbackSupported ) ) ]
25
+ // [Fact(Skip = "Platform not supported by this test.", SkipUnless = nameof(IsStdErrCallbackSupported))]
26
+ [ Fact ]
29
27
public async Task CreateAsync_ValidProcessInvalidServer_StdErrCallbackInvoked ( )
30
28
{
31
29
string id = Guid . NewGuid ( ) . ToString ( "N" ) ;
@@ -43,12 +41,92 @@ public async Task CreateAsync_ValidProcessInvalidServer_StdErrCallbackInvoked()
43
41
} ;
44
42
45
43
StdioClientTransport transport = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ?
46
- new ( new ( ) { Command = "cmd" , Arguments = [ "/C " , $ "echo \" { id } \" >&2"] , StandardErrorLines = stdErrCallback } , LoggerFactory ) :
47
- new ( new ( ) { Command = "ls " , Arguments = [ id ] , StandardErrorLines = stdErrCallback } , LoggerFactory ) ;
44
+ new ( new ( ) { Command = "cmd" , Arguments = [ "/c " , $ "echo { id } >&2 & exit /b 1 "] , StandardErrorLines = stdErrCallback } , LoggerFactory ) :
45
+ new ( new ( ) { Command = "sh " , Arguments = [ "-c" , $ "echo { id } >&2; exit 1" ] , StandardErrorLines = stdErrCallback } , LoggerFactory ) ;
48
46
49
47
await Assert . ThrowsAsync < IOException > ( ( ) => McpClient . CreateAsync ( transport , loggerFactory : LoggerFactory , cancellationToken : TestContext . Current . CancellationToken ) ) ;
50
48
51
49
Assert . InRange ( count , 1 , int . MaxValue ) ;
52
50
Assert . Contains ( id , sb . ToString ( ) ) ;
53
51
}
52
+
53
+ [ Theory ]
54
+ [ InlineData ( null ) ]
55
+ [ InlineData ( "argument with spaces" ) ]
56
+ [ InlineData ( "&" ) ]
57
+ [ InlineData ( "|" ) ]
58
+ [ InlineData ( ">" ) ]
59
+ [ InlineData ( "<" ) ]
60
+ [ InlineData ( "^" ) ]
61
+ [ InlineData ( " & " ) ]
62
+ [ InlineData ( " | " ) ]
63
+ [ InlineData ( " > " ) ]
64
+ [ InlineData ( " < " ) ]
65
+ [ InlineData ( " ^ " ) ]
66
+ [ InlineData ( "& " ) ]
67
+ [ InlineData ( "| " ) ]
68
+ [ InlineData ( "> " ) ]
69
+ [ InlineData ( "< " ) ]
70
+ [ InlineData ( "^ " ) ]
71
+ [ InlineData ( " &" ) ]
72
+ [ InlineData ( " |" ) ]
73
+ [ InlineData ( " >" ) ]
74
+ [ InlineData ( " <" ) ]
75
+ [ InlineData ( " ^" ) ]
76
+ [ InlineData ( "^&<>|" ) ]
77
+ [ InlineData ( "^&<>| " ) ]
78
+ [ InlineData ( " ^&<>|" ) ]
79
+ [ InlineData ( "\t ^&<>" ) ]
80
+ [ InlineData ( "^&\t <>" ) ]
81
+ [ InlineData ( "ls /tmp | grep foo.txt > /dev/null" ) ]
82
+ [ InlineData ( "let rec Y f x = f (Y f) x" ) ]
83
+ [ InlineData ( "value with \" quotes\" and spaces" ) ]
84
+ [ InlineData ( "C:\\ Program Files\\ Test App\\ app.dll" ) ]
85
+ [ InlineData ( "C:\\ EndsWithBackslash\\ " ) ]
86
+ [ InlineData ( "--already-looks-like-flag" ) ]
87
+ [ InlineData ( "-starts-with-dash" ) ]
88
+ [ InlineData ( "name=value=another" ) ]
89
+ [ InlineData ( "$(echo injected)" ) ]
90
+ [ InlineData ( "value-with-\" quotes\" -and-\\ backslashes\\ " ) ]
91
+ [ InlineData ( "http://localhost:1234/callback?foo=1&bar=2" ) ]
92
+ public async Task EscapesCliArgumentsCorrectly ( string ? cliArgumentValue )
93
+ {
94
+ if ( PlatformDetection . IsMonoRuntime && cliArgumentValue ? . EndsWith ( "\\ " ) is true )
95
+ {
96
+ Assert . Skip ( "mono runtime does not handle arguments ending with backslash correctly." ) ;
97
+ }
98
+
99
+ string cliArgument = $ "--cli-arg={ cliArgumentValue } ";
100
+
101
+ StdioClientTransportOptions options = new ( )
102
+ {
103
+ Name = "TestServer" ,
104
+ Command = ( PlatformDetection . IsMonoRuntime , PlatformDetection . IsWindows ) switch
105
+ {
106
+ ( true , _ ) => "mono" ,
107
+ ( _, true ) => "TestServer.exe" ,
108
+ _ => "dotnet" ,
109
+ } ,
110
+ Arguments = ( PlatformDetection . IsMonoRuntime , PlatformDetection . IsWindows ) switch
111
+ {
112
+ ( true , _ ) => [ "TestServer.exe" , cliArgument ] ,
113
+ ( _, true ) => [ cliArgument ] ,
114
+ _ => [ "TestServer.dll" , cliArgument ] ,
115
+ } ,
116
+ } ;
117
+
118
+ var transport = new StdioClientTransport ( options , LoggerFactory ) ;
119
+
120
+ // Act: Create client (handshake) and list tools to ensure full round trip works with the argument present.
121
+ await using var client = await McpClient . CreateAsync ( transport , loggerFactory : LoggerFactory , cancellationToken : TestContext . Current . CancellationToken ) ;
122
+ var tools = await client . ListToolsAsync ( cancellationToken : TestContext . Current . CancellationToken ) ;
123
+
124
+ // Assert
125
+ Assert . NotNull ( tools ) ;
126
+ Assert . NotEmpty ( tools ) ;
127
+
128
+ var result = await client . CallToolAsync ( "echoCliArg" , cancellationToken : TestContext . Current . CancellationToken ) ;
129
+ var content = Assert . IsType < TextContentBlock > ( Assert . Single ( result . Content ) ) ;
130
+ Assert . Equal ( cliArgumentValue ?? "" , content . Text ) ;
131
+ }
54
132
}
0 commit comments