217 project_root: str | Path |
None =
None,
218 profile: str |
None =
None,
219 config_path: Path |
None =
None,
220 transport: str =
"stdio",
221 host: str =
"127.0.0.1",
223 metrics_port: int |
None =
None,
225 """Initialise and run the ``oct-mcp`` server.
229 1. Resolve and validate project root (Layer 1 — Gateway).
230 2. Load :class:`McpConfig` (with env var overrides).
231 3. Apply centralised policy overrides (Phase 5C, M-C7).
232 4. Override profile if ``--profile`` CLI arg was provided.
233 5. Instantiate all pipeline layers.
234 6. Start Prometheus metrics server (optional, Phase 5C).
235 7. Create FastMCP server.
236 8. Set ``_server_state`` singleton.
237 9. Register 9 read-only (Phase 5A) + 11 write (Phase 5B) tools.
238 10. Run the FastMCP server (stdio or SSE transport).
243 Project root path override (``None`` = auto-discover).
245 MCP profile override (``None`` = use config file / env var).
247 Path to the ``~/.oct-mcp/config.json`` config file
248 (``None`` = use default location).
250 MCP transport: ``"stdio"`` (default) or ``"sse"``
251 (HTTP/Server-Sent Events for remote access).
253 Host to bind for SSE transport (default ``"127.0.0.1"``).
254 Only used when *transport* is ``"sse"``.
256 Port to bind for SSE transport (default ``3001``).
257 Only used when *transport* is ``"sse"``.
259 If set, starts a Prometheus ``/metrics`` server on this port.
260 Overrides ``config.metrics_port``. Requires ``oct[metrics]``.
263 func =
"start_server"
269 config = load_config(config_path)
272 if config.policy_source:
274 overrides = load_policy_source(config.policy_source)
276 config = apply_policy_overrides(config, overrides)
280 if profile
and profile
in VALID_MCP_PROFILES:
281 config.profile = profile
282 _dbg(
"MCP", func, f
"profile overridden to {profile} via CLI", 2)
286 f
"starting server profile={config.profile} transport={transport} root={root}",
292 session_id = str(uuid.uuid4())
294 policy =
McpPolicy(profile=config.profile, safe_mode=config.safe_mode)
295 backend = make_sandbox_backend(config)
298 memory_limit_mb=config.memory_limit_mb,
302 timeout_default=config.timeout_default_seconds,
303 timeout_test=config.timeout_test_seconds,
304 max_output_bytes=config.max_output_bytes,
308 max_output_bytes=config.max_output_bytes,
311 audit_log_path=config.audit_log_path,
312 max_files=config.audit_log_max_files,
313 max_size_bytes=int(config.audit_log_max_size_mb * 1024 * 1024),
315 rate_limiter =
RateLimiter(limit_per_minute=config.rate_limit_per_minute)
320 effective_metrics_port = metrics_port
if metrics_port
is not None else config.metrics_port
321 if effective_metrics_port
is not None:
322 metrics.start_server(effective_metrics_port)
323 _dbg(
"MCP", func, f
"Prometheus metrics on port {effective_metrics_port}", 2)
326 mcp_server = FastMCP(
329 "Option C Tools MCP server — exposes oct linter, health check, "
330 "skeleton exporter, dependency checker, test runner, type checker, "
331 "diagnostics, and git tools as MCP tools."
342 audit_logger=audit_logger,
343 rate_limiter=rate_limiter,
346 session_id=session_id,
350 from oct.mcp import tools
as _tools_module
351 _tools_module.register_tools(mcp_server)
352 _tools_module.register_write_tools(mcp_server)
354 _dbg(
"MCP", func, f
"server ready session_id={session_id}", 2)
357 if transport ==
"sse":
358 mcp_server.run(transport=
"sse", host=host, port=port)
360 mcp_server.run(transport=
"stdio")
364 """Load ``redaction_patterns`` from ``debug_config.json`` if trusted.
366 Only reads the project-level debug config when
367 ``config.trust_repo_config`` is ``True`` and the profile is not
368 ``"airgapped"`` or ``"strict"``.
370 Returns an empty list (safe default) on any read/parse failure.
372 func =
"_load_debug_config_patterns"
373 if not config.trust_repo_config:
375 if config.profile
in (
"strict",
"airgapped"):
379 canonical = root /
"oc_diagnostics" /
"debug_config.json"
380 legacy = root /
"debug_config.json"
381 if canonical.exists():
382 debug_cfg_path = canonical
383 elif legacy.exists():
387 f
"using legacy debug_config.json at {legacy} — migrate to oc_diagnostics/",
390 debug_cfg_path = legacy
395 raw = json.loads(debug_cfg_path.read_text(encoding=
"utf-8"))
396 patterns = raw.get(
"redaction_patterns", [])
397 if isinstance(patterns, list):
398 _dbg(
"MCP", func, f
"loaded {len(patterns)} redaction patterns", 2)
400 except Exception
as exc:
401 _dbg(
"MCP", func, f
"could not read debug_config.json: {exc}", 1)
None start_server(str|Path|None project_root=None, str|None profile=None, Path|None config_path=None, str transport="stdio", str host="127.0.0.1", int port=3001, int|None metrics_port=None)