|
oct
|
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 |
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)
|
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.
|
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.
|
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.
| 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.
| ServerState oct.mcp.server.get_server_state | ( | ) |
| 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.