{"record":{"id":"f6245d507f2ea559","repo":"mongodb/laravel-mongodb","slug":"only-read-aggregation-stages-are-allowed-found-s","errorCode":null,"errorMessage":"Only read aggregation stages are allowed. Found: %s","messagePattern":"Only read aggregation stages are allowed\\. Found: (.+?)","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Tools/DatabaseQuery.php","lineNumber":195,"sourceCode":"            'set',\n            'setWindowFields',\n            'shardedDataDistribution',\n            'skip',\n            'sort',\n            'sortByCount',\n            'unwind',\n            'unset',\n            'vectorSearch',\n        ]);\n\n        // A pipeline is a list of single-key stage documents, e.g. [['$match' => [...]], ...].\n        foreach ($pipeline as $stage) {\n            $operator = array_key_first($stage);\n            $stageName = str_replace('$', '', (string) $operator);\n            $stageBody = $stage[$operator];\n\n            if (! in_array($stageName, $allowList, true)) {\n                throw new InvalidArgumentException(sprintf('Only read aggregation stages are allowed. Found: %s', $stageName));\n            }\n\n            if (! in_array($stageName, $supportsNestedWrites, true)) {\n                continue;\n            }\n\n            switch ($stageName) {\n                case 'facet':\n                    array_walk($stageBody, fn ($pipeline) => $this->ensureNoNestedWriteInAggregation($pipeline, $level + 1));\n\n                    break;\n\n                case 'lookup':\n                case 'unionWith':\n                    // Both stages take an optional single sub-pipeline; unionWith may also be a plain collection name.\n                    if (is_array($stageBody) && isset($stageBody['pipeline'])) {\n                        $this->ensureNoNestedWriteInAggregation($stageBody['pipeline'], $level + 1);\n                    }","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/mongodb/laravel-mongodb/blob/0634653039468ceb0268a69192bdac64469ed043/src/Tools/DatabaseQuery.php#L177-L213","documentation":"A stage in the aggregate pipeline (top-level or nested inside $facet/$lookup/$unionWith) is not on the library's allow list of read-only aggregation stages. The allow list deliberately excludes write stages ($merge, $out) and rejects any unknown/future stage by default, so the tool stays strictly read-only.","triggerScenarios":"Including {\"$merge\": ...} or {\"$out\": ...} in the pipeline; a misspelled stage (e.g. \"$math\" instead of \"$match\", or \"$sortByCount\" with wrong casing); using an exotic/unsupported stage like \"$indexStats\" is fine but \"$planCacheStats\" variants or server-specific stages not on the list will fail; nesting a write stage inside a $facet/$lookup/$unionWith sub-pipeline.","commonSituations":"Trying to persist aggregation results with $out/$merge through the AI tool; typos in stage names; the first element of a stage object not being the stage operator (malformed stage document); copying stages from newer MongoDB versions that the allow list doesn't know yet.","solutions":["Remove $merge/$out stages — this tool is read-only; persist results via application code instead.","Fix the stage name spelling and ensure each pipeline element is a single-key document like {\"$match\": {...}}.","Re-express disallowed stages with allowed equivalents (e.g. $sortByCount is allowed; use $group + $sort).","For stages genuinely missing from the allow list, run the aggregation in your own code with the MongoDB PHP library."],"exampleFix":"// before\n{\"aggregate\": \"users\", \"pipeline\": [{\"$match\": {\"active\": true}}, {\"$out\": \"active_users\"}]}\n// after\n{\"aggregate\": \"users\", \"pipeline\": [{\"$match\": {\"active\": true}}, {\"$project\": {\"name\": 1, \"email\": 1}}]}","handlingStrategy":"validation","validationCode":"$readStages = ['facet','lookup','unionWith','addFields','bucket','bucketAuto','changeStream','collStats','count','densify','documents','fill','geoNear','graphLookup','group','indexStats','limit','listLocalSessions','listSampledQueries','listSearchIndexes','listSessions','match','planCacheStats','project','redact','replaceRoot','replaceWith','sample','search','searchMeta','set','setWindowFields','shardedDataDistribution','skip','sort','sortByCount','unwind','unset','vectorSearch'];\nforeach ($pipeline as $stage) {\n    $name = ltrim((string) array_key_first($stage), '$');\n    if (!in_array($name, $readStages, true)) {\n        throw new \\InvalidArgumentException(\"Stage not allowed: $name\");\n    }\n}","typeGuard":"$isReadStage = fn (array $stage): bool => in_array(ltrim((string) array_key_first($stage), '$'), [\n    'facet','lookup','unionWith','addFields','match','group','project','sort','limit','skip','unwind','set','unset','count','sample','replaceRoot','replaceWith','bucket','bucketAuto','densify','fill','geoNear','graphLookup','redact','setWindowFields','sortByCount','documents','vectorSearch','search','searchMeta','changeStream','collStats','indexStats','planCacheStats','currentOp','listLocalSessions','listSampledQueries','listSearchIndexes','listSessions','shardedDataDistribution','changeStreamSplitLargeEvent'\n], true);","tryCatchPattern":"try {\n    $result = $tool->handle($request);\n} catch (\\InvalidArgumentException $e) {\n    if (str_starts_with($e->getMessage(), 'Only read aggregation stages are allowed')) {\n        // remove/replace the offending stage (e.g. $merge, $out) and retry\n    }\n}","preventionTips":["Never include $merge or $out in pipelines sent to this read-only tool.","Ensure each pipeline element is a single-key document with the stage operator as the key.","Double-check stage spelling ($match, not $math) before sending.","Validate nested sub-pipelines inside $facet/$lookup/$unionWith too — the same allow list applies to them."],"tags":["mongodb","aggregation","read-only","mcp-tool","invalid-argument"],"backgroundTag":"unsupported-operation","analyzedSha":"0634653039468ceb0268a69192bdac64469ed043","analyzedAt":"2026-09-15T02:56:37.067Z","contentChangedAt":"2026-09-15T02:56:37.067Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}