diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 8af0db644dd8e305a797f549b441cbfe512f0818..545d82b8f369b9730f1ebd735d71baba274f80e4 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -79,8 +79,14 @@ class QueryType < ::Types::BaseObject
 
     field :issue, Types::IssueType,
           null: true,
-          description: 'Find an Issue.' do
-            argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.'
+          description: 'Find an issue.' do
+            argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the issue.'
+          end
+
+    field :merge_request, Types::MergeRequestType,
+          null: true,
+          description: 'Find a merge request.' do
+            argument :id, ::Types::GlobalIDType[::MergeRequest], required: true, description: 'The global ID of the merge request.'
           end
 
     field :instance_statistics_measurements,
@@ -119,6 +125,13 @@ def issue(id:)
       GitlabSchema.find_by_gid(id)
     end
 
+    def merge_request(id:)
+      # TODO: remove this line when the compatibility layer is removed
+      # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+      id = ::Types::GlobalIDType[::MergeRequest].coerce_isolated_input(id)
+      GitlabSchema.find_by_gid(id)
+    end
+
     def milestone(id:)
       # TODO: remove this line when the compatibility layer is removed
       # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
diff --git a/changelogs/unreleased/graphql-expose-merge-request-at-root.yml b/changelogs/unreleased/graphql-expose-merge-request-at-root.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8bc84565c0106c0c2adf601b3cf4ba9b0d0a6660
--- /dev/null
+++ b/changelogs/unreleased/graphql-expose-merge-request-at-root.yml
@@ -0,0 +1,5 @@
+---
+title: Allow merge request search via GraphQL
+merge_request: 60190
+author: Lee Tickett @leetickett
+type: added
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 765478c73cd3d944cc0cc58cb9882811a5207e1b..8d80b69a3bbc326838c831a322f8f554ff849ccd 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -165,7 +165,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
 
 ### `Query.issue`
 
-Find an Issue.
+Find an issue.
 
 Returns [`Issue`](#issue).
 
@@ -173,7 +173,7 @@ Returns [`Issue`](#issue).
 
 | Name | Type | Description |
 | ---- | ---- | ----------- |
-| <a id="queryissueid"></a>`id` | [`IssueID!`](#issueid) | The global ID of the Issue. |
+| <a id="queryissueid"></a>`id` | [`IssueID!`](#issueid) | The global ID of the issue. |
 
 ### `Query.iteration`
 
@@ -197,6 +197,18 @@ This field returns a [connection](#connections). It accepts the
 four standard [pagination arguments](#connection-pagination-arguments):
 `before: String`, `after: String`, `first: Int`, `last: Int`.
 
+### `Query.mergeRequest`
+
+Find a merge request.
+
+Returns [`MergeRequest`](#mergerequest).
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="querymergerequestid"></a>`id` | [`MergeRequestID!`](#mergerequestid) | The global ID of the merge request. |
+
 ### `Query.metadata`
 
 Metadata about GitLab.
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index d3dcdd260b0ed7fa26ca1c1a86635feb3726aa89..a877e19c06968284511673eb95950100dccad4b4 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -21,6 +21,7 @@
       user
       users
       issue
+      merge_request
       usage_trends_measurements
       runner_platforms
     ]
@@ -60,11 +61,21 @@
   describe 'issue field' do
     subject { described_class.fields['issue'] }
 
-    it 'returns issue' do
+    it "finds an issue by it's gid" do
+      is_expected.to have_graphql_arguments(:id)
       is_expected.to have_graphql_type(Types::IssueType)
     end
   end
 
+  describe 'merge_request field' do
+    subject { described_class.fields['mergeRequest'] }
+
+    it "finds a merge_request by it's gid" do
+      is_expected.to have_graphql_arguments(:id)
+      is_expected.to have_graphql_type(Types::MergeRequestType)
+    end
+  end
+
   describe 'usage_trends_measurements field' do
     subject { described_class.fields['usageTrendsMeasurements'] }
 
diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb
index e8b8caf6c2da632f43d371ef386fd47b7bbf7533..42ca3348384abb3c9750e2ec3bd60b2826946511 100644
--- a/spec/requests/api/graphql/issue/issue_spec.rb
+++ b/spec/requests/api/graphql/issue/issue_spec.rb
@@ -76,7 +76,7 @@ def query(fields)
             post_graphql(query, current_user: current_user)
           end
 
-          it "returns the Issue and field #{params['field']}" do
+          it "returns the issue and field #{params['field']}" do
             expect(issue_data.keys).to eq([field])
           end
         end
@@ -86,7 +86,7 @@ def query(fields)
     context 'when selecting multiple fields' do
       let(:issue_fields) { ['title', 'description', 'updatedBy { username }'] }
 
-      it 'returns the Issue with the specified fields' do
+      it 'returns the issue with the specified fields' do
         post_graphql(query, current_user: current_user)
 
         expect(issue_data.keys).to eq %w[title description updatedBy]
@@ -115,7 +115,7 @@ def query(fields)
       end
     end
 
-    context 'when passed a non-Issue gid' do
+    context 'when passed a non-issue gid' do
       let(:mr) { create(:merge_request) }
 
       it 'returns an error' do
diff --git a/spec/requests/api/graphql/merge_request/merge_request_spec.rb b/spec/requests/api/graphql/merge_request/merge_request_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..75dd01a07630a7566d75028cc3f617af37254f35
--- /dev/null
+++ b/spec/requests/api/graphql/merge_request/merge_request_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.merge_request(id)' do
+  include GraphqlHelpers
+
+  let_it_be(:project) { create(:project, :empty_repo) }
+  let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+  let_it_be(:current_user) { create(:user) }
+  let_it_be(:merge_request_params) { { 'id' => merge_request.to_global_id.to_s } }
+
+  let(:merge_request_data) { graphql_data['mergeRequest'] }
+  let(:merge_request_fields) { all_graphql_fields_for('MergeRequest'.classify) }
+
+  let(:query) do
+    graphql_query_for('mergeRequest', merge_request_params, merge_request_fields)
+  end
+
+  it_behaves_like 'a working graphql query' do
+    before do
+      post_graphql(query, current_user: current_user)
+    end
+  end
+
+  it_behaves_like 'a noteable graphql type we can query' do
+    let(:noteable) { merge_request }
+    let(:project) { merge_request.project }
+    let(:path_to_noteable) { [:merge_request] }
+
+    before do
+      project.add_reporter(current_user)
+    end
+
+    def query(fields)
+      graphql_query_for('mergeRequest', merge_request_params, fields)
+    end
+  end
+
+  context 'when the user does not have access to the merge request' do
+    it 'returns nil' do
+      post_graphql(query)
+
+      expect(merge_request_data).to be nil
+    end
+  end
+
+  context 'when the user does have access' do
+    before do
+      project.add_reporter(current_user)
+    end
+
+    it 'returns the merge request' do
+      post_graphql(query, current_user: current_user)
+
+      expect(merge_request_data).to include(
+        'title' => merge_request.title,
+        'description' => merge_request.description
+      )
+    end
+
+    context 'when selecting any single field' do
+      where(:field) do
+        scalar_fields_of('MergeRequest').map { |name| [name] }
+      end
+
+      with_them do
+        it_behaves_like 'a working graphql query' do
+          let(:merge_request_fields) do
+            field
+          end
+
+          before do
+            post_graphql(query, current_user: current_user)
+          end
+
+          it "returns the merge request and field #{params['field']}" do
+            expect(merge_request_data.keys).to eq([field])
+          end
+        end
+      end
+    end
+
+    context 'when selecting multiple fields' do
+      let(:merge_request_fields) { ['title', 'description', 'author { username }'] }
+
+      it 'returns the merge request with the specified fields' do
+        post_graphql(query, current_user: current_user)
+
+        expect(merge_request_data.keys).to eq %w[title description author]
+        expect(merge_request_data['title']).to eq(merge_request.title)
+        expect(merge_request_data['description']).to eq(merge_request.description)
+        expect(merge_request_data['author']['username']).to eq(merge_request.author.username)
+      end
+    end
+
+    context 'when passed a non-merge request gid' do
+      let(:issue) { create(:issue) }
+
+      it 'returns an error' do
+        gid = issue.to_global_id.to_s
+        merge_request_params['id'] = gid
+
+        post_graphql(query, current_user: current_user)
+
+        expect(graphql_errors).not_to be nil
+        expect(graphql_errors.first['message']).to eq("\"#{gid}\" does not represent an instance of MergeRequest")
+      end
+    end
+  end
+end