diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 760012006745a87522eab539548cc7c43e184de3..df65f46798c0baa5c1acd199278e589874e2f230 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1418,6 +1418,7 @@ Input type: `AiActionInput`
 | <a id="mutationaiactiongeneratedescription"></a>`generateDescription` | [`AiGenerateDescriptionInput`](#aigeneratedescriptioninput) | Input for generate_description AI action. |
 | <a id="mutationaiactionresolvevulnerability"></a>`resolveVulnerability` | [`AiResolveVulnerabilityInput`](#airesolvevulnerabilityinput) | Input for resolve_vulnerability AI action. |
 | <a id="mutationaiactionsummarizecomments"></a>`summarizeComments` | [`AiSummarizeCommentsInput`](#aisummarizecommentsinput) | Input for summarize_comments AI action. |
+| <a id="mutationaiactionsummarizenewmergerequest"></a>`summarizeNewMergeRequest` | [`AiSummarizeNewMergeRequestInput`](#aisummarizenewmergerequestinput) | Input for summarize_new_merge_request AI action. |
 | <a id="mutationaiactionsummarizereview"></a>`summarizeReview` | [`AiSummarizeReviewInput`](#aisummarizereviewinput) | Input for summarize_review AI action. |
 
 #### Fields
@@ -34985,6 +34986,19 @@ see the associated mutation type above.
 | ---- | ---- | ----------- |
 | <a id="aisummarizecommentsinputresourceid"></a>`resourceId` | [`AiModelID!`](#aimodelid) | Global ID of the resource to mutate. |
 
+### `AiSummarizeNewMergeRequestInput`
+
+Summarize a new merge request based on two branches. Returns `null` if the `add_ai_summary_for_new_mr` feature flag is disabled.
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="aisummarizenewmergerequestinputresourceid"></a>`resourceId` | [`AiModelID!`](#aimodelid) | Global ID of the resource to mutate. |
+| <a id="aisummarizenewmergerequestinputsourcebranch"></a>`sourceBranch` | [`String!`](#string) | Source branch of the changes. |
+| <a id="aisummarizenewmergerequestinputsourceprojectid"></a>`sourceProjectId` | [`ID`](#id) | ID of the project where the changes are from. |
+| <a id="aisummarizenewmergerequestinputtargetbranch"></a>`targetBranch` | [`String!`](#string) | Target branch of where the changes will be merged into. |
+
 ### `AiSummarizeReviewInput`
 
 #### Arguments
diff --git a/ee/app/graphql/types/ai/summarize_new_merge_request_input_type.rb b/ee/app/graphql/types/ai/summarize_new_merge_request_input_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f83ffea88b7a3618840b8b9373b11e58d6d03f48
--- /dev/null
+++ b/ee/app/graphql/types/ai/summarize_new_merge_request_input_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+  module Ai
+    class SummarizeNewMergeRequestInputType < BaseMethodInputType
+      graphql_name 'AiSummarizeNewMergeRequestInput'
+      description "Summarize a new merge request based on two branches. " \
+                  "Returns `null` if the `add_ai_summary_for_new_mr` feature flag is disabled."
+
+      argument :source_project_id, ::GraphQL::Types::ID,
+        required: false,
+        description: 'ID of the project where the changes are from.'
+
+      argument :source_branch, ::GraphQL::Types::String,
+        required: true,
+        description: 'Source branch of the changes.'
+
+      argument :target_branch, ::GraphQL::Types::String,
+        required: true,
+        description: 'Target branch of where the changes will be merged into.'
+    end
+  end
+end
diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb
index a12cf9f01f2dec1013993a52647b0b79d3ffef62..57290eee3e6e8e47199b58f7a32f1002caa45e17 100644
--- a/ee/app/models/gitlab_subscriptions/features.rb
+++ b/ee/app/models/gitlab_subscriptions/features.rb
@@ -245,6 +245,7 @@ class Features
       ssh_key_expiration_policy
       summarize_mr_changes
       summarize_my_mr_code_review
+      summarize_new_merge_request
       summarize_notes
       summarize_submitted_review
       stale_runner_cleanup_for_namespace
diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb
index a76cafec400edf27816ade8c32f5d577d4178000..bc321761bf2b70d030c055c270d19c848f1a46f7 100644
--- a/ee/app/policies/ee/project_policy.rb
+++ b/ee/app/policies/ee/project_policy.rb
@@ -327,6 +327,16 @@ module ProjectPolicy
           ).allowed?
       end
 
+      with_scope :subject
+      condition(:summarize_new_merge_request_enabled) do
+        ::Feature.enabled?(:add_ai_summary_for_new_mr, subject) &&
+          ::Gitlab::Llm::FeatureAuthorizer.new(
+            container: subject,
+            current_user: user,
+            feature_name: :summarize_new_merge_request
+          ).allowed?
+      end
+
       with_scope :subject
       condition(:generate_description_enabled) do
         ::Gitlab::Llm::FeatureAuthorizer.new(
@@ -864,6 +874,10 @@ module ProjectPolicy
         fill_in_merge_request_template_enabled & can?(:create_merge_request_in)
       end.enable :fill_in_merge_request_template
 
+      rule do
+        summarize_new_merge_request_enabled & can?(:create_merge_request_in)
+      end.enable :summarize_new_merge_request
+
       rule do
         generate_description_enabled & can?(:create_issue)
       end.enable :generate_description
diff --git a/ee/app/services/llm/execute_method_service.rb b/ee/app/services/llm/execute_method_service.rb
index e989690d90980085addcc0a857b9c780879bf9c0..d200570a13447416bc88c859c44f89d470738fa4 100644
--- a/ee/app/services/llm/execute_method_service.rb
+++ b/ee/app/services/llm/execute_method_service.rb
@@ -10,6 +10,7 @@ class ExecuteMethodService < BaseService
       resolve_vulnerability: ::Llm::ResolveVulnerabilityService,
       summarize_comments: Llm::GenerateSummaryService,
       summarize_review: Llm::MergeRequests::SummarizeReviewService,
+      summarize_new_merge_request: Llm::SummarizeNewMergeRequestService,
       explain_code: Llm::ExplainCodeService,
       generate_description: Llm::GenerateDescriptionService,
       generate_commit_message: Llm::GenerateCommitMessageService,
diff --git a/ee/app/services/llm/summarize_new_merge_request_service.rb b/ee/app/services/llm/summarize_new_merge_request_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..354cd667cf558ee4d331fd6cdefa3ebafb95b8ee
--- /dev/null
+++ b/ee/app/services/llm/summarize_new_merge_request_service.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Llm
+  class SummarizeNewMergeRequestService < ::Llm::BaseService
+    def valid?
+      super &&
+        resource.is_a?(Project) &&
+        Ability.allowed?(user, :summarize_new_merge_request, resource)
+    end
+
+    private
+
+    def ai_action
+      :summarize_new_merge_request
+    end
+
+    def perform
+      schedule_completion_worker
+    end
+  end
+end
diff --git a/ee/config/feature_flags/development/add_ai_summary_for_new_mr.yml b/ee/config/feature_flags/development/add_ai_summary_for_new_mr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2071f95b4dbf368392035bfa49cdea840690c61e
--- /dev/null
+++ b/ee/config/feature_flags/development/add_ai_summary_for_new_mr.yml
@@ -0,0 +1,9 @@
+---
+name: add_ai_summary_for_new_mr
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429882
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142739
+rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17533
+milestone: '16.9'
+group: group::code review
+type: development
+default_enabled: false
diff --git a/ee/lib/gitlab/llm/completions_factory.rb b/ee/lib/gitlab/llm/completions_factory.rb
index 49bf6d48d607b5965e03566d131e984eeb616425..a4b3eb34016ee71377a9df96e0fabb232a693f9b 100644
--- a/ee/lib/gitlab/llm/completions_factory.rb
+++ b/ee/lib/gitlab/llm/completions_factory.rb
@@ -64,6 +64,11 @@ class CompletionsFactory
           prompt_class: ::Gitlab::Llm::Templates::SummarizeMergeRequest,
           feature_category: :code_review_workflow
         },
+        summarize_new_merge_request: {
+          service_class: ::Gitlab::Llm::VertexAi::Completions::SummarizeNewMergeRequest,
+          prompt_class: ::Gitlab::Llm::Templates::SummarizeNewMergeRequest,
+          feature_category: :code_review_workflow
+        },
         generate_cube_query: {
           service_class: ::Gitlab::Llm::VertexAi::Completions::GenerateCubeQuery,
           prompt_class: ::Gitlab::Llm::VertexAi::Templates::GenerateCubeQuery,
diff --git a/ee/lib/gitlab/llm/stage_check.rb b/ee/lib/gitlab/llm/stage_check.rb
index 424d1c2d2cd92aa249dbe2f3feb32b3786dc133e..486106de6c0f353753d02a6cc6defbd206cab9e0 100644
--- a/ee/lib/gitlab/llm/stage_check.rb
+++ b/ee/lib/gitlab/llm/stage_check.rb
@@ -14,6 +14,7 @@ class StageCheck
         :resolve_vulnerability,
         :generate_commit_message,
         :fill_in_merge_request_template,
+        :summarize_new_merge_request,
         :summarize_submitted_review
       ].freeze
       BETA_FEATURES = [:chat].freeze
diff --git a/ee/lib/gitlab/llm/templates/summarize_new_merge_request.rb b/ee/lib/gitlab/llm/templates/summarize_new_merge_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3d9e8287d8792f92a520442cbaf1242ce9b8a6f6
--- /dev/null
+++ b/ee/lib/gitlab/llm/templates/summarize_new_merge_request.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Llm
+    module Templates
+      class SummarizeNewMergeRequest
+        include Gitlab::Utils::StrongMemoize
+
+        CHARACTER_LIMIT = 2000
+
+        def initialize(user, project, params = {})
+          @user = user
+          @project = project
+          @params = params
+        end
+
+        def to_prompt
+          return if extracted_diff.blank?
+
+          <<~PROMPT
+            You are a code assistant, developed to help summarize code in non-technical terms.
+
+            ```
+            #{extracted_diff}
+            ```
+
+            The code above, enclosed by three ticks, is the code diff of a merge request.
+
+            Write a summary of the changes in couple sentences, the way an expert engineer would summarize the
+            changes using simple - generally non-technical - terms.
+
+            You MUST ensure that it is no longer than 1800 characters. A character is considered anything, not only
+            letters.
+          PROMPT
+        end
+
+        private
+
+        attr_reader :user, :project, :params
+
+        def extracted_diff
+          compare = CompareService
+            .new(source_project, params[:source_branch])
+            .execute(project, params[:target_branch])
+
+          return unless compare
+
+          # Extract only the diff strings and discard everything else
+          compare.raw_diffs.to_a.map do |raw_diff|
+            # Each diff string starts with information about the lines changed,
+            # bracketed by @@. Removing this saves us tokens.
+            #
+            # Ex: @@ -0,0 +1,58 @@\n+# frozen_string_literal: true\n+\n+module MergeRequests\n+
+
+            next if raw_diff.diff.encoding != Encoding::UTF_8 || raw_diff.has_binary_notice?
+
+            diff_output(raw_diff.old_path, raw_diff.new_path, raw_diff.diff.sub(Gitlab::Regex.git_diff_prefix, ""))
+          end.join.truncate_words(CHARACTER_LIMIT)
+        end
+        strong_memoize_attr :extracted_diff
+
+        def diff_output(old_path, new_path, diff)
+          <<~DIFF
+            --- #{old_path}
+            +++ #{new_path}
+            #{diff}
+          DIFF
+        end
+
+        def source_project
+          return project unless params[:source_project_id]
+
+          source_project = Project.find_by_id(params[:source_project_id])
+
+          return source_project if source_project.present? && user.can?(:create_merge_request_from, source_project)
+
+          project
+        end
+      end
+    end
+  end
+end
diff --git a/ee/lib/gitlab/llm/vertex_ai/completions/summarize_new_merge_request.rb b/ee/lib/gitlab/llm/vertex_ai/completions/summarize_new_merge_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2078a8d6299d490aba4af0f1636a8ddf901b809c
--- /dev/null
+++ b/ee/lib/gitlab/llm/vertex_ai/completions/summarize_new_merge_request.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Llm
+    module VertexAi
+      module Completions
+        class SummarizeNewMergeRequest < Gitlab::Llm::Completions::Base
+          def execute
+            response = response_for(user, project, options)
+            response_modifier = ::Gitlab::Llm::VertexAi::ResponseModifiers::Predictions.new(response)
+
+            ::Gitlab::Llm::GraphqlSubscriptionResponseService.new(
+              user, project, response_modifier, options: response_options
+            ).execute
+
+            response_modifier
+          end
+
+          private
+
+          def project
+            resource
+          end
+
+          def response_for(user, project, options)
+            template = ai_prompt_class.new(user, project, options)
+            request(user, template)
+          end
+
+          def request(user, template)
+            ::Gitlab::Llm::VertexAi::Client
+              .new(user, tracking_context: tracking_context)
+              .text(content: template.to_prompt)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/factories/ai_messages.rb b/ee/spec/factories/ai_messages.rb
index 50cc1a0cc6b5ea0a094f76e845b5ad499e10e1ff..578cb6c0d3169195746ced289e36aa23b9711904 100644
--- a/ee/spec/factories/ai_messages.rb
+++ b/ee/spec/factories/ai_messages.rb
@@ -60,6 +60,10 @@
       ai_action { :fill_in_merge_request_template }
     end
 
+    trait :summarize_new_merge_request do
+      ai_action { :summarize_new_merge_request }
+    end
+
     trait :generate_commit_message do
       ai_action { :generate_commit_message }
     end
diff --git a/ee/spec/lib/gitlab/llm/templates/summarize_new_merge_request_spec.rb b/ee/spec/lib/gitlab/llm/templates/summarize_new_merge_request_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..28dc3ef056e9a1f63cb51a44c2e7bf0c5fb1c05f
--- /dev/null
+++ b/ee/spec/lib/gitlab/llm/templates/summarize_new_merge_request_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Llm::Templates::SummarizeNewMergeRequest, feature_category: :code_review_workflow do
+  let_it_be(:project) { create(:project, :repository) }
+  let_it_be(:user) { project.owner }
+
+  let(:source_project) { project }
+  let(:source_branch) { 'feature' }
+  let(:target_branch) { 'master' }
+
+  describe '#to_prompt' do
+    let(:params) do
+      {
+        source_project_id: source_project.id,
+        source_branch: source_branch,
+        target_branch: target_branch
+      }
+    end
+
+    subject(:template) { described_class.new(user, project, params) }
+
+    shared_examples_for 'prompt without errors' do
+      it "returns a prompt with diff" do
+        expect(template.to_prompt)
+          .to include("+class Feature\n+  def foo\n+    puts 'bar'\n+  end\n+end")
+      end
+    end
+
+    it_behaves_like "prompt without errors"
+
+    it 'is under the character limit' do
+      expect(template.to_prompt.size).to be <= described_class::CHARACTER_LIMIT
+    end
+
+    context 'when user cannot create merge request from source_project_id' do
+      let_it_be(:source_project) { create(:project) }
+
+      it_behaves_like "prompt without errors"
+    end
+
+    context 'when no source_project_id is specified' do
+      let(:params) do
+        {
+          source_project_id: nil,
+          source_branch: source_branch,
+          target_branch: target_branch
+        }
+      end
+
+      it_behaves_like "prompt without errors"
+    end
+
+    context "when there is a diff with an edge case" do
+      let(:good_diff) { { diff: "@@ -0,0 +1 @@hellothere\n+🌚\n" } }
+      let(:compare) { instance_double(Compare) }
+
+      before do
+        allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare)
+      end
+
+      context 'when a diff is not encoded with UTF-8' do
+        let(:other_diff) do
+          { diff: "@@ -1 +1 @@\n-This should not be in the prompt\n+#{(0..255).map(&:chr).join}\n" }
+        end
+
+        let(:diff_files) { Gitlab::Git::DiffCollection.new([good_diff, other_diff]) }
+
+        it 'does not raise any error and not contain the non-UTF diff' do
+          allow(compare).to receive(:raw_diffs).and_return(diff_files)
+
+          expect { template.to_prompt }.not_to raise_error
+
+          expect(template.to_prompt).to include("hellothere")
+          expect(template.to_prompt).not_to include("This should not be in the prompt")
+        end
+      end
+
+      context 'when a diff contains the binary notice' do
+        let(:binary_message) { Gitlab::Git::Diff.binary_message('a', 'b') }
+        let(:other_diff) { { diff: binary_message } }
+        let(:diff_files) { Gitlab::Git::DiffCollection.new([good_diff, other_diff]) }
+
+        it 'does not contain the binary diff' do
+          allow(compare).to receive(:raw_diffs).and_return(diff_files)
+
+          expect(template.to_prompt).to include("hellothere")
+          expect(template.to_prompt).not_to include(binary_message)
+        end
+      end
+
+      context 'when extracted diff is blank' do
+        before do
+          allow(template).to receive(:extracted_diff).and_return([])
+        end
+
+        it 'returns nil' do
+          expect(template.to_prompt).to be_nil
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/lib/gitlab/llm/vertex_ai/completions/summarize_new_merge_request_spec.rb b/ee/spec/lib/gitlab/llm/vertex_ai/completions/summarize_new_merge_request_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..563d994896e8a5e15adc1b8fae0933e7d67b6aeb
--- /dev/null
+++ b/ee/spec/lib/gitlab/llm/vertex_ai/completions/summarize_new_merge_request_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Llm::VertexAi::Completions::SummarizeNewMergeRequest, feature_category: :code_review_workflow do
+  let(:prompt_class) { Gitlab::Llm::Templates::SummarizeNewMergeRequest }
+  let(:options) { {} }
+  let(:response_modifier) { double }
+  let(:response_service) { double }
+  let_it_be(:user) { create(:user) }
+  let_it_be(:project) { create(:project) }
+  let(:params) do
+    [user, project, response_modifier, { options: { ai_action: :summarize_new_merge_request, request_id: 'uuid' } }]
+  end
+
+  let(:prompt_message) do
+    build(:ai_message, :summarize_new_merge_request, user: user, resource: project, request_id: 'uuid')
+  end
+
+  let(:completion) { described_class.new(prompt_message, prompt_class, options) }
+
+  describe '#execute' do
+    context 'when the text client returns a successful response' do
+      let(:example_answer) { "Super cool merge request summary" }
+
+      let(:example_response) do
+        {
+          "predictions" => [
+            {
+              "content" => example_answer,
+              "safetyAttributes" => {
+                "categories" => ["Violent"],
+                "scores" => [0.4000000059604645],
+                "blocked" => false
+              }
+            }
+          ]
+        }
+      end
+
+      before do
+        allow_next_instance_of(Gitlab::Llm::VertexAi::Client) do |client|
+          allow(client).to receive(:text).and_return(example_response.to_json)
+        end
+      end
+
+      it 'publishes the content from the AI response' do
+        expect(::Gitlab::Llm::VertexAi::ResponseModifiers::Predictions)
+          .to receive(:new)
+          .with(example_response.to_json)
+          .and_return(response_modifier)
+
+        expect(::Gitlab::Llm::GraphqlSubscriptionResponseService)
+          .to receive(:new)
+          .with(*params)
+          .and_return(response_service)
+
+        expect(response_service).to receive(:execute)
+
+        completion.execute
+      end
+    end
+
+    context 'when the text client returns an unsuccessful response' do
+      let(:error) { { error: 'Error' } }
+
+      before do
+        allow_next_instance_of(Gitlab::Llm::VertexAi::Client) do |client|
+          allow(client).to receive(:text).and_return(error.to_json)
+        end
+      end
+
+      it 'publishes the error to the graphql subscription' do
+        expect(::Gitlab::Llm::VertexAi::ResponseModifiers::Predictions)
+          .to receive(:new)
+          .with(error.to_json)
+          .and_return(response_modifier)
+
+        expect(::Gitlab::Llm::GraphqlSubscriptionResponseService)
+          .to receive(:new)
+          .with(*params)
+          .and_return(response_service)
+
+        expect(response_service).to receive(:execute)
+
+        completion.execute
+      end
+    end
+  end
+end
diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb
index 2719043098047c24bacb33257490e779a17b9fbf..ff5e4d373c1585aef54a98fa0e7f81c047666954 100644
--- a/ee/spec/policies/project_policy_spec.rb
+++ b/ee/spec/policies/project_policy_spec.rb
@@ -3066,6 +3066,49 @@ def create_member_role(member, abilities = member_role_abilities)
     end
   end
 
+  describe 'summarize_new_merge_request policy' do
+    let_it_be(:namespace) { group }
+    let_it_be(:project) { private_project }
+    let_it_be(:current_user) { maintainer }
+
+    let(:authorizer) { instance_double(::Gitlab::Llm::FeatureAuthorizer) }
+
+    before do
+      allow(::Gitlab::Llm::FeatureAuthorizer).to receive(:new).and_return(authorizer)
+      allow(project).to receive(:namespace).and_return(namespace)
+    end
+
+    context "when feature is authorized" do
+      before do
+        allow(authorizer).to receive(:allowed?).and_return(true)
+      end
+
+      it { is_expected.to be_allowed(:summarize_new_merge_request) }
+
+      context 'when add_ai_summary_for_new_mr feature flag is disabled' do
+        before do
+          stub_feature_flags(add_ai_summary_for_new_mr: false)
+        end
+
+        it { is_expected.to be_disallowed(:summarize_new_merge_request) }
+      end
+
+      context 'when user cannot create_merge_request_in' do
+        let(:current_user) { guest }
+
+        it { is_expected.to be_disallowed(:summarize_new_merge_request) }
+      end
+    end
+
+    context "when feature is not authorized" do
+      before do
+        allow(authorizer).to receive(:allowed?).and_return(false)
+      end
+
+      it { is_expected.to be_disallowed(:summarize_new_merge_request) }
+    end
+  end
+
   describe 'admin_target_branch_rule policy' do
     let(:current_user) { owner }
 
diff --git a/ee/spec/requests/api/graphql/mutations/projects/summarize_new_merge_request_spec.rb b/ee/spec/requests/api/graphql/mutations/projects/summarize_new_merge_request_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..140d28ee8cdf5c3145146aff033e7641ec5d0f09
--- /dev/null
+++ b/ee/spec/requests/api/graphql/mutations/projects/summarize_new_merge_request_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe 'AiAction for Summarize New Merge Request', :saas, feature_category: :code_review_workflow do
+  include GraphqlHelpers
+  include Graphql::Subscriptions::Notes::Helper
+
+  let_it_be(:group) { create(:group_with_plan, :public, plan: :ultimate_plan) }
+  let_it_be(:project) { create(:project, :public, group: group) }
+  let_it_be(:current_user) { create(:user, developer_projects: [project]) }
+
+  let(:mutation) do
+    params = {
+      summarize_new_merge_request: {
+        resource_id: project.to_gid,
+        source_branch: 'feature',
+        target_branch: 'master'
+      }
+    }
+
+    graphql_mutation(:ai_action, params) do
+      <<-QL.strip_heredoc
+        errors
+      QL
+    end
+  end
+
+  before do
+    stub_ee_application_setting(should_check_namespace_plan: true)
+    stub_licensed_features(summarize_new_merge_request: true, ai_features: true, experimental_features: true)
+    group.namespace_settings.update!(experiment_features_enabled: true)
+  end
+
+  before_all do
+    group.add_developer(current_user)
+  end
+
+  it 'successfully performs an explain code request' do
+    expect(Llm::CompletionWorker).to receive(:perform_for).with(
+      an_object_having_attributes(
+        user: current_user,
+        resource: project,
+        ai_action: :summarize_new_merge_request),
+      hash_including(
+        source_branch: 'feature',
+        target_branch: 'master'
+      )
+    )
+
+    post_graphql_mutation(mutation, current_user: current_user)
+
+    expect(graphql_mutation_response(:ai_action)['errors']).to eq([])
+  end
+
+  context 'when ai_global_switch feature flag is disabled' do
+    before do
+      stub_feature_flags(ai_global_switch: false)
+    end
+
+    it 'returns nil' do
+      expect(Llm::CompletionWorker).not_to receive(:perform_for)
+
+      post_graphql_mutation(mutation, current_user: current_user)
+
+      expect(fresh_response_data['errors'][0]['message']).to eq("required feature flag is disabled.")
+    end
+  end
+
+  context 'when experiment_features_enabled disabled' do
+    before do
+      group.namespace_settings.update!(experiment_features_enabled: false)
+    end
+
+    it 'returns nil' do
+      expect(Llm::CompletionWorker).not_to receive(:perform_for)
+
+      post_graphql_mutation(mutation, current_user: current_user)
+    end
+  end
+end
diff --git a/ee/spec/services/llm/summarize_new_merge_request_service_spec.rb b/ee/spec/services/llm/summarize_new_merge_request_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2f029d248d9e934eac5b4d353ef1deba2e20bce0
--- /dev/null
+++ b/ee/spec/services/llm/summarize_new_merge_request_service_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Llm::SummarizeNewMergeRequestService, :saas, feature_category: :code_review_workflow do
+  let_it_be(:user) { create(:user) }
+  let_it_be_with_reload(:group) { create(:group_with_plan, plan: :ultimate_plan) }
+  let_it_be(:project) { create(:project, :public, group: group) }
+
+  let(:summarize_new_merge_request_enabled) { true }
+  let(:current_user) { user }
+
+  describe '#perform' do
+    include_context 'with ai features enabled for group'
+
+    before_all do
+      group.add_guest(user)
+    end
+
+    before do
+      allow(Ability).to receive(:allowed?).and_call_original
+      allow(Ability)
+        .to receive(:allowed?)
+        .with(user, :summarize_new_merge_request, project)
+        .and_return(summarize_new_merge_request_enabled)
+    end
+
+    subject { described_class.new(current_user, project, {}).execute }
+
+    it_behaves_like 'schedules completion worker' do
+      subject { described_class.new(current_user, project, options) }
+
+      let(:options) { {} }
+      let(:resource) { project }
+      let(:action_name) { :summarize_new_merge_request }
+    end
+
+    context 'when user is not member of project group' do
+      let(:current_user) { create(:user) }
+
+      it { is_expected.to be_error.and have_attributes(message: eq(described_class::INVALID_MESSAGE)) }
+    end
+
+    context 'when general feature flag is disabled' do
+      before do
+        stub_feature_flags(ai_global_switch: false)
+      end
+
+      it { is_expected.to be_error.and have_attributes(message: eq(described_class::INVALID_MESSAGE)) }
+    end
+
+    context 'when project is not a project' do
+      let(:project) { create(:epic, group: group) }
+
+      it { is_expected.to be_error.and have_attributes(message: eq(described_class::INVALID_MESSAGE)) }
+    end
+
+    context 'when user has no ability to summarize_new_merge_request' do
+      let(:summarize_new_merge_request_enabled) { false }
+
+      it { is_expected.to be_error.and have_attributes(message: eq(described_class::INVALID_MESSAGE)) }
+    end
+  end
+end