Skip to content

Mark dynamic tools as read-only / safe #48

@BorisTyshkevich

Description

@BorisTyshkevich

Altinity MCP automatically exposes ClickHouse views as dynamic MCP tools. These view-backed tools are read-only by design (SELECT-only analytics), but in ChatGPT and other Apps SDK-based hosts they are still treated as potentially risky “write” tools, which trigger repetitive confirmation dialogs before each call.

The OpenAI Apps SDK now defines standard tool descriptor parameters, including annotations such as readOnlyHint, destructiveHint, and openWorldHint, which ChatGPT uses to decide when confirmations are required:

 * readOnlyHint: boolean – signal that the tool is read-only so ChatGPT can skip “Are you sure?” prompts when possible.
 * destructiveHint: boolean – indicate that the tool may delete or overwrite data so clients can require explicit confirmation.
 * openWorldHint: boolean – indicate that the tool publishes content or reaches beyond the current user’s account.

Requested change

  1. For all dynamic tools that are produced from ClickHouse views and guaranteed to be read-only, add annotations to the tool descriptor:

    • annotations.readOnlyHint = true
    • annotations.destructiveHint = false
    • annotations.openWorldHint = false (assuming these tools only query the configured ClickHouse instance)
  2. For general-purpose SQL tools like execute_query, either:

    • Leave annotations unset, or

    • Explicitly mark them as potentially destructive:

      • annotations.readOnlyHint = false
      • annotations.destructiveHint = true

Example JSON for a dynamic tool named "search"

This is an example of what a single view-backed dynamic tool could look like on the wire, following the Apps SDK reference:

{
  "name": "search",
  "title" : "github search"  // used by ChatGPT instead of name or description in some contexts
  "description": "Search over ClickHouse view mydb.search_view (read-only).",
  "inputSchema": {
    "type": "object",
    "properties": {
      "q": {
        "type": "string",
        "description": "Free-text search query used inside the WHERE clause."
      },
    "required": ["q"],
    "additionalProperties": false
  },
  "annotations": {
    "readOnlyHint": true,
    "destructiveHint": false,
    "openWorldHint": false
  }
}

Example dynamic tool SQL definition (extended one, with JSON format):

CREATE OR REPLACE VIEW mcp.search AS
SELECT number, title FROM github_events
WHERE title ilike '% '|| {query: String} ||' %'
COMMENT '{
    title: "github search",
    description: "Returns the list of issues with title.",
    annotations: { readOnlyHint: true, destructiveHint: false }
    query: "query string",
  }';

There are 4 important tool properties:

  • name
  • title
  • description
  • annotations

If the user does not specify them, altinity-mcp provides reasonable defaults.

The same with parameters, if they are not explained in COMMENTS, Altinity-MCP generates a description automatically.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions