diff --git a/Gemfile.lock b/Gemfile.lock
index 2fc9ccf..23571fb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -2,6 +2,7 @@ PATH
   remote: .
   specs:
     ruby_mcp (0.1.0)
+      addressable (~> 2.8, >= 2.8.7)
       zeitwerk (~> 2.7, >= 2.7.2)
 
 GEM
@@ -20,6 +21,8 @@ GEM
       securerandom (>= 0.3)
       tzinfo (~> 2.0, >= 2.0.5)
       uri (>= 0.13.1)
+    addressable (2.8.7)
+      public_suffix (>= 2.0.2, < 7.0)
     ast (2.4.3)
     base64 (0.2.0)
     benchmark (0.4.0)
@@ -53,6 +56,7 @@ GEM
     psych (5.2.3)
       date
       stringio
+    public_suffix (6.0.1)
     racc (1.8.1)
     rack (3.1.12)
     rainbow (3.1.1)
diff --git a/lib/ruby_mcp.rb b/lib/ruby_mcp.rb
index a26aa5c..96375f2 100644
--- a/lib/ruby_mcp.rb
+++ b/lib/ruby_mcp.rb
@@ -2,6 +2,7 @@
 require "securerandom"
 require "logger"
 require "zeitwerk"
+require "addressable"
 
 loader = Zeitwerk::Loader.for_gem
 loader.inflector.inflect("ruby_mcp" => "RubyMCP")
diff --git a/lib/ruby_mcp/capabilities/resources.rb b/lib/ruby_mcp/capabilities/resources.rb
new file mode 100644
index 0000000..8888ce4
--- /dev/null
+++ b/lib/ruby_mcp/capabilities/resources.rb
@@ -0,0 +1,9 @@
+module RubyMCP::Capabilities::Resources
+  def add_resource(...)
+    @resources.add(...)
+  end
+
+  def add_resource_template(uri_template:, name:, description:, mime_type:, reader:)
+    @resources.add_resource_template(uri_template:, name:, description:, mime_type:, reader:)
+  end
+end
diff --git a/lib/ruby_mcp/handlers.rb b/lib/ruby_mcp/handlers.rb
index cf49c99..60f55d0 100644
--- a/lib/ruby_mcp/handlers.rb
+++ b/lib/ruby_mcp/handlers.rb
@@ -21,6 +21,8 @@ def self.parse(json)
         ResourcesRead
       when "logging/setLevel"
         LoggingSetLevel
+      when "resources/templates/list"
+        ResourcesTemplatesList
       end.new
     end
   end
diff --git a/lib/ruby_mcp/handlers/resources_read.rb b/lib/ruby_mcp/handlers/resources_read.rb
index 12ad1cb..087cde0 100644
--- a/lib/ruby_mcp/handlers/resources_read.rb
+++ b/lib/ruby_mcp/handlers/resources_read.rb
@@ -1,12 +1,15 @@
 class RubyMCP::Handlers::ResourcesRead
   def handle(server, request)
-    if resource = server.resources.find(request.uri)
+    resources = server.resources.find(request.uri)
+    if resources.any?
       server.answer(request,
-        contents: [ {
-          uri: resource.uri,
-          mimeType: resource.mime_type,
-          text: resource.reader.call(resource)
-        } ]
+        contents: resources.map do |resource|
+          {
+            uri: request.uri,
+            mimeType: resource.mime_type,
+            text: resource.reader.call(resource)
+          }
+        end
       )
     else
       server.error(
diff --git a/lib/ruby_mcp/handlers/resources_templates_list.rb b/lib/ruby_mcp/handlers/resources_templates_list.rb
new file mode 100644
index 0000000..2bae626
--- /dev/null
+++ b/lib/ruby_mcp/handlers/resources_templates_list.rb
@@ -0,0 +1,12 @@
+class RubyMCP::Handlers::ResourcesTemplatesList < RubyMCP::Handlers
+  def handle(server, request)
+    server.answer(request, resourceTemplates: server.resources.templates.map do |template, value|
+      {
+        uriTemplate: template,
+        name: value.name,
+        description: value.description,
+        mimeType: value.mime_type
+      }
+    end)
+  end
+end
diff --git a/lib/ruby_mcp/requests.rb b/lib/ruby_mcp/requests.rb
index 3f3ef25..e50da9c 100644
--- a/lib/ruby_mcp/requests.rb
+++ b/lib/ruby_mcp/requests.rb
@@ -21,6 +21,8 @@ def self.parse(json)
         ResourcesRead
       when "logging/setLevel"
         LoggingSetLevel
+      when "resources/templates/list"
+        ResourcesTemplatesList
       else
         Request
       end.new(parsed)
diff --git a/lib/ruby_mcp/requests/resources_templates_list.rb b/lib/ruby_mcp/requests/resources_templates_list.rb
new file mode 100644
index 0000000..1ac36d1
--- /dev/null
+++ b/lib/ruby_mcp/requests/resources_templates_list.rb
@@ -0,0 +1,5 @@
+class RubyMCP::Requests::ResourcesTemplatesList < RubyMCP::Request
+  def allowed_in_lifecycle?(lifecycle)
+    lifecycle.operation_phase?
+  end
+end
diff --git a/lib/ruby_mcp/resources.rb b/lib/ruby_mcp/resources.rb
index a2d8c2c..db593cf 100644
--- a/lib/ruby_mcp/resources.rb
+++ b/lib/ruby_mcp/resources.rb
@@ -1,9 +1,11 @@
 module RubyMCP
   Resource = Data.define(:uri, :name, :description, :mime_type, :reader)
+  ResourceTemplate = Data.define(:uri_template, :name, :description, :mime_type, :reader)
 
   class Resources
     def initialize
       @resources = {}
+      @resource_templates = {}
     end
 
     def add(uri:, name:, description: nil, mime_type: nil, reader: nil)
@@ -11,7 +13,14 @@ def add(uri:, name:, description: nil, mime_type: nil, reader: nil)
     end
 
     def find(uri)
-      @resources[uri]
+      [ @resources[uri] ].concat(find_in_templates(uri).map(&:last)).compact
+    end
+
+    def add_resource_template(uri_template:, **args)
+      @resource_templates[uri_template] = ResourceTemplate.new(
+        uri_template: Addressable::Template.new(uri_template),
+        **args
+      )
     end
 
     def as_json
@@ -24,5 +33,18 @@ def as_json
         }
       end
     end
