googleapis/mcp-toolbox · error
this tool is read-only and cannot execute write queries
Error message
this tool is read-only and cannot execute write queries
What it means
RunCypher classifies the incoming Cypher statement with a query classifier and rejects it when the tool is configured as read-only (readOnly) but the statement is detected as a write (CREATE, MERGE, DELETE, etc.). This is a deliberate safety guard to prevent mutating a database through a read-only tool. The error is returned to the caller before any query is executed.
Source
Thrown at internal/sources/arcadedb/arcadedb.go:133
return s.Config
}
func (s *Source) ArcadeDBDriver() neo4j.Driver {
return s.Driver
}
func (s *Source) ArcadeDBDatabase() string {
return s.Database
}
func (s *Source) RunCypher(ctx context.Context, cypherStr string, params map[string]any, readOnly, dryRun bool) (any, error) {
cf := sourceClassifier.Classify(cypherStr)
if cf.Error != nil {
return nil, cf.Error
}
if cf.Type == classifier.WriteQuery && readOnly {
return nil, fmt.Errorf("this tool is read-only and cannot execute write queries")
}
if dryRun {
cypherStr = "EXPLAIN " + cypherStr
}
config := neo4j.ExecuteQueryWithDatabase(s.ArcadeDBDatabase())
results, err := neo4j.ExecuteQuery[*neo4j.EagerResult](ctx, s.ArcadeDBDriver(), cypherStr, params,
neo4j.EagerResultTransformer, config)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %w", err)
}
if dryRun {
summary := results.Summary
plan := summary.Plan()
if plan == nil {
return nil, fmt.Errorf("dry-run produced no execution plan")View on GitHub (pinned to 8cc6e09de2)
Solutions
- Remove write clauses from the query, or split reads and writes across tools with the correct readOnly settings.
- If writes are intended, reconfigure/redeploy the tool with readOnly disabled.
- Rephrase the query so read-only keywords appear only as literals if the classifier misfires.
- Verify cf.Type via the classifier output if you believe the classification is wrong.
Example fix
// before: write against a read-only tool
CREATE (n:Person {name: 'Ada'}) RETURN n
// after: read-only compatible query
MATCH (n:Person {name: 'Ada'}) RETURN n Defensive patterns
Strategy: validation
Validate before calling
var writeRe = regexp.MustCompile(`(?i)\\b(create|merge|delete|detach\\s+delete|set|remove)\\b`)
if readOnly && writeRe.MatchString(cypher) {
return errors.New("query contains write clauses; read-only tool")
} Try / catch
if err := runCypher(ctx, q); err != nil {
if strings.Contains(err.Error(), "read-only") {
// surface a user-facing 'this tool cannot modify data' message
return ErrReadOnlyViolation
}
return err
} Prevention
- Split read and write operations into separate tools with correct readOnly flags.
- Sanitize/validate LLM-generated Cypher before passing it to read-only tools.
- Keep write keywords out of string literals to avoid classifier false positives.
- Document readOnly behavior in the tool description so agents avoid write attempts.
When it happens
Trigger: Invoking a read-only-configured ArcadeDB tool with a Cypher write statement such as 'CREATE (n:Person)', 'MATCH (n) DELETE n', 'MERGE', 'SET', or 'DETACH DELETE' — anything the classifier labels WriteQuery.
Common situations: Users pointing an LLM agent at a read-only tool but asking it to insert/update data; classifier false positives on statements containing write keywords inside strings; misconfigured tool intended to be writable but declared read-only.
Related errors
- unable to execute query: %w
- failed to parse and verify JWT token: %w
- invalid JWT token
- parameter %q is secure and must not be passed in standard ar
- parameter %q is not secure and must not be passed in secureA
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/9480f5a194bec38c.
Report an issue: GitHub.