code-context-control 2.30.0__py3-none-any.whl → 2.32.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cli/c3.py CHANGED
@@ -85,7 +85,7 @@ console = Console() if HAS_RICH else None
85
85
  # Config
86
86
  CONFIG_DIR = ".c3"
87
87
  CONFIG_FILE = ".c3/config.json"
88
- __version__ = "2.30.0"
88
+ __version__ = "2.32.2"
89
89
 
90
90
 
91
91
  def _command_deps() -> CommandDeps:
@@ -239,6 +239,7 @@ _C3_MCP_ALLOW = [
239
239
  "mcp__c3__c3_memory", "mcp__c3__c3_validate", "mcp__c3__c3_edit",
240
240
  "mcp__c3__c3_agent", "mcp__c3__c3_delegate", "mcp__c3__c3_edits",
241
241
  "mcp__c3__c3_impact", "mcp__c3__c3_shell", "mcp__c3__c3_bitbucket",
242
+ "mcp__c3__c3_project",
242
243
  ]
243
244
 
244
245
  # Obsolete MCP tool names from earlier C3 versions. `c3 permissions clean`
@@ -4492,6 +4493,7 @@ back to native tools as the task progresses.
4492
4493
  - **Memory**: `c3_memory(action='recall')` — full recall. `index` + `fetch` for token-efficient two-step retrieval
4493
4494
  - **Delegate**: `c3_delegate(task, backend='ollama|codex|gemini|claude|auto')` — offload to other models
4494
4495
  - **Bitbucket** (v2.30.0+, when `c3 bitbucket login` has run): `c3_bitbucket(action='list_prs|get_pr|merge_pr|...')` — self-hosted Bitbucket Data Center / Server. Token in OS keyring; mutating actions auto-log to the edit ledger.
4496
+ - **Cross-project** (v2.31.0+): `c3_project(action='list|scan|search|read|edit|...', project='<name|path>')` — discover and operate on OTHER c3-installed projects. Reads run freely; writes (edit/shell/memory) need `allow_write=true`.
4495
4497
 
4496
4498
  ## Self-Check
4497
4499
  If you haven't called a c3_* tool in several turns during active development, re-engage
@@ -5556,6 +5558,60 @@ def _bb_cmd_set_default(args, project_path: str) -> None:
5556
5558
  print(f"[OK] Default repo: {args.project}/{args.repo}")
5557
5559
 
5558
5560
 
5561
+ def cmd_oracle(args):
5562
+ """Oracle Discovery API key + connection management."""
5563
+ sub = getattr(args, "oracle_cmd", None)
5564
+ if sub != "api":
5565
+ print("Usage: c3 oracle api {info,key,rotate,clear}")
5566
+ return
5567
+
5568
+ from oracle.config import load_config
5569
+ from oracle.mcp_oracle import mcp_url
5570
+ from oracle.services import api_auth
5571
+
5572
+ action = getattr(args, "action", "info") or "info"
5573
+
5574
+ if action == "rotate":
5575
+ print("Rotated. New Discovery API key:")
5576
+ print(f" {api_auth.rotate()}")
5577
+ return
5578
+ if action == "clear":
5579
+ removed = api_auth.clear()
5580
+ print("Discovery API key cleared." if removed else "No stored Discovery API key to clear.")
5581
+ return
5582
+
5583
+ key = api_auth.get_or_create_key()
5584
+ if action == "key":
5585
+ print(key)
5586
+ return
5587
+
5588
+ cfg = load_config()
5589
+ host = cfg.get("bind_host", "127.0.0.1")
5590
+ disp_host = "127.0.0.1" if host in ("0.0.0.0", "") else host
5591
+ rest_port = getattr(args, "port", None) or cfg.get("port", 3331)
5592
+ mcp_port = getattr(args, "mcp_port", None) or cfg.get("mcp_port", 3332)
5593
+ rest_base = f"http://{disp_host}:{rest_port}/api/discovery"
5594
+ url = mcp_url(host, mcp_port)
5595
+
5596
+ print("[oracle:api]")
5597
+ print(f" REST base : {rest_base}")
5598
+ print(f" OpenAPI : {rest_base}/openapi.json")
5599
+ print(f" MCP URL : {url}")
5600
+ print(f" Auth : Bearer {key}")
5601
+ print()
5602
+ print(" Claude .mcp.json entry:")
5603
+ snippet = {
5604
+ "mcpServers": {
5605
+ "c3-oracle": {
5606
+ "type": "http",
5607
+ "url": url,
5608
+ "headers": {"Authorization": f"Bearer {key}"},
5609
+ }
5610
+ }
5611
+ }
5612
+ print(json.dumps(snippet, indent=2))
5613
+
5614
+
5559
5615
  def cmd_projects(args):
5560
5616
  """Manage the global C3 project registry."""
5561
5617
  from services.project_manager import ProjectManager
@@ -6308,6 +6364,7 @@ def main():
6308
6364
  "projects": cmd_projects,
6309
6365
  "hub": cmd_hub,
6310
6366
  "bitbucket": cmd_bitbucket,
6367
+ "oracle": cmd_oracle,
6311
6368
  }
6312
6369
 
6313
6370
  cmd_func = commands.get(args.command)
cli/commands/parser.py CHANGED
@@ -322,4 +322,25 @@ def build_parser(version: str, parse_cli_ide_arg):
322
322
  bb_default.add_argument("--repo", required=True, help="Repository slug")
