# GoSQLX MCP Server Guide

Source: https://gosqlx.dev/docs/integrations/mcp/

## Table of Contents

1. [Overview](#overview)
2. [Installation](#installation)
3. [Quick Start](#quick-start)
4. [Starting the Server](#starting-the-server)
5. [Configuration](#configuration)
6. [Authentication](#authentication)
7. [Tools Reference](#tools-reference)
   - [validate\_sql](#validate_sql)
   - [format\_sql](#format_sql)
   - [parse\_sql](#parse_sql)
   - [extract\_metadata](#extract_metadata)
   - [security\_scan](#security_scan)
   - [lint\_sql](#lint_sql)
   - [analyze\_sql](#analyze_sql)
8. [AI Assistant Integration](#ai-assistant-integration)
9. [Embedding as a Go Library](#embedding-as-a-go-library)
10. [Troubleshooting](#troubleshooting)

---

## Overview

The GoSQLX MCP server (`gosqlx-mcp`) exposes all GoSQLX SQL capabilities as [Model Context Protocol](https://modelcontextprotocol.io) tools over streamable HTTP. This lets AI assistants like Claude and Cursor call SQL validation, formatting, parsing, linting, and security scanning directly during a conversation.

### Key Features

- **7 SQL Tools**: validate, format, parse, extract metadata, security scan, lint, and composite analyze
- **Streamable HTTP**: Compatible with any MCP client that supports the streamable HTTP transport
- **Optional Bearer Auth**: Protect the server with a token when exposing to a network
- **Multi-Dialect Validation**: generic, postgresql, mysql, mariadb, sqlserver, oracle, sqlite, snowflake, bigquery, redshift, clickhouse
- **Concurrent Analysis**: `analyze_sql` fans out all 6 tools via `sync.WaitGroup` - one round trip for a full SQL health report
- **Zero Business Logic Duplication**: Every tool delegates to the existing `pkg/gosqlx`, `pkg/linter`, and `pkg/sql/security` packages

---

## Remote Server (Public)

A public GoSQLX MCP server is available at `https://mcp.gosqlx.dev/mcp` - no installation, no API key, no signup required.

### Claude Code

```bash
claude mcp add --transport http gosqlx https://mcp.gosqlx.dev/mcp
```

### Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "gosqlx": {
      "url": "https://mcp.gosqlx.dev/mcp"
    }
  }
}
```

### Cursor

Add remote MCP server URL: `https://mcp.gosqlx.dev/mcp`

### Available Tools

All 7 GoSQLX SQL tools are available instantly:
- `validate_sql` - Check SQL syntax validity
- `format_sql` - Format SQL with configurable options
- `parse_sql` - Parse SQL into AST statement types
- `extract_metadata` - Extract tables, columns, and functions
- `security_scan` - Detect SQL injection patterns
- `lint_sql` - Lint against the ten style rules L001–L010 (not the L011–L030 safety, performance and naming rules)
- `analyze_sql` - Run all 6 tools in one concurrent request

### Rate Limits

The public server has smart rate limiting to ensure fair usage. These limits come from the rate-limit middleware that the `gosqlx-mcp` binary installs (`NewRateLimitMiddleware`); a server embedded with `pkg/mcp` through `Server.Start` or `Server.Handler` has none of them unless you add the middleware yourself (see [Embedding as a Go Library](#embedding-as-a-go-library)):
- **Burst**: 10 requests/second per IP
- **Sustained**: 120 weighted-requests/minute per IP
- Heavier tools (`analyze_sql`) consume more budget than simple ones (`validate_sql`)
- Limits tighten automatically under high server load
- **Streams**: at most 4 concurrent `GET /mcp` (SSE) streams per IP; each opened stream costs 1 unit of sustained budget. A `GET` refused because 4 streams are already open costs nothing
- **Malformed bodies**: a non-empty POST body that is not a JSON-RPC message or a non-empty batch costs 1 unit, so malformed requests are not free
- **Request body**: at most 64 KB, and it must arrive within 30 seconds

A denied request gets HTTP `429 Too Many Requests` with a `Retry-After` header (seconds) and a JSON-RPC error (code `-32000`) carrying the request's `id`. A denied `initialize`, alone or inside a JSON-RPC batch, is the exception: it returns HTTP 200 with the same JSON-RPC error, because mcp-go clients treat any 4xx on `initialize` as a legacy-SSE server. A `GET` refused by the stream cap gets HTTP 429 with `Retry-After: 60`. Every response to a request the limiter evaluates carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` (sustained budget left in the current minute) and `X-RateLimit-Reset` (Unix time the minute ends); stream-cap, body-size and body-timeout rejections, which happen before that check, do not. An oversized body gets HTTP `413` with a JSON-RPC error that names the limit; a body that stalls gets HTTP `408`.

The 64 KB limit is deliberate: `analyze_sql` allocates roughly 500× the SQL size (about 33 MB for 64 KB of SQL, 530 MB for 1 MB), so larger bodies would let a handful of requests exhaust a small instance.

### Health Check

```bash
curl https://mcp.gosqlx.dev/health
```

### Self-Hosting

To run your own instance, use Docker:

```bash
docker build -t gosqlx-mcp .
docker run -p 8080:8080 -e GOSQLX_MCP_HOST=0.0.0.0 gosqlx-mcp
```

Or deploy to Render:

```bash
# Connect repo on render.com, it auto-deploys
# Or use render.yaml blueprint
```

---

## Installation

### Install via go install (Recommended)

```bash
go install github.com/ajitpratap0/GoSQLX/cmd/gosqlx-mcp@latest
```

The binary is placed in `$GOPATH/bin`. Make sure that directory is in your `PATH`.

### Build from Source

```bash
git clone https://github.com/ajitpratap0/GoSQLX.git
cd GoSQLX
go build -o gosqlx-mcp ./cmd/gosqlx-mcp
```

### Run without Installing

```bash
go run github.com/ajitpratap0/GoSQLX/cmd/gosqlx-mcp@latest
```

---

## Quick Start

### Start the Server

```bash
gosqlx-mcp
# gosqlx-mcp: listening on 127.0.0.1:8080 (auth=false)
```

### Smoke Test with curl

```bash
# Validate SQL
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"validate_sql","arguments":{"sql":"SELECT 1"}}}'
```

Expected response:

```json
{"valid": true}
```

### MCP Inspector

```bash
npx @modelcontextprotocol/inspector http://127.0.0.1:8080/mcp
```

This opens an interactive browser UI to browse and call all 7 tools.

---

## Starting the Server

### Environment Variable Examples

```bash
# Local development (all defaults)
gosqlx-mcp

# Custom port
GOSQLX_MCP_PORT=9090 gosqlx-mcp

# Expose to network with bearer auth
GOSQLX_MCP_HOST=0.0.0.0 GOSQLX_MCP_PORT=8080 GOSQLX_MCP_AUTH_TOKEN=my-secret gosqlx-mcp
```

### Task Commands

If you have [Task](https://taskfile.dev) installed:

```bash
task mcp           # Run the MCP server
task mcp:build     # Build the gosqlx-mcp binary
task mcp:test      # Run MCP package tests
task mcp:install   # Install gosqlx-mcp to GOPATH/bin
```

### Graceful Shutdown

The server listens for context cancellation. When the process receives `SIGINT` or `SIGTERM`, it calls `http.Server.Shutdown` and drains in-flight requests before exiting.

---

## Configuration

The `gosqlx-mcp` server is configured exclusively via environment variables. No YAML file is read. All variables are optional - safe defaults are applied for local development.

| Variable | Default | Type | Validation |
|----------|---------|------|-----------|
| `GOSQLX_MCP_HOST` | `127.0.0.1` | string | Any valid bind address |
| `GOSQLX_MCP_PORT` | `8080` | integer | 1–65535; non-integer or out-of-range → startup error |
| `GOSQLX_MCP_AUTH_TOKEN` | *(empty)* | string | Empty = auth disabled; value is whitespace-trimmed |
| `GOSQLX_MCP_OPENAI_APPS_CHALLENGE` | *(empty)* | string | ChatGPT app domain-verification token served at `/.well-known/openai-apps-challenge` |
| `GOSQLX_MCP_TRUSTED_PROXY_HEADER` | *(empty)* | string | Header a trusted proxy sets to the client IP (e.g. `CF-Connecting-IP`); must be a valid header name and not `X-Forwarded-For` |
| `GOSQLX_MCP_TRUSTED_PROXY_HOPS` | `0` | integer | 0–16; number of trusted proxies that append to `X-Forwarded-For` |
| `GOSQLX_MCP_TRUSTED_PROXY_SECRET_HEADER` | *(empty)* | string | Optional. Header the proxy sets to the shared secret; set together with `GOSQLX_MCP_TRUSTED_PROXY_SECRET`. Must be a valid header name and not `X-Forwarded-For`, `Authorization` or the client-IP header. Requires the trusted proxy header or hops |
| `GOSQLX_MCP_TRUSTED_PROXY_SECRET` | *(empty)* | string | Optional. Shared secret, at least 16 characters; whitespace-trimmed. Never logged |

**Notes:**

- `GOSQLX_MCP_PORT` fails fast at startup with a descriptive error if the value is not a valid port number.
- `GOSQLX_MCP_AUTH_TOKEN` enables bearer token auth for all requests when set to a non-empty string.
- MCP server configuration is independent of `.gosqlx.yml` - the YAML config is not read by `gosqlx-mcp`.

### Client IP and reverse proxies

Rate limits are tracked per client IP (IPv6 clients per `/64`). By default the server uses the TCP peer address (`RemoteAddr`) and ignores `X-Forwarded-For`, `X-Real-IP` and similar headers, because any client can send them: trusting the left-most `X-Forwarded-For` entry would let a client pick a fresh rate-limit bucket on every request.

Behind a reverse proxy every request comes from the proxy, so all users would share one bucket. Opt in to a forwarded address:

- `GOSQLX_MCP_TRUSTED_PROXY_HEADER=CF-Connecting-IP`: use a single-IP header that your proxy overwrites on every request. Cloudflare sets `CF-Connecting-IP` to the visitor address. The hosted server runs on Render, whose edge is Cloudflare, and `render.yaml` sets this.
- `GOSQLX_MCP_TRUSTED_PROXY_HOPS=N`: use the entry `N` positions from the right of `X-Forwarded-For` (the address appended by the outermost of `N` trusted proxies). Entries further left are client-supplied and are ignored.

If the header is set, it takes precedence. A missing or unparseable value falls back to `RemoteAddr`. Only enable either option when every request reaches the server through that proxy; otherwise clients can set the header directly.

**Startup warning.** When neither option is set, `gosqlx-mcp` logs one warning at startup: behind a reverse proxy or load balancer every client then shares the proxy's address, so all of them share one rate-limit bucket and one 4-stream `GET` cap. The warning is expected for a server that clients reach directly; behind a proxy, set one of the options above.

#### Requiring a proxy secret (optional)

A trusted client-IP header is only as trustworthy as the path to the server. If the origin can also be reached without the proxy (for example through Render's `*.onrender.com` hostname rather than the Cloudflare zone), a client can send its own `CF-Connecting-IP` and pick its rate-limit key. To close that gap, have the proxy add a secret header and tell the server to trust forwarded addresses only on requests that carry it:

```bash
GOSQLX_MCP_TRUSTED_PROXY_HEADER=CF-Connecting-IP
GOSQLX_MCP_TRUSTED_PROXY_SECRET_HEADER=X-Origin-Secret
GOSQLX_MCP_TRUSTED_PROXY_SECRET=<random value, at least 16 characters>
```

With both secret variables set, `GOSQLX_MCP_TRUSTED_PROXY_HEADER` and `GOSQLX_MCP_TRUSTED_PROXY_HOPS` apply only when the last value of the secret header equals the secret (compared in constant time). Requests with a missing or wrong secret are not rejected; they are keyed by `RemoteAddr`, so traffic that bypasses the proxy shares one bucket instead of choosing its own. The secret header is removed before the request reaches the MCP handler, and the secret never appears in logs or configuration errors. With neither variable set, behaviour is unchanged.

To set the header with a Cloudflare Transform Rule:

1. Generate a secret, for example `openssl rand -hex 32`.
2. In the Cloudflare dashboard for the zone that serves the MCP hostname, open **Rules → Transform Rules → Modify Request Header** and create a rule.
3. Match the MCP hostname, for example *Hostname equals `mcp.example.com`*.
4. Add the action **Set static**, header name `X-Origin-Secret`, value the secret. *Set* overwrites any value a client sent, so clients cannot supply their own.
5. Deploy the rule, then set `GOSQLX_MCP_TRUSTED_PROXY_SECRET_HEADER` and `GOSQLX_MCP_TRUSTED_PROXY_SECRET` on the server as secret environment variables (on Render, in the dashboard rather than in `render.yaml`) and restart it.

Deploy the rule before the server variables: until both sides agree, requests fall back to the shared `RemoteAddr` bucket rather than failing. Rotate the secret the same way. The rule must cover every hostname that should get per-client limits.

### HTTP server limits

`gosqlx-mcp` sets `ReadHeaderTimeout` 10 s, `IdleTimeout` 120 s and `MaxHeaderBytes` 32 KB. It sets no global `ReadTimeout`/`WriteTimeout`, because those would cut long-lived `GET /mcp` SSE streams. POST bodies instead get a 30 s read deadline in the rate-limit middleware, so slow-body (slowloris) connections are closed with HTTP 408.

---

## Authentication

By default the server accepts all requests without authentication. To enable bearer token auth, set `GOSQLX_MCP_AUTH_TOKEN`:

```bash
GOSQLX_MCP_AUTH_TOKEN=supersecret gosqlx-mcp
# gosqlx-mcp: listening on 127.0.0.1:8080 (auth=true)
```

All requests must then include the `Authorization` header:

```bash
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer supersecret" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"validate_sql","arguments":{"sql":"SELECT 1"}}}'
```

Requests missing or carrying an incorrect token receive HTTP `401 Unauthorized`. The `Bearer` scheme is matched case-insensitively and surrounding whitespace is ignored. Tokens are compared in constant time (SHA-256 digests with `crypto/subtle`). The `BearerAuthMiddleware` wraps the streamable HTTP handler and is a no-op when auth is disabled.

---

## Tools Reference

All tools accept a required `sql` string parameter. Errors are reported at three levels:

- **Result data**: `validate_sql` reports invalid SQL as a normal result with `valid: false`; `analyze_sql` reports sub-tool failures under its `errors` key.
- **Tool errors** (`isError: true`, message in the text content): input problems the model can fix, such as a missing, empty or non-string `sql`, SQL that `parse_sql`, `extract_metadata` or `format_sql` cannot parse, or an optional argument of the wrong type or out of range (the message names the argument).
- **JSON-RPC errors**: unknown tools, malformed requests and genuine server faults.

Optional arguments must match their schema type. Absent or `null` means the default. For clients that stringify scalars, `"true"`/`"false"` and decimal-integer strings are also accepted.

---

### validate\_sql

**Description**: Validate SQL syntax. Optionally specify a dialect.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `sql` | string | yes | - | The SQL string to validate |
| `dialect` | string | no | *(generic)* | One of: `generic`, `postgresql`, `mysql`, `mariadb`, `sqlserver`, `oracle`, `sqlite`, `snowflake`, `bigquery`, `redshift`, `clickhouse` |

#### Response

| Field | Type | Description |
|-------|------|-------------|
| `valid` | bool | `true` if syntax is valid |
| `error` | string | *(present on failure)* Parse error message |
| `dialect` | string | *(present when dialect was specified)* Echo of the dialect used |

#### Example

```bash
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"validate_sql","arguments":{"sql":"SELECT id FROM users","dialect":"postgresql"}}}'
```

```json
{
  "valid": true,
  "dialect": "postgresql"
}
```

Invalid SQL:

```json
{
  "valid": false,
  "error": "unexpected token 'FORM' at position 7"
}
```

---

### format\_sql

**Description**: Format SQL with configurable indentation and keyword casing.

#### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `sql` | string | yes | - | The SQL string to format |
| `indent_size` | integer | no | `2` | Spaces per indent level (0–16) |
| `uppercase_keywords` | boolean | no | `false` | Uppercase SQL keywords |
| `add_semicolon` | boolean | no | `false` | Append a trailing semicolon |

#### Response

| Field | Type | Description |
|-------|------|-------------|
| `formatted_sql` | string | The formatted SQL output |
| `options` | object | Echo of the options used (`indent_size`, `uppercase_keywords`, `add_semicolon`) |

#### Example

```bash
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"format_sql","arguments":{"sql":"select id,name from users where active=true","uppercase_keywords":true,"indent_size":4}}}'
```

```json
{
  "formatted_sql": "SELECT\n    id,\n    name\nFROM users\nWHERE active = true",
  "options": {
    "indent_size": 4,
    "uppercase_keywords": true,
    "add_semicolon": false
  }
}
```

---

### parse\_sql

**Description**: Parse SQL and return an AST summary: statement count and types, optionally for a specific dialect.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `sql` | string | yes | The SQL string to parse |
| `dialect` | string | no | Defaults to `generic`; one of `generic`, `postgresql`, `mysql`, `mariadb`, `sqlserver`, `oracle`, `sqlite`, `snowflake`, `bigquery`, `redshift`, `clickhouse` |

#### Response

| Field | Type | Description |
|-------|------|-------------|
| `statement_count` | integer | Number of SQL statements parsed, excluding `GO` batch separators |
| `statement_types` | array of string | Go type names of each parsed statement (e.g. `*ast.SelectStatement`) |

SQL Server `GO` batch separators
(`*ast.BatchSeparator`) are left out of `statement_count` and `statement_types`.
`parse_sql` defaults to the generic dialect. Set `"dialect": "sqlserver"` to
parse T-SQL scripts containing `GO`; repeat counts on `GO` do not increase
`statement_count`.

#### Example

```bash
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"parse_sql","arguments":{"sql":"SELECT 1; INSERT INTO t VALUES (1)"}}}'
```

```json
{
  "statement_count": 2,
  "statement_types": [
    "*ast.SelectStatement",
    "*ast.InsertStatement"
  ]
}
```

---

### extract\_metadata

**Description**: Extract tables, columns, and functions referenced in SQL.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `sql` | string | yes | The SQL string to analyze |

#### Response

| Field | Type | Description |
|-------|------|-------------|
| `tables` | array of string | Table names referenced in the query |
| `columns` | array of string | Column names referenced in the query |
| `functions` | array of string | Function names called in the query |

#### Example

```bash
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"extract_metadata","arguments":{"sql":"SELECT u.id, COUNT(o.id) FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id"}}}'
```

```json
{
  "tables": ["users", "orders"],
  "columns": ["id", "id"],
  "functions": ["COUNT"]
}
```

Since v1.17.0 (issue #849), an unqualified name
in `ORDER BY` or `HAVING` that matches a SELECT-list alias is treated as the
alias, so `extract_metadata` and the metadata part of `analyze_sql` no longer
report SELECT aliases as columns. For `SELECT count(*) AS orders FROM t GROUP BY
customer_id ORDER BY orders`, `columns` holds only `customer_id`.

---

### security\_scan

**Description**: Scan SQL for injection patterns: tautologies, UNION attacks, stacked queries, comment bypasses, and more.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `sql` | string | yes | The SQL string to scan |

#### Response

| Field | Type | Description |
|-------|------|-------------|
| `is_clean` | bool | `true` if no findings detected |
| `has_critical` | bool | `true` if any CRITICAL severity finding |
| `has_high` | bool | `true` if any HIGH or CRITICAL finding |
| `total_count` | integer | Total number of findings |
| `critical_count` | integer | Number of CRITICAL findings |
| `high_count` | integer | Number of HIGH findings |
| `medium_count` | integer | Number of MEDIUM findings |
| `low_count` | integer | Number of LOW findings |
| `findings` | array of object | Each finding: `severity`, `pattern`, `description`, `risk`, `suggestion` |

#### Example

```bash
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"security_scan","arguments":{"sql":"SELECT * FROM users WHERE id = 1 OR 1=1"}}}'
```

```json
{
  "is_clean": false,
  "has_critical": true,
  "has_high": true,
  "total_count": 1,
  "critical_count": 1,
  "high_count": 0,
  "medium_count": 0,
  "low_count": 0,
  "findings": [
    {
      "severity": "CRITICAL",
      "pattern": "tautology",
      "description": "Tautology injection detected: always-true condition",
      "risk": "Authentication bypass or full table disclosure",
      "suggestion": "Use parameterized queries; never interpolate user input into SQL"
    }
  ]
}
```

---

### lint\_sql

**Description**: Lint SQL against the ten GoSQLX style rules L001–L010, the same set `gosqlx lint` runs, with L005's maximum line length fixed at 100 characters. The safety, performance and naming rules L011–L030, which `gosqlx.Lint` also runs, are not applied.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `sql` | string | yes | The SQL string to lint |

#### Response

| Field | Type | Description |
|-------|------|-------------|
| `violation_count` | integer | Number of violations found |
| `violations` | array of object | Each violation: `rule`, `rule_name`, `severity`, `message`, `line`, `column`, `suggestion` |

#### Lint Rules

| Rule | Name | Category |
|------|------|----------|
| L001 | TrailingWhitespace | whitespace |
| L002 | MixedIndentation | whitespace |
| L003 | ConsecutiveBlankLines | whitespace |
| L004 | IndentationDepth | whitespace |
| L005 | LongLines | whitespace |
| L006 | ColumnAlignment | style |
| L007 | KeywordCase | keywords |
| L008 | CommaPlacement | style |
| L009 | AliasingConsistency | style |
| L010 | RedundantWhitespace | whitespace |

#### Example

```bash
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"lint_sql","arguments":{"sql":"select id,name from users"}}}'
```

```json
{
  "violation_count": 1,
  "violations": [
    {
      "rule": "L007",
      "rule_name": "KeywordCase",
      "severity": "warning",
      "message": "Keyword 'select' should be uppercase",
      "line": 1,
      "column": 1,
      "suggestion": "Use 'SELECT' instead of 'select'"
    }
  ]
}
```

---

### analyze\_sql

**Description**: Run all 6 analysis tools concurrently and return a composite report. Results are keyed by tool name (`validate`, `parse`, `metadata`, `security`, `lint`, `format`). Partial failures appear under an `errors` key.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `sql` | string | yes | The SQL string to analyze |

#### Response

| Key | Type | Description |
|-----|------|-------------|
| `validate` | object | Output of `validate_sql` |
| `parse` | object | Output of `parse_sql` |
| `metadata` | object | Output of `extract_metadata` |
| `security` | object | Output of `security_scan` |
| `lint` | object | Output of `lint_sql` |
| `format` | object | Output of `format_sql` (indent_size=2, defaults) |
| `errors` | object | *(present only on partial failure)* Map of tool name → error message |

#### Example

```bash
curl -s -X POST http://127.0.0.1:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"analyze_sql","arguments":{"sql":"SELECT id FROM users"}}}'
```

```json
{
  "validate": {
    "valid": true
  },
  "parse": {
    "statement_count": 1,
    "statement_types": ["*ast.SelectStatement"]
  },
  "metadata": {
    "tables": ["users"],
    "columns": ["id"],
    "functions": []
  },
  "security": {
    "is_clean": true,
    "has_critical": false,
    "has_high": false,
    "total_count": 0,
    "critical_count": 0,
    "high_count": 0,
    "medium_count": 0,
    "low_count": 0,
    "findings": []
  },
  "lint": {
    "violation_count": 0,
    "violations": []
  },
  "format": {
    "formatted_sql": "SELECT id\nFROM users",
    "options": {
      "indent_size": 2,
      "uppercase_keywords": false,
      "add_semicolon": false
    }
  }
}
```

---

## AI Assistant Integration

### Claude Code plugin

The repository doubles as a Claude Code plugin marketplace. The `gosqlx` plugin
connects to the hosted server and adds SQL skills, `/gosqlx:analyze`,
`/gosqlx:format`, `/gosqlx:lint` and `/gosqlx:security` commands, a read-only
`sql-reviewer` agent and an optional hook that validates edited `.sql` files:

```
/plugin marketplace add ajitpratap0/GoSQLX
/plugin install gosqlx@gosqlx
```

See [`plugins/gosqlx/README.md`](https://github.com/ajitpratap0/GoSQLX/blob/main/plugins/gosqlx/README.md) for local mode and privacy notes.

### ChatGPT

`gosqlx-mcp` is also a ChatGPT app (OpenAI Apps SDK): `analyze_sql` renders an
interactive report widget, and every tool returns `structuredContent`. Add
`https://mcp.gosqlx.dev/mcp` as a connector in ChatGPT developer mode (no auth).
See [`integrations/chatgpt/README.md`](https://github.com/ajitpratap0/GoSQLX/blob/main/integrations/chatgpt/README.md) and
[CHATGPT_APP.md](/docs/integrations/chatgpt/).

### Claude Desktop

Add `gosqlx-mcp` to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "gosqlx": {
      "command": "gosqlx-mcp",
      "args": ["--stdio"]
    }
  }
}
```

After restarting Claude Desktop, the 7 GoSQLX tools appear in the tool panel. Claude can now validate, lint, and analyze SQL on your behalf in any conversation.

### Cursor

Add the MCP server to your Cursor configuration (`.cursor/mcp.json` in your project root, or the global `~/.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "gosqlx": {
      "url": "http://127.0.0.1:8080/mcp"
    }
  }
}
```

Start `gosqlx-mcp` before opening Cursor (or add it to a startup script). Cursor will connect to the running server and expose the tools in its Agent mode.

### With Authentication

When running with `GOSQLX_MCP_AUTH_TOKEN`:

```json
{
  "mcpServers": {
    "gosqlx": {
      "url": "http://127.0.0.1:8080/mcp",
      "headers": {
        "Authorization": "Bearer your-token-here"
      }
    }
  }
}
```

---

## Embedding as a Go Library

Import `pkg/mcp` directly to embed the MCP server in your own application:

```go
import "github.com/ajitpratap0/GoSQLX/pkg/mcp"

func main() {
    cfg, err := mcp.LoadConfig()
    if err != nil {
        log.Fatal(err)
    }
    srv := mcp.New(cfg)
    if err := srv.Start(context.Background()); err != nil {
        log.Fatal(err)
    }
}
```

`Start` and `Handler` apply bearer auth only. They do **not** apply the rate limiting described under [Rate Limits](#rate-limits): there is no per-client rate limit, no `GET` stream cap, no request body size cap and no body read deadline. That is fine on `127.0.0.1`, but before exposing an embedded server to untrusted clients, serve `Handler` wrapped in `NewRateLimitMiddleware`, as `gosqlx-mcp` does:

```go
cfg, err := mcp.LoadConfig()
if err != nil {
    log.Fatal(err)
}
srv := mcp.New(cfg)
defer srv.Shutdown(context.Background())

httpSrv := &http.Server{
    Addr:              cfg.Addr(),
    Handler:           mcp.NewRateLimitMiddleware(cfg, srv.Handler()),
    ReadHeaderTimeout: 10 * time.Second,
}
log.Fatal(httpSrv.ListenAndServe())
```

`NewRateLimitMiddleware` honours the trusted-proxy settings in `cfg` (see [Client IP and reverse proxies](#client-ip-and-reverse-proxies)). `Shutdown` stops the idle-session sweeper that `Handler` starts; it does not close an `http.Server` you built yourself.

### Public API

| Symbol | Signature | Description |
|--------|-----------|-------------|
| `Config` | struct | Server configuration |
| `LoadConfig` | `() (*Config, error)` | Load from env vars |
| `DefaultConfig` | `() *Config` | Defaults: `127.0.0.1:8080`, auth disabled |
| `New` | `(cfg *Config) *Server` | Create server with all 7 tools registered |
| `(*Server).Start` | `(ctx context.Context) error` | Bind, serve, block until ctx cancelled. Applies bearer auth only, not `RateLimitMiddleware` |
| `(*Server).Handler` | `() http.Handler` | MCP transport behind bearer auth, without `RateLimitMiddleware`; wrap it with `NewRateLimitMiddleware` before exposing it to untrusted clients |
| `BearerAuthMiddleware` | `(cfg *Config, next http.Handler) http.Handler` | Auth wrapper; no-op when auth is disabled |
| `NewRateLimitMiddleware` | `(cfg *Config, next http.Handler) http.Handler` | Rate limiter keyed by client IP, honouring `cfg`'s trusted-proxy settings |
| `RateLimitMiddleware` | `(next http.Handler) http.Handler` | `NewRateLimitMiddleware` keyed on `RemoteAddr` only |
| `(*Config).Addr` | `() string` | Returns `"host:port"` |
| `(*Config).AuthEnabled` | `() bool` | Reports whether auth token is set |

### Config Struct

```go
type Config struct {
    Host      string // GOSQLX_MCP_HOST (default "127.0.0.1")
    Port      int    // GOSQLX_MCP_PORT (default 8080, range 1–65535)
    AuthToken string // GOSQLX_MCP_AUTH_TOKEN (default "" = auth disabled)

    OpenAIAppsChallenge string // GOSQLX_MCP_OPENAI_APPS_CHALLENGE (default "" = not served)
    TrustedProxyHeader  string // GOSQLX_MCP_TRUSTED_PROXY_HEADER (default "" = not used)
    TrustedProxyHops    int    // GOSQLX_MCP_TRUSTED_PROXY_HOPS (default 0 = X-Forwarded-For ignored)

    // Optional: trust the two fields above only when this header carries the secret.
    TrustedProxySecretHeader string // GOSQLX_MCP_TRUSTED_PROXY_SECRET_HEADER (default "" = not required)
    TrustedProxySecret       string // GOSQLX_MCP_TRUSTED_PROXY_SECRET
}
```

### Custom Context with Cancellation

```go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Cancel on SIGINT
go func() {
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    <-c
    cancel()
}()

cfg := mcp.DefaultConfig()
srv := mcp.New(cfg)
log.Fatal(srv.Start(ctx))
```

---

## Troubleshooting

### Server Won't Start - "address already in use"

Another process is using port 8080. Change the port:

```bash
GOSQLX_MCP_PORT=9090 gosqlx-mcp
```

Or find and stop the conflicting process:

```bash
lsof -i :8080
```

### Server Won't Start - "GOSQLX_MCP_PORT: expected integer"

The port value is not a valid integer:

```bash
# Wrong
GOSQLX_MCP_PORT=abc gosqlx-mcp

# Correct
GOSQLX_MCP_PORT=8080 gosqlx-mcp
```

### HTTP 401 on All Requests

Authentication is enabled but the token is missing or wrong. Check `GOSQLX_MCP_AUTH_TOKEN` and include the header:

```bash
curl ... -H "Authorization: Bearer your-token"
```

### "command not found: gosqlx-mcp"

`$GOPATH/bin` is not in your `PATH`:

```bash
export PATH="$PATH:$(go env GOPATH)/bin"
```

### MCP Inspector Can't Connect

Verify the server is running and listening on the correct address:

```bash
curl -s http://127.0.0.1:8080/mcp
# Should return an MCP protocol response, not "connection refused"
```

### analyze\_sql Returns Partial Results with "errors" Key

One or more sub-tools failed. The `errors` map identifies which tools failed and why. Successful results are always returned alongside errors.

```json
{
  "validate": {"valid": false, "error": "syntax error"},
  "errors": {
    "parse": "parse failed: unexpected token at position 0",
    "metadata": "parse failed: unexpected token at position 0"
  }
}
```

The format, security scan, and lint tools operate on the raw SQL string independently and may still succeed.

---

## Resources

- **Repository**: https://github.com/ajitpratap0/GoSQLX
- **Issues**: https://github.com/ajitpratap0/GoSQLX/issues
- **MCP Specification**: https://modelcontextprotocol.io/specification
- **mark3labs/mcp-go**: https://github.com/mark3labs/mcp-go
