@@ -33,27 +33,24 @@ afterAll(async () => {
3333
3434test ( 'Tool Definition' , async ( ) => {
3535 const list = await client . listTools ( )
36- const [ firstTool ] = list . tools
37- invariant ( firstTool , 'π¨ No tools found' )
3836
39- expect ( firstTool ) . toEqual (
40- expect . objectContaining ( {
41- name : expect . stringMatching ( / ^ c r e a t e _ e n t r y $ / i) ,
42- description : expect . stringMatching ( / ^ c r e a t e a n e w j o u r n a l e n t r y $ / i) ,
43- inputSchema : expect . objectContaining ( {
44- type : 'object' ,
45- properties : expect . objectContaining ( {
46- title : expect . objectContaining ( {
47- type : 'string' ,
48- description : expect . stringMatching ( / t i t l e / i) ,
49- } ) ,
50- content : expect . objectContaining ( {
51- type : 'string' ,
52- description : expect . stringMatching ( / c o n t e n t / i) ,
53- } ) ,
54- } ) ,
55- } ) ,
56- } ) ,
37+ // π¨ Proactive check: Should have both create_entry and get_entry tools
38+ invariant (
39+ list . tools . length >= 2 ,
40+ 'π¨ Should have both create_entry and get_entry tools for this exercise' ,
41+ )
42+
43+ const createTool = list . tools . find ( ( tool ) =>
44+ tool . name . toLowerCase ( ) . includes ( 'create' ) ,
45+ )
46+ const getTool = list . tools . find ( ( tool ) =>
47+ tool . name . toLowerCase ( ) . includes ( 'get' ) ,
48+ )
49+
50+ invariant ( createTool , 'π¨ No create_entry tool found' )
51+ invariant (
52+ getTool ,
53+ 'π¨ No get_entry tool found - this exercise requires implementing get_entry tool' ,
5754 )
5855} )
5956
@@ -79,3 +76,135 @@ test('Tool Call', async () => {
7976 } ) ,
8077 )
8178} )
79+
80+ test ( 'Embedded Resource in Tool Response' , async ( ) => {
81+ // First create an entry to get
82+ await client . callTool ( {
83+ name : 'create_entry' ,
84+ arguments : {
85+ title : 'Embedded Resource Test' ,
86+ content : 'This entry should be returned as an embedded resource' ,
87+ } ,
88+ } )
89+
90+ try {
91+ const result = await client . callTool ( {
92+ name : 'get_entry' ,
93+ arguments : {
94+ id : 1 ,
95+ } ,
96+ } )
97+
98+ // π¨ The key learning objective: Tool responses should include embedded resources
99+ // with type: 'resource' instead of just text content
100+
101+ // Type guard for content array
102+ const content = result . content as Array < any >
103+ invariant (
104+ Array . isArray ( content ) ,
105+ 'π¨ Tool response content must be an array' ,
106+ )
107+
108+ // Check if response includes embedded resource content type
109+ const hasEmbeddedResource = content . some (
110+ ( item : any ) => item . type === 'resource' ,
111+ )
112+
113+ if ( ! hasEmbeddedResource ) {
114+ throw new Error (
115+ 'Tool response should include embedded resource content type' ,
116+ )
117+ }
118+
119+ // Find the embedded resource content
120+ const embeddedResource = content . find (
121+ ( item : any ) => item . type === 'resource' ,
122+ ) as any
123+
124+ // π¨ Proactive checks: Embedded resource should have proper structure
125+ invariant (
126+ embeddedResource ,
127+ 'π¨ Tool response should include embedded resource content type' ,
128+ )
129+ invariant (
130+ embeddedResource . resource ,
131+ 'π¨ Embedded resource must have resource field' ,
132+ )
133+ invariant (
134+ embeddedResource . resource . uri ,
135+ 'π¨ Embedded resource must have uri field' ,
136+ )
137+ invariant (
138+ embeddedResource . resource . mimeType ,
139+ 'π¨ Embedded resource must have mimeType field' ,
140+ )
141+ invariant (
142+ embeddedResource . resource . text ,
143+ 'π¨ Embedded resource must have text field' ,
144+ )
145+ invariant (
146+ typeof embeddedResource . resource . uri === 'string' ,
147+ 'π¨ Embedded resource uri must be a string' ,
148+ )
149+ invariant (
150+ embeddedResource . resource . uri . includes ( 'entries' ) ,
151+ 'π¨ Embedded resource URI should reference an entry' ,
152+ )
153+
154+ expect ( embeddedResource ) . toEqual (
155+ expect . objectContaining ( {
156+ type : 'resource' ,
157+ resource : expect . objectContaining ( {
158+ uri : expect . stringMatching ( / e p i c m e : \/ \/ e n t r i e s \/ \d + / ) ,
159+ mimeType : 'application/json' ,
160+ text : expect . any ( String ) ,
161+ } ) ,
162+ } ) ,
163+ )
164+
165+ // π¨ Proactive check: Embedded resource text should be valid JSON with entry data
166+ let entryData : any
167+ try {
168+ entryData = JSON . parse ( embeddedResource . resource . text )
169+ } catch ( error ) {
170+ throw new Error ( 'π¨ Embedded resource text must be valid JSON' )
171+ }
172+
173+ invariant (
174+ entryData . id ,
175+ 'π¨ Embedded entry resource should contain id field' ,
176+ )
177+ invariant (
178+ entryData . title ,
179+ 'π¨ Embedded entry resource should contain title field' ,
180+ )
181+ invariant (
182+ entryData . content ,
183+ 'π¨ Embedded entry resource should contain content field' ,
184+ )
185+ } catch ( error ) {
186+ console . error ( 'π¨ Embedded resources not implemented in get_entry tool!' )
187+ console . error (
188+ 'π¨ This exercise teaches you how to embed resources in tool responses' ,
189+ )
190+ console . error ( 'π¨ You need to:' )
191+ console . error (
192+ 'π¨ 1. Implement a get_entry tool that takes an id parameter' ,
193+ )
194+ console . error (
195+ 'π¨ 2. Instead of returning just text, return content with type: "resource"' ,
196+ )
197+ console . error (
198+ 'π¨ 3. Include resource object with uri, mimeType, and text fields' ,
199+ )
200+ console . error (
201+ 'π¨ 4. The text field should contain the JSON representation of the entry' ,
202+ )
203+ console . error (
204+ 'π¨ Example: { type: "resource", resource: { uri: "epicme://entries/1", mimeType: "application/json", text: "{\\"id\\": 1, ...}" } }' ,
205+ )
206+ throw new Error (
207+ `π¨ get_entry tool should return embedded resource content type. ${ error } ` ,
208+ )
209+ }
210+ } )
0 commit comments