Skip to content

feat: add _meta support to Tool struct and JSON encoder - #108

Merged
zoedsoupe merged 1 commit into
zoedsoupe:mainfrom
aretecp:main
Mar 13, 2026
Merged

zoedsoupe merged 1 commit into
zoedsoupe:mainfrom
aretecp:main

Conversation

@sglyon

@sglyon sglyon commented Mar 10, 2026 •

Copy link
Copy Markdown
Contributor

Problem

I have an Elixir app that provides an MCP server and I wanted to utilize the new MCP Apps extension (https://modelcontextprotocol.io/extensions/apps/overview). To do this, I needed support for adding the _meta field to tool declarations so clients know which visual resource to render alongside a tool's output.

Solution

I added a meta field to the Tool struct and wired it through the JSON encoder so _meta appears in the serialized MCP tool object. Tools can now declare meta: %{"ui" => %{"resourceUri" => "ui://my-app/dashboard"}} via use Anubis.Server.Component.

Rationale

This is the minimal change needed to support the MCP spec's _meta field — I only touched the Tool struct and its serialization path.

Summary by CodeRabbit

Release Notes

  • New Features

    • Tool components now support optional metadata that can be provided during tool definition and is automatically included in exported tool information and JSON responses.
  • Tests

    • Added comprehensive tests validating metadata functionality, including JSON encoding and callback optionality.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 10, 2026 •

Copy link
Copy Markdown

Walkthrough

This pull request introduces optional metadata capability to tool components throughout the Anubis framework. The implementation adds a new meta field to the Tool struct, creates an optional meta() callback in the tool behavior, modifies the component macro to generate meta functions conditionally, updates the server initialization to extract and pass meta values, and extends JSON encoding to include meta as "_meta" when present. Support is added at each layer: component definition, macro generation, server extraction, struct representation, and JSON serialization.

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant Macro as Component Macro
    participant Server as Server.ex
    participant Tool as Tool Struct
    participant JSON as JSON Encoder
    
    Dev->>Macro: Define component with meta: %{...}
    Macro->>Macro: Generate meta/0 function
    Server->>Macro: mod.meta() if exported?
    Macro-->>Server: Returns meta map
    Server->>Tool: Pass meta to Tool struct
    Tool->>Tool: Store meta field
    Tool->>JSON: Serialize to JSON
    JSON-->>JSON: Include _meta field if meta present
    JSON-->>Dev: Return encoded JSON with metadata
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Suggested reviewers

  • zoedsoupe

Review Notes 😎

Overview: Clean addition of optional metadata to tools, mirroring the existing annotations pattern nicely. The changes are incremental and well-scoped across the framework layers.

Strengths:

  • ✅ Follows established patterns (mirrors annotations implementation)
  • ✅ Optional callback properly registered via @optional_callbacks
  • ✅ Conditional generation prevents breaking existing code
  • ✅ Solid test coverage with both positive and negative cases

Observations (P1–P2):

  • P1: In lib/anubis/server/component/tool.ex, the JSON encoding adds "_meta": tool.meta when meta != nil. Confirm this underscore prefix convention aligns with your API spec and existing external contract expectations.
  • P2: In lib/anubis/server.ex, the meta extraction calls mod.meta() only when Anubis.exported?/3 returns true. Verify this guard pattern matches how annotations are handled—want consistency if both are optional.
  • P2: Test module test/anubis/server/component/tool_meta_test.exs validates JSON encoding nicely, but consider adding an integration test that verifies meta flows correctly through register_tool/3 in frame.ex.

Minor: The test support file adds several new tool modules—nice breadth for future tests. No issues there. 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add _meta support to Tool struct and JSON encoder' clearly and concisely describes the main change—adding metadata support to the Tool component.
Description check ✅ Passed The description covers all required template sections: Problem (MCP Apps extension support), Solution (meta field implementation), and Rationale (minimal change scope).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@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.

Caution

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

⚠️ Outside diff range comments (1)
lib/anubis/server/frame.ex (1)

125-131: ⚠️ Potential issue | 🟡 Minor

P2: Missing :meta in tool_opt typespec. 📝

The register_tool/3 implementation at line 154 uses opts[:meta], but the tool_opt type definition doesn't include it. This creates a documentation gap and may cause Dialyzer warnings if strict typing is enabled.

🔧 Proposed fix
   `@spec` register_tool(t(), String.t(), list(tool_opt)) :: t()
         when tool_opt:
                {:description, String.t() | nil}
                | {:input_schema, map() | nil}
                | {:output_schema, map() | nil}
                | {:title, String.t() | nil}
                | {:annotations, map() | nil}
+               | {:meta, map() | nil}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/anubis/server/frame.ex` around lines 125 - 131, The typespec for tool
options (tool_opt) is missing the :meta option used in register_tool/3; update
the tool_opt union to include {:meta, map() | nil} so the `@spec`
register_tool(t(), String.t(), list(tool_opt)) :: t() accurately reflects
runtime usage (refer to register_tool/3 and opts[:meta] in the function body)
and prevent Dialyzer/documentation mismatches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@lib/anubis/server/frame.ex`:
- Around line 125-131: The typespec for tool options (tool_opt) is missing the
:meta option used in register_tool/3; update the tool_opt union to include
{:meta, map() | nil} so the `@spec` register_tool(t(), String.t(), list(tool_opt))
:: t() accurately reflects runtime usage (refer to register_tool/3 and
opts[:meta] in the function body) and prevent Dialyzer/documentation mismatches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c79a9b04-f65a-415b-8ac7-0e837f859107

📥 Commits

Reviewing files that changed from the base of the PR and between 7df9c0d and d7b90a1.

📒 Files selected for processing (6)
  • lib/anubis/server.ex
  • lib/anubis/server/component.ex
  • lib/anubis/server/component/tool.ex
  • lib/anubis/server/frame.ex
  • test/anubis/server/component/tool_meta_test.exs
  • test/support/test_tools.ex

@zoedsoupe

Copy link
Copy Markdown
Owner

does that field addition need to have a MCP version guardrail?

@sglyon

sglyon commented Mar 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Not sure. I haven't dealt with MCP protocol versions.

I do know that MCP apps is an official MCP extension: https://modelcontextprotocol.io/extensions/client-matrix

EDIT:

My understanding is that client that support he MCP apps will have to broadcast that they do. Clients that don't support it ignore the extra field and are unaffected.

See the "Negotiation" section of the docs page: https://modelcontextprotocol.io/extensions/overview#negotiation

@zoedsoupe
zoedsoupe merged commit 6ac49d1 into zoedsoupe:main Mar 13, 2026
10 checks passed
@zoedsoupe

Copy link
Copy Markdown
Owner

it seems ok to proceed

@zoedsoupe zoedsoupe mentioned this pull request Mar 13, 2026
zoedsoupe added a commit that referenced this pull request Mar 14, 2026
…b.com:zoedsoupe/anubis-mcp into refactor/phase-5-remove-dead-code-update-docs

* 'refactor/phase-5-remove-dead-code-update-docs' of github.com:zoedsoupe/anubis-mcp:
  Fix misleading session store warning when disabled (#107)
  feat: add _meta support to Tool struct and JSON encoder (#108)
  Fix memory leak by preventing stale SSE unregister dropping active heandlers (#109)
  deps:(deps-dev): bump credo from 1.7.15 to 1.7.17 (#105)
  deps:(deps): bump telemetry from 1.3.0 to 1.4.1 (#106)
  deps:(deps-dev): bump styler from 1.10.1 to 1.11.0 (#101)
@sglyon

sglyon commented Mar 16, 2026

Copy link
Copy Markdown
Contributor Author

Excellent, thank you. I have had this in prod via my fork since opening the PR and it has been working great. To render a custom UI within the MCP client is fun

zoedsoupe added a commit that referenced this pull request Mar 16, 2026
🚀 Want to release this?
---


##
[1.0.0](v0.17.1...v1.0.0)
(2026-03-16)


### ⚠ BREAKING CHANGES

* remove client base module and client macro
([#110](#110))
* **phase-3:** server re-implementation and simplification
([#96](#96))

### Features

* add _meta support to Tool struct and JSON encoder
([#108](#108))
([6ac49d1](6ac49d1))


### Bug Fixes

* **phase-5:** remove dead code and update docs
([#104](#104))
([eea86af](eea86af))
* regression for input/output server schema
([85f8ebb](85f8ebb))
* remove client base module and client macro
([#110](#110))
([1f9f13c](1f9f13c))
* server examples and sse server transport
([944bafb](944bafb))
* session serializion errors
([#112](#112))
([cb8c0e3](cb8c0e3)),
closes [#60](#60)
* Start SSE keepalive when first handler is registered
([#83](#83))
([c3c01e9](c3c01e9))
* stdio server transport working
([#111](#111))
([b331281](b331281))


### Code Refactoring

* **phase-3:** server re-implementation and simplification
([#96](#96))
([badb0f0](badb0f0))
* **phase-4:** client extraction of handlers
([#100](#100))
([08b98c0](08b98c0))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
zoedsoupe pushed a commit that referenced this pull request Jul 16, 2026
## Problem

I have an Elixir app that provides an MCP server and I wanted to utilize
the new MCP Apps extension
(https://modelcontextprotocol.io/extensions/apps/overview). To do this,
I needed support for adding the _meta field to tool declarations so
clients know which visual resource to render alongside a tool's output.

## Solution

I added a meta field to the Tool struct and wired it through the JSON
encoder so _meta appears in the serialized MCP tool object. Tools can
now declare `meta: %{"ui" => %{"resourceUri" =>
"ui://my-app/dashboard"}}` via `use Anubis.Server.Component`.

## Rationale

This is the minimal change needed to support the MCP spec's _meta field
— I only touched the Tool struct and its serialization path.



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Tool components now support optional metadata that can be provided
during tool definition and is automatically included in exported tool
information and JSON responses.

* **Tests**
* Added comprehensive tests validating metadata functionality, including
JSON encoding and callback optionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
zoedsoupe added a commit that referenced this pull request Jul 16, 2026
🚀 Want to release this?
---


##
[1.0.0](v0.17.1...v1.0.0)
(2026-03-16)


### ⚠ BREAKING CHANGES

* remove client base module and client macro
([#110](#110))
* **phase-3:** server re-implementation and simplification
([#96](#96))

### Features

* add _meta support to Tool struct and JSON encoder
([#108](#108))
([3b26a1c](3b26a1c))


### Bug Fixes

* **phase-5:** remove dead code and update docs
([#104](#104))
([2a33dfc](2a33dfc))
* regression for input/output server schema
([fbf138b](fbf138b))
* remove client base module and client macro
([#110](#110))
([a8ec690](a8ec690))
* server examples and sse server transport
([f4d3097](f4d3097))
* session serializion errors
([#112](#112))
([8416cef](8416cef)),
closes [#60](#60)
* Start SSE keepalive when first handler is registered
([#83](#83))
([67d93bb](67d93bb))
* stdio server transport working
([#111](#111))
([b3d42a8](b3d42a8))


### Code Refactoring

* **phase-3:** server re-implementation and simplification
([#96](#96))
([14c4e4f](14c4e4f))
* **phase-4:** client extraction of handlers
([#100](#100))
([dc0ea85](dc0ea85))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
This was referenced Jul 16, 2026
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.

2 participants