Skip to content

Add optional write access mode with DROP protection#93

Merged
joe-clickhouse merged 12 commits into
mainfrom
joe/24-optional-write-access
Feb 23, 2026
Merged

Add optional write access mode with DROP protection#93
joe-clickhouse merged 12 commits into
mainfrom
joe/24-optional-write-access

Conversation

@joe-clickhouse

Copy link
Copy Markdown
Collaborator

Summary

This PR enables LLMs to perform write operations on ClickHouse databases with built-in safety controls to prevent accidental data loss.

Write Access Mode

  • New CLICKHOUSE_ALLOW_WRITE_ACCESS flag (default: false) enables DDL and DML operations (INSERT, UPDATE, CREATE, ALTER, DROP)
  • When enabled, queries run with readonly=0 instead of the default readonly=1

Two-Tier DROP Protection

  • CLICKHOUSE_ALLOW_DROP (default: false) provides additional safety for destructive operations
  • Both flags must be true to allow DROP operations
  • Other write operations (INSERT, CREATE, etc.) only require CLICKHOUSE_ALLOW_WRITE_ACCESS=true

Usage

Enable write access:

"env": {
  "CLICKHOUSE_ALLOW_WRITE_ACCESS": "true"
}

Enable write access including DROP:

"env": {
  "CLICKHOUSE_ALLOW_WRITE_ACCESS": "true",
  "CLICKHOUSE_ALLOW_DROP": "true"
}

Closes #24

@joe-clickhouse joe-clickhouse linked an issue Oct 23, 2025 that may be closed by this pull request

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds optional write access capabilities to the ClickHouse MCP server with built-in safety mechanisms. By default, the server maintains read-only behavior, but can now be explicitly configured to allow write operations with DROP protection.

Key changes:

  • Renamed run_select_query to run_query to reflect broader SQL support
  • Added CLICKHOUSE_ALLOW_WRITE_ACCESS flag to enable DDL/DML operations
  • Added CLICKHOUSE_ALLOW_DROP flag for additional protection against destructive operations

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
mcp_clickhouse/mcp_env.py Added two new configuration properties (allow_write_access and allow_drop) to control write permissions
mcp_clickhouse/mcp_server.py Implemented DROP validation, refactored readonly setting logic, renamed query function, and added dynamic tool description
mcp_clickhouse/init.py Updated exports to reflect function rename
tests/test_tool.py Renamed function references and added comprehensive test coverage for write mode, DROP protection, and read-only mode
tests/test_mcp_server.py Updated function calls to use new run_query name
README.md Updated documentation to reflect new function name and write access features

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread mcp_clickhouse/mcp_server.py Outdated
Comment thread mcp_clickhouse/mcp_server.py

@laeg laeg left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@laeg laeg requested a review from Copilot December 8, 2025 18:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mcp_clickhouse/mcp_server.py Outdated
return

# Simple pattern matching for DROP operations
drop_pattern = r'\bDROP\s+(TABLE|DATABASE|VIEW|DICTIONARY)\b'

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern for DROP detection may not catch all variations. Consider ClickHouse-specific DROP operations like DROP TEMPORARY TABLE, DROP DETACHED TABLE, or DROP TABLE IF EXISTS. The pattern should include the optional TEMPORARY, DETACHED, and IF EXISTS keywords to ensure comprehensive DROP detection.

Suggested change
drop_pattern = r'\bDROP\s+(TABLE|DATABASE|VIEW|DICTIONARY)\b'
# Updated pattern to match DROP [TEMPORARY] [DETACHED] [IF EXISTS] (TABLE|DATABASE|VIEW|DICTIONARY)
drop_pattern = (
r'\bDROP\s+'
r'(TEMPORARY\s+)?'
r'(DETACHED\s+)?'
r'(IF\s+EXISTS\s+)?'
r'(TABLE|DATABASE|VIEW|DICTIONARY)\b'
)

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Will allow arbitrary words between DROP and the object type.

Comment thread mcp_clickhouse/mcp_server.py Outdated
Comment on lines +407 to +408
drop_pattern = r'\bDROP\s+(TABLE|DATABASE|VIEW|DICTIONARY)\b'
if re.search(drop_pattern, query, re.IGNORECASE):

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DROP detection can be bypassed using comments or string literals. For example, SELECT '/* DROP TABLE foo */' FROM bar or SELECT 'DROP TABLE' FROM foo would trigger false positives, while DR/**/OP TABLE foo could bypass detection. Consider using a proper SQL parser or at minimum stripping comments and string literals before pattern matching.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full SQL parsing is overkill. Won't do.

Comment thread tests/test_tool.py
def setUpClass(cls):
"""Set up the environment before tests."""
cls.env_patcher = patch.dict(os.environ, {"CLICKHOUSE_ALLOW_WRITE_ACCESS": "false"})
cls.env_patcher.start()

Copilot AI Dec 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment patcher is started in setUpClass but there's a risk it won't be stopped if setUpClass raises an exception before completing. Consider using addClassCleanup() instead of relying solely on tearDownClass() to ensure cleanup happens even on setup failures.

Suggested change
cls.env_patcher.start()
cls.env_patcher.start()
cls.addClassCleanup(cls.env_patcher.stop)

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Will fix.

@joe-clickhouse joe-clickhouse merged commit d82fc87 into main Feb 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optional write access?

3 participants