Resources provide access to data via URIs. They can represent files, database records, API endpoints, or any other data source that can be accessed through a URI.
Static resources have a fixed URI that doesn't change.
The easiest way to expose a local file is using the file property. This automatically handles the URI generation, MIME type detection, and file reading.
export default defineMcpResource({
name: 'readme',
description: 'Project README file',
file: 'README.md', // Relative to project root
})
This generates:
file:///path/to/project/README.mdtext/markdown)For more control, you can define the uri and handler manually:
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
export default defineMcpResource({
name: 'custom-readme',
title: 'README',
description: 'Project README file',
uri: 'file:///README.md',
metadata: {
mimeType: 'text/markdown',
},
handler: async (uri: URL) => {
const filePath = fileURLToPath(uri)
const content = await readFile(filePath, 'utf-8')
return {
contents: [{
uri: uri.toString(),
mimeType: 'text/markdown',
text: content,
}],
}
},
})
You can omit name and title - they will be automatically generated from the filename:
export default defineMcpResource({
// name and title are auto-generated from filename:
// name: 'project-readme'
// title: 'Project Readme'
file: 'README.md'
})
The filename project-readme.ts automatically becomes:
name: project-readme (kebab-case)title: Project Readme (title case)You can still provide name or title explicitly to override the auto-generated values.
A resource definition consists of:
export default defineMcpResource({
name: 'resource-name',
file: 'path/to/file.txt', // Local file path
metadata: { ... }
})
export default defineMcpResource({
name: 'resource-name', // Unique identifier
uri: 'uri://...', // Static URI or ResourceTemplate
handler: async (uri) => { // Handler function
return { contents: [...] }
},
})
Use ResourceTemplate to create dynamic resources that accept variables:
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
import type { Variables } from '@modelcontextprotocol/sdk/shared/uriTemplate.js'
export default defineMcpResource({
name: 'file',
title: 'File Resource',
uri: new ResourceTemplate('file:///project/{+path}', {
list: async () => {
// Return list of available resources
return {
resources: [
{ uri: 'file:///project/README.md', name: 'README.md' },
{ uri: 'file:///project/src/index.ts', name: 'src/index.ts' },
],
}
},
}),
handler: async (uri: URL, variables: Variables) => {
const path = variables.path as string
const filePath = join(process.cwd(), path)
const content = await readFile(filePath, 'utf-8')
return {
contents: [{
uri: uri.toString(),
mimeType: 'text/plain',
text: content,
}],
}
},
})
ResourceTemplate allows you to create resources with variable parts in the URI:
new ResourceTemplate('file:///project/{+path}', {
list: async () => {
// Optional: Return list of available resources
return {
resources: [
{ uri: 'file:///project/file1.txt', name: 'File 1' },
{ uri: 'file:///project/file2.txt', name: 'File 2' },
],
}
},
})
Variables in the URI are defined with {variableName}:
// Single variable
new ResourceTemplate('file:///project/{path}', { ... })
// Variable allowing slashes (reserved expansion)
new ResourceTemplate('file:///project/{+path}', { ... })
// Multiple variables
new ResourceTemplate('api://users/{userId}/posts/{postId}', { ... })
The handler receives the resolved URI and optional variables:
// Static resource handler
handler: async (uri: URL) => {
return {
contents: [{
uri: uri.toString(),
mimeType: 'text/plain',
text: 'Content',
}],
}
}
// Dynamic resource handler
handler: async (uri: URL, variables: Variables) => {
const path = variables.path as string
// Use variables to resolve the resource
return {
contents: [{
uri: uri.toString(),
mimeType: 'text/plain',
text: 'Content',
}],
}
}
Add metadata to help clients understand the resource:
export default defineMcpResource({
name: 'readme',
description: 'Project README file',
file: 'README.md',
})
Resources can return different MIME types:
return {
contents: [{
uri: uri.toString(),
mimeType: 'text/markdown',
text: '# Markdown content',
}],
}
return {
contents: [{
uri: uri.toString(),
mimeType: 'application/json',
text: JSON.stringify({ key: 'value' }),
}],
}
return {
contents: [{
uri: uri.toString(),
mimeType: 'image/png',
blob: Buffer.from(binaryData),
}],
}
Handle errors gracefully in your handlers:
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
export default defineMcpResource({
name: 'readme',
uri: 'file:///README.md',
handler: async (uri: URL) => {
try {
const filePath = fileURLToPath(uri)
const content = await readFile(filePath, 'utf-8')
return {
contents: [{
uri: uri.toString(),
mimeType: 'text/markdown',
text: content,
}],
}
}
catch (error) {
return {
contents: [{
uri: uri.toString(),
mimeType: 'text/plain',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
}],
isError: true,
}
}
},
})
Organize your resources in the server/mcp/resources/ directory:
server/
└── mcp/
└── resources/
├── readme.ts
└── file.ts
Each file should export a default resource definition.
You can use any URI scheme that makes sense for your use case:
file:// - File system resourcesapi:// - API endpointshttp:// / https:// - Web resourcescustom:// - Custom schemes