diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index b792411dbfe..d86890ce38a 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -3375,6 +3375,7 @@ a:focus, a:hover {
+
This section contains descriptions of the data types referenced
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index e0f4b6e199b..065a1d7d05e 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -311,6 +311,9 @@ const String FLUTTER_REQUEST_SET_WIDGET_PROPERTY_VALUE_ID = 'id';
const String FLUTTER_REQUEST_SET_WIDGET_PROPERTY_VALUE_VALUE = 'value';
const String FLUTTER_RESPONSE_GET_WIDGET_DESCRIPTION_PROPERTIES = 'properties';
const String FLUTTER_RESPONSE_SET_WIDGET_PROPERTY_VALUE_CHANGE = 'change';
+const String LSP_REQUEST_HANDLE = 'lsp.handle';
+const String LSP_REQUEST_HANDLE_LSP_MESSAGE = 'lspMessage';
+const String LSP_RESPONSE_HANDLE_LSP_RESPONSE = 'lspResponse';
const String SEARCH_NOTIFICATION_RESULTS = 'search.results';
const String SEARCH_NOTIFICATION_RESULTS_ID = 'id';
const String SEARCH_NOTIFICATION_RESULTS_IS_LAST = 'isLast';
diff --git a/pkg/analysis_server/lib/protocol/protocol_generated.dart b/pkg/analysis_server/lib/protocol/protocol_generated.dart
index ce86acd1449..e5c436d0283 100644
--- a/pkg/analysis_server/lib/protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_generated.dart
@@ -12992,6 +12992,130 @@ class LibraryPathSet implements HasToJson {
);
}
+/// lsp.handle params
+///
+/// {
+/// "lspMessage": object
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class LspHandleParams implements RequestParams {
+ /// The LSP RequestMessage.
+ Object lspMessage;
+
+ LspHandleParams(this.lspMessage);
+
+ factory LspHandleParams.fromJson(
+ JsonDecoder jsonDecoder, String jsonPath, Object? json) {
+ json ??= {};
+ if (json is Map) {
+ Object lspMessage;
+ if (json.containsKey('lspMessage')) {
+ lspMessage = json['lspMessage'] as Object;
+ } else {
+ throw jsonDecoder.mismatch(jsonPath, 'lspMessage');
+ }
+ return LspHandleParams(lspMessage);
+ } else {
+ throw jsonDecoder.mismatch(jsonPath, 'lsp.handle params', json);
+ }
+ }
+
+ factory LspHandleParams.fromRequest(Request request) {
+ return LspHandleParams.fromJson(
+ RequestDecoder(request), 'params', request.params);
+ }
+
+ @override
+ Map toJson() {
+ var result = {};
+ result['lspMessage'] = lspMessage;
+ return result;
+ }
+
+ @override
+ Request toRequest(String id) {
+ return Request(id, 'lsp.handle', toJson());
+ }
+
+ @override
+ String toString() => json.encode(toJson());
+
+ @override
+ bool operator ==(other) {
+ if (other is LspHandleParams) {
+ return lspMessage == other.lspMessage;
+ }
+ return false;
+ }
+
+ @override
+ int get hashCode => lspMessage.hashCode;
+}
+
+/// lsp.handle result
+///
+/// {
+/// "lspResponse": object
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class LspHandleResult implements ResponseResult {
+ /// The LSP ResponseMessage returned by the handler.
+ Object lspResponse;
+
+ LspHandleResult(this.lspResponse);
+
+ factory LspHandleResult.fromJson(
+ JsonDecoder jsonDecoder, String jsonPath, Object? json) {
+ json ??= {};
+ if (json is Map) {
+ Object lspResponse;
+ if (json.containsKey('lspResponse')) {
+ lspResponse = json['lspResponse'] as Object;
+ } else {
+ throw jsonDecoder.mismatch(jsonPath, 'lspResponse');
+ }
+ return LspHandleResult(lspResponse);
+ } else {
+ throw jsonDecoder.mismatch(jsonPath, 'lsp.handle result', json);
+ }
+ }
+
+ factory LspHandleResult.fromResponse(Response response) {
+ return LspHandleResult.fromJson(
+ ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id)),
+ 'result',
+ response.result);
+ }
+
+ @override
+ Map toJson() {
+ var result = {};
+ result['lspResponse'] = lspResponse;
+ return result;
+ }
+
+ @override
+ Response toResponse(String id) {
+ return Response(id, result: toJson());
+ }
+
+ @override
+ String toString() => json.encode(toJson());
+
+ @override
+ bool operator ==(other) {
+ if (other is LspHandleResult) {
+ return lspResponse == other.lspResponse;
+ }
+ return false;
+ }
+
+ @override
+ int get hashCode => lspResponse.hashCode;
+}
+
/// MessageAction
///
/// {
diff --git a/pkg/analysis_server/lib/src/handler/legacy/lsp_over_legacy_handler.dart b/pkg/analysis_server/lib/src/handler/legacy/lsp_over_legacy_handler.dart
new file mode 100644
index 00000000000..8f6de76bb21
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/lsp_over_legacy_handler.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:analysis_server/lsp_protocol/protocol_custom_generated.dart';
+import 'package:analysis_server/lsp_protocol/protocol_special.dart';
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/lsp/constants.dart';
+import 'package:analysis_server/src/lsp/handlers/handler_states.dart';
+import 'package:analysis_server/src/lsp/handlers/handlers.dart' as lsp;
+import 'package:analysis_server/src/lsp/json_parsing.dart';
+
+/// The handler for the `lsp.handle` request.
+class LspOverLegacyHandler extends LegacyHandler {
+ /// To match behaviour of the LSP server where only one
+ /// InitializedStateMessageHandler exists for a server (and handlers can be
+ /// stateful), we hand the handler off the server.
+ ///
+ /// Using a static causes issues for in-process tests, so this ensures a new
+ /// server always gets a new handler.
+ final _handlers = Expando();
+
+ LspOverLegacyHandler(
+ super.server, super.request, super.cancellationToken, super.performance) {
+ _handlers[server] ??= InitializedStateMessageHandler(server);
+ }
+
+ InitializedStateMessageHandler get handler => _handlers[server]!;
+
+ @override
+ Future handle() async {
+ final params = LspHandleParams.fromRequest(request);
+ final lspMessageJson = params.lspMessage;
+ final reporter = LspJsonReporter();
+ final lspMessage = lspMessageJson is Map &&
+ RequestMessage.canParse(lspMessageJson, reporter)
+ ? RequestMessage.fromJson(lspMessageJson)
+ : null;
+
+ if (lspMessage != null) {
+ await handleRequest(lspMessage);
+ } else {
+ final message =
+ "The 'lspMessage' parameter was not a valid LSP request:\n"
+ "${reporter.errors.join('\n')}";
+ final error = RequestError(RequestErrorCode.INVALID_PARAMETER, message);
+ sendResponse(Response(request.id, error: error));
+ }
+ }
+
+ Future handleRequest(RequestMessage message) async {
+ final messageInfo = lsp.MessageInfo(
+ performance: performance,
+ timeSinceRequest: request.timeSinceRequest,
+ );
+
+ ErrorOr