# @Resource

> Declare data at a fixed URI that clients can list and read, like a policy document or a settings file.

Source: https://frontmcp.dev/reference/sdk/resource

`@Resource` declares an MCP [resource](https://frontmcp.dev/learn/exposing-data-with-resources): data at a fixed URI, like `policy://sla`, that a client can list and read. The client application decides when to read it, usually to attach it to a conversation. For a family of URIs that share a pattern, like `ticket://{id}`, use [`@ResourceTemplate`](https://frontmcp.dev/reference/sdk/resource-template).

```ts
@Resource(options)
class MyResource extends ResourceContext {
  async execute(uri) { /* ... */ }
}
```

---

## Reference

### `@Resource(options)`

Apply `@Resource` to a class that extends `ResourceContext`, and list the class in an app's `resources` array.

```ts sla.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({
  name: "sla",
  uri: "policy://sla",
  description: "How fast the help desk replies, by ticket priority",
  mimeType: "application/json",
})
class SlaPolicy extends ResourceContext {
  async execute() {
    return { high: "4 hours", normal: "1 business day", low: "3 business days" };
  }
}
```

[See more examples below.](#usage)

#### Options

Required:

| Option | Type | Description |
| --- | --- | --- |
| `name` | `string` | Identifies the resource. Unique within the server. |
| `uri` | `string` | The address clients read. It must start with a scheme and `://`, like `policy://sla` or `file:///docs/faq.md`. |

Optional:

| Option | Type | Description |
| --- | --- | --- |
| `title` | `string` | A display name for people. Clients show it instead of `name`. |
| `description` | `string` | What the data is and when it's useful. Clients and models read it in `resources/list`. |
| `mimeType` | `string` | The content type, like `application/json`. Listed in `resources/list`, and used for the content blocks FrontMCP builds from what `execute()` returns. Without it, FrontMCP picks one per block. Blocks you return in `{ contents }` are sent as they are. |
| `icons` | `Icon[]` | Images clients can show: `src`, and optionally `mimeType`, `sizes` and `theme` (`"light"` or `"dark"`). |
| `annotations` | `ResourceAnnotations` | Hints for clients: `audience` (`["user"]`, `["assistant"]` or both), `priority` (from 0 to 1) and `lastModified` (an ISO 8601 date). |
| `_meta` | `Record<string, unknown>` | Extra metadata, sent as is in `resources/list`. Use reverse-DNS keys, like `com.example/owner`. |
| `availableWhen` | `EntryAvailability` | Only offer the resource on certain platforms, runtimes or deployments. Elsewhere it isn't listed, and reading it fails with [`-32003`](#reads-fail-with--32003-resource--is-not-available-in-the-current-environment). See [Environment awareness](https://frontmcp.dev/reference/server/environment). |

#### `execute(uri, params)`

FrontMCP calls `execute()` every time a client reads the resource.

- `uri`: the URI that was read. For a `@Resource`, that's always its `uri` option.
- `params`: template parameters. Always `{}` for a `@Resource`.
- **Returns** the content. FrontMCP turns it into the `contents` array of the `resources/read` result:

| `execute()` returns | The client receives |
| --- | --- |
| An object or number, like `{ high: "4 hours" }` | One text block with the value as JSON. MIME type `application/json`. |
| A string | One text block. The MIME type comes from the URI's extension (`.md` is `text/markdown`) or the text itself, and is `text/plain` otherwise. |
| A `Uint8Array` or `Buffer` | One `blob` block, base64-encoded. MIME type `application/octet-stream`, unless you set `mimeType`. |
| An object with a `text` or `blob` string | That block, with the resource's URI. |
| An array | One block per item, converted as above. Items without their own `uri` get `#0`, `#1`… appended to the resource's URI. |
| `{ contents: [...] }` | Exactly those blocks. Use this for full control. |

Inside `execute()`, `this` is the `ResourceContext`: `this.uri` and `this.params` hold the same values as the arguments, `this.get()` returns [providers](https://frontmcp.dev/reference/sdk/provider), `this.fetch()` makes outbound HTTP requests, `this.context` and `this.auth` describe the request and the caller, and `this.notifyUpdated()` tells subscribed clients that the content changed. `this.notify()`, `this.progress()` and `this.elicit()` belong to tools, not resources.

#### `resource(options)(handler)`

The function form takes the same options. The handler receives `(uri, params)` and returns a `ReadResourceResult`, the `{ contents: [...] }` shape.

#### Caveats

- The class **must extend `ResourceContext`**. Using `@Resource` on any other class is a compile error.
- `uri` **must have a scheme followed by `://`**. Otherwise the server doesn't start, and reports "URI must have a valid scheme".
- Reads match the URI **exactly**. `policy://sla/` and `policy://SLA` are different URIs, and reading them fails with "Resource not found".
- A resource takes no arguments. For data that depends on an id, use [`@ResourceTemplate`](https://frontmcp.dev/reference/sdk/resource-template). If a resource and a template both match a URI, the resource wins.
- If `execute()` throws `ResourceNotFoundError(uri)`, the client gets the same error as for a URI that matches nothing: `-32602`, `Resource not found: …`. A `PublicMcpError` also gives `-32602`, with your error's message as the whole message.
- Any other error is an internal one: JSON-RPC error `-32603`, with `Resource "…" read failed:` and your error's message in development. With `NODE_ENV=production`, FrontMCP hides the message and sends `Internal FrontMCP error. Please contact support with error ID: …` instead. See [troubleshooting](#reads-fail-with--32603-resource--read-failed).
- `this.fail(error)` fails the read the same way as `throw error` for a `PublicMcpError`. For any other error it's still `-32603`, but the message is your error's message without the `read failed:` prefix, and `data.code` is `SERVER_ERROR` instead of `RESOURCE_READ_ERROR`. Production hides it the same way.

---

## Usage

### Exposing data at a fixed URI

Give the resource a URI with your own scheme and a description that says what's in it. Return a plain object and FrontMCP sends it as JSON.

```ts sla.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({
  name: "sla",
  title: "Reply-time policy",
  uri: "policy://sla",
  description: "How fast the help desk replies, by ticket priority. Read it before promising a customer a reply time.",
})
export class SlaPolicy extends ResourceContext {
  async execute() {
    return { high: "4 hours", normal: "1 business day", low: "3 business days" };
  }
}
```

Open the **Capabilities** tab to see the resource as `resources/list` describes it. Clients read it with `resources/read` and the URI.

### Choosing what the client receives

What `execute()` returns decides the content blocks and their MIME types.

<Examples title="Content types">

#### Example: Text
A string becomes one text block. The URI ends in `.md`, so the MIME type is `text/markdown`.

```ts replies.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({ name: "reply-guide", uri: "guide://replies.md", description: "How to word replies to customers" })
export class ReplyGuide extends ResourceContext {
  async execute() {
    return "# Replying to customers\n\n- Greet them by name.\n- Say what you did, then what happens next.\n- Link the ticket id, like T-1.";
  }
}
```

#### Example: Full control
Return `{ contents }` to set every field yourself, here a CSV export.

```ts export.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({ name: "open-tickets-csv", uri: "export://tickets/open.csv", description: "Open tickets as CSV" })
export class OpenTicketsCsv extends ResourceContext {
  async execute(uri: string) {
    const csv = "id,title,priority\nT-1,Cannot log in,high\nT-3,Login link expired,normal";
    return { contents: [{ uri, mimeType: "text/csv", text: csv }] };
  }
}
```

#### Example: Binary
Bytes arrive base64-encoded in `blob`. Set `mimeType`, or clients get `application/octet-stream`.

```ts logo.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

// The first bytes of a PNG file, to keep the example short.
const png = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);

@Resource({ name: "logo", uri: "brand://logo.png", mimeType: "image/png", description: "The help desk logo" })
export class Logo extends ResourceContext {
  async execute() {
    return png;
  }
}
```

#### Example: Several blocks
Return an array for several blocks. Blocks without a `uri` get `#0`, `#1`… after the resource's URI.

```ts handbook.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({ name: "handbook", uri: "policy://handbook", description: "The support handbook, one block per section" })
export class Handbook extends ResourceContext {
  async execute(uri: string) {
    return [
      { text: "Escalate high-priority tickets after 2 hours.", mimeType: "text/plain" },
      { uri: `${uri}#hours`, mimeType: "application/json", text: JSON.stringify({ open: "08:00", close: "18:00" }) },
    ];
  }
}
```

### Reading from a provider

Get shared services with `this.get()`, and register the provider in the same app's `providers` array.

```ts on-duty.resource.ts active
import { Resource, ResourceContext } from "@frontmcp/sdk";
import { Rota } from "./rota";

@Resource({ name: "on-duty", uri: "desk://on-duty", description: "Which agents are on duty right now" })
export class OnDuty extends ResourceContext {
  async execute() {
    return { agents: this.get(Rota).onDuty() };
  }
}
```

```ts rota.ts
import { Provider, ProviderScope } from "@frontmcp/sdk";

@Provider({ name: "Rota", scope: ProviderScope.GLOBAL })
export class Rota {
  onDuty() {
    return ["Nour", "Sam"];
  }
}
```

### Describing a resource for clients

Everything except `availableWhen` goes out in `resources/list`. Clients use `title` and `icons` for display, and `annotations` to decide who the content is for and how prominent it should be. The test below reads the list the way a client would.

```ts sla.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({
  name: "sla",
  title: "Reply-time policy",
  uri: "policy://sla",
  description: "How fast the help desk replies, by ticket priority",
  mimeType: "application/json",
  annotations: { audience: ["assistant"], priority: 0.8, lastModified: "2026-09-01T09:00:00Z" },
  _meta: { "com.example/owner": "support-ops" },
})
export class SlaPolicy extends ResourceContext {
  async execute() {
    return { high: "4 hours", normal: "1 business day", low: "3 business days" };
  }
}
```

```ts sla.test.ts
import { test, expect } from "@frontmcp/testing";

test("resources/list describes the policy", async ({ mcp }) => {
  const resources = await mcp.resources.list();
  expect(resources).toContainResource("policy://sla");
  expect(resources[0]).toMatchObject({
    name: "sla",
    title: "Reply-time policy",
    mimeType: "application/json",
    annotations: { audience: ["assistant"], priority: 0.8 },
    _meta: { "com.example/owner": "support-ops" },
  });
});

test("reading it returns JSON", async ({ mcp }) => {
  const content = await mcp.resources.read("policy://sla");
  expect(content).toHaveMimeType("application/json");
  expect(content.json()).toEqual({ high: "4 hours", normal: "1 business day", low: "3 business days" });
});
```

### Writing a resource as a function

For a resource that doesn't need context, the function form is shorter. Its handler returns the full `{ contents }` shape, which is sent as it is, so set each block's `mimeType` there.

```ts hours.resource.ts
import { resource } from "@frontmcp/sdk";

export const openingHours = resource({
  name: "opening-hours",
  uri: "policy://hours",
  mimeType: "application/json",
  description: "When the help desk answers tickets",
})((uri) => ({
  contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ weekdays: "08:00-18:00", weekends: "closed" }) }],
}));
```

---

## Troubleshooting

### The server doesn't start: "URI must have a valid scheme"

The `uri` option needs a scheme followed by `://`. `policy:sla` and `/policy/sla` are rejected; `policy://sla` works.

