> ## Documentation Index
> Fetch the complete documentation index at: https://ngquct-feat-ai-sql-walkthroughs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Tools

> JSON-RPC tool catalog with input schemas, output schemas, scope requirements, and examples

# MCP Tools

The MCP server exposes tools and resources over JSON-RPC. The tools are grouped by category below. Every tool is scope-gated: a request must come with a token whose scope and connection allowlist permit the call.

## Transports

The same tool catalog is available over two transports:

* **HTTP**: MCP Streamable HTTP at `http://127.0.0.1:<port>/mcp` (port from the handshake file). POST for JSON-RPC requests, GET for the SSE stream that carries server-initiated notifications. Bearer token in `Authorization` header.
* **stdio**: bundled `tablepro-mcp` CLI bridges stdio JSON-RPC to localhost HTTP. No token needed because the bridge reuses the in-app handshake.

The server accepts `2025-03-26`, `2025-06-18`, and `2025-11-25`. On `initialize` it echoes whichever version the client requested. If the client asks for something else, the server returns `2025-11-25`. See [Versioning](/external-api/versioning).

## Scopes and access

Each tool entry below lists its minimum token scope. The effective permission is `MIN(token.scope, connection.externalAccess)`; see [Tokens](/external-api/tokens) for the full scope table.

Two per-connection settings gate every call on top of the token:

* **External access**. `blocked` hides the connection entirely: `list_connections` omits it, `list_recent_tabs` filters its tabs, and any tool that targets it returns `403 forbidden`. `readOnly` makes write tools return `403` even with a `readWrite` token.
* **AI policy `askEachTime`**. The first tool call per session that targets the connection shows an in-app approval dialog. TablePro waits up to 30 seconds. Deny returns `403 forbidden` with message "User denied MCP access to this connection"; no answer fails the call with a timeout error. An approval covers that connection for the rest of the session.

## Connection tools

### `list_connections`

List saved connections. Connections with `externalAccess: blocked` are silently omitted.

**Input**: none.

**Output**:

```json theme={null}
{
  "connections": [
    {
      "id": "9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1",
      "name": "Production",
      "type": "PostgreSQL",
      "host": "db.example.com",
      "port": 5432,
      "database": "app",
      "username": "app",
      "is_connected": false,
      "ai_policy": "askEachTime",
      "safe_mode": "silent"
    }
  ]
}
```

**Scope**: `readOnly`.

### `connect`

Open a database connection.

**Input**:

```json theme={null}
{ "connection_id": "9f1f0c3e-..." }
```

**Output**:

```json theme={null}
{
  "status": "connected",
  "current_database": "app",
  "current_schema": "public",
  "server_version": "PostgreSQL 16.2"
}
```

`current_schema` and `server_version` are present when known.

**Scope**: `readOnly`.

### `disconnect`

Close a connection.

**Input**: `{ "connection_id": "..." }`

**Output**: `{ "status": "disconnected" }` on success.

**Scope**: `readWrite`.

### `get_connection_status`

Return version, uptime, and active database for a connection.

**Input**: `{ "connection_id": "..." }`

**Output**:

```json theme={null}
{
  "status": "connected",
  "current_database": "app",
  "current_schema": "public",
  "server_version": "PostgreSQL 16.2",
  "connected_at": "2026-04-26T10:14:22Z",
  "last_active_at": "2026-04-26T10:14:22Z"
}
```

`status` is one of `connected`, `connecting`, `disconnected`, `error`. When `error`, an `error` object with a `message` field is included.

**Scope**: `readOnly`.

## Schema tools

### `list_databases`

**Input**: `{ "connection_id": "..." }`

**Output**: `{ "databases": ["app", "analytics"] }` (array of database names)

**Scope**: `readOnly`.

### `list_schemas`

**Input**: `{ "connection_id": "...", "database": "app" }` (database optional)

**Output**: `{ "schemas": ["public", "reporting"] }` (array of schema names)

**Scope**: `readOnly`.

### `list_tables`

**Input**:

```json theme={null}
{
  "connection_id": "...",
  "database": "app",
  "schema": "public",
  "include_row_counts": false
}
```

**Output**:

```json theme={null}
{
  "tables": [
    { "name": "users", "type": "table" },
    { "name": "orders_view", "type": "view" }
  ]
}
```

When `include_row_counts` is true and the driver supports it, each entry also includes `row_count`.

**Scope**: `readOnly`.

### `describe_table`

Columns, indexes, foreign keys, primary key, DDL.

**Input**:

```json theme={null}
{
  "connection_id": "...",
  "table": "users",
  "schema": "public"
}
```

`schema` is optional. The connection's current schema is used when omitted. To target a different database, call `switch_database` first.

**Output**:

```json theme={null}
{
  "columns": [
    {
      "name": "id",
      "data_type": "uuid",
      "is_nullable": false,
      "is_primary_key": true
    },
    {
      "name": "email",
      "data_type": "text",
      "is_nullable": false
    }
  ],
  "indexes": [
    {
      "name": "users_email_idx",
      "columns": ["email"],
      "is_unique": true,
      "is_primary": false,
      "type": "btree"
    }
  ],
  "foreign_keys": [],
  "ddl": "CREATE TABLE users (...)",
  "approximate_row_count": 12345
}
```

