Add resources and prompts

Add a notes resource

Expose the note catalog as readable, addressable context without turning a simple read into a tool call.

A tool asks the server to do something. A resource is different. It gives a piece of readable context a stable address, and the client decides when to read it.

Our note catalog is a natural resource. Reading it has no side effect and needs no arguments. A client can attach it to a conversation as context, the same way you’d attach a file.

Add this registration inside createServer(), after the two tools:

  server.registerResource(
    'notes-catalog',
    'notes://catalog',
    {
      title: 'Project notes catalog',
      mimeType: 'application/json'
    },
    async uri => ({
      contents: [{
        uri: uri.href,
        mimeType: 'application/json',
        text: JSON.stringify(
          notes.map(({ id, title, tags }) => ({ id, title, tags })),
          null,
          2
        )
      }]
    })
  )

The first argument is the resource name. The second is its URI, notes://catalog. The notes: scheme is ours. It doesn’t point at a file on disk, it identifies an application resource. A client still has to call resources/read to get the contents. The URI is an address, not the data.

In the returned object, uri must identify what was requested, and mimeType tells the client how to interpret the text. Here it’s JSON, so keep the JSON valid and keep the catalog bounded. Three notes is trivial. Three thousand would need paging or a different design.

What the catalog leaves out

We expose only id, title, and tags. Note bodies stay behind get_note, where the caller has to name one ID. This is deliberate. If the catalog included bodies, attaching it would load every note into the model’s context, whether the conversation needs it or not.

URIs are input too

Resource URIs become untrusted input as soon as they contain variables. This one is fixed, so there’s nothing to expand or normalize. If you later add something like notes://note/{id}, validate the decoded id and apply the same authorization the lookup tool uses. A resource template is just another way to ask for data.

Check it in the Inspector

Run npm run check, restart the Inspector, and open the Resources tab. You should see one entry. Read notes://catalog and you get this:

[
  { "id": "deploy-checklist", "title": "Deploy checklist", "tags": ["deploy", "release"] },
  { "id": "incident-notes", "title": "Incident notes", "tags": ["operations"] },
  { "id": "review-guide", "title": "Review guide", "tags": ["quality"] }
]

Check three things: the listed URI matches the URI in the read result, the media type is application/json, and no body field appears anywhere. If a body shows up, the map() lost its destructuring, and you’re leaking the thing the catalog was designed to hide.

Lesson completed