resource-templates.md•7.33 kB
# Resource Templates
Resource Templates are dynamic resources that can accept parameters in their URIs. Unlike static resources, they use URI patterns to match different paths and extract parameters. In mcp-nest, they're defined using the `@ResourceTemplate()` decorator.
## Basic Resource Template
```typescript
import { Injectable, Scope } from '@nestjs/common';
import { ResourceTemplate } from '@rekog/mcp-nest';
@Injectable({ scope: Scope.REQUEST })
export class GreetingResource {
@ResourceTemplate({
name: 'user-language',
description: "Get a specific user's preferred language",
mimeType: 'application/json',
uriTemplate: 'mcp://users/{name}',
})
getUserLanguage({ uri, name }) {
const users = {
alice: 'en',
carlos: 'es',
marie: 'fr',
hans: 'de',
yuki: 'ja',
'min-jun': 'ko',
wei: 'zh',
sofia: 'it',
joão: 'pt',
};
const language = users[name.toLowerCase()] || 'en';
return {
contents: [
{
uri: uri,
mimeType: 'application/json',
text: JSON.stringify({ name, language }, null, 2),
},
],
};
}
}
```
## URI Template Patterns
Resource templates use `path-to-regexp` style patterns:
### Single Parameter
```typescript
uriTemplate: 'mcp://users/{userId}'
// Matches: mcp://users/123
// Extracts: { userId: '123' }
```
### Multiple Parameters
```typescript
uriTemplate: 'mcp://users/{userId}/posts/{postId}'
// Matches: mcp://users/123/posts/456
// Extracts: { userId: '123', postId: '456' }
```
### Optional Parameters
```typescript
uriTemplate: 'mcp://files/{path*}'
// Matches: mcp://files/docs/readme.md
// Extracts: { path: 'docs/readme.md' }
```
## Method Signature
Resource template methods receive:
- `uri`: The actual URI that was requested
- Individual parameters extracted from the URI pattern
- Additional context if needed
## Real-World Examples
### File System Resource
```typescript
@ResourceTemplate({
name: 'file-content',
description: 'Read file contents from the system',
mimeType: 'text/plain',
uriTemplate: 'mcp://files/{path*}',
})
getFileContent({ uri, path }) {
// Validate path for security
if (path.includes('..') || path.startsWith('/')) {
return {
contents: [{
uri,
mimeType: 'text/plain',
text: 'Error: Invalid file path',
}],
};
}
// In real implementation, read actual file
const content = `Content of file: ${path}`;
return {
contents: [{
uri,
mimeType: 'text/plain',
text: content,
}],
};
}
```
### User Profile Resource
```typescript
@ResourceTemplate({
name: 'user-profile',
description: 'Get user profile information',
mimeType: 'application/json',
uriTemplate: 'mcp://profiles/{userId}',
})
async getUserProfile({ uri, userId }) {
// In real app, query database
const profile = await this.userService.findById(userId);
if (!profile) {
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify({ error: 'User not found' }),
}],
};
}
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(profile),
}],
};
}
```
### API Data Resource
```typescript
@ResourceTemplate({
name: 'api-endpoint',
description: 'Proxy external API data',
mimeType: 'application/json',
uriTemplate: 'mcp://api/{service}/{endpoint}',
})
async getApiData({ uri, service, endpoint }) {
const allowedServices = ['github', 'weather', 'news'];
if (!allowedServices.includes(service)) {
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify({ error: 'Service not allowed' }),
}],
};
}
// Proxy to external API
const data = await this.httpService.get(`https://api.${service}.com/${endpoint}`);
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(data),
}],
};
}
```
## Error Handling
Always handle invalid parameters gracefully:
```typescript
@ResourceTemplate({
name: 'database-record',
description: 'Get database records by ID',
mimeType: 'application/json',
uriTemplate: 'mcp://db/{table}/{id}',
})
async getRecord({ uri, table, id }) {
try {
// Validate table name
const allowedTables = ['users', 'posts', 'comments'];
if (!allowedTables.includes(table)) {
throw new Error('Table not allowed');
}
// Validate ID format
if (!/^\d+$/.test(id)) {
throw new Error('Invalid ID format');
}
const record = await this.dbService.findOne(table, id);
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(record || { error: 'Record not found' }),
}],
};
} catch (error) {
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify({ error: error.message }),
}],
};
}
}
```
## Testing Your Resource Templates
### 1. Start the Server
Run the playground server:
```bash
npx ts-node-dev --respawn playground/servers/server-stateful.ts
```
### 2. List Available Resource Templates
```bash
npx @modelcontextprotocol/inspector@0.16.2 --cli http://localhost:3030/mcp --transport http --method resources/templates/list
```
Expected output:
```json
{
"resourceTemplates": [
{
"name": "user-language",
"description": "Get a specific user's preferred language",
"mimeType": "application/json",
"uriTemplate": "mcp://users/{name}"
}
]
}
```
### 3. Access Resource Templates with Different Parameters
**Test with a known user:**
```bash
npx @modelcontextprotocol/inspector@0.16.2 --cli http://localhost:3030/mcp --transport http --method resources/read --uri "mcp://users/carlos"
```
Expected output:
```json
{
"contents": [
{
"uri": "mcp://users/alice",
"mimeType": "application/json",
"text": "{\n \"name\": \"alice\",\n \"language\": \"en\"\n}"
}
]
}
```
**Test with another user:**
```bash
npx @modelcontextprotocol/inspector@0.16.2 --cli http://localhost:3030/mcp --transport http --method resources/read --uri "mcp://users/carlos"
```
Expected output:
```json
{
"contents": [
{
"uri": "mcp://users/carlos",
"mimeType": "application/json",
"text": "{\n \"name\": \"carlos\",\n \"language\": \"es\"\n}"
}
]
}
```
**Test with unknown user (fallback behavior):**
```bash
npx @modelcontextprotocol/inspector@0.16.2 --cli http://localhost:3030/mcp --transport http --method resources/read --uri "mcp://users/unknown"
```
Expected output:
```json
{
"contents": [
{
"uri": "mcp://users/unknown",
"mimeType": "application/json",
"text": "{\n \"name\": \"unknown\",\n \"language\": \"en\"\n}"
}
]
}
```
### 4. Interactive Testing
For interactive testing, use the MCP Inspector UI:
```bash
npx @modelcontextprotocol/inspector@0.16.2
```
Connect to `http://localhost:3030/mcp` and try accessing different URIs to test your templates.
## Example Location
See the complete example at: `playground/resources/greeting.resource.ts`
## Related
- For static resources, see [Resources](resources.md)
- For executable functions, see [Tools](tools.md)