feat: serve familiar as a remote HTTP MCP server (bearer auth) #63

Merged
Latte merged 3 commits from feat/remote-http into dev 2026-07-04 16:46:14 +00:00
Owner

What

Makes familiar reachable over HTTPS as a remote MCP server so it can be wired up as a custom connector in chat.mistral.ai (or any remote MCP client). This is purely a transport-layer addition — the EVE-SSO/ESI multi-character auth in auth.py is unchanged.

stdio stays the default and is completely untouched: familiar / python -m familiar run exactly as before.

Changes

  • familiar serve — new subcommand that runs the FastMCP streamable-HTTP transport. Binds 127.0.0.1:8080/mcp by default (a TLS reverse proxy is expected to terminate HTTPS in front of it). Host/port/path are env-configurable via FAMILIAR_HTTP_HOST / FAMILIAR_HTTP_PORT / FAMILIAR_HTTP_PATH.
  • Bearer auth — the expected token is read from FAMILIAR_HTTP_TOKEN. HTTP mode refuses to start without it (no open server). Requests without a valid Authorization: Bearer <token> get 401 + WWW-Authenticate: Bearer; a valid token passes the MCP handshake. Uses FastMCP's built-in StaticTokenVerifier. stdio gets no token check (local transport).
  • Writes off by default over HTTP — a network-reachable, LLM-driven endpoint shouldn't send mail or set waypoints by accident, so over HTTP writes default to disabled unless FAMILIAR_ENABLE_WRITES is explicitly truthy. Only the default resolution changes; the writes.guard choke point and per-call confirm=true requirement are untouched. Over stdio the default is unchanged (writes on).
  • Headless login — familiar login --no-browser prints the EVE SSO authorize URL, opens no browser, and reads the pasted callback URL (or bare code) from stdin, for provisioning characters on a headless server. The interactive localhost-callback login stays the default. Docs also cover the alternative: log in on the desktop and copy the encrypted store (token.enc + key) to the server.
  • fastmcp pin bumped to >=3.0.0 for the streamable-HTTP + verifier API; uv.lock refreshed.

Tests

New tests/test_serve.py (follows the existing conftest style, no async-test plugin):

  • 401 without a token / with a wrong token (incl. the WWW-Authenticate header), 200 with a valid token — driven through the ASGI app in-process.
  • Start refusal when FAMILIAR_HTTP_TOKEN is unset or blank.
  • FAMILIAR_HTTP_* config defaults and overrides.
  • Write default in HTTP context (disabled by default, env still wins, stdio stays enabled).
  • Headless-login callback parsing and the manual_login exchange flow.

uv run ruff format . && uv run ruff check . && uv run pytest is green, coverage 91% (>= 80%). Also smoke-tested end-to-end: FAMILIAR_HTTP_TOKEN=… familiar serve binds the port, returns 401 without a token and 200 with the right bearer; stdio still boots unchanged.

Docs

README.md and doc/getting-started.md get a "Serve over HTTPS (remote MCP)" section (env vars, an example nginx location block with proxy_buffering off; and a long read timeout, headless-login/copy-the-store options, and the Mistral connector steps). doc/architecture.md describes the stdio/HTTP transport split and the HTTP write default. CLAUDE.md gets the new commands and env vars.

