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: strdb_workload: str = Nonelimit: 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 MCPinitializehandshake - Token validated against the Identity Domain's JWKS (derived from
TOKEN_FACTORY_DOMAIN_URL/DOMAIN_URL, orIAM_IDCS_JWKS_URLoverride) - After validation,
ociclients.pyexchanges the token for an OCITokenExchangeSignerviaTokenFactory
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 explicitOAuth(mcp_url=..., callback_port=...)client object) - Use this mode only to bootstrap
MCP_ACCESS_TOKENviatests/get_token.py, then switch back toserver.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 frommcp_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/SECRETmust 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:
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¶
Operational Notes¶
- Tool surface area is fully controlled by
TOOL_REGISTRYinregistry.py - Server logs write to stderr/log files; stdout is reserved for MCP protocol framing
list_autonomous_db_versionsis read-only; no mutating Database control-plane operations are exposed yet (see the module'sspecs/SPEC.mdNon-Goals)- The
/healthendpoint is always available without authentication