Skip to main content
Resources in WebMCP are data endpoints that AI agents can read. They expose dynamic or static content like configuration, files, or API data to AI models.
Resources are part of the Model Context Protocol (MCP) specification. WebMCP implements resources for browser environments, enabling web applications to expose data to AI agents.

Overview

ConceptDescription
PurposeExpose readable data to AI agents
URIUnique identifier (static or template with parameters)
MIME TypeContent type hint for parsing
OutputArray of content objects with text or binary data

When to Use Resources

Configuration Data

Expose application settings and preferences

File Systems

Provide access to virtual or real file content

Dynamic State

Share current application state with AI

External Data

Proxy API responses and external content

Static Resources

Register a resource with a fixed URI:
// Register a configuration resource
navigator.modelContext.registerResource({
  uri: 'config://app-settings',
  name: 'App Settings',
  description: 'Current application configuration',
  mimeType: 'application/json',
  async read() {
    const settings = {
      theme: 'dark',
      language: 'en',
      notifications: true,
    };

    return {
      contents: [{
        uri: 'config://app-settings',
        text: JSON.stringify(settings, null, 2),
        mimeType: 'application/json',
      }],
    };
  },
});

Template Resources

Use URI templates with {param} placeholders for dynamic access:
// Register a file reader with URI template
navigator.modelContext.registerResource({
  uri: 'file://{path}',
  name: 'File Reader',
  description: 'Read files from the virtual filesystem',
  mimeType: 'text/plain',
  async read(uri, params) {
    // params.path contains the extracted parameter
    const path = params?.path || 'unknown';

    const content = await fetchFileContent(path);

    return {
      contents: [{
        uri: uri.href,
        text: content,
        mimeType: getMimeType(path),
      }],
    };
  },
});

// AI can now read any file:
// readResource('file://readme.txt')  -> path = "readme.txt"
// readResource('file://config.json') -> path = "config.json"

Multiple Parameters

Templates can have multiple parameters:
navigator.modelContext.registerResource({
  uri: 'api://users/{userId}/posts/{postId}',
  name: 'User Post',
  description: 'Fetch a specific post by a user',
  mimeType: 'application/json',
  async read(uri, params) {
    const { userId, postId } = params || {};

    const post = await fetch(`/api/users/${userId}/posts/${postId}`);
    const data = await post.json();

    return {
      contents: [{
        uri: uri.href,
        text: JSON.stringify(data, null, 2),
        mimeType: 'application/json',
      }],
    };
  },
});

How AI Agents Read Resources

When an AI agent wants to read a resource, it calls readResource() through the MCP protocol. This invokes your registered read() handler:
// AI agent calls readResource('config://app-settings')
// Your handler is invoked and returns the content

navigator.modelContext.registerResource({
  uri: 'config://app-settings',
  name: 'App Settings',
  async read() {
    // This handler is called when AI reads the resource
    console.log('AI reading app settings');
    return {
      contents: [{
        uri: 'config://app-settings',
        text: JSON.stringify(settings),
        mimeType: 'application/json',
      }],
    };
  },
});
The readResource() method is called by AI agents through the MCP protocol, not directly from your application code. Your read() handler is invoked automatically when an agent requests the resource.

Listing Resources

// List static resources
const resources = navigator.modelContext.listResources();
resources.forEach(r => {
  console.log(`${r.uri}: ${r.name}`);
});

// List resource templates
const templates = navigator.modelContext.listResourceTemplates();
templates.forEach(t => {
  console.log(`${t.uriTemplate}: ${t.name}`);
});

Multiple Contents

Resources can return multiple content items:
navigator.modelContext.registerResource({
  uri: 'bundle://app-data',
  name: 'App Data Bundle',
  description: 'Multiple related data items',
  async read() {
    return {
      contents: [
        {
          uri: 'bundle://app-data#config',
          text: JSON.stringify(config),
          mimeType: 'application/json',
        },
        {
          uri: 'bundle://app-data#user',
          text: JSON.stringify(user),
          mimeType: 'application/json',
        },
        {
          uri: 'bundle://app-data#preferences',
          text: JSON.stringify(preferences),
          mimeType: 'application/json',
        },
      ],
    };
  },
});

Binary Content

Resources can return binary content using base64 encoding:
navigator.modelContext.registerResource({
  uri: 'image://{name}',
  name: 'Image Resource',
  description: 'Read images as base64',
  mimeType: 'image/png',
  async read(uri, params) {
    const name = params?.name;
    const imageBlob = await fetchImage(name);
    const base64 = await blobToBase64(imageBlob);

    return {
      contents: [{
        uri: uri.href,
        blob: base64,  // Base64-encoded binary data
        mimeType: 'image/png',
      }],
    };
  },
});

Dynamic vs Static Registration

Use registerResource() for resources that may change at runtime:
// Register dynamically
const registration = navigator.modelContext.registerResource({
  uri: 'session://current-data',
  // ...
});

// Unregister when no longer needed
registration.unregister();
Use for:
  • Session-specific data
  • Component-scoped resources
  • Temporary data access

URI Scheme Conventions

SchemePurposeExample
config://Configuration dataconfig://app-settings
file://File system accessfile://{path}
user://User-related datauser://{id}/profile
api://External API dataapi://weather/{city}
db://Database recordsdb://products/{sku}
session://Session statesession://current
cache://Cached datacache://recent-queries

Best Practices

Choose URI schemes that indicate the data type:
// Good - clear purpose
'config://theme-settings'
'user://current/preferences'

// Avoid - unclear
'data://123'
'resource://get'
Help AI agents parse content correctly:
mimeType: 'application/json'  // JSON data
mimeType: 'text/plain'        // Plain text
mimeType: 'text/markdown'     // Markdown
mimeType: 'text/csv'          // CSV data
Return helpful error information:
async read(uri, params) {
  const path = params?.path;

  if (!fileExists(path)) {
    return {
      contents: [{
        uri: uri.href,
        text: `Error: File not found: ${path}`,
        mimeType: 'text/plain',
      }],
    };
  }

  // ... normal read
}
Return only relevant data. Let AI agents request specific resources rather than dumping everything.
Use clear descriptions so AI agents know what resources are available and what they contain.

Common Patterns

Configuration Resources

navigator.modelContext.registerResource({
  uri: 'config://feature-flags',
  name: 'Feature Flags',
  description: 'Current feature flag states',
  mimeType: 'application/json',
  async read() {
    return {
      contents: [{
        uri: 'config://feature-flags',
        text: JSON.stringify({
          darkMode: true,
          newEditor: false,
          betaFeatures: ['ai-assist', 'live-collab'],
        }),
        mimeType: 'application/json',
      }],
    };
  },
});

Database Records

navigator.modelContext.registerResource({
  uri: 'db://products/{sku}',
  name: 'Product Details',
  description: 'Fetch product information by SKU',
  mimeType: 'application/json',
  async read(uri, params) {
    const product = await db.products.findBySku(params?.sku);

    return {
      contents: [{
        uri: uri.href,
        text: JSON.stringify(product),
        mimeType: 'application/json',
      }],
    };
  },
});

Real-Time State

navigator.modelContext.registerResource({
  uri: 'state://editor-content',
  name: 'Editor Content',
  description: 'Current content in the editor',
  mimeType: 'text/plain',
  async read() {
    const content = editorRef.current?.getValue() || '';

    return {
      contents: [{
        uri: 'state://editor-content',
        text: content,
        mimeType: 'text/plain',
      }],
    };
  },
});