diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpRetryConfig.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpRetryConfig.java
new file mode 100644
index 0000000000..44889f9ffa
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpRetryConfig.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.config.connector.mcp;
+
+import lombok.Data;
+
+@Data
+public class McpRetryConfig {
+ // maximum number of retries, default 2, minimum 0
+ private int maxRetries = 2;
+
+ // retry interval, default 1000ms
+ private int interval = 1000;
+
+ // Default value is false, indicating that only requests with network-level errors will be retried.
+ // If set to true, all failed requests will be retried, including network-level errors and non-2xx responses.
+ private boolean retryOnNonSuccess = false;
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpSinkConfig.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpSinkConfig.java
new file mode 100644
index 0000000000..ce645513c5
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpSinkConfig.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.config.connector.mcp;
+
+import org.apache.eventmesh.common.config.connector.SinkConfig;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class McpSinkConfig extends SinkConfig {
+
+ public SinkConnectorConfig connectorConfig;
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpSourceConfig.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpSourceConfig.java
new file mode 100644
index 0000000000..320cc3761f
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/McpSourceConfig.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.config.connector.mcp;
+
+import org.apache.eventmesh.common.config.connector.SourceConfig;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class McpSourceConfig extends SourceConfig {
+
+ public SourceConnectorConfig connectorConfig;
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/SinkConnectorConfig.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/SinkConnectorConfig.java
new file mode 100644
index 0000000000..54a02fb6b5
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/SinkConnectorConfig.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.config.connector.mcp;
+
+
+import lombok.Data;
+
+@Data
+public class SinkConnectorConfig {
+
+ private String connectorName;
+
+ private String[] urls;
+
+ // keepAlive, default true
+ private boolean keepAlive = true;
+
+ // timeunit: ms, default 60000ms
+ private int keepAliveTimeout = 60 * 1000; // Keep units consistent
+
+ // timeunit: ms, default 5000ms, recommended scope: 5000ms - 10000ms
+ private int connectionTimeout = 5000;
+
+ // timeunit: ms, default 5000ms
+ private int idleTimeout = 5000;
+
+ // maximum number of HTTP/1 connections a client will pool, default 50
+ private int maxConnectionPoolSize = 50;
+
+ // retry config
+ private McpRetryConfig retryConfig = new McpRetryConfig();
+
+ private String deliveryStrategy = "ROUND_ROBIN";
+
+ private boolean skipDeliverException = false;
+
+ // managed pipelining param, default true
+ private boolean isParallelized = true;
+
+ private int parallelism = 2;
+
+
+ /**
+ * Fill default values if absent (When there are multiple default values for a field)
+ *
+ * @param config SinkConnectorConfig
+ */
+ public static void populateFieldsWithDefaults(SinkConnectorConfig config) {
+ /*
+ * set default values for idleTimeout
+ * recommended scope: common(5s - 10s), webhook(15s - 30s)
+ */
+ final int commonHttpIdleTimeout = 5000;
+
+ // Set default values for idleTimeout
+ if (config.getIdleTimeout() == 0) {
+ config.setIdleTimeout(commonHttpIdleTimeout);
+ }
+
+ }
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/SourceConnectorConfig.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/SourceConnectorConfig.java
new file mode 100644
index 0000000000..18808942e0
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/config/connector/mcp/SourceConnectorConfig.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.config.connector.mcp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import lombok.Data;
+
+@Data
+public class SourceConnectorConfig {
+
+ private String connectorName;
+
+ private String path = "/";
+
+ private int port;
+
+ // timeunit: ms, default 5000ms
+ private int idleTimeout = 5000;
+
+ /**
+ *
+ *
The maximum size allowed for form attributes when Content-Type is application/x-www-form-urlencoded or multipart/form-data
+ *
Default is 1MB (1024 * 1024 bytes).
+ *
If you receive a "size exceed allowed maximum capacity" error, you can increase this value.
+ *
Note: This applies only when handling form data submissions.
+ *
+ */
+ private int maxFormAttributeSize = 1024 * 1024;
+
+ // max size of the queue, default 1000
+ private int maxStorageSize = 1000;
+
+ // batch size, default 10
+ private int batchSize = 10;
+
+ // protocol, default CloudEvent
+ private String protocol = "Mcp";
+
+ // extra config, e.g. GitHub secret
+ private Map extraConfig = new HashMap<>();
+
+ // data consistency enabled, default true
+ private boolean dataConsistencyEnabled = false;
+
+ private String forwardPath;
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/utils/JsonUtils.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/utils/JsonUtils.java
index f2328541c4..9b1bfe6f4e 100644
--- a/eventmesh-common/src/main/java/org/apache/eventmesh/common/utils/JsonUtils.java
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/utils/JsonUtils.java
@@ -162,6 +162,17 @@ public static T parseObject(byte[] bytes, Class clazz) {
}
}
+ public static T parseObject(String text, TypeReference typeReference) {
+ if (StringUtils.isEmpty(text)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, typeReference);
+ } catch (JsonProcessingException e) {
+ throw new JsonException("deserialize json string to object error", e);
+ }
+ }
+
/**
* parse json string to object.
*
diff --git a/eventmesh-connectors/eventmesh-connector-http/src/main/resources/server-config.yml b/eventmesh-connectors/eventmesh-connector-http/src/main/resources/server-config.yml
index 0cd7b5b5ab..5f66dd0f68 100644
--- a/eventmesh-connectors/eventmesh-connector-http/src/main/resources/server-config.yml
+++ b/eventmesh-connectors/eventmesh-connector-http/src/main/resources/server-config.yml
@@ -16,4 +16,4 @@
#
sourceEnable: true
-sinkEnable: false
+sinkEnable: true
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/build.gradle b/eventmesh-connectors/eventmesh-connector-mcp/build.gradle
new file mode 100644
index 0000000000..82072c2876
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+dependencies {
+ api project(":eventmesh-openconnect:eventmesh-openconnect-java")
+ implementation project(":eventmesh-common")
+ implementation project(":eventmesh-connectors:eventmesh-connector-http")
+ implementation project(":eventmesh-protocol-plugin:eventmesh-protocol-api")
+ implementation "io.cloudevents:cloudevents-core"
+ implementation "com.google.guava:guava"
+ implementation "io.cloudevents:cloudevents-json-jackson"
+ implementation ("io.grpc:grpc-protobuf:1.68.0") {
+ exclude group: "com.google.protobuf", module: "protobuf-java"
+ }
+ implementation 'io.cloudevents:cloudevents-http-vertx:3.0.0'
+ implementation 'io.vertx:vertx-web:4.5.8'
+ implementation 'io.vertx:vertx-web-client:4.5.9'
+ implementation 'dev.failsafe:failsafe:3.3.2'
+
+
+ testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.4'
+ testImplementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.4'
+ testImplementation 'org.mock-server:mockserver-netty:5.15.0'
+ implementation 'io.netty:netty-codec-http:4.1.114.Final'
+ compileOnly 'org.projectlombok:lombok'
+ annotationProcessor 'org.projectlombok:lombok'
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/gradle.properties b/eventmesh-connectors/eventmesh-connector-mcp/gradle.properties
new file mode 100644
index 0000000000..5e98eb968e
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/gradle.properties
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+pluginType=connector
+pluginName=mcp
\ No newline at end of file
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/config/McpServerConfig.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/config/McpServerConfig.java
new file mode 100644
index 0000000000..33357b6a29
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/config/McpServerConfig.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.eventmesh.connector.mcp.config;
+
+import org.apache.eventmesh.common.config.connector.Config;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class McpServerConfig extends Config {
+
+ private boolean sourceEnable;
+
+ private boolean sinkEnable;
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/server/McpConnectServer.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/server/McpConnectServer.java
new file mode 100644
index 0000000000..71ea9ae752
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/server/McpConnectServer.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.server;
+
+import org.apache.eventmesh.connector.mcp.config.McpServerConfig;
+import org.apache.eventmesh.connector.mcp.sink.McpSinkConnector;
+import org.apache.eventmesh.connector.mcp.source.McpSourceConnector;
+import org.apache.eventmesh.openconnect.Application;
+import org.apache.eventmesh.openconnect.util.ConfigUtil;
+
+public class McpConnectServer {
+ public static void main(String[] args) throws Exception {
+ McpServerConfig serverConfig = ConfigUtil.parse(McpServerConfig.class, "server-config.yml");
+
+ if (serverConfig.isSourceEnable()) {
+ Application mcpSourceApp = new Application();
+ mcpSourceApp.run(McpSourceConnector.class);
+ }
+
+ if (serverConfig.isSinkEnable()) {
+ Application mcpSinkApp = new Application();
+ mcpSinkApp.run(McpSinkConnector.class);
+ }
+ }
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/McpSinkConnector.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/McpSinkConnector.java
new file mode 100644
index 0000000000..3d65fb9b5d
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/McpSinkConnector.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink;
+
+import org.apache.eventmesh.common.EventMeshThreadFactory;
+import org.apache.eventmesh.common.config.connector.Config;
+import org.apache.eventmesh.common.config.connector.mcp.McpSinkConfig;
+import org.apache.eventmesh.common.config.connector.mcp.SinkConnectorConfig;
+import org.apache.eventmesh.connector.mcp.sink.handler.McpSinkHandler;
+import org.apache.eventmesh.connector.mcp.sink.handler.impl.CommonMcpSinkHandler;
+import org.apache.eventmesh.connector.mcp.sink.handler.impl.McpSinkHandlerRetryWrapper;
+import org.apache.eventmesh.openconnect.api.ConnectorCreateService;
+import org.apache.eventmesh.openconnect.api.connector.ConnectorContext;
+import org.apache.eventmesh.openconnect.api.connector.SinkConnectorContext;
+import org.apache.eventmesh.openconnect.api.sink.Sink;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+
+@Slf4j
+public class McpSinkConnector implements Sink, ConnectorCreateService {
+
+ private McpSinkConfig mcpSinkConfig;
+
+ @Getter
+ private McpSinkHandler sinkHandler;
+
+ private ThreadPoolExecutor executor;
+
+ private final AtomicBoolean isStart = new AtomicBoolean(false);
+
+ @Override
+ public Class extends Config> configClass() {
+ return McpSinkConfig.class;
+ }
+
+ @Override
+ public Sink create() {
+ return new McpSinkConnector();
+ }
+
+ @Override
+ public void init(Config config) throws Exception {
+ this.mcpSinkConfig = (McpSinkConfig) config;
+ doInit();
+ }
+
+ @Override
+ public void init(ConnectorContext connectorContext) throws Exception {
+ SinkConnectorContext sinkConnectorContext = (SinkConnectorContext) connectorContext;
+ this.mcpSinkConfig = (McpSinkConfig) sinkConnectorContext.getSinkConfig();
+ doInit();
+ }
+
+ @SneakyThrows
+ private void doInit() {
+ // Fill default values if absent
+ SinkConnectorConfig.populateFieldsWithDefaults(this.mcpSinkConfig.connectorConfig);
+ // Create different handlers for different configurations
+ McpSinkHandler nonRetryHandler;
+
+ nonRetryHandler = new CommonMcpSinkHandler(this.mcpSinkConfig.connectorConfig);
+
+ int maxRetries = this.mcpSinkConfig.connectorConfig.getRetryConfig().getMaxRetries();
+ if (maxRetries == 0) {
+ // Use the original sink handler
+ this.sinkHandler = nonRetryHandler;
+ } else if (maxRetries > 0) {
+ // Wrap the sink handler with a retry handler
+ this.sinkHandler = new McpSinkHandlerRetryWrapper(this.mcpSinkConfig.connectorConfig, nonRetryHandler);
+ } else {
+ throw new IllegalArgumentException("Max retries must be greater than or equal to 0.");
+ }
+
+ boolean isParallelized = this.mcpSinkConfig.connectorConfig.isParallelized();
+ int parallelism = isParallelized ? this.mcpSinkConfig.connectorConfig.getParallelism() : 1;
+
+ // Use the executor's built-in queue with a reasonable capacity
+ executor = new ThreadPoolExecutor(
+ parallelism,
+ parallelism,
+ 0L,
+ TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<>(), // Built-in queue with capacity
+ new EventMeshThreadFactory("mcp-sink-handler")
+ );
+ }
+
+ @Override
+ public void start() throws Exception {
+ this.sinkHandler.start();
+ isStart.set(true);
+ }
+
+ @Override
+ public void commit(ConnectRecord record) {
+
+ }
+
+ @Override
+ public String name() {
+ return this.mcpSinkConfig.connectorConfig.getConnectorName();
+ }
+
+ @Override
+ public void onException(ConnectRecord record) {
+
+ }
+
+ @Override
+ public void stop() throws Exception {
+ isStart.set(false);
+
+ log.info("Stopping mcp sink connector, shutting down executor...");
+ executor.shutdown();
+
+ try {
+ // Wait for existing tasks to complete
+ if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
+ log.warn("Executor did not terminate gracefully, forcing shutdown");
+ executor.shutdownNow();
+ // Wait a bit more for tasks to respond to being cancelled
+ if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
+ log.error("Executor did not terminate after forced shutdown");
+ }
+ }
+ } catch (InterruptedException e) {
+ log.warn("Interrupted while waiting for executor termination");
+ executor.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+
+ this.sinkHandler.stop();
+ }
+
+ @Override
+ public void put(List sinkRecords) {
+ if (!isStart.get()) {
+ log.warn("Connector is not started, ignoring sink records");
+ return;
+ }
+
+ for (ConnectRecord sinkRecord : sinkRecords) {
+ if (Objects.isNull(sinkRecord)) {
+ log.warn("ConnectRecord data is null, ignore.");
+ continue;
+ }
+ log.info("McpSinkConnector put record: {}", sinkRecord);
+
+ try {
+ // Use executor.submit() instead of custom queue
+ executor.submit(() -> {
+ try {
+ sinkHandler.handle(sinkRecord);
+ } catch (Exception e) {
+ log.error("Failed to handle sink record via mcp", e);
+ }
+ });
+ } catch (Exception e) {
+ log.error("Failed to submit sink record to executor", e);
+ }
+ }
+ }
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpAttemptEvent.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpAttemptEvent.java
new file mode 100644
index 0000000000..451fc3523d
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpAttemptEvent.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.data;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Single MCP attempt event
+ */
+public class McpAttemptEvent {
+
+ public static final String PREFIX = "mcp-attempt-event-";
+
+ private final int maxAttempts;
+
+ private final AtomicInteger attempts;
+
+ private Throwable lastException;
+
+
+ public McpAttemptEvent(int maxAttempts) {
+ this.maxAttempts = maxAttempts;
+ this.attempts = new AtomicInteger(0);
+ }
+
+ /**
+ * Increment the attempts
+ */
+ public void incrementAttempts() {
+ attempts.incrementAndGet();
+ }
+
+ /**
+ * Update the event, incrementing the attempts and setting the last exception
+ *
+ * @param exception the exception to update, can be null
+ */
+ public void updateEvent(Throwable exception) {
+ // increment the attempts
+ incrementAttempts();
+
+ // update the last exception
+ lastException = exception;
+ }
+
+ /**
+ * Check if the attempts are less than the maximum attempts
+ *
+ * @return true if the attempts are less than the maximum attempts, false otherwise
+ */
+ public boolean canAttempt() {
+ return attempts.get() < maxAttempts;
+ }
+
+ public boolean isComplete() {
+ if (attempts.get() == 0) {
+ // No start yet
+ return false;
+ }
+
+ // If no attempt can be made or the last exception is null, the event completed
+ return !canAttempt() || lastException == null;
+ }
+
+
+ public int getMaxAttempts() {
+ return maxAttempts;
+ }
+
+ public int getAttempts() {
+ return attempts.get();
+ }
+
+ public Throwable getLastException() {
+ return lastException;
+ }
+
+ /**
+ * Get the limited exception message with the default limit of 256
+ *
+ * @return the limited exception message
+ */
+ public String getLimitedExceptionMessage() {
+ return getLimitedExceptionMessage(256);
+ }
+
+ /**
+ * Get the limited exception message with the specified limit
+ *
+ * @param maxLimit the maximum limit of the exception message
+ * @return the limited exception message
+ */
+ public String getLimitedExceptionMessage(int maxLimit) {
+ if (lastException == null) {
+ return "";
+ }
+ String message = lastException.getMessage();
+ if (message == null) {
+ return "";
+ }
+ if (message.length() > maxLimit) {
+ return message.substring(0, maxLimit);
+ }
+ return message;
+ }
+
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpConnectRecord.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpConnectRecord.java
new file mode 100644
index 0000000000..26f9c1e6fb
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpConnectRecord.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.data;
+
+import org.apache.eventmesh.common.remote.offset.http.HttpRecordOffset;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.KeyValue;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import lombok.Builder;
+import lombok.Getter;
+
+/**
+ * a special ConnectRecord for McpSinkConnector
+ */
+@Getter
+@Builder
+public class McpConnectRecord implements Serializable {
+
+ private static final long serialVersionUID = 5271462532332251473L;
+
+ /**
+ * The unique identifier for the McpConnectRecord
+ */
+ private final String mcpRecordId = UUID.randomUUID().toString();
+
+ /**
+ * The time when the McpConnectRecord was created
+ */
+ private LocalDateTime createTime;
+
+ /**
+ * The type of the McpConnectRecord
+ */
+ private String type;
+
+ /**
+ * The event id of the McpConnectRecord
+ */
+ private String eventId;
+
+ private Object data;
+
+ private KeyValue extensions;
+
+ @Override
+ public String toString() {
+ return "McpConnectRecord{"
+ + "createTime=" + createTime
+ + ", mcpRecordId='" + mcpRecordId
+ + ", type='" + type
+ + ", eventId='" + eventId
+ + ", data=" + data
+ + ", extensions=" + extensions
+ + '}';
+ }
+
+ /**
+ * Convert ConnectRecord to McpConnectRecord
+ *
+ * @param record the ConnectRecord to convert
+ * @return the converted McpConnectRecord
+ */
+ public static McpConnectRecord convertConnectRecord(ConnectRecord record, String type) {
+ Map offsetMap = new HashMap<>();
+ if (record != null && record.getPosition() != null && record.getPosition().getRecordOffset() != null) {
+ if (HttpRecordOffset.class.equals(record.getPosition().getRecordOffsetClazz())) {
+ offsetMap = ((HttpRecordOffset) record.getPosition().getRecordOffset()).getOffsetMap();
+ }
+ }
+ String offset = "0";
+ if (!offsetMap.isEmpty()) {
+ offset = offsetMap.values().iterator().next().toString();
+ }
+ if (record.getData() instanceof byte[]) {
+ String data = Base64.getEncoder().encodeToString((byte[]) record.getData());
+ record.addExtension("isBase64", true);
+ return McpConnectRecord.builder()
+ .type(type)
+ .createTime(LocalDateTime.now())
+ .eventId(type + "-" + offset)
+ .data(data)
+ .extensions(record.getExtensions())
+ .build();
+ } else {
+ record.addExtension("isBase64", false);
+ return McpConnectRecord.builder()
+ .type(type)
+ .createTime(LocalDateTime.now())
+ .eventId(type + "-" + offset)
+ .data(record.getData())
+ .extensions(record.getExtensions())
+ .build();
+ }
+ }
+
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportMetadata.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportMetadata.java
new file mode 100644
index 0000000000..72595b2637
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportMetadata.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * Metadata for an MCP export operation.
+ */
+@Data
+@Builder
+public class McpExportMetadata implements Serializable {
+
+ private static final long serialVersionUID = 1121010466793041920L;
+
+ private String url;
+
+ private int code;
+
+ private String message;
+
+ private LocalDateTime receivedTime;
+
+ private String recordId;
+
+ private String retriedBy;
+
+ private int retryNum;
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportRecord.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportRecord.java
new file mode 100644
index 0000000000..c9a35c193b
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportRecord.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.data;
+
+import java.io.Serializable;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Represents an MCP export record containing metadata and data to be exported.
+ */
+@Data
+@AllArgsConstructor
+public class McpExportRecord implements Serializable {
+
+ private static final long serialVersionUID = 6010283911452947157L;
+
+ private McpExportMetadata metadata;
+
+ private Object data;
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportRecordPage.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportRecordPage.java
new file mode 100644
index 0000000000..3e0e615523
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/McpExportRecordPage.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.data;
+
+import java.io.Serializable;
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Represents a page of MCP export records.
+ */
+@Data
+@AllArgsConstructor
+public class McpExportRecordPage implements Serializable {
+
+ private static final long serialVersionUID = 1143791658357035990L;
+
+ private int pageNum;
+
+ private int pageSize;
+
+ private List pageItems;
+
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/MultiMcpRequestContext.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/MultiMcpRequestContext.java
new file mode 100644
index 0000000000..f24d0e3fd1
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/data/MultiMcpRequestContext.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.data;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * Multi Mcp request context
+ */
+public class MultiMcpRequestContext {
+
+ public static final String NAME = "multi-http-request-context";
+
+ /**
+ * The remaining requests to be processed.
+ */
+ private final AtomicInteger remainingRequests;
+
+ /**
+ * The last failed event.
+ * If retries occur but still fail, it will be logged, and only the last one will be retained.
+ */
+ private McpAttemptEvent lastFailedEvent;
+
+ public MultiMcpRequestContext(int remainingEvents) {
+ this.remainingRequests = new AtomicInteger(remainingEvents);
+ }
+
+ /**
+ * Decrement the remaining requests by 1.
+ */
+ public void decrementRemainingRequests() {
+ remainingRequests.decrementAndGet();
+ }
+
+ /**
+ * Check if all requests have been processed.
+ *
+ * @return true if all requests have been processed, false otherwise.
+ */
+ public boolean isAllRequestsProcessed() {
+ return remainingRequests.get() == 0;
+ }
+
+ public int getRemainingRequests() {
+ return remainingRequests.get();
+ }
+
+ public McpAttemptEvent getLastFailedEvent() {
+ return lastFailedEvent;
+ }
+
+ public void setLastFailedEvent(McpAttemptEvent lastFailedEvent) {
+ this.lastFailedEvent = lastFailedEvent;
+ }
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/AbstractMcpSinkHandler.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/AbstractMcpSinkHandler.java
new file mode 100644
index 0000000000..5c7435d037
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/AbstractMcpSinkHandler.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.handler;
+
+import org.apache.eventmesh.common.config.connector.mcp.SinkConnectorConfig;
+import org.apache.eventmesh.connector.mcp.sink.data.McpAttemptEvent;
+import org.apache.eventmesh.connector.mcp.sink.data.McpConnectRecord;
+import org.apache.eventmesh.connector.mcp.sink.data.MultiMcpRequestContext;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import lombok.Getter;
+
+public abstract class AbstractMcpSinkHandler implements McpSinkHandler {
+ @Getter
+ private final SinkConnectorConfig sinkConnectorConfig;
+
+ @Getter
+ private final List urls;
+
+ private final McpDeliveryStrategy deliveryStrategy;
+
+ private int roundRobinIndex = 0;
+
+ protected AbstractMcpSinkHandler(SinkConnectorConfig sinkConnectorConfig) {
+ this.sinkConnectorConfig = sinkConnectorConfig;
+ this.deliveryStrategy = McpDeliveryStrategy.valueOf(sinkConnectorConfig.getDeliveryStrategy());
+ // Initialize URLs
+ String[] urlStrings = sinkConnectorConfig.getUrls();
+ this.urls = Arrays.stream(urlStrings)
+ .map(URI::create)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Processes a ConnectRecord by sending it over HTTP or HTTPS. This method should be called for each ConnectRecord that needs to be processed.
+ *
+ * @param record the ConnectRecord to process
+ */
+ @Override
+ public void handle(ConnectRecord record) {
+ // build attributes
+ Map attributes = new ConcurrentHashMap<>();
+
+ switch (deliveryStrategy) {
+ case ROUND_ROBIN:
+ attributes.put(MultiMcpRequestContext.NAME, new MultiMcpRequestContext(1));
+ URI url = urls.get(roundRobinIndex);
+ roundRobinIndex = (roundRobinIndex + 1) % urls.size();
+ sendRecordToUrl(record, attributes, url);
+ break;
+ case BROADCAST:
+ attributes.put(MultiMcpRequestContext.NAME, new MultiMcpRequestContext(urls.size()));
+ // send the record to all URLs
+ urls.forEach(url0 -> sendRecordToUrl(record, attributes, url0));
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown delivery strategy: " + deliveryStrategy);
+ }
+ }
+
+ private void sendRecordToUrl(ConnectRecord record, Map attributes, URI url) {
+ // convert ConnectRecord to HttpConnectRecord
+ String type = String.format("%s.%s.%s",
+ this.sinkConnectorConfig.getConnectorName(), url.getScheme(),
+ "common");
+ McpConnectRecord mcpConnectRecord = McpConnectRecord.convertConnectRecord(record, type);
+
+ // add AttemptEvent to the attributes
+ McpAttemptEvent attemptEvent = new McpAttemptEvent(this.sinkConnectorConfig.getRetryConfig().getMaxRetries() + 1);
+ attributes.put(McpAttemptEvent.PREFIX + mcpConnectRecord.getMcpRecordId(), attemptEvent);
+
+ // deliver the record
+ deliver(url, mcpConnectRecord, attributes, record);
+ }
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/McpDeliveryStrategy.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/McpDeliveryStrategy.java
new file mode 100644
index 0000000000..07cbbe3d46
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/McpDeliveryStrategy.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.handler;
+
+public enum McpDeliveryStrategy {
+ ROUND_ROBIN,
+ BROADCAST
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/McpSinkHandler.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/McpSinkHandler.java
new file mode 100644
index 0000000000..c10d96337b
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/McpSinkHandler.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.handler;
+
+import org.apache.eventmesh.connector.mcp.sink.data.McpConnectRecord;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.net.URI;
+import java.util.Map;
+
+import io.vertx.core.Future;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.ext.web.client.HttpResponse;
+
+/**
+ * Interface for handling ConnectRecords via HTTP or HTTPS. Classes implementing this interface are responsible for processing ConnectRecords by
+ * sending them over HTTP or HTTPS, with additional support for handling multiple requests and asynchronous processing.
+ *
+ *
Any class that needs to process ConnectRecords via HTTP or HTTPS should implement this interface.
+ * Implementing classes must provide implementations for the {@link #start()}, {@link #handle(ConnectRecord)},
+ * {@link #deliver(URI, McpConnectRecord, Map, ConnectRecord)}, and {@link #stop()} methods.
+ *
+ *
Implementing classes should ensure thread safety and handle MCP communication efficiently.
+ * The {@link #start()} method initializes any necessary resources for MCP communication. The {@link #handle(ConnectRecord)} method processes a
+ * ConnectRecord by sending it over HTTP or HTTPS. The {@link #deliver(URI, McpConnectRecord, Map, ConnectRecord)} method processes HttpConnectRecord
+ * on specified URL while returning its own processing logic {@link #stop()} method releases any resources used for MCP communication.
+ *
+ *
It's recommended to handle exceptions gracefully within the {@link #deliver(URI, McpConnectRecord, Map, ConnectRecord)} method
+ * to prevent message loss or processing interruptions.
+ */
+public interface McpSinkHandler {
+
+ /**
+ * Initializes the MCP handler. This method should be called before using the handler.
+ */
+ void start();
+
+ /**
+ * Processes a ConnectRecord by sending it over HTTP or HTTPS. This method should be called for each ConnectRecord that needs to be processed.
+ *
+ * @param record the ConnectRecord to process
+ */
+ void handle(ConnectRecord record);
+
+
+ /**
+ * Processes HttpConnectRecord on specified URL while returning its own processing logic
+ *
+ * @param url URI to which the HttpConnectRecord should be sent
+ * @param mcpConnectRecord HttpConnectRecord to process
+ * @param attributes additional attributes to be used in processing
+ * @return processing chain
+ */
+ Future> deliver(URI url, McpConnectRecord mcpConnectRecord, Map attributes, ConnectRecord connectRecord);
+
+ /**
+ * Cleans up and releases resources used by the MCP handler. This method should be called when the handler is no longer needed.
+ */
+ void stop();
+}
+
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/impl/CommonMcpSinkHandler.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/impl/CommonMcpSinkHandler.java
new file mode 100644
index 0000000000..1d884f4a19
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/impl/CommonMcpSinkHandler.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.handler.impl;
+
+import org.apache.eventmesh.common.config.connector.mcp.SinkConnectorConfig;
+import org.apache.eventmesh.common.utils.JsonUtils;
+import org.apache.eventmesh.connector.http.util.HttpUtils;
+import org.apache.eventmesh.connector.mcp.sink.data.McpAttemptEvent;
+import org.apache.eventmesh.connector.mcp.sink.data.McpConnectRecord;
+import org.apache.eventmesh.connector.mcp.sink.data.MultiMcpRequestContext;
+import org.apache.eventmesh.connector.mcp.sink.handler.AbstractMcpSinkHandler;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.callback.SendExceptionContext;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.callback.SendResult;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.net.URI;
+import java.time.ZoneId;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.vertx.core.Future;
+import io.vertx.core.MultiMap;
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.web.client.HttpResponse;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.client.WebClientOptions;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+
+/**
+ * Common MCP Sink Handler implementation to handle ConnectRecords by sending them over MCP to configured URLs.
+ *
+ *
This handler initializes a WebClient for making HTTP requests based on the provided SinkConnectorConfig.
+ * It handles processing ConnectRecords by converting them to HttpConnectRecord and sending them asynchronously to each configured URL using the
+ * WebClient.
+ *
+ *
The handler uses Vert.x's WebClient to perform HTTP/HTTPS requests. It initializes the WebClient in the {@link #start()}
+ * method and closes it in the {@link #stop()} method to manage resources efficiently.
+ *
+ *
Each ConnectRecord is processed and sent to all configured URLs concurrently using asynchronous HTTP requests.
+ */
+@Slf4j
+@Getter
+public class CommonMcpSinkHandler extends AbstractMcpSinkHandler {
+
+ private WebClient webClient;
+
+
+ public CommonMcpSinkHandler(SinkConnectorConfig sinkConnectorConfig) {
+ super(sinkConnectorConfig);
+ }
+
+ /**
+ * Initializes the WebClient for making HTTP requests based on the provided SinkConnectorConfig.
+ */
+ @Override
+ public void start() {
+ // Create WebClient
+ doInitWebClient();
+ }
+
+ /**
+ * Initializes the WebClient with the provided configuration options.
+ */
+ private void doInitWebClient() {
+ SinkConnectorConfig sinkConnectorConfig = getSinkConnectorConfig();
+ final Vertx vertx = Vertx.vertx();
+ WebClientOptions options = new WebClientOptions()
+ .setKeepAlive(sinkConnectorConfig.isKeepAlive())
+ .setKeepAliveTimeout(sinkConnectorConfig.getKeepAliveTimeout() / 1000)
+ .setIdleTimeout(sinkConnectorConfig.getIdleTimeout())
+ .setIdleTimeoutUnit(TimeUnit.MILLISECONDS)
+ .setConnectTimeout(sinkConnectorConfig.getConnectionTimeout())
+ .setMaxPoolSize(sinkConnectorConfig.getMaxConnectionPoolSize())
+ .setPipelining(sinkConnectorConfig.isParallelized());
+ this.webClient = WebClient.create(vertx, options);
+ }
+
+ /**
+ * Processes HttpConnectRecord on specified URL while returning its own processing logic. This method sends the HttpConnectRecord to the specified
+ * URL using the WebClient.
+ *
+ * @param url URI to which the HttpConnectRecord should be sent
+ * @param mcpConnectRecord HttpConnectRecord to process
+ * @param attributes additional attributes to be used in processing
+ * @return processing chain
+ */
+ @Override
+ public Future> deliver(URI url, McpConnectRecord mcpConnectRecord, Map attributes,
+ ConnectRecord connectRecord) {
+ // create headers
+ Map extensionMap = new HashMap<>();
+ Set extensionKeySet = mcpConnectRecord.getExtensions().keySet();
+ for (String extensionKey : extensionKeySet) {
+ Object v = mcpConnectRecord.getExtensions().getObject(extensionKey);
+ extensionMap.put(extensionKey, v);
+ }
+
+ MultiMap headers = HttpHeaders.headers()
+ .set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8")
+ .set(HttpHeaderNames.ACCEPT, "application/json; charset=utf-8")
+ .set("extension", JsonUtils.toJSONString(extensionMap));
+ // get timestamp and offset
+ Long timestamp = mcpConnectRecord.getCreateTime()
+ .atZone(ZoneId.systemDefault())
+ .toInstant()
+ .toEpochMilli();
+
+ // send the request
+ return this.webClient.post(url.getPath())
+ .host(url.getHost())
+ .port(url.getPort() == -1 ? (Objects.equals(url.getScheme(), "https") ? 443 : 80) : url.getPort())
+ .putHeaders(headers)
+ .ssl(Objects.equals(url.getScheme(), "https"))
+ .sendJson(mcpConnectRecord.getData())
+ .onSuccess(res -> {
+ log.info("Request sent successfully. Record: timestamp={}", timestamp);
+
+ Exception e = null;
+
+ // log the response
+ if (HttpUtils.is2xxSuccessful(res.statusCode())) {
+ if (log.isDebugEnabled()) {
+ log.debug("Received successful response: statusCode={}. Record: timestamp={}, responseBody={}",
+ res.statusCode(), timestamp, res.bodyAsString());
+ } else {
+ log.info("Received successful response: statusCode={}. Record: timestamp={}", res.statusCode(), timestamp);
+ }
+ } else {
+ if (log.isDebugEnabled()) {
+ log.warn("Received non-2xx response: statusCode={}. Record: timestamp={}, responseBody={}",
+ res.statusCode(), timestamp, res.bodyAsString());
+ } else {
+ log.warn("Received non-2xx response: statusCode={}. Record: timestamp={}", res.statusCode(), timestamp);
+ }
+
+ e = new RuntimeException("Unexpected HTTP response code: " + res.statusCode());
+ }
+
+ // try callback
+ tryCallback(mcpConnectRecord, e, attributes, connectRecord);
+ }).onFailure(err -> {
+ log.error("Request failed to send. Record: timestamp={}", timestamp, err);
+
+ // try callback
+ tryCallback(mcpConnectRecord, err, attributes, connectRecord);
+ });
+ }
+
+ /**
+ * Tries to call the callback based on the result of the request.
+ *
+ * @param mcpConnectRecord the McpConnectRecord to use
+ * @param e the exception thrown during the request, may be null
+ * @param attributes additional attributes to be used in processing
+ */
+ private void tryCallback(McpConnectRecord mcpConnectRecord, Throwable e, Map attributes, ConnectRecord record) {
+ // get and update the attempt event
+ McpAttemptEvent attemptEvent = (McpAttemptEvent) attributes.get(McpAttemptEvent.PREFIX + mcpConnectRecord.getMcpRecordId());
+ attemptEvent.updateEvent(e);
+
+ // get and update the multiHttpRequestContext
+ MultiMcpRequestContext multiMcpRequestContext = getAndUpdateMultiMcpRequestContext(attributes, attemptEvent);
+
+ if (multiMcpRequestContext.isAllRequestsProcessed()) {
+ // do callback
+ if (record.getCallback() == null) {
+ if (log.isDebugEnabled()) {
+ log.warn("ConnectRecord callback is null. Ignoring callback. {}", record);
+ } else {
+ log.warn("ConnectRecord callback is null. Ignoring callback.");
+ }
+ return;
+ }
+
+ // get the last failed event
+ McpAttemptEvent lastFailedEvent = multiMcpRequestContext.getLastFailedEvent();
+ if (lastFailedEvent == null) {
+ // success
+ record.getCallback().onSuccess(convertToSendResult(record));
+ } else {
+ // failure
+ record.getCallback().onException(buildSendExceptionContext(record, lastFailedEvent.getLastException()));
+ }
+ } else {
+ log.warn("still have requests to process, size {}|attempt num {}",
+ multiMcpRequestContext.getRemainingRequests(), attemptEvent.getAttempts());
+ }
+ }
+
+
+ /**
+ * Gets and updates the multi mcp request context based on the provided attributes and HttpConnectRecord.
+ *
+ * @param attributes the attributes to use
+ * @param attemptEvent the McpAttemptEvent to use
+ * @return the updated multi mcp request context
+ */
+ private MultiMcpRequestContext getAndUpdateMultiMcpRequestContext(Map attributes, McpAttemptEvent attemptEvent) {
+ // get the multi http request context
+ MultiMcpRequestContext multiMcpRequestContext = (MultiMcpRequestContext) attributes.get(MultiMcpRequestContext.NAME);
+
+ // Check if the current attempted event has completed
+ if (attemptEvent.isComplete()) {
+ // decrement the counter
+ multiMcpRequestContext.decrementRemainingRequests();
+
+ if (attemptEvent.getLastException() != null) {
+ // if all attempts are exhausted, set the last failed event
+ multiMcpRequestContext.setLastFailedEvent(attemptEvent);
+ }
+ }
+
+ return multiMcpRequestContext;
+ }
+
+ private SendResult convertToSendResult(ConnectRecord record) {
+ SendResult result = new SendResult();
+ result.setMessageId(record.getRecordId());
+ if (org.apache.commons.lang3.StringUtils.isNotEmpty(record.getExtension("topic"))) {
+ result.setTopic(record.getExtension("topic"));
+ }
+ return result;
+ }
+
+ private SendExceptionContext buildSendExceptionContext(ConnectRecord record, Throwable e) {
+ SendExceptionContext sendExceptionContext = new SendExceptionContext();
+ sendExceptionContext.setMessageId(record.getRecordId());
+ sendExceptionContext.setCause(e);
+ if (org.apache.commons.lang3.StringUtils.isNotEmpty(record.getExtension("topic"))) {
+ sendExceptionContext.setTopic(record.getExtension("topic"));
+ }
+ return sendExceptionContext;
+ }
+
+
+ /**
+ * Cleans up and releases resources used by the MCP handler.
+ */
+ @Override
+ public void stop() {
+ if (this.webClient != null) {
+ this.webClient.close();
+ } else {
+ log.warn("WebClient is null, ignore.");
+ }
+ }
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/impl/McpSinkHandlerRetryWrapper.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/impl/McpSinkHandlerRetryWrapper.java
new file mode 100644
index 0000000000..c2e990857e
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/sink/handler/impl/McpSinkHandlerRetryWrapper.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.sink.handler.impl;
+
+import org.apache.eventmesh.common.config.connector.mcp.McpRetryConfig;
+import org.apache.eventmesh.common.config.connector.mcp.SinkConnectorConfig;
+import org.apache.eventmesh.connector.http.util.HttpUtils;
+import org.apache.eventmesh.connector.mcp.sink.data.McpConnectRecord;
+import org.apache.eventmesh.connector.mcp.sink.handler.AbstractMcpSinkHandler;
+import org.apache.eventmesh.connector.mcp.sink.handler.McpSinkHandler;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.net.ConnectException;
+import java.net.URI;
+import java.time.Duration;
+import java.util.Map;
+
+import io.vertx.core.Future;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.ext.web.client.HttpResponse;
+
+import lombok.extern.slf4j.Slf4j;
+
+import dev.failsafe.Failsafe;
+import dev.failsafe.RetryPolicy;
+
+/**
+ * McpSinkHandlerRetryWrapper is a wrapper class for the McpSinkHandler that provides retry functionality for failed Mcp requests.
+ */
+@Slf4j
+public class McpSinkHandlerRetryWrapper extends AbstractMcpSinkHandler {
+
+ private final McpRetryConfig mcpRetryConfig;
+
+ private final McpSinkHandler sinkHandler;
+
+ private final RetryPolicy> retryPolicy;
+
+ public McpSinkHandlerRetryWrapper(SinkConnectorConfig sinkConnectorConfig, McpSinkHandler sinkHandler) {
+ super(sinkConnectorConfig);
+ this.sinkHandler = sinkHandler;
+ this.mcpRetryConfig = getSinkConnectorConfig().getRetryConfig();
+ this.retryPolicy = buildRetryPolicy();
+ }
+
+ private RetryPolicy> buildRetryPolicy() {
+ return RetryPolicy.>builder()
+ .handleIf(e -> e instanceof ConnectException)
+ .handleResultIf(response -> mcpRetryConfig.isRetryOnNonSuccess() && !HttpUtils.is2xxSuccessful(response.statusCode()))
+ .withMaxRetries(mcpRetryConfig.getMaxRetries())
+ .withDelay(Duration.ofMillis(mcpRetryConfig.getInterval()))
+ .onRetry(event -> {
+ if (log.isDebugEnabled()) {
+ log.warn("Failed to deliver message after {} attempts. Retrying in {} ms. Error: {}",
+ event.getAttemptCount(), mcpRetryConfig.getInterval(), event.getLastException());
+ } else {
+ log.warn("Failed to deliver message after {} attempts. Retrying in {} ms.",
+ event.getAttemptCount(), mcpRetryConfig.getInterval());
+ }
+ }).onFailure(event -> {
+ if (log.isDebugEnabled()) {
+ log.error("Failed to deliver message after {} attempts. Error: {}",
+ event.getAttemptCount(), event.getException());
+ } else {
+ log.error("Failed to deliver message after {} attempts.",
+ event.getAttemptCount());
+ }
+ }).build();
+ }
+
+ /**
+ * Initializes the WebClient for making Mcp requests based on the provided SinkConnectorConfig.
+ */
+ @Override
+ public void start() {
+ sinkHandler.start();
+ }
+
+
+ /**
+ * Processes McpConnectRecord on specified URL while returning its own processing logic This method provides the retry power to process the
+ * McpConnectRecord
+ *
+ * @param url URI to which the McpConnectRecord should be sent
+ * @param mcpConnectRecord McpConnectRecord to process
+ * @param attributes additional attributes to pass to the processing chain
+ * @return processing chain
+ */
+ @Override
+ public Future> deliver(URI url, McpConnectRecord mcpConnectRecord, Map attributes,
+ ConnectRecord connectRecord) {
+ Failsafe.with(retryPolicy)
+ .getStageAsync(() -> sinkHandler.deliver(url, mcpConnectRecord, attributes, connectRecord).toCompletionStage());
+ return null;
+ }
+
+
+ /**
+ * Cleans up and releases resources used by the Mcp handler.
+ */
+ @Override
+ public void stop() {
+ sinkHandler.stop();
+ }
+}
diff --git a/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/source/McpSourceConnector.java b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/source/McpSourceConnector.java
new file mode 100644
index 0000000000..e5fe258124
--- /dev/null
+++ b/eventmesh-connectors/eventmesh-connector-mcp/src/main/java/org/apache/eventmesh/connector/mcp/source/McpSourceConnector.java
@@ -0,0 +1,659 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.connector.mcp.source;
+
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CACHE_CONTROL_NO_CACHE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CONNECTION_KEEP_ALIVE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CONTENT_TYPE_JSON;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CONTENT_TYPE_JSON_PLAIN;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CONTENT_TYPE_SSE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CORS_ALLOWED_HEADERS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CORS_ALLOWED_METHODS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CORS_ALLOW_ALL;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.CORS_EXPOSED_HEADERS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.DEFAULT_CONNECTOR_NAME;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.DEFAULT_HEARTBEAT_INTERVAL_MS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.DEFAULT_IDLE_TIMEOUT_MS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.DEFAULT_NO_MESSAGE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.DEFAULT_PROTOCOL_VERSION;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.DEFAULT_SERVER_NAME;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.DEFAULT_SERVER_VERSION;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.ENDPOINT_HEALTH;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.ERROR_INTERNAL;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.ERROR_INVALID_PARAMS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.ERROR_METHOD_NOT_FOUND;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_ACCEPT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_CACHE_CONTROL;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_CONNECTION;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_CONTENT_TYPE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_CORS_ALLOW_HEADERS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_CORS_ALLOW_METHODS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_CORS_ALLOW_ORIGIN;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_CORS_EXPOSE_HEADERS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HEADER_X_ACCEL_BUFFERING;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HTTP_METHOD_OPTIONS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HTTP_STATUS_INTERNAL_ERROR;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.HTTP_STATUS_NO_CONTENT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_CAPABILITIES;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_CONNECTOR;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_CONTENT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_DESCRIPTION;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_ERROR;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_ERROR_CODE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_ERROR_MESSAGE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_ID;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_JSONRPC;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_METHOD;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_NAME;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_PARAMS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_PROPERTIES;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_PROTOCOL_VERSION;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_REQUIRED;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_RESULT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_SERVER_INFO;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_STATUS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_TEXT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_TOOLS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_TYPE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.KEY_VERSION;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.MAX_POLL_WAIT_TIME_MS;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.METHOD_INITIALIZE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.METHOD_NOTIFICATIONS_INITIALIZED;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.METHOD_PING;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.METHOD_TOOLS_CALL;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.METHOD_TOOLS_LIST;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.PARAM_DESC_MESSAGE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.PARAM_DESC_MESSAGE_CONTENT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.PARAM_DESC_TOPIC;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.PARAM_MESSAGE;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.PARAM_TOPIC;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.SSE_DATA_OPEN;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.SSE_EVENT_OPEN;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.SSE_HEARTBEAT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.VALUE_JSONRPC_VERSION;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.VALUE_STATUS_UP;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.VALUE_TYPE_OBJECT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.VALUE_TYPE_STRING;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.VALUE_TYPE_TEXT;
+import static org.apache.eventmesh.connector.mcp.source.McpSourceConstants.X_ACCEL_BUFFERING_NO;
+
+import org.apache.eventmesh.common.config.connector.Config;
+import org.apache.eventmesh.common.config.connector.mcp.McpSourceConfig;
+import org.apache.eventmesh.common.exception.EventMeshException;
+import org.apache.eventmesh.connector.mcp.source.protocol.Protocol;
+import org.apache.eventmesh.connector.mcp.source.protocol.ProtocolFactory;
+import org.apache.eventmesh.openconnect.api.ConnectorCreateService;
+import org.apache.eventmesh.openconnect.api.connector.ConnectorContext;
+import org.apache.eventmesh.openconnect.api.connector.SourceConnectorContext;
+import org.apache.eventmesh.openconnect.api.source.Source;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpServer;
+import io.vertx.core.http.HttpServerOptions;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.Route;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.handler.BodyHandler;
+import io.vertx.ext.web.handler.LoggerHandler;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * MCP Source Connector for EventMesh Implements MCP protocol server allowing AI clients to interact with EventMesh via MCP protocol
+ */
+@Slf4j
+public class McpSourceConnector implements Source, ConnectorCreateService {
+
+ private McpSourceConfig sourceConfig;
+
+ private BlockingQueue