GoSQLX MCP Server Guide
Table of Contents
- Overview
- Installation
- Quick Start
- Starting the Server
- Configuration
- Authentication
- Tools Reference
- AI Assistant Integration
- Embedding as a Go Library
- 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_sqlfans out all 6 tools viasync.WaitGroup- one round trip for a full SQL health report - Zero Business Logic Duplication: Every tool delegates to the existing
pkg/gosqlx,pkg/linter, andpkg/sql/securitypackages
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/mcpClaude 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 validityformat_sql- Format SQL with configurable optionsparse_sql- Parse SQL into AST statement typesextract_metadata- Extract tables, columns, and functionssecurity_scan- Detect SQL injection patternslint_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. AGETrefused 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/healthSelf-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-mcpOr deploy to Render:
# Connect repo on render.com, it auto-deploys
# Or use render.yaml blueprintInstallation
Install via go install (Recommended)
go install github.com/ajitpratap0/GoSQLX/cmd/gosqlx-mcp@latestThe 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-mcpRun without Installing
go run github.com/ajitpratap0/GoSQLX/cmd/gosqlx-mcp@latestQuick 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/mcpThis 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-mcpTask 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/binGraceful 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_PORTfails fast at startup with a descriptive error if the value is not a valid port number.GOSQLX_MCP_AUTH_TOKENenables 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 bygosqlx-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 setsCF-Connecting-IPto the visitor address. The hosted server runs on Render, whose edge is Cloudflare, andrender.yamlsets this.GOSQLX_MCP_TRUSTED_PROXY_HOPS=N: use the entryNpositions from the right ofX-Forwarded-For(the address appended by the outermost ofNtrusted 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:
- Generate a secret, for example
openssl rand -hex 32. - In the Cloudflare dashboard for the zone that serves the MCP hostname, open Rules → Transform Rules → Modify Request Header and create a rule.
- Match the MCP hostname, for example Hostname equals
mcp.example.com. - 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. - Deploy the rule, then set
GOSQLX_MCP_TRUSTED_PROXY_SECRET_HEADERandGOSQLX_MCP_TRUSTED_PROXY_SECRETon the server as secret environment variables (on Render, in the dashboard rather than inrender.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_sqlreports invalid SQL as a normal result withvalid: false;analyze_sqlreports sub-tool failures under itserrorskey. - Tool errors (
isError: true, message in the text content): input problems the model can fix, such as a missing, empty or non-stringsql, SQL thatparse_sql,extract_metadataorformat_sqlcannot 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@gosqlxSee 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-mcpOr find and stop the conflicting process:
lsof -i :8080Server 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-mcpHTTP 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
- 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