Files
ottyl/tickets/109-mcp-not-tied-to-a-single-session-at-startup.md
T

3.6 KiB

109 — Stop binding ottyl mcp to one session for its whole lifetime

Summary

ottyl mcp --session <name> resolves the session name to a port once, at process startup, and then serves stdio JSON-RPC for the life of the process against that fixed URL. In practice this means every ottyl session an agent wants to talk to needs its own registered MCP server entry (e.g. ottyl-hygienic-gyrfalcon), and switching which running ottyl instance a client is looking at requires killing that entry and re-registering a new one — which, for MCP clients that only read server config at startup, means the whole host process needs restarting before the new tools are even callable. This got hit directly: an agent needed to switch from an old dead session (enormous-warthog) to a new one (hygienic-gyrfalcon) mid-conversation and had no way to do it without editing MCP config and waiting for a restart.

Where

  • src/cli/mod.rs:43-49 — Command::Mcp { session: Option<String>, port: Option<u16> }, the CLI surface.
  • src/cli/mod.rs:171-195 (run_mcp) — resolves session to a port via sessions::resolve_session exactly once, builds one OttylMcp::new(base_url), then blocks forever on service.waiting(). There is no re-resolution after startup.
  • src/api/mcp_server.rs:254-282 — OttylMcp stores base_url: String as a plain field set once in new(); every #[tool] method builds request URLs via self.url(path) (mcp_server.rs:282-284), so the fixed base URL is threaded through every single tool call.
  • src/cli/sessions.rs — SessionInfo { name, port, pid } and resolve_session/list_sessions, the registry this would need to re-query if the bound session can change after startup.

Direction

Two ways to remove the "pick a session forever at startup" behavior; pick one during implementation rather than building both:

  1. Request/response CLI, no long-running process. Add a CLI subcommand (e.g. ottyl call <session> <tool> <json-args>) that resolves the session, makes the one HTTP call, prints the result, and exits — no rmcp/stdio JSON-RPC server at all. Simplest fix for the "MCP config never changes, session does" mismatch, but loses whatever the MCP client gives you over a bare CLI call (tool discovery/schema, a persistent connection) — check whether that matters to how this is actually invoked before going this route.
  2. Long-running MCP server, session switchable at runtime. Keep ottyl mcp as a stdio server, but stop fixing base_url at construction: either (a) accept a session (or port) argument on every tool call, resolved against sessions::resolve_session per-call instead of once in run_mcp, with the constructor's --session/--port becoming just a default; or (b) add an explicit switch_session tool that mutates the stored target (base_url would need to move from a plain field to something mutable, e.g. Mutex<String> or resolved fresh from the registry on every call from a stored session name instead of a pre-resolved URL).

Scope

  • Land one of the two directions above for ottyl mcp / src/api/mcp_server.rs; out of scope to build both.
  • If going with direction 2, update SKILL.md's "Connect an MCP client" section, since it currently documents the resolve-once-at-startup model.
  • Out of scope: changing anything about the underlying /api/... HTTP routes themselves, or the session registry format in src/cli/sessions.rs — this is purely about how the MCP-facing process picks and holds (or doesn't hold) its target session.