Skip to content

feat: add timeout for client/server -> transport calling option - #50

Merged
zoedsoupe merged 4 commits into
mainfrom
feat/client-server-transport-timeout
Oct 27, 2025
Merged

zoedsoupe merged 4 commits into
mainfrom
feat/client-server-transport-timeout

Conversation

@zoedsoupe

@zoedsoupe zoedsoupe commented Oct 24, 2025 •

Copy link
Copy Markdown
Owner

Problem

Client functions like call_tool/4 already had the timeout option passed to the internal Operation struct, which is handled by the GenServer, however the send_message/2 callback of all transports weren't accepting a timeout option, which fallbacks to 5s

Solution

Remove send_message/2 and implement send_message/3 that accepts a kw as options (for now only timeout) to control this internal timeout flow.

This option can be passed on the start_link parameters of both client and server processes

Rationale

It probably correctly solves #45 without relying on :infinity (need testing)

Summary by CodeRabbit

  • New Features

    • Per-operation and per-server configurable timeouts applied end-to-end, so requests, notifications and responses respect configured timeouts.
    • Transport sends (SSE, WebSocket, HTTP, STDIO) now honor per-call timeout options.
  • Breaking Changes

    • Message-sending APIs now accept an options/timeout argument — call sites must be updated.
    • Operations and client/server state include timeout; callers may need to supply timeout values.
  • Tests

    • Test harnesses updated to pass and assert timeout values.

@coderabbitai

coderabbitai Bot commented Oct 24, 2025 •

Copy link
Copy Markdown

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Walkthrough

This PR threads configurable per-operation timeouts through client and server flows. A default timeout constant and a parse option were added; client and server states gain a timeout field. Operation structs require a timeout supplied by callers. All transport send_message APIs were changed to accept options (including timeout) and forward opts[:timeout] into GenServer.call. Call sites across initialize, requests, notifications, sampling, and logging were updated to pass operation.timeout or state.timeout. Tests and test-support mocks/stubs were updated for the new send_message arity.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client.Base
    participant S as Client.State
    participant O as Operation
    participant SV as Server.Base
    participant T as Transport (SSE/STDIO/WS/HTTP)
    participant G as GenServer/Network

    C->>S: init(opts{timeout: T_default})
    S-->>C: state{timeout: T_default}

    C->>O: Operation.new(method, timeout: T_op)
    O-->>C: operation{timeout: T_op}

    C->>T: send_message(message, opts{timeout: O.timeout || S.timeout})
    note right of T #E8F0FF: Transport.send_message/3 forwards\nopts[:timeout] into GenServer.call
    T->>G: GenServer.call(..., timeout: provided_timeout)
    alt success
        G-->>T: response
        T-->>C: :ok | {:ok, resp}
    else timeout/error
        G-->>T: {:error, :timeout} | {:error, reason}
        T-->>C: {:error, reason}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • P0 (highest) — Anubis.Transport.Behaviour change: ensure all implementations/clients conform to new send_message/3 callback and dialyzer specs.
  • P1 — Transport modules (SSE, STDIO, StreamableHTTP, WebSocket): verify GenServer.call timeouts, guards, and that no blocking/cast semantics were accidentally broken (stdio had casts replaced by calls and commented-out behavior).
  • P2 — Client/Server base modules, Operation and State: validate timeout defaulting, propagation (operation vs state), and init/parse_options consistency.
  • P3 — Tests and test-support mocks/stubs: confirm mocks/stubs use correct arity and reasonable default timeout values.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.41% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "feat: add timeout for client/server -> transport calling option" directly aligns with the main change in the changeset. The pull request introduces send_message/3 with timeout support across all transport implementations (SSE, STDIO, StreamableHTTP, WebSocket), replacing the previous send_message/2. The title clearly indicates this is about adding a timeout option to transport calls from client and server processes. It's concise, avoids noise, and provides enough specificity that a developer scanning history would immediately understand the primary change without ambiguity.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/client-server-transport-timeout

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 772db6f and a24bc12.

