Skip to content

bug: MCP Server tool macro does not define description/0 on generated modules #45

@ppsplus-bradh

Description

@ppsplus-bradh

Summary

The tool macro in ClaudeCode.MCP.Server generates nested modules that lack a working description/0 callback, causing Anubis.Server.Component.get_description/1 to return "" instead of the description text.

Reproduction

MIX_ENV=test mix run -e '
IO.inspect(function_exported?(ClaudeCode.TestTools.Add, :description, 0))
# => false

IO.inspect(Anubis.Server.Component.get_description(ClaudeCode.TestTools.Add))
# => ""  (expected: "Add two numbers")
'

Root Cause

In lib/claude_code/mcp/server.ex, the tool macro's quote block (lines 177-196) sets @moduledoc before use Anubis.Server.Component:

quote do
  defmodule Module.concat(__MODULE__, unquote(module_name)) do
    @moduledoc unquote(description_text)          # line 179: set @moduledoc
    use Anubis.Server.Component, type: :tool      # line 180: __using__ captures @moduledoc

    @impl true
    def description, do: unquote(description_text) # line 188: define callback
  end
end

Two issues:

  1. @moduledoc capture timing: Anubis's __using__ macro defines def __description__, do: @moduledoc (component.ex line 51). This snapshots @moduledoc at the point use expands, but the @moduledoc assignment at line 179 may not have taken effect yet in the compilation pipeline.

  2. description/0 not exported: Despite @impl true + def description at line 188, function_exported?(Add, :description, 0) returns false. The description/0 callback is declared as @optional_callbacks in Anubis.Server.Component.Tool (line 201), so no default is generated. The def description in the macro should provide the implementation, but it's not surviving compilation.

Impact

  • Anubis.Server.Component.get_description/1 returns "" for all tool modules
  • Test "have description from @moduledoc" in server_test.exs:58 fails
  • This appears as a flaky CI failure because the test asserts on different modules each run (Add one run, Greet the next), but the underlying issue is deterministic — all modules are affected

Observed CI Failures

Suggested Fix

Swap the ordering so use comes before @moduledoc, or set description purely through the description/0 callback without relying on @moduledoc:

# Option A: move use before @moduledoc
defmodule Module.concat(__MODULE__, unquote(module_name)) do
  use Anubis.Server.Component, type: :tool
  @moduledoc unquote(description_text)
  # ...
end

# Option B: don't use @moduledoc at all, rely solely on description/0
defmodule Module.concat(__MODULE__, unquote(module_name)) do
  use Anubis.Server.Component, type: :tool
  @impl true
  def description, do: unquote(description_text)
  # ...
end

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions