Oracle Discovery API
Point Claude, or any function-calling LLM, at a running Oracle and let it use C3's cross-project code & memory intelligence as tools. Discover projects, search code and memory across all of them, traverse the memory graph, and surface insights, without ever opening the chat UI. The same Oracle process also serves a companion mobile app and the C3 Desk desktop tray client over a separate, capability-gated /api/mobile/* surface.
Overview
The Oracle normally runs the LLM relationship inwards: its own model (Ollama) calls C3 tools to analyze your projects. The Discovery API flips that — it exposes those same capabilities outwards, so an external LLM can use the Oracle as a discovery tool over your whole C3 estate.
Two transports, one tool core. The exact same tool registry is served over both MCP (streamable HTTP/SSE, native for Claude Code / Claude Desktop) and an OpenAPI REST surface (for any function-calling LLM). They run on separate loopback ports because Flask is WSGI and the MCP transport is ASGI, but they dispatch through one definition of each tool — so the two surfaces can never drift apart.
What you get
| Surface | Where | Use it for |
|---|---|---|
| MCP server | http://127.0.0.1:3332/mcp |
Native tool discovery for Claude Code / Desktop and any MCP client |
| REST + OpenAPI | http://127.0.0.1:3331/api/discovery |
Function-calling for any LLM (GPT, Gemini, custom agents) |
c3 oracle api CLI |
Terminal | Get the token + a ready-to-paste .mcp.json snippet; rotate / clear the key |
Safe by construction. Only read and safe-action tools are exposed — there are no code-editing tools on this surface. Both servers bind 127.0.0.1 (loopback) by default and require a Bearer token.
Quick start
Start the Oracle, grab your token, and connect a client.
Start the Oracle
c3 oracle serve --no-browser
# or: run it at login, no terminal window
c3 oracle serve --install
Prints the REST + MCP URLs and ensures an API key exists. The MCP server starts in a background thread. --install (v2.91.0) registers a windowless background service (Task Scheduler with a Run-key fallback on Windows, a LaunchAgent on macOS, a systemd user unit on Linux) and starts it now, so the Oracle comes back on its own at login. The same buttons live in Hub → Settings → Oracle service, which also fills in the Hub's Oracle URL automatically once the service starts.
Get the token
c3 oracle api info
Prints the Bearer token, both URLs, and a ready-to-paste Claude .mcp.json entry. Use c3 oracle api key for just the token. You can also generate, rotate, and copy the token visually in the Oracle dashboard under Settings → Discovery API.
Connect
# Claude → paste the snippet
# Any LLM → fetch openapi.json
See the two sections below for the MCP config and the OpenAPI flow.
Connect Claude (MCP)
Add the entry from c3 oracle api info to your .mcp.json (project root) or Claude Desktop config:
{
"mcpServers": {
"c3-oracle": {
"type": "http",
"url": "http://127.0.0.1:3332/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
Claude lists the c3-oracle tools (list_projects, c3_search_cross, query_memory, read_graph, …). Ask it to "discover what C3 projects exist and search them for X".
Connect any LLM (OpenAPI REST)
Every tool is also a plain REST operation. Point a function-calling LLM at the spec, then call tools with the Bearer header.
# Discover the tools (OpenAPI 3.1)
curl -H "Authorization: Bearer <token>" \
http://127.0.0.1:3331/api/discovery/openapi.json
# Invoke a tool by name
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"tool":"list_projects","args":{}}' \
http://127.0.0.1:3331/api/discovery/call
# Or use the per-tool path (body = the tool's args)
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"query":"rate limiter","top_k":5}' \
http://127.0.0.1:3331/api/discovery/tools/c3_search_cross
REST endpoints
All under /api/discovery; every request requires Authorization: Bearer <token>.
| Endpoint | Description |
|---|---|
GET /tools | Available tools, each with a JSON Schema and capability tier |
POST /call | Invoke any tool: body {"tool": name, "args": {…}} |
POST /tools/<name> | Invoke a named tool with the request body as its arguments object |
POST /call/stream | Same as /call but streams start → result|error → [DONE] as SSE |
GET /openapi.json | OpenAPI 3.1 document — one operation per tool |
GET /mcp-info | MCP URL + auth scheme for the streamable-HTTP transport |
Tools & capability tiers
The exposed set is capped by api_max_tier in ~/.c3/oracle/config.json (read or action). Code-editing tools are never registered.
Read tier — discovery
| Tool | Does |
|---|---|
list_projects | All registered C3 projects with paths + fact counts |
search_facts | Search memory facts across ALL projects |
query_memory | Search/list facts within one project |
project_health | Memory health check for a project |
analyze_project | LLM-powered analysis of a project's memory themes |
cross_insights | Cross-project insights |
read_graph | Memory-graph statistics for a project |
c3_search / c3_search_cross | Code-intelligence search (one project / all projects) |
c3_read | Read exact file content (symbols / line ranges) |
c3_compress | Token-efficient structural file map |
c3_validate | Syntax/type validation on a file |
c3_status | Project health / budget / sessions overview |
c3_memory_query | Read-only memory query (recall/list/score/graph/trends) |
c3_edits / c3_edits_cross | Query the edit ledger (one / all projects) |
activity_report | Cross-project daily digest: sessions, tool calls, edits, git mutations, token/cost (optional LLM narration) |
Action tier — safe writes (no code edits)
| Tool | Does |
|---|---|
suggest_action | Create a pending memory suggestion a human approves (not a direct write) |
delegate_task | Run a configured Oracle agent and return its result |
Mobile & desktop companion apps
The same Oracle process also serves /api/mobile/* — a separate, Bearer-gated surface for the C3 mobile app and the C3 Desk desktop tray client (oracle/services/mobile_api.py and friends). It is capability-gated: GET /api/mobile/info reports what this Oracle build actually exposes, so a client hides a feature it cannot use instead of discovering it by trial and error. api_version is currently 5.
| Capability | Since | What it adds |
|---|---|---|
| Core gateway | v2.68.0 | Cross-project activity feed, project overview + health, PM board read/write, daily digest, notification ack, and a network-reachable credential vault (list/get/check/set/delete, allowlist-serialized — never a reveal route) and Access Guard (rules, masks, path checks, tool discipline). Bearer required on every method, GETs included. |
feed_wait | v2.73.0 | GET /api/mobile/feed?wait=0..30 holds the request open until something new matches the watermark, instead of the app polling every 15s. Requires since; refused with before. Waiters capped at 4; past the cap it degrades to an immediate answer. |
| Ops + chat | v2.76.0 | edits, locks, status, insights, suggestions, review routes — the phone can read state, not just events. Chat is poll-based, not SSE: POST /chat/turn, GET /turn/<id>?after=, DELETE /turn/<id>, so a backgrounded app can resume a turn instead of losing it when the OS kills the socket. |
| Jobs + CI | v2.126.0 | GET /api/mobile/jobs (read-only c3_shell_job rows), GET /api/mobile/ci/runs / ci/run (local CI history). |
Per-client tokens and the loopback listener (v2.125.0)
Before v2.125.0 the gateway had one credential, the Discovery token, shared by every client. Now each client (a phone, or the C3 Desk tray app) can carry its own token:
POST /api/mobile/clientsmints one. It has no Bearer of its own; it requires a local caller plus the owner-only~/.c3/oracle/bootstrap.key, and returns the token exactly once.GET /api/mobile/clientslists issued clients (hashes never included);DELETE /api/mobile/clients/<id>revokes one immediately.- Tokens are stored as SHA-256 hashes in
~/.c3/oracle/clients.json, owner-only ACL. The Discovery token still works everywhere — it maps to a syntheticmobileclient so existing phones keep working unchanged. - Dashboard: Settings → Mobile app → Show pairing code now mints a per-device token for the QR rather than embedding the Discovery token, so rotating the Discovery token no longer un-pairs every phone. A Paired devices list lets you revoke one by one.
A loopback listener (oracle/listeners.py, config key loopback_listener, default true) solves a specific problem: if bind_host is set to a specific non-loopback address (a Tailscale IP, say), http://127.0.0.1 used to be refused even from the Oracle's own machine — which broke the same-box C3 Desk client. When bind_host is non-loopback, the same Flask app now also listens on 127.0.0.1 on the same port, with the same Host allowlist and Bearer gates. A wildcard or already-loopback bind is unchanged.
Event notifications and session boundaries (v2.126.0)
A finished background job, a local-CI run, a session ending, or the MCP runtime coming ready each write one notification with a machine-readable kind and a ref_id (services/notifications.py, EVENT_KINDS):
shell_job | ref_id = job id. info for done; warning for failed/timeout/cancelled/lost. |
ci | ref_id = run id. info for FULL_CI_PASS; warning for PARTIAL_PASS/FAIL. |
session | ref_id = host session id. "Session started" / "Session ended", from new SessionStart/SessionEnd hooks. |
mcp | ref_id = C3 session id. "C3 connected" once the MCP runtime is ready to serve — what a desktop "MCP failed to connect" indicator keys on. |
Every producer is best-effort: a notification failure never fails the job, run, hook or server it reports on. override.channel (mobile / desktop / both) is also consumed here, so the desktop client only toasts an override request meant for it.
Authentication & security
A single Bearer token guards both Discovery transports. The REST surface checks it in a Flask before_request hook; the MCP transport checks it in a pure-ASGI middleware (so streaming responses pass through intact). The mobile gateway (previous section) authenticates separately, with the Discovery token or a per-client token.
Where the token lives
| OS keyring | Token text, under service c3-oracle-api. Backed by Windows Credential Manager, macOS Keychain, or Linux Secret Service. |
C3_ORACLE_API_KEY |
Environment override for headless / CI / containers where no keyring backend exists. When set, it wins and is never persisted to the keyring. |
Rate limiting and audit logging (v2.64.0)
Tool-executing routes (/call, /call/stream, /tools/<name>) sit behind a per-caller token bucket — default 60 calls/min with a quarter-minute burst (api_rate_limit_per_min, api_rate_burst; 0 disables). Listing tools, the OpenAPI document, and mcp-info stay unthrottled. Exhaustion answers 429 with Retry-After.
Every tool call also appends one line to ~/.c3/oracle/discovery_audit.jsonl (api_audit_enabled), readable via GET /api/activity/discovery. The log stores a hash of the arguments, never the arguments themselves, and identifies the caller by token fingerprint rather than the token. Auditing fails open — a broken log never breaks a call.
Exposing on a network. The default bind is 127.0.0.1. To let a remote LLM service reach the Oracle, set bind_host to 0.0.0.0 in ~/.c3/oracle/config.json — and put it behind TLS + a firewall yourself. The token alone is not a substitute for transport security.
Rotate or revoke at any time with c3 oracle api rotate / c3 oracle api clear.
CLI commands
| Command | Description |
|---|---|
c3 oracle api info | Print REST + MCP URLs, the token, and a ready-to-paste .mcp.json snippet |
c3 oracle api key | Print just the token (generating one if needed) |
c3 oracle api rotate | Replace the token with a fresh one |
c3 oracle api clear | Delete the stored token from the keyring |
c3 oracle serve | Run the dashboard + discovery endpoints in this terminal (--no-browser, --port) |
c3 oracle serve --install | Register the Oracle as a login/startup service (no terminal window) and start it now. Windows: Task Scheduler (Run-key fallback); macOS: launchd; Linux: systemd user unit |
c3 oracle serve --uninstall | Remove the startup registration (a running Oracle keeps running) |
c3 oracle serve --status | Installed? Running? Port, bind host, URL, and the launcher log path |
c3 oracle open | Sign in to a running dashboard via a single-use URL (the hub's Open Oracle button does the same thing) |
The Hub exposes the same controls under Settings → Oracle service (Install / Uninstall / Start / Stop / Open). Starting the Oracle from there also fills in the Hub's Oracle URL when it is empty, so the Open Oracle button appears in the top bar. That button opens a signed-in dashboard: it redeems the Oracle's owner-only bootstrap key for you. Reaching the dashboard by pasting its URL instead leaves it read-only — the page renders and the header looks healthy, but chat, Save and Test Ollama answer 401 unauthorized.
~/.c3/oracle/config.json keys
bind_host | Interface to bind. Default 127.0.0.1. Use 0.0.0.0 to expose on a network (add TLS/firewall). |
loopback_listener | When bind_host is a specific non-loopback address, also serve the same app on 127.0.0.1 (same Host allowlist, same Bearer gates). Default true (v2.125.0). |
allowed_hosts | Extra hostnames/IPs the Host + Origin guard accepts. Needed when bind_host is non-loopback so legitimate browsers/clients aren't blocked (v2.33.0). |
api_enabled | Serve the /api/discovery/* REST surface. Default true. |
api_require_auth | Require the Bearer token. Default true. |
api_max_tier | Cap exposed tools: read (discovery only) or action (adds suggest_action, delegate_task). Default action. |
api_rate_limit_per_min | Per-caller token-bucket rate for tool-executing Discovery routes. Default 60; 0 disables (v2.64.0). |
api_audit_enabled | Append a hashed-argument audit line per Discovery tool call to discovery_audit.jsonl. Default true (v2.64.0). |
mcp_enabled | Start the MCP HTTP/SSE server. Default true. |
mcp_port | MCP transport port. Default 3332. |
Troubleshooting
| Symptom | Cause & fix |
|---|---|
401 unauthorized |
Missing or wrong token. Get the current one with c3 oracle api key and send it as Authorization: Bearer <token>. |
| Claude doesn't list the tools | Confirm the Oracle is running and mcp_enabled is true; check the URL is http://127.0.0.1:3332/mcp and the Authorization header is present in .mcp.json. |
| Tool not found in the catalog | It may be above your api_max_tier (e.g. suggest_action needs action), or it is a code-editing tool — those are never exposed. |
| Need a token without a keyring | Set C3_ORACLE_API_KEY in the environment (CI / containers). It overrides the keyring and is not persisted. |
The 'keyring' package is required |
pip install keyring — a C3 dependency that may be missing if you upgraded without re-installing. |
| Oracle is down after a reboot | Nothing restarts a terminal-launched Oracle. Register it once with c3 oracle serve --install (or Hub → Settings → Oracle service → Install); it then starts at login with no window. Startup problems land in ~/.c3/oracle/service.log (the launcher) and ~/.c3/oracle/oracle.log (the server). |
| Installed, but "stopped" right after login | If bind_host is a LAN/VPN address, the launcher waits up to 120 s for that address to come up before binding, and Task Scheduler retries a failed start three times a minute apart. Check service.log for waiting for bind host; if the address never appears, the Oracle starts anyway and fails to bind — fix the interface or set bind_host to 0.0.0.0. |
Full endpoint and schema details live in oracle-guide/api-reference.md and oracle-guide/discovery-api.md.