1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! Integration test for the `#[reflect_trait]` proc macro.
//!
//! Verifies the full lifecycle:
//! 1. Apply `#[reflect_trait(test_greet::Greetable)]` to a marker trait block
//! 2. The macro generates a factory, vtable, param structs, and inventory submission
//! 3. Prime the factory for a concrete type (`FlagType`)
//! 4. Register the type in a `DynamicToolRegistry`
//! 5. Instantiate tools for that type
//! 6. Confirm the generated tools appear in the tool list
use elicitation::{DynamicToolRegistry, Elicit, ElicitPlugin};
use elicitation_macros::reflect_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
// ── Fake third-party trait ────────────────────────────────────────────────────
mod test_greet {
/// A minimal fake "third-party" trait whose methods we want as MCP tools.
pub trait Greetable {
/// Produce a greeting string for this value.
fn greet(name: String) -> String;
/// Check whether a name is valid for this value.
fn is_valid_name(&self, name: String) -> bool;
}
}
// ── reflect_trait macro application ───────────────────────────────────────────
// Apply the macro — this generates:
// - `GreetParams`, `IsValidNameParams` param structs
// - `GreetableVTable`
// - `GreetableFactory` (implements AnyToolFactory) + inventory submission
// - `prime_test_greet__greetable::<T>()` free function
#[reflect_trait(test_greet::Greetable)]
trait GreetableTools {
fn greet(name: String) -> String;
fn is_valid_name(&self, name: String) -> bool;
}
// ── Concrete type implementing the third-party trait ─────────────────────────
/// A simple bool-wrapper that implements Greetable.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Elicit)]
pub struct FlagType {
pub value: bool,
}
impl test_greet::Greetable for FlagType {
fn greet(name: String) -> String {
format!("Hello, {name}!")
}
fn is_valid_name(&self, name: String) -> bool {
!name.is_empty() && self.value
}
}
/// A second concrete type — used only in the "not primed" test to ensure
/// the vtable hasn't been populated by another test for this TypeId.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Elicit)]
pub struct UnprimedFlag {
pub label: String,
}
impl test_greet::Greetable for UnprimedFlag {
fn greet(name: String) -> String {
format!("Hi {name}")
}
fn is_valid_name(&self, name: String) -> bool {
name == self.label
}
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[test]
fn reflect_trait_factory_registered_in_inventory() {
// The factory should be registered at link time via inventory::submit!
let found = inventory::iter::<elicitation::ToolFactoryRegistration>
.into_iter()
.any(|r| r.trait_name == "test_greet::Greetable");
assert!(found, "GreetableFactory not found in inventory");
}
#[test]
fn reflect_trait_meta_tool_visible_before_instantiation() {
let registry = DynamicToolRegistry::new();
let tools = registry.list_tools();
let names: Vec<&str> = tools.iter().map(|t| t.name.as_ref()).collect();
// A meta-tool for the Greetable factory should appear (from inventory)
assert!(
names.iter().any(|n| n.contains("greet")),
"expected meta-tool containing 'greet', got: {names:?}"
);
// No flag__ tools yet
assert!(
names.iter().all(|n| !n.contains("flag__")),
"no dynamic tools before instantiation"
);
}
#[tokio::test]
async fn reflect_trait_instantiate_creates_method_tools() {
// Prime the factory for FlagType — monomorphizes vtable closures
prime_test_greet__greetable::<FlagType>();
let registry = DynamicToolRegistry::new().register_type::<FlagType>("flag");
// Instantiate Greetable tools for the "flag" slot
registry
.instantiate("test_greet::Greetable", "flag")
.await
.expect("instantiate should succeed");
let tools = registry.list_tools();
let names: Vec<&str> = tools.iter().map(|t| t.name.as_ref()).collect();
assert!(
names.contains(&"flag__greet"),
"expected flag__greet, got: {names:?}"
);
assert!(
names.contains(&"flag__is_valid_name"),
"expected flag__is_valid_name, got: {names:?}"
);
}
#[tokio::test]
async fn reflect_trait_instantiate_error_if_not_primed() {
// Register UnprimedFlag WITHOUT priming the factory for its TypeId
let registry = DynamicToolRegistry::new().register_type::<UnprimedFlag>("unprimed_flag");
let result = registry
.instantiate("test_greet::Greetable", "unprimed_flag")
.await;
assert!(
result.is_err(),
"expected error when factory not primed for type"
);
}