diff --git a/include/opendht/dht_proxy_client.h b/include/opendht/dht_proxy_client.h
index e3f8287575a31b39af9e1425f296157d9aa95bf3..df3fe3638307a25b7440c53464650e67b55585ca 100644
--- a/include/opendht/dht_proxy_client.h
+++ b/include/opendht/dht_proxy_client.h
@@ -227,13 +227,8 @@ public:
         return types.getType(type_id);
     }
 
-    std::vector<Sp<Value>> getLocal(const InfoHash&, Value::Filter) const {
-        return {};
-    }
-
-    Sp<Value> getLocalById(const InfoHash&, Value::Id) const {
-        return {};
-    }
+    std::vector<Sp<Value>> getLocal(const InfoHash& k, Value::Filter filter) const;
+    Sp<Value> getLocalById(const InfoHash& k, Value::Id id) const;
 
     /**
      * NOTE: The following methods will not be implemented because the
diff --git a/src/dht_proxy_client.cpp b/src/dht_proxy_client.cpp
index 8562bb39cd46b04e18f9aa2debf12e9aaf0f07db..9a5b2d05b6738973bc82a52e1c85bf455c9b43bf 100644
--- a/src/dht_proxy_client.cpp
+++ b/src/dht_proxy_client.cpp
@@ -84,6 +84,22 @@ DhtProxyClient::~DhtProxyClient()
     cancelAllListeners();
 }
 
+std::vector<Sp<Value>>
+DhtProxyClient::getLocal(const InfoHash& k, Value::Filter filter) const {
+    auto s = listeners_.find(k);
+    if (s == listeners_.end())
+        return {};
+    return s->second.ops.get(filter);
+}
+
+Sp<Value>
+DhtProxyClient::getLocalById(const InfoHash& k, Value::Id id) const {
+    auto s = listeners_.find(k);
+    if (s == listeners_.end())
+        return {};
+    return s->second.ops.get(id);
+}
+
 void
 DhtProxyClient::cancelAllOperations()
 {
diff --git a/src/op_cache.h b/src/op_cache.h
index e20f8845462297cdce30d21b0a0cca030d3d91f7..e2f26008723f444dc516e2850c471078b08afb2e 100644
--- a/src/op_cache.h
+++ b/src/op_cache.h
@@ -139,6 +139,23 @@ public:
         return listeners.empty();
     }
 
+    std::vector<Sp<Value>> get(Value::Filter& filter) const {
+        std::vector<Sp<Value>> ret;
+        if (not filter)
+            ret.reserve(values.size());
+        for (const auto& v : values)
+            if (not filter or filter(*v.second.data))
+                ret.emplace_back(v.second.data);
+        return ret;
+    }
+
+    Sp<Value> get(Value::Id id) const {
+        auto v = values.find(id);
+        if (v == values.end())
+            return {};
+        return v->second.data;
+    }
+
     size_t searchToken;
 private:
     std::map<size_t, LocalListener> listeners;
@@ -188,6 +205,28 @@ public:
         return false;
     }
 
+    std::vector<Sp<Value>> get(Value::Filter& filter) const {
+        if (ops.size() == 1)
+            return ops.begin()->second.get(filter);
+        std::map<Value::Id, Sp<Value>> c;
+        for (const auto& op : ops) {
+            for (const auto& v : op.second.get(filter))
+                c.emplace(v->id, v);
+        }
+        std::vector<Sp<Value>> ret;
+        ret.reserve(c.size());
+        for (auto& v : c)
+            ret.emplace_back(std::move(v.second));
+        return ret;
+    }
+
+    Sp<Value> get(Value::Id id) const {
+        for (const auto& op : ops)
+            if (auto v = op.second.get(id))
+                return v;
+        return {};
+    }
+
 private:
     std::map<Sp<Query>, OpCache> ops;
     size_t nextToken_ {1};