Skip to content

OCI Database MCP Server

Module: inception_mcp_servers/mcp_oci_control_plane_database/ Endpoint: http://127.0.0.1:8004/mcp/ Health: http://127.0.0.1:8004/health Venv: .venv

Exposes Oracle Cloud Infrastructure's Database Cloud Service control-plane API (oci.database.DatabaseClient) — resource/admin-level operations (list supported Autonomous Database versions, and future control-plane ops), as distinct from data-plane SQL access. Sibling to the Object Storage MCP server's control-plane pattern, not to ADW MCP's wallet-mTLS SQL pattern — see the module's own specs/SPEC.md §1.


Exposed Tools

Controlled by TOOL_REGISTRY in registry.py:

Tool Parameters Description
list_autonomous_db_versions compartment_id: str
db_workload: str = None
limit: int = None
Lists Autonomous Database versions supported in a compartment. Read-only.
whoami — Returns the caller's identity claims from the verified bearer token.
get_token — Returns the caller's own access token (debugging/bootstrap use).

No resources exposed

This server exposes tools only.

Extended, unregistered tool surface

tools.py also contains 146 additional control-plane functions ported from oracle/mcp's oci-database-mcp-server (its full surface — list_autonomous_databases, pluggable database create/update/delete, DB systems, Exadata infrastructure, backups, patches, etc.). None are in TOOL_REGISTRY, so none are reachable — enable individually, one at a time, after review. See the module's README.md/specs/SPEC.md for the full list.

Response shape:

{
  "success": true,
  "data": [
    {
      "version": "19c",
      "db_workload": "OLTP",
      "is_dedicated": false,
      "details": "www.oracle.com/atp",
      "is_free_tier_enabled": true,
      "is_dev_tier_enabled": true,
      "is_paid_enabled": true,
      "is_default_for_free": true,
      "is_default_for_paid": true
    }
  ],
  "count": 1
}

Architecture

MCP Client
  │
  ▼ HTTP POST /mcp/  (stateless — no session state between calls)
┌─────────────────────────────────────────┐
│              server.py                  │
│  OCITokenAuthMiddleware validates        │
│  bearer token before any tool runs      │
└──────────┬──────────────────────────────┘
           │ routes to list_autonomous_db_versions / whoami / get_token
           ▼
┌─────────────────────────────────────────┐
│              tools.py                   │
│  ociclients.get_database_client()       │
│  TokenFactory().get_token(token,        │
│    "oci-service") -> TokenExchangeSigner│
└──────────┬──────────────────────────────┘
           │ oci.database.DatabaseClient(signer=...)
           ▼
┌─────────────────────────────────────────┐
│   OCI Database Cloud Service (control    │
│   plane) -- caller's own IAM identity   │
└─────────────────────────────────────────┘

Key design choices

Choice Rationale
Control-plane family (OCI SDK + TokenExchangeSigner), not data-plane list_autonomous_db_versions lists a resource type, it doesn't query rows in a schema — matches mcp_os's auth model, not mcp_adw's wallet/SQL model
oci-service TokenFactory scope Same exchange mcp_os's get_os_client performs, just handed to DatabaseClient instead of ObjectStorageClient
No server-side compartment_id default Always a caller-supplied tool parameter — a caller may legitimately query different compartments across calls
Per-call DatabaseClient construction No shared session state across callers

Authentication

Two server modes, mirroring mcp_os:

server.py — Non-interactive (bearer token), normal operation

  • Uses OCITokenAuthMiddleware + TokenVerifier (JWTVerifier with IDCS JWKS)
  • Every request must include Authorization: Bearer <token>, including the MCP initialize handshake
  • Token validated against the Identity Domain's JWKS (derived from TOKEN_FACTORY_DOMAIN_URL/DOMAIN_URL, or IAM_IDCS_JWKS_URL override)
  • After validation, ociclients.py exchanges the token for an OCI TokenExchangeSigner via TokenFactory

server_oidc.py — Interactive (OIDC browser flow), bootstrap only

  • Uses fastmcp.server.auth.providers.oci.OCIProvider — a real OAuth-discovery proxy sitting between the client and IDCS
  • Clients connect with auth="oauth" (or an explicit OAuth(mcp_url=..., callback_port=...) client object)
  • Use this mode only to bootstrap MCP_ACCESS_TOKEN via tests/get_token.py, then switch back to server.py
  • The IDCS confidential application's allowed redirect URIs must include http://{host}:{port}/auth/callback — the proxy's own callback, not the local client's — this is a distinct requirement from mcp_os's own registered app unless deliberately shared

