Resources

MCP resources provide structured data that can be included in conversations - from static files to dynamically generated content. Resources come in two types: normal resources and resource templates.

Table of contents

  1. Static Resources
    1. Discovering Resources
    2. Using Resources in Conversations
    3. Adding Multiple Resources
    4. Resource Content Types
  2. Resource Templates
    1. Discovering Resource Templates
    2. Using Resource Templates
    3. Template Content Generation
    4. Template Argument Validation
  3. Argument Completion
  4. Advanced Resource Usage
  5. Working with Different Content Types
    1. Text Resources
    2. Structured Data Resources
    3. Binary Resources
    4. Subscribing to Resources
      1. Subscription Management
      2. Handling Resource Updates
      3. Best Practices for Resource Subscriptions
  6. Error Handling
    1. Resource Not Found
    2. Template Argument Errors
    3. Resource Loading Errors
  7. Resource Links in Tool Results
    1. Dynamic Resource Creation
    2. Tool-Generated Resources
    3. Resource Reference Format
    4. Tool-Resource Workflows
    5. Temporary vs Persistent Resources
    6. Best Practices
  8. Next Steps

Static Resources

Static resources are pre-defined data that doesn’t change based on parameters.

Discovering Resources

client = RubyLLM::MCP.client(
  name: "content-server",
  transport_type: :stdio,
  config: {
    command: "npx",
    args: ["@modelcontextprotocol/server-filesystem", "/path/to/docs"]
  }
)

# Get all available resources
resources = client.resources
puts "Available resources:"
resources.each do |resource|
  puts "- #{resource.name}: #{resource.description}"
  puts "  URI: #{resource.uri}"
  puts "  Type: #{resource.mime_type}"
end

Using Resources in Conversations

# Get a specific resource
readme = client.resource("project_readme")
puts "Resource: #{readme.name}"
puts "Description: #{readme.description}"
puts "Content: #{readme.content}"

# Use resource in a chat
chat = RubyLLM.chat(model: "gpt-4")
chat.with_resource(readme)

response = chat.ask("Summarize the README file")
puts response

Adding Multiple Resources

# Add multiple resources to a conversation
chat = RubyLLM.chat(model: "gpt-4")

# Method 1: Add resources individually
chat.with_resource(client.resource("project_readme"))
chat.with_resource(client.resource("api_documentation"))
chat.with_resource(client.resource("changelog"))

# Method 2: Add multiple resources at once
chat.with_resources(
  client.resource("project_readme"),
  client.resource("api_documentation"),
  client.resource("changelog")
)

response = chat.ask("Analyze the project documentation")
puts response

Resource Content Types

Resources can contain different types of content:

# Text resource
text_resource = client.resource("config_file")
puts "Text content: #{text_resource.content}"

# JSON resource
json_resource = client.resource("api_schema")
schema = JSON.parse(json_resource.content)
puts "API endpoints: #{schema['endpoints']}"

# Binary resource (images, files)
image_resource = client.resource("diagram")
puts "Image data: #{image_resource.content.bytesize} bytes"
puts "MIME type: #{image_resource.mime_type}"

Resource Templates

Resource templates are parameterized resources that generate content dynamically based on arguments.

Discovering Resource Templates

# Get all resource templates
templates = client.resource_templates
puts "Available templates:"
templates.each do |template|
  puts "- #{template.name}: #{template.description}"
  puts "  URI template: #{template.uri_template}"

  # Show required arguments
  template.arguments.each do |arg|
    required = arg.required ? " (required)" : ""
    puts "  - #{arg.name}: #{arg.description}#{required}"
  end
end

Using Resource Templates

# Get a specific template
log_template = client.resource_template("application_logs")

# Use template with arguments in a chat
chat = RubyLLM.chat(model: "gpt-4")
chat.with_resource_template(log_template, arguments: {
  date: "2024-01-15",
  level: "error",
  service: "api"
})

response = chat.ask("What errors occurred in the API service?")
puts response

Template Content Generation

# Generate content from template without using in chat
user_template = client.resource_template("user_profile")
content = user_template.to_content(arguments: {
  user_id: "12345",
  include_history: true
})

puts "Generated content: #{content}"

Template Argument Validation

template = client.resource_template("report_generator")

# Check required arguments
required_args = template.arguments.select(&:required)
puts "Required arguments:"
required_args.each do |arg|
  puts "- #{arg.name}: #{arg.description}"
end

# Use with all required arguments
chat = RubyLLM.chat(model: "gpt-4")
chat.with_resource_template(template, arguments: {
  start_date: "2024-01-01",
  end_date: "2024-01-31",
  format: "summary"
})

Argument Completion

Some MCP servers support argument completion for resource templates:

# Get completion suggestions for template arguments
template = client.resource_template("user_logs")

# Complete a partial argument value
suggestions = template.complete("user_id", "123")
puts "Suggestions: #{suggestions.values}"
puts "Total matches: #{suggestions.total}"
puts "Has more: #{suggestions.has_more}"

# Use suggestions in your application
if suggestions.values.any?
  puts "Did you mean:"
  suggestions.values.each_with_index do |suggestion, index|
    puts "#{index + 1}. #{suggestion}"
  end
end

Advanced Resource Usage

Working with Different Content Types

Text Resources

# Plain text
text_resource = client.resource("plain_text_file")
puts text_resource.content

# Markdown
markdown_resource = client.resource("documentation")
puts "Markdown content: #{markdown_resource.content}"

# Code files
code_resource = client.resource("source_code")
puts "Code: #{code_resource.content}"

Structured Data Resources

# JSON resource
json_resource = client.resource("configuration")
config = JSON.parse(json_resource.content)
puts "Config: #{config}"

# YAML resource
yaml_resource = client.resource("metadata")
metadata = YAML.safe_load(yaml_resource.content)
puts "Metadata: #{metadata}"

# CSV resource
csv_resource = client.resource("data_export")
require 'csv'
data = CSV.parse(csv_resource.content, headers: true)
puts "Rows: #{data.length}"

Binary Resources

# Image resource
image_resource = client.resource("chart_image")
puts "Image size: #{image_resource.content.bytesize} bytes"
puts "MIME type: #{image_resource.mime_type}"

# Save binary content
File.binwrite("chart.png", image_resource.content)

Subscribing to Resources

MCP allows you to subscribe to resources if the server has the capabilities to do so. When you subscribe to a resource, it will be marked and automatically refreshed on the next usage when the underlying content changes.

# Check if a resource supports subscriptions
resource = client.resource("live_data")
if resource.subscribable?
  puts "Resource supports subscriptions"
else
  puts "Resource does not support subscriptions"
end

# Subscribe to a resource for automatic updates
client.subscribe_to_resource("live_data")

# Use the resource - content will be automatically refreshed if it changed
chat = RubyLLM.chat(model: "gpt-4")
chat.with_resource(resource)

# The resource content will be automatically updated on subsequent uses
response = chat.ask("What's the latest data?")
puts response

Subscription Management

# List subscribed resources
subscriptions = client.subscribed_resources
puts "Subscribed to #{subscriptions.length} resources:"
subscriptions.each do |resource_name|
  puts "- #{resource_name}"
end

# Unsubscribe from a resource
client.unsubscribe_from_resource("live_data")

# Check subscription status
is_subscribed = client.subscribed_to?("live_data")
puts "Subscribed to live_data: #{is_subscribed}"

Handling Resource Updates

# Set up a callback for when subscribed resources update
client.on_resource_updated do |resource_uri|
  puts "Subscribed resource updated: #{resource_uri}"

  # Optionally refresh cached data
  resource_name = extract_resource_name(resource_uri)
  updated_resource = client.resource(resource_name, refresh: true)

  puts "Updated content preview: #{updated_resource.content[0..100]}..."
end

# Subscribe to multiple resources
%w[live_metrics real_time_logs current_status].each do |resource_name|
  if client.resource(resource_name).subscribable?
    client.subscribe_to_resource(resource_name)
    puts "Subscribed to #{resource_name}"
  end
end

Best Practices for Resource Subscriptions

# Only subscribe to resources you actively use
class SmartResourceSubscriber
  def initialize(client)
    @client = client
    @active_resources = Set.new
  end

  def use_resource_with_subscription(resource_name)
    # Subscribe on first use
    unless @active_resources.include?(resource_name)
      resource = @client.resource(resource_name)

      if resource.subscribable?
        @client.subscribe_to_resource(resource_name)
        @active_resources.add(resource_name)
        puts "Auto-subscribed to #{resource_name}"
      end
    end

    @client.resource(resource_name)
  end

  def cleanup_unused_subscriptions
    # Unsubscribe from resources not used recently
    @client.subscribed_resources.each do |resource_name|
      unless @active_resources.include?(resource_name)
        @client.unsubscribe_from_resource(resource_name)
        puts "Auto-unsubscribed from unused resource: #{resource_name}"
      end
    end
  end
end

# Usage
subscriber = SmartResourceSubscriber.new(client)
resource = subscriber.use_resource_with_subscription("live_metrics")

Error Handling

Resource Not Found

begin
  resource = client.resource("nonexistent_resource")
rescue RubyLLM::MCP::Errors::ResourceNotFound => e
  puts "Resource not found: #{e.message}"
end

Template Argument Errors

template = client.resource_template("user_report")

begin
  content = template.to_content(arguments: {
    # Missing required argument
    start_date: "2024-01-01"
  })
rescue RubyLLM::MCP::Errors::TemplateError => e
  puts "Template error: #{e.message}"
  puts "Missing arguments: #{e.missing_arguments}"
end

Resource Loading Errors

begin
  resource = client.resource("large_file")
  content = resource.content
rescue RubyLLM::MCP::Errors::ResourceError => e
  puts "Failed to load resource: #{e.message}"
end
class ContextBuilder
  def initialize(client)
    @client = client
    @resources = []
    @templates = []
  end

  def add_resource(name)
    @resources << name
    self
  end

  def add_template(name, arguments)
    @templates << { name: name, arguments: arguments }
    self
  end

  def build_for_chat(chat)
    @resources.each do |name|
      chat.with_resource(@client.resource(name))
    end

    @templates.each do |template_config|
      template = @client.resource_template(template_config[:name])
      chat.with_resource_template(template, arguments: template_config[:arguments])
    end

    chat
  end
end

# Usage
chat = RubyLLM.chat(model: "gpt-4")
context = ContextBuilder.new(client)
  .add_resource("project_overview")
  .add_resource("architecture_guide")
  .add_template("recent_commits", { days: 7 })
  .build_for_chat(chat)

response = chat.ask("Analyze the project")
puts response

Resource links in tool results are available in MCP Protocol 2025-06-18.

Tools can now return resource references in their results, allowing dynamic resource creation and enhanced tool-resource integration.

Dynamic Resource Creation

When tools execute and return resource references, these become automatically available as resources:

# Execute a tool that creates or references resources
file_tool = client.tool("create_file")
result = file_tool.execute(filename: "report.txt", content: "Analysis results...")

# If the tool returns a resource reference, it becomes available as a resource
if result.is_a?(RubyLLM::MCP::Content)
  # Resource content is automatically parsed and made available
  puts result.to_s

  # The resource may also be added to the client's resource list
  updated_resources = client.resources(refresh: true)
  puts "New resources: #{updated_resources.map(&:name)}"
end

Tool-Generated Resources

Tools can create resources dynamically and return references to them:

# A tool that generates a report and returns it as a resource
report_tool = client.tool("generate_report")
result = report_tool.execute(
  data_source: "sales_data",
  format: "pdf",
  timeframe: "last_quarter"
)

# The tool returns a resource reference that can be used immediately
if result.is_a?(RubyLLM::MCP::Content)
  # Use the generated resource in a conversation
  chat = RubyLLM.chat(model: "gpt-4")
  chat.with_resource(result)

  response = chat.ask("Summarize this report")
  puts response
end

Resource Reference Format

Tools can return resources in their content using the resource type:

{
  "content": [
    {
      "type": "resource",
      "resource": {
        "uri": "file:///path/to/created/file.txt",
        "name": "generated_report",
        "description": "Quarterly sales report",
        "mimeType": "text/plain",
        "text": "Report content here..."
      }
    }
  ]
}

The client automatically converts these references into usable Resource objects.

Tool-Resource Workflows

Combine tools and resources for powerful workflows:

# 1. Use a tool to analyze data and create a resource
analysis_tool = client.tool("analyze_data")
analysis_result = analysis_tool.execute(dataset: "user_behavior")

# 2. Use the generated resource in another tool call
if analysis_result.is_a?(RubyLLM::MCP::Content)
  chat = RubyLLM.chat(model: "gpt-4")
  chat.with_resource(analysis_result)

  # 3. Generate recommendations based on the analysis
  recommendation_tool = client.tool("generate_recommendations")
  recommendations = recommendation_tool.execute(
    analysis_resource: analysis_result.uri
  )

  puts "Recommendations: #{recommendations}"
end

Temporary vs Persistent Resources

Tool-generated resources can be:

  • Temporary: Exist only for the current session
  • Persistent: Saved and available in future sessions
# Check if a resource is temporary or persistent
resource = client.resource("generated_report")
if resource.uri.start_with?("temp://")
  puts "This is a temporary resource"
else
  puts "This is a persistent resource"
end

Best Practices

Resource Naming:

  • Use descriptive names for generated resources
  • Include timestamps or unique identifiers when appropriate
  • Consider namespacing for organization

Content Management:

  • Clean up temporary resources when no longer needed
  • Monitor resource creation to prevent storage issues
  • Implement proper access controls for persistent resources

Integration Patterns:

  • Chain tools that create and consume resources
  • Use resource templates for consistent resource generation
  • Combine with notifications for resource update tracking

Next Steps