oct
Loading...
Searching...
No Matches
oct.mcp.server Namespace Reference

Classes

class  RateLimiter
class  ServerState

Functions

ServerState get_server_state ()
Path _resolve_project_root (str|Path|None root_arg)
str _oct_version ()
MCPServer build_mcp_server ()
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)
list _load_debug_config_patterns (Path root, McpConfig config)

Variables

tuple SERVER_INSTRUCTIONS

Detailed Description

Purpose
-------
MCP server core (Layers 1 + orchestration) for the ``oct-mcp`` server.
Creates a :class:`mcp.server.MCPServer` instance (MCP Python SDK v2),
binds it to a validated project root (Layer 1 - Gateway), and exposes
the twenty Phase 5A/5B tools via :mod:`oct.mcp.tools`.

Responsibilities
----------------
- Implement :func:`build_mcp_server`: construct the SDK server object
  and register all twenty tools, without running any transport.
- Implement :func:`start_server`: resolve + validate project root,
  load config, instantiate all pipeline layers, register tools, run.
- Implement the token-bucket rate limiter (Layer 1) via
  :class:`RateLimiter`.
- Expose ``_server_state`` for tools to access the shared pipeline
  instances (singleton pattern: one server = one context).

Diagnostics
-----------
Domain: MCP
Levels:
    L1 - errors: startup failures, workspace binding failures
    L2 - lifecycle: server start/stop, workspace bound
    L3 - request details
    L4 - deep trace

Contracts
---------
- :func:`start_server` raises ``SystemExit(1)`` on fatal startup errors
  (invalid project root, no ``oct`` command, standard output that
  cannot be handed back to the stdio transport) so ``__main__.py`` can
  propagate a clean exit code.
- The SDK is MCP Python SDK v2 (``mcp>=2.2,<3``) with no v1 fallback:
  the two lines differ in threading and error semantics, and a security
  layer must not straddle them (plan 244, P1).
- Under v2 a synchronous tool handler runs on a worker thread
  (``anyio.to_thread.run_sync``) and concurrent requests overlap, so
  every tool call is serialised by the process-wide lock held in
  :mod:`oct.mcp.tools`. The rate limiter, executor, audit logger and
  metrics behind ``_server_state`` therefore keep the single-threaded
  contract they were written for (plan 244, P3).
- Under the ``stdio`` transport, standard output (fd 1) is the JSON-RPC
  channel. :func:`oct.mcp.__main__.main` diverts it to standard error
  for the startup window, and :func:`start_server` hands it back with
  :func:`oct.mcp.stdout_diversion.restore_stdout` immediately before
  ``MCPServer.run(transport="stdio")``, whose ``stdio_server()`` then
  diverts fd 1 again for the serving window. When the hand-back fails,
  :func:`start_server` exits with code 1 instead of calling ``run``,
  because the SDK would take the still-diverted fd 1 as its wire and
  serve the protocol on standard error. Nothing is written after
  ``run`` returns; ``_dbg`` flushes every record, so only an unflushed
  ``print()`` made while serving, or a record that a broken standard
  error rejected while serving, could still reach the wire when the
  SDK hands fd 1 back at shutdown.
- The rate limiter is per-server-instance (not per-session), which is
  sufficient for the stdio transport where one server process handles
  one client session.
- ``_server_state`` is set before :meth:`MCPServer.run` is called and is
  available to all tool handlers during their execution.

Dependencies
------------
- oct.core.diagnostics (_dbg structured logger)
- oct.mcp.audit (McpAuditLogger, the Layer 6 audit log)
- oct.mcp.config (McpConfig, load_config and VALID_MCP_PROFILES for the server settings)
- oct.mcp.executor (ToolExecutor, which maps tool arguments to oct CLI calls)
- oct.mcp.metrics (McpMetrics, the optional Prometheus counters and exporter)
- oct.mcp.policy (McpPolicy, the Layer 3 profile rules)
- oct.mcp.redactor (Redactor, the Layer 5 output sanitiser)
- oct.mcp.safety (SafetyGate, the Layer 2.5 consent and dry-run gate)
- oct.mcp.sandbox (SandboxExecutor and make_sandbox_backend, the Layer 4 sandbox)
- oct.mcp.stdout_diversion (restore_stdout and stdout_is_diverted, which hand fd 1 back to the stdio transport and confirm it)
- oct.core.project_root (lazy; find_project_root when no project root is given)
- oct.mcp.policy_loader (lazy; the centralised policy overrides when a policy source is set)
- oct.mcp.tools (lazy; registers the twenty tools on the SDK server)
- mcp (MCPServer from the MCP Python SDK v2, and its transports)

