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