73 lines
3.6 KiB
Markdown
73 lines
3.6 KiB
Markdown
# 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.
|