Fixes a dead_code warning (clippy -D warnings would fail on it) --
server.rs was calling listener.local_addr() itself before handing the
listener to http_transport::serve(), leaving the handle's own
local_addr field unread.
Replaces the four not_implemented() stubs. mcp_status/set_mcp_enabled
reach the process-wide RmcpServer singleton (behind `#[cfg(feature =
"mcp")]`, with a not_implemented fallback for builds without it);
set_mcp_scope/mcp_access_log are plain settings/Store I/O and work in
every build regardless of the `mcp` cargo feature. The token is only
ever returned once, right when set_mcp_enabled mints it -- it is never
re-readable afterwards, same as any other freshly-issued secret.
Builds its own tokio runtime (there is no Tauri app in this mode) and
tracing goes to stderr, not stdout -- stdout is the MCP JSON-RPC channel.
Connects to the same wa.db as the GUI instance via SqliteStore::connect,
builds a WaMcpHandler with no AppHandle (None), and runs it to
completion via mcp::stdio_transport::serve_once. A build without the
`mcp` feature prints an error and exits(1) instead of silently opening
the GUI.
http_transport.rs needs tokio_util::sync::CancellationToken directly;
rmcp pulls tokio-util transitively but that doesn't make it `use`-able
from our own crate without a direct Cargo.toml entry.
Checked before whispassist_lib::run() builds the Tauri app -- the flag
routes straight to run_mcp_stdio() (next commit) and returns instead of
opening a window.
The --mcp-stdio child process (main.rs, next commits) has no running
Tauri app/window to emit "mcp://access" events to -- only the GUI
instance's HTTP transport does. The mcp_access_log DB row is still
written unconditionally either way (FR-MCP-5); only the live event is
skipped when there's no AppHandle.
start() mints/stores a fresh token, then either binds the loopback HTTP
listener (bind_loopback + http_transport::serve) or, for stdio, just
records "enabled" and hands back the whishassist.exe --mcp-stdio command
line the agent's client config should spawn -- there is nothing to run
in-process for stdio (see mcp::stdio_transport). stop() tears down the
HTTP listener if any and deletes the stored token. instance() is a
process-wide singleton (OnceLock) so separate Tauri command invocations
(set_mcp_enabled/mcp_status/set_mcp_scope) share the same running state.
serve_once() runs one MCP session over the current process's stdin/
stdout to completion -- the entry point main.rs's --mcp-stdio flag calls
into (next commit). No bearer-token check here: spawning this process
at all requires the same local-user privilege as any other command, so
process-spawn capability is the trust boundary for stdio, not a header.
Mirrors sync::credentials: mint_and_store() generates a fresh 32-byte
random token per set_mcp_enabled call and writes it via `keyring`
(Windows Credential Manager); the token is never persisted to
settings.json/wa.db/logs. verify() is a constant-time compare so token
checking doesn't leak timing information about a partial match
(NFR-SEC-5).
bind_loopback() refuses to bind anything that doesn't resolve to a
loopback address -- unit tested directly (non-loopback IP, unparseable
host, and a real 127.0.0.1:0 bind). serve() runs a hyper HTTP/1 accept
loop in front of rmcp's StreamableHttpService (a bare tower_service, not
an axum app); every request needs `Authorization: Bearer <token>`
(constant-time compared via mcp::token::verify) before it ever reaches
the MCP service -- an unauthenticated request never reaches rmcp at all.
rmcp gains transport-streamable-http-server + transport-io (the actual
serving code -- the base "server" feature only got tool routing) plus
client + transport-streamable-http-client-reqwest, used solely by this
crate's own in-process MCP-client tests (WA never opens an outbound MCP
connection at runtime, so this is not new egress, FR-MCP-7). hyper/
hyper-util/http-body-util/http/bytes/tower-service are the low-level glue
to run HTTP/1 over a loopback TcpListener in front of rmcp's
StreamableHttpService (a bare tower_service::Service, not an axum app).
All are already in Cargo.lock transitively via reqwest/tauri -- no new
crates, just promoted to direct deps under the existing `mcp` feature.
WaMcpHandler wires list_recent_meetings/get_transcript/get_action_items/
get_feature_brief to Store, re-checking ExposeScope + the recordings
gate independently in every handler (FR-MCP-3) and logging every read
via record_mcp_access + an "mcp://access" event (FR-MCP-5) before scope
is even evaluated, so the audit trail is "what was asked for", not just
"what was returned". get_feature_brief calls through the existing
commands::get_feature_brief stub per the M1/M2 integration contract --
it always errors right now since M1's brief storage isn't implemented
in this worktree yet; the `selected`-scope exposed-flag check is left
as a marked TODO for when M1 lands.
Pure functions (no DB, no cargo feature gate) so scope control is
testable without a running server: meetings_visible/brief_visible/
recording_gate_ok/meeting_allowed. Documents the conservative choice for
`selected` scope on meetings/transcript/action-items -- there's no
per-meeting selection flag in the schema yet (only feature_briefs.exposed
does), so `selected` behaves like `none` there until a real "pick which
meetings" mechanism exists, rather than silently behaving like `all`.
ExposeScope/McpTransport gain as_str/parse (privacy-safe fallback: an
unparsed scope becomes None, not All/Selected) so commands.rs can read
them out of Settings' string fields. Declares the mcp submodules
(scope always compiled; handler/http_transport/server/stdio_transport/
token behind the `mcp` cargo feature) and removes the todo!() RmcpServer
stub -- its real implementation moves to mcp::server in the next commits.
Adds list_action_items (backs the get_action_items MCP tool -- distinct
from list_pending_reminders, which is reminder-scoped across all
meetings) and record_mcp_access/list_mcp_access_log (FR-MCP-5 audit
trail) to the Store trait + SqliteStore, reusing the mcp_access_log
table from migrations/0003_ai_mcp.sql.
Adds mcp_enabled/mcp_transport/mcp_port/mcp_expose/mcp_expose_recordings
to Settings, all defaulting to off/none so an upgrading settings.json
gets a fully-local default (FR-MCP-1). The auth token itself never lives
here -- OS credential store only (see mcp::token, next commit).