---
title: Resources
description: Static and template resources exposed over the MCP resources protocol
slug: servers/resources
icon: book-open
keywords: ['frontmcp', 'mcp', 'model context protocol', 'mcp server', 'server', 'components', 'resources', '@Resource', 'uri', 'typescript']
---

Resources expose **readable data** to an AI model's context. Unlike tools that execute actions with side effects, resources are designed for read-only data retrieval—configuration files, user profiles, documents, API responses, or any content the model needs to reference.

<Info>
This feature implements the [MCP Resources specification](https://modelcontextprotocol.io/specification/2025-11-25/server/resources). FrontMCP handles all protocol details automatically.
</Info>

<Tip>
**Nx users:** Scaffold with `nx g @frontmcp/nx:resource my-resource --project my-app`. See [Resource Generator](/frontmcp/nx-plugin/generators/resource).
</Tip>

## Why Resources?

In the Model Context Protocol, resources serve a fundamentally different purpose than tools:

| Aspect         | Resource                        | Tool                     |
| -------------- | ------------------------------- | ------------------------ |
| **Purpose**    | Provide data to read            | Execute actions          |
| **Direction**  | Model pulls data on demand      | Model triggers execution |
| **Idempotent** | Always (read-only)              | Not necessarily          |
| **Use case**   | Context loading, data retrieval | Side effects, mutations  |

Resources are ideal for:

- **Configuration** — expose app settings, feature flags, environment info
- **User data** — profiles, preferences, permissions
- **Documents** — files, templates, knowledge base articles
- **API responses** — cached or live data from external services
- **System state** — logs, metrics, status information

---

## Static Resources

Static resources have a **fixed URI** and return content at that specific address. Use them for singleton data like configuration or global state.

### Class Style

```ts
import { Resource, ResourceContext } from '@frontmcp/sdk';

@Resource({
  name: 'app-config',
  uri: 'config://app',
  mimeType: 'application/json',
  description: 'Application configuration and settings',
})
class AppConfig extends ResourceContext {
  execute(uri: string, params: Record<string, string>) {
    return {
      version: '2.1.0',
      environment: process.env.NODE_ENV,
      features: {
        darkMode: true,
        analytics: false,
      },
    };
  }
}
```

### Function Style

For simpler resources, use the functional builder:

```ts
import { resource } from '@frontmcp/sdk';

const AppConfig = resource({
  name: 'app-config',
  uri: 'config://app',
  mimeType: 'application/json',
})(() => ({
  version: '2.1.0',
  environment: process.env.NODE_ENV,
}));
```

---

## Resource Templates

Resource templates use **dynamic URIs** with parameters following [RFC 6570](https://tools.ietf.org/html/rfc6570) Level 1 syntax. Parameters are extracted automatically and passed to `execute()`.

### Class Style

```ts
import { ResourceTemplate, ResourceContext } from '@frontmcp/sdk';

@ResourceTemplate({
  name: 'user-profile',
  uriTemplate: 'users://{userId}/profile',
  mimeType: 'application/json',
  description: 'Fetch user profile by ID',
})
class UserProfile extends ResourceContext {
  execute(uri: string, params: Record<string, string>) {
    const { userId } = params;
    // Fetch user from database, API, etc.
    return {
      id: userId,
      name: 'Jane Doe',
      email: 'jane@example.com',
      role: 'admin',
    };
  }
}
```

### URI Template Patterns

```ts
// Single parameter
'users://{userId}'              // matches users://123

// Multiple parameters
'repos://{owner}/{repo}'        // matches repos://acme/widget

// With path segments
'files://{path}/content'        // matches files://docs/content

// Complex paths
'api://{version}/users/{id}'    // matches api://v2/users/456
```

Parameters are extracted into a `Record<string, string>` and passed as the second argument to `execute()`.

---

## Registering Resources

Add resources to your app via the `resources` array:

```ts
import { App } from '@frontmcp/sdk';

@App({
  id: 'my-app',
  name: 'My Application',
  resources: [AppConfig, UserProfile],
})
class MyApp {}
```

Resources can also be generated dynamically by **adapters** (e.g., OpenAPI adapter) or **plugins**.

### Loading from npm or Remote Servers

Mix local resources with those loaded from npm or proxied from remote servers:

```ts
import { App, Resource } from '@frontmcp/sdk';

@App({
  id: 'my-app',
  name: 'My Application',
  resources: [
    AppConfig,                                                        // Local class
    Resource.esm('@acme/tools@^1.0.0', 'status'),                    // Single resource from npm
    Resource.remote('https://api.example.com/mcp', 'system-health'), // Single resource from remote
  ],
})
class MyApp {}
```

<Info>
`Resource.esm()` and `Resource.remote()` load individual resources. For loading **entire apps**, use [`App.esm()`](/frontmcp/servers/esm-packages) or [`App.remote()`](/frontmcp/servers/apps#remote-apps).
</Info>

---

## Return Values

Resources support multiple return formats. The SDK automatically converts your return value to the MCP `ReadResourceResult` format.

### Simple Returns

```ts
// Object -> auto-serialized to JSON
execute() {
  return { key: 'value', count: 42 };
}

// String -> text content
execute() {
  return 'Plain text content';
}

// Buffer -> binary blob (base64 encoded)
execute() {
  return Buffer.from(imageData);
}
```

### Full MCP Format

For complete control over the response, return the full `ReadResourceResult` structure:

```ts
execute(uri: string) {
  return {
    contents: [
      {
        uri,
        mimeType: 'text/plain',
        text: 'First content block',
      },
      {
        uri: `${uri}#image`,
        mimeType: 'image/png',
        blob: 'base64EncodedImageData...',
      },
    ],
  };
}
```

### Multiple Content Items

Return an array to include multiple content blocks:

```ts
execute(uri: string) {
  return [
    { uri, mimeType: 'text/markdown', text: '# Document Title' },
    { uri: `${uri}#metadata`, mimeType: 'application/json', text: '{"author":"Jane"}' },
  ];
}
```

---

## Resource Metadata

### Static Resource (`@Resource`)

```ts
@Resource({
  name: string,           // Required: unique identifier
  uri: string,            // Required: static URI (e.g., 'config://app')
  title?: string,         // Optional: human-readable display name
  description?: string,   // Optional: hint for the LLM
  mimeType?: string,      // Optional: content type (e.g., 'application/json')
  icons?: Icon[],         // Optional: UI icons
})
```

### Resource Template (`@ResourceTemplate`)

```ts
@ResourceTemplate({
  name: string,           // Required: unique identifier
  uriTemplate: string,    // Required: RFC 6570 URI template (e.g., 'users://{id}')
  title?: string,         // Optional: human-readable display name
  description?: string,   // Optional: hint for the LLM
  mimeType?: string,      // Optional: content type for all matching URIs
  icons?: Icon[],         // Optional: UI icons
})
```

**Field descriptions:**

| Field                 | Description                                                  |
| --------------------- | ------------------------------------------------------------ |
| `name`                | Programmatic identifier used internally and in MCP responses |
| `uri` / `uriTemplate` | The address clients use to request this resource             |
| `title`               | Human-friendly name for UI display                           |
| `description`         | Helps the model understand when to use this resource         |
| `mimeType`            | Content type hint; auto-detected for JSON/text if omitted    |
| `icons`               | Array of icons for visual representation in clients          |
| `availableWhen`       | Restrict discovery to specific environments. See [Environment Awareness](/frontmcp/features/environment-awareness) |

---

## Resource Context

Class-based resources have access to a rich execution context via `this`:

```ts
@Resource({ name: 'data', uri: 'app://data' })
class DataResource extends ResourceContext {
  execute(uri: string, params: Record<string, string>) {
    // Request information
    this.uri;              // The actual URI being read
    this.params;           // Extracted template parameters (empty for static)
    this.metadata;         // Resource metadata (name, uri, description, etc.)

    // Authentication
    this.context.authInfo; // Partial<AuthInfo> — preferred (token, user, sessionId)
    this.auth;             // FrontMcpAuthContext (typed identity helpers)
    // this.authInfo is a deprecated alias kept for backward-compat

    // Dependency injection
    this.get(DbService);   // Resolve a provider
    this.tryGet(Cache);    // Resolve or return undefined

    // Scope access
    this.scope;            // Access the current scope

    // Utilities
    this.fetch(url);       // Built-in fetch for HTTP requests

    // Flow control
    this.respond(value);   // End execution with a response
  }
}
```

### Using Providers

Inject services via the `get()` method:

```ts
@Resource({
  name: 'users-list',
  uri: 'data://users',
})
class UsersList extends ResourceContext {
  async execute() {
    const db = this.get(DatabaseProvider);
    const users = await db.query('SELECT * FROM users LIMIT 100');
    return users;
  }
}
```

---

## Real-World Examples

### Configuration Resource

```ts
@Resource({
  name: 'feature-flags',
  uri: 'config://features',
  mimeType: 'application/json',
  description: 'Current feature flag configuration',
})
class FeatureFlags extends ResourceContext {
  execute() {
    return {
      newDashboard: process.env.FEATURE_NEW_DASHBOARD === 'true',
      betaFeatures: process.env.FEATURE_BETA === 'true',
      maxUploadSize: parseInt(process.env.MAX_UPLOAD_SIZE || '10485760'),
    };
  }
}
```

### Database-Backed Template

```ts
@ResourceTemplate({
  name: 'order-details',
  uriTemplate: 'orders://{orderId}',
  mimeType: 'application/json',
  description: 'Retrieve order details by order ID',
})
class OrderDetails extends ResourceContext {
  async execute(uri: string, params: Record<string, string>) {
    const db = this.get(DatabaseProvider);
    const order = await db.orders.findById(params.orderId);

    if (!order) {
      throw new Error(`Order ${params.orderId} not found`);
    }

    return {
      id: order.id,
      status: order.status,
      items: order.items,
      total: order.total,
      createdAt: order.createdAt,
    };
  }
}
```

### File System Resource

```ts
@ResourceTemplate({
  name: 'document',
  uriTemplate: 'docs://{path}',
  description: 'Read documentation files',
})
class DocumentResource extends ResourceContext {
  async execute(uri: string, params: Record<string, string>) {
    const fs = await import('fs/promises');
    const path = await import('path');

    const docPath = path.join(process.cwd(), 'docs', params.path);
    const content = await fs.readFile(docPath, 'utf-8');
    const ext = path.extname(params.path);

    return {
      contents: [{
        uri,
        mimeType: ext === '.md' ? 'text/markdown' : 'text/plain',
        text: content,
      }],
    };
  }
}
```

### API Proxy Resource

```ts
@ResourceTemplate({
  name: 'github-repo',
  uriTemplate: 'github://{owner}/{repo}',
  mimeType: 'application/json',
  description: 'Fetch GitHub repository information',
})
class GitHubRepo extends ResourceContext {
  async execute(uri: string, params: Record<string, string>) {
    const { owner, repo } = params;
    const response = await this.fetch(
      `https://api.github.com/repos/${owner}/${repo}`,
      {
        headers: {
          'Accept': 'application/vnd.github.v3+json',
          'User-Agent': 'FrontMCP-Resource',
        },
      }
    );

    if (!response.ok) {
      throw new Error(`GitHub API error: ${response.status}`);
    }

    return response.json();
  }
}
```

---

## MCP Protocol Integration

Resources integrate with the MCP protocol via three flows:

| Flow                       | Description                       |
| -------------------------- | --------------------------------- |
| `resources/list`           | Returns all static resources      |
| `resources/templates/list` | Returns all resource templates    |
| `resources/read`           | Reads content from a specific URI |

When a client requests `resources/read` with a URI:

1. The SDK matches the URI against registered resources and templates
2. For templates, parameters are extracted from the URI
3. The `execute()` method is called with the URI and parameters
4. The return value is converted to MCP `ReadResourceResult` format

### Capabilities

FrontMCP automatically advertises resource capabilities during MCP initialization:

```json
{
  "capabilities": {
    "resources": {
      "subscribe": true,
      "listChanged": true
    }
  }
}
```

| Capability    | Description                                                                                                  |
| ------------- | ------------------------------------------------------------------------------------------------------------ |
| `subscribe`   | When `true`, clients can subscribe to individual resource changes via `resources/subscribe`                  |
| `listChanged` | When `true`, the server will send `notifications/resources/list_changed` when resources are added or removed |

Per the [MCP spec](https://modelcontextprotocol.io/specification/2025-11-25/server/resources), both capabilities are **server-level** — they apply to all resources globally, not per-resource. There is no `subscribe` field on individual `Resource` or `ResourceTemplate` objects.

The SDK sets these capabilities automatically based on your registered resources:

- `listChanged: true` when you have any resources registered
- `subscribe: true` when any resources are registered — clients can subscribe to **any** resource via `resources/subscribe`

<Info>
You do **not** need to add `subscribe: true` to `@Resource()` or `@ResourceTemplate()` decorators. Subscription support is automatic for all resources when the server has any resources registered. Subscriptions work in-memory on single-server deployments (stdio, binary, single-instance HTTP) — no Redis or pub/sub required.
</Info>

### Change Notifications

When resources change dynamically (e.g., via adapters or plugins), FrontMCP automatically sends `notifications/resources/list_changed` to connected clients. Clients that support this notification will refresh their resource list.

For subscribed resources, the server sends `notifications/resources/updated` when the content changes. Resource authors can trigger this notification by calling `this.notifyUpdated()` from within a `ResourceContext`, or programmatically via `scope.resources.notifyContentChanged(uri)`.

<Info>
Resource subscriptions work out of the box on single-server deployments (stdio, binary, single-instance HTTP) using in-memory tracking — no Redis or pub/sub configuration is needed. The `pubsub` config is only required for **distributed multi-instance** deployments where subscription state must be shared across server instances.
</Info>

<Tip>
For the full protocol specification, see [MCP Resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources).
</Tip>

<Info>
To learn how clients discover your resources via MCP flows like `resources/list` and `resources/templates/list`, see [Discovery APIs](/frontmcp/servers/discovery).
</Info>

---

## Best Practices

**Do:**

- Use descriptive `name` and `description` fields to help models understand resource purpose
- Return structured data (objects) when possible for better model comprehension
- Use templates for parameterized data instead of creating many static resources
- Keep resources focused—one resource per data concern
- Handle errors gracefully and return meaningful error messages

**Don't:**

- Use resources for operations with side effects (use tools instead)
- Return extremely large datasets—paginate or summarize when needed
- Expose sensitive data without proper authentication checks
- Hardcode values that should come from configuration
- Add `subscribe: true` to `@Resource()` — subscriptions are automatic at the server level per MCP spec

### URI Template Encoding

Resource template parameters are **percent-encoded** per [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) Level 1 during URI expansion. This is important when parameters contain special characters like `://`, `/`, or spaces.

For example, if a tool returns a raw ID like `x-coredata://3823FD9C/ICNote/p216` and you use it as a template parameter in `notes://note/{noteId}`, the expanded URI becomes:

```
notes://note/x-coredata%3A%2F%2F3823FD9C%2FICNote%2Fp216
```

When the framework matches this URI back against the template, parameters are automatically decoded to their original values. The round-trip is consistent:
- `expandUriTemplate("notes://note/{noteId}", { noteId: "x-coredata://..." })` → percent-encoded URI
- `matchUriTemplate("notes://note/{noteId}", encodedUri)` → `{ noteId: "x-coredata://..." }` (decoded)

<Warning>
When clients construct resource URIs from IDs returned by tools, they must percent-encode the parameters (e.g., using `encodeURIComponent()`) to match the canonical URI format. The `resources/subscribe` and `resources/read` requests use the encoded URI.
</Warning>
