Module: RailsMcpServer::Extensions::ServerTemplating::InstanceMethods

Defined in:
lib/rails-mcp-server/extensions/server_templating.rb

Overview

Instance methods to be prepended

Instance Method Summary collapse

Instance Method Details

#handle_request(*args) ⇒ Object

Override handle_request to ensure resources/templates/list endpoint is available



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rails-mcp-server/extensions/server_templating.rb', line 110

def handle_request(*args)
  # Extract arguments - handle different signatures
  if args.length == 2
    json_str, headers = args
    headers ||= {}
  else
    json_str = args[0]
    headers = {}
  end

  begin
    request = JSON.parse(json_str)
  rescue JSON::ParserError, TypeError
    return send_error(-32_600, "Invalid Request", nil)
  end

  @logger.debug("Received request: #{request.inspect}")

  # Check if it's a valid JSON-RPC 2.0 request
  unless request["jsonrpc"] == "2.0" && request["method"]
    return send_error(-32_600, "Invalid Request", request["id"])
  end

  method = request["method"]
  params = request["params"] || {}
  id = request["id"]

  # Handle the resources/templates/list endpoint specifically since it might not exist in original
  if method == "resources/templates/list"
    @logger.debug("Handling resources/templates/list via extension")
    return handle_resources_templates_list(id)
  end

  # For all other methods, call the original implementation
  begin
    super
  rescue NoMethodError => e
    # If super doesn't work, provide our own fallback
    @logger.debug("Original handle_request not available, using fallback: #{e.message}")
    handle_request_fallback(method, params, id, headers)
  end
rescue => e
  @logger.error("Error handling request: #{e.message}, #{e.backtrace.join("\n")}")
  send_error(-32_600, "Internal error: #{e.message}", id)
end

#handle_resources_list(id) ⇒ Object

The target server already has these methods, but we can add defensive checks



80
81
82
83
84
85
86
87
88
89
# File 'lib/rails-mcp-server/extensions/server_templating.rb', line 80

def handle_resources_list(id)
  # Handle both hash-based and array-based resource storage
  resources_collection = @resources.is_a?(Hash) ? @resources.values : @resources

  resources_list = resources_collection.select do |resource|
    !resource.respond_to?(:templated?) || resource.non_templated?
  end.map(&:metadata) # rubocop:disable Performance/ChainArrayAllocation

  send_result({resources: resources_list}, id)
end

#handle_resources_read(params, id) ⇒ Object

Add some defensive programming to handle_resources_read



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rails-mcp-server/extensions/server_templating.rb', line 30

def handle_resources_read(params, id)
  uri = params["uri"]

  return send_error(-32_602, "Invalid params: missing resource URI", id) unless uri

  @logger.debug("Looking for resource with URI: #{uri}")

  begin
    resource = read_resource(uri)
    return send_error(-32_602, "Resource not found: #{uri}", id) unless resource

    # Defensive check for templated method
    is_templated = resource.respond_to?(:templated?) ? resource.templated? : false
    @logger.debug("Found resource: #{resource.respond_to?(:resource_name) ? resource.resource_name : resource.name}, templated: #{is_templated}")

    base_content = {uri: uri}
    base_content[:mimeType] = resource.mime_type if resource.mime_type

    # Handle both templated and non-templated resources
    resource_instance = if is_templated && resource.respond_to?(:instance)
      resource.instance(uri)
    else
      # Fallback for non-templated resources or resources without instance method
      resource.respond_to?(:instance) ? resource.instance : resource
    end

    # Defensive check for params method
    if resource_instance.respond_to?(:params)
      @logger.debug("Resource instance params: #{resource_instance.params.inspect}")
    end

    result = if resource_instance.respond_to?(:binary?) && resource_instance.binary?
      {
        contents: [base_content.merge(blob: Base64.strict_encode64(resource_instance.content))]
      }
    else
      {
        contents: [base_content.merge(text: resource_instance.content)]
      }
    end

    send_result(result, id)
  rescue => e
    @logger.error("Error reading resource: #{e.message}")
    @logger.error(e.backtrace.join("\n"))
    send_error(-32_600, "Internal error reading resource: #{e.message}", id)
  end
end

#handle_resources_templates_list(id) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/rails-mcp-server/extensions/server_templating.rb', line 91

def handle_resources_templates_list(id)
  @logger.debug("Handling resources/templates/list request")

  # Handle both hash-based and array-based resource storage
  resources_collection = @resources.is_a?(Hash) ? @resources.values : @resources

  templated_resources_list = resources_collection.select do |resource|
    resource.respond_to?(:templated?) && resource.templated?
  end.map do |resource| # rubocop:disable Performance/ChainArrayAllocation
     = resource.
    @logger.debug("Template resource metadata: #{}")
    
  end

  @logger.info("Returning #{templated_resources_list.length} templated resources")
  send_result({resourceTemplates: templated_resources_list}, id)
end

#read_resource(uri) ⇒ Object

The target server already has most functionality, but we can add defensive checks



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/rails-mcp-server/extensions/server_templating.rb', line 9

def read_resource(uri)
  # Handle both hash-based and array-based resource storage
  if @resources.is_a?(Hash)
    # First try exact match (hash lookup)
    exact_match = @resources[uri]
    return exact_match if exact_match

    # Then try templated resource matching
    @resources.values.find { |r| r.respond_to?(:match) && r.match(uri) }
  else
    # Array-based storage (original target server behavior)
    resource = @resources.find { |r| r.respond_to?(:match) && r.match(uri) }

    # Fallback: if no templated match, try exact URI match for backward compatibility
    resource ||= @resources.find { |r| r.respond_to?(:uri) && r.uri == uri }

    resource
  end
end