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

  1. Use one of the allowed commands: aggregate, count, distinct, or find, with the collection as the value of the first key.
  2. For writes, execute them in your own application code (e.g. MongoDB\Laravel facade or the MongoDB library) instead of the read-only tool.
  3. Replace shell helpers: findOne -> find with limit 1; insertMany/updateMany -> application code.
  4. 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

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


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)