diff --git a/MAINTAINERS b/MAINTAINERS
index 448fb55a09..58d94b392f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3106,6 +3106,7 @@ M: Mario Fleischmann <mario.fleischmann@lauterbach.com>
 S: Maintained
 F: mcd/*
 F: docs/interop/mcd.rst
+F: qapi/mcd.json
 
 Memory API
 M: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/docs/interop/mcd.rst b/docs/interop/mcd.rst
index 9587cfb010..de7b7a8d5c 100644
--- a/docs/interop/mcd.rst
+++ b/docs/interop/mcd.rst
@@ -38,6 +38,20 @@ The resulting system can be visualized as follows::
     | qemu-system-*  |      | -machine |           | -cpu |
     +----------------+      +----------+           +------+
 
+
+Debugging via QMP
+-----------------
+
+Since the MCD API does not define a communication protocol, the QEMU Machine
+Protocol (QMP) is utilized to implement a remote procedure call mechanism.
+Each function within the API corresponds to one QMP command, ensuring a
+one-to-one mapping between the API's functions and the QMP commands.
+
+If you are not familiar with QMP, see the :doc:`qmp-spec` for the
+protocol, and the :doc:`qemu-qmp-ref` for a full reference of all
+commands.
+
+
 API Reference
 -------------
 
diff --git a/mcd/mcdserver.c b/mcd/mcdserver.c
new file mode 100644
index 0000000000..a20708db1b
--- /dev/null
+++ b/mcd/mcdserver.c
@@ -0,0 +1,431 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * mcdserver - Multi-Core Debug (MCD) API implementation
+ *
+ * Copyright (c) 2025 Lauterbach GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "mcd_api.h"
+
+static const mcd_error_info_st MCD_ERROR_NOT_IMPLEMENTED = {
+    .return_status = MCD_RET_ACT_HANDLE_ERROR,
+    .error_code = MCD_ERR_FN_UNIMPLEMENTED,
+    .error_events = MCD_ERR_EVT_NONE,
+    .error_str = "",
+};
+
+static const mcd_error_info_st MCD_ERROR_NONE = {
+    .return_status = MCD_RET_ACT_NONE,
+    .error_code = MCD_ERR_NONE,
+    .error_events = MCD_ERR_EVT_NONE,
+    .error_str = "",
+};
+
+/**
+ * struct mcdserver_state - State of the MCD server
+ *
+ * @last_error: Error info of most recent executed function.
+ */
+typedef struct mcdserver_state {
+    const mcd_error_info_st *last_error;
+} mcdserver_state;
+
+static mcdserver_state g_server_state = {
+    .last_error = &MCD_ERROR_NONE,
+};
+
+mcd_return_et mcd_initialize_f(const mcd_api_version_st *version_req,
+                               mcd_impl_version_info_st *impl_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+void mcd_exit_f(void)
+{
+    return;
+}
+
+mcd_return_et mcd_qry_servers_f(const char *host, bool running,
+                                uint32_t start_index, uint32_t *num_servers,
+                                mcd_server_info_st *server_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_open_server_f(const char *system_key,
+                                const char *config_string,
+                                mcd_server_st **server)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_close_server_f(const mcd_server_st *server)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_set_server_config_f(const mcd_server_st *server,
+                                      const char *config_string)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_server_config_f(const mcd_server_st *server,
+                                      uint32_t *max_len,
+                                      char *config_string)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_systems_f(uint32_t start_index, uint32_t *num_systems,
+                                mcd_core_con_info_st *system_con_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_devices_f(const mcd_core_con_info_st *system_con_info,
+                                uint32_t start_index, uint32_t *num_devices,
+                                mcd_core_con_info_st *device_con_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_cores_f(const mcd_core_con_info_st *connection_info,
+                              uint32_t start_index, uint32_t *num_cores,
+                              mcd_core_con_info_st *core_con_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_core_modes_f(const mcd_core_st *core,
+                                   uint32_t start_index, uint32_t *num_modes,
+                                   mcd_core_mode_info_st *core_mode_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_open_core_f(const mcd_core_con_info_st *core_con_info,
+                              mcd_core_st **core)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_close_core_f(const mcd_core_st *core)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+void mcd_qry_error_info_f(const mcd_core_st *core,
+                          mcd_error_info_st *error_info)
+{
+    if (error_info) {
+        *error_info = *g_server_state.last_error;
+    }
+}
+
+mcd_return_et mcd_qry_device_description_f(const mcd_core_st *core, char *url,
+                                           uint32_t *url_length)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_max_payload_size_f(const mcd_core_st *core,
+                                         uint32_t *max_payload)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_input_handle_f(const mcd_core_st *core,
+                                     uint32_t *input_handle)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_mem_spaces_f(const mcd_core_st *core,
+                                   uint32_t start_index,
+                                   uint32_t *num_mem_spaces,
+                                   mcd_memspace_st *mem_spaces)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_mem_blocks_f(const mcd_core_st *core,
+                                   uint32_t mem_space_id, uint32_t start_index,
+                                   uint32_t *num_mem_blocks,
+                                   mcd_memblock_st *mem_blocks)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_active_overlays_f(const mcd_core_st *core,
+                                        uint32_t start_index,
+                                        uint32_t *num_active_overlays,
+                                        uint32_t *active_overlays)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_reg_groups_f(const mcd_core_st *core,
+                                   uint32_t start_index,
+                                   uint32_t *num_reg_groups,
+                                   mcd_register_group_st *reg_groups)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_reg_map_f(const mcd_core_st *core, uint32_t reg_group_id,
+                                uint32_t start_index, uint32_t *num_regs,
+                                mcd_register_info_st *reg_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_reg_compound_f(const mcd_core_st *core,
+                                     uint32_t compound_reg_id,
+                                     uint32_t start_index,
+                                     uint32_t *num_reg_ids,
+                                     uint32_t *reg_id_array)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_trig_info_f(const mcd_core_st *core,
+                                  mcd_trig_info_st *trig_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_ctrigs_f(const mcd_core_st *core, uint32_t start_index,
+                               uint32_t *num_ctrigs,
+                               mcd_ctrig_info_st *ctrig_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_create_trig_f(const mcd_core_st *core, void *trig,
+                                uint32_t *trig_id)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_trig_f(const mcd_core_st *core, uint32_t trig_id,
+                             uint32_t max_trig_size, void *trig)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_remove_trig_f(const mcd_core_st *core, uint32_t trig_id)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_trig_state_f(const mcd_core_st *core, uint32_t trig_id,
+                                   mcd_trig_state_st *trig_state)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_activate_trig_set_f(const mcd_core_st *core)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_remove_trig_set_f(const mcd_core_st *core)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_trig_set_f(const mcd_core_st *core, uint32_t start_index,
+                                 uint32_t *num_trigs, uint32_t *trig_ids)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_trig_set_state_f(const mcd_core_st *core,
+                                       mcd_trig_set_state_st *trig_state)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_execute_txlist_f(const mcd_core_st *core,
+                                   mcd_txlist_st *txlist)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_run_f(const mcd_core_st *core, bool global)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_stop_f(const mcd_core_st *core, bool global)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_run_until_f(const mcd_core_st *core, bool global,
+                              bool absolute_time, uint64_t run_until_time)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_current_time_f(const mcd_core_st *core,
+                                     uint64_t *current_time)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_step_f(const mcd_core_st *core, bool global,
+                         mcd_core_step_type_et step_type, uint32_t n_steps)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_set_global_f(const mcd_core_st *core, bool enable)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_state_f(const mcd_core_st *core, mcd_core_state_st *state)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_execute_command_f(const mcd_core_st *core,
+                                    const char *command_string,
+                                    uint32_t result_string_size,
+                                    char *result_string)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_rst_classes_f(const mcd_core_st *core,
+                                    uint32_t *rst_class_vector)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_rst_class_info_f(const mcd_core_st *core,
+                                       uint8_t rst_class,
+                                       mcd_rst_info_st *rst_info)
+{
+
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_rst_f(const mcd_core_st *core, uint32_t rst_class_vector,
+                        bool rst_and_halt)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_chl_open_f(const mcd_core_st *core, mcd_chl_st *channel)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_send_msg_f(const mcd_core_st *core, const mcd_chl_st *channel,
+                             uint32_t msg_len, const uint8_t *msg)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_receive_msg_f(const mcd_core_st *core,
+                                const mcd_chl_st *channel, uint32_t timeout,
+                                uint32_t *msg_len, uint8_t *msg)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_chl_reset_f(const mcd_core_st *core,
+                              const mcd_chl_st *channel)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_chl_close_f(const mcd_core_st *core,
+                              const mcd_chl_st *channel)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_traces_f(const mcd_core_st *core, uint32_t start_index,
+                               uint32_t *num_traces,
+                               mcd_trace_info_st *trace_info)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_qry_trace_state_f(const mcd_core_st *core, uint32_t trace_id,
+                                    mcd_trace_state_st *state)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_set_trace_state_f(const mcd_core_st *core, uint32_t trace_id,
+                                    mcd_trace_state_st *state)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
+
+mcd_return_et mcd_read_trace_f(const mcd_core_st *core, uint32_t trace_id,
+                               uint64_t start_index, uint32_t *num_frames,
+                               uint32_t trace_data_size, void *trace_data)
+{
+    g_server_state.last_error = &MCD_ERROR_NOT_IMPLEMENTED;
+    return g_server_state.last_error->return_status;
+}
diff --git a/mcd/mcdstub_qapi.c b/mcd/mcdstub_qapi.c
new file mode 100644
index 0000000000..6f79ae38a9
--- /dev/null
+++ b/mcd/mcdstub_qapi.c
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * MCD server stub using QMP
+ *
+ * see qapi/mcd.json for the declarations of the (un)marshalling functions
+ *
+ * Copyright (c) 2025 Lauterbach GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "mcd_api.h"
diff --git a/mcd/meson.build b/mcd/meson.build
new file mode 100644
index 0000000000..bc783dae54
--- /dev/null
+++ b/mcd/meson.build
@@ -0,0 +1,12 @@
+# MCD is only supported in system emulation
+mcd_system_ss = ss.source_set()
+
+mcd_system_ss.add(files('mcdserver.c', 'mcdstub_qapi.c'))
+mcd_system_ss = mcd_system_ss.apply({})
+
+libmcd_system = static_library('mcd_system',
+                                mcd_system_ss.sources() + genh,
+                                build_by_default: false)
+
+mcd_system = declare_dependency(objects: libmcd_system.extract_all_objects(recursive: false))
+system_ss.add(mcd_system)
diff --git a/meson.build b/meson.build
index 4899d896de..5e1b4865f7 100644
--- a/meson.build
+++ b/meson.build
@@ -3685,6 +3685,7 @@ subdir('authz')
 subdir('crypto')
 subdir('ui')
 subdir('gdbstub')
+subdir('mcd')
 if have_system
   subdir('hw')
 else
diff --git a/qapi/mcd.json b/qapi/mcd.json
new file mode 100644
index 0000000000..701fd03ece
--- /dev/null
+++ b/qapi/mcd.json
@@ -0,0 +1,6 @@
+# -*- Mode: Python -*-
+# vim: filetype=python
+
+##
+# = Multi-Core Debug (MCD) API
+##
diff --git a/qapi/meson.build b/qapi/meson.build
index eadde4db30..d05f17fe95 100644
--- a/qapi/meson.build
+++ b/qapi/meson.build
@@ -40,6 +40,7 @@ qapi_all_modules = [
   'machine-common',
   'machine',
   'machine-target',
+  'mcd',
   'migration',
   'misc',
   'misc-target',
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index 2877aff73d..07907b0a53 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -82,3 +82,4 @@
 { 'include': 'cryptodev.json' }
 { 'include': 'cxl.json' }
 { 'include': 'uefi.json' }
+{ 'include': 'mcd.json' }
