Skip to main content

AprMcpServer

Struct AprMcpServer 

Source
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

Source

pub fn new() -> Self

Construct a new server.

Source

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).

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn serve_stream<R, W>( &mut self, reader: R, out: Arc<Mutex<W>>, ) -> Result<()>
where R: BufRead, W: Write + Send + 'static,

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 mcp invocation loses every tools/call result 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.

Source

pub fn in_flight_handle(&self) -> Arc<Mutex<HashMap<Value, CancelHandle>>>

Handle for tests that want to inspect the in-flight registry.

Trait Implementations§

Source§

impl Debug for AprMcpServer

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for AprMcpServer

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.