+
+    def templates
+      @resource_templates
+    end
+
+    private
+
+    def find_in_templates(uri)
+      addressable_uri = Addressable::URI.parse(uri)
+      @resource_templates.find_all do |template, resource_template|
+        resource_template.uri_template.extract(addressable_uri)
+      end
+    end
   end
 end
diff --git a/lib/ruby_mcp/server.rb b/lib/ruby_mcp/server.rb
index 1d1ba5c..e31e75a 100644
--- a/lib/ruby_mcp/server.rb
+++ b/lib/ruby_mcp/server.rb
@@ -1,6 +1,7 @@
 module RubyMCP
   class Server
     include Capabilities::Logging
+    include Capabilities::Resources
 
     attr_reader :lifecycle, :prompts, :resources
 
diff --git a/ruby_mcp.gemspec b/ruby_mcp.gemspec
index 63f07cf..1c4faaa 100644
--- a/ruby_mcp.gemspec
+++ b/ruby_mcp.gemspec
@@ -28,5 +28,6 @@ Gem::Specification.new do |spec|
   spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
   spec.require_paths = [ "lib" ]
 
-  spec.add_dependency "zeitwerk", '~> 2.7', '>= 2.7.2'
+  spec.add_dependency "zeitwerk", "~> 2.7", ">= 2.7.2"
+  spec.add_dependency "addressable", "~> 2.8", ">= 2.8.7"
 end
diff --git a/test/capabilities/test_resources_capability.rb b/test/capabilities/test_resources_capability.rb
index 40f0791..5294274 100644
--- a/test/capabilities/test_resources_capability.rb
+++ b/test/capabilities/test_resources_capability.rb
@@ -122,4 +122,92 @@ def test_read_resources_not_found
       }
     )
   end
+
+  def test_read_template
+    @server.add_resource_template(
+      uri_template: "https://{host}.de",
+      name: "german_website",
+      description: "Every german website",
+      mime_type: "text/html",
+      reader: ->(resource) {
+        "The first demo content of #{resource.name}"
+      }
+    )
+
+    @transport.client_message(
+      jsonrpc: "2.0",
+      id: 1,
+      method: "resources/read",
+      params: {
+        uri: "https://example.de"
+      }
+    )
+
+    @transport.process_message
+
+    assert_last_response(
+      jsonrpc: "2.0",
+      id: 1,
+      result: {
+        contents: [
+          {
+            uri: "https://example.de",
+            mimeType: "text/html",
+            text: "The first demo content of german_website"
+          }
+        ]
+      }
+    )
+  end
+
+  def test_list_templates
+    @server.add_resource_template(
+      uri_template: "https://{host}.de",
+      name: "german_website",
+      description: "Every german website",
+      mime_type: "text/html",
+      reader: ->(resource) {
+        "The first demo content of #{resource.name}"
+      }
+    )
+
+    @server.add_resource_template(
+      uri_template: "https://{host}.com",
+      name: "com_website",
+      description: "Every com website",
+      mime_type: "text/html",
+      reader: ->(resource) {
+        "The first demo content of #{resource.name}"
+      }
+    )
+
+    @transport.client_message(
+      jsonrpc: "2.0",
+      id: 1,
+      method: "resources/templates/list",
+    )
+
+    @transport.process_message
+
+    assert_last_response(
+      "jsonrpc": "2.0",
+      "id": 1,
+      "result": {
+        "resourceTemplates": [
+          {
+            uriTemplate: "https://{host}.de",
+            name: "german_website",
+            description: "Every german website",
+            mimeType: "text/html"
+          },
+          {
+            uriTemplate: "https://{host}.com",
+            name: "com_website",
+            description: "Every com website",
+            mimeType: "text/html"
+          }
+        ]
+      }
+    )
+  end
 end