Skip to main content

Implementing Resource Providers

Learn how to expose data through MCP resources, handle different data types, and build dynamic resource systems

Understanding MCP Resources

Resources are the foundation of data access in MCP. They represent any data that an AI application might need to read - from simple configuration files to complex database queries. Think of resources as a universal API for exposing information.

What Makes a Good Resource?

A well-designed resource should be:

  • Discoverable: Easy to find through resource listings
  • Self-describing: Clear name and description
  • Consistent: Predictable URI scheme and format
  • Secure: Access-controlled and validated

<CodeExample title="Resource Structure" language="typescript" code={`interface Resource { uri: string; // Unique identifier (e.g., "file:///path/to/file") name: string; // Human-readable name description?: string; // Optional description mimeType?: string; // Content type (default: text/plain) }

interface ResourceContent { uri: string; mimeType: string; text?: string; // Text content blob?: string; // Base64-encoded binary }`} highlightLines={[2, 9, 10, 11]} />

Resource Lifecycle

Understanding the resource lifecycle helps you build robust providers:

  1. Discovery: Client requests resources/list
  2. Selection: AI chooses relevant resources
  3. Access: Client requests resources/read with specific URI
  4. Processing: AI analyzes the content
  5. Updates: Optional notifications when resources change
💡

Best Practice: Design your URI scheme to be hierarchical and meaningful. Use prefixes like config://, db://, or file:// to categorize resources.

Resource Structure

The anatomy of an MCP resource

Creating Static Resources

Static resources are the simplest type - they return fixed content that doesn't change often. They're perfect for configuration, metadata, and system information.

Building Your First Resource Provider

<CodeExample title="Static Resource Implementation" language="typescript" code={`// Define static resources const staticResources = [ { uri: 'config://app/settings', name: 'Application Settings', description: 'Current application configuration', mimeType: 'application/json' }, { uri: 'config://app/version', name: 'Version Information', description: 'Application version and build info', mimeType: 'application/json' } ];

// Implement list handler server.setRequestHandler('resources/list', async () => { return { resources: staticResources }; });

// Implement read handler server.setRequestHandler('resources/read', async (request) => { const { uri } = request.params;

switch (uri) { case 'config://app/settings': return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify({ theme: 'dark', language: 'en', debugMode: false }, null, 2) }] };

case 'config://app/version':
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify({
        version: '1.0.0',
        buildDate: new Date().toISOString(),
        nodeVersion: process.version
      }, null, 2)
    }]
  };
  
default:
  throw new Error(\`Resource not found: \${uri}\`);

} });`} />

Organizing Static Resources

As your application grows, organize static resources logically:

Pro Tip: Use consistent naming patterns. For example, prefix all configuration with config:// and all system info with system://.

Static Resource Implementation

Expose configuration and metadata as resources

Building Dynamic Resource Providers

Module content not available.

Advanced Resource Patterns

As your MCP servers grow more sophisticated, you'll need advanced patterns to handle large datasets and complex requirements.

Pagination for Large Resource Lists

When you have many resources, pagination prevents overwhelming clients:

<CodeExample title="Paginated Resource Lists" language="typescript" code={`interface PaginatedListRequest { params: { cursor?: string; limit?: number; }; }

server.setRequestHandler('resources/list', async (request: PaginatedListRequest) => { const { cursor, limit = 100 } = request.params;

// Get all resources (in practice, from database with LIMIT/OFFSET) const allResources = await getAllResources();

// Find starting position const startIndex = cursor ? allResources.findIndex(r => r.uri === cursor) + 1 : 0;

// Get page of resources const pageResources = allResources.slice(startIndex, startIndex + limit);

// Prepare response with nextCursor const response = { resources: pageResources };

// Add next cursor if there are more resources if (startIndex + limit < allResources.length) { response.nextCursor = pageResources[pageResources.length - 1].uri; }

return response; });`} highlightLines={[8, 9, 15, 16, 19, 26, 27, 28]} />

Resource Filtering

Allow clients to filter resources by type, pattern, or attributes:

