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

Croway 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 138d8c645eb8 CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold 
follows the rules of camel-rest-openapi (#27960)
138d8c645eb8 is described below

commit 138d8c645eb8af8cca4eb45f19e36098747e7592
Author: Federico Mariani <[email protected]>
AuthorDate: Sat Oct 17 14:59:09 2026 +0200

    CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules of 
camel-rest-openapi (#27960)
    
    * CAMEL-25642: camel-jbang-mcp - camel_openapi_scaffold follows the rules 
of camel-rest-openapi
    
    - The id of an operation without operationId comes from the new
      RestOpenApiHelper.generateOperationId, which 
DefaultRestOpenapiProcessorStrategy
      now uses too (GENOPID_GET.pets._petId_, not GENOPID_GET_pets_petId).
    - With missingOperation=mock the scaffold explains the mode and lists one
      camel-mock/ file per path, named after the request path (no method suffix,
      path parameters matched by value), with what Camel answers without the 
file.
      camel_openapi_mock_guidance, whose walk duplicated the scaffold's, is 
removed.
    - camel_openapi_validate reports every security scheme as not enforced, and
      OpenAPI 3.1 as information (swagger-parser parses it, the component serves
      its paths like 3.0).
    - Test binding the scaffolded petstore routes and mock file with the real
      rest-openapi component.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    
    * CAMEL-25642: the upgrade guide shows the path parameter literally, not as 
an AsciiDoc attribute
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    
    * CAMEL-25642: camel-jbang-mcp - a visible mock file name for the root 
path, null-safe scheme text, and a unit test of the generated operation id
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    
    * CAMEL-25642: camel-jbang-mcp - mock file names carry a sample path 
parameter value, HTTP 204 for an operation without a '200' response, and media 
type wording
    
    - the mock file of a path with parameters is named with the sample value 1
      (camel-mock/pets/1.json for /pets/{petId}), as camel-rest-openapi matches 
the
      request path; requestPath keeps the template
    - an operation without a '200' response is listed as answering HTTP 204 
with an
      empty body (CAMEL-25663 fixed the error the text described)
    - the note and the mock mode explanation say the '200' example is taken for 
the
      media type of the request's Content-Type or Accept header (Accept */* 
takes
      application/json, then application/xml), otherwise HTTP 204
    - MockFileInfo javadoc, the camel-jbang-mcp doc page and the tests follow
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    
    * CAMEL-25642: camel-jbang-mcp - shorter per-path mock note, firstExample 
javadoc, and the example of the media type that names the file
    
    - The per-method note of a mock file points to modeExplanation instead of 
repeating its media type sentence
    - firstExample javadoc: the example is a preview of the file content; at 
runtime camel-rest-openapi takes the
      example of the media type matching the request's Content-Type or Accept 
header
    - The example comes from the media type that gives the file extension when 
it has one, otherwise from the first
      media type with an example, whose type then gives the extension, so 
content and extension agree
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../DefaultRestOpenapiProcessorStrategy.java       |   9 +-
 .../component/rest/openapi/RestOpenApiHelper.java  |  18 +
 .../rest/openapi/RestOpenApiHelperTest.java        |  18 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  12 +
 .../modules/ROOT/pages/camel-jbang-mcp.adoc        |  28 +-
 dsl/camel-jbang/camel-jbang-mcp/pom.xml            |  11 +
 .../dsl/jbang/core/commands/mcp/OpenApiTools.java  | 465 ++++++++++-----------
 .../mcp/OpenApiScaffoldRestOpenApiTest.java        | 134 ++++++
 .../jbang/core/commands/mcp/OpenApiToolsTest.java  | 155 +++----
 .../src/test/resources/petstore.yaml               |  83 ++++
 10 files changed, 599 insertions(+), 334 deletions(-)

diff --git 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
index bd27d14c96cd..2a3cda90b127 100644
--- 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
+++ 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
@@ -68,8 +68,6 @@ public class DefaultRestOpenapiProcessorStrategy extends 
ServiceSupport
 
     private static final String BODY_VERBS = "DELETE,PUT,POST,PATCH";
 
-    private static final String GEN_OPID = "GENOPID_";
-
     private CamelContext camelContext;
     private ProducerCache producerCache;
     private String component = "direct";
@@ -163,12 +161,11 @@ public class DefaultRestOpenapiProcessorStrategy extends 
ServiceSupport
      *
      * @param  path       The path for this operation, such as /users.
      * @param  httpMethod The operation to perform
-     * @return            A generated operation id based on the path and the 
operation. Slashes and braces in the path
-     *                    are replaced with placeholder characters.
+     * @return            A generated operation id based on the path and the 
operation, see
+     *                    {@link RestOpenApiHelper#generateOperationId(String, 
HttpMethod)}
      */
     private String generateOperationId(String path, HttpMethod httpMethod) {
-        final String sanitizedPath = path.replace('/', '.').replaceAll("[{}]", 
"_");
-        final String opId = GEN_OPID + httpMethod.name() + sanitizedPath;
+        final String opId = RestOpenApiHelper.generateOperationId(path, 
httpMethod);
         LOG.debug("Generated operationId {} for path {} and method {}", opId, 
path, httpMethod.name());
         return opId;
     }
diff --git 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiHelper.java
 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiHelper.java
index 0b692d1a36b7..b88e7e4042b6 100644
--- 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiHelper.java
+++ 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiHelper.java
@@ -26,6 +26,7 @@ import java.util.regex.Pattern;
 import java.util.stream.Stream;
 
 import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.PathItem.HttpMethod;
 import io.swagger.v3.oas.models.servers.Server;
 import org.apache.camel.CamelContext;
 import org.apache.camel.spi.ContentTypeAware;
@@ -44,10 +45,27 @@ public final class RestOpenApiHelper {
     private static final List<String> YAML_CONTENT_TYPES = 
Arrays.asList("application/yaml", "application/yml",
             "text/yaml", "text/yml", "text/x-yaml");
 
+    private static final String GENERATED_OPERATION_ID_PREFIX = "GENOPID_";
+
     private RestOpenApiHelper() {
         // utility class
     }
 
+    /**
+     * The operation id that the default contract-first strategy ({@link 
DefaultRestOpenapiProcessorStrategy}) gives an
+     * operation of the specification without an {@code operationId}: {@code 
GENOPID_}, the HTTP method, then the path
+     * with {@code /} replaced by {@code .} and the braces of the path 
parameters by {@code _}. For example {@code GET
+     * /pets/{petId}} becomes {@code GENOPID_GET.pets._petId_}, and the 
operation is routed to
+     * {@code direct:GENOPID_GET.pets._petId_}.
+     *
+     * @param  path       the path of the operation in the specification, such 
as {@code /pets/{petId}}
+     * @param  httpMethod the HTTP method of the operation
+     * @return            the generated operation id
+     */
+    public static String generateOperationId(String path, HttpMethod 
httpMethod) {
+        return GENERATED_OPERATION_ID_PREFIX + httpMethod.name() + 
path.replace('/', '.').replaceAll("[{}]", "_");
+    }
+
     public static String isMediaRange(final String given, final String name) {
         return notEmpty(given, name);
     }
diff --git 
a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiHelperTest.java
 
b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiHelperTest.java
index b653940c939a..fd04f57dc38a 100644
--- 
a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiHelperTest.java
+++ 
b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiHelperTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.rest.openapi;
 import java.io.InputStream;
 import java.util.stream.Stream;
 
+import io.swagger.v3.oas.models.PathItem.HttpMethod;
 import org.apache.camel.spi.ContentTypeAware;
 import org.apache.camel.spi.Resource;
 import org.apache.camel.support.ResourceSupport;
@@ -100,6 +101,23 @@ public class RestOpenApiHelperTest {
                 Arguments.of("classpath", null, false));
     }
 