📒 Files selected for processing (2)
  • lib/anubis/client/base.ex (21 hunks)
  • test/anubis/client/state_test.exs (8 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
test/**/*.exs

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.exs: Use Anubis.MCP.Case for MCP protocol testing with MockTransport (avoid real transports)
Use Anubis.MCP.Builders for message construction in tests (init_request/1, ping_request/0, tools_list_request/1, build_request/2, build_response/2, build_notification/2)
Use provided setup helpers in tests (setup_client/2, initialize_client/2, initialized_client/2, setup_server/2, initialize_server/2, initialized_server/2, server_with_mock_transport/2)
Use MCP-specific assertions (assert_mcp_response/2, assert_mcp_error/3, assert_mcp_notification/2, assert_success/2, assert_resources/2, assert_tools/2)

Files:

  • test/anubis/client/state_test.exs
**/*.{ex,exs}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ex,exs}: Only add code comments if strictly necessary; avoid comments generally
Formatting must follow .formatter.exs rules with Peri imports; run mix format

Files:

  • test/anubis/client/state_test.exs
  • lib/anubis/client/base.ex
test/**/*_test.exs

📄 CodeRabbit inference engine (CLAUDE.md)

Write descriptive test blocks (e.g., clear describe/it contexts)

Files:

  • test/anubis/client/state_test.exs
lib/anubis/client/**/*.ex

📄 CodeRabbit inference engine (CLAUDE.md)

lib/anubis/client/**/*.ex: In Anubis.Client, use transport via state configuration and send via transport.layer.send_message(transport.name, data)
Use Anubis.Client.State for client state operations (State.new/1, add/remove request, progress callbacks, capability validation/merge)
Use Anubis.Client.Operation to configure requests (progress, timeouts, method/params)
Use Anubis.Client.Request for request lifecycle tracking (ID generation, timing, caller refs)

Files:

  • lib/anubis/client/base.ex
lib/**/*.ex

📄 CodeRabbit inference engine (CLAUDE.md)

lib/**/*.ex: Use @type/@SPEC for all public functions
Use snake_case for function names and PascalCase for modules
Group imports at the top, organized by category (Elixir stdlib, deps, project modules)
Include @moduledoc for modules and @doc with examples for public functions
Prefer pattern matching on {:ok, } and {:error, reason} for error handling
Define defaults as module attributes (e.g., @default
*)
Module structure order: moduledoc, types, constants, public API, GenServer callbacks, private helpers

Files:

  • lib/anubis/client/base.ex
🧠 Learnings (3)
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/client/**/*.ex : Use Anubis.Client.State for client state operations (State.new/1, add/remove request, progress callbacks, capability validation/merge)

Applied to files:

  • test/anubis/client/state_test.exs
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/client/**/*.ex : Use Anubis.Client.Operation to configure requests (progress, timeouts, method/params)

Applied to files:

  • lib/anubis/client/base.ex
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/client/**/*.ex : In Anubis.Client, use transport via state configuration and send via transport.layer.send_message(transport.name, data)

Applied to files:

  • lib/anubis/client/base.ex
🧬 Code graph analysis (2)
test/anubis/client/state_test.exs (2)
lib/anubis/client/operation.ex (1)
  • new (48-55)
lib/anubis/client/state.ex (1)
  • new (43-51)
lib/anubis/client/base.ex (4)
lib/anubis/server/base.ex (4)
  • send_to_transport (679-681)
  • send_to_transport (683-687)
  • encode_request (779-787)
  • encode_notification (673-677)
lib/anubis/client/state.ex (1)
  • add_request_from_operation (55-75)
lib/anubis/mcp/message.ex (4)
  • encode_request (428-430)
  • encode_request (443-448)
  • encode_notification (455-457)
  • encode_notification (469-473)
test/support/stub_transport.ex (1)
  • send_message (83-85)
🔇 Additional comments (13)
lib/anubis/client/base.ex (9)

115-142: LGTM: Default timeout constant and parse_options integration look solid 👍

The 30-second default is reasonable for MCP operations, and the integration into parse_options with proper typing and defaults is clean.


172-264: LGTM: Consistent timeout threading in public API operations 🎯

Nice consistent pattern across ping, list_resources, list_resource_templates, and read_resource:

  • Extract timeout from opts with sensible default
  • Buffer timeout (+1s) prevents GenServer.call race conditions

277-486: LGTM: Timeout consistency maintained across remaining operations ✅

Same solid pattern applied to list_prompts, get_prompt, list_tools, call_tool, and complete. Consistency FTW!


780-822: LGTM: State initialization properly threads timeout 🎯

The state initialization correctly extracts and stores the timeout from parsed options, making it available throughout the client lifecycle.


824-895: LGTM: Proper timeout propagation in GenServer callbacks ✅

Smart distinction:

  • Request operations use operation.timeout (user-configurable per call)
  • Progress notifications use state.timeout (client-level default)

968-998: LGTM: Initialize handshake properly uses state timeout 🤝

The initialization flow correctly uses state.timeout for the handshake operation since it's not a user-configurable call.


1020-1067: LGTM: Server request responses use state timeout correctly 👌

Both roots/list and ping server requests properly use state.timeout when sending responses back to the server.


1503-1518: LGTM: Transport helper functions cleanly updated 🚀

The send_to_transport helper now properly accepts and forwards timeout options. The send_notification helper correctly uses state.timeout for client-initiated notifications.


1571-1633: LGTM: Sampling flows properly use state timeout ✅

Both send_sampling_response and send_sampling_error correctly use state.timeout when responding to server sampling requests. Consistent with the overall pattern!

test/anubis/client/state_test.exs (4)

9-30: LGTM: State initialization test properly verifies timeout field ✅

The test correctly verifies that State.new/1 accepts and stores the timeout value. The hard-coded 30_000 is acceptable here since it's testing the internal state structure.

Note: Previous review suggested using a shared constant (e.g., @default_operation_timeout), but since this is testing State internals and the default is defined in Base, the current approach is fine.


32-57: LGTM: Request addition test updated for required timeout 👍

The test properly includes the required timeout parameter in Operation.new/1. The request lifecycle tracking is correctly verified.


59-244: LGTM: All test operations properly include timeout parameter ✅

All Operation.new/1 calls throughout the test suite have been consistently updated to include the required timeout parameter. Good coverage of request lifecycle, progress callbacks, and pending request management.


331-340: LGTM: Test helper properly includes timeout field 🎯

The new_test_state/0 helper correctly provides all required fields including timeout: 30_000, ensuring consistency across test cases.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@zoedsoupe
zoedsoupe marked this pull request as ready for review October 24, 2025 18:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
test/anubis/server/transport/stdio_test.exs (1)

26-42: Test updated correctly, but describe block title outdated (P3).

The test properly uses the new send_message/3 API with the timeout option. However, the describe "send_message/2" block should be updated to describe "send_message/3" to reflect the new arity.

-  describe "send_message/2" do
+  describe "send_message/3" do
lib/anubis/server/transport/streamable_http.ex (3)

450-452: [P0] Timer not canceled (key mismatch) → use :task_timeout

Map.has_key? checks :timeout_ref but the stored key is :task_timeout. Timer never canceled => stray :task_timeout messages and avoidable mailbox churn.

Apply:

-    if Map.has_key?(task_info, :timeout_ref) do
-      Process.cancel_timer(task_info.task_timeout)
-    end
+    if Map.has_key?(task_info, :task_timeout) do
+      Process.cancel_timer(task_info.task_timeout)
+    end

106-119: [P2] Update docs/spec for new arity and options

Docs still list only transport/message. Add opts (currently :timeout) with default behavior; include @SPEC.

Apply:

@@
-  ## Parameters
-    * `transport` - The transport process
-    * `message` - The message to send
+  ## Parameters
+    * `transport` - The transport process
+    * `message` - The message to send (binary)
+    * `opts` - Keyword options
+      - `:timeout` (ms, default: #{@default_send_timeout}) – GenServer.call timeout
@@
-  @impl Transport
-  def send_message(transport, message, opts) when is_binary(message) do
+  @impl Transport
+  @spec send_message(GenServer.server(), binary(), keyword()) :: :ok | {:error, term()}
+  def send_message(transport, message, opts) when is_binary(message) do

As per coding guidelines.

Also applies to: 120-123


85-92: [P3] Define module default(s) as attributes

Consider moving other literal timeouts to module attributes (e.g., 5_000 in register_sse_handler/2) for consistency.

test/anubis/transport/sse_test.exs (1)

69-69: [P3] Update describe title to new arity

Change “send_message/2” → “send_message/3” for accuracy.

-  describe "send_message/2" do
+  describe "send_message/3" do
lib/anubis/server/transport/sse.ex (1)

120-132: P0: Guard against nil timeout and update API docs/spec.

Passing opts[:timeout] directly can be nil, which will crash GenServer.call/3. Use a sane default and document/spec the new opts.

Apply this change to the function:

-  def send_message(transport, message, opts) when is_binary(message) do
-    GenServer.call(transport, {:send_message, message}, opts[:timeout])
-  end
+  def send_message(transport, message, opts) when is_binary(message) do
+    timeout = Keyword.get(opts, :timeout, @default_call_timeout)
+    GenServer.call(transport, {:send_message, message}, timeout)
+  end

And add (outside this hunk):

# near the top, with other constants
@default_call_timeout 5_000

# just above send_message/3
@spec send_message(GenServer.server(), binary(), keyword()) :: :ok | {:error, term()}

Also extend the @doc to include:

  • opts: [:timeout] – timeout in ms for the call; defaults to 5_000.
    As per coding guidelines.

Also applies to: 134-136

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9af9b8d and 21eb1f1.

📒 Files selected for processing (23)
  • lib/anubis/client/base.ex (10 hunks)
  • lib/anubis/client/operation.ex (1 hunks)
  • lib/anubis/client/state.ex (3 hunks)
  • lib/anubis/server/base.ex (7 hunks)
  • lib/anubis/server/transport/sse.ex (1 hunks)
  • lib/anubis/server/transport/stdio.ex (2 hunks)
  • lib/anubis/server/transport/streamable_http.ex (1 hunks)
  • lib/anubis/transport/behaviour.ex (1 hunks)
  • lib/anubis/transport/sse.ex (1 hunks)
  • lib/anubis/transport/stdio.ex (1 hunks)
  • lib/anubis/transport/streamable_http.ex (1 hunks)
  • lib/anubis/transport/websocket.ex (1 hunks)
  • test/anubis/client/base_test.exs (40 hunks)
  • test/anubis/client/state_test.exs (3 hunks)
  • test/anubis/server/transport/sse_test.exs (1 hunks)
  • test/anubis/server/transport/stdio_test.exs (1 hunks)
  • test/anubis/server/transport/streamable_http_test.exs (1 hunks)
  • test/anubis/transport/sse_test.exs (6 hunks)
  • test/anubis/transport/stdio_test.exs (2 hunks)
  • test/anubis/transport/streamable_http_test.exs (10 hunks)
  • test/anubis/transport/websocket_test.exs (1 hunks)
  • test/support/mock_transport.ex (1 hunks)
  • test/support/stub_transport.ex (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
lib/anubis/transport/**/*.ex

📄 CodeRabbit inference engine (CLAUDE.md)

All transports implement Anubis.Transport.Behaviour with required callbacks start_link/1, send_message/2, shutdown/1 (e.g., Anubis.Transport.{STDIO,SSE,WebSocket,StreamableHTTP})

Files:

  • lib/anubis/transport/sse.ex
  • lib/anubis/transport/streamable_http.ex
  • lib/anubis/transport/websocket.ex
  • lib/anubis/transport/behaviour.ex
  • lib/anubis/transport/stdio.ex
**/*.{ex,exs}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ex,exs}: Only add code comments if strictly necessary; avoid comments generally
Formatting must follow .formatter.exs rules with Peri imports; run mix format

Files:

  • lib/anubis/transport/sse.ex
  • lib/anubis/client/operation.ex
  • lib/anubis/server/transport/stdio.ex
  • lib/anubis/transport/streamable_http.ex
  • test/anubis/server/transport/stdio_test.exs
  • lib/anubis/transport/websocket.ex
  • test/support/mock_transport.ex
  • lib/anubis/server/transport/streamable_http.ex
  • test/anubis/server/transport/sse_test.exs
  • test/support/stub_transport.ex
  • lib/anubis/server/base.ex
  • lib/anubis/server/transport/sse.ex
  • lib/anubis/transport/behaviour.ex
  • test/anubis/client/state_test.exs
  • test/anubis/transport/sse_test.exs
  • test/anubis/server/transport/streamable_http_test.exs
  • lib/anubis/client/state.ex
  • test/anubis/transport/streamable_http_test.exs
  • lib/anubis/transport/stdio.ex
  • test/anubis/client/base_test.exs
  • lib/anubis/client/base.ex
  • test/anubis/transport/websocket_test.exs
  • test/anubis/transport/stdio_test.exs
lib/**/*.ex

📄 CodeRabbit inference engine (CLAUDE.md)

lib/**/*.ex: Use @type/@SPEC for all public functions
Use snake_case for function names and PascalCase for modules
Group imports at the top, organized by category (Elixir stdlib, deps, project modules)
Include @moduledoc for modules and @doc with examples for public functions
Prefer pattern matching on {:ok, } and {:error, reason} for error handling
Define defaults as module attributes (e.g., @default
*)
Module structure order: moduledoc, types, constants, public API, GenServer callbacks, private helpers

Files:

  • lib/anubis/transport/sse.ex
  • lib/anubis/client/operation.ex
  • lib/anubis/server/transport/stdio.ex
  • lib/anubis/transport/streamable_http.ex
  • lib/anubis/transport/websocket.ex
  • lib/anubis/server/transport/streamable_http.ex
  • lib/anubis/server/base.ex
  • lib/anubis/server/transport/sse.ex
  • lib/anubis/transport/behaviour.ex
  • lib/anubis/client/state.ex
  • lib/anubis/transport/stdio.ex
  • lib/anubis/client/base.ex
lib/anubis/client/**/*.ex

📄 CodeRabbit inference engine (CLAUDE.md)

lib/anubis/client/**/*.ex: In Anubis.Client, use transport via state configuration and send via transport.layer.send_message(transport.name, data)
Use Anubis.Client.State for client state operations (State.new/1, add/remove request, progress callbacks, capability validation/merge)
Use Anubis.Client.Operation to configure requests (progress, timeouts, method/params)
Use Anubis.Client.Request for request lifecycle tracking (ID generation, timing, caller refs)

Files:

  • lib/anubis/client/operation.ex
  • lib/anubis/client/state.ex
  • lib/anubis/client/base.ex
lib/anubis/server/**/*.ex

📄 CodeRabbit inference engine (CLAUDE.md)

lib/anubis/server/**/*.ex: Build servers on Anubis.Server.Base and implement Anubis.Server.Behaviour callbacks (init/1, handle_request/2, handle_notification/2, server_info/0; optional server_capabilities/0)
Configure server transport via options (transport: [layer: TransportModule, name: process_name]) and use Anubis.Server.Transport.{STDIO,StreamableHTTP}

Files:

  • lib/anubis/server/transport/stdio.ex
  • lib/anubis/server/transport/streamable_http.ex
  • lib/anubis/server/base.ex
  • lib/anubis/server/transport/sse.ex
test/**/*.exs

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.exs: Use Anubis.MCP.Case for MCP protocol testing with MockTransport (avoid real transports)
Use Anubis.MCP.Builders for message construction in tests (init_request/1, ping_request/0, tools_list_request/1, build_request/2, build_response/2, build_notification/2)
Use provided setup helpers in tests (setup_client/2, initialize_client/2, initialized_client/2, setup_server/2, initialize_server/2, initialized_server/2, server_with_mock_transport/2)
Use MCP-specific assertions (assert_mcp_response/2, assert_mcp_error/3, assert_mcp_notification/2, assert_success/2, assert_resources/2, assert_tools/2)

Files:

  • test/anubis/server/transport/stdio_test.exs
  • test/anubis/server/transport/sse_test.exs
  • test/anubis/client/state_test.exs
  • test/anubis/transport/sse_test.exs
  • test/anubis/server/transport/streamable_http_test.exs
  • test/anubis/transport/streamable_http_test.exs
  • test/anubis/client/base_test.exs
  • test/anubis/transport/websocket_test.exs
  • test/anubis/transport/stdio_test.exs
test/**/*_test.exs

📄 CodeRabbit inference engine (CLAUDE.md)

Write descriptive test blocks (e.g., clear describe/it contexts)

Files:

  • test/anubis/server/transport/stdio_test.exs
  • test/anubis/server/transport/sse_test.exs
  • test/anubis/client/state_test.exs
  • test/anubis/transport/sse_test.exs
  • test/anubis/server/transport/streamable_http_test.exs
  • test/anubis/transport/streamable_http_test.exs
  • test/anubis/client/base_test.exs
  • test/anubis/transport/websocket_test.exs
  • test/anubis/transport/stdio_test.exs
🧠 Learnings (6)
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/server/**/*.ex : Configure server transport via options (transport: [layer: TransportModule, name: process_name]) and use Anubis.Server.Transport.{STDIO,StreamableHTTP}

Applied to files:

  • lib/anubis/server/transport/stdio.ex
  • lib/anubis/server/transport/streamable_http.ex
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/client/**/*.ex : In Anubis.Client, use transport via state configuration and send via transport.layer.send_message(transport.name, data)

Applied to files:

  • lib/anubis/server/transport/stdio.ex
  • lib/anubis/server/transport/streamable_http.ex
  • test/support/stub_transport.ex
  • lib/anubis/server/base.ex
  • lib/anubis/transport/behaviour.ex
  • test/anubis/client/base_test.exs
  • lib/anubis/client/base.ex
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/transport/**/*.ex : All transports implement Anubis.Transport.Behaviour with required callbacks start_link/1, send_message/2, shutdown/1 (e.g., Anubis.Transport.{STDIO,SSE,WebSocket,StreamableHTTP})

Applied to files:

  • lib/anubis/server/transport/stdio.ex
  • test/support/mock_transport.ex
  • lib/anubis/server/transport/streamable_http.ex
  • lib/anubis/transport/behaviour.ex
  • test/anubis/server/transport/streamable_http_test.exs
  • test/anubis/client/base_test.exs
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/client/**/*.ex : Use Anubis.Client.State for client state operations (State.new/1, add/remove request, progress callbacks, capability validation/merge)

Applied to files:

  • lib/anubis/client/state.ex
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to test/**/*.exs : Use Anubis.MCP.Case for MCP protocol testing with MockTransport (avoid real transports)

Applied to files:

  • test/anubis/client/base_test.exs
📚 Learning: 2025-10-07T19:46:22.924Z
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/client/**/*.ex : Use Anubis.Client.Operation to configure requests (progress, timeouts, method/params)

Applied to files:

  • lib/anubis/client/base.ex
🧬 Code graph analysis (18)
lib/anubis/transport/sse.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/server/transport/stdio.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/transport/streamable_http.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
test/anubis/server/transport/stdio_test.exs (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/transport/websocket.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
test/support/mock_transport.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/server/transport/streamable_http.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
test/anubis/server/transport/sse_test.exs (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/server/base.ex (2)
lib/anubis/client/base.ex (1)
  • send_to_transport (1499-1503)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/server/transport/sse.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/transport/behaviour.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
test/anubis/transport/sse_test.exs (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
test/anubis/server/transport/streamable_http_test.exs (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
test/anubis/transport/streamable_http_test.exs (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/transport/stdio.ex (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/client/base.ex (3)
lib/anubis/server/base.ex (3)
  • send_to_transport (679-681)
  • send_to_transport (683-687)
  • encode_notification (673-677)
test/support/stub_transport.ex (1)
  • send_message (83-85)
lib/anubis/mcp/message.ex (2)
  • encode_notification (455-457)
  • encode_notification (469-473)
test/anubis/transport/websocket_test.exs (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
test/anubis/transport/stdio_test.exs (1)
test/support/stub_transport.ex (1)
  • send_message (83-85)
🔇 Additional comments (20)
test/support/stub_transport.ex (1)

83-85: LGTM! Stub transport signature updated correctly.

The arity change to send_message/3 with an unused _opts parameter aligns perfectly with the new transport behavior. For a test stub, ignoring the options is acceptable and keeps the implementation simple.

lib/anubis/client/state.ex (3)

17-17: Type spec looks good! 👍

The timeout: pos_integer() field addition is correctly typed and positioned within the state struct definition.


32-32: Struct field added correctly.

The :timeout field is properly declared in the defstruct, making it a required field that must be provided during initialization.


48-49: No issues found — all callers provide timeout (P2 resolved) 😎

Verification shows both callers of State.new/1 supply timeout:

  • Test suite (state_test.exs): timeout: 30_000
  • Production code (base.ex:785): timeout: opts.timeout

No KeyError risk exists. The original concern is satisfied.

lib/anubis/server/base.ex (5)

63-64: Timeout option configured correctly.

The :timeout option with a 30-second default is a reasonable choice for server transport operations. The schema correctly defaults the value.


94-95: State initialization looks good.

The timeout field is correctly initialized from the parsed options and will be available for all transport operations.


679-687: send_to_transport/3 properly updated.

The helper function correctly threads the opts parameter through to the transport layer's send_message/3 callback. Error handling is preserved.


728-728: Timeout correctly passed for sampling request.

The send_to_transport call properly includes the timeout option from server state.


859-859: Roots request timeout handling is correct.

Both send_to_transport calls for roots requests properly include the timeout option from server state.

Also applies to: 895-895

test/anubis/transport/stdio_test.exs (1)

60-61: Test updates look solid! ✓

The tests correctly use the new 3-arity send_message/3 API with explicit timeout values. The 5-second timeout is appropriate for test scenarios.

Also applies to: 83-83

test/anubis/server/transport/streamable_http_test.exs (1)

128-129: [P3] LGTM on send_message/3 usage

Call correctly provides timeout option. ✅

test/anubis/transport/websocket_test.exs (1)

115-116: [P3] LGTM on new API usage

send_message/3 invoked with timeout as intended. 👍

test/anubis/transport/sse_test.exs (1)

134-135: [P3] LGTM on send_message/3 calls

All updated calls pass timeout; assertions look good. 🚀

Also applies to: 171-172, 383-384, 423-424, 497-498

test/support/mock_transport.ex (1)

9-9: [P3] LGTM: mock updated to 3-arity with default opts

Matches behavior callback; keeps tests simple.

test/anubis/server/transport/sse_test.exs (1)

129-129: LGTM: uses new send_message/3 API correctly.

Call shape and opts usage are good. 😎

lib/anubis/client/base.ex (4)

139-141: Nice! Timeout configuration properly threaded through 🎯

The timeout option is correctly added to the client initialization schema with a sensible default from Operation.default_timeout(). This allows global configuration while still permitting per-operation overrides.

Pattern observed:

  • Client-level default: state.timeout (from this option)
  • Per-operation override: operation.timeout (when specified)

789-791: Clean state initialization with timeout 👌

The timeout is correctly propagated into the client state, making it available for all transport operations that don't have explicit per-operation timeouts.


1499-1503: Solid helper update – timeout propagation looks good ✅

The send_to_transport helper correctly:

  1. Accepts opts parameter
  2. Forwards opts to transport.layer.send_message/3
  3. Wraps transport errors consistently

All callsites reviewed (lines 832, 890, 984, 1023, 1049, 1507) correctly pass timeout: operation.timeout or timeout: state.timeout depending on context. The pattern is consistent and correct:

  • Use operation.timeout for client-initiated operations
  • Use state.timeout for notifications and server-request responses

1598-1598: P3: Minor inconsistency – direct transport call bypasses error wrapping 🤔

Lines 1598 and 1610 call transport.layer.send_message directly instead of using the send_to_transport/3 helper. This is fine but bypasses the error wrapping logic that converts transport errors to Error.transport(:send_failure, ...).

These are in the sampling callback execution path where errors might be handled differently, but for consistency, consider:

-:ok = transport.layer.send_message(transport.name, response, timeout: state.timeout)
+send_to_transport(state.transport, response, timeout: state.timeout)

Not critical since these are in fire-and-forget Task contexts, but worth noting for maintainability.

Also applies to: 1610-1610

⛔ Skipped due to learnings
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/client/**/*.ex : In Anubis.Client, use transport via state configuration and send via transport.layer.send_message(transport.name, data)
test/anubis/client/base_test.exs (1)

21-21: LGTM – Consistent arity update across all mocks 👍

All send_message expectations updated from arity-2 to arity-3 to match the new transport behavior. The mechanical changes are consistent throughout the file.

Optional enhancement: Consider adding at least one test case that validates the timeout option is correctly propagated to the transport layer. For example:

test "passes timeout option to transport", %{client: client} do
  expect(Anubis.MockTransport, :send_message, fn _, message, opts ->
    assert opts[:timeout] > 0
    :ok
  end)
  
  Anubis.Client.Base.ping(client, timeout: 1000)
end

This would verify the integration behavior matches the PR objectives.

Also applies to: 49-49, 72-72

⛔ Skipped due to learnings
Learnt from: CR
PR: zoedsoupe/anubis-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-07T19:46:22.924Z
Learning: Applies to lib/anubis/client/**/*.ex : In Anubis.Client, use transport via state configuration and send via transport.layer.send_message(transport.name, data)

Comment thread lib/anubis/client/operation.ex Outdated
Comment thread lib/anubis/server/base.ex Outdated
Comment thread lib/anubis/server/transport/stdio.ex
Comment thread lib/anubis/server/transport/stdio.ex
Comment thread lib/anubis/server/transport/streamable_http.ex
Comment thread lib/anubis/transport/streamable_http.ex
Comment thread lib/anubis/transport/websocket.ex
Comment thread test/anubis/client/state_test.exs
Comment thread test/anubis/server/transport/streamable_http_test.exs Outdated
Comment thread test/anubis/transport/streamable_http_test.exs
@zoedsoupe

Copy link
Copy Markdown
Owner Author

@coderabbitai ignore

@zoedsoupe
zoedsoupe merged commit 1e37c23 into main Oct 27, 2025
9 of 10 checks passed
@zoedsoupe
zoedsoupe deleted the feat/client-server-transport-timeout branch October 27, 2025 17:20
@zoedsoupe zoedsoupe mentioned this pull request Oct 27, 2025
@coderabbitai

coderabbitai Bot commented Oct 27, 2025

Copy link
Copy Markdown
✅ Actions performed

Reviews paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant