This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new b913894b86d9 CAMEL-26358: camel-mcp-server - run the official MCP 
conformance suite against the Camel Main engine (#29186)
b913894b86d9 is described below

commit b913894b86d9085ae2cb2a388a154088381e87f6
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Nov 9 13:41:20 2026 +0100

    CAMEL-26358: camel-mcp-server - run the official MCP conformance suite 
against the Camel Main engine (#29186)
---
 components/camel-ai/camel-mcp-server/pom.xml       |  39 +++
 .../integration/OfficialMcpConformanceIT.java      | 295 +++++++++++++++++++++
 .../src/test/resources/expected-failures.yaml      |  82 ++++++
 .../camel-ai/camel-mcp-server/test-execution.md    |  41 +++
 4 files changed, 457 insertions(+)

diff --git a/components/camel-ai/camel-mcp-server/pom.xml 
b/components/camel-ai/camel-mcp-server/pom.xml
index eb827fc23b7c..5249bf39eef5 100644
--- a/components/camel-ai/camel-mcp-server/pom.xml
+++ b/components/camel-ai/camel-mcp-server/pom.xml
@@ -120,4 +120,43 @@
 
     </dependencies>
 
+    <profiles>
+        <!--
+            Official MCP conformance profile (-Poc).
+
+            Runs OfficialMcpConformanceIT, which executes
+            `npx @modelcontextprotocol/conformance server` against the Camel 
Main HTTP MCP
+            server.  Node.js ≥ 18 and npm registry access are required; the 
npx package is
+            cached by npm after the first run.
+
+            Usage:
+                mvn verify -Poc
+                # or equivalently:
+                mvn verify -Dofficial-mcp-conformance
+
+            The profile is intentionally NOT active by default: it needs 
Node.js and
+            network access, neither of which is available in all CI 
environments.
+        -->
+        <profile>
+            <id>oc</id>
+            <properties>
+                <!-- Activate OfficialMcpConformanceIT (read by 
@EnabledIfSystemProperty) -->
+                <official-mcp-conformance>true</official-mcp-conformance>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+                                
<official-mcp-conformance>true</official-mcp-conformance>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
 </project>
diff --git 
a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/integration/OfficialMcpConformanceIT.java
 
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/integration/OfficialMcpConformanceIT.java
new file mode 100644
index 000000000000..151047ad7f14
--- /dev/null
+++ 
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/integration/OfficialMcpConformanceIT.java
@@ -0,0 +1,295 @@
+/*
+ * 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.camel.component.mcp.server.vertx.integration;
+
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mcp.server.McpServerBridge;
+import org.apache.camel.component.mcp.server.McpServerConfiguration;
+import org.apache.camel.component.platform.http.main.MainHttpServer;
+import org.apache.camel.test.AvailablePortFinder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.assertj.core.api.Assertions.fail;
+
+/**
+ * Runs the official {@code @modelcontextprotocol/conformance} npm suite 
against the Camel Main HTTP MCP server.
+ * <p>
+ * The suite ({@code npx @modelcontextprotocol/conformance server --url ...}) 
connects to the server as an MCP client,
+ * sends protocol-defined requests, and validates the responses against the 
MCP specification. Known gaps — features not
+ * yet implemented by Camel's MCP server — are listed in {@code 
expected-failures.yaml} so the test stays green while
+ * regressions still cause CI to fail.
+ * <p>
+ * The server routes registered here deliberately match the tool names and 
return values documented by each conformance
+ * scenario. Run {@code npx @modelcontextprotocol/conformance server --url ... 
--scenario <name>} against a live server
+ * to view the per-scenario requirements.
+ * <p>
+ * <strong>Prerequisites:</strong> Node.js ≥ 18 and npm registry access (for 
the first run; npx caches the package after
+ * the initial download). Enable this test by activating the {@code oc} Maven 
profile ({@code -Poc}) or by setting the
+ * system property {@code -Dofficial-mcp-conformance}.
+ */
+@EnabledIfSystemProperty(named = "official-mcp-conformance", matches = ".*")
+class OfficialMcpConformanceIT extends CamelTestSupport {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(OfficialMcpConformanceIT.class);
+
+    /**
+     * Tag used to select which ai-tool routes are exposed by the bridge. All 
conformance routes share this tag so that
+     * non-conformance routes registered elsewhere in test infrastructure are 
never accidentally exposed.
+     */
+    private static final String CONFORMANCE_TAG = "official-conformance";
+
+    /**
+     * The pinned npm package reference for the official conformance suite. 
Used in both the command and the re-run hint
+     * so bumping the version only requires changing this constant.
+     */
+    private static final String CONFORMANCE_PACKAGE = 
"@modelcontextprotocol/[email protected]";
+
+    /**
+     * Minimal 1×1 red-pixel PNG encoded as Base64, used by the 
tools-call-image and tools-call-mixed-content scenarios.
+     */
+    private static final String MINIMAL_PNG_BASE64
+            = 
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI6QAAAABJRU5ErkJggg==";
+
+    /**
+     * Minimal silent WAV (44-byte RIFF/PCM header, 0 samples) as Base64, for 
the tools-call-audio scenario.
+     */
+    private static final String MINIMAL_WAV_BASE64
+            = "UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
+
+    private final int port = AvailablePortFinder.getNextAvailable();
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext camelContext = super.createCamelContext();
+
+        MainHttpServer server = new MainHttpServer();
+        server.setCamelContext(camelContext);
+        server.setHost("0.0.0.0");
+        server.setPort(port);
+        camelContext.addService(server);
+
+        McpServerConfiguration configuration = new McpServerConfiguration();
+        configuration.setTags(CONFORMANCE_TAG);
+        McpServerBridge bridge = new McpServerBridge(configuration);
+        camelContext.addService(bridge);
+
+        return camelContext;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+
+                // ---- resources-read-text 
---------------------------------------------------
+                // The conformance suite reads test://static-text and expects 
a TextResourceContents
+                // response. VertxMcpServerEngine routes reads to ai-resource 
routes by URI.
+                
from("ai-resource:static_text?resourceUri=test://static-text&tags=" + 
CONFORMANCE_TAG
+                     + "&description=A static text resource for conformance 
testing&mimeType=text/plain")
+                        .routeId("conformance-resource-static-text")
+                        .setBody(constant("This is a static text resource."));
+
+                // ---- resources-read-binary 
-------------------------------------------------
+                // The conformance suite reads test://static-binary and 
expects a BlobResourceContents
+                // response with mimeType=image/png. The engine returns the 
byte[] as a blob when
+                // the route body is a byte[] and mimeType is set.
+                
from("ai-resource:static_binary?resourceUri=test://static-binary&tags=" + 
CONFORMANCE_TAG
+                     + "&description=A static binary resource for conformance 
testing&mimeType=image/png")
+                        .routeId("conformance-resource-static-binary")
+                        
.setBody(constant(Base64.getDecoder().decode(MINIMAL_PNG_BASE64)));
+
+                // ---- tools-call-simple-text 
------------------------------------------------
+                // Expected: text "This is a simple text response for testing."
+                from("ai-tool:test_simple_text?tags=" + CONFORMANCE_TAG
+                     + "&description=Returns simple text content")
+                        .routeId("conformance-simple-text")
+                        .setBody(constant("This is a simple text response for 
testing."));
+
+                // ---- tools-call-image (expected failure) 
------------------------------------
+                // Requires ImageContent in the MCP response — Camel wraps 
String bodies as TextContent only.
+                from("ai-tool:test_image_content?tags=" + CONFORMANCE_TAG
+                     + "&description=Returns an image content item")
+                        .routeId("conformance-image")
+                        .setBody(constant(MINIMAL_PNG_BASE64));
+
+                // ---- tools-call-audio (expected failure) 
------------------------------------
+                // Requires AudioContent in the MCP response — not yet 
supported.
+                from("ai-tool:test_audio_content?tags=" + CONFORMANCE_TAG
+                     + "&description=Returns an audio content item")
+                        .routeId("conformance-audio")
+                        .setBody(constant(MINIMAL_WAV_BASE64));
+
+                // ---- tools-call-embedded-resource (expected failure) 
------------------------
+                // Requires ResourceContent in the MCP response — not yet 
supported.
+                from("ai-tool:test_embedded_resource?tags=" + CONFORMANCE_TAG
+                     + "&description=Returns a resource content item")
+                        .routeId("conformance-embedded-resource")
+                        .setBody(constant("This is an embedded resource 
content."));
+
+                // ---- tools-call-mixed-content (expected failure) 
----------------------------
+                // Requires multiple content types in one response — not yet 
supported.
+                from("ai-tool:test_multiple_content_types?tags=" + 
CONFORMANCE_TAG
+                     + "&description=Returns multiple content types")
+                        .routeId("conformance-mixed-content")
+                        .setBody(constant("Multiple content types test:"));
+
+                // ---- tools-call-with-logging (expected failure) 
-----------------------------
+                // Requires server→client log notifications during tool 
execution — not yet supported.
+                from("ai-tool:test_tool_with_logging?tags=" + CONFORMANCE_TAG
+                     + "&description=Sends log notifications during execution")
+                        .routeId("conformance-with-logging")
+                        .setBody(constant("Tool execution completed"));
+
+                // ---- tools-call-error 
-------------------------------------------------------
+                // Scenario expects: isError=true with non-empty error text.
+                // Camel MCP bridge wraps uncaught exceptions as isError=true 
responses with a
+                // generic "Tool execution failed" message; the scenario only 
checks non-empty.
+                from("ai-tool:test_error_handling?tags=" + CONFORMANCE_TAG
+                     + "&description=Always returns an error")
+                        .routeId("conformance-error")
+                        .process(e -> {
+                            throw new IllegalStateException("This tool 
intentionally returns an error for testing");
+                        });
+
+                // ---- tools-call-with-progress (expected failure) 
----------------------------
+                // Requires progress notifications (notifications/progress) — 
not yet supported.
+                from("ai-tool:test_tool_with_progress?tags=" + CONFORMANCE_TAG
+                     + "&description=Reports progress during execution")
+                        .routeId("conformance-progress")
+                        .setBody(constant("Tool execution completed with 
progress"));
+
+                // ---- tools-call-sampling (expected failure) 
---------------------------------
+                // Requires the server to call sampling/createMessage on the 
client — not yet supported.
+                from("ai-tool:test_sampling?tags=" + CONFORMANCE_TAG
+                     + "&description=Requests LLM sampling from the client"
+                     + 
"&parameter.prompt=string&parameter.prompt.description=The prompt to 
sample&parameter.prompt.required=true")
+                        .routeId("conformance-sampling")
+                        .setBody(constant("Sampling not supported"));
+
+                // ---- tools-call-elicitation + elicitation-sep* (expected 
failures) ----------
+                // Requires the server to call elicitation/create on the 
client — not yet supported.
+                from("ai-tool:test_elicitation?tags=" + CONFORMANCE_TAG
+                     + "&description=Requests user input via elicitation"
+                     + 
"&parameter.message=string&parameter.message.description=Message to 
show&parameter.message.required=true")
+                        .routeId("conformance-elicitation")
+                        .setBody(constant("Elicitation not supported"));
+
+                from("ai-tool:test_elicitation_sep1034_defaults?tags=" + 
CONFORMANCE_TAG
+                     + "&description=Elicitation with default values 
(SEP-1034)")
+                        .routeId("conformance-elicitation-sep1034")
+                        .setBody(constant("Elicitation not supported"));
+
+                from("ai-tool:test_elicitation_sep1330_enums?tags=" + 
CONFORMANCE_TAG
+                     + "&description=Elicitation with enum schemas (SEP-1330)")
+                        .routeId("conformance-elicitation-sep1330")
+                        .setBody(constant("Elicitation not supported"));
+            }
+        };
+    }
+
+    @Test
+    void testOfficialMcpConformanceSuite() throws Exception {
+        String serverUrl = "http://localhost:"; + port + "/mcp";
+        Path expectedFailures = expectedFailuresPath();
+
+        LOG.info("Running official MCP conformance suite against {}", 
serverUrl);
+
+        List<String> command = buildConformanceCommand(serverUrl, 
expectedFailures);
+
+        Path log = Files.createTempFile("mcp-conformance", ".log");
+        ProcessBuilder pb = new ProcessBuilder(command);
+        pb.environment().put("CI", "true");
+        pb.redirectErrorStream(true);
+        pb.redirectOutput(log.toFile());
+        Process process = pb.start();
+        try {
+            boolean finished = process.waitFor(5, TimeUnit.MINUTES);
+            String output = Files.readString(log, StandardCharsets.UTF_8);
+            LOG.info("[conformance]\n{}", output);
+            if (!finished) {
+                fail("Official MCP conformance suite timed out after 5 
minutes.\nOutput:\n" + output);
+            }
+
+            int exitCode = process.exitValue();
+            if (exitCode != 0) {
+                fail("Official MCP conformance suite failed (exit " + exitCode 
+ ").\n"
+                     + "Output:\n" + output + "\n"
+                     + "To register a known failure add the scenario name to " 
+ expectedFailures.getFileName() + ".\n"
+                     + "Re-run manually: npx " + CONFORMANCE_PACKAGE + " 
server"
+                     + " --url " + serverUrl
+                     + " --expected-failures " + 
expectedFailures.toAbsolutePath());
+            }
+        } finally {
+            process.descendants().forEach(ProcessHandle::destroyForcibly);
+            process.destroyForcibly();
+            Files.deleteIfExists(log);
+        }
+    }
+
+    // -------------------------------------------------- helpers
+
+    /**
+     * Locates {@code expected-failures.yaml} from the test-resources 
directory. Maven copies the file to
+     * {@code target/test-classes/} during the process-test-resources phase, 
so the classpath lookup works both under
+     * Maven and in an IDE that mirrors that layout.
+     */
+    private static Path expectedFailuresPath() throws Exception {
+        URL resource = 
OfficialMcpConformanceIT.class.getResource("/expected-failures.yaml");
+        if (resource != null) {
+            return Paths.get(resource.toURI());
+        }
+        // Fallback for IDE runs whose resource root is the source directory
+        Path fromSource = 
Paths.get("src/test/resources/expected-failures.yaml");
+        if (Files.exists(fromSource)) {
+            return fromSource;
+        }
+        // Return a path whose absence will be reported clearly by npx
+        return Paths.get("expected-failures.yaml");
+    }
+
+    private static List<String> buildConformanceCommand(String serverUrl, Path 
expectedFailures) throws Exception {
+        if (!Files.exists(expectedFailures)) {
+            fail("expected-failures.yaml not found at " + 
expectedFailures.toAbsolutePath()
+                 + " — cannot run the conformance suite without a baseline");
+        }
+        List<String> cmd = new ArrayList<>();
+        cmd.add("npx");
+        cmd.add("--yes");
+        cmd.add(CONFORMANCE_PACKAGE);
+        cmd.add("server");
+        cmd.add("--url");
+        cmd.add(serverUrl);
+        cmd.add("--expected-failures");
+        cmd.add(expectedFailures.toAbsolutePath().toString());
+        return cmd;
+    }
+}
diff --git 
a/components/camel-ai/camel-mcp-server/src/test/resources/expected-failures.yaml
 
b/components/camel-ai/camel-mcp-server/src/test/resources/expected-failures.yaml
new file mode 100644
index 000000000000..f9b922297fb3
--- /dev/null
+++ 
b/components/camel-ai/camel-mcp-server/src/test/resources/expected-failures.yaml
@@ -0,0 +1,82 @@
+#
+# 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.
+#
+
+# MCP conformance baseline — known failures for the Camel Main HTTP MCP server
+#
+# Exit-code semantics (npx @modelcontextprotocol/conformance server 
--expected-failures):
+#   - Scenario in baseline AND fails  → exit 0  (expected failure, do not 
block CI)
+#   - Scenario in baseline AND passes → exit 1  (stale entry, remove it!)
+#   - Scenario NOT in baseline AND fails → exit 1  (unexpected regression, fix 
it!)
+#
+# When Camel gains a capability listed here, remove the corresponding line and
+# let the entry drive the fix (exit 1 = stale baseline).
+
+server:
+  # --- completion/complete endpoint (not yet implemented) ---
+  # Camel MCP server does not expose a completion/complete endpoint.
+  - completion-complete
+
+  # --- Tool content types beyond plain text (not yet implemented) ---
+  # Camel's MCP bridge wraps all tool results as plain TextContent. The 
conformance
+  # suite expects the server to return typed content (image, audio, 
embedded-resource)
+  # for these scenarios. Tracking issue: improve McpToolCallResult to support 
typed content.
+  - tools-call-image
+  - tools-call-audio
+  - tools-call-embedded-resource
+  - tools-call-mixed-content
+
+  # --- Server-to-client notifications during tool execution (not yet 
implemented) ---
+  # Progress notifications (notifications/progress) and log forwarding to the 
client
+  # during tool execution require reactive plumbing that is not yet wired.
+  - tools-call-with-logging
+  - tools-call-with-progress
+
+  # --- Client-side sampling (not yet implemented) ---
+  # The sampling/createMessage round-trip (server requests the client to call 
an LLM)
+  # is not yet implemented.
+  - tools-call-sampling
+
+  # --- Client-side elicitation (not yet implemented) ---
+  # The elicitation/create round-trip (server requests the user to fill in a 
form)
+  # is not yet implemented.
+  - tools-call-elicitation
+  - elicitation-sep1034-defaults
+  - elicitation-sep1330-enums
+
+  # --- resources/templates (not yet implemented) ---
+  # URI template expansion (test://template/{id}/data) is not yet supported.
+  - resources-templates-read
+
+  # --- resources/subscribe and resources/unsubscribe (not yet implemented) ---
+  # The bridge does not yet track per-URI subscriptions or send update 
notifications.
+  - resources-subscribe
+  - resources-unsubscribe
+
+  # --- prompts/* (not yet implemented, CAMEL-26355) ---
+  # Camel MCP server does not expose a prompts endpoint. Prompts are distinct 
from
+  # ai-tool and ai-resource routes and have no Camel DSL equivalent yet.
+  - prompts-list
+  - prompts-get-simple
+  - prompts-get-with-args
+  - prompts-get-embedded-resource
+  - prompts-get-with-image
+
+  # --- DNS rebinding protection (partial — one of two checks fails, 
CAMEL-26359) ---
+  # Camel's HTTP server accepts requests with a valid localhost Host/Origin 
header,
+  # but does not yet reject requests with a non-localhost Host/Origin header.
+  # The passing check (valid-localhost-accepted) is NOT listed here (it 
already passes).
+  - dns-rebinding-protection
diff --git a/components/camel-ai/camel-mcp-server/test-execution.md 
b/components/camel-ai/camel-mcp-server/test-execution.md
index 5aa3d1449cd0..0368ce7f1182 100644
--- a/components/camel-ai/camel-mcp-server/test-execution.md
+++ b/components/camel-ai/camel-mcp-server/test-execution.md
@@ -36,3 +36,44 @@ mvn verify -Dollama.instance.type=openai 
-Dopenai.api.key=sk-...
 # enable GPU for the Ollama container
 mvn verify -Dollama.container.enable.gpu=enabled
 ```
+
+## Official MCP conformance suite
+
+```bash
+mvn verify -Poc
+```
+
+Runs `OfficialMcpConformanceIT`, which starts a Camel Main HTTP server with 
the tool
+routes required by each conformance scenario and executes:
+
+```
+npx @modelcontextprotocol/[email protected] server \
+    --url http://localhost:<port>/mcp \
+    --expected-failures src/test/resources/expected-failures.yaml
+```
+
+To upgrade the pinned version, update the 
`@modelcontextprotocol/[email protected]` reference
+in `buildConformanceCommand()` and re-run the suite to identify new scenarios 
that need baseline entries.
+
+**Prerequisites:** Node.js ≥ 18 and npm registry access (the 
`@modelcontextprotocol/conformance`
+package is downloaded on the first run and cached by npm).
+
+Known gaps (features not yet implemented by Camel's MCP server) are listed in
+`src/test/resources/expected-failures.yaml` with a comment explaining the 
missing
+capability. Remove an entry from that file once the corresponding feature is 
implemented
+— the stale-baseline check will then enforce that the implementation actually 
passes.
+
+You can also enable the IT without the profile:
+
+```bash
+mvn verify -Dofficial-mcp-conformance
+```
+
+Or run a single scenario manually against a live server:
+
+```bash
+npx @modelcontextprotocol/conformance server \
+    --url http://localhost:8080/mcp \
+    --scenario tools-call-simple-text
+```
+

Reply via email to