Resources are a standardized way for MCP servers to expose read-only data to clients. They provide contextual information that can help AI models understand your application, such as files, database schemas, configuration, or any data accessible via a URI.
Each resource is uniquely identified by a URI (e.g., file:///project/README.md or api://users/123).
Understanding the difference between resources and tools is essential:
| Aspect | Resources | Tools |
|---|---|---|
| Purpose | Provide context and data | Perform actions |
| Invocation | Application-driven (user or app selects) | AI-driven (model decides to call) |
| Nature | Read-only data access | Can read and modify state |
| Control | User/application controls what's included | AI decides when to use |
| Examples | Files, configs, DB schemas, logs | Send email, create file, query API |
When to use resources:
When to use tools:
Resources follow an application-driven model. Here's the typical flow:
sequenceDiagram
participant App as Host Application
participant Server as MCP Server
participant AI as AI Model
App->>Server: resources/list
Server-->>App: Available resources
Note over App: Display in UI (tree, search...)
App->>App: User selects resources
App->>Server: resources/read (selected URIs)
Server-->>App: Resource contents
App->>AI: Include content as context
resources/list to discover available resourcesresources/readStatic 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