### Reads fail with `-32602` "Resource not found"

No resource or template matches the URI exactly, or `execute()` threw `ResourceNotFoundError`. Check for a trailing slash, different letter case, or a typo, and compare with `resources/list`. Under MCP 2026-07-28 this error has code `-32602`; earlier protocol versions use `-32002`. A resource whose `availableWhen.surface` leaves out `"mcp"` gets the same answer: MCP clients can't read it.

```ts sla.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({ name: "sla", uri: "policy://sla" })
export class SlaPolicy extends ResourceContext {
  async execute() {
    return { high: "4 hours" };
  }
}
```

### Reads fail with `-32603` "Resource … read failed"

`execute()` threw an error that isn't a `PublicMcpError`, so FrontMCP treats it as a failure of the server. In development, the message after `read failed:` is your error's message:

```ts sla.resource.ts
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({ name: "sla", uri: "policy://sla" })
export class SlaPolicy extends ResourceContext {
  async execute(): Promise<unknown> {
    // 🚩 A server failure, and production hides the message
    throw new Error("The SLA policy hasn't been published yet");
  }
}
```

With `NODE_ENV=production`, the client only gets `Internal FrontMCP error. Please contact support with error ID: …`. If the client should read the message, throw a `PublicMcpError`. The read then fails with `-32602` and your message, in production too:

