diff --git a/coderd/mcp/mcp.go b/coderd/mcp/mcp.go index 84cbfdda2cd9f..f17ab5ae7cd93 100644 --- a/coderd/mcp/mcp.go +++ b/coderd/mcp/mcp.go @@ -79,11 +79,15 @@ func (s *Server) RegisterTools(client *codersdk.Client) error { return xerrors.Errorf("failed to initialize tool dependencies: %w", err) } - // Register all available tools + // Register all available tools, but exclude tools that require dependencies not available in the + // remote MCP context for _, tool := range toolsdk.All { + if tool.Name == toolsdk.ToolNameReportTask { + continue + } + s.mcpServer.AddTools(mcpFromSDK(tool, toolDeps)) } - return nil } diff --git a/codersdk/toolsdk/toolsdk.go b/codersdk/toolsdk/toolsdk.go index 670b5af145786..862d0c34a5316 100644 --- a/codersdk/toolsdk/toolsdk.go +++ b/codersdk/toolsdk/toolsdk.go @@ -253,6 +253,10 @@ ONLY report an "idle" or "failure" state if you have FULLY completed the task. if len(args.Summary) > 160 { return codersdk.Response{}, xerrors.New("summary must be less than 160 characters") } + // Check if task reporting is available to prevent nil pointer dereference + if deps.report == nil { + return codersdk.Response{}, xerrors.New("task reporting not available. Please ensure a task reporter is configured.") + } err := deps.report(args) if err != nil { return codersdk.Response{}, err diff --git a/codersdk/toolsdk/toolsdk_test.go b/codersdk/toolsdk/toolsdk_test.go index 5e4a33ba67575..c201190bd3456 100644 --- a/codersdk/toolsdk/toolsdk_test.go +++ b/codersdk/toolsdk/toolsdk_test.go @@ -686,3 +686,57 @@ func TestMain(m *testing.M) { os.Exit(code) } + +func TestReportTaskNilPointerDeref(t *testing.T) { + t.Parallel() + + // Create deps without a task reporter (simulating remote MCP server scenario) + client, _ := coderdtest.NewWithDatabase(t, nil) + deps, err := toolsdk.NewDeps(client) + require.NoError(t, err) + + // Prepare test arguments + args := toolsdk.ReportTaskArgs{ + Summary: "Test task", + Link: "https://example.com", + State: string(codersdk.WorkspaceAppStatusStateWorking), + } + + _, err = toolsdk.ReportTask.Handler(t.Context(), deps, args) + + // We expect an error, not a panic + require.Error(t, err) + require.Contains(t, err.Error(), "task reporting not available") +} + +func TestReportTaskWithReporter(t *testing.T) { + t.Parallel() + + // Create deps with a task reporter + client, _ := coderdtest.NewWithDatabase(t, nil) + + called := false + reporter := func(args toolsdk.ReportTaskArgs) error { + called = true + require.Equal(t, "Test task", args.Summary) + require.Equal(t, "https://example.com", args.Link) + require.Equal(t, string(codersdk.WorkspaceAppStatusStateWorking), args.State) + return nil + } + + deps, err := toolsdk.NewDeps(client, toolsdk.WithTaskReporter(reporter)) + require.NoError(t, err) + + args := toolsdk.ReportTaskArgs{ + Summary: "Test task", + Link: "https://example.com", + State: string(codersdk.WorkspaceAppStatusStateWorking), + } + + result, err := toolsdk.ReportTask.Handler(t.Context(), deps, args) + require.NoError(t, err) + require.True(t, called) + + // Verify response + require.Equal(t, "Thanks for reporting!", result.Message) +}