Skip to content

SQLcl MCP Server

Module: inception_mcp_servers/mcp_sqlcl/
Endpoint: http://127.0.0.1:3001/mcp/
Health: http://127.0.0.1:3001/health
Venv: .venv_mcp_sqlcl

Exposes Oracle Database operations as MCP tools via SQLcl. The server bridges AI agent tool calls to a locally running SQLcl process, enabling natural-language-driven SQL execution with full IAM identity propagation.


Exposed Tools

Controlled by TOOL_REGISTRY in registry.py:

Tool Parameters Description
get_token Returns the current OAuth bearer token and JWT claims from the auth context
connect connection_name, model?, confirm_switch? Connects to a named SQLcl database connection
execute_sql query: str Executes a SQL query and returns parsed JSON results
schema_information Returns enriched metadata about the connected schema
test_connection Runs a SELECT against dual to verify connectivity

To add or remove tools, edit TOOL_REGISTRY in registry.py. Any function not listed there cannot be called.


Architecture

MCP Client
  ▼ HTTP POST /mcp/
┌─────────────────────────┐
│      server_oidc.py     │  ← Interactive: OCIProvider (browser OAuth)
│   or server_token.py    │  ← Non-interactive: OCITokenAuthMiddleware (JWT bearer)
└──────────┬──────────────┘
           │ routes to registered tool
┌─────────────────────────┐
│       registry.py       │  ← TOOL_REGISTRY: connect, execute_sql, schema_information …
└──────────┬──────────────┘
           │ calls
┌─────────────────────────┐
│        tools.py         │
│  Worker queue + session │
└──────────┬──────────────┘
           │ stdio
┌─────────────────────────┐
│   SQLcl (sql -mcp)      │  ← Oracle Database
│  connect / run-sql /    │
│  schema-information     │
└─────────────────────────┘

SQLcl session model

  • A single background worker task owns the SQLcl stdio ClientSession
  • All tool calls submit requests through an asyncio.Queue to the worker
  • The worker serializes calls to the SQLcl process — no concurrent stdio access
  • On error, the session is reset and the call retried once
  • Session state (active connection name) is tracked globally within the process

SQLcl native operations (called internally)

The underlying sql -mcp process exposes these tools, called by tools.py:

SQLcl Tool Used by
list-connections connect (on failure, to hint available names)
connect connect
run-sql execute_sql, test_connection
schema-information schema_information

Authentication

Interactive mode (server_oidc.py)

  • Provider: fastmcp.server.auth.providers.oci.OCIProvider
  • Client connects with auth="oauth" — FastMCP opens a browser for OCI IAM login
  • After login, tool calls are authorized by the issued access token
  • Required env vars: IDCS_DOMAIN, IDCS_CLIENT_ID, IDCS_CLIENT_SECRET

Bearer token mode (server_token.py)

  • Middleware: OCITokenAuthMiddleware + TokenVerifier
  • Client supplies Authorization: Bearer <token> on every request
  • Token validated via IDCS JWKS: https://{IDCS_DOMAIN}/admin/v1/SigningCert/jwk
  • Issuer: https://identity.oraclecloud.com/
  • Audience: https://{IDCS_DOMAIN}:443
  • Algorithm: RS256
  • Required env vars: IDCS_DOMAIN, MCP_ACCESS_TOKEN

Token acquisition — OCI API key exchange

For non-interactive clients (agents, CLI, server-side gateways) that cannot do browser OAuth, the server supports tokens obtained via OCI HTTP Signature token exchange (RFC 8693).

The client signs a POST request to the IDCS token endpoint using its OCI API key (RSA-SHA256), requesting grant_type=urn:ietf:params:oauth:grant-type:token-exchange. The returned access_token is then passed as a Bearer header on all subsequent MCP calls.

This flow is implemented in the Smart Dispatch gateway and CLI using the service_get_token auth library:

from agent_get_token import AgentTokenClient

