E2E Identity Propagation → Object Storage (oci-service)¶
Folder: user_e2e_token_exchange_os_oci-service/
The token_factory target_tool="oci-service" reference. A user logs in via IDCS OAuth2, and their access token is propagated through a FastAPI backend to mcp_os, which exchanges it for an OCI TokenExchangeSigner to call the Object Storage API on behalf of the user.
Architecture¶
Browser ──► FastAPI backend (port 8000)
│ OAuth2 authorization code flow (IDCS), CSRF-protected
│ Proxies tool calls with BearerAuth
▼
MCP Object Storage server (port 3002)
│ OCITokenAuthMiddleware validates JWT (JWKS) before any tool runs
│ Tools read the token from the verified auth context, never a tool argument
│ TokenFactory.get_token(token, 'oci-service') → OCI TokenExchangeSigner
▼
OCI Object Storage
Code¶
from oci.identity import IdentityClient
from auth.token_factory import TokenFactory
factory = TokenFactory()
signer = factory.get_token(caller_access_token, "oci-service")
identity_client = IdentityClient(config={"region": "us-chicago-1"}, signer=signer)
regions = identity_client.list_regions().data
mcp_os's src/auth/ociclients.py runs exactly this shape:
factory = TokenFactory()
signer = factory.get_token(raw_token, "oci-service")
return oci.object_storage.ObjectStorageClient(config={"region": region}, signer=signer)
raw_token comes from the request's verified auth context (fastmcp.server.dependencies.get_access_token()), never a client-supplied tool parameter — TokenFactory decodes the credential it's given with verify_signature=False by design, trusting that OCITokenAuthMiddleware already validated it.
MCP Tools Used¶
| Tool | Parameters |
|---|---|
get_os_namespace |
region |
list_objects |
region, bucket_name |
upload_object_remote |
region, bucket_name, object_name, object_content |
delete_object |
region, bucket_name, object_name |
whoami |
— |
Token Flow¶
- User clicks Login →
/loginissues a single-use, randomstatevalue and redirects to IDCS/oauth2/v1/authorize - IDCS authenticates user → redirects to
/callback?code=...&state=... - Backend validates
state(CSRF protection), exchangescodeforaccess_token - Backend redirects to
/#access_token=<token>(URL fragment); UI stores it inlocalStorage - UI attaches
Authorization: Bearer <token>to all/os/*requests - Backend creates a FastMCP
ClientwithBearerAuth(token)→ calls an MCP tool onmcp_os mcp_os'sOCITokenAuthMiddlewarevalidates the JWT against the Identity Domain's JWKS →TokenFactory.get_token(token, 'oci-service')returns an OCITokenExchangeSigner→ the signer authenticates the Object Storage API call under that identity
oci-service vs. oracle-database vs. oauth¶
| Aspect | oracle-database (ADB pattern) |
oci-service (this pattern) |
|---|---|---|
| MCP server | mcp_adw :8003 |
mcp_os :3002 |
| Return type | DB token + private key | OCI TokenExchangeSigner |
| OCI SDK client | ADW thin driver | ObjectStorageClient |
oci-service is specifically for native OCI SDK clients that accept a signer= parameter (Object Storage, Identity, and similar). It's not the right choice for a plain HTTPS Bearer-token call — see E2E Identity Propagation → OIC for a pattern where oauth, not oci-service, was the correct choice for exactly that reason.
When to use: Applications that read or write OCI Object Storage on behalf of a human user and need the storage operations attributed to that user's identity for audit.