Resource Server Example

Version: v1.5.0+
Features: Resources, Subscriptions, Notifications
Complexity: Intermediate

Overview

A complete MCP server demonstrating resource handling with:

  • File resources - Application logs and configuration
  • System resources - Live metrics and status
  • Data resources - User data with subscriptions
  • Real-time notifications - Push updates to subscribers

Perfect for learning:

  • How to define resources with [McpResource]
  • Resource content types (text, JSON, blob)
  • Resource subscriptions for targeted notifications
  • Integration with file system, databases, APIs

Quick Start

Run the Server

cd Examples/ResourceMcpServer
dotnet run

Server starts at: http://localhost:5000

Test Resources

# List all resources
curl -X POST http://localhost:5000/mcp \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{
    "jsonrpc": "2.0",
    "method": "resources/list",
    "id": 1
  }'

# Read application logs
curl -X POST http://localhost:5000/mcp \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{
    "jsonrpc": "2.0",
    "method": "resources/read",
    "params": {
      "uri": "file://logs/app.log"
    },
    "id": 2
  }'

Available Resources

File Resources

1. Application Logs (file://logs/app.log)

Returns recent log entries:

{
  "uri": "file://logs/app.log",
  "mimeType": "text/plain",
  "text": "[2025-12-20 18:00:00] INFO: Application started\n[2025-12-20 18:01:00] DEBUG: Initializing..."
}

Use case: Monitor application activity, debug issues

2. Application Settings (file://config/settings.json)

Returns current configuration:

{
  "uri": "file://config/settings.json",
  "mimeType": "application/json",
  "text": "{\"environment\":\"Development\",\"logging\":{...}}"
}

Use case: Inspect configuration, verify settings

System Resources

3. System Metrics (system://metrics)

Returns live system metrics:

{
  "uri": "system://metrics",
  "mimeType": "application/json",
  "text": "{\"cpu\":45.2,\"memory\":2048,\"uptime\":\"02:30:15\"}"
}

Use case: Monitor performance, health checks

4. System Status (system://status)

Returns application health:

{
  "uri": "system://status",
  "mimeType": "application/json",
  "text": "{\"status\":\"healthy\",\"version\":\"1.8.0\",\"ready\":true}"
}

Use case: Health monitoring, readiness checks

Data Resources

5. User Data (data://users)

Returns user records (with subscriptions support):

{
  "uri": "data://users",
  "mimeType": "application/json",
  "text": "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]"
}

Use case: CRUD operations, real-time updates

Resource Subscriptions

Subscribe to resources for real-time notifications when they change:

1. Subscribe to a Resource

curl -X POST http://localhost:5000/mcp \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{
    "jsonrpc": "2.0",
    "method": "resources/subscribe",
    "params": {
      "uri": "data://users"
    },
    "id": 3
  }'

Response:

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

2. Open SSE Stream

curl -N http://localhost:5000/mcp \
  -H "Accept: text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -H "MCP-Session-Id: <session-id>"

You’ll receive notifications when data://users is updated:

id: 1
event: message
data: {"jsonrpc":"2.0","method":"notifications/resources/updated","params":{"uri":"data://users"}}

3. Update Resource (Triggers Notification)

curl -X POST http://localhost:5000/mcp \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "update_users",
      "arguments": {
        "newUser": {"id": 3, "name": "Charlie"}
      }
    },
    "id": 4
  }'

All subscribed sessions receive notification!

Code Examples

Defining a File Resource

using Mcp.Gateway.Tools;

public class FileResource
{
    [McpResource("file://logs/app.log",
        Name = "Application Logs",
        Description = "Recent application log entries",
        MimeType = "text/plain")]
    public JsonRpcMessage AppLogs(JsonRpcMessage request)
    {
        // Read log file (simplified example)
        var logs = File.ReadAllText("logs/app.log");
        
        return ToolResponse.Success(
            request.Id,
            new ReadResourceResult
            {
                Meta = new Dictionary<string, object> {
                    { "tools.gateway.mcp/status", "Hello World" }
                },
                Contents = [new ResourceContent(
                    Uri: "file://logs/app.log",
                    MimeType: "text/plain",
                    Text: logs)]
            });
    }
}

System Resource with Live Data

[McpResource("system://metrics",
    Name = "System Metrics",
    Description = "Live system performance metrics",
    MimeType = "application/json")]
public JsonRpcMessage SystemMetrics(JsonRpcMessage request)
{
    var metrics = new
    {
        cpu = GetCpuUsage(),
        memory = GetMemoryUsage(),
        uptime = GetUptime()
    };
    
    var json = JsonSerializer.Serialize(metrics);
    
    return ToolResponse.Success(
        request.Id,
        new ReadResourceResult
        {
            Contents = [new ResourceContent(
                Uri: "system://metrics",
                MimeType: "application/json",
                Text: json)]
        });
}

Data Resource with Notifications

using Mcp.Gateway.Tools.Notifications;

public class DataResource
{
    private readonly INotificationSender _notificationSender;
    
    // Option 1: Constructor injection (must register in DI)
    public DataResource(INotificationSender notificationSender)
    {
        _notificationSender = notificationSender;
    }
    
    [McpTool("update_users")]
    public async Task<JsonRpcMessage> UpdateUsers(JsonRpcMessage request)
    {
        // Update user data (simplified)
        await UpdateUserDatabase();
        
        // Notify subscribers
        await _notificationSender.SendNotificationAsync(
            NotificationMessage.ResourcesUpdated("data://users"));
        
        return ToolResponse.Success(request.Id, new { updated = true });
    }
}

// Option 2: Method parameter injection (no registration needed)
public class DataResource
{
    [McpTool("update_users")]
    public async Task<JsonRpcMessage> UpdateUsers(
        JsonRpcMessage request,
        INotificationSender notificationSender)  // ← Auto-injected!
    {
        await UpdateUserDatabase();
        
        await notificationSender.SendNotificationAsync(
            NotificationMessage.ResourcesUpdated("data://users"));
        
        return ToolResponse.Success(request.Id, new { updated = true });
    }
}

Testing with JavaScript Client

// 1. List resources
const listResponse = await fetch('http://localhost:5000/mcp', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'MCP-Protocol-Version': '2025-11-25'
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'resources/list',
    id: 1
  })
});

const resources = await listResponse.json();
console.log('Available resources:', resources.result.resources);

// 2. Subscribe to resource
const session = { id: null };

const subscribeResponse = await fetch('http://localhost:5000/mcp', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'MCP-Protocol-Version': '2025-11-25'
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'resources/subscribe',
    params: { uri: 'data://users' },
    id: 2
  })
});

// Save session ID
session.id = subscribeResponse.headers.get('MCP-Session-Id');

// 3. Open SSE stream
const eventSource = new EventSource(
  'http://localhost:5000/mcp',
  {
    headers: {
      'MCP-Protocol-Version': '2025-11-25',
      'MCP-Session-Id': session.id
    }
  }
);

// 4. Listen for updates
eventSource.addEventListener('message', (event) => {
  const notification = JSON.parse(event.data);
  
  if (notification.method === 'notifications/resources/updated') {
    console.log('Resource updated:', notification.params.uri);
    
    // Re-read the resource
    readResource(notification.params.uri);
  }
});

// 5. Read resource
async function readResource(uri) {
  const response = await fetch('http://localhost:5000/mcp', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'MCP-Protocol-Version': '2025-11-25'
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'resources/read',
      params: { uri },
      id: Date.now()
    })
  });
  
  const data = await response.json();
  console.log('Resource content:', data.result);
}

Integration Tests

The ResourceMcpServer includes comprehensive tests:

cd Examples/ResourceMcpServerTests
dotnet test

Test coverage:

  • ✅ List all resources
  • ✅ Read each resource type
  • ✅ Subscribe to resources
  • ✅ Receive notifications on updates
  • ✅ Unsubscribe from resources
  • ✅ Session management

Best Practices

1. Resource URIs

Use clear, descriptive URIs:

// ✅ GOOD
"file://logs/app.log"
"system://metrics"
"data://users"

// ❌ BAD
"file://1"
"sys"
"data"

2. MIME Types

Use standard MIME types:

// Text content
MimeType = "text/plain"

// JSON data
MimeType = "application/json"

// Binary data
MimeType = "application/octet-stream"

3. Descriptions

Write clear, helpful descriptions:

// ✅ GOOD
Description = "Recent application log entries (last 100 lines)"

// ❌ BAD
Description = "Logs"

4. Error Handling

Handle missing resources gracefully:

[McpResource("file://data/report.pdf")]
public JsonRpcMessage Report(JsonRpcMessage request)
{
    var filePath = "data/report.pdf";
    
    if (!File.Exists(filePath))
    {
        throw new ToolNotFoundException(
            $"Resource not found: file://data/report.pdf");
    }
    
    var data = File.ReadAllBytes(filePath);
    var base64 = Convert.ToBase64String(data);
    
    var content = new ResourceContent(
        Uri: "file://data/report.pdf",
        MimeType: "application/pdf",
        Blob: base64);

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

Common Use Cases

1. Configuration Management

[McpResource("file://config/app.json")]
public JsonRpcMessage Config(JsonRpcMessage request)
{
    var config = File.ReadAllText("appsettings.json");

    var content = new ResourceContent(
        Uri: "file://config/app.json",
        MimeType: "application/json",
        Text: config);

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

2. Log Monitoring

[McpResource("file://logs/errors.log")]
public JsonRpcMessage ErrorLogs(JsonRpcMessage request)
{
    var lines = File.ReadLines("logs/errors.log")
        .TakeLast(100)
        .ToList();
    
    var content = string.Join("\n", lines);
    
    return ToolResponse.Success(request.Id,
        new ReadResourceResult 
        {
            Contents = [new ResourceContent(
                Uri: "file://logs/errors.log",
                MimeType: "text/plain",
                Text: content)]
        });
}

3. Database Access

[McpResource("data://products")]
public async Task<JsonRpcMessage> Products(JsonRpcMessage request)
{
    var products = await _dbContext.Products.ToListAsync();
    var json = JsonSerializer.Serialize(products);
    
    return ToolResponse.Success(request.Id,
        new ReadResourceResult
        {
            Contents = [new ResourceContent(
                Uri: "data://products",
                MimeType: "application/json",
                Text: json)]
        });
}

4. API Integration

[McpResource("api://weather/current")]
public async Task<JsonRpcMessage> Weather(JsonRpcMessage request)
{
    var response = await _httpClient.GetStringAsync(
        "https://api.weather.com/current");
    
    return ToolResponse.Success(request.Id,
        new ReadResourceResult
        {
            Contents = [new ResourceContent(
                Uri: "api://weather/current",
                MimeType: "application/json",
                Text: response)]
        });
}

See Also