client = AgentTokenClient(
    token_url=OCI_IDENTITY_TOKEN_URL,
    tenancy_ocid=OCI_IDENTITY_TENANCY_OCID,
    user_ocid=OCI_IDENTITY_USER_OCID,
    fingerprint=OCI_IDENTITY_FINGERPRINT,
    private_key_file=OCI_IDENTITY_PRIVATE_KEY_FILE,
    scope="urn:opc:idm:__myscopes__",
)
bearer_token = client.get_access_token()

Required env vars: OCI_IDENTITY_TOKEN_URL, OCI_IDENTITY_TENANCY_OCID, OCI_IDENTITY_USER_OCID, OCI_IDENTITY_FINGERPRINT, OCI_IDENTITY_PRIVATE_KEY_FILE.

See Solution Patterns — Service Token Acquisition for the full pattern.

Identity propagation to the database (ociclients.py)

Once a validated Bearer token is in scope, the MCP server can exchange it for a database-scoped token to preserve the caller's identity in Oracle:

# src/auth/ociclients.py
from token_factory import TokenFactory

def get_db_token(mcp_token) -> str:
    factory = TokenFactory.from_env()
    result = factory.get_token(mcp_token.token, "oracle-database")
    return result["token"]

The TokenFactory exchanges the IDCS access token for a short-lived Oracle DB token so the database connection is opened under the end user's mapped identity — enabling VPD row-level security and a complete audit trail. Tokens are cached in memory by JTI to avoid redundant exchanges within a session.


Configuration

# .env
MCP_HOST_MCP_SQLCL=127.0.0.1
MCP_PORT_MCP_SQLCL=3001

SQLCL_PATH=/Applications/sqlcl/bin/sql
SQLCL_CONNECTION=<named-connection>

# OCI IAM
IDCS_DOMAIN=<domain>.identity.oraclecloud.com    # bare hostname only
IDCS_CLIENT_ID=<client-id>
IDCS_CLIENT_SECRET=<client-secret>

# Token mode only
MCP_ACCESS_TOKEN=<oauth-access-token>

Running the Server

cd inception_mcp_servers/mcp_sqlcl

# First-time setup
.venv_mcp_sqlcl/bin/python -m pip install -r requirements.txt

# Interactive OIDC server
.venv_mcp_sqlcl/bin/python -m src.server_oidc

# Non-interactive bearer token server
.venv_mcp_sqlcl/bin/python -m src.server_token

Free the port if needed:

lsof -ti tcp:3001 | xargs kill -9

Testing

Bootstrap a token (run against server_oidc.py)

# server_oidc.py must be running
python -m tests.get_token
# Opens OCI browser login → writes MCP_ACCESS_TOKEN to .env

Non-interactive regression test (run against server_token.py)

python -m tests.test_mcp_sqlcl --url http://127.0.0.1:3001/mcp/ --limit 10
# Optional: --connection-name <name>  --model <model>  --access-token <token>

MCP Inspector

fastmcp dev --server-spec src/server_oidc.py:mcp   # interactive
fastmcp dev --server-spec src/server_token.py:mcp  # bearer token

SQLcl Prerequisites

Install SQLcl and save a named connection before starting the server:

# Download SQLcl
mkdir sqlcl && cd sqlcl
wget https://download.oracle.com/otn_software/java/sqldeveloper/sqlcl-latest.zip
unzip sqlcl-latest.zip

# Save a named connection
./bin/sql /nolog
SQL> conn -save myConn -savepwd username/password@host:1521/service

Configure SQLCL_PATH and SQLCL_CONNECTION in .env to match.

Full SQLcl MCP docs: Oracle SQLcl MCP Server


CSV Result Parsing

SQLcl returns query results as CSV text. tools.py parses this into structured JSON:

{
  "success": true,
  "data": [
    {"EMPLOYEE_ID": "100", "LAST_NAME": "King", "SALARY": "24000"}
  ],
  "rowCount": 1,
  "timestamp": "2026-04-18T21:00:00",
  "query": "SELECT ..."
}

Quoted CSV values and embedded commas are handled correctly by the custom CSV parser.