@@ -27,7 +27,7 @@ use goose::{
2727 agents:: { extension:: ToolInfo , extension_manager:: get_parameter_names} ,
2828 config:: permission:: PermissionLevel ,
2929} ;
30- use rmcp:: model:: { CallToolRequestParams , Content } ;
30+ use rmcp:: model:: CallToolRequestParams ;
3131use serde:: { Deserialize , Serialize } ;
3232use serde_json:: Value ;
3333use std:: collections:: HashSet ;
@@ -134,13 +134,214 @@ pub struct CallToolRequest {
134134 arguments : Value ,
135135}
136136
137+ /// Schema-only types for OpenAPI generation.
138+ ///
139+ /// rmcp's `Content` uses `#[serde(tag = "type")]` so the wire format includes a
140+ /// `type` discriminator (e.g. `{"type":"text","text":"..."}`), but utoipa doesn't
141+ /// reflect that in the generated schema. These types exist solely to produce a
142+ /// correct OpenAPI spec — actual serialization goes through rmcp's `Content` via
143+ /// `serde_json::Value`.
144+ #[ allow( dead_code) ]
145+ pub struct TextContentBlock ;
146+ impl < ' s > utoipa:: ToSchema < ' s > for TextContentBlock {
147+ fn schema ( ) -> (
148+ & ' s str ,
149+ utoipa:: openapi:: RefOr < utoipa:: openapi:: schema:: Schema > ,
150+ ) {
151+ use utoipa:: openapi:: schema:: { ObjectBuilder , SchemaType } ;
152+ (
153+ "TextContentBlock" ,
154+ ObjectBuilder :: new ( )
155+ . property (
156+ "type" ,
157+ ObjectBuilder :: new ( )
158+ . schema_type ( SchemaType :: String )
159+ . enum_values ( Some ( [ "text" ] ) ) ,
160+ )
161+ . required ( "type" )
162+ . property ( "text" , ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) )
163+ . required ( "text" )
164+ . property (
165+ "_meta" ,
166+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: Object ) ,
167+ )
168+ . into ( ) ,
169+ )
170+ }
171+ }
172+
173+ #[ allow( dead_code) ]
174+ pub struct ImageContentBlock ;
175+ impl < ' s > utoipa:: ToSchema < ' s > for ImageContentBlock {
176+ fn schema ( ) -> (
177+ & ' s str ,
178+ utoipa:: openapi:: RefOr < utoipa:: openapi:: schema:: Schema > ,
179+ ) {
180+ use utoipa:: openapi:: schema:: { ObjectBuilder , SchemaType } ;
181+ (
182+ "ImageContentBlock" ,
183+ ObjectBuilder :: new ( )
184+ . property (
185+ "type" ,
186+ ObjectBuilder :: new ( )
187+ . schema_type ( SchemaType :: String )
188+ . enum_values ( Some ( [ "image" ] ) ) ,
189+ )
190+ . required ( "type" )
191+ . property ( "data" , ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) )
192+ . required ( "data" )
193+ . property (
194+ "mimeType" ,
195+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) ,
196+ )
197+ . required ( "mimeType" )
198+ . property (
199+ "_meta" ,
200+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: Object ) ,
201+ )
202+ . into ( ) ,
203+ )
204+ }
205+ }
206+
207+ #[ allow( dead_code) ]
208+ pub struct ResourceContentBlock ;
209+ impl < ' s > utoipa:: ToSchema < ' s > for ResourceContentBlock {
210+ fn schema ( ) -> (
211+ & ' s str ,
212+ utoipa:: openapi:: RefOr < utoipa:: openapi:: schema:: Schema > ,
213+ ) {
214+ use utoipa:: openapi:: schema:: { ObjectBuilder , SchemaType } ;
215+ (
216+ "ResourceContentBlock" ,
217+ ObjectBuilder :: new ( )
218+ . property (
219+ "type" ,
220+ ObjectBuilder :: new ( )
221+ . schema_type ( SchemaType :: String )
222+ . enum_values ( Some ( [ "resource" ] ) ) ,
223+ )
224+ . required ( "type" )
225+ . property (
226+ "resource" ,
227+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: Object ) ,
228+ )
229+ . required ( "resource" )
230+ . property (
231+ "_meta" ,
232+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: Object ) ,
233+ )
234+ . into ( ) ,
235+ )
236+ }
237+ }
238+
239+ #[ allow( dead_code) ]
240+ pub struct AudioContentBlock ;
241+ impl < ' s > utoipa:: ToSchema < ' s > for AudioContentBlock {
242+ fn schema ( ) -> (
243+ & ' s str ,
244+ utoipa:: openapi:: RefOr < utoipa:: openapi:: schema:: Schema > ,
245+ ) {
246+ use utoipa:: openapi:: schema:: { ObjectBuilder , SchemaType } ;
247+ (
248+ "AudioContentBlock" ,
249+ ObjectBuilder :: new ( )
250+ . property (
251+ "type" ,
252+ ObjectBuilder :: new ( )
253+ . schema_type ( SchemaType :: String )
254+ . enum_values ( Some ( [ "audio" ] ) ) ,
255+ )
256+ . required ( "type" )
257+ . property ( "data" , ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) )
258+ . required ( "data" )
259+ . property (
260+ "mimeType" ,
261+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) ,
262+ )
263+ . required ( "mimeType" )
264+ . into ( ) ,
265+ )
266+ }
267+ }
268+
269+ #[ allow( dead_code) ]
270+ pub struct ResourceLinkContentBlock ;
271+ impl < ' s > utoipa:: ToSchema < ' s > for ResourceLinkContentBlock {
272+ fn schema ( ) -> (
273+ & ' s str ,
274+ utoipa:: openapi:: RefOr < utoipa:: openapi:: schema:: Schema > ,
275+ ) {
276+ use utoipa:: openapi:: schema:: { ObjectBuilder , SchemaType } ;
277+ (
278+ "ResourceLinkContentBlock" ,
279+ ObjectBuilder :: new ( )
280+ . property (
281+ "type" ,
282+ ObjectBuilder :: new ( )
283+ . schema_type ( SchemaType :: String )
284+ . enum_values ( Some ( [ "resource_link" ] ) ) ,
285+ )
286+ . required ( "type" )
287+ . property ( "uri" , ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) )
288+ . required ( "uri" )
289+ . property ( "name" , ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) )
290+ . required ( "name" )
291+ . property (
292+ "title" ,
293+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) ,
294+ )
295+ . property (
296+ "description" ,
297+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) ,
298+ )
299+ . property (
300+ "mimeType" ,
301+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: String ) ,
302+ )
303+ . property (
304+ "_meta" ,
305+ ObjectBuilder :: new ( ) . schema_type ( SchemaType :: Object ) ,
306+ )
307+ . into ( ) ,
308+ )
309+ }
310+ }
311+
312+ /// A content block in a tool response, discriminated by a `type` field.
313+ #[ allow( dead_code) ]
314+ pub enum ContentBlock { }
315+
316+ impl < ' s > utoipa:: ToSchema < ' s > for ContentBlock {
317+ fn schema ( ) -> (
318+ & ' s str ,
319+ utoipa:: openapi:: RefOr < utoipa:: openapi:: schema:: Schema > ,
320+ ) {
321+ use utoipa:: openapi:: schema:: { OneOfBuilder , Ref } ;
322+ (
323+ "ContentBlock" ,
324+ OneOfBuilder :: new ( )
325+ . item ( Ref :: from_schema_name ( "TextContentBlock" ) )
326+ . item ( Ref :: from_schema_name ( "ImageContentBlock" ) )
327+ . item ( Ref :: from_schema_name ( "ResourceContentBlock" ) )
328+ . item ( Ref :: from_schema_name ( "AudioContentBlock" ) )
329+ . item ( Ref :: from_schema_name ( "ResourceLinkContentBlock" ) )
330+ . into ( ) ,
331+ )
332+ }
333+ }
334+
137335#[ derive( Serialize , utoipa:: ToSchema ) ]
336+ #[ serde( rename_all = "camelCase" ) ]
138337pub struct CallToolResponse {
139- content : Vec < Content > ,
338+ #[ schema( value_type = Vec <ContentBlock >) ]
339+ content : Vec < Value > ,
140340 #[ serde( skip_serializing_if = "Option::is_none" ) ]
141341 structured_content : Option < Value > ,
142342 is_error : bool ,
143343 #[ serde( skip_serializing_if = "Option::is_none" ) ]
344+ #[ serde( rename = "_meta" ) ]
144345 _meta : Option < Value > ,
145346}
146347
@@ -968,8 +1169,14 @@ async fn call_tool(
9681169 . await
9691170 . map_err ( |_| StatusCode :: INTERNAL_SERVER_ERROR ) ?;
9701171
1172+ let content = result
1173+ . content
1174+ . into_iter ( )
1175+ . filter_map ( |c| serde_json:: to_value ( c) . ok ( ) )
1176+ . collect ( ) ;
1177+
9711178 Ok ( Json ( CallToolResponse {
972- content : result . content ,
1179+ content,
9731180 structured_content : result. structured_content ,
9741181 is_error : result. is_error . unwrap_or ( false ) ,
9751182 _meta : result. meta . and_then ( |m| serde_json:: to_value ( m) . ok ( ) ) ,
0 commit comments