+    @ParameterizedTest
+    @MethodSource("generatedOperationIds")
+    void generateOperationIdFollowsTheContractFirstRule(String path, 
HttpMethod method, String expected) {
+        assertThat(RestOpenApiHelper.generateOperationId(path, 
method)).isEqualTo(expected);
+    }
+
+    static Stream<Arguments> generatedOperationIds() {
+        return Stream.of(
+                Arguments.of("/pets", HttpMethod.GET, "GENOPID_GET.pets"),
+                Arguments.of("/pets/{petId}", HttpMethod.GET, 
"GENOPID_GET.pets._petId_"),
+                Arguments.of("/users/{userId}/orders/{orderId}", 
HttpMethod.DELETE,
+                        "GENOPID_DELETE.users._userId_.orders._orderId_"),
+                Arguments.of("/", HttpMethod.POST, "GENOPID_POST."),
+                Arguments.of("/pets/", HttpMethod.GET, "GENOPID_GET.pets."),
+                Arguments.of("/v1.0/pets-list", HttpMethod.PATCH, 
"GENOPID_PATCH.v1.0.pets-list"));
+    }
+
     static final class FakeContentTypeAwareResource extends ResourceSupport 
implements ContentTypeAware {
         private String contentType;
 
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 83252eab6082..e9117fe96d6d 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -709,6 +709,18 @@ it audits the `camelVersion` argument, else lists every 
advisory of the declared
 `warnings`. Its `reachable` is null when no routes are given, advisories 
without components are returned as
 `uncheckedAdvisories`, and `clean` is true only when none of them apply.
 
+=== camel-jbang-mcp - camel_openapi_mock_guidance is removed
+
+The `camel_openapi_mock_guidance` MCP tool is removed. Call 
`camel_openapi_scaffold` with
+`missingOperation=mock` instead: its answer explains the mode and lists the 
`camel-mock/` files, one per path
+of the spec. The scaffold and `camel_openapi_validate` now follow the rules of 
camel-rest-openapi: an operation
+without `operationId` is routed to `direct:GENOPID_<METHOD><path>` with `/` 
replaced by `.` and the braces of
+path parameters by `_` (`direct:GENOPID_GET.pets._petId_` for `+GET 
/pets/{petId}+`), and a mock file has the
+request path as its name, without the HTTP method (`camel-mock/pets/42.json` 
for `GET /pets/42`). The
+security scheme diagnostics of `camel_openapi_validate` are now all warnings 
with a `SECURITY_<type>` code
+(`SECURITY_APIKEY`, `SECURITY_HTTP`, ...), as camel-rest-openapi enforces none 
of them, and `OPENAPI_31` is
+reported as information instead of a warning.
+
 === camel-jpa, camel-hazelcast - an exchange marked rollback only is not 
consumed
 
 When a route marks the exchange rollback only (for example with 
`markRollbackOnly()` or `markRollbackOnlyLast()`)
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index 4be96356681e..765691486c2e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -255,10 +255,9 @@ The assistant uses `camel_error_diagnose` to identify the 
exception chain, extra
 === OpenAPI contract-first
 
 * _"Validate this OpenAPI spec for Camel compatibility"_ — uses 
`camel_openapi_validate`
-* _"Generate a Camel YAML scaffold with mock mode"_ — uses 
`camel_openapi_scaffold`
-* _"Show me the mock directory structure"_ — uses `camel_openapi_mock_guidance`
+* _"Generate a Camel YAML scaffold with mock mode, and tell me where the mock 
files go"_ — uses `camel_openapi_scaffold`
 
-Combine all three for a complete prototyping workflow: validate, scaffold, 
then implement routes one at a time while Camel mocks the rest.
+Combine both for a complete prototyping workflow: validate, scaffold, then 
implement routes one at a time while Camel mocks the rest.
 
 === Runtime introspection
 
@@ -711,27 +710,28 @@ The advisories are also browseable as MCP resources: 
`camel://security/advisorie
 
 Since Camel 4.6, the recommended approach for building REST APIs from OpenAPI 
specifications is **contract-first**:
 referencing the OpenAPI spec directly at runtime via `rest:openApi` rather 
than generating REST DSL code. These tools
-help validate, scaffold, and provide mock guidance for that workflow.
+help validate and scaffold that workflow, following the rules of the 
`rest-openapi` component.
 
 [cols="1,3",options="header"]
 |===
 | Tool | Description
 
 | `camel_openapi_validate`
-| Validates an OpenAPI specification for compatibility with Camel's 
contract-first REST support. Checks for missing
-  `operationId` fields, unsupported security schemes, OpenAPI 3.1 limitations, 
webhooks usage, and empty paths.
+| Validates an OpenAPI specification for compatibility with Camel's 
contract-first REST support. Reports operations
+  without `operationId` (with the `direct:GENOPID_...` id Camel routes them 
to), security schemes (Camel enforces
+  none of them), webhooks (not served), OpenAPI 3.1 (parsed and served like 
3.0) and empty paths.
   Returns errors, warnings, and info-level diagnostics.
 
 | `camel_openapi_scaffold`
 | Generates a Camel YAML scaffold for contract-first OpenAPI integration. 
Produces a `rest:openApi` configuration
-  block referencing the spec file and a `direct:<operationId>` route stub for 
each operation, with `Content-Type`
-  and `CamelHttpResponseCode` headers pre-configured from the spec. Supports 
configuring the `missingOperation`
-  mode (`fail`, `ignore`, or `mock`).
-
-| `camel_openapi_mock_guidance`
-| Provides guidance on configuring Camel's `missingOperation` modes (`fail`, 
`ignore`, `mock`). For `mock` mode,
-  returns the `camel-mock/` directory structure, mock file paths derived from 
the API paths, and example content
-  from the spec. Explains the behavior of each mode.
+  block referencing the spec file and a `direct:<operationId>` route stub for 
each operation (an operation without
+  `operationId` gets the id the `rest-openapi` component generates), with 
`Content-Type` and `CamelHttpResponseCode`
+  headers pre-configured from the spec. Supports configuring the 
`missingOperation` mode (`fail`, `ignore`, or
+  `mock`) and explains it. With `mock`, it also lists one `camel-mock/` file 
per path of the spec, named after the
+  request path: a path parameter carries a sample value, such as 
`camel-mock/pets/1.json` for `+/pets/{petId}+`,
+  which answers `/pets/1` (one file per value). It gives the example of the 
spec and what Camel answers without the
+  file: the example of the `200` response for the media type of the request's 
`Content-Type` or `Accept` header,
+  otherwise HTTP 204 with an empty body.
 |===
 
 === Migration
diff --git a/dsl/camel-jbang/camel-jbang-mcp/pom.xml 
b/dsl/camel-jbang/camel-jbang-mcp/pom.xml
index 107e15a92ed5..875b94462163 100644
--- a/dsl/camel-jbang/camel-jbang-mcp/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-mcp/pom.xml
@@ -112,6 +112,12 @@
             <artifactId>camel-yaml-dsl</artifactId>
         </dependency>
 
+        <!-- rest-openapi component: the OpenAPI tools follow its rules 
(generated operation ids) -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-rest-openapi</artifactId>
+        </dependency>
+
         <!-- Swagger/OpenAPI parser for contract-first OpenAPI tools -->
         <dependency>
             <groupId>io.swagger.core.v3</groupId>
@@ -143,6 +149,11 @@
         </dependency>
 
         <!-- test dependencies -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-direct</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-xml-io-dsl</artifactId>
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java
index 71505833a632..a6d5a2d5694f 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java
@@ -17,10 +17,12 @@
 package org.apache.camel.dsl.jbang.core.commands.mcp;
 
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
-import java.util.TreeSet;
 
 import jakarta.enterprise.context.ApplicationScoped;
 
@@ -36,12 +38,16 @@ import io.swagger.v3.oas.models.responses.ApiResponses;
 import io.swagger.v3.oas.models.security.SecurityScheme;
 import io.swagger.v3.parser.OpenAPIV3Parser;
 import io.swagger.v3.parser.core.models.SwaggerParseResult;
+import org.apache.camel.component.rest.openapi.RestOpenApiHelper;
 
 /**
  * MCP Tools for contract-first OpenAPI support in Apache Camel.
  *
  * Since Camel 4.6, the recommended approach is contract-first: referencing 
the OpenAPI spec directly at runtime via
- * rest:openApi. These tools help validate, scaffold, and provide mock 
guidance for that workflow.
+ * rest:openApi. These tools help validate and scaffold that workflow, 
following the rules of the camel-rest-openapi
+ * component (DefaultRestOpenapiProcessorStrategy): an operation without 
operationId is routed to the id of
+ * {@link RestOpenApiHelper#generateOperationId(String, PathItem.HttpMethod)}, 
and in mock mode a mock file is served
+ * when its location without extension ends with the request path.
  */
 @McpSecured
 @ApplicationScoped
@@ -49,10 +55,23 @@ public class OpenApiTools {
 
     private static final Set<String> VALID_MISSING_OPERATION_MODES = 
Set.of("fail", "ignore", "mock");
 
+    /**
+     * The methods that, in mock mode and without a mock file, answer with the 
request body.
+     */
+    private static final Set<String> BODY_VERBS = Set.of("DELETE", "PUT", 
"POST", "PATCH");
+    // the value a path parameter takes in the name of a mock file
+    private static final String SAMPLE_PATH_PARAMETER_VALUE = "1";
+
+    private static final String NOT_ENFORCED
+            = " is defined, but camel-rest-openapi does not enforce security 
schemes: requests are not authenticated"
+              + " against it. Secure the HTTP layer (for a Bearer token, the 
rest-openapi oauthProfile option)"
+              + " or check the credential in the route.";
+
     @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
           description = "Validate an OpenAPI specification for use with 
Camel's contract-first REST support. "
-                        + "Checks for compatibility issues like missing 
operationIds, unsupported security schemes, "
-                        + "and OpenAPI 3.1 features that Camel does not fully 
support.")
+                        + "Reports operations without operationId (with the id 
Camel routes them to), "
+                        + "security schemes (which Camel does not enforce), 
webhooks (which Camel does not serve) "
+                        + "and specs without paths.")
     public ValidateResult camel_openapi_validate(
             @ToolArg(description = "OpenAPI 3.x specification content (JSON or 
YAML string)") String spec) {
 
@@ -62,12 +81,11 @@ public class OpenApiTools {
         List<DiagnosticMessage> warnings = new ArrayList<>();
         List<DiagnosticMessage> info = new ArrayList<>();
 
-        // Check OpenAPI version for 3.1 limitations
         if (openAPI.getOpenapi() != null && 
openAPI.getOpenapi().startsWith("3.1")) {
-            warnings.add(new DiagnosticMessage(
+            info.add(new DiagnosticMessage(
                     "OPENAPI_31",
-                    "OpenAPI 3.1 detected. Camel supports 3.0.x fully; 3.1 
features like "
-                                  + "webhooks and advanced JSON Schema may not 
be supported.",
+                    "OpenAPI 3.1 detected. camel-rest-openapi parses 3.1 
specifications and serves their paths "
+                                  + "like those of a 3.0 specification; 
webhooks are not served.",
                     null));
         }
 
@@ -92,14 +110,12 @@ public class OpenApiTools {
                 }
 
                 for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : 
pathItem.readOperationsMap().entrySet()) {
-                    Operation op = opEntry.getValue();
-                    String method = opEntry.getKey().name();
-                    if (op.getOperationId() == null || 
op.getOperationId().isBlank()) {
-                        String generated = "GENOPID_" + method + 
path.replace("/", "_").replace("{", "").replace("}", "");
+                    if (opEntry.getValue().getOperationId() == null) {
                         warnings.add(new DiagnosticMessage(
                                 "MISSING_OPERATION_ID",
-                                "Operation " + method + " " + path + " has no 
operationId. "
-                                                        + "Camel will 
auto-generate: " + generated,
+                                "Operation " + opEntry.getKey().name() + " " + 
path + " has no operationId. "
+                                                        + "Camel routes it to 
direct:"
+                                                        + 
RestOpenApiHelper.generateOperationId(path, opEntry.getKey()),
                                 path));
                     }
                 }
@@ -117,9 +133,7 @@ public class OpenApiTools {
         // Check security schemes
         if (openAPI.getComponents() != null && 
openAPI.getComponents().getSecuritySchemes() != null) {
             for (Map.Entry<String, SecurityScheme> schemeEntry : 
openAPI.getComponents().getSecuritySchemes().entrySet()) {
-                String name = schemeEntry.getKey();
-                SecurityScheme scheme = schemeEntry.getValue();
-                checkSecurityScheme(name, scheme, warnings, info);
+                checkSecurityScheme(schemeEntry.getKey(), 
schemeEntry.getValue(), warnings);
             }
         }
 
@@ -131,8 +145,11 @@ public class OpenApiTools {
 
     @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
           description = "Generate Camel YAML scaffold for contract-first 
OpenAPI integration. "
-                        + "Produces a rest:openApi configuration block and 
route stubs for each operation "
-                        + "defined in the spec. This is the recommended 
approach since Camel 4.6.")
+                        + "Produces a rest:openApi configuration block and a 
direct:<operationId> route stub for "
+                        + "each operation defined in the spec (with the id 
Camel generates when the spec has none), "
+                        + "and explains the missingOperation mode. With 
missingOperation=mock it also lists the "
+                        + "camel-mock/ files Camel serves for the operations 
without a route. "
+                        + "This is the recommended approach since Camel 4.6.")
     public ScaffoldResult camel_openapi_scaffold(
             @ToolArg(description = "OpenAPI 3.x specification content (JSON or 
YAML string)") String spec,
             @ToolArg(description = "Filename of the OpenAPI spec file as it 
will be referenced at runtime "
@@ -140,14 +157,14 @@ public class OpenApiTools {
                      required = false) String specFilename,
             @ToolArg(description = "Behavior when a route is missing for an 
operationId: "
                                    + "'fail' (default, throw error), 'ignore' 
(skip silently), "
-                                   + "or 'mock' (return mock responses)",
+                                   + "or 'mock' (return mock responses; the 
answer then lists the mock files)",
                      required = false) String missingOperation) {
 
         OpenAPI openAPI = parseSpec(spec);
 
         String filename = (specFilename == null || specFilename.isBlank()) ? 
"openapi.json" : specFilename.strip();
-        String mode
-                = (missingOperation == null || missingOperation.isBlank()) ? 
"fail" : missingOperation.strip().toLowerCase();
+        String mode = (missingOperation == null || missingOperation.isBlank())
+                ? "fail" : missingOperation.strip().toLowerCase(Locale.ROOT);
 
         if (!VALID_MISSING_OPERATION_MODES.contains(mode)) {
             throw new ToolCallException(
@@ -199,127 +216,10 @@ public class OpenApiTools {
             yaml.append("                expression: \"TODO: implement 
").append(stub.operationId()).append("\"\n");
         }
 
-        return new ScaffoldResult(yaml.toString(), stubs.size(), filename, 
mode, apiTitle);
-    }
-
-    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
-          description = "Get guidance on configuring Camel's contract-first 
REST missingOperation modes "
-                        + "(fail, ignore, mock). For 'mock' mode, provides 
directory structure, mock file paths, "
-                        + "and example content derived from the OpenAPI spec.")
-    public MockGuidanceResult camel_openapi_mock_guidance(
-            @ToolArg(description = "OpenAPI 3.x specification content (JSON or 
YAML string)") String spec,
-            @ToolArg(description = "The missingOperation mode to get guidance 
for: "
-                                   + "'mock' (default), 'fail', or 'ignore'",
-                     required = false) String mode) {
-
-        OpenAPI openAPI = parseSpec(spec);
-
-        String effectiveMode = (mode == null || mode.isBlank()) ? "mock" : 
mode.strip().toLowerCase();
-        if (!VALID_MISSING_OPERATION_MODES.contains(effectiveMode)) {
-            throw new ToolCallException(
-                    "'mode' must be 'fail', 'ignore', or 'mock', got: " + 
mode, null);
-        }
-
-        String modeExplanation = getModeExplanation(effectiveMode);
-
-        // Configuration YAML snippet
-        StringBuilder configYaml = new StringBuilder();
-        configYaml.append("- rest:\n");
-        configYaml.append("    openApi:\n");
-        configYaml.append("      specification: openapi.json\n");
-        configYaml.append("      missingOperation: 
").append(effectiveMode).append("\n");
-
-        List<MockFileInfo> mockFiles = new ArrayList<>();
-        String directoryStructure = null;
-
-        if ("mock".equals(effectiveMode)) {
-            // Build mock file info from spec
-            Set<String> directories = new TreeSet<>();
-            directories.add("camel-mock/");
-
-            if (openAPI.getPaths() != null) {
-                for (Map.Entry<String, PathItem> pathEntry : 
openAPI.getPaths().entrySet()) {
-                    String path = pathEntry.getKey();
-                    PathItem pathItem = pathEntry.getValue();
-
-                    if (pathItem.readOperationsMap() == null) {
-                        continue;
-                    }
-
-                    for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : 
pathItem.readOperationsMap().entrySet()) {
-                        String method = opEntry.getKey().name();
-                        Operation op = opEntry.getValue();
-
-                        // Determine response content type and example
-                        String contentType = null;
-                        String exampleContent = null;
-                        String responseCode = null;
-
-                        if (op.getResponses() != null) {
-                            Map.Entry<String, ApiResponse> successResponse = 
findFirstSuccessResponse(op.getResponses());
-                            if (successResponse != null) {
-                                responseCode = successResponse.getKey();
-                                ApiResponse resp = successResponse.getValue();
-                                if (resp.getContent() != null && 
!resp.getContent().isEmpty()) {
-                                    Map.Entry<String, MediaType> firstContent
-                                            = 
resp.getContent().entrySet().iterator().next();
-                                    contentType = firstContent.getKey();
-                                    MediaType mediaType = 
firstContent.getValue();
-                                    if (mediaType.getExample() != null) {
-                                        exampleContent = 
mediaType.getExample().toString();
-                                    }
-                                }
-                            }
-                        }
-
-                        // Determine file extension from content type
-                        String ext = getFileExtension(contentType);
-
-                        // Build mock file path: camel-mock/<path>.<ext>
-                        String cleanPath = path.startsWith("/") ? 
path.substring(1) : path;
-                        // Replace path parameters with placeholder directory 
names
-                        cleanPath = cleanPath.replace("{", "_").replace("}", 
"_");
-                        String filePath = "camel-mock/" + cleanPath + "." + 
method.toLowerCase() + ext;
-
-                        // Track parent directories
-                        String[] parts = filePath.split("/");
-                        StringBuilder dirBuilder = new StringBuilder();
-                        for (int i = 0; i < parts.length - 1; i++) {
-                            dirBuilder.append(parts[i]).append("/");
-                            directories.add(dirBuilder.toString());
-                        }
-
-                        String operationId = op.getOperationId() != null ? 
op.getOperationId() : method + " " + path;
+        List<MockFileInfo> mockFiles = "mock".equals(mode) ? 
collectMockFiles(openAPI) : null;
 
-                        String note = null;
-                        if ("GET".equals(method) && exampleContent == null) {
-                            note = "GET without a mock file returns HTTP 204 
(No Content)";
-                        } else if ("POST".equals(method) || 
"PUT".equals(method) || "DELETE".equals(method)) {
-                            if (exampleContent == null) {
-                                note = method + " without a mock file echoes 
the request body back";
-                            }
-                        }
-
-                        mockFiles.add(new MockFileInfo(filePath, operationId, 
contentType, exampleContent, note));
-                    }
-                }
-            }
-
-            // Build directory structure string
-            StringBuilder dirStructure = new StringBuilder();
-            for (String dir : directories) {
-                int depth = dir.split("/").length - 1;
-                dirStructure.append("  
".repeat(depth)).append(dir.substring(dir.lastIndexOf('/') == dir.length() - 1
-                        ? dir.substring(0, dir.length() - 1).lastIndexOf('/') 
+ 1
-                        : dir.lastIndexOf('/') + 1));
-                dirStructure.append("\n");
-            }
-            directoryStructure = dirStructure.toString().stripTrailing();
-        }
-
-        return new MockGuidanceResult(
-                effectiveMode, modeExplanation, configYaml.toString(),
-                directoryStructure, mockFiles.isEmpty() ? null : mockFiles);
+        return new ScaffoldResult(
+                yaml.toString(), stubs.size(), filename, mode, apiTitle, 
getModeExplanation(mode), mockFiles);
     }
 
     // -- Shared helpers --
@@ -353,6 +253,13 @@ public class OpenApiTools {
         return count;
     }
 
+    /**
+     * The id camel-rest-openapi routes the operation to: its operationId, or 
the generated one.
+     */
+    private static String operationId(String path, PathItem.HttpMethod method, 
Operation op) {
+        return op.getOperationId() != null ? op.getOperationId() : 
RestOpenApiHelper.generateOperationId(path, method);
+    }
+
     private List<OperationStub> collectOperationStubs(OpenAPI openAPI) {
         List<OperationStub> stubs = new ArrayList<>();
         if (openAPI.getPaths() == null) {
@@ -368,14 +275,9 @@ public class OpenApiTools {
             }
 
             for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : 
pathItem.readOperationsMap().entrySet()) {
-                String method = opEntry.getKey().name().toLowerCase();
+                String method = 
opEntry.getKey().name().toLowerCase(Locale.ROOT);
                 Operation op = opEntry.getValue();
-
-                String operationId = op.getOperationId();
-                if (operationId == null || operationId.isBlank()) {
-                    operationId = "GENOPID_" + method.toUpperCase()
-                                  + path.replace("/", "_").replace("{", 
"").replace("}", "");
-                }
+                String operationId = operationId(path, opEntry.getKey(), op);
 
                 String responseCode = null;
                 String contentType = null;
@@ -407,6 +309,134 @@ public class OpenApiTools {
         return stubs;
     }
 
+    /**
+     * One mock file per path of the spec. For an operation without a route, 
camel-rest-openapi serves the first
+     * resource of mockIncludePattern (default classpath:camel-mock/**) whose 
location without extension ends with the
+     * request path, whatever the method: a path parameter is matched by its 
value, so {@code /pets/{petId}} takes one
+     * file per value, and the listed file carries the sample value {@code 1}, 
such as {@code camel-mock/pets/1.json}.
+     */
+    private List<MockFileInfo> collectMockFiles(OpenAPI openAPI) {
+        List<MockFileInfo> files = new ArrayList<>();
+        if (openAPI.getPaths() == null) {
+            return files;
+        }
+        for (Map.Entry<String, PathItem> pathEntry : 
openAPI.getPaths().entrySet()) {
+            String path = pathEntry.getKey();
+            Map<PathItem.HttpMethod, Operation> operations = 
pathEntry.getValue().readOperationsMap();
+            if (operations == null || operations.isEmpty()) {
+                continue;
+            }
+
+            List<String> operationIds = new ArrayList<>();
+            Map<String, String> withoutFile = new LinkedHashMap<>();
+            String responseType = null;
+            String example = null;
+            String exampleType = null;
+            for (Map.Entry<PathItem.HttpMethod, Operation> opEntry : 
operations.entrySet()) {
+                String method = opEntry.getKey().name();
+                Operation op = opEntry.getValue();
+                operationIds.add(operationId(path, opEntry.getKey(), op));
+
+                if (op.getResponses() != null) {
+                    Map.Entry<String, ApiResponse> success = 
findFirstSuccessResponse(op.getResponses());
+                    if (responseType == null && success != null && 
success.getValue().getContent() != null
+                            && !success.getValue().getContent().isEmpty()) {
+                        responseType = 
success.getValue().getContent().keySet().iterator().next();
+                    }
+                }
+                if (BODY_VERBS.contains(method)) {
+                    withoutFile.put(method, "echoes the request body with HTTP 
200");
+                    continue;
+                }
+                ApiResponse ok = op.getResponses() != null ? 
op.getResponses().get("200") : null;
+                Map.Entry<String, String> opExample = firstExample(ok, 
responseType);
+                if (opExample != null) {
+                    withoutFile.put(method, "answers the example of its '200' 
response with HTTP 200 for a matching"
+                                            + " Content-Type or Accept (see 
modeExplanation), otherwise HTTP 204");
+                    if (example == null) {
+                        exampleType = opExample.getKey();
+                        example = opExample.getValue();
+                    }
+                } else {
+                    // no example in the '200' response, or no '200' response 
at all (CAMEL-25663): an empty answer
+                    withoutFile.put(method, "answers HTTP 204 with an empty 
body");
+                }
+            }
+
+            // the file content is the example, so the extension follows the 
media type the example comes from
+            String fileType = exampleType != null ? exampleType : responseType;
+            String ext = fileType != null && fileType.contains("xml") ? ".xml" 
: ".json";
+            StringBuilder note = new StringBuilder("Served with HTTP 200 for 
any method of ").append(path)
+                    .append(" without a route.");
+            // the component matches the request path, not the template: a 
path parameter takes a sample value
+            String sample = path.replaceAll("\\{[^}]+}", 
SAMPLE_PATH_PARAMETER_VALUE);
+            if (path.contains("{")) {
+                note.append(" Camel matches the request path, so the file name 
carries the value the request uses:"
+                            + " camel-mock")
+                        .append(sample).append(ext).append(" answers 
").append(sample)
+                        .append(", add one file per value.");
+            }
+            note.append(" Without the file, ");
+            withoutFile.forEach((method, answer) -> 
note.append(method).append(' ').append(answer).append("; "));
+            note.setLength(note.length() - 2);
+            note.append('.');
+
+            // the component matches a resource whose location without 
extension ends with the request path without its
+            // leading slash; for the root path that is the empty string, so 
any mock file matches
+            String fileName = "/".equals(path) ? "camel-mock/index" + ext : 
"camel-mock" + sample + ext;
+            if ("/".equals(path)) {
+                note.append(" Camel matches the root path to any mock file 
(the first one found), so with more than"
+                            + " one file the one that answers / is not 
predictable.");
+            }
+            files.add(new MockFileInfo(fileName, path, operationIds, example, 
note.toString()));
+        }
+        return files;
+    }
+
+    /**
+     * An example of the "200" response, used as a preview of the mock file 
content, with the media type it comes from:
+     * the example of the preferred media type when it has one, otherwise the 
first media type with an example. At
+     * runtime camel-rest-openapi takes the example of the media type matching 
the request's Content-Type or Accept
+     * header.
+     */
+    private static Map.Entry<String, String> firstExample(ApiResponse ok, 
String preferredType) {
+        if (ok == null || ok.getContent() == null) {
+            return null;
+        }
+        if (preferredType != null) {
+            String preferred = exampleOf(ok.getContent().get(preferredType));
+            if (preferred != null) {
+                return Map.entry(preferredType, preferred);
+            }
+        }
+        for (Map.Entry<String, MediaType> mt : ok.getContent().entrySet()) {
+            String example = exampleOf(mt.getValue());
+            if (example != null) {
+                return Map.entry(mt.getKey(), example);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * The example, or the first of the examples, of a media type.
+     */
+    private static String exampleOf(MediaType mt) {
+        if (mt == null) {
+            return null;
+        }
+        if (mt.getExample() != null) {
+            return mt.getExample().toString();
+        }
+        if (mt.getExamples() != null && !mt.getExamples().isEmpty()) {
+            Object value = 
mt.getExamples().values().iterator().next().getValue();
+            if (value != null) {
+                return value.toString();
+            }
+        }
+        return null;
+    }
+
     private Map.Entry<String, ApiResponse> 
findFirstSuccessResponse(ApiResponses responses) {
         // Try 2xx codes in order
         for (Map.Entry<String, ApiResponse> entry : responses.entrySet()) {
@@ -422,99 +452,46 @@ public class OpenApiTools {
         return null;
     }
 
-    private void checkSecurityScheme(
-            String name, SecurityScheme scheme,
-            List<DiagnosticMessage> warnings, List<DiagnosticMessage> info) {
-
+    private void checkSecurityScheme(String name, SecurityScheme scheme, 
List<DiagnosticMessage> warnings) {
         if (scheme.getType() == null) {
             return;
         }
-
-        switch (scheme.getType()) {
-            case APIKEY:
-                if (scheme.getIn() == SecurityScheme.In.QUERY) {
-                    info.add(new DiagnosticMessage(
-                            "SECURITY_APIKEY_QUERY",
-                            "Security scheme '" + name + "' (apiKey in query) 
is supported by Camel.",
-                            null));
-                } else {
-                    warnings.add(new DiagnosticMessage(
-                            "SECURITY_APIKEY_NOT_QUERY",
-                            "Security scheme '" + name + "' (apiKey in " + 
scheme.getIn()
-                                                         + ") is defined but 
not enforced by Camel's REST OpenAPI integration. "
-                                                         + "You must handle 
authentication in your route logic.",
-                            null));
-                }
-                break;
-            case HTTP:
-                warnings.add(new DiagnosticMessage(
-                        "SECURITY_HTTP",
-                        "Security scheme '" + name + "' (HTTP " + 
scheme.getScheme()
-                                         + ") is defined but not enforced by 
Camel's REST OpenAPI integration. "
-                                         + "You must handle authentication in 
your route logic.",
-                        null));
-                break;
-            case OAUTH2:
-                warnings.add(new DiagnosticMessage(
-                        "SECURITY_OAUTH2",
-                        "Security scheme '" + name
-                                           + "' (OAuth2) is defined but not 
enforced by Camel's REST OpenAPI integration. "
-                                           + "You must handle authentication 
in your route logic.",
-                        null));
-                break;
-            case OPENIDCONNECT:
-                warnings.add(new DiagnosticMessage(
-                        "SECURITY_OPENIDCONNECT",
-                        "Security scheme '" + name
-                                                  + "' (OpenID Connect) is 
defined but not enforced by Camel's REST OpenAPI integration. "
-                                                  + "You must handle 
authentication in your route logic.",
-                        null));
-                break;
-            case MUTUALTLS:
-                warnings.add(new DiagnosticMessage(
-                        "SECURITY_MUTUALTLS",
-                        "Security scheme '" + name
-                                              + "' (Mutual TLS) is defined but 
not enforced by Camel's REST OpenAPI integration. "
-                                              + "You must handle 
authentication in your route logic.",
-                        null));
-                break;
-            default:
-                break;
-        }
+        String kind = switch (scheme.getType()) {
+            case APIKEY -> "apiKey in " + 
Objects.requireNonNullElse(scheme.getIn(), "<unset>");
+            case HTTP -> "HTTP " + 
Objects.requireNonNullElse(scheme.getScheme(), "<unset>");
+            case OAUTH2 -> "OAuth2";
+            case OPENIDCONNECT -> "OpenID Connect";
+            case MUTUALTLS -> "Mutual TLS";
+        };
+        warnings.add(new DiagnosticMessage(
+                "SECURITY_" + scheme.getType().name(),
+                "Security scheme '" + name + "' (" + kind + ")" + NOT_ENFORCED,
+                null));
     }
 
     private String getModeExplanation(String mode) {
         return switch (mode) {
-            case "fail" -> "In 'fail' mode (the default), Camel throws an 
exception at startup if any operationId "
-                           + "in the OpenAPI spec does not have a 
corresponding direct:<operationId> route. "
-                           + "This ensures all API operations are explicitly 
implemented.";
-            case "ignore" -> "In 'ignore' mode, Camel silently skips 
operations that do not have a corresponding "
-                             + "direct:<operationId> route. Requests to those 
endpoints return HTTP 404. "
-                             + "Useful during incremental development.";
-            case "mock" -> "In 'mock' mode, Camel auto-generates mock 
responses for operations without a "
-                           + "direct:<operationId> route. For GET requests, it 
looks for mock data files under "
-                           + "camel-mock/ directory. For POST/PUT/DELETE, it 
echoes the request body. "
-                           + "GET without a mock file returns HTTP 204. Useful 
for prototyping and testing.";
+            case "fail" -> "In 'fail' mode, Camel fails at startup, listing 
the unmapped operations, if an operation "
+                           + "of the OpenAPI spec has no direct:<operationId> 
route. 'fail' is the default, except "
+                           + "under the 'dev' profile (the default of camel 
run), where the default is 'mock'.";
+            case "ignore" -> "In 'ignore' mode, Camel logs the operations 
without a direct:<operationId> route at "
+                             + "startup and starts anyway. A request to such 
an operation is not routed: Camel "
+                             + "answers with the request message as is. Useful 
during incremental development.";
+            case "mock" -> "In 'mock' mode, an operation without a 
direct:<operationId> route is answered with "
+                           + "HTTP 200 and the content of the first file of 
mockIncludePattern (default "
+                           + "classpath:camel-mock/**: camel-mock/ next to the 
routes with camel run, "
+                           + "src/main/resources/camel-mock/ in a Maven 
project) whose location without extension "
+                           + "ends with the request path, whatever the method. 
When the request has a JSON or XML "
+                           + "Content-Type, only a .json or .xml file matches. 
Without a file, DELETE, PUT, POST "
+                           + "and PATCH echo the request body with HTTP 200; 
the other methods answer the example of "
+                           + "the '200' response of the spec for the media 
type matching the request's Content-Type "
+                           + "or Accept header (Accept */* takes 
application/json, then application/xml), and "
+                           + "otherwise (such as a request without Accept 
header, or no '200' example) HTTP 204 with "
+                           + "an empty body.";
             default -> "";
         };
     }
 
-    private String getFileExtension(String contentType) {
-        if (contentType == null) {
-            return ".json";
-        }
-        if (contentType.contains("json")) {
-            return ".json";
-        }
-        if (contentType.contains("xml")) {
-            return ".xml";
-        }
-        if (contentType.contains("text")) {
-            return ".txt";
-        }
-        return ".json";
-    }
-
     // -- Result records --
 
     public record DiagnosticMessage(String code, String message, String path) {
@@ -543,22 +520,26 @@ public class OpenApiTools {
             int operationCount,
             String specFilename,
             String missingOperation,
-            String apiTitle) {
+            String apiTitle,
+            String modeExplanation,
+            List<MockFileInfo> mockFiles) {
     }
 
+    /**
+     * A mock file for one path of the spec.
+     *
+     * @param filePath       the file to add, named after a request path: each 
path parameter carries the sample value
+     *                       {@code 1}, such as {@code camel-mock/pets/1.json} 
for {@code /pets/{petId}}
+     * @param requestPath    the path of the spec, as a template, such as 
{@code /pets/{petId}}
+     * @param operationIds   the operations of the path
+     * @param exampleContent an example of the spec for the file content, or 
null
+     * @param note           what Camel answers with and without the file
+     */
     public record MockFileInfo(
             String filePath,
-            String operation,
-            String contentType,
+            String requestPath,
+            List<String> operationIds,
             String exampleContent,
             String note) {
     }
-
-    public record MockGuidanceResult(
-            String mode,
-            String modeExplanation,
-            String configurationYaml,
-            String directoryStructure,
-            List<MockFileInfo> mockFiles) {
-    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiScaffoldRestOpenApiTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiScaffoldRestOpenApiTest.java
new file mode 100644
index 000000000000..5359fee48490
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiScaffoldRestOpenApiTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.dsl.jbang.core.commands.mcp;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.component.platform.http.PlatformHttpConstants;
+import org.apache.camel.component.platform.http.PlatformHttpEndpoint;
+import org.apache.camel.component.platform.http.spi.PlatformHttpConsumer;
+import org.apache.camel.component.platform.http.spi.PlatformHttpEngine;
+import org.apache.camel.component.rest.openapi.RestOpenApiComponent;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultConsumer;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.ResourceHelper;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Binds what camel_openapi_scaffold generates for the petstore spec with the 
real camel-rest-openapi component. The
+ * HTTP server is replaced by an engine that hands the requests to the 
rest-openapi consumer in-process.
+ */
+class OpenApiScaffoldRestOpenApiTest {
+
+    private static String petstore;
+
+    private final OpenApiTools tools = new OpenApiTools();
+    private final AtomicReference<Processor> consumerProcessor = new 
AtomicReference<>();
+
+    @TempDir
+    Path dir;
+
+    @BeforeAll
+    static void loadSpec() throws Exception {
+        try (InputStream is = 
OpenApiScaffoldRestOpenApiTest.class.getResourceAsStream("/petstore.yaml")) {
+            petstore = new String(is.readAllBytes(), StandardCharsets.UTF_8);
+        }
+    }
+
+    @Test
+    void scaffoldedRouteOfAnOperationWithoutOperationIdIsCalled() throws 
Exception {
+        OpenApiTools.ScaffoldResult scaffold = 
tools.camel_openapi_scaffold(petstore, "petstore.yaml", null);
+
+        // in the default 'fail' mode the context only starts when every 
operation of the spec has its route
+        try (CamelContext context = start(scaffold.yaml(), null)) {
+            Exchange answer = call(context, "/v1/pets/42");
+
+            assertThat(answer.getException()).isNull();
+            
assertThat(answer.getMessage().getBody(String.class)).isEqualTo("TODO: 
implement GENOPID_GET.pets._petId_");
+        }
+    }
+
+    @Test
+    void scaffoldedMockFileIsServed() throws Exception {
+        OpenApiTools.ScaffoldResult scaffold = 
tools.camel_openapi_scaffold(petstore, "petstore.yaml", "mock");
+
+        assertThat(scaffold.mockFiles())
+                .extracting(OpenApiTools.MockFileInfo::filePath)
+                .containsExactly("camel-mock/pets.json", 
"camel-mock/pets/1.json");
+        Path file = dir.resolve(scaffold.mockFiles().get(1).filePath());
+        Files.createDirectories(file.getParent());
+        Files.writeString(file, "{\"id\": 1, \"name\": \"Tom\"}");
+
+        // keep only the rest block, so that no operation has a route
+        String restOnly = scaffold.yaml().substring(0, 
scaffold.yaml().indexOf("- route:"));
+        try (CamelContext context = start(restOnly, "file:" + 
dir.toAbsolutePath() + "/camel-mock/**")) {
+            Exchange answer = call(context, "/v1/pets/1");
+
+            assertThat(answer.getException()).isNull();
+            
assertThat(answer.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE)).isEqualTo(200);
+            
assertThat(answer.getMessage().getBody(String.class)).isEqualToIgnoringNewLines("{\"id\":
 1, \"name\": \"Tom\"}");
+        }
+    }
+
+    private CamelContext start(String yaml, String mockIncludePattern) throws 
Exception {
+        CamelContext context = new DefaultCamelContext();
+        PlatformHttpEngine engine = (endpoint, processor) -> {
+            consumerProcessor.set(processor);
+            return new InVmConsumer(endpoint, processor);
+        };
+        
context.getRegistry().bind(PlatformHttpConstants.PLATFORM_HTTP_ENGINE_NAME, 
engine);
+        if (mockIncludePattern != null) {
+            context.getComponent("rest-openapi", 
RestOpenApiComponent.class).setMockIncludePattern(mockIncludePattern);
+        }
+        
PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("scaffold.camel.yaml",
 yaml));
+        context.start();
+        return context;
+    }
+
+    private Exchange call(CamelContext context, String path) throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setHeader(Exchange.HTTP_METHOD, "GET");
+        exchange.getMessage().setHeader(Exchange.HTTP_PATH, path);
+        consumerProcessor.get().process(exchange);
+        return exchange;
+    }
+
+    private static final class InVmConsumer extends DefaultConsumer implements 
PlatformHttpConsumer {
+
+        InVmConsumer(PlatformHttpEndpoint endpoint, Processor processor) {
+            super(endpoint, processor);
+        }
+
+        @Override
+        public PlatformHttpEndpoint getEndpoint() {
+            return (PlatformHttpEndpoint) super.getEndpoint();
+        }
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiToolsTest.java
index 8dfc29830956..faf26194810f 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiToolsTest.java
@@ -221,7 +221,7 @@ class OpenApiToolsTest {
         assertThat(result.valid()).isTrue();
         assertThat(result.warnings()).anySatisfy(w -> {
             assertThat(w.code()).isEqualTo("MISSING_OPERATION_ID");
-            assertThat(w.message()).contains("GENOPID_");
+            assertThat(w.message()).contains("direct:GENOPID_GET.items");
         });
     }
 
@@ -229,14 +229,12 @@ class OpenApiToolsTest {
     void validateSecuritySchemeWarnings() {
         OpenApiTools.ValidateResult result = 
tools.camel_openapi_validate(SPEC_WITH_SECURITY);
 
-        // apiKey in query should be info
-        assertThat(result.info()).anySatisfy(i -> 
assertThat(i.code()).isEqualTo("SECURITY_APIKEY_QUERY"));
-        // apiKey in header should be warning
-        assertThat(result.warnings()).anySatisfy(w -> 
assertThat(w.code()).isEqualTo("SECURITY_APIKEY_NOT_QUERY"));
-        // HTTP bearer should be warning
-        assertThat(result.warnings()).anySatisfy(w -> 
assertThat(w.code()).isEqualTo("SECURITY_HTTP"));
-        // OAuth2 should be warning
-        assertThat(result.warnings()).anySatisfy(w -> 
assertThat(w.code()).isEqualTo("SECURITY_OAUTH2"));
+        // camel-rest-openapi enforces none of the schemes, an apiKey in query 
included
+        assertThat(result.info()).isEmpty();
+        assertThat(result.warnings())
+                .extracting(OpenApiTools.DiagnosticMessage::code)
+                .containsExactlyInAnyOrder("SECURITY_APIKEY", 
"SECURITY_APIKEY", "SECURITY_HTTP", "SECURITY_OAUTH2");
+        assertThat(result.warnings()).allSatisfy(w -> 
assertThat(w.message()).contains("does not enforce"));
     }
 
     @Test
@@ -252,7 +250,8 @@ class OpenApiToolsTest {
         OpenApiTools.ValidateResult result = 
tools.camel_openapi_validate(SPEC_31_WITH_WEBHOOKS);
 
         assertThat(result.warnings()).anySatisfy(w -> 
assertThat(w.code()).isEqualTo("WEBHOOKS_PRESENT"));
-        assertThat(result.warnings()).anySatisfy(w -> 
assertThat(w.code()).isEqualTo("OPENAPI_31"));
+        // 3.1 is parsed and served like 3.0: information, not a warning
+        assertThat(result.info()).anySatisfy(i -> 
assertThat(i.code()).isEqualTo("OPENAPI_31"));
     }
 
     // ---- Scaffold tests ----
@@ -351,84 +350,96 @@ class OpenApiToolsTest {
                 .hasMessageContaining("missingOperation");
     }
 
-    // ---- Mock guidance tests ----
-
-    @Test
-    void mockGuidanceDefaultModeIsMock() {
-        OpenApiTools.MockGuidanceResult result = 
tools.camel_openapi_mock_guidance(MINIMAL_SPEC, null);
-
-        assertThat(result.mode()).isEqualTo("mock");
-        assertThat(result.modeExplanation()).contains("mock");
-    }
-
     @Test
-    void mockGuidanceGeneratesMockFilePaths() {
-        OpenApiTools.MockGuidanceResult result = 
tools.camel_openapi_mock_guidance(MINIMAL_SPEC, "mock");
-
-        assertThat(result.mockFiles()).isNotNull();
-        assertThat(result.mockFiles()).anySatisfy(f -> 
assertThat(f.filePath()).contains("camel-mock/"));
-    }
-
-    @Test
-    void mockGuidanceFailModeExplanation() {
-        OpenApiTools.MockGuidanceResult result = 
tools.camel_openapi_mock_guidance(MINIMAL_SPEC, "fail");
-
-        assertThat(result.mode()).isEqualTo("fail");
-        assertThat(result.modeExplanation()).contains("fail");
-        assertThat(result.modeExplanation()).contains("exception");
-        // No mock files for fail mode
-        assertThat(result.mockFiles()).isNull();
-        assertThat(result.directoryStructure()).isNull();
-    }
-
-    @Test
-    void mockGuidanceIgnoreModeExplanation() {
-        OpenApiTools.MockGuidanceResult result = 
tools.camel_openapi_mock_guidance(MINIMAL_SPEC, "ignore");
+    void scaffoldMockModeListsOneMockFilePerPath() {
+        OpenApiTools.ScaffoldResult result = 
tools.camel_openapi_scaffold(MINIMAL_SPEC, null, "mock");
 
-        assertThat(result.mode()).isEqualTo("ignore");
-        assertThat(result.modeExplanation()).contains("ignore");
-        assertThat(result.modeExplanation()).contains("404");
+        assertThat(result.modeExplanation()).contains("ends with the request 
path");
+        assertThat(result.mockFiles()).singleElement().satisfies(f -> {
+            assertThat(f.filePath()).isEqualTo("camel-mock/pets.json");
+            assertThat(f.operationIds()).containsExactly("listPets", 
"createPet");
+            assertThat(f.exampleContent()).contains("dog");
+            assertThat(f.note()).contains("GET answers the example of its 
'200' response with HTTP 200 for a matching"
+                                          + " Content-Type or Accept (see 
modeExplanation), otherwise HTTP 204",
+                    "POST echoes the request body");
+        });
     }
 
     @Test
-    void mockGuidanceConfigYamlCorrect() {
-        OpenApiTools.MockGuidanceResult result = 
tools.camel_openapi_mock_guidance(MINIMAL_SPEC, "mock");
-
-        assertThat(result.configurationYaml()).contains("missingOperation: 
mock");
-        assertThat(result.configurationYaml()).contains("specification: 
openapi.json");
-    }
+    void scaffoldNamesTheMockFileAfterTheMediaTypeOfItsExample() {
+        String spec = """
+                {
+                  "openapi": "3.0.0",
+                  "info": { "title": "Pets", "version": "1.0" },
+                  "paths": {
+                    "/pets": {
+                      "get": {
+                        "operationId": "listPets",
+                        "responses": {
+                          "200": {
+                            "description": "OK",
+                            "content": {
+                              "application/json": { "schema": { "type": 
"array" } },
+                              "application/xml": { "example": 
"<pets><pet>dog</pet></pets>" }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+                """;
+        OpenApiTools.ScaffoldResult result = 
tools.camel_openapi_scaffold(spec, null, "mock");
 
-    @Test
-    void mockGuidanceNullSpecThrows() {
-        assertThatThrownBy(() -> tools.camel_openapi_mock_guidance(null, 
"mock"))
-                .isInstanceOf(ToolCallException.class)
-                .hasMessageContaining("spec");
+        assertThat(result.mockFiles()).singleElement().satisfies(f -> {
+            assertThat(f.filePath()).isEqualTo("camel-mock/pets.xml");
+            
assertThat(f.exampleContent()).isEqualTo("<pets><pet>dog</pet></pets>");
+        });
     }
 
     @Test
-    void mockGuidanceExampleContentPopulated() {
-        OpenApiTools.MockGuidanceResult result = 
tools.camel_openapi_mock_guidance(MINIMAL_SPEC, "mock");
-
-        // The MINIMAL_SPEC has example data on the GET /pets response
-        assertThat(result.mockFiles()).isNotNull();
-        assertThat(result.mockFiles()).anySatisfy(f -> {
-            assertThat(f.operation()).isEqualTo("listPets");
-            assertThat(f.exampleContent()).isNotNull();
+    void scaffoldNamesTheRootPathMockFileVisibly() {
+        OpenApiTools.ScaffoldResult result
+                = 
tools.camel_openapi_scaffold(MINIMAL_SPEC.replace("\"/pets\":", "\"/\":"), 
null, "mock");
+
+        assertThat(result.mockFiles()).singleElement().satisfies(f -> {
+            assertThat(f.filePath()).isEqualTo("camel-mock/index.json");
+            assertThat(f.requestPath()).isEqualTo("/");
+            assertThat(f.note()).contains("any mock file");
         });
     }
 
     @Test
-    void mockGuidanceInvalidModeThrows() {
-        assertThatThrownBy(() -> 
tools.camel_openapi_mock_guidance(MINIMAL_SPEC, "invalid"))
-                .isInstanceOf(ToolCallException.class)
-                .hasMessageContaining("mode");
+    void scaffoldNamesThePathParameterMockFileWithASampleValue() {
+        String spec = """
+                {
+                  "openapi": "3.0.0",
+                  "info": { "title": "Pets", "version": "1.0" },
+                  "paths": {
+                    "/pets/{petId}": {
+                      "get": {
+                        "operationId": "showPetById",
+                        "responses": { "201": { "description": "Created" } }
+                      }
+                    }
+                  }
+                }
+                """;
+        OpenApiTools.ScaffoldResult result = 
tools.camel_openapi_scaffold(spec, null, "mock");
+
+        assertThat(result.mockFiles()).singleElement().satisfies(f -> {
+            assertThat(f.filePath()).isEqualTo("camel-mock/pets/1.json");
+            assertThat(f.requestPath()).isEqualTo("/pets/{petId}");
+            assertThat(f.note()).contains("camel-mock/pets/1.json answers 
/pets/1",
+                    "GET answers HTTP 204 with an empty body");
+        });
     }
 
     @Test
-    void mockGuidanceDirectoryStructurePresent() {
-        OpenApiTools.MockGuidanceResult result = 
tools.camel_openapi_mock_guidance(MINIMAL_SPEC, "mock");
+    void scaffoldListsNoMockFileOutsideMockMode() {
+        OpenApiTools.ScaffoldResult result = 
tools.camel_openapi_scaffold(MINIMAL_SPEC, null, "ignore");
 
-        assertThat(result.directoryStructure()).isNotNull();
-        assertThat(result.directoryStructure()).contains("camel-mock");
+        assertThat(result.modeExplanation()).contains("ignore");
+        assertThat(result.mockFiles()).isNull();
     }
 }
diff --git a/dsl/camel-jbang/camel-jbang-mcp/src/test/resources/petstore.yaml 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/resources/petstore.yaml
new file mode 100644
index 000000000000..fe2b9ad4e8e9
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-mcp/src/test/resources/petstore.yaml
@@ -0,0 +1,83 @@
+#
+# 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.
+#
+
+# The OpenAPI Initiative petstore example, with no operationId on GET 
/pets/{petId}
+openapi: 3.0.3
+info:
+  title: Swagger Petstore
+  version: 1.0.0
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    get:
+      operationId: listPets
+      summary: List all pets
+      responses:
+        '200':
+          description: A paged array of pets
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Pets'
+    post:
+      operationId: createPets
+      summary: Create a pet
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Pet'
+      responses:
+        '201':
+          description: Null response
+  /pets/{petId}:
+    get:
+      summary: Info for a specific pet
+      parameters:
+        - name: petId
+          in: path
+          required: true
+          description: The id of the pet to retrieve
+          schema:
+            type: string
+      responses:
+        '200':
+          description: Expected response to a valid request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Pet'
+components:
+  schemas:
+    Pet:
+      type: object
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: '#/components/schemas/Pet'

Reply via email to