Function Documentation

◆ _load_debug_config_patterns()

list oct.mcp.server._load_debug_config_patterns ( Path root,
McpConfig config )
protected
Load ``redaction_patterns`` from ``debug_config.json`` if trusted.

Only reads the project-level debug config when
``config.trust_repo_config`` is ``True`` and the profile is not
``"airgapped"`` or ``"strict"``.

Returns an empty list (safe default) on any read/parse failure.

Definition at line 477 of file server.py.

Here is the caller graph for this function:

◆ _oct_version()

str oct.mcp.server._oct_version ( )
protected
Return the installed ``oct`` distribution version.

The MCP server advertises this to the host as its ``version``. It is
read from distribution metadata rather than written as a literal so
the wire value can never drift from the installed package.

Returns
-------
The ``oct`` version string, or ``"0.0.0"`` when ``oct`` is not
installed as a distribution (a source checkout on ``sys.path``).

Definition at line 262 of file server.py.

Here is the caller graph for this function:

◆ _resolve_project_root()

Path oct.mcp.server._resolve_project_root ( str | Path | None root_arg)
protected
Resolve and validate the project root directory.

Parameters
----------
root_arg
    Path provided via ``--project-root`` CLI argument, or ``None``
    to auto-discover from the current working directory.

Returns
-------
Resolved absolute :class:`Path`.

Raises
------
SystemExit(1)
    If the directory does not exist or is not a directory.

Definition at line 205 of file server.py.

Here is the caller graph for this function:

◆ build_mcp_server()

MCPServer oct.mcp.server.build_mcp_server ( )
Create the SDK server object and register all twenty tools.

Factored out of :func:`start_server` so the construction path can be
exercised - and the advertised tool list inspected - without binding
a transport (plan 244, W1).

Returns
-------
An :class:`mcp.server.MCPServer` with the nine Phase 5A read-only
tools and the eleven Phase 5B write tools registered.

Definition at line 283 of file server.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_server_state()

ServerState oct.mcp.server.get_server_state ( )
Return the current :class:`ServerState`.

Raises ``RuntimeError`` if called outside a running server context
(i.e. before :func:`start_server` has initialised the state).

Definition at line 186 of file server.py.

◆ start_server()

None oct.mcp.server.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 )
Initialise and run the ``oct-mcp`` server.

Steps:

1. Resolve and validate project root (Layer 1 - Gateway).
2. Load :class:`McpConfig` (with env var overrides).
3. Apply centralised policy overrides (Phase 5C, M-C7).
4. Override profile if ``--profile`` CLI arg was provided.
5. Instantiate all pipeline layers.
6. Start Prometheus metrics server (optional, Phase 5C).
7. Set ``_server_state`` singleton.
8. Build the SDK server with all 20 tools registered
   (:func:`build_mcp_server`).
9. Run the server (stdio, streamable HTTP, or SSE transport). Under
   stdio, first hand fd 1 back to the transport
   (:func:`oct.mcp.stdout_diversion.restore_stdout`; a no-op when no
   diversion is active, as in an in-process call), and exit with
   code 1 if a diversion is still recorded afterwards.

Parameters
----------
project_root
    Project root path override (``None`` = auto-discover).
profile
    MCP profile override (``None`` = use config file / env var).
config_path
    Path to the ``~/.oct-mcp/config.json`` config file
    (``None`` = use default location).
transport
    MCP transport: ``"stdio"`` (default) for local AI hosts,
    ``"streamable-http"`` for remote access, or ``"sse"`` for the
    legacy HTTP/Server-Sent Events transport.
host
    Host to bind for the HTTP transports (default ``"127.0.0.1"``).
    Ignored when *transport* is ``"stdio"``.
port
    Port to bind for the HTTP transports (default ``3001``).
    Ignored when *transport* is ``"stdio"``.
metrics_port
    If set, starts a Prometheus ``/metrics`` server on this port.
    Overrides ``config.metrics_port``. Requires ``oct[metrics]``.

Definition at line 315 of file server.py.

Here is the call graph for this function:

Variable Documentation

◆ SERVER_INSTRUCTIONS

tuple oct.mcp.server.SERVER_INSTRUCTIONS
Initial value:
1= (
2 "Option C Tools MCP server - exposes oct linter, health check, "
3 "skeleton exporter, dependency checker, test runner, type checker, "
4 "diagnostics, and git tools as MCP tools."
5)

Definition at line 255 of file server.py.