Skip to content

Commit 233fc0f

Browse files
committed
improve annotations
1 parent fc1354c commit 233fc0f

File tree

20 files changed

+466
-266
lines changed

20 files changed

+466
-266
lines changed

exercises/01.advanced-tools/01.problem.annotations/src/tools.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ import {
1313
import { type EpicMeMCP } from './index.ts'
1414
import { createWrappedVideo } from './video.ts'
1515

16+
// 🧝‍♀️ Because the tool annotations defaults and values are confusing, I'm giving
17+
// this to you to help you use them correctly!
18+
type ToolAnnotations = {
19+
// defaults to true, so only allow false
20+
openWorldHint?: false
21+
} & (
22+
| {
23+
// when readOnlyHint is true, none of the other annotations can be changed
24+
readOnlyHint: true
25+
}
26+
| {
27+
destructiveHint?: false // Only allow false (default is true)
28+
idempotentHint?: true // Only allow true (default is false)
29+
}
30+
)
31+
1632
export async function initializeTools(agent: EpicMeMCP) {
1733
agent.server.registerTool(
1834
'create_entry',
@@ -21,6 +37,7 @@ export async function initializeTools(agent: EpicMeMCP) {
2137
description: 'Create a new journal entry',
2238
// 🐨 add the appropriate annotations here for create_entry.
2339
// 💰 Is this destructive? Is it open world?
40+
annotations: {} satisfies ToolAnnotations,
2441
inputSchema: createEntryInputSchema,
2542
},
2643
async (entry) => {
@@ -52,6 +69,7 @@ export async function initializeTools(agent: EpicMeMCP) {
5269
description: 'Get a journal entry by ID',
5370
// 🐨 add the appropriate annotations here for get_entry.
5471
// 💰 Is this read-only? Is it open world?
72+
annotations: {} satisfies ToolAnnotations,
5573
inputSchema: entryIdSchema,
5674
},
5775
async ({ id }) => {
@@ -70,6 +88,7 @@ export async function initializeTools(agent: EpicMeMCP) {
7088
description: 'List all journal entries',
7189
// 🐨 add the appropriate annotations here for list_entries.
7290
// 💰 Is this read-only? Is it open world?
91+
annotations: {} satisfies ToolAnnotations,
7392
},
7493
async () => {
7594
const entries = await agent.db.getEntries()
@@ -91,6 +110,7 @@ export async function initializeTools(agent: EpicMeMCP) {
91110
'Update a journal entry. Fields that are not provided (or set to undefined) will not be updated. Fields that are set to null or any other value will be updated.',
92111
// 🐨 add the appropriate annotations here for update_entry.
93112
// 💰 Is this destructive? Idempotent? Open world?
113+
annotations: {} satisfies ToolAnnotations,
94114
inputSchema: updateEntryInputSchema,
95115
},
96116
async ({ id, ...updates }) => {
@@ -115,6 +135,7 @@ export async function initializeTools(agent: EpicMeMCP) {
115135
description: 'Delete a journal entry',
116136
// 🐨 add the appropriate annotations here for delete_entry.
117137
// 💰 Is this idempotent? Open world?
138+
annotations: {} satisfies ToolAnnotations,
118139
inputSchema: entryIdSchema,
119140
},
120141
async ({ id }) => {
@@ -140,6 +161,7 @@ export async function initializeTools(agent: EpicMeMCP) {
140161
description: 'Create a new tag',
141162
// 🐨 add the appropriate annotations here for create_tag.
142163
// 💰 Is this destructive? Open world?
164+
annotations: {} satisfies ToolAnnotations,
143165
inputSchema: createTagInputSchema,
144166
},
145167
async (tag) => {
@@ -162,6 +184,7 @@ export async function initializeTools(agent: EpicMeMCP) {
162184
description: 'Get a tag by ID',
163185
// 🐨 add the appropriate annotations here for get_tag.
164186
// 💰 Is this read-only? Open world?
187+
annotations: {} satisfies ToolAnnotations,
165188
inputSchema: tagIdSchema,
166189
},
167190
async ({ id }) => {
@@ -180,6 +203,7 @@ export async function initializeTools(agent: EpicMeMCP) {
180203
description: 'List all tags',
181204
// 🐨 add the appropriate annotations here for list_tags.
182205
// 💰 Is this read-only? Open world?
206+
annotations: {} satisfies ToolAnnotations,
183207
},
184208
async () => {
185209
const tags = await agent.db.getTags()
@@ -197,6 +221,7 @@ export async function initializeTools(agent: EpicMeMCP) {
197221
description: 'Update a tag',
198222
// 🐨 add the appropriate annotations here for update_tag.
199223
// 💰 Is this destructive? Idempotent? Open world?
224+
annotations: {} satisfies ToolAnnotations,
200225
inputSchema: updateTagInputSchema,
201226
},
202227
async ({ id, ...updates }) => {
@@ -219,6 +244,7 @@ export async function initializeTools(agent: EpicMeMCP) {
219244
description: 'Delete a tag',
220245
// 🐨 add the appropriate annotations here for delete_tag.
221246
// 💰 Is this idempotent? Open world?
247+
annotations: {} satisfies ToolAnnotations,
222248
inputSchema: tagIdSchema,
223249
},
224250
async ({ id }) => {
@@ -243,6 +269,7 @@ export async function initializeTools(agent: EpicMeMCP) {
243269
description: 'Add a tag to an entry',
244270
// 🐨 add the appropriate annotations here for add_tag_to_entry.
245271
// 💰 Is this destructive? Idempotent? Open world?
272+
annotations: {} satisfies ToolAnnotations,
246273
inputSchema: entryTagIdSchema,
247274
},
248275
async ({ entryId, tagId }) => {
@@ -274,6 +301,7 @@ export async function initializeTools(agent: EpicMeMCP) {
274301
'Create a "wrapped" video highlighting stats of your journaling this year',
275302
// 🐨 add the appropriate annotations here for create_wrapped_video.
276303
// 💰 Is this destructive? Open world?
304+
annotations: {} satisfies ToolAnnotations,
277305
inputSchema: {
278306
year: z
279307
.number()

exercises/01.advanced-tools/01.solution.annotations/src/tools.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ import {
1313
import { type EpicMeMCP } from './index.ts'
1414
import { createWrappedVideo } from './video.ts'
1515

16+
type ToolAnnotations = {
17+
// defaults to true, so only allow false
18+
openWorldHint?: false
19+
} & (
20+
| {
21+
// when readOnlyHint is true, none of the other annotations can be changed
22+
readOnlyHint: true
23+
}
24+
| {
25+
destructiveHint?: false // Only allow false (default is true)
26+
idempotentHint?: true // Only allow true (default is false)
27+
}
28+
)
29+
1630
export async function initializeTools(agent: EpicMeMCP) {
1731
agent.server.registerTool(
1832
'create_entry',
@@ -22,7 +36,7 @@ export async function initializeTools(agent: EpicMeMCP) {
2236
annotations: {
2337
destructiveHint: false,
2438
openWorldHint: false,
25-
},
39+
} satisfies ToolAnnotations,
2640
inputSchema: createEntryInputSchema,
2741
},
2842
async (entry) => {
@@ -55,7 +69,7 @@ export async function initializeTools(agent: EpicMeMCP) {
5569
annotations: {
5670
readOnlyHint: true,
5771
openWorldHint: false,
58-
},
72+
} satisfies ToolAnnotations,
5973
inputSchema: entryIdSchema,
6074
},
6175
async ({ id }) => {
@@ -75,7 +89,7 @@ export async function initializeTools(agent: EpicMeMCP) {
7589
annotations: {
7690
readOnlyHint: true,
7791
openWorldHint: false,
78-
},
92+
} satisfies ToolAnnotations,
7993
},
8094
async () => {
8195
const entries = await agent.db.getEntries()
@@ -99,7 +113,7 @@ export async function initializeTools(agent: EpicMeMCP) {
99113
destructiveHint: false,
100114
idempotentHint: true,
101115
openWorldHint: false,
102-
},
116+
} satisfies ToolAnnotations,
103117
inputSchema: updateEntryInputSchema,
104118
},
105119
async ({ id, ...updates }) => {
@@ -123,9 +137,8 @@ export async function initializeTools(agent: EpicMeMCP) {
123137
title: 'Delete Entry',
124138
description: 'Delete a journal entry',
125139
annotations: {
126-
idempotentHint: true,
127140
openWorldHint: false,
128-
},
141+
} satisfies ToolAnnotations,
129142
inputSchema: entryIdSchema,
130143
},
131144
async ({ id }) => {
@@ -152,7 +165,7 @@ export async function initializeTools(agent: EpicMeMCP) {
152165
annotations: {
153166
destructiveHint: false,
154167
openWorldHint: false,
155-
},
168+
} satisfies ToolAnnotations,
156169
inputSchema: createTagInputSchema,
157170
},
158171
async (tag) => {
@@ -176,7 +189,7 @@ export async function initializeTools(agent: EpicMeMCP) {
176189
annotations: {
177190
readOnlyHint: true,
178191
openWorldHint: false,
179-
},
192+
} satisfies ToolAnnotations,
180193
inputSchema: tagIdSchema,
181194
},
182195
async ({ id }) => {
@@ -196,7 +209,7 @@ export async function initializeTools(agent: EpicMeMCP) {
196209
annotations: {
197210
readOnlyHint: true,
198211
openWorldHint: false,
199-
},
212+
} satisfies ToolAnnotations,
200213
},
201214
async () => {
202215
const tags = await agent.db.getTags()
@@ -216,7 +229,7 @@ export async function initializeTools(agent: EpicMeMCP) {
216229
destructiveHint: false,
217230
idempotentHint: true,
218231
openWorldHint: false,
219-
},
232+
} satisfies ToolAnnotations,
220233
inputSchema: updateTagInputSchema,
221234
},
222235
async ({ id, ...updates }) => {
@@ -238,9 +251,8 @@ export async function initializeTools(agent: EpicMeMCP) {
238251
title: 'Delete Tag',
239252
description: 'Delete a tag',
240253
annotations: {
241-
idempotentHint: true,
242254
openWorldHint: false,
243-
},
255+
} satisfies ToolAnnotations,
244256
inputSchema: tagIdSchema,
245257
},
246258
async ({ id }) => {
@@ -267,7 +279,7 @@ export async function initializeTools(agent: EpicMeMCP) {
267279
destructiveHint: false,
268280
idempotentHint: true,
269281
openWorldHint: false,
270-
},
282+
} satisfies ToolAnnotations,
271283
inputSchema: entryTagIdSchema,
272284
},
273285
async ({ entryId, tagId }) => {
@@ -300,7 +312,7 @@ export async function initializeTools(agent: EpicMeMCP) {
300312
annotations: {
301313
destructiveHint: false,
302314
openWorldHint: false,
303-
},
315+
} satisfies ToolAnnotations,
304316
inputSchema: {
305317
year: z
306318
.number()

exercises/01.advanced-tools/02.problem.structured/src/tools.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export async function initializeTools(agent: EpicMeMCP) {
2626
annotations: {
2727
destructiveHint: false,
2828
openWorldHint: false,
29-
},
29+
} satisfies ToolAnnotations,
3030
inputSchema: createEntryInputSchema,
3131
// 🐨 add an outputSchema here with an entry that is an entryWithTagsSchema
3232
},
@@ -67,7 +67,7 @@ export async function initializeTools(agent: EpicMeMCP) {
6767
annotations: {
6868
readOnlyHint: true,
6969
openWorldHint: false,
70-
},
70+
} satisfies ToolAnnotations,
7171
inputSchema: entryIdSchema,
7272
// 🐨 add an outputSchema here with an entry that is an entrySchema
7373
},
@@ -94,7 +94,7 @@ export async function initializeTools(agent: EpicMeMCP) {
9494
annotations: {
9595
readOnlyHint: true,
9696
openWorldHint: false,
97-
},
97+
} satisfies ToolAnnotations,
9898
// 🐨 add an outputSchema here with entries that is an array of entryWithTagsSchema
9999
},
100100
async () => {
@@ -122,7 +122,7 @@ export async function initializeTools(agent: EpicMeMCP) {
122122
destructiveHint: false,
123123
idempotentHint: true,
124124
openWorldHint: false,
125-
},
125+
} satisfies ToolAnnotations,
126126
inputSchema: updateEntryInputSchema,
127127
// 🐨 add an outputSchema here with an entry that is an entryWithTagsSchema
128128
},
@@ -151,9 +151,8 @@ export async function initializeTools(agent: EpicMeMCP) {
151151
title: 'Delete Entry',
152152
description: 'Delete a journal entry',
153153
annotations: {
154-
idempotentHint: true,
155154
openWorldHint: false,
156-
},
155+
} satisfies ToolAnnotations,
157156
inputSchema: entryIdSchema,
158157
// 🐨 add an outputSchema here with success (boolean) and entry (entryWithTagsSchema)
159158
},
@@ -185,7 +184,7 @@ export async function initializeTools(agent: EpicMeMCP) {
185184
annotations: {
186185
destructiveHint: false,
187186
openWorldHint: false,
188-
},
187+
} satisfies ToolAnnotations,
189188
inputSchema: createTagInputSchema,
190189
// 🐨 add an outputSchema here with a tag that is a tagSchema
191190
},
@@ -214,7 +213,7 @@ export async function initializeTools(agent: EpicMeMCP) {
214213
annotations: {
215214
readOnlyHint: true,
216215
openWorldHint: false,
217-
},
216+
} satisfies ToolAnnotations,
218217
inputSchema: tagIdSchema,
219218
// 🐨 add an outputSchema here with a tag that is a tagSchema
220219
},
@@ -242,7 +241,7 @@ export async function initializeTools(agent: EpicMeMCP) {
242241
annotations: {
243242
readOnlyHint: true,
244243
openWorldHint: false,
245-
},
244+
} satisfies ToolAnnotations,
246245
// 🐨 add an outputSchema here with tags that is an array of tagSchema
247246
},
248247
async () => {
@@ -269,7 +268,7 @@ export async function initializeTools(agent: EpicMeMCP) {
269268
destructiveHint: false,
270269
idempotentHint: true,
271270
openWorldHint: false,
272-
},
271+
} satisfies ToolAnnotations,
273272
inputSchema: updateTagInputSchema,
274273
// 🐨 add an outputSchema here with a tag that is a tagSchema
275274
},
@@ -296,9 +295,8 @@ export async function initializeTools(agent: EpicMeMCP) {
296295
title: 'Delete Tag',
297296
description: 'Delete a tag',
298297
annotations: {
299-
idempotentHint: true,
300298
openWorldHint: false,
301-
},
299+
} satisfies ToolAnnotations,
302300
inputSchema: tagIdSchema,
303301
// 🐨 add an outputSchema here with success (boolean) and tag (tagSchema)
304302
},
@@ -330,7 +328,7 @@ export async function initializeTools(agent: EpicMeMCP) {
330328
destructiveHint: false,
331329
idempotentHint: true,
332330
openWorldHint: false,
333-
},
331+
} satisfies ToolAnnotations,
334332
inputSchema: entryTagIdSchema,
335333
// 🐨 add an outputSchema here with a tag that is a tagSchema and an entry that is an entrySchema
336334
},
@@ -369,7 +367,7 @@ export async function initializeTools(agent: EpicMeMCP) {
369367
annotations: {
370368
destructiveHint: false,
371369
openWorldHint: false,
372-
},
370+
} satisfies ToolAnnotations,
373371
inputSchema: {
374372
year: z
375373
.number()
@@ -422,6 +420,20 @@ export async function initializeTools(agent: EpicMeMCP) {
422420
)
423421
}
424422

423+
type ToolAnnotations = {
424+
// defaults to true, so only allow false
425+
openWorldHint?: false
426+
} & (
427+
| {
428+
// when readOnlyHint is true, none of the other annotations can be changed
429+
readOnlyHint: true
430+
}
431+
| {
432+
destructiveHint?: false // Only allow false (default is true)
433+
idempotentHint?: true // Only allow true (default is false)
434+
}
435+
)
436+
425437
function createText(text: unknown): CallToolResult['content'][number] {
426438
if (typeof text === 'string') {
427439
return { type: 'text', text }

0 commit comments

Comments
 (0)