Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/oci-ag-mcp-server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# ---------- MCP Authentication (OIDC) ----------
OCI_CONFIG_URL=
OCI_MCP_CLIENT_ID=
OCI_MCP_CLIENT_SECRET=

# ---------- Access Governance API (Client Credentials) ----------
OCI_AG_CLIENT_ID=
OCI_AG_CLIENT_SECRET=
OCI_TOKEN_URL=
AG_BASE_URL=
AG_SCOPE=
90 changes: 90 additions & 0 deletions src/oci-ag-mcp-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Oracle Access Governance MCP Server

## Overview

The Oracle Access Governance MCP Server exposes Access Governance (AG) capabilities as MCP tools, enabling secure interaction from MCP-compatible clients (e.g., Claude, custom clients etc.).

It integrates with OCI IAM (Identity Domains) using OAuth 2.0 (OIDC) to authenticate MCP clients. All tool executions require a valid token issued by OCI IAM.

### Flow
1. User authenticates via OCI IAM (OIDC)
2. MCP server receives an access/ID token
3. Token is validated
4. Authenticated requests are allowed to invoke MCP tools
5. Server uses client credentials flow to call OCI AG APIs

---

## Setup

### 1. Clone the repository

```
git clone https://github.com/anuj-git1412/oci-ag-mcp-server.git
cd oci-ag-mcp-server
```

### 2. Configure OAuth (OCI IAM)

Setting up authentication requires registering a confidential client in OCI IAM domain.
The application must include the following redirect URI:

```
http://localhost:8000/mcp/auth/callback
```

Register a second client app for access to AG APIs.

---

### 3. Create environment configuration

```
cp .env.example .env
```

Update `.env` with your values:

```
# ---------- MCP Authentication (OIDC) ----------
OCI_CONFIG_URL=
OCI_MCP_CLIENT_ID=
OCI_MCP_CLIENT_SECRET=

# ---------- Access Governance API (Client Credentials) ----------
OCI_AG_CLIENT_ID=
OCI_AG_CLIENT_SECRET=
OCI_TOKEN_URL=
AG_BASE_URL=
AG_SCOPE=
```

## Running the Server

```
uvx oracle.oci-ag-mcp-server
```

---

## Available Tools

| Tool Name | Description |
|------------------------------|------------------------------------------------------------------------------|
| `list_identities` | Retrieve all identities (users) from the AG environment. |
| `list_identity_collections` | Retrieve all identity collections (groups of users) from the AG environment. |
| `create_identity_collection` | Creates a new identity collection in the AG environment. |
| `list_access_bundles` | Retrieve all access bundles from the AG environment. |
| `list_orchestrated_systems` | Retrieve all orchestrated systems from the AG environment. |
| `list_access_requests` | Retrieve all access requests from the AG environment. |
| `create_access_request` | Creates a new access request in the AG environment. |
| `health_check` | Returns basic health status. |
---

## License

Copyright (c) 2026 Oracle and/or its affiliates.

Licensed under the Universal Permissive License v1.0:
https://oss.oracle.com/licenses/upl/

169 changes: 169 additions & 0 deletions src/oci-ag-mcp-server/examples/ag_mcp_test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import asyncio
import json
from fastmcp import Client
from fastmcp.client.auth import OAuth


# ---------- AUTH ----------

class DebugOAuth(OAuth):
async def redirect_handler(self, authorization_url: str):
return await super().redirect_handler(authorization_url)

oauth = DebugOAuth(scopes=["openid", "profile", "email", "groups", "approles", "get_approles"])


# ---------- CONTEXT ----------

CONTEXT: dict = {}


def store_result(key, result):
if not result.structured_content:
return

content = result.structured_content

if isinstance(content, dict) and "result" in content:
CONTEXT[key] = content["result"]
else:
CONTEXT[key] = content


def get_first_id(key):
items = CONTEXT.get(key, [])
if not items:
raise Exception(f"No data found for {key}")
return items[0]["id"]


# ---------- TOOL EXECUTION ----------

async def run_tool(client, tool_name, payload=None):
payload = payload or {}

result = await client.call_tool(tool_name, payload)

if result.structured_content:
print(json.dumps(result.structured_content, indent=2))
else:
print(result)

return result

# ---------- FLOWS ----------

async def health_check_flow(client):
await run_tool(client, "health_check")


async def list_identity_collections_flow(client):
res = await run_tool(client, "list_identity_collections")
store_result("collections", res)


async def list_identities_flow(client):
res = await run_tool(client, "list_identities")
store_result("identities", res)


async def list_access_bundles_flow(client):
await run_tool(client, "list_access_bundles")


async def list_orchestrated_systems_flow(client):
await run_tool(client, "list_orchestrated_systems")


async def list_access_requests_flow(client):
await run_tool(client, "list_access_requests")


async def create_identity_collection_flow(client):
display_name = input("Collection Display Name: ")
owner = input("Owner (email or name): ")
included_raw = input("Included identities (comma separated, optional): ")

included = [x.strip() for x in included_raw.split(",") if x.strip()]

await run_tool(
client,
"create_identity_collection",
{
"display_name": display_name,
"owner": owner,
"included_identities": included,
},
)


async def create_access_request_flow(client):
justification = input("Justification: ")
created_by = input("Requester (name/email): ")

beneficiaries_raw = input("Beneficiaries (comma separated): ")
bundles_raw = input("Access Bundles (comma separated): ")

beneficiaries = [x.strip() for x in beneficiaries_raw.split(",") if x.strip()]
bundles = [x.strip() for x in bundles_raw.split(",") if x.strip()]

await run_tool(
client,
"create_access_request",
{
"justification": justification,
"created_by_user": created_by,
"beneficiaries": beneficiaries,
"access_bundles": bundles,
},
)


# ---------- MENU ----------

FLOWS = {
"1": ("Health Check", health_check_flow),
"2": ("List Identity Collections", list_identity_collections_flow),
"3": ("List Identities", list_identities_flow),
"4": ("Create Identity Collection", create_identity_collection_flow),
"5": ("List Access Bundles", list_access_bundles_flow),
"6": ("List Orchestrated Systems", list_orchestrated_systems_flow),
"7": ("List Access Requests", list_access_requests_flow),
"8": ("Create Access Request", create_access_request_flow),
}


# ---------- MAIN ----------

async def main():
client = Client(
"http://localhost:8000/mcp",
auth=oauth,
)

async with client:
await client.list_tools()

while True:
print("\n=== ACTION MENU ===")
for k, v in FLOWS.items():
print(f"{k}. {v[0]}")
print("0. Exit")

choice = input("Select action: ").strip()

if choice == "0":
break

if choice not in FLOWS:
continue

try:
_, flow = FLOWS[choice]
await flow(client)
except Exception:
pass


if __name__ == "__main__":
asyncio.run(main())
5 changes: 5 additions & 0 deletions src/oci-ag-mcp-server/oracle/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Copyright (c) 2026, Oracle and/or its affiliates.
Licensed under the Universal Permissive License v1.0 as shown at
https://oss.oracle.com/licenses/upl.
"""
5 changes: 5 additions & 0 deletions src/oci-ag-mcp-server/oracle/oci_ag_mcp_server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Copyright (c) 2026, Oracle and/or its affiliates.
Licensed under the Universal Permissive License v1.0 as shown at
https://oss.oracle.com/licenses/upl.
"""
Loading