Skip to content

Commit eea86af

Browse files
authored
fix(phase-5): remove dead code and update docs (#104)
## Problem After phases 3, 3.5, and 4 restructured the server architecture (session-centric, Plug-based HTTP path, functional transport for clients only), several artifacts were left behind: - Server transports (`STDIO`, `StreamableHTTP`, `SSE`) still implement `@behaviour Anubis.Transport` with functional callbacks (`transport_init`, `parse`, `encode`, `extract_metadata`) that are never called in production — the Plug uses `Message.decode` directly, and the STDIO GenServer does too - `Client.State.protocol_module` is stored during init but never read - Documentation references outdated APIs, wrong callback names, incorrect option keys, and stale protocol versions Related issues: #77, #7, #90, #14, #64, #25, #28 ## Solution **Dead code removal:** - Removed `@behaviour Anubis.Transport` and all functional callbacks from `Server.Transport.STDIO`, `Server.Transport.StreamableHTTP`, and `Server.Transport.SSE` (including helper functions like `split_complete_lines`, `decode_lines`, `find_header`) - Removed `protocol_module` field from `Client.State` (type, struct, init logic) - Updated `Anubis.Transport` moduledoc to list only client adapters - Removed all server-side functional transport tests from `behaviour_test.exs` **Documentation fixes:** - `README.md`: Rewrote Quick Start to use Component/schema API, fixed `handle_tool` → `handle_tool_call` (#77), fixed `max:` → `max_length:` (#64), updated `protocol_version` to `"2025-06-18"`, showed both static (`component`) and dynamic (`register_tool`) patterns - `building-a-client.md`: Fixed `name:` → `client_name:` for multiple instances (#7), updated protocol version - `building-a-server.md`: Fixed version `"~> 0.11"` → `"~> 0.17"`, fixed `bug_report_content`/`build_report_content` mismatch, fixed broken `mix anubis.stdio.sse` task, fixed `frame.private.session_id` → `frame.context.session_id` - `recipes.md`: Fixed `frame.transport[:headers]` → `frame.context.headers`, `frame.private.session_id` → `frame.context.session_id`, `send_notification` → `Anubis.Server.send_resources_list_changed()`, `send_log_message` → correct signature, `handle_request` with `tools/call` → `handle_tool_call/3` - `reference.md`: Removed spurious `type: :string` from enum field example (#90), added SSE deprecation note - `introduction.md`: Updated protocol version to `"2025-06-18"` - `CONTRIBUTING.md`: Fixed MIT → LGPL-v3 - `mix.exs`: Fixed `pages/home.md` → `pages/introduction.md` ## Rationale Straight cleanup pass — no behavioral changes. The functional `Anubis.Transport` callbacks on server transports were dead code since the architecture moved to direct `Message.decode` in Plug/GenServer handlers. Removing them reduces surface area and avoids confusion about which code path is actually used. Documentation fixes are driven directly by user-reported issues on GitHub. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an Echo tool and a schema-based DSL for declaring tools. * **Documentation** * Updated MCP protocol/version to 2025-06-18 across docs. * Reorganized Getting Started; transport examples use base_url and mark SSE deprecated. * Updated examples and guides to use structured Response return shapes. * **Bug Fixes** * Broadened encoding error handling for transports to surface failures more reliably. * **Chores** * License note changed to LGPL‑v3. * Added example build/test targets. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent efee54a commit eea86af

30 files changed

Lines changed: 287 additions & 631 deletions

CHANGELOG.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,35 @@ All notable changes to this project are documented in this file.
44

55
## [0.17.1](https://github.com/zoedsoupe/anubis-mcp/compare/v0.17.0...v0.17.1) (2026-02-28)
66

7-
87
### Bug Fixes
98

10-
* Check Process.alive? before sending to SSE handler ([#82](https://github.com/zoedsoupe/anubis-mcp/issues/82)) ([e1dc705](https://github.com/zoedsoupe/anubis-mcp/commit/e1dc705f1ae8ee7e8670d26c7fdfc30583d19efd))
11-
9+
- Check Process.alive? before sending to SSE handler ([#82](https://github.com/zoedsoupe/anubis-mcp/issues/82)) ([e1dc705](https://github.com/zoedsoupe/anubis-mcp/commit/e1dc705f1ae8ee7e8670d26c7fdfc30583d19efd))
1210

1311
### Code Refactoring
1412

15-
* **phase-1:** abstract protocol version negotiation ([#93](https://github.com/zoedsoupe/anubis-mcp/issues/93)) ([05a2362](https://github.com/zoedsoupe/anubis-mcp/commit/05a2362a672ef462e73bc9a3f637c3f203c0978e))
16-
* **phase-2:** transport layer as functions, backward compatible ([#95](https://github.com/zoedsoupe/anubis-mcp/issues/95)) ([105d6a9](https://github.com/zoedsoupe/anubis-mcp/commit/105d6a91e31d8dbf606ec7916317f9add94acf4e))
13+
- **phase-1:** abstract protocol version negotiation ([#93](https://github.com/zoedsoupe/anubis-mcp/issues/93)) ([05a2362](https://github.com/zoedsoupe/anubis-mcp/commit/05a2362a672ef462e73bc9a3f637c3f203c0978e))
14+
- **phase-2:** transport layer as functions, backward compatible ([#95](https://github.com/zoedsoupe/anubis-mcp/issues/95)) ([105d6a9](https://github.com/zoedsoupe/anubis-mcp/commit/105d6a91e31d8dbf606ec7916317f9add94acf4e))
1715

1816
## [0.17.0](https://github.com/zoedsoupe/anubis-mcp/compare/v0.16.0...v0.17.0) (2025-12-09)
1917

20-
2118
### Features
2219

23-
* **redis:** add redix_opts for SSL/TLS support ([#59](https://github.com/zoedsoupe/anubis-mcp/issues/59)) ([33658ab](https://github.com/zoedsoupe/anubis-mcp/commit/33658abab69e1f0c361a4dbf4e9665bb900d2f7e))
24-
20+
- **redis:** add redix_opts for SSL/TLS support ([#59](https://github.com/zoedsoupe/anubis-mcp/issues/59)) ([33658ab](https://github.com/zoedsoupe/anubis-mcp/commit/33658abab69e1f0c361a4dbf4e9665bb900d2f7e))
2521

2622
### Bug Fixes
2723

28-
* added server component description/0 callback ([#58](https://github.com/zoedsoupe/anubis-mcp/issues/58)) ([a094473](https://github.com/zoedsoupe/anubis-mcp/commit/a094473916f7ac414369bb2faab1593fd141a7f1))
29-
* redix should be loaded ([#71](https://github.com/zoedsoupe/anubis-mcp/issues/71)) ([09b872f](https://github.com/zoedsoupe/anubis-mcp/commit/09b872fe5dc48665beee3be8a7b7ae9943ce48ae))
24+
- added server component description/0 callback ([#58](https://github.com/zoedsoupe/anubis-mcp/issues/58)) ([a094473](https://github.com/zoedsoupe/anubis-mcp/commit/a094473916f7ac414369bb2faab1593fd141a7f1))
25+
- redix should be loaded ([#71](https://github.com/zoedsoupe/anubis-mcp/issues/71)) ([09b872f](https://github.com/zoedsoupe/anubis-mcp/commit/09b872fe5dc48665beee3be8a7b7ae9943ce48ae))
3026

3127
## [0.16.0](https://github.com/zoedsoupe/anubis-mcp/compare/v0.15.0...v0.16.0) (2025-11-18)
3228

33-
3429
### Features
3530

36-
* redis based session store (continue from [#48](https://github.com/zoedsoupe/anubis-mcp/issues/48)) ([#55](https://github.com/zoedsoupe/anubis-mcp/issues/55)) ([fddea32](https://github.com/zoedsoupe/anubis-mcp/commit/fddea327ef8d91c57c4dc65f527aadc3e8d105a2))
37-
31+
- redis based session store (continue from [#48](https://github.com/zoedsoupe/anubis-mcp/issues/48)) ([#55](https://github.com/zoedsoupe/anubis-mcp/issues/55)) ([fddea32](https://github.com/zoedsoupe/anubis-mcp/commit/fddea327ef8d91c57c4dc65f527aadc3e8d105a2))
3832

3933
### Bug Fixes
4034

41-
* correct arguments in Logging.should_log? ([#47](https://github.com/zoedsoupe/anubis-mcp/issues/47)) ([6f550e6](https://github.com/zoedsoupe/anubis-mcp/commit/6f550e647fd5e6e7c6cdfb233e1cc8a4ac530fc7))
35+
- correct arguments in Logging.should_log? ([#47](https://github.com/zoedsoupe/anubis-mcp/issues/47)) ([6f550e6](https://github.com/zoedsoupe/anubis-mcp/commit/6f550e647fd5e6e7c6cdfb233e1cc8a4ac530fc7))
4236

4337
## [0.15.0](https://github.com/zoedsoupe/anubis-mcp/compare/v0.14.1...v0.15.0) (2025-11-03)
4438

CLAUDE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,3 @@ mix docs
136136
### Testing Guidelines
137137

138138
- Always implement test helper modules in @test/support/ context, analyzing if there aren't any existing ones that could be used
139-
- memo

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,4 @@ Releases are managed by the maintainers. Version numbers follow [Semantic Versio
113113

114114
## License
115115

116-
By contributing to Anubis MCP, you agree that your contributions will be licensed under the project's [MIT License](./LICENSE).
116+
By contributing to Anubis MCP, you agree that your contributions will be licensed under the project's [LGPL-v3 License](./LICENSE).

README.md

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,38 @@ end
2626
### Server
2727

2828
```elixir
29-
# Define a server with tools capabilities
29+
# Define a tool as a Component (compile-time registration)
30+
defmodule MyApp.Echo do
31+
@moduledoc "Echoes everything the user says to the LLM"
32+
33+
use Anubis.Server.Component, type: :tool
34+
35+
alias Anubis.Server.Response
36+
37+
schema do
38+
field :text, :string, required: true, max_length: 150, description: "the text to be echoed"
39+
end
40+
41+
@impl true
42+
def execute(%{text: text}, frame) do
43+
{:reply, Response.text(Response.tool(), text), frame}
44+
end
45+
end
46+
3047
defmodule MyApp.MCPServer do
3148
use Anubis.Server,
3249
name: "My Server",
3350
version: "1.0.0",
3451
capabilities: [:tools]
3552

36-
@impl true
37-
# this callback will be called when the
38-
# MCP initialize lifecycle completes
39-
def init(_client_info, frame) do
40-
{:ok,frame
41-
|> assign(counter: 0)
42-
|> register_tool("echo",
43-
input_schema: %{
44-
text: {:required, :string, max: 150, description: "the text to be echoed"}
45-
},
46-
annotations: %{read_only: true},
47-
description: "echoes everything the user says to the LLM") }
48-
end
53+
# Static component registration — dispatches to MyApp.Echo.execute/2
54+
component MyApp.Echo
4955

5056
@impl true
51-
def handle_tool("echo", %{text: text}, frame) do
52-
Logger.info("This tool was called #{frame.assigns.counter + 1}")
53-
{:reply, text, assign(frame, counter: frame.assigns.counter + 1)}
57+
def init(_client_info, frame) do
58+
# You can also register tools dynamically at runtime via the Frame:
59+
# frame = register_tool(frame, "dynamic_tool", description: "...", input_schema: %{...})
60+
{:ok, frame}
5461
end
5562
end
5663

@@ -77,7 +84,7 @@ defmodule MyApp.MCPClient do
7784
use Anubis.Client,
7885
name: "MyApp",
7986
version: "1.0.0",
80-
protocol_version: "2025-03-26"
87+
protocol_version: "2025-06-18"
8188
end
8289

8390
# Add to your application supervisor

flake.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
default = pkgs.mkShell {
2828
name = "anubis-mcp-dev";
2929
packages = with pkgs; [
30-
(elixir-with-otp erlang_28)."1.18.4"
30+
(elixir-with-otp erlang_28).latest
3131
erlang_28
3232
redis
3333
uv

justfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@ ascii-server:
1919
[working-directory: 'priv/dev/echo-elixir']
2020
echo-ex-server transport="sse":
2121
MCP_TRANSPORT={{transport}} {{ if transport == "sse" { "iex -S mix phx.server" } else { "mix run --no-halt" } }}
22+
23+
update-deps-examples:
24+
for p in priv/dev/upcase priv/dev/ascii priv/dev/echo-elixir; do \
25+
(cd "$p" && mix deps.update --all && mix compile --force --warnings-as-errors) || exit 1; \
26+
done
27+
28+
compile-examples:
29+
for p in priv/dev/upcase priv/dev/ascii priv/dev/echo-elixir; do \
30+
(cd "$p" && mix compile --force --warnings-as-errors) || exit 1; \
31+
done

lib/anubis/client/state.ex

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ defmodule Anubis.Client.State do
1414
server_capabilities: map() | nil,
1515
server_info: map() | nil,
1616
protocol_version: String.t(),
17-
protocol_module: module() | nil,
1817
timeout: pos_integer(),
1918
transport: map(),
2019
pending_requests: %{String.t() => Request.t()},
@@ -31,7 +30,6 @@ defmodule Anubis.Client.State do
3130
:server_info,
3231
:timeout,
3332
:protocol_version,
34-
:protocol_module,
3533
:transport,
3634
pending_requests: %{},
3735
progress_callbacks: %{},
@@ -42,17 +40,10 @@ defmodule Anubis.Client.State do
4240

4341
@spec new(map()) :: t()
4442
def new(opts) do
45-
protocol_module =
46-
case Anubis.Protocol.Registry.get(opts.protocol_version) do
47-
{:ok, mod} -> mod
48-
:error -> nil
49-
end
50-
5143
%__MODULE__{
5244
client_info: opts.client_info,
5345
capabilities: opts.capabilities,
5446
protocol_version: opts.protocol_version,
55-
protocol_module: protocol_module,
5647
transport: opts.transport,
5748
timeout: opts.timeout
5849
}

lib/anubis/server.ex

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,23 @@ defmodule Anubis.Server do
2222
end
2323
2424
defmodule MyServer.Calculator do
25+
@moduledoc "Add two numbers"
26+
2527
use Anubis.Server.Component, type: :tool
2628
27-
def definition do
28-
%{
29-
name: "add",
30-
description: "Add two numbers",
31-
input_schema: %{
32-
type: "object",
33-
properties: %{
34-
a: %{type: "number"},
35-
b: %{type: "number"}
36-
}
37-
}
38-
}
29+
schema do
30+
field :a, :number, required: true
31+
field :b, :number, required: true
3932
end
4033
41-
def call(%{"a" => a, "b" => b}), do: {:ok, a + b}
34+
def execute(%{a: a, b: b}, _frame) do
35+
{:ok, a + b}
36+
end
4237
end
4338
44-
# Start your server
45-
{:ok, _pid} = Anubis.Server.start_link(MyServer, [], transport: :stdio)
39+
# In your supervision tree
40+
children = [Anubis.Server.Registry, {MyServer, transport: :stdio}]
41+
Supervisor.start_link(children, strategy: :one_for_one)
4642
4743
Your server is now a living process that AI assistants can connect to, discover available
4844
tools, and execute calculations through a secure protocol boundary.

lib/anubis/server/component/prompt.ex

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule Anubis.Server.Component.Prompt do
1010
defmodule MyServer.Prompts.CodeReview do
1111
@behaviour Anubis.Server.Behaviour.Prompt
1212
13-
alias Anubis.Server.Frame
13+
alias Anubis.Server.{Frame, Response}
1414
1515
@impl true
1616
def name, do: "code_review"
@@ -70,7 +70,11 @@ defmodule Anubis.Server.Component.Prompt do
7070
# Can track prompt usage
7171
new_frame = Frame.assign(frame, :last_prompt_used, "code_review")
7272
73-
{:ok, messages, new_frame}
73+
response =
74+
Response.prompt()
75+
|> Response.user_message(Enum.map_join(messages, "\n", & &1["content"]["text"]))
76+
77+
{:reply, response, new_frame}
7478
end
7579
end
7680
"""
@@ -169,23 +173,21 @@ defmodule Anubis.Server.Component.Prompt do
169173
170174
## Return Values
171175
172-
- `{:ok, messages}` - Messages generated successfully, frame unchanged
173-
- `{:ok, messages, new_frame}` - Messages generated with frame updates
174-
- `{:error, reason}` - Failed to generate messages
176+
- `{:reply, %Response{}, frame}` - Messages generated successfully
177+
- `{:noreply, frame}` - No reply needed
178+
- `{:error, %Error{}, frame}` - Failed to generate messages
175179
176-
## Message Format
180+
## Building Responses
177181
178-
Messages should follow the MCP message format:
182+
Use `Response.prompt/0` to create a prompt response, then add messages with
183+
`Response.user_message/2` or `Response.system_message/2`:
179184
180-
%{
181-
"role" => "user" | "assistant",
182-
"content" => %{
183-
"type" => "text",
184-
"text" => "The message content"
185-
}
186-
}
185+
response =
186+
Response.prompt()
187+
|> Response.user_message("Please review this code")
188+
|> Response.system_message("You are a code reviewer")
187189
188-
Multiple messages can be returned to create a conversation context.
190+
{:reply, response, frame}
189191
"""
190192
@callback get_messages(args :: arguments(), frame :: Frame.t()) ::
191193
{:reply, response :: Response.t(), new_state :: Frame.t()}

0 commit comments

Comments
 (0)