323
323
  bb_default.add_argument("project_path", nargs="?", default=".")
324
324
 
325
+ # ── Oracle Discovery API (v2.32.0) ──────────────────────────────────
326
+ p_oracle = subparsers.add_parser(
327
+ "oracle",
328
+ help="Oracle Discovery API key + connection management",
329
+ )
330
+ or_subs = p_oracle.add_subparsers(dest="oracle_cmd")
331
+ or_api = or_subs.add_parser(
332
+ "api",
333
+ help="Show connection info / manage the Discovery API key",
334
+ )
335
+ or_api.add_argument(
336
+ "action",
337
+ nargs="?",
338
+ default="info",
339
+ choices=["info", "key", "rotate", "clear"],
340
+ help="info (default): print REST+MCP URLs and a .mcp.json snippet; "
341
+ "key: print the token; rotate: replace it; clear: delete it",
342
+ )
343
+ or_api.add_argument("--port", type=int, default=None, help="Override REST port in printed info")
344
+ or_api.add_argument("--mcp-port", type=int, default=None, help="Override MCP port in printed info")
345
+
325
346
  return parser
cli/docs.html CHANGED
@@ -1148,7 +1148,7 @@ python cli/c3.py install-mcp . gemini</code></pre>
1148
1148
 
1149
1149
  <!-- ─── MCP Tools ───────────────────── -->
1150
1150
  <h2 id="mcp-tools">MCP Tools Reference</h2>
1151
- <p>C3 exposes 15 MCP tools. All core tools work without Ollama; delegate requires it. The Bitbucket integration is optional and activated via <code>c3 bitbucket login</code>.</p>
1151
+ <p>C3 exposes 16 MCP tools. All core tools work without Ollama; delegate requires it. The Bitbucket integration is optional and activated via <code>c3 bitbucket login</code>.</p>
1152
1152
 
1153
1153
  <h3>Discovery &amp; Compression</h3>
1154
1154
  <table>
cli/mcp_server.py CHANGED
@@ -699,6 +699,65 @@ async def c3_bitbucket(
699
699
  )
700
700
 
701
701
 
702
+ @mcp.tool()
703
+ async def c3_project(
704
+ action: str,
705
+ project: str = "",
706
+ query: str = "",
707
+ file_path: str = "",
708
+ symbols: Any = None,
709
+ lines: Any = None,
710
+ mode: str = "map",
711
+ view: str = "health",
712
+ top_k: int = 5,
713
+ max_tokens: int = 1200,
714
+ search_action: str = "code",
715
+ mem_action: str = "recall",
716
+ fact: str = "",
717
+ category: str = "",
718
+ fact_id: str = "",
719
+ edits_action: str = "history",
720
+ file: str = "",
721
+ tag: str = "",
722
+ limit: int = 50,
723
+ target: str = "",
724
+ old_string: str = "",
725
+ new_string: str = "",
726
+ summary: str = "",
727
+ edits: str = "",
728
+ replace_all: bool = False,
729
+ tags: str = "",
730
+ cmd: str = "",
731
+ timeout: int = 60,
732
+ scan_roots: str = "",
733
+ allow_write: bool = False,
734
+ ctx: Context = None,
735
+ ) -> str:
736
+ """CROSS-PROJECT — run C3 against OTHER c3-installed projects (read-only safe in plan mode).
737
+ Discover: list (registry), scan (registry+filesystem), info, register, unregister.
738
+ Read : search, read, compress, status, memory, impact, edits, validate, filter.
739
+ Write : edit, shell, memory(add/update/delete) — require allow_write=true; logged to that project's ledger.
740
+ project = registered name OR absolute path (.c3 required). list/scan need no project.
741
+ search_action/mem_action/edits_action pick the sub-op for those verbs."""
742
+ svc = _svc(ctx)
743
+
744
+ def finalize(fname, fargs, fresp, fsumm, **kw):
745
+ return _finalize_response(ctx, fname, fargs, fresp, fsumm, **kw)
746
+
747
+ from cli.tools.project import handle_project
748
+ return await asyncio.to_thread(
749
+ handle_project, action, svc, finalize,
750
+ project=project, query=query, file_path=file_path, symbols=symbols,
751
+ lines=lines, mode=mode, view=view, top_k=top_k, max_tokens=max_tokens,
752
+ search_action=search_action, mem_action=mem_action, fact=fact,
753
+ category=category, fact_id=fact_id, edits_action=edits_action, file=file,
754
+ tag=tag, limit=limit, target=target, old_string=old_string,
755
+ new_string=new_string, summary=summary, edits=edits,
756
+ replace_all=replace_all, tags=tags, cmd=cmd, timeout=timeout,
757
+ scan_roots=scan_roots, allow_write=allow_write,
758
+ )
759
+
760
+
702
761
  def main() -> None:
703
762
  """Entry-point for the ``c3-mcp`` console script."""
704
763
  from services import error_reporting
