siebel-mcp-gateway
README.md
# Siebel MCP Gateway
Exposes the Oracle Siebel REST API as MCP tools over streamable HTTP, so an
agentic client can query/create/update/delete Siebel records and pull the
object catalog without holding Siebel credentials itself.
Mock mode models a synthetic healthcare-referral demo schema (patient →
community/hospital referral → Form 17 commitment → treatment history),
built to faithfully carry a set of documented, deliberate data-quality
findings rather than smooth them over — duplicate patient records across
two orgs, a status field that actually holds urgency, a script that
silently overrides a Workflow's stated limits, two "visits remaining"
fields that drift apart. All data is synthetic.
## Stack
Python 3.12+, the official `mcp` SDK (`MCPServer`, the current name for what
used to be called `FastMCP` in older SDK versions), `httpx` for outbound
Siebel calls, `uvicorn` as the ASGI server.
## Local run
```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Fill in .env, or for a first run without a live Siebel instance:
# MOCK_MODE=true
# MCP_GATEWAY_TOKEN=<any string you'll also give your client>
MOCK_MODE=true MCP_GATEWAY_TOKEN=dev-token \
uvicorn app.server:app --host 0.0.0.0 --port 8000
```
Health check: `curl http://localhost:8000/healthz` → `{"status":"ok"}` (no
auth required, so platform health checkers work).
MCP endpoint: `http://localhost:8000/mcp` — every request needs
`Authorization: Bearer <MCP_GATEWAY_TOKEN>`, since the endpoint itself has no
other access control once deployed publicly.
## Tests
```bash
python3 -m pytest -v
```
All tests run against the in-memory mock store or a mocked HTTP transport —
no network calls, no live Siebel instance required.
## Deploying to Render
1. Push this repo to GitHub.
2. **Grant Render access to the repo first, if it's not already connected.**
Render's GitHub App only sees repos it's been explicitly given access to
— a brand-new repo won't show up in Render's repo picker just because
you own it. Go to
[github.com/settings/installations](https://github.com/settings/installations)
→ find **Render** → **Configure** → either switch to "All repositories"
or add this repo to the allowed list → Save. Only then will it appear
back in Render's connect screen.
3. In the Render dashboard: **New → Blueprint** (not "Web Service" — this
repo has a `render.yaml`, and Blueprint is what reads it). Connect the
repo, confirm branch `main` and the default `render.yaml` path.
4. Render shows a form for every env var marked `sync: false` in
`render.yaml` — fill these in before deploying:
- `MCP_GATEWAY_TOKEN` — generate one, e.g. `openssl rand -hex 32`
- `MOCK_MODE` — `true` to start serving mock data immediately (recommended
while the real Siebel instance isn't ready yet), `false` if you already
have real Siebel credentials to enter below
- `SIEBEL_BASE_URL` / `SIEBEL_USERNAME` / `SIEBEL_PASSWORD` — only
required if `MOCK_MODE=false`; leave blank if starting in mock mode
5. Click **Deploy Blueprint**. Render assigns `https://<your-service>.onrender.com`.
To change any of these later (e.g. flip `MOCK_MODE` once the real Siebel
instance is ready): open the service (not the Blueprint) → **Environment**
tab → edit the value → **Save Changes**, which triggers a redeploy.
## Pointing your MCP client at the deployed gateway
- URL: `https://<your-service>.onrender.com/mcp`
- Transport: streamable HTTP
- Auth: a static bearer token/API key, not OAuth — set the header to
`Authorization: Bearer <MCP_GATEWAY_TOKEN>` (the same value from step 4
above). If your client's auth UI wants a header name and a raw value
separately rather than one combined header, header name is `Authorization`
and value is `Bearer <token>` (include the word "Bearer") — if that gets
a 401, try giving it just the raw token instead, since some clients add
the `Bearer ` prefix themselves.
## Notes from actually deploying this
- **The mock store is in-memory only.** Anything created/updated/deleted
during a session persists only as long as that server process stays up.
A redeploy, or Render's free-tier instance spinning down after ~15
minutes idle and cold-starting on the next request, resets it back to
the original seeded data. That's expected mock-mode behavior, not a bug.
- **The `mcp` Python SDK's client-side transport dependency is `httpx2`**,
not plain `httpx` — only relevant if you're writing your own MCP client
against this gateway using the SDK's `streamable_http_client` helper
rather than a higher-level client app; it expects an `httpx2.AsyncClient`
for the `http_client=` argument, not a regular `httpx.AsyncClient`.
## Flip-to-live checklist
Once the real Siebel instance is up:
- [ ] Set `SIEBEL_BASE_URL` to the real instance (no trailing slash), e.g.
`https://<siebel-host>/siebel/v1.0`
- [ ] Set `SIEBEL_USERNAME` / `SIEBEL_PASSWORD`
- [ ] Set `SIEBEL_VERIFY_TLS=false` only if the instance is still on a
self-signed cert — flip back to `true` once it has a real one
- [ ] Set `MOCK_MODE=false`
- [ ] Redeploy, then smoke-test with `siebel_list_objects` and
`search_facilities` before pointing real agent traffic at it
## Tools
Generic (work against any Business Component: `Contact`, `Employee`,
`Medical Facility`, `Appointment Slot`, `Referral Request`,
`Commitment Form`, `Treatment History`):
| Tool | Purpose |
|---|---|
| `siebel_query` | List/search records: `searchspec`, `fields`, `page_size`, `start_row` |
| `siebel_get` | Fetch one record by `row_id` |
| `siebel_create` | Create a record from a `fields` dict |
| `siebel_update` | Update a record's `fields` by `row_id` |
| `siebel_delete` | Delete a record by `row_id` |
| `siebel_list_objects` | List the business components the account exposes |
Convenience wrappers, thinner surface for common demo asks:
| Tool | Purpose |
|---|---|
| `search_facilities` | By specialty code and/or exact city |
| `search_contacts` | By last-name prefix |
| `create_referral` | Patient + doctor + specialty + urgency; starts at Stage Code = COMMUNITY_SEARCH |
## Notes on the Siebel REST API assumptions baked in here
- Auth is HTTP Basic on every outbound call (separate from this gateway's
own bearer-token check on inbound MCP requests — two different auth
layers, don't conflate them).
- URL grammar is `{BASE}/data/{BusinessObject}/{BusinessComponent}`. BO and
BC are **not** always the same name here — e.g. `Referral Request` is a
child BC under the `Patient Referral` BO, `Appointment Slot` is a child
BC under `Appointment Management`. Tools take the BC name; the client
looks up the right BO internally. Path segments are URL-encoded, so
multi-word names work.
- List responses arrive as `{"items": [...]}`; the `"links"` array on each
record is stripped before returning to the model, to save tokens.
- Non-2xx responses are surfaced as the HTTP status code plus Siebel's own
message text; a 401 gets a clear "check Siebel credentials" prefix.
Outbound calls time out at 30s.
- Several fields are **computed, not stored** (Age, Days Waiting, Visits
Remaining, Is Expired, Entry Gap Days, and the Facility/Doctor/Patient
join fields) — they're derived fresh on every read, matching how they'd
behave as real Business Component calculated/join fields rather than
physical columns.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues