@@ -80,6 +80,183 @@ test('Tool Definition', async () => {
8080 )
8181} )
8282
83+ test ( 'Tool annotations and structured output' , async ( ) => {
84+ await using setup = await setupClient ( )
85+ const { client } = setup
86+
87+ // Check create_entry and create_tag annotations (always enabled)
88+ let list = await client . listTools ( )
89+ let toolMap = Object . fromEntries ( list . tools . map ( ( t ) => [ t . name , t ] ) )
90+
91+ // Check create_entry annotations
92+ const createEntryTool = toolMap [ 'create_entry' ]
93+ invariant ( createEntryTool , '🚨 create_entry tool not found' )
94+ expect (
95+ createEntryTool . annotations ,
96+ '🚨 create_entry missing annotations' ,
97+ ) . toEqual (
98+ expect . objectContaining ( {
99+ destructiveHint : false ,
100+ openWorldHint : false ,
101+ } ) ,
102+ )
103+
104+ // Check create_tag annotations
105+ const createTagTool = toolMap [ 'create_tag' ]
106+ invariant ( createTagTool , '🚨 create_tag tool not found' )
107+ expect (
108+ createTagTool . annotations ,
109+ '🚨 create_tag missing annotations' ,
110+ ) . toEqual (
111+ expect . objectContaining ( {
112+ destructiveHint : false ,
113+ openWorldHint : false ,
114+ } ) ,
115+ )
116+
117+ // Create a tag and entry for further tool calls
118+ const tagResult = await client . callTool ( {
119+ name : 'create_tag' ,
120+ arguments : {
121+ name : 'TestTag' ,
122+ description : 'A tag for testing' ,
123+ } ,
124+ } )
125+ expect (
126+ tagResult . structuredContent ,
127+ '🚨 tagResult.structuredContent should be defined' ,
128+ ) . toBeDefined ( )
129+ const tag = ( tagResult . structuredContent as any ) . tag
130+ invariant ( tag , '🚨 No tag resource found' )
131+ invariant ( tag . id , '🚨 No tag ID found' )
132+
133+ const entryResult = await client . callTool ( {
134+ name : 'create_entry' ,
135+ arguments : {
136+ title : 'Test Entry' ,
137+ content : 'This is a test entry' ,
138+ } ,
139+ } )
140+ expect (
141+ entryResult . structuredContent ,
142+ '🚨 entryResult.structuredContent should be defined' ,
143+ ) . toBeDefined ( )
144+ const entry = ( entryResult . structuredContent as any ) . entry
145+ invariant ( entry , '🚨 No entry resource found' )
146+ invariant ( entry . id , '🚨 No entry ID found' )
147+
148+ // List tools again now that entry and tag exist
149+ list = await client . listTools ( )
150+ toolMap = Object . fromEntries ( list . tools . map ( ( t ) => [ t . name , t ] ) )
151+
152+ // Check delete_entry annotations
153+ const deleteEntryTool = toolMap [ 'delete_entry' ]
154+ invariant ( deleteEntryTool , '🚨 delete_entry tool not found' )
155+ expect (
156+ deleteEntryTool . annotations ,
157+ '🚨 delete_entry missing annotations' ,
158+ ) . toEqual (
159+ expect . objectContaining ( {
160+ idempotentHint : true ,
161+ openWorldHint : false ,
162+ } ) ,
163+ )
164+
165+ // Check delete_tag annotations
166+ const deleteTagTool = toolMap [ 'delete_tag' ]
167+ invariant ( deleteTagTool , '🚨 delete_tag tool not found' )
168+ expect (
169+ deleteTagTool . annotations ,
170+ '🚨 delete_tag missing annotations' ,
171+ ) . toEqual (
172+ expect . objectContaining ( {
173+ idempotentHint : true ,
174+ openWorldHint : false ,
175+ } ) ,
176+ )
177+
178+ // get_entry structuredContent
179+ const getEntryResult = await client . callTool ( {
180+ name : 'get_entry' ,
181+ arguments : { id : entry . id } ,
182+ } )
183+ const getEntryContent = ( getEntryResult . structuredContent as any ) . entry
184+ invariant ( getEntryContent , '🚨 get_entry missing entry in structuredContent' )
185+ expect ( getEntryContent . id , '🚨 get_entry structuredContent.id mismatch' ) . toBe (
186+ entry . id ,
187+ )
188+
189+ // get_tag structuredContent
190+ const getTagResult = await client . callTool ( {
191+ name : 'get_tag' ,
192+ arguments : { id : tag . id } ,
193+ } )
194+ const getTagContent = ( getTagResult . structuredContent as any ) . tag
195+ invariant ( getTagContent , '🚨 get_tag missing tag in structuredContent' )
196+ expect ( getTagContent . id , '🚨 get_tag structuredContent.id mismatch' ) . toBe (
197+ tag . id ,
198+ )
199+
200+ // update_entry structuredContent
201+ const updateEntryResult = await client . callTool ( {
202+ name : 'update_entry' ,
203+ arguments : { id : entry . id , title : 'Updated Entry' } ,
204+ } )
205+ const updateEntryContent = ( updateEntryResult . structuredContent as any ) . entry
206+ invariant (
207+ updateEntryContent ,
208+ '🚨 update_entry missing entry in structuredContent' ,
209+ )
210+ expect (
211+ updateEntryContent . title ,
212+ '🚨 update_entry structuredContent.title mismatch' ,
213+ ) . toBe ( 'Updated Entry' )
214+
215+ // update_tag structuredContent
216+ const updateTagResult = await client . callTool ( {
217+ name : 'update_tag' ,
218+ arguments : { id : tag . id , name : 'UpdatedTag' } ,
219+ } )
220+ const updateTagContent = ( updateTagResult . structuredContent as any ) . tag
221+ invariant ( updateTagContent , '🚨 update_tag missing tag in structuredContent' )
222+ expect (
223+ updateTagContent . name ,
224+ '🚨 update_tag structuredContent.name mismatch' ,
225+ ) . toBe ( 'UpdatedTag' )
226+
227+ // delete_entry structuredContent
228+ const deleteEntryResult = await client . callTool ( {
229+ name : 'delete_entry' ,
230+ arguments : { id : entry . id } ,
231+ } )
232+ const deleteEntryContent = deleteEntryResult . structuredContent as any
233+ invariant ( deleteEntryContent , '🚨 delete_entry missing structuredContent' )
234+ expect (
235+ deleteEntryContent . success ,
236+ '🚨 delete_entry structuredContent.success should be true' ,
237+ ) . toBe ( true )
238+ expect (
239+ deleteEntryContent . entry . id ,
240+ '🚨 delete_entry structuredContent.entry.id mismatch' ,
241+ ) . toBe ( entry . id )
242+
243+ // delete_tag structuredContent
244+ const deleteTagResult = await client . callTool ( {
245+ name : 'delete_tag' ,
246+ arguments : { id : tag . id } ,
247+ } )
248+ const deleteTagContent = deleteTagResult . structuredContent as any
249+ invariant ( deleteTagContent , '🚨 delete_tag missing structuredContent' )
250+ expect (
251+ deleteTagContent . success ,
252+ '🚨 delete_tag structuredContent.success should be true' ,
253+ ) . toBe ( true )
254+ expect (
255+ deleteTagContent . tag . id ,
256+ '🚨 delete_tag structuredContent.tag.id mismatch' ,
257+ ) . toBe ( tag . id )
258+ } )
259+
83260async function deferred < ResolvedValue > ( ) {
84261 const ref = { } as {
85262 promise : Promise < ResolvedValue >
0 commit comments