`default_value`, `extra`, and `comment` are present on a column when set. `ddl` and `approximate_row_count` are present when the driver supports them.

**Scope**: `readOnly`.

### `get_table_ddl`

Just the `CREATE TABLE` statement.

**Input**: same as `describe_table` (`connection_id`, `table`, `schema`).

**Output**: `{ "ddl": "CREATE TABLE ..." }`

**Scope**: `readOnly`.

## Query tools

### `execute_query`

Execute a SQL query. All queries are subject to the connection's safe mode policy. DROP, TRUNCATE, and ALTER...DROP must use `confirm_destructive_operation`.

**Input**:

```json theme={null}
{
  "connection_id": "...",
  "query": "SELECT id, email FROM users WHERE active = true LIMIT 100",
  "max_rows": 500,
  "timeout_seconds": 30,
  "database": "app",
  "schema": "public"
}
```

Defaults for `max_rows` and `timeout_seconds` come from **Settings > Integrations > Server Configuration** (default row limit, query timeout). `max_rows` is clamped to the configured maximum (default 10,000). `timeout_seconds` is clamped to 1-300. Single-statement queries only. Query size cap is 100 KB. `database` and `schema` are optional; when present, the tool calls `switch_database` and/or `switch_schema` before executing.

**Output**:

```json theme={null}
{
  "columns": ["id", "email"],
  "rows": [["9f1f...", "alice@example.com"]],
  "row_count": 1,
  "rows_affected": 0,
  "execution_time_ms": 14,
  "is_truncated": false
}
```

`columns` is an array of column-name strings. `rows` is an array of rows, where each row is an array of strings (or `null`) aligned to the `columns` order. `status_message` is added when the driver returns one.

**Scope**:

* `readOnly` for SELECT, SHOW, EXPLAIN.
* `readWrite` for INSERT, UPDATE, DELETE.
* DROP, TRUNCATE, ALTER...DROP are rejected. Use `confirm_destructive_operation`.

Safe Mode rules apply on top. A connection in Safe Mode `readOnly` returns `403` for any write SQL.

**Streaming progress**: pass `_meta.progressToken` in the request and the server sends `notifications/progress` events on the SSE channel as the query moves through "Connecting", "Executing", "Formatting result", and "Done". Clients that don't include a token get the final response only.

### `confirm_destructive_operation`

Run a DROP, TRUNCATE, or ALTER...DROP after a typed confirmation.

**Input**:

```json theme={null}
{
  "connection_id": "...",
  "query": "DROP TABLE legacy_events",
  "confirmation_phrase": "I understand this is irreversible"
}
```

The confirmation phrase is fixed: `I understand this is irreversible`. Anything else returns JSON-RPC `-32602` over HTTP 200 with message `Invalid params: confirmation_phrase must be exactly: I understand this is irreversible`.

**Output**: same shape as `execute_query`.

**Scope**: `readWrite` or `fullAccess` (both grant the `tools:write` MCP scope). The connection's external access must also permit writes; a `readOnly` connection rejects destructive operations even with a matching token.

### `export_data`

Export query or table data as CSV, JSON, or SQL.

**Input**:

```json theme={null}
{
  "connection_id": "...",
  "format": "csv",
  "tables": ["users", "orders"],
  "max_rows": 50000
}
```

`format` is one of `csv`, `json`, `sql`. `max_rows` defaults to 50,000, max 100,000. Provide either `tables` or `query`. Table names accept letters, digits, underscore, and `.` for schema-qualified names. Pass `output_path` to write to disk instead of returning data inline; the path must resolve inside the user's `~/Downloads` directory or the request is rejected with `400`.

**Output**: when `output_path` is set, returns `{ "path": "...", "rows_exported": N }`. Otherwise returns the export inline. A single export returns `{ "label": "...", "format": "csv", "row_count": N, "data": "..." }`. Multiple exports (multi-table requests) return `{ "exports": [ { "label": "...", "format": "csv", "row_count": N, "data": "..." }, ... ] }`.

**Scope**: `readOnly`.

### `switch_database` / `switch_schema`

**Input**: `{ "connection_id": "...", "database": "analytics" }` or `{ "connection_id": "...", "schema": "reporting" }`

**Output**: `{ "status": "switched", "current_database": "analytics" }` or `{ "status": "switched", "current_schema": "reporting" }`

**Scope**: `readWrite` (mutates session state).

## Navigation tools

These open or focus tabs and windows in the running TablePro app. They require `readOnly` scope and respect the connection allowlist; tabs from `externalAccess: blocked` connections are filtered out.

### `open_connection_window`

Open a connection in TablePro and bring its window to front. If the connection is already open, the existing window is focused.

**Input**: `{ "connection_id": "..." }`

**Output**:

```json theme={null}
{
  "status": "opened",
  "connection_id": "9f1f...",
  "window_id": "..."
}
```

**Scope**: `readOnly`.

### `open_table_tab`

Open a table tab.

**Input**:

```json theme={null}
{
  "connection_id": "...",
  "table_name": "users",
  "database_name": "app",
  "schema_name": "public"
}
```

`database_name` and `schema_name` are optional. If omitted, the connection's current database/schema is used.

**Output**:

```json theme={null}
{
  "status": "opened",
  "connection_id": "9f1f...",
  "table_name": "users",
  "window_id": "..."
}
```

**Scope**: `readOnly`.

### `focus_query_tab`

Bring an existing tab to front. The `tab_id` comes from `list_recent_tabs`.

**Input**: `{ "tab_id": "..." }`

**Output**:

```json theme={null}
{
  "status": "focused",
  "tab_id": "...",
  "window_id": "...",
  "connection_id": "9f1f..."
}
```

If the tab is no longer open, the call returns `-32602 invalid params` with detail `tab not found`.

**Scope**: `readOnly`.

### `list_recent_tabs`

Read the cross-window tab registry. Tabs from connections with `externalAccess: blocked` are filtered out.

**Input**: `{ "limit": 20 }` (optional, 1-500, default 20).

**Output**:

```json theme={null}
{
  "tabs": [
    {
      "tab_id": "...",
      "connection_id": "9f1f...",
      "connection_name": "Production",
      "tab_type": "query",
      "display_title": "users by signup date",
      "is_active": true,
      "table_name": "users",
      "database_name": "app",
      "schema_name": "public",
      "window_id": "..."
    }
  ]
}
```

`tab_type` is one of `query`, `table`, `createTable`, `erDiagram`, `serverDashboard`, `usersRoles`. `table_name`, `database_name`, `schema_name`, and `window_id` are present when known.

**Scope**: `readOnly`.

## History tools

### `search_query_history`

Full-text search over the query history database.

**Input**:

```json theme={null}
{
  "query": "users active",
  "connection_id": "9f1f...",
  "limit": 50,
  "since": 1745577262,
  "until": 1745663662
}
```

`connection_id` is optional. `limit` is 1-500, default 50. `since` and `until` are optional Unix epoch seconds; both bounds are inclusive. Either may be set on its own. Pass an empty `query` ("") to skip the full-text filter and only narrow by date or connection.

**Output**:

```json theme={null}
{
  "entries": [
    {
      "id": "...",
      "connection_id": "9f1f...",
      "database_name": "app",
      "query": "SELECT * FROM users WHERE active = true",
      "executed_at": 1745663662.0,
      "execution_time_ms": 18,
      "row_count": 142,
      "was_successful": true
    }
  ]
}
```

`executed_at` is a Unix timestamp in seconds. `error_message` is included when `was_successful` is false.

**Scope**: `readOnly`.

## Errors

Tool failures come back as JSON-RPC error envelopes. Codes follow the JSON-RPC spec plus TablePro's reserved range:

| JSON-RPC code | HTTP status | Meaning                                                                                                   |
| ------------- | ----------- | --------------------------------------------------------------------------------------------------------- |
| `-32700`      | 400         | Parse error (malformed JSON body)                                                                         |
| `-32600`      | 400         | Invalid request (bad envelope, missing `Mcp-Session-Id`)                                                  |
| `-32601`      | 200 / 404   | Method or resource URI not found                                                                          |
| `-32602`      | 200         | Invalid params (bad input, unknown tab id, unknown connection, wrong confirmation phrase)                 |
| `-32603`      | 500         | Internal error                                                                                            |
| `-32001`      | 404         | Session not found                                                                                         |
| `-32002`      | 200         | Request cancelled                                                                                         |
| `-32003`      | 200         | Request timeout (e.g. query timeout)                                                                      |
| `-32004`      | 404         | Resource not found                                                                                        |
| `-32005`      | 413         | Payload too large                                                                                         |
| `-32006`      | -           | Server disabled (reserved, not currently returned)                                                        |
| `-32007`      | 403 / 401   | Forbidden (token scope, allowlist, or `externalAccess` rejects); 401 when the token is unknown or revoked |
| `-32008`      | 401         | Token expired                                                                                             |
| `-32009`      | 401         | Unauthenticated (missing bearer token)                                                                    |
| `-32000`      | 429 / 503   | Server error (rate limited, service unavailable)                                                          |

Error responses include a `message`. Example:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 7,
  "error": {
    "code": -32007,
    "message": "Forbidden: Connection is read only for external clients"
  }
}
```

A `404` from `GET/POST/DELETE /mcp` with a stale `Mcp-Session-Id` returns the JSON-RPC envelope with `code: -32001, message: "Session not found"`. Per the [MCP spec](https://modelcontextprotocol.io), clients MUST treat that response as a signal to start a new `initialize` handshake before retrying.

`401` responses carry a `WWW-Authenticate` challenge. A missing token returns `Bearer realm="TablePro"`. An unknown or revoked token returns `Bearer error="invalid_token"`. An expired token returns `Bearer error="invalid_token", error_description="token expired"`.
