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

  1. Remove write clauses from the query, or split reads and writes across tools with the correct readOnly settings.
  2. If writes are intended, reconfigure/redeploy the tool with readOnly disabled.
  3. Rephrase the query so read-only keywords appear only as literals if the classifier misfires.
  4. 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

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


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/9480f5a194bec38c. Report an issue: GitHub.