diff --git a/docs/mcp-tool-behavior-hints.md b/docs/mcp-tool-behavior-hints.md new file mode 100644 index 0000000000..cdda9e1119 --- /dev/null +++ b/docs/mcp-tool-behavior-hints.md @@ -0,0 +1,21 @@ +# MCP tool behavior hints + +The tool catalog publishes all four MCP behavior hints for every registered tool. They describe the full range of accepted arguments, including collection and table behavior. They do not change execution, authorize requests, or deduplicate retries. + +## Behavior behind the hints + +Provider listing reads configured provider descriptions. It does not invoke embedding or reranking models. Schema validation and database or credential-service access remain within the managed service boundary. Open world identifies tools that can send input to an embedding or reranking provider. + +Find and findOne can vectorize a sort clause, and findAndRerank can invoke both embedding and reranking. These tools do not change stored data, but repeated calls can repeat provider work and charges. Their idempotency hint stays false. Sortable single-document deletes can also vectorize a query. + +Insert tools cover tables as well as collections. Table inserts can overwrite existing row values, so both tools carry the destructive hint. Missing collection IDs are generated per invocation. Inserts and updates can also vectorize document contents. None of these tools advertises safe replay. + +Filtered deleteMany calls stop at the configured deletion limit. A repeated call can remove another batch. Single-document deletes can select a different matching document on a second call. Updates can increment values, replace data, or generate an upsert. A new RPC ID does not prevent any of those effects. + +Named schema creates and drops have no additional schema effect when repeated with the same arguments and no intervening changes. Their idempotency hint does not promise the same response. Depending on command options, a second call can report that an object already exists or is missing. Alterations keep a conservative false idempotency hint across their supported operation forms and may remove columns or rename fields. + +## Verification + +McpToolAnnotationsTest reads the catalog over HTTP and checks all 35 tool definitions. The test covers every hint and fails if a new tool has no reviewed metadata. It does not execute mutations or contact a model provider. + +The hint fields follow the [Quarkiverse annotation API](https://github.com/quarkiverse/quarkus-mcp-server/blob/2.0.3/core/runtime/src/main/java/io/quarkiverse/mcp/server/Tool.java) and the [MCP tool contract](https://modelcontextprotocol.io/specification/2026-07-28/server/tools). diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/CollectionCommandTools.java b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/CollectionCommandTools.java index efb89bf227..6012d2d815 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/CollectionCommandTools.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/CollectionCommandTools.java @@ -27,7 +27,14 @@ public class CollectionCommandTools { @Inject McpResource mcpResource; - @Tool(description = "Command that alters the column definition in a table.") + @Tool( + description = "Command that alters the column definition in a table.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = false)) public Uni alterTable( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the table") String table, @@ -37,7 +44,14 @@ public Uni alterTable( return mcpResource.processCollectionCommand(keyspace, table, command); } - @Tool(description = "Command that returns count of documents in a collection") + @Tool( + description = "Command that returns count of documents in a collection", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni countDocuments( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection") String collection, @@ -47,7 +61,14 @@ public Uni countDocuments( return mcpResource.processCollectionCommand(keyspace, collection, command); } - @Tool(description = "Creates a regular index for a column in a table.") + @Tool( + description = "Creates a regular index for a column in a table.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni createIndex( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the table") String table, @@ -70,7 +91,13 @@ public Uni createIndex( @Tool( description = - "Creates an index on a text column that can be used for lexical filtering and sorting.") + "Creates an index on a text column that can be used for lexical filtering and sorting.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni createTextIndex( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the table") String table, @@ -91,7 +118,14 @@ public Uni createTextIndex( return mcpResource.processCollectionCommand(keyspace, table, command); } - @Tool(description = "Creates an index on a vector column that can be used for vector sorting.") + @Tool( + description = "Creates an index on a vector column that can be used for vector sorting.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni createVectorIndex( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the table") String table, @@ -114,7 +148,13 @@ public Uni createVectorIndex( @Tool( description = - "Command that finds documents based on the filter and deletes them from a collection") + "Command that finds documents based on the filter and deletes them from a collection", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = false)) public Uni deleteMany( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -127,7 +167,14 @@ public Uni deleteMany( return mcpResource.processCollectionCommand(keyspace, collection, command); } - @Tool(description = "Command that finds a single document and deletes it from a collection") + @Tool( + description = "Command that finds a single document and deletes it from a collection", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = true)) public Uni deleteOne( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -139,7 +186,14 @@ public Uni deleteOne( return mcpResource.processCollectionCommand(keyspace, collection, command); } - @Tool(description = "Command that returns estimated count of documents in a collection") + @Tool( + description = "Command that returns estimated count of documents in a collection", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni estimatedDocumentCount( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection") String collection) { @@ -148,7 +202,14 @@ public Uni estimatedDocumentCount( return mcpResource.processCollectionCommand(keyspace, collection, command); } - @Tool(description = "Finds documents using vector and lexical sorting, then reranks the results.") + @Tool( + description = "Finds documents using vector and lexical sorting, then reranks the results.", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = false, + openWorldHint = true)) public Uni findAndRerank( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection") String collection, @@ -170,7 +231,14 @@ public Uni findAndRerank( return mcpResource.processCollectionCommand(keyspace, collection, command); } - @Tool(description = "Command that finds JSON documents from a collection.") + @Tool( + description = "Command that finds JSON documents from a collection.", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = false, + openWorldHint = true)) public Uni find( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -185,7 +253,13 @@ public Uni find( @Tool( description = - "Command that finds a single JSON document from a collection and deletes it. The deleted document is returned") + "Command that finds a single JSON document from a collection and deletes it. The deleted document is returned", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = true)) public Uni findOneAndDelete( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -199,7 +273,13 @@ public Uni findOneAndDelete( @Tool( description = - "Command that finds a single JSON document from a collection and replaces it with the replacement document.") + "Command that finds a single JSON document from a collection and replaces it with the replacement document.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = true)) public Uni findOneAndReplace( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -216,7 +296,13 @@ public Uni findOneAndReplace( @Tool( description = - "Command that finds a single JSON document from a collection and updates the values provided in the update clause.") + "Command that finds a single JSON document from a collection and updates the values provided in the update clause.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = true)) public Uni findOneAndUpdate( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -230,7 +316,14 @@ public Uni findOneAndUpdate( return mcpResource.processCollectionCommand(keyspace, collection, command); } - @Tool(description = "Command that finds a single JSON document from a collection.") + @Tool( + description = "Command that finds a single JSON document from a collection.", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = false, + openWorldHint = true)) public Uni findOne( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -243,7 +336,14 @@ public Uni findOne( return mcpResource.processCollectionCommand(keyspace, collection, command); } - @Tool(description = "Command that lists all available indexes in a table.") + @Tool( + description = "Command that lists all available indexes in a table.", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni listIndexes( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the table") String table, @@ -254,7 +354,14 @@ public Uni listIndexes( return mcpResource.processCollectionCommand(keyspace, table, command); } - @Tool(description = "Command that inserts multiple JSON documents to a collection.") + @Tool( + description = "Command that inserts multiple JSON documents to a collection.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = true)) public Uni insertMany( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -266,7 +373,14 @@ public Uni insertMany( return mcpResource.processCollectionCommand(keyspace, collection, command); } - @Tool(description = "Command that inserts a single JSON document to a collection.") + @Tool( + description = "Command that inserts a single JSON document to a collection.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = true)) public Uni insertOne( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -278,7 +392,13 @@ public Uni insertOne( @Tool( description = - "Command that finds documents from a collection and updates it with the values provided in the update clause.") + "Command that finds documents from a collection and updates it with the values provided in the update clause.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = true)) public Uni updateMany( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, @@ -292,7 +412,13 @@ public Uni updateMany( @Tool( description = - "Command that finds a single JSON document from a table or collection and updates the values provided in the update clause.") + "Command that finds a single JSON document from a table or collection and updates the values provided in the update clause.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = true)) public Uni updateOne( @ToolArg(description = "Name of the keyspace") String keyspace, @ToolArg(description = "Name of the collection/table") String collection, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandTools.java b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandTools.java index b09de16b5c..40ce8ff994 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandTools.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandTools.java @@ -20,7 +20,14 @@ public class GeneralCommandTools { @Inject McpResource mcpResource; - @Tool(description = "Command that creates a keyspace.") + @Tool( + description = "Command that creates a keyspace.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni createKeyspace( @ToolArg(description = "Required name of the new Keyspace") String name, @ToolArg(description = "Options for creating a new keyspace.", required = false) @@ -30,7 +37,14 @@ public Uni createKeyspace( return mcpResource.processCommand(() -> mcpResource.buildDatabaseContext(command), command); } - @Tool(description = "Command that deletes a Keyspace.") + @Tool( + description = "Command that deletes a Keyspace.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = true, + openWorldHint = false)) public Uni dropKeyspace( @ToolArg(description = "Required name of the Keyspace to remove") @NotEmpty String name) { @@ -38,14 +52,28 @@ public Uni dropKeyspace( return mcpResource.processCommand(() -> mcpResource.buildDatabaseContext(command), command); } - @Tool(description = "Command that lists all available keyspaces") + @Tool( + description = "Command that lists all available keyspaces", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni findKeyspaces() { var command = new FindKeyspacesCommand(); return mcpResource.processCommand(() -> mcpResource.buildDatabaseContext(command), command); } - @Tool(description = "Lists the available Embedding Providers for this database.") + @Tool( + description = "Lists the available Embedding Providers for this database.", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni findEmbeddingProviders( @ToolArg( description = @@ -57,7 +85,14 @@ public Uni findEmbeddingProviders( return mcpResource.processCommand(() -> mcpResource.buildDatabaseContext(command), command); } - @Tool(description = "Lists the available Reranking Providers for this database.") + @Tool( + description = "Lists the available Reranking Providers for this database.", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni findRerankingProviders( @ToolArg( description = diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/KeyspaceCommandTools.java b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/KeyspaceCommandTools.java index ef22b4effa..9edca85109 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/KeyspaceCommandTools.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/KeyspaceCommandTools.java @@ -22,7 +22,14 @@ public class KeyspaceCommandTools { * Below are converted from {@link * io.stargate.sgv2.jsonapi.api.model.command.CollectionOnlyCommand} */ - @Tool(description = "Command that creates a collection.") + @Tool( + description = "Command that creates a collection.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni createCollection( @ToolArg(description = "Name of the existing keyspace") String keyspace, @ToolArg(description = "Name of the new collection") String collection, @@ -34,7 +41,14 @@ public Uni createCollection( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that deletes a collection if one exists.") + @Tool( + description = "Command that deletes a collection if one exists.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = true, + openWorldHint = false)) public Uni deleteCollection( @ToolArg(description = "Name of the existing Keyspace") String keyspace, @ToolArg(description = "Name of the existing Collection") String collection) { @@ -44,7 +58,14 @@ public Uni deleteCollection( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that lists all available collections in a keyspace.") + @Tool( + description = "Command that lists all available collections in a keyspace.", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni findCollections( @ToolArg(description = "Name of the existing Keyspace") String keyspace, @ToolArg(description = "include collection properties.", required = false) @@ -58,7 +79,14 @@ public Uni findCollections( /** * Below are converted from {@link io.stargate.sgv2.jsonapi.api.model.command.TableOnlyCommand} */ - @Tool(description = "Command that alters a user defined type.") + @Tool( + description = "Command that alters a user defined type.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = false, + openWorldHint = false)) public Uni alterType( @ToolArg(description = "Keyspace of the type to alter") String keyspace, @ToolArg(description = "Name of the type to alter") String name, @@ -71,7 +99,14 @@ public Uni alterType( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that creates an API Table.") + @Tool( + description = "Command that creates an API Table.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni createTable( @ToolArg(description = "Name of the existing keyspace") String keyspace, @ToolArg(description = "Name of the new Table") String table, @@ -84,7 +119,14 @@ public Uni createTable( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that creates a user defined type.") + @Tool( + description = "Command that creates a user defined type.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni createType( @ToolArg(description = "Name of the existing keyspace") String keyspace, @ToolArg(description = "Name of the type to create") String name, @@ -97,7 +139,14 @@ public Uni createType( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that drops an index for a column.") + @Tool( + description = "Command that drops an index for a column.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = true, + openWorldHint = false)) public Uni dropIndex( @ToolArg(description = "Name of the existing keyspace") String keyspace, @ToolArg(description = "Name of the Index to remove") String name, @@ -109,7 +158,14 @@ public Uni dropIndex( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that drops a table if one exists.") + @Tool( + description = "Command that drops a table if one exists.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = true, + openWorldHint = false)) public Uni dropTable( @ToolArg(description = "Name of the existing keyspace") String keyspace, @ToolArg(description = "Name of the Table to remove") String table, @@ -121,7 +177,14 @@ public Uni dropTable( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that drops a user defined type if one exists.") + @Tool( + description = "Command that drops a user defined type if one exists.", + annotations = + @Tool.Annotations( + readOnlyHint = false, + destructiveHint = true, + idempotentHint = true, + openWorldHint = false)) public Uni dropType( @ToolArg(description = "Name of the existing keyspace") String keyspace, @ToolArg(description = "Name of the Type to remove") String name, @@ -133,7 +196,14 @@ public Uni dropType( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that lists all available tables in a keyspace") + @Tool( + description = "Command that lists all available tables in a keyspace", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni listTables( @ToolArg(description = "Name of the existing keyspace") String keyspace, @ToolArg(description = "Options for the `listTables` command.", required = false) @@ -144,7 +214,14 @@ public Uni listTables( () -> mcpResource.buildKeyspaceContext(keyspace, command), command); } - @Tool(description = "Command that lists all available types in a keyspace") + @Tool( + description = "Command that lists all available types in a keyspace", + annotations = + @Tool.Annotations( + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = false)) public Uni listTypes( @ToolArg(description = "Name of the existing keyspace") String keyspace, @ToolArg(description = "Options for the `listTypes` command.", required = false) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpToolAnnotationsTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpToolAnnotationsTest.java new file mode 100644 index 0000000000..fd1436bc25 --- /dev/null +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpToolAnnotationsTest.java @@ -0,0 +1,102 @@ +package io.stargate.sgv2.jsonapi.api.v1.mcp; + +import static io.stargate.sgv2.jsonapi.api.v1.mcp.McpHttpTestSupport.request; +import static io.stargate.sgv2.jsonapi.api.v1.mcp.McpHttpTestSupport.rpcPayload; +import static org.assertj.core.api.Assertions.assertThat; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** Checks the behavior hints clients receive from the registered tools over HTTP. */ +@QuarkusTest +@TestProfile(NoGlobalResourcesTestProfile.Impl.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class McpToolAnnotationsTest { + private final Map> tools = new HashMap<>(); + + @BeforeEach + void listTools() { + if (!tools.isEmpty()) { + return; + } + List> registeredTools = + request("tools/list") + .header("Token", "annotations-test-credential") + .body(rpcPayload(1, "tools/list", Map.of())) + .post("/v1/mcp") + .then() + .statusCode(200) + .extract() + .jsonPath() + .getList("result.tools"); + for (var tool : registeredTools) { + assertThat(tools.put((String) tool.get("name"), tool)).isNull(); + } + } + + @Test + void listsEveryToolWithBehaviorHints() { + assertThat(tools).hasSize(35); + tools.forEach((name, tool) -> assertThat(tool).as(name).containsKey("annotations")); + } + + @ParameterizedTest(name = "{0}") + @CsvSource({ + "createKeyspace, false, false, true, false", + "dropKeyspace, false, true, true, false", + "findKeyspaces, true, false, true, false", + "findEmbeddingProviders, true, false, true, false", + "findRerankingProviders, true, false, true, false", + "createCollection, false, false, true, false", + "deleteCollection, false, true, true, false", + "findCollections, true, false, true, false", + "alterType, false, true, false, false", + "createTable, false, false, true, false", + "createType, false, false, true, false", + "dropIndex, false, true, true, false", + "dropTable, false, true, true, false", + "dropType, false, true, true, false", + "listTables, true, false, true, false", + "listTypes, true, false, true, false", + "alterTable, false, true, false, false", + "countDocuments, true, false, true, false", + "createIndex, false, false, true, false", + "createTextIndex, false, false, true, false", + "createVectorIndex, false, false, true, false", + "deleteMany, false, true, false, false", + "deleteOne, false, true, false, true", + "estimatedDocumentCount, true, false, true, false", + "findAndRerank, true, false, false, true", + "find, true, false, false, true", + "findOneAndDelete, false, true, false, true", + "findOneAndReplace, false, true, false, true", + "findOneAndUpdate, false, true, false, true", + "findOne, true, false, false, true", + "listIndexes, true, false, true, false", + "insertMany, false, true, false, true", + "insertOne, false, true, false, true", + "updateMany, false, true, false, true", + "updateOne, false, true, false, true" + }) + void publishesReviewedBehaviorHints( + String name, boolean readOnly, boolean destructive, boolean idempotent, boolean openWorld) { + assertThat(tools).containsKey(name); + assertThat(tools.get(name).get("annotations")) + .as("Behavior hints for %s", name) + .isEqualTo( + Map.of( + "readOnlyHint", readOnly, + "destructiveHint", destructive, + "idempotentHint", idempotent, + "openWorldHint", openWorld)); + } +}