```ts sla.resource.ts active
import { PublicMcpError, Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({ name: "sla", uri: "policy://sla" })
export class SlaPolicy extends ResourceContext {
  async execute(): Promise<unknown> {
    // ✅ The client sees exactly this message
    throw new PublicMcpError("The SLA policy hasn't been published yet");
  }
}
```

```ts sla.test.ts
import { test, expect } from "@frontmcp/testing";

test("the message reaches the client as it was thrown", async ({ mcp }) => {
  const result = await mcp.resources.read("policy://sla");
  expect(result).toBeError(-32602);
  expect(result.error?.message).toBe("The SLA policy hasn't been published yet");
});
```

A message ending in `read failed: Resource output not found` means `execute()` returned nothing. Return the content, or end with [`this.respond()`](https://frontmcp.dev/reference/sdk/respond), which works in resources since 1.9.3.

### Reads fail with `-32003` "Resource "…" is not available in the current environment"

The resource's `availableWhen` doesn't match where the server runs. It's left out of `resources/list`, and a client that reads it by URI gets this error, with what the resource requires and what the server is:

```ts deploy.resource.ts active
import { Resource, ResourceContext } from "@frontmcp/sdk";

@Resource({ name: "deploy-notes", uri: "policy://deno-deploy", mimeType: "text/plain", availableWhen: { runtime: ["deno"] } })
export class DeployNotes extends ResourceContext {
  async execute() {
    return "Deploy with deployctl.";
  }
}
```

```ts deploy.test.ts
import { test, expect } from "@frontmcp/testing";

test("it isn't listed, and reading it fails", async ({ mcp }) => {
  expect(await mcp.resources.list()).toEqual([]);
  const result = await mcp.resources.read("policy://deno-deploy");
  expect(result).toBeError(-32003);
  expect(result.error?.message).toMatch(/^Resource "policy:\/\/deno-deploy" is not available in the current environment \(requires: \{"runtime":\["deno"\]\}\)/);
});
```

The message lists the server's OS, runtime and deployment, in production too. If the resource should be readable here, fix its `availableWhen`.

### My resource isn't in `resources/list`

Check, in order:

1. The class is in its app's `resources` array, and the app is in `@FrontMcp({ apps })`.
2. It's a `@Resource`. Templates are listed separately, in `resources/templates/list`.
3. Its `availableWhen` matches the environment the server runs in.
