> ## Documentation Index
> Fetch the complete documentation index at: https://fastmcp-ts.docs.prefect.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Resources

> Read-only data addressed by URI, with static URIs and RFC 6570 templates

Resources are the data primitive: read-only values the client fetches by URI. Where a tool runs code and a prompt seeds a conversation, a resource answers a read — the client asks for a URI and your handler returns what lives there. A resource is addressed rather than called, which is why it carries a URI instead of an input schema, and why a single registration can serve either one fixed address or a whole family of them through a template.

## Registration

You register a resource with `server.resource(config, handler)` — the same config-first shape as every other primitive. FastMCP decides whether you've registered a static resource or a template by looking for a `{` in the URI string, so there is no separate method to learn. A plain URI is a static resource; a URI with a `{param}` placeholder is a template, and the extracted parameters are passed to your handler as an object.

```typescript server.ts theme={null}
import { FastMCP } from '@prefecthq/fastmcp-ts/server'

const server = new FastMCP({ name: 'my-server' })

// Static resource — one fixed address
server.resource(
  { uri: 'config://settings', description: 'App configuration' },
  () => JSON.stringify({ theme: 'dark', lang: 'en' }),
)

// Template — one registration serves every matching URI
server.resource(
  { uri: 'user://{id}', description: 'User by ID' },
  ({ id }) => `User #${id}`,
)
```

The two kinds surface differently to clients. Static resources appear in `resources/list` and are fetched with `resources/read`. Templates appear in `resources/templates/list` so a client knows the address *shape* it can fill in, and each concrete read is matched back to your template at request time using RFC 6570 extraction.

## URI templates

A template lets one handler serve an open-ended set of addresses, and the placeholder style controls how much of the URI each parameter captures. The default captures a single path segment, which suits identifiers; a wildcard captures many segments, which suits file paths; and a query style captures query-string parameters, which suits search and filtering. Choose the style that matches the shape of the data you're addressing.

| Style    | Example                   | Captures                |
| -------- | ------------------------- | ----------------------- |
| Simple   | `user://{id}`             | A single path segment   |
| Wildcard | `files://{path*}`         | Multiple path segments  |
| Query    | `search://items{?q,lang}` | Query-string parameters |

```typescript server.ts theme={null}
// Wildcard — {path*} captures "docs/guide/intro.md" whole
server.resource(
  { uri: 'files://{path*}', description: 'Read a file by path' },
  ({ path }) => readFileSync(path, 'utf8'),
)

// Query — {?q,lang} captures ?q=mcp&lang=en
server.resource(
  { uri: 'search://items{?q,lang}', description: 'Search items' },
  ({ q, lang }) => runSearch(q, lang ?? 'en'),
)
```

## Return values

Your handler returns a plain value and FastMCP converts it into an MCP `ReadResourceResult`. The conversion mirrors the tool conversion but is tuned for data: strings become text, raw bytes become a base64 blob, and structured values become JSON. The MIME type is inferred from the return type, and you override it with the `mimeType` config field when the default is wrong.

| Returned value             | Conversion                                                              |
| -------------------------- | ----------------------------------------------------------------------- |
| `string`                   | Text content (`mimeType` defaults to `text/plain`)                      |
| `Buffer` / `Uint8Array`    | Base64 blob content (`mimeType` defaults to `application/octet-stream`) |
| Plain object / array       | JSON-serialized text content (`mimeType` forced to `application/json`)  |
| `null` / `undefined`       | Empty text content                                                      |
| `ResourceResult(contents)` | Passed through as-is                                                    |

When you need full control over the contents array — multiple parts, custom MIME types per part, or hand-built MCP output — return a `ResourceResult`. It bypasses conversion so what you construct is exactly what the client receives.

```typescript server.ts theme={null}
import { ResourceResult } from '@prefecthq/fastmcp-ts/server'

server.resource({ uri: 'bundle://manifest' }, () => {
  return ResourceResult([
    { uri: 'bundle://manifest', mimeType: 'application/json', text: '{"v":1}' },
  ])
})
```

## Subscriptions

A resource's value can change after a client has read it, and subscriptions let the client find out instead of polling. Two sides meet here: your server announces that a resource changed, and a subscribed client re-reads it at its own pace.

