@@ -198,6 +198,69 @@ func TestCoderTools(t *testing.T) {
198
198
testutil .RequireJSONEq (t ,expected ,actual )
199
199
})
200
200
201
+ t .Run ("tool_and_command_restrictions" ,func (t * testing.T ) {
202
+ // Given: a restricted MCP server with only allowed tools and commands
203
+ restrictedPty := ptytest .New (t )
204
+ allowedTools := []string {"coder_workspace_exec" }
205
+ allowedCommands := []string {"echo" ,"ls" }
206
+ restrictedMCPSrv ,closeRestrictedSrv := startTestMCPServer (ctx ,t ,restrictedPty .Input (),restrictedPty .Output ())
207
+ t .Cleanup (func () {
208
+ _ = closeRestrictedSrv ()
209
+ })
210
+ mcptools .AllTools ().
211
+ WithOnlyAllowed (allowedTools ... ).
212
+ Register (restrictedMCPSrv , mcptools.ToolDeps {
213
+ Client :memberClient ,
214
+ Logger :& logger ,
215
+ AllowedExecCommands :allowedCommands ,
216
+ })
217
+
218
+ // When: the tools/list command is called
219
+ toolsListCmd := makeJSONRPCRequest (t ,"tools/list" ,"" ,nil )
220
+ restrictedPty .WriteLine (toolsListCmd )
221
+ _ = restrictedPty .ReadLine (ctx )// skip the echo
222
+
223
+ // Then: the response is a list of only the allowed tools.
224
+ toolsListResponse := restrictedPty .ReadLine (ctx )
225
+ require .Contains (t ,toolsListResponse ,"coder_workspace_exec" )
226
+ require .NotContains (t ,toolsListResponse ,"coder_whoami" )
227
+
228
+ // When: a disallowed tool is called
229
+ disallowedToolCmd := makeJSONRPCRequest (t ,"tools/call" ,"coder_whoami" ,map [string ]any {})
230
+ restrictedPty .WriteLine (disallowedToolCmd )
231
+ _ = restrictedPty .ReadLine (ctx )// skip the echo
232
+
233
+ // Then: the response is an error indicating the tool is not available.
234
+ disallowedToolResponse := restrictedPty .ReadLine (ctx )
235
+ require .Contains (t ,disallowedToolResponse ,"error" )
236
+ require .Contains (t ,disallowedToolResponse ,"not found" )
237
+
238
+ // When: an allowed exec command is called
239
+ randString := testutil .GetRandomName (t )
240
+ allowedCmd := makeJSONRPCRequest (t ,"tools/call" ,"coder_workspace_exec" ,map [string ]any {
241
+ "workspace" :r .Workspace .ID .String (),
242
+ "command" :"echo " + randString ,
243
+ })
244
+
245
+ // Then: the response is the output of the command.
246
+ restrictedPty .WriteLine (allowedCmd )
247
+ _ = restrictedPty .ReadLine (ctx )// skip the echo
248
+ actual := restrictedPty .ReadLine (ctx )
249
+ require .Contains (t ,actual ,randString )
250
+
251
+ // When: a disallowed exec command is called
252
+ disallowedCmd := makeJSONRPCRequest (t ,"tools/call" ,"coder_workspace_exec" ,map [string ]any {
253
+ "workspace" :r .Workspace .ID .String (),
254
+ "command" :"evil --hax" ,
255
+ })
256
+
257
+ // Then: the response is an error indicating the command is not allowed.
258
+ restrictedPty .WriteLine (disallowedCmd )
259
+ _ = restrictedPty .ReadLine (ctx )// skip the echo
260
+ errorResponse := restrictedPty .ReadLine (ctx )
261
+ require .Contains (t ,errorResponse ,`command \"evil\" is not allowed` )
262
+ })
263
+
201
264
t .Run ("coder_start_workspace" ,func (t * testing.T ) {
202
265
// Given: a separate workspace in the stopped state
203
266
stopWs := dbfake .WorkspaceBuild (t ,store , database.WorkspaceTable {