MCP SQLKit is a database access toolbox built on top of the MCP protocol. It exposes a minimal set of tools that allow any MCP-host (CLI, Claude Desktop, etc.) to:
- register database connectors (connection definitions),
- list existing connectors,
- execute SQL queries and obtain the result set as JSON,
- execute SQL DML / DDL statements and obtain execution metadata.
The project is written in Go, released under the Apache 2 licence and
distributed as a single self-contained executable (mcp-sqlkit).
- Features
- Quick start
- Configuration
- Available tools
- Connector secrets
- Authentication & authorization
- Secure connector-secret flow
- Installation
- Building from source
- Running the unit-tests
- Project structure
- Contributing
- Licence
Related open-source MCP servers: see GitHub MCP and Outlook MCP sections below.
• Multi-database support – ships with metadata for MySQL, BigQuery and is
easily extensible to other drivers supported by
database/sql.
• Namespace isolation – requests are scoped to the caller’s namespace so that multiple users (JWT subjects, OAuth2 e-mails, etc.) can share a single toolbox instance without seeing each other’s connectors.
• Zero-copy result streaming – query results are streamed directly from
database/sql into JSON without intermediate allocations.
• Built-in secret management – credentials are encrypted with
scy (Blowfish-GCM by default) and written
to an abstract secret URL. Out-of-the-box SQLKit supports file:// (local
disk), mem:// (in-memory) and any additional schemes implemented by the
scy project – for example Cloud Secret Manager, HashiCorp Vault, etc.
By default secrets are stored in-memory (mem://); enable filesystem
storage to persist them on disk.
• MCP secret-elicitation flow – if a connector lacks a stored secret SQLKit
uses the MCP Elicit API to instruct the client to kick-off a
browser-based flow. The user types the credentials (or completes an OAuth2
consent screen) directly in that page – the secret never passes through the
MCP client. Once submitted the connector transitions from PENDING_SECRET to
ACTIVE automatically.
The easiest way to try the toolbox is to run the server with both HTTP and STDIO transports enabled:
go run ./cmd/mcp-sqlkit -a :5000 --secretsBase mem://localhost/mcp-sqlkit/.secret/Tip: for persistence across restarts, use a file-backed secrets store, e.g.:
// CI_AGENCY.json
[
{ "ID": "$Sequences.CI_AGENCY/${tag}.1", "ACCOUNT_ID": "$Sequences.CI_ACCOUNT/${tag}.default", "NAME": "Agency ${Sequences.CI_AGENCY}" }
]
// expect/db/valid_audience_insert.json
{ "NAME": "Inserted Audience", "AD_ORDER_ID": "$AsInt(${adOrder1.ID})", "STATUS": 1 }
{
"NAME": "Inserted Audience",
"AD_ORDER_ID": "$AsInt(${adOrder1.ID})",
"STATUS": 1
}
--secretsBase file://~/.secret/mcp-sqlkit-a :5000– HTTP listen address (omit to disable HTTP transport).-s– enable STDIO transport (useful when the toolbox is launched by another process via pipes).--secretsBase– base URL for secrets storage (scy-backed). Examples:mem://localhost/mcp-sqlkit/.secret/(default, in-memory)file://~/.secret/mcp-sqlkit(persistent on disk)gcp://secretmanager/projects/...orvault://...(external managers)
The server will print something similar to:
2025/07/12 mcp-sqlkit server listening on HTTP :5000
2025/07/12 mcp-sqlkit server listening on stdio
At this point you can connect with any MCP client and invoke the tools listed in the next section.
Tip: You can use the base URL
- The server redirects the root path (
/) to the active transport (SSE by default). - That means
http://localhost:5000works out of the box without specifying/sseor/mcp.
All configuration knobs live under the mcp.Config structure
(mcp/config.go). The executable will look for a JSON file specified via the
-c / --config command-line flag; if not provided sensible defaults are
applied.
CLI overrides
- The
--secretsBaseflag overridesconnector.secretBaseLocationfrom the config file. - The
--public-base-urlflag sets the public base URL used in out-of-band (OOB) secret flows and OAuth redirects. Use this when the server is behind a proxy or running in Kubernetes so that generated links point to the externally reachable host, e.g.--public-base-url http://mcp-sqlkit.agently.svc.cluster.local:7789.
In addition to adding connectors at runtime you can pre-load connection
definitions via configuration so that they are available immediately after the
server starts. This is done with the connector.defaultConnectors array
(see db/connector.Config). Each entry contains a namespace and a list of
connectors.
- Global / shared connectors – set
namespaceto an empty string or"default". All callers (even unauthenticated) can use them. - Per-user connectors – set
namespaceto the e-mail / subject expected in the user’s ID-token. Only that principal will see the connector.
Example:
{
"connector": {
"defaultConnectors": [
{
// Shared read-only replica available to everyone.
"namespace": "default",
"connectors": [
{
"name": "analyticsRO",
"driver": "mysql",
"dsn": "analytics-ro:3306/analytics",
// optional inline secret – persisted at first start-up
"secrets": {
"URL": "file://~/.secret/mcpt/mysql/analytics/default",
"Key": "blowfish://default"
}
}
]
},
{
// Private BigQuery connector visible only to alice@example.com
"namespace": "alice@example.com",
"connectors": [
{
"name": "bqPrivate",
"driver": "bigquery",
"dsn": "bigquery://project/dataset"
}
]
}
]
}
}On start-up SQLKit persists any provided secrets (if they are not yet stored) and registers the connectors under their respective namespaces.
Configuration can also be built programmatically – see
cmd/mcp-sqlkit/main.go for a minimal example.
The package db/driver blank-imports the most common database/sql drivers
(MySQL, Postgres, SQLite, etc.) and the product-specific metadata
registrations used by github.com/viant/sqlx. Import it once in your main if
you use the services outside of the toolbox:
import _ "github.com/viant/mcp-sqlkit/db/driver"Toolbox binaries already import it for you, so no action is required when you
run mcp-sqlkit.
The toolbox registers the following MCP tools (see mcp/tool.go).
| Tool name | Description | Input struct |
|---|---|---|
dbQuery |
Execute a SQL query and return the result set | db/query.Input |
dbExec |
Execute DML/DDL and return rows affected | db/exec.Input |
dbListConnections |
List connectors visible to the caller | db/connector.ListInput |
Notes
- dbListConnections returns
dataas an array of items with shape{name, driver, dsn}. When no connectors are present,datais an empty array. - Tool responses include the JSON payload in both
content.textandcontent.datafields to accommodate different MCP clients. - On the very first call when no connectors exist, the server may trigger an elicitation flow (form + secret). After secrets are provided, subsequent
dbListConnectionscalls return the newly created connector. |dbSetConnection| Create or update a connector (upsert) |db/connector.ConnectionInput| |dbListTables| List tables for a given catalog/schema |db/meta.ListTablesInput| |dbListColumns| List columns for a given table |db/meta.ListColumnsInput|
Open-source companion MCP server that integrates with GitHub. It exposes tools to:
- List repositories, issues, pull requests
- Create issues and pull requests
- Add/list comments
- Explore repository contents (list paths, download files) and optionally check out locally
Getting started:
- Source: https://github.com/viant/mcp-toolbox/tree/main/github
- Run:
go run ./github/cmd/github-mcp -addr :7789(in mcp-toolbox)
Open-source companion MCP server that integrates with Microsoft 365 (Graph):
- List and send mail
- List/create calendar events
- List/create tasks
Getting started:
- Source: https://github.com/viant/mcp-toolbox/tree/main/outlook
- Run:
go run ./outlook/cmd/outlook-mcp -addr :7788(in mcp-toolbox)
- Register a connector (either via
dbSetConnectionor through the browser secret-elicitation flow described later). WithdbSetConnectionyou pass only non-secret fields:
{
"name": "mysqlLocal",
"driver": "mysql",
"host": "127.0.0.1",
"port": 3306,
"db": "test",
"options":"parseTime=true"
}The toolbox expands these into a driver-specific DSN template (e.g.
tcp(127.0.0.1:3306)/test?parseTime=true) and triggers the secret-elicitation
flow if credentials are missing.
- Call the
dbQuerytool with the following payload:
{
"query": "SELECT id, name FROM users WHERE status = ? LIMIT 10",
"connector": "mysqlLocal",
"parameters": ["active"]
}- The toolbox responds with a JSON array containing the rows.
When you add a connector whose credentials are not yet stored the toolbox
transitions it into a PENDING_SECRET state and – provided the client
supports the MCP Elicit method – initiates a browser-based flow where you can
enter the secret.
• Basic auth drivers (e.g. MySQL, Postgres) prompt for username and password.
• BigQuery driver triggers an OAuth2 authorisation code flow and appends the obtained token URLs to the DSN automatically.
Secrets are encrypted with scy and stored at:
<SecretBaseLocation>/<driver>/<database>/<namespace>
Key points about secret storage:
- Encryption uses Blowfish-GCM with keys managed by scy; SQLKit never stores secrets in plain text.
- The location scheme is pluggable – any URL that scy recognises works
(
file://,mem://,gsecret://,vault://, …). - By default SQLKit stores secrets using
mem://(in-memory AFS). To persist on disk setconnector.secretBaseLocationto afile://path (e.g.file://~/.secret/mcpt). You can also point it togsecret://orvault://according to your environment. - Prefer the CLI flag
--secretsBase <base-url>to set the storage location at runtime; it overrides the config and is recommended for persistent or remote deployments.
MCP SQLKit can run with or without OAuth 2.0 / OpenID Connect identity
verification. When an OAuth2 configuration is supplied and the flag
requireIdentityToken is set, every inbound MCP request must carry a valid
JWT ID-token (usually passed as Authorization: Bearer …). The toolbox
extracts the subject or e-mail claim to derive the request namespace so
that each principal sees only its own connectors.
Minimal JSON snippet enabling token verification (see mcp.Config):
{
"connector": {
"policy": {
"requireIdentityToken": true,
"oauth2Config": { // Standard golang.org/x/oauth2 cfg
"clientID": "<client_id>",
"clientSecret": "<secret>",
"endpoint": {
"authURL": "https://idp.example.com/auth",
"tokenURL": "https://idp.example.com/token"
},
"redirectURL": "http://localhost:5000/oauth2/callback",
"scopes": ["openid", "email"]
}
}
}
}When the policy block is omitted the toolbox falls back to a shared
namespace called default – useful for local development or CI where no
identity provider is available.
- With
-o(OAuth client) only: the namespace is derived from a hash of the access token, isolating connectors per token instance. - With
-o -i(OAuth + ID token): the namespace is derived from the ID-token subject/email and remains stable across access-token refreshes. - Without
-o/-i: the shareddefaultnamespace is used.
Security note (remote deployments)
- For multi-user remote deployments, ensure the server is configured to require ID tokens and that clients pass them (use
-o -iand setpolicy.requireIdentityToken: true). - Running without
-ior without an OAuth policy can place users in the same namespace (eitherdefaultor a shared access-token hash), which can unintentionally expose connectors/secrets across users if the same access token is reused (e.g., via a shared service account or proxy). - Recommended: enable
requireIdentityTokenand use per-user ID tokens to guarantee user-scoped isolation across sessions and hosts.
SQLKit enforces strict per-namespace isolation so that connection metadata and secrets do not leak between users:
- Per-request scoping: every tool call resolves the caller’s namespace from the request context and operates only within that namespace.
- Registry isolation: connectors are stored under
Namespaces[namespace]; listing and lookups cannot see entries from other namespaces. - Secret isolation: secrets are stored at
<SecretBaseLocation>/<driver>/<database>/<namespace>(or<...>.jsondepending on the storage scheme), so credentials are physically separated per namespace. - Elicitation isolation: the secret-elicitation flow binds a pending item to the namespace; upon completion, activation occurs only in that namespace.
Implications and tips:
- -o only (access token): namespaces are derived from a hash of the current token. If the client rotates tokens frequently, you may see different namespaces across calls (over‑isolation), but no leakage occurs.
- -o -i (ID token): namespaces are derived from subject/email and remain stable across token refreshes; recommended for user‑scoped isolation.
- Shared connectors: to intentionally share, preconfigure connectors under
namespace: "default"(or another explicit namespace) in the config.
Adding a connector is a two-step operation:
- Persist the connection definition (driver, DSN, etc.).
- Supply the associated secret (basic credentials or OAuth token).
Step 2 is performed via a browser-based page started by the Elicit request and served by SQLKit so that credentials never transit through the MCP client. The flow is identical for HTTP and stdio transports and differs only in the type of credential supplied.
The diagrams below use Mermaid (GitHub renders it automatically).
sequenceDiagram
autonumber
participant U as User
participant C as "MCP Client"
participant S as "SQLKit Server"
participant UI as "Browser (UI)"
participant SS as "Secret Store"
U->>C: dbSetConnection (missing secret)
C->>S: rpc dbSetConnection
S-->>C: Elicit request (flowURI to secret page)
note over S: entry stored in-memory
C-->>U: open browser to flowURI
U->>UI: navigate
UI->>S: GET secret page
U->>UI: enter user / pass
UI->>S: POST secret (TLS)
S->>SS: encrypt & persist (file:// or mem://)
S-->>UI: 204 No Content
S-->>C: optional MCP notification
note over S: connector state ➜ ACTIVE
sequenceDiagram
autonumber
participant U as User
participant C as "MCP Client"
participant S as "SQLKit Server"
participant UI as Browser
participant OP as "OAuth Provider"
participant SS as "Secret Store"
U->>C: dbSetConnection (BigQuery etc.)
C->>S: rpc dbSetConnection
S-->>C: Elicit request (flowURI)
C-->>U: open browser to flowURI
U->>UI: navigate
UI->>S: GET secret page (OAuth flow)
UI->>OP: redirect (Auth URL)
OP->>UI: login & consent
OP-->>UI: redirect back with code
UI->>S: POST code
S->>OP: exchange code → access/refresh/id tokens
S->>SS: encrypt & persist tokens
S-->>UI: success
S-->>C: optional MCP notification
note over S: connector state ➜ ACTIVE
Security properties:
- Secrets are always submitted over TLS (browser → SQLKit).
- The MCP client never sees the credential – it only carries the callback URL to the user.
- Secrets are encrypted via scy using Blowfish‐GCM; keys reside on the host – no plain-text on disk.
- When
secretBaseLocationis empty, secrets stay in-memory; perfect for demos but not recommended for production.
If a user invokes a tool that references an unknown connector SQLKit will
activate the MCP Elicit protocol method to collect the missing
definition on the fly. The server sends a JSON-Schema describing the
ConnectionInput structure; the MCP client renders it as a form, collects the
input and returns it without any secrets. The subsequent secret flow runs the
same way as in Branch A/B.
sequenceDiagram
autonumber
participant U as User
participant C as "MCP Client"
participant S as "SQLKit Server"
participant SS as "Secret Store"
U->>C: dbQuery (connector=unknown)
C->>S: rpc dbQuery
S-->>C: Elicit request (ConnectionInput schema)
C-->>U: render form
U->>C: fill host / port / db …
C->>S: Elicit result (accepted)
S->>S: create connector (may be PENDING_SECRET)
alt secret required
S-->>C: state = PENDING_SECRET (+callback URL)
else no secret
S-->>C: dbQuery result
end
S->>SS: (optional) persist secret when provided
Pre-built binaries are provided on the [releases page]
(https://github.com/viant/mcp-sqlkit/releases). Download the archive that
matches your OS / CPU, unpack it and move the mcp-sqlkit binary somewhere in
your PATH, e.g.:
curl -L -o mcp-sqlkit_linux_amd64.tar.gz \
https://github.com/viant/mcp-sqlkit/releases/latest/download/mcp-sqlkit_linux_amd64.tar.gz
tar -xzf mcp-sqlkit_linux_amd64.tar.gz
sudo mv mcp-sqlkit /usr/local/bin/Alternatively you can build and install directly with the Go tool-chain (Go 1.21+ recommended):
go install github.com/viant/mcp-sqlkit/cmd/mcp-sqlkit@latestThe command places the binary in $(go env GOPATH)/bin which should already
be on your PATH if you installed Go using the official instructions.
The project uses the Go tool-chain – clone and build as usual:
git clone https://github.com/viant/mcp-sqlkit.git
cd mcp-sqlkit
go build ./cmd/mcp-sqlkitThis yields a single binary mcp-sqlkit.
Tests follow a data-driven pattern (see db/query/cache_test.go for an
example). Run the full suite with:
go test ./...├── auth/ – Namespace derivation & JWT/OAuth2 helpers
├── cmd/ – CLI entry-points (currently only mcp-sqlkit)
├── db/ – Database-related logic
│ ├── connector/ – connector management, secret handling, UI flow
│ ├── exec/ – DML/DDL execution service
│ └── query/ – Query service with dynamic record type caching
├── mcp/ – Toolbox service, MCP handler & tool registration
└── policy/ – Security policy primitives
Pull requests are welcome – please make sure that:
- Code is formatted (
go fmt ./...) andgo vetpasses. - New logic is covered by data-driven tests that use
assert.EqualValues()fromstretchr/testify. - You do not introduce driver-specific hacks – prefer a generic solution so that other connectors can benefit as well.
This project is released under the Apache Licence, version 2.0 – see the
LICENSE file for the full text.
{ "connector": { // Base secret location (defaults to mem://localhost/mcp-sqlkit/.secret/) // Use any scy-supported scheme: // file://~/.secret/mcpt → persist on disk // mem://localhost/... → in-memory (default) // gsecret://... / vault://... → external managers "secretBaseLocation": "file://~/.secret/mcpt" }, // Tool responses include JSON in BOTH content.text and content.data // for broad client compatibility. The `useData` flag is retained for // backward compatibility and no longer changes the response shape. "useData": true }