Announce a change from server code with `server.notifyResourceUpdated(uri)`. Call it whenever the data behind a URI changes — after a write, on a timer, or from an event handler. FastMCP delivers `notifications/resources/updated` to every connection that subscribed to that URI and nothing to the rest, so calling it for a URI with no subscribers is safe and cheap. The notification carries only the URI, never the new value, which keeps updates light; each client re-reads the fresh data when it is ready to use it.

```typescript server.ts theme={null}
import { FastMCP } from '@prefecthq/fastmcp-ts/server'

const server = new FastMCP({ name: 'my-server' })
let settings = { theme: 'dark' }

server.resource({ uri: 'config://settings' }, () => JSON.stringify(settings))

// Later, when the settings change:
settings = { theme: 'light' }
server.notifyResourceUpdated('config://settings')
```

`notifyResourceUpdated` is the single change signal for both [protocol eras](/concepts/protocol-eras). The wire transport differs underneath — a legacy client subscribes with the `resources/subscribe` and `resources/unsubscribe` RPCs, while a modern client opens one long-lived `subscriptions/listen` stream — but your server calls the one method and FastMCP routes the notification to each subscriber on its own era. The legacy subscribe and unsubscribe requests both run through your [middleware](/servers/middleware) chain, and subscribe honors a resource's `auth` guard exactly as a read does.

Subscribing is a client-side action, so the subscribe API lives on the [client](/clients/client). The client passes a handler that fires on each update.

```typescript client.ts theme={null}
await client.subscribeResource('config://settings', (uri) => {
  console.log(`${uri} changed — re-read it`)
})

await client.unsubscribeResource('config://settings')
```

The handler fires whenever the server sends `notifications/resources/updated` for that URI. Because the notification names only the URI, the client reads the fresh value on its own schedule.

## Variable completion

When a resource is a template, each RFC 6570 variable can suggest values as the client fills it in. The `complete` config field maps a variable name to a completion callback, and a `completion/complete` request for that template routes to the matching callback. This lets a client offer real identifiers for `{id}` rather than expecting the user to already know them.

Each callback receives the partial `value` the user has typed and an optional `context` holding the template's other resolved variables, and it returns a plain `string[]` or a `CompletionResult` with explicit `total` and `hasMore` hints. The callback may be async, so it can query a store. Completion applies to templates only — a static URI has no variables to complete.

```typescript server.ts theme={null}
server.resource(
  {
    uri: 'user://{id}',
    description: 'User by ID',
    complete: {
      id: async (value) => {
        const ids = await listUserIds()
        return ids.filter((id) => id.startsWith(value))
      },
    },
  },
  ({ id }) => `User #${id}`,
)
```

FastMCP caps a bare `string[]` to the 100-item wire maximum and computes `total` and `hasMore` for you. The same callback shape completes [prompt arguments](/servers/prompts).

## Configuration

Beyond `uri` and the display metadata, the config object accepts these fields. Each is optional and each shapes how the resource is advertised or read.

| Field                          | Behavior                                                                                                                             |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `uri`                          | Required; a static URI or an RFC 6570 template                                                                                       |
| `name`, `title`, `description` | Display metadata, forwarded verbatim in list responses                                                                               |
| `mimeType`                     | Overrides the inferred MIME type                                                                                                     |
| `size`                         | Size hint in bytes; static resources only, since the size of a parameterized URI is unknown, so it is omitted from template listings |
| `annotations`                  | `audience`, `priority`, and `lastModified`, forwarded verbatim to clients                                                            |
| `timeout`                      | Per-read timeout in milliseconds; a handler that exceeds it propagates as an error to the client                                     |
| `disabled`                     | When `true`, hides the resource from listings and rejects reads                                                                      |
| `tags`                         | Free-form strings read by [transforms](/servers/transforms)                                                                          |
| `auth`                         | Per-resource authorization check — see [Authorization](/servers/auth/authorization)                                                  |
| `complete`                     | Completion callbacks for a template's RFC 6570 variables, keyed by variable name; templates only                                     |

## Dynamic registration

You can register a resource before or after `server.run()`. Adding one to a running server sends `notifications/resources/list_changed` to connected clients automatically, exactly as adding a tool sends the tools notification — the registration system is uniform across the three primitives.

```typescript server.ts theme={null}
// Later, while the server is already running
server.resource({ uri: 'status://current' }, () => 'ok')
// connected clients receive resources/list_changed
```
