Build a local TypeScript server
Add the get_note tool
Return one complete note by stable ID with structured output and an explicit not-found result.
8 minute lesson
Search returns stable IDs. A second tool can now read one complete note without making every search result large.
Add it below search_notes:
const noteSchema = z.object({
id: z.string(),
title: z.string(),
tags: z.array(z.string()),
body: z.string()
})
server.registerTool(
'get_note',
{
description: 'Read one project note by its exact ID',
inputSchema: z.object({ id: z.string().trim().min(1).max(100) }),
outputSchema: z.object({ note: noteSchema }),
annotations: {
readOnlyHint: true,
destructiveHint: false
}
},
async ({ id }) => {
const note = notes.find(candidate => candidate.id === id)
if (!note) {
return {
content: [{ type: 'text', text: `No note found with ID: ${id}` }],
isError: true
}
}
const output = { note }
return {
content: [{ type: 'text', text: JSON.stringify(output, null, 2) }],
structuredContent: output
}
}
)
There are two kinds of failure here.
An empty or malformed id fails schema validation before the handler runs. A valid-looking ID that is absent reaches the handler and returns a tool execution error with isError: true.
That distinction gives the model a useful correction path. It can fix the argument shape, or it can search again for a valid ID. Returning an empty success would hide the difference.
Do not include every known ID in the error. On a private server, that would turn one failed lookup into data discovery. The separate search tool is the authorized discovery path.
Test deploy-checklist, missing-note, and an empty string. Confirm that only the successful call contains structuredContent matching the output schema.
Lesson completed