Purpose
-------
Entry point for ``python -m oct.mcp``: declare the command-line surface
of the ``oct-mcp`` server, parse it, and hand the resolved options to
:func:`oct.mcp.server.start_server`.
Responsibilities
----------------
- Declare every CLI option (project root, profile, config file,
transport, bind host and port, metrics port) in one parser.
- Translate ``--config`` into a :class:`~pathlib.Path` before handing it
on, so the server layer never parses strings.
- Under the ``stdio`` transport, divert standard output (fd 1) to
standard error from just after argument parsing until
:func:`oct.mcp.server.start_server` hands it to the SDK transport
(:mod:`oct.mcp.stdout_diversion`), so nothing written during startup
reaches the JSON-RPC channel.
- Translate an interactive ``Ctrl-C`` into a clean exit code 0.
Usage
-----
python -m oct.mcp [--project-root PATH] [--profile PROFILE]
[--transport {stdio,sse,streamable-http}]
[--host HOST] [--port PORT]
[--metrics-port PORT]
Options
-------
--project-root PATH
Absolute or relative path to the Option C project root.
Defaults to auto-discovery from the current working directory.
--profile PROFILE
MCP security profile: ``default``, ``strict``, or ``airgapped``.
Overrides ``OCT_MCP_PROFILE`` and the config file value.
--config PATH
Path to the user-level config file.
Defaults to ``~/.oct-mcp/config.json``.
--transport {stdio,sse,streamable-http}
MCP transport. ``stdio`` (default) for local AI host integration
(Claude Code, Cursor, VS Code). ``streamable-http`` for remote
access. ``sse`` is the legacy remote transport, kept for hosts that
have not moved to streamable HTTP.
--host HOST
Bind host for the HTTP transports (default: ``127.0.0.1``).
Only used with ``--transport streamable-http`` or ``--transport
sse``. Use ``0.0.0.0`` with caution and only behind a reverse proxy
with TLS and authentication.
--port PORT
Bind port for the HTTP transports (default: ``3001``).
Only used with ``--transport streamable-http`` or ``--transport
sse``.
--metrics-port PORT
Start a Prometheus ``/metrics`` HTTP server on this port.
Disabled by default. Requires ``oct[metrics]`` to be installed.
Environment variables
---------------------
OCT_MCP_SAFE_MODE=1 Block all tool calls (emergency kill-switch).
OCT_TRUST_REPO_CONFIG=1 Trust project-level config (debug_config.json).
OCT_MCP_PROFILE=<name> Override active profile.
OCT_MCP_SANDBOX=<name> Override sandbox backend (auto/native/bubblewrap/sandbox_exec).
Diagnostics
-----------
Domain: MCP
Levels:
L1 - errors: startup failures raised out of :func:`start_server`
L2 - lifecycle: server start and shutdown
L3 - request details
L4 - deep trace
This module emits one ``_dbg`` record of its own, the L3 server entry,
after argument parsing and after the stdio diversion has started, so it
can never reach the protocol channel. Every other diagnostic for a run
comes from :mod:`oct.mcp.server` and the pipeline layers beneath it.
Contracts
---------
- :func:`main` returns ``None``. A fatal startup error reaches the
caller as the ``SystemExit(1)`` raised by
:func:`oct.mcp.server.start_server`, so the process exit code is the
server layer's, not this module's.
- An unrecognised ``--transport`` or ``--profile`` value is refused by
``argparse`` with exit code 2 and the offending value named.
- ``KeyboardInterrupt`` exits 0: stopping a stdio server with Ctrl-C is
the normal way a host detaches, not a failure.
- Under the ``stdio`` transport, fd 1 is diverted before the first
``_dbg`` call. :func:`oct.mcp.server.start_server` hands it back
immediately before the transport runs, and exits with code 1 instead
of serving when that fails; :func:`main` hands it back after a
startup failure or a ``Ctrl-C``. ``--help`` and ``argparse`` errors
are printed before the diversion, to standard output and standard
error as before. The HTTP transports are not diverted.
- Nothing imported before the diversion starts (the ``oct.mcp``
package and this module's module-level imports) may emit a ``_dbg``
record at import time, because such a record would reach the wire.
:mod:`oct.mcp.server` is imported lazily, after the diversion. The
end-to-end test with every domain at level 9 in
``tests/tests_mcp/test_mcp_stdout_diversion.py`` guards this.
Dependencies
------------
- oct.core.diagnostics (_dbg structured logger)
- oct.mcp.stdout_diversion (divert_stdout and restore_stdout, which keep startup output off the stdio wire)
- oct.mcp.server (lazy; start_server, which runs the server with the parsed options)