Skip to content

Conversation

@joe-clickhouse
Copy link
Contributor

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
Copy link
Contributor

Copilot AI left a comment

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.

Copy link

@laeg laeg left a comment

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
Copy link
Contributor

Copilot AI left a comment

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.

return

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

Copilot AI Dec 8, 2025

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.
Comment on lines +407 to +408
drop_pattern = r'\bDROP\s+(TABLE|DATABASE|VIEW|DICTIONARY)\b'
if re.search(drop_pattern, query, re.IGNORECASE):
Copy link

Copilot AI Dec 8, 2025

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.
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()
Copy link

Copilot AI Dec 8, 2025

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.
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