Skip to content

Latest commit

 

History

History
912 lines (676 loc) · 31.9 KB

MCP_GUIDE.md

File metadata and controls

912 lines (676 loc) · 31.9 KB

GoSQLX MCP Server Guide

Table of Contents

  1. Overview
  2. Installation
  3. Quick Start
  4. Starting the Server
  5. Configuration
  6. Authentication
  7. Tools Reference
  8. AI Assistant Integration
  9. Embedding as a Go Library
  10. Troubleshooting

Overview

The GoSQLX MCP server (gosqlx-mcp) exposes all GoSQLX SQL capabilities as Model Context Protocol 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

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

Claude Desktop

Add to your claude_desktop_config.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):

  • 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

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

Self-Hosting

To run your own instance, use Docker:

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

Or deploy to Render:

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

Installation

Install via go install (Recommended)

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

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

Run without Installing

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

Quick Start

Start the Server

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

Smoke Test with curl

# 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:

{"valid": true}

MCP Inspector

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

# 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 installed:

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:

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:

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:

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

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"}}}'
{
  "valid": true,
  "dialect": "postgresql"
}

Invalid SQL:

{
  "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

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}}}'
{
  "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

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)"}}}'
{
  "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

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"}}}'
{
  "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

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"}}}'
{
  "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

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"}}}'
{
  "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

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"}}}'
{
  "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 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 and CHATGPT_APP.md.

Claude Desktop

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

{
  "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):

{
  "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:

{
  "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:

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

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

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

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:

GOSQLX_MCP_PORT=9090 gosqlx-mcp

Or find and stop the conflicting process:

lsof -i :8080

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

The port value is not a valid integer:

# 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:

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

"command not found: gosqlx-mcp"

$GOPATH/bin is not in your PATH:

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

MCP Inspector Can't Connect

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

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.

{
  "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