316 project_root: str | Path |
None =
None,
317 profile: str |
None =
None,
318 config_path: Path |
None =
None,
319 transport: str =
"stdio",
320 host: str =
"127.0.0.1",
322 metrics_port: int |
None =
None,
324 """Initialise and run the ``oct-mcp`` server.
328 1. Resolve and validate project root (Layer 1 - Gateway).
329 2. Load :class:`McpConfig` (with env var overrides).
330 3. Apply centralised policy overrides (Phase 5C, M-C7).
331 4. Override profile if ``--profile`` CLI arg was provided.
332 5. Instantiate all pipeline layers.
333 6. Start Prometheus metrics server (optional, Phase 5C).
334 7. Set ``_server_state`` singleton.
335 8. Build the SDK server with all 20 tools registered
336 (:func:`build_mcp_server`).
337 9. Run the server (stdio, streamable HTTP, or SSE transport). Under
338 stdio, first hand fd 1 back to the transport
339 (:func:`oct.mcp.stdout_diversion.restore_stdout`; a no-op when no
340 diversion is active, as in an in-process call), and exit with
341 code 1 if a diversion is still recorded afterwards.
346 Project root path override (``None`` = auto-discover).
348 MCP profile override (``None`` = use config file / env var).
350 Path to the ``~/.oct-mcp/config.json`` config file
351 (``None`` = use default location).
353 MCP transport: ``"stdio"`` (default) for local AI hosts,
354 ``"streamable-http"`` for remote access, or ``"sse"`` for the
355 legacy HTTP/Server-Sent Events transport.
357 Host to bind for the HTTP transports (default ``"127.0.0.1"``).
358 Ignored when *transport* is ``"stdio"``.
360 Port to bind for the HTTP transports (default ``3001``).
361 Ignored when *transport* is ``"stdio"``.
363 If set, starts a Prometheus ``/metrics`` server on this port.
364 Overrides ``config.metrics_port``. Requires ``oct[metrics]``.
367 func =
"start_server"
373 config = load_config(config_path)
376 if config.policy_source:
378 overrides = load_policy_source(config.policy_source)
380 config = apply_policy_overrides(config, overrides)
384 if profile
and profile
in VALID_MCP_PROFILES:
385 config.profile = profile
386 _dbg(
"MCP", func, f
"profile overridden to {profile} via CLI", 2)
390 f
"starting server profile={config.profile} transport={transport} root={root}",
396 session_id = str(uuid.uuid4())
398 policy =
McpPolicy(profile=config.profile, safe_mode=config.safe_mode)
399 backend = make_sandbox_backend(config)
402 memory_limit_mb=config.memory_limit_mb,
406 timeout_default=config.timeout_default_seconds,
407 timeout_test=config.timeout_test_seconds,
408 max_output_bytes=config.max_output_bytes,
412 max_output_bytes=config.max_output_bytes,
415 audit_log_path=config.audit_log_path,
416 max_files=config.audit_log_max_files,
417 max_size_bytes=int(config.audit_log_max_size_mb * 1024 * 1024),
419 rate_limiter =
RateLimiter(limit_per_minute=config.rate_limit_per_minute)
424 effective_metrics_port = metrics_port
if metrics_port
is not None else config.metrics_port
425 if effective_metrics_port
is not None:
426 metrics.start_server(effective_metrics_port)
427 _dbg(
"MCP", func, f
"Prometheus metrics on port {effective_metrics_port}", 2)
436 audit_logger=audit_logger,
437 rate_limiter=rate_limiter,
440 session_id=session_id,
446 _dbg(
"MCP", func, f
"server ready session_id={session_id}", 2)
449 if transport ==
"streamable-http":
450 mcp_server.run(transport=
"streamable-http", host=host, port=port)
451 elif transport ==
"sse":
452 mcp_server.run(transport=
"sse", host=host, port=port)
458 if stdout_is_diverted():
464 "SYSTEM_ERROR: standard output is still diverted; "
465 "the stdio transport was not started",
469 "oct-mcp: error: could not hand standard output back to the "
474 mcp_server.run(transport=
"stdio")
478 """Load ``redaction_patterns`` from ``debug_config.json`` if trusted.
480 Only reads the project-level debug config when
481 ``config.trust_repo_config`` is ``True`` and the profile is not
482 ``"airgapped"`` or ``"strict"``.
484 Returns an empty list (safe default) on any read/parse failure.
486 func =
"_load_debug_config_patterns"
487 if not config.trust_repo_config:
489 if config.profile
in (
"strict",
"airgapped"):
493 canonical = root /
"oc_diagnostics" /
"debug_config.json"
494 legacy = root /
"debug_config.json"
495 if canonical.exists():
496 debug_cfg_path = canonical
497 elif legacy.exists():
501 f
"using legacy debug_config.json at {legacy} - migrate to oc_diagnostics/",
504 debug_cfg_path = legacy
509 raw = json.loads(debug_cfg_path.read_text(encoding=
"utf-8"))
510 patterns = raw.get(
"redaction_patterns", [])
511 if isinstance(patterns, list):
512 _dbg(
"MCP", func, f
"loaded {len(patterns)} redaction patterns", 2)
514 except Exception
as exc:
515 _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)