Skip to content

Unrecognized extension notifications (_-prefixed) are rejected instead of ignored #228

Closed

Description

@sasasin

Summary

When the SDK receives an unrecognized notification whose method starts with _ (an ACP extension notification), it sends a method_not_found error response back to the remote side. This contradicts:

  1. The SDK's own RFD design — "ClientToAgent - Errors on unhandled requests, ignores unhandled notifications"
  2. The ACP specification — "implementations SHOULD ignore unrecognized notifications"
  3. The JSON-RPC 2.0 specification — "The Server MUST NOT reply to a Notification"

The SDK currently only silently ignores $/-prefixed notifications. Extension notifications (_-prefixed) fall through to the rejection branch and produce an id: null error response on the wire, which can destabilize the remote agent.

Reproduction scenario

A concrete example of this occurring in the wild: Zed editor (which uses this SDK as its ACP client) connecting to xAI's Grok Build agent. Grok Build sends extension notifications such as:

{"jsonrpc":"2.0","method":"_x.ai/mcp/servers_updated","params":{...}}
{"jsonrpc":"2.0","method":"_x.ai/settings/update","params":{...}}
{"jsonrpc":"2.0","method":"_x.ai/announcements/update","params":{...}}

These are valid ACP extension notifications (method starts with _, no id field). The SDK rejects each one by sending back:

{"jsonrpc":"2.0","id":null,"error":{"code":-32601,"message":"Method not found","data":"_x.ai/mcp/servers_updated"}}

This id: null error response causes noise on the agent side (e.g. stderr messages about unexpected responses), and in some cases disrupts the connection stability. An external proxy (zed-acp-coding-agent-proxy) was created specifically to filter out these notifications before they reach the SDK, working around this issue.

Code path

The rejection happens via the following path:

1. No handler matches the extension notification.

Zed (and likely any client that doesn't explicitly handle every possible vendor extension) has no handler registered for _x.ai/... methods. The Client role's default_handle_dispatch_from returns Handled::No { retry: false }.

2. is_protocol_level_notification excludes _-prefixed methods.

src/agent-client-protocol/src/jsonrpc.rs lines 1993–1999:

fn is_protocol_level_notification(dispatch: &Dispatch) -> bool {
    let Dispatch::Notification(message) = dispatch else {
        return false;
    };
    let (method, _params) = peel_successor_envelopes(&message.method, &message.params);
    method.starts_with("$/")
}

Only $/-prefixed notifications are silently ignored. Extension notifications (_-prefixed) do not match.

3. The notification falls through to the rejection branch.

src/agent-client-protocol/src/jsonrpc/incoming_actor.rs lines 454–463:

    } else {
        match dispatch {
            Dispatch::Request(..) | Dispatch::Notification(_) => {
                tracing::info!(?method, "Rejecting message with error, no handler");
                let method = dispatch.method().to_string();
                dispatch.respond_with_error(
                    crate::Error::method_not_found().data(method),
                    connection.clone(),
                )
            }
            // ...
        }
    }

Unhandled Notification and Request share the same rejection path. For notifications, respond_with_error calls cx.send_error_notification(error), which sends OutgoingMessage::Error → serialized as RawJsonRpcMessage::response(RequestId::Null, Err(error)).

Specification references

ACP v1 Extensibility (agentclientprotocol.com/protocol/v1/extensibility):

Custom notifications are regular JSON-RPC notifications that start with an underscore (_). [...] Unlike with custom requests, implementations SHOULD ignore unrecognized notifications.

ACP Rust SDK RFD (agentclientprotocol.com/rfds/rust-sdk-v1):

ClientToAgent - Errors on unhandled requests, ignores unhandled notifications

JSON-RPC 2.0 (jsonrpc.org/specification § 4.1):

A Notification is a Request object without an "id" member. [...] The Server MUST NOT reply to a Notification, including those that are within a batch request.

Suggested fix

The simplest fix would be to broaden is_protocol_level_notification (or add a separate check) so that all unhandled notifications are silently ignored, not just $/-prefixed ones. This would align with both the ACP spec's SHOULD and the JSON-RPC 2.0 MUST NOT.

For example, the check in incoming_actor.rs at line 454–463 could differentiate between requests and notifications:

    } else {
        match dispatch {
            Dispatch::Notification(_) => {
                // ACP spec: "implementations SHOULD ignore unrecognized notifications"
                // JSON-RPC 2.0: "The Server MUST NOT reply to a Notification"
                tracing::debug!(?method, "Ignoring unhandled notification");
                Ok(())
            }
            Dispatch::Request(..) => {
                tracing::info!(?method, "Rejecting request with error, no handler");
                let method = dispatch.method().to_string();
                dispatch.respond_with_error(
                    crate::Error::method_not_found().data(method),
                    connection.clone(),
                )
            }
            Dispatch::Response(result, router) => {
                tracing::trace!(?method, "Forwarding response");
                router.respond_with_result(result)
            }
        }
    }

This makes the distinction clear: requests get method_not_found (correct per both ACP and JSON-RPC 2.0), while notifications are silently dropped (correct per both specs).

An alternative minimal change would be to extend is_protocol_level_notification to also match _-prefixed methods, but ignoring all unhandled notifications (regardless of prefix) seems more robust and more aligned with the specs.

Environment

  • SDK version observed: agent-client-protocol 1.0.0 (from crates.io)
  • Downstream client: Zed editor (latest main branch)
  • Downstream agent: xAI Grok Build (@xai-official/grok npm package)

Activity

  1. benbrandt commented on Jun 29, 2026

    @benbrandt
    Member

    Thanks for flagging, fix on the way!

  2. added a commit that references this issue on Jun 29, 2026
    c416bec
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions