Resources API Reference

Complete reference for MCP Gateway Resources API.

Overview

Resources represent data that can be read by AI assistants. Common use cases:

  • File contents
  • Database records
  • API responses
  • Live metrics
  • Configuration data

Quick Reference

Method Description
resources/list List all available resources
resources/read Read a specific resource
resources/subscribe Subscribe to resource updates (v1.8.0)
resources/unsubscribe Unsubscribe from updates (v1.8.0)

resources/list

List all resources available on the server.

Request

{
  "jsonrpc": "2.0",
  "method": "resources/list",
  "params": {
    "cursor": "optional-cursor"
  },
  "id": 1
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "resources": [
      {
        "uri": "file://data/users.json",
        "name": "User Data",
        "description": "User records in JSON format",
        "mimeType": "application/json",
        "icons": [{"src": "icon.png", "mimeType": "image/png", "theme": "light"}]
      }
    ],
    "nextCursor": "optional-cursor"
  },
  "id": 1
}

resources/read

Read a specific resource by URI.

Request

{
  "jsonrpc": "2.0",
  "method": "resources/read",
  "params": {
    "uri": "file://data/users.json"
  },
  "id": 2
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "contents": [
      {
        "uri": "file://data/users.json",
        "mimeType": "application/json",
        "text": "[{\"id\":1,\"name\":\"Alice\"}]"
      }
    ]
  },
  "id": 2
}

resources/subscribe (v1.8.0)

Subscribe to notifications when a resource changes.

Request

{
  "jsonrpc": "2.0",
  "method": "resources/subscribe",
  "params": {
    "uri": "file://data/users.json"
  },
  "id": 3
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "subscribed": true,
    "uri": "file://data/users.json"
  },
  "id": 3
}

Requirements:

  • Session management must be enabled
  • Resource must exist (validated before subscription)
  • Exact URI matching only (v1.8.0)

resources/unsubscribe (v1.8.0)

Unsubscribe from resource update notifications.

Request

{
  "jsonrpc": "2.0",
  "method": "resources/unsubscribe",
  "params": {
    "uri": "file://data/users.json"
  },
  "id": 4
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "unsubscribed": true,
    "uri": "file://data/users.json"
  },
  "id": 4
}

Note: Idempotent - safe to call multiple times.

Defining Resources

File Resource

using Mcp.Gateway.Tools;

public class FileResources
{
    [McpResource("file://data/users.json",
        Name = "User Data",
        Description = "User records in JSON format",
        MimeType = "application/json")]
    [McpIcon("icon.png")]
    [McpIcon("icon2.png", "image/png", Sizes = new[] { "16x16", "32x32", "48x48", "any" })]
    [McpIcon("icon-light.png", "image/png", McpIconTheme.Light)]
    [McpIcon("icon-dark.png", "image/png", McpIconTheme.Dark)]
    public JsonRpcMessage GetUsers(JsonRpcMessage request)
    {
        var data = File.ReadAllText("data/users.json");
        
        var content = new ResourceContent(
            Uri: "file://data/users.json",
            MimeType: "application/json",
            Text: data);

        return ToolResponse.Success(
            request.Id,
            new ReadResourceResult
            {
                Contents = [content]
            });
    }
}

Database Resource

[McpResource("db://users",
    Name = "User Database",
    Description = "All users from database",
    MimeType = "application/json")]
public async Task<JsonRpcMessage> GetUsersFromDb(JsonRpcMessage request)
{
    var users = await _dbContext.Users.ToListAsync();
    var json = JsonSerializer.Serialize(users);
    
    var content = new ResourceContent(
        Uri: "db://users",
        MimeType: "application/json",
        Text: json);

    return ToolResponse.Success(
        request.Id,
        new ReadResourceResult
        {
            Contents = [content]
        });
}

Live Metrics Resource

[McpResource("system://metrics",
    Name = "System Metrics",
    Description = "Live system metrics",
    MimeType: "application/json")]
public JsonRpcMessage GetMetrics(JsonRpcMessage request)
{
    var metrics = new
    {
        cpu = GetCpuUsage(),
        memory = GetMemoryUsage(),
        timestamp = DateTime.UtcNow
    };
    
    var content = new ResourceContent(
        Uri: "system://metrics",
        MimeType: "application/json",
        Text: JsonSerializer.Serialize(metrics));

    return ToolResponse.Success(
        request.Id,
        new ReadResourceResult
        {
            Contents = [content]
        });
}

Resource Attributes

[McpResource]

Marks a method as an MCP resource.

[McpResource(
    string uri,                     // Required: Resource URI
    string? Name = null,            // Optional: Display name
    string? Description = null,     // Optional: Description
    string? MimeType = null)]       // Optional: Content type

Sending Notifications

When a resource changes, notify subscribed clients using dependency injection:

Option 1: Constructor Injection

Requires class registration in DI:

using Mcp.Gateway.Tools.Notifications;

public class FileResources
{
    private readonly INotificationSender _notificationSender;
    
    public FileResources(INotificationSender notificationSender)
    {
        _notificationSender = notificationSender;
    }
    
    [McpTool("update_users")]
    public async Task<JsonRpcMessage> UpdateUsers(
        TypedJsonRpc<UpdateUsersArgs> request)
    {
        var args = request.GetParams()!;
        
        // Update the file
        await File.WriteAllTextAsync("data/users.json", args.Data);
        
        // Notify subscribed sessions (v1.8.0)
        await _notificationSender.SendNotificationAsync(
            NotificationMessage.ResourcesUpdated("file://data/users.json"));
        
        return ToolResponse.Success(request.Id, new { updated = true });
    }
}

// Register in DI:
builder.Services.AddScoped<FileResources>();

Option 2: Method Parameter Injection

No class registration needed - parameters resolved from DI:

using Mcp.Gateway.Tools.Notifications;

public class FileResources
{
    [McpTool("update_users")]
    public async Task<JsonRpcMessage> UpdateUsers(
        TypedJsonRpc<UpdateUsersArgs> request,
        INotificationSender notificationSender)  // ← Automatically injected!
    {
        var args = request.GetParams()!;
        
        // Update the file
        await File.WriteAllTextAsync("data/users.json", args.Data);
        
        // Notify subscribed sessions (v1.8.0)
        await notificationSender.SendNotificationAsync(
            NotificationMessage.ResourcesUpdated("file://data/users.json"));
        
        return ToolResponse.Success(request.Id, new { updated = true });
    }
}

// No registration needed - class auto-discovered!

Parameter resolution order:

  1. JsonRpcMessage or TypedJsonRpc<T> - The request (must be first parameter)
  2. Additional parameters - Resolved from DI container (in order)

Benefits of method parameter injection:

  • ✅ No class registration needed
  • ✅ Simpler for resources with few dependencies
  • ✅ Clear what each method needs
  • ✅ Easier testing (mock parameters directly)

URI Schemes

Common URI schemes:

Scheme Example Use Case
file:// file://data/config.json File system
db:// db://users Database records
http:// http://api.example.com/data HTTP endpoints
system:// system://metrics System information
custom:// custom://my-resource Custom schemes

MIME Types

Common MIME types:

Type Description
application/json JSON data
text/plain Plain text
text/markdown Markdown
text/html HTML
application/xml XML

Best Practices

1. Use Descriptive URIs

// ✅ GOOD
[McpResource("file://logs/app-2025-12-20.log")]

// ❌ BAD
[McpResource("file://log1")]

2. Specify MIME Type

[McpResource("file://data/users.json",
    MimeType = "application/json")]

3. Handle Missing Resources

[McpResource("file://data/users.json")]
public JsonRpcMessage GetUsers(JsonRpcMessage request)
{
    if (!File.Exists("data/users.json"))
    {
        throw new ToolInvalidParamsException(
            "Resource not found: users.json");
    }
    
    var data = File.ReadAllText("data/users.json");
    return ToolResponse.Success(...);
}

4. Cache When Appropriate

private readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());

[McpResource("db://users")]
public async Task<JsonRpcMessage> GetUsers(JsonRpcMessage request)
{
    var cacheKey = "users";
    
    if (!_cache.TryGetValue(cacheKey, out string? data))
    {
        var users = await _dbContext.Users.ToListAsync();
        data = JsonSerializer.Serialize(users);
        
        _cache.Set(cacheKey, data, TimeSpan.FromMinutes(5));
    }
    
    return ToolResponse.Success(...);
}

Resource Subscriptions (v1.8.0)

Server-Side

Resources support subscriptions automatically when:

  1. Session management is enabled
  2. Resource is registered via [McpResource]
  3. Server sends notifications on changes
// Resource definition
[McpResource("file://data/users.json")]
public JsonRpcMessage GetUsers(JsonRpcMessage request) { ... }

// When resource changes
await _notificationSender.SendNotificationAsync(
    NotificationMessage.ResourcesUpdated("file://data/users.json"));
// Only subscribed sessions receive notification!

Client-Side

// 1. Subscribe
await fetch('/mcp', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'MCP-Session-Id': sessionId
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'resources/subscribe',
    params: { uri: 'file://data/users.json' },
    id: 1
  })
});

// 2. Open SSE stream
const eventSource = new EventSource('/mcp', {
  headers: { 'MCP-Session-Id': sessionId }
});

eventSource.addEventListener('message', (event) => {
  const notification = JSON.parse(event.data);
  if (notification.method === 'notifications/resources/updated') {
    // Re-fetch the resource
    fetchResource(notification.params.uri);
  }
});

// 3. Unsubscribe
await fetch('/mcp', {
  method: 'POST',
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'resources/unsubscribe',
    params: { uri: 'file://data/users.json' },
    id: 2
  })
});

Dynamic Resources (v1.8.5)

Support for registering and unregistering resources at runtime without using attributes. This is useful for scenarios where resources are not known at compile time, such as dynamic file lists or user-specific data.

Registering a Resource

Use the RegisterResource method on ToolService (available via dependency injection).

[McpTool("add-dynamic-resource")]
public async Task<JsonRpcMessage> AddResource(
    JsonRpcMessage request, 
    ToolService toolService)
{
    // Define the resource handler
    Func<JsonRpcMessage, Task<JsonRpcMessage>> handler = async (req) =>
    {
        var content = new ResourceContent(
            Uri: "dynamic://data/1",
            MimeType: "application/json",
            Text: "{\"value\": 42}"
        );

        var result = new ReadResourceResult
        {
            Contents = [content]
        }

        return ToolResponse.Success(req.Id, result);
    };

    // Register dynamically
    toolService.RegisterResource(
        uri: "dynamic://data/1",
        handler: handler,
        name: "Dynamic Data",
        description: "A resource created at runtime",
        mimeType: "application/json"
    );

    return ToolResponse.Success(request.Id, new { added = true });
}

Unregistering a Resource

Use the UnregisterResource method to remove a resource at runtime.

[McpTool("remove-dynamic-resource")]
public JsonRpcMessage RemoveResource(
    JsonRpcMessage request, 
    ToolService toolService)
{
    // Remove the resource
    toolService.UnregisterResource("dynamic://data/1");

    return ToolResponse.Success(request.Id, new { removed = true });
}

Key Features

  • Runtime Metadata: Define Name, Description, and MimeType programmatically without [McpResource].
  • Seamless Integration: Dynamically registered resources appear in resources/list and work with resources/read just like static ones.
  • Thread Safety: Registration and unregistration are thread-safe and can be called concurrently.

Testing

Unit Test

[Fact]
public void GetUsers_FileExists_ReturnsContent()
{
    // Arrange
    var resources = new FileResources();
    var request = JsonRpcMessage.CreateRequest("resources/read", "1");
    
    // Act
    var response = resources.GetUsers(request);
    
    // Assert
    Assert.NotNull(response.Result);
}

Integration Test

[Fact]
public async Task ResourcesRead_ValidUri_ReturnsContent()
{
    // Arrange
    using var server = new McpGatewayFixture();
    var client = server.CreateClient();
    
    var request = new
    {
        jsonrpc = "2.0",
        method = "resources/read",
        @params = new { uri = "file://data/users.json" },
        id = 1
    };
    
    // Act
    var response = await client.PostAsJsonAsync("/mcp", request);
    var result = await response.Content.ReadFromJsonAsync<JsonDocument>();
    
    // Assert
    Assert.True(result.RootElement.TryGetProperty("result", out var resultProp));
    Assert.True(resultProp.TryGetProperty("contents", out _));
}

See Also