## What Makes familiar reachable over HTTPS as a remote MCP server so it can be wired up as a custom connector in chat.mistral.ai (or any remote MCP client). This is purely a transport-layer addition — the EVE-SSO/ESI multi-character auth in `auth.py` is unchanged. **stdio stays the default and is completely untouched**: `familiar` / `python -m familiar` run exactly as before. ## Changes - **`familiar serve`** — new subcommand that runs the FastMCP **streamable-HTTP** transport. Binds `127.0.0.1:8080/mcp` by default (a TLS reverse proxy is expected to terminate HTTPS in front of it). Host/port/path are env-configurable via `FAMILIAR_HTTP_HOST` / `FAMILIAR_HTTP_PORT` / `FAMILIAR_HTTP_PATH`. - **Bearer auth** — the expected token is read from `FAMILIAR_HTTP_TOKEN`. HTTP mode **refuses to start without it** (no open server). Requests without a valid `Authorization: Bearer <token>` get `401` + `WWW-Authenticate: Bearer`; a valid token passes the MCP handshake. Uses FastMCP's built-in `StaticTokenVerifier`. stdio gets no token check (local transport). - **Writes off by default over HTTP** — a network-reachable, LLM-driven endpoint shouldn't send mail or set waypoints by accident, so over HTTP writes default to disabled unless `FAMILIAR_ENABLE_WRITES` is explicitly truthy. Only the default resolution changes; the `writes.guard` choke point and per-call `confirm=true` requirement are untouched. Over stdio the default is unchanged (writes on). - **Headless login** — `familiar login --no-browser` prints the EVE SSO authorize URL, opens no browser, and reads the pasted callback URL (or bare code) from stdin, for provisioning characters on a headless server. The interactive localhost-callback login stays the default. Docs also cover the alternative: log in on the desktop and copy the encrypted store (`token.enc` + `key`) to the server. - **fastmcp pin** bumped to `>=3.0.0` for the streamable-HTTP + verifier API; `uv.lock` refreshed. ## Tests New `tests/test_serve.py` (follows the existing conftest style, no async-test plugin): - 401 without a token / with a wrong token (incl. the `WWW-Authenticate` header), 200 with a valid token — driven through the ASGI app in-process. - Start refusal when `FAMILIAR_HTTP_TOKEN` is unset or blank. - `FAMILIAR_HTTP_*` config defaults and overrides. - Write default in HTTP context (disabled by default, env still wins, stdio stays enabled). - Headless-login callback parsing and the `manual_login` exchange flow. `uv run ruff format . && uv run ruff check . && uv run pytest` is green, coverage 91% (>= 80%). Also smoke-tested end-to-end: `FAMILIAR_HTTP_TOKEN=… familiar serve` binds the port, returns 401 without a token and 200 with the right bearer; stdio still boots unchanged. ## Docs `README.md` and `doc/getting-started.md` get a "Serve over HTTPS (remote MCP)" section (env vars, an example nginx location block with `proxy_buffering off;` and a long read timeout, headless-login/copy-the-store options, and the Mistral connector steps). `doc/architecture.md` describes the stdio/HTTP transport split and the HTTP write default. `CLAUDE.md` gets the new commands and env vars.
Latte added 3 commits 2026-07-04 16:22:51 +00:00
Add a `familiar serve` subcommand that runs the FastMCP streamable-HTTP
transport for use as a remote MCP server (e.g. a Mistral custom connector),
while stdio stays the default and is completely unchanged.

- config: FAMILIAR_HTTP_HOST/PORT/PATH (default 127.0.0.1:8080/mcp) and a
  live http_token() reader for FAMILIAR_HTTP_TOKEN.
- server: configure_http()/serve_http() attach a StaticTokenVerifier so a
  missing/wrong bearer token gets 401 + WWW-Authenticate: Bearer and a valid
  one passes the handshake. Refuses to start (SystemExit) without a token.
- writes: in HTTP mode the write default flips to disabled unless
  FAMILIAR_ENABLE_WRITES is explicitly truthy; guard() logic is untouched.
- auth: manual_login() + parse_callback() power `login --no-browser`, printing
  the SSO URL and reading the pasted callback from stdin (no local browser).
- bump fastmcp pin to >=3.0.0 for the streamable-HTTP + verifier API.
- 401 without/with a bad token (incl. WWW-Authenticate: Bearer), 200 with a
  valid one, driven through the ASGI app in-process.
- configure_http refuses to start when the token is unset or blank.
- FAMILIAR_HTTP_* config defaults and overrides; http_token() strip/empty.
- HTTP write default disabled by default, env still wins, stdio stays enabled.
- parse_callback (URL / bare code / empty) and the manual_login exchange flow.
docs: document the remote-HTTP serve mode, env vars, nginx and Mistral setup
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
ceb907b2fe
- README + getting-started: a "Serve over HTTPS (remote MCP)" section with the
  `familiar serve` command, the FAMILIAR_HTTP_* env vars, the write-off-by-
  default note, an nginx location block (proxy_buffering off, long read
  timeout), the headless-login / copy-the-store options, and the steps to add
  it as a Mistral custom connector.
- architecture: describe the stdio/HTTP transport split, the bearer-auth gate,
  and the HTTP write default.
- CLAUDE.md: add the new commands and env vars to Commands + Architecture.
Latte merged commit 3b2c04cde3 into dev 2026-07-04 16:46:14 +00:00
Latte deleted branch feat/remote-http 2026-07-04 16:46:14 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Hiddenden/familiar#63