Skip to content

Commit dc472db

Browse files
authored
Merge pull request #3189 from oracle-devrel/add-oci-enterprise-ai-agents-oac-mcp-server
Add OCI Enterprise AI Agents OAC MCP server asset
2 parents 2478528 + f0c66b1 commit dc472db

18 files changed

Lines changed: 7177 additions & 0 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
Copyright (c) 2026 Oracle and/or its affiliates.
2+
3+
The Universal Permissive License (UPL), Version 1.0
4+
5+
Subject to the condition set forth below, permission is hereby granted to any
6+
person obtaining a copy of this software, associated documentation and/or data
7+
(collectively the "Software"), free of charge and under any and all copyright
8+
rights in the Software, and any and all patent rights owned or freely
9+
licensable by each licensor hereunder covering either (i) the unmodified
10+
Software as contributed to or provided by such licensor, or (ii) the Larger
11+
Works (as defined below), to deal in both
12+
13+
(a) the Software, and
14+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
15+
one is included with the Software (each a "Larger Work" to which the Software
16+
is contributed by such licensors),
17+
18+
without restriction, including without limitation the rights to copy, create
19+
derivative works of, display, perform, and distribute the Software and make,
20+
use, sell, offer for sale, import, export, have made, and have sold the
21+
Software and the Larger Work(s), and to sublicense the foregoing rights on
22+
either these or other terms.
23+
24+
This license is subject to the following condition:
25+
26+
The above copyright notice and either this complete permission notice or at a
27+
minimum a reference to the UPL must be included in all copies or substantial
28+
portions of the Software.
29+
30+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
SOFTWARE.
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# OCI Enterprise AI Agents — OAC MCP Server
2+
3+
A React assistant that lets [OCI Enterprise AI](https://www.oracle.com/artificial-intelligence/enterprise-ai/) query **governed Oracle Analytics Cloud data** through the **OAC MCP server**. The model reasons through the OCI Generative AI Responses API, the app executes the delegated OAC MCP tool calls, and every numeric result is rendered as a chart — with the Logical SQL and the full tool timeline shown next to it.
4+
5+
Built with **Next.js 16**, **React 19**, **Recharts**, and a small **Python bridge**.
6+
7+
![Result chart](images/02-demo-chart.png)
8+
9+
---
10+
11+
## Quick Start
12+
13+
```bash
14+
cd files
15+
npm install
16+
python3 -m venv .venv
17+
.venv/bin/pip install -r scripts/requirements.txt
18+
cp .env.example .env # fill in your values (see Configuration)
19+
npm run dev
20+
```
21+
22+
Open [http://localhost:3000](http://localhost:3000). Click the **Graph Demo** quick prompt and **Run Analysis** to verify the whole React → Python → chart path *without any OAC credentials*. Then configure OAC access and run a real analysis (e.g. the **Sample Sales** quick prompt).
23+
24+
### Requirements
25+
- Node.js 22+
26+
- Python 3.9+
27+
- An OCI tenancy with **Generative AI** access: the OpenAI-compatible Responses endpoint, an API key, and a Generative AI project OCID
28+
- An **Oracle Analytics Cloud** instance whose MCP endpoint (`/api/mcp`) is enabled, and a user with access to at least one subject area or dataset (the demo prompts use `Sample Sales Lite` / `Sample Targets Lite`)
29+
30+
---
31+
32+
## Configuration
33+
34+
### Required environment variables
35+
36+
Create `files/.env` (copy `.env.example`):
37+
38+
```env
39+
# OCI Generative AI — OpenAI-compatible Responses API for your region
40+
GENAI_BASE_URL=https://inference.generativeai.eu-frankfurt-1.oci.oraclecloud.com/openai/v1
41+
GENAI_API_KEY=<oci-genai-api-key>
42+
GENAI_PROJECT_ID=ocid1.generativeaiproject.oc1..xxxxx
43+
44+
# Oracle Analytics Cloud MCP endpoint of your instance
45+
OAC_MCP_SERVER_URL=https://<your-oac-instance>.analytics.ocp.oraclecloud.com/api/mcp
46+
47+
# Optional — derived from OAC_MCP_SERVER_URL when not set
48+
# OAC_TOKEN_REFRESH_URL=https://<your-oac-instance>.analytics.ocp.oraclecloud.com/api/dv/api/v1/tokens/token/refresh
49+
50+
# Optional — python used by the API route (default: files/.venv/bin/python, then python3)
51+
# PYTHON_BIN=/usr/bin/python3
52+
```
53+
54+
### OAC authentication
55+
56+
Two options, in the order the bridge resolves them:
57+
58+
1. **Access token** — set `OAC_ACCESS_TOKEN` (or `MCP_BEARER_TOKEN`) in `.env`, or paste a token in the UI. OAC access tokens expire after ~1 hour.
59+
2. **`tokens.json` (recommended)** — in OAC go to **Profile → Mobile Authentication → Download token file** and save it as `files/tokens.json` (shape in `tokens.example.json`). The bridge reads it on every request and **refreshes it automatically** ~10 minutes before expiry, persisting the rotated refresh token back to the file. This survives well beyond the 1-hour access-token window.
60+
61+
> The refresh endpoint authenticates with the *access token itself*, so once a downloaded token file fully expires it cannot be refreshed — download a fresh `tokens.json` from OAC.
62+
63+
---
64+
65+
## Architecture
66+
67+
```
68+
Browser (React + Recharts)
69+
│ POST /api/oac-demo { prompt, previousResponseId, ... }
70+
71+
Next.js API route (files/src/app/api/oac-demo/route.js)
72+
│ spawns one process per request, JSON over stdin/stdout
73+
74+
Python bridge (files/scripts/oac_react_api.py)
75+
├──► OCI Generative AI Responses API (model reasoning, function calling)
76+
└──► OAC MCP server /api/mcp (JSON-RPC 2.0: initialize, tools/list, tools/call)
77+
```
78+
79+
The model never talks to OAC directly. The bridge advertises the three OAC MCP tools to the model as function tools, executes each requested call against the OAC MCP JSON-RPC endpoint, compacts the result (large metadata is trimmed before it re-enters the context), and chains it back to the Responses API with `previous_response_id`. After the loop finishes, the bridge extracts the last Logical SQL result rows into a Recharts-friendly `{categoryKey, valueKeys, data}` payload for the UI.
80+
81+
Only three read-only OAC MCP tools are allowed:
82+
83+
| Tool | Purpose |
84+
|---|---|
85+
| `discover_data` | List governed Subject Areas and uploaded datasets |
86+
| `describe_data` | Inspect columns, measures, dimensions of one model |
87+
| `execute_logical_sql` | Run read-only Logical SQL against the governed model |
88+
89+
---
90+
91+
## Features
92+
93+
### Chat with chained sessions
94+
Follow-up questions keep `previous_response_id`, so "break that down by month" works. **New Session** resets the chain.
95+
96+
![Home](images/01-home.png)
97+
98+
### Governed analytics through MCP
99+
The assistant discovers the right subject area or dataset, describes only the relevant metadata, and builds Logical SQL from the exact described column names — including the `XSA('owner'.'Dataset')` syntax for uploaded datasets.
100+
101+
### Automatic result charts
102+
Any executed Logical SQL result with at least one category column and one numeric measure is rendered as a bar chart. A **Graph Demo** quick prompt renders a deterministic local chart to verify the UI path with no OAC access at all.
103+
104+
### Tool timeline + Logical SQL panel
105+
Every MCP call is listed with status, arguments, and compacted output; all executed Logical SQL statements are shown verbatim.
106+
107+
### Token diagnostics
108+
On 401/403 the bridge reports the token source used, whether the JWT is readable/expired, and whether the token audience matches the OAC host — the usual failure causes, stated directly in the error message.
109+
110+
### Display masking
111+
Dataset owner e-mails, OCIDs, and `XSA('owner'...)` identifiers are masked in the rendered UI so demo recordings don't leak identifiers.
112+
113+
---
114+
115+
## Project Structure
116+
117+
```
118+
oci-enterprise-ai-agents-oac-mcp-server/
119+
├── README.md
120+
├── LICENSE
121+
├── images/ # screenshots used in this README
122+
└── files/
123+
├── package.json # next, react, recharts, react-markdown, remark-gfm, lucide-react
124+
├── next.config.mjs
125+
├── jsconfig.json
126+
├── .env.example # copy to .env and fill in
127+
├── tokens.example.json # shape of the downloaded OAC token file
128+
├── src/app/
129+
│ ├── layout.js # minimal root layout
130+
│ ├── globals.css
131+
│ ├── page.js # the assistant UI (chat, stages, chart, timeline)
132+
│ ├── page.module.css
133+
│ └── api/oac-demo/route.js # spawns the Python bridge per request
134+
└── scripts/
135+
├── oac_react_api.py # JSON bridge: Responses API + OAC MCP + chart builder
136+
├── refresh_oac_tokens.py # CLI + importable helper for the tokens.json refresh flow
137+
└── requirements.txt # openai, requests
138+
```
139+
140+
---
141+
142+
## Data Flow
143+
144+
### One chat turn
145+
146+
1. UI POSTs `{action: "chat", prompt, previousResponseId, ...}` to `/api/oac-demo`
147+
2. The route spawns `scripts/oac_react_api.py` and writes the payload to stdin
148+
3. The bridge resolves an OAC token (see below) and preflights `initialize` against the OAC MCP endpoint — 401/403 fail fast with token diagnostics
149+
4. The bridge lists the OAC MCP tools and exposes them to the model as function tools via the Responses API
150+
5. Each model tool call is executed by the bridge against OAC MCP (`tools/call`), compacted, and returned to the model; the loop repeats until the model produces a final answer
151+
6. The bridge extracts Logical SQL, builds the chart payload from the last SQL result rows, and emits one JSON object to stdout; the route returns it to the UI
152+
153+
### Token resolution order
154+
155+
1. `accessToken` pasted in the UI payload
156+
2. `files/tokens.json` — used directly while valid, auto-refreshed (and persisted) when within 10 minutes of expiry
157+
3. `OAC_ACCESS_TOKEN` / `MCP_BEARER_TOKEN` environment variables
158+
159+
### Chart building
160+
161+
`build_chart_payload` walks the tool timeline backwards to the last successful `execute_logical_sql`, normalizes rows (JSON batches, column/value arrays, or markdown tables), picks the first text column as category and up to three numeric columns as series, and caps the data at 40 rows.
162+
163+
---
164+
165+
## OCI API endpoints used
166+
167+
### Generative AI (inference)
168+
- `POST {GENAI_BASE_URL}/responses` — model reasoning with function tools, chained via `previous_response_id` (OpenAI-compatible Responses API; authenticated with `GENAI_API_KEY` + `GENAI_PROJECT_ID`)
169+
170+
### Oracle Analytics Cloud
171+
- `POST {OAC_MCP_SERVER_URL}` — MCP JSON-RPC 2.0: `initialize`, `tools/list`, `tools/call` (Bearer token)
172+
- `POST .../api/dv/api/v1/tokens/token/refresh` — refreshes a downloaded token file: current access token in the `Authorization` header, refresh token as `text/plain` body; returns rotated `accessToken`/`refreshToken`
173+
174+
---
175+
176+
## Commands
177+
178+
```bash
179+
npm run dev # Dev server (Turbopack)
180+
npm run build # Production build
181+
npm run start # Production server
182+
npm run lint # ESLint
183+
184+
# Refresh a downloaded token file manually
185+
.venv/bin/python scripts/refresh_oac_tokens.py tokens.json
186+
187+
# Smoke-test the bridge without the UI
188+
echo '{"action":"config"}' | .venv/bin/python scripts/oac_react_api.py
189+
```
190+
191+
---
192+
193+
## Troubleshooting
194+
195+
**`OAC MCP initialize returned HTTP 401/403`**
196+
The token is expired, malformed, or issued for a different OAC instance. The error includes the token source used and JWT diagnostics. Fix: paste a fresh access token, or download a new `tokens.json` from the *same* OAC instance as `OAC_MCP_SERVER_URL`.
197+
198+
**`401` from the token refresh endpoint**
199+
The refresh flow authenticates with the access token itself, so a fully expired `tokens.json` cannot be refreshed. Download a fresh one from OAC.
200+
201+
**`Missing oacMcpUrl.`**
202+
`OAC_MCP_SERVER_URL` is not set in `files/.env` and no URL was supplied by the UI.
203+
204+
**`Python bridge returned no JSON.`**
205+
The route couldn't run the bridge. Ensure the venv exists at `files/.venv` (or set `PYTHON_BIN`) and that `pip install -r scripts/requirements.txt` succeeded. The stderr excerpt is included in the error response.
206+
207+
**Pasted MCP URL ends in `/ui/api/mcp`**
208+
That's the browser UI route; programmatic calls get a 302 to the login page. The bridge rewrites it to `/api/mcp` automatically.
209+
210+
**`EADDRINUSE: address already in use`**
211+
Another dev server holds port 3000. Run `npm run dev -- --port 3001`.
212+
213+
**No chart appears after an analysis**
214+
The chart needs at least one text/category column and one numeric measure in the *executed* SQL result. The chart panel explains which condition failed (no SQL executed, SQL failed, or no plottable rows).
215+
216+
---
217+
218+
## Tech Stack
219+
220+
- **Framework** — Next.js 16 (App Router, Turbopack)
221+
- **UI** — React 19, Lucide icons, CSS Modules
222+
- **Charts** — Recharts
223+
- **Markdown** — react-markdown + remark-gfm
224+
- **Bridge** — Python 3, `openai` (OCI Responses API), `requests` (OAC MCP JSON-RPC + token refresh)
225+
- **Protocols** — MCP (JSON-RPC 2.0 over streamable HTTP), OpenAI-compatible Responses API
226+
227+
---
228+
229+
## License
230+
231+
Copyright (c) 2026 Oracle and/or its affiliates.
232+
233+
Licensed under the Universal Permissive License (UPL), Version 1.0.
234+
235+
See [LICENSE](LICENSE) for more details.
236+
237+
ORACLE AND ITS AFFILIATES DO NOT PROVIDE ANY WARRANTY WHATSOEVER, EXPRESS OR IMPLIED, FOR ANY SOFTWARE, MATERIAL OR CONTENT OF ANY KIND CONTAINED OR PRODUCED WITHIN THIS REPOSITORY, AND IN PARTICULAR SPECIFICALLY DISCLAIM ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. FURTHERMORE, ORACLE AND ITS AFFILIATES DO NOT REPRESENT THAT ANY CUSTOMARY SECURITY REVIEW HAS BEEN PERFORMED WITH RESPECT TO ANY SOFTWARE, MATERIAL OR CONTENT CONTAINED OR PRODUCED WITHIN THIS REPOSITORY. IN ADDITION, AND WITHOUT LIMITING THE FOREGOING, THIRD PARTIES MAY HAVE POSTED SOFTWARE, MATERIAL OR CONTENT TO THIS REPOSITORY WITHOUT ANY REVIEW. USE AT YOUR OWN RISK.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# OCI Generative AI — OpenAI-compatible Responses API endpoint for your region.
2+
# Example: https://inference.generativeai.eu-frankfurt-1.oci.oraclecloud.com/openai/v1
3+
GENAI_BASE_URL=<oci-genai-openai-compatible-base-url>
4+
GENAI_API_KEY=<oci-genai-api-key>
5+
GENAI_PROJECT_ID=<ocid1.generativeaiproject.oc1...>
6+
7+
# Oracle Analytics Cloud MCP endpoint of your OAC instance.
8+
OAC_MCP_SERVER_URL=https://<your-oac-instance>.analytics.ocp.oraclecloud.com/api/mcp
9+
10+
# OAC token refresh endpoint (same instance as above).
11+
OAC_TOKEN_REFRESH_URL=https://<your-oac-instance>.analytics.ocp.oraclecloud.com/api/dv/api/v1/tokens/token/refresh
12+
13+
# Option A: paste a current OAC access token (expires after ~1 hour).
14+
# OAC_ACCESS_TOKEN=<oac-access-token>
15+
16+
# Option B (recommended): download tokens.json from OAC (Profile > Mobile
17+
# Authentication > Download token file) and place it next to this file as
18+
# tokens.json. The bridge refreshes it automatically before it expires.
19+
# OAC_TOKENS_FILE=tokens.json
20+
21+
# Optional: python interpreter used by the Next.js API route.
22+
# Defaults to .venv/bin/python inside this folder, then python3 on PATH.
23+
# PYTHON_BIN=/usr/bin/python3
24+
25+
# Optional: model + region defaults used by the bridge.
26+
# OCI_RESPONSES_MODEL=openai.gpt-oss-120b
27+
# OCI_REGION=eu-frankfurt-1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"paths": {
4+
"@/*": ["./src/*"]
5+
}
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) 2026 Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
4+
/** @type {import('next').NextConfig} */
5+
const nextConfig = {};
6+
7+
export default nextConfig;

0 commit comments

Comments
 (0)