Important

Never run server.py and server_oidc.py on the same port at the same time. Do not connect an OAuth-only client to server.py — the bearer middleware validates every request including initialize, so an OAuth-only client is rejected before login can complete.


Bootstrap Flow (first-time token acquisition)

1. Stop server.py if running on the target port
2. Start server_oidc.py
3. Run tests/get_token.py  ->  opens a browser for IDCS login
4. After login, get_token tool returns the access token
5. Token is written to .env as MCP_ACCESS_TOKEN
6. Stop server_oidc.py, start server.py for normal operation

tests/get_token.py also runs its own local OAuth callback listener (default port 8000, override via OAUTH_CALLBACK_PORT) to catch the redirect back from the proxy — that port must be free locally, but does not itself need IDCS registration.


Configuration

MCP_HOST_OCI_DATABASE=127.0.0.1
MCP_PORT_OCI_DATABASE=8004
MCP_ACCESS_TOKEN=                    # populated by the bootstrap flow

IAM_IDCS_CLIENT_ID=
IAM_IDCS_CLIENT_SECRET=
IAM_IDCS_REDIRECT_URI=               # http://<host>:<port>/auth/callback

IAM_OCI_REGION=
IAM_USE_PRINCIPALS=false             # false = token propagation (default), true = instance principals

# TokenFactory's own canonical config keys -- see
# inception_core/libs/src/auth/token_factory/README.md
DOMAIN_URL=
TOKEN_FACTORY_DOMAIN_URL=
TOKEN_FACTORY_DOMAIN_REGION=
TOKEN_FACTORY_OCI_REGION=
TOKEN_FACTORY_OCI_AUTH_MODE=
TOKEN_FACTORY_CREDS_SECRET_OCID=
TOKEN_FACTORY_ISSUER=
TOKEN_FACTORY_PRIVATE_KEY_SECRET_OCID=
TOKEN_FACTORY_CERT_SECRET_OCID=
TOKEN_FACTORY_KID=

IAM_IDCS_CLIENT_ID/SECRET must belong to an app whose allowed redirect URIs include this server's own /auth/callback — reusing another server's app only works if that app's redirect URIs were widened to include it.


Running the Server

cd inception_mcp_servers/mcp_oci_control_plane_database

# First-time setup
uv sync

# Normal operation (bearer mode)
uv run python -m src.server

Free the port if needed:

lsof -ti tcp:8004 | xargs kill -9

VM / production deployment

mkdir -p logs
nohup uv run python -m src.server > logs/mcp_oci_control_plane_database.log 2>&1 &

ps aux | grep src.server
kill <pid>

Dependency Pin Note

mcp==1.22.0 is pinned explicitly in pyproject.toml, matching mcp_os's proven-working version. Newer mcp SDK releases enforce a stricter RFC 8707 resource-match check that fastmcp==2.13.2's OCIProvider doesn't satisfy — letting mcp float to whatever fastmcp's own mcp!=1.21.1,<2.0.0,>=1.19.0 constraint resolves to reproduces OAuthFlowError: Protected resource .../mcp does not match expected ... during the bootstrap login.


Tests

cd inception_mcp_servers/mcp_oci_control_plane_database

# Bootstrap (interactive, run once against server_oidc.py)
uv run python -m tests.get_token

# Regression test (non-interactive, run against server.py)
export TEST_COMPARTMENT_ID=<a real compartment OCID>
uv run python -m tests.client

Both must be invoked as modules (-m tests.get_token / -m tests.client), not as bare scripts (python tests/client.py) — a bare script invocation puts tests/ on sys.path[0] instead of the project root, so the src.* imports fail with ModuleNotFoundError: No module named 'src'.

MCP Inspector

fastmcp dev --server-spec src/server.py:mcp

Operational Notes

  • Tool surface area is fully controlled by TOOL_REGISTRY in registry.py
  • Server logs write to stderr/log files; stdout is reserved for MCP protocol framing
  • list_autonomous_db_versions is read-only; no mutating Database control-plane operations are exposed yet (see the module's specs/SPEC.md Non-Goals)
  • The /health endpoint is always available without authentication