@@ -50,6 +50,49 @@ pub fn cached_schema_for_type<T: JsonSchema + std::any::Any>() -> Arc<JsonObject
5050 } )
5151}
5252
53+ /// Generate and validate a JSON schema for outputSchema (must have root type "object").
54+ pub fn schema_for_output < T : JsonSchema + std:: any:: Any > ( ) -> Result < Arc < JsonObject > , String > {
55+ thread_local ! {
56+ static CACHE_FOR_OUTPUT : std:: sync:: RwLock <HashMap <TypeId , Result <Arc <JsonObject >, String >>> = Default :: default ( ) ;
57+ } ;
58+
59+ CACHE_FOR_OUTPUT . with ( |cache| {
60+ // Try to get from cache first
61+ if let Some ( result) = cache
62+ . read ( )
63+ . expect ( "output schema cache lock poisoned" )
64+ . get ( & TypeId :: of :: < T > ( ) )
65+ {
66+ return result. clone ( ) ;
67+ }
68+
69+ // Generate and validate schema
70+ let schema = schema_for_type :: < T > ( ) ;
71+ let result = match schema. get ( "type" ) {
72+ Some ( serde_json:: Value :: String ( t) ) if t == "object" => Ok ( Arc :: new ( schema) ) ,
73+ Some ( serde_json:: Value :: String ( t) ) => Err ( format ! (
74+ "MCP specification requires tool outputSchema to have root type 'object', but found '{}'." ,
75+ t
76+ ) ) ,
77+ None => Err (
78+ "Schema is missing 'type' field. MCP specification requires outputSchema to have root type 'object'." . to_string ( )
79+ ) ,
80+ Some ( other) => Err ( format ! (
81+ "Schema 'type' field has unexpected format: {:?}. Expected \" object\" ." ,
82+ other
83+ ) ) ,
84+ } ;
85+
86+ // Cache the result (both success and error cases)
87+ cache
88+ . write ( )
89+ . expect ( "output schema cache lock poisoned" )
90+ . insert ( TypeId :: of :: < T > ( ) , result. clone ( ) ) ;
91+
92+ result
93+ } )
94+ }
95+
5396/// Trait for extracting parts from a context, unifying tool and prompt extraction
5497pub trait FromContextPart < C > : Sized {
5598 fn from_context_part ( context : & mut C ) -> Result < Self , crate :: ErrorData > ;
@@ -143,3 +186,25 @@ pub trait AsRequestContext {
143186 fn as_request_context ( & self ) -> & RequestContext < RoleServer > ;
144187 fn as_request_context_mut ( & mut self ) -> & mut RequestContext < RoleServer > ;
145188}
189+
190+ #[ cfg( test) ]
191+ mod tests {
192+ use super :: * ;
193+
194+ #[ derive( serde:: Serialize , serde:: Deserialize , JsonSchema ) ]
195+ struct TestObject {
196+ value : i32 ,
197+ }
198+
199+ #[ test]
200+ fn test_schema_for_output_rejects_primitive ( ) {
201+ let result = schema_for_output :: < i32 > ( ) ;
202+ assert ! ( result. is_err( ) , ) ;
203+ }
204+
205+ #[ test]
206+ fn test_schema_for_output_accepts_object ( ) {
207+ let result = schema_for_output :: < TestObject > ( ) ;
208+ assert ! ( result. is_ok( ) , ) ;
209+ }
210+ }
0 commit comments