mcp-server: accept JSON-RPC notifications (no id field)
JSON-RPC 2.0 notifications are fire-and-forget and omit the `id` field entirely. The server was requiring `id: Value`, so MCP clients sending `notifications/initialized` after the handshake got a `parse error: missing field \`id\`` response back. Claude Code then treated the handshake as broken and never called `tools/list`, so none of our memory/journal/graph/channel tools showed up in the client's tool list. Make `Request.id` an `Option<Value>` with `#[serde(default)]`: - Missing id → notification (None) → no response of any kind. - Explicit null id → still a request, null response id (legal). - Parse errors still respond with id:null per spec. Update respond() / respond_error() to no-op on None so a dispatched handler that tries to reply to a notification silently does nothing instead of violating the spec. Verified with a manual handshake: initialize → notification → tools/list now round-trips cleanly. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
199ce0ecdc
commit
11e2f20820
1 changed files with 16 additions and 4 deletions
|
|
@ -23,7 +23,11 @@ struct Request {
|
|||
method: String,
|
||||
#[serde(default)]
|
||||
params: Value,
|
||||
id: Value,
|
||||
/// Absent for JSON-RPC notifications (fire-and-forget); present for
|
||||
/// requests that expect a response. Note that `Some(Value::Null)` is
|
||||
/// an explicit null id (still a request), distinct from `None`.
|
||||
#[serde(default)]
|
||||
id: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
@ -50,7 +54,10 @@ struct DaemonResponse {
|
|||
id: Value,
|
||||
}
|
||||
|
||||
fn respond(id: Value, result: Value) {
|
||||
/// Write a successful response for a request. Silently no-ops for
|
||||
/// notifications (id = None) — they are fire-and-forget.
|
||||
fn respond(id: Option<Value>, result: Value) {
|
||||
let Some(id) = id else { return; };
|
||||
let resp = Response { jsonrpc: "2.0".into(), result, id };
|
||||
let json = serde_json::to_string(&resp).unwrap();
|
||||
let mut stdout = io::stdout().lock();
|
||||
|
|
@ -58,7 +65,11 @@ fn respond(id: Value, result: Value) {
|
|||
let _ = stdout.flush();
|
||||
}
|
||||
|
||||
fn respond_error(id: Value, code: i64, message: &str) {
|
||||
/// Write an error response for a request. Silently no-ops for
|
||||
/// notifications (id = None) per JSON-RPC 2.0 — a server MUST NOT reply
|
||||
/// to a notification, even with an error.
|
||||
fn respond_error(id: Option<Value>, code: i64, message: &str) {
|
||||
let Some(id) = id else { return; };
|
||||
let resp = ErrorResponse {
|
||||
jsonrpc: "2.0".into(),
|
||||
error: json!({ "code": code, "message": message }),
|
||||
|
|
@ -232,7 +243,8 @@ fn main() {
|
|||
Err(e) => {
|
||||
eprintln!("consciousness-mcp: bad json-rpc: {e}");
|
||||
eprintln!("consciousness-mcp: input was: {line}");
|
||||
respond_error(Value::Null, -32700, &format!("parse error: {e}"));
|
||||
// Per spec, parse errors get a response with id: null.
|
||||
respond_error(Some(Value::Null), -32700, &format!("parse error: {e}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue