TanStack
MCP

Serve Resources and Prompts

Until you serve a resource, a host cannot read your document. Until you serve a prompt, a host cannot start from that prompt.

Define Resources and Prompts

  1. Define each document with resourceDefinition.
  2. Define each prompt with promptDefinition.
  3. Pass resources and prompts to createMCPServer.
ts
import {
  createMCPServer,
  promptDefinition,
  resourceDefinition,
} from '@tanstack/ai-mcp/server'
import { z } from 'zod'

const readme = resourceDefinition({
  name: 'readme',
  mimeType: 'text/markdown',
  uri: 'file:///readme.md',
}).read(async () => ({ text: '# Hello' }))

const file = resourceDefinition({
  name: 'file',
  mimeType: 'text/plain',
  uriTemplate: 'file:///{path}',
}).read(async () => ({ text: 'file body' }))

const summarize = promptDefinition({
  name: 'summarize',
  description: 'Summarize a topic',
  argsSchema: z.object({ topic: z.string() }),
}).render(async (args) => [{ role: 'user', content: args.topic }])

const server = createMCPServer({
  name: 'library',
  version: '1.0.0',
  resources: [readme, file],
  prompts: [summarize],
})

export function handleMcp(request: Request) {
  return server.fetch(request)
}

Resources

A resource must have uri or uriTemplate.

  • uri: one document. An example is file:///readme.md.
  • uriTemplate: a URI pattern. An example is file:///{path}.

If the resource has no uri and no uriTemplate, resourceDefinition throws This resource has no uri and no uriTemplate. Pass a uri or a uriTemplate.

If you pass uri and uriTemplate, the server uses uri.

read returns { text } for a text document. For a binary document, read returns { blob } with a base64 string.

Serve One Resource per Item

A template such as myapp://items/{itemId}/summary serves many documents. The read must know which item the host asked for, and which user asks. A host also needs a list of the items that exist.

  1. Read itemId from the variables argument of read.
  2. Read the user from ctx.context.
  3. Add list to return the concrete resources for resources/list.
ts
import { createMCPServer, resourceDefinition } from '@tanstack/ai-mcp/server'

const summaries = new Map([
  ['1', 'First item'],
  ['2', 'Second item'],
])

const summary = resourceDefinition({
  name: 'item-summary',
  mimeType: 'text/plain',
  uriTemplate: 'myapp://items/{itemId}/summary',
  list: async () => ({
    resources: [...summaries.keys()].map((id) => ({
      uri: `myapp://items/${id}/summary`,
      name: `Item ${id}`,
    })),
  }),
}).read(async (uri, variables, ctx) => {
  const itemId = String(variables.itemId)
  const userId = String(ctx.context.userId)
  return { text: `${summaries.get(itemId) ?? 'Unknown'} (for ${userId})` }
})

const server = createMCPServer({
  name: 'items',
  version: '1.0.0',
  resources: [summary],
})

export function handleMcp(request: Request, userId: string) {
  return server.handle(request, { context: { userId } })
}

read gets three arguments:

  • uri: the URI the host asked for, as a URL.
  • variables: the values from the template. A resource with uri gets {}.
  • ctx.context: the values from handle(request, { context }), plus the verified token as authInfo. A tool gets the same values, plus requestInput and sample.

list gets the same ctx. It returns { resources }, with a uri and a name for each resource. Only a resource with uriTemplate can have list.

Prompts

argsSchema.parse runs first. Then render receives the parsed arguments.

render returns an array of messages. Each message has these fields:

  • role: user or assistant.
  • content: the message text.

If role is not user or assistant, the server sends that message as user.

What the Host Gets

The host reads file:///readme.md. The text is # Hello.

The host starts from the summarize prompt with topic weather. The role is user. The content string is weather.