pub struct AprMcpServer { /* private fields */ }Expand description
MCP server exposing the apr CLI as tools.
M1: initialize, tools/list, tools/call with apr.version.
M3: notifications/cancelled routed to in-flight apr.run workers.
Implementations§
Source§impl AprMcpServer
impl AprMcpServer
Sourcepub fn handle_request(&mut self, request: &JsonRpcRequest) -> JsonRpcResponse
pub fn handle_request(&mut self, request: &JsonRpcRequest) -> JsonRpcResponse
Dispatch a single JSON-RPC request synchronously.
This is the in-process test entry point. It does NOT exercise the
threading / cancellation machinery — apr.run runs inline with a
dummy never-firing cancel receiver and NO notification sink is
attached, so apr.finetune silently falls back to its synchronous
path even if the request carries params._meta.progressToken. Use
Self::run_stdio for the full M3 dispatcher or
Self::handle_request_with_sink to drive FALSIFY-MCP-PROGRESS-001
in tests.
The dispatcher enforces one protocol-level invariant before routing:
FALSIFY-MCP-005 (jsonrpc must be exactly "2.0" or the response is
-32600 Invalid Request). Version negotiation is NOT a gate — see
Self::handle_initialize (FALSIFY-MCP-007).
Sourcepub fn handle_request_with_sink(
&mut self,
request: &JsonRpcRequest,
sink: &NotificationSink,
) -> Option<JsonRpcResponse>
pub fn handle_request_with_sink( &mut self, request: &JsonRpcRequest, sink: &NotificationSink, ) -> Option<JsonRpcResponse>
Dispatch one request with an explicit notification sink (test entry point for FALSIFY-MCP-PROGRESS-001).
The sink is only exercised for tools/call dispatches where
(a) the client supplied params._meta.progressToken on the original
request AND (b) the target tool supports progress streaming
(currently apr.finetune and apr.run). Other methods ignore the
sink.
handle_request_with_sink returns None for notifications (methods
prefixed with notifications/) because notifications have no id and
MUST NOT receive a response per JSON-RPC 2.0. All other methods
return Some(response).
Sourcepub fn tool_definitions(&self) -> Vec<ToolDefinition>
pub fn tool_definitions(&self) -> Vec<ToolDefinition>
All tool definitions registered on this server.
HELIX-IDEA-002 / FALSIFY-INVENTORY-001: returns whatever
crate::tools::ToolIndex::definitions contains, which is
populated at startup by iterating
inventory::iter::<McpToolEntry>. Adding a new tool requires only
a register_mcp_tool! invocation in that tool’s module — no
edit here.
Sourcepub fn register_in_flight(
in_flight: &Arc<Mutex<HashMap<Value, CancelHandle>>>,
id: Value,
) -> Receiver<()>
pub fn register_in_flight( in_flight: &Arc<Mutex<HashMap<Value, CancelHandle>>>, id: Value, ) -> Receiver<()>
Register a new in-flight request and return its cancel receiver.
Exposed for testing the cancellation routing without spawning a real
worker. Production code calls this from Self::run_stdio.
Sourcepub fn cancel_in_flight(
in_flight: &Arc<Mutex<HashMap<Value, CancelHandle>>>,
id: &Value,
) -> bool
pub fn cancel_in_flight( in_flight: &Arc<Mutex<HashMap<Value, CancelHandle>>>, id: &Value, ) -> bool
Route a notifications/cancelled to the matching in-flight request.
Idempotent: repeated cancels for the same id after the first are
silently dropped. References to completed / unknown ids are no-ops.
Returns true iff a live handle was signalled.
Sourcepub fn run_stdio(&mut self) -> Result<()>
pub fn run_stdio(&mut self) -> Result<()>
Run the server over stdio (blocking).
Thin wrapper: binds Self::serve_stream to the real stdin/stdout.
All loop behaviour — and every falsifier for it — lives in
serve_stream, which is generic over its streams precisely so the
read loop can be exercised in-process. run_stdio itself is the one
piece that cannot be unit-tested, so it is kept to two lines with no
logic of its own.
§Errors
Returns an error if stdin/stdout I/O fails.
Sourcepub fn serve_stream<R, W>(
&mut self,
reader: R,
out: Arc<Mutex<W>>,
) -> Result<()>
pub fn serve_stream<R, W>( &mut self, reader: R, out: Arc<Mutex<W>>, ) -> Result<()>
Serve one JSON-RPC-over-newline-delimited-JSON session to completion.
Reads one message per line from reader. initialize, tools/list,
ping, and unknown methods dispatch inline. tools/call spawns a
worker thread so a subsequent notifications/cancelled message can
flow through the main loop and signal the worker’s cancel channel.
Workers write their responses directly to out (guarded by a mutex)
so the main loop never has to wait on them mid-stream.
Three transport invariants live here, all found by dogfooding the shipped binary and all invisible to a dispatcher-level test:
- FALSIFY-MCP-010 — on EOF the loop MUST join every worker it
spawned before returning. Without that join the process exits the
instant stdin closes, so the canonical
printf ... | apr mcpinvocation loses everytools/callresult while still exiting 0 — indistinguishable, to the client, from a tool that produced no output. - FALSIFY-MCP-011 — lines are read as BYTES and decoded per line.
A line that is not valid UTF-8 is a malformed message, answered
with
-32700, not a transport failure that takes the session down with it. - FALSIFY-MCP-DRAIN-001 (#2608) — EOF is not the only way out of
the read loop. Every
?inside it (a stdin read error, a stdout write error) is an exit too, and each one used to abandon the in-flight workers exactly the way the 0.63.0 EOF path did. The drain therefore lives here, on the only return path, rather than at the bottom of the loop where three of the four exits skip it.
W must be Send + 'static because the same handle is shared with
every worker thread.
§Errors
Returns an error if reading or writing the streams fails. The error is reported only AFTER the drain, so a failed session still delivers the answers it already owes.
Sourcepub fn in_flight_handle(&self) -> Arc<Mutex<HashMap<Value, CancelHandle>>> ⓘ
pub fn in_flight_handle(&self) -> Arc<Mutex<HashMap<Value, CancelHandle>>> ⓘ
Handle for tests that want to inspect the in-flight registry.