mongodb/laravel-mongodb · error · InvalidArgumentException
Only read commands are allowed
Error message
Only read commands are allowed (%s).
What it means
The first key of the `command` document is not one of the four allowed read-only MQL CRUD commands: aggregate, count, distinct, find. The tool is marked read-only (#[IsReadOnly]) and intentionally refuses write or admin commands such as insert, update, delete, drop, or findAndModify.
Solutions
- Use one of the allowed commands: aggregate, count, distinct, or find, with the collection as the value of the first key.
- For writes, execute them in your own application code (e.g. MongoDB\Laravel facade or the MongoDB library) instead of the read-only tool.
- Replace shell helpers: findOne -> find with limit 1; insertMany/updateMany -> application code.
- For diagnostics like dbStats, run them via a direct MongoDB client, not this tool.
Example fix
// before
{"connection": "mongodb", "command": {"insert": "users", "documents": [{"name": "Ada"}]}}
// after
{"connection": "mongodb", "command": {"find": "users", "filter": {}, "limit": 10}} Defensive patterns
Strategy: validation
Validate before calling
$allowed = ['aggregate', 'count', 'distinct', 'find'];
if (!isset($command[array_key_first($command)]) || !in_array(array_key_first($command), $allowed, true)) {
throw new \InvalidArgumentException('Only read commands are allowed: ' . implode(', ', $allowed));
} Type guard
$isReadCommand = fn (array $c): bool => in_array(array_key_first($c), ['aggregate', 'count', 'distinct', 'find'], true);
Try / catch
try {
$result = $tool->handle($request);
} catch (\InvalidArgumentException $e) {
// retry with an allowed read command: aggregate/count/distinct/find
} Prevention
- Restrict generated commands to aggregate, count, distinct, find.
- Map shell helpers before sending: findOne -> find + limit:1.
- Never send write/admin commands to a tool marked read-only.
When it happens
Trigger: Passing a command whose first key is not in the allow list, e.g. {"insert": "users", ...}, {"update": ...}, {"dropDatabase": 1}, {"dbStats": 1}, or a mistyped operation like {"findOne": "users"} (findOne is not a command).
Common situations: Developer tries to mutate data through the AI tool; copy-pastes a shell helper (findOne, insertMany) as a command name; uses an aggregation-stage name ($match) at the top level instead of inside an aggregate pipeline; expects admin/diagnostic commands to be supported.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Please pass a valid MongoDB command
- Only read aggregation stages are allowed. Found
- Aggregation nesting exceeds the maximum of
- A pipeline exceeds the maximum of
- Database is not properly configured.
AI-assisted analysis of mongodb/laravel-mongodb@0634653039 (2026-09-15).
Data as JSON: /api/errors/18e23de7c145d9ed.
Report an issue: GitHub.
Appendix: source
Thrown at src/Tools/DatabaseQuery.php:114
/**
* @param array<string, mixed> $command
*
* @return array<int, mixed>
*
* @throws InvalidArgumentException
*/
private function handleMql(array $command, Connection $connection): array
{
if ($command === []) {
throw new InvalidArgumentException('Please pass a valid MongoDB command');
}
// Allowed CRUD commands (https://www.mongodb.com/docs/manual/reference/mql/crud-commands/)
$allowList = ['aggregate', 'count', 'distinct', 'find'];
$operation = array_key_first($command);
if (! in_array($operation, $allowList, true)) {
throw new InvalidArgumentException(sprintf('Only read commands are allowed (%s).', implode(', ', $allowList)));
}
if ($operation === 'aggregate') {
// Check nested write ops recursively with conservative allow list
$this->ensureNoNestedWriteInAggregation($command['pipeline'] ?? []);
}
return $connection->getDatabase()->command($command)->toArray();
}
/**
* @param array<int, array<string, mixed>> $pipeline
*
* @throws InvalidArgumentException
*/
private function ensureNoNestedWriteInAggregation(array $pipeline, int $level = 1): void
{
if ($level > self::MAX_NESTING_LEVEL) {View on GitHub (pinned to 0634653039)