@@ -138,3 +138,242 @@ func TestCallToolResultWithResourceLink(t *testing.T) {
138138 assert .Equal (t , "A test document" , resourceLink .Description )
139139 assert .Equal (t , "application/pdf" , resourceLink .MIMEType )
140140}
141+
142+ func TestResourceContentsMetaField (t * testing.T ) {
143+ tests := []struct {
144+ name string
145+ inputJSON string
146+ expectedType string
147+ expectedMeta map [string ]any
148+ }{
149+ {
150+ name : "TextResourceContents with empty _meta" ,
151+ inputJSON : `{
152+ "uri":"file://empty-meta.txt",
153+ "mimeType":"text/plain",
154+ "text":"x",
155+ "_meta": {}
156+ }` ,
157+ expectedType : "text" ,
158+ expectedMeta : map [string ]any {},
159+ },
160+ {
161+ name : "TextResourceContents with _meta field" ,
162+ inputJSON : `{
163+ "uri": "file://test.txt",
164+ "mimeType": "text/plain",
165+ "text": "Hello World",
166+ "_meta": {
167+ "mcpui.dev/ui-preferred-frame-size": ["800px", "600px"],
168+ "mcpui.dev/ui-initial-render-data": {
169+ "test": "value"
170+ }
171+ }
172+ }` ,
173+ expectedType : "text" ,
174+ expectedMeta : map [string ]any {
175+ "mcpui.dev/ui-preferred-frame-size" : []interface {}{"800px" , "600px" },
176+ "mcpui.dev/ui-initial-render-data" : map [string ]any {
177+ "test" : "value" ,
178+ },
179+ },
180+ },
181+ {
182+ name : "BlobResourceContents with _meta field" ,
183+ inputJSON : `{
184+ "uri": "file://image.png",
185+ "mimeType": "image/png",
186+ "blob": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
187+ "_meta": {
188+ "width": 100,
189+ "height": 100,
190+ "format": "PNG"
191+ }
192+ }` ,
193+ expectedType : "blob" ,
194+ expectedMeta : map [string ]any {
195+ "width" : float64 (100 ), // JSON numbers are always float64
196+ "height" : float64 (100 ),
197+ "format" : "PNG" ,
198+ },
199+ },
200+ {
201+ name : "TextResourceContents without _meta field" ,
202+ inputJSON : `{
203+ "uri": "file://simple.txt",
204+ "mimeType": "text/plain",
205+ "text": "Simple content"
206+ }` ,
207+ expectedType : "text" ,
208+ expectedMeta : nil ,
209+ },
210+ {
211+ name : "BlobResourceContents without _meta field" ,
212+ inputJSON : `{
213+ "uri": "file://simple.png",
214+ "mimeType": "image/png",
215+ "blob": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
216+ }` ,
217+ expectedType : "blob" ,
218+ expectedMeta : nil ,
219+ },
220+ }
221+
222+ for _ , tc := range tests {
223+ t .Run (tc .name , func (t * testing.T ) {
224+ // Parse the JSON as a generic map first
225+ var contentMap map [string ]any
226+ err := json .Unmarshal ([]byte (tc .inputJSON ), & contentMap )
227+ require .NoError (t , err )
228+
229+ // Use ParseResourceContents to convert to ResourceContents
230+ resourceContent , err := ParseResourceContents (contentMap )
231+ require .NoError (t , err )
232+ require .NotNil (t , resourceContent )
233+
234+ // Test based on expected type
235+ if tc .expectedType == "text" {
236+ textContent , ok := resourceContent .(TextResourceContents )
237+ require .True (t , ok , "Expected TextResourceContents" )
238+
239+ // Verify standard fields
240+ assert .Equal (t , contentMap ["uri" ], textContent .URI )
241+ assert .Equal (t , contentMap ["mimeType" ], textContent .MIMEType )
242+ assert .Equal (t , contentMap ["text" ], textContent .Text )
243+
244+ // Verify _meta field
245+ assert .Equal (t , tc .expectedMeta , textContent .Meta )
246+
247+ } else if tc .expectedType == "blob" {
248+ blobContent , ok := resourceContent .(BlobResourceContents )
249+ require .True (t , ok , "Expected BlobResourceContents" )
250+
251+ // Verify standard fields
252+ assert .Equal (t , contentMap ["uri" ], blobContent .URI )
253+ assert .Equal (t , contentMap ["mimeType" ], blobContent .MIMEType )
254+ assert .Equal (t , contentMap ["blob" ], blobContent .Blob )
255+
256+ // Verify _meta field
257+ assert .Equal (t , tc .expectedMeta , blobContent .Meta )
258+ }
259+
260+ // Test round-trip marshaling to ensure _meta is preserved
261+ marshaledJSON , err := json .Marshal (resourceContent )
262+ require .NoError (t , err )
263+
264+ var marshaledMap map [string ]any
265+ err = json .Unmarshal (marshaledJSON , & marshaledMap )
266+ require .NoError (t , err )
267+
268+ // Verify _meta field is preserved in marshaled output
269+ v , ok := marshaledMap ["_meta" ]
270+ if tc .expectedMeta != nil {
271+ // Special case: empty maps are omitted due to omitempty tag
272+ if len (tc .expectedMeta ) == 0 {
273+ assert .False (t , ok , "_meta should be omitted when empty due to omitempty" )
274+ } else {
275+ require .True (t , ok , "_meta should be present" )
276+ assert .Equal (t , tc .expectedMeta , v )
277+ }
278+ } else {
279+ assert .False (t , ok , "_meta should be omitted when nil" )
280+ }
281+ })
282+ }
283+ }
284+
285+ func TestParseResourceContentsInvalidMeta (t * testing.T ) {
286+ tests := []struct {
287+ name string
288+ inputJSON string
289+ expectedErr string
290+ }{
291+ {
292+ name : "TextResourceContents with invalid _meta (string)" ,
293+ inputJSON : `{
294+ "uri": "file://test.txt",
295+ "mimeType": "text/plain",
296+ "text": "Hello World",
297+ "_meta": "invalid_meta_string"
298+ }` ,
299+ expectedErr : "_meta must be an object" ,
300+ },
301+ {
302+ name : "TextResourceContents with invalid _meta (number)" ,
303+ inputJSON : `{
304+ "uri": "file://test.txt",
305+ "mimeType": "text/plain",
306+ "text": "Hello World",
307+ "_meta": 123
308+ }` ,
309+ expectedErr : "_meta must be an object" ,
310+ },
311+ {
312+ name : "TextResourceContents with invalid _meta (array)" ,
313+ inputJSON : `{
314+ "uri": "file://test.txt",
315+ "mimeType": "text/plain",
316+ "text": "Hello World",
317+ "_meta": ["invalid", "array"]
318+ }` ,
319+ expectedErr : "_meta must be an object" ,
320+ },
321+ {
322+ name : "TextResourceContents with invalid _meta (boolean)" ,
323+ inputJSON : `{
324+ "uri": "file://test.txt",
325+ "mimeType": "text/plain",
326+ "text": "Hello World",
327+ "_meta": true
328+ }` ,
329+ expectedErr : "_meta must be an object" ,
330+ },
331+ {
332+ name : "TextResourceContents with invalid _meta (null)" ,
333+ inputJSON : `{
334+ "uri": "file://test.txt",
335+ "mimeType": "text/plain",
336+ "text": "Hello World",
337+ "_meta": null
338+ }` ,
339+ expectedErr : "_meta must be an object" ,
340+ },
341+ {
342+ name : "BlobResourceContents with invalid _meta (string)" ,
343+ inputJSON : `{
344+ "uri": "file://image.png",
345+ "mimeType": "image/png",
346+ "blob": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
347+ "_meta": "invalid_meta_string"
348+ }` ,
349+ expectedErr : "_meta must be an object" ,
350+ },
351+ {
352+ name : "BlobResourceContents with invalid _meta (number)" ,
353+ inputJSON : `{
354+ "uri": "file://image.png",
355+ "mimeType": "image/png",
356+ "blob": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
357+ "_meta": 456
358+ }` ,
359+ expectedErr : "_meta must be an object" ,
360+ },
361+ }
362+
363+ for _ , tc := range tests {
364+ t .Run (tc .name , func (t * testing.T ) {
365+ // Parse the JSON as a generic map first
366+ var contentMap map [string ]any
367+ err := json .Unmarshal ([]byte (tc .inputJSON ), & contentMap )
368+ require .NoError (t , err )
369+
370+ // Use ParseResourceContents to convert to ResourceContents
371+ resourceContent , err := ParseResourceContents (contentMap )
372+
373+ // Expect an error
374+ require .Error (t , err )
375+ assert .Contains (t , err .Error (), tc .expectedErr )
376+ assert .Nil (t , resourceContent )
377+ })
378+ }
379+ }
0 commit comments