Skip to content

Commit 846f013

Browse files
committed
feat(anthropic-ext): add anthropic_max_result_size_chars_meta(u32) -> Meta helper
The existing `Tool::with_anthropic_max_result_size_chars` builder targets hand-constructed `Tool` instances, but macro-driven tool routers set `_meta` via the `#[tool(meta = <Expr>)]` attribute, which expects a `Meta` value. Expose a free function that returns a standalone `Meta` carrying the `anthropic/maxResultSizeChars` key, clamped to `MAX_RESULT_SIZE_CHARS_CEILING` identically to the builder. Usage site collapses to a one-liner per handler: meta = anthropic_max_result_size_chars_meta(500_000),
1 parent f9b42c9 commit 846f013

1 file changed

Lines changed: 61 additions & 1 deletion

File tree

crates/rmcp/src/anthropic_ext.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1717
use serde_json::Value;
1818

19-
use crate::model::{ExtensionCapabilities, JsonObject, Tool};
19+
use crate::model::{ExtensionCapabilities, JsonObject, Meta, Tool};
2020

2121
/// `_meta` key Claude Code honours on a tool's `tools/list` entry to raise the
2222
/// per-tool text-output size threshold past the default 25 000-token cap.
@@ -59,6 +59,38 @@ impl Tool {
5959
}
6060
}
6161

62+
/// Build a [`Meta`] carrying the `anthropic/maxResultSizeChars` key for direct
63+
/// use in the `#[tool(meta = ...)]` macro attribute. The value is clamped to
64+
/// [`MAX_RESULT_SIZE_CHARS_CEILING`].
65+
///
66+
/// Prefer this helper in macro-driven tool routers where you cannot call
67+
/// [`Tool::with_anthropic_max_result_size_chars`] at construction time. The
68+
/// `_meta` key is silently ignored by every MCP client other than Claude Code.
69+
///
70+
/// See <https://docs.anthropic.com/en/docs/claude-code/mcp#raise-the-limit-for-a-specific-tool>.
71+
///
72+
/// # Example
73+
///
74+
/// ```ignore
75+
/// use rmcp::anthropic_ext::anthropic_max_result_size_chars_meta;
76+
///
77+
/// #[tool(
78+
/// annotations(read_only_hint = true),
79+
/// meta = anthropic_max_result_size_chars_meta(500_000),
80+
/// )]
81+
/// async fn big_output(&self) -> Result<Json<Resp>, String> { /* ... */ }
82+
/// ```
83+
#[must_use]
84+
pub fn anthropic_max_result_size_chars_meta(chars: u32) -> Meta {
85+
let clamped = chars.min(MAX_RESULT_SIZE_CHARS_CEILING);
86+
let mut meta = Meta::new();
87+
meta.0.insert(
88+
MAX_RESULT_SIZE_CHARS_META_KEY.to_string(),
89+
Value::from(clamped),
90+
);
91+
meta
92+
}
93+
6294
/// Insert the `claude/channel` extension capability into an
6395
/// [`ExtensionCapabilities`] map with empty settings. Pass the resulting map
6496
/// to [`ServerCapabilitiesBuilder::enable_extensions_with`] when building
@@ -146,6 +178,34 @@ mod tests {
146178
assert!(stored.is_empty(), "expected empty settings object");
147179
}
148180

181+
#[test]
182+
fn anthropic_max_result_size_chars_meta_under_ceiling() {
183+
let meta = anthropic_max_result_size_chars_meta(100_000);
184+
let stored = meta
185+
.0
186+
.get(MAX_RESULT_SIZE_CHARS_META_KEY)
187+
.cloned()
188+
.expect("key present");
189+
assert_eq!(stored, Value::from(100_000u32));
190+
}
191+
192+
#[test]
193+
fn anthropic_max_result_size_chars_meta_clamps_above_ceiling() {
194+
let meta = anthropic_max_result_size_chars_meta(1_000_000);
195+
let stored = meta
196+
.0
197+
.get(MAX_RESULT_SIZE_CHARS_META_KEY)
198+
.cloned()
199+
.expect("key present");
200+
assert_eq!(stored, Value::from(MAX_RESULT_SIZE_CHARS_CEILING));
201+
}
202+
203+
#[test]
204+
fn anthropic_max_result_size_chars_meta_is_standalone() {
205+
let meta = anthropic_max_result_size_chars_meta(100_000);
206+
assert_eq!(meta.0.len(), 1, "helper should only emit the size key");
207+
}
208+
149209
#[test]
150210
fn warn_if_over_2kb_returns_true_above_limit() {
151211
let body = "x".repeat(lint::SIZE_LIMIT_BYTES + 1);

0 commit comments

Comments
 (0)