Skip to main content
Glama
TeoDreams

SQL Anywhere MCP Server

by TeoDreams
README.md
# SQL Anywhere MCP Server (read-only)

![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)
![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue.svg)

An MCP server that lets **Claude** read a **SAP SQL Anywhere** database and answer
questions in plain language — no SQL by hand.

Example: ask *"how many customers do we have in Lombardy?"* and Claude writes and
runs the query for you.

> **Read-only by design.** It can only run `SELECT` queries — it cannot insert,
> update, delete, or alter anything. See [Security](#security).

## Requirements

- Python 3.10+
- The SQL Anywhere ODBC driver and a configured **DSN** for your database
- A dedicated, **read-only database user** (this project never creates users or changes permissions)
- Claude Code or Claude Desktop

## Install

```bash
git clone https://github.com/TeoDreams/sqlanywhere-mcp-server.git
cd sqlanywhere-mcp-server
pip install -r requirements.txt
```

## Configure

Set four values via a `.env` file or environment variables.

**`.env` file** (copy the template, then edit — it is gitignored):

```bash
cp .env.example .env
```

```ini
SQLANY_DSN=YOUR_DSN
SQLANY_UID=readonly_user
SQLANY_PWD=your_password
SQLANY_MAX_ROWS=200
SQLANY_QUERY_TIMEOUT=30
```

**Or environment variables:**

```powershell
# Windows (PowerShell)
[Environment]::SetEnvironmentVariable("SQLANY_DSN", "YOUR_DSN", "User")
[Environment]::SetEnvironmentVariable("SQLANY_UID", "readonly_user", "User")
[Environment]::SetEnvironmentVariable("SQLANY_PWD", "your_password", "User")
```

```bash
# Linux / macOS
export SQLANY_DSN=YOUR_DSN
export SQLANY_UID=readonly_user
export SQLANY_PWD=your_password
```

## Add to Claude

**Claude Code** — pick a scope:

```bash
# Global: available in all your projects
claude mcp add --scope user sqlanywhere-readonly -- python /path/to/sqlanywhere_mcp_server.py

# Project: only this repo, shared with the team via a committed .mcp.json
claude mcp add --scope project sqlanywhere-readonly -- python /path/to/sqlanywhere_mcp_server.py

claude mcp list
```

The default scope (`local`, if you omit `--scope`) registers the server only for you in the
current project. Credentials stay out of a committed `.mcp.json` because they come from your
environment variables / `.env`, not the command.

**Claude Desktop** — add this to the config file (`%APPDATA%\Claude\claude_desktop_config.json`
on Windows, `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS,
`~/.config/Claude/claude_desktop_config.json` on Linux), then restart the app:

```json
{
  "mcpServers": {
    "sqlanywhere-readonly": {
      "command": "python",
      "args": ["/path/to/sqlanywhere_mcp_server.py"]
    }
  }
}
```

## Usage

Ask Claude things like:

- *"Describe the structure of the customers table"*
- *"Show me the first rows of the products table"*
- *"How many orders were placed this month?"*
- *"Will this query use an index, or scan the whole table?"*

Claude uses the two tools on its own:

| Tool | What it does |
|---|---|
| `list_tables` | Lists table names (handy for large schemas) |
| `describe_schema` | Lists tables and columns (structure only, no data) |
| `run_query` | Runs a `SELECT` and returns the rows (up to the row cap) |
| `explain_query` | Shows the execution plan of a `SELECT` **without running it** |

## Security

- Only queries starting with `SELECT` run; write/DDL keywords (`INSERT`, `UPDATE`,
  `DELETE`, `DROP`, `ALTER`, etc.) are rejected before connecting.
- Returned rows are capped (default 200). This caps the **response**, not the query — the
  `SELECT` still runs in full on the server. A per-query timeout (`SQLANY_QUERY_TIMEOUT`,
  default 30s, `0` = no limit) aborts queries that run too long.
- Credentials come only from environment variables or `.env`, and are never printed in
  error messages.

## Query plans

`explain_query` uses SQL Anywhere's `EXPLANATION()` function, which returns the optimizer's
access plan **without executing the query**. (`GRAPHICAL_PLAN()` is deliberately not used —
it can execute the statement to collect statistics.)

SQL Anywhere gates plan access behind a database option that is **off by default**. If
`explain_query` reports that plans are unavailable, a DBA must enable it for your read-only user:

```sql
SET OPTION readonly_user.QUERY_PLAN_TEXT_ACCESS = ON;
```

## Troubleshooting

> If `describe_schema` returns nothing, your SQL Anywhere version may expose the schema
> through `SYS.SYSTAB` / `SYS.SYSTABCOL` instead of the `SYS.SYSTABLE` / `SYS.SYSCOLUMN`
> views used here — adjust the query in `describe_schema` accordingly.

## License

[MIT](LICENSE)