<CodeExample title="Filtered Resource Lists" language="typescript" code={`interface FilteredListRequest { params: { filter?: { mimeType?: string; uriPattern?: string; tags?: string[]; }; }; }

server.setRequestHandler('resources/list', async (request: FilteredListRequest) => { const { filter } = request.params; let resources = await getAllResources();

if (filter) { // Filter by MIME type if (filter.mimeType) { resources = resources.filter(r => r.mimeType === filter.mimeType); }

// Filter by URI pattern
if (filter.uriPattern) {
  const pattern = new RegExp(filter.uriPattern);
  resources = resources.filter(r => pattern.test(r.uri));
}

// Filter by tags (if your resources have tags)
if (filter.tags && filter.tags.length > 0) {
  resources = resources.filter(r => 
    r.tags && filter.tags.some(tag => r.tags.includes(tag))
  );
}

}

return { resources }; });`} />

Resource Caching

For expensive operations, implement intelligent caching:

<CodeExample title="Resource Caching Strategy" language="typescript" code={`class CachedResourceProvider { private cache = new Map<string, { content: any; timestamp: number }>(); private cacheTimeout = 5 * 60 * 1000; // 5 minutes

async readResource(uri: string) { // Check cache first const cached = this.cache.get(uri); if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { return cached.content; }

// Generate fresh content
const content = await this.generateContent(uri);

// Cache the result
this.cache.set(uri, {
  content,
  timestamp: Date.now()
});

return content;

}

// Invalidate cache when resources change invalidateCache(uri?: string) { if (uri) { this.cache.delete(uri); } else { this.cache.clear(); } } }`} />

Paginated Resource Lists

Handle large resource collections with pagination

Filtered Resource Lists

Support filtering resources by type or attributes

Implementing Resource Subscriptions

For real-time applications, implement resource subscriptions to notify clients when data changes.

<CodeExample title="Resource Update Notifications" language="typescript" code={`import { EventEmitter } from 'events';

class ResourceManager extends EventEmitter { private resources = new Map<string, Resource>();

updateResource(uri: string, updates: Partial<Resource>) { const existing = this.resources.get(uri); if (!existing) throw new Error('Resource not found');

const updated = { ...existing, ...updates };
this.resources.set(uri, updated);

// Emit update event
this.emit('resource:updated', {
  uri,
  resource: updated
});

}

deleteResource(uri: string) { if (!this.resources.delete(uri)) { throw new Error('Resource not found'); }

// Emit delete event
this.emit('resource:deleted', { uri });

} }

// In your server setup const resourceManager = new ResourceManager();

// Subscribe to changes resourceManager.on('resource:updated', ({ uri, resource }) => { // Send notification to connected clients server.sendNotification('resources/updated', { uri, resource }); });

resourceManager.on('resource:deleted', ({ uri }) => { server.sendNotification('resources/deleted', { uri }); });`} />

File System Watching

Monitor file system changes for automatic updates:

<CodeExample title="File System Watching" language="typescript" code={`import { watch } from 'fs';

class WatchedFileProvider { private watchers = new Map<string, any>();

constructor(private basePath: string, private server: Server) { this.setupWatcher(); }

private setupWatcher() { const watcher = watch(this.basePath, { recursive: true }, (eventType, filename) => { if (!filename) return;

  const fullPath = path.join(this.basePath, filename);
  const uri = \`file://\${fullPath}\`;
  
  if (eventType === 'change') {
    // File modified
    this.server.sendNotification('resources/updated', { uri });
  } else if (eventType === 'rename') {
    // File created or deleted
    this.checkFileExists(fullPath).then(exists => {
      if (exists) {
        this.server.sendNotification('resources/created', { uri });
      } else {
        this.server.sendNotification('resources/deleted', { uri });
      }
    });
  }
});

this.watchers.set(this.basePath, watcher);

}

private async checkFileExists(path: string): Promise<boolean> { try { await fs.access(path); return true; } catch { return false; } }

cleanup() { this.watchers.forEach(watcher => watcher.close()); this.watchers.clear(); } }`} />

Resource Update Notifications

Notify clients when resources change

Resource Implementation Quiz

Check your understanding of MCP resource providers

1. What is the primary purpose of MCP resources?

  • A)To execute actions and modify data
  • B)To provide read-only access to data
  • C)To manage server configuration
  • D)To handle authentication

2. Which fields are required for a resource?

  • A)uri
  • B)name
  • C)description
  • D)mimeType

3. Resources can only return text content, not binary data.

True or False question

Show Answer

Correct Answer: A

False! Resources can return binary data using the 'blob' field with base64 encoding.

4. Complete the resource read handler to return JSON content: