Chapter 5: Resources Management
Haiyue
16min
Chapter 5: Resources Management
Learning Objectives
- Understand the concept and purpose of Resources
- Learn to create and manage various resource types
- Master resource reading, updating, and listening mechanisms
- Implement resource providers for file systems, databases, etc.
- Learn resource permissions and security control
1. Resources Concept Overview
1.1 What are Resources
Resources are a mechanism in the MCP protocol for providing various types of data sources and content to AI models. Unlike Tools, Resources are primarily used for:
- Providing readable data content
- Exposing various information sources to AI models
- Implementing dynamic content access
- Supporting real-time data updates
1.2 Resources vs Tools Differences
| Feature | Resources | Tools |
|---|---|---|
| Purpose | Provide data and content | Execute operations and tasks |
| Access Method | Read-focused | Execution-focused |
| Data Flow | Server → Client | Client → Server → Client |
| Real-time | Supports change listening | On-demand execution |
2. Resource Types and Structure
2.1 Resource Basic Structure
interface Resource {
uri: string; // Resource unique identifier
name: string; // Resource display name
description?: string; // Resource description
mimeType?: string; // MIME type
annotations?: { // Metadata annotations
audience?: ["human" | "assistant"];
priority?: number;
};
}
2.2 Common Resource Types
- File Resources - Local file system content
- Database Resources - Database query results
- API Resources - External API data
- Configuration Resources - Application configuration information
- Log Resources - System log data
3. Implementing Resources Functionality
3.1 Basic Resource Provider
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { z } from "zod";
class ResourceManager {
private server: Server;
private resources: Map<string, any> = new Map();
constructor(server: Server) {
this.server = server;
this.setupResourceHandlers();
}
private setupResourceHandlers() {
// List available resources
this.server.setRequestHandler(
"resources/list",
async () => {
return {
resources: Array.from(this.resources.values())
};
}
);
// Read specific resource
this.server.setRequestHandler(
"resources/read",
async (request) => {
const { uri } = request.params;
return await this.readResource(uri);
}
);
}
async readResource(uri: string) {
const resource = this.resources.get(uri);
if (!resource) {
throw new Error(`Resource not found: ${uri}`);
}
// Read content based on resource type
const content = await this.loadResourceContent(resource);
return {
contents: [{
uri: resource.uri,
mimeType: resource.mimeType,
text: content
}]
};
}
private async loadResourceContent(resource: any): Promise<string> {
// Implement specific resource content loading logic
return "Resource content";
}
}
3.2 File System Resource Provider
import { promises as fs } from "fs";
import path from "path";
class FileSystemResourceProvider {
private basePath: string;
constructor(basePath: string) {
this.basePath = path.resolve(basePath);
}
async listResources(): Promise<Resource[]> {
const files = await this.scanDirectory(this.basePath);
return files.map(file => ({
uri: `file://${file}`,
name: path.basename(file),
description: `File: ${path.relative(this.basePath, file)}`,
mimeType: this.getMimeType(file)
}));
}
async readResource(uri: string): Promise<string> {
const filePath = uri.replace("file://", "");
// Security check: ensure file is within allowed directory
if (!filePath.startsWith(this.basePath)) {
throw new Error("Access denied: file outside allowed directory");
}
try {
const content = await fs.readFile(filePath, "utf-8");
return content;
} catch (error) {
throw new Error(`Failed to read file: ${error.message}`);
}
}
private async scanDirectory(dir: string): Promise<string[]> {
const files: string[] = [];
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...await this.scanDirectory(fullPath));
} else {
files.push(fullPath);
}
}
return files;
}
private getMimeType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
const mimeTypes: Record<string, string> = {
".txt": "text/plain",
".md": "text/markdown",
".json": "application/json",
".js": "text/javascript",
".ts": "text/typescript",
".py": "text/x-python",
".html": "text/html",
".css": "text/css"
};
return mimeTypes[ext] || "text/plain";
}
}
4. Database Resource Provider
4.1 Database Query Resources
import { Database } from "sqlite3";
class DatabaseResourceProvider {
private db: Database;
constructor(dbPath: string) {
this.db = new Database(dbPath);
}
async listResources(): Promise<Resource[]> {
return [
{
uri: "db://users",
name: "Users Table",
description: "All users in the system",
mimeType: "application/json"
},
{
uri: "db://orders",
name: "Orders Table",
description: "Order records",
mimeType: "application/json"
}
];
}
async readResource(uri: string): Promise<string> {
const table = uri.replace("db://", "");
return new Promise((resolve, reject) => {
this.db.all(`SELECT * FROM ${table} LIMIT 100`, (err, rows) => {
if (err) {
reject(new Error(`Database query failed: ${err.message}`));
} else {
resolve(JSON.stringify(rows, null, 2));
}
});
});
}
async executeQuery(query: string): Promise<string> {
return new Promise((resolve, reject) => {
this.db.all(query, (err, rows) => {
if (err) {
reject(new Error(`Query failed: ${err.message}`));
} else {
resolve(JSON.stringify(rows, null, 2));
}
});
});
}
}
5. Resource Listening and Change Notifications
5.1 Resource Change Listening
import { EventEmitter } from "events";
import { watch } from "chokidar";
class ResourceWatcher extends EventEmitter {
private watchers: Map<string, any> = new Map();
watchFileResource(uri: string, filePath: string) {
const watcher = watch(filePath);
watcher.on("change", () => {
this.emit("resourceChanged", {
uri,
changeType: "modified"
});
});
watcher.on("unlink", () => {
this.emit("resourceChanged", {
uri,
changeType: "deleted"
});
});
this.watchers.set(uri, watcher);
}
unwatchResource(uri: string) {
const watcher = this.watchers.get(uri);
if (watcher) {
watcher.close();
this.watchers.delete(uri);
}
}
stopAllWatchers() {
for (const watcher of this.watchers.values()) {
watcher.close();
}
this.watchers.clear();
}
}
5.2 Notify Client of Resource Changes
class ManagedResourceProvider {
private server: Server;
private watcher: ResourceWatcher;
constructor(server: Server) {
this.server = server;
this.watcher = new ResourceWatcher();
this.setupChangeNotifications();
}
private setupChangeNotifications() {
this.watcher.on("resourceChanged", (change) => {
// Notify client of resource changes
this.server.notification({
method: "notifications/resources/updated",
params: {
uri: change.uri,
changeType: change.changeType
}
});
});
}
addFileResource(uri: string, filePath: string) {
// Add file resource and start watching
this.watcher.watchFileResource(uri, filePath);
}
}
6. Resource Permissions and Security Control
6.1 Access Permission Control
interface ResourcePermission {
uri: string;
allowedOperations: ("read" | "write" | "delete")[];
accessLevel: "public" | "protected" | "private";
}
class ResourceSecurityManager {
private permissions: Map<string, ResourcePermission> = new Map();
setResourcePermission(permission: ResourcePermission) {
this.permissions.set(permission.uri, permission);
}
checkReadAccess(uri: string, userContext?: any): boolean {
const permission = this.permissions.get(uri);
if (!permission) {
return false; // Default deny access
}
// Check read permission
if (!permission.allowedOperations.includes("read")) {
return false;
}
// Perform permission checks based on access level
switch (permission.accessLevel) {
case "public":
return true;
case "protected":
return this.validateUserAccess(userContext);
case "private":
return this.validateAdminAccess(userContext);
default:
return false;
}
}
private validateUserAccess(userContext?: any): boolean {
// Implement user permission validation logic
return userContext && userContext.authenticated;
}
private validateAdminAccess(userContext?: any): boolean {
// Implement admin permission validation logic
return userContext && userContext.role === "admin";
}
}
6.2 Input Validation and Sanitization
class ResourceValidator {
static validateUri(uri: string): boolean {
// URI format validation
try {
new URL(uri);
return true;
} catch {
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(uri);
}
}
static sanitizeContent(content: string, mimeType: string): string {
switch (mimeType) {
case "text/html":
return this.sanitizeHtml(content);
case "application/json":
return this.sanitizeJson(content);
default:
return content;
}
}
private static sanitizeHtml(html: string): string {
// Remove potentially malicious HTML content
return html
.replace(/<script[^>]*>.*?<\/script>/gi, "")
.replace(/on\w+="[^"]*"/gi, "")
.replace(/javascript:/gi, "");
}
private static sanitizeJson(json: string): string {
try {
// Validate JSON format and re-serialize
const parsed = JSON.parse(json);
return JSON.stringify(parsed, null, 2);
} catch {
throw new Error("Invalid JSON content");
}
}
}
7. Complete Resource Server Example
7.1 Comprehensive Resource Server
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
class ComprehensiveResourceServer {
private server: Server;
private fileProvider: FileSystemResourceProvider;
private dbProvider: DatabaseResourceProvider;
private securityManager: ResourceSecurityManager;
private watcher: ResourceWatcher;
constructor() {
this.server = new Server(
{
name: "comprehensive-resource-server",
version: "1.0.0"
},
{
capabilities: {
resources: {
subscribe: true, // Support resource change subscriptions
listChanged: true // Support resource list changes
}
}
}
);
this.fileProvider = new FileSystemResourceProvider("./data");
this.dbProvider = new DatabaseResourceProvider("./db.sqlite");
this.securityManager = new ResourceSecurityManager();
this.watcher = new ResourceWatcher();
this.setupHandlers();
}
private setupHandlers() {
// Resource list
this.server.setRequestHandler("resources/list", async () => {
const fileResources = await this.fileProvider.listResources();
const dbResources = await this.dbProvider.listResources();
return {
resources: [...fileResources, ...dbResources]
};
});
// Read resource
this.server.setRequestHandler("resources/read", async (request) => {
const { uri } = request.params;
// Permission check
if (!this.securityManager.checkReadAccess(uri)) {
throw new Error("Access denied");
}
// Route to appropriate provider
let content: string;
if (uri.startsWith("file://")) {
content = await this.fileProvider.readResource(uri);
} else if (uri.startsWith("db://")) {
content = await this.dbProvider.readResource(uri);
} else {
throw new Error("Unsupported resource type");
}
return {
contents: [{
uri,
mimeType: "text/plain",
text: content
}]
};
});
// Resource subscription
this.server.setRequestHandler("resources/subscribe", async (request) => {
const { uri } = request.params;
if (uri.startsWith("file://")) {
const filePath = uri.replace("file://", "");
this.watcher.watchFileResource(uri, filePath);
}
return {};
});
// Unsubscribe
this.server.setRequestHandler("resources/unsubscribe", async (request) => {
const { uri } = request.params;
this.watcher.unwatchResource(uri);
return {};
});
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Comprehensive Resource Server running on stdio");
}
}
// Start server
const server = new ComprehensiveResourceServer();
server.start().catch(console.error);
8. Best Practices
8.1 Resource Design Principles
- URI Uniqueness - Ensure each resource has a unique URI
- Descriptive Naming - Use clear, descriptive resource names
- Appropriate MIME Types - Correctly set resource MIME types
- Least Privilege - Provide only necessary access permissions
- Error Handling - Gracefully handle resource access errors
8.2 Performance Optimization
- Caching Mechanism - Implement caching for frequently accessed resources
- Lazy Loading - Load resource content only when needed
- Pagination Support - Implement pagination for large resources
- Compression - Enable compression for large content
8.3 Security Considerations
- Path Traversal Protection - Prevent ../ path traversal attacks
- Content Filtering - Filter sensitive information and malicious content
- Access Logging - Log resource access activities
- Rate Limiting - Prevent resource abuse
Summary
Through this chapter, we have mastered:
- Basic concepts and architecture of Resources
- How to implement file system and database resource providers
- Resource listening and change notification mechanisms
- Permission control and security protection
- Complete Resource Server implementation
Resources provide powerful data access capabilities for MCP Server, enabling AI models to access various types of information sources, serving as an important foundation for building intelligent applications.