cli/tools/project.py ADDED
@@ -0,0 +1,287 @@
1
+ """c3_project tool -- run C3 against OTHER c3-installed projects.
2
+
3
+ Discovery and read ops run freely against any registered/.c3 project. Write ops
4
+ (``edit``, ``shell``, and memory mutations) require ``allow_write=True`` and are
5
+ recorded on the *target* project (its edit ledger + activity log), so a foreign
6
+ mutation leaves an audit trail in the project it touched.
7
+
8
+ The heavy lifting reuses the existing per-tool handlers unchanged -- only the
9
+ ``svc`` (a ``C3Runtime``) differs, supplied by the shared foreign-runtime cache.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ from pathlib import Path
15
+
16
+ from services.project_runtime import (
17
+ discover_projects,
18
+ resolve_project,
19
+ shared_cache,
20
+ )
21
+
22
+ # Memory sub-actions that mutate the target project's fact store.
23
+ _MEMORY_WRITE = {"add", "update", "delete", "consolidate", "consolidate_deep", "ground"}
24
+ # Dispatch verbs that mutate the target project.
25
+ _WRITE_OPS = {"edit", "shell"}
26
+ _DISCOVERY_OPS = {"list", "scan", "info", "register", "unregister"}
27
+ _READ_OPS = {
28
+ "search", "read", "compress", "status", "memory",
29
+ "impact", "edits", "validate", "filter",
30
+ }
31
+
32
+
33
+ def _foreign_finalize(_name, _args, resp, _summ="", **_kw):
34
+ """No-op finalize for proxied calls.
35
+
36
+ The home session's finalize wraps the whole ``c3_project`` response, so the
37
+ inner handlers must not also charge budget / log against either session.
38
+ """
39
+ return resp
40
+
41
+
42
+ def _foreign_facts(*_a, **_kw):
43
+ return ""
44
+
45
+
46
+ def _runtime_for(path: str):
47
+ """Indirection point (monkeypatched in tests) -> foreign ``C3Runtime``."""
48
+ return shared_cache().get(path)
49
+
50
+
51
+ # ── Discovery renderers ────────────────────────────────────────────────────
52
+
53
+
54
+ def _render_discovery(scan_roots_csv: str, do_scan: bool) -> str:
55
+ roots = [r.strip() for r in (scan_roots_csv or "").split(",") if r.strip()] or None
56
+ data = discover_projects(scan_roots=roots, scan=do_scan)
57
+ reg = data["registered"]
58
+ unreg = data["unregistered"]
59
+
60
+ out = [f"Registered C3 projects ({len(reg)}):"]
61
+ if not reg:
62
+ out.append(" (none -- c3_project(action='register', project='<path>') to add one)")
63
+ for p in reg:
64
+ flag = "" if p["accessible"] else " [MISSING]"
65
+ out.append(f" - {p['name']:<28} {p['ide']:<12} {p['path']}{flag}")
66
+
67
+ if do_scan:
68
+ out.append("")
69
+ out.append(f"Unregistered .c3 projects found nearby ({len(unreg)}):")
70
+ if not unreg:
71
+ out.append(" (none found near registered projects)")
72
+ for p in unreg:
73
+ out.append(f" - {p['name']:<28} {'':<12} {p['path']}")
74
+ if unreg:
75
+ out.append("")
76
+ out.append("Register one: c3_project(action='register', project='<path>')")
77
+ return "\n".join(out)
78
+
79
+
80
+ def _render_info(project: str) -> str:
81
+ try:
82
+ resolved = resolve_project(project)
83
+ except ValueError as e:
84
+ return f"[c3_project:error] {e}"
85
+ p = Path(resolved["path"])
86
+ out = [
87
+ f"Project: {resolved['name']}",
88
+ f" path : {resolved['path']}",
89
+ f" .c3 present : {(p / '.c3').is_dir()}",
90
+ f" accessible : {p.is_dir()}",
91
+ ]
92
+ try:
93
+ from services.project_manager import ProjectManager
94
+
95
+ details = ProjectManager().get_project_details(resolved["path"]) or {}
96
+ for key in ("ide", "c3_version", "facts_count", "last_session", "active"):
97
+ if key in details and details[key] not in (None, ""):
98
+ out.append(f" {key:<11} : {details[key]}")
99
+ except Exception:
100
+ pass
101
+ return "\n".join(out)
102
+
103
+
104
+ def _do_register(project: str) -> str:
105
+ if not (project or "").strip():
106
+ return "[c3_project:error] register requires project='<path>'."
107
+ path = Path(project).expanduser()
108
+ if not path.exists():
109
+ return f"[c3_project:error] Path does not exist: {project}"
110
+ if not (path / ".c3").is_dir():
111
+ return (
112
+ f"[c3_project:error] No .c3 directory in {path}. "
113
+ "Run 'c3 init' there first."
114
+ )
115
+ from services.project_manager import ProjectManager
116
+
117
+ entry = ProjectManager().add_project(str(path.resolve()))
118
+ return f"Registered: {entry['name']} ({entry['path']})"
119
+
120
+
121
+ def _do_unregister(project: str) -> str:
122
+ try:
123
+ resolved = resolve_project(project)
124
+ except ValueError as e:
125
+ return f"[c3_project:error] {e}"
126
+ from services.project_manager import ProjectManager
127
+
128
+ removed = ProjectManager().remove_project(resolved["path"])
129
+ return (
130
+ f"Unregistered: {resolved['name']}"
131
+ if removed
132
+ else f"Not in registry: {resolved['name']}"
133
+ )
134
+
135
+
136
+ # ── Proxied op dispatch ────────────────────────────────────────────────────
137
+
138
+
139
+ def _proxy(action, fsvc, *, query, file_path, symbols, lines, mode, view, top_k,
140
+ max_tokens, search_action, mem_action, fact, category, fact_id,
141
+ edits_action, file, tag, limit, target, old_string, new_string,
142
+ summary, edits, replace_all, tags, cmd, timeout, project_path):
143
+ if action == "search":
144
+ from cli.tools.search import handle_search
145
+
146
+ return handle_search(query, search_action, top_k, max_tokens,
147
+ fsvc, _foreign_finalize, _foreign_facts)
148
+ if action == "read":
149
+ from cli.tools.read import handle_read
150
+
151
+ return handle_read(file_path, symbols=symbols, lines=lines,
152
+ svc=fsvc, finalize=_foreign_finalize)
153
+ if action == "compress":
154
+ from cli.tools.compress import handle_compress
155
+
156
+ return handle_compress(file_path, mode, fsvc, _foreign_finalize, _foreign_facts)
157
+ if action == "status":
158
+ from cli.tools.status import handle_status
159
+
160
+ return handle_status(view, False, fsvc, _foreign_finalize)
161
+ if action == "memory":
162
+ from cli.tools.memory import handle_memory
163
+
164
+ return handle_memory(mem_action, query, fact, category, top_k,
165
+ fsvc, _foreign_finalize, fact_id=fact_id)
166
+ if action == "impact":
167
+ from cli.tools.impact import handle_impact
168
+
169
+ imode = mode if mode in ("symbol", "unstaged") else "symbol"
170
+ return handle_impact(target, file_path, imode, fsvc, _foreign_finalize)
171
+ if action == "edits":
172
+ from cli.tools.edits import handle_edits
173
+
174
+ return handle_edits(edits_action, file, "", "", "", tags, limit, "", "",
175
+ tag, fsvc, _foreign_finalize)
176
+ if action == "validate":
177
+ from cli.tools.validate import handle_validate
178
+
179
+ return asyncio.run(handle_validate(file_path, fsvc, _foreign_finalize))
180
+ if action == "filter":
181
+ from cli.tools.filter import handle_filter
182
+
183
+ return handle_filter(file_path, "", query, 100, "smart", False,
184
+ fsvc, _foreign_finalize)
185
+ if action == "edit":
186
+ from cli.tools.edit import handle_edit
187
+
188
+ return handle_edit(file_path, old_string, new_string, summary, tags,
189
+ replace_all, fsvc, _foreign_finalize, edits)
190
+ if action == "shell":
191
+ from cli.tools.shell import handle_shell
192
+
193
+ return asyncio.run(handle_shell(cmd, project_path, timeout, True, True,
194
+ fsvc, _foreign_finalize))
195
+ return f"[c3_project:error] Unhandled op '{action}'."
196
+
197
+
198
+ # ── Entry point ────────────────────────────────────────────────────────────
199
+
200
+
201
+ def handle_project(action, svc, finalize, *, project="", query="", file_path="",
202
+ symbols=None, lines=None, mode="map", view="health", top_k=5,
203
+ max_tokens=1200, search_action="code", mem_action="recall",
204
+ fact="", category="", fact_id="", edits_action="history",
205
+ file="", tag="", limit=50, target="", old_string="",
206
+ new_string="", summary="", edits="", replace_all=False,
207
+ tags="", cmd="", timeout=60, scan_roots="", allow_write=False):
208
+ action = (action or "").strip().lower()
209
+
210
+ def done(resp, summ="ok"):
211
+ return finalize("c3_project", {"action": action, "project": project},
212
+ resp, summ)
213
+
214
+ if not action:
215
+ return done(
216
+ "[c3_project:error] action required. "
217
+ f"Discovery: {', '.join(sorted(_DISCOVERY_OPS))}. "
218
+ f"Read: {', '.join(sorted(_READ_OPS))}. "
219
+ f"Write (allow_write=true): {', '.join(sorted(_WRITE_OPS))}.",
220
+ "error")
221
+
222
+ # ── Discovery (no foreign runtime needed) ──────────────────────────
223
+ if action in ("list", "scan"):
224
+ return done(_render_discovery(scan_roots, action == "scan"), f"{action} projects")
225
+ if action == "info":
226
+ return done(_render_info(project), "project info")
227
+ if action == "register":
228
+ return done(_do_register(project), "register project")
229
+ if action == "unregister":
230
+ return done(_do_unregister(project), "unregister project")
231
+
232
+ if action not in _READ_OPS and action not in _WRITE_OPS:
233
+ return done(
234
+ f"[c3_project:error] Unknown action '{action}'. "
235
+ f"Discovery: {', '.join(sorted(_DISCOVERY_OPS))}. "
236
+ f"Read: {', '.join(sorted(_READ_OPS))}. "
237
+ f"Write (allow_write=true): {', '.join(sorted(_WRITE_OPS))}.",
238
+ "error")
239
+
240
+ # ── Write guard ────────────────────────────────────────────────────
241
+ is_write = action in _WRITE_OPS or (
242
+ action == "memory" and (mem_action or "").lower() in _MEMORY_WRITE
243
+ )
244
+ if is_write and not allow_write:
245
+ label = action + (f"/{mem_action}" if action == "memory" else "")
246
+ return done(
247
+ f"[c3_project:blocked] '{label}' would modify project '{project}'. "
248
+ "Re-run with allow_write=true to proceed.",
249
+ "blocked")
250
+
251
+ # ── Resolve + borrow the foreign runtime ───────────────────────────
252
+ try:
253
+ resolved = resolve_project(project)
254
+ except ValueError as e:
255
+ return done(f"[c3_project:error] {e}", "error")
256
+ try:
257
+ fsvc = _runtime_for(resolved["path"])
258
+ except Exception as e:
259
+ return done(
260
+ f"[c3_project:error] Could not load '{resolved['name']}': {e}", "error")
261
+
262
+ banner = f"[c3_project:{resolved['name']}] {action}\n"
263
+ try:
264
+ body = _proxy(
265
+ action, fsvc, query=query, file_path=file_path, symbols=symbols,
266
+ lines=lines, mode=mode, view=view, top_k=top_k, max_tokens=max_tokens,
267
+ search_action=search_action, mem_action=mem_action, fact=fact,
268
+ category=category, fact_id=fact_id, edits_action=edits_action,
269
+ file=file, tag=tag, limit=limit, target=target, old_string=old_string,
270
+ new_string=new_string, summary=summary, edits=edits,
271
+ replace_all=replace_all, tags=tags, cmd=cmd, timeout=timeout,
272
+ project_path=resolved["path"],
273
+ )
274
+ except Exception as e:
275
+ return done(f"{banner}[error] {type(e).__name__}: {e}", "error")
276
+
277
+ # Audit foreign mutations on the target project itself.
278
+ if is_write and getattr(fsvc, "activity_log", None):
279
+ try:
280
+ fsvc.activity_log.log("cross_project_write", {
281
+ "action": action,
282
+ "from_project": getattr(svc, "project_path", ""),
283
+ })
284
+ except Exception:
285
+ pass
286
+
287
+ return done(banner + (body or ""), f"{action} on {resolved['name']}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.30.0
3
+ Version: 2.32.2
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Gemini, Copilot). Retrieve less, read less, edit safer.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -228,7 +228,7 @@ Per-project knobs for everything: budget thresholds, feature flag mode, edit led
228
228
 
229
229
  ## The MCP tool suite
230
230
 
231
- C3 exposes 15 tools as a native MCP server. Your IDE calls them directly:
231
+ C3 exposes 16 tools as a native MCP server. Your IDE calls them directly:
232
232
 
233
233
  | Tool | What it does |
234
234
  |---|---|
@@ -247,8 +247,9 @@ C3 exposes 15 tools as a native MCP server. Your IDE calls them directly:
247
247
  | `c3_agent` | Multi-step agentic workflows (review, investigate, refactor) |
248
248
  | `c3_edits` | Edit-ledger queries + version diffs + restore points |
249
249
  | `c3_bitbucket` | Bitbucket Data Center integration — PRs, branches, builds, repo admin (v2.30.0) |
250
+ | `c3_project` | Cross-project — discover & operate on other c3-installed projects; guarded writes (v2.31.0) |
250
251
 
251
- Every tool is **read-only safe in plan mode** (except `c3_edit`, `c3_shell`, and write actions on `c3_bitbucket`).
252
+ Every tool is **read-only safe in plan mode** (except `c3_edit`, `c3_shell`, and write actions on `c3_bitbucket` / `c3_project`).
252
253
 
253
254
  ### Bitbucket Data Center / Server (v2.30.0)
254
255
 
@@ -280,6 +281,34 @@ to the C3 edit ledger so the audit trail covers platform-side changes too.
280
281
  The **Hub UI** (per-project) gains a "Bitbucket" tab with sub-views for
281
282
  Overview / Pull Requests / Branches / Activity / Admin.
282
283
 
284
+ ### Oracle Discovery API (v2.32.0)
285
+
286
+ The **Oracle** is C3's optional cross-project memory agent (a local web app). As of
287
+ v2.32.0 it can expose C3's cross-project code & memory intelligence as **tools for an
288
+ external LLM** — point Claude (or any function-calling model) at a running Oracle and
289
+ it can discover your projects and search code, memory, and the cross-project graph
290
+ across all of them.
291
+
292
+ Two transports share one tool core:
293
+
294
+ - **MCP** (streamable HTTP/SSE) at `http://127.0.0.1:3332/mcp` — native for Claude
295
+ Code / Claude Desktop / any MCP client.
296
+ - **OpenAPI REST** at `http://127.0.0.1:3331/api/discovery` — for any LLM with
297
+ function-calling (fetch `/openapi.json` to auto-register the tools).
298
+
299
+ ```bash
300
+ # Start the Oracle (serves the REST + MCP discovery endpoints)
301
+ python oracle/oracle_server.py --no-browser
302
+
303
+ # Print the Bearer token + a ready-to-paste .mcp.json snippet
304
+ c3 oracle api info
305
+ ```
306
+
307
+ Only **read** and **safe-action** tools are exposed (no code editing); requests need a
308
+ **Bearer token** (stored in the OS keyring) and both servers bind `127.0.0.1` by
309
+ default. Generate, rotate, and copy the token from the dashboard's **Settings →
310
+ Discovery API** tab. See the [Oracle Discovery API guide](oracle-guide/discovery-api.md).
311
+
283
312
  ---
284
313
 
285
314
  ## Tiered local AI (optional)
@@ -354,6 +383,7 @@ The author may introduce a paid offering or relicense future major versions; no
354
383
 
355
384
  - **PyPI:** https://pypi.org/project/code-context-control/
356
385
  - **Changelog:** [`CHANGELOG.md`](CHANGELOG.md)
386
+ - **Oracle Discovery API:** [`oracle-guide/discovery-api.md`](oracle-guide/discovery-api.md)
357
387
  - **Security policy:** [`SECURITY.md`](SECURITY.md)
358
388
  - **Licensing FAQ:** [`LICENSING.md`](LICENSING.md)
359
389
  - **Issues:** https://github.com/drknowhow/code-context-control/issues
@@ -1,7 +1,7 @@
1
1
  cli/__init__.py,sha256=ec66drCZGNMRU4V6ov0zVhYZph1us12Vn8OvG_LJyRY,22
2
2
  cli/_hook_utils.py,sha256=1_hTA-Wz62xB8jnSAH4C5TfCkrwEP0g2kq_-oRfQLm4,3724
3
- cli/c3.py,sha256=k3ghW-SIZ65O-W00qob2vmAcPqoLinhkjikIXneFnGQ,286191
4
- cli/docs.html,sha256=olU9bJk37qUIKrZpuW8MeIWuket4CmRteeWuWKiVWPQ,143653
3
+ cli/c3.py,sha256=mv6N7y8teSwK18x0q-XLjoLTZhWzrJYIsyH8rlbwj-o,288183
4
+ cli/docs.html,sha256=JgtBFUuUkvmYowPREYiGhhcRbB5e2UjkRc00MIF0hsU,143653
5
5
  cli/edits.html,sha256=UjAhoCmBmQ89cklGvJqzC6eyNP2tc8H6T-e01DVkLvE,43418
6
6
  cli/hook_auto_snapshot.py,sha256=amtliVDzKUQr6KBR0pdBA8vXghAV-gKr19jBaJVnP_w,5006
7
7
  cli/hook_c3_signal.py,sha256=897TiANhZx01jbClscyX1DeEnmMgVP_GVNgifKWF0AY,1918
@@ -17,14 +17,14 @@ cli/hook_terse_advisor.py,sha256=pD7Bap7OYOKqtYz7cX8nWSRLH7ook-tSD2Ov2MNp_sA,590
17
17
  cli/hub.html,sha256=Hl-XPZGT1mMiKrbX9c5OsEw6mXEumwIB3vp1WlWaplM,183966
18
18
  cli/hub_server.py,sha256=gnUJdCgX5ZKZHwLKLYW5ki-P_9HH9Zyi_Hb_yoSKwSI,61979
19
19
  cli/mcp_proxy.py,sha256=92htuT-p0j-cDTbyqlIJpGoQ85_Aw7UuB8L_Toi_u20,17511
20
- cli/mcp_server.py,sha256=gmRJEITPrnS8VYWzbTyeLooQa9M_3CXpEXmEwF1QMno,30127
20
+ cli/mcp_server.py,sha256=FR-FsVsRzU0H_uI504fXqPNVGbTgCukntYtL36sHxrM,32329
21
21
  cli/server.py,sha256=g9iyi_UhxvyyqIz8ihCsIpDg-fXr88vZXdLllEKsHBU,122525
22
22
  cli/ui.html,sha256=xcdt74nlFEXx-0Bx6-Okw-WSVZPAXL0iukxU0ytI6CA,5694
23
23
  cli/ui_legacy.html,sha256=cI8tC6RKmE2NIJOcsu7CY-zT4VznjcbD6NTjxb_fvUY,378460
24
24
  cli/ui_nano.html,sha256=UAwQ6bbTOXAoGq191AZ7slhngR9edJSa3IhqpynveDg,27740
25
25
  cli/commands/__init__.py,sha256=0Z8MABNzwSFJGT4Xv9R5AJVR8XxraTsuVTz5b0bShmo,38
26
26
  cli/commands/common.py,sha256=3fXTjFCIo7q-Tr6KtxfjuLPu-Apl7PeS5mN9B5-bqkc,11494
27
- cli/commands/parser.py,sha256=Cr02ijp7CjjVkI8OaBq3anhQnI9BPFDeA0KSevVSyxY,21765
27
+ cli/commands/parser.py,sha256=la5LkdaTahEVdeUtZD-Scp9rClrypMlKJMC9HMHH05k,22720
28
28
  cli/tools/__init__.py,sha256=HO6eVKzDm1KPqsKTHY6lfIsnFnv3C7Bd9o3Q_V88idg,127
29
29
  cli/tools/_helpers.py,sha256=dM3tXHWE1uUA_HP5po0hjSEaTXrXJ8hvh_MrABiBq9I,3011
30
30
  cli/tools/agent.py,sha256=fdt5kmw9O-AEKlYaij-PPmCJ4sI7bmdej56V4NGWaqI,48145
@@ -36,6 +36,7 @@ cli/tools/edits.py,sha256=-Tv5eqw_X-dYc9l4kFWr_8vX1TAUb9QNMv0fpy0rXjQ,5304
36
36
  cli/tools/filter.py,sha256=0C37_Cm2YEsXp5pA4w-tWV8V_UHu5xflNp4bLMZqrbo,11501
37
37
  cli/tools/impact.py,sha256=jjWkFTxHu-gBpZZNd2HTdBl22itA6-wwwOZXxk_qBl8,6257
38
38
  cli/tools/memory.py,sha256=OdYBcIEFo4sr5aCG0_uO48uyJ-Kzof7LC2Ou1THGFuc,23317
39
+ cli/tools/project.py,sha256=PKHLGRFZdwBBEgryyV440mybTeQMqnlLgHdfXDnPahw,11868
39
40
  cli/tools/read.py,sha256=z2A3UWt3MOriv2Z4-YWNvWNB07MrYT5368_ZuulNHIE,9397
40
41
  cli/tools/search.py,sha256=qB8C3w8yuu59aepvnuJNlzbsirtSEZA8zz4neKlE7Xs,13246
41
42
  cli/tools/session.py,sha256=LIZbmEhNdh6rAsT6Dbpb21UY8xF9oubvpjGwfnXxQK4,4573
@@ -56,17 +57,19 @@ cli/ui/components/memory.js,sha256=v5IsHTxLHpXX4xCsUaZ_UPprZEabdgP4jiWc298iV2U,2
56
57
  cli/ui/components/sessions.js,sha256=FIKtil76B8tCkAmcFV7hlj6GQ_DCJK2jCzvEmdK7NBE,30837
57
58
  cli/ui/components/settings.js,sha256=8LVTV2TQl9tcRXhXbtBEJOCBdiyk-x2QASoVYZUAuEA,71442
58
59
  cli/ui/components/sidebar.js,sha256=cAY_jwYB-o1X_wWn__VXlG4IegVObuE3NmVsuFWqxtg,7417
59
- code_context_control-2.30.0.dist-info/licenses/LICENSE,sha256=l8Kh5QCNWNvR6kIt8L0BUZvc2LAFiHv2c-FnsGnUZf4,11301
60
+ code_context_control-2.32.2.dist-info/licenses/LICENSE,sha256=l8Kh5QCNWNvR6kIt8L0BUZvc2LAFiHv2c-FnsGnUZf4,11301
60
61
  core/__init__.py,sha256=TSDCEcM4V7gcZVM3w2ykJaqEUch4Dkon-rivV17T73s,2501
61
62
  core/config.py,sha256=0RBVni99wqJIxAYU6uweWVOmdI-FJvQ8d3IV5Mp1Muc,12818
62
63
  core/ide.py,sha256=9LzsDVK2LL8RVpL40l6oNGiasZ3D8OCU_9i9A0gJKBo,6876
63
64
  oracle/__init__.py,sha256=-OTD7Jh4mUMA4QgPGthPLWXttgZLpkIPhGQ87ZfHBx0,63
64
- oracle/config.py,sha256=MJuZoryJXYQkE8pNDoHEgSP0rnxx9mG54wH7YjOMqH0,2660
65
- oracle/oracle.html,sha256=h5tfEkKHPhQV6c5XuEpwePLqS6UwvgbGSSHQFeVVHAw,179193
66
- oracle/oracle_server.py,sha256=coNj78GSbKJKn2fZyk7Q27NOMZsnYCzix4TWzt8LcGg,25315
65
+ oracle/config.py,sha256=ErjH6Y_F51jGXpYo_4boGhdIk-AIo3rDH3xDwUGs7B8,3193
66
+ oracle/mcp_oracle.py,sha256=VN_I-bYvawGTqcpEyVyBz8-GIEZEVJWbM1lBA7Wi1BM,5972
67
+ oracle/oracle.html,sha256=KW1jeqmUwvAH2mDlhCLo05nrHW8PfmCYv0iQhL5d74s,185219
68
+ oracle/oracle_server.py,sha256=ehgjXcaea6TUbtD7-yJ0t3Vr6AttC_eyhfL35jPJo7Q,33337
67
69
  oracle/services/__init__.py,sha256=Nb4POd1_YIwLVYsGfr-DiK-iKTelkU0fh9m7wjeLQHA,23
70
+ oracle/services/api_auth.py,sha256=1PW3pG--1DJb_F6qMhP3gBTYHxxPE2bsHmVmIhC81Y8,3566
68
71
  oracle/services/c3_bridge.py,sha256=Khj2jao2oENe4yFA31Ny0yI3fcV8XBerjsLslt4ns3U,9652
69
- oracle/services/chat_engine.py,sha256=rkt5xsCVw3SpuquHUskX-W91KpxLET8ba7dpg9yIuLM,48755
72
+ oracle/services/chat_engine.py,sha256=d4ynBqdSYyHMa0EVCT6sFEjSJrSCBahW_mdk8gP8nXU,49157
70
73
  oracle/services/chat_store.py,sha256=mizKwDyFGESKh63V-XgyNd3jQVrN2JCQc-7u8z3_1F4,6252
71
74
  oracle/services/cross_memory.py,sha256=F8JeYkEFSwQL3iGS9KV1onaMSFj3opr6f3nCxwvznV0,5484
72
75
  oracle/services/federated_graph.py,sha256=F808OkuqAbKXHqugP0PfQRLk-KrROLfjSLGLBVb6p-c,17735
@@ -77,6 +80,8 @@ oracle/services/memory_writer.py,sha256=hpvYsxjhl293jEjGcRKfjG5njUaAOXKqbmJaUu1W
77
80
  oracle/services/ollama_bridge.py,sha256=d0458HTaQO9m-Ur4bRIt9izxbJFDj1Dbea9sfo7MRCU,12958
78
81
  oracle/services/project_scanner.py,sha256=SGHYKU00fm57L5VyDuet-CAACfrhUZY2xvIlZR27aj4,3142
79
82
  oracle/services/review_agent.py,sha256=99PQ1oKxRDo_4COMOy3CJOawqk08MRJ4cS66_w7SWCo,7720
83
+ oracle/services/tool_executor.py,sha256=xtAkBWkclh_FwOgzprjGkyRIV8-A7Lc3bz0UFPShrv0,1113
84
+ oracle/services/tool_registry.py,sha256=y18ZWP1eLkCI3ZSKJDBjK1Wlp_qK7Kr4o_gm_r5gBXI,17396
80
85
  services/__init__.py,sha256=3Kn4cZweLm7at8wFdBdZ-Zwo8hHcnVIsmY5f29nzi2Y,116
81
86
  services/activity_log.py,sha256=YsW8-HBQEFh2vYTlvnzK7doNsR-XEtBbWXJ-324XigU,3370
82
87
  services/agent_base.py,sha256=a-gdSd_jtZtbjXo1WS8CnWCagXgKaGZd5ShcG6s0kT4,4809
@@ -85,7 +90,7 @@ services/auto_memory.py,sha256=v__ZS1e68533_Yv491mZtvuZnheC63q6_uTvWhBw3Lw,14290
85
90
  services/benchmark_dashboard.py,sha256=iR-DnqnoKbqHMJ4d-ZkIvJBYfzwTa7r-jzO6j2BYDfQ,27711
86
91
  services/bitbucket_client.py,sha256=v8xGEcnIEmURvcg38XwmiCGh7-_QnjhAJEb0te_yZzQ,16107
87
92
  services/bitbucket_credentials.py,sha256=2qLA9pQMol4y95y4DJMNBsBBPUsJQCKbLFo2iiCnfvI,7364
88
- services/claude_md.py,sha256=VXjhfVd8TS-NdJJQSqkT4R17nuxKg1Eo4tsPcSkvjeQ,34547
93
+ services/claude_md.py,sha256=K3iAi6Lhllpf14k-37NzqhM8LepIlXszf0dDyvAnIC4,34966
89
94
  services/compressor.py,sha256=uSVyTYfvxFrRYupzyKj-HzkBP0RwARrGYFz_DnMSEaM,25169
90
95
  services/context_snapshot.py,sha256=upxrxcBUPX7MrOlgUo7oD9rvm2H1SJLK8FI1tgHrAjg,14045
91
96
  services/conversation_store.py,sha256=vPiMiKAE22RCBSSphgGH9Vx-lPV45SmttOwgVVWahL4,33398
@@ -110,6 +115,7 @@ services/ollama_client.py,sha256=UiC5Abca622-ac-4goCkAjwo7iUye9g9JGFMN6Ea08M,798
110
115
  services/output_filter.py,sha256=097HPxy0BM8fXePktb7sOf-Yfkx4qqHy0HurNRjhNDY,18635
111
116
  services/parser.py,sha256=vv3B4Di1q4Sr2QFAxvsk5tsYNVcAEw8oAQMhXjLURDE,54011
112
117
  services/project_manager.py,sha256=zHT4Ll9fSD09M1v-PpeoVO0G0DueCm2lbtVc3GlxbjA,35304
118
+ services/project_runtime.py,sha256=Au2i3XXI4RgDdaG7vLKQ_x6GklE4tTB86cP85Aq_NIg,10783
113
119
  services/protocol.py,sha256=gfvk4y-bEwGlXlOwBoyqKuADXe42NAKfdWl3BAzyVJc,11962
114
120
  services/proxy_state.py,sha256=u5rd0k6CrOsywZA8FpRu_hMLwhR0TAJhZjy5MdWbCGc,6107
115
121
  services/retrieval_broker.py,sha256=9X67VZ_6AkbAzopHuuMFKmP4CGZLnW576kjSKMenBnw,5261
@@ -147,8 +153,8 @@ tui/screens/search_view.py,sha256=MMHjVdlk3HZSuDBSvq8IGrqv_Mh5Us6YqXQ80bcWSMk,19
147
153
  tui/screens/session_view.py,sha256=eZ1eDwHTvPOck1wCCviixtOaCxIkBT_95ytNNNriGNA,5991
148
154
  tui/screens/stats.py,sha256=p81PjzdaIv7hllb8f45-rlVe4lJZwSdIMqu7e86_u5s,6223
149
155
  tui/screens/ui_view.py,sha256=1QJCgLh2YfgWIpvzRG1KOGXYEaOYX6ojN61Azjf2oX0,2125
150
- code_context_control-2.30.0.dist-info/METADATA,sha256=m-D1-H3PVUd_W4eeTLbg025f7ys1NQ77qg_016RcBUc,17742
151
- code_context_control-2.30.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
152
- code_context_control-2.30.0.dist-info/entry_points.txt,sha256=7kX_WUsDCF2hbXzvbNyscyaBb9AeA-DJY5v_5hN0DlU,93
153
- code_context_control-2.30.0.dist-info/top_level.txt,sha256=wRt41zBybVF3qAiNXHz9BURbkKvUvfhmWWtKMhaw6eE,29
154
- code_context_control-2.30.0.dist-info/RECORD,,
156
+ code_context_control-2.32.2.dist-info/METADATA,sha256=wPXVQQGc2FfMlK5H2UWOq9pPAmRY_MWXXpJjj9ALNE4,19221
157
+ code_context_control-2.32.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
158
+ code_context_control-2.32.2.dist-info/entry_points.txt,sha256=7kX_WUsDCF2hbXzvbNyscyaBb9AeA-DJY5v_5hN0DlU,93
159
+ code_context_control-2.32.2.dist-info/top_level.txt,sha256=wRt41zBybVF3qAiNXHz9BURbkKvUvfhmWWtKMhaw6eE,29
160
+ code_context_control-2.32.2.dist-info/RECORD,,